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