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: 440 545 80.7 %
Date: 2026-04-01 09:29:43 Functions: 55 62 88.7 %

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

Generated by: LCOV version 1.14