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