36#include "pjsip-ua/sip_inv.h"
47#include <libavutil/display.h>
52#include <dhtnet/upnp/upnp_control.h>
53#include <dhtnet/ice_transport_factory.h>
55#include <opendht/crypto.h>
56#include <opendht/thread_pool.h>
57#include <fmt/ranges.h>
72 return DeviceParams {};
98 const std::string& callId,
100 const std::vector<libjami::MediaMap>&
mediaList)
103 , enableIce_(
account->isIceForMediaEnabled())
104 , srtpEnabled_(
account->isSrtpEnabled())
109 upnp_ = std::make_shared<dhtnet::upnp::Controller>(
Manager::instance().upnpContext());
124 JAMI_WARNING(
"[call:{}] No media offered in the incoming invite. An offer will be provided in "
133 JAMI_DEBUG(
"[call:{:s}] Create a new [{:s}] SIP call with {:d} media",
155SIPCall::findRtpStreamIndex(
const std::string&
label)
const
157 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), [&
label](
const RtpStream&
rtp) {
158 return label == rtp.mediaAttribute_->label_;
162 if (
iter != rtpStreams_.end())
163 return static_cast<int>(std::distance(rtpStreams_.begin(),
iter));
170SIPCall::createRtpSession(RtpStream& stream)
172 if (
not stream.mediaAttribute_)
173 throw std::runtime_error(
"Missing media attribute");
178 stream.rtpSession_ = std::make_shared<AudioRtpSession>(
id_, streamId,
recorder_);
183 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation_);
187 throw std::runtime_error(
"Unsupported media type");
191 if (
not stream.rtpSession_)
192 throw std::runtime_error(
"Failed to create RTP session");
197SIPCall::configureRtpSession(
const std::shared_ptr<RtpSession>&
rtpSession,
198 const std::shared_ptr<MediaAttribute>&
mediaAttr,
202 JAMI_DEBUG(
"[call:{}] Configuring [{}] RTP session",
207 throw std::runtime_error(
"Must have a valid RTP session");
210 auto new_mtu = sipTransport_->getTlsMtu();
221 dht::ThreadPool::io().run([w = std::move(w)] {
238 dht::ThreadPool::io().run([w = std::move(w),
streamIdx] {
255SIPCall::setupVoiceCallback(
const std::shared_ptr<RtpSession>&
rtpSession)
266 std::string streamId =
"";
269 if (auto* videoManager = Manager::instance().getVideoManager()) {
270 if (not videoManager->videoDeviceMonitor.getDeviceList().empty()) {
272 streamId = sip_utils::streamId(
"", sip_utils::DEFAULT_VIDEO_STREAMID);
284 conference->setVoiceActivity(streamId, voice);
289 thisPtr->sendVoiceActivity(
"-1", voice);
294 JAMI_ERROR(
"Voice activity callback unable to lock weak ptr to SIPCall");
300std::shared_ptr<SIPAccountBase>
301SIPCall::getSIPAccount()
const
303 return std::static_pointer_cast<SIPAccountBase>(getAccount().lock());
308SIPCall::createCallAVStreams()
312 if (std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->hasConference()) {
313 clearCallAVStreams();
319 auto baseId = getCallId();
320 auto mediaMap = [](
const std::shared_ptr<jami::MediaFrame>& m) -> AVFrame* {
324 std::lock_guard lk(avStreamsMtx_);
325 for (
const auto& rtpSession : getRtpSessionList()) {
326 auto isVideo = rtpSession->getMediaType() == MediaType::MEDIA_VIDEO;
328 StreamData previewStreamData {baseId,
false, streamType, getPeerNumber(), getAccountId()};
329 StreamData receiveStreamData {baseId,
true, streamType, getPeerNumber(), getAccountId()};
333 auto videoRtp = std::static_pointer_cast<video::VideoRtpSession>(rtpSession);
334 if (
auto& videoPreview = videoRtp->getVideoLocal())
335 createCallAVStream(previewStreamData, *videoPreview, std::make_shared<MediaStreamSubject>(mediaMap));
337 if (
auto& videoReceive = videoRtp->getVideoReceive())
338 createCallAVStream(receiveStreamData, *videoReceive, std::make_shared<MediaStreamSubject>(mediaMap));
341 auto audioRtp = std::static_pointer_cast<AudioRtpSession>(rtpSession);
343 if (
auto& localAudio = audioRtp->getAudioLocal())
344 createCallAVStream(previewStreamData, *localAudio, std::make_shared<MediaStreamSubject>(mediaMap));
346 if (
auto& audioReceive = audioRtp->getAudioReceive())
347 createCallAVStream(receiveStreamData,
348 (AVMediaStream&) *audioReceive,
349 std::make_shared<MediaStreamSubject>(mediaMap));
357SIPCall::createCallAVStream(
const StreamData& streamData,
358 AVMediaStream& streamSource,
359 const std::shared_ptr<MediaStreamSubject>& mediaStreamSubject)
361 const std::string AVStreamId = streamData.
id + std::to_string(
static_cast<int>(streamData.
type))
363 auto it = callAVStreams.find(AVStreamId);
364 if (it != callAVStreams.end())
366 it = callAVStreams.insert(it, {AVStreamId, mediaStreamSubject});
367 streamSource.attachPriorityObserver(it->second);
368 jami::Manager::instance().getJamiPluginManager().getCallServicesManager().createAVSubject(streamData, it->second);
372SIPCall::clearCallAVStreams()
374 std::lock_guard lk(avStreamsMtx_);
375 callAVStreams.clear();
380SIPCall::setCallMediaLocal()
382 if (localAudioPort_ == 0
384 || localVideoPort_ == 0
387 generateMediaPorts();
391SIPCall::generateMediaPorts()
393 auto account = getSIPAccount();
395 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
404 const unsigned callLocalAudioPort = account->generateAudioPort();
405 if (localAudioPort_ != 0)
406 account->releasePort(localAudioPort_);
407 localAudioPort_ = callLocalAudioPort;
408 sdp_->setLocalPublishedAudioPorts(callLocalAudioPort, rtcpMuxEnabled_ ? 0 : callLocalAudioPort + 1);
412 const unsigned int callLocalVideoPort = account->generateVideoPort();
413 if (localVideoPort_ != 0)
414 account->releasePort(localVideoPort_);
416 assert(localAudioPort_ != callLocalVideoPort);
417 localVideoPort_ = callLocalVideoPort;
418 sdp_->setLocalPublishedVideoPorts(callLocalVideoPort, rtcpMuxEnabled_ ? 0 : callLocalVideoPort + 1);
423SIPCall::getContactHeader()
const
425 return contactHeader_;
429SIPCall::setSipTransport(
const std::shared_ptr<SipTransport>& transport,
const std::string& contactHdr)
431 if (transport != sipTransport_) {
432 JAMI_DEBUG(
"[call:{}] Setting transport to [{}]", getCallId(), fmt::ptr(transport.get()));
435 sipTransport_ = transport;
436 contactHeader_ = contactHdr;
443 if (contactHeader_.empty()) {
444 JAMI_WARNING(
"[call:{}] Contact header is empty", getCallId());
447 if (isSrtpEnabled() and not sipTransport_->isSecure()) {
448 JAMI_WARNING(
"[call:{}] Crypto (SRTP) is negotiated over an unencrypted signaling channel", getCallId());
451 if (not isSrtpEnabled() and sipTransport_->isSecure()) {
452 JAMI_WARNING(
"[call:{}] The signaling channel is encrypted but the media is unencrypted", getCallId());
455 const auto list_id =
reinterpret_cast<uintptr_t
>(
this);
456 sipTransport_->removeStateListener(list_id);
460 ->addStateListener(list_id, [wthis_ = weak()](pjsip_transport_state state,
const pjsip_transport_state_info*) {
461 if (
auto this_ = wthis_.lock()) {
462 JAMI_DEBUG(
"[call:{}] SIP transport state [{}] - connection state [{}]",
464 static_cast<int>(state),
465 static_cast<unsigned>(this_->getConnectionState()));
468 auto isAlive = SipTransport::isAlive(state);
469 if (not isAlive and this_->getConnectionState() != ConnectionState::DISCONNECTED) {
470 JAMI_WARNING(
"[call:{}] Ending call because underlying SIP transport was closed",
472 this_->stopAllMedia();
473 this_->detachAudioFromConference();
474 this_->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
481SIPCall::requestReinvite(
const std::vector<MediaAttribute>& mediaAttrList,
bool needNewIce)
483 JAMI_DEBUG(
"[call:{}] Sending a SIP re-invite to request media change", getCallId());
485 if (isWaitingForIceAndMedia_) {
486 remainingRequest_ = Request::SwitchInput;
488 if (SIPSessionReinvite(mediaAttrList, needNewIce) == PJ_SUCCESS and reinvIceMedia_) {
489 isWaitingForIceAndMedia_ =
true;
499SIPCall::SIPSessionReinvite(
const std::vector<MediaAttribute>& mediaAttrList,
bool needNewIce)
501 assert(not mediaAttrList.empty());
503 std::lock_guard lk {callMutex_};
506 if (not inviteSession_ or inviteSession_->invite_tsx)
509 JAMI_DEBUG(
"[call:{}] Preparing and sending a re-invite (state={})",
511 pjsip_inv_state_name(inviteSession_->state));
512 JAMI_DEBUG(
"[call:{}] New ICE required for this re-invite: [{}]", getCallId(), needNewIce ?
"Yes" :
"No");
516 generateMediaPorts();
519 sdp_->setActiveRemoteSdpSession(
nullptr);
520 sdp_->setActiveLocalSdpSession(
nullptr);
522 auto acc = getSIPAccount();
524 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
528 if (not sdp_->createOffer(mediaAttrList))
531 if (isIceEnabled() and needNewIce) {
532 if (not createIceMediaTransport(
true) or not initIceMediaTransport(
true)) {
535 addLocalIceAttributes();
537 mediaRestartRequired_ =
true;
540 pjsip_tx_data* tdata;
541 auto* local_sdp = sdp_->getLocalSdpSession();
542 auto result = pjsip_inv_reinvite(inviteSession_.get(),
nullptr, local_sdp, &tdata);
543 if (result == PJ_SUCCESS) {
548 sip_utils::addUserAgentHeader(acc->getUserAgentName(), tdata);
550 result = pjsip_inv_send_msg(inviteSession_.get(), tdata);
551 if (result == PJ_SUCCESS)
553 JAMI_ERROR(
"[call:{}] Failed to send REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
555 pjsip_inv_cancel_reinvite(inviteSession_.get(), &tdata);
557 JAMI_ERROR(
"[call:{}] Failed to create REINVITE msg (pjsip: {})", getCallId(), sip_utils::sip_strerror(result));
563SIPCall::SIPSessionReinvite()
565 auto mediaList = getMediaAttributeList();
566 return SIPSessionReinvite(mediaList, isNewIceMediaRequired(mediaList));
570SIPCall::sendSIPInfo(std::string_view body, std::string_view subtype)
572 std::lock_guard lk {callMutex_};
573 if (not inviteSession_ or not inviteSession_->dlg)
576 constexpr pj_str_t methodName = CONST_PJ_STR(
"INFO");
577 constexpr pj_str_t type = CONST_PJ_STR(
"application");
580 pjsip_method_init_np(&method, (pj_str_t*) &methodName);
583 pjsip_tx_data* tdata;
584 if (pjsip_dlg_create_request(inviteSession_->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
585 JAMI_ERROR(
"[call:{}] Unable to create dialog", getCallId());
590 pj_str_t content = CONST_PJ_STR(body);
591 pj_str_t pj_subtype = CONST_PJ_STR(subtype);
592 tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
593 if (tdata->msg->body == NULL)
594 pjsip_tx_data_dec_ref(tdata);
596 pjsip_dlg_send_request(inviteSession_->dlg, tdata, Manager::instance().sipVoIPLink().getModId(), NULL);
600SIPCall::updateRecState(
bool state)
602 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
603 "<media_control><vc_primitive><to_encoder>"
605 + std::to_string(state)
607 "</to_encoder></vc_primitive></media_control>";
610 JAMI_DEBUG(
"[call:{}] Sending recording state via SIP INFO", getCallId());
613 sendSIPInfo(BODY,
"media_control+xml");
614 }
catch (
const std::exception& e) {
615 JAMI_ERROR(
"[call:{}] Error sending recording state: {}", getCallId(), e.what());
620SIPCall::requestKeyframe(
int streamIdx)
622 auto now = clock::now();
626 std::string streamIdPart;
628 streamIdPart = fmt::format(
"<stream_id>{}</stream_id>", streamIdx);
629 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
630 "<media_control><vc_primitive> "
631 + streamIdPart +
"<to_encoder>"
632 +
"<picture_fast_update/>"
633 "</to_encoder></vc_primitive></media_control>";
634 JAMI_DEBUG(
"[call:{}] Sending video keyframe request via SIP INFO", getCallId());
636 sendSIPInfo(BODY,
"media_control+xml");
637 }
catch (
const std::exception& e) {
638 JAMI_ERROR(
"[call:{}] Error sending video keyframe request: {}", getCallId(), e.what());
640 lastKeyFrameReq_ = now;
644SIPCall::sendMuteState(
bool state)
646 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
647 "<media_control><vc_primitive><to_encoder>"
649 + std::to_string(state)
651 "</to_encoder></vc_primitive></media_control>";
654 JAMI_DEBUG(
"[call:{}] Sending mute state via SIP INFO", getCallId());
657 sendSIPInfo(BODY,
"media_control+xml");
658 }
catch (
const std::exception& e) {
659 JAMI_ERROR(
"[call:{}] Error sending mute state: {}", getCallId(), e.what());
664SIPCall::sendVoiceActivity(std::string_view streamId,
bool state)
667 std::string streamIdPart =
"";
668 if (streamId !=
"-1" && !
streamId.empty()) {
669 streamIdPart = fmt::format(
"<stream_id>{}</stream_id>", streamId);
672 std::string BODY =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
673 "<media_control><vc_primitive>"
677 + std::to_string(state)
679 "</to_encoder></vc_primitive></media_control>";
682 sendSIPInfo(BODY,
"media_control+xml");
683 }
catch (
const std::exception& e) {
684 JAMI_ERROR(
"[call:{}] Error sending voice activity state: {}", getCallId(), e.what());
689SIPCall::setInviteSession(pjsip_inv_session* inviteSession)
691 std::lock_guard lk {callMutex_};
693 if (inviteSession ==
nullptr and inviteSession_) {
694 JAMI_DEBUG(
"[call:{}] Delete current invite session", getCallId());
695 }
else if (inviteSession !=
nullptr) {
701 if (PJ_SUCCESS != pjsip_inv_add_ref(inviteSession)) {
702 JAMI_WARNING(
"[call:{}] Attempting to set invalid invite session [{}]",
704 fmt::ptr(inviteSession));
705 inviteSession_.reset(
nullptr);
708 JAMI_DEBUG(
"[call:{}] Set new invite session [{}]", getCallId(), fmt::ptr(inviteSession));
714 inviteSession_.reset(inviteSession);
718SIPCall::terminateSipSession(
int status)
720 JAMI_DEBUG(
"[call:{}] Terminate SIP session", getCallId());
721 std::lock_guard lk {callMutex_};
722 if (inviteSession_ and inviteSession_->state != PJSIP_INV_STATE_DISCONNECTED) {
723 pjsip_tx_data* tdata =
nullptr;
724 auto ret = pjsip_inv_end_session(inviteSession_.get(), status,
nullptr, &tdata);
725 if (ret == PJ_SUCCESS) {
727 auto account = getSIPAccount();
729 sip_utils::addContactHeader(contactHeader_, tdata);
731 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
733 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
734 throw std::runtime_error(
735 fmt::format(
"[call:{}] The account owning this call is invalid", getCallId()));
738 ret = pjsip_inv_send_msg(inviteSession_.get(), tdata);
739 if (ret != PJ_SUCCESS)
740 JAMI_ERROR(
"[call:{}] Failed to send terminate msg, SIP error ({})",
742 sip_utils::sip_strerror(ret));
745 JAMI_ERROR(
"[call:{}] Failed to terminate INVITE@{}, SIP error ({})",
747 fmt::ptr(inviteSession_.get()),
748 sip_utils::sip_strerror(ret));
754SIPCall::answer(
const std::vector<libjami::MediaMap>& mediaList)
756 std::lock_guard lk {callMutex_};
757 auto account = getSIPAccount();
759 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
763 if (not inviteSession_) {
764 JAMI_ERROR(
"[call:{}] No invite session for this call", getCallId());
769 JAMI_ERROR(
"[call:{}] No SDP session for this call", getCallId());
773 auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
775 if (newMediaAttrList.empty() and rtpStreams_.empty()) {
776 JAMI_ERROR(
"[call:{}] Media list must not be empty!", getCallId());
782 if (newMediaAttrList.empty()) {
783 JAMI_DEBUG(
"[call:{}] Media list is empty, using current media", getCallId());
784 }
else if (newMediaAttrList.size() != rtpStreams_.size()) {
787 JAMI_ERROR(
"[call:{:s}] Media list size {:d} in answer does not match. Expected {:d}",
789 newMediaAttrList.size(),
794 auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList() : newMediaAttrList;
796 JAMI_DEBUG(
"[call:{}] Answering incoming call with following media:", getCallId());
797 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
798 auto const& mediaAttr = mediaAttrList.at(idx);
799 JAMI_DEBUG(
"[call:{:s}] Media @{:d} - {:s}", getCallId(), idx, mediaAttr.toString(
true));
803 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
804 updateMediaStream(mediaAttrList[idx], idx);
808 sdp_->processIncomingOffer(mediaAttrList);
810 if (isIceEnabled() and remoteHasValidIceAttributes()) {
814 if (not inviteSession_->neg) {
825 JAMI_WARNING(
"[call:{}] No negotiator session, peer sent an empty INVITE (without SDP)", getCallId());
827 Manager::instance().sipVoIPLink().createSDPOffer(inviteSession_.get());
829 generateMediaPorts();
832 if (isIceEnabled()) {
834 sdp_->setActiveRemoteSdpSession(
nullptr);
835 sdp_->setActiveLocalSdpSession(
nullptr);
837 auto opts = account->getIceOptions();
839 auto publicAddr = account->getPublishedIpAddress();
842 opts.accountPublicAddr = publicAddr;
843 if (
auto interfaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
844 publicAddr.getFamily())) {
845 opts.accountLocalAddr = interfaceAddr;
846 if (createIceMediaTransport(
false) and initIceMediaTransport(
true, std::move(opts))) {
847 addLocalIceAttributes();
850 JAMI_WARNING(
"[call:{}] Unable to init ICE transport, missing local address", getCallId());
853 JAMI_WARNING(
"[call:{}] Unable to init ICE transport, missing public address", getCallId());
858 if (!inviteSession_->last_answer)
859 throw std::runtime_error(
"Should only be called for initial answer");
862 pjsip_tx_data* tdata;
863 if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata) != PJ_SUCCESS)
864 throw std::runtime_error(
"Unable to init invite request answer (200 OK)");
866 if (contactHeader_.empty()) {
867 throw std::runtime_error(
"Unable to answer with an invalid contact header");
870 JAMI_DEBUG(
"[call:{}] Answering with contact header: {}", getCallId(), contactHeader_);
872 sip_utils::addContactHeader(contactHeader_, tdata);
875 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
877 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
879 throw std::runtime_error(
"Unable to send invite request answer (200 OK)");
882 setState(CallState::ACTIVE, ConnectionState::CONNECTED);
886SIPCall::answerMediaChangeRequest(
const std::vector<libjami::MediaMap>& mediaList,
bool isRemote)
888 std::lock_guard lk {callMutex_};
890 auto account = getSIPAccount();
892 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
896 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
900 if (not account->isVideoEnabled()) {
901 for (
auto& mediaAttr : mediaAttrList) {
902 if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
903 mediaAttr.enabled_ =
false;
908 if (mediaAttrList.empty()) {
909 JAMI_WARNING(
"[call:{}] Media list is empty. Ignoring the media change request", getCallId());
914 JAMI_ERROR(
"[call:{}] No valid SDP session", getCallId());
918 JAMI_DEBUG(
"[call:{}] Current media", getCallId());
920 for (
auto const& rtp : rtpStreams_) {
921 JAMI_DEBUG(
"[call:{}] Media @{}: {}", getCallId(), idx++, rtp.mediaAttribute_->toString(
true));
924 JAMI_DEBUG(
"[call:{}] Answering to media change request with new media", getCallId());
926 for (
auto const& newMediaAttr : mediaAttrList) {
927 JAMI_DEBUG(
"[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.toString(
true));
930 if (!updateAllMediaStreams(mediaAttrList, isRemote))
933 if (not sdp_->processIncomingOffer(mediaAttrList)) {
934 JAMI_WARNING(
"[call:{}] Unable to process the new offer, ignoring", getCallId());
938 if (not sdp_->getRemoteSdpSession()) {
939 JAMI_ERROR(
"[call:{}] No valid remote SDP session", getCallId());
943 if (isIceEnabled() and remoteHasValidIceAttributes()) {
944 JAMI_WARNING(
"[call:{}] Requesting a new ICE media", getCallId());
945 setupIceResponse(
true);
948 if (not sdp_->startNegotiation()) {
949 JAMI_ERROR(
"[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
953 if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
954 JAMI_ERROR(
"[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
958 pjsip_tx_data* tdata;
959 if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, NULL, &tdata) != PJ_SUCCESS) {
960 JAMI_ERROR(
"[call:{}] Unable to init answer to a re-invite request", getCallId());
964 if (not contactHeader_.empty()) {
965 sip_utils::addContactHeader(contactHeader_, tdata);
969 sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
971 if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
972 JAMI_ERROR(
"[call:{}] Unable to send answer to a re-invite request", getCallId());
977 JAMI_DEBUG(
"[call:{}] Successfully answered the media change request", getCallId());
981SIPCall::hangup(
int code)
983 std::lock_guard lk {callMutex_};
984 pendingRecord_ =
false;
985 if (inviteSession_ and inviteSession_->dlg) {
986 pjsip_route_hdr* route = inviteSession_->dlg->route_set.next;
987 while (route and route != &inviteSession_->dlg->route_set) {
989 int printed = pjsip_hdr_print_on(route, buf,
sizeof(buf));
992 JAMI_DEBUG(
"[call:{}] Route header {}", getCallId(), buf);
997 int status = PJSIP_SC_OK;
1000 else if (inviteSession_->state <= PJSIP_INV_STATE_EARLY and inviteSession_->role != PJSIP_ROLE_UAC)
1001 status = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
1002 else if (inviteSession_->state >= PJSIP_INV_STATE_DISCONNECTED)
1003 status = PJSIP_SC_DECLINE;
1006 terminateSipSession(status);
1011 detachAudioFromConference();
1012 setState(Call::ConnectionState::DISCONNECTED, code);
1013 dht::ThreadPool::io().run([w = weak(), code] {
1014 if (
auto shared = w.lock())
1015 shared->removeCall(code);
1020SIPCall::detachAudioFromConference()
1023 if (
auto conf = getConference()) {
1024 if (
auto mixer = conf->getVideoMixer()) {
1026 mixer->removeAudioOnlySource(getCallId(), stream->streamId());
1036 if (!isIncoming() or getConnectionState() == ConnectionState::CONNECTED or !inviteSession_)
1042 terminateSipSession(PJSIP_SC_DECLINE);
1044 setState(Call::ConnectionState::DISCONNECTED, PJSIP_SC_DECLINE);
1051 auto mod_ua_id = Manager::instance().sipVoIPLink().getModId();
1053 switch (pjsip_evsub_get_state(sub)) {
1054 case PJSIP_EVSUB_STATE_ACCEPTED:
1058 pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
1061 case PJSIP_EVSUB_STATE_TERMINATED:
1062 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1065 case PJSIP_EVSUB_STATE_ACTIVE: {
1069 pjsip_rx_data* r_data =
event->body.rx_msg.rdata;
1074 std::string request(pjsip_rx_data_get_info(r_data));
1076 pjsip_status_line status_line = {500, *pjsip_get_status_text(500)};
1078 if (!r_data->msg_info.msg)
1081 if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD
1082 and request.find(
"NOTIFY") != std::string::npos) {
1083 pjsip_msg_body* body = r_data->msg_info.msg->body;
1088 if (pj_stricmp2(&body->content_type.type,
"message") or pj_stricmp2(&body->content_type.subtype,
"sipfrag"))
1091 if (pjsip_parse_status_line((
char*) body->data, body->len, &status_line) != PJ_SUCCESS)
1095 if (!r_data->msg_info.cid)
1098 auto* call =
static_cast<SIPCall*
>(pjsip_evsub_get_mod_data(sub, mod_ua_id));
1102 if (status_line.code / 100 == 2) {
1103 if (call->inviteSession_)
1105 Manager::instance().hangupCall(call->getAccountId(), call->getCallId());
1106 pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL);
1112 case PJSIP_EVSUB_STATE_NULL:
1113 case PJSIP_EVSUB_STATE_SENT:
1114 case PJSIP_EVSUB_STATE_PENDING:
1115 case PJSIP_EVSUB_STATE_UNKNOWN:
1122SIPCall::transferCommon(
const pj_str_t* dst)
1124 if (not inviteSession_ or not inviteSession_->dlg)
1127 pjsip_evsub_user xfer_cb;
1128 pj_bzero(&xfer_cb,
sizeof(xfer_cb));
1129 xfer_cb.on_evsub_state = &transfer_client_cb;
1133 if (pjsip_xfer_create_uac(inviteSession_->dlg, &xfer_cb, &sub) != PJ_SUCCESS)
1141 pjsip_evsub_set_mod_data(sub, Manager::instance().sipVoIPLink().getModId(),
this);
1146 pjsip_tx_data* tdata;
1148 if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS)
1152 if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS)
1159SIPCall::transfer(
const std::string& to)
1161 auto account = getSIPAccount();
1163 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
1168 if (Call::isRecording())
1171 std::string toUri = account->getToUri(to);
1172 const pj_str_t dst(CONST_PJ_STR(toUri));
1174 JAMI_DEBUG(
"[call:{}] Transferring to {}", getCallId(), std::string_view(dst.ptr, dst.slen));
1176 if (!transferCommon(&dst))
1181SIPCall::attendedTransfer(
const std::string& to)
1183 auto toCall = Manager::instance().callFactory.getCall<
SIPCall>(to);
1187 if (not toCall->inviteSession_ or not toCall->inviteSession_->dlg)
1191 pjsip_uri* uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri);
1193 char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = {
'<'};
1194 pj_str_t dst = {str_dest_buf, 1};
1196 dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1,
sizeof(str_dest_buf) - 1);
1197 dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen,
1198 sizeof(str_dest_buf) - dst.slen,
1201 "%%3Bto-tag%%3D%.*s"
1202 "%%3Bfrom-tag%%3D%.*s>",
1203 (
int) target_dlg->call_id->id.slen,
1204 target_dlg->call_id->id.ptr,
1205 (
int) target_dlg->remote.info->tag.slen,
1206 target_dlg->remote.info->tag.ptr,
1207 (
int) target_dlg->local.info->tag.slen,
1208 target_dlg->local.info->tag.ptr);
1210 return transferCommon(&dst);
1217 if (isWaitingForIceAndMedia_) {
1218 holdCb_ = std::move(cb);
1219 remainingRequest_ = Request::Hold;
1223 auto result = hold();
1234 if (getConnectionState() != ConnectionState::CONNECTED) {
1235 JAMI_WARNING(
"[call:{}] Not connected, ignoring hold request", getCallId());
1239 if (not setState(CallState::HOLD)) {
1240 JAMI_WARNING(
"[call:{}] Failed to set state to HOLD", getCallId());
1246 for (
auto& stream : rtpStreams_) {
1247 stream.mediaAttribute_->hold_ =
true;
1250 if (SIPSessionReinvite() != PJ_SUCCESS) {
1251 JAMI_WARNING(
"[call:{}] Reinvite failed", getCallId());
1256 isWaitingForIceAndMedia_ = (reinvIceMedia_ !=
nullptr);
1258 JAMI_DEBUG(
"[call:{}] Set state to HOLD", getCallId());
1266 if (isWaitingForIceAndMedia_) {
1267 JAMI_DEBUG(
"[call:{}] ICE negotiation in progress. Resume request will be once ICE "
1268 "negotiation completes",
1270 resumeCb_ = std::move(cb);
1271 remainingRequest_ = Request::Resume;
1274 JAMI_DEBUG(
"[call:{}] Resuming the call", getCallId());
1275 auto result = resume();
1286 auto account = getSIPAccount();
1288 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
1292 bool success =
false;
1294 success = internalResume([] {});
1295 }
catch (
const SdpException& e) {
1296 JAMI_ERROR(
"[call:{}] {}", getCallId(), e.what());
1297 throw VoipLinkException(
"SDP issue in resume");
1301 isWaitingForIceAndMedia_ = success and (reinvIceMedia_ !=
nullptr);
1307SIPCall::internalResume(
const std::function<
void()>& sdp_cb)
1309 if (getConnectionState() != ConnectionState::CONNECTED) {
1310 JAMI_WARNING(
"[call:{}] Not connected, ignoring resume request", getCallId());
1313 if (not
setState(CallState::ACTIVE))
1319 for (
auto& stream : rtpStreams_) {
1320 stream.mediaAttribute_->hold_ =
false;
1323 if (SIPSessionReinvite(getMediaAttributeList(),
true) != PJ_SUCCESS) {
1325 if (isWaitingForIceAndMedia_) {
1326 remainingRequest_ = Request::Hold;
1338SIPCall::switchInput(
const std::string& source)
1340 JAMI_DEBUG(
"[call:{}] Set selected source to {}", getCallId(), source);
1342 for (
auto const& stream : rtpStreams_) {
1343 auto mediaAttr = stream.mediaAttribute_;
1344 mediaAttr->sourceUri_ = source;
1349 bool isRec = Call::isRecording();
1351 if (isWaitingForIceAndMedia_) {
1352 remainingRequest_ = Request::SwitchInput;
1356 if (SIPSessionReinvite(getMediaAttributeList(),
true) == PJ_SUCCESS and reinvIceMedia_) {
1357 isWaitingForIceAndMedia_ =
true;
1361 readyToRecord_ =
false;
1362 pendingRecord_ =
true;
1367SIPCall::peerHungup()
1369 pendingRecord_ =
false;
1374 terminateSipSession(PJSIP_SC_NOT_FOUND);
1375 detachAudioFromConference();
1380SIPCall::carryingDTMFdigits(
char code)
1382 int duration = Manager::instance().voipPreferences.getPulseLength();
1383 char dtmf_body[1000];
1388 ret = snprintf(dtmf_body,
sizeof dtmf_body - 1,
"Signal=16\r\nDuration=%d\r\n", duration);
1390 ret = snprintf(dtmf_body,
sizeof dtmf_body - 1,
"Signal=%c\r\nDuration=%d\r\n", code, duration);
1394 sendSIPInfo({dtmf_body, (size_t) ret},
"dtmf-relay");
1395 }
catch (
const std::exception& e) {
1396 JAMI_ERROR(
"[call:{}] Error sending DTMF: {}", getCallId(), e.what());
1401SIPCall::setVideoOrientation(
int streamIdx,
int rotation)
1403 std::string streamIdPart;
1404 if (streamIdx != -1)
1405 streamIdPart = fmt::format(
"<stream_id>{}</stream_id>", streamIdx);
1406 std::string sip_body =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1407 "<media_control><vc_primitive><to_encoder>"
1408 "<device_orientation="
1409 + std::to_string(-rotation) +
"/>" +
"</to_encoder>" + streamIdPart
1410 +
"</vc_primitive></media_control>";
1412 JAMI_DEBUG(
"[call:{}] Sending device orientation via SIP INFO {} for stream {}", getCallId(), rotation, streamIdx);
1414 sendSIPInfo(sip_body,
"media_control+xml");
1418SIPCall::sendTextMessage(
const std::map<std::string, std::string>& messages,
const std::string& from)
1423 if (not subcalls_.empty()) {
1424 pendingOutMessages_.emplace_back(messages, from);
1425 for (
auto& c : subcalls_)
1426 c->sendTextMessage(messages, from);
1428 if (inviteSession_) {
1437 if (not isSipMethodAllowedByPeer(sip_utils::SIP_METHODS::MESSAGE)) {
1438 JAMI_WARNING(
"[call:{}] Peer does not allow \"{}\" method",
1440 sip_utils::SIP_METHODS::MESSAGE);
1443 JAMI_LOG(
"[call:{}] Peer's allowed methods: {}", getCallId(), peerAllowedMethods_);
1447 im::sendSipMessage(inviteSession_.get(), messages);
1450 JAMI_ERROR(
"[call:{}] Failed to send SIP text message", getCallId());
1453 pendingOutMessages_.emplace_back(messages, from);
1454 JAMI_ERROR(
"[call:{}] sendTextMessage: no invite session for this call", getCallId());
1460SIPCall::removeCall(
int code)
1465 std::lock_guard lk {callMutex_};
1466 JAMI_DEBUG(
"[call:{}] removeCall()", getCallId());
1468 sdp_->setActiveLocalSdpSession(
nullptr);
1469 sdp_->setActiveRemoteSdpSession(
nullptr);
1471 Call::removeCall(code);
1474 std::lock_guard lk(transportMtx_);
1475 resetTransport(std::move(iceMedia_));
1476 resetTransport(std::move(reinvIceMedia_));
1480 setSipTransport({});
1484SIPCall::onFailure(
int code)
1486 if (setState(CallState::MERROR, ConnectionState::DISCONNECTED, code)) {
1487 runOnMainThread([w = weak(), code] {
1488 if (
auto shared = w.lock()) {
1489 auto& call = *shared;
1490 Manager::instance().callFailure(call);
1491 call.removeCall(code);
1498SIPCall::onBusyHere()
1500 if (getCallType() == CallType::OUTGOING)
1501 setState(CallState::PEER_BUSY, ConnectionState::DISCONNECTED);
1503 setState(CallState::BUSY, ConnectionState::DISCONNECTED);
1505 runOnMainThread([w = weak()] {
1506 if (
auto shared = w.lock()) {
1507 auto& call = *shared;
1508 Manager::instance().callBusy(call);
1517 runOnMainThread([w = weak()] {
1518 if (
auto shared = w.lock()) {
1519 auto& call = *shared;
1520 Manager::instance().peerHungupCall(call);
1527SIPCall::onAnswered()
1530 runOnMainThread([w = weak()] {
1531 if (
auto shared = w.lock()) {
1532 if (shared->getConnectionState() != ConnectionState::CONNECTED) {
1533 shared->setState(CallState::ACTIVE, ConnectionState::CONNECTED);
1534 if (not shared->isSubcall()) {
1535 Manager::instance().peerAnsweredCall(*shared);
1543SIPCall::sendKeyframe(
int streamIdx)
1546 dht::ThreadPool::computation().run([w = weak(), streamIdx] {
1547 if (
auto sthis = w.lock()) {
1548 JAMI_DEBUG(
"[call:{}] Handling picture fast update request", sthis->getCallId());
1549 if (streamIdx == -1) {
1550 for (const auto& videoRtp : sthis->getRtpSessionList(MediaType::MEDIA_VIDEO))
1551 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->forceKeyFrame();
1552 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(sthis->rtpStreams_.size())) {
1554 auto& stream = sthis->rtpStreams_[streamIdx];
1555 if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
1556 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->forceKeyFrame();
1564SIPCall::isIceEnabled()
const
1570SIPCall::setPeerUaVersion(std::string_view ua)
1572 if (peerUserAgent_ == ua or ua.empty()) {
1577 if (peerUserAgent_.empty()) {
1578 JAMI_DEBUG(
"[call:{}] Set peer's User-Agent to [{}]", getCallId(), ua);
1579 }
else if (not peerUserAgent_.empty()) {
1582 JAMI_WARNING(
"[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]",
1588 peerUserAgent_ = ua;
1591 constexpr std::string_view PACK_NAME(PACKAGE_NAME
" ");
1592 auto pos = ua.find(PACK_NAME);
1593 if (pos == std::string_view::npos) {
1595 JAMI_WARNING(
"[call:{}] Unable to find the expected package name in peer's User-Agent", getCallId());
1599 ua = ua.substr(pos + PACK_NAME.length());
1601 std::string_view version;
1605 if (pos != std::string_view::npos) {
1607 version = ua.substr(0, pos);
1611 if (pos != std::string_view::npos) {
1612 version = ua.substr(0, pos);
1616 if (version.empty()) {
1617 JAMI_DEBUG(
"[call:{}] Unable to parse peer's version", getCallId());
1622 if (peerVersion.size() > 4u) {
1623 JAMI_WARNING(
"[call:{}] Unable to parse peer's version", getCallId());
1629 if (not peerSupportMultiStream_) {
1630 JAMI_DEBUG(
"Peer's version [{}] does not support multi-stream. "
1631 "Min required version: [{}]",
1638 if (not peerSupportMultiAudioStream_) {
1639 JAMI_DEBUG(
"Peer's version [{}] does not support multi-audio-stream. "
1640 "Min required version: [{}]",
1647 if (not peerSupportMultiIce_) {
1648 JAMI_DEBUG(
"Peer's version [{}] does not support more than 2 ICE media streams. "
1649 "Min required version: [{}]",
1655 peerSupportReuseIceInReinv_ = Account::meetMinimumRequiredVersion(peerVersion,
1657 if (not peerSupportReuseIceInReinv_) {
1658 JAMI_LOG(
"Peer's version [{:s}] does not support re-invite without ICE renegotiation. "
1659 "Min required version: [{:s}]",
1666SIPCall::setPeerAllowMethods(std::vector<std::string> methods)
1668 std::lock_guard lock {callMutex_};
1669 peerAllowedMethods_ = std::move(methods);
1673SIPCall::isSipMethodAllowedByPeer(
const std::string_view method)
const
1675 std::lock_guard lock {callMutex_};
1677 return std::find(peerAllowedMethods_.begin(), peerAllowedMethods_.end(), method) != peerAllowedMethods_.end();
1681SIPCall::onPeerRinging()
1683 JAMI_DEBUG(
"[call:{}] Peer ringing", getCallId());
1684 setState(ConnectionState::RINGING);
1688SIPCall::addLocalIceAttributes()
1690 if (not isIceEnabled())
1693 auto iceMedia = getIceMedia();
1696 JAMI_ERROR(
"[call:{}] Invalid ICE instance", getCallId());
1700 auto start = std::chrono::steady_clock::now();
1702 if (not iceMedia->isInitialized()) {
1703 JAMI_DEBUG(
"[call:{}] Waiting for ICE initialization", getCallId());
1706 JAMI_ERROR(
"[call:{}] ICE initialization timed out", getCallId());
1712 auto duration = std::chrono::steady_clock::now() - start;
1714 JAMI_WARNING(
"[call:{:s}] ICE initialization time was unexpectedly high ({})",
1716 std::chrono::duration_cast<std::chrono::milliseconds>(duration));
1721 if (not iceMedia->isInitialized()) {
1722 JAMI_ERROR(
"[call:{}] ICE session is uninitialized", getCallId());
1728 if (getState() == Call::CallState::OVER) {
1729 JAMI_WARNING(
"[call:{}] The call was terminated while waiting for ICE initialization", getCallId());
1733 auto account = getSIPAccount();
1735 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
1739 JAMI_ERROR(
"[call:{}] No sdp detected", getCallId());
1743 JAMI_DEBUG(
"[call:{}] Add local attributes for ICE instance [{}]", getCallId(), fmt::ptr(iceMedia.get()));
1745 sdp_->addIceAttributes(iceMedia->getLocalAttributes());
1747 if (account->isIceCompIdRfc5245Compliant()) {
1748 unsigned streamIdx = 0;
1749 for (
auto const& stream : rtpStreams_) {
1750 if (not stream.mediaAttribute_->enabled_) {
1752 JAMI_DEBUG(
"[call:{}] Media [{}] @ {} is disabled, don't add local candidates",
1754 stream.mediaAttribute_->toString(),
1758 JAMI_DEBUG(
"[call:{}] Add ICE local candidates for media [{}] @ {}",
1760 stream.mediaAttribute_->toString(),
1763 sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx,
ICE_COMP_ID_RTP));
1765 if (not rtcpMuxEnabled_) {
1766 sdp_->addIceCandidates(streamIdx, iceMedia->getLocalCandidates(streamIdx,
ICE_COMP_ID_RTP + 1));
1773 unsigned compId = 1;
1774 for (
auto const& stream : rtpStreams_) {
1775 if (not stream.mediaAttribute_->enabled_) {
1779 JAMI_DEBUG(
"[call:{}] Add ICE local candidates for media [{}] @ {}",
1781 stream.mediaAttribute_->toString(),
1784 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1788 if (not rtcpMuxEnabled_) {
1789 sdp_->addIceCandidates(idx, iceMedia->getLocalCandidates(compId));
1798std::vector<IceCandidate>
1799SIPCall::getAllRemoteCandidates(dhtnet::IceTransport& transport)
const
1801 std::vector<IceCandidate> rem_candidates;
1802 for (
unsigned mediaIdx = 0; mediaIdx < static_cast<unsigned>(rtpStreams_.size()); mediaIdx++) {
1804 for (
auto& line : sdp_->getIceCandidates(mediaIdx)) {
1805 if (transport.parseIceAttributeLine(mediaIdx, line, cand)) {
1806 JAMI_DEBUG(
"[call:{}] Add remote ICE candidate: {}", getCallId(), line);
1807 rem_candidates.emplace_back(std::move(cand));
1811 return rem_candidates;
1814std::shared_ptr<SystemCodecInfo>
1815SIPCall::getVideoCodec()
const
1821 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
1822 return videoRtp->getCodec();
1827std::shared_ptr<SystemCodecInfo>
1828SIPCall::getAudioCodec()
const
1831 for (
const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
1832 return audioRtp->getCodec();
1842 stream.mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttr);
1846 if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO && stream.mediaAttribute_->sourceUri_.empty()) {
1847 if (
auto* videoManager = Manager::instance().getVideoManager()) {
1848 stream.mediaAttribute_->sourceUri_ = videoManager->videoDeviceMonitor.getMRLForDefaultDevice();
1853 rtpStreams_.emplace_back(std::move(stream));
1857SIPCall::initMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList)
1859 for (
size_t idx = 0; idx < mediaAttrList.size(); idx++) {
1860 auto const& mediaAttr = mediaAttrList.at(idx);
1861 if (mediaAttr.
type_ != MEDIA_AUDIO && mediaAttr.
type_ != MEDIA_VIDEO) {
1862 JAMI_ERROR(
"[call:{}] Unexpected media type {}", getCallId(),
static_cast<int>(mediaAttr.
type_));
1866 addMediaStream(mediaAttr);
1867 auto& stream = rtpStreams_.back();
1869 createRtpSession(stream);
1870 JAMI_DEBUG(
"[call:{:s}] Added media @{:d}: {:s}", getCallId(), idx, stream.mediaAttribute_->toString(
true));
1871 }
catch (
const std::exception& e) {
1872 JAMI_ERROR(
"[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
1876 rtpStreams_.pop_back();
1880 JAMI_DEBUG(
"[call:{:s}] Created {:d} media stream(s)", getCallId(), rtpStreams_.size());
1882 return rtpStreams_.size();
1886SIPCall::hasVideo()
const
1889 std::function<bool(
const RtpStream& stream)> videoCheck = [](
auto const& stream) {
1892 return validVideo || validRemoteVideo;
1895 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), videoCheck);
1897 return iter != rtpStreams_.end();
1904SIPCall::isCaptureDeviceMuted(
const MediaType& mediaType)
const
1908 std::function<bool(
const RtpStream& stream)> mutedCheck = [&mediaType](
auto const& stream) {
1911 const auto iter = std::find_if(rtpStreams_.begin(), rtpStreams_.end(), mutedCheck);
1912 return iter == rtpStreams_.end();
1916SIPCall::setupNegotiatedMedia()
1918 JAMI_DEBUG(
"[call:{}] Updating negotiated media", getCallId());
1920 if (not sipTransport_ or not sdp_) {
1921 JAMI_ERROR(
"[call:{}] Call is in an invalid state", getCallId());
1925 auto slots = sdp_->getMediaSlots();
1926 bool peer_hold {
true};
1929 for (
const auto& slot : slots) {
1931 const auto& local = slot.first;
1932 const auto& remote = slot.second;
1935 if (not local.enabled) {
1936 JAMI_DEBUG(
"[call:{}] [SDP:slot#{}] The media is disabled, skipping", getCallId(), streamIdx);
1940 if (
static_cast<size_t>(streamIdx) >= rtpStreams_.size()) {
1941 throw std::runtime_error(
"Stream index is out-of-range");
1944 auto const& rtpStream = rtpStreams_[streamIdx];
1946 if (not rtpStream.mediaAttribute_) {
1947 throw std::runtime_error(
"Missing media attribute");
1951 rtpStream.mediaAttribute_->enabled_ = local.enabled and remote.enabled;
1953 if (not rtpStream.rtpSession_)
1954 throw std::runtime_error(
"Must have a valid RTP session");
1956 if (local.type != MEDIA_AUDIO && local.type != MEDIA_VIDEO) {
1957 JAMI_ERROR(
"[call:{}] Unexpected media type {}", getCallId(),
static_cast<int>(local.type));
1958 throw std::runtime_error(
"Invalid media attribute");
1961 if (local.type != remote.type) {
1962 JAMI_ERROR(
"[call:{}] [SDP:slot#{}] Inconsistent media type between local and remote",
1968 if (local.enabled and not local.codec) {
1969 JAMI_WARNING(
"[call:{}] [SDP:slot#{}] Missing local codec", getCallId(), streamIdx);
1973 if (remote.enabled and not remote.codec) {
1974 JAMI_WARNING(
"[call:{}] [SDP:slot#{}] Missing remote codec", getCallId(), streamIdx);
1978 if (isSrtpEnabled() and local.enabled and not local.crypto) {
1979 JAMI_WARNING(
"[call:{}] [SDP:slot#{}] Secure mode but no local crypto attributes. "
1980 "Ignoring the media",
1986 if (isSrtpEnabled() and remote.enabled and not remote.crypto) {
1987 JAMI_WARNING(
"[call:{}] [SDP:slot#{}] Secure mode but no crypto remote attributes. "
1988 "Ignoring the media",
1995 peer_hold &= remote.hold;
1997 configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote);
2001 if (not isSubcall() and peerHold_ != peer_hold) {
2002 peerHold_ = peer_hold;
2003 emitSignal<libjami::CallSignal::PeerHold>(getCallId(), peerHold_);
2008SIPCall::startAllMedia()
2010 JAMI_DEBUG(
"[call:{}] Starting all media", getCallId());
2012 if (not sipTransport_ or not sdp_) {
2013 JAMI_ERROR(
"[call:{}] The call is in invalid state", getCallId());
2017 if (isSrtpEnabled() && not sipTransport_->isSecure()) {
2018 JAMI_WARNING(
"[call:{}] Crypto (SRTP) is negotiated over an insecure signaling transport", getCallId());
2022 readyToRecord_ =
false;
2025 if (getState() != CallState::HOLD) {
2026 bool iceRunning = isIceRunning();
2027 auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession());
2029 for (
auto& rtpStream : rtpStreams_) {
2030 if (not rtpStream.mediaAttribute_) {
2031 throw std::runtime_error(
"Missing media attribute");
2033 if (idx >= remoteMediaList.size()) {
2034 JAMI_ERROR(
"[call:{}] Remote media list smaller than streams (idx={}, size={})",
2037 remoteMediaList.size());
2040 rtpStream.remoteMediaAttribute_ = std::make_shared<MediaAttribute>(remoteMediaList[idx]);
2041 if (not rtpStream.mediaAttribute_->enabled_) {
2042 JAMI_DEBUG(
"[call:{}] Skipping start for disabled stream @{}", getCallId(), idx);
2046 if (rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
2047 rtpStream.rtpSession_->setMuted(rtpStream.remoteMediaAttribute_->muted_, RtpSession::Direction::RECV);
2049 dht::ThreadPool::io().run(
2052 isVideo = rtpStream.remoteMediaAttribute_->type_ == MediaType::MEDIA_VIDEO,
2054 rtpSession = rtpStream.rtpSession_,
2056 = std::make_shared<std::pair<std::unique_ptr<dhtnet::IceSocket>, std::unique_ptr<dhtnet::IceSocket>>>(
2057 std::move(rtpStream.rtpSocket_), std::move(rtpStream.rtcpSocket_))]()
mutable {
2060 rtpSession->start(std::move(rtpSocketPair->first), std::move(rtpSocketPair->second));
2062 rtpSession->start(nullptr, nullptr);
2065 if (auto call = w.lock())
2066 call->requestKeyframe(static_cast<int>(idx));
2069 if (auto call = w.lock()) {
2071 call->createCallAVStreams();
2074 } catch (
const std::exception& e) {
2075 JAMI_ERROR(
"[call:{}] Failed to start RTP session {}: {}",
2076 w.lock() ? w.lock()->getCallId() :
"unknown",
2086 isWaitingForIceAndMedia_ =
false;
2087 if (remainingRequest_ != Request::NoRequest) {
2089 switch (remainingRequest_) {
2097 case Request::Resume:
2101 resumeCb_ =
nullptr;
2104 case Request::SwitchInput:
2105 SIPSessionReinvite();
2110 remainingRequest_ = Request::NoRequest;
2113 mediaRestartRequired_ =
false;
2117SIPCall::restartMediaSender()
2119 JAMI_DEBUG(
"[call:{}] Restarting TX media streams", getCallId());
2120 for (
const auto& rtpSession : getRtpSessionList())
2121 rtpSession->restartSender();
2125SIPCall::stopAllMedia()
2127 JAMI_DEBUG(
"[call:{}] Stopping all media", getCallId());
2131 std::lock_guard lk(sinksMtx_);
2132 for (
auto it = callSinksMap_.begin(); it != callSinksMap_.end();) {
2134 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
2136 auto& sink = videoReceive->getSink();
2137 sink->detach(it->second.get());
2141 it = callSinksMap_.erase(it);
2147 std::condition_variable cv;
2148 unsigned int stoppedCount = 0;
2149 unsigned int totalStreams = rtpStreams_.size();
2151 for (
const auto& rtpSession : getRtpSessionList()) {
2152 dht::ThreadPool::io().run([&, rtpSession]() {
2155 }
catch (
const std::exception& e) {
2156 JAMI_ERROR(
"Failed to stop RTP session: {}", e.what());
2159 std::lock_guard lk(mtx);
2166 std::unique_lock lk(mtx);
2167 cv.wait(lk, [&] {
return stoppedCount == totalStreams; });
2171 clearCallAVStreams();
2172 std::lock_guard lk(avStreamsMtx_);
2173 Manager::instance().getJamiPluginManager().getCallServicesManager().clearAVSubject(getCallId());
2179SIPCall::muteMedia(
const std::string& mediaType,
bool mute)
2181 auto type = MediaAttribute::stringToMediaType(mediaType);
2183 if (type == MediaType::MEDIA_AUDIO) {
2184 JAMI_WARNING(
"[call:{}] {} all audio media", getCallId(), mute ?
"muting " :
"unmuting ");
2186 }
else if (type == MediaType::MEDIA_VIDEO) {
2187 JAMI_WARNING(
"[call:{}] {} all video media", getCallId(), mute ?
"muting" :
"unmuting");
2189 JAMI_ERROR(
"[call:{}] Invalid media type {}", getCallId(), mediaType);
2194 auto mediaList = getMediaAttributeList();
2197 for (
auto& mediaAttr : mediaList) {
2198 if (mediaAttr.
type_ == type) {
2204 requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList));
2208SIPCall::updateMediaStream(
const MediaAttribute& newMediaAttr,
size_t streamIdx)
2210 assert(streamIdx < rtpStreams_.size());
2212 auto const& rtpStream = rtpStreams_[streamIdx];
2213 assert(rtpStream.rtpSession_);
2215 auto const& mediaAttr = rtpStream.mediaAttribute_;
2218 bool notifyMute =
false;
2225 mediaAttr->
muted_ ?
"muted " :
"unmuted ");
2231 JAMI_DEBUG(
"[call:{}] {} [{}]", getCallId(), mediaAttr->
muted_ ?
"muting" :
"unmuting", mediaAttr->label_);
2238 if (notifyMute and mediaAttr->
type_ == MediaType::MEDIA_AUDIO) {
2239 rtpStream.rtpSession_->setMediaSource(mediaAttr->
sourceUri_);
2240 rtpStream.rtpSession_->setMuted(mediaAttr->
muted_);
2241 sendMuteState(mediaAttr->
muted_);
2242 if (not isSubcall())
2243 emitSignal<libjami::CallSignal::AudioMuted>(getCallId(), mediaAttr->
muted_);
2248 if (notifyMute and mediaAttr->
type_ == MediaType::MEDIA_VIDEO) {
2249 rtpStream.rtpSession_->setMediaSource(mediaAttr->
sourceUri_);
2250 rtpStream.rtpSession_->setMuted(mediaAttr->
muted_);
2252 if (not isSubcall())
2253 emitSignal<libjami::CallSignal::VideoMuted>(getCallId(), mediaAttr->
muted_);
2259SIPCall::updateAllMediaStreams(
const std::vector<MediaAttribute>& mediaAttrList,
bool isRemote)
2261 JAMI_DEBUG(
"[call:{}] New local media", getCallId());
2263 if (mediaAttrList.size() > PJ_ICE_MAX_COMP / 2) {
2264 JAMI_DEBUG(
"[call:{:s}] Too many media streams, limit it ({:d} vs {:d})",
2265 getCallId().c_str(),
2266 mediaAttrList.size(),
2272 for (
auto const& newMediaAttr : mediaAttrList) {
2273 JAMI_DEBUG(
"[call:{}] Media @{}: {}", getCallId(), idx++, newMediaAttr.
toString(
true));
2276 JAMI_DEBUG(
"[call:{}] Updating local media streams", getCallId());
2278 for (
auto const& newAttr : mediaAttrList) {
2279 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2281 if (streamIdx < 0) {
2283 addMediaStream(newAttr);
2284 auto& stream = rtpStreams_.back();
2286 stream.mediaAttribute_->muted_ = isRemote ? true : stream.mediaAttribute_->muted_;
2288 createRtpSession(stream);
2289 JAMI_DEBUG(
"[call:{:s}] Added a new media stream @{:d}: {:s}",
2292 stream.mediaAttribute_->toString(
true));
2293 }
catch (
const std::exception& e) {
2294 JAMI_ERROR(
"[call:{:s}] Failed to create RTP session for media @{:d}: {:s}. Ignoring the media",
2298 rtpStreams_.pop_back();
2301 updateMediaStream(newAttr, streamIdx);
2307 if (mediaAttrList.size() < rtpStreams_.size()) {
2309 for (
auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2311 auto& stream = rtpStreams_[i];
2312 if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
2313 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->exitConference();
2316 for (
auto i = mediaAttrList.size(); i < rtpStreams_.size(); ++i) {
2318 auto& stream = rtpStreams_[i];
2319 if (stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO) {
2320 JAMI_WARNING(
"[call:{}] Audio stream {} absent from new media list, stopping RTP session",
2322 stream.rtpSession_->streamId());
2323 std::static_pointer_cast<AudioRtpSession>(stream.rtpSession_)->stop();
2326 rtpStreams_.resize(mediaAttrList.size());
2332SIPCall::isReinviteRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2334 if (mediaAttrList.size() != rtpStreams_.size())
2337 for (
auto const& newAttr : mediaAttrList) {
2338 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2340 if (streamIdx < 0) {
2346 if (newAttr.sourceUri_ != rtpStreams_[streamIdx].mediaAttribute_->sourceUri_) {
2351 if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2354 if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) {
2365SIPCall::isNewIceMediaRequired(
const std::vector<MediaAttribute>& mediaAttrList)
2369 if (not peerSupportReuseIceInReinv_)
2373 if (mediaAttrList.size() != rtpStreams_.size())
2376 for (
auto const& newAttr : mediaAttrList) {
2377 auto streamIdx = findRtpStreamIndex(newAttr.label_);
2378 if (streamIdx < 0) {
2382 auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_;
2383 if (newAttr.sourceUri_ != currAttr->sourceUri_) {
2391 if (newAttr.type_ == MediaType::MEDIA_VIDEO) {
2394 if (newAttr.muted_ != currAttr->muted_) {
2405SIPCall::requestMediaChange(
const std::vector<libjami::MediaMap>& mediaList)
2407 std::lock_guard lk {callMutex_};
2408 auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled());
2409 bool hasFileSharing {
false};
2411 for (
const auto& media : mediaAttrList) {
2412 if (!media.enabled_ || media.sourceUri_.empty())
2418 const auto pos = media.sourceUri_.find(sep);
2419 if (pos == std::string::npos)
2422 const auto prefix = media.sourceUri_.substr(0, pos);
2423 if ((pos + sep.size()) >= media.sourceUri_.size())
2427 hasFileSharing =
true;
2428 mediaPlayerId_ = media.sourceUri_;
2430 createMediaPlayer(mediaPlayerId_);
2435 if (!hasFileSharing) {
2437 closeMediaPlayer(mediaPlayerId_);
2439 mediaPlayerId_ =
"";
2443 auto account = getSIPAccount();
2445 JAMI_ERROR(
"[call:{}] No account detected", getCallId());
2448 if (not account->isVideoEnabled()) {
2449 for (
auto& mediaAttr : mediaAttrList) {
2450 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO) {
2453 JAMI_ERROR(
"[call:{}] New media has video, but it's disabled in the account. "
2454 "Ignoring the change request!",
2464 if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
2465 JAMI_WARNING(
"[call:{}] Peer does not support multi-stream. Media change request ignored", getCallId());
2471 if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) {
2472 JAMI_WARNING(
"[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored", getCallId());
2473 for (
auto it = mediaAttrList.begin(); it != mediaAttrList.end();) {
2474 if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) {
2475 it = mediaAttrList.erase(it);
2484 if (!peerSupportMultiIce_) {
2485 if (mediaList.size() > 2)
2486 JAMI_WARNING(
"[call:{}] Peer does not support more than 2 ICE medias. "
2487 "Media change request modified",
2491 auto hasVideo =
false, hasAudio =
false;
2492 for (
auto it = mediaAttrList.rbegin(); it != mediaAttrList.rend(); ++it) {
2493 if (it->type_ == MediaType::MEDIA_VIDEO && !hasVideo) {
2495 videoAttr.
label_ = sip_utils::DEFAULT_VIDEO_STREAMID;
2497 }
else if (it->type_ == MediaType::MEDIA_AUDIO && !hasAudio) {
2499 audioAttr.
label_ = sip_utils::DEFAULT_AUDIO_STREAMID;
2502 if (hasVideo && hasAudio)
2505 mediaAttrList.clear();
2508 mediaAttrList.emplace_back(audioAttr);
2510 mediaAttrList.emplace_back(videoAttr);
2513 if (mediaAttrList.empty()) {
2514 JAMI_ERROR(
"[call:{}] Invalid media change request: new media list is empty", getCallId());
2517 JAMI_DEBUG(
"[call:{}] Requesting media change. List of new media:", getCallId());
2520 for (
auto const& newMediaAttr : mediaAttrList) {
2521 JAMI_DEBUG(
"[call:{}] Media @{:d}: {}", getCallId(), idx++, newMediaAttr.
toString(
true));
2524 auto needReinvite = isReinviteRequired(mediaAttrList);
2525 auto needNewIce = isNewIceMediaRequired(mediaAttrList);
2527 if (!updateAllMediaStreams(mediaAttrList,
false))
2531 JAMI_DEBUG(
"[call:{}] Media change requires a new negotiation (re-invite)", getCallId());
2532 requestReinvite(mediaAttrList, needNewIce);
2534 JAMI_DEBUG(
"[call:{}] Media change DOES NOT require a new negotiation (re-invite)", getCallId());
2535 reportMediaNegotiationStatus();
2541std::vector<std::map<std::string, std::string>>
2542SIPCall::currentMediaList()
const
2544 return MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList());
2547std::vector<MediaAttribute>
2548SIPCall::getMediaAttributeList()
const
2550 std::lock_guard lk {callMutex_};
2551 std::vector<MediaAttribute> mediaList;
2552 mediaList.reserve(rtpStreams_.size());
2553 for (
auto const& stream : rtpStreams_)
2554 mediaList.emplace_back(*stream.mediaAttribute_);
2558std::map<std::string, bool>
2559SIPCall::getAudioStreams()
const
2561 std::map<std::string, bool> audioMedias {};
2562 auto medias = getMediaAttributeList();
2563 for (
const auto& media : medias) {
2565 auto label = fmt::format(
"{}_{}", getCallId(), media.label_);
2566 audioMedias.emplace(label, media.muted_);
2572std::map<std::string, bool>
2573SIPCall::getRemoteAudioStreams()
const
2575 std::lock_guard lk {callMutex_};
2576 std::map<std::string, bool> audioMedias {};
2577 for (
const auto& stream : rtpStreams_) {
2578 if (stream.mediaAttribute_ && stream.mediaAttribute_->type_ ==
MEDIA_AUDIO) {
2579 auto label = fmt::format(
"{}_{}", getCallId(), stream.mediaAttribute_->label_);
2580 bool muted = stream.remoteMediaAttribute_ ? stream.remoteMediaAttribute_->muted_
2581 : stream.mediaAttribute_->muted_;
2582 audioMedias.emplace(label, muted);
2589SIPCall::onMediaNegotiationComplete()
2591 runOnMainThread([w = weak()] {
2592 if (
auto this_ = w.lock()) {
2593 std::lock_guard lk {this_->callMutex_};
2594 JAMI_DEBUG(
"[call:{}] Media negotiation complete", this_->getCallId());
2597 if (not this_->inviteSession_ or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED
2598 or not this_->sdp_) {
2608 if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) {
2609 if (not this_->isSubcall()) {
2611 this_->startIceMedia();
2615 if (this_->mediaRestartRequired_) {
2616 this_->setupNegotiatedMedia();
2618 JAMI_WARNING(
"[call:{}] ICE media disabled, using default media ports", this_->getCallId());
2620 this_->stopAllMedia();
2621 this_->startAllMedia();
2625 this_->reportMediaNegotiationStatus();
2632SIPCall::reportMediaNegotiationStatus()
2635 auto callId = isSubcall() ? parent_->getCallId() : getCallId();
2636 emitSignal<libjami::CallSignal::MediaNegotiationStatus>(
2638 std::lock_guard lk {mediaStateMutex_};
2639 auto previousState = isAudioOnly_;
2640 auto newState = !hasVideo();
2642 if (!readyToRecord_) {
2646 if (previousState != newState && Call::isRecording()) {
2649 pendingRecord_ =
true;
2651 isAudioOnly_ = newState;
2653 if (pendingRecord_ && readyToRecord_) {
2659SIPCall::startIceMedia()
2661 JAMI_DEBUG(
"[call:{}] Starting ICE", getCallId());
2662 auto iceMedia = getIceMedia();
2663 if (not iceMedia or iceMedia->isFailed()) {
2664 JAMI_ERROR(
"[call:{}] Media ICE init failed", getCallId());
2665 onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
2669 if (iceMedia->isStarted()) {
2671 if (iceMedia->isRunning())
2676 if (not iceMedia->isInitialized()) {
2678 waitForIceInit_ =
true;
2685 auto rem_ice_attrs = sdp_->getIceAttributes();
2686 if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
2687 JAMI_ERROR(
"[call:{}] Missing remote media ICE attributes", getCallId());
2688 onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
2691 if (not iceMedia->startIce(rem_ice_attrs, getAllRemoteCandidates(*iceMedia))) {
2692 JAMI_ERROR(
"[call:{}] ICE media failed to start", getCallId());
2693 onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
2698SIPCall::onIceNegoSucceed()
2700 std::lock_guard lk {callMutex_};
2702 JAMI_DEBUG(
"[call:{}] ICE negotiation succeeded", getCallId());
2707 if (not inviteSession_ or inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED or not sdp_) {
2708 JAMI_ERROR(
"[call:{}] ICE negotiation succeeded, but call is in invalid state", getCallId());
2713 setupNegotiatedMedia();
2718 switchToIceReinviteIfNeeded();
2720 for (
unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) {
2722 auto& rtpStream = rtpStreams_[idx];
2723 if (not rtpStream.mediaAttribute_ or not rtpStream.mediaAttribute_->enabled_) {
2724 JAMI_DEBUG(
"[call:{}] Skipping ICE socket for disabled stream @{}", getCallId(), idx);
2727 rtpStream.rtpSocket_ = newIceSocket(compId);
2729 if (not rtcpMuxEnabled_) {
2730 rtpStream.rtcpSocket_ = newIceSocket(compId + 1);
2737 reportMediaNegotiationStatus();
2741SIPCall::checkMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2750 JAMI_DEBUG(
"[call:{}] Received a media change request", getCallId());
2752 auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList, isSrtpEnabled());
2753 if (remoteMediaAttrList.size() != rtpStreams_.size())
2756 for (
size_t i = 0; i < rtpStreams_.size(); i++) {
2757 if (remoteMediaAttrList[i].type_ != rtpStreams_[i].mediaAttribute_->type_)
2759 if (remoteMediaAttrList[i].enabled_ != rtpStreams_[i].mediaAttribute_->enabled_)
2767SIPCall::handleMediaChangeRequest(
const std::vector<libjami::MediaMap>& remoteMediaList)
2769 JAMI_DEBUG(
"[call:{}] Handling media change request", getCallId());
2771 auto account = getAccount().lock();
2779 if (not checkMediaChangeRequest(remoteMediaList)) {
2780 answerMediaChangeRequest(MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()));
2784 if (account->isAutoAnswerEnabled()) {
2792 std::vector<libjami::MediaMap> newMediaList;
2793 newMediaList.reserve(remoteMediaList.size());
2794 for (
auto const& stream : rtpStreams_) {
2795 newMediaList.emplace_back(MediaAttribute::toMediaMap(*stream.mediaAttribute_));
2798 assert(remoteMediaList.size() > 0);
2799 if (remoteMediaList.size() > newMediaList.size()) {
2800 for (
auto idx = newMediaList.size(); idx < remoteMediaList.size(); idx++) {
2801 newMediaList.emplace_back(remoteMediaList[idx]);
2804 answerMediaChangeRequest(newMediaList,
true);
2809 emitSignal<libjami::CallSignal::MediaChangeRequested>(getAccountId(), getCallId(), remoteMediaList);
2813SIPCall::onReceiveReinvite(
const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
2815 JAMI_DEBUG(
"[call:{}] Received a re-invite", getCallId());
2817 pj_status_t res = PJ_SUCCESS;
2825 sdp_->setActiveRemoteSdpSession(
nullptr);
2826 sdp_->setActiveLocalSdpSession(
nullptr);
2828 auto acc = getSIPAccount();
2834 Sdp::printSession(offer,
"Remote session (media change request)", SdpDirection::OFFER);
2836 sdp_->setReceivedOffer(offer);
2845 auto const& mediaAttrList = Sdp::getMediaAttributeListFromSdp(offer,
true);
2846 if (mediaAttrList.empty()) {
2847 JAMI_WARNING(
"[call:{}] Media list is empty, ignoring", getCallId());
2855 pjsip_tx_data* tdata =
nullptr;
2856 if (pjsip_inv_initial_answer(inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) {
2857 JAMI_ERROR(
"[call:{}] Unable to create answer TRYING", getCallId());
2861 dht::ThreadPool::io().run([callWkPtr = weak(), mediaAttrList] {
2862 if (
auto call = callWkPtr.lock()) {
2864 auto const& remoteMediaList = MediaAttribute::mediaAttributesToMediaMaps(mediaAttrList);
2865 if (auto conf = call->getConference()) {
2866 conf->handleMediaChangeRequest(call, remoteMediaList);
2868 call->handleMediaChangeRequest(remoteMediaList);
2877SIPCall::onReceiveOfferIn200OK(
const pjmedia_sdp_session* offer)
2879 if (not rtpStreams_.empty()) {
2880 JAMI_ERROR(
"[call:{}] Unexpected offer in '200 OK' answer", getCallId());
2884 auto acc = getSIPAccount();
2895 JAMI_DEBUG(
"[call:{}] Received an offer in '200 OK' answer", getCallId());
2897 auto mediaList = Sdp::getMediaAttributeListFromSdp(offer);
2900 if (mediaList.empty()) {
2901 JAMI_WARNING(
"[call:{}] Remote media list is empty, ignoring", getCallId());
2905 Sdp::printSession(offer,
"Remote session (offer in 200 OK answer)", SdpDirection::OFFER);
2908 sdp_->setActiveRemoteSdpSession(
nullptr);
2909 sdp_->setActiveLocalSdpSession(
nullptr);
2911 sdp_->setReceivedOffer(offer);
2915 for (
auto& mediaAttr : mediaList) {
2916 if (mediaAttr.
type_ == MediaType::MEDIA_VIDEO and not acc->isVideoEnabled()) {
2921 initMediaStreams(mediaList);
2923 sdp_->processIncomingOffer(mediaList);
2929 if (isIceEnabled() and remoteHasValidIceAttributes()) {
2933 sdp_->startNegotiation();
2935 if (pjsip_inv_set_sdp_answer(inviteSession_.get(), sdp_->getLocalSdpSession()) != PJ_SUCCESS) {
2936 JAMI_ERROR(
"[call:{}] Unable to start media negotiation for a re-invite request", getCallId());
2941SIPCall::openPortsUPnP()
2944 JAMI_ERROR(
"[call:{}] Current SDP instance is invalid", getCallId());
2957 JAMI_DEBUG(
"[call:{}] Opening ports via UPnP for SDP session", getCallId());
2960 upnp_->reserveMapping(sdp_->getLocalAudioPort(), dhtnet::upnp::PortType::UDP);
2962 upnp_->reserveMapping(sdp_->getLocalAudioControlPort(), dhtnet::upnp::PortType::UDP);
2966 upnp_->reserveMapping(sdp_->getLocalVideoPort(), dhtnet::upnp::PortType::UDP);
2968 upnp_->reserveMapping(sdp_->getLocalVideoControlPort(), dhtnet::upnp::PortType::UDP);
2972std::map<std::string, std::string>
2973SIPCall::getDetails()
const
2975 auto acc = getSIPAccount();
2981 auto details = Call::getDetails();
2985 for (
auto const& stream : rtpStreams_) {
2986 if (stream.mediaAttribute_->type_ == MediaType::MEDIA_VIDEO) {
2989 if (
auto const& rtpSession = stream.rtpSession_) {
2990 if (
auto codec = rtpSession->getCodec()) {
2994 if (
const auto& curvideoRtpSession = std::static_pointer_cast<video::VideoRtpSession>(rtpSession)) {
2996 std::to_string(curvideoRtpSession->getVideoBitrateInfo().videoBitrateCurrent));
3002 }
else if (stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3003 if (
auto const& rtpSession = stream.rtpSession_) {
3004 if (
auto codec = rtpSession->getCodec()) {
3017 if (not peerRegisteredName_.empty())
3020#ifdef ENABLE_CLIENT_CERT
3021 std::lock_guard lk {callMutex_};
3022 if (transport_ and transport_->isSecure()) {
3023 const auto& tlsInfos = transport_->getTlsInfos();
3024 if (tlsInfos.cipher != PJ_TLS_UNKNOWN_CIPHER) {
3025 const auto& cipher = pj_ssl_cipher_name(tlsInfos.cipher);
3030 if (tlsInfos.peerCert) {
3032 auto ca = tlsInfos.peerCert->issuer;
3035 std::ostringstream name_str;
3037 details.emplace(name_str.str(), ca->toString());
3047 if (
auto transport = getIceMedia()) {
3048 if (transport && transport->isRunning())
3055SIPCall::enterConference(std::shared_ptr<Conference> conference)
3057 JAMI_DEBUG(
"[call:{}] Entering conference [{}]", getCallId(), conference->getConfId());
3060 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3062 auto& rbPool = Manager::instance().getRingBufferPool();
3063 auto medias = getAudioStreams();
3064 for (
const auto& media : medias) {
3065 rbPool.unbindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3067 rbPool.flush(RingBufferPool::DEFAULT_ID);
3071 if (conference->isVideoEnabled())
3072 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3073 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference);
3077 clearCallAVStreams();
3082SIPCall::exitConference()
3084 std::lock_guard lk {callMutex_};
3085 JAMI_DEBUG(
"[call:{}] Leaving conference", getCallId());
3087 auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty();
3089 auto& rbPool = Manager::instance().getRingBufferPool();
3090 auto medias = getAudioStreams();
3091 for (
const auto& media : medias) {
3092 if (!media.second) {
3093 rbPool.bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
3096 rbPool.flush(RingBufferPool::DEFAULT_ID);
3099 for (
const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO))
3100 std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->exitConference();
3103 createCallAVStreams();
3109SIPCall::setActiveMediaStream(
const std::string& accountUri,
3110 const std::string& deviceId,
3111 const std::string& streamId,
3114 auto remoteStreamId = streamId;
3117 std::lock_guard lk(sinksMtx_);
3118 const auto& localIt = local2RemoteSinks_.find(streamId);
3119 if (localIt != local2RemoteSinks_.end()) {
3120 remoteStreamId = localIt->second;
3125 if (Call::conferenceProtocolVersion() == 1) {
3126 Json::Value sinkVal;
3127 sinkVal[
"active"] = state;
3128 Json::Value mediasObj;
3129 mediasObj[remoteStreamId] = sinkVal;
3130 Json::Value deviceVal;
3131 deviceVal[
"medias"] = mediasObj;
3132 Json::Value deviceObj;
3133 deviceObj[deviceId] = deviceVal;
3134 Json::Value accountVal;
3135 deviceVal[
"devices"] = deviceObj;
3137 root[accountUri] = deviceVal;
3138 root[
"version"] = 1;
3139 Call::sendConfOrder(root);
3140 }
else if (Call::conferenceProtocolVersion() == 0) {
3142 root[
"activeParticipant"] = accountUri;
3143 Call::sendConfOrder(root);
3149SIPCall::setRotation(
int streamIdx,
int rotation)
3152 dht::ThreadPool::io().run([w = weak(), streamIdx, rotation] {
3153 if (
auto shared = w.lock()) {
3154 std::lock_guard lk {shared->callMutex_};
3155 shared->rotation_ = rotation;
3156 if (streamIdx == -1) {
3158 std::static_pointer_cast<
video::VideoRtpSession>(videoRtp)->setRotation(rotation);
3159 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(shared->rtpStreams_.size())) {
3161 auto& stream = shared->rtpStreams_[streamIdx];
3162 if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO)
3163 std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_)->setRotation(rotation);
3170SIPCall::createSinks(ConfInfo& infos)
3172 std::lock_guard lk(callMutex_);
3173 std::lock_guard lkS(sinksMtx_);
3177 for (
auto& participant : infos) {
3179 && participant.device == std::dynamic_pointer_cast<JamiAccount>(account_.lock())->currentDeviceId()) {
3180 for (
auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) {
3181 if (!iter->mediaAttribute_ || iter->mediaAttribute_->type_ == MediaType::MEDIA_AUDIO) {
3185 = std::static_pointer_cast<video::VideoRtpSession>(iter->rtpSession_)->getVideoLocal().get();
3186 auto size = std::make_pair(10, 10);
3188 size = std::make_pair(localVideo->getWidth(), localVideo->getHeight());
3190 const auto& mediaAttribute = iter->mediaAttribute_;
3191 if (participant.sinkId.find(mediaAttribute->label_) != std::string::npos) {
3192 local2RemoteSinks_[mediaAttribute->sourceUri_] = participant.sinkId;
3193 participant.sinkId = mediaAttribute->sourceUri_;
3194 participant.videoMuted = mediaAttribute->muted_;
3195 participant.w = size.first;
3196 participant.h = size.second;
3204 std::vector<std::shared_ptr<video::VideoFrameActiveWriter>> sinks;
3206 auto& videoReceive = std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->getVideoReceive();
3209 sinks.emplace_back(std::static_pointer_cast<video::VideoFrameActiveWriter>(videoReceive->getSink()));
3211 auto conf = conf_.lock();
3212 const auto&
id = conf ? conf->getConfId() : getCallId();
3213 Manager::instance().createSinkClients(
id, infos, sinks, callSinksMap_, getAccountId());
3217std::vector<std::shared_ptr<RtpSession>>
3220 std::vector<std::shared_ptr<RtpSession>> rtpList;
3221 rtpList.reserve(rtpStreams_.size());
3222 for (
auto const& stream : rtpStreams_) {
3223 if (type == MediaType::MEDIA_ALL || stream.rtpSession_->getMediaType() == type)
3224 rtpList.emplace_back(stream.rtpSession_);
3230SIPCall::monitor()
const
3234 auto acc = getSIPAccount();
3239 JAMI_LOG(
"- Call {} with {}:", getCallId(), getPeerNumber());
3240 JAMI_LOG(
"\t- Duration: {}", dht::print_duration(getCallDuration()));
3241 for (
const auto& stream : rtpStreams_)
3242 JAMI_LOG(
"\t- Media: {}", stream.mediaAttribute_->toString(
true));
3244 if (
auto codec = getVideoCodec())
3245 JAMI_LOG(
"\t- Video codec: {}", codec->name);
3247 if (
auto transport = getIceMedia()) {
3248 if (transport->isRunning())
3249 JAMI_LOG(
"\t- Media stream(s): {}", transport->link());
3254SIPCall::toggleRecording()
3256 pendingRecord_ =
true;
3257 if (not readyToRecord_)
3261 if (not Call::isRecording()) {
3262 auto account = getSIPAccount();
3267 auto title = fmt::format(
"Conversation at %TIMESTAMP between {} and {}", account->getUserUri(), peerUri_);
3268 recorder_->setMetadata(title,
"");
3269 for (
const auto& rtpSession : getRtpSessionList())
3270 rtpSession->initRecorder();
3272 updateRecState(
false);
3274 pendingRecord_ =
false;
3275 auto state = Call::toggleRecording();
3277 updateRecState(state);
3282SIPCall::deinitRecorder()
3284 for (
const auto& rtpSession : getRtpSessionList())
3285 rtpSession->deinitRecorder();
3289SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv)
const noexcept
3295 inv->mod_data[Manager::instance().sipVoIPLink().getModId()] =
nullptr;
3297 pjsip_inv_dec_ref(inv);
3301SIPCall::createIceMediaTransport(
bool isReinvite)
3303 auto mediaTransport = Manager::instance().getIceTransportFactory()->createTransport(getCallId());
3304 if (mediaTransport) {
3305 JAMI_DEBUG(
"[call:{}] Successfully created media ICE transport [ice:{}]",
3307 fmt::ptr(mediaTransport.get()));
3309 JAMI_ERROR(
"[call:{}] Failed to create media ICE transport", getCallId());
3313 setIceMedia(mediaTransport, isReinvite);
3315 return mediaTransport !=
nullptr;
3319SIPCall::initIceMediaTransport(
bool master, std::optional<dhtnet::IceTransportOptions> options)
3321 auto acc = getSIPAccount();
3327 JAMI_DEBUG(
"[call:{}] Init media ICE transport", getCallId());
3329 auto const& iceMedia = getIceMedia();
3331 JAMI_ERROR(
"[call:{}] Invalid media ICE transport", getCallId());
3335 auto iceOptions = options == std::nullopt ? acc->getIceOptions() : *options;
3337 auto optOnInitDone = std::move(iceOptions.onInitDone);
3338 auto optOnNegoDone = std::move(iceOptions.onNegoDone);
3339 iceOptions.onInitDone = [w = weak(), cb = std::move(optOnInitDone)](
bool ok) {
3340 runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3341 auto call = w.lock();
3344 if (!ok or !call or !call->waitForIceInit_.exchange(
false))
3347 std::lock_guard lk {call->callMutex_};
3348 auto rem_ice_attrs = call->sdp_->getIceAttributes();
3350 if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty())
3352 call->startIceMedia();
3355 iceOptions.onNegoDone = [w = weak(), cb = std::move(optOnNegoDone)](
bool ok) {
3356 runOnMainThread([w = std::move(w), cb = std::move(cb), ok] {
3359 if (
auto call = w.lock()) {
3361 std::lock_guard lk {call->callMutex_};
3362 call = call->isSubcall() ? std::dynamic_pointer_cast<SIPCall>(call->parent_) : call;
3364 JAMI_ERROR(
"[call:{}] Media ICE negotiation failed", call->getCallId());
3365 call->onFailure(PJSIP_SC_NOT_ACCEPTABLE_HERE);
3368 call->onIceNegoSucceed();
3373 iceOptions.master = master;
3374 iceOptions.streamsCount =
static_cast<unsigned>(rtpStreams_.size());
3378 for (
const auto& stream : rtpStreams_) {
3379 iceOptions.qosType.push_back(stream.mediaAttribute_->type_ == MediaType::MEDIA_AUDIO ? dhtnet::QosType::VOICE
3380 :
dhtnet::QosType::VIDEO);
3381 iceOptions.qosType.push_back(dhtnet::QosType::CONTROL);
3385 iceMedia->initIceInstance(iceOptions);
3390std::vector<std::string>
3391SIPCall::getLocalIceCandidates(
unsigned compId)
const
3393 std::lock_guard lk(transportMtx_);
3394 if (not iceMedia_) {
3395 JAMI_WARNING(
"[call:{}] No media ICE transport", getCallId());
3398 return iceMedia_->getLocalCandidates(compId);
3402SIPCall::resetTransport(std::shared_ptr<dhtnet::IceTransport>&& transport)
3406 dht::ThreadPool::io().run([transport = std::move(transport)]()
mutable { transport.reset(); });
3411SIPCall::merge(Call& call)
3413 JAMI_DEBUG(
"[call:{}] Merge subcall {}", getCallId(), call.getCallId());
3416 auto& subcall =
static_cast<SIPCall&
>(call);
3418 std::lock(callMutex_, subcall.callMutex_);
3419 std::lock_guard lk1 {callMutex_, std::adopt_lock};
3420 std::lock_guard lk2 {subcall.callMutex_, std::adopt_lock};
3421 inviteSession_ = std::move(subcall.inviteSession_);
3423 inviteSession_->mod_data[Manager::instance().sipVoIPLink().getModId()] =
this;
3424 setSipTransport(std::move(subcall.sipTransport_), std::move(subcall.contactHeader_));
3425 sdp_ = std::move(subcall.sdp_);
3426 peerHold_ = subcall.peerHold_;
3427 upnp_ = std::move(subcall.upnp_);
3428 localAudioPort_ = subcall.localAudioPort_;
3429 localVideoPort_ = subcall.localVideoPort_;
3430 peerUserAgent_ = subcall.peerUserAgent_;
3431 peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
3432 peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_;
3433 peerSupportMultiIce_ = subcall.peerSupportMultiIce_;
3434 peerAllowedMethods_ = subcall.peerAllowedMethods_;
3435 peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_;
3437 Call::merge(subcall);
3443SIPCall::remoteHasValidIceAttributes()
const
3446 throw std::runtime_error(
"Must have a valid SDP Session");
3449 auto rem_ice_attrs = sdp_->getIceAttributes();
3450 if (rem_ice_attrs.ufrag.empty()) {
3451 JAMI_DEBUG(
"[call:{}] No ICE username fragment attribute in remote SDP", getCallId());
3455 if (rem_ice_attrs.pwd.empty()) {
3456 JAMI_DEBUG(
"[call:{}] No ICE password attribute in remote SDP", getCallId());
3464SIPCall::setIceMedia(std::shared_ptr<dhtnet::IceTransport> ice,
bool isReinvite)
3466 std::lock_guard lk(transportMtx_);
3469 JAMI_DEBUG(
"[call:{}] Setting re-invite ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
3470 resetTransport(std::move(reinvIceMedia_));
3471 reinvIceMedia_ = std::move(ice);
3473 JAMI_DEBUG(
"[call:{}] Setting ICE session [{}]", getCallId(), fmt::ptr(ice.get()));
3474 resetTransport(std::move(iceMedia_));
3475 iceMedia_ = std::move(ice);
3480SIPCall::switchToIceReinviteIfNeeded()
3482 std::lock_guard lk(transportMtx_);
3484 if (reinvIceMedia_) {
3485 JAMI_DEBUG(
"[call:{}] Switching to re-invite ICE session [{}]", getCallId(), fmt::ptr(reinvIceMedia_.get()));
3486 std::swap(reinvIceMedia_, iceMedia_);
3489 resetTransport(std::move(reinvIceMedia_));
3493SIPCall::setupIceResponse(
bool isReinvite)
3495 JAMI_DEBUG(
"[call:{}] Setup ICE response", getCallId());
3497 auto account = getSIPAccount();
3502 auto opt = account->getIceOptions();
3506 opt.accountPublicAddr = account->getPublishedIpAddress();
3507 if (opt.accountPublicAddr) {
3508 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(),
3509 opt.accountPublicAddr.getFamily());
3513 opt.accountLocalAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), AF_INET);
3514 opt.accountPublicAddr = opt.accountLocalAddr;
3517 if (not opt.accountLocalAddr) {
3518 JAMI_ERROR(
"[call:{}] No local address, unable to initialize ICE", getCallId());
3519 onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
3523 if (not createIceMediaTransport(isReinvite) or not initIceMediaTransport(
false, opt)) {
3524 JAMI_ERROR(
"[call:{}] ICE initialization failed", getCallId());
3528 onFailure(PJSIP_SC_INTERNAL_SERVER_ERROR);
3533 mediaRestartRequired_ =
true;
3536 addLocalIceAttributes();
3540SIPCall::isIceRunning()
const
3542 std::lock_guard lk(transportMtx_);
3543 return iceMedia_ and iceMedia_->isRunning();
3546std::unique_ptr<dhtnet::IceSocket>
3547SIPCall::newIceSocket(
unsigned compId)
3549 return std::unique_ptr<dhtnet::IceSocket> {
new dhtnet::IceSocket(getIceMedia(),
static_cast<int>(compId))};
3553SIPCall::rtpSetupSuccess()
3555 std::lock_guard lk {mediaStateMutex_};
3557 readyToRecord_ =
true;
3559 auto previousState = isAudioOnly_;
3560 auto newState = !hasVideo();
3562 if (previousState != newState && Call::isRecording()) {
3565 pendingRecord_ =
true;
3567 isAudioOnly_ = newState;
3569 if (pendingRecord_ && readyToRecord_)
3574SIPCall::peerRecording(
bool state)
3576 auto conference = conf_.lock();
3577 const std::string&
id = conference ? conference->getConfId() : getCallId();
3579 JAMI_WARNING(
"[call:{}] Peer is recording", getCallId());
3580 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
true);
3583 emitSignal<libjami::CallSignal::RemoteRecordingChanged>(
id, getPeerNumber(),
false);
3585 peerRecording_ = state;
3586 if (
auto conf = conf_.lock())
3587 conf->updateRecording();
3591SIPCall::peerMuted(
bool muted,
int streamIdx)
3599 if (streamIdx == -1) {
3600 for (
const auto& audioRtp : getRtpSessionList(MediaType::MEDIA_AUDIO))
3601 audioRtp->setMuted(muted, RtpSession::Direction::RECV);
3602 }
else if (streamIdx > -1 && streamIdx <
static_cast<int>(rtpStreams_.size())) {
3603 auto& stream = rtpStreams_[streamIdx];
3604 if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_AUDIO)
3605 stream.rtpSession_->setMuted(muted, RtpSession::Direction::RECV);
3609 if (
auto conf = conf_.lock())
3610 conf->updateMuted();
3614SIPCall::peerVoice(
bool voice)
3618 if (
auto conference = conf_.lock()) {
3619 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 type_
Type of the call.
CallType
This determines if the call originated from the local user (OUTGOING) or from some remote peer (INCOM...
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)
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 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_HOLD[]
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 toggleRecording(const std::string &accountId, const std::string &callId)
bool resume(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(...)