Redesign VideoFrame

Eliminate the manual memory management and shuffle around where the
copies are made to eliminate the need for non-owning video frames.
This commit is contained in:
Thomas Goyne 2013-06-30 20:15:43 -07:00
parent 8760c9a547
commit a1d44cafc1
27 changed files with 207 additions and 485 deletions

View file

@ -294,7 +294,7 @@ struct video_frame_copy : public validator_video_loaded {
STR_HELP("Copy the currently displayed frame to the clipboard") STR_HELP("Copy the currently displayed frame to the clipboard")
void operator()(agi::Context *c) { void operator()(agi::Context *c) {
SetClipboard(wxBitmap(c->videoController->GetFrame(c->videoController->GetFrameN())->GetImage(), 24)); SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN())), 24));
} }
}; };
@ -306,7 +306,7 @@ struct video_frame_copy_raw : public validator_video_loaded {
STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles") STR_HELP("Copy the currently displayed frame to the clipboard, without the subtitles")
void operator()(agi::Context *c) { void operator()(agi::Context *c) {
SetClipboard(wxBitmap(c->videoController->GetFrame(c->videoController->GetFrameN(), true)->GetImage(), 24)); SetClipboard(wxBitmap(GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), true)), 24));
} }
}; };
@ -493,7 +493,7 @@ static void save_snapshot(agi::Context *c, bool raw) {
path = str(boost::format("%s_%03d_%d.png") % basepath.string() % session_shot_count++ % c->videoController->GetFrameN()); path = str(boost::format("%s_%03d_%d.png") % basepath.string() % session_shot_count++ % c->videoController->GetFrameN());
} while (agi::fs::FileExists(path)); } while (agi::fs::FileExists(path));
c->videoController->GetFrame(c->videoController->GetFrameN(), raw)->GetImage().SaveFile(to_wx(path), wxBITMAP_TYPE_PNG); GetImage(*c->videoController->GetFrame(c->videoController->GetFrameN(), raw)).SaveFile(to_wx(path), wxBITMAP_TYPE_PNG);
} }
/// Save the current video frame, with subtitles (if any) /// Save the current video frame, with subtitles (if any)

View file

@ -37,14 +37,14 @@
#include "factory_manager.h" #include "factory_manager.h"
class AssFile; class AssFile;
class AegiVideoFrame; struct VideoFrame;
class SubtitlesProvider { class SubtitlesProvider {
public: public:
virtual ~SubtitlesProvider() { }; virtual ~SubtitlesProvider() { };
virtual void LoadSubtitles(AssFile *subs)=0; virtual void LoadSubtitles(AssFile *subs)=0;
virtual void DrawSubtitles(AegiVideoFrame &dst,double time)=0; virtual void DrawSubtitles(VideoFrame &dst,double time)=0;
}; };
class SubtitlesProviderFactory : public Factory1<SubtitlesProvider, std::string> { class SubtitlesProviderFactory : public Factory1<SubtitlesProvider, std::string> {

View file

@ -37,16 +37,17 @@
#include <libaegisub/exception.h> #include <libaegisub/exception.h>
#include <libaegisub/vfr.h> #include <libaegisub/vfr.h>
#include <memory>
#include <string> #include <string>
class AegiVideoFrame; struct VideoFrame;
class VideoProvider { class VideoProvider {
public: public:
virtual ~VideoProvider() {} virtual ~VideoProvider() {}
/// Override this method to actually get frames /// Override this method to actually get frames
virtual const AegiVideoFrame GetFrame(int n)=0; virtual std::shared_ptr<VideoFrame> GetFrame(int n)=0;
// Override the following methods to get video information: // Override the following methods to get video information:
virtual int GetFrameCount() const=0; ///< Get total number of frames virtual int GetFrameCount() const=0; ///< Get total number of frames

View file

@ -40,6 +40,7 @@
#include "ass_style.h" #include "ass_style.h"
#include "subs_preview.h" #include "subs_preview.h"
#include "include/aegisub/subtitles_provider.h" #include "include/aegisub/subtitles_provider.h"
#include "video_frame.h"
#include "video_provider_dummy.h" #include "video_provider_dummy.h"
#include <libaegisub/util.h> #include <libaegisub/util.h>
@ -102,20 +103,18 @@ void SubtitlesPreview::SetColour(agi::Color col) {
void SubtitlesPreview::UpdateBitmap() { void SubtitlesPreview::UpdateBitmap() {
if (!vid) return; if (!vid) return;
AegiVideoFrame frame; auto frame = vid->GetFrame(0);
frame.CopyFrom(vid->GetFrame(0));
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>(frame.GetImage()); *bmp = static_cast<wxBitmap>(GetImage(*frame));
frame.Clear();
Refresh(); Refresh();
} }

View file

@ -87,21 +87,21 @@ void CSRISubtitlesProvider::LoadSubtitles(AssFile *subs) {
instance = csri_open_file(renderer, tempfile.string().c_str(), nullptr); instance = csri_open_file(renderer, tempfile.string().c_str(), nullptr);
} }
void CSRISubtitlesProvider::DrawSubtitles(AegiVideoFrame &dst,double time) { void CSRISubtitlesProvider::DrawSubtitles(VideoFrame &dst,double time) {
if (!instance) return; if (!instance) return;
csri_frame frame; csri_frame frame;
if (dst.flipped) { if (dst.flipped) {
frame.planes[0] = dst.data + (dst.h-1) * dst.pitch; frame.planes[0] = dst.data.data() + (dst.height-1) * dst.width * 4;
frame.strides[0] = -(signed)dst.pitch; frame.strides[0] = -(signed)dst.width * 4;
} }
else { else {
frame.planes[0] = dst.data; frame.planes[0] = dst.data.data();
frame.strides[0] = dst.pitch; frame.strides[0] = dst.width * 4;
} }
frame.pixfmt = CSRI_F_BGR_; frame.pixfmt = CSRI_F_BGR_;
csri_fmt format = { frame.pixfmt, dst.w, dst.h }; csri_fmt format = { frame.pixfmt, dst.width, dst.height };
std::lock_guard<std::mutex> lock(csri_mutex); std::lock_guard<std::mutex> lock(csri_mutex);
if (!csri_request_fmt(instance, &format)) if (!csri_request_fmt(instance, &format))

View file

@ -56,7 +56,7 @@ public:
~CSRISubtitlesProvider(); ~CSRISubtitlesProvider();
void LoadSubtitles(AssFile *subs); void LoadSubtitles(AssFile *subs);
void DrawSubtitles(AegiVideoFrame &dst, double time); void DrawSubtitles(VideoFrame &dst, double time);
static std::vector<std::string> GetSubTypes(); static std::vector<std::string> GetSubTypes();
}; };

View file

@ -144,8 +144,8 @@ void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) {
#define _b(c) (((c)>>8)&0xFF) #define _b(c) (((c)>>8)&0xFF)
#define _a(c) ((c)&0xFF) #define _a(c) ((c)&0xFF)
void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) { void LibassSubtitlesProvider::DrawSubtitles(VideoFrame &frame,double time) {
ass_set_frame_size(ass_renderer, frame.w, frame.h); ass_set_frame_size(ass_renderer, frame.width, frame.height);
ASS_Image* img = ass_render_frame(ass_renderer, ass_track, int(time * 1000), nullptr); ASS_Image* img = ass_render_frame(ass_renderer, ass_track, int(time * 1000), nullptr);
@ -154,7 +154,7 @@ void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) {
// This is repeated for all of them. // This is repeated for all of them.
using namespace boost::gil; using namespace boost::gil;
auto dst = interleaved_view(frame.w, frame.h, (bgra8_pixel_t*)frame.data, frame.pitch); auto dst = interleaved_view(frame.width, frame.height, (bgra8_pixel_t*)frame.data.data(), frame.width * 4);
if (frame.flipped) if (frame.flipped)
dst = flipped_up_down_view(dst); dst = flipped_up_down_view(dst);

View file

@ -47,7 +47,7 @@ public:
~LibassSubtitlesProvider(); ~LibassSubtitlesProvider();
void LoadSubtitles(AssFile *subs); void LoadSubtitles(AssFile *subs);
void DrawSubtitles(AegiVideoFrame &dst, double time); void DrawSubtitles(VideoFrame &dst, double time);
static void CacheFonts(); static void CacheFonts();
}; };

View file

@ -41,14 +41,11 @@ enum {
SUBS_FILE_ALREADY_LOADED = -2 SUBS_FILE_ALREADY_LOADED = -2
}; };
std::shared_ptr<AegiVideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) { std::shared_ptr<VideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr<AegiVideoFrame> frame(new AegiVideoFrame, [](AegiVideoFrame *frame) { std::shared_ptr<VideoFrame> frame;
frame->Clear();
delete frame;
});
try { try {
frame->CopyFrom(video_provider->GetFrame(frame_number)); frame = video_provider->GetFrame(frame_number);
} }
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); } catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
@ -193,8 +190,8 @@ void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
} }
} }
std::shared_ptr<AegiVideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) { std::shared_ptr<VideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
std::shared_ptr<AegiVideoFrame> ret; std::shared_ptr<VideoFrame> ret;
worker->Sync([&]{ worker->Sync([&]{
ret = ProcFrame(frame, time, raw); ret = ProcFrame(frame, time, raw);
}); });

View file

@ -29,12 +29,12 @@
#include <wx/event.h> #include <wx/event.h>
class AegiVideoFrame;
class AssEntry; class AssEntry;
class AssFile; class AssFile;
class SubtitlesProvider; class SubtitlesProvider;
class VideoProvider; class VideoProvider;
class VideoProviderError; class VideoProviderError;
struct VideoFrame;
namespace agi { namespace dispatch { class Queue; } } namespace agi { namespace dispatch { class Queue; } }
/// @class ThreadedFrameSource /// @class ThreadedFrameSource
@ -61,7 +61,7 @@ class ThreadedFrameSource {
/// currently loaded file is out of date. /// currently loaded file is out of date.
int single_frame; int single_frame;
std::shared_ptr<AegiVideoFrame> ProcFrame(int frame, double time, bool raw = false); std::shared_ptr<VideoFrame> ProcFrame(int frame, double time, bool raw = false);
/// Produce a frame if req_version is still the current version /// Produce a frame if req_version is still the current version
void ProcAsync(uint_fast32_t req_version); void ProcAsync(uint_fast32_t req_version);
@ -98,7 +98,7 @@ public:
/// @brief frame Frame number /// @brief frame Frame number
/// @brief time Exact start time of the frame in seconds /// @brief time Exact start time of the frame in seconds
/// @brief raw Get raw frame without subtitles /// @brief raw Get raw frame without subtitles
std::shared_ptr<AegiVideoFrame> GetFrame(int frame, double time, bool raw = false); std::shared_ptr<VideoFrame> GetFrame(int frame, double time, bool raw = false);
/// Get a reference to the video provider this is using /// Get a reference to the video provider this is using
VideoProvider *GetVideoProvider() const { return video_provider.get(); } VideoProvider *GetVideoProvider() const { return video_provider.get(); }
@ -113,11 +113,11 @@ public:
/// Event which signals that a requested frame is ready /// Event which signals that a requested frame is ready
struct FrameReadyEvent : public wxEvent { struct FrameReadyEvent : public wxEvent {
/// Frame which is ready /// Frame which is ready
std::shared_ptr<AegiVideoFrame> frame; std::shared_ptr<VideoFrame> frame;
/// Time which was used for subtitle rendering /// Time which was used for subtitle rendering
double time; double time;
wxEvent *Clone() const { return new FrameReadyEvent(*this); }; wxEvent *Clone() const { return new FrameReadyEvent(*this); };
FrameReadyEvent(std::shared_ptr<AegiVideoFrame> frame, double time) FrameReadyEvent(std::shared_ptr<VideoFrame> frame, double time)
: frame(frame), time(time) { } : frame(frame), time(time) { }
}; };

View file

@ -289,7 +289,7 @@ void VideoContext::GetFrameAsync(int n) {
provider->RequestFrame(n, TimeAtFrame(n)); provider->RequestFrame(n, TimeAtFrame(n));
} }
std::shared_ptr<AegiVideoFrame> VideoContext::GetFrame(int n, bool raw) { std::shared_ptr<VideoFrame> VideoContext::GetFrame(int n, bool raw) {
return provider->GetFrame(n, TimeAtFrame(n), raw); return provider->GetFrame(n, TimeAtFrame(n), raw);
} }

View file

@ -45,10 +45,10 @@
#include <wx/timer.h> #include <wx/timer.h>
class AegiVideoFrame;
class AssEntry; class AssEntry;
struct SubtitlesProviderErrorEvent; struct SubtitlesProviderErrorEvent;
class ThreadedFrameSource; class ThreadedFrameSource;
struct VideoFrame;
class VideoProvider; class VideoProvider;
struct VideoProviderErrorEvent; struct VideoProviderErrorEvent;
@ -176,7 +176,7 @@ public:
/// @param n Frame number to get /// @param n Frame number to get
/// @param raw If true, subtitles are not rendered on the frame /// @param raw If true, subtitles are not rendered on the frame
/// @return The requested frame /// @return The requested frame
std::shared_ptr<AegiVideoFrame> GetFrame(int n, bool raw = false); std::shared_ptr<VideoFrame> GetFrame(int n, bool raw = false);
/// Asynchronously get a video frame, triggering a EVT_FRAME_READY event when it's ready /// Asynchronously get a video frame, triggering a EVT_FRAME_READY event when it's ready
/// @param n Frame number to get /// @param n Frame number to get

View file

@ -42,7 +42,6 @@
#include <wx/glcanvas.h> #include <wx/glcanvas.h>
// Prototypes // Prototypes
class AegiVideoFrame;
struct FrameReadyEvent; struct FrameReadyEvent;
class VideoContext; class VideoContext;
class VideoOutGL; class VideoOutGL;
@ -50,6 +49,7 @@ class VisualToolBase;
class wxComboBox; class wxComboBox;
class wxTextCtrl; class wxTextCtrl;
class wxToolBar; class wxToolBar;
struct VideoFrame;
namespace agi { namespace agi {
struct Context; struct Context;
@ -104,7 +104,7 @@ class VideoDisplay : public wxGLCanvas {
bool freeSize; bool freeSize;
/// Frame which will replace the currently visible frame on the next render /// Frame which will replace the currently visible frame on the next render
std::shared_ptr<AegiVideoFrame> pending_frame; std::shared_ptr<VideoFrame> pending_frame;
/// @brief Draw an overscan mask /// @brief Draw an overscan mask
/// @param horizontal_percent The percent of the video reserved horizontally /// @param horizontal_percent The percent of the video reserved horizontally

View file

@ -1,146 +1,43 @@
// Copyright (c) 2007, Rodrigo Braz Monteiro // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file video_frame.cpp
/// @brief Wrapper around a frame of video data
/// @ingroup video
///
#include "config.h" #include "config.h"
#include "utils.h"
#include "video_frame.h" #include "video_frame.h"
void AegiVideoFrame::Reset() { #include <boost/gil/gil_all.hpp>
// Zero variables #include <wx/image.h>
data = 0;
pitch = 0;
memSize = 0;
w = 0;
h = 0;
// Set properties VideoFrame::VideoFrame(const unsigned char *data, size_t width, size_t height, size_t pitch, bool flipped)
flipped = false; : data(data, data + width * height * 4)
invertChannels = true; , width(width)
ownMem = true; , height(height)
, pitch(pitch)
, flipped(flipped)
{
} }
AegiVideoFrame::AegiVideoFrame() { wxImage GetImage(VideoFrame const& frame) {
Reset(); using namespace boost::gil;
}
/// @brief Create a solid black frame of the request size and format wxImage img(frame.width, frame.height);
/// @param width auto src = interleaved_view(frame.width, frame.height, (bgra8_pixel_t*)frame.data.data(), frame.pitch);
/// @param height auto dst = interleaved_view(frame.width, frame.height, (rgb8_pixel_t*)img.GetData(), 3 * frame.width);
AegiVideoFrame::AegiVideoFrame(unsigned int width, unsigned int height) { if (frame.flipped)
assert(width > 0 && width < 10000); src = flipped_up_down_view(src);
assert(height > 0 && height < 10000); copy_and_convert_pixels(src, dst);
Reset();
// Set format
w = width;
h = height;
pitch = w * GetBpp();
Allocate();
memset(data, 0, pitch * height);
}
void AegiVideoFrame::Allocate() {
assert(pitch > 0 && pitch < 10000);
assert(w > 0 && w < 10000);
assert(h > 0 && h < 10000);
unsigned int size = pitch * h;
// Reallocate, if necessary
if (memSize != size || !ownMem) {
if (ownMem) {
delete[] data;
}
data = new unsigned char[size];
memSize = size;
}
ownMem = true;
}
void AegiVideoFrame::Clear() {
if (ownMem) delete[] data;
Reset();
}
void AegiVideoFrame::CopyFrom(const AegiVideoFrame &source) {
w = source.w;
h = source.h;
pitch = source.pitch;
Allocate();
memcpy(data, source.data, memSize);
flipped = source.flipped;
invertChannels = source.invertChannels;
}
void AegiVideoFrame::SetTo(const unsigned char *source, unsigned int width, unsigned int height, unsigned int pitch) {
assert(pitch > 0 && pitch < 10000);
assert(width > 0 && width < 10000);
assert(height > 0 && height < 10000);
ownMem = false;
w = width;
h = height;
// Note that despite this cast, the contents of data should still never be modified
data = const_cast<unsigned char*>(source);
this->pitch = pitch;
}
wxImage AegiVideoFrame::GetImage() const {
unsigned char *buf = (unsigned char*)malloc(w*h*3);
if (!buf) throw std::bad_alloc();
int Bpp = GetBpp();
// Convert
for (unsigned int y=0;y<h;y++) {
unsigned char *dst = buf + y*w*3;
const unsigned char *src;
if (flipped) src = data + (h-y-1)*pitch;
else src = data + y*pitch;
for (unsigned int x=0;x<w;x++) {
*dst++ = *(src+2);
*dst++ = *(src+1);
*dst++ = *(src);
src += Bpp;
}
}
wxImage img(w,h);
img.SetData(buf);
return img; return img;
} }

View file

@ -1,94 +1,31 @@
// Copyright (c) 2007, Rodrigo Braz Monteiro // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file video_frame.h #include <vector>
/// @see video_frame.cpp
/// @ingroup video
///
#pragma once class wxImage;
#include <wx/image.h> struct VideoFrame {
std::vector<unsigned char> data;
class AegiVideoFrame { size_t width;
/// Whether the object owns its buffer. If this is false, **data should never be modified size_t height;
bool ownMem; size_t pitch;
/// @brief Reset values to the defaults
///
/// Note that this function DOES NOT deallocate memory.
/// Use Clear() for that
void Reset();
public:
/// @brief Allocate memory if needed
void Allocate();
/// The size in bytes of the frame buffer
unsigned int memSize;
/// Pointer to the data planes
unsigned char *data;
/// Width in pixels
unsigned int w;
/// Height in pixels
unsigned int h;
// Pitch, that is, the number of bytes used by each row.
unsigned int pitch;
/// First row is actually the bottom one
bool flipped; bool flipped;
/// Swap Red and Blue channels (controls RGB versus BGR ordering etc) VideoFrame(const unsigned char *data, size_t width, size_t height, size_t pitch, bool fipped);
bool invertChannels;
AegiVideoFrame();
AegiVideoFrame(unsigned int width, unsigned int height);
// @brief Clear this frame, freeing its memory if nessesary
void Clear();
/// @brief Copy from an AegiVideoFrame
/// @param source The frame to copy from
void CopyFrom(const AegiVideoFrame &source);
/// @brief Set the frame to an externally allocated block of memory
/// @param source Target frame data
/// @param width The frame width in pixels
/// @param height The frame height in pixels
/// @param pitch The frame's pitch
/// @param format The frame's format
void SetTo(const unsigned char *source, unsigned int width, unsigned int height, unsigned int pitch);
/// @brief Get this frame as a wxImage
wxImage GetImage() const;
int GetBpp() const { return 4; };
}; };
wxImage GetImage(VideoFrame const& frame);

View file

@ -265,19 +265,18 @@ void VideoOutGL::InitTextures(int width, int height, GLenum format, int bpp, boo
} }
} }
void VideoOutGL::UploadFrameData(const AegiVideoFrame& frame) { void VideoOutGL::UploadFrameData(VideoFrame const& frame) {
if (frame.h == 0 || frame.w == 0) return; if (frame.height == 0 || frame.width == 0) return;
GLuint format = frame.invertChannels ? GL_BGRA_EXT : GL_RGBA; InitTextures(frame.width, frame.height, GL_BGRA_EXT, 4, frame.flipped);
InitTextures(frame.w, frame.h, format, frame.GetBpp(), frame.flipped);
// Set the row length, needed to be able to upload partial rows // Set the row length, needed to be able to upload partial rows
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.pitch / frame.GetBpp())); CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.pitch / 4));
for (auto& ti : textureList) { for (auto& ti : textureList) {
CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID)); CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, ti.textureID));
CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW, CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ti.sourceW,
ti.sourceH, format, GL_UNSIGNED_BYTE, frame.data + ti.dataOffset)); ti.sourceH, GL_BGRA_EXT, GL_UNSIGNED_BYTE, &frame.data[ti.dataOffset]));
} }
CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); CHECK_ERROR(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));

View file

@ -1,29 +1,16 @@
// Copyright (c) 2009, Thomas Goyne // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
@ -36,12 +23,11 @@
#include <vector> #include <vector>
class AegiVideoFrame; struct VideoFrame;
/// @class VideoOutGL /// @class VideoOutGL
/// @brief OpenGL based video renderer /// @brief OpenGL based video renderer
class VideoOutGL { class VideoOutGL {
private:
struct TextureInfo; struct TextureInfo;
/// The maximum texture size supported by the user's graphics card /// The maximum texture size supported by the user's graphics card
@ -80,7 +66,7 @@ private:
public: public:
/// @brief Set the frame to be displayed when Render() is called /// @brief Set the frame to be displayed when Render() is called
/// @param frame The frame to be displayed /// @param frame The frame to be displayed
void UploadFrameData(const AegiVideoFrame& frame); void UploadFrameData(VideoFrame const& frame);
/// @brief Render a frame /// @brief Render a frame
/// @param x Bottom left x coordinate /// @param x Bottom left x coordinate

View file

@ -38,6 +38,7 @@
#include "video_provider_avs.h" #include "video_provider_avs.h"
#include "options.h" #include "options.h"
#include "video_frame.h"
#include <libaegisub/access.h> #include <libaegisub/access.h>
#include <libaegisub/charset_conv.h> #include <libaegisub/charset_conv.h>
@ -53,13 +54,9 @@
#endif #endif
AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename) AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename)
: last_fnum(-1)
{ {
agi::acs::CheckFileRead(filename); agi::acs::CheckFileRead(filename);
iframe.flipped = true;
iframe.invertChannels = true;
std::lock_guard<std::mutex> lock(avs.GetMutex()); std::lock_guard<std::mutex> lock(avs.GetMutex());
#ifdef _WIN32 #ifdef _WIN32
@ -121,12 +118,12 @@ AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename)
for (size_t i = 0; i < avis.dwLength; i++) { for (size_t i = 0; i < avis.dwLength; i++) {
if (AVIStreamIsKeyFrame(ppavi, i)) if (AVIStreamIsKeyFrame(ppavi, i))
KeyFrames.push_back(i); keyframes.push_back(i);
} }
// If every frame is a keyframe then just discard the keyframe data as it's useless // If every frame is a keyframe then just discard the keyframe data as it's useless
if (KeyFrames.size() == (size_t)avis.dwLength) if (keyframes.size() == (size_t)avis.dwLength)
KeyFrames.clear(); keyframes.clear();
// Clean up // Clean up
stream_release: stream_release:
@ -170,10 +167,6 @@ file_exit:
} }
} }
AvisynthVideoProvider::~AvisynthVideoProvider() {
iframe.Clear();
}
AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) { AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
IScriptEnvironment *env = avs.GetEnv(); IScriptEnvironment *env = avs.GetEnv();
char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str()); char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str());
@ -181,7 +174,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
// Avisynth file, just import it // Avisynth file, just import it
if (agi::fs::HasExtension(filename, "avs")) { if (agi::fs::HasExtension(filename, "avs")) {
LOG_I("avisynth/video") << "Opening .avs file with Import"; LOG_I("avisynth/video") << "Opening .avs file with Import";
decoderName = "Avisynth/Import"; decoder_name = "Avisynth/Import";
return env->Invoke("Import", videoFilename); return env->Invoke("Import", videoFilename);
} }
@ -191,7 +184,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
try { try {
const char *argnames[2] = { 0, "audio" }; const char *argnames[2] = { 0, "audio" };
AVSValue args[2] = { videoFilename, false }; AVSValue args[2] = { videoFilename, false };
decoderName = "Avisynth/AviSource"; decoder_name = "Avisynth/AviSource";
return env->Invoke("AviSource", AVSValue(args,2), argnames); return env->Invoke("AviSource", AVSValue(args,2), argnames);
} }
// On Failure, fallback to DSS // On Failure, fallback to DSS
@ -205,7 +198,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) { if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) {
LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source"; LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source";
auto script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename); auto script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename);
decoderName = "Avisynth/Mpeg2Dec3_Mpeg2Source"; decoder_name = "Avisynth/Mpeg2Dec3_Mpeg2Source";
//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this //if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
if (env->FunctionExists("SetPlanarLegacyAlignment")) { if (env->FunctionExists("SetPlanarLegacyAlignment")) {
@ -218,7 +211,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
// If that fails, try opening it with DGDecode // If that fails, try opening it with DGDecode
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("DGDecode_Mpeg2Source")) { if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("DGDecode_Mpeg2Source")) {
LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source"; LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source";
decoderName = "DGDecode_Mpeg2Source"; decoder_name = "DGDecode_Mpeg2Source";
return env->Invoke("Avisynth/Mpeg2Source", videoFilename); return env->Invoke("Avisynth/Mpeg2Source", videoFilename);
//note that DGDecode will also have issues like if the version is too //note that DGDecode will also have issues like if the version is too
@ -228,7 +221,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Source")) { if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Source")) {
LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source"; LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source";
AVSValue script = env->Invoke("Mpeg2Source", videoFilename); AVSValue script = env->Invoke("Mpeg2Source", videoFilename);
decoderName = "Avisynth/Mpeg2Source"; decoder_name = "Avisynth/Mpeg2Source";
//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this //if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
if (env->FunctionExists("SetPlanarLegacyAlignment")) if (env->FunctionExists("SetPlanarLegacyAlignment"))
@ -247,7 +240,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
// If DSS2 loaded properly, try using it // If DSS2 loaded properly, try using it
if (env->FunctionExists("dss2")) { if (env->FunctionExists("dss2")) {
LOG_I("avisynth/video") << "Opening file with DSS2"; LOG_I("avisynth/video") << "Opening file with DSS2";
decoderName = "Avisynth/DSS2"; decoder_name = "Avisynth/DSS2";
return env->Invoke("DSS2", videoFilename); return env->Invoke("DSS2", videoFilename);
} }
@ -261,7 +254,7 @@ AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
if (env->FunctionExists("DirectShowSource")) { if (env->FunctionExists("DirectShowSource")) {
const char *argnames[3] = { 0, "video", "audio" }; const char *argnames[3] = { 0, "video", "audio" };
AVSValue args[3] = { videoFilename, true, false }; AVSValue args[3] = { videoFilename, true, false };
decoderName = "Avisynth/DirectShowSource"; decoder_name = "Avisynth/DirectShowSource";
warning = "Warning! The file is being opened using Avisynth's DirectShowSource, which has unreliable seeking. Frame numbers might not match the real number. PROCEED AT YOUR OWN RISK!"; warning = "Warning! The file is being opened using Avisynth's DirectShowSource, which has unreliable seeking. Frame numbers might not match the real number. PROCEED AT YOUR OWN RISK!";
LOG_I("avisynth/video") << "Opening file with DirectShowSource"; LOG_I("avisynth/video") << "Opening file with DirectShowSource";
return env->Invoke("DirectShowSource", AVSValue(args,3), argnames); return env->Invoke("DirectShowSource", AVSValue(args,3), argnames);
@ -272,21 +265,10 @@ 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");
} }
const AegiVideoFrame AvisynthVideoProvider::GetFrame(int n) { std::shared_ptr<VideoFrame> AvisynthVideoProvider::GetFrame(int n) {
if (n == last_fnum) return iframe;
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());
iframe.pitch = frame->GetPitch(); return std::make_shared<VideoFrame>(frame->GetReadPtr(), frame->GetRowSize() / 4, frame->GetHeight(), frame->GetPitch(), true);
iframe.w = frame->GetRowSize() / (vi.BitsPerPixel() / 8);
iframe.h = frame->GetHeight();
iframe.Allocate();
memcpy(iframe.data, frame->GetReadPtr(), iframe.pitch * iframe.h);
last_fnum = n;
return iframe;
} }
#endif // HAVE_AVISYNTH #endif // HAVE_AVISYNTH

View file

@ -35,40 +35,37 @@
#ifdef WITH_AVISYNTH #ifdef WITH_AVISYNTH
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#define VideoFrame AVSVideoFrame
#include "avisynth.h" #include "avisynth.h"
#undef VideoFrame
#include "avisynth_wrap.h" #include "avisynth_wrap.h"
#include "video_frame.h"
class AvisynthVideoProvider: public VideoProvider { class AvisynthVideoProvider: public VideoProvider {
AviSynthWrapper avs; AviSynthWrapper avs;
AegiVideoFrame iframe; std::string decoder_name;
std::string decoderName;
agi::vfr::Framerate fps; agi::vfr::Framerate fps;
std::vector<int> KeyFrames; std::vector<int> keyframes;
std::string warning; std::string warning;
std::string colorspace; std::string colorspace;
PClip RGB32Video; PClip RGB32Video;
VideoInfo vi; VideoInfo vi;
int last_fnum;
AVSValue Open(agi::fs::path const& filename); AVSValue Open(agi::fs::path const& filename);
public: public:
AvisynthVideoProvider(agi::fs::path const& filename); AvisynthVideoProvider(agi::fs::path const& filename);
~AvisynthVideoProvider();
const AegiVideoFrame GetFrame(int n); std::shared_ptr<VideoFrame> GetFrame(int n);
int GetFrameCount() const { return vi.num_frames; }; int GetFrameCount() const { return vi.num_frames; }
agi::vfr::Framerate GetFPS() const { return fps; }; agi::vfr::Framerate GetFPS() const { return fps; }
int GetWidth() const { return vi.width; }; int GetWidth() const { return vi.width; }
int GetHeight() const { return vi.height; }; int GetHeight() const { return vi.height; }
double GetDAR() const { return 0; } double GetDAR() const { return 0; }
std::vector<int> GetKeyFrames() const { return KeyFrames; }; std::vector<int> GetKeyFrames() const { return keyframes; }
std::string GetWarning() const { return warning; } std::string GetWarning() const { return warning; }
std::string GetDecoderName() const { return decoderName; } std::string GetDecoderName() const { return decoder_name; }
std::string GetColorSpace() const { return colorspace; } std::string GetColorSpace() const { return colorspace; }
}; };
#endif #endif

View file

@ -1,37 +1,19 @@
// Copyright (c) 2008, Rodrigo Braz Monteiro // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file video_provider_cache.cpp
/// @brief Aggregate video provider caching previously requested frames
/// @ingroup video_input
///
#include "config.h" #include "config.h"
#include "video_provider_cache.h" #include "video_provider_cache.h"
@ -40,51 +22,44 @@
#include "video_frame.h" #include "video_frame.h"
#include <algorithm> #include <algorithm>
#include <functional>
/// A video frame and its frame number /// A video frame and its frame number
struct CachedFrame : public AegiVideoFrame { struct CachedFrame : public VideoFrame {
int frame_number; 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)
{
}
}; };
VideoProviderCache::VideoProviderCache(std::unique_ptr<VideoProvider>&& parent) VideoProviderCache::VideoProviderCache(std::unique_ptr<VideoProvider> parent)
: master(std::move(parent)) : master(std::move(parent))
, max_cache_size(OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20) // convert MB to bytes , max_cache_size(OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20) // convert MB to bytes
{ {
} }
VideoProviderCache::~VideoProviderCache() { VideoProviderCache::~VideoProviderCache() {
for_each(cache.begin(), cache.end(), std::mem_fn(&AegiVideoFrame::Clear));
} }
const AegiVideoFrame VideoProviderCache::GetFrame(int n) { std::shared_ptr<VideoFrame> VideoProviderCache::GetFrame(int n) {
size_t total_size = 0; size_t total_size = 0;
// See if frame is cached
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.push_front(*cur); cache.splice(cache.begin(), cache, cur); // Move to front
cache.erase(cur); return std::make_shared<VideoFrame>(cache.front());
return cache.front();
} }
total_size += cur->memSize; total_size += cur->data.size();
} }
// Not cached, retrieve it auto frame = master->GetFrame(n);
const AegiVideoFrame frame = master->GetFrame(n);
// Cache full, use oldest frame if (total_size >= max_cache_size)
if (total_size >= max_cache_size) {
cache.push_front(cache.back());
cache.pop_back(); cache.pop_back();
} cache.emplace_front(n, *frame);
// Cache not full, insert new one
else
cache.push_front(CachedFrame());
// Cache return frame;
cache.front().frame_number = n;
cache.front().CopyFrom(frame);
return cache.front();
} }

View file

@ -1,37 +1,19 @@
// Copyright (c) 2008, Rodrigo Braz Monteiro, Fredrik Mellbin // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file video_provider_cache.h
/// @see video_provider_cache.cpp
/// @ingroup video_input
///
#include <boost/container/list.hpp> #include <boost/container/list.hpp>
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
@ -54,10 +36,10 @@ class VideoProviderCache : public VideoProvider {
boost::container::list<CachedFrame> cache; boost::container::list<CachedFrame> cache;
public: public:
VideoProviderCache(std::unique_ptr<VideoProvider>&& master); VideoProviderCache(std::unique_ptr<VideoProvider> master);
~VideoProviderCache(); ~VideoProviderCache();
const AegiVideoFrame GetFrame(int n); std::shared_ptr<VideoFrame> GetFrame(int n);
int GetFrameCount() const { return master->GetFrameCount(); } int GetFrameCount() const { return master->GetFrameCount(); }
int GetWidth() const { return master->GetWidth(); } int GetWidth() const { return master->GetWidth(); }

View file

@ -37,6 +37,7 @@
#include "video_provider_dummy.h" #include "video_provider_dummy.h"
#include "colorspace.h" #include "colorspace.h"
#include "video_frame.h"
#include <libaegisub/color.h> #include <libaegisub/color.h>
#include <libaegisub/fs.h> #include <libaegisub/fs.h>
@ -46,18 +47,21 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/gil/gil_all.hpp>
void DummyVideoProvider::Create(double fps, int frames, int width, int height, unsigned char red, unsigned char green, unsigned char blue, bool pattern) { void DummyVideoProvider::Create(double fps, int frames, int width, int height, unsigned char red, unsigned char green, unsigned char blue, bool pattern) {
this->framecount = frames; this->framecount = frames;
this->fps = fps; this->fps = fps;
this->width = width; this->width = width;
this->height = height; this->height = height;
this->frame = AegiVideoFrame(width, height); data.resize(width * height * 4);
unsigned char *dst = frame.data; using namespace boost::gil;
unsigned char colors[2][4] = { auto dst = interleaved_view(width, height, (bgra8_pixel_t*)data.data(), 4 * width);
{ blue, green, red, 0 },
{ 0, 0, 0, 0 } bgra8_pixel_t colors[2] = {
bgra8_pixel_t(blue, green, red, 0),
bgra8_pixel_t(blue, green, red, 0)
}; };
if (pattern) { if (pattern) {
@ -66,20 +70,17 @@ void DummyVideoProvider::Create(double fps, int frames, int width, int height, u
rgb_to_hsl(red, blue, green, &h, &s, &l); rgb_to_hsl(red, blue, green, &h, &s, &l);
l += 24; l += 24;
if (l < 24) l -= 48; if (l < 24) l -= 48;
hsl_to_rgb(h, s, l, &colors[1][2], &colors[1][1], &colors[1][0]); hsl_to_rgb(h, s, l, &red, &blue, &green);
colors[1] = bgra8_pixel_t(blue, green, red, 0);
// Divide into a 8x8 grid and use light colours when row % 2 != col % 2 // Divide into a 8x8 grid and use light colours when row % 2 != col % 2
int ppitch = frame.pitch / frame.GetBpp(); auto out = dst.begin();
for (unsigned int y = 0; y < frame.h; ++y) { for (int y = 0; y < height; ++y)
for (int x = 0; x < ppitch; ++x) { for (int x = 0; x < width; ++x)
memcpy(dst, colors[((y / 8) & 1) != ((x / 8) & 1)], 4); *out++ = colors[((y / 8) & 1) != ((x / 8) & 1)];
dst += 4;
}
}
} }
else { else {
for (int i = frame.pitch * frame.h / frame.GetBpp() - 1; i >= 0; --i) fill_pixels(dst, colors[0]);
memcpy(dst + i * 4, colors[0], 4);
} }
} }
@ -115,10 +116,10 @@ DummyVideoProvider::DummyVideoProvider(double fps, int frames, int width, int he
Create(fps, frames, width, height, colour.r, colour.g, colour.b, pattern); Create(fps, frames, width, height, colour.r, colour.g, colour.b, pattern);
} }
DummyVideoProvider::~DummyVideoProvider() {
frame.Clear();
}
std::string DummyVideoProvider::MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern) { std::string DummyVideoProvider::MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern) {
return str(boost::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 str(boost::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) {
return std::make_shared<VideoFrame>(data.data(), width, height, width * 4, false);
}

View file

@ -33,7 +33,6 @@
/// ///
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#include "video_frame.h"
namespace agi { struct Color; } namespace agi { struct Color; }
@ -48,8 +47,8 @@ class DummyVideoProvider : public VideoProvider {
int width; ///< Width in pixels int width; ///< Width in pixels
int height; ///< Height in pixels int height; ///< Height in pixels
/// The single image which is returned for all frames /// The data for the image returned for all frames
AegiVideoFrame frame; std::vector<unsigned char> data;
/// Create the dummy frame from the given parameters /// Create the dummy frame from the given parameters
/// @param fps Frame rate of the dummy video /// @param fps Frame rate of the dummy video
@ -75,14 +74,12 @@ public:
/// @param pattern Use a checkerboard pattern rather than a solid colour /// @param pattern Use a checkerboard pattern rather than a solid colour
DummyVideoProvider(double fps, int frames, int width, int height, agi::Color colour, bool pattern); DummyVideoProvider(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
/// Destructor
~DummyVideoProvider();
/// Make a fake filename which when passed to the constructor taking a /// Make a fake filename which when passed to the constructor taking a
/// 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);
const AegiVideoFrame GetFrame(int n) { return frame; } std::shared_ptr<VideoFrame> GetFrame(int n);
int GetFrameCount() const { return framecount; } int GetFrameCount() const { return framecount; }
int GetWidth() const { return width; } int GetWidth() const { return width; }
int GetHeight() const { return height; } int GetHeight() const { return height; }

View file

@ -41,6 +41,7 @@
#include "options.h" #include "options.h"
#include "utils.h" #include "utils.h"
#include "video_context.h" #include "video_context.h"
#include "video_frame.h"
#include <libaegisub/fs.h> #include <libaegisub/fs.h>
@ -52,7 +53,6 @@ FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(agi::fs::path const& filena
, VideoInfo(nullptr) , VideoInfo(nullptr)
, Width(-1) , Width(-1)
, Height(-1) , Height(-1)
, FrameNumber(-1)
{ {
ErrInfo.Buffer = FFMSErrMsg; ErrInfo.Buffer = FFMSErrMsg;
ErrInfo.BufferSize = sizeof(FFMSErrMsg); ErrInfo.BufferSize = sizeof(FFMSErrMsg);
@ -238,20 +238,16 @@ void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename) {
Timecodes = 25.0; Timecodes = 25.0;
else else
Timecodes = agi::vfr::Framerate(TimecodesVector); Timecodes = agi::vfr::Framerate(TimecodesVector);
FrameNumber = 0;
} }
const AegiVideoFrame FFmpegSourceVideoProvider::GetFrame(int n) { std::shared_ptr<VideoFrame> FFmpegSourceVideoProvider::GetFrame(int n) {
FrameNumber = mid(0, n, GetFrameCount() - 1); n = mid(0, n, GetFrameCount() - 1);
// decode frame auto frame = FFMS_GetFrame(VideoSource, n, &ErrInfo);
const FFMS_Frame *SrcFrame = FFMS_GetFrame(VideoSource, FrameNumber, &ErrInfo); if (!frame)
if (!SrcFrame)
throw VideoDecodeError(std::string("Failed to retrieve frame: ") + ErrInfo.Buffer); throw VideoDecodeError(std::string("Failed to retrieve frame: ") + ErrInfo.Buffer);
CurFrame.SetTo(SrcFrame->Data[0], Width, Height, SrcFrame->Linesize[0]); return std::make_shared<VideoFrame>(frame->Data[0], Width, Height, frame->Linesize[0], false);
return CurFrame;
} }
#endif /* WITH_FFMS2 */ #endif /* WITH_FFMS2 */

View file

@ -33,11 +33,8 @@
/// ///
#ifdef WITH_FFMS2 #ifdef WITH_FFMS2
#include <vector>
#include "ffmpegsource_common.h" #include "ffmpegsource_common.h"
#include "include/aegisub/video_provider.h" #include "include/aegisub/video_provider.h"
#include "video_frame.h"
/// @class FFmpegSourceVideoProvider /// @class FFmpegSourceVideoProvider
/// @brief Implements video loading through the FFMS library. /// @brief Implements video loading through the FFMS library.
@ -49,13 +46,10 @@ class FFmpegSourceVideoProvider : public VideoProvider, FFmpegSourceProvider {
int Width; ///< width in pixels int Width; ///< width in pixels
int Height; ///< height in pixels int Height; ///< height in pixels
double DAR; ///< display aspect ratio double DAR; ///< display aspect ratio
int FrameNumber; ///< current framenumber
std::vector<int> KeyFramesList; ///< list of keyframes std::vector<int> KeyFramesList; ///< list of keyframes
agi::vfr::Framerate Timecodes; ///< vfr object agi::vfr::Framerate Timecodes; ///< vfr object
std::string ColorSpace; ///< Colorspace name std::string ColorSpace; ///< Colorspace name
AegiVideoFrame CurFrame; ///< current video frame
char FFMSErrMsg[1024]; ///< FFMS error message char FFMSErrMsg[1024]; ///< FFMS error message
FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
@ -64,22 +58,16 @@ class FFmpegSourceVideoProvider : public VideoProvider, FFmpegSourceProvider {
public: public:
FFmpegSourceVideoProvider(agi::fs::path const& filename); FFmpegSourceVideoProvider(agi::fs::path const& filename);
const AegiVideoFrame GetFrame(int n); std::shared_ptr<VideoFrame> GetFrame(int n);
int GetFrameCount() const { return VideoInfo->NumFrames; } int GetFrameCount() const { return VideoInfo->NumFrames; }
int GetWidth() const { return Width; } int GetWidth() const { return Width; }
int GetHeight() const { return Height; } int GetHeight() const { return Height; }
double GetDAR() const { return DAR; } double GetDAR() const { return DAR; }
agi::vfr::Framerate GetFPS() const { return Timecodes; } agi::vfr::Framerate GetFPS() const { return Timecodes; }
std::string GetColorSpace() const { return ColorSpace; } std::string GetColorSpace() const { return ColorSpace; }
/// @brief Gets a list of keyframes
/// @return Returns a wxArrayInt of keyframes.
std::vector<int> GetKeyFrames() const { return KeyFramesList; }; std::vector<int> GetKeyFrames() const { return KeyFramesList; };
std::string GetDecoderName() const { return "FFmpegSource"; } std::string GetDecoderName() const { return "FFmpegSource"; }
/// @brief Gets the desired cache behavior.
/// @return Returns true.
bool WantsCaching() const { return true; } bool WantsCaching() const { return true; }
}; };
#endif /* WITH_FFMS2 */ #endif /* WITH_FFMS2 */

View file

@ -64,7 +64,6 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename)
, w (0) , w (0)
, h (0) , h (0)
, num_frames(-1) , num_frames(-1)
, cur_fn(-1)
, pixfmt(Y4M_PIXFMT_NONE) , pixfmt(Y4M_PIXFMT_NONE)
, imode(Y4M_ILACE_NOTSET) , imode(Y4M_ILACE_NOTSET)
{ {
@ -113,7 +112,6 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename)
num_frames = IndexFile(); num_frames = IndexFile();
if (num_frames <= 0 || seek_table.empty()) if (num_frames <= 0 || seek_table.empty())
throw VideoOpenError("Unable to determine file length"); throw VideoOpenError("Unable to determine file length");
cur_fn = 0;
fseeko(sf, 0, SEEK_SET); fseeko(sf, 0, SEEK_SET);
} }
@ -123,7 +121,6 @@ YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(agi::fs::path const& filename)
} }
} }
/// @brief Destructor
YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() { YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() {
fclose(sf); fclose(sf);
} }
@ -347,11 +344,8 @@ static FORCEINLINE int clamp(int x) {
return x; return x;
} }
/// @brief Gets a given frame std::shared_ptr<VideoFrame> YUV4MPEGVideoProvider::GetFrame(int n) {
/// @param n The frame number to return n = mid(0, n, num_frames - 1);
/// @return The video frame
const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) {
cur_fn = mid(0, n, num_frames - 1);
int uv_width = w / 2; int uv_width = w / 2;
switch (pixfmt) { switch (pixfmt) {
@ -380,17 +374,12 @@ const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) {
throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes"; throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes";
} }
AegiVideoFrame dst_frame;
dst_frame.invertChannels = true;
dst_frame.w = w;
dst_frame.h = h;
dst_frame.pitch = w * 4;
dst_frame.Allocate();
const unsigned char *src_y = &planes[0][0]; const unsigned char *src_y = &planes[0][0];
const unsigned char *src_u = &planes[1][0]; const unsigned char *src_u = &planes[1][0];
const unsigned char *src_v = &planes[2][0]; const unsigned char *src_v = &planes[2][0];
unsigned char *dst = dst_frame.data; std::vector<unsigned char> data;
data.resize(w * h * 4);
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) {
@ -413,5 +402,5 @@ const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) {
} }
} }
return dst_frame; return std::make_shared<VideoFrame>(data.data(), w, h, w * 4, false);
} }

View file

@ -108,7 +108,6 @@ class YUV4MPEGVideoProvider : public VideoProvider {
int frame_sz; /// size of each frame in bytes int frame_sz; /// size of each frame in bytes
int luma_sz; /// size of the luma plane of each frame, in bytes int luma_sz; /// size of the luma plane of each frame, in bytes
int chroma_sz; /// size of one of the two chroma planes of each frame, in bytes int chroma_sz; /// size of one of the two chroma planes of each frame, in bytes
int cur_fn; /// current frame number
Y4M_PixelFormat pixfmt; /// colorspace/pixel format Y4M_PixelFormat pixfmt; /// colorspace/pixel format
Y4M_InterlacingMode imode; /// interlacing mode (for the entire stream) Y4M_InterlacingMode imode; /// interlacing mode (for the entire stream)
@ -133,7 +132,7 @@ public:
YUV4MPEGVideoProvider(agi::fs::path const& filename); YUV4MPEGVideoProvider(agi::fs::path const& filename);
~YUV4MPEGVideoProvider(); ~YUV4MPEGVideoProvider();
const AegiVideoFrame GetFrame(int n); std::shared_ptr<VideoFrame> GetFrame(int n);
int GetFrameCount() const { return num_frames; } int GetFrameCount() const { return num_frames; }
int GetWidth() const { return w; } int GetWidth() const { return w; }