+2017-07-26 Carl Hetherington <cth@carlh.net>
+
+ * Multi-threaded decode of DCP when previewing.
+
2017-07-25 Carl Hetherington <cth@carlh.net>
* Configurable config.xml location (#780, #1100).
Butler::Butler (weak_ptr<const Film> film, shared_ptr<Player> player, AudioMapping audio_mapping, int audio_channels)
: _film (film)
, _player (player)
+ , _prepare_work (new boost::asio::io_service::work (_prepare_service))
, _pending_seek_accurate (false)
, _finished (false)
, _died (false)
_player_audio_connection = _player->Audio.connect (bind (&Butler::audio, this, _1));
_player_changed_connection = _player->Changed.connect (bind (&Butler::player_changed, this));
_thread = new boost::thread (bind (&Butler::thread, this));
+
+ /* Create some threads to do work on the PlayerVideos we are creating; at present this is used to
+ multi-thread JPEG2000 decoding.
+ */
+ for (int i = 0; i < boost::thread::hardware_concurrency(); ++i) {
+ _prepare_pool.create_thread (bind (&boost::asio::io_service::run, &_prepare_service));
+ }
}
Butler::~Butler ()
_stop_thread = true;
}
+ _prepare_work.reset ();
+ _prepare_pool.join_all ();
+ _prepare_service.stop ();
+
_thread->interrupt ();
try {
_thread->join ();
_summon.notify_all ();
}
+void
+Butler::prepare (weak_ptr<PlayerVideo> weak_video) const
+{
+ shared_ptr<PlayerVideo> video = weak_video.lock ();
+ /* If the weak_ptr cannot be locked the video obviously no longer requires any work */
+ if (video) {
+ video->prepare ();
+ }
+}
+
void
Butler::video (shared_ptr<PlayerVideo> video, DCPTime time)
{
}
}
+ _prepare_service.post (bind (&Butler::prepare, this, weak_ptr<PlayerVideo>(video)));
_video.put (video, time);
}
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/signals2.hpp>
+#include <boost/asio.hpp>
class Film;
class Player;
void audio (boost::shared_ptr<AudioBuffers> audio);
void player_changed ();
bool should_run () const;
+ void prepare (boost::weak_ptr<PlayerVideo> video) const;
boost::weak_ptr<const Film> _film;
boost::shared_ptr<Player> _player;
VideoRingBuffers _video;
AudioRingBuffers _audio;
+ boost::thread_group _prepare_pool;
+ boost::asio::io_service _prepare_service;
+ boost::shared_ptr<boost::asio::io_service::work> _prepare_work;
+
/** mutex to protect _pending_seek_position, _pending_seek_acurate, _finished, _died, _stop_thread */
boost::mutex _mutex;
boost::condition _summon;
virtual void send_binary (boost::shared_ptr<Socket>) const = 0;
/** @return true if our image is definitely the same as another, false if it is probably not */
virtual bool same (boost::shared_ptr<const ImageProxy>) const = 0;
+ /** Do any useful work that would speed up a subsequent call to ::image().
+ * This method may be called in a different thread to image().
+ */
+ virtual void prepare (boost::optional<dcp::Size> = boost::optional<dcp::Size>()) const {}
virtual AVPixelFormat pixel_format () const = 0;
};
socket->read (_data.data().get (), _data.size ());
}
-shared_ptr<Image>
-J2KImageProxy::image (optional<dcp::NoteHandler>, optional<dcp::Size> target_size) const
+void
+J2KImageProxy::prepare (optional<dcp::Size> target_size) const
{
- if (!_decompressed || target_size != _target_size) {
- int reduce = 0;
+ boost::mutex::scoped_lock lm (_mutex);
- while (target_size && (_size.width / pow(2, reduce)) > target_size->width && (_size.height / pow(2, reduce)) > target_size->height) {
- ++reduce;
- }
+ if (_decompressed && target_size == _target_size) {
+ return;
+ }
- --reduce;
- reduce = max (0, reduce);
- _decompressed = dcp::decompress_j2k (const_cast<uint8_t*> (_data.data().get()), _data.size (), reduce);
+ int reduce = 0;
- if (_decompressed->precision(0) < 12) {
- int const shift = 12 - _decompressed->precision (0);
- for (int c = 0; c < 3; ++c) {
- int* p = _decompressed->data (c);
- for (int y = 0; y < _decompressed->size().height; ++y) {
- for (int x = 0; x < _decompressed->size().width; ++x) {
- *p++ <<= shift;
- }
+ while (target_size && (_size.width / pow(2, reduce)) > target_size->width && (_size.height / pow(2, reduce)) > target_size->height) {
+ ++reduce;
+ }
+
+ --reduce;
+ reduce = max (0, reduce);
+ _decompressed = dcp::decompress_j2k (const_cast<uint8_t*> (_data.data().get()), _data.size (), reduce);
+
+ if (_decompressed->precision(0) < 12) {
+ int const shift = 12 - _decompressed->precision (0);
+ for (int c = 0; c < 3; ++c) {
+ int* p = _decompressed->data (c);
+ for (int y = 0; y < _decompressed->size().height; ++y) {
+ for (int x = 0; x < _decompressed->size().width; ++x) {
+ *p++ <<= shift;
}
}
}
-
- _target_size = target_size;
}
+ _target_size = target_size;
+}
+
+shared_ptr<Image>
+J2KImageProxy::image (optional<dcp::NoteHandler>, optional<dcp::Size> target_size) const
+{
+ prepare (target_size);
+
shared_ptr<Image> image (new Image (_pixel_format, _decompressed->size(), true));
/* Copy data in whatever format (sRGB or XYZ) into our Image; I'm assuming
/*
- Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2015-2017 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
#include "image_proxy.h"
#include <dcp/util.h>
#include <dcp/data.h>
+#include <boost/thread/mutex.hpp>
namespace dcp {
class MonoPictureFrame;
void send_binary (boost::shared_ptr<Socket>) const;
/** @return true if our image is definitely the same as another, false if it is probably not */
bool same (boost::shared_ptr<const ImageProxy>) const;
+ void prepare (boost::optional<dcp::Size> = boost::optional<dcp::Size>()) const;
AVPixelFormat pixel_format () const {
return _pixel_format;
}
mutable boost::shared_ptr<dcp::OpenJPEGImage> _decompressed;
mutable boost::optional<dcp::Size> _target_size;
AVPixelFormat _pixel_format;
+ mutable boost::mutex _mutex;
};
{
return p == AV_PIX_FMT_XYZ12LE ? AV_PIX_FMT_XYZ12LE : AV_PIX_FMT_RGB48LE;
}
+
+void
+PlayerVideo::prepare ()
+{
+ _in->prepare (_inter_size);
+}
void set_subtitle (PositionImage);
+ void prepare ();
boost::shared_ptr<Image> image (dcp::NoteHandler note, boost::function<AVPixelFormat (AVPixelFormat)> pixel_format, bool aligned, bool fast) const;
static AVPixelFormat always_rgb (AVPixelFormat);