Add some player tests. Fix seek with content at non-DCP frame rate. A few other...
authorCarl Hetherington <cth@carlh.net>
Mon, 15 Jul 2013 15:36:49 +0000 (16:36 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 15 Jul 2013 15:36:49 +0000 (16:36 +0100)
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/playlist.h
test/play_test.cc [new file with mode: 0644]

index c8c2064248a941560b22f6684e9121f7098ead37..6ee8c50298d2a2a9a4a1b77e8604b6fdfa8af49c 100644 (file)
@@ -138,7 +138,7 @@ Player::pass ()
                        continue;
                }
 
-               if (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+               if (_video && dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
                        if ((*i)->video_position < earliest_t) {
                                earliest_t = (*i)->video_position;
                                earliest = *i;
@@ -146,7 +146,7 @@ Player::pass ()
                        }
                }
 
-               if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+               if (_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
                        if ((*i)->audio_position < earliest_t) {
                                earliest_t = (*i)->audio_position;
                                earliest = *i;
@@ -238,6 +238,10 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
                work_image = im;
        }
 
+#ifdef DCPOMATIC_DEBUG
+       _last_video = piece->content;
+#endif 
+
         Video (work_image, same, time);
        time += TIME_HZ / _film->dcp_video_frame_rate();
 
@@ -348,7 +352,10 @@ Player::flush ()
        
 }
 
-/** @return true on error */
+/** Seek so that the next pass() will yield (approximately) the requested frame.
+ *  Pass accurate = true to try harder to get close to the request.
+ *  @return true on error
+ */
 void
 Player::seek (Time t, bool accurate)
 {
@@ -374,11 +381,16 @@ Player::seek (Time t, bool accurate)
                (*i)->video_position = (*i)->audio_position = vc->start() + s;
 
                FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate());
-               VideoContent::Frame f = s * vc->video_frame_rate() / (frc.factor() * TIME_HZ);
+               /* Here we are converting from time (in the DCP) to a frame number in the content.
+                  Hence we need to use the DCP's frame rate and the double/skip correction, not
+                  the source's rate.
+               */
+               VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ);
                dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f, accurate);
        }
 
        _video_position = _audio_position = t;
+       
        /* XXX: don't seek audio because we don't need to... */
 }
 
@@ -501,6 +513,10 @@ Player::resampler (shared_ptr<AudioContent> c)
 void
 Player::emit_black ()
 {
+#ifdef DCPOMATIC_DEBUG
+       _last_video.reset ();
+#endif
+       
        /* XXX: use same here */
        Video (_black_frame, false, _video_position);
        _video_position += _film->video_frames_to_time (1);
index b3eadd7c07e25521506bf6fcc29bc251262e811f..92a35804397609c2263708e5566fc82b4488a944 100644 (file)
@@ -75,6 +75,7 @@ public:
        boost::signals2::signal<void ()> Changed;
 
 private:
+       friend class PlayerWrapper;
 
        void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
        void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
@@ -126,6 +127,10 @@ private:
                Time from;
                Time to;
        } _out_subtitle;
+
+#ifdef DCPOMATIC_DEBUG
+       boost::shared_ptr<Content> _last_video;
+#endif 
 };
 
 #endif
index 703a14663448c5468dc10910a3ca5c978d3a96df..0d6462f6c53d439ce3aec96895e29ed941f4bdb0 100644 (file)
@@ -72,25 +72,35 @@ Playlist::~Playlist ()
 void
 Playlist::content_changed (weak_ptr<Content> c, int p)
 {
-       if (p == ContentProperty::LENGTH && _sequence_video && !_sequencing_video) {
-               _sequencing_video = true;
-
-               ContentList cl = _content;
-               sort (cl.begin(), cl.end(), ContentSorter ());
-               Time last = 0;
-               for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) {
-                       if (!dynamic_pointer_cast<VideoContent> (*i)) {
-                               continue;
-                       }
+       if (p == ContentProperty::LENGTH) {
+               maybe_sequence_video ();
+       }
 
-                       (*i)->set_start (last);
-                       last = (*i)->end ();
-               }
+       ContentChanged (c, p);
+}
 
-               _sequencing_video = false;
+void
+Playlist::maybe_sequence_video ()
+{
+       if (!_sequence_video || _sequencing_video) {
+               return;
        }
        
-       ContentChanged (c, p);
+       _sequencing_video = true;
+       
+       ContentList cl = _content;
+       sort (cl.begin(), cl.end(), ContentSorter ());
+       Time last = 0;
+       for (ContentList::iterator i = cl.begin(); i != cl.end(); ++i) {
+               if (!dynamic_pointer_cast<VideoContent> (*i)) {
+                       continue;
+               }
+               
+               (*i)->set_start (last);
+               last = (*i)->end ();
+       }
+       
+       _sequencing_video = false;
 }
 
 string
index cf0f09b31bc0d2d88da6776149941759fcdb49be..805df4d70b3f30eddbc7cb21f1cd34ae50dc3a67 100644 (file)
@@ -86,6 +86,7 @@ public:
        Time video_end () const;
 
        void set_sequence_video (bool);
+       void maybe_sequence_video ();
 
        mutable boost::signals2::signal<void ()> Changed;
        mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged;
diff --git a/test/play_test.cc b/test/play_test.cc
new file mode 100644 (file)
index 0000000..12f80a2
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "player.h"
+
+/* This test needs stuff in Player that is only included in debug mode */
+#ifdef DCPOMATIC_DEBUG
+
+using boost::optional;
+
+struct Video
+{
+       boost::shared_ptr<Content> content;
+       boost::shared_ptr<const Image> image;
+       Time time;
+};
+
+class PlayerWrapper
+{
+public:
+       PlayerWrapper (shared_ptr<Player> p)
+               : _player (p)
+       {
+               _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _3));
+       }
+
+       void process_video (shared_ptr<const Image> i, bool, Time t)
+       {
+               Video v;
+               v.content = _player->_last_video;
+               v.image = i;
+               v.time = t;
+               _queue.push_front (v);
+       }
+
+       optional<Video> get_video ()
+       {
+               while (_queue.empty() && !_player->pass ()) {}
+               if (_queue.empty ()) {
+                       return optional<Video> ();
+               }
+               
+               Video v = _queue.back ();
+               _queue.pop_back ();
+               return v;
+       }
+
+       void seek (Time t, bool ac)
+       {
+               _player->seek (t, ac);
+               _queue.clear ();
+       }
+
+private:
+       shared_ptr<Player> _player;
+       std::list<Video> _queue;
+};
+
+BOOST_AUTO_TEST_CASE (play_test)
+{
+       shared_ptr<Film> film = new_test_film ("play_test");
+       film->set_dcp_content_type (DCPContentType::from_dci_name ("FTR"));
+       film->set_container (Ratio::from_id ("185"));
+       film->set_name ("play_test");
+
+       shared_ptr<FFmpegContent> A (new FFmpegContent (film, "test/data/red_24.mp4"));
+       film->examine_and_add_content (A);
+       wait_for_jobs ();
+
+       BOOST_CHECK_EQUAL (A->video_length(), 16);
+
+       shared_ptr<FFmpegContent> B (new FFmpegContent (film, "test/data/red_30.mp4"));
+       film->examine_and_add_content (B);
+       wait_for_jobs ();
+
+       BOOST_CHECK_EQUAL (B->video_length(), 16);
+       
+       /* Film should have been set to 25fps */
+       BOOST_CHECK_EQUAL (film->dcp_video_frame_rate(), 25);
+
+       BOOST_CHECK_EQUAL (A->start(), 0);
+       /* A is 16 frames long at 25 fps */
+       BOOST_CHECK_EQUAL (B->start(), 16 * TIME_HZ / 25);
+
+       shared_ptr<Player> player = film->player ();
+       PlayerWrapper wrap (player);
+       /* Seek and audio don't get on at the moment */
+       player->disable_audio ();
+
+       for (int i = 0; i < 32; ++i) {
+               optional<Video> v = wrap.get_video ();
+               BOOST_CHECK (v);
+               if (i < 16) {
+                       BOOST_CHECK (v.get().content == A);
+               } else {
+                       BOOST_CHECK (v.get().content == B);
+               }
+       }
+
+       player->seek (10 * TIME_HZ / 25, true);
+       optional<Video> v = wrap.get_video ();
+       BOOST_CHECK (v);
+       cout << (v.get().time * 25 / TIME_HZ) << "\n";
+       BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
+}
+
+#endif