LCOV - code coverage report
Current view: top level - foo/src/media/video - video_rtp_session.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 436 545 80.0 %
Date: 2026-02-28 10:41:24 Functions: 55 62 88.7 %

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

Generated by: LCOV version 1.14