diff --git a/app/codec/CMakeLists.txt b/app/codec/CMakeLists.txt index 791500cdd..606efc295 100644 --- a/app/codec/CMakeLists.txt +++ b/app/codec/CMakeLists.txt @@ -25,6 +25,8 @@ set(OLIVE_SOURCES codec/encoder.cpp codec/frame.h codec/frame.cpp + codec/framepool.h + codec/framepool.cpp codec/samplebuffer.h codec/samplebuffer.cpp codec/waveinput.h diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt index bee2d0e9e..5cac2e216 100644 --- a/app/codec/ffmpeg/CMakeLists.txt +++ b/app/codec/ffmpeg/CMakeLists.txt @@ -23,7 +23,5 @@ 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/avframeptr.h b/app/codec/ffmpeg/avframeptr.h index b0f4bda95..232470910 100644 --- a/app/codec/ffmpeg/avframeptr.h +++ b/app/codec/ffmpeg/avframeptr.h @@ -1,3 +1,23 @@ +/*** + + 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 AVFRAMEPTR_H #define AVFRAMEPTR_H @@ -17,8 +37,6 @@ class AVFrameWrapper { public: AVFrameWrapper() { frame_ = av_frame_alloc(); - birthtime_ = QDateTime::currentMSecsSinceEpoch(); - accessed_ = birthtime_; } virtual ~AVFrameWrapper() { @@ -31,25 +49,9 @@ public: return frame_; } - inline const qint64& birthtime() const { - return birthtime_; - } - - inline const qint64& last_accessed() const { - return accessed_; - } - - void access() { - accessed_ = QDateTime::currentMSecsSinceEpoch(); - } - private: AVFrame* frame_; - qint64 birthtime_; - - qint64 accessed_; - }; using AVFramePtr = std::shared_ptr; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index fbbb869ee..154b61877 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -57,6 +57,8 @@ FFmpegDecoder::FFmpegDecoder() : 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() @@ -197,39 +199,33 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid i->cache_lock()->unlock(); break; - } else if (i->CacheWillContainTime(target_ts)) { + } else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) { // Found our instance, allow others to enter the list list_locker.unlock(); - do { - // Allow instance to continue to the next frame - i->cache_wait_cond()->wait(i->cache_lock()); + 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); + // 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(); } - } while (!return_frame); - - // Got our frame, allow cache to continue - i->cache_lock()->unlock(); - break; - - } else if (i->CacheCouldContainTime(target_ts)) { - - // Found our instance, allow others to enter the list - list_locker.unlock(); - - // Wait for this instance to finish working - while (i->IsWorking()) { - i->cache_wait_cond()->wait(i->cache_lock()); + } else { + working_instance = i; } - - // Grab this instance - working_instance = i; - - // We DON'T unlock here, since we'll be starting our own retrieve break; } else if (i->IsWorking()) { @@ -877,7 +873,7 @@ void FFmpegDecoderInstance::ClearFrameCache() cache_at_zero_ = false; } -AVFramePtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool cache_is_locked) +FramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool cache_is_locked) { if (!cache_is_locked) { cache_lock_.lock(); @@ -889,9 +885,7 @@ AVFramePtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool c 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 (cached_frames_.isEmpty() - || target_ts < cached_frames_.first()->frame()->pts - || target_ts > cached_frames_.last()->frame()->pts + 2*second_ts_) { + if (!CacheCouldContainTime(target_ts)) { ClearFrameCache(); Seek(seek_ts); @@ -904,7 +898,7 @@ AVFramePtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool c int ret; AVPacket* pkt = av_packet_alloc(); - AVFramePtr return_frame = nullptr; + FramePool::ElementPtr return_frame = nullptr; // Allocate a new frame AVFrameWrapper working_frame; @@ -960,16 +954,17 @@ AVFramePtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, bool c } else { // Whatever it is, keep this frame in memory for the time being just in case - AVFramePtr cached = cached_frames_.append(working_frame.frame()); + FramePool::ElementPtr cached = frame_pool_.Get(); + cached->set_timestamp(working_frame.frame()->pts); 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->frame()->pts == target_ts) { + if (cached->timestamp() == target_ts) { return_frame = cached; break; - } else if (cached->frame()->pts > target_ts) { + } else if (cached->timestamp() > target_ts) { if (cached_frames_.isEmpty() && cache_at_zero_) { return_frame = cached; break; @@ -1029,7 +1024,7 @@ int64_t FFmpegDecoderInstance::RangeStart() const if (cached_frames_.isEmpty()) { return AV_NOPTS_VALUE; } - return cached_frames_.first()->frame()->pts; + return cached_frames_.first()->timestamp(); } int64_t FFmpegDecoderInstance::RangeEnd() const @@ -1037,25 +1032,25 @@ int64_t FFmpegDecoderInstance::RangeEnd() const if (cached_frames_.isEmpty()) { return AV_NOPTS_VALUE; } - return cached_frames_.last()->frame()->pts; + 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()->frame()->pts) - || (cache_at_eof_ && t > cached_frames_.last()->frame()->pts)); + || (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()->frame()->pts && t <= cache_target_time_; + 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()->frame()->pts && t <= (cache_target_time_ + 2*second_ts_); + return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= (cache_target_time_ + 2*second_ts_); } bool FFmpegDecoderInstance::CacheIsEmpty() const @@ -1063,15 +1058,15 @@ bool FFmpegDecoderInstance::CacheIsEmpty() const return cached_frames_.isEmpty(); } -AVFramePtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const +FramePool::ElementPtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const { - if (t < cached_frames_.first()->frame()->pts) { + if (t < cached_frames_.first()->timestamp()) { if (cache_at_zero_) { return cached_frames_.first(); } - } else if (t > cached_frames_.last()->frame()->pts) { + } else if (t > cached_frames_.last()->timestamp()) { if (cache_at_eof_) { return cached_frames_.last(); @@ -1081,10 +1076,10 @@ AVFramePtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const // We already have this frame in the cache, find it for (int i=0;iframe()->pts == t // Test for an exact match - || (i < cached_frames_.size() - 1 && cached_frames_.at(i+1)->frame()->pts > t)) { // Or for this frame to be the "closest" + if (this_frame->timestamp() == 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" return this_frame; @@ -1097,7 +1092,8 @@ AVFramePtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const void FFmpegDecoderInstance::RemoveFramesBefore(const qint64 &t) { - if (cached_frames_.remove_old_frames(t)) { + while (cached_frames_.size() > 1 && cached_frames_.first()->last_accessed() < t) { + cached_frames_.removeFirst(); cache_at_zero_ = false; } } @@ -1186,6 +1182,14 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in return; } + // Create buffer pool + if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO + && !frame_pool_.Allocate()) { + 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))); } @@ -1218,6 +1222,8 @@ void FFmpegDecoderInstance::ClearResources() avformat_close_input(&fmt_ctx_); fmt_ctx_ = nullptr; } + + frame_pool_.Destroy(); } void FFmpegDecoder::ClearTimerEvent() diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 40643c7a4..746446f1e 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -35,8 +35,8 @@ extern "C" { #include "audio/sampleformat.h" #include "avframeptr.h" #include "codec/decoder.h" +#include "codec/framepool.h" #include "codec/waveoutput.h" -#include "ffmpegframecache.h" #include "project/item/footage/videostream.h" OLIVE_NAMESPACE_ENTER @@ -56,7 +56,7 @@ public: bool CacheWillContainTime(const int64_t& t) const; bool CacheCouldContainTime(const int64_t& t) const; bool CacheIsEmpty() const; - AVFramePtr GetFrameFromCache(const int64_t& t) const; + FramePool::ElementPtr GetFrameFromCache(const int64_t& t) const; void RemoveFramesBefore(const qint64& t); @@ -65,7 +65,7 @@ public: void ClearFrameCache(); - AVFramePtr RetrieveFrame(const int64_t &target_ts, bool cache_is_locked); + FramePool::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_) @@ -96,8 +96,8 @@ private: QWaitCondition cache_wait_cond_; QMutex cache_lock_; - //QList cached_frames_; - FFmpegFrameCache::Client cached_frames_; + QList cached_frames_; + FramePool frame_pool_; int64_t cache_target_time_; diff --git a/app/codec/ffmpeg/ffmpegframecache.cpp b/app/codec/ffmpeg/ffmpegframecache.cpp deleted file mode 100644 index d64ac8c37..000000000 --- a/app/codec/ffmpeg/ffmpegframecache.cpp +++ /dev/null @@ -1,144 +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_; -QLinkedList FFmpegFrameCache::frame_pool_; - -AVFramePtr FFmpegFrameCache::Client::append(int width, int height, int format) -{ - AVFramePtr f = FFmpegFrameCache::Get(width, height, format); - - frames_.append(f); - - return f; -} - -AVFramePtr FFmpegFrameCache::Client::append(AVFrame *copy) -{ - AVFramePtr f = append(copy->width, copy->height, copy->format); - - av_frame_copy(f->frame(), copy); - f->frame()->pts = copy->pts; - - return f; -} - -void FFmpegFrameCache::Client::clear() -{ - foreach (AVFramePtr cf, frames_) { - FFmpegFrameCache::Release(cf); - } - frames_.clear(); -} - -bool FFmpegFrameCache::Client::isEmpty() const -{ - return frames_.isEmpty(); -} - -AVFramePtr FFmpegFrameCache::Client::first() const -{ - return frames_.first(); -} - -AVFramePtr FFmpegFrameCache::Client::at(int i) const -{ - return frames_.at(i); -} - -AVFramePtr FFmpegFrameCache::Client::last() const -{ - return frames_.last(); -} - -int FFmpegFrameCache::Client::size() const -{ - return frames_.size(); -} - -void FFmpegFrameCache::Client::accessedFirst() -{ - frames_.first()->access(); -} - -void FFmpegFrameCache::Client::accessedLast() -{ - frames_.last()->access(); -} - -void FFmpegFrameCache::Client::accessed(int i) -{ - frames_[i]->access(); -} - -int FFmpegFrameCache::Client::remove_old_frames(qint64 older_than) -{ - int counter = 0; - - while (frames_.size() > 1 && frames_.first()->last_accessed() < older_than) { - FFmpegFrameCache::Release(frames_.takeFirst()); - counter++; - } - - return counter; -} - -AVFramePtr FFmpegFrameCache::Get(int width, int height, int format) -{ - QMutexLocker locker(&pool_lock_); - - // See if we have a frame matching this description in the pool - QLinkedList::iterator i; - for (i=frame_pool_.begin();i!=frame_pool_.end();i++) { - AVFramePtr f = (*i); - - if (f->frame()->width == width - && f->frame()->height == height - && f->frame()->format == format) { - frame_pool_.erase(i); - return f; - } - } - - // Otherwise we'll need to create one - AVFramePtr f = std::make_shared(); - f->frame()->width = width; - f->frame()->height = height; - f->frame()->format = format; - av_frame_get_buffer(f->frame(), 1); - - return f; -} - -void FFmpegFrameCache::Release(AVFramePtr 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 084b5e869..000000000 --- a/app/codec/ffmpeg/ffmpegframecache.h +++ /dev/null @@ -1,76 +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 "avframeptr.h" -#include "render/videoparams.h" - -OLIVE_NAMESPACE_ENTER - -class FFmpegFrameCache -{ -public: - FFmpegFrameCache() = default; - - static AVFramePtr Get(int width, int height, int format); - - static void Release(AVFramePtr f); - - class Client - { - public: - Client() = default; - - AVFramePtr append(int width, int height, int format); - AVFramePtr append(AVFrame *copy); - void clear(); - - bool isEmpty() const; - AVFramePtr first() const; - AVFramePtr at(int i) const; - AVFramePtr last() const; - int size() const; - - void accessedFirst(); - void accessedLast(); - void accessed(int i); - - int remove_old_frames(qint64 older_than); - - private: - QList frames_; - - }; - -private: - static QMutex pool_lock_; - - static QLinkedList frame_pool_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // FFMPEGFRAMECACHE_H diff --git a/app/codec/framepool.cpp b/app/codec/framepool.cpp new file mode 100644 index 000000000..d0fceaa14 --- /dev/null +++ b/app/codec/framepool.cpp @@ -0,0 +1,97 @@ +/*** + + 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 "framepool.h" + +OLIVE_NAMESPACE_ENTER + +FramePool::FramePool() +{ + data_ = nullptr; +} + +bool FramePool::Allocate(int width, int height, PixelFormat::Format format, int nb_elements) +{ + delete [] data_; + + element_sz_ = width * height * PixelFormat::BytesPerPixel(format); + + if ((data_ = new char[element_sz_ * nb_elements])) { + // Only allocate available array if data allocation succeeded + available_.resize(nb_elements); + available_.fill(true); + + return true; + } else { + available_.clear(); + + return false; + } +} + +void FramePool::Destroy() +{ + delete [] data_; + available_.clear(); +} + +FramePool::~FramePool() +{ + delete [] data_; +} + +FramePool::ElementPtr FramePool::Get() +{ + for (int i=0;i(this, data_ + i * element_sz_); + } + } + + return nullptr; +} + +void FramePool::Release(FramePool::Element *e) +{ + quintptr offs = reinterpret_cast(e->data()); + quintptr start = reinterpret_cast(data_); + + quintptr diff = offs - start; + + int index = diff / element_sz_; + + available_.replace(index, true); +} + +FramePool::Element::Element(FramePool *parent, char *data) +{ + parent_ = parent; + data_ = data; + accessed_ = QDateTime::currentMSecsSinceEpoch(); +} + +FramePool::Element::~Element() +{ + parent_->Release(this); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/codec/framepool.h b/app/codec/framepool.h new file mode 100644 index 000000000..86da3bed2 --- /dev/null +++ b/app/codec/framepool.h @@ -0,0 +1,97 @@ +/*** + + 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 FRAMEPOOL_H +#define FRAMEPOOL_H + +#include +#include + +#include "render/pixelformat.h" + +OLIVE_NAMESPACE_ENTER + +class FramePool +{ +public: + FramePool(); + + ~FramePool(); + + bool Allocate(int width, int height, PixelFormat::Format format, int nb_elements); + + void Destroy(); + + DISABLE_COPY_MOVE(FramePool) + + class Element { + public: + Element(FramePool* parent, char* data); + ~Element(); + + inline char* 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: + FramePool* parent_; + + char* data_; + + int64_t timestamp_; + + int64_t accessed_; + + }; + + using ElementPtr = std::shared_ptr; + + ElementPtr Get(); + + void Release(Element* e); + +private: + char* data_; + + int element_sz_; + + QVector available_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // FRAMEPOOL_H