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