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