Ring Daemon
Loading...
Searching...
No Matches
media_decoder.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "libav_deps.h" // MUST BE INCLUDED FIRST
19#include "media_decoder.h"
20#include "media_device.h"
21#include "media_buffer.h"
22#include "media_io_handle.h"
24#include "decoder_finder.h"
25#include "manager.h"
26
27#ifdef ENABLE_HWACCEL
28#include "video/accel.h"
29#endif
30
31#include "string_utils.h"
32#include "logger.h"
33#include "client/jami_signal.h"
34
35#include <unistd.h>
36#include <cstddef>
37#include <thread> // hardware_concurrency
38#include <chrono>
39#include <algorithm>
40#include <asio/steady_timer.hpp>
41
42namespace jami {
43
44// maximum number of packets the jitter buffer can queue
45const unsigned jitterBufferMaxSize_ {1500};
46// maximum time a packet can be queued
47const constexpr auto jitterBufferMaxDelay_ = std::chrono::milliseconds(50);
48
50 : inputCtx_(avformat_alloc_context())
51 , startTime_(AV_NOPTS_VALUE)
52{}
53
55{
56 if (streamInfoTimer_) {
57 streamInfoTimer_->cancel();
58 streamInfoTimer_.reset();
59 }
60 if (inputCtx_)
61 avformat_close_input(&inputCtx_);
62 av_dict_free(&options_);
63}
64
65const char*
67{
68 switch (status) {
69 case Status::Success:
70 return "Success";
72 return "End of file";
74 return "Read overflow";
76 return "Read error";
78 return "Fallback";
80 return "Restart required";
81 default:
82 return "Undefined";
83 }
84}
85
86int
88{
89 inputParams_ = params;
90 const auto* iformat = av_find_input_format(params.format.c_str());
91
92 if (!iformat && !params.format.empty())
93 JAMI_WARN("Unable to find format \"%s\"", params.format.c_str());
94
95 std::string input;
96
97 if (params.input == "pipewiregrab") {
98 //
99 // We rely on pipewiregrab for screen/window sharing on Wayland.
100 // Because pipewiregrab is a "video source filter" (part of FFmpeg's libavfilter
101 // library), its options must all be passed as part of the `input` string.
102 //
103 input = fmt::format("pipewiregrab=draw_mouse=1:fd={}:node={}", params.fd, params.node);
104 JAMI_LOG("Attempting to open input {}", input);
105 //
106 // In all other cases, we use the `options_` AVDictionary to pass options to FFmpeg.
107 //
108 // NOTE: We rely on the "lavfi" virtual input device to read pipewiregrab's output
109 // and create a corresponding stream (cf. the getDeviceParams function in
110 // daemon/src/media/video/v4l2/video_device_impl.cpp). The `options_` dictionary
111 // could be used to set lavfi's parameters if that was ever needed, but it isn't at
112 // the moment. (Doc: https://ffmpeg.org/ffmpeg-devices.html#lavfi)
113 //
114 } else {
115 if (params.width and params.height) {
116 auto sizeStr = fmt::format("{}x{}", params.width, params.height);
117 av_dict_set(&options_, "video_size", sizeStr.c_str(), 0);
118 }
119
120 if (params.framerate) {
121#ifdef _WIN32
122 // On Windows, framerate settings don't reduce to avrational values
123 // that correspond to valid video device formats.
124 // e.g. A the rational<double>(10000000, 333333) or 30.000030000
125 // will be reduced by av_reduce to 999991/33333 or 30.00003000003
126 // which cause the device opening routine to fail.
127 // So we treat this imprecise reduction and adjust the value,
128 // or let dshow choose the framerate, which is, unfortunately,
129 // NOT the highest according to our experimentations.
130 auto framerate {params.framerate.real()};
131 framerate = params.framerate.numerator() / (params.framerate.denominator() + 0.5);
132 if (params.framerate.denominator() != 4999998)
133 av_dict_set(&options_, "framerate", jami::to_string(framerate).c_str(), 0);
134#else
135 av_dict_set(&options_, "framerate", jami::to_string(params.framerate.real()).c_str(), 0);
136#endif
137 }
138
139 if (params.offset_x || params.offset_y) {
140 av_dict_set(&options_, "offset_x", std::to_string(params.offset_x).c_str(), 0);
141 av_dict_set(&options_, "offset_y", std::to_string(params.offset_y).c_str(), 0);
142 }
143 if (params.channel)
144 av_dict_set(&options_, "channel", std::to_string(params.channel).c_str(), 0);
145 av_dict_set(&options_, "loop", params.loop.c_str(), 0);
146 av_dict_set(&options_, "sdp_flags", params.sdp_flags.c_str(), 0);
147
148 // Set jitter buffer options
149 av_dict_set(&options_, "reorder_queue_size", std::to_string(jitterBufferMaxSize_).c_str(), 0);
150 auto us = std::chrono::duration_cast<std::chrono::microseconds>(jitterBufferMaxDelay_).count();
151 av_dict_set(&options_, "max_delay", std::to_string(us).c_str(), 0);
152
153 if (!params.pixel_format.empty()) {
154 av_dict_set(&options_, "pixel_format", params.pixel_format.c_str(), 0);
155 }
156 if (!params.window_id.empty()) {
157 av_dict_set(&options_, "window_id", params.window_id.c_str(), 0);
158 }
159 av_dict_set(&options_, "draw_mouse", "1", 0);
160 av_dict_set(&options_, "is_area", std::to_string(params.is_area).c_str(), 0);
161
162#if defined(__APPLE__) && TARGET_OS_MAC
163 input = params.name;
164#else
165 input = params.input;
166#endif
167
168 JAMI_LOG("Attempting to open input {} with format {}, pixel format {}, size {}x{}, rate {}",
169 input,
170 params.format,
171 params.pixel_format,
172 params.width,
173 params.height,
174 params.framerate.real());
175 }
176
177 // Ask FFmpeg to open the input using the options set above
178 if (params.disable_dts_probe_delay && params.format == "sdp") {
179 av_opt_set_int(inputCtx_, "max_ts_probe", 0, AV_OPT_SEARCH_CHILDREN);
180 av_opt_set_int(inputCtx_, "fpsprobesize", 0, AV_OPT_SEARCH_CHILDREN);
181 } else {
182 // Don't waste time fetching framerate when finding stream info
183 av_opt_set_int(inputCtx_, "fpsprobesize", 1, AV_OPT_SEARCH_CHILDREN);
184 }
185
186 int ret = avformat_open_input(&inputCtx_, input.c_str(), iformat, options_ ? &options_ : NULL);
187
188 if (ret) {
189 JAMI_ERROR("avformat_open_input failed: {}", libav_utils::getError(ret));
190 } else if (inputCtx_->nb_streams > 0 && inputCtx_->streams[0]->codecpar) {
191 baseWidth_ = inputCtx_->streams[0]->codecpar->width;
192 baseHeight_ = inputCtx_->streams[0]->codecpar->height;
193 JAMI_LOG("Opened input using format {:s} and resolution {:d}x{:d}", params.format, baseWidth_, baseHeight_);
194 }
195
196 return ret;
197}
198
201{
202 return inputCtx_->duration;
203}
204
205bool
207{
208 std::lock_guard lk(inputCtxMutex_);
209 if (av_seek_frame(inputCtx_, -1, timestamp, AVSEEK_FLAG_BACKWARD) >= 0) {
210 clearFrames();
211 return true;
212 }
213 return false;
214}
215
216void
218{
219 if (not streamInfoFound_) {
220 inputCtx_->max_analyze_duration = 30l * AV_TIME_BASE;
221 if (videoStream && keyFrameRequestCb_) {
222 if (!streamInfoTimer_)
223 streamInfoTimer_ = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext());
224 streamInfoTimer_->expires_after(std::chrono::milliseconds(1500));
225 streamInfoTimer_->async_wait([weak = weak_from_this()](const std::error_code& ec) {
226 if (ec)
227 return;
228 if (auto self = weak.lock()) {
229 if (!self->streamInfoFound_) {
230 JAMI_LOG("findStreamInfo: 1500ms elapsed, requesting keyframe to aid probing");
231 if (self->keyFrameRequestCb_)
232 self->keyFrameRequestCb_();
233 }
234 }
235 });
236 }
237
238 int err = avformat_find_stream_info(inputCtx_, nullptr);
239 if (err < 0) {
240 JAMI_ERROR("Unable to find stream info: {}", libav_utils::getError(err));
241 }
242 streamInfoFound_ = true;
243 if (streamInfoTimer_) {
244 streamInfoTimer_->cancel();
245 streamInfoTimer_.reset();
246 }
247 }
248}
249
250int
251MediaDemuxer::selectStream(AVMediaType type)
252{
253 auto sti = av_find_best_stream(inputCtx_, type, -1, -1, nullptr, 0);
254 if (type == AVMEDIA_TYPE_VIDEO && sti >= 0) {
255 auto* st = inputCtx_->streams[sti];
256 auto disposition = st->disposition;
257 if (disposition & AV_DISPOSITION_ATTACHED_PIC) {
258 JAMI_DBG("Skipping attached picture stream");
259 sti = -1;
260 }
261 }
262 return sti;
263}
264
265void
266MediaDemuxer::setInterruptCallback(int (*cb)(void*), void* opaque)
267{
268 if (cb) {
269 inputCtx_->interrupt_callback.callback = cb;
270 inputCtx_->interrupt_callback.opaque = opaque;
271 } else {
272 inputCtx_->interrupt_callback.callback = 0;
273 }
274}
275void
276MediaDemuxer::setNeedFrameCb(std::function<void()> cb)
277{
278 needFrameCb_ = std::move(cb);
279}
280
281void
282MediaDemuxer::setFileFinishedCb(std::function<void(bool)> cb)
283{
284 fileFinishedCb_ = std::move(cb);
285}
286
287void
288MediaDemuxer::setKeyFrameRequestCb(std::function<void()> cb)
289{
290 keyFrameRequestCb_ = std::move(cb);
291}
292
293void
294MediaDemuxer::clearFrames()
295{
296 {
297 std::lock_guard lk {videoBufferMutex_};
298 while (!videoBuffer_.empty()) {
299 videoBuffer_.pop();
300 }
301 }
302 {
303 std::lock_guard lk {audioBufferMutex_};
304 while (!audioBuffer_.empty()) {
305 audioBuffer_.pop();
306 }
307 }
308}
309
310bool
311MediaDemuxer::emitFrame(bool isAudio)
312{
313 if (isAudio) {
314 return pushFrameFrom(audioBuffer_, isAudio, audioBufferMutex_);
315 } else {
316 return pushFrameFrom(videoBuffer_, isAudio, videoBufferMutex_);
317 }
318}
319
320bool
321MediaDemuxer::pushFrameFrom(std::queue<std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>>& buffer,
322 bool isAudio,
323 std::mutex& mutex)
324{
325 std::unique_lock lock(mutex);
326 if (buffer.empty()) {
327 if (currentState_ == MediaDemuxer::CurrentState::Finished) {
328 fileFinishedCb_(isAudio);
329 } else {
330 needFrameCb_();
331 }
332 return false;
333 }
334 auto packet = std::move(buffer.front());
335 if (!packet) {
336 return false;
337 }
338 auto streamIndex = packet->stream_index;
339 if (static_cast<unsigned>(streamIndex) >= streams_.size() || streamIndex < 0) {
340 return false;
341 }
342 if (auto& cb = streams_[streamIndex]) {
343 buffer.pop();
344 lock.unlock();
345 cb(*packet.get());
346 }
347 return true;
348}
349
350MediaDemuxer::Status
351MediaDemuxer::demuxe()
352{
353 auto packet = std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>(av_packet_alloc(), [](AVPacket* p) {
354 if (p)
355 av_packet_free(&p);
356 });
357
358 bool isVideo;
359 {
360 std::lock_guard lk(inputCtxMutex_);
361 int ret = av_read_frame(inputCtx_, packet.get());
362 if (ret == AVERROR(EAGAIN)) {
363 return Status::Success;
364 } else if (ret == AVERROR_EOF) {
365 return Status::EndOfFile;
366 } else if (ret < 0) {
367 JAMI_ERR("Unable to read frame: %s\n", libav_utils::getError(ret).c_str());
368 return Status::ReadError;
369 }
370
371 auto streamIndex = packet->stream_index;
372 if (static_cast<unsigned>(streamIndex) >= streams_.size() || streamIndex < 0) {
373 return Status::Success;
374 }
375
376 isVideo = inputCtx_->streams[streamIndex]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
377 }
378
379 if (isVideo) {
380 std::lock_guard lk {videoBufferMutex_};
381 videoBuffer_.push(std::move(packet));
382 if (videoBuffer_.size() >= 90) {
383 return Status::ReadBufferOverflow;
384 }
385 } else {
386 std::lock_guard lk {audioBufferMutex_};
387 audioBuffer_.push(std::move(packet));
388 if (audioBuffer_.size() >= 300) {
389 return Status::ReadBufferOverflow;
390 }
391 }
392 return Status::Success;
393}
394
395void
396MediaDemuxer::setIOContext(MediaIOHandle* ioctx)
397{
398 inputCtx_->pb = ioctx->getContext();
399}
400
402MediaDemuxer::decode()
403{
404 if (inputParams_.format == "x11grab" || inputParams_.format == "dxgigrab") {
405 auto ret = inputCtx_->iformat->read_header(inputCtx_);
406 if (ret == AVERROR_EXTERNAL) {
407 JAMI_ERR("Unable to read frame: %s\n", libav_utils::getError(ret).c_str());
408 return Status::ReadError;
409 }
410 auto* codecpar = inputCtx_->streams[0]->codecpar;
411 if (baseHeight_ != codecpar->height || baseWidth_ != codecpar->width) {
412 baseHeight_ = codecpar->height;
413 baseWidth_ = codecpar->width;
414 inputParams_.height = ((baseHeight_ >> 3) << 3);
415 inputParams_.width = ((baseWidth_ >> 3) << 3);
416 return Status::RestartRequired;
417 }
418 }
419
420 libjami::PacketBuffer packet(av_packet_alloc());
421 int ret = av_read_frame(inputCtx_, packet.get());
422 if (ret == AVERROR(EAGAIN)) {
423 /*no data available. Calculate time until next frame.
424 We do not use the emulated frame mechanism from the decoder because it will affect all
425 platforms. With the current implementation, the demuxer will be waiting just in case when
426 av_read_frame returns EAGAIN. For some platforms, av_read_frame is blocking and it will
427 never happen.
428 */
429 if (inputParams_.framerate.numerator() == 0)
430 return Status::Success;
431 rational<double> frameTime = 1e6 / inputParams_.framerate;
432 int64_t timeToSleep = lastReadPacketTime_ - av_gettime_relative() + frameTime.real<int64_t>();
433 if (timeToSleep <= 0) {
434 return Status::Success;
435 }
436 std::this_thread::sleep_for(std::chrono::microseconds(timeToSleep));
437 return Status::Success;
438 } else if (ret == AVERROR_EOF) {
439 return Status::EndOfFile;
440 } else if (ret == AVERROR(EACCES)) {
441 return Status::RestartRequired;
442 } else if (ret < 0) {
443 auto media = inputCtx_->streams[0]->codecpar->codec_type;
444 const auto* const type = media == AVMediaType::AVMEDIA_TYPE_AUDIO
445 ? "AUDIO"
446 : (media == AVMediaType::AVMEDIA_TYPE_VIDEO ? "VIDEO" : "UNSUPPORTED");
447 JAMI_ERR("Unable to read [%s] frame: %s\n", type, libav_utils::getError(ret).c_str());
448 return Status::ReadError;
449 }
450
451 auto streamIndex = packet->stream_index;
452 if (static_cast<unsigned>(streamIndex) >= streams_.size() || streamIndex < 0) {
453 return Status::Success;
454 }
455
456 lastReadPacketTime_ = av_gettime_relative();
457
458 auto& cb = streams_[streamIndex];
459 if (cb) {
460 DecodeStatus ret = cb(*packet.get());
461 if (ret == DecodeStatus::FallBack)
462 return Status::FallBack;
463 }
464 return Status::Success;
465}
466
467MediaDecoder::MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index)
468 : demuxer_(demuxer)
469 , avStream_(demuxer->getStream(index))
470{
471 demuxer->setStreamCallback(index, [this](AVPacket& packet) { return decode(packet); });
472 setupStream();
473}
474
475MediaDecoder::MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index, MediaObserver observer)
476 : demuxer_(demuxer)
477 , avStream_(demuxer->getStream(index))
478 , callback_(std::move(observer))
479{
480 demuxer->setStreamCallback(index, [this](AVPacket& packet) { return decode(packet); });
481 setupStream();
482}
483
484bool
486{
487 return demuxer_->emitFrame(isAudio);
488}
489
493
495 : demuxer_(new MediaDemuxer)
496 , callback_(std::move(o))
497{}
498
500{
501#ifdef ENABLE_HWACCEL
502 if (decoderCtx_ && decoderCtx_->hw_device_ctx)
503 av_buffer_unref(&decoderCtx_->hw_device_ctx);
504#endif
505 if (decoderCtx_)
506 avcodec_free_context(&decoderCtx_);
507}
508
509void
514
515int
517{
518 return demuxer_->openInput(p);
519}
520
521void
523{
524 demuxer_->setInterruptCallback(cb, opaque);
525}
526
527void
529{
530 demuxer_->setIOContext(ioctx);
531}
532
533void
535{
536 demuxer_->setKeyFrameRequestCb(std::move(cb));
537}
538
539int
541{
542 demuxer_->findStreamInfo(type == AVMEDIA_TYPE_VIDEO);
543 auto stream = demuxer_->selectStream(type);
544 if (stream < 0) {
545 JAMI_ERR("No stream found for type %i", static_cast<int>(type));
546 return -1;
547 }
548 avStream_ = demuxer_->getStream(stream);
549 if (avStream_ == nullptr) {
550 JAMI_ERR("No stream found at index %i", stream);
551 return -1;
552 }
553 demuxer_->setStreamCallback(stream, [this](AVPacket& packet) { return decode(packet); });
554 return setupStream();
555}
556
557int
558MediaDecoder::setupStream()
559{
560 int ret = 0;
561 avcodec_free_context(&decoderCtx_);
562
563 if (prepareDecoderContext() < 0)
564 return -1; // failed
565
566#ifdef ENABLE_HWACCEL
567 // if there was a fallback to software decoding, do not enable accel
568 // it has been disabled already by the video_receive_thread/video_input
569 enableAccel_ &= Manager::instance().videoPreferences.getDecodingAccelerated();
570
571 if (enableAccel_ and not fallback_) {
572 auto APIs = video::HardwareAccel::getCompatibleAccel(decoderCtx_->codec_id,
573 decoderCtx_->width,
574 decoderCtx_->height,
576 for (const auto& it : APIs) {
577 accel_ = std::make_unique<video::HardwareAccel>(it); // save accel
578 auto ret = accel_->initAPI(false, nullptr);
579 if (ret < 0) {
580 accel_.reset();
581 continue;
582 }
583 if (prepareDecoderContext() < 0)
584 return -1; // failed
585 accel_->setDetails(decoderCtx_);
586 decoderCtx_->opaque = accel_.get();
587 decoderCtx_->pix_fmt = accel_->getFormat();
588 if (avcodec_open2(decoderCtx_, inputDecoder_, &options_) < 0) {
589 // Failed to open codec
590 JAMI_WARN("Fail to open hardware decoder for %s with %s",
591 avcodec_get_name(decoderCtx_->codec_id),
592 it.getName().c_str());
593 avcodec_free_context(&decoderCtx_);
594 decoderCtx_ = nullptr;
595 accel_.reset();
596 continue;
597 } else {
598 // Codec opened successfully.
599 JAMI_WARN("Using hardware decoding for %s with %s",
600 avcodec_get_name(decoderCtx_->codec_id),
601 it.getName().c_str());
602 break;
603 }
604 }
605 }
606#endif
607
608 JAMI_LOG("Using {} ({}) decoder for {}",
609 inputDecoder_->long_name,
610 inputDecoder_->name,
611 av_get_media_type_string(avStream_->codecpar->codec_type));
612 decoderCtx_->thread_count = std::max(1, std::min(8, static_cast<int>(std::thread::hardware_concurrency()) / 2));
613 if (emulateRate_)
614 JAMI_DBG() << "Using framerate emulation";
615 startTime_ = av_gettime(); // Used to set pts after decoding, and for rate emulation
616
617#ifdef ENABLE_HWACCEL
618 if (!accel_) {
619 JAMI_WARN("Not using hardware decoding for %s", avcodec_get_name(decoderCtx_->codec_id));
620 ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr);
621 }
622#else
623 ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr);
624#endif
625 if (ret < 0) {
626 JAMI_ERR() << "Unable to open codec: " << libav_utils::getError(ret);
627 return -1;
628 }
629
630 return 0;
631}
632
633int
634MediaDecoder::prepareDecoderContext()
635{
636 inputDecoder_ = findDecoder(avStream_->codecpar->codec_id);
637 if (!inputDecoder_) {
638 JAMI_ERROR("Unsupported codec");
639 return -1;
640 }
641
642 decoderCtx_ = avcodec_alloc_context3(inputDecoder_);
643 if (!decoderCtx_) {
644 JAMI_ERROR("Failed to create decoder context");
645 return -1;
646 }
647 avcodec_parameters_to_context(decoderCtx_, avStream_->codecpar);
648 decoderCtx_->pkt_timebase = avStream_->time_base;
649 width_ = decoderCtx_->width;
650 height_ = decoderCtx_->height;
651 decoderCtx_->framerate = avStream_->avg_frame_rate;
652 if (avStream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
653 if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
654 decoderCtx_->framerate = inputParams_.framerate;
655 if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
656 decoderCtx_->framerate = {30, 1};
657 } else if (avStream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
658 if (decoderCtx_->codec_id == AV_CODEC_ID_OPUS) {
659 av_opt_set_int(decoderCtx_, "decode_fec", fecEnabled_ ? 1 : 0, AV_OPT_SEARCH_CHILDREN);
660 }
662 inputDecoder_, Manager::instance().getRingBufferPool().getInternalAudioFormat().sampleFormat);
663 decoderCtx_->sample_fmt = format;
664 decoderCtx_->request_sample_fmt = format;
665 }
666 return 0;
667}
668
669void
674
677{
678 int frameFinished = 0;
679 auto ret = avcodec_send_packet(decoderCtx_, &packet);
680 // TODO: Investigate avcodec_send_packet returning AVERROR_INVALIDDATA.
681 // * Bug Windows documented here: git.jami.net/savoirfairelinux/jami-daemon/-/issues/1116
682 // where avcodec_send_packet returns AVERROR_INVALIDDATA when the size information in the
683 // packet is incorrect. Falling back onto sw decoding in this causes a segfault.
684 // * A second problem occurs on some Windows devices with intel CPUs in which hardware
685 // decoding fails with AVERROR_INVALIDDATA when using H.264. However, in this scenario,
686 // falling back to software decoding works fine.
687 // We need to figure out why this behavior occurs and how to discriminate between the two.
688 if (ret < 0 && ret != AVERROR(EAGAIN)) {
689#ifdef ENABLE_HWACCEL
690 if (accel_) {
691 JAMI_WARN("Decoding error falling back to software");
692 fallback_ = true;
693 accel_.reset();
694 avcodec_flush_buffers(decoderCtx_);
695 setupStream();
697 }
698#endif
699 avcodec_flush_buffers(decoderCtx_);
701 }
702
703#ifdef ENABLE_VIDEO
704 auto f = (inputDecoder_->type == AVMEDIA_TYPE_VIDEO)
705 ? std::static_pointer_cast<MediaFrame>(std::make_shared<VideoFrame>())
707#else
708 auto f = std::static_pointer_cast<MediaFrame>(std::make_shared<AudioFrame>());
709#endif
710 auto* frame = f->pointer();
711 ret = avcodec_receive_frame(decoderCtx_, frame);
712 // time_base is not set in AVCodecContext for decoding
713 // fail to set it causes pts to be incorrectly computed down in the function
714 if (inputDecoder_->type == AVMEDIA_TYPE_VIDEO) {
715 decoderCtx_->time_base.num = decoderCtx_->framerate.den;
716 decoderCtx_->time_base.den = decoderCtx_->framerate.num;
717 } else {
718 decoderCtx_->time_base.num = 1;
719 decoderCtx_->time_base.den = decoderCtx_->sample_rate;
720 }
721 frame->time_base = decoderCtx_->time_base;
722 if (resolutionChangedCallback_) {
723 if (decoderCtx_->width != width_ or decoderCtx_->height != height_) {
724 JAMI_DBG("Resolution changed from %dx%d to %dx%d", width_, height_, decoderCtx_->width, decoderCtx_->height);
725 width_ = decoderCtx_->width;
726 height_ = decoderCtx_->height;
727 resolutionChangedCallback_(width_, height_);
728 }
729 }
730 if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
732 }
733 if (ret >= 0)
734 frameFinished = 1;
735
736 if (frameFinished) {
737 if (inputDecoder_->type == AVMEDIA_TYPE_VIDEO) {
738 frame->format = (AVPixelFormat) correctPixFmt(frame->format);
739 } else {
740 // It's possible (albeit rare) for avcodec_receive_frame to return a frame with
741 // unspecified channel order. This can cause issues later on in the resampler
742 // because swr_convert_frame expects the ch_layout of the input frame to match
743 // the in_ch_layout of the SwrContext, but swr_init sets in_ch_layout to a default
744 // value based on the number of channels if the channel order of the input frame
745 // is unspecified.
746 if (frame->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) {
747 av_channel_layout_default(&frame->ch_layout, frame->ch_layout.nb_channels);
748 }
749 }
750 auto packetTimestamp = frame->pts; // in stream time base
751 frame->pts = av_rescale_q_rnd(av_gettime() - startTime_,
752 {1, AV_TIME_BASE},
753 decoderCtx_->time_base,
755 lastTimestamp_ = frame->pts;
756 if (emulateRate_ and packetTimestamp != AV_NOPTS_VALUE) {
757 auto startTime = avStream_->start_time == AV_NOPTS_VALUE ? 0 : avStream_->start_time;
758 rational<double> frame_time = rational<double>(getTimeBase())
759 * rational<double>(static_cast<double>(packetTimestamp - startTime));
760 auto target_relative = static_cast<std::int64_t>(frame_time.real() * 1e6);
761 auto target_absolute = startTime_ + target_relative;
762 if (target_relative < seekTime_) {
764 }
765 // required frame found. Reset seek time
766 if (target_relative >= seekTime_) {
767 resetSeekTime();
768 }
769 auto now = av_gettime();
770 if (target_absolute > now) {
771 std::this_thread::sleep_for(std::chrono::microseconds(target_absolute - now));
772 }
773 }
774
775 if (callback_)
776 callback_(std::move(f));
777
778 if (contextCallback_ && firstDecode_.load()) {
779 firstDecode_.exchange(false);
780 contextCallback_();
781 }
783 }
785}
786
787void
789{
790 seekTime_ = time;
791}
792
795{
796 auto ret = demuxer_->decode();
798 avcodec_flush_buffers(decoderCtx_);
799 setupStream();
801 }
802 return ret;
803}
804
805#ifdef ENABLE_VIDEO
806#ifdef ENABLE_HWACCEL
807void
808MediaDecoder::enableAccel(bool enableAccel)
809{
812 if (!enableAccel) {
813 accel_.reset();
814 if (decoderCtx_)
815 decoderCtx_->opaque = nullptr;
816 }
817}
818#endif
819
822{
825
826 int frameFinished = 0;
827 int ret = 0;
828 ret = avcodec_send_packet(decoderCtx_, &inpacket);
829 if (ret < 0 && ret != AVERROR(EAGAIN))
831
832 auto result = std::make_shared<MediaFrame>();
833 ret = avcodec_receive_frame(decoderCtx_, result->pointer());
834 if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
836 if (ret >= 0)
837 frameFinished = 1;
838
839 if (frameFinished) {
841 if (callback_)
842 callback_(std::move(result));
844 }
845
847}
848#endif // ENABLE_VIDEO
849
850int
852{
853 return decoderCtx_ ? decoderCtx_->width : 0;
854}
855
856int
858{
859 return decoderCtx_ ? decoderCtx_->height : 0;
860}
861
862std::string
864{
865 return decoderCtx_ ? decoderCtx_->codec->name : "";
866}
867
870{
871 return {(double) avStream_->avg_frame_rate.num, (double) avStream_->avg_frame_rate.den};
872}
873
875MediaDecoder::getTimeBase() const
876{
877 return {(unsigned) avStream_->time_base.num, (unsigned) avStream_->time_base.den};
878}
879
882{
883 return decoderCtx_->pix_fmt;
884}
885
886int
887MediaDecoder::correctPixFmt(int input_pix_fmt)
888{
889 // https://ffmpeg.org/pipermail/ffmpeg-user/2014-February/020152.html
890 int pix_fmt;
891 switch (input_pix_fmt) {
894 break;
897 break;
900 break;
903 break;
904 default:
906 break;
907 }
908 return pix_fmt;
909}
910
911MediaStream
912MediaDecoder::getStream(const std::string& name) const
913{
914 if (!decoderCtx_) {
915 JAMI_WARN("No decoder context");
916 return {};
917 }
918 auto ms = MediaStream(name, decoderCtx_, lastTimestamp_);
919#ifdef ENABLE_HWACCEL
920 // accel_ is null if not using accelerated codecs
921 if (accel_)
922 ms.format = accel_->getSoftwareFormat();
923#endif
924 return ms;
925}
926
927} // namespace jami
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
DecodeStatus flush()
void updateStartTime(int64_t startTime)
AVDictionary * options_
int setup(AVMediaType type)
void setKeyFrameRequestCb(std::function< void()> cb)
void setIOContext(MediaIOHandle *ioctx)
int openInput(const DeviceParams &)
bool emitFrame(bool isAudio)
void setInterruptCallback(int(*cb)(void *), void *opaque)
MediaDemuxer::Status decode()
rational< double > getFps() const
AVPixelFormat getPixelFormat() const
MediaStream getStream(const std::string &name="") const
void setSeekTime(int64_t time)
std::string getDecoderName() const
int openInput(const DeviceParams &)
static const char * getStatusStr(Status status)
bool seekFrame(int stream_index, int64_t timestamp)
void findStreamInfo(bool videoStream=false)
int64_t getDuration() const
AVIOContext * getContext()
Naive implementation of the boost::rational interface, described here: https://www....
Definition rational.h:39
constexpr R real() const
Definition rational.h:89
static std::list< HardwareAccel > getCompatibleAccel(AVCodecID id, int width, int height, CodecType type)
Definition accel.cpp:417
void av_buffer_unref(AVBufferRef **buf)
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
AVSampleFormat choose_sample_fmt_default(const AVCodec *codec, AVSampleFormat defaultFormat)
std::string getError(int err)
void emitSignal(Args... args)
Definition jami_signal.h:64
const constexpr auto jitterBufferMaxDelay_
const unsigned jitterBufferMaxSize_
std::function< void(std::shared_ptr< MediaFrame > &&)> MediaObserver
std::string to_string(double value)
libjami::MediaFrame MediaFrame
libjami::AudioFrame AudioFrame
@ CODEC_DECODER
Definition media_codec.h:40
const AVCodec * findDecoder(const enum AVCodecID codec_id)
Attempt to find standalone AVCodec decoder using AVCodecID, or fallback to the default decoder.
std::unique_ptr< AVPacket, AVPacket_deleter > PacketBuffer
DeviceParams Parameters used by MediaDecoder and MediaEncoder to open a LibAV device/stream.
rational< double > framerate
void av_packet_free(AVPacket **frame)