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
|