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