/*** Olive - Non-Linear Video Editor Copyright (C) 2021 Olive Team 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "ffmpegdecoder.h" extern "C" { #include #include #include #include #include #include } #include #include #include #include #include #include #include #include #include "common/define.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "render/framehashcache.h" #include "render/diskmanager.h" namespace olive { FFmpegDecoder::FFmpegDecoder() : filter_graph_(nullptr), buffersrc_ctx_(nullptr), buffersink_ctx_(nullptr), pool_(QThread::idealThreadCount()*2), is_working_(false), cache_at_zero_(false), cache_at_eof_(false) { } bool FFmpegDecoder::OpenInternal() { if (instance_.Open(stream().filename().toUtf8(), stream().stream())) { AVStream* s = instance_.avstream(); // Store one second in the source's timebase second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); if (s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // Get an Olive compatible AVPixelFormat ideal_pix_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(static_cast(s->codecpar->format)); // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_); native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt_); if (native_pix_fmt_ == VideoParams::kFormatInvalid || native_channel_count_ == 0) { qDebug() << "Failed to find valid native pixel format for" << ideal_pix_fmt_; return false; } } return true; } return false; } /*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) { // This is a still image QString img_filename = stream().filename(); int64_t ts; // If it's an image sequence, we'll probably need to transform the filename if (stream().GetStream().video_type() == Track::kVideoTypeImageSequence) { ts = stream().GetTimeInTimebaseUnits(timecode); img_filename = TransformImageSequenceFileName(stream().filename(), ts); } else { ts = 0; } AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); FramePtr output_frame = nullptr; Instance i; i.Open(img_filename.toUtf8(), stream().GetRealStreamIndex()); int ret = i.GetFrame(pkt, frame); if (ret >= 0) { VideoParams video_params = stream().video_params(); // Create frame to return output_frame = Frame::Create(); output_frame->set_video_params(VideoParams(frame->width, frame->height, native_pix_fmt_, native_channel_count_, video_params.pixel_aspect_ratio(), video_params.interlacing(), divider)); output_frame->set_timestamp(timecode); output_frame->allocate(); uint8_t* copy_data = reinterpret_cast(output_frame->data()); int copy_linesize = output_frame->linesize_bytes(); FFmpegBufferToNativeBuffer(frame->data, frame->linesize, ©_data, ©_linesize); } else { qWarning() << "Failed to retrieve still image from decoder"; } i.Close(); av_frame_free(&frame); av_packet_free(&pkt); return output_frame; }*/ FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled) { if (!InitScaler(params)) { return nullptr; } AVStream* s = instance_.avstream(); // Retrieve frame FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, cancelled); // We found the frame, we'll return a copy if (return_frame) { FramePtr copy = Frame::Create(); copy->set_video_params(VideoParams(s->codecpar->width, s->codecpar->height, native_pix_fmt_, native_channel_count_, av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect, VideoParams::kInterlaceNone, filter_params_.divider)); copy->set_timestamp(timecode); copy->allocate(); // This data will already match the frame memcpy(copy->data(), return_frame->data(), copy->allocated_size()); return copy; } return nullptr; } void FFmpegDecoder::CloseInternal() { ClearFrameCache(); instance_.Close(); } int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame) { // Ensure scaler is correct for these parameters int ret; AVFrame* working_frame = av_frame_alloc(); // Try to pull frame from buffersink while ((ret = av_buffersink_get_frame(buffersink_ctx_, output_frame)) == AVERROR(EAGAIN)) { // If no frame is ready in the buffersink, pull from codec ret = instance_.GetFrame(packet, working_frame); if (ret >= 0) { // Override this frame's interlacing parameters from user switch (filter_params_.src_interlacing) { case VideoParams::kInterlaceNone: working_frame->interlaced_frame = 0; break; case VideoParams::kInterlacedTopFirst: working_frame->interlaced_frame = 1; working_frame->top_field_first = 1; break; case VideoParams::kInterlacedBottomFirst: working_frame->interlaced_frame = 1; working_frame->top_field_first = 0; break; } // If succeeded in pulling from codec, send to buffer source ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, working_frame, AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) { // If failed to send to buffer source, return break and error code qDebug() << "Failed to feed filter graph:" << FFmpegError(ret); break; } } else { // If failed to read from decoder, return break and error code break; } } av_frame_free(&working_frame); return ret; } QString FFmpegDecoder::id() const { return QStringLiteral("ffmpeg"); } FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicInt *cancelled) const { // Return value FootageDescription desc(id()); // Variable for receiving errors from FFmpeg int error_code; // Convert QString to a C string QByteArray filename_c = filename.toUtf8(); // Open file in a format context AVFormatContext* fmt_ctx = nullptr; error_code = avformat_open_input(&fmt_ctx, filename_c, nullptr, nullptr); // Handle format context error if (error_code == 0) { // Retrieve metadata about the media avformat_find_stream_info(fmt_ctx, nullptr); int64_t footage_duration = fmt_ctx->duration; // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { // FFmpeg AVStream AVStream* avstream = fmt_ctx->streams[i]; // Find decoder for this stream, if it exists we can proceed AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id); if (decoder && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)) { if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { bool image_is_still = false; rational pixel_aspect_ratio; rational frame_rate; VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone; { // Read at least two frames to get more information about this video stream AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); { Instance instance; instance.Open(filename_c, avstream->index); // Read first frame and retrieve some metadata if (instance.GetFrame(pkt, frame) >= 0) { // Check if video is interlaced and what field dominance it has if so if (frame->interlaced_frame) { if (frame->top_field_first) { interlacing = VideoParams::kInterlacedTopFirst; } else { interlacing = VideoParams::kInterlacedBottomFirst; } } pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(), instance.avstream(), frame); frame_rate = av_guess_frame_rate(instance.fmt_ctx(), instance.avstream(), frame); } // Read second frame int ret = instance.GetFrame(pkt, frame); if (ret >= 0) { // Check if we need a manual duration if (avstream->duration == AV_NOPTS_VALUE) { if (footage_duration == AV_NOPTS_VALUE) { // Manually read through file for duration int64_t new_dur; do { new_dur = frame->pts; } while (instance.GetFrame(pkt, frame) >= 0); avstream->duration = new_dur; } else { // Fallback to footage duration avstream->duration = Timecode::rescale_timestamp_ceil(footage_duration, rational(1, AV_TIME_BASE), avstream->time_base); } } } else if (ret == AVERROR_EOF) { // Video has only one frame in it, treat it like a still image image_is_still = true; } instance.Close(); } av_frame_free(&frame); av_packet_free(&pkt); } AVPixelFormat compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)); VideoParams stream; stream.set_stream_index(i); stream.set_width(avstream->codecpar->width); stream.set_height(avstream->codecpar->height); stream.set_video_type((image_is_still) ? VideoParams::kVideoTypeStill : VideoParams::kVideoTypeVideo); stream.set_format(GetNativePixelFormat(compatible_pix_fmt)); stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); stream.set_interlacing(interlacing); stream.set_pixel_aspect_ratio(pixel_aspect_ratio); stream.set_frame_rate(frame_rate); stream.set_start_time(avstream->start_time); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); // Defaults to false, requires user intervention if incorrect stream.set_premultiplied_alpha(false); desc.AddVideoStream(stream); } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // Create an audio stream object uint64_t channel_layout = avstream->codecpar->channel_layout; if (!channel_layout) { channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } if (avstream->duration == AV_NOPTS_VALUE) { // Loop through stream until we get the whole duration if (footage_duration == AV_NOPTS_VALUE) { Instance instance; instance.Open(filename_c, avstream->index); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); int64_t new_dur; do { new_dur = frame->pts; } while (instance.GetFrame(pkt, frame) >= 0); avstream->duration = new_dur; av_frame_free(&frame); av_packet_free(&pkt); instance.Close(); } else { avstream->duration = Timecode::rescale_timestamp_ceil(footage_duration, rational(1, AV_TIME_BASE), avstream->time_base); } } AudioParams stream; stream.set_stream_index(i); stream.set_channel_layout(channel_layout); stream.set_sample_rate(avstream->codecpar->sample_rate); stream.set_format(AudioParams::kInternalFormat); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); desc.AddAudioStream(stream); } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { qDebug() << "Subtitle probing: Stub"; } } } } // Free all memory avformat_close_input(&fmt_ctx); return desc; } QString FFmpegDecoder::FFmpegError(int error_code) { char err[1024]; av_strerror(error_code, err, 512); return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioParams ¶ms, const QAtomicInt *cancelled) { // Iterate through each audio frame and extract the PCM data // Seek to starting point instance_.Seek(0); // Handle NULL channel layout uint64_t channel_layout = ValidateChannelLayout(instance_.avstream()); if (!channel_layout) { qCritical() << "Failed to determine channel layout of audio file, could not conform"; return false; } // Create resampling context SwrContext* resampler = swr_alloc_set_opts(nullptr, params.channel_layout(), FFmpegUtils::GetFFmpegSampleFormat(params.format()), params.sample_rate(), channel_layout, static_cast(instance_.avstream()->codecpar->format), instance_.avstream()->codecpar->sample_rate, 0, nullptr); swr_init(resampler); QFile wave_out(filename); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); int ret; bool success = false; int64_t duration = instance_.avstream()->duration; if (duration == 0 || duration == AV_NOPTS_VALUE) { duration = instance_.fmt_ctx()->duration; if (!(duration == 0 || duration == AV_NOPTS_VALUE)) { // Rescale from AVFormatContext timebase to AVStream timebase duration = av_rescale_q_rnd(duration, {1, AV_TIME_BASE}, instance_.avstream()->time_base, AV_ROUND_UP); } } if (wave_out.open(QFile::WriteOnly)) { while (true) { // Check if we have a `cancelled` ptr and its value if (cancelled && *cancelled) { break; } ret = instance_.GetFrame(pkt, frame); if (ret < 0) { if (ret == AVERROR_EOF) { success = true; } else { char err_str[512]; av_strerror(ret, err_str, 512); qWarning() << "Failed to conform:" << ret << err_str; } break; } // Allocate buffers int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); char* data = new char[params.samples_to_bytes(nb_samples)]; // Resample audio to our destination parameters nb_samples = swr_convert(resampler, reinterpret_cast(&data), nb_samples, const_cast(frame->data), frame->nb_samples); if (nb_samples < 0) { char err_str[512]; av_strerror(nb_samples, err_str, 512); qWarning() << "libswresample failed with error:" << nb_samples << err_str; break; } // Write packed WAV data to the disk cache wave_out.write(data, params.samples_to_bytes(nb_samples)); // If we allocated an output for the resampler, delete it here if (data != reinterpret_cast(frame->data[0])) { delete [] data; } SignalProcessingProgress(frame->pts, duration); } wave_out.close(); } else { qWarning() << "Failed to open WAVE output for indexing"; } swr_free(&resampler); av_frame_free(&frame); av_packet_free(&pkt); return success; } VideoParams::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_RGB24: case AV_PIX_FMT_RGBA: return VideoParams::kFormatUnsigned8; case AV_PIX_FMT_RGB48: case AV_PIX_FMT_RGBA64: return VideoParams::kFormatUnsigned16; default: return VideoParams::kFormatInvalid; } } int FFmpegDecoder::GetNativeChannelCount(AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_RGB24: case AV_PIX_FMT_RGB48: return VideoParams::kRGBChannelCount; case AV_PIX_FMT_RGBA: case AV_PIX_FMT_RGBA64: return VideoParams::kRGBAChannelCount; default: return 0; } } uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) { if (stream->codecpar->channel_layout) { return stream->codecpar->channel_layout; } return av_get_default_channel_layout(stream->codecpar->channels); } const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing) { if (interlacing == VideoParams::kInterlacedTopFirst) { return "tff"; } else { return "bff"; } } /* OLD UNUSED CODE: Keeping this around in case the code proves useful void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) { QFile save_frame(GetIndexFilename().append(QString::number(f->pts))); if (save_frame.open(QFile::WriteOnly)) { // Save frame to media index int cached_buffer_sz = av_image_get_buffer_size(static_cast(f->format), f->width, f->height, 1); QByteArray cached_frame(cached_buffer_sz, Qt::Uninitialized); av_image_copy_to_buffer(reinterpret_cast(cached_frame.data()), cached_frame.size(), f->data, f->linesize, static_cast(f->format), f->width, f->height, 1); save_frame.write(qCompress(cached_frame, 1)); save_frame.close(); DiskManager::instance()->CreatedFile(save_frame.fileName(), QByteArray()); } // See if we stored this frame in the disk cache QByteArray frame_loader; if (!got_frame) { QFile compressed_frame(GetIndexFilename().append(QString::number(target_ts))); if (compressed_frame.exists() && compressed_frame.size() > 0 && compressed_frame.open(QFile::ReadOnly)) { DiskManager::instance()->Accessed(compressed_frame.fileName()); // Read data frame_loader = qUncompress(compressed_frame.readAll()); av_image_fill_arrays(input_data, input_linesize, reinterpret_cast(frame_loader.data()), static_cast(avstream_->codecpar->format), avstream_->codecpar->width, avstream_->codecpar->height, 1); got_frame = true; } } } */ void FFmpegDecoder::ClearFrameCache() { if (!cached_frames_.isEmpty()) { cached_frames_.clear(); cache_at_eof_ = false; cache_at_zero_ = false; // Filter graph may rely on "continuous" video frames, so we free the scaler here FreeScaler(); InitScaler(filter_params_); } } FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled) { int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); const int64_t min_seek = -instance_.avstream()->start_time; int64_t seek_ts = target_ts; bool still_seeking = false; if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { // If we are de-interlacing, the timebase is doubled because we get one frame per field, so we // double the target timestamp too target_ts *= 2; } if (time != kAnyTimecode) { // If the frame wasn't in the frame cache, see if this frame cache is too old to use if (cached_frames_.isEmpty() || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) { ClearFrameCache(); instance_.Seek(seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } still_seeking = true; } else { // Search cache for frame FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts); if (cached_frame) { return cached_frame; } } } int ret; AVPacket* pkt = av_packet_alloc(); FFmpegFramePool::ElementPtr return_frame = nullptr; // Allocate a new frame AVFrame* working_frame = av_frame_alloc(); while (true) { // Break out of loop if we've cancelled if (cancelled && *cancelled) { break; } // Pull from the decoder av_frame_unref(working_frame); ret = GetFilteredFrame(pkt, working_frame); // Handle any errors that aren't EOF (EOF is handled later on) if (ret < 0 && ret != AVERROR_EOF) { qCritical() << "Failed to retrieve frame:" << ret; break; } if (still_seeking) { // Handle a failure to seek (occurs on some media) // We'll only be here if the frame cache was emptied earlier if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->pts > target_ts)) { seek_ts = qMax(min_seek, seek_ts - second_ts_); instance_.Seek(seek_ts); if (seek_ts == min_seek) { cache_at_zero_ = true; } continue; } else { still_seeking = false; } } if (ret == AVERROR_EOF) { // Handle an "expected" EOF by using the last frame of our cache cache_at_eof_ = true; if (cached_frames_.isEmpty()) { qCritical() << "Unexpected codec EOF - unable to retrieve frame"; } else { return_frame = cached_frames_.last(); } break; } else { // Cut down to thread count - 1 before we acquire a new frame if (cached_frames_.size() == QThread::idealThreadCount()) { RemoveFirstFrame(); } FFmpegFramePool::ElementPtr cached = pool_.Get(); if (!cached) { qCritical() << "Frame pool failed to return a valid frame - out of memory?"; break; } // Store in queue, converting to native format uint8_t* destination_data = cached->data(); int destination_linesize = Frame::generate_linesize_bytes(working_frame->width, native_pix_fmt_, native_channel_count_); av_image_copy(&destination_data, &destination_linesize, const_cast(working_frame->data), working_frame->linesize, static_cast(working_frame->format), working_frame->width, working_frame->height); // Set timestamp so this frame can be identified later cached->set_timestamp(working_frame->pts); // Store frame before just in case FFmpegFramePool::ElementPtr previous; if (cached_frames_.isEmpty()) { previous = nullptr; } else { previous = cached_frames_.last(); } // Append this frame and signal to other threads that a new frame has arrived cached_frames_.append(cached); // If this is a valid frame, see if this or the frame before it are the one we need if (cached->timestamp() == target_ts || time == kAnyTimecode) { return_frame = cached; break; } else if (cached->timestamp() > target_ts) { if (!previous && cache_at_zero_) { return_frame = cached; break; } else { return_frame = previous; break; } } } } av_frame_free(&working_frame); av_packet_free(&pkt); return return_frame; } bool FFmpegDecoder::InitScaler(const RetrieveVideoParams& params) { if (params == filter_params_ && filter_graph_) { // We have an appropriate filter for these parameters, just return true return true; } // We need to (re)create the filter, delete current if necessary ClearFrameCache(); // Set our params to this filter_params_ = params; // Allocate filter graph filter_graph_ = avfilter_graph_alloc(); if (!filter_graph_) { qWarning() << "Failed to allocate filter graph"; return false; } AVStream* s = instance_.avstream(); int src_width = s->codecpar->width; int src_height = s->codecpar->height; // Define filter parameters static const int kFilterArgSz = 1024; char filter_args[kFilterArgSz]; snprintf(filter_args, kFilterArgSz, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", src_width, src_height, s->codecpar->format, s->time_base.num, s->time_base.den, s->codecpar->sample_aspect_ratio.num, s->codecpar->sample_aspect_ratio.den); // Create path in and out of the filter graph (the buffer in and the buffersink out) avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("buffer"), "in", filter_args, nullptr, filter_graph_); avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, filter_graph_); // Link filters as necessary AVFilterContext *last_filter = buffersrc_ctx_; // Add interlacing filter if necessary if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) { // Footage is interlaced, our renderer works in progressive so we'll need to de-interlace AVFilterContext* interlace_filter; snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s", GetInterlacingModeInFFmpeg(filter_params_.src_interlacing)); avfilter_graph_create_filter(&interlace_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, filter_graph_); avfilter_link(last_filter, 0, interlace_filter, 0); last_filter = interlace_filter; } // Add scale filter if necessary int dst_width, dst_height; if (filter_params_.divider > 1) { AVFilterContext* scale_filter; dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider); dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider); snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=%d", dst_width, dst_height, params.dst_interlacing != VideoParams::kInterlaceNone); avfilter_graph_create_filter(&scale_filter, avfilter_get_by_name("scale"), "scale", filter_args, nullptr, filter_graph_); avfilter_link(last_filter, 0, scale_filter, 0); last_filter = scale_filter; } else { dst_width = src_width; dst_height = src_height; } // Add format filter if necessary if (ideal_pix_fmt_ != s->codecpar->format) { AVFilterContext* format_filter; snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt_); avfilter_graph_create_filter(&format_filter, avfilter_get_by_name("format"), "format", filter_args, nullptr, filter_graph_); avfilter_link(last_filter, 0, format_filter, 0); last_filter = format_filter; } // Finally, link the last filter with the buffersink avfilter_link(last_filter, 0, buffersink_ctx_, 0); // Configure graph if (int ret = avfilter_graph_config(filter_graph_, nullptr) < 0) { qDebug() << "Failed to configure graph:" << FFmpegError(ret); return false; } // Configure frame pool if (pool_.width() != dst_width || pool_.height() != dst_height) { // Set new frame pool parameters pool_.SetParameters(dst_width, dst_height, native_pix_fmt_, native_channel_count_); } return true; } void FFmpegDecoder::FreeScaler() { if (filter_graph_) { avfilter_graph_free(&filter_graph_); filter_graph_ = nullptr; buffersrc_ctx_ = nullptr; buffersink_ctx_ = nullptr; } } FFmpegFramePool::ElementPtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const { if (t < cached_frames_.first()->timestamp()) { if (cache_at_zero_) { cached_frames_.first()->access(); return cached_frames_.first(); } } else if (t > cached_frames_.last()->timestamp()) { if (cache_at_eof_) { cached_frames_.last()->access(); return cached_frames_.last(); } } else { // We already have this frame in the cache, find it for (int i=0;itimestamp() == t // Test for an exact match || (i < cached_frames_.size() - 1 && cached_frames_.at(i+1)->timestamp() > t)) { // Or for this frame to be the "closest" this_frame->access(); return this_frame; } } } return nullptr; } void FFmpegDecoder::RemoveFirstFrame() { cached_frames_.removeFirst(); cache_at_zero_ = false; } FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), opts_(nullptr) { } bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index) { // Open file in a format context int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr); // Handle format context error if (error_code != 0) { qCritical() << "Failed to open input:" << filename << FFmpegError(error_code); return false; } // Get stream information from format error_code = avformat_find_stream_info(fmt_ctx_, nullptr); // Handle get stream information error if (error_code < 0) { qCritical() << "Failed to find stream info:" << FFmpegError(error_code); return false; } // Get reference to correct AVStream avstream_ = fmt_ctx_->streams[stream_index]; // Find decoder AVCodec* codec = avcodec_find_decoder(avstream_->codecpar->codec_id); // Handle failure to find decoder if (codec == nullptr) { qCritical() << "Failed to find appropriate decoder for this codec:" << filename << stream_index << avstream_->codecpar->codec_id; return false; } // Allocate context for the decoder codec_ctx_ = avcodec_alloc_context3(codec); if (codec_ctx_ == nullptr) { qCritical() << "Failed to allocate codec context"; return false; } // Copy parameters from the AVStream to the AVCodecContext error_code = avcodec_parameters_to_context(codec_ctx_, avstream_->codecpar); // Handle failure to copy parameters if (error_code < 0) { qCritical() << "Failed to copy parameters from AVStream to AVCodecContext"; return false; } // Set multithreading setting error_code = av_dict_set(&opts_, "threads", "auto", 0); // Handle failure to set multithreaded decoding if (error_code < 0) { qCritical() << "Failed to set codec options, performance may suffer"; } // Open codec error_code = avcodec_open2(codec_ctx_, codec, &opts_); if (error_code < 0) { char buf[512]; av_strerror(error_code, buf, 512); qCritical() << "Failed to open codec" << codec->id << error_code << buf; return false; } return true; } void FFmpegDecoder::Instance::Close() { if (opts_) { av_dict_free(&opts_); opts_ = nullptr; } if (codec_ctx_) { avcodec_free_context(&codec_ctx_); codec_ctx_ = nullptr; } if (fmt_ctx_) { avformat_close_input(&fmt_ctx_); fmt_ctx_ = nullptr; } } int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) { bool eof = false; int ret; // Clear any previous frames av_frame_unref(frame); while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) { // Find next packet in the correct stream index do { // Free buffer in packet if there is one av_packet_unref(pkt); // Read packet from file ret = av_read_frame(fmt_ctx_, pkt); } while (pkt->stream_index != avstream_->index && ret >= 0); if (ret == AVERROR_EOF) { // Don't break so that receive gets called again, but don't try to read again eof = true; // Send a null packet to signal end of avcodec_send_packet(codec_ctx_, nullptr); } else if (ret < 0) { // Handle other error by breaking loop and returning the code we received break; } else { // Successful read, send the packet ret = avcodec_send_packet(codec_ctx_, pkt); // We don't need the packet anymore, so free it av_packet_unref(pkt); if (ret < 0) { break; } } } return ret; } void FFmpegDecoder::Instance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); } }