diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index 3d75f8a2c..9f3424e38 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -22,5 +22,7 @@ set(OLIVE_SOURCES codec/ffmpeg/ffmpegdecoder.cpp codec/ffmpeg/ffmpegencoder.h codec/ffmpeg/ffmpegencoder.cpp + codec/ffmpeg/ffmpegframecache.h + codec/ffmpeg/ffmpegframecache.cpp PARENT_SCOPE ) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 425a03bfa..095be431f 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -1,4 +1,4 @@ -/*** +/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team @@ -39,7 +39,6 @@ extern "C" { #include "common/timecodefunctions.h" #include "ffmpegcommon.h" #include "render/diskmanager.h" -#include "render/memorymanager.h" #include "render/pixelservice.h" FFmpegDecoder::FFmpegDecoder() : @@ -50,17 +49,13 @@ FFmpegDecoder::FFmpegDecoder() : cache_at_eof_(false), opts_(nullptr) { - 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() { Close(); - clear_timer_.stop(); } bool FFmpegDecoder::Open() @@ -182,6 +177,8 @@ bool FFmpegDecoder::Open() } second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base))); + + clear_timer_.start(); } // All allocation succeeded so we set the state to open @@ -215,9 +212,6 @@ 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; @@ -242,10 +236,12 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) if (cache_at_zero_ && target_ts < cached_frames_.first()->pts) { found_frame = cached_frames_.first(); + cached_frames_.accessedFirst(); } else if (cache_at_eof_ && target_ts > cached_frames_.last()->pts) { found_frame = cached_frames_.last(); + cached_frames_.accessedLast(); } else if (target_ts >= cached_frames_.first()->pts && target_ts <= cached_frames_.last()->pts) { @@ -258,6 +254,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) || (i < cached_frames_.size() - 1 && cached_frames_.at(i+1)->pts > target_ts)) { // Or for this frame to be the "closest" found_frame = this_frame; + cached_frames_.accessed(i); break; @@ -385,10 +382,6 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode) } } - if (MemoryManager::instance()->RegisterMemory()) { - RemoveFirstFromFrameCache(); - } - // Whatever it is, keep this frame in memory for the time being just in case cached_frames_.append(working_frame); } @@ -484,6 +477,8 @@ void FFmpegDecoder::Close() QMutexLocker locker(&mutex_); ClearResources(); + + clear_timer_.stop(); } QString FFmpegDecoder::id() @@ -921,7 +916,7 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) } } -void FFmpegDecoder::RemoveFirstFromFrameCache() +/*void FFmpegDecoder::RemoveFirstFromFrameCache() { if (cached_frames_.isEmpty()) { return; @@ -941,7 +936,7 @@ void FFmpegDecoder::RemoveLastFromFrameCache() AVFrame* last = cached_frames_.takeLast(); av_frame_free(&last); cache_at_eof_ = false; -} +}*/ void FFmpegDecoder::ClearFrameCache() { @@ -982,26 +977,9 @@ void FFmpegDecoder::ClearResources() open_ = false; } -void FFmpegDecoder::FreeMemory() -{ - if (mutex_.tryLock()) { - if (cached_frames_.size() > 1) { - RemoveFirstFromFrameCache(); - } - - mutex_.unlock(); - } -} - void FFmpegDecoder::ClearTimerEvent() { QMutexLocker locker(&mutex_); - ClearFrameCache(); -} - -void FFmpegDecoder::RestartClearTimer() -{ - clear_timer_.stop(); - clear_timer_.start(); + cached_frames_.remove_old_frames(QDateTime::currentMSecsSinceEpoch() - 5000); } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 3fb00d24f..1ab5429cd 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -34,6 +34,7 @@ extern "C" { #include "audio/sampleformat.h" #include "codec/decoder.h" #include "codec/waveoutput.h" +#include "ffmpegframecache.h" #include "project/item/footage/videostream.h" /** @@ -101,8 +102,8 @@ private: void CacheFrameToDisk(AVFrame* f); - void RemoveFirstFromFrameCache(); - void RemoveLastFromFrameCache(); + //void RemoveFirstFromFrameCache(); + //void RemoveLastFromFrameCache(); void ClearFrameCache(); void ClearResources(); @@ -116,7 +117,7 @@ private: SwsContext* scale_ctx_; - QList cached_frames_; + FFmpegFrameCache cached_frames_; bool cache_at_zero_; bool cache_at_eof_; @@ -127,12 +128,8 @@ private: QTimer clear_timer_; private slots: - void FreeMemory(); - void ClearTimerEvent(); - void RestartClearTimer(); - }; #endif // FFMPEGDECODER_H diff --git a/app/codec/ffmpeg/ffmpegframecache.cpp b/app/codec/ffmpeg/ffmpegframecache.cpp new file mode 100644 index 000000000..6b910f04a --- /dev/null +++ b/app/codec/ffmpeg/ffmpegframecache.cpp @@ -0,0 +1,80 @@ +#include "ffmpegframecache.h" + +#include +#include + +int FFmpegFrameCache::global_frame_count_ = 0; + +FFmpegFrameCache::FFmpegFrameCache() +{ + +} + +void FFmpegFrameCache::append(AVFrame *f) +{ + global_frame_count_++; + + frames_.append({f, QDateTime::currentMSecsSinceEpoch()}); +} + +void FFmpegFrameCache::clear() +{ + global_frame_count_ -= frames_.size(); + + frames_.clear(); +} + +bool FFmpegFrameCache::isEmpty() const +{ + return frames_.isEmpty(); +} + +AVFrame *FFmpegFrameCache::first() const +{ + return frames_.first().frame; +} + +AVFrame *FFmpegFrameCache::at(int i) const +{ + return frames_.at(i).frame; +} + +AVFrame *FFmpegFrameCache::last() const +{ + return frames_.last().frame; +} + +int FFmpegFrameCache::size() const +{ + return frames_.size(); +} + +void FFmpegFrameCache::accessedFirst() +{ + frames_.first().accessed = QDateTime::currentMSecsSinceEpoch(); +} + +void FFmpegFrameCache::accessedLast() +{ + frames_.last().accessed = QDateTime::currentMSecsSinceEpoch(); +} + +void FFmpegFrameCache::accessed(int i) +{ + frames_[i].accessed = QDateTime::currentMSecsSinceEpoch(); +} + +void FFmpegFrameCache::remove_old_frames(qint64 older_than) +{ + int counter = 0; + + while (!frames_.isEmpty() && frames_.first().accessed < older_than) { + CachedFrame cf = frames_.takeFirst(); + av_frame_free(&cf.frame); + counter++; + } + + qDebug() << " * Removed" << counter << "frames"; + global_frame_count_ -= counter; + qDebug() << " * Global frames:" << global_frame_count_; +} diff --git a/app/codec/ffmpeg/ffmpegframecache.h b/app/codec/ffmpeg/ffmpegframecache.h new file mode 100644 index 000000000..49bd4a512 --- /dev/null +++ b/app/codec/ffmpeg/ffmpegframecache.h @@ -0,0 +1,43 @@ +#ifndef FFMPEGFRAMECACHE_H +#define FFMPEGFRAMECACHE_H + +extern "C" { +#include +} + +#include +#include + +class FFmpegFrameCache +{ +public: + FFmpegFrameCache(); + + void append(AVFrame* f); + void clear(); + + bool isEmpty() const; + AVFrame* first() const; + AVFrame* at(int i) const; + AVFrame* last() const; + int size() const; + + void accessedFirst(); + void accessedLast(); + void accessed(int i); + + void remove_old_frames(qint64 older_than); + +private: + struct CachedFrame { + AVFrame* frame; + qint64 accessed; + }; + + QList frames_; + + static int global_frame_count_; + +}; + +#endif // FFMPEGFRAMECACHE_H diff --git a/app/core.cpp b/app/core.cpp index bd6f4f632..df478bb3e 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -48,7 +48,6 @@ #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" @@ -112,9 +111,6 @@ void Core::Start() // Set up the index manager for renderers IndexManager::CreateInstance(); - // Set up memory manager - MemoryManager::CreateInstance(); - // Load application config Config::Load(); @@ -155,8 +151,6 @@ void Core::Stop() IndexManager::DestroyInstance(); - MemoryManager::DestroyInstance(); - delete main_window_; } diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 05c2001ae..99225da03 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -26,8 +26,6 @@ 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/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index f74569f50..9efb7ecc6 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -63,9 +63,10 @@ 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()); - if (decoder == nullptr && stream != nullptr) { + if (!decoder && stream) { // Create a new Decoder here decoder = Decoder::CreateFromID(stream->footage()->decoder()); decoder->set_stream(stream); @@ -74,7 +75,7 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) decoder_cache_->Add(stream.get(), decoder); } else { decoder = nullptr; - qWarning() << "Failed to open decoder for" << stream->footage()->filename(); + qWarning() << "Failed to open decoder for" << stream->footage()->filename() << "::" << stream->index(); } } diff --git a/app/render/memorymanager.cpp b/app/render/memorymanager.cpp deleted file mode 100644 index 60a72b3cd..000000000 --- a/app/render/memorymanager.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "memorymanager.h" - -#include - -#ifdef Q_OS_WINDOWS -#include -#else -#include -#endif - -MemoryManager* MemoryManager::instance_ = nullptr; - -// FIXME: Hardcoded, should be a preference -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_); -} - -bool MemoryManager::RegisterMemory() -{ - if (ShouldFreeMemory()) { - emit FreeMemory(); - - return true; - } - - return false; -} - -uint64_t MemoryManager::GetAvailableMemory() -{ -#ifdef Q_OS_WINDOWS - MEMORYSTATUSEX status; - status.dwLength = sizeof(status); - GlobalMemoryStatusEx(&status); - return status.ullAvailPhys; -#else - long pages = sysconf(_SC_AVPHYS_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 deleted file mode 100644 index 4d2b18787..000000000 --- a/app/render/memorymanager.h +++ /dev/null @@ -1,32 +0,0 @@ -#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(); - - bool RegisterMemory(); - -signals: - void FreeMemory(); - -private: - static uint64_t GetAvailableMemory(); - - static MemoryManager* instance_; - - static const uint64_t minimum_available_memory_; - -}; - -#endif // MEMORYMANAGER_H