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: 63.6 % 571 363
Test Date: 2026-07-29 09:02:12 Functions: 68.0 % 50 34

            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          125 : VideoRtpSession::VideoRtpSession(const string& callId,
      53              :                                  const string& streamId,
      54              :                                  const DeviceParams& localVideoParams,
      55          125 :                                  const std::shared_ptr<MediaRecorder>& rec)
      56              :     : RtpSession(callId, streamId, MediaType::MEDIA_VIDEO)
      57          125 :     , localVideoParams_(localVideoParams)
      58          125 :     , videoBitrateInfo_ {}
      59          307 :     , rtcpCheckerThread_([] { return true; }, [this] { processRtcpChecker(); }, [] {})
      60          250 :     , cc(std::make_unique<CongestionControl>())
      61              : {
      62          125 :     recorder_ = rec;
      63          125 :     setupVideoBitrateInfo(); // reset bitrate
      64          125 :     JAMI_LOG("[{:p}] Video RTP session created for call {} (recorder {:p})",
      65              :              fmt::ptr(this),
      66              :              callId_,
      67              :              fmt::ptr(recorder_));
      68          125 : }
      69              : 
      70          125 : VideoRtpSession::~VideoRtpSession()
      71              : {
      72          125 :     deinitRecorder();
      73          125 :     stop();
      74          125 :     JAMI_LOG("[{:p}] Video RTP session destroyed", fmt::ptr(this));
      75          125 : }
      76              : 
      77              : const VideoBitrateInfo&
      78            7 : VideoRtpSession::getVideoBitrateInfo()
      79              : {
      80            7 :     return videoBitrateInfo_;
      81              : }
      82              : 
      83              : /// Setup internal VideoBitrateInfo structure from media descriptors.
      84              : ///
      85              : void
      86           64 : VideoRtpSession::updateMedia(const MediaDescription& send, const MediaDescription& receive)
      87              : {
      88           64 :     BaseType::updateMedia(send, receive);
      89              :     // adjust send->codec bitrate info for higher video resolutions
      90           64 :     auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
      91           64 :     if (codecVideo) {
      92           64 :         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            1 :             const auto pixels = static_cast<unsigned>(videoMixer_->getWidth())
      99            1 :                                 * static_cast<unsigned>(videoMixer_->getHeight());
     100            1 :             if (pixels > 0 and (not confBitrateSeeded_ or pixels != confSeededPixels_)) {
     101            1 :                 codecVideo->bitrate = std::max((unsigned int) (pixels * 0.001), SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
     102            1 :                 codecVideo->maxBitrate = std::max((unsigned int) (pixels * 0.0015),
     103              :                                                   SystemCodecInfo::DEFAULT_MAX_BITRATE);
     104            1 :                 confBitrateSeeded_ = true;
     105            1 :                 confSeededPixels_ = pixels;
     106              :             }
     107              :         } else {
     108           63 :             const auto pixels = localVideoParams_.height * localVideoParams_.width;
     109           63 :             codecVideo->bitrate = std::max((unsigned int) (pixels * 0.001), SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
     110           63 :             codecVideo->maxBitrate = std::max((unsigned int) (pixels * 0.0015), SystemCodecInfo::DEFAULT_MAX_BITRATE);
     111              :         }
     112              :     }
     113           64 :     setupVideoBitrateInfo();
     114           64 : }
     115              : 
     116              : void
     117           64 : VideoRtpSession::setRequestKeyFrameCallback(std::function<void(void)> cb)
     118              : {
     119           64 :     cbKeyFrameRequest_ = std::move(cb);
     120           64 : }
     121              : 
     122              : void
     123           66 : VideoRtpSession::startSender()
     124              : {
     125           66 :     std::lock_guard lock(mutex_);
     126              : 
     127           67 :     JAMI_LOG("[{}] Start video RTP sender: input [{}] - muted [{}]",
     128              :              fmt::ptr(this),
     129              :              conference_ ? "Video Mixer" : input_,
     130              :              send_.hold ? "YES" : "NO");
     131              : 
     132           66 :     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           66 :     if (send_.enabled and not send_.hold) {
     139           56 :         if (sender_) {
     140            2 :             if (videoLocal_)
     141            2 :                 videoLocal_->detach(sender_.get());
     142            2 :             if (videoMixer_)
     143            0 :                 videoMixer_->detach(sender_.get());
     144            2 :             JAMI_WARNING("[{}] Restarting video sender", fmt::ptr(this));
     145              :         }
     146              : 
     147           56 :         if (not conference_) {
     148           55 :             videoLocal_ = getVideoInput(input_);
     149           55 :             if (videoLocal_) {
     150           55 :                 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           55 :                 auto newParams = videoLocal_->getParams();
     157              :                 try {
     158           55 :                     if (newParams.valid() && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
     159           47 :                         localVideoParams_ = newParams.get();
     160              :                     } else {
     161            8 :                         JAMI_ERROR("[{}] No valid new video parameters", fmt::ptr(this));
     162            8 :                         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           55 :             } 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           48 :         socketPair_->stopSendOp();
     180              : 
     181           48 :         auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     182           48 :         auto autoQuality = codecVideo->isAutoQualityEnabled;
     183              : 
     184           48 :         send_.linkableHW = conference_ == nullptr;
     185           48 :         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           95 :         bool allowHwAccel = (localVideoParams_.format != "x11grab" && localVideoParams_.format != "dxgigrab"
     191           95 :                              && localVideoParams_.format != "lavfi");
     192              : 
     193           48 :         if (socketPair_)
     194           48 :             initSeqVal_ = socketPair_->lastSeqValOut();
     195              : 
     196              :         try {
     197           48 :             sender_.reset();
     198           48 :             socketPair_->stopSendOp(false);
     199           48 :             MediaStream ms = !videoMixer_
     200           48 :                                  ? MediaStream("video sender",
     201              :                                                AV_PIX_FMT_YUV420P,
     202           47 :                                                1 / static_cast<rational<int>>(localVideoParams_.framerate),
     203           47 :                                                localVideoParams_.width == 0 ? 1080
     204            7 :                                                                             : static_cast<int>(localVideoParams_.width),
     205           47 :                                                localVideoParams_.height == 0
     206              :                                                    ? 720
     207            7 :                                                    : static_cast<int>(localVideoParams_.height),
     208           47 :                                                static_cast<int>(send_.bitrate),
     209              :                                                static_cast<rational<int>>(localVideoParams_.framerate))
     210          144 :                                  : videoMixer_->getStream("Video Sender");
     211           48 :             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            1 :                 auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     219            1 :                 const auto pixels = static_cast<unsigned>(ms.width) * static_cast<unsigned>(ms.height);
     220            1 :                 if (codecVideo and pixels > 0 and (not confBitrateSeeded_ or pixels != confSeededPixels_)) {
     221            1 :                     codecVideo->bitrate = std::max((unsigned int) (pixels * 0.001),
     222              :                                                    SystemCodecInfo::DEFAULT_VIDEO_BITRATE);
     223            1 :                     codecVideo->maxBitrate = std::max((unsigned int) (pixels * 0.0015),
     224              :                                                       SystemCodecInfo::DEFAULT_MAX_BITRATE);
     225            1 :                     videoBitrateInfo_.videoBitrateCurrent = codecVideo->bitrate;
     226            1 :                     videoBitrateInfo_.videoBitrateMax = codecVideo->maxBitrate;
     227            1 :                     confBitrateSeeded_ = true;
     228            1 :                     confSeededPixels_ = pixels;
     229              :                 }
     230            1 :                 send_.bitrate = videoBitrateInfo_.videoBitrateCurrent;
     231            1 :                 ms.bitrate = static_cast<int>(send_.bitrate);
     232            1 :             }
     233           48 :             sender_.reset(
     234           96 :                 new VideoSender(getRemoteRtpUri(), ms, send_, *socketPair_, initSeqVal_ + 1, mtu_, allowHwAccel));
     235           48 :             if (changeOrientationCallback_)
     236           48 :                 sender_->setChangeOrientationCallback(changeOrientationCallback_);
     237           48 :             if (socketPair_)
     238            0 :                 socketPair_->setPacketLossCallback([this]() { cbKeyFrameRequest_(); });
     239              : 
     240           48 :         } catch (const MediaEncoderException& e) {
     241            0 :             JAMI_ERROR("{}", e.what());
     242            0 :             send_.enabled = false;
     243            0 :         }
     244           48 :         lastMediaRestart_ = clock::now();
     245           48 :         last_REMB_inc_ = clock::now();
     246           48 :         last_REMB_dec_ = clock::now();
     247           48 :         if (autoQuality and not rtcpCheckerThread_.isRunning())
     248           46 :             rtcpCheckerThread_.start();
     249            2 :         else if (not autoQuality and rtcpCheckerThread_.isRunning())
     250            0 :             rtcpCheckerThread_.join();
     251              :         // Block reads to received feedback packets
     252           48 :         if (socketPair_)
     253           48 :             socketPair_->setReadBlockingMode(true);
     254           48 :     }
     255           66 : }
     256              : 
     257              : void
     258            4 : VideoRtpSession::restartSender()
     259              : {
     260            4 :     std::lock_guard lock(mutex_);
     261              : 
     262              :     // ensure that start has been called before restart
     263            4 :     if (not socketPair_)
     264            2 :         return;
     265              : 
     266            2 :     startSender();
     267              : 
     268            2 :     if (conference_)
     269            0 :         setupConferenceVideoPipeline(*conference_, Direction::SEND);
     270              :     else
     271            2 :         setupVideoPipeline();
     272            4 : }
     273              : 
     274              : void
     275          312 : VideoRtpSession::stopSender(bool forceStopSocket)
     276              : {
     277              :     // Concurrency protection must be done by caller.
     278              : 
     279          315 :     JAMI_LOG("[{}] Stop video RTP sender: input [{}] - muted [{}]",
     280              :              fmt::ptr(this),
     281              :              conference_ ? "Video Mixer" : input_,
     282              :              send_.hold ? "YES" : "NO");
     283              : 
     284          312 :     if (sender_) {
     285           46 :         if (videoLocal_)
     286           45 :             videoLocal_->detach(sender_.get());
     287           46 :         if (videoMixer_)
     288            0 :             videoMixer_->detach(sender_.get());
     289           46 :         sender_.reset();
     290              :     }
     291              : 
     292          312 :     if (socketPair_) {
     293           71 :         bool const isReceivingVideo = receive_.enabled && !receive_.hold;
     294           71 :         if (forceStopSocket || !isReceivingVideo) {
     295           68 :             socketPair_->stopSendOp();
     296           68 :             socketPair_->setReadBlockingMode(false);
     297              :         }
     298              :     }
     299          312 : }
     300              : 
     301              : void
     302           64 : VideoRtpSession::startReceiver()
     303              : {
     304              :     // Concurrency protection must be done by caller.
     305              : 
     306           64 :     JAMI_LOG("[{}] Starting receiver", fmt::ptr(this));
     307              : 
     308           64 :     if (receive_.enabled and not receive_.hold) {
     309           55 :         if (receiveThread_)
     310           13 :             JAMI_WARNING("[{}] Already has a receiver, restarting", fmt::ptr(this));
     311           55 :         receiveThread_.reset(new VideoReceiveThread(callId_, !conference_, receive_.receiving_sdp, mtu_));
     312              : 
     313              :         // ensure that start has been called
     314           55 :         if (not socketPair_)
     315            0 :             return;
     316              : 
     317              :         // XXX keyframe requests can timeout if unanswered
     318           55 :         receiveThread_->addIOContext(*socketPair_);
     319           55 :         receiveThread_->setSuccessfulSetupCb(onSuccessfulSetup_);
     320           55 :         receiveThread_->startLoop();
     321          103 :         receiveThread_->setRequestKeyFrameCallback([this]() { cbKeyFrameRequest_(); });
     322          110 :         receiveThread_->setRotation(rotation_.load());
     323           55 :         if (videoMixer_ and conference_) {
     324              :             // Note, this should be managed differently, this is a bit hacky
     325            1 :             auto audioId = streamId_;
     326            3 :             string_replace(audioId, "video", "audio");
     327            1 :             auto activeStream = videoMixer_->verifyActive(audioId);
     328            1 :             videoMixer_->removeAudioOnlySource(callId_, audioId);
     329            1 :             if (activeStream)
     330            0 :                 videoMixer_->setActiveStream(streamId_);
     331            1 :         }
     332           55 :         receiveThread_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     333            0 :             asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     334            0 :                 if (auto shared = w.lock())
     335            0 :                     shared->attachRemoteRecorder(ms);
     336            0 :             });
     337            0 :         });
     338           55 :     } else {
     339            9 :         JAMI_LOG("[{}] Video receiver disabled", fmt::ptr(this));
     340            9 :         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           64 :     if (socketPair_)
     360           64 :         socketPair_->setReadBlockingMode(true);
     361              : }
     362              : 
     363              : void
     364          301 : VideoRtpSession::stopReceiver(bool forceStopSocket)
     365              : {
     366              :     // Concurrency protection must be done by caller.
     367              : 
     368          301 :     JAMI_LOG("[{}] Stopping receiver", fmt::ptr(this));
     369              : 
     370          301 :     if (not receiveThread_)
     371          187 :         return;
     372              : 
     373          114 :     if (videoMixer_) {
     374            0 :         auto activeStream = videoMixer_->verifyActive(streamId_);
     375            0 :         auto audioId = streamId_;
     376            0 :         string_replace(audioId, "video", "audio");
     377            0 :         videoMixer_->addAudioOnlySource(callId_, audioId);
     378            0 :         receiveThread_->detach(videoMixer_.get());
     379            0 :         if (activeStream)
     380            0 :             videoMixer_->setActiveStream(audioId);
     381            0 :     }
     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          114 :     bool const isSendingVideo = send_.enabled && !send_.hold;
     387          114 :     if (socketPair_) {
     388           61 :         if (forceStopSocket || !isSendingVideo) {
     389           61 :             socketPair_->setReadBlockingMode(false);
     390           61 :             socketPair_->stopSendOp();
     391              :         }
     392              :     }
     393              : 
     394          114 :     auto ms = receiveThread_->getInfo();
     395          114 :     if (auto* ob = recorder_->getStream(ms.name)) {
     396            0 :         receiveThread_->detach(ob);
     397            0 :         recorder_->removeStream(ms);
     398              :     }
     399              : 
     400          114 :     if (forceStopSocket || !isSendingVideo)
     401          114 :         receiveThread_->stopLoop();
     402          114 :     receiveThread_->stopSink();
     403          114 : }
     404              : 
     405              : void
     406           66 : VideoRtpSession::start(std::unique_ptr<dhtnet::IceSocket> rtp_sock, std::unique_ptr<dhtnet::IceSocket> rtcp_sock)
     407              : {
     408           66 :     std::lock_guard lock(mutex_);
     409              : 
     410           66 :     if (not send_.enabled and not receive_.enabled) {
     411            2 :         stop();
     412            2 :         return;
     413              :     }
     414              : 
     415              :     try {
     416           64 :         if (rtp_sock and rtcp_sock) {
     417           62 :             if (send_.addr) {
     418           62 :                 rtp_sock->setDefaultRemoteAddress(send_.addr);
     419              :             }
     420              : 
     421           62 :             auto& rtcpAddr = send_.rtcp_addr ? send_.rtcp_addr : send_.addr;
     422           62 :             if (rtcpAddr) {
     423           62 :                 rtcp_sock->setDefaultRemoteAddress(rtcpAddr);
     424              :             }
     425           62 :             socketPair_.reset(new SocketPair(std::move(rtp_sock), std::move(rtcp_sock)));
     426              :         } else {
     427            2 :             socketPair_.reset(new SocketPair(getRemoteRtpUri().c_str(), receive_.addr.getPort()));
     428              :         }
     429              : 
     430           64 :         last_REMB_inc_ = clock::now();
     431           64 :         last_REMB_dec_ = clock::now();
     432              : 
     433           67 :         socketPair_->setRtpDelayCallback([&](int gradient, int deltaT) { delayMonitor(gradient, deltaT); });
     434              : 
     435           64 :         if (send_.crypto and receive_.crypto) {
     436          256 :             socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(),
     437          128 :                                     receive_.crypto.getSrtpKeyInfo().c_str(),
     438          128 :                                     send_.crypto.getCryptoSuite().c_str(),
     439          128 :                                     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           64 :     startReceiver();
     447           64 :     startSender();
     448              : 
     449           64 :     if (conference_) {
     450            1 :         if (send_.enabled and not send_.hold) {
     451            1 :             setupConferenceVideoPipeline(*conference_, Direction::SEND);
     452              :         }
     453            1 :         if (receive_.enabled and not receive_.hold) {
     454            1 :             setupConferenceVideoPipeline(*conference_, Direction::RECV);
     455              :         }
     456              :     } else {
     457           63 :         setupVideoPipeline();
     458              :     }
     459           66 : }
     460              : 
     461              : void
     462          301 : VideoRtpSession::stop()
     463              : {
     464          301 :     std::lock_guard lock(mutex_);
     465              : 
     466          301 :     stopSender(true);
     467          301 :     stopReceiver(true);
     468              : 
     469          301 :     if (socketPair_)
     470           64 :         socketPair_->interrupt();
     471              : 
     472          301 :     rtcpCheckerThread_.join();
     473              : 
     474              :     // reset default video quality if exist
     475          301 :     if (videoBitrateInfo_.videoQualityCurrent != SystemCodecInfo::DEFAULT_NO_QUALITY)
     476           32 :         videoBitrateInfo_.videoQualityCurrent = SystemCodecInfo::DEFAULT_CODEC_QUALITY;
     477              : 
     478          301 :     videoBitrateInfo_.videoBitrateCurrent = SystemCodecInfo::DEFAULT_VIDEO_BITRATE;
     479          301 :     confBitrateSeeded_ = false;
     480          301 :     confSeededPixels_ = 0;
     481          301 :     storeVideoBitrateInfo();
     482              : 
     483          301 :     socketPair_.reset();
     484          301 :     videoLocal_.reset();
     485          301 : }
     486              : 
     487              : void
     488          136 : VideoRtpSession::setMuted(bool mute, Direction dir)
     489              : {
     490          136 :     std::lock_guard lock(mutex_);
     491              : 
     492              :     // Sender
     493          136 :     if (dir == Direction::SEND) {
     494           70 :         if (send_.hold == mute) {
     495           57 :             JAMI_LOG("[{}] Local already {}", fmt::ptr(this), mute ? "muted" : "un-muted");
     496           57 :             return;
     497              :         }
     498              : 
     499           13 :         if ((send_.hold = mute)) {
     500           11 :             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           11 :             stopSender();
     508              :         } else {
     509            2 :             restartSender();
     510              :         }
     511           13 :         return;
     512              :     }
     513              : 
     514              :     // Receiver
     515           66 :     if (receive_.hold == mute) {
     516           66 :         JAMI_LOG("[{}] Remote already {}", fmt::ptr(this), mute ? "muted" : "un-muted");
     517           66 :         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          136 : }
     536              : 
     537              : void
     538          111 : VideoRtpSession::forceKeyFrame()
     539              : {
     540          111 :     std::lock_guard lock(mutex_);
     541              : #if __ANDROID__
     542              :     if (videoLocal_)
     543              :         emitSignal<libjami::VideoSignal::RequestKeyFrame>(videoLocal_->getName());
     544              : #else
     545          110 :     if (sender_)
     546           74 :         sender_->forceKeyFrame();
     547              : #endif
     548          111 : }
     549              : 
     550              : void
     551          126 : VideoRtpSession::setRotation(int rotation)
     552              : {
     553          126 :     rotation_.store(rotation);
     554          126 :     if (receiveThread_)
     555            1 :         receiveThread_->setRotation(rotation);
     556          126 : }
     557              : 
     558              : void
     559           65 : VideoRtpSession::setupVideoPipeline()
     560              : {
     561           65 :     if (sender_) {
     562           47 :         if (videoLocal_) {
     563           47 :             JAMI_LOG("[{}] Setup video pipeline on local capture device", fmt::ptr(this));
     564           47 :             videoLocal_->attach(sender_.get());
     565              :         }
     566              :     } else {
     567           18 :         videoLocal_.reset();
     568              :     }
     569           65 : }
     570              : 
     571              : void
     572            2 : VideoRtpSession::setupConferenceVideoPipeline(Conference& conference, Direction dir)
     573              : {
     574            2 :     if (dir == Direction::SEND) {
     575            1 :         JAMI_DEBUG("[conf:{}] Setup video sender pipeline for call {}", conference.getConfId(), callId_);
     576            1 :         videoMixer_ = conference.getVideoMixer();
     577            1 :         if (sender_) {
     578              :             // Swap sender from local video to conference video mixer
     579            1 :             if (videoLocal_)
     580            0 :                 videoLocal_->detach(sender_.get());
     581            1 :             if (videoMixer_)
     582            1 :                 videoMixer_->attach(sender_.get());
     583              :         } else {
     584            0 :             JAMI_WARNING("[{}] no sender", fmt::ptr(this));
     585              :         }
     586              :     } else {
     587            1 :         JAMI_DEBUG("[conf:{}] Setup video receiver pipeline for call {}", conference.getConfId(), callId_);
     588            1 :         if (receiveThread_) {
     589            1 :             receiveThread_->stopSink();
     590            1 :             if (videoMixer_)
     591            1 :                 videoMixer_->attachVideo(receiveThread_.get(), callId_, streamId_);
     592              :         } else {
     593            0 :             JAMI_WARNING("[{}] no receiver", fmt::ptr(this));
     594              :         }
     595              :     }
     596            2 : }
     597              : 
     598              : void
     599            9 : VideoRtpSession::enterConference(Conference& conference)
     600              : {
     601            9 :     std::lock_guard lock(mutex_);
     602              : 
     603            9 :     exitConference();
     604              : 
     605            9 :     conference_ = &conference;
     606            9 :     videoMixer_ = conference.getVideoMixer();
     607            9 :     JAMI_DEBUG("[conf:{}] Entering conference", conference.getConfId());
     608              : 
     609            9 :     if (send_.enabled or receiveThread_) {
     610              :         // Restart encoder with conference parameter ON in order to unlink HW encoder
     611              :         // from HW decoder.
     612            0 :         restartSender();
     613            0 :         if (conference_) {
     614            0 :             setupConferenceVideoPipeline(conference, Direction::RECV);
     615              :         }
     616              :     }
     617            9 : }
     618              : 
     619              : void
     620           18 : VideoRtpSession::exitConference()
     621              : {
     622           18 :     std::lock_guard lock(mutex_);
     623              : 
     624           18 :     if (!conference_)
     625            9 :         return;
     626              : 
     627            9 :     JAMI_DEBUG("[conf:{}] Exiting conference", conference_->getConfId());
     628              : 
     629            9 :     if (videoMixer_) {
     630            9 :         if (sender_)
     631            1 :             videoMixer_->detach(sender_.get());
     632              : 
     633            9 :         if (receiveThread_) {
     634            1 :             auto activeStream = videoMixer_->verifyActive(streamId_);
     635            1 :             videoMixer_->detachVideo(receiveThread_.get());
     636            1 :             receiveThread_->startSink();
     637            1 :             if (activeStream)
     638            0 :                 videoMixer_->setActiveStream(streamId_);
     639              :         }
     640              : 
     641            9 :         videoMixer_.reset();
     642              :     }
     643              : 
     644            9 :     conference_ = nullptr;
     645            9 :     confBitrateSeeded_ = false;
     646            9 :     confSeededPixels_ = 0;
     647           18 : }
     648              : 
     649              : bool
     650           90 : VideoRtpSession::check_RCTP_Info_RR(RTCPInfo& rtcpi)
     651              : {
     652           90 :     auto rtcpInfoVect = socketPair_->getRtcpRR();
     653           90 :     unsigned totalLost = 0;
     654           90 :     unsigned totalJitter = 0;
     655           90 :     unsigned nbDropNotNull = 0;
     656           90 :     auto vectSize = rtcpInfoVect.size();
     657              : 
     658           90 :     if (vectSize != 0) {
     659            0 :         for (const auto& it : rtcpInfoVect) {
     660            0 :             if (it.fraction_lost != 0) // Exclude null drop
     661            0 :                 nbDropNotNull++;
     662            0 :             totalLost += it.fraction_lost;
     663            0 :             totalJitter += ntohl(it.jitter);
     664              :         }
     665            0 :         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            0 :         rtcpi.jitter = static_cast<unsigned int>(
     669            0 :             (static_cast<float>(totalJitter) / static_cast<float>(vectSize) / 90000.0f) * 1000.0f);
     670            0 :         rtcpi.nb_sample = vectSize;
     671            0 :         rtcpi.latency = static_cast<float>(socketPair_->getLastLatency());
     672            0 :         return true;
     673              :     }
     674           90 :     return false;
     675           90 : }
     676              : 
     677              : bool
     678           90 : VideoRtpSession::check_RCTP_Info_REMB(uint64_t* br)
     679              : {
     680           90 :     auto rtcpInfoVect = socketPair_->getRtcpREMB();
     681              : 
     682           90 :     if (!rtcpInfoVect.empty()) {
     683            0 :         auto pkt = rtcpInfoVect.back();
     684            0 :         auto temp = cc->parseREMB(pkt);
     685            0 :         *br = (temp >> 10) | ((temp << 6) & 0xff00) | ((temp << 16) & 0x30000);
     686            0 :         return true;
     687              :     }
     688           90 :     return false;
     689           90 : }
     690              : 
     691              : void
     692           90 : VideoRtpSession::adaptQualityAndBitrate()
     693              : {
     694           90 :     setupVideoBitrateInfo();
     695              : 
     696              :     uint64_t br;
     697           90 :     if (check_RCTP_Info_REMB(&br)) {
     698            0 :         delayProcessing(static_cast<int>(br));
     699              :     }
     700              : 
     701           90 :     RTCPInfo rtcpi {};
     702           90 :     if (check_RCTP_Info_RR(rtcpi)) {
     703            0 :         dropProcessing(&rtcpi);
     704              :     }
     705           90 : }
     706              : 
     707              : void
     708            0 : VideoRtpSession::dropProcessing(RTCPInfo* rtcpi)
     709              : {
     710              :     // If bitrate has changed, let time to receive fresh RTCP packets
     711            0 :     auto now = clock::now();
     712            0 :     auto restartTimer = now - lastMediaRestart_;
     713            0 :     if (restartTimer < DELAY_AFTER_RESTART) {
     714            0 :         return;
     715              :     }
     716              : #ifndef __ANDROID__
     717              :     // Do nothing if jitter is more than 1 second
     718            0 :     if (rtcpi->jitter > 1000) {
     719            0 :         return;
     720              :     }
     721              : #endif
     722            0 :     auto pondLoss = getPonderateLoss(rtcpi->packetLoss);
     723            0 :     auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent;
     724            0 :     int newBitrate = static_cast<int>(oldBitrate);
     725              : 
     726              :     // Fill histoLoss and histoJitter_ with samples
     727            0 :     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            0 :         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            0 :     setNewBitrate(newBitrate);
     748              : }
     749              : 
     750              : void
     751            0 : VideoRtpSession::delayProcessing(int br)
     752              : {
     753            0 :     int newBitrate = static_cast<int>(videoBitrateInfo_.videoBitrateCurrent);
     754            0 :     if (br == 0x6803)
     755            0 :         newBitrate = static_cast<int>(std::lround(newBitrate * 0.85f));
     756            0 :     else if (br == 0x7378) {
     757            0 :         auto now = clock::now();
     758            0 :         auto msSinceLastDecrease = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastBitrateDecrease);
     759            0 :         auto increaseCoefficient = std::min(static_cast<float>(msSinceLastDecrease.count()) / 600000.0f + 1.0f, 1.05f);
     760            0 :         newBitrate = static_cast<int>(std::lround(newBitrate * increaseCoefficient));
     761              :     } else
     762            0 :         return;
     763              : 
     764            0 :     setNewBitrate(newBitrate);
     765              : }
     766              : 
     767              : void
     768            0 : VideoRtpSession::setNewBitrate(unsigned int newBR)
     769              : {
     770            0 :     newBR = std::max(newBR, videoBitrateInfo_.videoBitrateMin);
     771            0 :     newBR = std::min(newBR, videoBitrateInfo_.videoBitrateMax);
     772              : 
     773            0 :     if (newBR < videoBitrateInfo_.videoBitrateCurrent)
     774            0 :         lastBitrateDecrease = clock::now();
     775              : 
     776            0 :     if (videoBitrateInfo_.videoBitrateCurrent != newBR) {
     777            0 :         videoBitrateInfo_.videoBitrateCurrent = newBR;
     778            0 :         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            0 :         if (sender_) {
     786            0 :             auto ret = sender_->setBitrate(newBR);
     787            0 :             if (ret == -1)
     788            0 :                 JAMI_ERROR("Fail to access the encoder");
     789            0 :             else if (ret == 0)
     790            0 :                 restartSender();
     791              :         } else {
     792            0 :             JAMI_ERROR("Fail to access the sender");
     793              :         }
     794              :     }
     795            0 : }
     796              : 
     797              : void
     798          279 : VideoRtpSession::setupVideoBitrateInfo()
     799              : {
     800          279 :     auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec);
     801          279 :     if (codecVideo) {
     802          154 :         videoBitrateInfo_ = {
     803          154 :             codecVideo->bitrate,
     804          154 :             codecVideo->minBitrate,
     805          154 :             codecVideo->maxBitrate,
     806          154 :             codecVideo->quality,
     807          154 :             codecVideo->minQuality,
     808          154 :             codecVideo->maxQuality,
     809          154 :             videoBitrateInfo_.cptBitrateChecking,
     810          154 :             videoBitrateInfo_.maxBitrateChecking,
     811          154 :             videoBitrateInfo_.packetLostThreshold,
     812              :         };
     813              :     } else {
     814          125 :         videoBitrateInfo_ = {0, 0, 0, 0, 0, 0, 0, MAX_ADAPTATIVE_BITRATE_ITERATION, PACKET_LOSS_THRESHOLD};
     815              :     }
     816          279 : }
     817              : 
     818              : void
     819          301 : VideoRtpSession::storeVideoBitrateInfo()
     820              : {
     821          301 :     if (auto codecVideo = std::static_pointer_cast<jami::SystemVideoCodecInfo>(send_.codec)) {
     822          165 :         codecVideo->bitrate = videoBitrateInfo_.videoBitrateCurrent;
     823          165 :         codecVideo->quality = videoBitrateInfo_.videoQualityCurrent;
     824          301 :     }
     825          301 : }
     826              : 
     827              : void
     828           90 : VideoRtpSession::processRtcpChecker()
     829              : {
     830           90 :     adaptQualityAndBitrate();
     831           90 :     socketPair_->waitForRTCP(std::chrono::seconds(rtcp_checking_interval));
     832           90 : }
     833              : 
     834              : void
     835            0 : VideoRtpSession::attachRemoteRecorder(const MediaStream& ms)
     836              : {
     837            0 :     std::lock_guard lock(mutex_);
     838            0 :     if (!recorder_ || !receiveThread_)
     839            0 :         return;
     840            0 :     if (auto* ob = recorder_->addStream(ms)) {
     841            0 :         receiveThread_->attach(ob);
     842              :     }
     843            0 : }
     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            5 : VideoRtpSession::initRecorder()
     858              : {
     859            5 :     if (!recorder_)
     860            0 :         return;
     861            5 :     if (receiveThread_) {
     862            5 :         receiveThread_->setRecorderCallback([w = weak_from_this()](const MediaStream& ms) {
     863            0 :             asio::post(*Manager::instance().ioContext(), [w = std::move(w), ms]() {
     864            0 :                 if (auto shared = w.lock())
     865            0 :                     shared->attachRemoteRecorder(ms);
     866            0 :             });
     867            0 :         });
     868              :     }
     869            5 :     if (videoLocal_ && !send_.hold) {
     870            2 :         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          128 : VideoRtpSession::deinitRecorder()
     881              : {
     882          128 :     if (!recorder_)
     883            0 :         return;
     884          128 :     if (receiveThread_) {
     885           43 :         auto ms = receiveThread_->getInfo();
     886           43 :         if (auto* ob = recorder_->getStream(ms.name)) {
     887            0 :             receiveThread_->detach(ob);
     888            0 :             recorder_->removeStream(ms);
     889              :         }
     890           43 :     }
     891          128 :     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           64 : VideoRtpSession::setChangeOrientationCallback(std::function<void(int)> cb)
     902              : {
     903           64 :     changeOrientationCallback_ = std::move(cb);
     904           64 :     if (sender_)
     905            9 :         sender_->setChangeOrientationCallback(changeOrientationCallback_);
     906           64 : }
     907              : 
     908              : float
     909            0 : VideoRtpSession::getPonderateLoss(float lastLoss)
     910              : {
     911            0 :     float pond = 0.0f, pondLoss = 0.0f, totalPond = 0.0f;
     912            0 :     constexpr float coefficient_a = -1 / 100.0f;
     913            0 :     constexpr float coefficient_b = 100.0f;
     914              : 
     915            0 :     auto now = clock::now();
     916              : 
     917            0 :     histoLoss_.emplace_back(now, lastLoss);
     918              : 
     919            0 :     for (auto it = histoLoss_.begin(); it != histoLoss_.end();) {
     920            0 :         auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(now - it->first);
     921              : 
     922              :         // 1ms      -> 100%
     923              :         // 2000ms   -> 80%
     924            0 :         if (delay <= EXPIRY_TIME_RTCP) {
     925            0 :             if (it->second == 0.0f)
     926            0 :                 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            0 :             totalPond += pond;
     930            0 :             pondLoss += it->second * pond;
     931            0 :             ++it;
     932              :         } else
     933            0 :             it = histoLoss_.erase(it);
     934              :     }
     935            0 :     if (totalPond == 0)
     936            0 :         return 0.0f;
     937              : 
     938            0 :     return pondLoss / totalPond;
     939              : }
     940              : 
     941              : void
     942            3 : VideoRtpSession::delayMonitor(int gradient, int deltaT)
     943              : {
     944            3 :     float estimation = cc->kalmanFilter(gradient);
     945            3 :     float thresh = cc->get_thresh();
     946              : 
     947            3 :     cc->update_thresh(estimation, deltaT);
     948              : 
     949            3 :     BandwidthUsage bwState = cc->get_bw_state(estimation, thresh);
     950            3 :     auto now = clock::now();
     951              : 
     952            3 :     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            3 :     } else if (bwState == BandwidthUsage::bwNormal) {
     971            3 :         auto remb_timer_inc = now - last_REMB_inc_;
     972            3 :         if (remb_timer_inc > DELAY_AFTER_REMB_INC) {
     973            0 :             uint8_t* buf = nullptr;
     974            0 :             uint64_t br = 0x7378; // INcrease
     975            0 :             auto v = cc->createREMB(br);
     976            0 :             buf = &v[0];
     977            0 :             socketPair_->writeData(buf, static_cast<int>(v.size()));
     978            0 :             last_REMB_inc_ = clock::now();
     979            0 :         }
     980              :     }
     981            3 : }
     982              : } // namespace video
     983              : } // namespace jami
        

Generated by: LCOV version 2.0-1