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 std::ostringstream msg;
783 msg <<
"[call:" << getCallId().c_str() <<
"] "
784 <<
"The account owning this call is invalid";
785 throw std::runtime_error(msg.str());
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)",
792 sip_utils::sip_strerror(ret).c_str());
795 JAMI_ERR(
"[call:%s] Failed to terminate INVITE@%p, SIP error (%s)",
797 inviteSession_.get(),
798 sip_utils::sip_strerror(ret).c_str());
806 std::lock_guard lk {callMutex_};
807 auto account = getSIPAccount();
813 if (not inviteSession_)
815 +
"] Answer: no invite session for this call");
817 if (!inviteSession_->neg) {
818 JAMI_WARN(
"[call:%s] Negotiator is NULL, INVITE received without an SDP",
819 getCallId().c_str());
821 Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
824 pjsip_tx_data* tdata;
825 if (!inviteSession_->last_answer)
826 throw std::runtime_error(
"Should only be called for initial answer");
829 if (pjsip_inv_answer(inviteSession_.get(),
832 !inviteSession_->neg ? sdp_->getLocalSdpSession() : NULL,
835 throw std::runtime_error(
"Unable to init invite request answer (200 OK)");
837 if (contactHeader_.empty()) {
838 throw std::runtime_error(
"Unable to answer with an invalid contact header");
841 JAMI_DBG(
"[call:%s] Answering with contact header: %s",
843 contactHeader_.c_str());
845 sip_utils::addContactHeader(contactHeader_, tdata);
848 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
850 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
852 throw std::runtime_error(
"Unable to send invite request answer (200 OK)");
855 setState(CallState::ACTIVE, ConnectionState::CONNECTED);
859SIPCall::answer(
const std::vector<libjami::MediaMap>& mediaList)
861 std::lock_guard lk {callMutex_};
862 auto account = getSIPAccount();
868 if (not inviteSession_) {
869 JAMI_ERR(
"[call:%s] No invite session for this call", getCallId().c_str());
874 JAMI_ERR(
"[call:%s] No SDP session for this call", getCallId().c_str());
878 auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
880 if (newMediaAttrList.empty() and rtpStreams_.empty()) {
881 JAMI_ERR(
"[call:%s] Media list must not be empty!", getCallId().c_str());
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()) {
892 JAMI_ERROR(
"[call:{:s}] Media list size {:d} in answer does not match. Expected {:d}",
894 newMediaAttrList.size(),
899 auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList()
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));
909 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
910 updateMediaStream(mediaAttrList[idx], idx);
914 sdp_->processIncomingOffer(mediaAttrList);
916 if (isIceEnabled() and remoteHasValidIceAttributes()) {
920 if (not inviteSession_->neg) {
931 JAMI_WARN(
"[call:%s] No negotiator session, peer sent an empty INVITE (without SDP)",
932 getCallId().c_str());
934 Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
936 generateMediaPorts();
939 if (isIceEnabled()) {
941 sdp_->setActiveRemoteSdpSession(
nullptr);
942 sdp_->setActiveLocalSdpSession(
nullptr);
944 auto opts = account->getIceOptions();
946 auto publicAddr = account->getPublishedIpAddress();
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();
958 JAMI_WARN(
"[call:%s] Unable to init ICE transport, missing local address",
959 getCallId().c_str());
962 JAMI_WARN(
"[call:%s] Unable to init ICE transport, missing public address",
963 getCallId().c_str());
968 if (!inviteSession_->last_answer)
969 throw std::runtime_error(
"Should only be called for initial answer");
972 pjsip_tx_data* tdata;
973 if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata)
975 throw std::runtime_error(
"Unable to init invite request answer (200 OK)");
977 if (contactHeader_.empty()) {
978 throw std::runtime_error(
"Unable to answer with an invalid contact header");
981 JAMI_DBG(
"[call:%s] Answering with contact header: %s",
983 contactHeader_.c_str());
985 sip_utils::addContactHeader(contactHeader_, tdata);
988 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
990 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
992 throw std::runtime_error(
"Unable to send invite request answer (200 OK)");
995 setState(CallState::ACTIVE, ConnectionState::CONNECTED);
999SIPCall::answerMediaChangeRequest(
const std::vector<libjami::MediaMap>& mediaList,
bool isRemote)
1001 std::lock_guard lk {callMutex_};
1003 auto account = getSIPAccount();
1005 JAMI_ERR(
"[call:%s] No account detected", getCallId().c_str());
1009 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
1013 if (not account->isVideoEnabled()) {
1014 for (
auto& mediaAttr : mediaAttrList) {
1015 if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
1016 mediaAttr.enabled_ =
false;
1021 if (mediaAttrList.empty()) {
1022 JAMI_WARN(
"[call:%s] Media list is empty. Ignoring the media change request",
1023 getCallId().c_str());
1028 JAMI_ERR(
"[call:%s] No valid SDP session", getCallId().c_str());
1032 JAMI_DBG(
"[call:%s] Current media", getCallId().c_str());
1034 for (
auto const& rtp : rtpStreams_) {
1035 JAMI_DBG(
"[call:%s] Media @%u: %s",
1036 getCallId().c_str(),
1038 rtp.mediaAttribute_->toString(
true).c_str());
1041 JAMI_DBG(
"[call:%s] Answering to media change request with new media", getCallId().c_str());
1043 for (
auto const& newMediaAttr : mediaAttrList) {
1044 JAMI_DBG(
"[call:%s] Media @%u: %s",
1045 getCallId().c_str(),
1047 newMediaAttr.toString(
true).c_str());
1050 if (!updateAllMediaStreams(mediaAttrList, isRemote))
1053 if (not sdp_->processIncomingOffer(mediaAttrList)) {
1054 JAMI_WARN(
"[call:%s] Unable to process the new offer, ignoring", getCallId().c_str());
1058 if (not sdp_->getRemoteSdpSession()) {
1059 JAMI_ERR(
"[call:%s] No valid remote SDP session", getCallId().c_str());
1063 if (isIceEnabled() and remoteHasValidIceAttributes()) {
1064 JAMI_WARN(
"[call:%s] Requesting a new ICE media", getCallId().c_str());
1065 setupIceResponse(
true);
1068 if (not sdp_->startNegotiation()) {
1069 JAMI_ERR(
"[call:%s] Unable to start media negotiation for a re-invite request",
1070 getCallId().c_str());
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());
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());
1086 if (not contactHeader_.empty()) {
1087 sip_utils::addContactHeader(contactHeader_, tdata);
1091 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
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());
1099 JAMI_DBG(
"[call:%s] Successfully answered the media change request", getCallId().c_str());
1103SIPCall::hangup(
int reason)
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) {
1111 int printed = pjsip_hdr_print_on(route, buf,
sizeof(buf));
1113 buf[printed] =
'\0';
1114 JAMI_DBG(
"[call:%s] Route header %s", getCallId().c_str(), buf);
1116 route = route->next;
1119 int status = PJSIP_SC_OK;
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;
1129 terminateSipSession(status);
1134 detachAudioFromConference();
1135 setState(Call::ConnectionState::DISCONNECTED, reason);
1136 dht::ThreadPool::io().run([w = weak()] {
1137 if (
auto shared = w.lock())
1138 shared->removeCall();
1143SIPCall::detachAudioFromConference()
1146 if (
auto conf = getConference()) {
1147 if (
auto mixer = conf->getVideoMixer()) {
1149 mixer->removeAudioOnlySource(getCallId(), stream->streamId());
1159 if (!isIncoming() or getConnectionState() == ConnectionState::CONNECTED or !inviteSession_)
1165 terminateSipSession(PJSIP_SC_DECLINE);
1167 setState(Call::ConnectionState::DISCONNECTED, ECONNABORTED);
1174 auto mod_ua_id = Manager::instance().sipVoIPLink().getModId();
1176 switch (pjsip_evsub_get_state(sub)) {
1177 case PJSIP_EVSUB_STATE_ACCEPTED:
1181 pj_assert(event->type == PJSIP_EVENT_TSX_STATE
1182 && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
1185 case PJSIP_EVSUB_STATE_TERMINATED:
1186 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1189 case PJSIP_EVSUB_STATE_ACTIVE: {
1193 pjsip_rx_data* r_data =
event->body.rx_msg.rdata;
1198 std::string request(pjsip_rx_data_get_info(r_data));
1200 pjsip_status_line status_line = {500, *pjsip_get_status_text(500)};
1202 if (!r_data->msg_info.msg)
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;
1212 if (pj_stricmp2(&body->content_type.type,
"message")
1213 or pj_stricmp2(&body->content_type.subtype,
"sipfrag"))
1216 if (pjsip_parse_status_line((
char*) body->data, body->len, &status_line) != PJ_SUCCESS)
1220 if (!r_data->msg_info.cid)
1223 auto call =
static_cast<SIPCall*
>(pjsip_evsub_get_mod_data(sub, mod_ua_id));
1227 if (status_line.code / 100 == 2) {
1228 if (call->inviteSession_)
1230 Manager::instance().hangupCall(call->getAccountId(), call->getCallId());
1231 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1237 case PJSIP_EVSUB_STATE_NULL:
1238 case PJSIP_EVSUB_STATE_SENT:
1239 case PJSIP_EVSUB_STATE_PENDING:
1240 case PJSIP_EVSUB_STATE_UNKNOWN:
1247SIPCall::transferCommon(
const pj_str_t* dst)
1249 if (not inviteSession_ or not inviteSession_->dlg)
1252 pjsip_evsub_user xfer_cb;
1253 pj_bzero(&xfer_cb,
sizeof(xfer_cb));
1254 xfer_cb.on_evsub_state = &transfer_client_cb;
1258 if (pjsip_xfer_create_uac(inviteSession_->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
1266 pjsip_evsub_set_mod_data(sub, Manager::instance().sipVoIPLink().getModId(),
this);
1271 pjsip_tx_data* tdata;
1273 if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
1277 if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
1284SIPCall::transfer(
const std::string& to)
1286 auto account = getSIPAccount();
1293 if (Call::isRecording())
1296 std::string toUri = account->getToUri(to);
1297 const pj_str_t dst(CONST_PJ_STR(toUri));
1299 JAMI_DBG(
"[call:%s] Transferring to %.*s", getCallId().c_str(), (
int) dst.slen, dst.ptr);
1301 if (!transferCommon(&dst))
1306SIPCall::attendedTransfer(
const std::string& to)
1308 auto toCall = Manager::instance().callFactory.getCall<
SIPCall>(to);
1312 if (not toCall->inviteSession_ or not toCall->inviteSession_->dlg)
1316 pjsip_uri* uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
1318 char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = {
'<'};
1319 pj_str_t dst = {str_dest_buf, 1};
1321 dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
1324 sizeof(str_dest_buf) - 1);
1325 dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
1326 sizeof(str_dest_buf) - dst.slen,
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);
1338 return transferCommon(&dst);
1345 if (isWaitingForIceAndMedia_) {
1346 holdCb_ = std::move(cb);
1347 remainingRequest_ = Request::HoldingOn;
1351 auto result = hold();
1362 if (getConnectionState() != ConnectionState::CONNECTED) {
1363 JAMI_WARN(
"[call:%s] Not connected, ignoring hold request", getCallId().c_str());
1367 if (not setState(CallState::HOLD)) {
1368 JAMI_WARN(
"[call:%s] Failed to set state to HOLD", getCallId().c_str());
1374 for (
auto& stream : rtpStreams_) {
1375 stream.mediaAttribute_->onHold_ =
true;
1378 if (SIPSessionReinvite() != PJ_SUCCESS) {
1379 JAMI_WARN(
"[call:%s] Reinvite failed", getCallId().c_str());
1384 isWaitingForIceAndMedia_ = (reinvIceMedia_ !=
nullptr);
1386 JAMI_DBG(
"[call:%s] Set state to HOLD", getCallId().c_str());
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;
1402 JAMI_DBG(
"[call:%s] Resuming the call", getCallId().c_str());
1403 auto result = unhold();
1414 auto account = getSIPAccount();
1420 bool success =
false;
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");
1429 isWaitingForIceAndMedia_ = success and (reinvIceMedia_ !=
nullptr);
1435SIPCall::internalOffHold(
const std::function<
void()>& sdp_cb)
1437 if (getConnectionState() != ConnectionState::CONNECTED) {
1438 JAMI_WARN(
"[call:%s] Not connected, ignoring resume request", getCallId().c_str());
1441 if (not
setState(CallState::ACTIVE))
1447 for (
auto& stream : rtpStreams_) {
1448 stream.mediaAttribute_->onHold_ =
false;
1451 if (SIPSessionReinvite(getMediaAttributeList(),
true) != PJ_SUCCESS) {
1452 JAMI_WARN(
"[call:%s] Resuming hold", getCallId().c_str());
1453 if (isWaitingForIceAndMedia_) {
1454 remainingRequest_ = Request::HoldingOn;
1466SIPCall::switchInput(
const std::string& source)
1468 JAMI_DBG(
"[call:%s] Set selected source to %s", getCallId().c_str(), source.c_str());
1470 for (
auto const& stream : rtpStreams_) {
1471 auto mediaAttr = stream.mediaAttribute_;
1472 mediaAttr->sourceUri_ = source;
1477 bool isRec = Call::isRecording();
1479 if (isWaitingForIceAndMedia_) {
1480 remainingRequest_ = Request::SwitchInput;
1484 if (SIPSessionReinvite(getMediaAttributeList(),
true) == PJ_SUCCESS and reinvIceMedia_) {
1485 isWaitingForIceAndMedia_ =
true;
1489 readyToRecord_ =
false;
1490 pendingRecord_ =
true;
1495SIPCall::peerHungup()
1497 pendingRecord_ =
false;
1502 terminateSipSession(PJSIP_SC_NOT_FOUND);
1503 detachAudioFromConference();
1508SIPCall::carryingDTMFdigits(
char code)
1510 int duration = Manager::instance().voipPreferences.getPulseLength();
1511 char dtmf_body[1000];
1516 ret = snprintf(dtmf_body,
sizeof dtmf_body - 1,
"Signal=16\r\nDuration=%d\r\n", duration);
1518 ret = snprintf(dtmf_body,
1519 sizeof dtmf_body - 1,
1520 "Signal=%c\r\nDuration=%d\r\n",
1526 sendSIPInfo({dtmf_body, (size_t) ret},
"dtmf-relay");
1527 }
catch (
const std::exception& e) {
1528 JAMI_ERR(
"Error sending DTMF: %s", e.what());
1533SIPCall::setVideoOrientation(
int streamIdx,
int rotation)
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>";
1544 JAMI_DBG(
"Sending device orientation via SIP INFO %d for stream %u", rotation, streamIdx);
1546 sendSIPInfo(sip_body,
"media_control+xml");
1550SIPCall::sendTextMessage(
const std::map<std::string, std::string>& messages,
const std::string& from)
1555 if (not subcalls_.empty()) {
1556 pendingOutMessages_.emplace_back(messages, from);
1557 for (
auto& c : subcalls_)
1558 c->sendTextMessage(messages, from);
1560 if (inviteSession_) {
1569 if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
1570 JAMI_WARN() << fmt::format(
"[call:{}] Peer does not allow \"{}\" method",
1572 sip_utils::SIP_METHODS::MESSAGE);
1575 JAMI_INFO() << fmt::format(
"[call:{}] Peer's allowed methods: {}",
1577 peerAllowedMethods_);
1581 im::sendSipMessage(inviteSession_.get(), messages);
1584 JAMI_ERR(
"[call:%s] Failed to send SIP text message", getCallId().c_str());
1587 pendingOutMessages_.emplace_back(messages, from);
1588 JAMI_ERR(
"[call:%s] sendTextMessage: no invite session for this call",
1589 getCallId().c_str());
1595SIPCall::removeCall()
1601 std::lock_guard lk {callMutex_};
1602 JAMI_DBG(
"[call:%s] removeCall()", getCallId().c_str());
1604 sdp_->setActiveLocalSdpSession(
nullptr);
1605 sdp_->setActiveRemoteSdpSession(
nullptr);
1610 std::lock_guard lk(transportMtx_);
1611 resetTransport(std::move(iceMedia_));
1612 resetTransport(std::move(reinvIceMedia_));
1616 setSipTransport({});
1620SIPCall::onFailure(
signed cause)
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);
1634SIPCall::onBusyHere()
1636 if (getCallType() == CallType::OUTGOING)
1637 setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED);
1639 setState(CallState::BUSY, ConnectionState::DISCONNECTED);
1641 runOnMainThread([w = weak()] {
1642 if (
auto shared = w.lock()) {
1643 auto& call = *shared;
1644 Manager::instance().callBusy(call);
1653 runOnMainThread([w = weak()] {
1654 if (
auto shared = w.lock()) {
1655 auto& call = *shared;
1656 Manager::instance().peerHungupCall(call);
1663SIPCall::onAnswered()
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);
1679SIPCall::sendKeyframe(
int streamIdx)
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())) {
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_)
1702SIPCall::isIceEnabled()
const
1708SIPCall::setPeerUaVersion(std::string_view ua)
1710 if (peerUserAgent_ == ua or ua.empty()) {
1715 if (peerUserAgent_.empty()) {
1716 JAMI_DEBUG(
"[call:{}] Set peer's User-Agent to [{}]",
1719 }
else if (not peerUserAgent_.empty()) {
1722 JAMI_WARNING(
"[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
1728 peerUserAgent_ = ua;
1731 constexpr std::string_view PACK_NAME(PACKAGE_NAME
" ");
1732 auto pos = ua.find(PACK_NAME);
1733 if (pos == std::string_view::npos) {
1735 JAMI_WARN(
"Unable to find the expected package name in peer's User-Agent");
1739 ua = ua.substr(pos + PACK_NAME.length());
1741 std::string_view version;
1745 if (pos != std::string_view::npos) {
1747 version = ua.substr(0, pos);
1751 if (pos != std::string_view::npos) {
1752 version = ua.substr(0, pos);
1756 if (version.empty()) {
1757 JAMI_DEBUG(
"[call:{}] Unable to parse peer's version", getCallId());
1762 if (peerVersion.size() > 4u) {
1763 JAMI_WARNING(
"[call:{}] Unable to parse peer's version", getCallId());
1768 peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion,
1770 if (not peerSupportMultiStream_) {
1771 JAMI_DEBUG(
"Peer's version [{}] does not support multi-stream. "
1772 "Min required version: [{}]",
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: [{}]",
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: [{}]",
1798 peerSupportReuseIceInReinv_
1800 if (not peerSupportReuseIceInReinv_) {
1801 JAMI_LOG(
"Peer's version [{:s}] does not support re-invite without ICE renegotiation. "
1802 "Min required version: [{:s}]",
1809SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
1811 std::lock_guard lock {callMutex_};
1812 peerAllowedMethods_ = std::move(methods);
1816SIPCall::isSipMethodAllowedByPeer(
const std::string_view method)
const
1818 std::lock_guard lock {callMutex_};
1820 return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method)
1821 != peerAllowedMethods_.end();
1825SIPCall::onPeerRinging()
1827 JAMI_DBG(
"[call:%s] Peer ringing", getCallId().c_str());
1828 setState(ConnectionState::RINGING);
1832SIPCall::addLocalIceAttributes()
1834 if (not isIceEnabled())
1837 auto iceMedia = getIceMedia();
1840 JAMI_ERR(
"[call:%s] Invalid ICE instance", getCallId().c_str());
1844 auto start = std::chrono::steady_clock::now();
1846 if (not iceMedia->isInitialized()) {
1847 JAMI_DBG(
"[call:%s] Waiting for ICE initialization", getCallId().c_str());
1850 JAMI_ERR(
"[call:%s] ICE initialization timed out", getCallId().c_str());
1856 auto duration = std::chrono::steady_clock::now() - start;
1858 JAMI_WARNING(
"[call:{:s}] ICE initialization time was unexpectedly high ({})",
1860 std::chrono::duration_cast<std::chrono::milliseconds>(duration));
1865 if (not iceMedia->isInitialized()) {
1866 JAMI_ERR(
"[call:%s] ICE session is uninitialized", getCallId().c_str());
1872 if (getState() == Call::CallState::OVER) {
1873 JAMI_WARN(
"[call:%s] The call was terminated while waiting for ICE initialization",
1874 getCallId().c_str());
1878 auto account = getSIPAccount();
1888 JAMI_DBG(
"[call:%s] Add local attributes for ICE instance [%p]",
1889 getCallId().c_str(),
1892 sdp_->addIceAttributes(iceMedia->getLocalAttributes());
1894 if (account->isIceCompIdRfc5245Compliant()) {
1895 unsigned streamIdx = 0;
1896 for (
auto const& stream : rtpStreams_) {
1897 if (not stream.mediaAttribute_->enabled_) {
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(),
1905 JAMI_DBG(
"[call:%s] Add ICE local candidates for media [%s] @ %u",
1906 getCallId().c_str(),
1907 stream.mediaAttribute_->toString().c_str(),
1910 sdp_->addIceCandidates(streamIdx,
1913 if (not rtcpMuxEnabled_) {
1914 sdp_->addIceCandidates(streamIdx,
1922 unsigned compId = 1;
1923 for (
auto const& stream : rtpStreams_) {
1924 if (not stream.mediaAttribute_->enabled_) {
1928 JAMI_DBG(
"[call:%s] Add ICE local candidates for media [%s] @ %u",
1929 getCallId().c_str(),
1930 stream.mediaAttribute_->toString().c_str(),
1933 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1937 if (not rtcpMuxEnabled_) {
1938 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1947std::vector<IceCandidate>
1948SIPCall::getAllRemoteCandidates(dhtnet::IceTransport& transport)
const
1950 std::vector<IceCandidate> rem_candidates;
1951 for (
unsigned mediaIdx = 0; mediaIdx < static_cast<unsigned>(rtpStreams_.size()); mediaIdx++) {
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(),
1958 rem_candidates.emplace_back(std::move(cand));
1962 return rem_candidates;
1965std::shared_ptr<SystemCodecInfo>
1966SIPCall::getVideoCodec()
const
1972 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
1973 return videoRtp->getCodec();
1978std::shared_ptr<SystemCodecInfo>
1979SIPCall::getAudioCodec()
const
1982 for (
const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
1983 return audioRtp->getCodec();
1993 stream.mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttr);
1997 if (stream.mediaAttribute_->sourceUri_.empty()) {
1998 if (
auto videoManager = Manager::instance().getVideoManager()) {
1999 stream.mediaAttribute_->sourceUri_
2000 = videoManager->videoDeviceMonitor.getMRLForDefaultDevice();
2005 rtpStreams_.emplace_back(std::move(stream));
2009SIPCall::initMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList)
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_);
2018 addMediaStream(mediaAttr);
2019 auto& stream = rtpStreams_.back();
2020 createRtpSession(stream);
2022 JAMI_DEBUG(
"[call:{:s}] Added media @{:d}: {:s}",
2025 stream.mediaAttribute_->toString(
true));
2028 JAMI_DEBUG(
"[call:{:s}] Created {:d} media stream(s)", getCallId(), rtpStreams_.size());
2030 return rtpStreams_.size();
2034SIPCall::hasVideo()
const
2037 std::function<bool(
const RtpStream& stream)> videoCheck = [](
auto const& stream) {
2042 return validVideo || validRemoteVideo;
2045 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
2047 return iter != rtpStreams_.end();
2054SIPCall::isCaptureDeviceMuted(
const MediaType& mediaType)
const
2058 std::function<bool(
const RtpStream& stream)> mutedCheck = [&mediaType](
auto const& stream) {
2061 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), mutedCheck);
2062 return iter == rtpStreams_.end();
2066SIPCall::setupNegotiatedMedia()
2068 JAMI_DBG(
"[call:%s] Updating negotiated media", getCallId().c_str());
2070 if (not sipTransport_ or not sdp_) {
2071 JAMI_ERR(
"[call:%s] Call is in an invalid state", getCallId().c_str());
2075 auto slots = sdp_->getMediaSlots();
2076 bool peer_holding {
true};
2079 for (
const auto& slot : slots) {
2081 const auto& local = slot.first;
2082 const auto& remote = slot.second;
2085 if (not local.enabled) {
2086 JAMI_DBG(
"[call:%s] [SDP:slot#%u] The media is disabled, skipping",
2087 getCallId().c_str(),
2092 if (
static_cast<size_t>(streamIdx) >= rtpStreams_.size()) {
2093 throw std::runtime_error(
"Stream index is out-of-range");
2096 auto const& rtpStream = rtpStreams_[streamIdx];
2098 if (not rtpStream.mediaAttribute_) {
2099 throw std::runtime_error(
"Missing media attribute");
2103 rtpStream.mediaAttribute_->enabled_ = local.enabled and remote.enabled;
2105 if (not rtpStream.rtpSession_)
2106 throw std::runtime_error(
"Must have a valid RTP session");
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");
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(),
2120 if (local.enabled and not local.codec) {
2121 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Missing local codec", getCallId().c_str(), streamIdx);
2125 if (remote.enabled and not remote.codec) {
2126 JAMI_WARN(
"[call:%s] [SDP:slot#%u] Missing remote codec",
2127 getCallId().c_str(),
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(),
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(),
2149 peer_holding &= remote.onHold;
2151 configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote);
2155 if (not isSubcall() and peerHolding_ != peer_holding) {
2156 peerHolding_ = peer_holding;
2157 emitSignal<libjami::CallSignal::PeerHold>(getCallId(), peerHolding_);
2162SIPCall::startAllMedia()
2164 JAMI_DBG(
"[call:%s] Starting all media", getCallId().c_str());
2166 if (not sipTransport_ or not sdp_) {
2167 JAMI_ERR(
"[call:%s] The call is in invalid state", getCallId().c_str());
2171 if (isSrtpEnabled() && not sipTransport_->isSecure()) {
2172 JAMI_WARN(
"[call:%s] Crypto (SRTP) is negotiated over an insecure signaling transport",
2173 getCallId().c_str());
2177 readyToRecord_ =
false;
2179 for (
auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
2180 if (not iter->mediaAttribute_) {
2181 throw std::runtime_error(
"Missing media attribute");
2186 if (getState() != CallState::HOLD) {
2187 if (isIceRunning()) {
2188 iter->rtpSession_->start(std::move(iter->rtpSocket_), std::move(iter->rtcpSocket_));
2190 iter->rtpSession_->start(
nullptr,
nullptr);
2196 isWaitingForIceAndMedia_ =
false;
2197 if (remainingRequest_ != Request::NoRequest) {
2199 switch (remainingRequest_) {
2200 case Request::HoldingOn:
2207 case Request::HoldingOff:
2211 offHoldCb_ =
nullptr;
2214 case Request::SwitchInput:
2215 SIPSessionReinvite();
2220 remainingRequest_ = Request::NoRequest;
2223 mediaRestartRequired_ =
false;
2227 createCallAVStreams();
2232SIPCall::restartMediaSender()
2234 JAMI_DBG(
"[call:%s] Restarting TX media streams", getCallId().c_str());
2235 for (
const auto& rtpSession : getRtpSessionList())
2236 rtpSession->restartSender();
2240SIPCall::stopAllMedia()
2242 JAMI_DBG(
"[call:%s] Stopping all media", getCallId().c_str());
2246 std::lock_guard lk(sinksMtx_);
2247 for (
auto it = callSinksMap_.begin(); it != callSinksMap_.end();) {
2249 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)
2250 ->getVideoReceive();
2252 auto& sink = videoReceive->getSink();
2253 sink->detach(it->second.get());
2257 it = callSinksMap_.erase(it);
2261 for (
const auto& rtpSession : getRtpSessionList())
2266 clearCallAVStreams();
2267 std::lock_guard lk(avStreamsMtx_);
2268 Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(
2275SIPCall::updateRemoteMedia()
2277 JAMI_DBG(
"[call:%s] Updating remote media", getCallId().c_str());
2279 auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession());
2281 if (remoteMediaList.size() != rtpStreams_.size()) {
2282 JAMI_ERR(
"[call:%s] Media size mismatch!", getCallId().c_str());
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}",
2295 remoteMedia->toString());
2297 if (not remoteMedia->muted_)
2298 requestKeyframe(findRtpStreamIndex(remoteMedia->label_));
2304SIPCall::muteMedia(
const std::string& mediaType,
bool mute)
2306 auto type = MediaAttribute::stringToMediaType(mediaType);
2308 if (type == MediaType::MEDIA_AUDIO) {
2309 JAMI_WARN(
"[call:%s] %s all audio media",
2310 getCallId().c_str(),
2311 mute ?
"muting " :
"unmuting ");
2313 }
else if (type == MediaType::MEDIA_VIDEO) {
2314 JAMI_WARN(
"[call:%s] %s all video media",
2315 getCallId().c_str(),
2316 mute ?
"muting" :
"unmuting");
2318 JAMI_ERR(
"[call:%s] Invalid media type %s", getCallId().c_str(), mediaType.c_str());
2323 auto mediaList = getMediaAttributeList();
2326 for (
auto& mediaAttr : mediaList) {
2327 if (mediaAttr.
type_ == type) {
2333 requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
2337SIPCall::updateMediaStream(
const MediaAttribute& newMediaAttr,
size_t streamIdx)
2339 assert(streamIdx < rtpStreams_.size());
2341 auto const& rtpStream = rtpStreams_[streamIdx];
2342 assert(rtpStream.rtpSession_);
2344 auto const& mediaAttr = rtpStream.mediaAttribute_;
2347 bool notifyMute =
false;
2354 mediaAttr->
muted_ ?
"muted " :
"unmuted ");
2362 mediaAttr->
muted_ ?
"muting" :
"unmuting",
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_);
2380 if (notifyMute and mediaAttr->
type_ == MediaType::MEDIA_VIDEO) {
2381 rtpStream.rtpSession_->setMediaSource(mediaAttr->
sourceUri_);
2382 rtpStream.rtpSession_->setMuted(mediaAttr->
muted_);
2384 if (not isSubcall())
2385 emitSignal<libjami::CallSignal::VideoMuted>(getCallId(), mediaAttr->
muted_);
2391SIPCall::updateAllMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList,
bool isRemote)
2393 JAMI_DBG(
"[call:%s] New local media", getCallId().c_str());
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(),
2404 for (
auto const& newMediaAttr : mediaAttrList) {
2405 JAMI_DBG(
"[call:%s] Media @%u: %s",
2406 getCallId().c_str(),
2408 newMediaAttr.
toString(
true).c_str());
2411 JAMI_DBG(
"[call:%s] Updating local media streams", getCallId().c_str());
2413 for (
auto const& newAttr : mediaAttrList) {
2414 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2416 if (streamIdx < 0) {
2418 addMediaStream(newAttr);
2419 auto& stream = rtpStreams_.back();
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(),
2428 updateMediaStream(newAttr, streamIdx);
2432 if (mediaAttrList.size() < rtpStreams_.size()) {
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_)
2442 rtpStreams_.resize(mediaAttrList.size());
2448SIPCall::isReinviteRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2450 if (mediaAttrList.size() != rtpStreams_.size())
2453 for (
auto const& newAttr : mediaAttrList) {
2454 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2456 if (streamIdx < 0) {
2462 if (newAttr.sourceUri_ != rtpStreams_[streamIdx].mediaAttribute_->sourceUri_) {
2467 if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2470 if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) {
2481SIPCall::isNewIceMediaRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2485 if (not peerSupportReuseIceInReinv_)
2489 if (mediaAttrList.size() != rtpStreams_.size())
2492 for (
auto const& newAttr : mediaAttrList) {
2493 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2494 if (streamIdx < 0) {
2498 auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_;
2499 if (newAttr.sourceUri_ != currAttr->sourceUri_) {
2511SIPCall::requestMediaChange(
const std::vector<libjami::MediaMap>& mediaList)
2513 std::lock_guard lk {callMutex_};
2514 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
2515 bool hasFileSharing {
false};
2517 for (
const auto& media : mediaAttrList) {
2518 if (!media.enabled_ || media.sourceUri_.empty())
2524 const auto pos = media.sourceUri_.find(sep);
2525 if (pos == std::string::npos)
2528 const auto prefix = media.sourceUri_.substr(0, pos);
2529 if ((pos + sep.size()) >= media.sourceUri_.size())
2533 hasFileSharing =
true;
2534 mediaPlayerId_ = media.sourceUri_;
2536 createMediaPlayer(mediaPlayerId_);
2541 if (!hasFileSharing) {
2543 closeMediaPlayer(mediaPlayerId_);
2545 mediaPlayerId_ =
"";
2549 auto account = getSIPAccount();
2551 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
2554 if (not account->isVideoEnabled()) {
2555 for (
auto& mediaAttr : mediaAttrList) {
2556 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO) {
2559 JAMI_ERROR(
"[call:{}] New media has video, but it's disabled in the account. "
2560 "Ignoring the change request!",
2570 if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
2571 JAMI_WARNING(
"[call:{}] Peer does not support multi-stream. Media change request ignored",
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",
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);
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",
2599 auto hasVideo =
false, hasAudio =
false;
2600 for (
auto it = mediaAttrList.rbegin(); it != mediaAttrList.rend(); ++it) {
2601 if (it->type_ == MediaType::MEDIA_VIDEO && !hasVideo) {
2603 videoAttr.
label_ = sip_utils::DEFAULT_VIDEO_STREAMID;
2605 }
else if (it->type_ == MediaType::MEDIA_AUDIO && !hasAudio) {
2607 audioAttr.
label_ = sip_utils::DEFAULT_AUDIO_STREAMID;
2610 if (hasVideo && hasAudio)
2613 mediaAttrList.clear();
2615 mediaAttrList.emplace_back(audioAttr);
2617 mediaAttrList.emplace_back(videoAttr);
2620 if (mediaAttrList.empty()) {
2621 JAMI_ERROR(
"[call:{}] Invalid media change request: new media list is empty", getCallId());
2624 JAMI_DEBUG(
"[call:{}] Requesting media change. List of new media:", getCallId());
2627 for (
auto const& newMediaAttr : mediaAttrList) {
2634 auto needReinvite = isReinviteRequired(mediaAttrList);
2635 auto needNewIce = isNewIceMediaRequired(mediaAttrList);
2637 if (!updateAllMediaStreams(mediaAttrList,
false))
2641 JAMI_DEBUG(
"[call:{}] Media change requires a new negotiation (re-invite)",
2643 requestReinvite(mediaAttrList, needNewIce);
2645 JAMI_DEBUG(
"[call:{}] Media change DOES NOT require a new negotiation (re-invite)",
2647 reportMediaNegotiationStatus();
2653std::vector<std::map<std::string, std::string>>
2654SIPCall::currentMediaList()
const
2656 return MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList());
2659std::vector<MediaAttribute>
2660SIPCall::getMediaAttributeList()
const
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_);
2670std::map<std::string, bool>
2671SIPCall::getAudioStreams()
const
2673 std::map<std::string, bool> audioMedias {};
2674 auto medias = getMediaAttributeList();
2675 for (
const auto& media : medias) {
2677 auto label = fmt::format(
"{}_{}", getCallId(), media.label_);
2678 audioMedias.emplace(label, media.muted_);
2685SIPCall::onMediaNegotiationComplete()
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());
2693 if (not this_->inviteSession_
2694 or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED
2695 or not this_->sdp_) {
2705 if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) {
2706 if (not this_->isSubcall()) {
2708 this_->startIceMedia();
2712 if (this_->mediaRestartRequired_) {
2713 this_->setupNegotiatedMedia();
2715 JAMI_WARN(
"[call:%s] ICE media disabled, using default media ports",
2716 this_->getCallId().c_str());
2718 this_->stopAllMedia();
2719 this_->startAllMedia();
2722 this_->updateRemoteMedia();
2723 this_->reportMediaNegotiationStatus();
2730SIPCall::reportMediaNegotiationStatus()
2733 auto callId = isSubcall() ? parent_->getCallId() : getCallId();
2734 emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
2737 currentMediaList());
2738 auto previousState = isAudioOnly_;
2739 auto newState = !hasVideo();
2741 if (previousState != newState && Call::isRecording()) {
2744 pendingRecord_ =
true;
2746 isAudioOnly_ = newState;
2748 if (pendingRecord_ && readyToRecord_) {
2754SIPCall::startIceMedia()
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());
2764 if (iceMedia->isStarted()) {
2766 if (iceMedia->isRunning())
2771 if (not iceMedia->isInitialized()) {
2773 waitForIceInit_ =
true;
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());
2786 if (not iceMedia->startIce(rem_ice_attrs, getAllRemoteCandidates(*iceMedia))) {
2787 JAMI_ERR(
"[call:%s] ICE media failed to start", getCallId().c_str());
2793SIPCall::onIceNegoSucceed()
2795 std::lock_guard lk {callMutex_};
2797 JAMI_DBG(
"[call:%s] ICE negotiation succeeded", getCallId().c_str());
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());
2809 setupNegotiatedMedia();
2814 switchToIceReinviteIfNeeded();
2816 for (
unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) {
2818 auto& rtpStream = rtpStreams_[idx];
2819 rtpStream.rtpSocket_ = newIceSocket(compId);
2821 if (not rtcpMuxEnabled_) {
2822 rtpStream.rtcpSocket_ = newIceSocket(compId + 1);
2829 updateRemoteMedia();
2830 reportMediaNegotiationStatus();
2834SIPCall::checkMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2843 JAMI_DBG(
"[call:%s] Received a media change request", getCallId().c_str());
2845 auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList,
2847 if (remoteMediaAttrList.size() != rtpStreams_.size())
2850 for (
size_t i = 0; i < rtpStreams_.size(); i++) {
2851 if (remoteMediaAttrList[i].type_ != rtpStreams_[i].mediaAttribute_->type_)
2853 if (remoteMediaAttrList[i].enabled_ != rtpStreams_[i].mediaAttribute_->enabled_)
2861SIPCall::handleMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2863 JAMI_DBG(
"[call:%s] Handling media change request", getCallId().c_str());
2865 auto account = getAccount().lock();
2873 if (not checkMediaChangeRequest(remoteMediaList)) {
2874 answerMediaChangeRequest(
2875 MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()));
2879 if (account->isAutoAnswerEnabled()) {
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_));
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]);
2899 answerMediaChangeRequest(newMediaList,
true);
2904 emitSignal<libjami::CallSignal::MediaChangeRequested>(getAccountId(),
2910SIPCall::onReceiveReinvite(
const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
2912 JAMI_DBG(
"[call:%s] Received a re-invite", getCallId().c_str());
2914 pj_status_t res = PJ_SUCCESS;
2917 JAMI_ERR(
"SDP session is invalid");
2922 sdp_->setActiveRemoteSdpSession(
nullptr);
2923 sdp_->setActiveLocalSdpSession(
nullptr);
2925 auto acc = getSIPAccount();
2931 Sdp::printSession(offer,
"Remote session (media change request)", SdpDirection::OFFER);
2933 sdp_->setReceivedOffer(offer);
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());
2952 pjsip_tx_data* tdata =
nullptr;
2953 if (pjsip_inv_initial_answer(inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata)
2955 JAMI_ERR(
"[call:%s] Unable to create answer TRYING", getCallId().c_str());
2959 dht::ThreadPool::io().run([callWkPtr = weak(), mediaAttrList] {
2960 if (
auto call = callWkPtr.lock()) {
2962 auto const& remoteMediaList = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
2963 if (auto conf = call->getConference()) {
2964 conf->handleMediaChangeRequest(call, remoteMediaList);
2966 call->handleMediaChangeRequest(remoteMediaList);
2975SIPCall::onReceiveOfferIn200OK(
const pjmedia_sdp_session* offer)
2977 if (not rtpStreams_.empty()) {
2978 JAMI_ERR(
"[call:%s] Unexpected offer in '200 OK' answer", getCallId().c_str());
2982 auto acc = getSIPAccount();
2993 JAMI_DBG(
"[call:%s] Received an offer in '200 OK' answer", getCallId().c_str());
2995 auto mediaList = Sdp::getMediaAttributeListFromSdp(offer);
2998 if (mediaList.empty()) {
2999 JAMI_WARN(
"[call:%s] Remote media list is empty, ignoring", getCallId().c_str());
3003 Sdp::printSession(offer,
"Remote session (offer in 200 OK answer)", SdpDirection::OFFER);
3006 sdp_->setActiveRemoteSdpSession(
nullptr);
3007 sdp_->setActiveLocalSdpSession(
nullptr);
3009 sdp_->setReceivedOffer(offer);
3013 for (
auto& mediaAttr : mediaList) {
3014 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO and not acc->isVideoEnabled()) {
3019 initMediaStreams(mediaList);
3021 sdp_->processIncomingOffer(mediaList);
3027 if (isIceEnabled() and remoteHasValidIceAttributes()) {
3031 sdp_->startNegotiation();
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());
3040SIPCall::openPortsUPnP()
3043 JAMI_ERR(
"[call:%s] Current SDP instance is invalid", getCallId().c_str());
3056 JAMI_DBG(
"[call:%s] Opening ports via UPnP for SDP session", getCallId().c_str());
3059 upnp_->reserveMapping(sdp_->getLocalAudioPort(), dhtnet::upnp::PortType::UDP);
3061 upnp_->reserveMapping(sdp_->getLocalAudioControlPort(), dhtnet::upnp::PortType::UDP);
3065 upnp_->reserveMapping(sdp_->getLocalVideoPort(), dhtnet::upnp::PortType::UDP);
3067 upnp_->reserveMapping(sdp_->getLocalVideoControlPort(), dhtnet::upnp::PortType::UDP);
3071std::map<std::string, std::string>
3072SIPCall::getDetails()
const
3074 auto acc = getSIPAccount();
3080 auto details = Call::getDetails();
3084 for (
auto const& stream : rtpStreams_) {
3085 if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
3087 stream.mediaAttribute_->sourceUri_);
3089 if (
auto const& rtpSession = stream.rtpSession_) {
3090 if (
auto codec = rtpSession->getCodec()) {
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));
3107 }
else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3108 if (
auto const& rtpSession = stream.rtpSession_) {
3109 if (
auto codec = rtpSession->getCodec()) {
3113 codec->getCodecSpecifications()
3124 if (not peerRegisteredName_.empty())
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);
3138 if (tlsInfos.peerCert) {
3140 auto ca = tlsInfos.peerCert->issuer;
3143 std::ostringstream name_str;
3145 details.emplace(name_str.str(), ca->toString());
3155 if (
auto transport = getIceMedia()) {
3156 if (transport && transport->isRunning())
3163SIPCall::enterConference(std::shared_ptr<Conference> conference)
3165 JAMI_DEBUG(
"[call:{}] Entering conference [{}]",
3167 conference->getConfId());
3170 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3172 auto& rbPool = Manager::instance().getRingBufferPool();
3173 auto medias = getAudioStreams();
3174 for (
const auto& media : medias) {
3175 rbPool.unbindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3177 rbPool.flush(RingBufferPool::DEFAULT_ID);
3181 if (conference->isVideoEnabled())
3182 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3183 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
3187 clearCallAVStreams();
3192SIPCall::exitConference()
3194 std::lock_guard lk {callMutex_};
3195 JAMI_DBG(
"[call:%s] Leaving conference", getCallId().c_str());
3197 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
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);
3206 rbPool.flush(RingBufferPool::DEFAULT_ID);
3209 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3210 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->exitConference();
3213 createCallAVStreams();
3219SIPCall::setActiveMediaStream(
const std::string& accountUri,
3220 const std::string& deviceId,
3221 const std::string& streamId,
3224 auto remoteStreamId = streamId;
3227 std::lock_guard lk(sinksMtx_);
3228 const auto& localIt = local2RemoteSinks_.find(streamId);
3229 if (localIt != local2RemoteSinks_.end()) {
3230 remoteStreamId = localIt->second;
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;
3247 root[accountUri] = deviceVal;
3248 root[
"version"] = 1;
3249 Call::sendConfOrder(root);
3250 }
else if (Call::conferenceProtocolVersion() == 0) {
3252 root[
"activeParticipant"] = accountUri;
3253 Call::sendConfOrder(root);
3259SIPCall::setRotation(
int streamIdx,
int rotation)
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) {
3268 std::static_pointer_cast<
video::VideoRtpSession>(videoRtp)->setRotation(rotation);
3269 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(shared->rtpStreams_.size())) {
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);
3282SIPCall::createSinks(ConfInfo& infos)
3284 std::lock_guard lk(callMutex_);
3285 std::lock_guard lkS(sinksMtx_);
3289 for (
auto& participant : infos) {
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) {
3297 auto localVideo = std::static_pointer_cast<video::VideoRtpSession>(iter->rtpSession_)
3298 ->getVideoLocal().get();
3299 auto size = std::make_pair(10, 10);
3301 size = std::make_pair(localVideo->getWidth(), localVideo->getHeight());
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;
3317 std::vector<std::shared_ptr<video::VideoFrameActiveWriter>> sinks;
3319 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)
3320 ->getVideoReceive();
3324 std::static_pointer_cast<video::VideoFrameActiveWriter>(videoReceive->getSink()));
3326 auto conf = conf_.lock();
3327 const auto&
id = conf ? conf->getConfId() : getCallId();
3328 Manager::instance().createSinkClients(
id, infos, sinks, callSinksMap_);
3332std::vector<std::shared_ptr<RtpSession>>
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_);
3345SIPCall::monitor()
const
3349 auto acc = getSIPAccount();
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());
3359 if (
auto codec = getVideoCodec())
3360 JAMI_DBG(
"\t- Video codec: %s", codec->name.c_str());
3362 if (
auto transport = getIceMedia()) {
3363 if (transport->isRunning())
3364 JAMI_DBG(
"\t- Media stream(s): %s", transport->link().c_str());
3369SIPCall::toggleRecording()
3371 pendingRecord_ =
true;
3372 if (not readyToRecord_)
3376 if (not Call::isRecording()) {
3377 auto account = getSIPAccount();
3382 auto title = fmt::format(
"Conversation at %TIMESTAMP between {} and {}",
3383 account->getUserUri(),
3385 recorder_->setMetadata(title,
"");
3386 for (
const auto& rtpSession : getRtpSessionList())
3387 rtpSession->initRecorder();
3389 updateRecState(
false);
3391 pendingRecord_ =
false;
3392 auto state = Call::toggleRecording();
3394 updateRecState(state);
3399SIPCall::deinitRecorder()
3401 for (
const auto& rtpSession : getRtpSessionList())
3402 rtpSession->deinitRecorder();
3406SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv)
const noexcept
3412 inv->mod_data[Manager::instance().sipVoIPLink().getModId()] =
nullptr;
3414 pjsip_inv_dec_ref(inv);
3418SIPCall::createIceMediaTransport(
bool isReinvite)
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());
3426 JAMI_ERR(
"[call:%s] Failed to create media ICE transport", getCallId().c_str());
3430 setIceMedia(mediaTransport, isReinvite);
3432 return mediaTransport !=
nullptr;
3436SIPCall::initIceMediaTransport(
bool master, std::optional<dhtnet::IceTransportOptions> options)
3438 auto acc = getSIPAccount();
3444 JAMI_DBG(
"[call:%s] Init media ICE transport", getCallId().c_str());
3446 auto const& iceMedia = getIceMedia();
3448 JAMI_ERR(
"[call:%s] Invalid media ICE transport", getCallId().c_str());
3452 auto iceOptions = options == std::nullopt ? acc->getIceOptions() : *options;
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();
3461 if (!ok or !call or !call->waitForIceInit_.exchange(
false))
3464 std::lock_guard lk {call->callMutex_};
3465 auto rem_ice_attrs = call->sdp_->getIceAttributes();
3467 if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty())
3469 call->startIceMedia();
3472 iceOptions.onNegoDone = [w = weak(), cb = std::move(optOnNegoDone)](
bool ok) {
3473 runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3476 if (
auto call = w.lock()) {
3478 std::lock_guard lk {call->callMutex_};
3479 call = call->isSubcall() ? std::dynamic_pointer_cast<SIPCall>(call->parent_) : call;
3481 JAMI_ERR(
"[call:%s] Media ICE negotiation failed", call->getCallId().c_str());
3482 call->onFailure(EIO);
3485 call->onIceNegoSucceed();
3490 iceOptions.master = master;
3491 iceOptions.streamsCount =
static_cast<unsigned>(rtpStreams_.size());
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);
3503 iceMedia->initIceInstance(iceOptions);
3508std::vector<std::string>
3509SIPCall::getLocalIceCandidates(
unsigned compId)
const
3511 std::lock_guard lk(transportMtx_);
3512 if (not iceMedia_) {
3513 JAMI_WARN(
"[call:%s] No media ICE transport", getCallId().c_str());
3516 return iceMedia_->getLocalCandidates(compId);
3520SIPCall::resetTransport(std::shared_ptr<dhtnet::IceTransport>&& transport)
3524 dht::ThreadPool::io().run(
3525 [transport = std::move(transport)]()
mutable { transport.reset(); });
3530SIPCall::merge(Call& call)
3532 JAMI_DBG(
"[call:%s] Merge subcall %s", getCallId().c_str(), call.getCallId().c_str());
3535 auto& subcall =
static_cast<SIPCall&
>(call);
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_);
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_;
3556 Call::merge(subcall);
3562SIPCall::remoteHasValidIceAttributes()
const
3565 throw std::runtime_error(
"Must have a valid SDP Session");
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());
3574 if (rem_ice_attrs.pwd.empty()) {
3575 JAMI_DBG(
"[call:%s] No ICE password attribute in remote SDP", getCallId().c_str());
3583SIPCall::setIceMedia(std::shared_ptr<dhtnet::IceTransport> ice,
bool isReinvite)
3585 std::lock_guard lk(transportMtx_);
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);
3592 JAMI_DBG(
"[call:%s] Setting ICE session [%p]", getCallId().c_str(), ice.get());
3593 resetTransport(std::move(iceMedia_));
3594 iceMedia_ = std::move(ice);
3599SIPCall::switchToIceReinviteIfNeeded()
3601 std::lock_guard lk(transportMtx_);
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_);
3610 resetTransport(std::move(reinvIceMedia_));
3614SIPCall::setupIceResponse(
bool isReinvite)
3616 JAMI_DBG(
"[call:%s] Setup ICE response", getCallId().c_str());
3618 auto account = getSIPAccount();
3623 auto opt = account->getIceOptions();
3627 opt.accountPublicAddr = account->getPublishedIpAddress();
3628 if (opt.accountPublicAddr) {
3629 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
3630 opt.accountPublicAddr.getFamily());
3634 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET);
3635 opt.accountPublicAddr = opt.accountLocalAddr;
3638 if (not opt.accountLocalAddr) {
3639 JAMI_ERR(
"[call:%s] No local address, unable to initialize ICE", getCallId().c_str());
3644 if (not createIceMediaTransport(isReinvite) or not initIceMediaTransport(
false, opt)) {
3645 JAMI_ERR(
"[call:%s] ICE initialization failed", getCallId().c_str());
3654 mediaRestartRequired_ =
true;
3657 addLocalIceAttributes();
3661SIPCall::isIceRunning()
const
3663 std::lock_guard lk(transportMtx_);
3664 return iceMedia_ and iceMedia_->isRunning();
3667std::unique_ptr<dhtnet::IceSocket>
3668SIPCall::newIceSocket(
unsigned compId)
3670 return std::unique_ptr<dhtnet::IceSocket> {
new dhtnet::IceSocket(getIceMedia(), compId)};
3674SIPCall::rtpSetupSuccess()
3676 std::lock_guard lk {setupSuccessMutex_};
3678 readyToRecord_ =
true;
3680 auto previousState = isAudioOnly_;
3681 auto newState = !hasVideo();
3683 if (previousState != newState && Call::isRecording()) {
3686 pendingRecord_ =
true;
3688 isAudioOnly_ = newState;
3690 if (pendingRecord_ && readyToRecord_)
3695SIPCall::peerRecording(
bool state)
3697 auto conference = conf_.lock();
3698 const std::string&
id = conference ? conference->getConfId() : getCallId();
3700 JAMI_WARN(
"[call:%s] Peer is recording", getCallId().c_str());
3701 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
true);
3704 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
false);
3706 peerRecording_ = state;
3707 if (
auto conf = conf_.lock())
3708 conf->updateRecording();
3712SIPCall::peerMuted(
bool muted,
int streamIdx)
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);
3730 if (
auto conf = conf_.lock())
3731 conf->updateMuted();
3735SIPCall::peerVoice(
bool voice)
3739 if (
auto conference = conf_.lock()) {
3740 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(...)