LCOV - code coverage report
Current view: top level - foo/src/media/video - video_rtp_session.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 441 542 81.4 %
Date: 2025-12-18 10:07:43 Functions: 54 62 87.1 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "client/videomanager.h"
      19             : #include "video_rtp_session.h"
      20             : #include "video_sender.h"
      21             : #include "video_receive_thread.h"
      22             : #include "video_mixer.h"
      23             : #include "socket_pair.h"
      24             : #include "sip/sipvoiplink.h" // for enqueueKeyframeRequest
      25             : #include "manager.h"
      26             : #include "media_const.h"
      27             : #ifdef ENABLE_PLUGIN
      28             : #include "plugin/streamdata.h"
      29             : #include "plugin/jamipluginmanager.h"
      30             : #endif
      31             : #include "logger.h"
      32             : #include "string_utils.h"
      33             : #include "call.h"
      34             : #include "conference.h"
      35             : #include "congestion_control.h"
      36             : 
      37             : #include "account_const.h"
      38             : 
      39             : #include <dhtnet/ice_socket.h>
      40             : #include <asio/post.hpp>
      41             : #include <asio/io_context.hpp>
      42             : 
      43             : #include <sstream>
      44             : #include <map>
      45             : #include <string>
      46             : #include <thread>
      47             : #include <chrono>
      48             : 
      49             : namespace jami {
      50             : namespace video {
      51             : 
      52             : using std::string;
      53             : 
      54             : static constexpr unsigned MAX_REMB_DEC {1};
      55             : 
      56             : constexpr auto DELAY_AFTER_RESTART = std::chrono::milliseconds(1000);
      57             : constexpr auto EXPIRY_TIME_RTCP = std::chrono::seconds(2);
      58             : constexpr auto DELAY_AFTER_REMB_INC = std::chrono::seconds(1);
      59             : constexpr auto DELAY_AFTER_REMB_DEC = std::chrono::milliseconds(500);
      60             : 
      61         301 : VideoRtpSession::VideoRtpSession(const string& callId,
      62             :                                  const string& streamId,
      63             :                                  const DeviceParams& localVideoParams,
      64         301 :                                  const std::shared_ptr<MediaRecorder>& rec)
      65             :     : RtpSession(callId, streamId, MediaType::MEDIA_VIDEO)
      66         301 :     , localVideoParams_(localVideoParams)
      67         301 :     , videoBitrateInfo_ {}
      68         939 :     , rtcpCheckerThread_([] { return true; }, [this] { processRtcpChecker(); }, [] {})
      69         602 :     , cc(std::make_unique<CongestionControl>())
      70             : {
      71         301 :     recorder_ = rec;
      72         301 :     setupVideoBitrateInfo(); // reset bitrate
      73        1204 :     JAMI_LOG("[{:p}] Video RTP session created for call {} (recorder {:p})",
      74             :              fmt::ptr(this),
      75             :              callId_,
      76             :              fmt::ptr(recorder_));
      77         301 : }
      78             : 
      79         301 : VideoRtpSession::~VideoRtpSession()
      80             : {
      81         301 :     deinitRecorder();
      82         301 :     stop();
      83        1204 :     JAMI_LOG("[{:p}] Video RTP session destroyed", fmt::ptr(this));
      84         301 : }
      85             : 
      86             : const VideoBitrateInfo&
      87          83 : VideoRtpSession::getVideoBitrateInfo()
      88             : {
      89          83 :     return videoBitrateInfo_;
      90             : }
      91             : 
      92             : /// Setup internal VideoBitrateInfo structure from media descriptors.
      93             : ///
      94             : void
      95         146 : VideoRtpSession::updateMedia(const MediaDescription& send, const MediaDescription& receive)
      96             : {
      97         146 :     BaseType::updateMedia(send, receive);
      98             :     // adjust send->codec bitrate info for higher video resolutions
      99         146 :     auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     100         146 :     if (codecVideo) {
     101         146 :         auto const pixels = localVideoParams_.height * localVideoParams_.width;
     102         146 :         codecVideo->bitrate = std::max((unsigned int) (pixels * 0.001), SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
     103         146 :         codecVideo->maxBitrate = std::max((unsigned int) (pixels * 0.0015), SystemCodecInfo::DEFAULT_MAX_BITRATE);
     104             :     }
     105         146 :     setupVideoBitrateInfo();
     106         146 : }
     107             : 
     108             : void
     109         146 : VideoRtpSession::setRequestKeyFrameCallback(std::function<void(void)> cb)
     110             : {
     111         146 :     cbKeyFrameRequest_ = std::move(cb);
     112         146 : }
     113             : 
     114             : void
     115         166 : VideoRtpSession::startSender()
     116             : {
     117         166 :     std::lock_guard lock(mutex_);
     118             : 
     119         166 :     JAMI_DBG("[%p] Start video RTP sender: input [%s] - muted [%s]",
     120             :              this,
     121             :              conference_ ? "Video Mixer" : input_.c_str(),
     122             :              send_.onHold ? "YES" : "NO");
     123             : 
     124         166 :     if (not socketPair_) {
     125             :         // Ignore if the transport is not set yet
     126           0 :         JAMI_WARN("[%p] Transport not set yet", this);
     127           0 :         return;
     128             :     }
     129             : 
     130         166 :     if (send_.enabled and not send_.onHold) {
     131         157 :         if (sender_) {
     132          19 :             if (videoLocal_)
     133          18 :                 videoLocal_->detach(sender_.get());
     134          19 :             if (videoMixer_)
     135          19 :                 videoMixer_->detach(sender_.get());
     136          19 :             JAMI_WARN("[%p] Restarting video sender", this);
     137             :         }
     138             : 
     139         157 :         if (not conference_) {
     140         108 :             videoLocal_ = getVideoInput(input_);
     141         108 :             if (videoLocal_) {
     142         108 :                 videoLocal_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     143           0 :                     asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     144           0 :                         if (auto shared = w.lock())
     145           0 :                             shared->attachLocalRecorder(ms);
     146           0 :                     });
     147           0 :                 });
     148         108 :                 auto newParams = videoLocal_->getParams();
     149             :                 try {
     150         108 :                     if (newParams.valid() && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
     151          98 :                         localVideoParams_ = newParams.get();
     152             :                     } else {
     153          10 :                         JAMI_ERR("[%p] No valid new video parameters", this);
     154          10 :                         return;
     155             :                     }
     156           0 :                 } catch (const std::exception& e) {
     157           0 :                     JAMI_ERR("Exception during retrieving video parameters: %s", e.what());
     158           0 :                     return;
     159           0 :                 }
     160         108 :             } else {
     161           0 :                 JAMI_WARN("Unable to lock video input");
     162           0 :                 return;
     163             :             }
     164             : 
     165             : #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
     166             :             videoLocal_->setupSink(localVideoParams_.width, localVideoParams_.height);
     167             : #endif
     168             :         }
     169             : 
     170             :         // be sure to not send any packets before saving last RTP seq value
     171         147 :         socketPair_->stopSendOp();
     172             : 
     173         147 :         auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     174         147 :         auto autoQuality = codecVideo->isAutoQualityEnabled;
     175             : 
     176         147 :         send_.linkableHW = conference_ == nullptr;
     177         147 :         send_.bitrate = videoBitrateInfo_.videoBitrateCurrent;
     178             :         // NOTE:
     179             :         // Current implementation does not handle resolution change
     180             :         // (needed by window sharing feature) with HW codecs, so HW
     181             :         // codecs will be disabled for now.
     182         265 :         bool allowHwAccel = (localVideoParams_.format != "x11grab" && localVideoParams_.format != "dxgigrab"
     183         265 :                              && localVideoParams_.format != "lavfi");
     184             : 
     185         147 :         if (socketPair_)
     186         147 :             initSeqVal_ = socketPair_->lastSeqValOut();
     187             : 
     188             :         try {
     189         147 :             sender_.reset();
     190         147 :             socketPair_->stopSendOp(false);
     191         147 :             MediaStream ms = !videoMixer_ ? MediaStream("video sender",
     192             :                                                         AV_PIX_FMT_YUV420P,
     193         245 :                                                         1 / static_cast<rational<int>>(localVideoParams_.framerate),
     194          98 :                                                         localVideoParams_.width == 0 ? 1080u : localVideoParams_.width,
     195          98 :                                                         localVideoParams_.height == 0 ? 720u : localVideoParams_.height,
     196          98 :                                                         send_.bitrate,
     197             :                                                         static_cast<rational<int>>(localVideoParams_.framerate))
     198         539 :                                           : videoMixer_->getStream("Video Sender");
     199         147 :             sender_.reset(
     200         294 :                 new VideoSender(getRemoteRtpUri(), ms, send_, *socketPair_, initSeqVal_ + 1, mtu_, allowHwAccel));
     201         147 :             if (changeOrientationCallback_)
     202         147 :                 sender_->setChangeOrientationCallback(changeOrientationCallback_);
     203         147 :             if (socketPair_)
     204         147 :                 socketPair_->setPacketLossCallback([this]() { cbKeyFrameRequest_(); });
     205             : 
     206         147 :         } catch (const MediaEncoderException& e) {
     207           0 :             JAMI_ERR("%s", e.what());
     208           0 :             send_.enabled = false;
     209           0 :         }
     210         147 :         lastMediaRestart_ = clock::now();
     211         147 :         last_REMB_inc_ = clock::now();
     212         147 :         last_REMB_dec_ = clock::now();
     213         147 :         if (autoQuality and not rtcpCheckerThread_.isRunning())
     214         128 :             rtcpCheckerThread_.start();
     215          19 :         else if (not autoQuality and rtcpCheckerThread_.isRunning())
     216           0 :             rtcpCheckerThread_.join();
     217             :         // Block reads to received feedback packets
     218         147 :         if (socketPair_)
     219         147 :             socketPair_->setReadBlockingMode(true);
     220         147 :     }
     221         166 : }
     222             : 
     223             : void
     224          22 : VideoRtpSession::restartSender()
     225             : {
     226          22 :     std::lock_guard lock(mutex_);
     227             : 
     228             :     // ensure that start has been called before restart
     229          22 :     if (not socketPair_)
     230           2 :         return;
     231             : 
     232          20 :     startSender();
     233             : 
     234          20 :     if (conference_)
     235          20 :         setupConferenceVideoPipeline(*conference_, Direction::SEND);
     236             :     else
     237           0 :         setupVideoPipeline();
     238          22 : }
     239             : 
     240             : void
     241         663 : VideoRtpSession::stopSender(bool forceStopSocket)
     242             : {
     243             :     // Concurrency protection must be done by caller.
     244             : 
     245         663 :     JAMI_DBG("[%p] Stop video RTP sender: input [%s] - muted [%s]",
     246             :              this,
     247             :              conference_ ? "Video Mixer" : input_.c_str(),
     248             :              send_.onHold ? "YES" : "NO");
     249             : 
     250         663 :     if (sender_) {
     251         128 :         if (videoLocal_)
     252          98 :             videoLocal_->detach(sender_.get());
     253         128 :         if (videoMixer_)
     254           3 :             videoMixer_->detach(sender_.get());
     255         128 :         sender_.reset();
     256             :     }
     257             : 
     258         663 :     if (socketPair_) {
     259         150 :         bool const isReceivingVideo = receive_.enabled && !receive_.onHold;
     260         150 :         if (forceStopSocket || !isReceivingVideo) {
     261         148 :             socketPair_->stopSendOp();
     262         148 :             socketPair_->setReadBlockingMode(false);
     263             :         }
     264             :     }
     265         663 : }
     266             : 
     267             : void
     268         146 : VideoRtpSession::startReceiver()
     269             : {
     270             :     // Concurrency protection must be done by caller.
     271             : 
     272         146 :     JAMI_DBG("[%p] Starting receiver", this);
     273             : 
     274         146 :     if (receive_.enabled and not receive_.onHold) {
     275         139 :         if (receiveThread_)
     276          14 :             JAMI_WARN("[%p] Already has a receiver, restarting", this);
     277         139 :         receiveThread_.reset(new VideoReceiveThread(callId_, !conference_, receive_.receiving_sdp, mtu_));
     278             : 
     279             :         // ensure that start has been called
     280         139 :         if (not socketPair_)
     281           0 :             return;
     282             : 
     283             :         // XXX keyframe requests can timeout if unanswered
     284         139 :         receiveThread_->addIOContext(*socketPair_);
     285         139 :         receiveThread_->setSuccessfulSetupCb(onSuccessfulSetup_);
     286         139 :         receiveThread_->startLoop();
     287         139 :         receiveThread_->setRequestKeyFrameCallback([this]() { cbKeyFrameRequest_(); });
     288         278 :         receiveThread_->setRotation(rotation_.load());
     289         139 :         if (videoMixer_ and conference_) {
     290             :             // Note, this should be managed differently, this is a bit hacky
     291          30 :             auto audioId = streamId_;
     292          30 :             string_replace(audioId, "video", "audio");
     293          30 :             auto activeStream = videoMixer_->verifyActive(audioId);
     294          30 :             videoMixer_->removeAudioOnlySource(callId_, audioId);
     295          30 :             if (activeStream)
     296           0 :                 videoMixer_->setActiveStream(streamId_);
     297          30 :         }
     298         139 :         receiveThread_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     299          31 :             asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     300          31 :                 if (auto shared = w.lock())
     301          31 :                     shared->attachRemoteRecorder(ms);
     302          31 :             });
     303          31 :         });
     304         139 :     } else {
     305           7 :         JAMI_DBG("[%p] Video receiver disabled", this);
     306           7 :         if (videoMixer_ and conference_) {
     307             :             // Note, this should be managed differently, this is a bit hacky
     308           0 :             auto audioId_ = streamId_;
     309           0 :             string_replace(audioId_, "video", "audio");
     310           0 :             if (receiveThread_) {
     311           0 :                 auto activeStream = videoMixer_->verifyActive(streamId_);
     312           0 :                 videoMixer_->addAudioOnlySource(callId_, audioId_);
     313           0 :                 receiveThread_->detach(videoMixer_.get());
     314           0 :                 if (activeStream)
     315           0 :                     videoMixer_->setActiveStream(audioId_);
     316             :             } else {
     317             :                 // Add audio-only source when video is disabled or muted.
     318             :                 // Called after ICE negotiation, when peers can properly create video sinks.
     319           0 :                 if (not receive_.enabled or receive_.onHold) {
     320           0 :                     videoMixer_->addAudioOnlySource(callId_, audioId_);
     321             :                 }
     322             :             }
     323           0 :         }
     324             :     }
     325         146 :     if (socketPair_)
     326         146 :         socketPair_->setReadBlockingMode(true);
     327             : }
     328             : 
     329             : void
     330         655 : VideoRtpSession::stopReceiver(bool forceStopSocket)
     331             : {
     332             :     // Concurrency protection must be done by caller.
     333             : 
     334         655 :     JAMI_DBG("[%p] Stopping receiver", this);
     335             : 
     336         655 :     if (not receiveThread_)
     337         373 :         return;
     338             : 
     339         282 :     if (videoMixer_) {
     340           3 :         auto activeStream = videoMixer_->verifyActive(streamId_);
     341           3 :         auto audioId = streamId_;
     342           3 :         string_replace(audioId, "video", "audio");
     343           3 :         videoMixer_->addAudioOnlySource(callId_, audioId);
     344           3 :         receiveThread_->detach(videoMixer_.get());
     345           3 :         if (activeStream)
     346           0 :             videoMixer_->setActiveStream(audioId);
     347           3 :     }
     348             : 
     349             :     // We need to disable the read operation, otherwise the
     350             :     // receiver thread will block since the peer stopped sending
     351             :     // RTP packets.
     352         282 :     bool const isSendingVideo = send_.enabled && !send_.onHold;
     353         282 :     if (socketPair_) {
     354         142 :         if (forceStopSocket || !isSendingVideo) {
     355         142 :             socketPair_->setReadBlockingMode(false);
     356         142 :             socketPair_->stopSendOp();
     357             :         }
     358             :     }
     359             : 
     360         282 :     auto ms = receiveThread_->getInfo();
     361         282 :     if (auto ob = recorder_->getStream(ms.name)) {
     362          31 :         receiveThread_->detach(ob);
     363          31 :         recorder_->removeStream(ms);
     364             :     }
     365             : 
     366         282 :     if (forceStopSocket || !isSendingVideo)
     367         282 :         receiveThread_->stopLoop();
     368         282 :     receiveThread_->stopSink();
     369         282 : }
     370             : 
     371             : void
     372         150 : VideoRtpSession::start(std::unique_ptr<dhtnet::IceSocket> rtp_sock, std::unique_ptr<dhtnet::IceSocket> rtcp_sock)
     373             : {
     374         150 :     std::lock_guard lock(mutex_);
     375             : 
     376         150 :     if (not send_.enabled and not receive_.enabled) {
     377           4 :         stop();
     378           4 :         return;
     379             :     }
     380             : 
     381             :     try {
     382         146 :         if (rtp_sock and rtcp_sock) {
     383         144 :             if (send_.addr) {
     384         144 :                 rtp_sock->setDefaultRemoteAddress(send_.addr);
     385             :             }
     386             : 
     387         144 :             auto& rtcpAddr = send_.rtcp_addr ? send_.rtcp_addr : send_.addr;
     388         144 :             if (rtcpAddr) {
     389         144 :                 rtcp_sock->setDefaultRemoteAddress(rtcpAddr);
     390             :             }
     391         144 :             socketPair_.reset(new SocketPair(std::move(rtp_sock), std::move(rtcp_sock)));
     392             :         } else {
     393           2 :             socketPair_.reset(new SocketPair(getRemoteRtpUri().c_str(), receive_.addr.getPort()));
     394             :         }
     395             : 
     396         146 :         last_REMB_inc_ = clock::now();
     397         146 :         last_REMB_dec_ = clock::now();
     398             : 
     399        5840 :         socketPair_->setRtpDelayCallback([&](int gradient, int deltaT) { delayMonitor(gradient, deltaT); });
     400             : 
     401         146 :         if (send_.crypto and receive_.crypto) {
     402         584 :             socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(),
     403         292 :                                     receive_.crypto.getSrtpKeyInfo().c_str(),
     404         292 :                                     send_.crypto.getCryptoSuite().c_str(),
     405         292 :                                     send_.crypto.getSrtpKeyInfo().c_str());
     406             :         }
     407           0 :     } catch (const std::runtime_error& e) {
     408           0 :         JAMI_ERR("[%p] Socket creation failed: %s", this, e.what());
     409           0 :         return;
     410           0 :     }
     411             : 
     412         146 :     startReceiver();
     413         146 :     startSender();
     414             : 
     415         146 :     if (conference_) {
     416          30 :         if (send_.enabled and not send_.onHold) {
     417          30 :             setupConferenceVideoPipeline(*conference_, Direction::SEND);
     418             :         }
     419          30 :         if (receive_.enabled and not receive_.onHold) {
     420          30 :             setupConferenceVideoPipeline(*conference_, Direction::RECV);
     421             :         }
     422             :     } else {
     423         116 :         setupVideoPipeline();
     424             :     }
     425         150 : }
     426             : 
     427             : void
     428         653 : VideoRtpSession::stop()
     429             : {
     430         653 :     std::lock_guard lock(mutex_);
     431             : 
     432         653 :     stopSender(true);
     433         653 :     stopReceiver(true);
     434             : 
     435         653 :     if (socketPair_)
     436         146 :         socketPair_->interrupt();
     437             : 
     438         653 :     rtcpCheckerThread_.join();
     439             : 
     440             :     // reset default video quality if exist
     441         653 :     if (videoBitrateInfo_.videoQualityCurrent != SystemCodecInfo::DEFAULT_NO_QUALITY)
     442         103 :         videoBitrateInfo_.videoQualityCurrent = SystemCodecInfo::DEFAULT_CODEC_QUALITY;
     443             : 
     444         653 :     videoBitrateInfo_.videoBitrateCurrent = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
     445         653 :     storeVideoBitrateInfo();
     446             : 
     447         653 :     socketPair_.reset();
     448         653 :     videoLocal_.reset();
     449         653 : }
     450             : 
     451             : void
     452         302 : VideoRtpSession::setMuted(bool mute, Direction dir)
     453             : {
     454         302 :     std::lock_guard lock(mutex_);
     455             : 
     456             :     // Sender
     457         302 :     if (dir == Direction::SEND) {
     458         152 :         if (send_.onHold == mute) {
     459         140 :             JAMI_DBG("[%p] Local already %s", this, mute ? "muted" : "un-muted");
     460         140 :             return;
     461             :         }
     462             : 
     463          12 :         if ((send_.onHold = mute)) {
     464          10 :             if (videoLocal_) {
     465           3 :                 auto ms = videoLocal_->getInfo();
     466           3 :                 if (auto ob = recorder_->getStream(ms.name)) {
     467           0 :                     videoLocal_->detach(ob);
     468           0 :                     recorder_->removeStream(ms);
     469             :                 }
     470           3 :             }
     471          10 :             stopSender();
     472             :         } else {
     473           2 :             restartSender();
     474             :         }
     475          12 :         return;
     476             :     }
     477             : 
     478             :     // Receiver
     479         150 :     if (receive_.onHold == mute) {
     480         148 :         JAMI_DBG("[%p] Remote already %s", this, mute ? "muted" : "un-muted");
     481         148 :         return;
     482             :     }
     483             : 
     484           2 :     if ((receive_.onHold = mute)) {
     485           2 :         if (receiveThread_) {
     486           0 :             auto ms = receiveThread_->getInfo();
     487           0 :             if (auto ob = recorder_->getStream(ms.name)) {
     488           0 :                 receiveThread_->detach(ob);
     489           0 :                 recorder_->removeStream(ms);
     490             :             }
     491           0 :         }
     492           2 :         stopReceiver();
     493             :     } else {
     494           0 :         startReceiver();
     495           0 :         if (conference_ and not receive_.onHold) {
     496           0 :             setupConferenceVideoPipeline(*conference_, Direction::RECV);
     497             :         }
     498             :     }
     499         302 : }
     500             : 
     501             : void
     502         146 : VideoRtpSession::forceKeyFrame()
     503             : {
     504         146 :     std::lock_guard lock(mutex_);
     505             : #if __ANDROID__
     506             :     if (videoLocal_)
     507             :         emitSignal<libjami::VideoSignal::RequestKeyFrame>(videoLocal_->getName());
     508             : #else
     509         146 :     if (sender_)
     510         121 :         sender_->forceKeyFrame();
     511             : #endif
     512         146 : }
     513             : 
     514             : void
     515         350 : VideoRtpSession::setRotation(int rotation)
     516             : {
     517         350 :     rotation_.store(rotation);
     518         350 :     if (receiveThread_)
     519          49 :         receiveThread_->setRotation(rotation);
     520         350 : }
     521             : 
     522             : void
     523         116 : VideoRtpSession::setupVideoPipeline()
     524             : {
     525         116 :     if (sender_) {
     526          98 :         if (videoLocal_) {
     527          98 :             JAMI_DBG("[%p] Setup video pipeline on local capture device", this);
     528          98 :             videoLocal_->attach(sender_.get());
     529             :         }
     530             :     } else {
     531          18 :         videoLocal_.reset();
     532             :     }
     533         116 : }
     534             : 
     535             : void
     536         100 : VideoRtpSession::setupConferenceVideoPipeline(Conference& conference, Direction dir)
     537             : {
     538         100 :     if (dir == Direction::SEND) {
     539         200 :         JAMI_DEBUG("[conf:{}] Setup video sender pipeline for call {}", conference.getConfId(), callId_);
     540          50 :         videoMixer_ = conference.getVideoMixer();
     541          50 :         if (sender_) {
     542             :             // Swap sender from local video to conference video mixer
     543          49 :             if (videoLocal_)
     544          18 :                 videoLocal_->detach(sender_.get());
     545          49 :             if (videoMixer_)
     546          49 :                 videoMixer_->attach(sender_.get());
     547             :         } else {
     548           1 :             JAMI_WARN("[%p] no sender", this);
     549             :         }
     550             :     } else {
     551         200 :         JAMI_DEBUG("[conf:{}] Setup video receiver pipeline for call {}", conference.getConfId(), callId_);
     552          50 :         if (receiveThread_) {
     553          50 :             receiveThread_->stopSink();
     554          50 :             if (videoMixer_)
     555          50 :                 videoMixer_->attachVideo(receiveThread_.get(), callId_, streamId_);
     556             :         } else {
     557           0 :             JAMI_WARN("[%p] no receiver", this);
     558             :         }
     559             :     }
     560         100 : }
     561             : 
     562             : void
     563          59 : VideoRtpSession::enterConference(Conference& conference)
     564             : {
     565          59 :     std::lock_guard lock(mutex_);
     566             : 
     567          59 :     exitConference();
     568             : 
     569          59 :     conference_ = &conference;
     570          59 :     videoMixer_ = conference.getVideoMixer();
     571         236 :     JAMI_DEBUG("[conf:{}] Entering conference", conference.getConfId());
     572             : 
     573          59 :     if (send_.enabled or receiveThread_) {
     574             :         // Restart encoder with conference parameter ON in order to unlink HW encoder
     575             :         // from HW decoder.
     576          20 :         restartSender();
     577          20 :         if (conference_) {
     578          20 :             setupConferenceVideoPipeline(conference, Direction::RECV);
     579             :         }
     580             :     }
     581          59 : }
     582             : 
     583             : void
     584         117 : VideoRtpSession::exitConference()
     585             : {
     586         117 :     std::lock_guard lock(mutex_);
     587             : 
     588         117 :     if (!conference_)
     589          58 :         return;
     590             : 
     591         236 :     JAMI_DEBUG("[conf:{}] Exiting conference", conference_->getConfId());
     592             : 
     593          59 :     if (videoMixer_) {
     594          59 :         if (sender_)
     595          46 :             videoMixer_->detach(sender_.get());
     596             : 
     597          59 :         if (receiveThread_) {
     598          49 :             auto activeStream = videoMixer_->verifyActive(streamId_);
     599          49 :             videoMixer_->detachVideo(receiveThread_.get());
     600          49 :             receiveThread_->startSink();
     601          49 :             if (activeStream)
     602           0 :                 videoMixer_->setActiveStream(streamId_);
     603             :         }
     604             : 
     605          59 :         videoMixer_.reset();
     606             :     }
     607             : 
     608          59 :     conference_ = nullptr;
     609         117 : }
     610             : 
     611             : bool
     612         382 : VideoRtpSession::check_RCTP_Info_RR(RTCPInfo& rtcpi)
     613             : {
     614         382 :     auto rtcpInfoVect = socketPair_->getRtcpRR();
     615         382 :     unsigned totalLost = 0;
     616         382 :     unsigned totalJitter = 0;
     617         382 :     unsigned nbDropNotNull = 0;
     618         382 :     auto vectSize = rtcpInfoVect.size();
     619             : 
     620         382 :     if (vectSize != 0) {
     621          20 :         for (const auto& it : rtcpInfoVect) {
     622          10 :             if (it.fraction_lost != 0) // Exclude null drop
     623           0 :                 nbDropNotNull++;
     624          10 :             totalLost += it.fraction_lost;
     625          10 :             totalJitter += ntohl(it.jitter);
     626             :         }
     627          10 :         rtcpi.packetLoss = nbDropNotNull ? (float) (100 * totalLost) / (256.0 * nbDropNotNull) : 0;
     628             :         // Jitter is expressed in timestamp unit -> convert to milliseconds
     629             :         // https://stackoverflow.com/questions/51956520/convert-jitter-from-rtp-timestamp-unit-to-millisseconds
     630          10 :         rtcpi.jitter = (totalJitter / vectSize / 90000.0f) * 1000;
     631          10 :         rtcpi.nb_sample = vectSize;
     632          10 :         rtcpi.latency = socketPair_->getLastLatency();
     633          10 :         return true;
     634             :     }
     635         372 :     return false;
     636         382 : }
     637             : 
     638             : bool
     639         382 : VideoRtpSession::check_RCTP_Info_REMB(uint64_t* br)
     640             : {
     641         382 :     auto rtcpInfoVect = socketPair_->getRtcpREMB();
     642             : 
     643         382 :     if (!rtcpInfoVect.empty()) {
     644         182 :         auto pkt = rtcpInfoVect.back();
     645         182 :         auto temp = cc->parseREMB(pkt);
     646         182 :         *br = (temp >> 10) | ((temp << 6) & 0xff00) | ((temp << 16) & 0x30000);
     647         182 :         return true;
     648             :     }
     649         200 :     return false;
     650         382 : }
     651             : 
     652             : void
     653         382 : VideoRtpSession::adaptQualityAndBitrate()
     654             : {
     655         382 :     setupVideoBitrateInfo();
     656             : 
     657             :     uint64_t br;
     658         382 :     if (check_RCTP_Info_REMB(&br)) {
     659         182 :         delayProcessing(br);
     660             :     }
     661             : 
     662         382 :     RTCPInfo rtcpi {};
     663         382 :     if (check_RCTP_Info_RR(rtcpi)) {
     664          10 :         dropProcessing(&rtcpi);
     665             :     }
     666         382 : }
     667             : 
     668             : void
     669          10 : VideoRtpSession::dropProcessing(RTCPInfo* rtcpi)
     670             : {
     671             :     // If bitrate has changed, let time to receive fresh RTCP packets
     672          10 :     auto now = clock::now();
     673          10 :     auto restartTimer = now - lastMediaRestart_;
     674          10 :     if (restartTimer < DELAY_AFTER_RESTART) {
     675           0 :         return;
     676             :     }
     677             : #ifndef __ANDROID__
     678             :     // Do nothing if jitter is more than 1 second
     679          10 :     if (rtcpi->jitter > 1000) {
     680           0 :         return;
     681             :     }
     682             : #endif
     683          10 :     auto pondLoss = getPonderateLoss(rtcpi->packetLoss);
     684          10 :     auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent;
     685          10 :     int newBitrate = oldBitrate;
     686             : 
     687             :     // Fill histoLoss and histoJitter_ with samples
     688          10 :     if (restartTimer < DELAY_AFTER_RESTART + std::chrono::seconds(1)) {
     689           0 :         return;
     690             :     } else {
     691             :         // If ponderate drops are inferior to 10% that mean drop are not from congestion but from
     692             :         // network...
     693             :         // ... we can increase
     694          10 :         if (pondLoss >= 5.0f && rtcpi->packetLoss > 0.0f) {
     695           0 :             newBitrate *= 1.0f - rtcpi->packetLoss / 150.0f;
     696           0 :             histoLoss_.clear();
     697           0 :             lastMediaRestart_ = now;
     698           0 :             JAMI_DBG("[BandwidthAdapt] Detected transmission bandwidth overuse, decrease bitrate from "
     699             :                      "%u Kbps to %d Kbps, ratio %f (ponderate loss: %f%%, packet loss rate: %f%%)",
     700             :                      oldBitrate,
     701             :                      newBitrate,
     702             :                      (float) newBitrate / oldBitrate,
     703             :                      pondLoss,
     704             :                      rtcpi->packetLoss);
     705             :         }
     706             :     }
     707             : 
     708          10 :     setNewBitrate(newBitrate);
     709             : }
     710             : 
     711             : void
     712         182 : VideoRtpSession::delayProcessing(int br)
     713             : {
     714         182 :     int newBitrate = videoBitrateInfo_.videoBitrateCurrent;
     715         182 :     if (br == 0x6803)
     716           0 :         newBitrate *= 0.85f;
     717         182 :     else if (br == 0x7378) {
     718         182 :         auto now = clock::now();
     719         182 :         auto msSinceLastDecrease = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastBitrateDecrease);
     720         182 :         auto increaseCoefficient = std::min(msSinceLastDecrease.count() / 600000.0f + 1.0f, 1.05f);
     721         182 :         newBitrate *= increaseCoefficient;
     722             :     } else
     723           0 :         return;
     724             : 
     725         182 :     setNewBitrate(newBitrate);
     726             : }
     727             : 
     728             : void
     729         192 : VideoRtpSession::setNewBitrate(unsigned int newBR)
     730             : {
     731         192 :     newBR = std::max(newBR, videoBitrateInfo_.videoBitrateMin);
     732         192 :     newBR = std::min(newBR, videoBitrateInfo_.videoBitrateMax);
     733             : 
     734         192 :     if (newBR < videoBitrateInfo_.videoBitrateCurrent)
     735           0 :         lastBitrateDecrease = clock::now();
     736             : 
     737         192 :     if (videoBitrateInfo_.videoBitrateCurrent != newBR) {
     738          37 :         videoBitrateInfo_.videoBitrateCurrent = newBR;
     739          37 :         storeVideoBitrateInfo();
     740             : 
     741             : #if __ANDROID__
     742             :         if (auto input_device = std::dynamic_pointer_cast<VideoInput>(videoLocal_))
     743             :             emitSignal<libjami::VideoSignal::SetBitrate>(input_device->getConfig().name, (int) newBR);
     744             : #endif
     745             : 
     746          37 :         if (sender_) {
     747          37 :             auto ret = sender_->setBitrate(newBR);
     748          37 :             if (ret == -1)
     749           0 :                 JAMI_ERR("Fail to access the encoder");
     750          37 :             else if (ret == 0)
     751           0 :                 restartSender();
     752             :         } else {
     753           0 :             JAMI_ERR("Fail to access the sender");
     754             :         }
     755             :     }
     756         192 : }
     757             : 
     758             : void
     759         829 : VideoRtpSession::setupVideoBitrateInfo()
     760             : {
     761         829 :     auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     762         829 :     if (codecVideo) {
     763         528 :         videoBitrateInfo_ = {
     764         528 :             codecVideo->bitrate,
     765         528 :             codecVideo->minBitrate,
     766         528 :             codecVideo->maxBitrate,
     767         528 :             codecVideo->quality,
     768         528 :             codecVideo->minQuality,
     769         528 :             codecVideo->maxQuality,
     770         528 :             videoBitrateInfo_.cptBitrateChecking,
     771         528 :             videoBitrateInfo_.maxBitrateChecking,
     772         528 :             videoBitrateInfo_.packetLostThreshold,
     773             :         };
     774             :     } else {
     775         301 :         videoBitrateInfo_ = {0, 0, 0, 0, 0, 0, 0, MAX_ADAPTATIVE_BITRATE_ITERATION, PACKET_LOSS_THRESHOLD};
     776             :     }
     777         829 : }
     778             : 
     779             : void
     780         690 : VideoRtpSession::storeVideoBitrateInfo()
     781             : {
     782         690 :     if (auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec)) {
     783         456 :         codecVideo->bitrate = videoBitrateInfo_.videoBitrateCurrent;
     784         456 :         codecVideo->quality = videoBitrateInfo_.videoQualityCurrent;
     785         690 :     }
     786         690 : }
     787             : 
     788             : void
     789         382 : VideoRtpSession::processRtcpChecker()
     790             : {
     791         382 :     adaptQualityAndBitrate();
     792         382 :     socketPair_->waitForRTCP(std::chrono::seconds(rtcp_checking_interval));
     793         382 : }
     794             : 
     795             : void
     796          32 : VideoRtpSession::attachRemoteRecorder(const MediaStream& ms)
     797             : {
     798          32 :     std::lock_guard lock(mutex_);
     799          32 :     if (!recorder_ || !receiveThread_)
     800           0 :         return;
     801          32 :     if (auto ob = recorder_->addStream(ms)) {
     802          32 :         receiveThread_->attach(ob);
     803             :     }
     804          32 : }
     805             : 
     806             : void
     807           0 : VideoRtpSession::attachLocalRecorder(const MediaStream& ms)
     808             : {
     809           0 :     std::lock_guard lock(mutex_);
     810           0 :     if (!recorder_ || !videoLocal_ || !Manager::instance().videoPreferences.getRecordPreview())
     811           0 :         return;
     812           0 :     if (auto ob = recorder_->addStream(ms)) {
     813           0 :         videoLocal_->attach(ob);
     814             :     }
     815           0 : }
     816             : 
     817             : void
     818           1 : VideoRtpSession::initRecorder()
     819             : {
     820           1 :     if (!recorder_)
     821           0 :         return;
     822           1 :     if (receiveThread_) {
     823           1 :         receiveThread_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     824           1 :             asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     825           1 :                 if (auto shared = w.lock())
     826           1 :                     shared->attachRemoteRecorder(ms);
     827           1 :             });
     828           1 :         });
     829             :     }
     830           1 :     if (videoLocal_ && !send_.onHold) {
     831           1 :         videoLocal_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     832           0 :             asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     833           0 :                 if (auto shared = w.lock())
     834           0 :                     shared->attachLocalRecorder(ms);
     835           0 :             });
     836           0 :         });
     837             :     }
     838             : }
     839             : 
     840             : void
     841         302 : VideoRtpSession::deinitRecorder()
     842             : {
     843         302 :     if (!recorder_)
     844           0 :         return;
     845         302 :     if (receiveThread_) {
     846         125 :         auto ms = receiveThread_->getInfo();
     847         125 :         if (auto ob = recorder_->getStream(ms.name)) {
     848           0 :             receiveThread_->detach(ob);
     849           0 :             recorder_->removeStream(ms);
     850             :         }
     851         125 :     }
     852         302 :     if (videoLocal_) {
     853           0 :         auto ms = videoLocal_->getInfo();
     854           0 :         if (auto ob = recorder_->getStream(ms.name)) {
     855           0 :             videoLocal_->detach(ob);
     856           0 :             recorder_->removeStream(ms);
     857             :         }
     858           0 :     }
     859             : }
     860             : 
     861             : void
     862         146 : VideoRtpSession::setChangeOrientationCallback(std::function<void(int)> cb)
     863             : {
     864         146 :     changeOrientationCallback_ = std::move(cb);
     865         146 :     if (sender_)
     866          10 :         sender_->setChangeOrientationCallback(changeOrientationCallback_);
     867         146 : }
     868             : 
     869             : float
     870          10 : VideoRtpSession::getPonderateLoss(float lastLoss)
     871             : {
     872          10 :     float pond = 0.0f, pondLoss = 0.0f, totalPond = 0.0f;
     873          10 :     constexpr float coefficient_a = -1 / 100.0f;
     874          10 :     constexpr float coefficient_b = 100.0f;
     875             : 
     876          10 :     auto now = clock::now();
     877             : 
     878          10 :     histoLoss_.emplace_back(now, lastLoss);
     879             : 
     880          20 :     for (auto it = histoLoss_.begin(); it != histoLoss_.end();) {
     881          10 :         auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(now - it->first);
     882             : 
     883             :         // 1ms      -> 100%
     884             :         // 2000ms   -> 80%
     885          10 :         if (delay <= EXPIRY_TIME_RTCP) {
     886          10 :             if (it->second == 0.0f)
     887          10 :                 pond = 20.0f; // Reduce weight of null drop
     888             :             else
     889           0 :                 pond = std::min(delay.count() * coefficient_a + coefficient_b, 100.0f);
     890          10 :             totalPond += pond;
     891          10 :             pondLoss += it->second * pond;
     892          10 :             ++it;
     893             :         } else
     894           0 :             it = histoLoss_.erase(it);
     895             :     }
     896          10 :     if (totalPond == 0)
     897           0 :         return 0.0f;
     898             : 
     899          10 :     return pondLoss / totalPond;
     900             : }
     901             : 
     902             : void
     903        5694 : VideoRtpSession::delayMonitor(int gradient, int deltaT)
     904             : {
     905        5694 :     float estimation = cc->kalmanFilter(gradient);
     906        5694 :     float thresh = cc->get_thresh();
     907             : 
     908        5694 :     cc->update_thresh(estimation, deltaT);
     909             : 
     910        5694 :     BandwidthUsage bwState = cc->get_bw_state(estimation, thresh);
     911        5694 :     auto now = clock::now();
     912             : 
     913        5694 :     if (bwState == BandwidthUsage::bwOverusing) {
     914           0 :         auto remb_timer_dec = now - last_REMB_dec_;
     915           0 :         if ((not remb_dec_cnt_) or (remb_timer_dec > DELAY_AFTER_REMB_DEC)) {
     916           0 :             last_REMB_dec_ = now;
     917           0 :             remb_dec_cnt_ = 0;
     918             :         }
     919             : 
     920             :         // Limit REMB decrease to MAX_REMB_DEC every DELAY_AFTER_REMB_DEC ms
     921           0 :         if (remb_dec_cnt_ < MAX_REMB_DEC && remb_timer_dec < DELAY_AFTER_REMB_DEC) {
     922           0 :             remb_dec_cnt_++;
     923           0 :             JAMI_WARN("[BandwidthAdapt] Detected reception bandwidth overuse");
     924           0 :             uint8_t* buf = nullptr;
     925           0 :             uint64_t br = 0x6803; // Decrease 3
     926           0 :             auto v = cc->createREMB(br);
     927           0 :             buf = &v[0];
     928           0 :             socketPair_->writeData(buf, v.size());
     929           0 :             last_REMB_inc_ = clock::now();
     930           0 :         }
     931        5694 :     } else if (bwState == BandwidthUsage::bwNormal) {
     932        4515 :         auto remb_timer_inc = now - last_REMB_inc_;
     933        4515 :         if (remb_timer_inc > DELAY_AFTER_REMB_INC) {
     934         182 :             uint8_t* buf = nullptr;
     935         182 :             uint64_t br = 0x7378; // INcrease
     936         182 :             auto v = cc->createREMB(br);
     937         182 :             buf = &v[0];
     938         182 :             socketPair_->writeData(buf, v.size());
     939         182 :             last_REMB_inc_ = clock::now();
     940         182 :         }
     941             :     }
     942        5694 : }
     943             : } // namespace video
     944             : } // namespace jami

Generated by: LCOV version 1.14