global: implemented rudimentary memory manager to use available system memory
without forcing the OS to page swap
This commit is contained in:
@@ -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<VideoStream>(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<VideoStream>(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<cached_frames_.size();i++) {
|
||||
av_frame_free(&cached_frames_[i]);
|
||||
AVFrame* f = cached_frames_.at(i);
|
||||
av_frame_free(&f);
|
||||
}
|
||||
|
||||
cached_frames_.clear();
|
||||
cache_at_eof_ = false;
|
||||
cache_at_zero_ = false;
|
||||
}
|
||||
|
||||
void FFmpegDecoder::FreeMemory()
|
||||
{
|
||||
if (!allow_clear_event_) {
|
||||
mutex_.lock();
|
||||
}
|
||||
|
||||
if (cached_frames_.size() > 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();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QTimer>
|
||||
#include <QVector>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "memorymanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include <windows.h>
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef MEMORYMANAGER_H
|
||||
#define MEMORYMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user