37#include "pjsip-ua/sip_inv.h"
48#include <libavutil/display.h>
57#include <dhtnet/upnp/upnp_control.h>
58#include <dhtnet/ice_transport_factory.h>
60#include <opendht/crypto.h>
61#include <opendht/thread_pool.h>
62#include <fmt/ranges.h>
79 return DeviceParams {};
106 const std::string& callId,
108 const std::vector<libjami::MediaMap>&
mediaList)
111 , enableIce_(
account->isIceForMediaEnabled())
112 , srtpEnabled_(
account->isSrtpEnabled())
117 upnp_ = std::make_shared<dhtnet::upnp::Controller>(
Manager::instance().upnpContext());
135 "[call:%s] No media offered in the incoming invite. An offer will be provided in "
145 JAMI_DEBUG(
"[call:{:s}] Create a new [{:s}] SIP call with {:d} media",
168SIPCall::findRtpStreamIndex(
const std::string&
label)
const
170 const auto iter = std::find_if(rtpStreams_.begin(),
173 return label == rtp.mediaAttribute_->label_;
177 if (
iter != rtpStreams_.end())
178 return std::distance(rtpStreams_.begin(),
iter);
185SIPCall::createRtpSession(RtpStream& stream)
187 if (
not stream.mediaAttribute_)
188 throw std::runtime_error(
"Missing media attribute");
193 stream.rtpSession_ = std::make_shared<AudioRtpSession>(
id_, streamId,
recorder_);
197 stream.rtpSession_ = std::make_shared<video::VideoRtpSession>(
id_,
201 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation_);
205 throw std::runtime_error(
"Unsupported media type");
209 if (
not stream.rtpSession_)
210 throw std::runtime_error(
"Failed to create RTP session");
215SIPCall::configureRtpSession(
const std::shared_ptr<RtpSession>&
rtpSession,
216 const std::shared_ptr<MediaAttribute>&
mediaAttr,
220 JAMI_DBG(
"[call:%s] Configuring [%s] RTP session",
225 throw std::runtime_error(
"Must have a valid RTP session");
228 auto new_mtu = sipTransport_->getTlsMtu();
245 dht::ThreadPool::io().run([w = std::move(w)] {
262 dht::ThreadPool::io().run([w = std::move(w),
streamIdx] {
279SIPCall::setupVoiceCallback(
const std::shared_ptr<RtpSession>&
rtpSession)
290 std::string streamId =
"";
293 if (auto videoManager = Manager::instance().getVideoManager()) {
294 if (not videoManager->videoDeviceMonitor.getDeviceList().empty()) {
296 streamId = sip_utils::streamId(
"", sip_utils::DEFAULT_VIDEO_STREAMID);
308 conference->setVoiceActivity(streamId, voice);
313 thisPtr->sendVoiceActivity(
"-1", voice);
318 JAMI_ERR(
"Voice activity callback unable to lock weak ptr to SIPCall");
324std::shared_ptr<SIPAccountBase>
325SIPCall::getSIPAccount()
const
327 return std::static_pointer_cast<SIPAccountBase>(getAccount().lock());
332SIPCall::createCallAVStreams()
336 if (std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->hasConference()) {
337 clearCallAVStreams();
343 auto baseId = getCallId();
344 auto mediaMap = [](
const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
348 for (
const auto& rtpSession : getRtpSessionList()) {
349 auto isVideo = rtpSession->getMediaType() == MediaType::MEDIA_VIDEO;
351 StreamData previewStreamData {baseId,
false, streamType, getPeerNumber(), getAccountId()};
352 StreamData receiveStreamData {baseId,
true, streamType, getPeerNumber(), getAccountId()};
356 auto videoRtp = std::static_pointer_cast<video::VideoRtpSession>(rtpSession);
357 if (
auto& videoPreview = videoRtp->getVideoLocal())
358 createCallAVStream(previewStreamData,
360 std::make_shared<MediaStreamSubject>(mediaMap));
362 if (
auto& videoReceive = videoRtp->getVideoReceive())
363 createCallAVStream(receiveStreamData,
365 std::make_shared<MediaStreamSubject>(mediaMap));
368 auto audioRtp = std::static_pointer_cast<AudioRtpSession>(rtpSession);
370 if (
auto& localAudio = audioRtp->getAudioLocal())
371 createCallAVStream(previewStreamData,
373 std::make_shared<MediaStreamSubject>(mediaMap));
375 if (
auto& audioReceive = audioRtp->getAudioReceive())
376 createCallAVStream(receiveStreamData,
377 (AVMediaStream&) *audioReceive,
378 std::make_shared<MediaStreamSubject>(mediaMap));
387 AVMediaStream& streamSource,
388 const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject)
392 std::lock_guard lk(avStreamsMtx_);
393 auto it = callAVStreams.find(AVStreamId);
394 if (it != callAVStreams.end())
396 it = callAVStreams.insert(it, {AVStreamId, mediaStreamSubject});
397 streamSource.attachPriorityObserver(it->second);
399 .getJamiPluginManager()
400 .getCallServicesManager()
405SIPCall::clearCallAVStreams()
407 std::lock_guard lk(avStreamsMtx_);
408 callAVStreams.clear();
413SIPCall::setCallMediaLocal()
415 if (localAudioPort_ == 0
417 || localVideoPort_ == 0
420 generateMediaPorts();
424SIPCall::generateMediaPorts()
426 auto account = getSIPAccount();
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);
446 const unsigned int callLocalVideoPort = account->generateVideoPort();
447 if (localVideoPort_ != 0)
448 account->releasePort(localVideoPort_);
450 assert(localAudioPort_ != callLocalVideoPort);
451 localVideoPort_ = callLocalVideoPort;
452 sdp_->setLocalPublishedVideoPorts(callLocalVideoPort,
453 rtcpMuxEnabled_ ? 0 : callLocalVideoPort + 1);
458SIPCall::getContactHeader()
const
460 return contactHeader_;
464SIPCall::setSipTransport(
const std::shared_ptr<SipTransport>& transport,
465 const std::string& contactHdr)
467 if (transport != sipTransport_) {
468 JAMI_DBG(
"[call:%s] Setting transport to [%p]", getCallId().c_str(), transport.get());
471 sipTransport_ = transport;
472 contactHeader_ = contactHdr;
479 if (contactHeader_.empty()) {
480 JAMI_WARN(
"[call:%s] Contact header is empty", getCallId().c_str());
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());
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());
493 const auto list_id =
reinterpret_cast<uintptr_t
>(
this);
494 sipTransport_->removeStateListener(list_id);
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(),
503 static_cast<unsigned>(this_->getConnectionState()));
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);
519SIPCall::requestReinvite(
const std::vector<MediaAttribute>& mediaAttrList,
bool needNewIce)
521 JAMI_DBG(
"[call:%s] Sending a SIP re-invite to request media change", getCallId().c_str());
523 if (isWaitingForIceAndMedia_) {
524 remainingRequest_ = Request::SwitchInput;
526 if (SIPSessionReinvite(mediaAttrList, needNewIce) == PJ_SUCCESS and reinvIceMedia_) {
527 isWaitingForIceAndMedia_ =
true;
537SIPCall::SIPSessionReinvite(
const std::vector<MediaAttribute>& mediaAttrList,
bool needNewIce)
539 assert(not mediaAttrList.empty());
541 std::lock_guard lk {callMutex_};
544 if (not inviteSession_ or inviteSession_->invite_tsx)
547 JAMI_DBG(
"[call:%s] Preparing and sending a re-invite (state=%s)",
549 pjsip_inv_state_name(inviteSession_->state));
550 JAMI_DBG(
"[call:%s] New ICE required for this re-invite: [%s]",
552 needNewIce ?
"Yes" :
"No");
556 generateMediaPorts();
559 sdp_->setActiveRemoteSdpSession(
nullptr);
560 sdp_->setActiveLocalSdpSession(
nullptr);
562 auto acc = getSIPAccount();
568 if (not sdp_->createOffer(mediaAttrList))
571 if (isIceEnabled() and needNewIce) {
572 if (not createIceMediaTransport(
true) or not initIceMediaTransport(
true)) {
575 addLocalIceAttributes();
577 mediaRestartRequired_ =
true;
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) {
588 sip_utils::addUserAgentHeader(acc->getUserAgentName(), tdata);
590 result = pjsip_inv_send_msg(inviteSession_.get(), tdata);
591 if (result == PJ_SUCCESS)
593 JAMI_ERR(
"[call:%s] Failed to send REINVITE msg (pjsip: %s)",
595 sip_utils::sip_strerror(result).c_str());
597 pjsip_inv_cancel_reinvite(inviteSession_.get(), &tdata);
599 JAMI_ERR(
"[call:%s] Failed to create REINVITE msg (pjsip: %s)",
601 sip_utils::sip_strerror(result).c_str());
607SIPCall::SIPSessionReinvite()
609 auto mediaList = getMediaAttributeList();
610 return SIPSessionReinvite(mediaList, isNewIceMediaRequired(mediaList));
614SIPCall::sendSIPInfo(std::string_view body, std::string_view subtype)
616 std::lock_guard lk {callMutex_};
617 if (not inviteSession_ or not inviteSession_->dlg)
620 constexpr pj_str_t methodName = CONST_PJ_STR(
"INFO");
621 constexpr pj_str_t type = CONST_PJ_STR(
"application");
624 pjsip_method_init_np(&method, (pj_str_t*) &methodName);
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());
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);
640 pjsip_dlg_send_request(inviteSession_->dlg,
642 Manager::instance().sipVoIPLink().getModId(),
647SIPCall::updateRecState(
bool state)
649 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
650 "<media_control><vc_primitive><to_encoder>"
652 + std::to_string(state)
654 "</to_encoder></vc_primitive></media_control>";
657 JAMI_DBG(
"Sending recording state via SIP INFO");
660 sendSIPInfo(BODY,
"media_control+xml");
661 }
catch (
const std::exception& e) {
662 JAMI_ERR(
"Error sending recording state: %s", e.what());
667SIPCall::requestKeyframe(
int streamIdx)
669 auto now = clock::now();
671 and lastKeyFrameReq_ != time_point::min())
674 std::string streamIdPart;
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");
684 sendSIPInfo(BODY,
"media_control+xml");
685 }
catch (
const std::exception& e) {
686 JAMI_ERR(
"Error sending video keyframe request: %s", e.what());
688 lastKeyFrameReq_ = now;
692SIPCall::sendMuteState(
bool state)
694 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
695 "<media_control><vc_primitive><to_encoder>"
697 + std::to_string(state)
699 "</to_encoder></vc_primitive></media_control>";
702 JAMI_DBG(
"Sending mute state via SIP INFO");
705 sendSIPInfo(BODY,
"media_control+xml");
706 }
catch (
const std::exception& e) {
707 JAMI_ERR(
"Error sending mute state: %s", e.what());
712SIPCall::sendVoiceActivity(std::string_view streamId,
bool state)
715 std::string streamIdPart =
"";
716 if (streamId !=
"-1" && !
streamId.empty()) {
717 streamIdPart = fmt::format(
"<stream_id>{}</stream_id>", streamId);
720 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
721 "<media_control><vc_primitive>"
725 + std::to_string(state)
727 "</to_encoder></vc_primitive></media_control>";
730 sendSIPInfo(BODY,
"media_control+xml");
731 }
catch (
const std::exception& e) {
732 JAMI_ERR(
"Error sending voice activity state: %s", e.what());
737SIPCall::setInviteSession(pjsip_inv_session* inviteSession)
739 std::lock_guard lk {callMutex_};
741 if (inviteSession ==
nullptr and inviteSession_) {
742 JAMI_DBG(
"[call:%s] Delete current invite session", getCallId().c_str());
743 }
else if (inviteSession !=
nullptr) {
749 if (PJ_SUCCESS != pjsip_inv_add_ref(inviteSession)) {
750 JAMI_WARN(
"[call:%s] Attempting to set invalid invite session [%p]",
753 inviteSession_.reset(
nullptr);
756 JAMI_DBG(
"[call:%s] Set new invite session [%p]", getCallId().c_str(), inviteSession);
762 inviteSession_.reset(inviteSession);
766SIPCall::terminateSipSession(
int status)
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) {
775 auto account = getSIPAccount();
777 sip_utils::addContactHeader(contactHeader_, tdata);
779 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
782 throw std::runtime_error(fmt::format(
"[call:{}] The account owning this call is invalid", getCallId()));
785 ret = pjsip_inv_send_msg(inviteSession_.get(), tdata);
786 if (ret != PJ_SUCCESS)
787 JAMI_ERR(
"[call:%s] Failed to send terminate msg, SIP error (%s)",
789 sip_utils::sip_strerror(ret).c_str());
792 JAMI_ERR(
"[call:%s] Failed to terminate INVITE@%p, SIP error (%s)",
794 inviteSession_.get(),
795 sip_utils::sip_strerror(ret).c_str());
803 std::lock_guard lk {callMutex_};
804 auto account = getSIPAccount();
810 if (not inviteSession_)
812 +
"] Answer: no invite session for this call");
814 if (!inviteSession_->neg) {
815 JAMI_WARN(
"[call:%s] Negotiator is NULL, INVITE received without an SDP",
816 getCallId().c_str());
818 Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
821 pjsip_tx_data* tdata;
822 if (!inviteSession_->last_answer)
823 throw std::runtime_error(
"Should only be called for initial answer");
826 if (pjsip_inv_answer(inviteSession_.get(),
829 !inviteSession_->neg ? sdp_->getLocalSdpSession() : NULL,
832 throw std::runtime_error(
"Unable to init invite request answer (200 OK)");
834 if (contactHeader_.empty()) {
835 throw std::runtime_error(
"Unable to answer with an invalid contact header");
838 JAMI_DBG(
"[call:%s] Answering with contact header: %s",
840 contactHeader_.c_str());
842 sip_utils::addContactHeader(contactHeader_, tdata);
845 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
847 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
849 throw std::runtime_error(
"Unable to send invite request answer (200 OK)");
852 setState(CallState::ACTIVE, ConnectionState::CONNECTED);
856SIPCall::answer(
const std::vector<libjami::MediaMap>& mediaList)
858 std::lock_guard lk {callMutex_};
859 auto account = getSIPAccount();
865 if (not inviteSession_) {
866 JAMI_ERR(
"[call:%s] No invite session for this call", getCallId().c_str());
871 JAMI_ERR(
"[call:%s] No SDP session for this call", getCallId().c_str());
875 auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
877 if (newMediaAttrList.empty() and rtpStreams_.empty()) {
878 JAMI_ERR(
"[call:%s] Media list must not be empty!", getCallId().c_str());
884 if (newMediaAttrList.empty()) {
885 JAMI_DBG(
"[call:%s] Media list is empty, using current media", getCallId().c_str());
886 }
else if (newMediaAttrList.size() != rtpStreams_.size()) {
889 JAMI_ERROR(
"[call:{:s}] Media list size {:d} in answer does not match. Expected {:d}",
891 newMediaAttrList.size(),
896 auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList()
899 JAMI_DBG(
"[call:%s] Answering incoming call with following media:", getCallId().c_str());
900 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
901 auto const& mediaAttr = mediaAttrList.at(idx);
902 JAMI_DEBUG(
"[call:{:s}] Media @{:d} - {:s}", getCallId(), idx, mediaAttr.toString(
true));
906 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
907 updateMediaStream(mediaAttrList[idx], idx);
911 sdp_->processIncomingOffer(mediaAttrList);
913 if (isIceEnabled() and remoteHasValidIceAttributes()) {
917 if (not inviteSession_->neg) {
928 JAMI_WARN(
"[call:%s] No negotiator session, peer sent an empty INVITE (without SDP)",
929 getCallId().c_str());
931 Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
933 generateMediaPorts();
936 if (isIceEnabled()) {
938 sdp_->setActiveRemoteSdpSession(
nullptr);
939 sdp_->setActiveLocalSdpSession(
nullptr);
941 auto opts = account->getIceOptions();
943 auto publicAddr = account->getPublishedIpAddress();
946 opts.accountPublicAddr = publicAddr;
947 if (
auto interfaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
948 publicAddr.getFamily())) {
949 opts.accountLocalAddr = interfaceAddr;
950 if (createIceMediaTransport(
false)
951 and initIceMediaTransport(
true, std::move(opts))) {
952 addLocalIceAttributes();
955 JAMI_WARN(
"[call:%s] Unable to init ICE transport, missing local address",
956 getCallId().c_str());
959 JAMI_WARN(
"[call:%s] Unable to init ICE transport, missing public address",
960 getCallId().c_str());
965 if (!inviteSession_->last_answer)
966 throw std::runtime_error(
"Should only be called for initial answer");
969 pjsip_tx_data* tdata;
970 if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata)
972 throw std::runtime_error(
"Unable to init invite request answer (200 OK)");
974 if (contactHeader_.empty()) {
975 throw std::runtime_error(
"Unable to answer with an invalid contact header");
978 JAMI_DBG(
"[call:%s] Answering with contact header: %s",
980 contactHeader_.c_str());
982 sip_utils::addContactHeader(contactHeader_, tdata);
985 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
987 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
989 throw std::runtime_error(
"Unable to send invite request answer (200 OK)");
992 setState(CallState::ACTIVE, ConnectionState::CONNECTED);
996SIPCall::answerMediaChangeRequest(
const std::vector<libjami::MediaMap>& mediaList,
bool isRemote)
998 std::lock_guard lk {callMutex_};
1000 auto account = getSIPAccount();
1002 JAMI_ERR(
"[call:%s] No account detected", getCallId().c_str());
1006 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
1010 if (not account->isVideoEnabled()) {
1011 for (
auto& mediaAttr : mediaAttrList) {
1012 if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
1013 mediaAttr.enabled_ =
false;
1018 if (mediaAttrList.empty()) {
1019 JAMI_WARN(
"[call:%s] Media list is empty. Ignoring the media change request",
1020 getCallId().c_str());
1025 JAMI_ERR(
"[call:%s] No valid SDP session", getCallId().c_str());
1029 JAMI_DBG(
"[call:%s] Current media", getCallId().c_str());
1031 for (
auto const& rtp : rtpStreams_) {
1032 JAMI_DBG(
"[call:%s] Media @%u: %s",
1033 getCallId().c_str(),
1035 rtp.mediaAttribute_->toString(
true).c_str());
1038 JAMI_DBG(
"[call:%s] Answering to media change request with new media", getCallId().c_str());
1040 for (
auto const& newMediaAttr : mediaAttrList) {
1041 JAMI_DBG(
"[call:%s] Media @%u: %s",
1042 getCallId().c_str(),
1044 newMediaAttr.toString(
true).c_str());
1047 if (!updateAllMediaStreams(mediaAttrList, isRemote))
1050 if (not sdp_->processIncomingOffer(mediaAttrList)) {
1051 JAMI_WARN(
"[call:%s] Unable to process the new offer, ignoring", getCallId().c_str());
1055 if (not sdp_->getRemoteSdpSession()) {
1056 JAMI_ERR(
"[call:%s] No valid remote SDP session", getCallId().c_str());
1060 if (isIceEnabled() and remoteHasValidIceAttributes()) {
1061 JAMI_WARN(
"[call:%s] Requesting a new ICE media", getCallId().c_str());
1062 setupIceResponse(
true);
1065 if (not sdp_->startNegotiation()) {
1066 JAMI_ERR(
"[call:%s] Unable to start media negotiation for a re-invite request",
1067 getCallId().c_str());
1071 if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
1072 JAMI_ERR(
"[call:%s] Unable to start media negotiation for a re-invite request",
1073 getCallId().c_str());
1077 pjsip_tx_data* tdata;
1078 if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, NULL, &tdata) != PJ_SUCCESS) {
1079 JAMI_ERR(
"[call:%s] Unable to init answer to a re-invite request", getCallId().c_str());
1083 if (not contactHeader_.empty()) {
1084 sip_utils::addContactHeader(contactHeader_, tdata);
1088 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
1090 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
1091 JAMI_ERR(
"[call:%s] Unable to send answer to a re-invite request", getCallId().c_str());
1096 JAMI_DBG(
"[call:%s] Successfully answered the media change request", getCallId().c_str());
1100SIPCall::hangup(
int reason)
1102 std::lock_guard lk {callMutex_};
1103 pendingRecord_ =
false;
1104 if (inviteSession_ and inviteSession_->dlg) {
1105 pjsip_route_hdr* route = inviteSession_->dlg->route_set.next;
1106 while (route and route != &inviteSession_->dlg->route_set) {
1108 int printed = pjsip_hdr_print_on(route, buf,
sizeof(buf));
1110 buf[printed] =
'\0';
1111 JAMI_DBG(
"[call:%s] Route header %s", getCallId().c_str(), buf);
1113 route = route->next;
1116 int status = PJSIP_SC_OK;
1119 else if (inviteSession_->state <= PJSIP_INV_STATE_EARLY
1120 and inviteSession_->role != PJSIP_ROLE_UAC)
1121 status = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
1122 else if (inviteSession_->state >= PJSIP_INV_STATE_DISCONNECTED)
1123 status = PJSIP_SC_DECLINE;
1126 terminateSipSession(status);
1131 detachAudioFromConference();
1132 setState(Call::ConnectionState::DISCONNECTED, reason);
1133 dht::ThreadPool::io().run([w = weak()] {
1134 if (
auto shared = w.lock())
1135 shared->removeCall();
1140SIPCall::detachAudioFromConference()
1143 if (
auto conf = getConference()) {
1144 if (
auto mixer = conf->getVideoMixer()) {
1146 mixer->removeAudioOnlySource(getCallId(), stream->streamId());
1156 if (!isIncoming() or getConnectionState() == ConnectionState::CONNECTED or !inviteSession_)
1162 terminateSipSession(PJSIP_SC_DECLINE);
1164 setState(Call::ConnectionState::DISCONNECTED, ECONNABORTED);
1171 auto mod_ua_id = Manager::instance().sipVoIPLink().getModId();
1173 switch (pjsip_evsub_get_state(sub)) {
1174 case PJSIP_EVSUB_STATE_ACCEPTED:
1178 pj_assert(event->type == PJSIP_EVENT_TSX_STATE
1179 && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
1182 case PJSIP_EVSUB_STATE_TERMINATED:
1183 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1186 case PJSIP_EVSUB_STATE_ACTIVE: {
1190 pjsip_rx_data* r_data =
event->body.rx_msg.rdata;
1195 std::string request(pjsip_rx_data_get_info(r_data));
1197 pjsip_status_line status_line = {500, *pjsip_get_status_text(500)};
1199 if (!r_data->msg_info.msg)
1202 if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD
1203 and request.find(
"NOTIFY") != std::string::npos) {
1204 pjsip_msg_body* body = r_data->msg_info.msg->body;
1209 if (pj_stricmp2(&body->content_type.type,
"message")
1210 or pj_stricmp2(&body->content_type.subtype,
"sipfrag"))
1213 if (pjsip_parse_status_line((
char*) body->data, body->len, &status_line) != PJ_SUCCESS)
1217 if (!r_data->msg_info.cid)
1220 auto call =
static_cast<SIPCall*
>(pjsip_evsub_get_mod_data(sub, mod_ua_id));
1224 if (status_line.code / 100 == 2) {
1225 if (call->inviteSession_)
1227 Manager::instance().hangupCall(call->getAccountId(), call->getCallId());
1228 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1234 case PJSIP_EVSUB_STATE_NULL:
1235 case PJSIP_EVSUB_STATE_SENT:
1236 case PJSIP_EVSUB_STATE_PENDING:
1237 case PJSIP_EVSUB_STATE_UNKNOWN:
1244SIPCall::transferCommon(
const pj_str_t* dst)
1246 if (not inviteSession_ or not inviteSession_->dlg)
1249 pjsip_evsub_user xfer_cb;
1250 pj_bzero(&xfer_cb,
sizeof(xfer_cb));
1251 xfer_cb.on_evsub_state = &transfer_client_cb;
1255 if (pjsip_xfer_create_uac(inviteSession_->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
1263 pjsip_evsub_set_mod_data(sub, Manager::instance().sipVoIPLink().getModId(),
this);
1268 pjsip_tx_data* tdata;
1270 if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
1274 if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
1281SIPCall::transfer(
const std::string& to)
1283 auto account = getSIPAccount();
1290 if (Call::isRecording())
1293 std::string toUri = account->getToUri(to);
1294 const pj_str_t dst(CONST_PJ_STR(toUri));
1296 JAMI_DBG(
"[call:%s] Transferring to %.*s", getCallId().c_str(), (
int) dst.slen, dst.ptr);
1298 if (!transferCommon(&dst))
1303SIPCall::attendedTransfer(
const std::string& to)
1305 auto toCall = Manager::instance().callFactory.getCall<
SIPCall>(to);
1309 if (not toCall->inviteSession_ or not toCall->inviteSession_->dlg)
1313 pjsip_uri* uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
1315 char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = {
'<'};
1316 pj_str_t dst = {str_dest_buf, 1};
1318 dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1321 sizeof(str_dest_buf) - 1);
1322 dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
1323 sizeof(str_dest_buf) - dst.slen,
1326 "%%3Bto-tag%%3D%.*s"
1327 "%%3Bfrom-tag%%3D%.*s>",
1328 (
int) target_dlg->call_id->id.slen,
1329 target_dlg->call_id->id.ptr,
1330 (
int) target_dlg->remote.info->tag.slen,
1331 target_dlg->remote.info->tag.ptr,
1332 (
int) target_dlg->local.info->tag.slen,
1333 target_dlg->local.info->tag.ptr);
1335 return transferCommon(&dst);
1342 if (isWaitingForIceAndMedia_) {
1343 holdCb_ = std::move(cb);
1344 remainingRequest_ = Request::HoldingOn;
1348 auto result = hold();
1359 if (getConnectionState() != ConnectionState::CONNECTED) {
1360 JAMI_WARN(
"[call:%s] Not connected, ignoring hold request", getCallId().c_str());
1364 if (not setState(CallState::HOLD)) {
1365 JAMI_WARN(
"[call:%s] Failed to set state to HOLD", getCallId().c_str());
1371 for (
auto& stream : rtpStreams_) {
1372 stream.mediaAttribute_->onHold_ =
true;
1375 if (SIPSessionReinvite() != PJ_SUCCESS) {
1376 JAMI_WARN(
"[call:%s] Reinvite failed", getCallId().c_str());
1381 isWaitingForIceAndMedia_ = (reinvIceMedia_ !=
nullptr);
1383 JAMI_DBG(
"[call:%s] Set state to HOLD", getCallId().c_str());
1391 if (isWaitingForIceAndMedia_) {
1392 JAMI_DBG(
"[call:%s] ICE negotiation in progress. Resume request will be once ICE "
1393 "negotiation completes",
1394 getCallId().c_str());
1395 offHoldCb_ = std::move(cb);
1396 remainingRequest_ = Request::HoldingOff;
1399 JAMI_DBG(
"[call:%s] Resuming the call", getCallId().c_str());
1400 auto result = unhold();
1411 auto account = getSIPAccount();
1417 bool success =
false;
1419 success = internalOffHold([] {});
1420 }
catch (
const SdpException& e) {
1421 JAMI_ERR(
"[call:%s] %s", getCallId().c_str(), e.what());
1422 throw VoipLinkException(
"SDP issue in offhold");
1426 isWaitingForIceAndMedia_ = success and (reinvIceMedia_ !=
nullptr);
1432SIPCall::internalOffHold(
const std::function<
void()>& sdp_cb)
1434 if (getConnectionState() != ConnectionState::CONNECTED) {
1435 JAMI_WARN(
"[call:%s] Not connected, ignoring resume request", getCallId().c_str());
1438 if (not
setState(CallState::ACTIVE))
1444 for (
auto& stream : rtpStreams_) {
1445 stream.mediaAttribute_->onHold_ =
false;
1448 if (SIPSessionReinvite(getMediaAttributeList(),
true) != PJ_SUCCESS) {
1449 JAMI_WARN(
"[call:%s] Resuming hold", getCallId().c_str());
1450 if (isWaitingForIceAndMedia_) {
1451 remainingRequest_ = Request::HoldingOn;
1463SIPCall::switchInput(
const std::string& source)
1465 JAMI_DBG(
"[call:%s] Set selected source to %s", getCallId().c_str(), source.c_str());
1467 for (
auto const& stream : rtpStreams_) {
1468 auto mediaAttr = stream.mediaAttribute_;
1469 mediaAttr->sourceUri_ = source;
1474 bool isRec = Call::isRecording();
1476 if (isWaitingForIceAndMedia_) {
1477 remainingRequest_ = Request::SwitchInput;
1481 if (SIPSessionReinvite(getMediaAttributeList(),
true) == PJ_SUCCESS and reinvIceMedia_) {
1482 isWaitingForIceAndMedia_ =
true;
1486 readyToRecord_ =
false;
1487 pendingRecord_ =
true;
1492SIPCall::peerHungup()
1494 pendingRecord_ =
false;
1499 terminateSipSession(PJSIP_SC_NOT_FOUND);
1500 detachAudioFromConference();
1505SIPCall::carryingDTMFdigits(
char code)
1507 int duration = Manager::instance().voipPreferences.getPulseLength();
1508 char dtmf_body[1000];
1513 ret = snprintf(dtmf_body,
sizeof dtmf_body - 1,
"Signal=16\r\nDuration=%d\r\n", duration);
1515 ret = snprintf(dtmf_body,
1516 sizeof dtmf_body - 1,
1517 "Signal=%c\r\nDuration=%d\r\n",
1523 sendSIPInfo({dtmf_body, (size_t) ret},
"dtmf-relay");
1524 }
catch (
const std::exception& e) {
1525 JAMI_ERR(
"Error sending DTMF: %s", e.what());
1530SIPCall::setVideoOrientation(
int streamIdx,
int rotation)
1532 std::string streamIdPart;
1533 if (streamIdx != -1)
1534 streamIdPart = fmt::format(
"<stream_id>{}</stream_id>", streamIdx);
1535 std::string sip_body =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1536 "<media_control><vc_primitive><to_encoder>"
1537 "<device_orientation="
1538 + std::to_string(-rotation) +
"/>" +
"</to_encoder>" + streamIdPart
1539 +
"</vc_primitive></media_control>";
1541 JAMI_DBG(
"Sending device orientation via SIP INFO %d for stream %u", rotation, streamIdx);
1543 sendSIPInfo(sip_body,
"media_control+xml");
1547SIPCall::sendTextMessage(
const std::map<std::string, std::string>& messages,
const std::string& from)
1552 if (not subcalls_.empty()) {
1553 pendingOutMessages_.emplace_back(messages, from);
1554 for (
auto& c : subcalls_)
1555 c->sendTextMessage(messages, from);
1557 if (inviteSession_) {
1566 if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
1567 JAMI_WARN() << fmt::format(
"[call:{}] Peer does not allow \"{}\" method",
1569 sip_utils::SIP_METHODS::MESSAGE);
1572 JAMI_INFO() << fmt::format(
"[call:{}] Peer's allowed methods: {}",
1574 peerAllowedMethods_);
1578 im::sendSipMessage(inviteSession_.get(), messages);
1581 JAMI_ERR(
"[call:%s] Failed to send SIP text message", getCallId().c_str());
1584 pendingOutMessages_.emplace_back(messages, from);
1585 JAMI_ERR(
"[call:%s] sendTextMessage: no invite session for this call",
1586 getCallId().c_str());
1592SIPCall::removeCall()
1598 std::lock_guard lk {callMutex_};
1599 JAMI_DBG(
"[call:%s] removeCall()", getCallId().c_str());
1601 sdp_->setActiveLocalSdpSession(
nullptr);
1602 sdp_->setActiveRemoteSdpSession(
nullptr);
1607 std::lock_guard lk(transportMtx_);
1608 resetTransport(std::move(iceMedia_));
1609 resetTransport(std::move(reinvIceMedia_));
1613 setSipTransport({});
1617SIPCall::onFailure(
signed cause)
1619 if (setState(CallState::MERROR, ConnectionState::DISCONNECTED, cause)) {
1620 runOnMainThread([w = weak()] {
1621 if (
auto shared = w.lock()) {
1622 auto& call = *shared;
1623 Manager::instance().callFailure(call);
1631SIPCall::onBusyHere()
1633 if (getCallType() == CallType::OUTGOING)
1634 setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED);
1636 setState(CallState::BUSY, ConnectionState::DISCONNECTED);
1638 runOnMainThread([w = weak()] {
1639 if (
auto shared = w.lock()) {
1640 auto& call = *shared;
1641 Manager::instance().callBusy(call);
1650 runOnMainThread([w = weak()] {
1651 if (
auto shared = w.lock()) {
1652 auto& call = *shared;
1653 Manager::instance().peerHungupCall(call);
1660SIPCall::onAnswered()
1662 JAMI_WARN(
"[call:%s] onAnswered()", getCallId().c_str());
1663 runOnMainThread([w = weak()] {
1664 if (
auto shared = w.lock()) {
1665 if (shared->getConnectionState() != ConnectionState::CONNECTED) {
1666 shared->setState(CallState::ACTIVE, ConnectionState::CONNECTED);
1667 if (not shared->isSubcall()) {
1668 Manager::instance().peerAnsweredCall(*shared);
1676SIPCall::sendKeyframe(
int streamIdx)
1679 dht::ThreadPool::computation().run([w = weak(), streamIdx] {
1680 if (
auto sthis = w.lock()) {
1681 JAMI_DBG(
"Handling picture fast update request");
1682 if (streamIdx == -1) {
1683 for (const auto& videoRtp : sthis->getRtpSessionList(MediaType::MEDIA_VIDEO))
1684 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->forceKeyFrame();
1685 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(sthis->rtpStreams_.size())) {
1687 auto& stream = sthis->rtpStreams_[streamIdx];
1688 if (stream.rtpSession_
1689 && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
1690 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)
1699SIPCall::isIceEnabled()
const
1705SIPCall::setPeerUaVersion(std::string_view ua)
1707 if (peerUserAgent_ == ua or ua.empty()) {
1712 if (peerUserAgent_.empty()) {
1713 JAMI_DEBUG(
"[call:{}] Set peer's User-Agent to [{}]",
1716 }
else if (not peerUserAgent_.empty()) {
1719 JAMI_WARNING(
"[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
1725 peerUserAgent_ = ua;
1728 constexpr std::string_view PACK_NAME(PACKAGE_NAME
" ");
1729 auto pos = ua.find(PACK_NAME);
1730 if (pos == std::string_view::npos) {
1732 JAMI_WARN(
"Unable to find the expected package name in peer's User-Agent");
1736 ua = ua.substr(pos + PACK_NAME.length());
1738 std::string_view version;
1742 if (pos != std::string_view::npos) {
1744 version = ua.substr(0, pos);
1748 if (pos != std::string_view::npos) {
1749 version = ua.substr(0, pos);
1753 if (version.empty()) {
1754 JAMI_DEBUG(
"[call:{}] Unable to parse peer's version", getCallId());
1759 if (peerVersion.size() > 4u) {
1760 JAMI_WARNING(
"[call:{}] Unable to parse peer's version", getCallId());
1765 peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion,
1767 if (not peerSupportMultiStream_) {
1768 JAMI_DEBUG(
"Peer's version [{}] does not support multi-stream. "
1769 "Min required version: [{}]",
1775 peerSupportMultiAudioStream_ = Account::meetMinimumRequiredVersion(peerVersion,
1777 if (not peerSupportMultiAudioStream_) {
1778 JAMI_DEBUG(
"Peer's version [{}] does not support multi-audio-stream. "
1779 "Min required version: [{}]",
1785 peerSupportMultiIce_ = Account::meetMinimumRequiredVersion(peerVersion,
1787 if (not peerSupportMultiIce_) {
1788 JAMI_DEBUG(
"Peer's version [{}] does not support more than 2 ICE media streams. "
1789 "Min required version: [{}]",
1795 peerSupportReuseIceInReinv_
1797 if (not peerSupportReuseIceInReinv_) {
1798 JAMI_LOG(
"Peer's version [{:s}] does not support re-invite without ICE renegotiation. "
1799 "Min required version: [{:s}]",
1806SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
1808 std::lock_guard lock {callMutex_};
1809 peerAllowedMethods_ = std::move(methods);
1813SIPCall::isSipMethodAllowedByPeer(
const std::string_view method)
const
1815 std::lock_guard lock {callMutex_};
1817 return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method)
1818 != peerAllowedMethods_.end();
1822SIPCall::onPeerRinging()
1824 JAMI_DBG(
"[call:%s] Peer ringing", getCallId().c_str());
1825 setState(ConnectionState::RINGING);
1829SIPCall::addLocalIceAttributes()
1831 if (not isIceEnabled())
1834 auto iceMedia = getIceMedia();
1837 JAMI_ERR(
"[call:%s] Invalid ICE instance", getCallId().c_str());
1841 auto start = std::chrono::steady_clock::now();
1843 if (not iceMedia->isInitialized()) {
1844 JAMI_DBG(
"[call:%s] Waiting for ICE initialization", getCallId().c_str());
1847 JAMI_ERR(
"[call:%s] ICE initialization timed out", getCallId().c_str());
1853 auto duration = std::chrono::steady_clock::now() - start;
1855 JAMI_WARNING(
"[call:{:s}] ICE initialization time was unexpectedly high ({})",
1857 std::chrono::duration_cast<std::chrono::milliseconds>(duration));
1862 if (not iceMedia->isInitialized()) {
1863 JAMI_ERR(
"[call:%s] ICE session is uninitialized", getCallId().c_str());
1869 if (getState() == Call::CallState::OVER) {
1870 JAMI_WARN(
"[call:%s] The call was terminated while waiting for ICE initialization",
1871 getCallId().c_str());
1875 auto account = getSIPAccount();
1885 JAMI_DBG(
"[call:%s] Add local attributes for ICE instance [%p]",
1886 getCallId().c_str(),
1889 sdp_->addIceAttributes(iceMedia->getLocalAttributes());
1891 if (account->isIceCompIdRfc5245Compliant()) {
1892 unsigned streamIdx = 0;
1893 for (
auto const& stream : rtpStreams_) {
1894 if (not stream.mediaAttribute_->enabled_) {
1896 JAMI_DBG(
"[call:%s] Media [%s] @ %u is disabled, don't add local candidates",
1897 getCallId().c_str(),
1898 stream.mediaAttribute_->toString().c_str(),
1902 JAMI_DBG(
"[call:%s] Add ICE local candidates for media [%s] @ %u",
1903 getCallId().c_str(),
1904 stream.mediaAttribute_->toString().c_str(),
1907 sdp_->addIceCandidates(streamIdx,
1910 if (not rtcpMuxEnabled_) {
1911 sdp_->addIceCandidates(streamIdx,
1919 unsigned compId = 1;
1920 for (
auto const& stream : rtpStreams_) {
1921 if (not stream.mediaAttribute_->enabled_) {
1925 JAMI_DBG(
"[call:%s] Add ICE local candidates for media [%s] @ %u",
1926 getCallId().c_str(),
1927 stream.mediaAttribute_->toString().c_str(),
1930 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1934 if (not rtcpMuxEnabled_) {
1935 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1944std::vector<IceCandidate>
1945SIPCall::getAllRemoteCandidates(dhtnet::IceTransport& transport)
const
1947 std::vector<IceCandidate> rem_candidates;
1948 for (
unsigned mediaIdx = 0; mediaIdx < static_cast<unsigned>(rtpStreams_.size()); mediaIdx++) {
1950 for (
auto& line : sdp_->getIceCandidates(mediaIdx)) {
1951 if (transport.parseIceAttributeLine(mediaIdx, line, cand)) {
1952 JAMI_DBG(
"[call:%s] Add remote ICE candidate: %s",
1953 getCallId().c_str(),
1955 rem_candidates.emplace_back(std::move(cand));
1959 return rem_candidates;
1962std::shared_ptr<SystemCodecInfo>
1963SIPCall::getVideoCodec()
const
1969 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
1970 return videoRtp->getCodec();
1975std::shared_ptr<SystemCodecInfo>
1976SIPCall::getAudioCodec()
const
1979 for (
const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
1980 return audioRtp->getCodec();
1990 stream.mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttr);
1994 if (stream.mediaAttribute_->sourceUri_.empty()) {
1995 if (
auto videoManager = Manager::instance().getVideoManager()) {
1996 stream.mediaAttribute_->sourceUri_
1997 = videoManager->videoDeviceMonitor.getMRLForDefaultDevice();
2002 rtpStreams_.emplace_back(std::move(stream));
2006SIPCall::initMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList)
2008 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
2009 auto const& mediaAttr = mediaAttrList.at(idx);
2010 if (mediaAttr.
type_ != MEDIA_AUDIO && mediaAttr.
type_ != MEDIA_VIDEO) {
2011 JAMI_ERR(
"[call:%s] Unexpected media type %u", getCallId().c_str(), mediaAttr.
type_);
2015 addMediaStream(mediaAttr);
2016 auto& stream = rtpStreams_.back();
2017 createRtpSession(stream);
2019 JAMI_DEBUG(
"[call:{:s}] Added media @{:d}: {:s}",
2022 stream.mediaAttribute_->toString(
true));
2025 JAMI_DEBUG(
"[call:{:s}] Created {:d} media stream(s)", getCallId(), rtpStreams_.size());
2027 return rtpStreams_.size();
2031SIPCall::hasVideo()
const
2034 std::function<bool(
const RtpStream& stream)> videoCheck = [](
auto const& stream) {
2039 return validVideo || validRemoteVideo;
2042 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
2044 return iter != rtpStreams_.end();
2051SIPCall::isCaptureDeviceMuted(
const MediaType& mediaType)
const
2055 std::function<bool(
const RtpStream& stream)> mutedCheck = [&mediaType](
auto const& stream) {
2058 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), mutedCheck);
2059 return iter == rtpStreams_.end();
2063SIPCall::setupNegotiatedMedia()
2065 JAMI_DBG(
"[call:%s] Updating negotiated media", getCallId().c_str());
2067 if (not sipTransport_ or not sdp_) {
2068 JAMI_ERR(
"[call:%s] Call is in an invalid state", getCallId().c_str());
2072 auto slots = sdp_->getMediaSlots();
2073 bool peer_holding {
true};
2076 for (
const auto& slot : slots) {
2078 const auto& local = slot.first;
2079 const auto& remote = slot.second;
2082 if (not local.enabled) {
2083 JAMI_DBG(
"[call:%s] [SDP:slot#%u] The media is disabled, skipping",
2084 getCallId().c_str(),
2089 if (
static_cast<size_t>(streamIdx) >= rtpStreams_.size()) {
2090 throw std::runtime_error(
"Stream index is out-of-range");
2093 auto const& rtpStream = rtpStreams_[streamIdx];
2095 if (not rtpStream.mediaAttribute_) {
2096 throw std::runtime_error(
"Missing media attribute");
2100 rtpStream.mediaAttribute_->enabled_ = local.enabled and remote.enabled;
2102 if (not rtpStream.rtpSession_)
2103 throw std::runtime_error(
"Must have a valid RTP session");
2105 if (local.type != MEDIA_AUDIO && local.type != MEDIA_VIDEO) {
2106 JAMI_ERR(
"[call:%s] Unexpected media type %u", getCallId().c_str(), local.type);
2107 throw std::runtime_error(
"Invalid media attribute");
2110 if (local.type != remote.type) {
2111 JAMI_ERR(
"[call:%s] [SDP:slot#%u] Inconsistent media type between local and remote",
2112 getCallId().c_str(),
2117 if (local.enabled and not local.codec) {
2118 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Missing local codec", getCallId().c_str(), streamIdx);
2122 if (remote.enabled and not remote.codec) {
2123 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Missing remote codec",
2124 getCallId().c_str(),
2129 if (isSrtpEnabled() and local.enabled and not local.crypto) {
2130 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Secure mode but no local crypto attributes. "
2131 "Ignoring the media",
2132 getCallId().c_str(),
2137 if (isSrtpEnabled() and remote.enabled and not remote.crypto) {
2138 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Secure mode but no crypto remote attributes. "
2139 "Ignoring the media",
2140 getCallId().c_str(),
2146 peer_holding &= remote.onHold;
2148 configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote);
2152 if (not isSubcall() and peerHolding_ != peer_holding) {
2153 peerHolding_ = peer_holding;
2154 emitSignal<libjami::CallSignal::PeerHold>(getCallId(), peerHolding_);
2159SIPCall::startAllMedia()
2161 JAMI_DBG(
"[call:%s] Starting all media", getCallId().c_str());
2163 if (not sipTransport_ or not sdp_) {
2164 JAMI_ERR(
"[call:%s] The call is in invalid state", getCallId().c_str());
2168 if (isSrtpEnabled() && not sipTransport_->isSecure()) {
2169 JAMI_WARN(
"[call:%s] Crypto (SRTP) is negotiated over an insecure signaling transport",
2170 getCallId().c_str());
2174 readyToRecord_ =
false;
2177 if (getState() != CallState::HOLD) {
2178 bool iceRunning = isIceRunning();
2179 auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession());
2181 for (
auto& rtpStream : rtpStreams_) {
2182 if (not rtpStream.mediaAttribute_) {
2183 throw std::runtime_error(
"Missing media attribute");
2185 rtpStream.remoteMediaAttribute_ = std::make_shared<MediaAttribute>(remoteMediaList[idx]);
2186 if (rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
2187 rtpStream.rtpSession_->setMuted(rtpStream.remoteMediaAttribute_->muted_, RtpSession::Direction::RECV);
2189 dht::ThreadPool::io().run([
2192 isVideo = rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO,
2194 rtpSession = rtpStream.rtpSession_,
2195 rtpSocketPair = std::make_shared<std::pair<std::unique_ptr<dhtnet::IceSocket>, std::unique_ptr<dhtnet::IceSocket>>>(std::move(rtpStream.rtpSocket_), std::move(rtpStream.rtcpSocket_))
2199 rtpSession->start(std::move(rtpSocketPair->first), std::move(rtpSocketPair->second));
2201 rtpSession->start(nullptr, nullptr);
2204 if (auto call = w.lock())
2205 call->requestKeyframe(idx);
2207 } catch (
const std::exception& e) {
2208 JAMI_ERR(
"[call:%s] Failed to start RTP session %zu: %s",
2209 w.lock() ? w.lock()->getCallId().c_str() :
"unknown", idx, e.what());
2217 isWaitingForIceAndMedia_ =
false;
2218 if (remainingRequest_ != Request::NoRequest) {
2220 switch (remainingRequest_) {
2221 case Request::HoldingOn:
2228 case Request::HoldingOff:
2232 offHoldCb_ =
nullptr;
2235 case Request::SwitchInput:
2236 SIPSessionReinvite();
2241 remainingRequest_ = Request::NoRequest;
2244 mediaRestartRequired_ =
false;
2248 createCallAVStreams();
2253SIPCall::restartMediaSender()
2255 JAMI_DBG(
"[call:%s] Restarting TX media streams", getCallId().c_str());
2256 for (
const auto& rtpSession : getRtpSessionList())
2257 rtpSession->restartSender();
2261SIPCall::stopAllMedia()
2263 JAMI_DBG(
"[call:%s] Stopping all media", getCallId().c_str());
2267 std::lock_guard lk(sinksMtx_);
2268 for (
auto it = callSinksMap_.begin(); it != callSinksMap_.end();) {
2270 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)
2271 ->getVideoReceive();
2273 auto& sink = videoReceive->getSink();
2274 sink->detach(it->second.get());
2278 it = callSinksMap_.erase(it);
2284 std::condition_variable cv;
2285 unsigned int stoppedCount = 0;
2286 unsigned int totalStreams = rtpStreams_.size();
2288 for (
const auto& rtpSession : getRtpSessionList()) {
2289 dht::ThreadPool::io().run([&, rtpSession]() {
2292 }
catch (
const std::exception& e) {
2293 JAMI_ERR(
"Failed to stop RTP session: %s", e.what());
2296 std::lock_guard lk(mtx);
2303 std::unique_lock lk(mtx);
2305 return stoppedCount == totalStreams;
2310 clearCallAVStreams();
2311 std::lock_guard lk(avStreamsMtx_);
2312 Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(
2319SIPCall::muteMedia(
const std::string& mediaType,
bool mute)
2321 auto type = MediaAttribute::stringToMediaType(mediaType);
2323 if (type == MediaType::MEDIA_AUDIO) {
2324 JAMI_WARN(
"[call:%s] %s all audio media",
2325 getCallId().c_str(),
2326 mute ?
"muting " :
"unmuting ");
2328 }
else if (type == MediaType::MEDIA_VIDEO) {
2329 JAMI_WARN(
"[call:%s] %s all video media",
2330 getCallId().c_str(),
2331 mute ?
"muting" :
"unmuting");
2333 JAMI_ERR(
"[call:%s] Invalid media type %s", getCallId().c_str(), mediaType.c_str());
2338 auto mediaList = getMediaAttributeList();
2341 for (
auto& mediaAttr : mediaList) {
2342 if (mediaAttr.
type_ == type) {
2348 requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
2352SIPCall::updateMediaStream(
const MediaAttribute& newMediaAttr,
size_t streamIdx)
2354 assert(streamIdx < rtpStreams_.size());
2356 auto const& rtpStream = rtpStreams_[streamIdx];
2357 assert(rtpStream.rtpSession_);
2359 auto const& mediaAttr = rtpStream.mediaAttribute_;
2362 bool notifyMute =
false;
2369 mediaAttr->
muted_ ?
"muted " :
"unmuted ");
2377 mediaAttr->
muted_ ?
"muting" :
"unmuting",
2385 if (notifyMute and mediaAttr->
type_ == MediaType::MEDIA_AUDIO) {
2386 rtpStream.rtpSession_->setMediaSource(mediaAttr->
sourceUri_);
2387 rtpStream.rtpSession_->setMuted(mediaAttr->
muted_);
2388 sendMuteState(mediaAttr->
muted_);
2389 if (not isSubcall())
2390 emitSignal<libjami::CallSignal::AudioMuted>(getCallId(), mediaAttr->
muted_);
2395 if (notifyMute and mediaAttr->
type_ == MediaType::MEDIA_VIDEO) {
2396 rtpStream.rtpSession_->setMediaSource(mediaAttr->
sourceUri_);
2397 rtpStream.rtpSession_->setMuted(mediaAttr->
muted_);
2399 if (not isSubcall())
2400 emitSignal<libjami::CallSignal::VideoMuted>(getCallId(), mediaAttr->
muted_);
2406SIPCall::updateAllMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList,
bool isRemote)
2408 JAMI_DBG(
"[call:%s] New local media", getCallId().c_str());
2410 if (mediaAttrList.size() > PJ_ICE_MAX_COMP / 2) {
2411 JAMI_DEBUG(
"[call:{:s}] Too many media streams, limit it ({:d} vs {:d})",
2412 getCallId().c_str(),
2413 mediaAttrList.size(),
2419 for (
auto const& newMediaAttr : mediaAttrList) {
2420 JAMI_DBG(
"[call:%s] Media @%u: %s",
2421 getCallId().c_str(),
2423 newMediaAttr.
toString(
true).c_str());
2426 JAMI_DBG(
"[call:%s] Updating local media streams", getCallId().c_str());
2428 for (
auto const& newAttr : mediaAttrList) {
2429 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2431 if (streamIdx < 0) {
2433 addMediaStream(newAttr);
2434 auto& stream = rtpStreams_.back();
2436 stream.mediaAttribute_->muted_ = isRemote ? true : stream.mediaAttribute_->muted_;
2437 createRtpSession(stream);
2438 JAMI_DBG(
"[call:%s] Added a new media stream [%s] @ index %i",
2439 getCallId().c_str(),
2440 stream.mediaAttribute_->label_.c_str(),
2443 updateMediaStream(newAttr, streamIdx);
2447 if (mediaAttrList.size() < rtpStreams_.size()) {
2450 for (
auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2451 auto& stream = rtpStreams_[i];
2452 if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
2453 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)
2457 rtpStreams_.resize(mediaAttrList.size());
2463SIPCall::isReinviteRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2465 if (mediaAttrList.size() != rtpStreams_.size())
2468 for (
auto const& newAttr : mediaAttrList) {
2469 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2471 if (streamIdx < 0) {
2477 if (newAttr.sourceUri_ != rtpStreams_[streamIdx].mediaAttribute_->sourceUri_) {
2482 if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2485 if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) {
2496SIPCall::isNewIceMediaRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2500 if (not peerSupportReuseIceInReinv_)
2504 if (mediaAttrList.size() != rtpStreams_.size())
2507 for (
auto const& newAttr : mediaAttrList) {
2508 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2509 if (streamIdx < 0) {
2513 auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_;
2514 if (newAttr.sourceUri_ != currAttr->sourceUri_) {
2526SIPCall::requestMediaChange(
const std::vector<libjami::MediaMap>& mediaList)
2528 std::lock_guard lk {callMutex_};
2529 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
2530 bool hasFileSharing {
false};
2532 for (
const auto& media : mediaAttrList) {
2533 if (!media.enabled_ || media.sourceUri_.empty())
2539 const auto pos = media.sourceUri_.find(sep);
2540 if (pos == std::string::npos)
2543 const auto prefix = media.sourceUri_.substr(0, pos);
2544 if ((pos + sep.size()) >= media.sourceUri_.size())
2548 hasFileSharing =
true;
2549 mediaPlayerId_ = media.sourceUri_;
2551 createMediaPlayer(mediaPlayerId_);
2556 if (!hasFileSharing) {
2558 closeMediaPlayer(mediaPlayerId_);
2560 mediaPlayerId_ =
"";
2564 auto account = getSIPAccount();
2566 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
2569 if (not account->isVideoEnabled()) {
2570 for (
auto& mediaAttr : mediaAttrList) {
2571 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO) {
2574 JAMI_ERROR(
"[call:{}] New media has video, but it's disabled in the account. "
2575 "Ignoring the change request!",
2585 if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
2586 JAMI_WARNING(
"[call:{}] Peer does not support multi-stream. Media change request ignored",
2593 if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) {
2594 JAMI_WARNING(
"[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored",
2596 for (
auto it = mediaAttrList.begin(); it != mediaAttrList.end();) {
2597 if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) {
2598 it = mediaAttrList.erase(it);
2607 if (!peerSupportMultiIce_) {
2608 if (mediaList.size() > 2)
2609 JAMI_WARNING(
"[call:{}] Peer does not support more than 2 ICE medias. "
2610 "Media change request modified",
2614 auto hasVideo =
false, hasAudio =
false;
2615 for (
auto it = mediaAttrList.rbegin(); it != mediaAttrList.rend(); ++it) {
2616 if (it->type_ == MediaType::MEDIA_VIDEO && !hasVideo) {
2618 videoAttr.
label_ = sip_utils::DEFAULT_VIDEO_STREAMID;
2620 }
else if (it->type_ == MediaType::MEDIA_AUDIO && !hasAudio) {
2622 audioAttr.
label_ = sip_utils::DEFAULT_AUDIO_STREAMID;
2625 if (hasVideo && hasAudio)
2628 mediaAttrList.clear();
2630 mediaAttrList.emplace_back(audioAttr);
2632 mediaAttrList.emplace_back(videoAttr);
2635 if (mediaAttrList.empty()) {
2636 JAMI_ERROR(
"[call:{}] Invalid media change request: new media list is empty", getCallId());
2639 JAMI_DEBUG(
"[call:{}] Requesting media change. List of new media:", getCallId());
2642 for (
auto const& newMediaAttr : mediaAttrList) {
2649 auto needReinvite = isReinviteRequired(mediaAttrList);
2650 auto needNewIce = isNewIceMediaRequired(mediaAttrList);
2652 if (!updateAllMediaStreams(mediaAttrList,
false))
2656 JAMI_DEBUG(
"[call:{}] Media change requires a new negotiation (re-invite)",
2658 requestReinvite(mediaAttrList, needNewIce);
2660 JAMI_DEBUG(
"[call:{}] Media change DOES NOT require a new negotiation (re-invite)",
2662 reportMediaNegotiationStatus();
2668std::vector<std::map<std::string, std::string>>
2669SIPCall::currentMediaList()
const
2671 return MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList());
2674std::vector<MediaAttribute>
2675SIPCall::getMediaAttributeList()
const
2677 std::lock_guard lk {callMutex_};
2678 std::vector<MediaAttribute> mediaList;
2679 mediaList.reserve(rtpStreams_.size());
2680 for (
auto const& stream : rtpStreams_)
2681 mediaList.emplace_back(*stream.mediaAttribute_);
2685std::map<std::string, bool>
2686SIPCall::getAudioStreams()
const
2688 std::map<std::string, bool> audioMedias {};
2689 auto medias = getMediaAttributeList();
2690 for (
const auto& media : medias) {
2692 auto label = fmt::format(
"{}_{}", getCallId(), media.label_);
2693 audioMedias.emplace(label, media.muted_);
2700SIPCall::onMediaNegotiationComplete()
2702 runOnMainThread([w = weak()] {
2703 if (
auto this_ = w.lock()) {
2704 std::lock_guard lk {this_->callMutex_};
2705 JAMI_DBG(
"[call:%s] Media negotiation complete", this_->getCallId().c_str());
2708 if (not this_->inviteSession_
2709 or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED
2710 or not this_->sdp_) {
2720 if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) {
2721 if (not this_->isSubcall()) {
2723 this_->startIceMedia();
2727 if (this_->mediaRestartRequired_) {
2728 this_->setupNegotiatedMedia();
2730 JAMI_WARN(
"[call:%s] ICE media disabled, using default media ports",
2731 this_->getCallId().c_str());
2733 this_->stopAllMedia();
2734 this_->startAllMedia();
2738 this_->reportMediaNegotiationStatus();
2745SIPCall::reportMediaNegotiationStatus()
2748 auto callId = isSubcall() ? parent_->getCallId() : getCallId();
2749 emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
2752 currentMediaList());
2753 auto previousState = isAudioOnly_;
2754 auto newState = !hasVideo();
2756 if (previousState != newState && Call::isRecording()) {
2759 pendingRecord_ =
true;
2761 isAudioOnly_ = newState;
2763 if (pendingRecord_ && readyToRecord_) {
2769SIPCall::startIceMedia()
2771 JAMI_DBG(
"[call:%s] Starting ICE", getCallId().c_str());
2772 auto iceMedia = getIceMedia();
2773 if (not iceMedia or iceMedia->isFailed()) {
2774 JAMI_ERR(
"[call:%s] Media ICE init failed", getCallId().c_str());
2779 if (iceMedia->isStarted()) {
2781 if (iceMedia->isRunning())
2786 if (not iceMedia->isInitialized()) {
2788 waitForIceInit_ =
true;
2795 auto rem_ice_attrs = sdp_->getIceAttributes();
2796 if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
2797 JAMI_ERR(
"[call:%s] Missing remote media ICE attributes", getCallId().c_str());
2801 if (not iceMedia->startIce(rem_ice_attrs, getAllRemoteCandidates(*iceMedia))) {
2802 JAMI_ERR(
"[call:%s] ICE media failed to start", getCallId().c_str());
2808SIPCall::onIceNegoSucceed()
2810 std::lock_guard lk {callMutex_};
2812 JAMI_DBG(
"[call:%s] ICE negotiation succeeded", getCallId().c_str());
2817 if (not inviteSession_ or inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED or not sdp_) {
2818 JAMI_ERR(
"[call:%s] ICE negotiation succeeded, but call is in invalid state",
2819 getCallId().c_str());
2824 setupNegotiatedMedia();
2829 switchToIceReinviteIfNeeded();
2831 for (
unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) {
2833 auto& rtpStream = rtpStreams_[idx];
2834 rtpStream.rtpSocket_ = newIceSocket(compId);
2836 if (not rtcpMuxEnabled_) {
2837 rtpStream.rtcpSocket_ = newIceSocket(compId + 1);
2844 reportMediaNegotiationStatus();
2848SIPCall::checkMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2857 JAMI_DBG(
"[call:%s] Received a media change request", getCallId().c_str());
2859 auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList,
2861 if (remoteMediaAttrList.size() != rtpStreams_.size())
2864 for (
size_t i = 0; i < rtpStreams_.size(); i++) {
2865 if (remoteMediaAttrList[i].type_ != rtpStreams_[i].mediaAttribute_->type_)
2867 if (remoteMediaAttrList[i].enabled_ != rtpStreams_[i].mediaAttribute_->enabled_)
2875SIPCall::handleMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2877 JAMI_DBG(
"[call:%s] Handling media change request", getCallId().c_str());
2879 auto account = getAccount().lock();
2887 if (not checkMediaChangeRequest(remoteMediaList)) {
2888 answerMediaChangeRequest(
2889 MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()));
2893 if (account->isAutoAnswerEnabled()) {
2901 std::vector<libjami::MediaMap> newMediaList;
2902 newMediaList.reserve(remoteMediaList.size());
2903 for (
auto const& stream : rtpStreams_) {
2904 newMediaList.emplace_back(MediaAttribute::toMediaMap(*stream.mediaAttribute_));
2907 assert(remoteMediaList.size() > 0);
2908 if (remoteMediaList.size() > newMediaList.size()) {
2909 for (
auto idx = newMediaList.size(); idx < remoteMediaList.size(); idx++) {
2910 newMediaList.emplace_back(remoteMediaList[idx]);
2913 answerMediaChangeRequest(newMediaList,
true);
2918 emitSignal<libjami::CallSignal::MediaChangeRequested>(getAccountId(),
2924SIPCall::onReceiveReinvite(
const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
2926 JAMI_DBG(
"[call:%s] Received a re-invite", getCallId().c_str());
2928 pj_status_t res = PJ_SUCCESS;
2931 JAMI_ERR(
"SDP session is invalid");
2936 sdp_->setActiveRemoteSdpSession(
nullptr);
2937 sdp_->setActiveLocalSdpSession(
nullptr);
2939 auto acc = getSIPAccount();
2945 Sdp::printSession(offer,
"Remote session (media change request)", SdpDirection::OFFER);
2947 sdp_->setReceivedOffer(offer);
2956 auto const& mediaAttrList = Sdp::getMediaAttributeListFromSdp(offer,
true);
2957 if (mediaAttrList.empty()) {
2958 JAMI_WARN(
"[call:%s] Media list is empty, ignoring", getCallId().c_str());
2966 pjsip_tx_data* tdata =
nullptr;
2967 if (pjsip_inv_initial_answer(inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata)
2969 JAMI_ERR(
"[call:%s] Unable to create answer TRYING", getCallId().c_str());
2973 dht::ThreadPool::io().run([callWkPtr = weak(), mediaAttrList] {
2974 if (
auto call = callWkPtr.lock()) {
2976 auto const& remoteMediaList = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
2977 if (auto conf = call->getConference()) {
2978 conf->handleMediaChangeRequest(call, remoteMediaList);
2980 call->handleMediaChangeRequest(remoteMediaList);
2989SIPCall::onReceiveOfferIn200OK(
const pjmedia_sdp_session* offer)
2991 if (not rtpStreams_.empty()) {
2992 JAMI_ERR(
"[call:%s] Unexpected offer in '200 OK' answer", getCallId().c_str());
2996 auto acc = getSIPAccount();
3007 JAMI_DBG(
"[call:%s] Received an offer in '200 OK' answer", getCallId().c_str());
3009 auto mediaList = Sdp::getMediaAttributeListFromSdp(offer);
3012 if (mediaList.empty()) {
3013 JAMI_WARN(
"[call:%s] Remote media list is empty, ignoring", getCallId().c_str());
3017 Sdp::printSession(offer,
"Remote session (offer in 200 OK answer)", SdpDirection::OFFER);
3020 sdp_->setActiveRemoteSdpSession(
nullptr);
3021 sdp_->setActiveLocalSdpSession(
nullptr);
3023 sdp_->setReceivedOffer(offer);
3027 for (
auto& mediaAttr : mediaList) {
3028 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO and not acc->isVideoEnabled()) {
3033 initMediaStreams(mediaList);
3035 sdp_->processIncomingOffer(mediaList);
3041 if (isIceEnabled() and remoteHasValidIceAttributes()) {
3045 sdp_->startNegotiation();
3047 if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
3048 JAMI_ERR(
"[call:%s] Unable to start media negotiation for a re-invite request",
3049 getCallId().c_str());
3054SIPCall::openPortsUPnP()
3057 JAMI_ERR(
"[call:%s] Current SDP instance is invalid", getCallId().c_str());
3070 JAMI_DBG(
"[call:%s] Opening ports via UPnP for SDP session", getCallId().c_str());
3073 upnp_->reserveMapping(sdp_->getLocalAudioPort(), dhtnet::upnp::PortType::UDP);
3075 upnp_->reserveMapping(sdp_->getLocalAudioControlPort(), dhtnet::upnp::PortType::UDP);
3079 upnp_->reserveMapping(sdp_->getLocalVideoPort(), dhtnet::upnp::PortType::UDP);
3081 upnp_->reserveMapping(sdp_->getLocalVideoControlPort(), dhtnet::upnp::PortType::UDP);
3085std::map<std::string, std::string>
3086SIPCall::getDetails()
const
3088 auto acc = getSIPAccount();
3094 auto details = Call::getDetails();
3098 for (
auto const& stream : rtpStreams_) {
3099 if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
3101 stream.mediaAttribute_->sourceUri_);
3103 if (
auto const& rtpSession = stream.rtpSession_) {
3104 if (
auto codec = rtpSession->getCodec()) {
3108 std::to_string(codec->minBitrate));
3110 std::to_string(codec->maxBitrate));
3111 if (
const auto& curvideoRtpSession
3112 = std::static_pointer_cast<video::VideoRtpSession>(rtpSession)) {
3114 std::to_string(curvideoRtpSession->getVideoBitrateInfo()
3115 .videoBitrateCurrent));
3121 }
else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3122 if (
auto const& rtpSession = stream.rtpSession_) {
3123 if (
auto codec = rtpSession->getCodec()) {
3127 codec->getCodecSpecifications()
3138 if (not peerRegisteredName_.empty())
3142#ifdef ENABLE_CLIENT_CERT
3143 std::lock_guard lk {callMutex_};
3144 if (transport_ and transport_->isSecure()) {
3145 const auto& tlsInfos = transport_->getTlsInfos();
3146 if (tlsInfos.cipher != PJ_TLS_UNKNOWN_CIPHER) {
3147 const auto& cipher = pj_ssl_cipher_name(tlsInfos.cipher);
3152 if (tlsInfos.peerCert) {
3154 auto ca = tlsInfos.peerCert->issuer;
3157 std::ostringstream name_str;
3159 details.emplace(name_str.str(), ca->toString());
3169 if (
auto transport = getIceMedia()) {
3170 if (transport && transport->isRunning())
3177SIPCall::enterConference(std::shared_ptr<Conference> conference)
3179 JAMI_DEBUG(
"[call:{}] Entering conference [{}]",
3181 conference->getConfId());
3184 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3186 auto& rbPool = Manager::instance().getRingBufferPool();
3187 auto medias = getAudioStreams();
3188 for (
const auto& media : medias) {
3189 rbPool.unbindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3191 rbPool.flush(RingBufferPool::DEFAULT_ID);
3195 if (conference->isVideoEnabled())
3196 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3197 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
3201 clearCallAVStreams();
3206SIPCall::exitConference()
3208 std::lock_guard lk {callMutex_};
3209 JAMI_DBG(
"[call:%s] Leaving conference", getCallId().c_str());
3211 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3213 auto& rbPool = Manager::instance().getRingBufferPool();
3214 auto medias = getAudioStreams();
3215 for (
const auto& media : medias) {
3216 if (!media.second) {
3217 rbPool.bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3220 rbPool.flush(RingBufferPool::DEFAULT_ID);
3223 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3224 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->exitConference();
3227 createCallAVStreams();
3233SIPCall::setActiveMediaStream(
const std::string& accountUri,
3234 const std::string& deviceId,
3235 const std::string& streamId,
3238 auto remoteStreamId = streamId;
3241 std::lock_guard lk(sinksMtx_);
3242 const auto& localIt = local2RemoteSinks_.find(streamId);
3243 if (localIt != local2RemoteSinks_.end()) {
3244 remoteStreamId = localIt->second;
3249 if (Call::conferenceProtocolVersion() == 1) {
3250 Json::Value sinkVal;
3251 sinkVal[
"active"] = state;
3252 Json::Value mediasObj;
3253 mediasObj[remoteStreamId] = sinkVal;
3254 Json::Value deviceVal;
3255 deviceVal[
"medias"] = mediasObj;
3256 Json::Value deviceObj;
3257 deviceObj[deviceId] = deviceVal;
3258 Json::Value accountVal;
3259 deviceVal[
"devices"] = deviceObj;
3261 root[accountUri] = deviceVal;
3262 root[
"version"] = 1;
3263 Call::sendConfOrder(root);
3264 }
else if (Call::conferenceProtocolVersion() == 0) {
3266 root[
"activeParticipant"] = accountUri;
3267 Call::sendConfOrder(root);
3273SIPCall::setRotation(
int streamIdx,
int rotation)
3276 dht::ThreadPool::io().run([w = weak(), streamIdx, rotation] {
3277 if (
auto shared = w.lock()) {
3278 std::lock_guard lk {shared->callMutex_};
3279 shared->rotation_ = rotation;
3280 if (streamIdx == -1) {
3282 std::static_pointer_cast<
video::VideoRtpSession>(videoRtp)->setRotation(rotation);
3283 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(shared->rtpStreams_.size())) {
3285 auto& stream = shared->rtpStreams_[streamIdx];
3286 if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
3287 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)
3288 ->setRotation(rotation);
3296SIPCall::createSinks(ConfInfo& infos)
3298 std::lock_guard lk(callMutex_);
3299 std::lock_guard lkS(sinksMtx_);
3303 for (
auto& participant : infos) {
3305 && participant.device
3306 == std::dynamic_pointer_cast<JamiAccount>(account_.lock())->currentDeviceId()) {
3307 for (
auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
3308 if (!iter->mediaAttribute_ || iter->mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3311 auto localVideo = std::static_pointer_cast<video::VideoRtpSession>(iter->rtpSession_)
3312 ->getVideoLocal().get();
3313 auto size = std::make_pair(10, 10);
3315 size = std::make_pair(localVideo->getWidth(), localVideo->getHeight());
3317 const auto& mediaAttribute = iter->mediaAttribute_;
3318 if (participant.sinkId.find(mediaAttribute->label_) != std::string::npos) {
3319 local2RemoteSinks_[mediaAttribute->sourceUri_] = participant.sinkId;
3320 participant.sinkId = mediaAttribute->sourceUri_;
3321 participant.videoMuted = mediaAttribute->muted_;
3322 participant.w =
size.first;
3323 participant.h =
size.second;
3331 std::vector<std::shared_ptr<video::VideoFrameActiveWriter>> sinks;
3333 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)
3334 ->getVideoReceive();
3338 std::static_pointer_cast<video::VideoFrameActiveWriter>(videoReceive->getSink()));
3340 auto conf = conf_.lock();
3341 const auto&
id = conf ? conf->getConfId() : getCallId();
3342 Manager::instance().createSinkClients(
id, infos, sinks, callSinksMap_);
3346std::vector<std::shared_ptr<RtpSession>>
3349 std::vector<std::shared_ptr<RtpSession>> rtpList;
3350 rtpList.reserve(rtpStreams_.size());
3351 for (
auto const& stream : rtpStreams_) {
3352 if (type == MediaType::MEDIA_ALL || stream.rtpSession_->getMediaType() == type)
3353 rtpList.emplace_back(stream.rtpSession_);
3359SIPCall::monitor()
const
3363 auto acc = getSIPAccount();
3368 JAMI_DBG(
"- Call %s with %s:", getCallId().c_str(), getPeerNumber().c_str());
3369 JAMI_DBG(
"\t- Duration: %s", dht::print_duration(getCallDuration()).c_str());
3370 for (
const auto& stream : rtpStreams_)
3371 JAMI_DBG(
"\t- Media: %s", stream.mediaAttribute_->toString(
true).c_str());
3373 if (
auto codec = getVideoCodec())
3374 JAMI_DBG(
"\t- Video codec: %s", codec->name.c_str());
3376 if (
auto transport = getIceMedia()) {
3377 if (transport->isRunning())
3378 JAMI_DBG(
"\t- Media stream(s): %s", transport->link().c_str());
3383SIPCall::toggleRecording()
3385 pendingRecord_ =
true;
3386 if (not readyToRecord_)
3390 if (not Call::isRecording()) {
3391 auto account = getSIPAccount();
3396 auto title = fmt::format(
"Conversation at %TIMESTAMP between {} and {}",
3397 account->getUserUri(),
3399 recorder_->setMetadata(title,
"");
3400 for (
const auto& rtpSession : getRtpSessionList())
3401 rtpSession->initRecorder();
3403 updateRecState(
false);
3405 pendingRecord_ =
false;
3406 auto state = Call::toggleRecording();
3408 updateRecState(state);
3413SIPCall::deinitRecorder()
3415 for (
const auto& rtpSession : getRtpSessionList())
3416 rtpSession->deinitRecorder();
3420SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv)
const noexcept
3426 inv->mod_data[Manager::instance().sipVoIPLink().getModId()] =
nullptr;
3428 pjsip_inv_dec_ref(inv);
3432SIPCall::createIceMediaTransport(
bool isReinvite)
3434 auto mediaTransport = Manager::instance().getIceTransportFactory()->createTransport(getCallId());
3435 if (mediaTransport) {
3436 JAMI_DBG(
"[call:%s] Successfully created media ICE transport [ice:%p]",
3437 getCallId().c_str(),
3438 mediaTransport.get());
3440 JAMI_ERR(
"[call:%s] Failed to create media ICE transport", getCallId().c_str());
3444 setIceMedia(mediaTransport, isReinvite);
3446 return mediaTransport !=
nullptr;
3450SIPCall::initIceMediaTransport(
bool master, std::optional<dhtnet::IceTransportOptions> options)
3452 auto acc = getSIPAccount();
3458 JAMI_DBG(
"[call:%s] Init media ICE transport", getCallId().c_str());
3460 auto const& iceMedia = getIceMedia();
3462 JAMI_ERR(
"[call:%s] Invalid media ICE transport", getCallId().c_str());
3466 auto iceOptions = options == std::nullopt ? acc->getIceOptions() : *options;
3468 auto optOnInitDone = std::move(iceOptions.onInitDone);
3469 auto optOnNegoDone = std::move(iceOptions.onNegoDone);
3470 iceOptions.onInitDone = [w = weak(), cb = std::move(optOnInitDone)](
bool ok) {
3471 runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3472 auto call = w.lock();
3475 if (!ok or !call or !call->waitForIceInit_.exchange(
false))
3478 std::lock_guard lk {call->callMutex_};
3479 auto rem_ice_attrs = call->sdp_->getIceAttributes();
3481 if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty())
3483 call->startIceMedia();
3486 iceOptions.onNegoDone = [w = weak(), cb = std::move(optOnNegoDone)](
bool ok) {
3487 runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3490 if (
auto call = w.lock()) {
3492 std::lock_guard lk {call->callMutex_};
3493 call = call->isSubcall() ? std::dynamic_pointer_cast<SIPCall>(call->parent_) : call;
3495 JAMI_ERR(
"[call:%s] Media ICE negotiation failed", call->getCallId().c_str());
3496 call->onFailure(EIO);
3499 call->onIceNegoSucceed();
3504 iceOptions.master = master;
3505 iceOptions.streamsCount =
static_cast<unsigned>(rtpStreams_.size());
3509 for (
const auto& stream : rtpStreams_) {
3510 iceOptions.qosType.push_back(stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO
3511 ? dhtnet::QosType::VOICE
3512 :
dhtnet::QosType::VIDEO);
3513 iceOptions.qosType.push_back(dhtnet::QosType::CONTROL);
3517 iceMedia->initIceInstance(iceOptions);
3522std::vector<std::string>
3523SIPCall::getLocalIceCandidates(
unsigned compId)
const
3525 std::lock_guard lk(transportMtx_);
3526 if (not iceMedia_) {
3527 JAMI_WARN(
"[call:%s] No media ICE transport", getCallId().c_str());
3530 return iceMedia_->getLocalCandidates(compId);
3534SIPCall::resetTransport(std::shared_ptr<dhtnet::IceTransport>&& transport)
3538 dht::ThreadPool::io().run(
3539 [transport = std::move(transport)]()
mutable { transport.reset(); });
3544SIPCall::merge(Call& call)
3546 JAMI_DBG(
"[call:%s] Merge subcall %s", getCallId().c_str(), call.getCallId().c_str());
3549 auto& subcall =
static_cast<SIPCall&
>(call);
3551 std::lock(callMutex_, subcall.callMutex_);
3552 std::lock_guard lk1 {callMutex_, std::adopt_lock};
3553 std::lock_guard lk2 {subcall.callMutex_, std::adopt_lock};
3554 inviteSession_ = std::move(subcall.inviteSession_);
3556 inviteSession_->mod_data[Manager::instance().sipVoIPLink().getModId()] =
this;
3557 setSipTransport(std::move(subcall.sipTransport_), std::move(subcall.contactHeader_));
3558 sdp_ = std::move(subcall.sdp_);
3559 peerHolding_ = subcall.peerHolding_;
3560 upnp_ = std::move(subcall.upnp_);
3561 localAudioPort_ = subcall.localAudioPort_;
3562 localVideoPort_ = subcall.localVideoPort_;
3563 peerUserAgent_ = subcall.peerUserAgent_;
3564 peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
3565 peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_;
3566 peerSupportMultiIce_ = subcall.peerSupportMultiIce_;
3567 peerAllowedMethods_ = subcall.peerAllowedMethods_;
3568 peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_;
3570 Call::merge(subcall);
3576SIPCall::remoteHasValidIceAttributes()
const
3579 throw std::runtime_error(
"Must have a valid SDP Session");
3582 auto rem_ice_attrs = sdp_->getIceAttributes();
3583 if (rem_ice_attrs.ufrag.empty()) {
3584 JAMI_DBG(
"[call:%s] No ICE username fragment attribute in remote SDP", getCallId().c_str());
3588 if (rem_ice_attrs.pwd.empty()) {
3589 JAMI_DBG(
"[call:%s] No ICE password attribute in remote SDP", getCallId().c_str());
3597SIPCall::setIceMedia(std::shared_ptr<dhtnet::IceTransport> ice,
bool isReinvite)
3599 std::lock_guard lk(transportMtx_);
3602 JAMI_DBG(
"[call:%s] Setting re-invite ICE session [%p]", getCallId().c_str(), ice.get());
3603 resetTransport(std::move(reinvIceMedia_));
3604 reinvIceMedia_ = std::move(ice);
3606 JAMI_DBG(
"[call:%s] Setting ICE session [%p]", getCallId().c_str(), ice.get());
3607 resetTransport(std::move(iceMedia_));
3608 iceMedia_ = std::move(ice);
3613SIPCall::switchToIceReinviteIfNeeded()
3615 std::lock_guard lk(transportMtx_);
3617 if (reinvIceMedia_) {
3618 JAMI_DBG(
"[call:%s] Switching to re-invite ICE session [%p]",
3619 getCallId().c_str(),
3620 reinvIceMedia_.get());
3621 std::swap(reinvIceMedia_, iceMedia_);
3624 resetTransport(std::move(reinvIceMedia_));
3628SIPCall::setupIceResponse(
bool isReinvite)
3630 JAMI_DBG(
"[call:%s] Setup ICE response", getCallId().c_str());
3632 auto account = getSIPAccount();
3637 auto opt = account->getIceOptions();
3641 opt.accountPublicAddr = account->getPublishedIpAddress();
3642 if (opt.accountPublicAddr) {
3643 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
3644 opt.accountPublicAddr.getFamily());
3648 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET);
3649 opt.accountPublicAddr = opt.accountLocalAddr;
3652 if (not opt.accountLocalAddr) {
3653 JAMI_ERR(
"[call:%s] No local address, unable to initialize ICE", getCallId().c_str());
3658 if (not createIceMediaTransport(isReinvite) or not initIceMediaTransport(
false, opt)) {
3659 JAMI_ERR(
"[call:%s] ICE initialization failed", getCallId().c_str());
3668 mediaRestartRequired_ =
true;
3671 addLocalIceAttributes();
3675SIPCall::isIceRunning()
const
3677 std::lock_guard lk(transportMtx_);
3678 return iceMedia_ and iceMedia_->isRunning();
3681std::unique_ptr<dhtnet::IceSocket>
3682SIPCall::newIceSocket(
unsigned compId)
3684 return std::unique_ptr<dhtnet::IceSocket> {
new dhtnet::IceSocket(getIceMedia(), compId)};
3688SIPCall::rtpSetupSuccess()
3690 std::lock_guard lk {setupSuccessMutex_};
3692 readyToRecord_ =
true;
3694 auto previousState = isAudioOnly_;
3695 auto newState = !hasVideo();
3697 if (previousState != newState && Call::isRecording()) {
3700 pendingRecord_ =
true;
3702 isAudioOnly_ = newState;
3704 if (pendingRecord_ && readyToRecord_)
3709SIPCall::peerRecording(
bool state)
3711 auto conference = conf_.lock();
3712 const std::string&
id = conference ? conference->getConfId() : getCallId();
3714 JAMI_WARN(
"[call:%s] Peer is recording", getCallId().c_str());
3715 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
true);
3718 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
false);
3720 peerRecording_ = state;
3721 if (
auto conf = conf_.lock())
3722 conf->updateRecording();
3726SIPCall::peerMuted(
bool muted,
int streamIdx)
3734 if (streamIdx == -1) {
3735 for (
const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
3736 audioRtp->setMuted(muted, RtpSession::Direction::RECV);
3737 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(rtpStreams_.size())) {
3738 auto& stream = rtpStreams_[streamIdx];
3739 if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO)
3740 stream.rtpSession_->setMuted(muted, RtpSession::Direction::RECV);
3744 if (
auto conf = conf_.lock())
3745 conf->updateMuted();
3749SIPCall::peerVoice(
bool voice)
3753 if (
auto conference = conf_.lock()) {
3754 conference->updateVoiceActivity();
const std::string & getCallId() const
Return a reference on the call id.
CallState getState() const
Get the call state of the call (protected by mutex)
std::function< void(bool)> OnReadyCb
CallType
This determines if the call originated from the local user (OUTGOING) or from some remote peer (INCOM...
CallType type_
Type of the call.
const std::string id_
MultiDevice: parent call, nullptr otherwise. Access protected by callMutex_.
std::recursive_mutex callMutex_
Protect every attribute that can be changed by two threads.
static LIBJAMI_TEST_EXPORT Manager & instance()
std::shared_ptr< MediaRecorder > recorder_
void terminateSipSession(int status)
SIPCall(const std::shared_ptr< SIPAccountBase > &account, const std::string &id, Call::CallType type, const std::vector< libjami::MediaMap > &mediaList)
Constructor.
bool isSrtpEnabled() const
void setSipTransport(const std::shared_ptr< SipTransport > &transport, const std::string &contactHdr={})
std::shared_ptr< SIPAccountBase > getSIPAccount() const
std::unique_ptr< pjsip_inv_session, InvSessionDeleter > inviteSession_
std::weak_ptr< const SIPCall > weak() const
void setInviteSession(pjsip_inv_session *inviteSession=nullptr)
#define JAMI_ERROR(formatstr,...)
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
#define JAMI_LOG(formatstr,...)
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
static const std::vector< unsigned > REUSE_ICE_IN_REINVITE_REQUIRED_VERSION
static const std::vector< unsigned > MULTIICE_REQUIRED_VERSION
static constexpr auto MULTIAUDIO_REQUIRED_VERSION_STR
static constexpr int ICE_COMP_ID_RTP
bool closeMediaPlayer(const std::string &id)
void emitSignal(Args... args)
static constexpr std::chrono::milliseconds MS_BETWEEN_2_KEYFRAME_REQUEST
static constexpr std::chrono::milliseconds EXPECTED_ICE_INIT_MAX_TIME
pj_ice_sess_cand IceCandidate
static const std::vector< unsigned > MULTIAUDIO_REQUIRED_VERSION
static constexpr std::chrono::seconds DEFAULT_ICE_NEGO_TIMEOUT
static const std::vector< unsigned > MULTISTREAM_REQUIRED_VERSION
std::vector< unsigned > split_string_to_unsigned(std::string_view str, char delim)
static const std::vector< unsigned > NEW_CONFPROTOCOL_VERSION
std::string_view string_remove_suffix(std::string_view str, char separator)
static constexpr auto NEW_CONFPROTOCOL_VERSION_STR
static void transfer_client_cb(pjsip_evsub *sub, pjsip_event *event)
static constexpr std::chrono::seconds DEFAULT_ICE_INIT_TIMEOUT
static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR
static constexpr auto MULTISTREAM_REQUIRED_VERSION_STR
static constexpr auto MULTIICE_REQUIRED_VERSION_STR
static void runOnMainThread(Callback &&cb)
static constexpr int ICE_COMP_COUNT_PER_STREAM
static constexpr const char SAMPLE_RATE[]
static constexpr char VIDEO_MAX_BITRATE[]
static constexpr char PEER_HOLDING[]
static constexpr char AUDIO_CODEC[]
static constexpr char VIDEO_MIN_BITRATE[]
static constexpr char VIDEO_SOURCE[]
static constexpr char VIDEO_CODEC[]
static constexpr char AUDIO_SAMPLE_RATE[]
static constexpr char VIDEO_BITRATE[]
static constexpr char SOCKETS[]
static constexpr char REGISTERED_NAME[]
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.
Specific VoIPLink for SIP (SIP core for incoming and outgoing events).
Contains information about an AV subject.
std::shared_ptr< MediaAttribute > remoteMediaAttribute_
std::shared_ptr< MediaAttribute > mediaAttribute_
#define jami_tracepoint(...)