diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp index fcfe1874c5622b34243ab5065a389a43f14b84f7..82528e4a63b127d89d622979ceba1dee39805e98 100644 --- a/src/media/audio/audio_input.cpp +++ b/src/media/audio/audio_input.cpp @@ -116,15 +116,23 @@ AudioInput::readFromFile() { if (!decoder_) return; - const auto ret = decoder_->decode(); - switch (ret) { - case MediaDemuxer::Status::Success: + + 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"; break; - case MediaDemuxer::Status::EndOfFile: + case MediaDecoder::Status::RestartRequired: + case MediaDecoder::Status::EOFError: createDecoder(); break; - case MediaDemuxer::Status::ReadError: - JAMI_ERR() << "Failed to decode frame"; + case MediaDecoder::Status::FrameFinished: + fileBuf_->put(std::move(frame)); + break; + case MediaDecoder::Status::Success: + default: break; } } @@ -239,14 +247,11 @@ 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 << "'"; @@ -254,7 +259,7 @@ AudioInput::createDecoder() return false; } - if (decoder->setupAudio() < 0) { + if (decoder->setupFromAudioData() < 0) { JAMI_ERR() << "Could not setup decoder for '" << devOpts_.input << "'"; foundDevOpts(devOpts_); return false; diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp index e2c3ca0df70f78eb4656fcdafa5385f7b71e154d..ffd765e3c21d2b24b8c368954de9aeb9a4f23f69 100644 --- a/src/media/audio/audio_receive_thread.cpp +++ b/src/media/audio/audio_receive_thread.cpp @@ -57,10 +57,7 @@ AudioReceiveThread::~AudioReceiveThread() bool AudioReceiveThread::setup() { - audioDecoder_.reset(new MediaDecoder([this](std::shared_ptr<MediaFrame>&& frame) mutable { - notify(frame); - ringbuffer_->put(std::move(std::static_pointer_cast<AudioFrame>(frame))); - })); + audioDecoder_.reset(new MediaDecoder()); audioDecoder_->setInterruptCallback(interruptCb, this); // custom_io so the SDP demuxer will not open any UDP connections @@ -81,10 +78,11 @@ AudioReceiveThread::setup() // Now replace our custom AVIOContext with one that will read packets audioDecoder_->setIOContext(demuxContext_.get()); - if (audioDecoder_->setupAudio()) { + if (audioDecoder_->setupFromAudioData()) { JAMI_ERR("decoder IO startup failed"); return false; } + Smartools::getInstance().setRemoteAudioCodec(audioDecoder_->getDecoderName()); ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_); @@ -94,7 +92,31 @@ AudioReceiveThread::setup() void AudioReceiveThread::process() { - audioDecoder_->decode(); + 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; + } } void diff --git a/src/media/audio/audiobuffer.cpp b/src/media/audio/audiobuffer.cpp index 836dd30c1f30ddd2c3f37323b65ada84f34605d3..acbf4b25de3a334c95655a6edf890d8fd1b5068e 100644 --- a/src/media/audio/audiobuffer.cpp +++ b/src/media/audio/audiobuffer.cpp @@ -317,14 +317,14 @@ AudioBuffer::append(const AudioFrame& audioFrame) setFormat(newFormat); } - auto f = frames(); - auto newSize = f + frame->nb_samples; - resize(newSize); + 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 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++; + for (size_t c = 0; c < samples_.size(); ++c) { + samples_[c].insert(samples_[c].end(), toAppend.samples_[c].begin(), toAppend.samples_[c].end()); + } return 0; } diff --git a/src/media/audio/sound/audiofile.cpp b/src/media/audio/sound/audiofile.cpp index 2fe8bc2f6620374684b7ed79fb6f8e095a75bcec..7c177f5e25cc487145a4502e6ea68176caa9ef5e 100644 --- a/src/media/audio/sound/audiofile.cpp +++ b/src/media/audio/sound/audiofile.cpp @@ -59,22 +59,46 @@ AudioFile::onBufferFinish() AudioFile::AudioFile(const std::string &fileName, unsigned int sampleRate) : AudioLoop(sampleRate), filepath_(fileName), updatePlaybackScale_(0) { - 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)); - }); + auto decoder = std::make_unique<MediaDecoder>(); DeviceParams dev; dev.input = fileName; if (decoder->openInput(dev) < 0) throw AudioFileException("File could not be opened: " + fileName); - if (decoder->setupAudio() < 0) + if (decoder->setupFromAudioData() < 0) throw AudioFileException("Decoder setup failed: " + fileName); - while (decoder->decode() != MediaDemuxer::Status::EndOfFile); + 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; + } + } delete buffer_; buffer_ = buf.release(); diff --git a/src/media/media_buffer.h b/src/media/media_buffer.h index 3911c2eb2a1df1ed13fb9b8ce0c43af7592875e3..016160857c280e7f08c337ef2cb7e833fdc034d7 100644 --- a/src/media/media_buffer.h +++ b/src/media/media_buffer.h @@ -22,7 +22,6 @@ #include "config.h" #include "videomanager_interface.h" -#include "observer.h" #include <memory> #include <functional> @@ -39,7 +38,6 @@ namespace jami { using MediaFrame = DRing::MediaFrame; using AudioFrame = DRing::AudioFrame; -using MediaObserver = std::function<void(std::shared_ptr<MediaFrame>&&)>; #ifdef ENABLE_VIDEO diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 62740967a31760ee7b032435e49cf8eb143014c1..bd8c456dac9765de68c3ee4440435722585526c0 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -3,7 +3,6 @@ * * 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 @@ -54,20 +53,23 @@ 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 }; -MediaDemuxer::MediaDemuxer() : +MediaDecoder::MediaDecoder() : inputCtx_(avformat_alloc_context()), startTime_(AV_NOPTS_VALUE) -{} +{ +} -MediaDemuxer::~MediaDemuxer() +MediaDecoder::~MediaDecoder() { + if (decoderCtx_) + avcodec_free_context(&decoderCtx_); if (inputCtx_) avformat_close_input(&inputCtx_); + av_dict_free(&options_); } -int -MediaDemuxer::openInput(const DeviceParams& params) +int MediaDecoder::openInput(const DeviceParams& params) { inputParams_ = params; AVInputFormat *iformat = av_find_input_format(params.format.c_str()); @@ -121,6 +123,12 @@ MediaDemuxer::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(), @@ -136,26 +144,7 @@ MediaDemuxer::openInput(const DeviceParams& params) return ret; } -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) +void MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque) { if (cb) { inputCtx_->interrupt_callback.callback = cb; @@ -165,104 +154,48 @@ void MediaDemuxer::setInterruptCallback(int (*cb)(void*), void *opaque) } } -void MediaDemuxer::setIOContext(MediaIOHandle *ioctx) +void MediaDecoder::setIOContext(MediaIOHandle *ioctx) { inputCtx_->pb = ioctx->getContext(); } - -MediaDemuxer::Status -MediaDemuxer::decode() -{ - 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; -} - -MediaDecoder::MediaDecoder(const std::shared_ptr<MediaDemuxer>& demuxer, int index) - : demuxer_(demuxer), - avStream_(demuxer->getStream(index)) +int MediaDecoder::setupFromAudioData() { - demuxer->setStreamCallback(index, [this](AVPacket& packet) { - decode(packet); - }); - setupStream(); + return setupStream(AVMEDIA_TYPE_AUDIO); } -MediaDecoder::MediaDecoder() - : demuxer_(new MediaDemuxer) - {} - -MediaDecoder::MediaDecoder(MediaObserver o) - : demuxer_(new MediaDemuxer), callback_(std::move(o)) - {} - -MediaDecoder::~MediaDecoder() +int +MediaDecoder::selectStream(AVMediaType type) { -#ifdef RING_ACCEL - if (decoderCtx_ && decoderCtx_->hw_device_ctx) - av_buffer_unref(&decoderCtx_->hw_device_ctx); -#endif - if (decoderCtx_) - avcodec_free_context(&decoderCtx_); + return av_find_best_stream(inputCtx_, type, -1, -1, &inputDecoder_, 0); } int -MediaDecoder::openInput(const DeviceParams& p) +MediaDecoder::setupStream(AVMediaType mediaType) { - return demuxer_->openInput(p); -} + int ret = 0; + std::string streamType = av_get_media_type_string(mediaType); -void -MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque) -{ - demuxer_->setInterruptCallback(cb, opaque); -} + avcodec_free_context(&decoderCtx_); -void -MediaDecoder::setIOContext(MediaIOHandle *ioctx) -{ - demuxer_->setIOContext(ioctx); -} + // 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; + } + } -int MediaDecoder::setup(AVMediaType type) -{ - demuxer_->findStreamInfo(); - auto stream = demuxer_->selectStream(type); - if (stream < 0) + if ((streamIndex_ = selectStream(mediaType)) < 0) { + JAMI_ERR() << "No suitable " << streamType << " stream found"; 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"; @@ -276,15 +209,23 @@ MediaDecoder::setupStream() } avcodec_parameters_to_context(decoderCtx_, avStream_->codecpar); decoderCtx_->framerate = avStream_->avg_frame_rate; - if (avStream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (mediaType == 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); @@ -297,16 +238,10 @@ MediaDecoder::setupStream() } else { JAMI_WARN() << "Hardware decoding disabled by user preference"; } -#endif } +#endif - 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 - - JAMI_DBG() << "Decoding using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")"; + JAMI_DBG() << "Decoding " << streamType << " using " << inputDecoder_->long_name << " (" << inputDecoder_->name << ")"; decoderCtx_->err_recognition = (AV_EF_CRCCHECK | AV_EF_BITSTREAM | AV_EF_BUFFER | AV_EF_EXPLODE | AV_EF_CAREFUL | AV_EF_COMPLIANT | AV_EF_AGGRESSIVE); ret = avcodec_open2(decoderCtx_, inputDecoder_, nullptr); if (ret < 0) { @@ -317,17 +252,39 @@ MediaDecoder::setupStream() return 0; } +#ifdef ENABLE_VIDEO +int MediaDecoder::setupFromVideoData() +{ + return setupStream(AVMEDIA_TYPE_VIDEO); +} + MediaDecoder::Status -MediaDecoder::decode(AVPacket& packet) +MediaDecoder::decode(VideoFrame& result) { + 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; - auto ret = avcodec_send_packet(decoderCtx_, &packet); + ret = avcodec_send_packet(decoderCtx_, &inpacket); 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; @@ -335,6 +292,8 @@ MediaDecoder::decode(AVPacket& packet) if (ret >= 0) frameFinished = 1; + av_packet_unref(&inpacket); + if (frameFinished) { frame->format = (AVPixelFormat) correctPixFmt(frame->format); auto packetTimestamp = frame->pts; // in stream time base @@ -351,18 +310,73 @@ MediaDecoder::decode(AVPacket& packet) 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 -MediaDemuxer::Status -MediaDecoder::decode() +MediaDecoder::Status +MediaDecoder::decode(AudioFrame& decodedFrame) { - return demuxer_->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; } #ifdef ENABLE_VIDEO @@ -381,7 +395,7 @@ MediaDecoder::enableAccel(bool enableAccel) #endif MediaDecoder::Status -MediaDecoder::flush() +MediaDecoder::flush(VideoFrame& result) { AVPacket inpacket; av_init_packet(&inpacket); @@ -392,8 +406,7 @@ MediaDecoder::flush() if (ret < 0 && ret != AVERROR(EAGAIN)) return ret == AVERROR_EOF ? Status::Success : Status::DecodeError; - auto result = std::make_shared<MediaFrame>(); - ret = avcodec_receive_frame(decoderCtx_, result->pointer()); + ret = avcodec_receive_frame(decoderCtx_, result.pointer()); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) return Status::DecodeError; if (ret >= 0) @@ -401,9 +414,7 @@ MediaDecoder::flush() if (frameFinished) { av_packet_unref(&inpacket); - if (callback_) - callback_(std::move(result)); - return Status::FrameFinished; + return Status::EOFError; } return Status::Success; diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index 619fdde94552ea99eedaa9e3feca7d0900f4b315..70cc790fe2ed582c7abfaddee57df105618e4b75 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -22,22 +22,17 @@ #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> @@ -65,128 +60,85 @@ 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&); - void setInterruptCallback(int (*cb)(void*), void *opaque); - void setIOContext(MediaIOHandle *ioctx); - - int setup(AVMediaType type); - int setupAudio() { return setup(AVMEDIA_TYPE_AUDIO); } - int setupVideo() { return setup(AVMEDIA_TYPE_VIDEO); } - - MediaDemuxer::Status decode(); - Status flush(); - - int getWidth() const; - int getHeight() const; - std::string getDecoderName() const; - - rational<double> getFps() const; - AVPixelFormat getPixelFormat() const; - - void setOptions(const std::map<std::string, std::string>& options); #ifdef RING_ACCEL - void enableAccel(bool enableAccel); +namespace video { +class HardwareAccel; +} #endif - MediaStream getStream(std::string name = "") const; +class MediaDecoder { + public: + enum class Status { + Success, + FrameFinished, + EOFError, + ReadError, + DecodeError, + RestartRequired + }; + + MediaDecoder(); + ~MediaDecoder(); + + void emulateRate() { emulateRate_ = true; } + void setInterruptCallback(int (*cb)(void*), void *opaque); + int openInput(const DeviceParams&); + + void setIOContext(MediaIOHandle *ioctx); +#ifdef ENABLE_VIDEO + int setupFromVideoData(); + Status decode(VideoFrame&); + Status flush(VideoFrame&); +#endif // ENABLE_VIDEO + + int setupFromAudioData(); + Status decode(AudioFrame&); + + int getWidth() const; + int getHeight() const; + std::string getDecoderName() const; -private: - NON_COPYABLE(MediaDecoder); + rational<double> getFps() const; + AVPixelFormat getPixelFormat() const; + + void setOptions(const std::map<std::string, std::string>& options); +#ifdef RING_ACCEL + void enableAccel(bool enableAccel); +#endif - Status decode(AVPacket&); + MediaStream getStream(std::string name = "") const; - rational<unsigned> getTimeBase() const; + private: + NON_COPYABLE(MediaDecoder); - std::shared_ptr<MediaDemuxer> demuxer_; + rational<unsigned> getTimeBase() const; - AVCodec *inputDecoder_ = nullptr; - AVCodecContext *decoderCtx_ = nullptr; - AVStream *avStream_ = nullptr; - bool emulateRate_ = false; - int64_t startTime_; - int64_t lastTimestamp_; + AVCodec *inputDecoder_ = nullptr; + AVCodecContext *decoderCtx_ = nullptr; + AVFormatContext *inputCtx_ = nullptr; + AVStream *avStream_ = nullptr; + int streamIndex_ = -1; + bool emulateRate_ = false; + int64_t startTime_; + int64_t lastTimestamp_{0}; - DeviceParams inputParams_; + DeviceParams inputParams_; - int correctPixFmt(int input_pix_fmt); - int setupStream(); + int correctPixFmt(int input_pix_fmt); + int setupStream(AVMediaType mediaType); + int selectStream(AVMediaType type); - bool fallback_ = false; + bool fallback_ = false; #ifdef RING_ACCEL - bool enableAccel_ = true; - std::unique_ptr<video::HardwareAccel> accel_; - unsigned short accelFailures_ = 0; + bool enableAccel_ = true; + std::unique_ptr<video::HardwareAccel> accel_; + unsigned short accelFailures_ = 0; #endif - MediaObserver callback_; -protected: - AVDictionary *options_ = nullptr; + protected: + AVDictionary *options_ = nullptr; }; } // namespace jami diff --git a/src/media/video/video_base.cpp b/src/media/video/video_base.cpp index 9404aa745021732428312de209191323f1f3eed1..94c306fa1d076ac384c12ac6ca1d466e1f7340f0 100644 --- a/src/media/video/video_base.cpp +++ b/src/media/video/video_base.cpp @@ -47,14 +47,6 @@ VideoGenerator::publishFrame() notify(std::static_pointer_cast<MediaFrame>(lastFrame_)); } -void -VideoGenerator::publishFrame(std::shared_ptr<VideoFrame> frame) -{ - std::lock_guard<std::mutex> lk(mutex_); - lastFrame_ = std::move(frame); - notify(std::static_pointer_cast<MediaFrame>(lastFrame_)); -} - void VideoGenerator::flushFrames() { diff --git a/src/media/video/video_base.h b/src/media/video/video_base.h index d4e2e19e71dcf7da6936dcecf82fae0a3ce77b4a..63935a354759168a20a7611d056eded10a1d848a 100644 --- a/src/media/video/video_base.h +++ b/src/media/video/video_base.h @@ -80,7 +80,6 @@ public: // getNewFrame and publishFrame must be called by the same thread only VideoFrame& getNewFrame(); void publishFrame(); - void publishFrame(std::shared_ptr<VideoFrame>); void flushFrames(); private: diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp index 14ac429f7d58d28d35cb83edd999d60d6b348fbc..83e99b8a5554bb6f8f5365aa318144e7bdd975df 100644 --- a/src/media/video/video_input.cpp +++ b/src/media/video/video_input.cpp @@ -228,18 +228,40 @@ VideoInput::isCapturing() const noexcept bool VideoInput::captureFrame() { // Return true if capture could continue, false if must be stop + if (not decoder_) return false; - switch (decoder_->decode()) { - case MediaDemuxer::Status::EndOfFile: - createDecoder(); - return static_cast<bool>(decoder_); - case MediaDemuxer::Status::ReadError: - JAMI_ERR() << "Failed to decode frame"; - return false; - default: - return true; + auto& frame = getNewFrame(); + const auto ret = decoder_->decode(frame); + switch (ret) { + case MediaDecoder::Status::ReadError: + return false; + + // try to keep decoding + case MediaDecoder::Status::DecodeError: + return true; + + case MediaDecoder::Status::RestartRequired: + createDecoder(); +#ifdef RING_ACCEL + JAMI_WARN("Disabling hardware decoding due to previous failure"); + decoder_->enableAccel(false); +#endif + return static_cast<bool>(decoder_); + + // End of streamed file + case MediaDecoder::Status::EOFError: + createDecoder(); + return static_cast<bool>(decoder_); + + case MediaDecoder::Status::FrameFinished: + publishFrame(); + return true; + // continue decoding + case MediaDecoder::Status::Success: + default: + return true; } } @@ -341,9 +363,7 @@ VideoInput::createDecoder() return; } - auto decoder = std::make_unique<MediaDecoder>([this](const std::shared_ptr<MediaFrame>& frame) mutable { - publishFrame(std::static_pointer_cast<VideoFrame>(frame)); - }); + auto decoder = std::unique_ptr<MediaDecoder>(new MediaDecoder()); if (emulateRate_) decoder->emulateRate(); @@ -359,7 +379,7 @@ VideoInput::createDecoder() } /* Data available, finish the decoding */ - if (decoder->setupVideo() < 0) { + if (decoder->setupFromVideoData() < 0) { JAMI_ERR("decoder IO startup failed"); foundDecOpts(decOpts_); return; @@ -501,7 +521,7 @@ VideoInput::initFile(std::string path) DeviceParams p; p.input = path; auto dec = std::make_unique<MediaDecoder>(); - if (dec->openInput(p) < 0 || dec->setupVideo() < 0) { + if (dec->openInput(p) < 0 || dec->setupFromVideoData() < 0) { return initCamera(jami::getVideoDeviceMonitor().getDefaultDevice()); } diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp index 476fe843ca802c2ce2cf65e63944b2aa719dd3d1..6ab47e4ad29f50b7f5a1b6e23b0570824e76b1ee 100644 --- a/src/media/video/video_receive_thread.cpp +++ b/src/media/video/video_receive_thread.cpp @@ -55,9 +55,9 @@ VideoReceiveThread::VideoReceiveThread(const std::string& id, , sink_ {Manager::instance().createSinkClient(id)} , mtu_(mtu) , rotation_(0) - , requestKeyFrameCallback_() + , requestKeyFrameCallback_(0) , loop_(std::bind(&VideoReceiveThread::setup, this), - std::bind(&VideoReceiveThread::decodeFrame, this), + std::bind(&VideoReceiveThread::process, this), std::bind(&VideoReceiveThread::cleanup, this)) {} @@ -76,11 +76,7 @@ VideoReceiveThread::startLoop() // main thread to block while this executes, so it happens in the video thread. bool VideoReceiveThread::setup() { - videoDecoder_.reset(new MediaDecoder([this](const std::shared_ptr<MediaFrame>& frame) mutable { - if (auto displayMatrix = displayMatrix_) - av_frame_new_side_data_from_buf(frame->pointer(), AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(displayMatrix.get())); - publishFrame(std::static_pointer_cast<VideoFrame>(frame)); - })); + videoDecoder_.reset(new MediaDecoder()); dstWidth_ = args_.width; dstHeight_ = args_.height; @@ -124,7 +120,7 @@ bool VideoReceiveThread::setup() if (requestKeyFrameCallback_) requestKeyFrameCallback_(); - if (videoDecoder_->setupVideo()) { + if (videoDecoder_->setupFromVideoData()) { JAMI_ERR("decoder IO startup failed"); return false; } @@ -153,6 +149,9 @@ bool VideoReceiveThread::setup() return true; } +void VideoReceiveThread::process() +{ decodeFrame(); } + void VideoReceiveThread::cleanup() { detach(sink_.get()); @@ -186,15 +185,53 @@ void VideoReceiveThread::addIOContext(SocketPair& socketPair) demuxContext_.reset(socketPair.createIOContext(mtu_)); } -void VideoReceiveThread::decodeFrame() +bool VideoReceiveThread::decodeFrame() { - auto status = videoDecoder_->decode(); - if (status == MediaDemuxer::Status::EndOfFile || - status == MediaDemuxer::Status::ReadError) { - loop_.stop(); + auto& frame = getNewFrame(); + const auto ret = videoDecoder_->decode(frame); + + if (auto displayMatrix = displayMatrix_) + av_frame_new_side_data_from_buf(frame.pointer(), AV_FRAME_DATA_DISPLAYMATRIX, av_buffer_ref(displayMatrix.get())); + + switch (ret) { + case MediaDecoder::Status::FrameFinished: + publishFrame(); + return true; + + case MediaDecoder::Status::DecodeError: + JAMI_WARN("video decoding failure"); + if (requestKeyFrameCallback_) + { + auto keyFrameCheckTimer = std::chrono::steady_clock::now() - lastKeyFrameTime_; + if (keyFrameCheckTimer >= MS_BETWEEN_2_KEYFRAME_REQUEST) + { + lastKeyFrameTime_ = std::chrono::steady_clock::now(); + requestKeyFrameCallback_(); + } + } + break; + + case MediaDecoder::Status::ReadError: + JAMI_ERR("fatal error, read failed"); + loop_.stop(); + break; + + case MediaDecoder::Status::RestartRequired: + // disable accel, reset decoder's AVCodecContext +#ifdef RING_ACCEL + videoDecoder_->enableAccel(false); +#endif + videoDecoder_->setupFromVideoData(); + break; + case MediaDecoder::Status::Success: + case MediaDecoder::Status::EOFError: + break; } + + return false; } + void VideoReceiveThread::enterConference() { if (!loop_.isRunning()) diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h index 8e85e50817dde64b0aaa006227113066474c5388..413c529857bc45ed9d61a4ee22ea95d4c1cb5221 100644 --- a/src/media/video/video_receive_thread.h +++ b/src/media/video/video_receive_thread.h @@ -94,7 +94,7 @@ private: std::function<void(void)> requestKeyFrameCallback_; void openDecoder(); - void decodeFrame(); + bool decodeFrame(); static int interruptCb(void *ctx); static int readFunction(void *opaque, uint8_t *buf, int buf_size); diff --git a/src/observer.h b/src/observer.h index 7c7e5f8f821df1c06c89e617cab97f490cce2a37..a4b84a44f7e12f87c8c6f61b9fe72f790cde988f 100644 --- a/src/observer.h +++ b/src/observer.h @@ -28,7 +28,6 @@ #include <memory> #include <set> #include <mutex> -#include <functional> #include <ciso646> // fix windows compiler bug namespace jami { @@ -43,7 +42,6 @@ class Observable { public: Observable() : observers_(), mutex_() {} - virtual ~Observable() { std::lock_guard<std::mutex> lk(mutex_); for (auto& o : observers_) @@ -98,17 +96,4 @@ public: virtual void detached(Observable<T>*) {}; }; - -template <typename T> -class FuncObserver : public Observer<T> -{ -public: - using F = std::function<void(const T&)>; - FuncObserver(F f) : f_(f) {}; - virtual ~FuncObserver() {}; - void update(Observable<T>*, const T& t) override { f_(t); } -private: - F f_; -}; - }; // namespace jami diff --git a/test/unitTest/media/test_media_decoder.cpp b/test/unitTest/media/test_media_decoder.cpp index c2181c0684c49d30ad9f6e9f37054e9c674ed8b7..e44755bd415929736df9ec3627e81b9b4a1e149b 100644 --- a/test/unitTest/media/test_media_decoder.cpp +++ b/test/unitTest/media/test_media_decoder.cpp @@ -61,6 +61,7 @@ MediaDecoderTest::setUp() { DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); libav_utils::ring_avcodec_init(); + decoder_.reset(new MediaDecoder); } void @@ -79,26 +80,28 @@ MediaDecoderTest::testAudioFile() writeWav(); - decoder_.reset(new MediaDecoder([this](const std::shared_ptr<MediaFrame>&& f) mutable { - CPPUNIT_ASSERT(f->pointer()->sample_rate == decoder_->getStream().sampleRate); - CPPUNIT_ASSERT(f->pointer()->channels == decoder_->getStream().nbChannels); - })); DeviceParams dev; dev.input = filename_; CPPUNIT_ASSERT(decoder_->openInput(dev) >= 0); - CPPUNIT_ASSERT(decoder_->setupAudio() >= 0); + CPPUNIT_ASSERT(decoder_->setupFromAudioData() >= 0); bool done = false; while (!done) { - switch (decoder_->decode()) { - case MediaDemuxer::Status::ReadError: + AudioFrame frame; + switch (decoder_->decode(frame)) { + case MediaDecoder::Status::FrameFinished: + CPPUNIT_ASSERT(frame.pointer()->sample_rate == decoder_->getStream().sampleRate); + CPPUNIT_ASSERT(frame.pointer()->channels == decoder_->getStream().nbChannels); + break; + case MediaDecoder::Status::DecodeError: + case MediaDecoder::Status::ReadError: CPPUNIT_ASSERT_MESSAGE("Decode error", false); done = true; break; - case MediaDemuxer::Status::EndOfFile: + case MediaDecoder::Status::EOFError: done = true; break; - case MediaDemuxer::Status::Success: + case MediaDecoder::Status::Success: default: break; }