Adapt Butler to keep a buffer of main and sign language video types.
authorCarl Hetherington <cth@carlh.net>
Wed, 19 Oct 2022 22:45:17 +0000 (00:45 +0200)
committerCarl Hetherington <cth@carlh.net>
Thu, 22 Dec 2022 23:12:00 +0000 (00:12 +0100)
src/lib/butler.cc
src/lib/butler.h
src/lib/ffmpeg_encoder.cc
src/wx/video_view.cc
test/butler_test.cc
test/dcp_playback_test.cc
test/player_test.cc
test/threed_test.cc

index 570cf2dd87b00ef201b199379eb7ed29b9fc1a13..5b6139a81fbbb86bbdb168a84028d1eda5f1ba11 100644 (file)
@@ -136,36 +136,46 @@ Butler::~Butler ()
 bool
 Butler::should_run () const
 {
-       if (_video.size() >= MAXIMUM_VIDEO_READAHEAD * 10) {
-               /* This is way too big */
-               optional<DCPTime> pos = _audio.peek();
-               if (pos) {
-                       throw ProgrammingError
-                               (__FILE__, __LINE__, String::compose ("Butler video buffers reached %1 frames (audio is %2 at %3)", _video.size(), _audio.size(), pos->get()));
-               } else {
-                       throw ProgrammingError
-                               (__FILE__, __LINE__, String::compose ("Butler video buffers reached %1 frames (audio is %2)", _video.size(), _audio.size()));
+       auto check_video_much_too_big = [this](VideoType type) {
+               if (_video[type].size() >= MAXIMUM_VIDEO_READAHEAD * 10) {
+                       /* This is way too big */
+                       auto pos = _audio.peek();
+                       if (pos) {
+                               throw ProgrammingError
+                                       (__FILE__, __LINE__, String::compose("Butler video (%1) buffers reached %2 frames (audio is %3 at %4)", video_type_to_string(type), _video[type].size(), _audio.size(), pos->get()));
+                       } else {
+                               throw ProgrammingError
+                                       (__FILE__, __LINE__, String::compose("Butler video (%1) buffers reached %2 frames (audio is %3)", video_type_to_string(type), _video[type].size(), _audio.size()));
+                       }
                }
-       }
+       };
+
+       check_video_much_too_big(VideoType::MAIN);
+       check_video_much_too_big(VideoType::SIGN_LANGUAGE);
 
        if (_audio.size() >= MAXIMUM_AUDIO_READAHEAD * 10) {
                /* This is way too big */
                auto pos = _audio.peek();
                if (pos) {
                        throw ProgrammingError
-                               (__FILE__, __LINE__, String::compose ("Butler audio buffers reached %1 frames at %2 (video is %3)", _audio.size(), pos->get(), _video.size()));
+                               (__FILE__, __LINE__, String::compose ("Butler audio buffers reached %1 frames at %2 (main video is %3)", _audio.size(), pos->get(), _video[VideoType::MAIN].size()));
                } else {
                        throw ProgrammingError
-                               (__FILE__, __LINE__, String::compose ("Butler audio buffers reached %1 frames (video is %3)", _audio.size(), _video.size()));
+                               (__FILE__, __LINE__, String::compose ("Butler audio buffers reached %1 frames (main video is %3)", _audio.size(), _video[VideoType::MAIN].size()));
                }
        }
 
-       if (_video.size() >= MAXIMUM_VIDEO_READAHEAD * 2) {
-               LOG_WARNING ("Butler video buffers reached %1 frames (audio is %2)", _video.size(), _audio.size());
-       }
+       auto check_video_too_big = [this](VideoType type) {
+               if (_video[type].size() >= MAXIMUM_VIDEO_READAHEAD * 2) {
+                       LOG_WARNING ("Butler video buffers reached %1 frames (audio is %2)", _video[type].size(), _audio.size());
+               }
+       };
+
+       check_video_too_big(VideoType::MAIN);
+       check_video_too_big(VideoType::SIGN_LANGUAGE);
 
        if (_audio.size() >= MAXIMUM_AUDIO_READAHEAD * 2) {
-               LOG_WARNING ("Butler audio buffers reached %1 frames (video is %2)", _audio.size(), _video.size());
+               LOG_WARNING ("Butler audio buffers reached %1 frames (main video is %2)", _audio.size(), _video[VideoType::MAIN].size());
        }
 
        if (_stop_thread || _finished || _died || _suspended) {
@@ -173,13 +183,18 @@ Butler::should_run () const
                return false;
        }
 
-       if (_video.size() < MINIMUM_VIDEO_READAHEAD || (!_disable_audio && _audio.size() < MINIMUM_AUDIO_READAHEAD)) {
+       if (_video[VideoType::MAIN].size() < MINIMUM_VIDEO_READAHEAD
+           || (_player.have_sign_language() && _video[VideoType::SIGN_LANGUAGE].size() < MINIMUM_VIDEO_READAHEAD)
+           || (!_disable_audio && _audio.size() < MINIMUM_AUDIO_READAHEAD)
+           ) {
                /* Definitely do run: we need data */
                return true;
        }
 
        /* Run if we aren't full of video or audio */
-       return (_video.size() < MAXIMUM_VIDEO_READAHEAD) && (_audio.size() < MAXIMUM_AUDIO_READAHEAD);
+       return _video[VideoType::MAIN].size() < MAXIMUM_VIDEO_READAHEAD
+               && _video[VideoType::SIGN_LANGUAGE].size() < MAXIMUM_VIDEO_READAHEAD
+               && (_audio.size() < MAXIMUM_AUDIO_READAHEAD);
 }
 
 
@@ -244,7 +259,7 @@ try
  *  @param e if non-0 this is filled with an error code (if an error occurs) or is untouched if no error occurs.
  */
 pair<shared_ptr<PlayerVideo>, DCPTime>
-Butler::get_video (Behaviour behaviour, Error* e)
+Butler::get_video(VideoType type, Behaviour behaviour, Error* e)
 {
        boost::mutex::scoped_lock lm (_mutex);
 
@@ -261,22 +276,22 @@ Butler::get_video (Behaviour behaviour, Error* e)
                }
        };
 
-       if (_video.empty() && (_finished || _died || (_suspended && behaviour == Behaviour::NON_BLOCKING))) {
+       if (_video[type].empty() && (_finished || _died || (_suspended && behaviour == Behaviour::NON_BLOCKING))) {
                setup_error (e, Error::Code::AGAIN);
-               return make_pair(shared_ptr<PlayerVideo>(), DCPTime());
+               return {};
        }
 
        /* Wait for data if we have none */
-       while (_video.empty() && !_finished && !_died) {
+       while (_video[type].empty() && !_finished && !_died) {
                _arrived.wait (lm);
        }
 
-       if (_video.empty()) {
+       if (_video[type].empty()) {
                setup_error (e, Error::Code::NONE);
-               return make_pair(shared_ptr<PlayerVideo>(), DCPTime());
+               return {};
        }
 
-       auto const r = _video.get ();
+       auto const r = _video[type].get();
        _summon.notify_all ();
        return r;
 }
@@ -310,7 +325,8 @@ Butler::seek_unlocked (DCPTime position, bool accurate)
        _pending_seek_position = position;
        _pending_seek_accurate = accurate;
 
-       _video.clear ();
+       _video[VideoType::MAIN].clear();
+       _video[VideoType::SIGN_LANGUAGE].clear();
        _audio.clear ();
        _closed_caption.clear ();
 
@@ -357,7 +373,7 @@ Butler::video (shared_ptr<PlayerVideo> video, DCPTime time)
 
        _prepare_service.post (bind(&Butler::prepare, this, weak_ptr<PlayerVideo>(video)));
 
-       _video.put (video, time);
+       _video[video->type()].put(video, time);
 }
 
 
@@ -398,8 +414,8 @@ Butler::get_audio (Behaviour behaviour, float* out, Frame frames)
 pair<size_t, string>
 Butler::memory_used () const
 {
-       /* XXX: should also look at _audio.memory_used() */
-       return _video.memory_used();
+       /* XXX: should also look at _audio and _video[VideoType::SIGN_LANGUAGE] */
+       return _video[VideoType::MAIN].memory_used();
 }
 
 
@@ -410,7 +426,8 @@ Butler::player_change (ChangeType type, int property)
                if (type == ChangeType::DONE) {
                        auto film = _film.lock();
                        if (film) {
-                               _video.reset_metadata(film, _player.video_container_size(VideoType::MAIN));
+                               _video[VideoType::MAIN].reset_metadata(film, _player.video_container_size(VideoType::MAIN));
+                               _video[VideoType::SIGN_LANGUAGE].reset_metadata(film, _player.video_container_size(VideoType::SIGN_LANGUAGE));
                        }
                }
                return;
@@ -429,7 +446,7 @@ Butler::player_change (ChangeType type, int property)
                }
 
                DCPTime seek_to;
-               auto next = _video.get().second;
+               auto next = _video[VideoType::MAIN].get().second;
                if (_awaiting && _awaiting > next) {
                        /* We have recently done a player_changed seek and our buffers haven't been refilled yet,
                           so assume that we're seeking to the same place as last time.
index 6bb0467af1f526ed5670dde6237511c47cc0f8df..cf816ce7204cd04f1af0eadb4eb8cb01773504a6 100644 (file)
@@ -26,6 +26,7 @@
 #include "audio_mapping.h"
 #include "audio_ring_buffers.h"
 #include "change_signaller.h"
+#include "enum_indexed_vector.h"
 #include "exception_store.h"
 #include "text_ring_buffers.h"
 #include "text_type.h"
@@ -89,7 +90,7 @@ public:
                NON_BLOCKING
        };
 
-       std::pair<std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> get_video (Behaviour behaviour, Error* e = nullptr);
+       std::pair<std::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> get_video(VideoType type, Behaviour behaviour, Error* e = nullptr);
        boost::optional<dcpomatic::DCPTime> get_audio (Behaviour behaviour, float* out, Frame frames);
        boost::optional<TextRingBuffers::Data> get_closed_caption ();
 
@@ -109,7 +110,7 @@ private:
        Player& _player;
        boost::thread _thread;
 
-       VideoRingBuffers _video;
+       EnumIndexedVector<VideoRingBuffers, VideoType> _video;
        AudioRingBuffers _audio;
        TextRingBuffers _closed_caption;
 
index 001645af3c59e21ac7fdfaf89e55f205812a1763..17ee93b478cce5184f7822500b00fcea04ef8cbc 100644 (file)
@@ -190,7 +190,7 @@ FFmpegEncoder::go ()
 
                for (int j = 0; j < gets_per_frame; ++j) {
                        Butler::Error e;
-                       auto v = _butler.get_video(Butler::Behaviour::BLOCKING, &e);
+                       auto v = _butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, &e);
                        _butler.rethrow();
                        if (v.first) {
                                auto fe = encoder->get (v.first->eyes());
index c271cb65e83e213edb54f566c5b4098b351b968f..74d543265fa4a59e1779631839f7fb3a050c0ffc 100644 (file)
@@ -75,7 +75,7 @@ VideoView::get_next_frame (bool non_blocking)
 
        do {
                Butler::Error e;
-               auto pv = butler->get_video (non_blocking ? Butler::Behaviour::NON_BLOCKING : Butler::Behaviour::BLOCKING, &e);
+               auto pv = butler->get_video(VideoType::MAIN, non_blocking ? Butler::Behaviour::NON_BLOCKING : Butler::Behaviour::BLOCKING, &e);
                if (e.code == Butler::Error::Code::DIED) {
                        LOG_ERROR ("Butler died with %1", e.summary());
                }
index 97e4ccc0effbdec2718baa3fd399bc8303398940..35d0007b3f5519fe8119a3336a6873560508a089 100644 (file)
@@ -75,9 +75,9 @@ BOOST_AUTO_TEST_CASE (butler_test1)
                Butler::Audio::ENABLED
                );
 
-       BOOST_CHECK (butler.get_video(Butler::Behaviour::BLOCKING, 0).second == DCPTime());
-       BOOST_CHECK (butler.get_video(Butler::Behaviour::BLOCKING, 0).second == DCPTime::from_frames(1, 24));
-       BOOST_CHECK (butler.get_video(Butler::Behaviour::BLOCKING, 0).second == DCPTime::from_frames(2, 24));
+       BOOST_CHECK (butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0).second == DCPTime());
+       BOOST_CHECK (butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0).second == DCPTime::from_frames(1, 24));
+       BOOST_CHECK (butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0).second == DCPTime::from_frames(2, 24));
        /* XXX: check the frame contents */
 
        float buffer[256 * 6];
@@ -125,14 +125,14 @@ BOOST_AUTO_TEST_CASE (butler_test2)
        int const audio_frames_per_video_frame = 48000 / 25;
        float audio_buffer[audio_frames_per_video_frame * 6];
        for (int i = 0; i < 16; ++i) {
-               butler.get_video(Butler::Behaviour::BLOCKING, 0);
+               butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                butler.get_audio(Butler::Behaviour::BLOCKING, audio_buffer, audio_frames_per_video_frame);
        }
 
        butler.seek (DCPTime::from_seconds(60), false);
 
        for (int i = 0; i < 240; ++i) {
-               butler.get_video(Butler::Behaviour::BLOCKING, 0);
+               butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                butler.get_audio(Butler::Behaviour::BLOCKING, audio_buffer, audio_frames_per_video_frame);
        }
 
index a5b69e1819051da1ab404ff7bf3a1e61af737adf..528f8fb6994efb6dc8f6f6251bbe8a2c4e6b8b25 100644 (file)
@@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE (dcp_playback_test)
 
        std::vector<float> audio_buffer(2000 * 6);
        while (true) {
-               auto p = butler->get_video (Butler::Behaviour::BLOCKING, 0);
+               auto p = butler->get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                if (!p.first) {
                        break;
                }
index bc35f757725df149834aa2201a456e97fa755c66..a089e68713e0d57a842844e65ac9102130eda860 100644 (file)
@@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test)
        for (int i = 0; i < 10; ++i) {
                auto t = DCPTime::from_frames (i, 24);
                butler->seek (t, true);
-               auto video = butler->get_video(Butler::Behaviour::BLOCKING, 0);
+               auto video = butler->get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                BOOST_CHECK_EQUAL(video.second.get(), t.get());
                write_image(video.first->image(bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true), String::compose("build/test/player_seek_test_%1.png", i));
                /* This 14.08 is empirically chosen (hopefully) to accept changes in rendering between the reference and a test machine
@@ -278,7 +278,7 @@ BOOST_AUTO_TEST_CASE (player_seek_test2)
        for (int i = 0; i < 10; ++i) {
                auto t = DCPTime::from_seconds(5) + DCPTime::from_frames (i, 24);
                butler->seek (t, true);
-               auto video = butler->get_video(Butler::Behaviour::BLOCKING, 0);
+               auto video = butler->get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                BOOST_CHECK_EQUAL(video.second.get(), t.get());
                write_image(
                        video.first->image(bind(PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true), String::compose("build/test/player_seek_test2_%1.png", i)
@@ -497,7 +497,7 @@ BOOST_AUTO_TEST_CASE (encrypted_dcp_with_no_kdm_gives_no_butler_error)
 
        float buffer[2000 * 6];
        for (int i = 0; i < length; ++i) {
-               butler.get_video(Butler::Behaviour::BLOCKING, 0);
+               butler.get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, 0);
                butler.get_audio(Butler::Behaviour::BLOCKING, buffer, 2000);
        }
 
index 4b6b727d411e295d7248abbd12b81c17c9748cfe..fb774c5413b665a920cfc0108444f4ac65a9f0a4 100644 (file)
@@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE (threed_test_butler_overfill)
        butler->seek(dcpomatic::DCPTime(), true);
        Butler::Error error;
        for (auto i = 0; i < 960; ++i) {
-               butler->get_video(Butler::Behaviour::BLOCKING, &error);
+               butler->get_video(VideoType::MAIN, Butler::Behaviour::BLOCKING, &error);
                butler->get_audio(Butler::Behaviour::BLOCKING, audio.data(), audio_frames);
        }
        BOOST_REQUIRE (error.code == Butler::Error::Code::NONE);