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