LCOV - code coverage report
Current view: top level - src/media/video - video_rtp_session.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 81.7 % 580 474
Test Date: 2026-11-09 10:41:14 Functions: 86.0 % 50 43

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

Generated by: LCOV version 2.0-1