Aegisub/aegisub/src/threaded_frame_source.cpp
Thomas Goyne 397b234fba Move video decoding and subtitle rendering to a worker thread
Makes the click event handler for the grid no longer slow when video
autoscroll is enabled, making it harder to accidently select multiple
lines.

Makes seeking speed no longer limited by decoding/rendering speed;
seeking faster than video can be decoded simply results in dropped
frames.

Makes editing the file while a slow-rendering frame is visible far more
responsive.

Originally committed to SVN as r4702.
2010-07-23 05:58:39 +00:00

213 lines
6.7 KiB
C++

// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from 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/
//
// $Id$
/// @file threaded_frame_source.cpp
/// @see threaded_frame_source.h
/// @ingroup video
///
#include "threaded_frame_source.h"
#ifndef AGI_PRE
#include <iterator>
#include <functional>
#endif
#include "ass_dialogue.h"
#include "ass_exporter.h"
#include "ass_file.h"
#include "compat.h"
#include "subtitles_provider_manager.h"
#include "video_provider_manager.h"
// Test if a line is a dialogue line which is not visible at the given time
struct invisible_line : public std::unary_function<const AssEntry*, bool> {
double time;
invisible_line(double time) : time(time * 1000.) { }
bool operator()(const AssEntry *entry) const {
const AssDialogue *diag = dynamic_cast<const AssDialogue*>(entry);
return diag && (diag->Start.GetMS() > time || diag->End.GetMS() <= time);
}
};
AegiVideoFrame const& ThreadedFrameSource::ProcFrame(int frameNum, double time, bool raw) {
AegiVideoFrame *frame;
{
wxMutexLocker locker(providerMutex);
frame = frameBuffer + frameBufferIdx;
frameBufferIdx = !frameBufferIdx;
try {
frame->CopyFrom(videoProvider->GetFrame(frameNum));
}
catch (const wchar_t *err) { throw VideoProviderErrorEvent(err); }
catch (wxString const& err) { throw VideoProviderErrorEvent(err); }
}
// This deliberately results in a call to LoadSubtitles while a render
// is pending making the queued render use the new file
if (!raw) {
try {
wxMutexLocker locker(fileMutex);
if (subs.get() && singleFrame != frameNum) {
// Generally edits and seeks come in groups; if the last thing done
// was seek it is more likely that the user will seek again and
// vice versa. As such, if this is the first frame requested after
// an edit, only export the currently visible lines (because the
// other lines will probably not be viewed before the file changes
// again), and if it's a different frame, export the entire file.
if (singleFrame == -1) {
singleFrame = frameNum;
// Copying a nontrivially sized AssFile is fairly slow, so
// instead muck around with its innards to just temporarily
// remove the non-visible lines without deleting them
std::list<AssEntry*> visible;
std::remove_copy_if(subs->Line.begin(), subs->Line.end(),
std::back_inserter(visible),
invisible_line(time));
try {
std::swap(subs->Line, visible);
provider->LoadSubtitles(subs.get());
}
catch(...) {
std::swap(subs->Line, visible);
throw;
}
}
else {
provider->LoadSubtitles(subs.get());
subs.reset();
}
}
}
catch (const wchar_t *err) { throw SubtitlesProviderErrorEvent(err); }
catch (wxString const& err) { throw SubtitlesProviderErrorEvent(err); }
provider->DrawSubtitles(*frame, time);
}
return *frame;
}
void *ThreadedFrameSource::Entry() {
while (!TestDestroy() && run) {
jobMutex.Lock();
if (nextSubs.get()) {
wxMutexLocker locker(fileMutex);
subs = nextSubs;
singleFrame = -1;
}
if (nextTime == -1.) {
jobReady.Wait();
continue;
}
double time = nextTime;
int frameNum = nextFrame;
nextTime = -1.;
jobMutex.Unlock();
try {
AegiVideoFrame const& frame = ProcFrame(frameNum, time);
std::tr1::shared_ptr<wxMutexLocker> evtLock(new wxMutexLocker(evtMutex));
FrameReadyEvent *evt = new FrameReadyEvent(&frame, time, evtLock);
evt->SetEventType(EVT_FRAME_READY);
parent->QueueEvent(evt);
}
catch (wxEvent const& err) {
// Pass error back to parent thread
parent->QueueEvent(err.Clone());
}
}
return EXIT_SUCCESS;
}
ThreadedFrameSource::ThreadedFrameSource(wxString videoFileName, wxEvtHandler *parent)
: wxThread()
, provider(SubtitlesProviderFactoryManager::GetProvider())
, videoProvider(VideoProviderFactoryManager::GetProvider(videoFileName))
, parent(parent)
, nextTime(-1.)
, jobReady(jobMutex)
, frameBufferIdx(0)
, run(true)
{
Create();
Run();
}
void ThreadedFrameSource::LoadSubtitles(AssFile *subs) {
AssExporter exporter(subs);
exporter.AddAutoFilters();
AssFile *exported = exporter.ExportTransform();
wxMutexLocker locker(jobMutex);
// Set nextSubs and let the worker thread move it to subs so that we don't
// have to lock fileMutex on the GUI thread, as that can be locked for
// extended periods of time with slow-rendering subtitles
nextSubs.reset(exported);
}
void ThreadedFrameSource::RequestFrame(int frame, double time) {
wxMutexLocker locker(jobMutex);
nextTime = time;
nextFrame = frame;
jobReady.Signal();
}
AegiVideoFrame const& ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
return ProcFrame(frame, time, raw);
}
void ThreadedFrameSource::End() {
run = false;
jobReady.Signal();
}
ThreadedFrameSource::~ThreadedFrameSource() {
frameBuffer[0].Clear();
frameBuffer[1].Clear();
}
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
wxDEFINE_EVENT(EVT_VIDEO_ERROR, VideoProviderErrorEvent);
wxDEFINE_EVENT(EVT_SUBTITLES_ERROR, SubtitlesProviderErrorEvent);
VideoProviderErrorEvent::VideoProviderErrorEvent(wxString msg)
: agi::Exception(STD_STR(msg), NULL)
{
SetEventType(EVT_VIDEO_ERROR);
}
SubtitlesProviderErrorEvent::SubtitlesProviderErrorEvent(wxString msg)
: agi::Exception(STD_STR(msg), NULL)
{
SetEventType(EVT_SUBTITLES_ERROR);
}