Commit 693c8725 authored by Philippe Gorley's avatar Philippe Gorley Committed by Philippe Gorley

media: split demuxer and decoder

Change-Id: I237a3c4110d3b553d2b9cee99abbede4b10a80d2
parent dd787db8
......@@ -116,23 +116,15 @@ AudioInput::readFromFile()
{
if (!decoder_)
return;
auto frame = std::make_unique<AudioFrame>();
const auto ret = decoder_->decode(*frame);
switch(ret) {
case MediaDecoder::Status::ReadError:
case MediaDecoder::Status::DecodeError:
JAMI_ERR() << "Failed to decode frame";
const auto ret = decoder_->decode();
switch (ret) {
case MediaDemuxer::Status::Success:
break;
case MediaDecoder::Status::RestartRequired:
case MediaDecoder::Status::EOFError:
case MediaDemuxer::Status::EndOfFile:
createDecoder();
break;
case MediaDecoder::Status::FrameFinished:
fileBuf_->put(std::move(frame));
break;
case MediaDecoder::Status::Success:
default:
case MediaDemuxer::Status::ReadError:
JAMI_ERR() << "Failed to decode frame";
break;
}
}
......@@ -247,11 +239,14 @@ AudioInput::createDecoder()
return false;
}
auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
fileBuf_->put(std::move(std::static_pointer_cast<AudioFrame>(frame)));
});
// NOTE don't emulate rate, file is read as frames are needed
auto decoder = std::make_unique<MediaDecoder>();
decoder->setInterruptCallback(
[](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
this);
[](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this);
if (decoder->openInput(devOpts_) < 0) {
JAMI_ERR() << "Could not open input '" << devOpts_.input << "'";
......@@ -259,7 +254,7 @@ AudioInput::createDecoder()
return false;
}
if (decoder->setupFromAudioData() < 0) {
if (decoder->setupAudio() < 0) {
JAMI_ERR() << "Could not setup decoder for '" << devOpts_.input << "'";
foundDevOpts(devOpts_);
return false;
......
......@@ -57,7 +57,10 @@ AudioReceiveThread::~AudioReceiveThread()
bool
AudioReceiveThread::setup()
{
audioDecoder_.reset(new MediaDecoder());
audioDecoder_.reset(new MediaDecoder([this](std::shared_ptr<MediaFrame>&& frame) mutable {
notify(frame);
ringbuffer_->put(std::move(std::static_pointer_cast<AudioFrame>(frame)));
}));
audioDecoder_->setInterruptCallback(interruptCb, this);
// custom_io so the SDP demuxer will not open any UDP connections
......@@ -78,11 +81,10 @@ AudioReceiveThread::setup()
// Now replace our custom AVIOContext with one that will read packets
audioDecoder_->setIOContext(demuxContext_.get());
if (audioDecoder_->setupFromAudioData()) {
if (audioDecoder_->setupAudio()) {
JAMI_ERR("decoder IO startup failed");
return false;
}
Smartools::getInstance().setRemoteAudioCodec(audioDecoder_->getDecoderName());
ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_);
......@@ -92,31 +94,7 @@ AudioReceiveThread::setup()
void
AudioReceiveThread::process()
{
auto decodedFrame = std::make_shared<AudioFrame>();
switch (audioDecoder_->decode(*decodedFrame)) {
case MediaDecoder::Status::FrameFinished:
notify(std::static_pointer_cast<MediaFrame>(decodedFrame));
ringbuffer_->put(std::move(decodedFrame));
return;
case MediaDecoder::Status::DecodeError:
JAMI_WARN("decoding failure, trying to reset decoder...");
if (not setup()) {
JAMI_ERR("fatal error, rx thread re-setup failed");
loop_.stop();
} else if (not audioDecoder_->setupFromAudioData()) {
JAMI_ERR("fatal error, a-decoder setup failed");
loop_.stop();
}
break;
case MediaDecoder::Status::ReadError:
JAMI_ERR("fatal error, read failed");
loop_.stop();
break;
case MediaDecoder::Status::Success:
case MediaDecoder::Status::EOFError:
default:
break;
}
audioDecoder_->decode();
}
void
......
......@@ -317,14 +317,14 @@ AudioBuffer::append(const AudioFrame& audioFrame)
setFormat(newFormat);
}
AudioBuffer toAppend(frame->nb_samples,
{(unsigned)frame->sample_rate, (unsigned)frame->channels});
toAppend.deinterleave(reinterpret_cast<const AudioSample*>(frame->extended_data[0]),
frame->nb_samples, frame->channels);
auto f = frames();
auto newSize = f + frame->nb_samples;
resize(newSize);
for (size_t c = 0; c < samples_.size(); ++c) {
samples_[c].insert(samples_[c].end(), toAppend.samples_[c].begin(), toAppend.samples_[c].end());
}
auto in = reinterpret_cast<const AudioSample*>(frame->extended_data[0]);
for (unsigned c=channels(); f < newSize; f++)
for (unsigned j = 0; j < c; j++)
samples_[j][f] = *in++;
return 0;
}
......
......@@ -59,46 +59,22 @@ AudioFile::onBufferFinish()
AudioFile::AudioFile(const std::string &fileName, unsigned int sampleRate) :
AudioLoop(sampleRate), filepath_(fileName), updatePlaybackScale_(0)
{
auto decoder = std::make_unique<MediaDecoder>();
const auto& format = getFormat();
auto buf = std::make_unique<AudioBuffer>(0, format);
Resampler r {};
auto decoder = std::make_unique<MediaDecoder>([this, &r, &format, &buf](const std::shared_ptr<MediaFrame>& frame) mutable {
buf->append(*r.resample(std::static_pointer_cast<AudioFrame>(frame), format));
});
DeviceParams dev;
dev.input = fileName;
if (decoder->openInput(dev) < 0)
throw AudioFileException("File could not be opened: " + fileName);
if (decoder->setupFromAudioData() < 0)
if (decoder->setupAudio() < 0)
throw AudioFileException("Decoder setup failed: " + fileName);
auto resampler = std::make_unique<Resampler>();
const auto& format = getFormat();
auto buf = std::make_unique<AudioBuffer>(0, format);
bool done = false;
while (!done) {
AudioFrame input;
AudioFrame output;
auto resampled = output.pointer();
switch (decoder->decode(input)) {
case MediaDecoder::Status::FrameFinished:
resampled->sample_rate = format.sample_rate;
resampled->channel_layout = av_get_default_channel_layout(format.nb_channels);
resampled->channels = format.nb_channels;
resampled->format = format.sampleFormat;
if (resampler->resample(input.pointer(), resampled) < 0)
throw AudioFileException("Frame could not be resampled");
if (buf->append(output) < 0)
throw AudioFileException("Error while decoding: " + fileName);
break;
case MediaDecoder::Status::DecodeError:
case MediaDecoder::Status::ReadError:
throw AudioFileException("File cannot be decoded: " + fileName);
case MediaDecoder::Status::EOFError:
done = true;
break;
case MediaDecoder::Status::Success:
default:
break;
}
}
while (decoder->decode() != MediaDemuxer::Status::EndOfFile);
delete buffer_;
buffer_ = buf.release();
......
......@@ -22,6 +22,7 @@
#include "config.h"
#include "videomanager_interface.h"
#include "observer.h"
#include <memory>
#include <functional>
......@@ -38,6 +39,7 @@ namespace jami {
using MediaFrame = DRing::MediaFrame;
using AudioFrame = DRing::AudioFrame;
using MediaObserver = std::function<void(std::shared_ptr<MediaFrame>&&)>;
#ifdef ENABLE_VIDEO
......
......@@ -3,6 +3,7 @@
*
* Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com>
* Author: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -53,23 +54,20 @@ const constexpr auto jitterBufferMaxDelay_ = std::chrono::milliseconds(50);
// maximum number of times accelerated decoding can fail in a row before falling back to software
const constexpr unsigned MAX_ACCEL_FAILURES { 5 };
MediaDecoder::MediaDecoder() :
MediaDemuxer::MediaDemuxer() :
inputCtx_(avformat_alloc_context()),
startTime_(AV_NOPTS_VALUE)
{
}
{}
MediaDecoder::~MediaDecoder()
MediaDemuxer::~MediaDemuxer()
{
if (decoderCtx_)
avcodec_free_context(&decoderCtx_);
if (inputCtx_)
avformat_close_input(&inputCtx_);
av_dict_free(&options_);
}
int MediaDecoder::openInput(const DeviceParams& params)
int
MediaDemuxer::openInput(const DeviceParams& params)
{
inputParams_ = params;
AVInputFormat *iformat = av_find_input_format(params.format.c_str());
......@@ -123,12 +121,6 @@ int MediaDecoder::openInput(const DeviceParams& params)
JAMI_DBG("Trying to open device %s with format %s, pixel format %s, size %dx%d, rate %lf", params.input.c_str(),
params.format.c_str(), params.pixel_format.c_str(), params.width, params.height, params.framerate.real());
#ifdef RING_ACCEL
// if there was a fallback to software decoding, do not enable accel
// it has been disabled already by the video_receive_thread/video_input
enableAccel_ &= Manager::instance().videoPreferences.getDecodingAccelerated();
#endif
int ret = avformat_open_input(
&inputCtx_,
params.input.c_str(),
......@@ -144,7 +136,26 @@ int MediaDecoder::openInput(const DeviceParams& params)
return ret;
}
void MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque)
void
MediaDemuxer::findStreamInfo()
{
if (not streamInfoFound_) {
inputCtx_->max_analyze_duration = 30 * AV_TIME_BASE;
int err;
if ((err = avformat_find_stream_info(inputCtx_, nullptr)) < 0) {
JAMI_ERR() << "Could not find stream info: " << libav_utils::getError(err);
}
streamInfoFound_ = true;
}
}
int
MediaDemuxer::selectStream(AVMediaType type)
{
return av_find_best_stream(inputCtx_, type, -1, -1, nullptr, 0);
}
void MediaDemuxer::setInterruptCallback(int (*cb)(void*), void *opaque)
{
if (cb) {
inputCtx_->interrupt_callback.callback = cb;
......@@ -154,48 +165,104 @@ void MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque)
}
}
void MediaDecoder::setIOContext(MediaIOHandle *ioctx)
void MediaDemuxer::setIOContext(MediaIOHandle *ioctx)
{ inputCtx_->pb = ioctx->getContext(); }
int MediaDecoder::setupFromAudioData()
MediaDemuxer::Status
MediaDemuxer::decode()
{
return setupStream(AVMEDIA_TYPE_AUDIO);
auto packet = std::unique_ptr<AVPacket, std::function<void(AVPacket*)>>(av_packet_alloc(), [](AVPacket* p){
if (p)
av_packet_free(&p);
});
int ret = av_read_frame(inputCtx_, packet.get());
if (ret == AVERROR(EAGAIN)) {
return Status::Success;
} else if (ret == AVERROR_EOF) {
return Status::EndOfFile;
} else if (ret < 0) {
JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
return Status::ReadError;
}
auto streamIndex = packet->stream_index;
if (static_cast<unsigned>(streamIndex) >= streams_.size() || streamIndex < 0) {
return Status::Success;
}
auto& cb = streams_[streamIndex];
if (cb)
cb(*packet.get());
return Status::Success;
}
int
MediaDecoder::selectStream(AVMediaType type)
MediaDecoder::MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index)
: demuxer_(demuxer),
avStream_(demuxer->getStream(index))
{
demuxer->setStreamCallback(index, [this](AVPacket& packet) {
decode(packet);
});
setupStream();
}
MediaDecoder::MediaDecoder()
: demuxer_(new MediaDemuxer)
{}
MediaDecoder::MediaDecoder(MediaObserver o)
: demuxer_(new MediaDemuxer), callback_(std::move(o))
{}
MediaDecoder::~MediaDecoder()
{
return av_find_best_stream(inputCtx_, type, -1, -1, &inputDecoder_, 0);
#ifdef RING_ACCEL
if (decoderCtx_ && decoderCtx_->hw_device_ctx)
av_buffer_unref(&decoderCtx_->hw_device_ctx);
#endif
if (decoderCtx_)
avcodec_free_context(&decoderCtx_);
}
int
MediaDecoder::setupStream(AVMediaType mediaType)
MediaDecoder::openInput(const DeviceParams& p)
{
int ret = 0;
std::string streamType = av_get_media_type_string(mediaType);
return demuxer_->openInput(p);
}
avcodec_free_context(&decoderCtx_);
void
MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque)
{
demuxer_->setInterruptCallback(cb, opaque);
}
// Increase analyze time to solve synchronization issues between callers.
static const unsigned MAX_ANALYZE_DURATION = 30;
inputCtx_->max_analyze_duration = MAX_ANALYZE_DURATION * AV_TIME_BASE;
// If fallback from accel, don't check for stream info, it's already done
if (!fallback_) {
JAMI_DBG() << "Finding " << streamType << " stream info";
if ((ret = avformat_find_stream_info(inputCtx_, nullptr)) < 0) {
// Always fail here
JAMI_ERR() << "Could not find " << streamType << " stream info: " << libav_utils::getError(ret);
return -1;
}
}
void
MediaDecoder::setIOContext(MediaIOHandle *ioctx)
{
demuxer_->setIOContext(ioctx);
}
if ((streamIndex_ = selectStream(mediaType)) < 0) {
JAMI_ERR() << "No suitable " << streamType << " stream found";
int MediaDecoder::setup(AVMediaType type)
{
demuxer_->findStreamInfo();
auto stream = demuxer_->selectStream(type);
if (stream < 0)
return -1;
}
avStream_ = demuxer_->getStream(stream);
demuxer_->setStreamCallback(stream, [this](AVPacket& packet) {
decode(packet);
});
return setupStream();
}
int
MediaDecoder::setupStream()
{
int ret = 0;
avcodec_free_context(&decoderCtx_);
avStream_ = inputCtx_->streams[streamIndex_];
inputDecoder_ = findDecoder(avStream_->codecpar->codec_id);
if (!inputDecoder_) {
JAMI_ERR() << "Unsupported codec";
......@@ -209,23 +276,15 @@ MediaDecoder::setupStream(AVMediaType mediaType)
}
avcodec_parameters_to_context(decoderCtx_, avStream_->codecpar);
decoderCtx_->framerate = avStream_->avg_frame_rate;
if (mediaType == AVMEDIA_TYPE_VIDEO) {
if (avStream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
decoderCtx_->framerate = inputParams_.framerate;
if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
decoderCtx_->framerate = av_inv_q(decoderCtx_->time_base);
if (decoderCtx_->framerate.num == 0 || decoderCtx_->framerate.den == 0)
decoderCtx_->framerate = {30, 1};
}
decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2));
if (emulateRate_)
JAMI_DBG() << "Using framerate emulation";
startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation
#ifdef RING_ACCEL
if (mediaType == AVMEDIA_TYPE_VIDEO) {
if (enableAccel_) {
accel_ = video::HardwareAccel::setupDecoder(decoderCtx_->codec_id,
decoderCtx_->width, decoderCtx_->height);
......@@ -238,10 +297,15 @@ MediaDecoder::setupStream(AVMediaType mediaType)
} else {
JAMI_WARN() << "Hardware decoding disabled by user preference";
}
}
#endif
}
JAMI_DBG() << "Decoding " << av_get_media_type_string(avStream_->codecpar->codec_type) << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")";
JAMI_DBG() << "Decoding " << streamType << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")";
decoderCtx_->thread_count = std::max(1u, std::min(8u, std::thread::hardware_concurrency()/2));
if (emulateRate_)
JAMI_DBG() << "Using framerate emulation";
startTime_ = av_gettime(); // used to set pts after decoding, and for rate emulation
decoderCtx_->err_recognition = (AV_EF_CRCCHECK | AV_EF_BITSTREAM | AV_EF_BUFFER | AV_EF_CAREFUL | AV_EF_COMPLIANT | AV_EF_AGGRESSIVE);
if(inputDecoder_->id != AV_CODEC_ID_MPEG4)
......@@ -256,39 +320,17 @@ MediaDecoder::setupStream(AVMediaType mediaType)
return 0;
}
#ifdef ENABLE_VIDEO
int MediaDecoder::setupFromVideoData()
{
return setupStream(AVMEDIA_TYPE_VIDEO);
}
MediaDecoder::Status
MediaDecoder::decode(VideoFrame& result)
MediaDecoder::decode(AVPacket& packet)
{
AVPacket inpacket;
av_init_packet(&inpacket);
int ret = av_read_frame(inputCtx_, &inpacket);
if (ret == AVERROR(EAGAIN)) {
return Status::Success;
} else if (ret == AVERROR_EOF) {
return Status::EOFError;
} else if (ret < 0) {
JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
return Status::ReadError;
}
// is this a packet from the video stream?
if (inpacket.stream_index != streamIndex_) {
av_packet_unref(&inpacket);
return Status::Success;
}
auto frame = result.pointer();
int frameFinished = 0;
ret = avcodec_send_packet(decoderCtx_, &inpacket);
auto ret = avcodec_send_packet(decoderCtx_, &packet);
if (ret < 0 && ret != AVERROR(EAGAIN)) {
return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
}
auto f = std::make_shared<MediaFrame>();
auto frame = f->pointer();
ret = avcodec_receive_frame(decoderCtx_, frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
return Status::DecodeError;
......@@ -296,9 +338,11 @@ MediaDecoder::decode(VideoFrame& result)
if (ret >= 0)
frameFinished = 1;
av_packet_unref(&inpacket);
if (frameFinished) {
// channel layout is needed if frame will be resampled
if (!frame->channel_layout)
frame->channel_layout = av_get_default_channel_layout(frame->channels);
frame->format = (AVPixelFormat) correctPixFmt(frame->format);
auto packetTimestamp = frame->pts; // in stream time base
frame->pts = av_rescale_q_rnd(av_gettime() - startTime_,
......@@ -314,73 +358,18 @@ MediaDecoder::decode(VideoFrame& result)
std::this_thread::sleep_for(std::chrono::microseconds(target - now));
}
}
if (callback_)
callback_(std::move(f));
return Status::FrameFinished;
}
return Status::Success;
}
#endif // ENABLE_VIDEO
MediaDecoder::Status
MediaDecoder::decode(AudioFrame& decodedFrame)
MediaDemuxer::Status
MediaDecoder::decode()
{
const auto frame = decodedFrame.pointer();
AVPacket inpacket;
av_init_packet(&inpacket);
int ret = av_read_frame(inputCtx_, &inpacket);
if (ret == AVERROR(EAGAIN)) {
return Status::Success;
} else if (ret == AVERROR_EOF) {
return Status::EOFError;
} else if (ret < 0) {
JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str());
return Status::ReadError;
}
// is this a packet from the audio stream?
if (inpacket.stream_index != streamIndex_) {
av_packet_unref(&inpacket);
return Status::Success;
}
int frameFinished = 0;
ret = avcodec_send_packet(decoderCtx_, &inpacket);
if (ret < 0 && ret != AVERROR(EAGAIN))
return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
ret = avcodec_receive_frame(decoderCtx_, frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return Status::DecodeError;
if (ret >= 0)
frameFinished = 1;
if (frameFinished) {
av_packet_unref(&inpacket);
// channel layout is needed if frame will be resampled
if (!frame->channel_layout)
frame->channel_layout = av_get_default_channel_layout(frame->channels);
auto packetTimestamp = frame->pts;
// NOTE don't use clock to rescale audio pts, it may create artifacts
frame->pts = av_rescale_q_rnd(frame->pts, avStream_->time_base, decoderCtx_->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
lastTimestamp_ = frame->pts;
if (emulateRate_ and packetTimestamp != AV_NOPTS_VALUE) {
auto frame_time = getTimeBase()*(packetTimestamp - avStream_->start_time);
auto target = startTime_ + static_cast<std::int64_t>(frame_time.real() * 1e6);
auto now = av_gettime();
if (target > now) {
std::this_thread::sleep_for(std::chrono::microseconds(target - now));
}
}
return Status::FrameFinished;
}
return Status::Success;
return demuxer_->decode();
}
#ifdef ENABLE_VIDEO
......@@ -399,7 +388,7 @@ MediaDecoder::enableAccel(bool enableAccel)
#endif
MediaDecoder::Status
MediaDecoder::flush(VideoFrame& result)
MediaDecoder::flush()
{
AVPacket inpacket;
av_init_packet(&inpacket);
......@@ -410,7 +399,8 @@ MediaDecoder::flush(VideoFrame& result)
if (ret < 0 && ret != AVERROR(EAGAIN))
return ret == AVERROR_EOF ? Status::Success : Status::DecodeError;
ret = avcodec_receive_frame(decoderCtx_, result.pointer());
auto result = std::make_shared<MediaFrame>();
ret = avcodec_receive_frame(decoderCtx_, result->pointer());
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return Status::DecodeError;
if (ret >= 0)
......@@ -418,7 +408,9 @@ MediaDecoder::flush(VideoFrame& result)
if (frameFinished) {
av_packet_unref(&inpacket);
return Status::EOFError;
if (callback_)
callback_(std::move(result));
return Status::FrameFinished;
}
return Status::Success;
......
......@@ -22,17 +22,22 @@
#include "config.h"
#include "rational.h"
#include "observer.h"
#ifdef ENABLE_VIDEO
#include "video/video_base.h"
#include "video/video_scaler.h"
#endif // ENABLE_VIDEO
#ifdef RING_ACCEL
#include "video/accel.h"
#endif
#include "logger.h"
#include "audio/audiobuffer.h"
#include "media_device.h"
#include "media_stream.h"
#include "rational.h"
#include "noncopyable.h"
#include <map>
......@@ -60,85 +65,128 @@ struct AudioFormat;
class RingBuffer;
class Resampler;
class MediaIOHandle;
class MediaDecoder;
class MediaDemuxer {
public:
MediaDemuxer();
~MediaDemuxer();
enum class Status {
Success,
EndOfFile,
ReadError
};
using StreamCallback = std::function<void(AVPacket&)>;
int openInput(const DeviceParams&);
void setInterruptCallback(int (*cb)(void*), void *opaque);
void setIOContext(MediaIOHandle *ioctx);
void findStreamInfo();
int selectStream(AVMediaType type);
void setStreamCallback(unsigned stream, StreamCallback cb = {}) {
if (streams_.size() <= stream)
streams_.resize(stream + 1);
streams_[stream] = std::move(cb);
}
AVStream* getStream(unsigned stream) {
if (stream >= inputCtx_->nb_streams)
throw std::invalid_argument("Invalid stream index");
return inputCtx_->streams[stream];
}
Status decode();
private:
bool streamInfoFound_ {false};
AVFormatContext *inputCtx_ = nullptr;
std::vector<StreamCallback> streams_;
int64_t startTime_;
DeviceParams inputParams_;
AVDictionary *options_ = nullptr;
};
class MediaDecoder
{
public:
enum class Status {
Success,
FrameFinished,
EndOfFile,
ReadError,
DecodeError,
RestartRequired
};
MediaDecoder();
MediaDecoder(MediaObserver observer);
MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index);
MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, AVMediaType type) : MediaDecoder(demuxer, demuxer->selectStream(type)) {}
~MediaDecoder();
void emulateRate() { emulateRate_ = true; }
int openInput(const DeviceParams&);