LCOV - code coverage report
Current view: top level - src/sip - sipcall.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1391 1962 70.9 %
Date: 2024-12-21 08:56:24 Functions: 152 200 76.0 %

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

Generated by: LCOV version 1.14