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