Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "call_factory.h"
19 : #include "sip/sipcall.h"
20 : #include "sip/sipaccount.h"
21 : #include "sip/sipaccountbase.h"
22 : #include "sip/sipvoiplink.h"
23 : #include "jamidht/jamiaccount.h"
24 : #include "logger.h"
25 : #include "sdp.h"
26 : #include "manager.h"
27 : #include "string_utils.h"
28 :
29 : #include "connectivity/sip_utils.h"
30 : #include "audio/audio_rtp_session.h"
31 : #include "im/instant_messaging.h"
32 : #include "jami/account_const.h"
33 : #include "jami/call_const.h"
34 : #include "jami/media_const.h"
35 : #include "client/jami_signal.h"
36 : #include "pjsip-ua/sip_inv.h"
37 : #include "video/video_mixer.h"
38 :
39 : #ifdef ENABLE_PLUGIN
40 : #include "plugin/jamipluginmanager.h"
41 : #endif
42 :
43 : #ifdef ENABLE_VIDEO
44 : #include "client/videomanager.h"
45 : #include "video/video_rtp_session.h"
46 : #include <chrono>
47 : #include <libavutil/display.h>
48 : #include <video/sinkclient.h>
49 : #endif
50 : #include "audio/ringbufferpool.h"
51 :
52 : #include <dhtnet/upnp/upnp_control.h>
53 : #include <dhtnet/ice_transport_factory.h>
54 :
55 : #include <opendht/crypto.h>
56 : #include <opendht/thread_pool.h>
57 : #include <fmt/ranges.h>
58 :
59 : #include "tracepoint.h"
60 :
61 : namespace jami {
62 :
63 : using sip_utils::CONST_PJ_STR;
64 : using namespace libjami::Call;
65 :
66 : #ifdef ENABLE_VIDEO
67 : static DeviceParams
68 315 : getVideoSettings()
69 : {
70 315 : if (auto* videomon = jami::getVideoDeviceMonitor())
71 630 : return videomon->getDeviceParams(videomon->getDefaultDevice());
72 0 : return DeviceParams {};
73 : }
74 : #endif
75 :
76 : static constexpr std::chrono::seconds DEFAULT_ICE_INIT_TIMEOUT {35}; // seconds
77 : static constexpr std::chrono::milliseconds EXPECTED_ICE_INIT_MAX_TIME {5000};
78 : static constexpr std::chrono::milliseconds MS_BETWEEN_2_KEYFRAME_REQUEST {1000};
79 : static constexpr int ICE_COMP_ID_RTP {1};
80 : static constexpr int ICE_COMP_COUNT_PER_STREAM {2};
81 : static constexpr auto MULTISTREAM_REQUIRED_VERSION_STR = "10.0.2"sv;
82 : static const std::vector<unsigned> MULTISTREAM_REQUIRED_VERSION
83 : = split_string_to_unsigned(MULTISTREAM_REQUIRED_VERSION_STR, '.');
84 : static constexpr auto MULTIICE_REQUIRED_VERSION_STR = "13.3.0"sv;
85 : static const std::vector<unsigned> MULTIICE_REQUIRED_VERSION = split_string_to_unsigned(MULTIICE_REQUIRED_VERSION_STR,
86 : '.');
87 : static constexpr auto NEW_CONFPROTOCOL_VERSION_STR = "13.1.0"sv;
88 : static const std::vector<unsigned> NEW_CONFPROTOCOL_VERSION = split_string_to_unsigned(NEW_CONFPROTOCOL_VERSION_STR,
89 : '.');
90 : static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.2"sv;
91 : static const std::vector<unsigned> REUSE_ICE_IN_REINVITE_REQUIRED_VERSION
92 : = split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.');
93 : static constexpr auto MULTIAUDIO_REQUIRED_VERSION_STR = "13.11.0"sv;
94 : static const std::vector<unsigned> MULTIAUDIO_REQUIRED_VERSION
95 : = split_string_to_unsigned(MULTIAUDIO_REQUIRED_VERSION_STR, '.');
96 :
97 395 : SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
98 : const std::string& callId,
99 : Call::CallType type,
100 395 : const std::vector<libjami::MediaMap>& mediaList)
101 : : Call(account, callId, type)
102 395 : , sdp_(new Sdp(callId))
103 395 : , enableIce_(account->isIceForMediaEnabled())
104 1185 : , srtpEnabled_(account->isSrtpEnabled())
105 : {
106 : jami_tracepoint(call_start, callId.c_str());
107 :
108 395 : if (account->getUPnPActive())
109 0 : upnp_ = std::make_shared<dhtnet::upnp::Controller>(Manager::instance().upnpContext());
110 :
111 395 : setCallMediaLocal();
112 :
113 : // Set the media caps.
114 395 : sdp_->setLocalMediaCapabilities(MediaType::MEDIA_AUDIO, account->getActiveAccountCodecInfoList(MEDIA_AUDIO));
115 : #ifdef ENABLE_VIDEO
116 395 : sdp_->setLocalMediaCapabilities(MediaType::MEDIA_VIDEO, account->getActiveAccountCodecInfoList(MEDIA_VIDEO));
117 : #endif
118 :
119 395 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
120 :
121 395 : if (mediaAttrList.size() == 0) {
122 0 : if (type_ == Call::CallType::INCOMING) {
123 : // Handle incoming call without media offer.
124 0 : JAMI_WARNING("[call:{}] No media offered in the incoming invite. An offer will be provided in "
125 : "the answer",
126 : getCallId());
127 0 : mediaAttrList = getSIPAccount()->createDefaultMediaList(false, getState() == CallState::HOLD);
128 : } else {
129 0 : JAMI_WARNING("[call:{}] Creating an outgoing call with empty offer", getCallId());
130 : }
131 : }
132 :
133 395 : JAMI_DEBUG("[call:{:s}] Create a new [{:s}] SIP call with {:d} media",
134 : getCallId(),
135 : type == Call::CallType::INCOMING ? "INCOMING"
136 : : (type == Call::CallType::OUTGOING ? "OUTGOING" : "MISSED"),
137 : mediaList.size());
138 :
139 395 : initMediaStreams(mediaAttrList);
140 395 : }
141 :
142 395 : SIPCall::~SIPCall()
143 : {
144 395 : std::lock_guard lk {callMutex_};
145 :
146 395 : setSipTransport({});
147 395 : setInviteSession(); // prevents callback usage
148 :
149 : #ifdef ENABLE_VIDEO
150 395 : closeMediaPlayer(mediaPlayerId_);
151 : #endif
152 395 : }
153 :
154 : int
155 686 : SIPCall::findRtpStreamIndex(const std::string& label) const
156 : {
157 686 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), [&label](const RtpStream& rtp) {
158 1086 : return label == rtp.mediaAttribute_->label_;
159 : });
160 :
161 : // Return the index if there is a match.
162 686 : if (iter != rtpStreams_.end())
163 1352 : return static_cast<int>(std::distance(rtpStreams_.begin(), iter));
164 :
165 : // No match found.
166 10 : return -1;
167 : }
168 :
169 : void
170 712 : SIPCall::createRtpSession(RtpStream& stream)
171 : {
172 712 : if (not stream.mediaAttribute_)
173 0 : throw std::runtime_error("Missing media attribute");
174 :
175 : // To get audio_0 ; video_0
176 712 : auto streamId = sip_utils::streamId(id_, stream.mediaAttribute_->label_);
177 712 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
178 397 : stream.rtpSession_ = std::make_shared<AudioRtpSession>(id_, streamId, recorder_);
179 : }
180 : #ifdef ENABLE_VIDEO
181 315 : else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
182 315 : stream.rtpSession_ = std::make_shared<video::VideoRtpSession>(id_, streamId, getVideoSettings(), recorder_);
183 315 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation_);
184 : }
185 : #endif
186 : else {
187 0 : throw std::runtime_error("Unsupported media type");
188 : }
189 :
190 : // Must be valid at this point.
191 712 : if (not stream.rtpSession_)
192 0 : throw std::runtime_error("Failed to create RTP session");
193 : ;
194 712 : }
195 :
196 : void
197 354 : SIPCall::configureRtpSession(const std::shared_ptr<RtpSession>& rtpSession,
198 : const std::shared_ptr<MediaAttribute>& mediaAttr,
199 : const MediaDescription& localMedia,
200 : const MediaDescription& remoteMedia)
201 : {
202 354 : JAMI_DEBUG("[call:{}] Configuring [{}] RTP session",
203 : getCallId(),
204 : MediaAttribute::mediaTypeToString(mediaAttr->type_));
205 :
206 354 : if (not rtpSession)
207 0 : throw std::runtime_error("Must have a valid RTP session");
208 :
209 : // Configure the media stream
210 354 : auto new_mtu = sipTransport_->getTlsMtu();
211 354 : rtpSession->setMtu(new_mtu);
212 354 : rtpSession->updateMedia(remoteMedia, localMedia);
213 :
214 : // Mute/un-mute media
215 354 : rtpSession->setMuted(mediaAttr->muted_);
216 :
217 354 : rtpSession->setMediaSource(mediaAttr->sourceUri_);
218 :
219 354 : rtpSession->setSuccessfulSetupCb([w = weak()](MediaType, bool) {
220 : // This sends SIP messages on socket, so move to io
221 540 : dht::ThreadPool::io().run([w = std::move(w)] {
222 540 : if (auto thisPtr = w.lock())
223 540 : thisPtr->rtpSetupSuccess();
224 540 : });
225 540 : });
226 :
227 354 : if (localMedia.type == MediaType::MEDIA_AUDIO) {
228 197 : setupVoiceCallback(rtpSession);
229 : }
230 :
231 : #ifdef ENABLE_VIDEO
232 354 : if (localMedia.type == MediaType::MEDIA_VIDEO) {
233 157 : auto videoRtp = std::dynamic_pointer_cast<video::VideoRtpSession>(rtpSession);
234 157 : assert(videoRtp && mediaAttr);
235 157 : auto streamIdx = findRtpStreamIndex(mediaAttr->label_);
236 157 : videoRtp->setRequestKeyFrameCallback([w = weak(), streamIdx] {
237 : // This sends SIP messages on socket, so move to I/O
238 82 : dht::ThreadPool::io().run([w = std::move(w), streamIdx] {
239 82 : if (auto thisPtr = w.lock())
240 82 : thisPtr->requestKeyframe(streamIdx);
241 82 : });
242 82 : });
243 157 : videoRtp->setChangeOrientationCallback([w = weak(), streamIdx](int angle) {
244 : // This sends SIP messages on socket, so move to I/O
245 48 : dht::ThreadPool::io().run([w, angle, streamIdx] {
246 48 : if (auto thisPtr = w.lock())
247 48 : thisPtr->setVideoOrientation(streamIdx, angle);
248 48 : });
249 48 : });
250 157 : }
251 : #endif
252 354 : }
253 :
254 : void
255 197 : SIPCall::setupVoiceCallback(const std::shared_ptr<RtpSession>& rtpSession)
256 : {
257 : // need to downcast to access setVoiceCallback
258 197 : auto audioRtp = std::dynamic_pointer_cast<AudioRtpSession>(rtpSession);
259 :
260 197 : audioRtp->setVoiceCallback([w = weak()](bool voice) {
261 : // this is called whenever voice is detected on the local audio
262 :
263 0 : runOnMainThread([w, voice] {
264 0 : if (auto thisPtr = w.lock()) {
265 : // TODO: once we support multiple streams, change this to the right one
266 0 : std::string streamId = "";
267 :
268 : #ifdef ENABLE_VIDEO
269 0 : if (auto* videoManager = Manager::instance().getVideoManager()) {
270 0 : if (not videoManager->videoDeviceMonitor.getDeviceList().empty()) {
271 : // if we have a video device
272 0 : streamId = sip_utils::streamId("", sip_utils::DEFAULT_VIDEO_STREAMID);
273 : }
274 : }
275 : #endif
276 :
277 : // send our local voice activity
278 0 : if (auto conference = thisPtr->conf_.lock()) {
279 : // we are in a conference
280 :
281 : // updates conference info and sends it to others via ConfInfo
282 : // (only if there was a change)
283 : // also emits signal with updated conference info
284 0 : conference->setVoiceActivity(streamId, voice);
285 : } else {
286 : // we are in a one-to-one call
287 : // send voice activity over SIP
288 : // TODO: change the streamID once multiple streams are supported
289 0 : thisPtr->sendVoiceActivity("-1", voice);
290 :
291 : // TODO: maybe emit signal here for local voice activity
292 0 : }
293 0 : } else {
294 0 : JAMI_ERROR("Voice activity callback unable to lock weak ptr to SIPCall");
295 0 : }
296 0 : });
297 0 : });
298 197 : }
299 :
300 : std::shared_ptr<SIPAccountBase>
301 1972 : SIPCall::getSIPAccount() const
302 : {
303 1972 : return std::static_pointer_cast<SIPAccountBase>(getAccount().lock());
304 : }
305 :
306 : #ifdef ENABLE_PLUGIN
307 : void
308 425 : SIPCall::createCallAVStreams()
309 : {
310 : #ifdef ENABLE_VIDEO
311 745 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
312 381 : if (std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->hasConference()) {
313 61 : clearCallAVStreams();
314 61 : return;
315 : }
316 425 : }
317 : #endif
318 :
319 364 : auto baseId = getCallId();
320 5266 : auto mediaMap = [](const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
321 5266 : return m->pointer();
322 : };
323 :
324 364 : std::lock_guard lk(avStreamsMtx_);
325 1056 : for (const auto& rtpSession : getRtpSessionList()) {
326 692 : auto isVideo = rtpSession->getMediaType() == MediaType::MEDIA_VIDEO;
327 692 : auto streamType = isVideo ? StreamType::video : StreamType::audio;
328 692 : StreamData previewStreamData {baseId, false, streamType, getPeerNumber(), getAccountId()};
329 692 : StreamData receiveStreamData {baseId, true, streamType, getPeerNumber(), getAccountId()};
330 : #ifdef ENABLE_VIDEO
331 692 : if (isVideo) {
332 : // Preview
333 320 : auto videoRtp = std::static_pointer_cast<video::VideoRtpSession>(rtpSession);
334 320 : if (auto& videoPreview = videoRtp->getVideoLocal())
335 224 : createCallAVStream(previewStreamData, *videoPreview, std::make_shared<MediaStreamSubject>(mediaMap));
336 : // Receive
337 320 : if (auto& videoReceive = videoRtp->getVideoReceive())
338 296 : createCallAVStream(receiveStreamData, *videoReceive, std::make_shared<MediaStreamSubject>(mediaMap));
339 320 : } else {
340 : #endif
341 372 : auto audioRtp = std::static_pointer_cast<AudioRtpSession>(rtpSession);
342 : // Preview
343 372 : if (auto& localAudio = audioRtp->getAudioLocal())
344 355 : createCallAVStream(previewStreamData, *localAudio, std::make_shared<MediaStreamSubject>(mediaMap));
345 : // Receive
346 372 : if (auto& audioReceive = audioRtp->getAudioReceive())
347 279 : createCallAVStream(receiveStreamData,
348 279 : (AVMediaStream&) *audioReceive,
349 558 : std::make_shared<MediaStreamSubject>(mediaMap));
350 : #ifdef ENABLE_VIDEO
351 372 : }
352 : #endif
353 1056 : }
354 364 : }
355 :
356 : void
357 1154 : SIPCall::createCallAVStream(const StreamData& streamData,
358 : AVMediaStream& streamSource,
359 : const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject)
360 : {
361 2308 : const std::string AVStreamId = streamData.id + std::to_string(static_cast<int>(streamData.type))
362 3462 : + std::to_string(streamData.direction);
363 1154 : auto it = callAVStreams.find(AVStreamId);
364 1154 : if (it != callAVStreams.end())
365 448 : return;
366 706 : it = callAVStreams.insert(it, {AVStreamId, mediaStreamSubject});
367 706 : streamSource.attachPriorityObserver(it->second);
368 706 : jami::Manager::instance().getJamiPluginManager().getCallServicesManager().createAVSubject(streamData, it->second);
369 1154 : }
370 :
371 : void
372 582 : SIPCall::clearCallAVStreams()
373 : {
374 582 : std::lock_guard lk(avStreamsMtx_);
375 582 : callAVStreams.clear();
376 582 : }
377 : #endif // ENABLE_PLUGIN
378 :
379 : void
380 395 : SIPCall::setCallMediaLocal()
381 : {
382 395 : if (localAudioPort_ == 0
383 : #ifdef ENABLE_VIDEO
384 0 : || localVideoPort_ == 0
385 : #endif
386 : )
387 395 : generateMediaPorts();
388 395 : }
389 :
390 : void
391 422 : SIPCall::generateMediaPorts()
392 : {
393 422 : auto account = getSIPAccount();
394 422 : if (!account) {
395 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
396 0 : return;
397 : }
398 :
399 : // TODO. Setting specfic range for RTP ports is obsolete, in
400 : // particular in the context of ICE.
401 :
402 : // Reference: http://www.cs.columbia.edu/~hgs/rtp/faq.html#ports
403 : // We only want to set ports to new values if they haven't been set
404 422 : const unsigned callLocalAudioPort = account->generateAudioPort();
405 422 : if (localAudioPort_ != 0)
406 27 : account->releasePort(localAudioPort_);
407 422 : localAudioPort_ = callLocalAudioPort;
408 422 : sdp_->setLocalPublishedAudioPorts(callLocalAudioPort, rtcpMuxEnabled_ ? 0 : callLocalAudioPort + 1);
409 :
410 : #ifdef ENABLE_VIDEO
411 : // https://projects.savoirfairelinux.com/issues/17498
412 422 : const unsigned int callLocalVideoPort = account->generateVideoPort();
413 422 : if (localVideoPort_ != 0)
414 27 : account->releasePort(localVideoPort_);
415 : // this should already be guaranteed by SIPAccount
416 422 : assert(localAudioPort_ != callLocalVideoPort);
417 422 : localVideoPort_ = callLocalVideoPort;
418 422 : sdp_->setLocalPublishedVideoPorts(callLocalVideoPort, rtcpMuxEnabled_ ? 0 : callLocalVideoPort + 1);
419 : #endif
420 422 : }
421 :
422 : const std::string&
423 204 : SIPCall::getContactHeader() const
424 : {
425 204 : return contactHeader_;
426 : }
427 :
428 : void
429 1099 : SIPCall::setSipTransport(const std::shared_ptr<SipTransport>& transport, const std::string& contactHdr)
430 : {
431 1099 : if (transport != sipTransport_) {
432 584 : JAMI_DEBUG("[call:{}] Setting transport to [{}]", getCallId(), fmt::ptr(transport.get()));
433 : }
434 :
435 1099 : sipTransport_ = transport;
436 1099 : contactHeader_ = contactHdr;
437 :
438 1099 : if (not transport) {
439 : // Done.
440 807 : return;
441 : }
442 :
443 292 : if (contactHeader_.empty()) {
444 0 : JAMI_WARNING("[call:{}] Contact header is empty", getCallId());
445 : }
446 :
447 292 : if (isSrtpEnabled() and not sipTransport_->isSecure()) {
448 18 : JAMI_WARNING("[call:{}] Crypto (SRTP) is negotiated over an unencrypted signaling channel", getCallId());
449 : }
450 :
451 292 : if (not isSrtpEnabled() and sipTransport_->isSecure()) {
452 0 : JAMI_WARNING("[call:{}] The signaling channel is encrypted but the media is unencrypted", getCallId());
453 : }
454 :
455 292 : const auto list_id = reinterpret_cast<uintptr_t>(this);
456 292 : sipTransport_->removeStateListener(list_id);
457 :
458 : // Listen for transport destruction
459 292 : sipTransport_
460 292 : ->addStateListener(list_id, [wthis_ = weak()](pjsip_transport_state state, const pjsip_transport_state_info*) {
461 121 : if (auto this_ = wthis_.lock()) {
462 14 : JAMI_DEBUG("[call:{}] SIP transport state [{}] - connection state [{}]",
463 : this_->getCallId(),
464 : static_cast<int>(state),
465 : static_cast<unsigned>(this_->getConnectionState()));
466 :
467 : // End the call if the SIP transport was shut down
468 14 : auto isAlive = SipTransport::isAlive(state);
469 14 : if (not isAlive and this_->getConnectionState() != ConnectionState::DISCONNECTED) {
470 6 : JAMI_WARNING("[call:{}] Ending call because underlying SIP transport was closed",
471 : this_->getCallId());
472 6 : this_->stopAllMedia();
473 6 : this_->detachAudioFromConference();
474 6 : this_->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
475 : }
476 121 : }
477 121 : });
478 : }
479 :
480 : void
481 15 : SIPCall::requestReinvite(const std::vector<MediaAttribute>& mediaAttrList, bool needNewIce)
482 : {
483 15 : JAMI_DEBUG("[call:{}] Sending a SIP re-invite to request media change", getCallId());
484 :
485 15 : if (isWaitingForIceAndMedia_) {
486 0 : remainingRequest_ = Request::SwitchInput;
487 : } else {
488 15 : if (SIPSessionReinvite(mediaAttrList, needNewIce) == PJ_SUCCESS and reinvIceMedia_) {
489 15 : isWaitingForIceAndMedia_ = true;
490 : }
491 : }
492 15 : }
493 :
494 : /**
495 : * Send a reINVITE inside an active dialog to modify its state
496 : * Local SDP session should be modified before calling this method
497 : */
498 : int
499 28 : SIPCall::SIPSessionReinvite(const std::vector<MediaAttribute>& mediaAttrList, bool needNewIce)
500 : {
501 28 : assert(not mediaAttrList.empty());
502 :
503 28 : std::lock_guard lk {callMutex_};
504 :
505 : // Do nothing if no invitation processed yet
506 28 : if (not inviteSession_ or inviteSession_->invite_tsx)
507 1 : return PJ_SUCCESS;
508 :
509 27 : JAMI_DEBUG("[call:{}] Preparing and sending a re-invite (state={})",
510 : getCallId(),
511 : pjsip_inv_state_name(inviteSession_->state));
512 27 : JAMI_DEBUG("[call:{}] New ICE required for this re-invite: [{}]", getCallId(), needNewIce ? "Yes" : "No");
513 :
514 : // Generate new ports to receive the new media stream
515 : // LibAV doesn't discriminate SSRCs and will be confused about Seq changes on a given port
516 27 : generateMediaPorts();
517 :
518 27 : sdp_->clearIce();
519 27 : sdp_->setActiveRemoteSdpSession(nullptr);
520 27 : sdp_->setActiveLocalSdpSession(nullptr);
521 :
522 27 : auto acc = getSIPAccount();
523 27 : if (not acc) {
524 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
525 0 : return !PJ_SUCCESS;
526 : }
527 :
528 27 : if (not sdp_->createOffer(mediaAttrList))
529 0 : return !PJ_SUCCESS;
530 :
531 27 : if (isIceEnabled() and needNewIce) {
532 21 : if (not createIceMediaTransport(true) or not initIceMediaTransport(true)) {
533 0 : return !PJ_SUCCESS;
534 : }
535 21 : addLocalIceAttributes();
536 : // Media transport changed, must restart the media.
537 21 : mediaRestartRequired_ = true;
538 : }
539 :
540 : pjsip_tx_data* tdata;
541 27 : auto* local_sdp = sdp_->getLocalSdpSession();
542 27 : auto result = pjsip_inv_reinvite(inviteSession_.get(), nullptr, local_sdp, &tdata);
543 27 : if (result == PJ_SUCCESS) {
544 26 : if (!tdata)
545 0 : return PJ_SUCCESS;
546 :
547 : // Add user-agent header
548 26 : sip_utils::addUserAgentHeader(acc->getUserAgentName(), tdata);
549 :
550 26 : result = pjsip_inv_send_msg(inviteSession_.get(), tdata);
551 26 : if (result == PJ_SUCCESS)
552 26 : return PJ_SUCCESS;
553 0 : JAMI_ERROR("[call:{}] Failed to send REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
554 : // Canceling internals without sending (anyways the send has just failed!)
555 0 : pjsip_inv_cancel_reinvite(inviteSession_.get(), &tdata);
556 : } else
557 1 : JAMI_ERROR("[call:{}] Failed to create REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
558 :
559 1 : return !PJ_SUCCESS;
560 28 : }
561 :
562 : int
563 6 : SIPCall::SIPSessionReinvite()
564 : {
565 6 : auto mediaList = getMediaAttributeList();
566 12 : return SIPSessionReinvite(mediaList, isNewIceMediaRequired(mediaList));
567 6 : }
568 :
569 : void
570 312 : SIPCall::sendSIPInfo(std::string_view body, std::string_view subtype)
571 : {
572 312 : std::lock_guard lk {callMutex_};
573 312 : if (not inviteSession_ or not inviteSession_->dlg)
574 0 : throw VoipLinkException("Unable to get invite dialog");
575 :
576 312 : constexpr pj_str_t methodName = CONST_PJ_STR("INFO");
577 312 : constexpr pj_str_t type = CONST_PJ_STR("application");
578 :
579 : pjsip_method method;
580 312 : pjsip_method_init_np(&method, (pj_str_t*) &methodName);
581 :
582 : /* Create request message. */
583 : pjsip_tx_data* tdata;
584 312 : if (pjsip_dlg_create_request(inviteSession_->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
585 0 : JAMI_ERROR("[call:{}] Unable to create dialog", getCallId());
586 0 : return;
587 : }
588 :
589 : /* Create "application/<subtype>" message body. */
590 312 : pj_str_t content = CONST_PJ_STR(body);
591 312 : pj_str_t pj_subtype = CONST_PJ_STR(subtype);
592 312 : tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
593 312 : if (tdata->msg->body == NULL)
594 0 : pjsip_tx_data_dec_ref(tdata);
595 : else
596 312 : pjsip_dlg_send_request(inviteSession_->dlg, tdata, Manager::instance().sipVoIPLink().getModId(), NULL);
597 312 : }
598 :
599 : void
600 19 : SIPCall::updateRecState(bool state)
601 : {
602 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
603 : "<media_control><vc_primitive><to_encoder>"
604 : "<recording_state="
605 38 : + std::to_string(state)
606 : + "/>"
607 19 : "</to_encoder></vc_primitive></media_control>";
608 : // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details
609 :
610 19 : JAMI_DEBUG("[call:{}] Sending recording state via SIP INFO", getCallId());
611 :
612 : try {
613 19 : sendSIPInfo(BODY, "media_control+xml");
614 0 : } catch (const std::exception& e) {
615 0 : JAMI_ERROR("[call:{}] Error sending recording state: {}", getCallId(), e.what());
616 0 : }
617 19 : }
618 :
619 : void
620 241 : SIPCall::requestKeyframe(int streamIdx)
621 : {
622 241 : auto now = clock::now();
623 241 : if ((now - lastKeyFrameReq_) < MS_BETWEEN_2_KEYFRAME_REQUEST and lastKeyFrameReq_ != time_point::min())
624 1 : return;
625 :
626 240 : std::string streamIdPart;
627 240 : if (streamIdx != -1)
628 480 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx);
629 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
630 : "<media_control><vc_primitive> "
631 480 : + streamIdPart + "<to_encoder>"
632 : + "<picture_fast_update/>"
633 480 : "</to_encoder></vc_primitive></media_control>";
634 240 : JAMI_DEBUG("[call:{}] Sending video keyframe request via SIP INFO", getCallId());
635 : try {
636 240 : sendSIPInfo(BODY, "media_control+xml");
637 0 : } catch (const std::exception& e) {
638 0 : JAMI_ERROR("[call:{}] Error sending video keyframe request: {}", getCallId(), e.what());
639 0 : }
640 240 : lastKeyFrameReq_ = now;
641 240 : }
642 :
643 : void
644 5 : SIPCall::sendMuteState(bool state)
645 : {
646 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
647 : "<media_control><vc_primitive><to_encoder>"
648 : "<mute_state="
649 10 : + std::to_string(state)
650 : + "/>"
651 5 : "</to_encoder></vc_primitive></media_control>";
652 : // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details
653 :
654 5 : JAMI_DEBUG("[call:{}] Sending mute state via SIP INFO", getCallId());
655 :
656 : try {
657 5 : sendSIPInfo(BODY, "media_control+xml");
658 0 : } catch (const std::exception& e) {
659 0 : JAMI_ERROR("[call:{}] Error sending mute state: {}", getCallId(), e.what());
660 0 : }
661 5 : }
662 :
663 : void
664 0 : SIPCall::sendVoiceActivity(std::string_view streamId, bool state)
665 : {
666 : // dont send streamId if it's -1
667 0 : std::string streamIdPart = "";
668 0 : if (streamId != "-1" && !streamId.empty()) {
669 0 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamId);
670 : }
671 :
672 : std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
673 : "<media_control><vc_primitive>"
674 0 : + streamIdPart
675 0 : + "<to_encoder>"
676 0 : "<voice_activity="
677 0 : + std::to_string(state)
678 : + "/>"
679 0 : "</to_encoder></vc_primitive></media_control>";
680 :
681 : try {
682 0 : sendSIPInfo(BODY, "media_control+xml");
683 0 : } catch (const std::exception& e) {
684 0 : JAMI_ERROR("[call:{}] Error sending voice activity state: {}", getCallId(), e.what());
685 0 : }
686 0 : }
687 :
688 : void
689 1229 : SIPCall::setInviteSession(pjsip_inv_session* inviteSession)
690 : {
691 1229 : std::lock_guard lk {callMutex_};
692 :
693 1229 : if (inviteSession == nullptr and inviteSession_) {
694 214 : JAMI_DEBUG("[call:{}] Delete current invite session", getCallId());
695 1015 : } else if (inviteSession != nullptr) {
696 : // NOTE: The first reference of the invite session is owned by pjsip. If
697 : // that counter goes down to zero the invite will be destroyed, and the
698 : // unique_ptr will point freed datas. To avoid this, we increment the
699 : // ref counter and let our unique_ptr share the ownership of the session
700 : // with pjsip.
701 214 : if (PJ_SUCCESS != pjsip_inv_add_ref(inviteSession)) {
702 0 : JAMI_WARNING("[call:{}] Attempting to set invalid invite session [{}]",
703 : getCallId(),
704 : fmt::ptr(inviteSession));
705 0 : inviteSession_.reset(nullptr);
706 0 : return;
707 : }
708 214 : JAMI_DEBUG("[call:{}] Set new invite session [{}]", getCallId(), fmt::ptr(inviteSession));
709 : } else {
710 : // Nothing to do.
711 801 : return;
712 : }
713 :
714 428 : inviteSession_.reset(inviteSession);
715 1229 : }
716 :
717 : void
718 208 : SIPCall::terminateSipSession(int status)
719 : {
720 208 : JAMI_DEBUG("[call:{}] Terminate SIP session", getCallId());
721 208 : std::lock_guard lk {callMutex_};
722 208 : if (inviteSession_ and inviteSession_->state != PJSIP_INV_STATE_DISCONNECTED) {
723 105 : pjsip_tx_data* tdata = nullptr;
724 105 : auto ret = pjsip_inv_end_session(inviteSession_.get(), status, nullptr, &tdata);
725 105 : if (ret == PJ_SUCCESS) {
726 105 : if (tdata) {
727 105 : auto account = getSIPAccount();
728 105 : if (account) {
729 105 : sip_utils::addContactHeader(contactHeader_, tdata);
730 : // Add user-agent header
731 105 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
732 : } else {
733 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
734 : throw std::runtime_error(
735 0 : fmt::format("[call:{}] The account owning this call is invalid", getCallId()));
736 : }
737 :
738 105 : ret = pjsip_inv_send_msg(inviteSession_.get(), tdata);
739 105 : if (ret != PJ_SUCCESS)
740 0 : JAMI_ERROR("[call:{}] Failed to send terminate msg, SIP error ({})",
741 : getCallId(),
742 : sip_utils::sip_strerror(ret));
743 105 : }
744 : } else
745 0 : JAMI_ERROR("[call:{}] Failed to terminate INVITE@{}, SIP error ({})",
746 : getCallId(),
747 : fmt::ptr(inviteSession_.get()),
748 : sip_utils::sip_strerror(ret));
749 : }
750 208 : setInviteSession();
751 208 : }
752 :
753 : void
754 97 : SIPCall::answer(const std::vector<libjami::MediaMap>& mediaList)
755 : {
756 97 : std::lock_guard lk {callMutex_};
757 97 : auto account = getSIPAccount();
758 97 : if (not account) {
759 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
760 0 : return;
761 : }
762 :
763 97 : if (not inviteSession_) {
764 0 : JAMI_ERROR("[call:{}] No invite session for this call", getCallId());
765 0 : return;
766 : }
767 :
768 97 : if (not sdp_) {
769 0 : JAMI_ERROR("[call:{}] No SDP session for this call", getCallId());
770 0 : return;
771 : }
772 :
773 97 : auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
774 :
775 97 : if (newMediaAttrList.empty() and rtpStreams_.empty()) {
776 0 : JAMI_ERROR("[call:{}] Media list must not be empty!", getCallId());
777 0 : return;
778 : }
779 :
780 : // If the media list is empty, use the current media (this could happen
781 : // with auto-answer for instance), otherwise update the current media.
782 97 : if (newMediaAttrList.empty()) {
783 68 : JAMI_DEBUG("[call:{}] Media list is empty, using current media", getCallId());
784 29 : } else if (newMediaAttrList.size() != rtpStreams_.size()) {
785 : // This should never happen, as we make sure that the sizes match earlier
786 : // in handleIncomingConversationCall.
787 0 : JAMI_ERROR("[call:{:s}] Media list size {:d} in answer does not match. Expected {:d}",
788 : getCallId(),
789 : newMediaAttrList.size(),
790 : rtpStreams_.size());
791 0 : return;
792 : }
793 :
794 97 : auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList() : newMediaAttrList;
795 :
796 97 : JAMI_DEBUG("[call:{}] Answering incoming call with following media:", getCallId());
797 270 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
798 173 : auto const& mediaAttr = mediaAttrList.at(idx);
799 173 : JAMI_DEBUG("[call:{:s}] Media @{:d} - {:s}", getCallId(), idx, mediaAttr.toString(true));
800 : }
801 :
802 : // Apply the media attributes.
803 270 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
804 173 : updateMediaStream(mediaAttrList[idx], idx);
805 : }
806 :
807 : // Create the SDP answer
808 97 : sdp_->processIncomingOffer(mediaAttrList);
809 :
810 97 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
811 95 : setupIceResponse();
812 : }
813 :
814 97 : if (not inviteSession_->neg) {
815 : // We are answering to an INVITE that did not include a media offer (SDP).
816 : // The SIP specification (RFCs 3261/6337) requires that if a UA wants to
817 : // proceed with the call, it must provide a media offer (SDP) if the initial
818 : // INVITE did not offer one. In this case, the SDP offer will be included in
819 : // the SIP OK (200) answer. The peer UA will then include its SDP answer in
820 : // the SIP ACK message.
821 :
822 : // TODO. This code should be unified with the code used by accounts to create
823 : // SDP offers.
824 :
825 0 : JAMI_WARNING("[call:{}] No negotiator session, peer sent an empty INVITE (without SDP)", getCallId());
826 :
827 0 : Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
828 :
829 0 : generateMediaPorts();
830 :
831 : // Setup and create ICE offer
832 0 : if (isIceEnabled()) {
833 0 : sdp_->clearIce();
834 0 : sdp_->setActiveRemoteSdpSession(nullptr);
835 0 : sdp_->setActiveLocalSdpSession(nullptr);
836 :
837 0 : auto opts = account->getIceOptions();
838 :
839 0 : auto publicAddr = account->getPublishedIpAddress();
840 :
841 0 : if (publicAddr) {
842 0 : opts.accountPublicAddr = publicAddr;
843 0 : if (auto interfaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
844 0 : publicAddr.getFamily())) {
845 0 : opts.accountLocalAddr = interfaceAddr;
846 0 : if (createIceMediaTransport(false) and initIceMediaTransport(true, std::move(opts))) {
847 0 : addLocalIceAttributes();
848 : }
849 : } else {
850 0 : JAMI_WARNING("[call:{}] Unable to init ICE transport, missing local address", getCallId());
851 : }
852 : } else {
853 0 : JAMI_WARNING("[call:{}] Unable to init ICE transport, missing public address", getCallId());
854 : }
855 0 : }
856 : }
857 :
858 97 : if (!inviteSession_->last_answer)
859 0 : throw std::runtime_error("Should only be called for initial answer");
860 :
861 : // Set the SIP final answer (200 OK).
862 : pjsip_tx_data* tdata;
863 97 : if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata) != PJ_SUCCESS)
864 0 : throw std::runtime_error("Unable to init invite request answer (200 OK)");
865 :
866 97 : if (contactHeader_.empty()) {
867 0 : throw std::runtime_error("Unable to answer with an invalid contact header");
868 : }
869 :
870 97 : JAMI_DEBUG("[call:{}] Answering with contact header: {}", getCallId(), contactHeader_);
871 :
872 97 : sip_utils::addContactHeader(contactHeader_, tdata);
873 :
874 : // Add user-agent header
875 97 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
876 :
877 97 : if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
878 0 : setInviteSession();
879 0 : throw std::runtime_error("Unable to send invite request answer (200 OK)");
880 : }
881 :
882 97 : setState(CallState::ACTIVE, ConnectionState::CONNECTED);
883 97 : }
884 :
885 : void
886 26 : SIPCall::answerMediaChangeRequest(const std::vector<libjami::MediaMap>& mediaList, bool isRemote)
887 : {
888 26 : std::lock_guard lk {callMutex_};
889 :
890 26 : auto account = getSIPAccount();
891 26 : if (not account) {
892 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
893 0 : return;
894 : }
895 :
896 26 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
897 :
898 : // TODO. is the right place?
899 : // Disable video if disabled in the account.
900 26 : if (not account->isVideoEnabled()) {
901 0 : for (auto& mediaAttr : mediaAttrList) {
902 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
903 0 : mediaAttr.enabled_ = false;
904 : }
905 : }
906 : }
907 :
908 26 : if (mediaAttrList.empty()) {
909 0 : JAMI_WARNING("[call:{}] Media list is empty. Ignoring the media change request", getCallId());
910 0 : return;
911 : }
912 :
913 26 : if (not sdp_) {
914 0 : JAMI_ERROR("[call:{}] No valid SDP session", getCallId());
915 0 : return;
916 : }
917 :
918 26 : JAMI_DEBUG("[call:{}] Current media", getCallId());
919 26 : unsigned idx = 0;
920 73 : for (auto const& rtp : rtpStreams_) {
921 47 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, rtp.mediaAttribute_->toString(true));
922 : }
923 :
924 26 : JAMI_DEBUG("[call:{}] Answering to media change request with new media", getCallId());
925 26 : idx = 0;
926 77 : for (auto const& newMediaAttr : mediaAttrList) {
927 51 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.toString(true));
928 : }
929 :
930 26 : if (!updateAllMediaStreams(mediaAttrList, isRemote))
931 0 : return;
932 :
933 26 : if (not sdp_->processIncomingOffer(mediaAttrList)) {
934 0 : JAMI_WARNING("[call:{}] Unable to process the new offer, ignoring", getCallId());
935 0 : return;
936 : }
937 :
938 26 : if (not sdp_->getRemoteSdpSession()) {
939 0 : JAMI_ERROR("[call:{}] No valid remote SDP session", getCallId());
940 0 : return;
941 : }
942 :
943 26 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
944 20 : JAMI_WARNING("[call:{}] Requesting a new ICE media", getCallId());
945 20 : setupIceResponse(true);
946 : }
947 :
948 26 : if (not sdp_->startNegotiation()) {
949 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
950 0 : return;
951 : }
952 :
953 26 : if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
954 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
955 0 : return;
956 : }
957 :
958 : pjsip_tx_data* tdata;
959 26 : if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, NULL, &tdata) != PJ_SUCCESS) {
960 2 : JAMI_ERROR("[call:{}] Unable to init answer to a re-invite request", getCallId());
961 2 : return;
962 : }
963 :
964 24 : if (not contactHeader_.empty()) {
965 24 : sip_utils::addContactHeader(contactHeader_, tdata);
966 : }
967 :
968 : // Add user-agent header
969 24 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
970 :
971 24 : if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
972 0 : JAMI_ERROR("[call:{}] Unable to send answer to a re-invite request", getCallId());
973 0 : setInviteSession();
974 0 : return;
975 : }
976 :
977 24 : JAMI_DEBUG("[call:{}] Successfully answered the media change request", getCallId());
978 30 : }
979 :
980 : void
981 138 : SIPCall::hangup(int code)
982 : {
983 138 : std::lock_guard lk {callMutex_};
984 138 : pendingRecord_ = false;
985 138 : if (inviteSession_ and inviteSession_->dlg) {
986 109 : pjsip_route_hdr* route = inviteSession_->dlg->route_set.next;
987 109 : while (route and route != &inviteSession_->dlg->route_set) {
988 : char buf[1024];
989 0 : int printed = pjsip_hdr_print_on(route, buf, sizeof(buf));
990 0 : if (printed >= 0) {
991 0 : buf[printed] = '\0';
992 0 : JAMI_DEBUG("[call:{}] Route header {}", getCallId(), buf);
993 : }
994 0 : route = route->next;
995 : }
996 :
997 109 : int status = PJSIP_SC_OK;
998 109 : if (code)
999 2 : status = code;
1000 107 : else if (inviteSession_->state <= PJSIP_INV_STATE_EARLY and inviteSession_->role != PJSIP_ROLE_UAC)
1001 1 : status = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
1002 106 : else if (inviteSession_->state >= PJSIP_INV_STATE_DISCONNECTED)
1003 6 : status = PJSIP_SC_DECLINE;
1004 :
1005 : // Notify the peer
1006 109 : terminateSipSession(status);
1007 : }
1008 :
1009 : // Stop all RTP streams
1010 138 : stopAllMedia();
1011 138 : detachAudioFromConference();
1012 138 : setState(Call::ConnectionState::DISCONNECTED, code);
1013 138 : dht::ThreadPool::io().run([w = weak(), code] {
1014 138 : if (auto shared = w.lock())
1015 138 : shared->removeCall(code);
1016 138 : });
1017 138 : }
1018 :
1019 : void
1020 249 : SIPCall::detachAudioFromConference()
1021 : {
1022 : #ifdef ENABLE_VIDEO
1023 249 : if (auto conf = getConference()) {
1024 2 : if (auto mixer = conf->getVideoMixer()) {
1025 4 : for (auto& stream : getRtpSessionList(MediaType::MEDIA_AUDIO)) {
1026 2 : mixer->removeAudioOnlySource(getCallId(), stream->streamId());
1027 2 : }
1028 2 : }
1029 249 : }
1030 : #endif
1031 249 : }
1032 :
1033 : void
1034 2 : SIPCall::refuse()
1035 : {
1036 2 : if (!isIncoming() or getConnectionState() == ConnectionState::CONNECTED or !inviteSession_)
1037 0 : return;
1038 :
1039 2 : stopAllMedia();
1040 :
1041 : // Notify the peer
1042 2 : terminateSipSession(PJSIP_SC_DECLINE);
1043 :
1044 2 : setState(Call::ConnectionState::DISCONNECTED, PJSIP_SC_DECLINE);
1045 2 : removeCall();
1046 : }
1047 :
1048 : static void
1049 5 : transfer_client_cb(pjsip_evsub* sub, pjsip_event* event)
1050 : {
1051 5 : auto mod_ua_id = Manager::instance().sipVoIPLink().getModId();
1052 :
1053 5 : switch (pjsip_evsub_get_state(sub)) {
1054 2 : case PJSIP_EVSUB_STATE_ACCEPTED:
1055 2 : if (!event)
1056 0 : return;
1057 :
1058 2 : pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
1059 2 : break;
1060 :
1061 1 : case PJSIP_EVSUB_STATE_TERMINATED:
1062 1 : pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1063 1 : break;
1064 :
1065 0 : case PJSIP_EVSUB_STATE_ACTIVE: {
1066 0 : if (!event)
1067 0 : return;
1068 :
1069 0 : pjsip_rx_data* r_data = event->body.rx_msg.rdata;
1070 :
1071 0 : if (!r_data)
1072 0 : return;
1073 :
1074 0 : std::string request(pjsip_rx_data_get_info(r_data));
1075 :
1076 0 : pjsip_status_line status_line = {500, *pjsip_get_status_text(500)};
1077 :
1078 0 : if (!r_data->msg_info.msg)
1079 0 : return;
1080 :
1081 0 : if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD
1082 0 : and request.find("NOTIFY") != std::string::npos) {
1083 0 : pjsip_msg_body* body = r_data->msg_info.msg->body;
1084 :
1085 0 : if (!body)
1086 0 : return;
1087 :
1088 0 : if (pj_stricmp2(&body->content_type.type, "message") or pj_stricmp2(&body->content_type.subtype, "sipfrag"))
1089 0 : return;
1090 :
1091 0 : if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS)
1092 0 : return;
1093 : }
1094 :
1095 0 : if (!r_data->msg_info.cid)
1096 0 : return;
1097 :
1098 0 : auto* call = static_cast<SIPCall*>(pjsip_evsub_get_mod_data(sub, mod_ua_id));
1099 0 : if (!call)
1100 0 : return;
1101 :
1102 0 : if (status_line.code / 100 == 2) {
1103 0 : if (call->inviteSession_)
1104 0 : call->terminateSipSession(PJSIP_SC_GONE);
1105 0 : Manager::instance().hangupCall(call->getAccountId(), call->getCallId());
1106 0 : pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1107 : }
1108 :
1109 0 : break;
1110 0 : }
1111 :
1112 2 : case PJSIP_EVSUB_STATE_NULL:
1113 : case PJSIP_EVSUB_STATE_SENT:
1114 : case PJSIP_EVSUB_STATE_PENDING:
1115 : case PJSIP_EVSUB_STATE_UNKNOWN:
1116 : default:
1117 2 : break;
1118 : }
1119 : }
1120 :
1121 : bool
1122 2 : SIPCall::transferCommon(const pj_str_t* dst)
1123 : {
1124 2 : if (not inviteSession_ or not inviteSession_->dlg)
1125 0 : return false;
1126 :
1127 : pjsip_evsub_user xfer_cb;
1128 2 : pj_bzero(&xfer_cb, sizeof(xfer_cb));
1129 2 : xfer_cb.on_evsub_state = &transfer_client_cb;
1130 :
1131 : pjsip_evsub* sub;
1132 :
1133 2 : if (pjsip_xfer_create_uac(inviteSession_->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
1134 0 : return false;
1135 :
1136 : /* Associate this VoIPLink of call with the client subscription
1137 : * We are unable to just associate call with the client subscription
1138 : * because after this function, we are unable to find the corresponding
1139 : * VoIPLink from the call any more. But the VoIPLink is useful!
1140 : */
1141 2 : pjsip_evsub_set_mod_data(sub, Manager::instance().sipVoIPLink().getModId(), this);
1142 :
1143 : /*
1144 : * Create REFER request.
1145 : */
1146 : pjsip_tx_data* tdata;
1147 :
1148 2 : if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
1149 0 : return false;
1150 :
1151 : /* Send. */
1152 2 : if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
1153 0 : return false;
1154 :
1155 2 : return true;
1156 : }
1157 :
1158 : void
1159 2 : SIPCall::transfer(const std::string& to)
1160 : {
1161 2 : auto account = getSIPAccount();
1162 2 : if (!account) {
1163 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1164 0 : return;
1165 : }
1166 :
1167 2 : deinitRecorder();
1168 2 : if (Call::isRecording())
1169 0 : stopRecording();
1170 :
1171 2 : std::string toUri = account->getToUri(to);
1172 2 : const pj_str_t dst(CONST_PJ_STR(toUri));
1173 :
1174 2 : JAMI_DEBUG("[call:{}] Transferring to {}", getCallId(), std::string_view(dst.ptr, dst.slen));
1175 :
1176 2 : if (!transferCommon(&dst))
1177 0 : throw VoipLinkException("Unable to transfer");
1178 2 : }
1179 :
1180 : bool
1181 0 : SIPCall::attendedTransfer(const std::string& to)
1182 : {
1183 0 : auto toCall = Manager::instance().callFactory.getCall<SIPCall>(to);
1184 0 : if (!toCall)
1185 0 : return false;
1186 :
1187 0 : if (not toCall->inviteSession_ or not toCall->inviteSession_->dlg)
1188 0 : return false;
1189 :
1190 0 : pjsip_dialog* target_dlg = toCall->inviteSession_->dlg;
1191 0 : pjsip_uri* uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
1192 :
1193 0 : char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = {'<'};
1194 0 : pj_str_t dst = {str_dest_buf, 1};
1195 :
1196 0 : dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1);
1197 0 : dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
1198 0 : sizeof(str_dest_buf) - dst.slen,
1199 : "?"
1200 : "Replaces=%.*s"
1201 : "%%3Bto-tag%%3D%.*s"
1202 : "%%3Bfrom-tag%%3D%.*s>",
1203 0 : (int) target_dlg->call_id->id.slen,
1204 0 : target_dlg->call_id->id.ptr,
1205 0 : (int) target_dlg->remote.info->tag.slen,
1206 0 : target_dlg->remote.info->tag.ptr,
1207 0 : (int) target_dlg->local.info->tag.slen,
1208 0 : target_dlg->local.info->tag.ptr);
1209 :
1210 0 : return transferCommon(&dst);
1211 0 : }
1212 :
1213 : bool
1214 6 : SIPCall::hold(OnReadyCb&& cb)
1215 : {
1216 : // If ICE is currently negotiating, we must wait before hold the call
1217 6 : if (isWaitingForIceAndMedia_) {
1218 0 : holdCb_ = std::move(cb);
1219 0 : remainingRequest_ = Request::Hold;
1220 0 : return false;
1221 : }
1222 :
1223 6 : auto result = hold();
1224 :
1225 6 : if (cb)
1226 6 : cb(result);
1227 :
1228 6 : return result;
1229 : }
1230 :
1231 : bool
1232 6 : SIPCall::hold()
1233 : {
1234 6 : if (getConnectionState() != ConnectionState::CONNECTED) {
1235 0 : JAMI_WARNING("[call:{}] Not connected, ignoring hold request", getCallId());
1236 0 : return false;
1237 : }
1238 :
1239 6 : if (not setState(CallState::HOLD)) {
1240 0 : JAMI_WARNING("[call:{}] Failed to set state to HOLD", getCallId());
1241 0 : return false;
1242 : }
1243 :
1244 6 : stopAllMedia();
1245 :
1246 17 : for (auto& stream : rtpStreams_) {
1247 11 : stream.mediaAttribute_->hold_ = true;
1248 : }
1249 :
1250 6 : if (SIPSessionReinvite() != PJ_SUCCESS) {
1251 0 : JAMI_WARNING("[call:{}] Reinvite failed", getCallId());
1252 0 : return false;
1253 : }
1254 :
1255 : // TODO. Do we need to check for reinvIceMedia_ ?
1256 6 : isWaitingForIceAndMedia_ = (reinvIceMedia_ != nullptr);
1257 :
1258 6 : JAMI_DEBUG("[call:{}] Set state to HOLD", getCallId());
1259 6 : return true;
1260 : }
1261 :
1262 : bool
1263 3 : SIPCall::resume(OnReadyCb&& cb)
1264 : {
1265 : // If ICE is currently negotiating, we must wait before attempting to resume the call
1266 3 : if (isWaitingForIceAndMedia_) {
1267 0 : JAMI_DEBUG("[call:{}] ICE negotiation in progress. Resume request will be once ICE "
1268 : "negotiation completes",
1269 : getCallId());
1270 0 : resumeCb_ = std::move(cb);
1271 0 : remainingRequest_ = Request::Resume;
1272 0 : return false;
1273 : }
1274 3 : JAMI_DEBUG("[call:{}] Resuming the call", getCallId());
1275 3 : auto result = resume();
1276 :
1277 3 : if (cb)
1278 3 : cb(result);
1279 :
1280 3 : return result;
1281 : }
1282 :
1283 : bool
1284 3 : SIPCall::resume()
1285 : {
1286 3 : auto account = getSIPAccount();
1287 3 : if (!account) {
1288 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1289 0 : return false;
1290 : }
1291 :
1292 3 : bool success = false;
1293 : try {
1294 3 : success = internalResume([] {});
1295 0 : } catch (const SdpException& e) {
1296 0 : JAMI_ERROR("[call:{}] {}", getCallId(), e.what());
1297 0 : throw VoipLinkException("SDP issue in resume");
1298 0 : }
1299 :
1300 : // Only wait for ICE if we have an ICE re-invite in progress
1301 3 : isWaitingForIceAndMedia_ = success and (reinvIceMedia_ != nullptr);
1302 :
1303 3 : return success;
1304 3 : }
1305 :
1306 : bool
1307 3 : SIPCall::internalResume(const std::function<void()>& sdp_cb)
1308 : {
1309 3 : if (getConnectionState() != ConnectionState::CONNECTED) {
1310 0 : JAMI_WARNING("[call:{}] Not connected, ignoring resume request", getCallId());
1311 : }
1312 :
1313 3 : if (not setState(CallState::ACTIVE))
1314 0 : return false;
1315 :
1316 3 : sdp_cb();
1317 :
1318 : {
1319 8 : for (auto& stream : rtpStreams_) {
1320 5 : stream.mediaAttribute_->hold_ = false;
1321 : }
1322 : // For now, call resume will always require new ICE negotiation.
1323 3 : if (SIPSessionReinvite(getMediaAttributeList(), true) != PJ_SUCCESS) {
1324 0 : JAMI_WARNING("[call:{}] Resuming hold", getCallId());
1325 0 : if (isWaitingForIceAndMedia_) {
1326 0 : remainingRequest_ = Request::Hold;
1327 : } else {
1328 0 : hold();
1329 : }
1330 0 : return false;
1331 : }
1332 : }
1333 :
1334 3 : return true;
1335 : }
1336 :
1337 : void
1338 4 : SIPCall::switchInput(const std::string& source)
1339 : {
1340 4 : JAMI_DEBUG("[call:{}] Set selected source to {}", getCallId(), source);
1341 :
1342 11 : for (auto const& stream : rtpStreams_) {
1343 7 : auto mediaAttr = stream.mediaAttribute_;
1344 7 : mediaAttr->sourceUri_ = source;
1345 7 : }
1346 :
1347 : // Check if the call is being recorded in order to continue
1348 : // … the recording after the switch
1349 4 : bool isRec = Call::isRecording();
1350 :
1351 4 : if (isWaitingForIceAndMedia_) {
1352 0 : remainingRequest_ = Request::SwitchInput;
1353 : } else {
1354 : // For now, switchInput will always trigger a re-invite
1355 : // with new ICE session.
1356 4 : if (SIPSessionReinvite(getMediaAttributeList(), true) == PJ_SUCCESS and reinvIceMedia_) {
1357 2 : isWaitingForIceAndMedia_ = true;
1358 : }
1359 : }
1360 4 : if (isRec) {
1361 0 : readyToRecord_ = false;
1362 0 : pendingRecord_ = true;
1363 : }
1364 4 : }
1365 :
1366 : void
1367 105 : SIPCall::peerHungup()
1368 : {
1369 105 : pendingRecord_ = false;
1370 : // Stop all RTP streams
1371 105 : stopAllMedia();
1372 :
1373 105 : if (inviteSession_)
1374 97 : terminateSipSession(PJSIP_SC_NOT_FOUND);
1375 105 : detachAudioFromConference();
1376 105 : Call::peerHungup();
1377 105 : }
1378 :
1379 : void
1380 0 : SIPCall::carryingDTMFdigits(char code)
1381 : {
1382 0 : int duration = Manager::instance().voipPreferences.getPulseLength();
1383 : char dtmf_body[1000];
1384 : int ret;
1385 :
1386 : // handle flash code
1387 0 : if (code == '!') {
1388 0 : ret = snprintf(dtmf_body, sizeof dtmf_body - 1, "Signal=16\r\nDuration=%d\r\n", duration);
1389 : } else {
1390 0 : ret = snprintf(dtmf_body, sizeof dtmf_body - 1, "Signal=%c\r\nDuration=%d\r\n", code, duration);
1391 : }
1392 :
1393 : try {
1394 0 : sendSIPInfo({dtmf_body, (size_t) ret}, "dtmf-relay");
1395 0 : } catch (const std::exception& e) {
1396 0 : JAMI_ERROR("[call:{}] Error sending DTMF: {}", getCallId(), e.what());
1397 0 : }
1398 0 : }
1399 :
1400 : void
1401 48 : SIPCall::setVideoOrientation(int streamIdx, int rotation)
1402 : {
1403 48 : std::string streamIdPart;
1404 48 : if (streamIdx != -1)
1405 96 : streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx);
1406 : std::string sip_body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1407 : "<media_control><vc_primitive><to_encoder>"
1408 : "<device_orientation="
1409 96 : + std::to_string(-rotation) + "/>" + "</to_encoder>" + streamIdPart
1410 48 : + "</vc_primitive></media_control>";
1411 :
1412 48 : JAMI_DEBUG("[call:{}] Sending device orientation via SIP INFO {} for stream {}", getCallId(), rotation, streamIdx);
1413 :
1414 48 : sendSIPInfo(sip_body, "media_control+xml");
1415 48 : }
1416 :
1417 : void
1418 244 : SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from)
1419 : {
1420 : // TODO: for now we ignore the "from" (the previous implementation for sending this info was
1421 : // buggy and verbose), another way to send the original message sender will be implemented
1422 : // in the future
1423 244 : if (not subcalls_.empty()) {
1424 0 : pendingOutMessages_.emplace_back(messages, from);
1425 0 : for (auto& c : subcalls_)
1426 0 : c->sendTextMessage(messages, from);
1427 : } else {
1428 242 : if (inviteSession_) {
1429 : try {
1430 : // Ignore if the peer does not allow "MESSAGE" SIP method
1431 : // NOTE:
1432 : // The SIP "Allow" header is not mandatory as per RFC-3261. If it's
1433 : // not present and since "MESSAGE" method is an extention method,
1434 : // we choose to assume that the peer does not support the "MESSAGE"
1435 : // method to prevent unexpected behavior when interoperating with
1436 : // some SIP implementations.
1437 241 : if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
1438 0 : JAMI_WARNING("[call:{}] Peer does not allow \"{}\" method",
1439 : getCallId(),
1440 : sip_utils::SIP_METHODS::MESSAGE);
1441 :
1442 : // Print peer's allowed methods
1443 0 : JAMI_LOG("[call:{}] Peer's allowed methods: {}", getCallId(), peerAllowedMethods_);
1444 0 : return;
1445 : }
1446 :
1447 244 : im::sendSipMessage(inviteSession_.get(), messages);
1448 :
1449 0 : } catch (...) {
1450 0 : JAMI_ERROR("[call:{}] Failed to send SIP text message", getCallId());
1451 0 : }
1452 : } else {
1453 0 : pendingOutMessages_.emplace_back(messages, from);
1454 0 : JAMI_ERROR("[call:{}] sendTextMessage: no invite session for this call", getCallId());
1455 : }
1456 : }
1457 : }
1458 :
1459 : void
1460 412 : SIPCall::removeCall(int code)
1461 : {
1462 : #ifdef ENABLE_PLUGIN
1463 412 : jami::Manager::instance().getJamiPluginManager().getCallServicesManager().clearCallHandlerMaps(getCallId());
1464 : #endif
1465 412 : std::lock_guard lk {callMutex_};
1466 412 : JAMI_DEBUG("[call:{}] removeCall()", getCallId());
1467 412 : if (sdp_) {
1468 334 : sdp_->setActiveLocalSdpSession(nullptr);
1469 334 : sdp_->setActiveRemoteSdpSession(nullptr);
1470 : }
1471 412 : Call::removeCall(code);
1472 :
1473 : {
1474 412 : std::lock_guard lk(transportMtx_);
1475 412 : resetTransport(std::move(iceMedia_));
1476 412 : resetTransport(std::move(reinvIceMedia_));
1477 412 : }
1478 :
1479 412 : setInviteSession();
1480 412 : setSipTransport({});
1481 412 : }
1482 :
1483 : void
1484 102 : SIPCall::onFailure(int code)
1485 : {
1486 102 : if (setState(CallState::MERROR, ConnectionState::DISCONNECTED, code)) {
1487 101 : runOnMainThread([w = weak(), code] {
1488 101 : if (auto shared = w.lock()) {
1489 98 : auto& call = *shared;
1490 98 : Manager::instance().callFailure(call);
1491 98 : call.removeCall(code);
1492 101 : }
1493 101 : });
1494 : }
1495 102 : }
1496 :
1497 : void
1498 2 : SIPCall::onBusyHere()
1499 : {
1500 2 : if (getCallType() == CallType::OUTGOING)
1501 1 : setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED);
1502 : else
1503 1 : setState(CallState::BUSY, ConnectionState::DISCONNECTED);
1504 :
1505 2 : runOnMainThread([w = weak()] {
1506 2 : if (auto shared = w.lock()) {
1507 0 : auto& call = *shared;
1508 0 : Manager::instance().callBusy(call);
1509 0 : call.removeCall();
1510 2 : }
1511 2 : });
1512 2 : }
1513 :
1514 : void
1515 103 : SIPCall::onClosed()
1516 : {
1517 103 : runOnMainThread([w = weak()] {
1518 103 : if (auto shared = w.lock()) {
1519 101 : auto& call = *shared;
1520 101 : Manager::instance().peerHungupCall(call);
1521 101 : call.removeCall();
1522 103 : }
1523 103 : });
1524 103 : }
1525 :
1526 : void
1527 192 : SIPCall::onAnswered()
1528 : {
1529 192 : JAMI_WARNING("[call:{}] onAnswered()", getCallId());
1530 192 : runOnMainThread([w = weak()] {
1531 192 : if (auto shared = w.lock()) {
1532 192 : if (shared->getConnectionState() != ConnectionState::CONNECTED) {
1533 97 : shared->setState(CallState::ACTIVE, ConnectionState::CONNECTED);
1534 97 : if (not shared->isSubcall()) {
1535 19 : Manager::instance().peerAnsweredCall(*shared);
1536 : }
1537 : }
1538 192 : }
1539 192 : });
1540 192 : }
1541 :
1542 : void
1543 239 : SIPCall::sendKeyframe(int streamIdx)
1544 : {
1545 : #ifdef ENABLE_VIDEO
1546 239 : dht::ThreadPool::computation().run([w = weak(), streamIdx] {
1547 239 : if (auto sthis = w.lock()) {
1548 239 : JAMI_DEBUG("[call:{}] Handling picture fast update request", sthis->getCallId());
1549 239 : if (streamIdx == -1) {
1550 0 : for (const auto& videoRtp : sthis->getRtpSessionList(MediaType::MEDIA_VIDEO))
1551 0 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->forceKeyFrame();
1552 239 : } else if (streamIdx > -1 && streamIdx < static_cast<int>(sthis->rtpStreams_.size())) {
1553 : // Apply request for requested stream
1554 239 : auto& stream = sthis->rtpStreams_[streamIdx];
1555 239 : if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
1556 239 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->forceKeyFrame();
1557 : }
1558 239 : }
1559 239 : });
1560 : #endif
1561 239 : }
1562 :
1563 : bool
1564 1106 : SIPCall::isIceEnabled() const
1565 : {
1566 1106 : return enableIce_;
1567 : }
1568 :
1569 : void
1570 505 : SIPCall::setPeerUaVersion(std::string_view ua)
1571 : {
1572 505 : if (peerUserAgent_ == ua or ua.empty()) {
1573 : // Silently ignore if it did not change or empty.
1574 293 : return;
1575 : }
1576 :
1577 213 : if (peerUserAgent_.empty()) {
1578 213 : JAMI_DEBUG("[call:{}] Set peer's User-Agent to [{}]", getCallId(), ua);
1579 0 : } else if (not peerUserAgent_.empty()) {
1580 : // Unlikely, but should be handled since we don't have control over the peer.
1581 : // Even if it's unexpected, we still attempt to parse the UA version.
1582 0 : JAMI_WARNING("[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
1583 : getCallId(),
1584 : peerUserAgent_,
1585 : ua);
1586 : }
1587 :
1588 213 : peerUserAgent_ = ua;
1589 :
1590 : // User-agent parsing
1591 213 : constexpr std::string_view PACK_NAME(PACKAGE_NAME " ");
1592 213 : auto pos = ua.find(PACK_NAME);
1593 213 : if (pos == std::string_view::npos) {
1594 : // Must have the expected package name.
1595 1 : JAMI_WARNING("[call:{}] Unable to find the expected package name in peer's User-Agent", getCallId());
1596 1 : return;
1597 : }
1598 :
1599 212 : ua = ua.substr(pos + PACK_NAME.length());
1600 :
1601 212 : std::string_view version;
1602 : // Unstable (un-released) versions has a hyphen + commit ID after
1603 : // the version number. Find the commit ID if any, and ignore it.
1604 212 : pos = ua.find('-');
1605 212 : if (pos != std::string_view::npos) {
1606 : // Get the version and ignore the commit ID.
1607 0 : version = ua.substr(0, pos);
1608 : } else {
1609 : // Extract the version number.
1610 212 : pos = ua.find(' ');
1611 212 : if (pos != std::string_view::npos) {
1612 212 : version = ua.substr(0, pos);
1613 : }
1614 : }
1615 :
1616 212 : if (version.empty()) {
1617 0 : JAMI_DEBUG("[call:{}] Unable to parse peer's version", getCallId());
1618 0 : return;
1619 : }
1620 :
1621 212 : auto peerVersion = split_string_to_unsigned(version, '.');
1622 212 : if (peerVersion.size() > 4u) {
1623 0 : JAMI_WARNING("[call:{}] Unable to parse peer's version", getCallId());
1624 0 : return;
1625 : }
1626 :
1627 : // Check if peer's version is at least 10.0.2 to enable multi-stream.
1628 212 : peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion, MULTISTREAM_REQUIRED_VERSION);
1629 212 : if (not peerSupportMultiStream_) {
1630 0 : JAMI_DEBUG("Peer's version [{}] does not support multi-stream. "
1631 : "Min required version: [{}]",
1632 : version,
1633 : MULTISTREAM_REQUIRED_VERSION_STR);
1634 : }
1635 :
1636 : // Check if peer's version is at least 13.11.0 to enable multi-audio-stream.
1637 212 : peerSupportMultiAudioStream_ = Account::meetMinimumRequiredVersion(peerVersion, MULTIAUDIO_REQUIRED_VERSION);
1638 212 : if (not peerSupportMultiAudioStream_) {
1639 0 : JAMI_DEBUG("Peer's version [{}] does not support multi-audio-stream. "
1640 : "Min required version: [{}]",
1641 : version,
1642 : MULTIAUDIO_REQUIRED_VERSION_STR);
1643 : }
1644 :
1645 : // Check if peer's version is at least 13.3.0 to enable multi-ICE.
1646 212 : peerSupportMultiIce_ = Account::meetMinimumRequiredVersion(peerVersion, MULTIICE_REQUIRED_VERSION);
1647 212 : if (not peerSupportMultiIce_) {
1648 0 : JAMI_DEBUG("Peer's version [{}] does not support more than 2 ICE media streams. "
1649 : "Min required version: [{}]",
1650 : version,
1651 : MULTIICE_REQUIRED_VERSION_STR);
1652 : }
1653 :
1654 : // Check if peer's version supports re-invite without ICE renegotiation.
1655 212 : peerSupportReuseIceInReinv_ = Account::meetMinimumRequiredVersion(peerVersion,
1656 : REUSE_ICE_IN_REINVITE_REQUIRED_VERSION);
1657 212 : if (not peerSupportReuseIceInReinv_) {
1658 0 : JAMI_LOG("Peer's version [{:s}] does not support re-invite without ICE renegotiation. "
1659 : "Min required version: [{:s}]",
1660 : version,
1661 : REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR);
1662 : }
1663 212 : }
1664 :
1665 : void
1666 314 : SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
1667 : {
1668 314 : std::lock_guard lock {callMutex_};
1669 314 : peerAllowedMethods_ = std::move(methods);
1670 314 : }
1671 :
1672 : bool
1673 244 : SIPCall::isSipMethodAllowedByPeer(const std::string_view method) const
1674 : {
1675 244 : std::lock_guard lock {callMutex_};
1676 :
1677 486 : return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method) != peerAllowedMethods_.end();
1678 244 : }
1679 :
1680 : void
1681 213 : SIPCall::onPeerRinging()
1682 : {
1683 213 : JAMI_DEBUG("[call:{}] Peer ringing", getCallId());
1684 213 : setState(ConnectionState::RINGING);
1685 213 : }
1686 :
1687 : void
1688 243 : SIPCall::addLocalIceAttributes()
1689 : {
1690 243 : if (not isIceEnabled())
1691 2 : return;
1692 :
1693 241 : auto iceMedia = getIceMedia();
1694 :
1695 241 : if (not iceMedia) {
1696 0 : JAMI_ERROR("[call:{}] Invalid ICE instance", getCallId());
1697 0 : return;
1698 : }
1699 :
1700 241 : auto start = std::chrono::steady_clock::now();
1701 :
1702 241 : if (not iceMedia->isInitialized()) {
1703 142 : JAMI_DEBUG("[call:{}] Waiting for ICE initialization", getCallId());
1704 : // we need an initialized ICE to progress further
1705 142 : if (iceMedia->waitForInitialization(DEFAULT_ICE_INIT_TIMEOUT) <= 0) {
1706 0 : JAMI_ERROR("[call:{}] ICE initialization timed out", getCallId());
1707 0 : return;
1708 : }
1709 : // ICE initialization may take longer than usual in some cases,
1710 : // for instance when TURN servers do not respond in time (DNS
1711 : // resolution or other issues).
1712 142 : auto duration = std::chrono::steady_clock::now() - start;
1713 142 : if (duration > EXPECTED_ICE_INIT_MAX_TIME) {
1714 0 : JAMI_WARNING("[call:{:s}] ICE initialization time was unexpectedly high ({})",
1715 : getCallId(),
1716 : std::chrono::duration_cast<std::chrono::milliseconds>(duration));
1717 : }
1718 : }
1719 :
1720 : // Check the state of ICE instance, the initialization may have failed.
1721 241 : if (not iceMedia->isInitialized()) {
1722 0 : JAMI_ERROR("[call:{}] ICE session is uninitialized", getCallId());
1723 0 : return;
1724 : }
1725 :
1726 : // Check the state, the call might have been canceled while waiting.
1727 : // for initialization.
1728 241 : if (getState() == Call::CallState::OVER) {
1729 0 : JAMI_WARNING("[call:{}] The call was terminated while waiting for ICE initialization", getCallId());
1730 0 : return;
1731 : }
1732 :
1733 241 : auto account = getSIPAccount();
1734 241 : if (not account) {
1735 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
1736 0 : return;
1737 : }
1738 241 : if (not sdp_) {
1739 0 : JAMI_ERROR("[call:{}] No sdp detected", getCallId());
1740 0 : return;
1741 : }
1742 :
1743 241 : JAMI_DEBUG("[call:{}] Add local attributes for ICE instance [{}]", getCallId(), fmt::ptr(iceMedia.get()));
1744 :
1745 241 : sdp_->addIceAttributes(iceMedia->getLocalAttributes());
1746 :
1747 241 : if (account->isIceCompIdRfc5245Compliant()) {
1748 2 : unsigned streamIdx = 0;
1749 6 : for (auto const& stream : rtpStreams_) {
1750 4 : if (not stream.mediaAttribute_->enabled_) {
1751 : // Dont add ICE candidates if the media is disabled
1752 0 : JAMI_DEBUG("[call:{}] Media [{}] @ {} is disabled, don't add local candidates",
1753 : getCallId(),
1754 : stream.mediaAttribute_->toString(),
1755 : streamIdx);
1756 0 : continue;
1757 : }
1758 4 : JAMI_DEBUG("[call:{}] Add ICE local candidates for media [{}] @ {}",
1759 : getCallId(),
1760 : stream.mediaAttribute_->toString(),
1761 : streamIdx);
1762 : // RTP
1763 4 : sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx, ICE_COMP_ID_RTP));
1764 : // RTCP if it has its own port
1765 4 : if (not rtcpMuxEnabled_) {
1766 4 : sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx, ICE_COMP_ID_RTP + 1));
1767 : }
1768 :
1769 4 : streamIdx++;
1770 : }
1771 : } else {
1772 239 : unsigned idx = 0;
1773 239 : unsigned compId = 1;
1774 676 : for (auto const& stream : rtpStreams_) {
1775 437 : if (not stream.mediaAttribute_->enabled_) {
1776 : // Skipping local ICE candidates if the media is disabled
1777 3 : continue;
1778 : }
1779 434 : JAMI_DEBUG("[call:{}] Add ICE local candidates for media [{}] @ {}",
1780 : getCallId(),
1781 : stream.mediaAttribute_->toString(),
1782 : idx);
1783 : // RTP
1784 434 : sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1785 434 : compId++;
1786 :
1787 : // RTCP if it has its own port
1788 434 : if (not rtcpMuxEnabled_) {
1789 434 : sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1790 434 : compId++;
1791 : }
1792 :
1793 434 : idx++;
1794 : }
1795 : }
1796 241 : }
1797 :
1798 : std::vector<IceCandidate>
1799 227 : SIPCall::getAllRemoteCandidates(dhtnet::IceTransport& transport) const
1800 : {
1801 227 : std::vector<IceCandidate> rem_candidates;
1802 644 : for (unsigned mediaIdx = 0; mediaIdx < static_cast<unsigned>(rtpStreams_.size()); mediaIdx++) {
1803 : IceCandidate cand;
1804 5373 : for (auto& line : sdp_->getIceCandidates(mediaIdx)) {
1805 4956 : if (transport.parseIceAttributeLine(mediaIdx, line, cand)) {
1806 4956 : JAMI_DEBUG("[call:{}] Add remote ICE candidate: {}", getCallId(), line);
1807 4956 : rem_candidates.emplace_back(std::move(cand));
1808 : }
1809 417 : }
1810 : }
1811 227 : return rem_candidates;
1812 0 : }
1813 :
1814 : std::shared_ptr<SystemCodecInfo>
1815 197 : SIPCall::getVideoCodec() const
1816 : {
1817 : #ifdef ENABLE_VIDEO
1818 : // Return first video codec as we negotiate only one codec for the call
1819 : // Note: with multistream we can negotiate codecs/stream, but it's not the case
1820 : // in practice (same for audio), so just return the first video codec.
1821 197 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
1822 197 : return videoRtp->getCodec();
1823 : #endif
1824 35 : return {};
1825 : }
1826 :
1827 : std::shared_ptr<SystemCodecInfo>
1828 0 : SIPCall::getAudioCodec() const
1829 : {
1830 : // Return first video codec as we negotiate only one codec for the call
1831 0 : for (const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
1832 0 : return audioRtp->getCodec();
1833 0 : return {};
1834 : }
1835 :
1836 : void
1837 712 : SIPCall::addMediaStream(const MediaAttribute& mediaAttr)
1838 : {
1839 : // Create and add the media stream with the provided attribute.
1840 : // Do not create the RTP sessions yet.
1841 712 : RtpStream stream;
1842 712 : stream.mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttr);
1843 :
1844 : // Set default media source if empty. Kept for backward compatibility.
1845 : #ifdef ENABLE_VIDEO
1846 712 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO && stream.mediaAttribute_->sourceUri_.empty()) {
1847 301 : if (auto* videoManager = Manager::instance().getVideoManager()) {
1848 301 : stream.mediaAttribute_->sourceUri_ = videoManager->videoDeviceMonitor.getMRLForDefaultDevice();
1849 : }
1850 : }
1851 : #endif
1852 :
1853 712 : rtpStreams_.emplace_back(std::move(stream));
1854 712 : }
1855 :
1856 : size_t
1857 395 : SIPCall::initMediaStreams(const std::vector<MediaAttribute>& mediaAttrList)
1858 : {
1859 1097 : for (size_t idx = 0; idx < mediaAttrList.size(); idx++) {
1860 702 : auto const& mediaAttr = mediaAttrList.at(idx);
1861 702 : if (mediaAttr.type_ != MEDIA_AUDIO && mediaAttr.type_ != MEDIA_VIDEO) {
1862 0 : JAMI_ERROR("[call:{}] Unexpected media type {}", getCallId(), static_cast<int>(mediaAttr.type_));
1863 0 : assert(false);
1864 : }
1865 :
1866 702 : addMediaStream(mediaAttr);
1867 702 : auto& stream = rtpStreams_.back();
1868 : try {
1869 702 : createRtpSession(stream);
1870 702 : JAMI_DEBUG("[call:{:s}] Added media @{:d}: {:s}", getCallId(), idx, stream.mediaAttribute_->toString(true));
1871 0 : } catch (const std::exception& e) {
1872 0 : JAMI_ERROR("[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
1873 : getCallId(),
1874 : idx,
1875 : e.what());
1876 0 : rtpStreams_.pop_back();
1877 0 : }
1878 : }
1879 :
1880 395 : JAMI_DEBUG("[call:{:s}] Created {:d} media stream(s)", getCallId(), rtpStreams_.size());
1881 :
1882 395 : return rtpStreams_.size();
1883 : }
1884 :
1885 : bool
1886 1516 : SIPCall::hasVideo() const
1887 : {
1888 : #ifdef ENABLE_VIDEO
1889 4317 : std::function<bool(const RtpStream& stream)> videoCheck = [](auto const& stream) {
1890 2801 : bool validVideo = stream.mediaAttribute_ && stream.mediaAttribute_->hasValidVideo();
1891 2801 : bool validRemoteVideo = stream.remoteMediaAttribute_ && stream.remoteMediaAttribute_->hasValidVideo();
1892 2801 : return validVideo || validRemoteVideo;
1893 1516 : };
1894 :
1895 1516 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
1896 :
1897 3032 : return iter != rtpStreams_.end();
1898 : #else
1899 : return false;
1900 : #endif
1901 1516 : }
1902 :
1903 : bool
1904 752 : SIPCall::isCaptureDeviceMuted(const MediaType& mediaType) const
1905 : {
1906 : // Return true only if all media of type 'mediaType' that use capture devices
1907 : // source, are muted.
1908 2555 : std::function<bool(const RtpStream& stream)> mutedCheck = [&mediaType](auto const& stream) {
1909 1051 : return (stream.mediaAttribute_->type_ == mediaType and not stream.mediaAttribute_->muted_);
1910 752 : };
1911 752 : const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), mutedCheck);
1912 1504 : return iter == rtpStreams_.end();
1913 752 : }
1914 :
1915 : void
1916 193 : SIPCall::setupNegotiatedMedia()
1917 : {
1918 193 : JAMI_DEBUG("[call:{}] Updating negotiated media", getCallId());
1919 :
1920 193 : if (not sipTransport_ or not sdp_) {
1921 0 : JAMI_ERROR("[call:{}] Call is in an invalid state", getCallId());
1922 0 : return;
1923 : }
1924 :
1925 193 : auto slots = sdp_->getMediaSlots();
1926 193 : bool peer_hold {true};
1927 193 : int streamIdx = -1;
1928 :
1929 551 : for (const auto& slot : slots) {
1930 358 : streamIdx++;
1931 358 : const auto& local = slot.first;
1932 358 : const auto& remote = slot.second;
1933 :
1934 : // Skip disabled media
1935 358 : if (not local.enabled) {
1936 4 : JAMI_DEBUG("[call:{}] [SDP:slot#{}] The media is disabled, skipping", getCallId(), streamIdx);
1937 4 : continue;
1938 : }
1939 :
1940 354 : if (static_cast<size_t>(streamIdx) >= rtpStreams_.size()) {
1941 0 : throw std::runtime_error("Stream index is out-of-range");
1942 : }
1943 :
1944 354 : auto const& rtpStream = rtpStreams_[streamIdx];
1945 :
1946 354 : if (not rtpStream.mediaAttribute_) {
1947 0 : throw std::runtime_error("Missing media attribute");
1948 : }
1949 :
1950 : // To enable a media, it must be enabled on both sides.
1951 354 : rtpStream.mediaAttribute_->enabled_ = local.enabled and remote.enabled;
1952 :
1953 354 : if (not rtpStream.rtpSession_)
1954 0 : throw std::runtime_error("Must have a valid RTP session");
1955 :
1956 354 : if (local.type != MEDIA_AUDIO && local.type != MEDIA_VIDEO) {
1957 0 : JAMI_ERROR("[call:{}] Unexpected media type {}", getCallId(), static_cast<int>(local.type));
1958 0 : throw std::runtime_error("Invalid media attribute");
1959 : }
1960 :
1961 354 : if (local.type != remote.type) {
1962 0 : JAMI_ERROR("[call:{}] [SDP:slot#{}] Inconsistent media type between local and remote",
1963 : getCallId(),
1964 : streamIdx);
1965 0 : continue;
1966 : }
1967 :
1968 354 : if (local.enabled and not local.codec) {
1969 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Missing local codec", getCallId(), streamIdx);
1970 0 : continue;
1971 : }
1972 :
1973 354 : if (remote.enabled and not remote.codec) {
1974 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Missing remote codec", getCallId(), streamIdx);
1975 0 : continue;
1976 : }
1977 :
1978 354 : if (isSrtpEnabled() and local.enabled and not local.crypto) {
1979 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Secure mode but no local crypto attributes. "
1980 : "Ignoring the media",
1981 : getCallId(),
1982 : streamIdx);
1983 0 : continue;
1984 : }
1985 :
1986 354 : if (isSrtpEnabled() and remote.enabled and not remote.crypto) {
1987 0 : JAMI_WARNING("[call:{}] [SDP:slot#{}] Secure mode but no crypto remote attributes. "
1988 : "Ignoring the media",
1989 : getCallId(),
1990 : streamIdx);
1991 0 : continue;
1992 : }
1993 :
1994 : // Aggregate hold info over all remote streams
1995 354 : peer_hold &= remote.hold;
1996 :
1997 354 : configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote);
1998 : }
1999 :
2000 : // TODO. Do we really use this?
2001 193 : if (not isSubcall() and peerHold_ != peer_hold) {
2002 0 : peerHold_ = peer_hold;
2003 0 : emitSignal<libjami::CallSignal::PeerHold>(getCallId(), peerHold_);
2004 : }
2005 193 : }
2006 :
2007 : void
2008 193 : SIPCall::startAllMedia()
2009 : {
2010 193 : JAMI_DEBUG("[call:{}] Starting all media", getCallId());
2011 :
2012 193 : if (not sipTransport_ or not sdp_) {
2013 0 : JAMI_ERROR("[call:{}] The call is in invalid state", getCallId());
2014 0 : return;
2015 : }
2016 :
2017 193 : if (isSrtpEnabled() && not sipTransport_->isSecure()) {
2018 18 : JAMI_WARNING("[call:{}] Crypto (SRTP) is negotiated over an insecure signaling transport", getCallId());
2019 : }
2020 :
2021 : // reset
2022 193 : readyToRecord_ = false;
2023 :
2024 : // Not restarting media loop on hold as it's a huge waste of CPU resources
2025 193 : if (getState() != CallState::HOLD) {
2026 193 : bool iceRunning = isIceRunning();
2027 193 : auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession());
2028 193 : size_t idx = 0;
2029 551 : for (auto& rtpStream : rtpStreams_) {
2030 358 : if (not rtpStream.mediaAttribute_) {
2031 0 : throw std::runtime_error("Missing media attribute");
2032 : }
2033 358 : if (idx >= remoteMediaList.size()) {
2034 0 : JAMI_ERROR("[call:{}] Remote media list smaller than streams (idx={}, size={})",
2035 : getCallId(),
2036 : idx,
2037 : remoteMediaList.size());
2038 0 : break;
2039 : }
2040 358 : rtpStream.remoteMediaAttribute_ = std::make_shared<MediaAttribute>(remoteMediaList[idx]);
2041 358 : if (not rtpStream.mediaAttribute_->enabled_) {
2042 2 : JAMI_DEBUG("[call:{}] Skipping start for disabled stream @{}", getCallId(), idx);
2043 2 : idx++;
2044 2 : continue;
2045 : }
2046 356 : if (rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
2047 159 : rtpStream.rtpSession_->setMuted(rtpStream.remoteMediaAttribute_->muted_, RtpSession::Direction::RECV);
2048 : }
2049 712 : dht::ThreadPool::io().run(
2050 1068 : [w = weak(),
2051 : idx,
2052 356 : isVideo = rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO,
2053 : iceRunning,
2054 356 : rtpSession = rtpStream.rtpSession_,
2055 : rtpSocketPair
2056 : = std::make_shared<std::pair<std::unique_ptr<dhtnet::IceSocket>, std::unique_ptr<dhtnet::IceSocket>>>(
2057 356 : std::move(rtpStream.rtpSocket_), std::move(rtpStream.rtcpSocket_))]() mutable {
2058 : try {
2059 356 : if (iceRunning) {
2060 350 : rtpSession->start(std::move(rtpSocketPair->first), std::move(rtpSocketPair->second));
2061 : } else {
2062 6 : rtpSession->start(nullptr, nullptr);
2063 : }
2064 356 : if (isVideo) {
2065 159 : if (auto call = w.lock())
2066 159 : call->requestKeyframe(static_cast<int>(idx));
2067 : }
2068 : #ifdef ENABLE_PLUGIN
2069 356 : if (auto call = w.lock()) {
2070 : // Create AVStreams associated with the call
2071 356 : call->createCallAVStreams();
2072 356 : }
2073 : #endif
2074 0 : } catch (const std::exception& e) {
2075 0 : JAMI_ERROR("[call:{}] Failed to start RTP session {}: {}",
2076 : w.lock() ? w.lock()->getCallId() : "unknown",
2077 : idx,
2078 : e.what());
2079 0 : }
2080 356 : });
2081 356 : idx++;
2082 : }
2083 193 : }
2084 :
2085 : // Media is restarted, we can process the last hold request.
2086 193 : isWaitingForIceAndMedia_ = false;
2087 193 : if (remainingRequest_ != Request::NoRequest) {
2088 0 : bool result = true;
2089 0 : switch (remainingRequest_) {
2090 0 : case Request::Hold:
2091 0 : result = hold();
2092 0 : if (holdCb_) {
2093 0 : holdCb_(result);
2094 0 : holdCb_ = nullptr;
2095 : }
2096 0 : break;
2097 0 : case Request::Resume:
2098 0 : result = resume();
2099 0 : if (resumeCb_) {
2100 0 : resumeCb_(result);
2101 0 : resumeCb_ = nullptr;
2102 : }
2103 0 : break;
2104 0 : case Request::SwitchInput:
2105 0 : SIPSessionReinvite();
2106 0 : break;
2107 0 : default:
2108 0 : break;
2109 : }
2110 0 : remainingRequest_ = Request::NoRequest;
2111 : }
2112 :
2113 193 : mediaRestartRequired_ = false;
2114 : }
2115 :
2116 : void
2117 0 : SIPCall::restartMediaSender()
2118 : {
2119 0 : JAMI_DEBUG("[call:{}] Restarting TX media streams", getCallId());
2120 0 : for (const auto& rtpSession : getRtpSessionList())
2121 0 : rtpSession->restartSender();
2122 0 : }
2123 :
2124 : void
2125 450 : SIPCall::stopAllMedia()
2126 : {
2127 450 : JAMI_DEBUG("[call:{}] Stopping all media", getCallId());
2128 :
2129 : #ifdef ENABLE_VIDEO
2130 : {
2131 450 : std::lock_guard lk(sinksMtx_);
2132 450 : for (auto it = callSinksMap_.begin(); it != callSinksMap_.end();) {
2133 0 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
2134 0 : auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
2135 0 : if (videoReceive) {
2136 0 : auto& sink = videoReceive->getSink();
2137 0 : sink->detach(it->second.get());
2138 : }
2139 0 : }
2140 0 : it->second->stop();
2141 0 : it = callSinksMap_.erase(it);
2142 : }
2143 450 : }
2144 : #endif
2145 : // Stop all RTP sessions in parallel
2146 450 : std::mutex mtx;
2147 450 : std::condition_variable cv;
2148 450 : unsigned int stoppedCount = 0;
2149 450 : unsigned int totalStreams = rtpStreams_.size();
2150 :
2151 1277 : for (const auto& rtpSession : getRtpSessionList()) {
2152 827 : dht::ThreadPool::io().run([&, rtpSession]() {
2153 : try {
2154 826 : rtpSession->stop();
2155 0 : } catch (const std::exception& e) {
2156 0 : JAMI_ERROR("Failed to stop RTP session: {}", e.what());
2157 0 : }
2158 :
2159 826 : std::lock_guard lk(mtx);
2160 827 : stoppedCount++;
2161 827 : cv.notify_one();
2162 827 : });
2163 449 : }
2164 :
2165 : // Wait for all streams to be stopped
2166 450 : std::unique_lock lk(mtx);
2167 1714 : cv.wait(lk, [&] { return stoppedCount == totalStreams; });
2168 :
2169 : #ifdef ENABLE_PLUGIN
2170 : {
2171 450 : clearCallAVStreams();
2172 450 : std::lock_guard lk(avStreamsMtx_);
2173 450 : Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(getCallId());
2174 450 : }
2175 : #endif
2176 449 : }
2177 :
2178 : void
2179 4 : SIPCall::muteMedia(const std::string& mediaType, bool mute)
2180 : {
2181 4 : auto type = MediaAttribute::stringToMediaType(mediaType);
2182 :
2183 4 : if (type == MediaType::MEDIA_AUDIO) {
2184 2 : JAMI_WARNING("[call:{}] {} all audio media", getCallId(), mute ? "muting " : "unmuting ");
2185 :
2186 2 : } else if (type == MediaType::MEDIA_VIDEO) {
2187 2 : JAMI_WARNING("[call:{}] {} all video media", getCallId(), mute ? "muting" : "unmuting");
2188 : } else {
2189 0 : JAMI_ERROR("[call:{}] Invalid media type {}", getCallId(), mediaType);
2190 0 : assert(false);
2191 : }
2192 :
2193 : // Get the current media attributes.
2194 4 : auto mediaList = getMediaAttributeList();
2195 :
2196 : // Mute/Unmute all medias with matching type.
2197 12 : for (auto& mediaAttr : mediaList) {
2198 8 : if (mediaAttr.type_ == type) {
2199 4 : mediaAttr.muted_ = mute;
2200 : }
2201 : }
2202 :
2203 : // Apply
2204 4 : requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
2205 4 : }
2206 :
2207 : void
2208 379 : SIPCall::updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx)
2209 : {
2210 379 : assert(streamIdx < rtpStreams_.size());
2211 :
2212 379 : auto const& rtpStream = rtpStreams_[streamIdx];
2213 379 : assert(rtpStream.rtpSession_);
2214 :
2215 379 : auto const& mediaAttr = rtpStream.mediaAttribute_;
2216 379 : assert(mediaAttr);
2217 :
2218 379 : bool notifyMute = false;
2219 :
2220 379 : if (newMediaAttr.muted_ == mediaAttr->muted_) {
2221 : // Nothing to do. Already in the desired state.
2222 366 : JAMI_DEBUG("[call:{}] [{}] already {}",
2223 : getCallId(),
2224 : mediaAttr->label_,
2225 : mediaAttr->muted_ ? "muted " : "unmuted ");
2226 :
2227 : } else {
2228 : // Update
2229 13 : mediaAttr->muted_ = newMediaAttr.muted_;
2230 13 : notifyMute = true;
2231 13 : JAMI_DEBUG("[call:{}] {} [{}]", getCallId(), mediaAttr->muted_ ? "muting" : "unmuting", mediaAttr->label_);
2232 : }
2233 :
2234 : // Only update source and type if actually set.
2235 379 : if (not newMediaAttr.sourceUri_.empty())
2236 13 : mediaAttr->sourceUri_ = newMediaAttr.sourceUri_;
2237 :
2238 379 : if (notifyMute and mediaAttr->type_ == MediaType::MEDIA_AUDIO) {
2239 5 : rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_);
2240 5 : rtpStream.rtpSession_->setMuted(mediaAttr->muted_);
2241 5 : sendMuteState(mediaAttr->muted_);
2242 5 : if (not isSubcall())
2243 5 : emitSignal<libjami::CallSignal::AudioMuted>(getCallId(), mediaAttr->muted_);
2244 5 : return;
2245 : }
2246 :
2247 : #ifdef ENABLE_VIDEO
2248 374 : if (notifyMute and mediaAttr->type_ == MediaType::MEDIA_VIDEO) {
2249 8 : rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_);
2250 8 : rtpStream.rtpSession_->setMuted(mediaAttr->muted_);
2251 :
2252 8 : if (not isSubcall())
2253 8 : emitSignal<libjami::CallSignal::VideoMuted>(getCallId(), mediaAttr->muted_);
2254 : }
2255 : #endif
2256 : }
2257 :
2258 : bool
2259 114 : SIPCall::updateAllMediaStreams(const std::vector<MediaAttribute>& mediaAttrList, bool isRemote)
2260 : {
2261 114 : JAMI_DEBUG("[call:{}] New local media", getCallId());
2262 :
2263 114 : if (mediaAttrList.size() > PJ_ICE_MAX_COMP / 2) {
2264 0 : JAMI_DEBUG("[call:{:s}] Too many media streams, limit it ({:d} vs {:d})",
2265 : getCallId().c_str(),
2266 : mediaAttrList.size(),
2267 : PJ_ICE_MAX_COMP);
2268 0 : return false;
2269 : }
2270 :
2271 114 : unsigned idx = 0;
2272 330 : for (auto const& newMediaAttr : mediaAttrList) {
2273 216 : JAMI_DEBUG("[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.toString(true));
2274 : }
2275 :
2276 114 : JAMI_DEBUG("[call:{}] Updating local media streams", getCallId());
2277 :
2278 330 : for (auto const& newAttr : mediaAttrList) {
2279 216 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2280 :
2281 216 : if (streamIdx < 0) {
2282 : // Media does not exist, add a new one.
2283 10 : addMediaStream(newAttr);
2284 10 : auto& stream = rtpStreams_.back();
2285 : // If the remote asks for a new stream, our side sends nothing
2286 10 : stream.mediaAttribute_->muted_ = isRemote ? true : stream.mediaAttribute_->muted_;
2287 : try {
2288 10 : createRtpSession(stream);
2289 10 : JAMI_DEBUG("[call:{:s}] Added a new media stream @{:d}: {:s}",
2290 : getCallId(),
2291 : idx,
2292 : stream.mediaAttribute_->toString(true));
2293 0 : } catch (const std::exception& e) {
2294 0 : JAMI_ERROR("[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
2295 : getCallId(),
2296 : idx,
2297 : e.what());
2298 0 : rtpStreams_.pop_back();
2299 0 : }
2300 : } else {
2301 206 : updateMediaStream(newAttr, streamIdx);
2302 : }
2303 : }
2304 :
2305 : // If the new media list is smaller than the number of existing streams, that means some streams have been removed
2306 : // We need to clean them up
2307 114 : if (mediaAttrList.size() < rtpStreams_.size()) {
2308 : #ifdef ENABLE_VIDEO
2309 4 : for (auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2310 : // Clean up video streams that are absent from the new media list
2311 2 : auto& stream = rtpStreams_[i];
2312 2 : if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
2313 2 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->exitConference();
2314 : }
2315 : #endif
2316 4 : for (auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2317 : // Clean up audio streams that are absent from the new media list
2318 2 : auto& stream = rtpStreams_[i];
2319 2 : if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO) {
2320 0 : JAMI_WARNING("[call:{}] Audio stream {} absent from new media list, stopping RTP session",
2321 : getCallId(),
2322 : stream.rtpSession_->streamId());
2323 0 : std::static_pointer_cast<AudioRtpSession>(stream.rtpSession_)->stop();
2324 : }
2325 : }
2326 2 : rtpStreams_.resize(mediaAttrList.size());
2327 : }
2328 114 : return true;
2329 : }
2330 :
2331 : bool
2332 88 : SIPCall::isReinviteRequired(const std::vector<MediaAttribute>& mediaAttrList)
2333 : {
2334 88 : if (mediaAttrList.size() != rtpStreams_.size())
2335 6 : return true;
2336 :
2337 224 : for (auto const& newAttr : mediaAttrList) {
2338 151 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2339 :
2340 151 : if (streamIdx < 0) {
2341 : // Always needs a re-invite when a new media is added.
2342 0 : return true;
2343 : }
2344 :
2345 : // Changing the source needs a re-invite
2346 151 : if (newAttr.sourceUri_ != rtpStreams_[streamIdx].mediaAttribute_->sourceUri_) {
2347 2 : return true;
2348 : }
2349 :
2350 : #ifdef ENABLE_VIDEO
2351 149 : if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2352 : // For now, only video mute triggers a re-invite.
2353 : // Might be done for audio as well if required.
2354 67 : if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) {
2355 7 : return true;
2356 : }
2357 : }
2358 : #endif
2359 : }
2360 :
2361 73 : return false;
2362 : }
2363 :
2364 : bool
2365 94 : SIPCall::isNewIceMediaRequired(const std::vector<MediaAttribute>& mediaAttrList)
2366 : {
2367 : // Always needs a new ICE media if the peer does not support
2368 : // re-invite without ICE renegotiation
2369 94 : if (not peerSupportReuseIceInReinv_)
2370 0 : return true;
2371 :
2372 : // Always needs a new ICE media when the number of media changes.
2373 94 : if (mediaAttrList.size() != rtpStreams_.size())
2374 6 : return true;
2375 :
2376 241 : for (auto const& newAttr : mediaAttrList) {
2377 162 : auto streamIdx = findRtpStreamIndex(newAttr.label_);
2378 162 : if (streamIdx < 0) {
2379 : // Always needs a new ICE media when a media is added or replaced.
2380 0 : return true;
2381 : }
2382 162 : auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_;
2383 162 : if (newAttr.sourceUri_ != currAttr->sourceUri_) {
2384 : // For now, media will be restarted if the source changes.
2385 : // TODO. This should not be needed if the decoder/receiver
2386 : // correctly handles dynamic media properties changes.
2387 2 : return true;
2388 : }
2389 :
2390 : #ifdef ENABLE_VIDEO
2391 160 : if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2392 : // Video mute/unmute changes trigger a reinvite, and reinvites always clear ICE.
2393 : // Therefore, we need recreate ICE transport.
2394 72 : if (newAttr.muted_ != currAttr->muted_) {
2395 7 : return true;
2396 : }
2397 : }
2398 : #endif
2399 : }
2400 :
2401 79 : return false;
2402 : }
2403 :
2404 : bool
2405 88 : SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
2406 : {
2407 88 : std::lock_guard lk {callMutex_};
2408 88 : auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
2409 88 : bool hasFileSharing {false};
2410 :
2411 253 : for (const auto& media : mediaAttrList) {
2412 165 : if (!media.enabled_ || media.sourceUri_.empty())
2413 162 : continue;
2414 :
2415 : // Supported MRL schemes
2416 10 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
2417 :
2418 10 : const auto pos = media.sourceUri_.find(sep);
2419 10 : if (pos == std::string::npos)
2420 7 : continue;
2421 :
2422 3 : const auto prefix = media.sourceUri_.substr(0, pos);
2423 3 : if ((pos + sep.size()) >= media.sourceUri_.size())
2424 0 : continue;
2425 :
2426 3 : if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
2427 3 : hasFileSharing = true;
2428 3 : mediaPlayerId_ = media.sourceUri_;
2429 : #ifdef ENABLE_VIDEO
2430 3 : createMediaPlayer(mediaPlayerId_);
2431 : #endif
2432 : }
2433 3 : }
2434 :
2435 88 : if (!hasFileSharing) {
2436 : #ifdef ENABLE_VIDEO
2437 85 : closeMediaPlayer(mediaPlayerId_);
2438 : #endif
2439 85 : mediaPlayerId_ = "";
2440 : }
2441 :
2442 : // Disable video if disabled in the account.
2443 88 : auto account = getSIPAccount();
2444 88 : if (not account) {
2445 0 : JAMI_ERROR("[call:{}] No account detected", getCallId());
2446 0 : return false;
2447 : }
2448 88 : if (not account->isVideoEnabled()) {
2449 0 : for (auto& mediaAttr : mediaAttrList) {
2450 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
2451 : // This an API misuse. The new medialist should not contain video
2452 : // if it was disabled in the account settings.
2453 0 : JAMI_ERROR("[call:{}] New media has video, but it's disabled in the account. "
2454 : "Ignoring the change request!",
2455 : getCallId());
2456 0 : return false;
2457 : }
2458 : }
2459 : }
2460 :
2461 : // If the peer does not support multi-stream and the size of the new
2462 : // media list is different from the current media list, the media
2463 : // change request will be ignored.
2464 88 : if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
2465 0 : JAMI_WARNING("[call:{}] Peer does not support multi-stream. Media change request ignored", getCallId());
2466 0 : return false;
2467 : }
2468 :
2469 : // If the peer does not support multi-audio-stream and the new
2470 : // media list has more than one audio. Ignore the one that comes from a file.
2471 88 : if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) {
2472 0 : JAMI_WARNING("[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored", getCallId());
2473 0 : for (auto it = mediaAttrList.begin(); it != mediaAttrList.end();) {
2474 0 : if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) {
2475 0 : it = mediaAttrList.erase(it);
2476 0 : continue;
2477 : }
2478 0 : ++it;
2479 : }
2480 : }
2481 :
2482 : // If peer doesn't support multiple ice, keep only the last audio/video
2483 : // This keep the old behaviour (if sharing both camera + sharing a file, will keep the shared file)
2484 88 : if (!peerSupportMultiIce_) {
2485 0 : if (mediaList.size() > 2)
2486 0 : JAMI_WARNING("[call:{}] Peer does not support more than 2 ICE medias. "
2487 : "Media change request modified",
2488 : getCallId());
2489 0 : MediaAttribute audioAttr(MediaType::MEDIA_AUDIO);
2490 0 : MediaAttribute videoAttr;
2491 0 : auto hasVideo = false, hasAudio = false;
2492 0 : for (auto it = mediaAttrList.rbegin(); it != mediaAttrList.rend(); ++it) {
2493 0 : if (it->type_ == MediaType::MEDIA_VIDEO && !hasVideo) {
2494 0 : videoAttr = *it;
2495 0 : videoAttr.label_ = sip_utils::DEFAULT_VIDEO_STREAMID;
2496 0 : hasVideo = true;
2497 0 : } else if (it->type_ == MediaType::MEDIA_AUDIO && !hasAudio) {
2498 0 : audioAttr = *it;
2499 0 : audioAttr.label_ = sip_utils::DEFAULT_AUDIO_STREAMID;
2500 0 : hasAudio = true;
2501 : }
2502 0 : if (hasVideo && hasAudio)
2503 0 : break;
2504 : }
2505 0 : mediaAttrList.clear();
2506 : // Note: use the order VIDEO/AUDIO to avoid reinvite.
2507 : // Note: always add at least one media for valid SDP (RFC4566)
2508 0 : mediaAttrList.emplace_back(audioAttr);
2509 0 : if (hasVideo)
2510 0 : mediaAttrList.emplace_back(videoAttr);
2511 0 : }
2512 :
2513 88 : if (mediaAttrList.empty()) {
2514 0 : JAMI_ERROR("[call:{}] Invalid media change request: new media list is empty", getCallId());
2515 0 : return false;
2516 : }
2517 88 : JAMI_DEBUG("[call:{}] Requesting media change. List of new media:", getCallId());
2518 :
2519 88 : unsigned idx = 0;
2520 253 : for (auto const& newMediaAttr : mediaAttrList) {
2521 165 : JAMI_DEBUG("[call:{}] Media @{:d}: {}", getCallId(), idx++, newMediaAttr.toString(true));
2522 : }
2523 :
2524 88 : auto needReinvite = isReinviteRequired(mediaAttrList);
2525 88 : auto needNewIce = isNewIceMediaRequired(mediaAttrList);
2526 :
2527 88 : if (!updateAllMediaStreams(mediaAttrList, false))
2528 0 : return false;
2529 :
2530 88 : if (needReinvite) {
2531 15 : JAMI_DEBUG("[call:{}] Media change requires a new negotiation (re-invite)", getCallId());
2532 15 : requestReinvite(mediaAttrList, needNewIce);
2533 : } else {
2534 73 : JAMI_DEBUG("[call:{}] Media change DOES NOT require a new negotiation (re-invite)", getCallId());
2535 73 : reportMediaNegotiationStatus();
2536 : }
2537 :
2538 88 : return true;
2539 88 : }
2540 :
2541 : std::vector<std::map<std::string, std::string>>
2542 274 : SIPCall::currentMediaList() const
2543 : {
2544 274 : return MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList());
2545 : }
2546 :
2547 : std::vector<MediaAttribute>
2548 1867 : SIPCall::getMediaAttributeList() const
2549 : {
2550 1867 : std::lock_guard lk {callMutex_};
2551 1867 : std::vector<MediaAttribute> mediaList;
2552 1867 : mediaList.reserve(rtpStreams_.size());
2553 5249 : for (auto const& stream : rtpStreams_)
2554 3382 : mediaList.emplace_back(*stream.mediaAttribute_);
2555 3734 : return mediaList;
2556 1867 : }
2557 :
2558 : std::map<std::string, bool>
2559 897 : SIPCall::getAudioStreams() const
2560 : {
2561 897 : std::map<std::string, bool> audioMedias {};
2562 897 : auto medias = getMediaAttributeList();
2563 2521 : for (const auto& media : medias) {
2564 1624 : if (media.type_ == MEDIA_AUDIO) {
2565 903 : auto label = fmt::format("{}_{}", getCallId(), media.label_);
2566 903 : audioMedias.emplace(label, media.muted_);
2567 903 : }
2568 : }
2569 1794 : return audioMedias;
2570 897 : }
2571 :
2572 : std::map<std::string, bool>
2573 128 : SIPCall::getRemoteAudioStreams() const
2574 : {
2575 128 : std::lock_guard lk {callMutex_};
2576 128 : std::map<std::string, bool> audioMedias {};
2577 362 : for (const auto& stream : rtpStreams_) {
2578 234 : if (stream.mediaAttribute_ && stream.mediaAttribute_->type_ == MEDIA_AUDIO) {
2579 128 : auto label = fmt::format("{}_{}", getCallId(), stream.mediaAttribute_->label_);
2580 128 : bool muted = stream.remoteMediaAttribute_ ? stream.remoteMediaAttribute_->muted_
2581 53 : : stream.mediaAttribute_->muted_;
2582 128 : audioMedias.emplace(label, muted);
2583 128 : }
2584 : }
2585 256 : return audioMedias;
2586 128 : }
2587 :
2588 : void
2589 242 : SIPCall::onMediaNegotiationComplete()
2590 : {
2591 242 : runOnMainThread([w = weak()] {
2592 242 : if (auto this_ = w.lock()) {
2593 240 : std::lock_guard lk {this_->callMutex_};
2594 240 : JAMI_DEBUG("[call:{}] Media negotiation complete", this_->getCallId());
2595 :
2596 : // If the call has already ended, we don't need to start the media.
2597 480 : if (not this_->inviteSession_ or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED
2598 480 : or not this_->sdp_) {
2599 0 : return;
2600 : }
2601 :
2602 : // This method is called to report media negotiation (SDP) for initial
2603 : // invite or subsequent invites (re-invite).
2604 : // If ICE is negotiated, the media update will be handled in the
2605 : // ICE callback, otherwise, it will be handled here.
2606 : // Note that ICE can be negotiated in the first invite and not negotiated
2607 : // in the re-invite. In this case, the media transport is unchanged (reused).
2608 240 : if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) {
2609 227 : if (not this_->isSubcall()) {
2610 : // Start ICE checks. Media will be started once ICE checks complete.
2611 149 : this_->startIceMedia();
2612 : }
2613 : } else {
2614 : // Update the negotiated media.
2615 13 : if (this_->mediaRestartRequired_) {
2616 5 : this_->setupNegotiatedMedia();
2617 : // No ICE, start media now.
2618 5 : JAMI_WARNING("[call:{}] ICE media disabled, using default media ports", this_->getCallId());
2619 : // Start the media.
2620 5 : this_->stopAllMedia();
2621 5 : this_->startAllMedia();
2622 : }
2623 :
2624 : // this_->updateRemoteMedia();
2625 13 : this_->reportMediaNegotiationStatus();
2626 : }
2627 482 : }
2628 : });
2629 242 : }
2630 :
2631 : void
2632 274 : SIPCall::reportMediaNegotiationStatus()
2633 : {
2634 : // Notify using the parent Id if it's a subcall.
2635 274 : auto callId = isSubcall() ? parent_->getCallId() : getCallId();
2636 274 : emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
2637 548 : callId, libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS, currentMediaList());
2638 :
2639 274 : updateRecordingMediaState(false);
2640 274 : }
2641 :
2642 : void
2643 814 : SIPCall::updateRecordingMediaState(bool readyToRecord)
2644 : {
2645 814 : bool restartRecording = false;
2646 814 : bool startPendingRecording = false;
2647 :
2648 : {
2649 814 : std::lock_guard lk {mediaStateMutex_};
2650 :
2651 814 : if (readyToRecord)
2652 540 : readyToRecord_ = true;
2653 :
2654 814 : auto previousState = isAudioOnly_;
2655 814 : auto newState = !hasVideo();
2656 :
2657 814 : if (!readyToRecord_) {
2658 204 : return;
2659 : }
2660 :
2661 610 : if (previousState != newState && Call::isRecording()) {
2662 2 : restartRecording = true;
2663 2 : pendingRecord_ = true;
2664 : }
2665 610 : isAudioOnly_ = newState;
2666 :
2667 610 : startPendingRecording = pendingRecord_ && readyToRecord_;
2668 814 : }
2669 :
2670 610 : if (restartRecording) {
2671 2 : deinitRecorder();
2672 2 : toggleRecording();
2673 : }
2674 :
2675 610 : if (startPendingRecording)
2676 7 : toggleRecording();
2677 : }
2678 :
2679 : void
2680 227 : SIPCall::startIceMedia()
2681 : {
2682 227 : JAMI_DEBUG("[call:{}] Starting ICE", getCallId());
2683 227 : auto iceMedia = getIceMedia();
2684 227 : if (not iceMedia or iceMedia->isFailed()) {
2685 0 : JAMI_ERROR("[call:{}] Media ICE init failed", getCallId());
2686 0 : onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
2687 0 : return;
2688 : }
2689 :
2690 227 : if (iceMedia->isStarted()) {
2691 : // NOTE: for incoming calls, the ICE is already there and running
2692 0 : if (iceMedia->isRunning())
2693 0 : onIceNegoSucceed();
2694 0 : return;
2695 : }
2696 :
2697 227 : if (not iceMedia->isInitialized()) {
2698 : // In this case, onInitDone will occurs after the startIceMedia
2699 0 : waitForIceInit_ = true;
2700 0 : return;
2701 : }
2702 :
2703 : // Start transport on SDP data and wait for negotiation
2704 227 : if (!sdp_)
2705 0 : return;
2706 227 : auto rem_ice_attrs = sdp_->getIceAttributes();
2707 227 : if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
2708 0 : JAMI_ERROR("[call:{}] Missing remote media ICE attributes", getCallId());
2709 0 : onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
2710 0 : return;
2711 : }
2712 227 : if (not iceMedia->startIce(rem_ice_attrs, getAllRemoteCandidates(*iceMedia))) {
2713 1 : JAMI_ERROR("[call:{}] ICE media failed to start", getCallId());
2714 1 : onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
2715 : }
2716 227 : }
2717 :
2718 : void
2719 188 : SIPCall::onIceNegoSucceed()
2720 : {
2721 188 : std::lock_guard lk {callMutex_};
2722 :
2723 188 : JAMI_DEBUG("[call:{}] ICE negotiation succeeded", getCallId());
2724 :
2725 : // Check if the call is already ended, so we don't need to restart medias
2726 : // This is typically the case in a multi-device context where one device
2727 : // can stop a call. So do not start medias
2728 188 : if (not inviteSession_ or inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED or not sdp_) {
2729 0 : JAMI_ERROR("[call:{}] ICE negotiation succeeded, but call is in invalid state", getCallId());
2730 0 : return;
2731 : }
2732 :
2733 : // Update the negotiated media.
2734 188 : setupNegotiatedMedia();
2735 :
2736 : // If this callback is for a re-invite session then update
2737 : // the ICE media transport.
2738 188 : if (isIceEnabled())
2739 188 : switchToIceReinviteIfNeeded();
2740 :
2741 540 : for (unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) {
2742 : // Create sockets for RTP and RTCP, and start the session.
2743 352 : auto& rtpStream = rtpStreams_[idx];
2744 352 : if (not rtpStream.mediaAttribute_ or not rtpStream.mediaAttribute_->enabled_) {
2745 2 : JAMI_DEBUG("[call:{}] Skipping ICE socket for disabled stream @{}", getCallId(), idx);
2746 2 : continue;
2747 : }
2748 350 : rtpStream.rtpSocket_ = newIceSocket(compId);
2749 :
2750 350 : if (not rtcpMuxEnabled_) {
2751 350 : rtpStream.rtcpSocket_ = newIceSocket(compId + 1);
2752 : }
2753 : }
2754 :
2755 : // Start/Restart the media using the new transport
2756 188 : stopAllMedia();
2757 188 : startAllMedia();
2758 188 : reportMediaNegotiationStatus();
2759 188 : }
2760 :
2761 : bool
2762 24 : SIPCall::checkMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList)
2763 : {
2764 : // The current media is considered to have changed if one of the
2765 : // following condtions is true:
2766 : //
2767 : // - the number of media changed
2768 : // - the type of one of the media changed (unlikely)
2769 : // - one of the media was enabled/disabled
2770 :
2771 24 : JAMI_DEBUG("[call:{}] Received a media change request", getCallId());
2772 :
2773 24 : auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList, isSrtpEnabled());
2774 24 : if (remoteMediaAttrList.size() != rtpStreams_.size())
2775 4 : return true;
2776 :
2777 57 : for (size_t i = 0; i < rtpStreams_.size(); i++) {
2778 37 : if (remoteMediaAttrList[i].type_ != rtpStreams_[i].mediaAttribute_->type_)
2779 0 : return true;
2780 37 : if (remoteMediaAttrList[i].enabled_ != rtpStreams_[i].mediaAttribute_->enabled_)
2781 0 : return true;
2782 : }
2783 :
2784 20 : return false;
2785 24 : }
2786 :
2787 : void
2788 24 : SIPCall::handleMediaChangeRequest(const std::vector<libjami::MediaMap>& remoteMediaList)
2789 : {
2790 24 : JAMI_DEBUG("[call:{}] Handling media change request", getCallId());
2791 :
2792 24 : auto account = getAccount().lock();
2793 24 : if (not account) {
2794 0 : JAMI_ERROR("No account detected");
2795 0 : return;
2796 : }
2797 :
2798 : // If the offered media does not differ from the current local media, the
2799 : // request is answered using the current local media.
2800 24 : if (not checkMediaChangeRequest(remoteMediaList)) {
2801 20 : answerMediaChangeRequest(MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()));
2802 20 : return;
2803 : }
2804 :
2805 4 : if (account->isAutoAnswerEnabled()) {
2806 : // NOTE:
2807 : // Since the auto-answer is enabled in the account, newly
2808 : // added media are accepted too.
2809 : // This also means that if original call was an audio-only call,
2810 : // the local camera will be enabled, unless the video is disabled
2811 : // in the account settings.
2812 :
2813 1 : std::vector<libjami::MediaMap> newMediaList;
2814 1 : newMediaList.reserve(remoteMediaList.size());
2815 2 : for (auto const& stream : rtpStreams_) {
2816 1 : newMediaList.emplace_back(MediaAttribute::toMediaMap(*stream.mediaAttribute_));
2817 : }
2818 :
2819 1 : assert(remoteMediaList.size() > 0);
2820 1 : if (remoteMediaList.size() > newMediaList.size()) {
2821 2 : for (auto idx = newMediaList.size(); idx < remoteMediaList.size(); idx++) {
2822 1 : newMediaList.emplace_back(remoteMediaList[idx]);
2823 : }
2824 : }
2825 1 : answerMediaChangeRequest(newMediaList, true);
2826 1 : return;
2827 1 : }
2828 :
2829 : // Report the media change request.
2830 3 : emitSignal<libjami::CallSignal::MediaChangeRequested>(getAccountId(), getCallId(), remoteMediaList);
2831 24 : }
2832 :
2833 : pj_status_t
2834 26 : SIPCall::onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
2835 : {
2836 26 : JAMI_DEBUG("[call:{}] Received a re-invite", getCallId());
2837 :
2838 26 : pj_status_t res = PJ_SUCCESS;
2839 :
2840 26 : if (not sdp_) {
2841 0 : JAMI_ERROR("SDP session is invalid");
2842 0 : return res;
2843 : }
2844 :
2845 26 : sdp_->clearIce();
2846 26 : sdp_->setActiveRemoteSdpSession(nullptr);
2847 26 : sdp_->setActiveLocalSdpSession(nullptr);
2848 :
2849 26 : auto acc = getSIPAccount();
2850 26 : if (not acc) {
2851 0 : JAMI_ERROR("No account detected");
2852 0 : return res;
2853 : }
2854 :
2855 26 : Sdp::printSession(offer, "Remote session (media change request)", SdpDirection::OFFER);
2856 :
2857 26 : sdp_->setReceivedOffer(offer);
2858 :
2859 : // Note: For multistream, here we must ignore disabled remote medias, because
2860 : // we will answer from our medias and remote enabled medias.
2861 : // Example: if remote disables its camera and share its screen, the offer will
2862 : // have an active and a disabled media (with port = 0).
2863 : // In this case, if we have only one video, we can just negotiate 1 video instead of 2
2864 : // with 1 disabled.
2865 : // cf. pjmedia_sdp_neg_modify_local_offer2 for more details.
2866 26 : auto const& mediaAttrList = Sdp::getMediaAttributeListFromSdp(offer, true);
2867 26 : if (mediaAttrList.empty()) {
2868 0 : JAMI_WARNING("[call:{}] Media list is empty, ignoring", getCallId());
2869 0 : return res;
2870 : }
2871 :
2872 26 : if (upnp_) {
2873 0 : openPortsUPnP();
2874 : }
2875 :
2876 26 : pjsip_tx_data* tdata = nullptr;
2877 26 : if (pjsip_inv_initial_answer(inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) {
2878 0 : JAMI_ERROR("[call:{}] Unable to create answer TRYING", getCallId());
2879 0 : return res;
2880 : }
2881 :
2882 26 : dht::ThreadPool::io().run([callWkPtr = weak(), mediaAttrList] {
2883 26 : if (auto call = callWkPtr.lock()) {
2884 : // Report the change request.
2885 26 : auto const& remoteMediaList = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
2886 26 : if (auto conf = call->getConference()) {
2887 2 : conf->handleMediaChangeRequest(call, remoteMediaList);
2888 : } else {
2889 24 : call->handleMediaChangeRequest(remoteMediaList);
2890 26 : }
2891 52 : }
2892 26 : });
2893 :
2894 26 : return res;
2895 26 : }
2896 :
2897 : void
2898 0 : SIPCall::onReceiveOfferIn200OK(const pjmedia_sdp_session* offer)
2899 : {
2900 0 : if (not rtpStreams_.empty()) {
2901 0 : JAMI_ERROR("[call:{}] Unexpected offer in '200 OK' answer", getCallId());
2902 0 : return;
2903 : }
2904 :
2905 0 : auto acc = getSIPAccount();
2906 0 : if (not acc) {
2907 0 : JAMI_ERROR("No account detected");
2908 0 : return;
2909 : }
2910 :
2911 0 : if (not sdp_) {
2912 0 : JAMI_ERROR("Invalid SDP session");
2913 0 : return;
2914 : }
2915 :
2916 0 : JAMI_DEBUG("[call:{}] Received an offer in '200 OK' answer", getCallId());
2917 :
2918 0 : auto mediaList = Sdp::getMediaAttributeListFromSdp(offer);
2919 : // If this method is called, it means we are expecting an offer
2920 : // in the 200OK answer.
2921 0 : if (mediaList.empty()) {
2922 0 : JAMI_WARNING("[call:{}] Remote media list is empty, ignoring", getCallId());
2923 0 : return;
2924 : }
2925 :
2926 0 : Sdp::printSession(offer, "Remote session (offer in 200 OK answer)", SdpDirection::OFFER);
2927 :
2928 0 : sdp_->clearIce();
2929 0 : sdp_->setActiveRemoteSdpSession(nullptr);
2930 0 : sdp_->setActiveLocalSdpSession(nullptr);
2931 :
2932 0 : sdp_->setReceivedOffer(offer);
2933 :
2934 : // If we send an empty offer, video will be accepted only if locally
2935 : // enabled by the user.
2936 0 : for (auto& mediaAttr : mediaList) {
2937 0 : if (mediaAttr.type_ == MediaType::MEDIA_VIDEO and not acc->isVideoEnabled()) {
2938 0 : mediaAttr.enabled_ = false;
2939 : }
2940 : }
2941 :
2942 0 : initMediaStreams(mediaList);
2943 :
2944 0 : sdp_->processIncomingOffer(mediaList);
2945 :
2946 0 : if (upnp_) {
2947 0 : openPortsUPnP();
2948 : }
2949 :
2950 0 : if (isIceEnabled() and remoteHasValidIceAttributes()) {
2951 0 : setupIceResponse();
2952 : }
2953 :
2954 0 : sdp_->startNegotiation();
2955 :
2956 0 : if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
2957 0 : JAMI_ERROR("[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
2958 : }
2959 0 : }
2960 :
2961 : void
2962 0 : SIPCall::openPortsUPnP()
2963 : {
2964 0 : if (not sdp_) {
2965 0 : JAMI_ERROR("[call:{}] Current SDP instance is invalid", getCallId());
2966 0 : return;
2967 : }
2968 :
2969 : /**
2970 : * Attempt to open the desired ports with UPnP,
2971 : * if they are used, use the alternative port and update the SDP session with the newly
2972 : * chosen port(s)
2973 : *
2974 : * TODO:
2975 : * No need to request mappings for specfic port numbers. Set the port to '0' to
2976 : * request the first available port (faster and more likely to succeed).
2977 : */
2978 0 : JAMI_DEBUG("[call:{}] Opening ports via UPnP for SDP session", getCallId());
2979 :
2980 : // RTP port.
2981 0 : upnp_->reserveMapping(sdp_->getLocalAudioPort(), dhtnet::upnp::PortType::UDP);
2982 : // RTCP port.
2983 0 : upnp_->reserveMapping(sdp_->getLocalAudioControlPort(), dhtnet::upnp::PortType::UDP);
2984 :
2985 : #ifdef ENABLE_VIDEO
2986 : // RTP port.
2987 0 : upnp_->reserveMapping(sdp_->getLocalVideoPort(), dhtnet::upnp::PortType::UDP);
2988 : // RTCP port.
2989 0 : upnp_->reserveMapping(sdp_->getLocalVideoControlPort(), dhtnet::upnp::PortType::UDP);
2990 : #endif
2991 : }
2992 :
2993 : std::map<std::string, std::string>
2994 370 : SIPCall::getDetails() const
2995 : {
2996 370 : auto acc = getSIPAccount();
2997 370 : if (!acc) {
2998 0 : JAMI_ERROR("No account detected");
2999 0 : return {};
3000 : }
3001 :
3002 370 : auto details = Call::getDetails();
3003 :
3004 370 : details.emplace(libjami::Call::Details::PEER_HOLD, peerHold_ ? TRUE_STR : FALSE_STR);
3005 :
3006 1032 : for (auto const& stream : rtpStreams_) {
3007 662 : if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
3008 292 : details.emplace(libjami::Call::Details::VIDEO_SOURCE, stream.mediaAttribute_->sourceUri_);
3009 : #ifdef ENABLE_VIDEO
3010 292 : if (auto const& rtpSession = stream.rtpSession_) {
3011 292 : if (auto codec = rtpSession->getCodec()) {
3012 87 : details.emplace(libjami::Call::Details::VIDEO_CODEC, codec->name);
3013 87 : details.emplace(libjami::Call::Details::VIDEO_MIN_BITRATE, std::to_string(codec->minBitrate));
3014 87 : details.emplace(libjami::Call::Details::VIDEO_MAX_BITRATE, std::to_string(codec->maxBitrate));
3015 87 : if (const auto& curvideoRtpSession = std::static_pointer_cast<video::VideoRtpSession>(rtpSession)) {
3016 87 : details.emplace(libjami::Call::Details::VIDEO_BITRATE,
3017 174 : std::to_string(curvideoRtpSession->getVideoBitrateInfo().videoBitrateCurrent));
3018 87 : }
3019 : } else
3020 292 : details.emplace(libjami::Call::Details::VIDEO_CODEC, "");
3021 : }
3022 : #endif
3023 370 : } else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3024 370 : if (auto const& rtpSession = stream.rtpSession_) {
3025 370 : if (auto codec = rtpSession->getCodec()) {
3026 103 : details.emplace(libjami::Call::Details::AUDIO_CODEC, codec->name);
3027 103 : details.emplace(
3028 : libjami::Call::Details::AUDIO_SAMPLE_RATE,
3029 309 : codec->getCodecSpecifications()[libjami::Account::ConfProperties::CodecInfo::SAMPLE_RATE]);
3030 : } else {
3031 267 : details.emplace(libjami::Call::Details::AUDIO_CODEC, "");
3032 267 : details.emplace(libjami::Call::Details::AUDIO_SAMPLE_RATE, "");
3033 370 : }
3034 : }
3035 : }
3036 : }
3037 :
3038 370 : if (not peerRegisteredName_.empty())
3039 2 : details.emplace(libjami::Call::Details::REGISTERED_NAME, peerRegisteredName_);
3040 :
3041 : #ifdef ENABLE_CLIENT_CERT
3042 : std::lock_guard lk {callMutex_};
3043 : if (transport_ and transport_->isSecure()) {
3044 : const auto& tlsInfos = transport_->getTlsInfos();
3045 : if (tlsInfos.cipher != PJ_TLS_UNKNOWN_CIPHER) {
3046 : const auto& cipher = pj_ssl_cipher_name(tlsInfos.cipher);
3047 : details.emplace(libjami::TlsTransport::TLS_CIPHER, cipher ? cipher : "");
3048 : } else {
3049 : details.emplace(libjami::TlsTransport::TLS_CIPHER, "");
3050 : }
3051 : if (tlsInfos.peerCert) {
3052 : details.emplace(libjami::TlsTransport::TLS_PEER_CERT, tlsInfos.peerCert->toString());
3053 : auto ca = tlsInfos.peerCert->issuer;
3054 : unsigned n = 0;
3055 : while (ca) {
3056 : std::ostringstream name_str;
3057 : name_str << libjami::TlsTransport::TLS_PEER_CA_ << n++;
3058 : details.emplace(name_str.str(), ca->toString());
3059 : ca = ca->issuer;
3060 : }
3061 : details.emplace(libjami::TlsTransport::TLS_PEER_CA_NUM, std::to_string(n));
3062 : } else {
3063 : details.emplace(libjami::TlsTransport::TLS_PEER_CERT, "");
3064 : details.emplace(libjami::TlsTransport::TLS_PEER_CA_NUM, "");
3065 : }
3066 : }
3067 : #endif
3068 370 : if (auto transport = getIceMedia()) {
3069 340 : if (transport && transport->isRunning())
3070 100 : details.emplace(libjami::Call::Details::SOCKETS, transport->link().c_str());
3071 370 : }
3072 370 : return details;
3073 370 : }
3074 :
3075 : void
3076 71 : SIPCall::enterConference(std::shared_ptr<Conference> conference)
3077 : {
3078 71 : JAMI_DEBUG("[call:{}] Entering conference [{}]", getCallId(), conference->getConfId());
3079 71 : conf_ = conference;
3080 : // Unbind audio. It will be rebinded in the conference if needed
3081 71 : auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3082 71 : if (hasAudio) {
3083 71 : auto& rbPool = Manager::instance().getRingBufferPool();
3084 71 : auto medias = getAudioStreams();
3085 142 : for (const auto& media : medias) {
3086 142 : rbPool.unbindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3087 : }
3088 71 : rbPool.flush(RingBufferPool::DEFAULT_ID);
3089 71 : }
3090 :
3091 : #ifdef ENABLE_VIDEO
3092 71 : if (conference->isVideoEnabled())
3093 130 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3094 130 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
3095 : #endif
3096 :
3097 : #ifdef ENABLE_PLUGIN
3098 71 : clearCallAVStreams();
3099 : #endif
3100 71 : }
3101 :
3102 : void
3103 69 : SIPCall::exitConference()
3104 : {
3105 69 : std::lock_guard lk {callMutex_};
3106 69 : JAMI_DEBUG("[call:{}] Leaving conference", getCallId());
3107 :
3108 69 : auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3109 69 : if (hasAudio) {
3110 69 : auto& rbPool = Manager::instance().getRingBufferPool();
3111 69 : auto medias = getAudioStreams();
3112 138 : for (const auto& media : medias) {
3113 69 : if (!media.second) {
3114 128 : rbPool.bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3115 : }
3116 : }
3117 69 : rbPool.flush(RingBufferPool::DEFAULT_ID);
3118 69 : }
3119 : #ifdef ENABLE_VIDEO
3120 125 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3121 125 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->exitConference();
3122 : #endif
3123 : #ifdef ENABLE_PLUGIN
3124 69 : createCallAVStreams();
3125 : #endif
3126 69 : conf_.reset();
3127 69 : }
3128 :
3129 : void
3130 0 : SIPCall::setActiveMediaStream(const std::string& accountUri,
3131 : const std::string& deviceId,
3132 : const std::string& streamId,
3133 : const bool& state)
3134 : {
3135 0 : auto remoteStreamId = streamId;
3136 : #ifdef ENABLE_VIDEO
3137 : {
3138 0 : std::lock_guard lk(sinksMtx_);
3139 0 : const auto& localIt = local2RemoteSinks_.find(streamId);
3140 0 : if (localIt != local2RemoteSinks_.end()) {
3141 0 : remoteStreamId = localIt->second;
3142 : }
3143 0 : }
3144 : #endif
3145 :
3146 0 : if (Call::conferenceProtocolVersion() == 1) {
3147 0 : Json::Value sinkVal;
3148 0 : sinkVal["active"] = state;
3149 0 : Json::Value mediasObj;
3150 0 : mediasObj[remoteStreamId] = sinkVal;
3151 0 : Json::Value deviceVal;
3152 0 : deviceVal["medias"] = mediasObj;
3153 0 : Json::Value deviceObj;
3154 0 : deviceObj[deviceId] = deviceVal;
3155 0 : Json::Value accountVal;
3156 0 : deviceVal["devices"] = deviceObj;
3157 0 : Json::Value root;
3158 0 : root[accountUri] = deviceVal;
3159 0 : root["version"] = 1;
3160 0 : Call::sendConfOrder(root);
3161 0 : } else if (Call::conferenceProtocolVersion() == 0) {
3162 0 : Json::Value root;
3163 0 : root["activeParticipant"] = accountUri;
3164 0 : Call::sendConfOrder(root);
3165 0 : }
3166 0 : }
3167 :
3168 : #ifdef ENABLE_VIDEO
3169 : void
3170 48 : SIPCall::setRotation(int streamIdx, int rotation)
3171 : {
3172 : // Retrigger on another thread to avoid to lock PJSIP
3173 48 : dht::ThreadPool::io().run([w = weak(), streamIdx, rotation] {
3174 48 : if (auto shared = w.lock()) {
3175 48 : std::lock_guard lk {shared->callMutex_};
3176 48 : shared->rotation_ = rotation;
3177 48 : if (streamIdx == -1) {
3178 0 : for (const auto& videoRtp : shared->getRtpSessionList(MediaType::MEDIA_VIDEO))
3179 0 : std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->setRotation(rotation);
3180 48 : } else if (streamIdx > -1 && streamIdx < static_cast<int>(shared->rtpStreams_.size())) {
3181 : // Apply request for requested stream
3182 48 : auto& stream = shared->rtpStreams_[streamIdx];
3183 48 : if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
3184 48 : std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation);
3185 : }
3186 96 : }
3187 48 : });
3188 48 : }
3189 :
3190 : void
3191 237 : SIPCall::createSinks(ConfInfo& infos)
3192 : {
3193 237 : std::lock_guard lk(callMutex_);
3194 237 : std::lock_guard lkS(sinksMtx_);
3195 237 : if (!hasVideo())
3196 29 : return;
3197 :
3198 769 : for (auto& participant : infos) {
3199 1122 : if (string_remove_suffix(participant.uri, '@') == account_.lock()->getUsername()
3200 1122 : && participant.device == std::dynamic_pointer_cast<JamiAccount>(account_.lock())->currentDeviceId()) {
3201 536 : for (auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
3202 358 : if (!iter->mediaAttribute_ || iter->mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3203 178 : continue;
3204 : }
3205 : auto* localVideo
3206 180 : = std::static_pointer_cast<video::VideoRtpSession>(iter->rtpSession_)->getVideoLocal().get();
3207 180 : auto size = std::make_pair(10, 10);
3208 180 : if (localVideo) {
3209 165 : size = std::make_pair(localVideo->getWidth(), localVideo->getHeight());
3210 : }
3211 180 : const auto& mediaAttribute = iter->mediaAttribute_;
3212 180 : if (participant.sinkId.find(mediaAttribute->label_) != std::string::npos) {
3213 175 : local2RemoteSinks_[mediaAttribute->sourceUri_] = participant.sinkId;
3214 175 : participant.sinkId = mediaAttribute->sourceUri_;
3215 175 : participant.videoMuted = mediaAttribute->muted_;
3216 175 : participant.w = size.first;
3217 175 : participant.h = size.second;
3218 175 : participant.x = 0;
3219 175 : participant.y = 0;
3220 : }
3221 : }
3222 : }
3223 : }
3224 :
3225 208 : std::vector<std::shared_ptr<video::VideoFrameActiveWriter>> sinks;
3226 418 : for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) {
3227 210 : auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
3228 210 : if (!videoReceive)
3229 31 : continue;
3230 179 : sinks.emplace_back(std::static_pointer_cast<video::VideoFrameActiveWriter>(videoReceive->getSink()));
3231 208 : }
3232 208 : auto conf = conf_.lock();
3233 208 : const auto& id = conf ? conf->getConfId() : getCallId();
3234 208 : Manager::instance().createSinkClients(id, infos, sinks, callSinksMap_, getAccountId());
3235 266 : }
3236 : #endif
3237 :
3238 : std::vector<std::shared_ptr<RtpSession>>
3239 2027 : SIPCall::getRtpSessionList(MediaType type) const
3240 : {
3241 2027 : std::vector<std::shared_ptr<RtpSession>> rtpList;
3242 2027 : rtpList.reserve(rtpStreams_.size());
3243 5837 : for (auto const& stream : rtpStreams_) {
3244 3809 : if (type == MediaType::MEDIA_ALL || stream.rtpSession_->getMediaType() == type)
3245 2710 : rtpList.emplace_back(stream.rtpSession_);
3246 : }
3247 2026 : return rtpList;
3248 0 : }
3249 :
3250 : void
3251 197 : SIPCall::monitor() const
3252 : {
3253 197 : if (isSubcall())
3254 0 : return;
3255 197 : auto acc = getSIPAccount();
3256 197 : if (!acc) {
3257 0 : JAMI_ERROR("No account detected");
3258 0 : return;
3259 : }
3260 197 : JAMI_LOG("- Call {} with {}:", getCallId(), getPeerNumber());
3261 197 : JAMI_LOG("\t- Duration: {}", dht::print_duration(getCallDuration()));
3262 558 : for (const auto& stream : rtpStreams_)
3263 361 : JAMI_LOG("\t- Media: {}", stream.mediaAttribute_->toString(true));
3264 : #ifdef ENABLE_VIDEO
3265 197 : if (auto codec = getVideoCodec())
3266 197 : JAMI_LOG("\t- Video codec: {}", codec->name);
3267 : #endif
3268 197 : if (auto transport = getIceMedia()) {
3269 188 : if (transport->isRunning())
3270 139 : JAMI_LOG("\t- Media stream(s): {}", transport->link());
3271 197 : }
3272 197 : }
3273 :
3274 : bool
3275 21 : SIPCall::toggleRecording()
3276 : {
3277 21 : pendingRecord_ = true;
3278 21 : if (not readyToRecord_)
3279 6 : return true;
3280 :
3281 : // add streams to recorder before starting the record
3282 15 : if (not Call::isRecording()) {
3283 9 : auto account = getSIPAccount();
3284 9 : if (!account) {
3285 0 : JAMI_ERROR("No account detected");
3286 0 : return false;
3287 : }
3288 18 : auto title = fmt::format("Conversation at %TIMESTAMP between {} and {}", account->getUserUri(), peerUri_);
3289 18 : recorder_->setMetadata(title, ""); // use default description
3290 24 : for (const auto& rtpSession : getRtpSessionList())
3291 24 : rtpSession->initRecorder();
3292 9 : } else {
3293 6 : updateRecState(false);
3294 : }
3295 15 : pendingRecord_ = false;
3296 15 : auto state = Call::toggleRecording();
3297 15 : if (state)
3298 9 : updateRecState(state);
3299 15 : return state;
3300 : }
3301 :
3302 : void
3303 4 : SIPCall::deinitRecorder()
3304 : {
3305 11 : for (const auto& rtpSession : getRtpSessionList())
3306 11 : rtpSession->deinitRecorder();
3307 4 : }
3308 :
3309 : void
3310 214 : SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv) const noexcept
3311 : {
3312 214 : if (!inv)
3313 0 : return;
3314 :
3315 : // The session lives in the dialog pool. Dropping the last reference
3316 : // runs inv_session_destroy(), which decrements the dialog session count
3317 : // and, when no transaction is left, destroys the dialog and frees that
3318 : // pool, then goes on to read the session's own fields from the freed
3319 : // memory. pjsip only ever does this while holding the dialog lock,
3320 : // which keeps the dialog alive until the lock is released. We drop our
3321 : // reference from the main thread, outside of any such lock, so take it
3322 : // here. Keep the dialog pointer in a local, as it must outlive inv.
3323 214 : auto* dlg = inv->dlg;
3324 214 : pjsip_dlg_inc_lock(dlg);
3325 :
3326 : // prevent this from getting accessed in callbacks
3327 214 : inv->mod_data[Manager::instance().sipVoIPLink().getModId()] = nullptr;
3328 : // NOTE: the counter is incremented by setInviteSession()
3329 214 : pjsip_inv_dec_ref(inv);
3330 :
3331 : // Actually destroys the dialog if it has no other session.
3332 214 : pjsip_dlg_dec_lock(dlg);
3333 : }
3334 :
3335 : bool
3336 244 : SIPCall::createIceMediaTransport(bool isReinvite)
3337 : {
3338 244 : auto mediaTransport = Manager::instance().getIceTransportFactory()->createTransport(getCallId());
3339 244 : if (mediaTransport) {
3340 244 : JAMI_DEBUG("[call:{}] Successfully created media ICE transport [ice:{}]",
3341 : getCallId(),
3342 : fmt::ptr(mediaTransport.get()));
3343 : } else {
3344 0 : JAMI_ERROR("[call:{}] Failed to create media ICE transport", getCallId());
3345 0 : return {};
3346 : }
3347 :
3348 244 : setIceMedia(mediaTransport, isReinvite);
3349 :
3350 244 : return mediaTransport != nullptr;
3351 244 : }
3352 :
3353 : bool
3354 244 : SIPCall::initIceMediaTransport(bool master, std::optional<dhtnet::IceTransportOptions> options)
3355 : {
3356 244 : auto acc = getSIPAccount();
3357 244 : if (!acc) {
3358 0 : JAMI_ERROR("No account detected");
3359 0 : return false;
3360 : }
3361 :
3362 244 : JAMI_DEBUG("[call:{}] Init media ICE transport", getCallId());
3363 :
3364 244 : auto const& iceMedia = getIceMedia();
3365 244 : if (not iceMedia) {
3366 0 : JAMI_ERROR("[call:{}] Invalid media ICE transport", getCallId());
3367 0 : return false;
3368 : }
3369 :
3370 244 : auto iceOptions = options == std::nullopt ? acc->getIceOptions() : *options;
3371 :
3372 244 : auto optOnInitDone = std::move(iceOptions.onInitDone);
3373 244 : auto optOnNegoDone = std::move(iceOptions.onNegoDone);
3374 488 : iceOptions.onInitDone = [w = weak(), cb = std::move(optOnInitDone)](bool ok) {
3375 244 : runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3376 244 : auto call = w.lock();
3377 244 : if (cb)
3378 0 : cb(ok);
3379 244 : if (!ok or !call or !call->waitForIceInit_.exchange(false))
3380 244 : return;
3381 :
3382 0 : std::lock_guard lk {call->callMutex_};
3383 0 : auto rem_ice_attrs = call->sdp_->getIceAttributes();
3384 : // Init done but no remote_ice_attributes, the ice->start will be triggered later
3385 0 : if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty())
3386 0 : return;
3387 0 : call->startIceMedia();
3388 244 : });
3389 488 : };
3390 488 : iceOptions.onNegoDone = [w = weak(), cb = std::move(optOnNegoDone)](bool ok) {
3391 187 : runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3392 188 : if (cb)
3393 0 : cb(ok);
3394 188 : if (auto call = w.lock()) {
3395 : // The ICE is related to subcalls, but medias are handled by parent call
3396 188 : std::lock_guard lk {call->callMutex_};
3397 188 : call = call->isSubcall() ? std::dynamic_pointer_cast<SIPCall>(call->parent_) : call;
3398 188 : if (!ok) {
3399 0 : JAMI_ERROR("[call:{}] Media ICE negotiation failed", call->getCallId());
3400 0 : call->onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3401 0 : return;
3402 : }
3403 188 : call->onIceNegoSucceed();
3404 376 : }
3405 : });
3406 432 : };
3407 :
3408 244 : iceOptions.master = master;
3409 244 : iceOptions.streamsCount = static_cast<unsigned>(rtpStreams_.size());
3410 : // Each RTP stream requires a pair of ICE components (RTP + RTCP).
3411 244 : iceOptions.compCountPerStream = ICE_COMP_COUNT_PER_STREAM;
3412 244 : iceOptions.qosType.reserve(rtpStreams_.size() * ICE_COMP_COUNT_PER_STREAM);
3413 690 : for (const auto& stream : rtpStreams_) {
3414 446 : iceOptions.qosType.push_back(stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO ? dhtnet::QosType::VOICE
3415 : : dhtnet::QosType::VIDEO);
3416 446 : iceOptions.qosType.push_back(dhtnet::QosType::CONTROL);
3417 : }
3418 :
3419 : // Init ICE.
3420 244 : iceMedia->initIceInstance(iceOptions);
3421 :
3422 244 : return true;
3423 244 : }
3424 :
3425 : std::vector<std::string>
3426 0 : SIPCall::getLocalIceCandidates(unsigned compId) const
3427 : {
3428 0 : std::lock_guard lk(transportMtx_);
3429 0 : if (not iceMedia_) {
3430 0 : JAMI_WARNING("[call:{}] No media ICE transport", getCallId());
3431 0 : return {};
3432 : }
3433 0 : return iceMedia_->getLocalCandidates(compId);
3434 0 : }
3435 :
3436 : void
3437 1434 : SIPCall::resetTransport(std::shared_ptr<dhtnet::IceTransport>&& transport)
3438 : {
3439 : // Move the transport to another thread and destroy it there if possible
3440 1434 : if (transport) {
3441 843 : dht::ThreadPool::io().run([transport = std::move(transport)]() mutable { transport.reset(); });
3442 : }
3443 1434 : }
3444 :
3445 : void
3446 78 : SIPCall::merge(Call& call)
3447 : {
3448 78 : JAMI_DEBUG("[call:{}] Merge subcall {}", getCallId(), call.getCallId());
3449 :
3450 : // This static cast is safe as this method is private and overload Call::merge
3451 78 : auto& subcall = static_cast<SIPCall&>(call);
3452 :
3453 78 : std::lock(callMutex_, subcall.callMutex_);
3454 78 : std::lock_guard lk1 {callMutex_, std::adopt_lock};
3455 78 : std::lock_guard lk2 {subcall.callMutex_, std::adopt_lock};
3456 78 : inviteSession_ = std::move(subcall.inviteSession_);
3457 78 : if (inviteSession_)
3458 78 : inviteSession_->mod_data[Manager::instance().sipVoIPLink().getModId()] = this;
3459 78 : setSipTransport(std::move(subcall.sipTransport_), std::move(subcall.contactHeader_));
3460 78 : sdp_ = std::move(subcall.sdp_);
3461 78 : peerHold_ = subcall.peerHold_;
3462 78 : upnp_ = std::move(subcall.upnp_);
3463 78 : localAudioPort_ = subcall.localAudioPort_;
3464 78 : localVideoPort_ = subcall.localVideoPort_;
3465 78 : peerUserAgent_ = subcall.peerUserAgent_;
3466 78 : peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
3467 78 : peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_;
3468 78 : peerSupportMultiIce_ = subcall.peerSupportMultiIce_;
3469 78 : peerAllowedMethods_ = subcall.peerAllowedMethods_;
3470 78 : peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_;
3471 :
3472 78 : Call::merge(subcall);
3473 78 : if (isIceEnabled())
3474 78 : startIceMedia();
3475 78 : }
3476 :
3477 : bool
3478 357 : SIPCall::remoteHasValidIceAttributes() const
3479 : {
3480 357 : if (not sdp_) {
3481 0 : throw std::runtime_error("Must have a valid SDP Session");
3482 : }
3483 :
3484 357 : auto rem_ice_attrs = sdp_->getIceAttributes();
3485 357 : if (rem_ice_attrs.ufrag.empty()) {
3486 15 : JAMI_DEBUG("[call:{}] No ICE username fragment attribute in remote SDP", getCallId());
3487 15 : return false;
3488 : }
3489 :
3490 342 : if (rem_ice_attrs.pwd.empty()) {
3491 0 : JAMI_DEBUG("[call:{}] No ICE password attribute in remote SDP", getCallId());
3492 0 : return false;
3493 : }
3494 :
3495 342 : return true;
3496 357 : }
3497 :
3498 : void
3499 422 : SIPCall::setIceMedia(std::shared_ptr<dhtnet::IceTransport> ice, bool isReinvite)
3500 : {
3501 422 : std::lock_guard lk(transportMtx_);
3502 :
3503 422 : if (isReinvite) {
3504 41 : JAMI_DEBUG("[call:{}] Setting re-invite ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
3505 41 : resetTransport(std::move(reinvIceMedia_));
3506 41 : reinvIceMedia_ = std::move(ice);
3507 : } else {
3508 381 : JAMI_DEBUG("[call:{}] Setting ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
3509 381 : resetTransport(std::move(iceMedia_));
3510 381 : iceMedia_ = std::move(ice);
3511 : }
3512 422 : }
3513 :
3514 : void
3515 188 : SIPCall::switchToIceReinviteIfNeeded()
3516 : {
3517 188 : std::lock_guard lk(transportMtx_);
3518 :
3519 188 : if (reinvIceMedia_) {
3520 32 : JAMI_DEBUG("[call:{}] Switching to re-invite ICE session [{}]", getCallId(), fmt::ptr(reinvIceMedia_.get()));
3521 32 : std::swap(reinvIceMedia_, iceMedia_);
3522 : }
3523 :
3524 188 : resetTransport(std::move(reinvIceMedia_));
3525 188 : }
3526 :
3527 : void
3528 115 : SIPCall::setupIceResponse(bool isReinvite)
3529 : {
3530 115 : JAMI_DEBUG("[call:{}] Setup ICE response", getCallId());
3531 :
3532 115 : auto account = getSIPAccount();
3533 115 : if (not account) {
3534 0 : JAMI_ERROR("No account detected");
3535 : }
3536 :
3537 115 : auto opt = account->getIceOptions();
3538 :
3539 : // Attempt to use the discovered public address. If not available,
3540 : // fallback on local address.
3541 115 : opt.accountPublicAddr = account->getPublishedIpAddress();
3542 115 : if (opt.accountPublicAddr) {
3543 107 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
3544 107 : opt.accountPublicAddr.getFamily());
3545 : } else {
3546 : // Just set the local address for both, most likely the account is not
3547 : // registered. Try IPv4 first, fallback to IPv6 for IPv6-only networks.
3548 8 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET);
3549 8 : if (not opt.accountLocalAddr)
3550 0 : opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET6);
3551 8 : opt.accountPublicAddr = opt.accountLocalAddr;
3552 : }
3553 :
3554 115 : if (not opt.accountLocalAddr) {
3555 0 : JAMI_ERROR("[call:{}] No local address, unable to initialize ICE", getCallId());
3556 0 : onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
3557 0 : return;
3558 : }
3559 :
3560 115 : if (not createIceMediaTransport(isReinvite) or not initIceMediaTransport(false, opt)) {
3561 0 : JAMI_ERROR("[call:{}] ICE initialization failed", getCallId());
3562 : // Fatal condition
3563 : // TODO: what's SIP rfc says about that?
3564 : // (same question in startIceMedia)
3565 0 : onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
3566 0 : return;
3567 : }
3568 :
3569 : // Media transport changed, must restart the media.
3570 115 : mediaRestartRequired_ = true;
3571 :
3572 : // WARNING: This call blocks! (need ICE init done)
3573 115 : addLocalIceAttributes();
3574 115 : }
3575 :
3576 : bool
3577 193 : SIPCall::isIceRunning() const
3578 : {
3579 193 : std::lock_guard lk(transportMtx_);
3580 386 : return iceMedia_ and iceMedia_->isRunning();
3581 193 : }
3582 :
3583 : std::unique_ptr<dhtnet::IceSocket>
3584 700 : SIPCall::newIceSocket(unsigned compId)
3585 : {
3586 700 : return std::unique_ptr<dhtnet::IceSocket> {new dhtnet::IceSocket(getIceMedia(), static_cast<int>(compId))};
3587 : }
3588 :
3589 : void
3590 540 : SIPCall::rtpSetupSuccess()
3591 : {
3592 540 : updateRecordingMediaState(true);
3593 540 : }
3594 :
3595 : void
3596 19 : SIPCall::peerRecording(bool state)
3597 : {
3598 19 : auto conference = conf_.lock();
3599 19 : const std::string& id = conference ? conference->getConfId() : getCallId();
3600 19 : if (state) {
3601 11 : JAMI_WARNING("[call:{}] Peer is recording", getCallId());
3602 11 : emitSignal<libjami::CallSignal::RemoteRecordingChanged>(id, getPeerNumber(), true);
3603 : } else {
3604 8 : JAMI_WARNING("Peer stopped recording");
3605 8 : emitSignal<libjami::CallSignal::RemoteRecordingChanged>(id, getPeerNumber(), false);
3606 : }
3607 19 : peerRecording_ = state;
3608 19 : if (auto conf = conf_.lock())
3609 19 : conf->updateRecording();
3610 19 : }
3611 :
3612 : void
3613 5 : SIPCall::peerMuted(bool muted, int streamIdx)
3614 : {
3615 5 : if (muted) {
3616 5 : JAMI_WARNING("Peer muted");
3617 : } else {
3618 0 : JAMI_WARNING("Peer unmuted");
3619 : }
3620 :
3621 5 : if (streamIdx == -1) {
3622 10 : for (const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
3623 10 : audioRtp->setMuted(muted, RtpSession::Direction::RECV);
3624 0 : } else if (streamIdx > -1 && streamIdx < static_cast<int>(rtpStreams_.size())) {
3625 0 : auto& stream = rtpStreams_[streamIdx];
3626 0 : if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO)
3627 0 : stream.rtpSession_->setMuted(muted, RtpSession::Direction::RECV);
3628 : }
3629 :
3630 5 : peerMuted_ = muted;
3631 5 : if (auto conf = conf_.lock())
3632 5 : conf->updateMuted();
3633 5 : }
3634 :
3635 : void
3636 0 : SIPCall::peerVoice(bool voice)
3637 : {
3638 0 : peerVoice_ = voice;
3639 :
3640 0 : if (auto conference = conf_.lock()) {
3641 0 : conference->updateVoiceActivity();
3642 : } else {
3643 : // one-to-one call
3644 : // maybe emit signal with partner voice activity
3645 0 : }
3646 0 : }
3647 :
3648 : } // namespace jami
|