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