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