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

Generated by: LCOV version 2.0-1