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