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