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