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: 442 543 81.4 %
Date: 2025-08-24 09:11:10 Functions: 46 54 85.2 %

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

Generated by: LCOV version 1.14