Multi-threaded decode of DCP when previewing.
authorCarl Hetherington <cth@carlh.net>
Wed, 26 Jul 2017 14:47:52 +0000 (15:47 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 26 Jul 2017 14:47:52 +0000 (15:47 +0100)
ChangeLog
src/lib/butler.cc
src/lib/butler.h
src/lib/image_proxy.h
src/lib/j2k_image_proxy.cc
src/lib/j2k_image_proxy.h
src/lib/player_video.cc
src/lib/player_video.h

index 687e30757454ce37c1e8e03a71157ee2e1847eee..582d02801c49ef9cc7e91bd9521764d0cee9159e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+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).
index 771c4e6334aa37b326acf5f1f9860d23430fc4cd..053d7c3bcf16fb1813d77b4a930d1ae3d1f6db9c 100644 (file)
@@ -48,6 +48,7 @@ using boost::optional;
 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)
@@ -60,6 +61,13 @@ Butler::Butler (weak_ptr<const Film> film, shared_ptr<Player> player, AudioMappi
        _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 ()
@@ -69,6 +77,10 @@ Butler::~Butler ()
                _stop_thread = true;
        }
 
+       _prepare_work.reset ();
+       _prepare_pool.join_all ();
+       _prepare_service.stop ();
+
        _thread->interrupt ();
        try {
                _thread->join ();
@@ -180,6 +192,16 @@ Butler::seek (DCPTime position, bool accurate)
        _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)
 {
@@ -191,6 +213,7 @@ Butler::video (shared_ptr<PlayerVideo> video, DCPTime time)
                }
        }
 
+       _prepare_service.post (bind (&Butler::prepare, this, weak_ptr<PlayerVideo>(video)));
        _video.put (video, time);
 }
 
index 9949ab79b59c6f9e65c47e4a31438931482761ca..1e8947f9d2d554f4e58af0bdc4f6cdef0474e77a 100644 (file)
@@ -27,6 +27,7 @@
 #include <boost/thread.hpp>
 #include <boost/thread/condition.hpp>
 #include <boost/signals2.hpp>
+#include <boost/asio.hpp>
 
 class Film;
 class Player;
@@ -50,6 +51,7 @@ private:
        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;
@@ -58,6 +60,10 @@ private:
        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;
index d7bd7b0e62811f9846ceb92a127a77be09d9b1f5..f9a06d1b701c7c84b1979f52375cbb6c74769f4e 100644 (file)
@@ -74,6 +74,10 @@ public:
        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;
 };
 
index f7e6dd5f4dd52f25ffc444f79de58e71376e4457..9e68951e95b8e8823ab12d46346e96ac8ff1ab51 100644 (file)
@@ -93,35 +93,45 @@ J2KImageProxy::J2KImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> soc
        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
index 41d68588f66dfa64c1a2c9af45710bffdddc4316..3133aac20c055642f9667657b920812d9fc52eeb 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    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.
 
@@ -21,6 +21,7 @@
 #include "image_proxy.h"
 #include <dcp/util.h>
 #include <dcp/data.h>
+#include <boost/thread/mutex.hpp>
 
 namespace dcp {
        class MonoPictureFrame;
@@ -44,6 +45,7 @@ public:
        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;
        }
@@ -68,4 +70,5 @@ private:
        mutable boost::shared_ptr<dcp::OpenJPEGImage> _decompressed;
        mutable boost::optional<dcp::Size> _target_size;
        AVPixelFormat _pixel_format;
+       mutable boost::mutex _mutex;
 };
index 6f0977e628091fa3885acfa3324b4910139d12ac..14291fc35c917d068b74f4ba9726915402663523 100644 (file)
@@ -246,3 +246,9 @@ PlayerVideo::keep_xyz_or_rgb (AVPixelFormat p)
 {
        return p == AV_PIX_FMT_XYZ12LE ? AV_PIX_FMT_XYZ12LE : AV_PIX_FMT_RGB48LE;
 }
+
+void
+PlayerVideo::prepare ()
+{
+       _in->prepare (_inter_size);
+}
index 5017d0e3c8f8bc8a1358d4fcf1d8e75055fd06bf..040145559a7e802bd66c67a41cde0da5fbc486bd 100644 (file)
@@ -57,6 +57,7 @@ public:
 
        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);