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.6 % 571 466
Test Date: 2026-07-06 08:25:38 Functions: 74.5 % 106 79

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

Generated by: LCOV version 2.0-1