diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index 75b4a9205..cb97725cc 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -26,14 +26,22 @@ #include #include "common/oiioutils.h" +#include "render/framemanager.h" namespace olive { Frame::Frame() : + data_(nullptr), + data_size_(0), timestamp_(0) { } +Frame::~Frame() +{ + destroy(); +} + FramePtr Frame::Create() { return std::make_shared(); @@ -66,7 +74,7 @@ Color Frame::get_pixel(int x, int y) const int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); - return Color(data_.data() + byte_offset, video_params().format(), video_params().channel_count()); + return Color(reinterpret_cast(data_ + byte_offset), video_params().format(), video_params().channel_count()); } bool Frame::contains_pixel(int x, int y) const @@ -82,7 +90,7 @@ void Frame::set_pixel(int x, int y, const Color &c) int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel(); - c.toData(data_.data() + byte_offset, video_params().format(), video_params().channel_count()); + c.toData(reinterpret_cast(data_ + byte_offset), video_params().format(), video_params().channel_count()); } bool Frame::allocate() @@ -93,11 +101,27 @@ bool Frame::allocate() return false; } - data_.resize(VideoParams::GetBufferSize(linesize_, height(), params_.format(), params_.channel_count())); + if (is_allocated()) { + // Already allocated + return true; + } + + data_size_ = VideoParams::GetBufferSize(linesize_, height(), params_.format(), params_.channel_count()); + data_ = FrameManager::Allocate(data_size_); return true; } +void Frame::destroy() +{ + if (is_allocated()) { + FrameManager::Deallocate(data_size_, data_); + + data_size_ = 0; + data_ = nullptr; + } +} + FramePtr Frame::convert(VideoParams::Format format) const { // Create new params with destination format diff --git a/app/codec/frame.h b/app/codec/frame.h index df3bef2d8..1859332bc 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -24,6 +24,7 @@ #include #include +#include "common/define.h" #include "common/rational.h" #include "render/color.h" #include "render/videoparams.h" @@ -41,6 +42,10 @@ class Frame public: Frame(); + ~Frame(); + + DISABLE_COPY_MOVE(Frame) + static FramePtr Create(); const VideoParams& video_params() const; @@ -102,7 +107,7 @@ public: */ char* data() { - return data_.data(); + return data_; } /** @@ -110,7 +115,7 @@ public: */ const char* const_data() const { - return data_.constData(); + return data_; } /** @@ -127,16 +132,13 @@ public: */ bool is_allocated() const { - return !data_.isEmpty(); + return data_; } /** * @brief Destroy a memory buffer allocated with allocate() */ - void destroy() - { - data_.clear(); - } + void destroy(); /** * @brief Returns the size of the array returned in data() in bytes @@ -145,7 +147,7 @@ public: */ int allocated_size() const { - return data_.size(); + return data_size_; } FramePtr convert(VideoParams::Format format) const; @@ -153,7 +155,8 @@ public: private: VideoParams params_; - QByteArray data_; + char* data_; + int data_size_; rational timestamp_; diff --git a/app/core.cpp b/app/core.cpp index 2379a2bc0..ad864b41f 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -51,6 +51,7 @@ #include "panel/project/project.h" #include "panel/viewer/viewer.h" #include "render/diskmanager.h" +#include "render/framemanager.h" #include "render/rendermanager.h" #ifdef USE_OTIO #include "task/project/loadotio/loadotio.h" @@ -140,6 +141,9 @@ void Core::Start() // Initialize RenderManager RenderManager::CreateInstance(); + // Initialize FrameManager + FrameManager::CreateInstance(); + // // Start application // @@ -184,6 +188,8 @@ void Core::Stop() } } + FrameManager::DestroyInstance(); + RenderManager::DestroyInstance(); MenuShared::DestroyInstance(); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 25f89e6d8..4d71d23cf 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -33,6 +33,8 @@ set(OLIVE_SOURCES render/diskmanager.h render/framehashcache.cpp render/framehashcache.h + render/framemanager.cpp + render/framemanager.h render/managedcolor.cpp render/managedcolor.h render/playbackcache.cpp diff --git a/app/render/framemanager.cpp b/app/render/framemanager.cpp new file mode 100644 index 000000000..fdb789ee1 --- /dev/null +++ b/app/render/framemanager.cpp @@ -0,0 +1,129 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "framemanager.h" + +#include +#include + +namespace olive { + +FrameManager* FrameManager::instance_ = nullptr; +const int FrameManager::kFrameLifetime = 5000; + +void FrameManager::CreateInstance() +{ + instance_ = new FrameManager(); +} + +void FrameManager::DestroyInstance() +{ + delete instance_; + instance_ = nullptr; +} + +FrameManager *FrameManager::instance() +{ + return instance_; +} + +char *FrameManager::Allocate(int size) +{ + if (instance()) { + return instance()->AllocateFromPool(size); + } else { + return new char[size]; + } +} + +void FrameManager::Deallocate(int size, char *buffer) +{ + if (instance()) { + instance()->DeallocateToPool(size, buffer); + } else { + delete [] buffer; + } +} + +FrameManager::FrameManager() +{ + clear_timer_.setInterval(kFrameLifetime); + connect(&clear_timer_, &QTimer::timeout, this, &FrameManager::GarbageCollection); + clear_timer_.start(); +} + +char *FrameManager::AllocateFromPool(int size) +{ + QMutexLocker locker(&mutex_); + + std::list& buffer_list = pool_[size]; + char* buf = nullptr; + + if (buffer_list.empty()) { + buf = new char[size]; + } else { + // Take this buffer from the list + buf = buffer_list.front().data; + buffer_list.pop_front(); + } + + return buf; +} + +void FrameManager::DeallocateToPool(int size, char *buffer) +{ + QMutexLocker locker(&mutex_); + + std::list& buffer_list = pool_[size]; + + buffer_list.push_back({QDateTime::currentMSecsSinceEpoch(), buffer}); +} + +void FrameManager::GarbageCollection() +{ + QMutexLocker locker(&mutex_); + + qint64 min_life = QDateTime::currentMSecsSinceEpoch() - kFrameLifetime; + + for (auto it=pool_.begin(); it!=pool_.end(); it++) { + std::list& list = it->second; + + while (list.size() > 0 && list.front().time < min_life) { + delete [] list.front().data; + list.pop_front(); + } + } +} + +FrameManager::~FrameManager() +{ + QMutexLocker locker(&mutex_); + + for (auto it=pool_.begin(); it!=pool_.end(); it++) { + std::list& list = it->second; + for (auto jt=list.begin(); jt!=list.end(); jt++) { + delete [] (*jt).data; + } + } + + pool_.clear(); +} + +} diff --git a/app/render/framemanager.h b/app/render/framemanager.h new file mode 100644 index 000000000..a7e6c5e95 --- /dev/null +++ b/app/render/framemanager.h @@ -0,0 +1,91 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef FRAMEMANAGER_H +#define FRAMEMANAGER_H + +#include +#include + +namespace olive { + +class FrameManager : public QObject +{ + Q_OBJECT +public: + static void CreateInstance(); + + static void DestroyInstance(); + + static FrameManager* instance(); + + static char* Allocate(int size); + + static void Deallocate(int size, char* buffer); + +private: + FrameManager(); + + virtual ~FrameManager() override; + + /** + * @brief Allocate buffer + * + * Caller takes ownership of buffer and can delete it if they want. It can also be returned to + * the manager with Deallocate and potentially be re-used later. + * + * Thread-safe. + */ + char* AllocateFromPool(int size); + + /** + * @brief Deallocate buffer + * + * Manager will take ownership and buffer will stay allocated for some time in case it can be + * re-used. + * + * Thread-safe. + */ + void DeallocateToPool(int size, char* buffer); + + static FrameManager* instance_; + + static const int kFrameLifetime; + + struct Buffer + { + qint64 time; + char* data; + }; + + std::map< int, std::list > pool_; + + QMutex mutex_; + + QTimer clear_timer_; + +private slots: + void GarbageCollection(); + +}; + +} + +#endif // FRAMEMANAGER_H diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 8b539f518..8e105ccdd 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -455,7 +455,6 @@ void ViewerWidget::UpdateTextureFromNode() // Check playback queue for a frame if (IsPlaying()) { - // We still run the playback queue even when FrameExistsAtTime returns false because we might be // playing backwards and about to start showing frames, so the queue should be prepared for // that. diff --git a/app/widget/viewer/viewerplaybacktimer.cpp b/app/widget/viewer/viewerplaybacktimer.cpp index f94657592..03452c3b8 100644 --- a/app/widget/viewer/viewerplaybacktimer.cpp +++ b/app/widget/viewer/viewerplaybacktimer.cpp @@ -21,6 +21,7 @@ #include "viewerplaybacktimer.h" #include +#include namespace olive { @@ -29,14 +30,14 @@ void ViewerPlaybackTimer::Start(const int64_t &start_timestamp, const int &playb start_msec_ = QDateTime::currentMSecsSinceEpoch(); start_timestamp_ = start_timestamp; playback_speed_ = playback_speed; - timebase_ = timebase; + timebase_ = timebase * 1000; } int64_t ViewerPlaybackTimer::GetTimestampNow() const { int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_; - int64_t frames_since_start = qRound(static_cast(real_time) / (timebase_ * 1000)); + int64_t frames_since_start = qFloor(static_cast(real_time) / (timebase_)); return start_timestamp_ + frames_since_start * playback_speed_; }