From 4bdccb889ce55be2bda276b858f1362bbdd0f3ee Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 12 Jun 2014 14:34:21 -0700 Subject: [PATCH] Reuse buffers for video frames At least on OS X, allocating the buffers is one of the more expensive parts of video playback, and on an arbitrary 720p H.264 file with simple subtitles this cuts CPU usage while playing by about 30%. --- src/async_video_provider.cpp | 15 +++++++++- src/async_video_provider.h | 2 ++ src/include/aegisub/video_provider.h | 3 +- src/subs_preview.cpp | 7 +++-- src/video_frame.cpp | 18 ------------ src/video_frame.h | 3 -- src/video_provider_avs.cpp | 11 +++++-- src/video_provider_cache.cpp | 43 ++++++++++++++-------------- src/video_provider_dummy.cpp | 8 ++++-- src/video_provider_dummy.h | 2 +- src/video_provider_ffmpegsource.cpp | 10 +++++-- src/video_provider_yuv4mpeg.cpp | 14 +++++---- 12 files changed, 72 insertions(+), 64 deletions(-) diff --git a/src/async_video_provider.cpp b/src/async_video_provider.cpp index 1b8582552..297bda7e0 100644 --- a/src/async_video_provider.cpp +++ b/src/async_video_provider.cpp @@ -20,6 +20,7 @@ #include "ass_file.h" #include "export_fixstyle.h" #include "include/aegisub/subtitles_provider.h" +#include "video_frame.h" #include "video_provider_manager.h" #include @@ -30,10 +31,22 @@ enum { }; std::shared_ptr AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) { + // Find an unused buffer to use or allocate a new one if needed std::shared_ptr frame; + for (auto& buffer : buffers) { + if (buffer.use_count() == 1) { + frame = buffer; + break; + } + } + + if (!frame) { + frame = std::make_shared(); + buffers.push_back(frame); + } try { - frame = source_provider->GetFrame(frame_number); + source_provider->GetFrame(frame_number, *frame); } catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); } diff --git a/src/async_video_provider.h b/src/async_video_provider.h index c77a2664c..b1193026d 100644 --- a/src/async_video_provider.h +++ b/src/async_video_provider.h @@ -76,6 +76,8 @@ class AsyncVideoProvider { /// they can be rendered std::atomic version{ 0 }; + std::vector> buffers; + public: /// @brief Load the passed subtitle file /// @param subs File to load diff --git a/src/include/aegisub/video_provider.h b/src/include/aegisub/video_provider.h index d257a6b1c..43ff24545 100644 --- a/src/include/aegisub/video_provider.h +++ b/src/include/aegisub/video_provider.h @@ -37,7 +37,6 @@ #include #include -#include #include struct VideoFrame; @@ -47,7 +46,7 @@ public: virtual ~VideoProvider() = default; /// Override this method to actually get frames - virtual std::shared_ptr GetFrame(int n)=0; + virtual void GetFrame(int n, VideoFrame &frame)=0; /// Set the YCbCr matrix to the specified one /// diff --git a/src/subs_preview.cpp b/src/subs_preview.cpp index 980e89f19..2f28fa818 100644 --- a/src/subs_preview.cpp +++ b/src/subs_preview.cpp @@ -101,18 +101,19 @@ void SubtitlesPreview::SetColour(agi::Color col) { void SubtitlesPreview::UpdateBitmap() { if (!vid) return; - auto frame = vid->GetFrame(0); + VideoFrame frame; + vid->GetFrame(0, frame); if (provider) { try { provider->LoadSubtitles(sub_file.get()); - provider->DrawSubtitles(*frame, 0.1); + provider->DrawSubtitles(frame, 0.1); } catch (...) { } } // Convert frame to bitmap - *bmp = static_cast(GetImage(*frame)); + *bmp = static_cast(GetImage(frame)); Refresh(); } diff --git a/src/video_frame.cpp b/src/video_frame.cpp index 09c4e9e6d..610005879 100644 --- a/src/video_frame.cpp +++ b/src/video_frame.cpp @@ -19,24 +19,6 @@ #include #include -VideoFrame::VideoFrame(const unsigned char *data, size_t width, size_t height, size_t pitch, bool flipped) -: data(data, data + width * height * 4) -, width(width) -, height(height) -, pitch(pitch) -, flipped(flipped) -{ -} - -VideoFrame::VideoFrame(std::vector&& data, size_t width, size_t height, size_t pitch, bool flipped) -: data(std::move(data)) -, width(width) -, height(height) -, pitch(pitch) -, flipped(flipped) -{ -} - namespace { // We actually have bgr_, not bgra, so we need a custom converter which ignores the alpha channel struct color_converter { diff --git a/src/video_frame.h b/src/video_frame.h index fc8a67efa..2a47ed69c 100644 --- a/src/video_frame.h +++ b/src/video_frame.h @@ -24,9 +24,6 @@ struct VideoFrame { size_t height; size_t pitch; bool flipped; - - VideoFrame(const unsigned char *data, size_t width, size_t height, size_t pitch, bool fipped); - VideoFrame(std::vector&& data, size_t width, size_t height, size_t pitch, bool fipped); }; wxImage GetImage(VideoFrame const& frame); diff --git a/src/video_provider_avs.cpp b/src/video_provider_avs.cpp index 4dfb58d9f..5273a128d 100644 --- a/src/video_provider_avs.cpp +++ b/src/video_provider_avs.cpp @@ -73,7 +73,7 @@ class AvisynthVideoProvider: public VideoProvider { public: AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix); - std::shared_ptr GetFrame(int n); + void GetFrame(int n, VideoFrame &frame) override; void SetColorSpace(std::string const& matrix) override { // Can't really do anything if this fails @@ -309,11 +309,16 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) { throw VideoNotSupported("No function suitable for opening the video found"); } -std::shared_ptr AvisynthVideoProvider::GetFrame(int n) { +void AvisynthVideoProvider::GetFrame(int n, VideoFrame &out) { std::lock_guard lock(avs.GetMutex()); auto frame = RGB32Video->GetFrame(n, avs.GetEnv()); - return std::make_shared(frame->GetReadPtr(), frame->GetRowSize() / 4, frame->GetHeight(), frame->GetPitch(), true); + auto ptr = frame->GetReadPtr(); + out.data.assign(ptr, ptr + frame->GetPitch() * frame->GetHeight()); + out.flipped = true; + out.height = frame->GetHeight(); + out.width = frame->GetRowSize() / 4; + out.pitch = frame->GetPitch(); } } diff --git a/src/video_provider_cache.cpp b/src/video_provider_cache.cpp index 58e2441dc..4271279e2 100644 --- a/src/video_provider_cache.cpp +++ b/src/video_provider_cache.cpp @@ -25,14 +25,14 @@ namespace { /// A video frame and its frame number -struct CachedFrame final : public VideoFrame { +struct CachedFrame { + VideoFrame frame; int frame_number; - CachedFrame(int frame_number, VideoFrame const& frame) - : VideoFrame(frame.data.data(), frame.width, frame.height, frame.pitch, frame.flipped) - , frame_number(frame_number) - { - } + CachedFrame(VideoFrame const& frame, int frame_number) + : frame(frame), frame_number(frame_number) { } + + CachedFrame(CachedFrame const&) = delete; }; /// @class VideoProviderCache @@ -45,19 +45,15 @@ class VideoProviderCache final : public VideoProvider { /// /// Note that this is a soft limit. The cache stops allocating new frames /// once it has exceeded the limit, but it never tries to shrink - const size_t max_cache_size; + const size_t max_cache_size = OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20; // convert MB to bytes /// Cache of video frames with the most recently used ones at the front std::list cache; public: - VideoProviderCache(std::unique_ptr master) - : master(std::move(master)) - , max_cache_size(OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20) // convert MB to bytes - { - } + VideoProviderCache(std::unique_ptr master) : master(std::move(master)) { } - std::shared_ptr GetFrame(int n) override; + void GetFrame(int n, VideoFrame &frame) override; void SetColorSpace(std::string const& m) override { cache.clear(); @@ -78,25 +74,28 @@ public: bool HasAudio() const override { return master->HasAudio(); } }; -std::shared_ptr VideoProviderCache::GetFrame(int n) { +void VideoProviderCache::GetFrame(int n, VideoFrame &out) { size_t total_size = 0; for (auto cur = cache.begin(); cur != cache.end(); ++cur) { if (cur->frame_number == n) { cache.splice(cache.begin(), cache, cur); // Move to front - return std::make_shared(cache.front()); + out = cache.front().frame; + return; } - total_size += cur->data.size(); + total_size += cur->frame.data.size(); } - auto frame = master->GetFrame(n); + master->GetFrame(n, out); - if (total_size >= max_cache_size) - cache.pop_back(); - cache.emplace_front(n, *frame); - - return frame; + if (total_size >= max_cache_size) { + cache.splice(cache.begin(), cache, --cache.end()); // Move last to front + cache.front().frame_number = n; + cache.front().frame = out; + } + else + cache.emplace_front(out, n); } } diff --git a/src/video_provider_dummy.cpp b/src/video_provider_dummy.cpp index 3ac660db1..39eb69ebf 100644 --- a/src/video_provider_dummy.cpp +++ b/src/video_provider_dummy.cpp @@ -92,8 +92,12 @@ std::string DummyVideoProvider::MakeFilename(double fps, int frames, int width, return agi::format("?dummy:%f:%d:%d:%d:%d:%d:%d:%s", fps, frames, width, height, (int)colour.r, (int)colour.g, (int)colour.b, (pattern ? "c" : "")); } -std::shared_ptr DummyVideoProvider::GetFrame(int) { - return std::make_shared(data.data(), width, height, width * 4, false); +void DummyVideoProvider::GetFrame(int, VideoFrame &frame) { + frame.data = data; + frame.width = width; + frame.height = height; + frame.pitch = width * 4; + frame.flipped = false; } namespace agi { class BackgroundRunner; } diff --git a/src/video_provider_dummy.h b/src/video_provider_dummy.h index 4bcc0d7a1..bf4841e79 100644 --- a/src/video_provider_dummy.h +++ b/src/video_provider_dummy.h @@ -64,7 +64,7 @@ public: /// string will result in a video with the given parameters static std::string MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern); - std::shared_ptr GetFrame(int n) override; + void GetFrame(int n, VideoFrame &frame) override; void SetColorSpace(std::string const&) override { } int GetFrameCount() const override { return framecount; } diff --git a/src/video_provider_ffmpegsource.cpp b/src/video_provider_ffmpegsource.cpp index 9c8b4cdc5..5fd14f69c 100644 --- a/src/video_provider_ffmpegsource.cpp +++ b/src/video_provider_ffmpegsource.cpp @@ -70,7 +70,7 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid public: FFmpegSourceVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br); - std::shared_ptr GetFrame(int n) override; + void GetFrame(int n, VideoFrame &out) override; void SetColorSpace(std::string const& matrix) override { #if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0) @@ -285,14 +285,18 @@ void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename, std::st Timecodes = agi::vfr::Framerate(TimecodesVector); } -std::shared_ptr FFmpegSourceVideoProvider::GetFrame(int n) { +void FFmpegSourceVideoProvider::GetFrame(int n, VideoFrame &out) { n = mid(0, n, GetFrameCount() - 1); auto frame = FFMS_GetFrame(VideoSource, n, &ErrInfo); if (!frame) throw VideoDecodeError(std::string("Failed to retrieve frame: ") + ErrInfo.Buffer); - return std::make_shared(frame->Data[0], Width, Height, frame->Linesize[0], false); + out.data.assign(frame->Data[0], frame->Data[0] + frame->Linesize[0] * Height); + out.flipped = false; + out.width = Width; + out.height = Height; + out.pitch = frame->Linesize[0]; } } diff --git a/src/video_provider_yuv4mpeg.cpp b/src/video_provider_yuv4mpeg.cpp index 6165fe40e..5f0761483 100644 --- a/src/video_provider_yuv4mpeg.cpp +++ b/src/video_provider_yuv4mpeg.cpp @@ -143,7 +143,7 @@ class YUV4MPEGVideoProvider final : public VideoProvider { public: YUV4MPEGVideoProvider(agi::fs::path const& filename); - std::shared_ptr GetFrame(int n) override; + void GetFrame(int n, VideoFrame &frame) override; void SetColorSpace(std::string const&) override { } int GetFrameCount() const override { return num_frames; } @@ -391,7 +391,7 @@ int YUV4MPEGVideoProvider::IndexFile(uint64_t pos) { return framecount; } -std::shared_ptr YUV4MPEGVideoProvider::GetFrame(int n) { +void YUV4MPEGVideoProvider::GetFrame(int n, VideoFrame &frame) { n = mid(0, n, num_frames - 1); int uv_width = w / 2; @@ -408,9 +408,8 @@ std::shared_ptr YUV4MPEGVideoProvider::GetFrame(int n) { auto src_y = reinterpret_cast(file.read(seek_table[n], luma_sz + chroma_sz * 2)); auto src_u = src_y + luma_sz; auto src_v = src_u + chroma_sz; - std::vector data; - data.resize(w * h * 4); - unsigned char *dst = &data[0]; + frame.data.resize(w * h * 4); + unsigned char *dst = &frame.data[0]; for (int py = 0; py < h; ++py) { for (int px = 0; px < w / 2; ++px) { @@ -433,7 +432,10 @@ std::shared_ptr YUV4MPEGVideoProvider::GetFrame(int n) { } } - return std::make_shared(std::move(data), w, h, w * 4, false); + frame.flipped = false; + frame.width = w; + frame.height = h; + frame.pitch = w * 4; } }