diff options
| author | Martin Lambers <marlam@marlam.de> | 2011-08-06 14:58:23 +0200 |
|---|---|---|
| committer | Martin Lambers <marlam@marlam.de> | 2011-08-06 14:58:23 +0200 |
| commit | 381a4ade2d43e3708ebff79f9725d918aee860b2 (patch) | |
| tree | 0feff372749645b4dec993d5ef585352eaa5a42e | |
| parent | 093a435d2444e1dfd9dcc4f01f4f05e928628fff (diff) | |
| download | bino-381a4ade2d43e3708ebff79f9725d918aee860b2.tar.gz | |
Initialize subtitle renderer in a separate thread in the background.
If fontconfig needs to build a cache first (mostly on Windows and Mac OS X),
the initialization of libass can take a long time. Therefore we do it in a
background thread and hope that it is finished when the first subtitle has
to be displayed.
If it is not, we wait for it and display a message to the user. Video
playback is paused for the duration of the wait so that no frames are dropped.
| -rw-r--r-- | src/base/thread.cpp | 17 | ||||
| -rw-r--r-- | src/base/thread.h | 9 | ||||
| -rw-r--r-- | src/player.cpp | 4 | ||||
| -rw-r--r-- | src/player_equalizer.cpp | 24 | ||||
| -rw-r--r-- | src/subtitle_renderer.cpp | 81 | ||||
| -rw-r--r-- | src/subtitle_renderer.h | 31 | ||||
| -rw-r--r-- | src/video_output.cpp | 1 | ||||
| -rw-r--r-- | src/video_output.h | 7 | ||||
| -rw-r--r-- | src/video_output_qt.cpp | 56 | ||||
| -rw-r--r-- | src/video_output_qt.h | 1 |
10 files changed, 209 insertions, 22 deletions
diff --git a/src/base/thread.cpp b/src/base/thread.cpp index 0ee3057..0062203 100644 --- a/src/base/thread.cpp +++ b/src/base/thread.cpp @@ -125,7 +125,10 @@ void *thread::__run(void *p) } catch (...) { - t->__exception = exc(_("Unknown exception.")); + // We must assume this means thread cancellation. In this + // case, we *must* rethrow the exception. + t->__running = false; + throw; } t->__running = false; return NULL; @@ -168,3 +171,15 @@ void thread::finish() throw exception(); } } + +void thread::cancel() +{ + __wait_mutex.lock(); + int e = pthread_cancel(__thread_id); + if (e != 0) + { + __wait_mutex.unlock(); + throw exc(str::asprintf(_("Cannot cancel thread: %s"), std::strerror(e)), e); + } + __wait_mutex.unlock(); +} diff --git a/src/base/thread.h b/src/base/thread.h index 8a07439..a4a9c80 100644 --- a/src/base/thread.h +++ b/src/base/thread.h @@ -130,6 +130,12 @@ public: // running, this function does nothing. void start(); + // Returns whether this thread is currently running. + bool is_running() + { + return __running; + } + // Wait for the thread to finish. If the thread is not running, this function // returns immediately. void wait(); @@ -138,6 +144,9 @@ public: // run() function might have thrown during its execution. void finish(); + // Cancel a thread. This is dangerous and should not be used. + void cancel(); + // Get an exception that the run() function might have thrown. const exc &exception() const { diff --git a/src/player.cpp b/src/player.cpp index bff89a1..7edf4a2 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -685,6 +685,10 @@ bool player::run_step() } if (prep_frame) { + if (_current_subtitle_box.is_valid() && _video_output) + { + _master_time_start += _video_output->wait_for_subtitle_renderer(); + } _video_output->prepare_next_frame(_video_frame, _current_subtitle_box); } else if (drop_frame) diff --git a/src/player_equalizer.cpp b/src/player_equalizer.cpp index 343cee6..17ac219 100644 --- a/src/player_equalizer.cpp +++ b/src/player_equalizer.cpp @@ -176,6 +176,26 @@ protected: void trigger_resize(int, int) { } public: + int64_t wait_for_subtitle_renderer() + { + if (!_subtitle_renderer.is_initialized()) + { + msg::wrn(_("Waiting for subtitle renderer initialization...")); + try + { + while (!_subtitle_renderer.is_initialized()) + { + usleep(10000); + } + } + catch (std::exception &e) + { + msg::err("%s", e.what()); + abort(); + } + } + return 0; + } bool supports_stereo() const { return false; } int screen_width() { return _channel->getPixelViewport().w / _channel->getViewport().w; } int screen_height() { return _channel->getPixelViewport().h / _channel->getViewport().h; } @@ -999,6 +1019,10 @@ protected: if (node->frame_data.prep_frame) { getWindow()->makeCurrent(); + if (node->frame_data.subtitle.is_valid()) + { + _video_output.wait_for_subtitle_renderer(); + } _video_output.prepare_next_frame(node->get_video_frame(), node->frame_data.subtitle); } if (node->frame_data.display_frame) diff --git a/src/subtitle_renderer.cpp b/src/subtitle_renderer.cpp index 93c2adc..dc6d470 100644 --- a/src/subtitle_renderer.cpp +++ b/src/subtitle_renderer.cpp @@ -45,6 +45,7 @@ extern "C" #include "gettext.h" #define _(string) gettext(string) +#include "dbg.h" #include "exc.h" #include "str.h" #include "blob.h" @@ -54,6 +55,17 @@ extern "C" #include "subtitle_renderer.h" +subtitle_renderer_initializer::subtitle_renderer_initializer(subtitle_renderer &renderer) : + _subtitle_renderer(renderer) +{ +} + +void subtitle_renderer_initializer::run() +{ + _subtitle_renderer.init(); +} + + /* Rendering subtitles with LibASS is not thread-safe. * * We have multiple concurrent subtitle rendering threads if we are running from Equalizer @@ -70,16 +82,28 @@ extern "C" static mutex global_libass_mutex; subtitle_renderer::subtitle_renderer() : - _ass_initialized(false), + _initializer(*this), + _initialized(false), _fontconfig_conffile(NULL), _ass_library(NULL), _ass_renderer(NULL), _ass_track(NULL) { + _initializer.start(); } subtitle_renderer::~subtitle_renderer() { + if (_initializer.is_running()) + { + try + { + _initializer.cancel(); + } + catch (...) + { + } + } if (_ass_track) { ass_free_track(_ass_track); @@ -98,6 +122,20 @@ subtitle_renderer::~subtitle_renderer() } } +bool subtitle_renderer::is_initialized() +{ + if (_initializer.is_running()) + { + return false; + } + else + { + // rethrow a possible exception if something went wrong + _initializer.finish(); + return _initialized; + } +} + bool subtitle_renderer::render_to_display_size(const subtitle_box &box) const { return (box.format != subtitle_box::image); @@ -108,12 +146,12 @@ void subtitle_renderer::prerender(const subtitle_box &box, int64_t timestamp, int width, int height, float pixel_aspect_ratio, int &bb_x, int &bb_y, int &bb_w, int &bb_h) { + assert(_initialized); _fmt = box.format; switch (_fmt) { case subtitle_box::text: case subtitle_box::ass: - init_ass(); prerender_ass(box, timestamp, params, width, height, pixel_aspect_ratio); break; case subtitle_box::image: @@ -254,30 +292,37 @@ static void libass_msg_callback(int level, const char *fmt, va_list args, void * msg::msg(l, std::string("LibASS: ") + s); } -void subtitle_renderer::init_ass() +void subtitle_renderer::init() { - if (!_ass_initialized) + if (!_initialized) { - global_libass_mutex.lock(); - _ass_library = ass_library_init(); - if (!_ass_library) + try { + global_libass_mutex.lock(); + _ass_library = ass_library_init(); + if (!_ass_library) + { + throw exc(_("Cannot initialize LibASS.")); + } + ass_set_message_cb(_ass_library, libass_msg_callback, NULL); + ass_set_extract_fonts(_ass_library, 1); + _ass_renderer = ass_renderer_init(_ass_library); + if (!_ass_renderer) + { + throw exc(_("Cannot initialize LibASS renderer.")); + } + ass_set_hinting(_ass_renderer, ASS_HINTING_NATIVE); + _fontconfig_conffile = get_fontconfig_conffile(); + ass_set_fonts(_ass_renderer, NULL, "sans-serif", 1, _fontconfig_conffile, 1); + _initialized = true; global_libass_mutex.unlock(); - throw exc(_("Cannot initialize LibASS.")); } - ass_set_message_cb(_ass_library, libass_msg_callback, NULL); - ass_set_extract_fonts(_ass_library, 1); - _ass_renderer = ass_renderer_init(_ass_library); - if (!_ass_renderer) + catch (...) { + // Either we threw this exception ourselves, or the thread was cancelled. global_libass_mutex.unlock(); - throw exc(_("Cannot initialize LibASS renderer.")); + throw; } - ass_set_hinting(_ass_renderer, ASS_HINTING_NATIVE); - _fontconfig_conffile = get_fontconfig_conffile(); - ass_set_fonts(_ass_renderer, NULL, "sans-serif", 1, _fontconfig_conffile, 1); - _ass_initialized = true; - global_libass_mutex.unlock(); } } diff --git a/src/subtitle_renderer.h b/src/subtitle_renderer.h index f8a2dce..6b7b032 100644 --- a/src/subtitle_renderer.h +++ b/src/subtitle_renderer.h @@ -31,14 +31,33 @@ extern "C" #include <ass/ass.h> } +#include "thread.h" + #include "media_data.h" +class subtitle_renderer; + +class subtitle_renderer_initializer : public thread +{ +private: + subtitle_renderer &_subtitle_renderer; + +public: + subtitle_renderer_initializer(subtitle_renderer &renderer); + void run(); +}; + class subtitle_renderer { private: + // Initialization + subtitle_renderer_initializer _initializer; + bool _initialized; + void init(); + friend class subtitle_renderer_initializer; + // Static ASS data - bool _ass_initialized; const char *_fontconfig_conffile; ASS_Library *_ass_library; ASS_Renderer *_ass_renderer; @@ -52,7 +71,6 @@ private: // ASS helper functions const char *get_fontconfig_conffile(); - void init_ass(); void blend_ass_image(const ASS_Image *img, uint32_t *buf); void set_ass_parameters(const parameters ¶ms); @@ -70,6 +88,15 @@ public: subtitle_renderer(); ~subtitle_renderer(); + // Check if the subtitle renderer is initialized. Since initialization may + // take a long time on systems where fontconfig needs to create its cache first, + // it is done in a separate thread in the background. + // You must make sure that the renderer is initialized before calling any of + // the functions below! + // In the case of initialization failure, this function will throw the appropriate + // exception. + bool is_initialized(); + /* * To render a subtitle, the following steps are necessary: * 1. Call render_to_display_size() to determine if the subtitle diff --git a/src/video_output.cpp b/src/video_output.cpp index d6b728c..3bf98b3 100644 --- a/src/video_output.cpp +++ b/src/video_output.cpp @@ -404,6 +404,7 @@ void video_output::update_subtitle_tex(int index, const video_frame &frame, cons int height = 0; if (subtitle.is_valid()) { + assert(_subtitle_renderer.is_initialized()); if (_subtitle_renderer.render_to_display_size(subtitle)) { width = video_display_width(); diff --git a/src/video_output.h b/src/video_output.h index a02f02d..2e8d6c8 100644 --- a/src/video_output.h +++ b/src/video_output.h @@ -36,7 +36,6 @@ class video_output : public controller { private: - subtitle_renderer _subtitle_renderer; bool _initialized; bool _srgb_textures_are_broken; // XXX: Hack: work around broken SRGB texture implementations @@ -95,6 +94,8 @@ private: void update_subtitle_tex(int index, const video_frame &frame, const subtitle_box &subtitle, const parameters ¶ms); protected: + subtitle_renderer _subtitle_renderer; + // Get total size of the video display area. For single window output, this // is the same as the current viewport. The Equalizer video output can override // this function. @@ -129,6 +130,10 @@ public: /* Initialize the video output, or throw an exception */ virtual void init(); + /* Wait for subtitle renderer initialization to finish. Has to be called + * if the video has subtitles. Returns the number of microseconds that the + * waiting took. */ + virtual int64_t wait_for_subtitle_renderer() = 0; /* Deinitialize the video output */ virtual void deinit(); diff --git a/src/video_output_qt.cpp b/src/video_output_qt.cpp index e293959..9721c19 100644 --- a/src/video_output_qt.cpp +++ b/src/video_output_qt.cpp @@ -30,6 +30,7 @@ static GLEWContext* glewGetContext() { return &_glewContext; } #include <cmath> #include <cstdlib> +#include <unistd.h> #include <QApplication> #include <QDesktopWidget> @@ -39,6 +40,8 @@ static GLEWContext* glewGetContext() { return &_glewContext; } #include <QMessageBox> #include <QPalette> #include <QProcess> +#include <QDialog> +#include <QLabel> #ifdef Q_WS_X11 # include <QX11Info> # include <X11/Xlib.h> @@ -51,6 +54,7 @@ static GLEWContext* glewGetContext() { return &_glewContext; } #include "msg.h" #include "str.h" #include "dbg.h" +#include "timer.h" #include "qt_app.h" #include "video_output_qt.h" @@ -329,6 +333,58 @@ void video_output_qt::init() } } +int64_t video_output_qt::wait_for_subtitle_renderer() +{ + if (_subtitle_renderer.is_initialized()) + { + return 0; + } + int64_t wait_start = timer::get_microseconds(timer::monotonic); + exc init_exception; + QDialog *mbox = NULL; + // Show a dialog only in GUI mode + if (_container_is_external && !_fullscreen) + { + mbox = new QDialog(_container_widget); + mbox->setModal(true); + mbox->setWindowTitle(_("Please wait")); + QGridLayout *mbox_layout = new QGridLayout; + QLabel *mbox_label = new QLabel(_("Waiting for subtitle renderer initialization...")); + mbox_layout->addWidget(mbox_label, 0, 0); + mbox->setLayout(mbox_layout); + mbox->show(); + } + else + { + msg::wrn(_("Waiting for subtitle renderer initialization...")); + } + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + try + { + while (!_subtitle_renderer.is_initialized()) + { + process_events(); + usleep(10000); + } + } + catch (std::exception &e) + { + init_exception = e; + } + QApplication::restoreOverrideCursor(); + if (mbox) + { + mbox->hide(); + delete mbox; + } + if (!init_exception.empty()) + { + throw init_exception; + } + int64_t wait_stop = timer::get_microseconds(timer::monotonic); + return (wait_stop - wait_start); +} + void video_output_qt::deinit() { if (_widget) diff --git a/src/video_output_qt.h b/src/video_output_qt.h index 3e4c9f6..8e07aa6 100644 --- a/src/video_output_qt.h +++ b/src/video_output_qt.h @@ -123,6 +123,7 @@ public: void grab_focus(); virtual void init(); + virtual int64_t wait_for_subtitle_renderer(); virtual void deinit(); virtual bool supports_stereo() const; |
