diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c655cd2ae..83e82e5b4 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -39,6 +39,7 @@ extern "C" { #include "common/timecodefunctions.h" #include "ffmpegcommon.h" #include "render/diskmanager.h" +#include "render/memorymanager.h" #include "render/pixelservice.h" FFmpegDecoder::FFmpegDecoder() : @@ -48,8 +49,15 @@ FFmpegDecoder::FFmpegDecoder() : cache_at_zero_(false), cache_at_eof_(false), opts_(nullptr), - multithreading_(true) + multithreading_(true), + allow_clear_event_(false) { + connect(this, &FFmpegDecoder::ConsumedMemory, MemoryManager::instance(), &MemoryManager::ConsumedMemory, Qt::DirectConnection); + connect(MemoryManager::instance(), &MemoryManager::FreeMemory, this, &FFmpegDecoder::FreeMemory, Qt::DirectConnection); + + clear_timer_.setInterval(5000); + clear_timer_.setSingleShot(true); + connect(&clear_timer_, &QTimer::timeout, this, &FFmpegDecoder::ClearTimerEvent); } FFmpegDecoder::~FFmpegDecoder() @@ -209,6 +217,9 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) { QMutexLocker locker(&mutex_); + // Reset clear timer + QMetaObject::invokeMethod(this, "RestartClearTimer", Qt::QueuedConnection); + if (!open_) { qWarning() << "Tried to retrieve video on a decoder that's still closed"; return nullptr; @@ -368,7 +379,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) if (working_frame->pts == target_ts) { found_frame = working_frame; } else if (working_frame->pts > target_ts) { - if (cache_at_zero_) { + if (cached_frames_.isEmpty() && cache_at_zero_) { found_frame = working_frame; } else { found_frame = cached_frames_.last(); @@ -376,6 +387,10 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) } } + allow_clear_event_ = true; + emit ConsumedMemory(); + allow_clear_event_ = false; + // Whatever it is, keep this frame in memory for the time being just in case cached_frames_.append(working_frame); } @@ -497,7 +512,7 @@ void FFmpegDecoder::Close() QString FFmpegDecoder::id() { - return "ffmpeg"; + return QStringLiteral("ffmpeg"); } bool FFmpegDecoder::SupportsVideo() @@ -855,60 +870,6 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) Seek(0); } -void FFmpegDecoder::UnconditionalVideoIndex(const QAtomicInt* cancelled) -{ - VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - - Seek(0); - - // This should be unnecessary, but just in case... - video_stream->clear_frame_index(); - - // Iterate through every single frame and get each timestamp - // NOTE: Expects no frames to have been read so far - - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - int ret; - - while (true) { - // Check if we have a `cancelled` ptr and its value - if (cancelled && *cancelled) { - break; - } - - ret = GetFrame(pkt, frame); - - if (ret >= 0) { - //CacheFrameToDisk(frame); - - video_stream->append_frame_index(frame->pts); - - SignalIndexProgress(frame->pts); - } else { - // Assume we've reached the end of the file - break; - } - } - - // Check if we have a `cancelled` ptr and its value - if (cancelled && *cancelled) { - video_stream->clear_frame_index(); - } else { - video_stream->append_frame_index(VideoStream::kEndTimestamp); - - // Save index to file - if (!video_stream->save_frame_index(GetIndexFilename())) { - qWarning() << QStringLiteral("Failed to save index for %1").arg(stream()->footage()->filename()); - } - } - - av_frame_free(&frame); - av_packet_free(&pkt); - - Seek(0); -} - int FFmpegDecoder::GetFrame(AVPacket *pkt, AVFrame *frame) { bool eof = false; @@ -954,24 +915,6 @@ int FFmpegDecoder::GetFrame(AVPacket *pkt, AVFrame *frame) return ret; } -void FFmpegDecoder::ValidateVideoIndex(const QAtomicInt* cancelled) -{ - VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - - if (!video_stream->is_frame_index_ready()) { - video_stream->load_frame_index(GetIndexFilename()); - } - - if (!video_stream->is_frame_index_ready()) { - // Reset state - Seek(0); - - UnconditionalVideoIndex(cancelled); - - Seek(0); - } -} - void FFmpegDecoder::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); @@ -1013,8 +956,8 @@ void FFmpegDecoder::RemoveFirstFromFrameCache() return; } - av_frame_free(&cached_frames_.first()); - cached_frames_.removeFirst(); + AVFrame* first = cached_frames_.takeFirst(); + av_frame_free(&first); cache_at_zero_ = false; } @@ -1024,17 +967,49 @@ void FFmpegDecoder::RemoveLastFromFrameCache() return; } - av_frame_free(&cached_frames_.last()); - cached_frames_.removeLast(); + AVFrame* last = cached_frames_.takeLast(); + av_frame_free(&last); cache_at_eof_ = false; } void FFmpegDecoder::ClearFrameCache() { for (int i=0;i 1) { + RemoveFirstFromFrameCache(); + } + + if (!allow_clear_event_) { + mutex_.unlock(); + } else { + allow_clear_event_ = false; + } +} + +void FFmpegDecoder::ClearTimerEvent() +{ + QMutexLocker locker(&mutex_); + + ClearFrameCache(); +} + +void FFmpegDecoder::RestartClearTimer() +{ + clear_timer_.stop(); + clear_timer_.start(); +} diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 041a59ced..29e19c7f1 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -27,6 +27,8 @@ extern "C" { #include } +#include +#include #include #include "audio/sampleformat.h" @@ -64,6 +66,9 @@ public: virtual void Index(const QAtomicInt *cancelled) override; +signals: + void ConsumedMemory(); + private: /** * @brief Handle an error @@ -96,9 +101,6 @@ private: virtual QString GetIndexFilename() override; void UnconditionalAudioIndex(const QAtomicInt* cancelled); - void UnconditionalVideoIndex(const QAtomicInt* cancelled); - - void ValidateVideoIndex(const QAtomicInt* cancelled); void Seek(int64_t timestamp); @@ -127,6 +129,17 @@ private: bool multithreading_; + QTimer clear_timer_; + + QAtomicInt allow_clear_event_; + +private slots: + void FreeMemory(); + + void ClearTimerEvent(); + + void RestartClearTimer(); + }; #endif // FFMPEGDECODER_H diff --git a/app/core.cpp b/app/core.cpp index ce5cdae06..81c00f6a9 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -50,6 +50,7 @@ #include "render/backend/opengl/opengltexturecache.h" #include "render/colormanager.h" #include "render/diskmanager.h" +#include "render/memorymanager.h" #include "render/pixelservice.h" #include "task/taskmanager.h" #include "ui/style/style.h" @@ -113,6 +114,9 @@ void Core::Start() // Set up the index manager for renderers IndexManager::CreateInstance(); + // Set up memory manager + MemoryManager::CreateInstance(); + // Load application config Config::Load(); @@ -153,6 +157,8 @@ void Core::Stop() IndexManager::DestroyInstance(); + MemoryManager::DestroyInstance(); + delete main_window_; } diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 99225da03..05c2001ae 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -26,6 +26,8 @@ set(OLIVE_SOURCES render/colorprocessor.cpp render/diskmanager.h render/diskmanager.cpp + render/memorymanager.h + render/memorymanager.cpp render/pixelformat.h render/pixelformat.cpp render/pixelservice.h diff --git a/app/render/memorymanager.cpp b/app/render/memorymanager.cpp new file mode 100644 index 000000000..cf059bee3 --- /dev/null +++ b/app/render/memorymanager.cpp @@ -0,0 +1,58 @@ +#include "memorymanager.h" + +#include + +#ifdef Q_OS_WINDOWS +#include +#endif + +MemoryManager* MemoryManager::instance_ = nullptr; + +const uint64_t MemoryManager::minimum_available_memory_ = 2147483648; + +MemoryManager::MemoryManager(QObject* parent) : + QObject(parent) +{ +} + +void MemoryManager::CreateInstance() +{ + instance_ = new MemoryManager(); +} + +MemoryManager *MemoryManager::instance() +{ + return instance_; +} + +void MemoryManager::DestroyInstance() +{ + delete instance_; + instance_ = nullptr; +} + +bool MemoryManager::ShouldFreeMemory() +{ + return (GetAvailableMemory() < minimum_available_memory_); +} + +void MemoryManager::ConsumedMemory() +{ + if (ShouldFreeMemory()) { + emit FreeMemory(); + } +} + +uint64_t MemoryManager::GetAvailableMemory() +{ +#ifdef Q_OS_WINDOWS + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return status.ullAvailPhys; +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + return pages * page_size; +#endif +} diff --git a/app/render/memorymanager.h b/app/render/memorymanager.h new file mode 100644 index 000000000..45270d73f --- /dev/null +++ b/app/render/memorymanager.h @@ -0,0 +1,33 @@ +#ifndef MEMORYMANAGER_H +#define MEMORYMANAGER_H + +#include + +class MemoryManager : public QObject +{ + Q_OBJECT +public: + MemoryManager(QObject* parent = nullptr); + + static void CreateInstance(); + static MemoryManager* instance(); + static void DestroyInstance(); + + static bool ShouldFreeMemory(); + +public slots: + void ConsumedMemory(); + +signals: + void FreeMemory(); + +private: + static uint64_t GetAvailableMemory(); + + static MemoryManager* instance_; + + static const uint64_t minimum_available_memory_; + +}; + +#endif // MEMORYMANAGER_H