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