diff --git a/app/codec/decoder.h b/app/codec/decoder.h index de617955f..07e028906 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -32,7 +32,6 @@ extern "C" { #include "codec/frame.h" #include "codec/samplebuffer.h" #include "codec/waveoutput.h" -#include "common/constructors.h" #include "common/rational.h" #include "project/item/footage/footage.h" diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 7b897d15b..e5868335b 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -25,7 +25,6 @@ #include #include "codec/frame.h" -#include "common/constructors.h" #include "common/timerange.h" #include "render/audioparams.h" #include "render/videoparams.h" diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index 9f3424e38..12267fe76 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -16,13 +16,14 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + codec/ffmpeg/avframeptr.h codec/ffmpeg/ffmpegcommon.h codec/ffmpeg/ffmpegcommon.cpp codec/ffmpeg/ffmpegdecoder.h codec/ffmpeg/ffmpegdecoder.cpp codec/ffmpeg/ffmpegencoder.h codec/ffmpeg/ffmpegencoder.cpp - codec/ffmpeg/ffmpegframecache.h - codec/ffmpeg/ffmpegframecache.cpp + codec/ffmpeg/ffmpegframepool.h + codec/ffmpeg/ffmpegframepool.cpp PARENT_SCOPE ) diff --git a/app/common/constructors.h b/app/codec/ffmpeg/avframeptr.h similarity index 60% rename from app/common/constructors.h rename to app/codec/ffmpeg/avframeptr.h index 07743c41c..2e7716d52 100644 --- a/app/common/constructors.h +++ b/app/codec/ffmpeg/avframeptr.h @@ -18,30 +18,43 @@ ***/ -#ifndef CONSTRUCTORS_H -#define CONSTRUCTORS_H +#ifndef AVFRAMEPTR_H +#define AVFRAMEPTR_H + +extern "C" { +#include +} + +#include +#include #include "common/define.h" OLIVE_NAMESPACE_ENTER -/** - * Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we - * use our own functions for portability. - */ +class AVFrameWrapper { +public: + AVFrameWrapper() { + frame_ = av_frame_alloc(); + } -#define DISABLE_COPY(Class) \ - Class(const Class &) = delete;\ - Class &operator=(const Class &) = delete; + virtual ~AVFrameWrapper() { + av_frame_free(&frame_); + } -#define DISABLE_MOVE(Class) \ - Class(Class &&) = delete; \ - Class &operator=(Class &&) = delete; + DISABLE_COPY_MOVE(AVFrameWrapper) -#define DISABLE_COPY_MOVE(Class) \ - DISABLE_COPY(Class) \ - DISABLE_MOVE(Class) + inline AVFrame* frame() const { + return frame_; + } + +private: + AVFrame* frame_; + +}; + +using AVFramePtr = std::shared_ptr; OLIVE_NAMESPACE_EXIT -#endif // CONSTRUCTORS_H +#endif // AVFRAMEPTR_H diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3117b0fb6..2dcc97622 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -44,19 +44,21 @@ extern "C" { OLIVE_NAMESPACE_ENTER +QHash< Stream*, QList > FFmpegDecoder::instances_; +QMutex FFmpegDecoder::instance_lock_; + +// FIXME: Hardcoded, ideally this value is dynamically chosen based on memory restraints +const int FFmpegDecoder::kMaxFrameLife = 2000; + FFmpegDecoder::FFmpegDecoder() : - fmt_ctx_(nullptr), - codec_ctx_(nullptr), scale_ctx_(nullptr), - scale_divider_(-1), - cache_at_zero_(false), - cache_at_eof_(false), - opts_(nullptr) + scale_divider_(0) { - // FIXME: Hardcoded, ideally this value is dynamically chosen based on memory restraints - clear_timer_.setInterval(250); + clear_timer_.setInterval(kMaxFrameLife); clear_timer_.moveToThread(qApp->thread()); connect(&clear_timer_, &QTimer::timeout, this, &FFmpegDecoder::ClearTimerEvent); + + av_buffer_pool_init(20, av_buffer_allocz); } FFmpegDecoder::~FFmpegDecoder() @@ -74,80 +76,20 @@ bool FFmpegDecoder::Open() Q_ASSERT(stream()); - int error_code; - // Convert QString to a C string - QByteArray ba = stream()->footage()->filename().toUtf8(); - const char* filename = ba.constData(); + QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - // Open file in a format context - error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr); + our_instance_ = new FFmpegDecoderInstance(fn_bytes.constData(), stream()->index()); - // Handle format context error - if (error_code != 0) { - FFmpegError(error_code); + if (!our_instance_->IsValid()) { + delete our_instance_; 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) { - 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) { - Error(QStringLiteral("Failed to find appropriate decoder for this codec (%1:%2 - %3)") - .arg(stream()->footage()->filename(), - QString::number(avstream_->index), - QString::number(avstream_->codecpar->codec_id))); - return false; - } - - // Allocate context for the decoder - codec_ctx_ = avcodec_alloc_context3(codec); - if (codec_ctx_ == nullptr) { - Error(QStringLiteral("Failed to allocate codec context (%1 :: %2)").arg(stream()->footage()->filename(), stream()->index())); - 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) { - FFmpegError(error_code); - return false; - } - - // Set multithreading setting - error_code = av_dict_set(&opts_, "threads", "auto", 0); - - // Handle failure to set multithreaded decoding - if (error_code < 0) { - FFmpegError(error_code); - return false; - } - - // Open codec - error_code = avcodec_open2(codec_ctx_, codec, &opts_); - if (error_code < 0) { - FFmpegError(error_code); - return false; - } - - if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (stream()->type() == Stream::kVideo) { // Get an Olive compatible AVPixelFormat - ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(static_cast(avstream_->codecpar->format)); + src_pix_fmt_ = static_cast(our_instance_->stream()->codecpar->format); + ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(src_pix_fmt_); // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats @@ -169,14 +111,25 @@ bool FFmpegDecoder::Open() qFatal("Invalid output format"); } - second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base))); + aspect_ratio_ = our_instance_->sample_aspect_ratio(); QMetaObject::invokeMethod(&clear_timer_, "start"); } + time_base_ = our_instance_->stream()->time_base; + start_time_ = our_instance_->stream()->start_time; + // All allocation succeeded so we set the state to open open_ = true; + { + QMutexLocker l(&instance_lock_); + + QList list = instances_.value(stream().get()); + list.append(our_instance_); + instances_.insert(stream().get(), list); + } + return true; } @@ -188,9 +141,11 @@ Decoder::RetrieveState FFmpegDecoder::GetRetrieveState(const rational& time) return kFailedToOpen; } - if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (stream()->type() == Stream::kVideo) { - } else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // Do nothing + + } else if (stream()->type() == Stream::kAudio) { AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); if (time > audio_stream->index_length() && !audio_stream->index_done()) { @@ -210,204 +165,154 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid return nullptr; } - if (avstream_->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { + if (stream()->type() != Stream::kVideo) { return nullptr; } - int64_t target_ts = Timecode::time_to_timestamp(timecode, avstream_->time_base) + avstream_->start_time; + int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_; - Frame* return_frame = nullptr; + FFmpegDecoderInstance* working_instance = nullptr; + FFmpegFramePool::ElementPtr return_frame = nullptr; - if (divider != scale_divider_) { - ClearFrameCache(); - FreeScaler(); - SetupScaler(divider); - } + // Find instance + do { + QMutexLocker list_locker(&instance_lock_); - // See if our RAM cache already has a frame that matches this timestamp - if (!cached_frames_.isEmpty()) { + QList non_ideal_contenders; - if (target_ts < cached_frames_.first()->native_timestamp()) { + QList instances = instances_.value(stream().get()); - if (cache_at_zero_) { - return_frame = cached_frames_.first(); - cached_frames_.accessedFirst(); - } + foreach (FFmpegDecoderInstance* i, instances) { - } else if (target_ts > cached_frames_.last()->native_timestamp()) { + i->cache_lock()->lock(); - if (cache_at_eof_) { - return_frame = cached_frames_.last(); - cached_frames_.accessedLast(); - } + if (i->CacheContainsTime(target_ts)) { - } else { + // Found our instance, allow others to enter the list - // We already have this frame in the cache, find it - for (int i=0;inative_timestamp() == target_ts // Test for an exact match - || (i < cached_frames_.size() - 1 && cached_frames_.at(i+1)->native_timestamp() > target_ts)) { // Or for this frame to be the "closest" + // Get the frame from this cache + return_frame = i->GetFrameFromCache(target_ts); - return_frame = this_frame; - cached_frames_.accessed(i); - - break; - - } - } - } - } - - // 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; - } - } - */ - - // If we have no disk cache, we'll need to find this frame ourselves - if (!return_frame) { - int64_t seek_ts = target_ts; - bool still_seeking = false; - - // 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()->native_timestamp() - || target_ts > cached_frames_.last()->native_timestamp() + 2*second_ts_) { - ClearFrameCache(); - - Seek(seek_ts); - if (seek_ts == 0) { - cache_at_zero_ = true; - } - - still_seeking = true; - } - - int ret; - AVPacket* pkt = av_packet_alloc(); - AVFrame* working_frame = av_frame_alloc(); - - while (true) { - // Allocate a new frame - - // Pull from the decoder - ret = GetFrame(pkt, working_frame); - - // Handle any errors that aren't EOF (EOF is handled later on) - if (ret < 0 && ret != AVERROR_EOF) { - FFmpegError(ret); + // Got our frame, allow cache to continue + i->cache_lock()->unlock(); 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)) { + } else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) { - seek_ts = qMax(static_cast(0), seek_ts - second_ts_); - Seek(seek_ts); - if (seek_ts == 0) { - cache_at_zero_ = true; + // Found our instance, allow others to enter the list + list_locker.unlock(); + + if (i->IsWorking()) { + do { + // Allow instance to continue to the next frame + i->cache_wait_cond()->wait(i->cache_lock()); + + // See if the cache now contains this frame, if so we'll exit this loop + if (i->CacheContainsTime(target_ts)) { + return_frame = i->GetFrameFromCache(target_ts); + } else if (!i->IsWorking()) { + // Grab this instance and continue it + working_instance = i; + break; + } + } while (!return_frame); + + if (working_instance != i) { + // We don't unlock if we're continuing this instance ourselves + i->cache_lock()->unlock(); } - continue; - } else { - - still_seeking = false; - + working_instance = i; } - } + break; - if (ret == AVERROR_EOF) { + } else if (i->IsWorking()) { - // Handle an "expected" EOF by using the last frame of our cache - cache_at_eof_ = true; - return_frame = cached_frames_.last(); + // Ignore currently working instances + i->cache_lock()->unlock(); + + } else if (i->CacheIsEmpty()) { + + // Prioritize this cache over others (leaves this instance LOCKED in case we end up using it later) + non_ideal_contenders.prepend(i); } else { - bool working_frame_is_the_one = false; + // De-prioritize this cache (leaves this instance LOCKED in case we end up using it later) + non_ideal_contenders.append(i); - // If this is a valid frame, see if this or the frame before it are the one we need - if (working_frame->pts == target_ts) { - working_frame_is_the_one = true; - } else if (working_frame->pts > target_ts) { - if (cached_frames_.isEmpty() && cache_at_zero_) { - working_frame_is_the_one = true; - } else { - return_frame = cached_frames_.last(); - } - } - - // Whatever it is, keep this frame in memory for the time being just in case - Frame* working_frame_converted = cached_frames_.append(VideoRenderingParams(avstream_->codecpar->width / divider, - avstream_->codecpar->height / divider, - avstream_->time_base, - native_pix_fmt_, - RenderMode::kOffline)); - - working_frame_converted->set_timestamp(Timecode::timestamp_to_time(target_ts, avstream_->time_base)); - working_frame_converted->set_sample_aspect_ratio(av_guess_sample_aspect_ratio(fmt_ctx_, avstream_, nullptr)); - working_frame_converted->set_native_timestamp(working_frame->pts); - - // Convert frame to RGBA for the rest of the pipeline - uint8_t* output_data = reinterpret_cast(working_frame_converted->data()); - int output_linesize = working_frame_converted->width() * PixelFormat::ChannelCount(native_pix_fmt_) * PixelFormat::BytesPerChannel(native_pix_fmt_); - - sws_scale(scale_ctx_, - working_frame->data, - working_frame->linesize, - 0, - avstream_->codecpar->height, - &output_data, - &output_linesize); - - if (working_frame_is_the_one) { - // We found the frame we want - return_frame = working_frame_converted; - } - } - - if (return_frame) { - break; } } - av_packet_free(&pkt); - av_frame_free(&working_frame); + // If we didn't find a suitable contender, grab the first non-suitable and roll with that + if (!return_frame && !working_instance && !non_ideal_contenders.isEmpty()) { + working_instance = non_ideal_contenders.takeFirst(); + } + + // For all instances we left locked but didn't end up using, lock them now + foreach (FFmpegDecoderInstance* unsuitable_instance, non_ideal_contenders) { + unsuitable_instance->cache_lock()->unlock(); + } + } while (!return_frame && !working_instance); + + if (!return_frame && working_instance) { + + // This instance SHOULD remain locked from our earlier loop, making this operation safe + working_instance->SetWorking(true); + + // Retrieve frame + return_frame = working_instance->RetrieveFrame(target_ts, true); + + // Set working to false and wake any threads waiting + working_instance->cache_lock()->lock(); + working_instance->SetWorking(false); + working_instance->cache_wait_cond()->wakeAll(); + working_instance->cache_lock()->unlock(); } // We found the frame, we'll return a copy if (return_frame) { + if (divider != scale_divider_) { + FreeScaler(); + InitScaler(divider); + } + + VideoStream* vs = static_cast(stream().get()); + + // Create frame to return FramePtr copy = Frame::Create(); - copy->set_video_params(return_frame->video_params()); - copy->set_timestamp(return_frame->timestamp()); - copy->set_sample_aspect_ratio(return_frame->sample_aspect_ratio()); + copy->set_video_params(VideoRenderingParams(vs->width() / divider, + vs->height() / divider, + native_pix_fmt_)); + copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_)); + copy->set_sample_aspect_ratio(aspect_ratio_); copy->allocate(); - memcpy(copy->data(), return_frame->data(), copy->allocated_size()); + // Align buffer to data/linesize points that can be passed to sws_scale + uint8_t* input_data[4]; + int input_linesize[4]; + + av_image_fill_arrays(input_data, + input_linesize, + reinterpret_cast(return_frame->data()), + src_pix_fmt_, + vs->width(), + vs->height(), + 1); + + // Convert frame to RGB/A for the rest of the pipeline + uint8_t* output_data = reinterpret_cast(copy->data()); + int output_linesize = copy->width() * PixelFormat::BytesPerPixel(native_pix_fmt_); + + sws_scale(scale_ctx_, + input_data, + input_linesize, + 0, + vs->height(), + &output_data, + &output_linesize); return copy; } @@ -424,7 +329,7 @@ SampleBufferPtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rat return nullptr; } - if (avstream_->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { + if (stream()->type() != Stream::kAudio) { return nullptr; } @@ -450,6 +355,16 @@ void FFmpegDecoder::Close() { QMutexLocker locker(&mutex_); + /* FIXME: Consider methods of clearing an instance (whichever is the least useful) + { + QMutexLocker l(&instance_lock_); + + QList list = instances_.value(stream().get()); + list.removeOne(this); + instances_.insert(stream().get(), list); + } + */ + ClearResources(); clear_timer_.stop(); @@ -488,7 +403,8 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) const char* filename = ba.constData(); // Open file in a format context - error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr); + AVFormatContext* fmt_ctx = nullptr; + error_code = avformat_open_input(&fmt_ctx, filename, nullptr, nullptr); QList streams_that_need_manual_duration; @@ -496,40 +412,40 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) if (error_code == 0) { // Retrieve metadata about the media - avformat_find_stream_info(fmt_ctx_, nullptr); + avformat_find_stream_info(fmt_ctx, nullptr); // Dump it into the Footage object - for (unsigned int i=0;inb_streams;i++) { + for (unsigned int i=0;inb_streams;i++) { - avstream_ = fmt_ctx_->streams[i]; + AVStream* avstream = fmt_ctx->streams[i]; StreamPtr str; - if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // Create a video stream object VideoStreamPtr video_stream = std::make_shared(); - video_stream->set_width(avstream_->codecpar->width); - video_stream->set_height(avstream_->codecpar->height); - video_stream->set_frame_rate(av_guess_frame_rate(fmt_ctx_, avstream_, nullptr)); - video_stream->set_start_time(avstream_->start_time); + video_stream->set_width(avstream->codecpar->width); + video_stream->set_height(avstream->codecpar->height); + video_stream->set_frame_rate(av_guess_frame_rate(fmt_ctx, avstream, nullptr)); + video_stream->set_start_time(avstream->start_time); str = video_stream; - } else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // Create an audio stream object AudioStreamPtr audio_stream = std::make_shared(); - uint64_t channel_layout = avstream_->codecpar->channel_layout; + uint64_t channel_layout = avstream->codecpar->channel_layout; if (!channel_layout) { - channel_layout = static_cast(av_get_default_channel_layout(avstream_->codecpar->channels)); + channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } audio_stream->set_channel_layout(channel_layout); - audio_stream->set_channels(avstream_->codecpar->channels); - audio_stream->set_sample_rate(avstream_->codecpar->sample_rate); + audio_stream->set_channels(avstream->codecpar->channels); + audio_stream->set_sample_rate(avstream->codecpar->sample_rate); str = audio_stream; @@ -539,7 +455,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) str = std::make_shared(); // Set the correct codec type based on FFmpeg's result - switch (avstream_->codecpar->codec_type) { + switch (avstream->codecpar->codec_type) { case AVMEDIA_TYPE_UNKNOWN: str->set_type(Stream::kUnknown); break; @@ -560,12 +476,12 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) } - str->set_index(avstream_->index); - str->set_timebase(avstream_->time_base); - str->set_duration(avstream_->duration); + str->set_index(avstream->index); + str->set_timebase(avstream->time_base); + str->set_duration(avstream->duration); // The container/stream info may not contain a duration, so we'll need to manually retrieve it - if (avstream_->duration == AV_NOPTS_VALUE) { + if (avstream->duration == AV_NOPTS_VALUE) { streams_that_need_manual_duration.append(str.get()); } @@ -593,7 +509,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) av_packet_unref(pkt); // Read packet from file - int ret = av_read_frame(fmt_ctx_, pkt); + int ret = av_read_frame(fmt_ctx, pkt); if (ret < 0) { // Handle errors that aren't EOF (which simply means the file is finished) @@ -622,7 +538,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) } // Free all memory - Close(); + avformat_close_input(&fmt_ctx); return result; } @@ -646,11 +562,6 @@ void FFmpegDecoder::Error(const QString &s) void FFmpegDecoder::Index(const QAtomicInt* cancelled) { - if (!open_) { - qWarning() << "Indexing function tried to run while decoder was closed"; - return; - } - QMutexLocker locker(stream()->index_process_lock()); if (stream()->type() == Stream::kAudio) { @@ -672,29 +583,26 @@ void FFmpegDecoder::Index(const QAtomicInt* cancelled) QString FFmpegDecoder::GetIndexFilename() { - if (!open_) { - qWarning() << "GetIndexFilename tried to run while decoder was closed"; - return QString(); - } - return GetMediaIndexFilename(GetUniqueFileIdentifier(stream()->footage()->filename())) - .append(QString::number(avstream_->index)); + .append(QString::number(stream()->index())); } void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) { // Iterate through each audio frame and extract the PCM data - Seek(0); + QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - uint64_t channel_layout = avstream_->codecpar->channel_layout; + FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index()); + + uint64_t channel_layout = index_instance.stream()->codecpar->channel_layout; if (!channel_layout) { - if (!avstream_->codecpar->channels) { + if (!index_instance.stream()->codecpar->channels) { // No channel data - we can't do anything with this return; } - channel_layout = static_cast(av_get_default_channel_layout(avstream_->codecpar->channels)); + channel_layout = static_cast(av_get_default_channel_layout(index_instance.stream()->codecpar->channels)); } AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); @@ -703,7 +611,7 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) audio_stream->clear_index(); SwrContext* resampler = nullptr; - AVSampleFormat src_sample_fmt = static_cast(avstream_->codecpar->format); + AVSampleFormat src_sample_fmt = static_cast(index_instance.stream()->codecpar->format); AVSampleFormat dst_sample_fmt; // We don't use planar types internally, so if this is a planar format convert it now @@ -711,12 +619,12 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) dst_sample_fmt = av_get_packed_sample_fmt(src_sample_fmt); resampler = swr_alloc_set_opts(nullptr, - static_cast(avstream_->codecpar->channel_layout), + static_cast(index_instance.stream()->codecpar->channel_layout), dst_sample_fmt, - avstream_->codecpar->sample_rate, - static_cast(avstream_->codecpar->channel_layout), + index_instance.stream()->codecpar->sample_rate, + static_cast(index_instance.stream()->codecpar->channel_layout), src_sample_fmt, - avstream_->codecpar->sample_rate, + index_instance.stream()->codecpar->sample_rate, 0, nullptr); @@ -725,7 +633,7 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) dst_sample_fmt = src_sample_fmt; } - AudioRenderingParams wave_params(avstream_->codecpar->sample_rate, + AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate, channel_layout, FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt)); WaveOutput wave_out(GetIndexFilename(), wave_params); @@ -743,7 +651,7 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) break; } - ret = GetFrame(pkt, frame); + ret = index_instance.GetFrame(pkt, frame); if (ret < 0) { @@ -821,11 +729,9 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) av_frame_free(&frame); av_packet_free(&pkt); - - Seek(0); } -int FFmpegDecoder::GetFrame(AVPacket *pkt, AVFrame *frame) +int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame) { bool eof = false; @@ -870,12 +776,34 @@ int FFmpegDecoder::GetFrame(AVPacket *pkt, AVFrame *frame) return ret; } -void FFmpegDecoder::Seek(int64_t timestamp) +QMutex *FFmpegDecoderInstance::cache_lock() +{ + return &cache_lock_; +} + +QWaitCondition *FFmpegDecoderInstance::cache_wait_cond() +{ + return &cache_wait_cond_; +} + +bool FFmpegDecoderInstance::IsWorking() const +{ + return is_working_; +} + +void FFmpegDecoderInstance::SetWorking(bool working) +{ + is_working_ = working; +} + +void FFmpegDecoderInstance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); } +/* 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))); @@ -903,48 +831,409 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) DiskManager::instance()->CreatedFile(save_frame.fileName(), QByteArray()); } -} -/*void FFmpegDecoder::RemoveFirstFromFrameCache() -{ - if (cached_frames_.isEmpty()) { - return; + // 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; + } } - - AVFrame* first = cached_frames_.takeFirst(); - av_frame_free(&first); - cache_at_zero_ = false; } +*/ -void FFmpegDecoder::RemoveLastFromFrameCache() -{ - if (cached_frames_.isEmpty()) { - return; - } - - AVFrame* last = cached_frames_.takeLast(); - av_frame_free(&last); - cache_at_eof_ = false; -}*/ - -void FFmpegDecoder::ClearFrameCache() +void FFmpegDecoderInstance::ClearFrameCache() { cached_frames_.clear(); cache_at_eof_ = false; cache_at_zero_ = false; } +FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool cache_is_locked) +{ + if (!cache_is_locked) { + cache_lock_.lock(); + } + + int64_t seek_ts = target_ts; + bool still_seeking = false; + + cache_target_time_ = target_ts; + + // If the frame wasn't in the frame cache, see if this frame cache is too old to use + if (!CacheCouldContainTime(target_ts)) { + ClearFrameCache(); + + Seek(seek_ts); + if (seek_ts == 0) { + cache_at_zero_ = true; + } + + still_seeking = true; + } + + int ret; + AVPacket* pkt = av_packet_alloc(); + FFmpegFramePool::ElementPtr return_frame = nullptr; + + // Allocate a new frame + AVFrameWrapper working_frame; + + while (true) { + + // Pull from the decoder + ret = GetFrame(pkt, working_frame.frame()); + + // Handle any errors that aren't EOF (EOF is handled later on) + if (ret < 0 && ret != AVERROR_EOF) { + cache_lock_.unlock(); + 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.frame()->pts > target_ts)) { + + seek_ts = qMax(static_cast(0), seek_ts - second_ts_); + Seek(seek_ts); + if (seek_ts == 0) { + cache_at_zero_ = true; + } + continue; + + } else { + + still_seeking = false; + + } + } + + if (cache_is_locked) { + cache_is_locked = false; + } else { + cache_lock_.lock(); + } + + if (ret == AVERROR_EOF) { + + // Handle an "expected" EOF by using the last frame of our cache + cache_at_eof_ = true; + + cache_wait_cond_.wakeAll(); + cache_lock_.unlock(); + + return_frame = cached_frames_.last(); + break; + + } else { + + // Whatever it is, keep this frame in memory for the time being just in case + FFmpegFramePool::ElementPtr cached = frame_pool_.Get(working_frame.frame()); + Q_ASSERT(cached); + + // Set timestamp so this frame can be identified later + cached->set_timestamp(working_frame.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); + + cache_wait_cond_.wakeAll(); + cache_lock_.unlock(); + + // If this is a valid frame, see if this or the frame before it are the one we need + if (cached->timestamp() == target_ts) { + 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_packet_free(&pkt); + + return return_frame; +} + void FFmpegDecoder::ClearResources() { + FreeScaler(); + + open_ = false; +} + +void FFmpegDecoder::InitScaler(int divider) +{ + VideoStream* vs = static_cast(stream().get()); + + scale_ctx_ = sws_getContext(vs->width(), + vs->height(), + src_pix_fmt_, + vs->width() / divider, + vs->height() / divider, + ideal_pix_fmt_, + SWS_FAST_BILINEAR, + nullptr, + nullptr, + nullptr); + + if (scale_ctx_) { + scale_divider_ = divider; + } else { + scale_divider_ = 0; + } +} + +void FFmpegDecoder::FreeScaler() +{ + if (scale_ctx_) { + sws_freeContext(scale_ctx_); + scale_ctx_ = nullptr; + + scale_divider_ = 0; + } +} + +int64_t FFmpegDecoderInstance::RangeStart() const +{ + if (cached_frames_.isEmpty()) { + return AV_NOPTS_VALUE; + } + return cached_frames_.first()->timestamp(); +} + +int64_t FFmpegDecoderInstance::RangeEnd() const +{ + if (cached_frames_.isEmpty()) { + return AV_NOPTS_VALUE; + } + return cached_frames_.last()->timestamp(); +} + +bool FFmpegDecoderInstance::CacheContainsTime(const int64_t &t) const +{ + return !cached_frames_.isEmpty() + && ((RangeStart() <= t && RangeEnd() >= t) + || (cache_at_zero_ && t < cached_frames_.first()->timestamp()) + || (cache_at_eof_ && t > cached_frames_.last()->timestamp())); +} + +bool FFmpegDecoderInstance::CacheWillContainTime(const int64_t &t) const +{ + return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= cache_target_time_; +} + +bool FFmpegDecoderInstance::CacheCouldContainTime(const int64_t &t) const +{ + return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= (cache_target_time_ + 2*second_ts_); +} + +bool FFmpegDecoderInstance::CacheIsEmpty() const +{ + return cached_frames_.isEmpty(); +} + +FFmpegFramePool::ElementPtr FFmpegDecoderInstance::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 FFmpegDecoderInstance::RemoveFramesBefore(const qint64 &t) +{ + // We keep one frame in memory as an identifier for what pts the decoder is up to + while (cached_frames_.size() > 1 && cached_frames_.first()->last_accessed() < t) { + cached_frames_.removeFirst(); + cache_at_zero_ = false; + } +} + +rational FFmpegDecoderInstance::sample_aspect_ratio() const +{ + return av_guess_sample_aspect_ratio(fmt_ctx_, avstream_, nullptr); +} + +AVStream *FFmpegDecoderInstance::stream() const +{ + return avstream_; +} + +FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) : + fmt_ctx_(nullptr), + opts_(nullptr), + is_working_(false), + cache_at_zero_(false), + cache_at_eof_(false) +{ + // 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) { + qDebug() << "Failed to open input:" << filename << error_code; + ClearResources(); + return; + } + + // Get stream information from format + error_code = avformat_find_stream_info(fmt_ctx_, nullptr); + + // Handle get stream information error + if (error_code < 0) { + qDebug() << "Failed to find stream info:" << error_code; + ClearResources(); + return; + } + + // 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; + ClearResources(); + return; + } + + // Allocate context for the decoder + codec_ctx_ = avcodec_alloc_context3(codec); + if (codec_ctx_ == nullptr) { + qCritical() << "Failed to allocate codec context"; + ClearResources(); + return; + } + + // 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"; + ClearResources(); + return; + } + + // 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) { + qDebug() << "Failed to open codec" << codec->id << error_code; + ClearResources(); + return; + } + + // Create frame pool + if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + + frame_pool_.SetParams(avstream_->codecpar->width, + avstream_->codecpar->height, + static_cast(avstream_->codecpar->format)); + + if (!frame_pool_.Allocate(64)) { + qDebug() << "Failed to allocate frame pool"; + ClearResources(); + return; + } + + } + + // Store one second in the source's timebase + second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base))); +} + +FFmpegDecoderInstance::~FFmpegDecoderInstance() +{ + ClearResources(); +} + +bool FFmpegDecoderInstance::IsValid() const +{ + return codec_ctx_; +} + +void FFmpegDecoderInstance::ClearResources() +{ + ClearFrameCache(); + + frame_pool_.Destroy(); + if (opts_) { av_dict_free(&opts_); opts_ = nullptr; } - ClearFrameCache(); - - FreeScaler(); - if (codec_ctx_) { avcodec_free_context(&codec_ctx_); codec_ctx_ = nullptr; @@ -954,45 +1243,13 @@ void FFmpegDecoder::ClearResources() avformat_close_input(&fmt_ctx_); fmt_ctx_ = nullptr; } - - open_ = false; -} - -void FFmpegDecoder::SetupScaler(const int ÷r) -{ - scale_ctx_ = sws_getContext(avstream_->codecpar->width, - avstream_->codecpar->height, - static_cast(avstream_->codecpar->format), - avstream_->codecpar->width / divider, - avstream_->codecpar->height / divider, - ideal_pix_fmt_, - SWS_FAST_BILINEAR, - nullptr, - nullptr, - nullptr); - - if (!scale_ctx_) { - Error(QStringLiteral("Failed to allocate SwsContext")); - } else { - scale_divider_ = divider; - } -} - -void FFmpegDecoder::FreeScaler() -{ - if (scale_ctx_) { - sws_freeContext(scale_ctx_); - scale_ctx_ = nullptr; - scale_divider_ = -1; - } } void FFmpegDecoder::ClearTimerEvent() { - QMutexLocker locker(&mutex_); - - cache_at_zero_ = false; - cached_frames_.remove_old_frames(QDateTime::currentMSecsSinceEpoch() - clear_timer_.interval()); + our_instance_->cache_lock()->lock(); + our_instance_->RemoveFramesBefore(QDateTime::currentMSecsSinceEpoch() - kMaxFrameLife); + our_instance_->cache_lock()->unlock(); } OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 8213426c1..a7939aa33 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -30,15 +30,84 @@ extern "C" { #include #include #include +#include #include "audio/sampleformat.h" +#include "avframeptr.h" #include "codec/decoder.h" #include "codec/waveoutput.h" -#include "ffmpegframecache.h" +#include "ffmpegframepool.h" #include "project/item/footage/videostream.h" OLIVE_NAMESPACE_ENTER +class FFmpegDecoderInstance { +public: + FFmpegDecoderInstance(const char* filename, int stream_index); + virtual ~FFmpegDecoderInstance(); + + DISABLE_COPY_MOVE(FFmpegDecoderInstance) + + bool IsValid() const; + + int64_t RangeStart() const; + int64_t RangeEnd() const; + bool CacheContainsTime(const int64_t& t) const; + bool CacheWillContainTime(const int64_t& t) const; + bool CacheCouldContainTime(const int64_t& t) const; + bool CacheIsEmpty() const; + FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const; + + void RemoveFramesBefore(const qint64& t); + + rational sample_aspect_ratio() const; + AVStream* stream() const; + + void ClearFrameCache(); + + FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, bool cache_is_locked); + + /** + * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) + * + * @return + * + * An FFmpeg error code, or >= 0 on success + */ + int GetFrame(AVPacket* pkt, AVFrame* frame); + + QMutex* cache_lock(); + QWaitCondition* cache_wait_cond(); + + bool IsWorking() const; + void SetWorking(bool working); + +private: + void ClearResources(); + + void Seek(int64_t timestamp); + + AVFormatContext* fmt_ctx_; + AVCodecContext* codec_ctx_; + AVStream* avstream_; + AVDictionary* opts_; + + int64_t second_ts_; + + QWaitCondition cache_wait_cond_; + QMutex cache_lock_; + QList cached_frames_; + FFmpegFramePool frame_pool_; + + int64_t cache_target_time_; + + bool is_working_; + + bool cache_at_zero_; + bool cache_at_eof_; + +}; + /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder */ @@ -87,50 +156,34 @@ private: */ void FFmpegError(int error_code); - /** - * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_) - * - * @return - * - * An FFmpeg error code, or >= 0 on success - */ - int GetFrame(AVPacket* pkt, AVFrame* frame); - virtual QString GetIndexFilename() override; void UnconditionalAudioIndex(const QAtomicInt* cancelled); - void Seek(int64_t timestamp); - - void CacheFrameToDisk(AVFrame* f); - - void ClearFrameCache(); - void ClearResources(); - void SetupScaler(const int& divider); + void InitScaler(int divider); void FreeScaler(); - AVFormatContext* fmt_ctx_; - AVCodecContext* codec_ctx_; - AVStream* avstream_; - - AVPixelFormat ideal_pix_fmt_; - PixelFormat::Format native_pix_fmt_; - SwsContext* scale_ctx_; int scale_divider_; + AVPixelFormat src_pix_fmt_; + AVPixelFormat ideal_pix_fmt_; + PixelFormat::Format native_pix_fmt_; - FFmpegFrameCache::Client cached_frames_; - bool cache_at_zero_; - bool cache_at_eof_; - - int64_t second_ts_; - - AVDictionary* opts_; + rational time_base_; + rational aspect_ratio_; + int64_t start_time_; QTimer clear_timer_; + FFmpegDecoderInstance* our_instance_; + + static QHash< Stream*, QList > instances_; + static QMutex instance_lock_; + + static const int kMaxFrameLife; + private slots: void ClearTimerEvent(); diff --git a/app/codec/ffmpeg/ffmpegframecache.cpp b/app/codec/ffmpeg/ffmpegframecache.cpp deleted file mode 100644 index 6f0a0313b..000000000 --- a/app/codec/ffmpeg/ffmpegframecache.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 "ffmpegframecache.h" - -#include -#include - -OLIVE_NAMESPACE_ENTER - -QMutex FFmpegFrameCache::pool_lock_; -QList FFmpegFrameCache::frame_pool_; - -Frame *FFmpegFrameCache::Client::append(const VideoRenderingParams& params) -{ - Frame* f = FFmpegFrameCache::Get(params); - - frames_.append({f, QDateTime::currentMSecsSinceEpoch()}); - - return f; -} - -void FFmpegFrameCache::Client::clear() -{ - foreach (const CachedFrame& cf, frames_) { - FFmpegFrameCache::Release(cf.frame); - } - frames_.clear(); -} - -bool FFmpegFrameCache::Client::isEmpty() const -{ - return frames_.isEmpty(); -} - -Frame *FFmpegFrameCache::Client::first() const -{ - return frames_.first().frame; -} - -Frame *FFmpegFrameCache::Client::at(int i) const -{ - return frames_.at(i).frame; -} - -Frame *FFmpegFrameCache::Client::last() const -{ - return frames_.last().frame; -} - -int FFmpegFrameCache::Client::size() const -{ - return frames_.size(); -} - -void FFmpegFrameCache::Client::accessedFirst() -{ - frames_.first().accessed = QDateTime::currentMSecsSinceEpoch(); -} - -void FFmpegFrameCache::Client::accessedLast() -{ - frames_.last().accessed = QDateTime::currentMSecsSinceEpoch(); -} - -void FFmpegFrameCache::Client::accessed(int i) -{ - frames_[i].accessed = QDateTime::currentMSecsSinceEpoch(); -} - -void FFmpegFrameCache::Client::remove_old_frames(qint64 older_than) -{ - while (!frames_.isEmpty() && frames_.first().accessed < older_than) { - FFmpegFrameCache::Release(frames_.takeFirst().frame); - } -} - -Frame* FFmpegFrameCache::Get(const VideoRenderingParams ¶ms) -{ - QMutexLocker locker(&pool_lock_); - - // See if we have a frame matching this description in the pool - for (int i=0;iwidth() == params.width() - && frame_pool_.at(i)->height() == params.height() - && frame_pool_.at(i)->format() == params.format()) { - return frame_pool_.takeAt(i); - } - } - - // Otherwise we'll need to create one - Frame* f = new Frame(); - f->set_video_params(params); - f->allocate(); - - return f; -} - -void FFmpegFrameCache::Release(Frame *f) -{ - QMutexLocker locker(&pool_lock_); - - frame_pool_.append(f); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegframecache.h b/app/codec/ffmpeg/ffmpegframecache.h deleted file mode 100644 index 20d75a5f9..000000000 --- a/app/codec/ffmpeg/ffmpegframecache.h +++ /dev/null @@ -1,80 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 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 . - -***/ - -#ifndef FFMPEGFRAMECACHE_H -#define FFMPEGFRAMECACHE_H - -#include -#include - -#include "codec/frame.h" -#include "render/videoparams.h" - -OLIVE_NAMESPACE_ENTER - -class FFmpegFrameCache -{ -public: - FFmpegFrameCache() = default; - - static Frame* Get(const VideoRenderingParams& params); - - static void Release(Frame* f); - - class Client - { - public: - Client() = default; - - Frame* append(const VideoRenderingParams ¶ms); - void clear(); - - bool isEmpty() const; - Frame* first() const; - Frame* at(int i) const; - Frame* last() const; - int size() const; - - void accessedFirst(); - void accessedLast(); - void accessed(int i); - - void remove_old_frames(qint64 older_than); - - private: - struct CachedFrame { - Frame* frame; - qint64 accessed; - }; - - QList frames_; - - }; - -private: - static QMutex pool_lock_; - - static QList frame_pool_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // FFMPEGFRAMECACHE_H diff --git a/app/codec/ffmpeg/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp new file mode 100644 index 000000000..b3b99cb6f --- /dev/null +++ b/app/codec/ffmpeg/ffmpegframepool.cpp @@ -0,0 +1,95 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 "ffmpegframepool.h" + +extern "C" { +#include +} + +OLIVE_NAMESPACE_ENTER + +FFmpegFramePool::FFmpegFramePool() : + width_(0), + height_(0), + format_(AV_PIX_FMT_NONE) +{ +} + +FFmpegFramePool::ElementPtr FFmpegFramePool::Get(AVFrame *copy) +{ + ElementPtr ele = MemoryPool::Get(); + + if (ele) { + av_image_copy_to_buffer(ele->data(), + GetElementSize(), + copy->data, + copy->linesize, + format_, + width_, + height_, + 1); + } + + return ele; +} + +void FFmpegFramePool::SetParams(int width, int height, AVPixelFormat format) +{ + int old_nb_elements; + + if (IsAllocated()) { + old_nb_elements = GetElementCount(); + + Destroy(); + } else { + old_nb_elements = 0; + } + + width_ = width; + height_ = height; + format_ = format; + + if (old_nb_elements) { + // Re-allocate automatically + Allocate(old_nb_elements); + } +} + +size_t FFmpegFramePool::GetElementSize() +{ + if (width_ == 0 || height_ == 0 || format_ == AV_PIX_FMT_NONE) { + return 0; + } + + int buf_sz = av_image_get_buffer_size(static_cast(format_), + width_, + height_, + 1); + + if (buf_sz < 0) { + qDebug() << "Failed to find buffer size:" << buf_sz; + return 0; + } + + return buf_sz; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h new file mode 100644 index 000000000..1239c20c1 --- /dev/null +++ b/app/codec/ffmpeg/ffmpegframepool.h @@ -0,0 +1,53 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef FFMPEGFRAMEPOOL_H +#define FFMPEGFRAMEPOOL_H + +#include "common/memorypool.h" +#include "render/pixelformat.h" +#include "render/videoparams.h" + +OLIVE_NAMESPACE_ENTER + +class FFmpegFramePool : public MemoryPool +{ +public: + FFmpegFramePool(); + + ElementPtr Get(AVFrame* copy); + + void SetParams(int width, int height, AVPixelFormat format); + +protected: + virtual size_t GetElementSize() override; + +private: + int width_; + + int height_; + + AVPixelFormat format_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // FFMPEGFRAMEPOOL_H diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h index 0a68c533f..83a2e4d9c 100644 --- a/app/codec/samplebuffer.h +++ b/app/codec/samplebuffer.h @@ -23,7 +23,6 @@ #include -#include "common/constructors.h" #include "render/audioparams.h" OLIVE_NAMESPACE_ENTER diff --git a/app/codec/waveinput.h b/app/codec/waveinput.h index bd4e7e5d8..606c1bfa7 100644 --- a/app/codec/waveinput.h +++ b/app/codec/waveinput.h @@ -23,7 +23,6 @@ #include -#include "common/constructors.h" #include "render/audioparams.h" OLIVE_NAMESPACE_ENTER diff --git a/app/codec/waveoutput.h b/app/codec/waveoutput.h index 71eb1cd8e..c3679079a 100644 --- a/app/codec/waveoutput.h +++ b/app/codec/waveoutput.h @@ -25,7 +25,6 @@ #include #include "audio/sampleformat.h" -#include "common/constructors.h" #include "render/audioparams.h" OLIVE_NAMESPACE_ENTER diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index e08078af0..2b1d61727 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -21,7 +21,6 @@ set(OLIVE_SOURCES common/cancelableobject.h common/channellayout.h common/clamp.h - common/constructors.h common/crashhandler.h common/crashhandler.cpp common/debug.h @@ -33,6 +32,7 @@ set(OLIVE_SOURCES common/flipmodifiers.cpp common/functiontimer.h common/lerp.h + common/memorypool.h common/qtutils.h common/qtutils.cpp common/range.h diff --git a/app/common/define.h b/app/common/define.h index 294388c17..405f34615 100644 --- a/app/common/define.h +++ b/app/common/define.h @@ -49,4 +49,21 @@ OLIVE_NAMESPACE_EXIT #define OLIVE_NS_ARG(x, y) QArgument(MACRO_VAL_AS_STR(OLIVE_NAMESPACE) "::" #x, y) +/** + * Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we + * use our own functions for portability. + */ + +#define DISABLE_COPY(Class) \ + Class(const Class &) = delete;\ + Class &operator=(const Class &) = delete; + +#define DISABLE_MOVE(Class) \ + Class(Class &&) = delete; \ + Class &operator=(Class &&) = delete; + +#define DISABLE_COPY_MOVE(Class) \ + DISABLE_COPY(Class) \ + DISABLE_MOVE(Class) + #endif // OLIVECOMMONDEFINE_H diff --git a/app/common/memorypool.h b/app/common/memorypool.h new file mode 100644 index 000000000..7b2105f0b --- /dev/null +++ b/app/common/memorypool.h @@ -0,0 +1,165 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 . + +***/ + +#ifndef MEMORYPOOL_H +#define MEMORYPOOL_H + +#include +#include +#include + +#include + +#include "common/define.h" + +OLIVE_NAMESPACE_ENTER + +template +class MemoryPool +{ +public: + MemoryPool() { + data_ = nullptr; + } + + ~MemoryPool() { + delete [] data_; + } + + DISABLE_COPY_MOVE(MemoryPool) + + bool Allocate(int nb_elements) { + delete [] data_; + + size_t ele_sz = GetElementSize(); + + if (!ele_sz) { + return false; + } + + if ((data_ = new char[ele_sz * nb_elements])) { + available_.resize(nb_elements); + available_.fill(true); + + return true; + } else { + available_.clear(); + + return false; + } + } + + void Destroy() { + delete [] data_; + data_ = nullptr; + + available_.clear(); + } + + inline bool IsAllocated() const { + return data_; + } + + inline int GetElementCount() const { + return available_.size(); + } + + class Element { + public: + Element(MemoryPool* parent, T* data) { + parent_ = parent; + data_ = data; + accessed_ = QDateTime::currentMSecsSinceEpoch(); + } + + ~Element() { + parent_->Release(this); + } + + inline T* data() const { + return data_; + } + + inline const int64_t& timestamp() const { + return timestamp_; + } + + inline void set_timestamp(const int64_t& timestamp) { + timestamp_ = timestamp; + } + + inline void access() { + accessed_ = QDateTime::currentMSecsSinceEpoch(); + } + + inline const int64_t& last_accessed() const { + return accessed_; + } + + private: + MemoryPool* parent_; + + T* data_; + + int64_t timestamp_; + + int64_t accessed_; + + }; + + using ElementPtr = std::shared_ptr; + + ElementPtr Get() { + for (int i=0;i(this, reinterpret_cast(data_ + i * GetElementSize())); + } + } + + // FIXME: Allocate a new "arena" + return nullptr; + } + + void Release(Element* e) { + quintptr diff = reinterpret_cast(e->data()) - reinterpret_cast(data_); + + int index = diff / GetElementSize(); + + available_.replace(index, true); + } + +protected: + virtual size_t GetElementSize() { + return sizeof(T); + } + +private: + char* data_; + + QVector available_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // MEMORYPOOL_H diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index db5fcade0..a3a026508 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -24,7 +24,6 @@ #include #include -#include "common/constructors.h" #include "common/rational.h" #include "project/item/item.h" #include "project/item/footage/audiostream.h" diff --git a/app/project/item/item.h b/app/project/item/item.h index ed2e7cbde..03739d0f8 100644 --- a/app/project/item/item.h +++ b/app/project/item/item.h @@ -28,7 +28,6 @@ #include #include -#include "common/constructors.h" #include "common/threadedobject.h" #include "common/xmlutils.h" #include "node/param.h" diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index 92705500e..60923cf89 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -46,7 +46,7 @@ bool AudioBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); } diff --git a/app/render/backend/audio/audioworker.cpp b/app/render/backend/audio/audioworker.cpp index 807bec957..bdaf4d6b9 100644 --- a/app/render/backend/audio/audioworker.cpp +++ b/app/render/backend/audio/audioworker.cpp @@ -22,8 +22,8 @@ OLIVE_NAMESPACE_ENTER -AudioWorker::AudioWorker(DecoderCache* decoder_cache, QHash *copy_map, QObject *parent) : - AudioRenderWorker(decoder_cache, copy_map, parent) +AudioWorker::AudioWorker(QHash *copy_map, QObject *parent) : + AudioRenderWorker(copy_map, parent) { } diff --git a/app/render/backend/audio/audioworker.h b/app/render/backend/audio/audioworker.h index 269b8547f..db49a1902 100644 --- a/app/render/backend/audio/audioworker.h +++ b/app/render/backend/audio/audioworker.h @@ -28,7 +28,7 @@ OLIVE_NAMESPACE_ENTER class AudioWorker : public AudioRenderWorker { public: - AudioWorker(DecoderCache* decoder_cache, QHash* copy_map, QObject* parent = nullptr); + AudioWorker(QHash* copy_map, QObject* parent = nullptr); protected: virtual void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) override; diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp index aaf68905a..b60417c37 100644 --- a/app/render/backend/audiorenderworker.cpp +++ b/app/render/backend/audiorenderworker.cpp @@ -30,8 +30,8 @@ OLIVE_NAMESPACE_ENTER -AudioRenderWorker::AudioRenderWorker(DecoderCache* decoder_cache, QHash *copy_map, QObject *parent) : - RenderWorker(decoder_cache, parent), +AudioRenderWorker::AudioRenderWorker(QHash *copy_map, QObject *parent) : + RenderWorker(parent), copy_map_(copy_map) { } diff --git a/app/render/backend/audiorenderworker.h b/app/render/backend/audiorenderworker.h index a8614b685..7da959984 100644 --- a/app/render/backend/audiorenderworker.h +++ b/app/render/backend/audiorenderworker.h @@ -29,7 +29,7 @@ class AudioRenderWorker : public RenderWorker { Q_OBJECT public: - AudioRenderWorker(DecoderCache* decoder_cache, QHash* copy_map, QObject* parent = nullptr); + AudioRenderWorker(QHash* copy_map, QObject* parent = nullptr); void SetParameters(const AudioRenderingParams& audio_params); diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index df6cd32cb..2db0cefa6 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -63,7 +63,7 @@ bool OpenGLBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); diff --git a/app/render/backend/opengl/openglframebuffer.h b/app/render/backend/opengl/openglframebuffer.h index 532dd8048..e7d8de9b4 100644 --- a/app/render/backend/opengl/openglframebuffer.h +++ b/app/render/backend/opengl/openglframebuffer.h @@ -23,7 +23,6 @@ #include -#include "common/constructors.h" #include "opengltexture.h" OLIVE_NAMESPACE_ENTER diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index d3510db4a..04f6e7d43 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -67,7 +67,7 @@ bool OpenGLProxy::Init() return true; } -void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) +void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const TimeRange &range, NodeValueTable* table) { // Ensure stream is video or image type if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) { @@ -105,13 +105,6 @@ void OpenGLProxy::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeR ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(video_params_.mode()); - FramePtr frame = decoder->RetrieveVideo(range.in(), video_params_.divider()); - - if (!frame) { - // Nothing to be done - return; - } - // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU if (ocio_method == ColorManager::kOCIOAccurate) { bool has_alpha = PixelFormat::FormatHasAlphaChannel(frame->format()); diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h index 77ee7a89b..1203b9330 100644 --- a/app/render/backend/opengl/openglproxy.h +++ b/app/render/backend/opengl/openglproxy.h @@ -63,7 +63,7 @@ public: void Close(); - void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table); + void FrameToValue(FramePtr frame, StreamPtr stream, const TimeRange &range, NodeValueTable* table); void RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params); diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h index 1b5562de2..b637b998c 100644 --- a/app/render/backend/opengl/opengltexture.h +++ b/app/render/backend/opengl/opengltexture.h @@ -25,7 +25,6 @@ #include #include "codec/frame.h" -#include "common/constructors.h" #include "render/pixelformat.h" OLIVE_NAMESPACE_ENTER diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 3508ab3ed..b141cb3b0 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -31,14 +31,18 @@ OLIVE_NAMESPACE_ENTER -OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, DecoderCache* decoder_cache, QObject *parent) : - VideoRenderWorker(frame_cache, decoder_cache, parent) +OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, QObject *parent) : + VideoRenderWorker(frame_cache, parent) { } void OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable *table) { - emit RequestFrameToValue(decoder, stream, range, table); + FramePtr frame = decoder->RetrieveVideo(range.in(), video_params().divider()); + + if (frame) { + emit RequestFrameToValue(frame, stream, range, table); + } } void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable *output_params) diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 1fc920523..1ec1846e9 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -34,11 +34,11 @@ OLIVE_NAMESPACE_ENTER class OpenGLWorker : public VideoRenderWorker { Q_OBJECT public: - OpenGLWorker(VideoRenderFrameCache* frame_cache, DecoderCache *decoder_cache, + OpenGLWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); signals: - void RequestFrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table); + void RequestFrameToValue(FramePtr frame, StreamPtr stream, const TimeRange &range, NodeValueTable* table); void RequestRunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params); diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 48b2fd66a..0cfb0eae4 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -105,8 +105,6 @@ void RenderBackend::Close() threads_.clear(); processors_.clear(); - - decoder_cache_.Clear(); } const QString &RenderBackend::GetError() const @@ -404,11 +402,6 @@ void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy) processor_busy_state_.replace(processors_.indexOf(worker), busy); } -DecoderCache *RenderBackend::decoder_cache() -{ - return &decoder_cache_; -} - bool RenderBackend::AllProcessorsAreAvailable() const { foreach (bool busy, processor_busy_state_) { diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 0beb63700..7b1b120fb 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -23,7 +23,6 @@ #include -#include "common/constructors.h" #include "dialog/rendercancel/rendercancel.h" #include "decodercache.h" #include "node/graph.h" @@ -118,14 +117,10 @@ protected: bool WorkerIsBusy(RenderWorker* worker) const; void SetWorkerBusyState(RenderWorker* worker, bool busy); - DecoderCache* decoder_cache(); - TimeRangeList cache_queue_; QVector processors_; - DecoderCache decoder_cache_; - bool compiled_; QHash render_job_info_; diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 07fee972a..58b14aef2 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -26,10 +26,9 @@ OLIVE_NAMESPACE_ENTER -RenderWorker::RenderWorker(DecoderCache *decoder_cache, QObject *parent) : +RenderWorker::RenderWorker(QObject *parent) : QObject(parent), - started_(false), - decoder_cache_(decoder_cache) + started_(false) { } @@ -50,6 +49,8 @@ void RenderWorker::Close() { CloseInternal(); + decoder_cache_.Clear(); + started_ = false; } @@ -82,11 +83,9 @@ StreamPtr RenderWorker::ResolveStreamFromInput(NodeInput *input) DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) { - QMutexLocker locker(decoder_cache_->lock()); - // Access a map of Node inputs and decoder instances and retrieve a frame! - DecoderPtr decoder = decoder_cache_->Get(stream.get()); + DecoderPtr decoder = decoder_cache_.Get(stream.get()); if (!decoder && stream) { // Create a new Decoder here @@ -94,7 +93,7 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) decoder->set_stream(stream); if (decoder->Open()) { - decoder_cache_->Add(stream.get(), decoder); + decoder_cache_.Add(stream.get(), decoder); } else { decoder = nullptr; qWarning() << "Failed to open decoder for" << stream->footage()->filename() << "::" << stream->index(); diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index 1125015a2..9e85da223 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -23,7 +23,6 @@ #include -#include "common/constructors.h" #include "decodercache.h" #include "node/node.h" #include "node/output/track/track.h" @@ -35,7 +34,7 @@ class RenderWorker : public QObject, public NodeTraverser { Q_OBJECT public: - RenderWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + RenderWorker(QObject* parent = nullptr); bool Init(); @@ -77,7 +76,7 @@ protected: private: bool started_; - DecoderCache* decoder_cache_; + DecoderCache decoder_cache_; NodeDependency path_; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index a1f6485be..c98649789 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -40,7 +40,8 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) : RenderBackend(parent), operating_mode_(VideoRenderWorker::kHashRenderCache), only_signal_last_frame_requested_(true), - limit_caching_(true) + limit_caching_(true), + pop_toggle_(false) { connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &VideoRenderBackend::FrameRemovedFromDiskCache); } @@ -164,7 +165,7 @@ VideoRenderFrameCache *VideoRenderBackend::frame_cache() QString VideoRenderBackend::GetCachedFrame(const rational &time) { - last_time_requested_ = time; + UpdateLastRequestedTime(time); if (viewer_node() == nullptr) { // Nothing is connected - nothing to show or render @@ -181,8 +182,6 @@ QString VideoRenderBackend::GetCachedFrame(const rational &time) return nullptr; } - Requeue(); - // Find frame in map QByteArray frame_hash = frame_cache_.TimeToHash(time); @@ -195,6 +194,13 @@ QString VideoRenderBackend::GetCachedFrame(const rational &time) return QString(); } +void VideoRenderBackend::UpdateLastRequestedTime(const rational &time) +{ + last_time_requested_ = time; + + Requeue(); +} + NodeInput *VideoRenderBackend::GetDependentInput() { return viewer_node()->texture_input(); @@ -208,44 +214,35 @@ bool VideoRenderBackend::CanRender() TimeRange VideoRenderBackend::PopNextFrameFromQueue() { // Try to find the frame that's closest to the last time requested (the playhead) + rational earliest_allowed_time = (pop_toggle_) ? 0 : last_time_requested_; + pop_toggle_ = !pop_toggle_; // Set up playhead frame range to see if the queue contains this frame precisely - TimeRange test_range(last_time_requested_, last_time_requested_ + params_.time_base()); + TimeRange test_range(earliest_allowed_time, earliest_allowed_time + params_.time_base()); // Use this variable to find the closest frame in the range - rational closest_time = -1; + rational closest_time = RATIONAL_MAX; foreach (const TimeRange& range_here, cache_queue_) { if (range_here.OverlapsWith(test_range, false, false)) { - closest_time = -1; + closest_time = RATIONAL_MAX; break; } - for (int j=0;j<2;j++) { - rational compare; + if (range_here.in() >= earliest_allowed_time) { + rational frame_here = Timecode::snap_time_to_timebase(range_here.in(), params_.time_base()); - if (j == 0) { - compare = Timecode::snap_time_to_timebase(range_here.in(), params_.time_base()); - if (compare > range_here.in()) { - compare -= params_.time_base(); - } - } else { - compare = Timecode::snap_time_to_timebase(range_here.out(), params_.time_base()); - if (compare >= range_here.out()) { - compare -= params_.time_base(); - } + if (frame_here > range_here.in()) { + frame_here = qMax(rational(), frame_here - params_.time_base()); } - if (closest_time < 0 - || qAbs(compare - last_time_requested_) < qAbs(closest_time - last_time_requested_)) { - closest_time = compare; - } + closest_time = qMin(closest_time, frame_here); } } TimeRange frame_range; - if (closest_time == -1) { + if (closest_time == RATIONAL_MAX) { frame_range = test_range; } else { frame_range = TimeRange(closest_time, closest_time + params_.time_base()); diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 522e61ced..a73d46df2 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -66,6 +66,8 @@ public: QString GetCachedFrame(const rational& time); + void UpdateLastRequestedTime(const rational& time); + VideoRenderFrameCache* frame_cache(); const VideoRenderingParams& params() const; @@ -129,6 +131,8 @@ private: bool limit_caching_; + bool pop_toggle_; + private slots: void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed); void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 6061975a4..492022824 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -34,8 +34,8 @@ OLIVE_NAMESPACE_ENTER -VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, DecoderCache* decoder_cache, QObject *parent) : - RenderWorker(decoder_cache, parent), +VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) : + RenderWorker(parent), frame_cache_(frame_cache), operating_mode_(kHashRenderCache) { diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 3d7b5e6b7..4a69f060c 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -63,7 +63,7 @@ public: kHashRenderCache = 0x7 }; - VideoRenderWorker(VideoRenderFrameCache* frame_cache, DecoderCache *decoder_cache, QObject* parent = nullptr); + VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); void SetParameters(const VideoRenderingParams& video_params); diff --git a/app/render/colorprocessor.h b/app/render/colorprocessor.h index 3cbde81c8..abc7129d5 100644 --- a/app/render/colorprocessor.h +++ b/app/render/colorprocessor.h @@ -25,7 +25,6 @@ namespace OCIO = OCIO_NAMESPACE::v1; #include "codec/frame.h" -#include "common/constructors.h" #include "render/color.h" OLIVE_NAMESPACE_ENTER diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index 6be547d10..2350f5f19 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -42,9 +42,7 @@ void ConformTask::Action() connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged); - decoder->Open(); decoder->Conform(params_, &IsCancelled()); - decoder->Close(); emit Succeeded(); } diff --git a/app/task/index/index.cpp b/app/task/index/index.cpp index 248901a73..0fb6393b7 100644 --- a/app/task/index/index.cpp +++ b/app/task/index/index.cpp @@ -42,9 +42,7 @@ void IndexTask::Action() connect(decoder.get(), &Decoder::IndexProgress, this, &IndexTask::ProgressChanged); - decoder->Open(); decoder->Index(&IsCancelled()); - decoder->Close(); emit Succeeded(); } diff --git a/app/task/taskmanager.h b/app/task/taskmanager.h index 0095b81fc..5adcfc0a7 100644 --- a/app/task/taskmanager.h +++ b/app/task/taskmanager.h @@ -24,7 +24,6 @@ #include #include -#include "common/constructors.h" #include "task/task.h" OLIVE_NAMESPACE_ENTER diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d8bde137b..442691e08 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -320,6 +320,7 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) { if (!GetConnectedNode() || time >= GetConnectedNode()->Length()) { main_gl_widget()->SetImage(QString()); + video_renderer_->UpdateLastRequestedTime(time); } else { QString frame_fn = video_renderer_->GetCachedFrame(time);