mirror of https://github.com/odrling/Aegisub
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%.
This commit is contained in:
parent
a574d6ac67
commit
4bdccb889c
|
@ -20,6 +20,7 @@
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "export_fixstyle.h"
|
#include "export_fixstyle.h"
|
||||||
#include "include/aegisub/subtitles_provider.h"
|
#include "include/aegisub/subtitles_provider.h"
|
||||||
|
#include "video_frame.h"
|
||||||
#include "video_provider_manager.h"
|
#include "video_provider_manager.h"
|
||||||
|
|
||||||
#include <libaegisub/dispatch.h>
|
#include <libaegisub/dispatch.h>
|
||||||
|
@ -30,10 +31,22 @@ enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) {
|
std::shared_ptr<VideoFrame> 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<VideoFrame> frame;
|
std::shared_ptr<VideoFrame> frame;
|
||||||
|
for (auto& buffer : buffers) {
|
||||||
|
if (buffer.use_count() == 1) {
|
||||||
|
frame = buffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
frame = std::make_shared<VideoFrame>();
|
||||||
|
buffers.push_back(frame);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
frame = source_provider->GetFrame(frame_number);
|
source_provider->GetFrame(frame_number, *frame);
|
||||||
}
|
}
|
||||||
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
|
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,8 @@ class AsyncVideoProvider {
|
||||||
/// they can be rendered
|
/// they can be rendered
|
||||||
std::atomic<uint_fast32_t> version{ 0 };
|
std::atomic<uint_fast32_t> version{ 0 };
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<VideoFrame>> buffers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Load the passed subtitle file
|
/// @brief Load the passed subtitle file
|
||||||
/// @param subs File to load
|
/// @param subs File to load
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
#include <libaegisub/vfr.h>
|
#include <libaegisub/vfr.h>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct VideoFrame;
|
struct VideoFrame;
|
||||||
|
@ -47,7 +46,7 @@ public:
|
||||||
virtual ~VideoProvider() = default;
|
virtual ~VideoProvider() = default;
|
||||||
|
|
||||||
/// Override this method to actually get frames
|
/// Override this method to actually get frames
|
||||||
virtual std::shared_ptr<VideoFrame> GetFrame(int n)=0;
|
virtual void GetFrame(int n, VideoFrame &frame)=0;
|
||||||
|
|
||||||
/// Set the YCbCr matrix to the specified one
|
/// Set the YCbCr matrix to the specified one
|
||||||
///
|
///
|
||||||
|
|
|
@ -101,18 +101,19 @@ void SubtitlesPreview::SetColour(agi::Color col) {
|
||||||
void SubtitlesPreview::UpdateBitmap() {
|
void SubtitlesPreview::UpdateBitmap() {
|
||||||
if (!vid) return;
|
if (!vid) return;
|
||||||
|
|
||||||
auto frame = vid->GetFrame(0);
|
VideoFrame frame;
|
||||||
|
vid->GetFrame(0, frame);
|
||||||
|
|
||||||
if (provider) {
|
if (provider) {
|
||||||
try {
|
try {
|
||||||
provider->LoadSubtitles(sub_file.get());
|
provider->LoadSubtitles(sub_file.get());
|
||||||
provider->DrawSubtitles(*frame, 0.1);
|
provider->DrawSubtitles(frame, 0.1);
|
||||||
}
|
}
|
||||||
catch (...) { }
|
catch (...) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert frame to bitmap
|
// Convert frame to bitmap
|
||||||
*bmp = static_cast<wxBitmap>(GetImage(*frame));
|
*bmp = static_cast<wxBitmap>(GetImage(frame));
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,24 +19,6 @@
|
||||||
#include <boost/gil/gil_all.hpp>
|
#include <boost/gil/gil_all.hpp>
|
||||||
#include <wx/image.h>
|
#include <wx/image.h>
|
||||||
|
|
||||||
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<unsigned char>&& 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 {
|
namespace {
|
||||||
// We actually have bgr_, not bgra, so we need a custom converter which ignores the alpha channel
|
// We actually have bgr_, not bgra, so we need a custom converter which ignores the alpha channel
|
||||||
struct color_converter {
|
struct color_converter {
|
||||||
|
|
|
@ -24,9 +24,6 @@ struct VideoFrame {
|
||||||
size_t height;
|
size_t height;
|
||||||
size_t pitch;
|
size_t pitch;
|
||||||
bool flipped;
|
bool flipped;
|
||||||
|
|
||||||
VideoFrame(const unsigned char *data, size_t width, size_t height, size_t pitch, bool fipped);
|
|
||||||
VideoFrame(std::vector<unsigned char>&& data, size_t width, size_t height, size_t pitch, bool fipped);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wxImage GetImage(VideoFrame const& frame);
|
wxImage GetImage(VideoFrame const& frame);
|
||||||
|
|
|
@ -73,7 +73,7 @@ class AvisynthVideoProvider: public VideoProvider {
|
||||||
public:
|
public:
|
||||||
AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix);
|
AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix);
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n);
|
void GetFrame(int n, VideoFrame &frame) override;
|
||||||
|
|
||||||
void SetColorSpace(std::string const& matrix) override {
|
void SetColorSpace(std::string const& matrix) override {
|
||||||
// Can't really do anything if this fails
|
// 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");
|
throw VideoNotSupported("No function suitable for opening the video found");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> AvisynthVideoProvider::GetFrame(int n) {
|
void AvisynthVideoProvider::GetFrame(int n, VideoFrame &out) {
|
||||||
std::lock_guard<std::mutex> lock(avs.GetMutex());
|
std::lock_guard<std::mutex> lock(avs.GetMutex());
|
||||||
|
|
||||||
auto frame = RGB32Video->GetFrame(n, avs.GetEnv());
|
auto frame = RGB32Video->GetFrame(n, avs.GetEnv());
|
||||||
return std::make_shared<VideoFrame>(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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,14 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
/// A video frame and its frame number
|
/// A video frame and its frame number
|
||||||
struct CachedFrame final : public VideoFrame {
|
struct CachedFrame {
|
||||||
|
VideoFrame frame;
|
||||||
int frame_number;
|
int frame_number;
|
||||||
|
|
||||||
CachedFrame(int frame_number, VideoFrame const& frame)
|
CachedFrame(VideoFrame const& frame, int frame_number)
|
||||||
: VideoFrame(frame.data.data(), frame.width, frame.height, frame.pitch, frame.flipped)
|
: frame(frame), frame_number(frame_number) { }
|
||||||
, frame_number(frame_number)
|
|
||||||
{
|
CachedFrame(CachedFrame const&) = delete;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @class VideoProviderCache
|
/// @class VideoProviderCache
|
||||||
|
@ -45,19 +45,15 @@ class VideoProviderCache final : public VideoProvider {
|
||||||
///
|
///
|
||||||
/// Note that this is a soft limit. The cache stops allocating new frames
|
/// 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
|
/// 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
|
/// Cache of video frames with the most recently used ones at the front
|
||||||
std::list<CachedFrame> cache;
|
std::list<CachedFrame> cache;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VideoProviderCache(std::unique_ptr<VideoProvider> master)
|
VideoProviderCache(std::unique_ptr<VideoProvider> master) : master(std::move(master)) { }
|
||||||
: master(std::move(master))
|
|
||||||
, max_cache_size(OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20) // convert MB to bytes
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n) override;
|
void GetFrame(int n, VideoFrame &frame) override;
|
||||||
|
|
||||||
void SetColorSpace(std::string const& m) override {
|
void SetColorSpace(std::string const& m) override {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
|
@ -78,25 +74,28 @@ public:
|
||||||
bool HasAudio() const override { return master->HasAudio(); }
|
bool HasAudio() const override { return master->HasAudio(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> VideoProviderCache::GetFrame(int n) {
|
void VideoProviderCache::GetFrame(int n, VideoFrame &out) {
|
||||||
size_t total_size = 0;
|
size_t total_size = 0;
|
||||||
|
|
||||||
for (auto cur = cache.begin(); cur != cache.end(); ++cur) {
|
for (auto cur = cache.begin(); cur != cache.end(); ++cur) {
|
||||||
if (cur->frame_number == n) {
|
if (cur->frame_number == n) {
|
||||||
cache.splice(cache.begin(), cache, cur); // Move to front
|
cache.splice(cache.begin(), cache, cur); // Move to front
|
||||||
return std::make_shared<VideoFrame>(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)
|
if (total_size >= max_cache_size) {
|
||||||
cache.pop_back();
|
cache.splice(cache.begin(), cache, --cache.end()); // Move last to front
|
||||||
cache.emplace_front(n, *frame);
|
cache.front().frame_number = n;
|
||||||
|
cache.front().frame = out;
|
||||||
return frame;
|
}
|
||||||
|
else
|
||||||
|
cache.emplace_front(out, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" : ""));
|
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<VideoFrame> DummyVideoProvider::GetFrame(int) {
|
void DummyVideoProvider::GetFrame(int, VideoFrame &frame) {
|
||||||
return std::make_shared<VideoFrame>(data.data(), width, height, width * 4, false);
|
frame.data = data;
|
||||||
|
frame.width = width;
|
||||||
|
frame.height = height;
|
||||||
|
frame.pitch = width * 4;
|
||||||
|
frame.flipped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace agi { class BackgroundRunner; }
|
namespace agi { class BackgroundRunner; }
|
||||||
|
|
|
@ -64,7 +64,7 @@ public:
|
||||||
/// string will result in a video with the given parameters
|
/// 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);
|
static std::string MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n) override;
|
void GetFrame(int n, VideoFrame &frame) override;
|
||||||
void SetColorSpace(std::string const&) override { }
|
void SetColorSpace(std::string const&) override { }
|
||||||
|
|
||||||
int GetFrameCount() const override { return framecount; }
|
int GetFrameCount() const override { return framecount; }
|
||||||
|
|
|
@ -70,7 +70,7 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid
|
||||||
public:
|
public:
|
||||||
FFmpegSourceVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
|
FFmpegSourceVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n) override;
|
void GetFrame(int n, VideoFrame &out) override;
|
||||||
|
|
||||||
void SetColorSpace(std::string const& matrix) override {
|
void SetColorSpace(std::string const& matrix) override {
|
||||||
#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0)
|
#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);
|
Timecodes = agi::vfr::Framerate(TimecodesVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> FFmpegSourceVideoProvider::GetFrame(int n) {
|
void FFmpegSourceVideoProvider::GetFrame(int n, VideoFrame &out) {
|
||||||
n = mid(0, n, GetFrameCount() - 1);
|
n = mid(0, n, GetFrameCount() - 1);
|
||||||
|
|
||||||
auto frame = FFMS_GetFrame(VideoSource, n, &ErrInfo);
|
auto frame = FFMS_GetFrame(VideoSource, n, &ErrInfo);
|
||||||
if (!frame)
|
if (!frame)
|
||||||
throw VideoDecodeError(std::string("Failed to retrieve frame: ") + ErrInfo.Buffer);
|
throw VideoDecodeError(std::string("Failed to retrieve frame: ") + ErrInfo.Buffer);
|
||||||
|
|
||||||
return std::make_shared<VideoFrame>(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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ class YUV4MPEGVideoProvider final : public VideoProvider {
|
||||||
public:
|
public:
|
||||||
YUV4MPEGVideoProvider(agi::fs::path const& filename);
|
YUV4MPEGVideoProvider(agi::fs::path const& filename);
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> GetFrame(int n) override;
|
void GetFrame(int n, VideoFrame &frame) override;
|
||||||
void SetColorSpace(std::string const&) override { }
|
void SetColorSpace(std::string const&) override { }
|
||||||
|
|
||||||
int GetFrameCount() const override { return num_frames; }
|
int GetFrameCount() const override { return num_frames; }
|
||||||
|
@ -391,7 +391,7 @@ int YUV4MPEGVideoProvider::IndexFile(uint64_t pos) {
|
||||||
return framecount;
|
return framecount;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
|
void YUV4MPEGVideoProvider::GetFrame(int n, VideoFrame &frame) {
|
||||||
n = mid(0, n, num_frames - 1);
|
n = mid(0, n, num_frames - 1);
|
||||||
|
|
||||||
int uv_width = w / 2;
|
int uv_width = w / 2;
|
||||||
|
@ -408,9 +408,8 @@ std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
|
||||||
auto src_y = reinterpret_cast<const unsigned char *>(file.read(seek_table[n], luma_sz + chroma_sz * 2));
|
auto src_y = reinterpret_cast<const unsigned char *>(file.read(seek_table[n], luma_sz + chroma_sz * 2));
|
||||||
auto src_u = src_y + luma_sz;
|
auto src_u = src_y + luma_sz;
|
||||||
auto src_v = src_u + chroma_sz;
|
auto src_v = src_u + chroma_sz;
|
||||||
std::vector<unsigned char> data;
|
frame.data.resize(w * h * 4);
|
||||||
data.resize(w * h * 4);
|
unsigned char *dst = &frame.data[0];
|
||||||
unsigned char *dst = &data[0];
|
|
||||||
|
|
||||||
for (int py = 0; py < h; ++py) {
|
for (int py = 0; py < h; ++py) {
|
||||||
for (int px = 0; px < w / 2; ++px) {
|
for (int px = 0; px < w / 2; ++px) {
|
||||||
|
@ -433,7 +432,10 @@ std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared<VideoFrame>(std::move(data), w, h, w * 4, false);
|
frame.flipped = false;
|
||||||
|
frame.width = w;
|
||||||
|
frame.height = h;
|
||||||
|
frame.pitch = w * 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue