Merge branch '1.0' into 1.0-seek
authorCarl Hetherington <cth@carlh.net>
Wed, 18 Dec 2013 16:32:44 +0000 (16:32 +0000)
committerCarl Hetherington <cth@carlh.net>
Wed, 18 Dec 2013 16:32:44 +0000 (16:32 +0000)
70 files changed:
doc/design/resampling.tex
src/lib/analyse_audio_job.cc
src/lib/analyse_audio_job.h
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_merger.h
src/lib/content.cc
src/lib/content.h
src/lib/decoded.h [new file with mode: 0644]
src/lib/decoder.cc
src/lib/decoder.h
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/film.cc
src/lib/film.h
src/lib/image_content.cc
src/lib/image_content.h
src/lib/image_decoder.cc
src/lib/image_decoder.h
src/lib/job.cc
src/lib/player.cc
src/lib/player.h
src/lib/playlist.cc
src/lib/playlist.h
src/lib/resampler.cc
src/lib/resampler.h
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/subtitle_decoder.cc
src/lib/subtitle_decoder.h
src/lib/timer.cc
src/lib/timer.h
src/lib/types.h
src/lib/util.cc
src/lib/util.h
src/lib/video_content.cc
src/lib/video_content.h
src/lib/video_decoder.cc
src/lib/video_decoder.h
src/tools/server_test.cc
src/wx/audio_plot.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/timecode.cc
src/wx/timecode.h
src/wx/timeline.cc
src/wx/timeline.h
src/wx/timeline_dialog.cc
src/wx/timeline_dialog.h
src/wx/timing_panel.cc
src/wx/timing_panel.h
src/wx/video_panel.cc
test/ffmpeg_seek_test.cc [new file with mode: 0644]
test/frame_rate_test.cc
test/long_ffmpeg_seek_test.cc [new file with mode: 0644]
test/play_test.cc
test/resampler_test.cc
test/test.cc
test/test.h
test/util_test.cc
test/wscript

index 44aeee9b1b4c8abcdea917a7aeeebe66620c7815..cf9cfb1edc220bcdfa6f242b4aeee94427c2a0d5 100644 (file)
@@ -1,4 +1,5 @@
 \documentclass{article}
+\usepackage{amsmath}
 \begin{document}
 
 Here is what resampling we need to do.  Content video is at $C_V$ fps, audio at $C_A$.  
@@ -18,6 +19,7 @@ $C_V$ is a DCI rate, $C_A$ is not.  e.g.\ if $C_V = 24$, $C_A = 44.1\times{}10^3
 \textbf{Resample $C_A$ to the DCI rate.}
 
 \section{Hard case 1}
+\label{sec:hard1}
 
 $C_V$ is not a DCI rate, $C_A$ is, e.g.\ if $C_V = 25$, $C_A =
 48\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
@@ -31,5 +33,24 @@ resample audio to $25 * 48\times{}10^3 / 24 = 50\times{}10^3$.
 \medskip
 \textbf{Resample $C_A$ to $C_V C_A / F_V$}
 
+\section{Hard case 2}
+
+Neither $C_V$ nor $C_A$ is not a DCI rate, e.g.\ if $C_V = 25$, $C_A =
+44.1\times{}10^3$.  We will run the video at a nearby DCI rate $F_V$,
+meaning that it will run faster or slower than it should.  We first
+resample the audio to a DCI rate $F_A$, then perform as with
+Section~\ref{sec:hard1} above.
+
+\medskip
+\textbf{Resample $C_A$ to $C_V F_A / F_V$}
+
+
+\section{The general case}
+
+Given a DCP running at $F_V$ and $F_A$ and a piece of content at $C_V$
+and $C_A$, resample the audio to $R_A$ where
+\begin{align*}
+R_A &= \frac{C_V F_A}{F_V}
+\end{align*}
 
 \end{document}
index 8186f9de49b9d89baa81c04d7f454f876b8de963..3f84bf16d7cc819240b8695a3628f962cd013ab0 100644 (file)
@@ -81,7 +81,7 @@ AnalyseAudioJob::run ()
 }
 
 void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, DCPTime)
 {
        for (int i = 0; i < b->frames(); ++i) {
                for (int j = 0; j < b->channels(); ++j) {
index 3d4881983b5234572ce40a47455c5b849a620328..2e93ef500ffcd05542f558d74ae4587a53f0fc9d 100644 (file)
@@ -33,7 +33,7 @@ public:
        void run ();
 
 private:
-       void audio (boost::shared_ptr<const AudioBuffers>, Time);
+       void audio (boost::shared_ptr<const AudioBuffers>, DCPTime);
 
        boost::weak_ptr<AudioContent> _content;
        OutputAudioFrame _done;
index 97372b962a7285368d2317d1d3be5c769d313eb4..de743571bf218b7e747b7792b46a057aa0965918 100644 (file)
@@ -39,7 +39,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
 int const AudioContentProperty::AUDIO_DELAY = 204;
 int const AudioContentProperty::AUDIO_MAPPING = 205;
 
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
        : Content (f, s)
        , _audio_gain (0)
        , _audio_delay (0)
@@ -148,3 +148,27 @@ AudioContent::technical_summary () const
 {
        return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
 }
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ *  is not very intelligent.
+ *
+ *  @param t Some duration to convert.
+ *  @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ *  the `controlling' video content is active.
+ */
+AudioContent::Frame
+AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const
+{
+       shared_ptr<const Film> film = _film.lock ();
+       assert (film);
+       
+       /* Consider the case where we're running a 25fps video at 24fps (i.e. slow)
+          Our audio is at 44.1kHz.  We will resample it to 48000 * 25 / 24 and then
+          run it at 48kHz (i.e. slow, to match).
+
+          After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples
+          in the source.
+       */
+       
+       return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ);
+}
index ca4a1f2348fea78b788632a38a6da5a058c091f1..e1b38bd97003c8c19b456b082e763750dc17ae25 100644 (file)
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
 public:
        typedef int64_t Frame;
        
-       AudioContent (boost::shared_ptr<const Film>, Time);
+       AudioContent (boost::shared_ptr<const Film>, DCPTime);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -74,6 +74,8 @@ public:
                return _audio_delay;
        }
 
+       Frame time_to_content_audio_frames (DCPTime, DCPTime) const;
+       
 private:
        /** Gain to apply to audio in dB */
        float _audio_gain;
index c0ef02f65d5ab5518dcb7e53aa145b1ff3f9a598..7ff8529c649a18c65ef6b858849d71bec42d5881 100644 (file)
@@ -35,24 +35,12 @@ using boost::shared_ptr;
 AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
        : Decoder (film)
        , _audio_content (content)
-       , _audio_position (0)
 {
 
 }
 
 void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
 {
-       Audio (data, frame);
-       _audio_position = frame + data->frames ();
-}
-
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- *  The player needs to know that there is no audio otherwise it will keep trying to
- *  pass() the decoder to get it to emit audio.
- */
-bool
-AudioDecoder::has_audio () const
-{
-       return _audio_content->audio_channels () > 0;
+       _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, time)));
 }
index ab6c4b8a931e12cfe892c8cb74d86ae7162d1a10..0cd0e9754c6c889fb77210b7109029ff50cfd1a7 100644 (file)
@@ -27,6 +27,7 @@
 #include "decoder.h"
 #include "content.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class AudioBuffers;
 
@@ -38,16 +39,15 @@ class AudioDecoder : public virtual Decoder
 public:
        AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
 
-       bool has_audio () const;
-
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+       boost::shared_ptr<const AudioContent> audio_content () const {
+               return _audio_content;
+       }
 
 protected:
 
-       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
-       boost::shared_ptr<const AudioContent> _audio_content;
-       AudioContent::Frame _audio_position;
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       
+       boost::shared_ptr<const AudioContent> _audio_content;   
 };
 
 #endif
index 226601e0ec61dffc92726bd8f98bbd976f21e9ad..f068b504e8dcbff0c0215a146de00550480bea97 100644 (file)
@@ -37,6 +37,8 @@ public:
        TimedAudioBuffers<T>
        pull (T time)
        {
+               assert (time >= _last_pull);
+               
                TimedAudioBuffers<T> out;
                
                F const to_return = _t_to_f (time - _last_pull);
@@ -97,9 +99,16 @@ public:
                if (_buffers->frames() == 0) {
                        return TimedAudioBuffers<T> ();
                }
-               
+
                return TimedAudioBuffers<T> (_buffers, _last_pull);
        }
+
+       void
+       clear (DCPTime t)
+       {
+               _last_pull = t;
+               _buffers.reset (new AudioBuffers (_buffers->channels(), 0));
+       }
        
 private:
        boost::shared_ptr<AudioBuffers> _buffers;
index f09012765a7cbe7616d1e2ae40a870981b74604a..7db349617ec031964648b19f0c3387a332b12b55 100644 (file)
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
 
 }
 
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
        : _film (f)
        , _position (p)
        , _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
                _paths.push_back ((*i)->content ());
        }
        _digest = node->string_child ("Digest");
-       _position = node->number_child<Time> ("Position");
-       _trim_start = node->number_child<Time> ("TrimStart");
-       _trim_end = node->number_child<Time> ("TrimEnd");
+       _position = node->number_child<DCPTime> ("Position");
+       _trim_start = node->number_child<DCPTime> ("TrimStart");
+       _trim_end = node->number_child<DCPTime> ("TrimEnd");
 }
 
 Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
 }
 
 void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -157,7 +157,7 @@ Content::set_position (Time p)
 }
 
 void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -168,7 +168,7 @@ Content::set_trim_start (Time t)
 }
 
 void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
@@ -200,7 +200,7 @@ Content::technical_summary () const
        return String::compose ("%1 %2 %3", path_summary(), digest(), position());
 }
 
-Time
+DCPTime
 Content::length_after_trim () const
 {
        return full_length() - trim_start() - trim_end();
@@ -210,7 +210,7 @@ Content::length_after_trim () const
  *  @return true if this time is trimmed by our trim settings.
  */
 bool
-Content::trimmed (Time t) const
+Content::trimmed (DCPTime t) const
 {
        return (t < trim_start() || t > (full_length() - trim_end ()));
 }
index 9cf6d866ab04906b3288188792c66f9403b8592e..1eee02e01463e434bd65acd3a1bac53bd7f72170 100644 (file)
@@ -49,7 +49,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
 {
 public:
        Content (boost::shared_ptr<const Film>);
-       Content (boost::shared_ptr<const Film>, Time);
+       Content (boost::shared_ptr<const Film>, DCPTime);
        Content (boost::shared_ptr<const Film>, boost::filesystem::path);
        Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -60,7 +60,7 @@ public:
        virtual std::string technical_summary () const;
        virtual std::string information () const = 0;
        virtual void as_xml (xmlpp::Node *) const;
-       virtual Time full_length () const = 0;
+       virtual DCPTime full_length () const = 0;
        virtual std::string identifier () const;
 
        boost::shared_ptr<Content> clone () const;
@@ -92,41 +92,41 @@ public:
                return _digest;
        }
 
-       void set_position (Time);
+       void set_position (DCPTime);
 
-       /** Time that this content starts; i.e. the time that the first
+       /** DCPTime that this content starts; i.e. the time that the first
         *  bit of the content (trimmed or not) will happen.
         */
-       Time position () const {
+       DCPTime position () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _position;
        }
 
-       void set_trim_start (Time);
+       void set_trim_start (DCPTime);
 
-       Time trim_start () const {
+       DCPTime trim_start () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_start;
        }
 
-       void set_trim_end (Time);
+       void set_trim_end (DCPTime);
        
-       Time trim_end () const {
+       DCPTime trim_end () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _trim_end;
        }
        
-       Time end () const {
+       DCPTime end () const {
                return position() + length_after_trim() - 1;
        }
 
-       Time length_after_trim () const;
+       DCPTime length_after_trim () const;
        
        void set_change_signals_frequent (bool f) {
                _change_signals_frequent = f;
        }
 
-       bool trimmed (Time) const;
+       bool trimmed (DCPTime) const;
 
        boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
 
@@ -145,9 +145,9 @@ protected:
        
 private:
        std::string _digest;
-       Time _position;
-       Time _trim_start;
-       Time _trim_end;
+       DCPTime _position;
+       DCPTime _trim_start;
+       DCPTime _trim_end;
        bool _change_signals_frequent;
 };
 
diff --git a/src/lib/decoded.h b/src/lib/decoded.h
new file mode 100644 (file)
index 0000000..a9b9227
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+    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.
+
+*/
+
+#ifndef DCPOMATIC_LIB_DECODED_H
+#define DCPOMATIC_LIB_DECODED_H
+
+#include "types.h"
+#include "rect.h"
+
+class Image;
+
+class Decoded
+{
+public:
+       Decoded ()
+               : content_time (0)
+               , dcp_time (0)
+       {}
+
+       Decoded (DCPTime ct)
+               : content_time (ct)
+               , dcp_time (0)
+       {}
+
+       virtual ~Decoded () {}
+
+       virtual void set_dcp_times (float speed_up, DCPTime offset) = 0;
+
+       ContentTime content_time;
+       DCPTime dcp_time;
+};
+
+/** One frame of video from a VideoDecoder */
+class DecodedVideo : public Decoded
+{
+public:
+       DecodedVideo ()
+               : eyes (EYES_BOTH)
+               , same (false)
+       {}
+
+       DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, ContentTime ct)
+               : Decoded (ct)
+               , image (im)
+               , eyes (e)
+               , same (s)
+       {}
+
+       void set_dcp_times (float speed_up, DCPTime offset) {
+               dcp_time = rint (content_time / speed_up) + offset;
+       }
+       
+       boost::shared_ptr<const Image> image;
+       Eyes eyes;
+       bool same;
+};
+
+class DecodedAudio : public Decoded
+{
+public:
+       DecodedAudio (boost::shared_ptr<const AudioBuffers> d, ContentTime ct)
+               : Decoded (ct)
+               , data (d)
+       {}
+
+       void set_dcp_times (float speed_up, DCPTime offset) {
+               dcp_time = rint (content_time / speed_up) + offset;
+       }
+       
+       boost::shared_ptr<const AudioBuffers> data;
+};
+
+class DecodedSubtitle : public Decoded
+{
+public:
+       DecodedSubtitle ()
+               : content_time_to (0)
+               , dcp_time_to (0)
+       {}
+
+       /* XXX: content/dcp time here */
+       DecodedSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, DCPTime f, DCPTime t)
+               : Decoded (f)
+               , image (im)
+               , rect (r)
+               , content_time_to (t)
+               , dcp_time_to (t)
+       {}
+
+       void set_dcp_times (float speed_up, DCPTime offset) {
+               dcp_time = rint (content_time / speed_up) + offset;
+               dcp_time_to = rint (content_time_to / speed_up) + offset;
+       }
+
+       boost::shared_ptr<Image> image;
+       dcpomatic::Rect<double> rect;
+       ContentTime content_time_to;
+       DCPTime dcp_time_to;
+};
+
+#endif
index 3f4cda6eb5a4345595410fe475314f3d24881bd7..18c5acd3551b3442eea6e8882d16f08f658f48b2 100644 (file)
@@ -36,3 +36,32 @@ Decoder::Decoder (shared_ptr<const Film> f)
 {
 
 }
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+       while (_pending.empty () && !pass ()) {}
+
+       if (_pending.empty ()) {
+               return shared_ptr<Decoded> ();
+       }
+
+       return _pending.front ();
+}
+
+shared_ptr<Decoded>
+Decoder::get ()
+{
+       shared_ptr<Decoded> d = peek ();
+       if (d) {
+               _pending.pop_front ();
+       }
+
+       return d;
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+       _pending.clear ();
+}
index d67592ed812544c644b8766bcb1b1be1c03e84de..654cacad4bc63d71131483422fcb231fe9a0729a 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
 #include <boost/utility.hpp>
+#include "types.h"
 
 class Film;
+class Decoded;
 
 /** @class Decoder.
  *  @brief Parent class for decoders of content.
@@ -39,18 +41,31 @@ public:
        Decoder (boost::shared_ptr<const Film>);
        virtual ~Decoder () {}
 
-       /** Perform one decode pass of the content, which may or may not
-        *  cause the object to emit some data.
+       /** Seek so that the next get_*() will yield the next thing
+        *  (video/sound frame, subtitle etc.) at or after the requested
+        *  time.  Pass accurate = true to try harder to get close to
+        *  the request.
         */
-       virtual void pass () = 0;
-       virtual bool done () const = 0;
+       virtual void seek (ContentTime time, bool accurate);
+       
+       boost::shared_ptr<Decoded> peek ();
+       boost::shared_ptr<Decoded> get ();
 
 protected:
 
+       /** Perform one decode pass of the content, which may or may not
+        *  result in a complete quantum (Decoded object) of decoded stuff
+        *  being made ready.
+        *  @return true if the decoder is done (i.e. no more data will be
+        *  produced by any future calls to pass() without a seek() first).
+        */
+       virtual bool pass () = 0;
        virtual void flush () {};
        
        /** The Film that we are decoding in */
        boost::weak_ptr<const Film> _film;
+
+       std::list<boost::shared_ptr<Decoded> > _pending;
 };
 
 #endif
index e5e5f317a0efeea02dda41588685e1e2d58d3fe7..53d12419a6872f88a8bdb8b5f58658867620d236 100644 (file)
@@ -191,6 +191,10 @@ FFmpeg::video_codec_context () const
 AVCodecContext *
 FFmpeg::audio_codec_context () const
 {
+       if (!_ffmpeg_content->audio_stream ()) {
+               return 0;
+       }
+       
        return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
 }
 
index 9533315a517e2a0501796bfe3d6f1522f55ff27a..65a8d24f1ce3f073f697b4e364495246511d78e6 100644 (file)
@@ -310,16 +310,15 @@ FFmpegContent::output_audio_frame_rate () const
        /* Resample to a DCI-approved sample rate */
        double t = dcp_audio_frame_rate (content_audio_frame_rate ());
 
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
 
        /* Compensate if the DCP is being run at a different frame rate
           to the source; that is, if the video is run such that it will
           look different in the DCP compared to the source (slower or faster).
-          skip/repeat doesn't come into effect here.
        */
 
        if (frc.change_speed) {
-               t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+               t /= frc.speed_up;
        }
 
        return rint (t);
@@ -446,13 +445,13 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
        FFmpegStream::as_xml (root);
 }
 
-Time
+DCPTime
 FFmpegContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+       FrameRateChange frc (video_frame_rate (), film->video_frame_rate ());
        return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
 }
 
index 7ff159b852b4cb69df4004b1f6a813ad2131e182..ba73c0f9b23cdf00d0812417fd1e70a0f11ef2d8 100644 (file)
@@ -134,7 +134,7 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
index 5a1b78762d92e6e30d55108dfae637f8b348e676..b2e8cc44a0d56b1c9fe1aa30f10ab122c0fa15d7 100644 (file)
@@ -70,7 +70,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
        , _decode_audio (audio)
        , _video_pts_offset (0)
        , _audio_pts_offset (0)
-       , _just_sought (false)
 {
        setup_subtitle ();
 
@@ -89,7 +88,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
        */
 
        bool const have_video = video && c->first_video();
-       bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+       bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
 
        /* First, make one of them start at 0 */
 
@@ -143,12 +142,15 @@ FFmpegDecoder::flush ()
                decode_audio_packet ();
        }
 
+#if 0  
+       /* XXX */
        /* Stop us being asked for any more data */
        _video_position = _ffmpeg_content->video_length ();
        _audio_position = _ffmpeg_content->audio_length ();
+#endif 
 }
 
-void
+bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
@@ -164,7 +166,7 @@ FFmpegDecoder::pass ()
                }
 
                flush ();
-               return;
+               return true;
        }
 
        avcodec_get_frame_defaults (_frame);
@@ -183,6 +185,7 @@ FFmpegDecoder::pass ()
        }
 
        av_free_packet (&_packet);
+       return false;
 }
 
 /** @param data pointer to array of pointers to buffers.
@@ -297,70 +300,131 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (ContentTime, ContentTime, int)> finished)
 {
-       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+       int frames_read = 0;
+       ContentTime last_video = 0;
+       ContentTime last_audio = 0;
+       bool flushing = false;
 
-       /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
-          a number plucked from the air) earlier than we want to end up.  The loop below
-          will hopefully then step through to where we want to be.
-       */
-       int initial = frame;
+       while (!finished (last_video, last_audio, frames_read)) {
+               int r = av_read_frame (_format_context, &_packet);
+               if (r < 0) {
+                       /* We should flush our decoders here, possibly yielding a few more frames,
+                          but the consequence of having to do that is too hideous to contemplate.
+                          Instead we give up and say that you can't seek too close to the end
+                          of a file.
+                       */
+                       return frames_read;
+               }
 
-       if (accurate) {
-               initial -= 5;
-       }
+               ++frames_read;
 
-       if (initial < 0) {
-               initial = 0;
+               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+               if (_packet.stream_index == _video_stream) {
+
+                       avcodec_get_frame_defaults (_frame);
+                       
+                       int finished = 0;
+                       r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+                       if (r >= 0 && finished) {
+                               last_video = rint (
+                                       (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * TIME_HZ
+                                       );
+                       }
+                               
+               } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+                       AVPacket copy_packet = _packet;
+                       while (copy_packet.size > 0) {
+
+                               int finished;
+                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+                               if (r >= 0 && finished) {
+                                       last_audio = rint (
+                                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _audio_pts_offset) * TIME_HZ
+                                               );
+                               }
+                                       
+                               copy_packet.data += r;
+                               copy_packet.size -= r;
+                       }
+               }
+               
+               av_free_packet (&_packet);
        }
 
-       /* Initial seek time in the stream's timebase */
-       int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
+       return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, ContentTime last_video, ContentTime last_audio) const
+{
+       return last_video >= seek || last_audio >= seek;
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+       return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+       int64_t const initial_v = ((double (t) / TIME_HZ) - _video_pts_offset) /
+               av_q2d (_format_context->streams[_video_stream]->time_base);
+
+       av_seek_frame (_format_context, _video_stream, initial_v, AVSEEK_FLAG_BACKWARD);
+
+       shared_ptr<FFmpegAudioStream> as = _ffmpeg_content->audio_stream ();
+       if (as) {
+               int64_t initial_a = ((double (t) / TIME_HZ) - _audio_pts_offset) /
+                       av_q2d (as->stream(_format_context)->time_base);
 
-       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+               av_seek_frame (_format_context, as->index (_format_context), initial_a, AVSEEK_FLAG_BACKWARD);
+       }
 
        avcodec_flush_buffers (video_codec_context());
+       if (audio_codec_context ()) {
+               avcodec_flush_buffers (audio_codec_context ());
+       }
        if (_subtitle_codec_context) {
                avcodec_flush_buffers (_subtitle_codec_context);
        }
+}
 
-       _just_sought = true;
-       _video_position = frame;
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+       Decoder::seek (time, accurate);
        
-       if (frame == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
-               return;
+       /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+          a number plucked from the air) earlier than we want to end up.  The loop below
+          will hopefully then step through to where we want to be.
+       */
+
+       ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
+       ContentTime initial_seek = time - pre_roll;
+       if (initial_seek < 0) {
+               initial_seek = 0;
        }
+       
+       /* Initial seek time in the video stream's timebase */
 
-       while (1) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       return;
-               }
+       seek_and_flush (initial_seek);
 
-               if (_packet.stream_index != _video_stream) {
-                       av_free_packet (&_packet);
-                       continue;
-               }
-               
-               avcodec_get_frame_defaults (_frame);
-               
-               int finished = 0;
-               r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
-               if (r >= 0 && finished) {
-                       _video_position = rint (
-                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
-                               );
+       if (time == 0 || !accurate) {
+               /* We're already there, or we're as close as we need to be */
+               return;
+       }
 
-                       if (_video_position >= (frame - 1)) {
-                               av_free_packet (&_packet);
-                               break;
-                       }
-               }
-               
-               av_free_packet (&_packet);
+       int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
+       
+       seek_and_flush (initial_seek);
+       if (N > 0) {
+               minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
        }
 }
 
@@ -377,6 +441,7 @@ FFmpegDecoder::decode_audio_packet ()
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
                if (decode_result < 0) {
                        shared_ptr<const Film> film = _film.lock ();
                        assert (film);
@@ -385,31 +450,16 @@ FFmpegDecoder::decode_audio_packet ()
                }
 
                if (frame_finished) {
-                       
-                       if (_audio_position == 0) {
-                               /* Where we are in the source, in seconds */
-                               double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                       * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset;
-
-                               if (pts > 0) {
-                                       /* Emit some silence */
-                                       shared_ptr<AudioBuffers> silence (
-                                               new AudioBuffers (
-                                                       _ffmpeg_content->audio_channels(),
-                                                       pts * _ffmpeg_content->content_audio_frame_rate()
-                                                       )
-                                               );
-                                       
-                                       silence->make_silent ();
-                                       audio (silence, _audio_position);
-                               }
-                       }
-                       
+                       ContentTime const t = rint (
+                               (av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+                                * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset) * TIME_HZ
+                               );
+
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
                        
-                       audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+                       audio (deinterleave_audio (_frame->data, data_size), t);
                }
                        
                copy_packet.data += decode_result;
@@ -458,45 +508,8 @@ FFmpegDecoder::decode_video_packet ()
                }
                
                if (i->second != AV_NOPTS_VALUE) {
-
-                       double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset;
-
-                       if (_just_sought) {
-                               /* We just did a seek, so disable any attempts to correct for where we
-                                  are / should be.
-                               */
-                               _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
-                               _just_sought = false;
-                       }
-
-                       double const next = _video_position / _ffmpeg_content->video_frame_rate();
-                       double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
-                       double delta = pts - next;
-
-                       while (delta > one_frame) {
-                               /* This PTS is more than one frame forward in time of where we think we should be; emit
-                                  a black frame.
-                               */
-
-                               /* XXX: I think this should be a copy of the last frame... */
-                               boost::shared_ptr<Image> black (
-                                       new Image (
-                                               static_cast<AVPixelFormat> (_frame->format),
-                                               libdcp::Size (video_codec_context()->width, video_codec_context()->height),
-                                               true
-                                               )
-                                       );
-                               
-                               black->make_black ();
-                               video (image, false, _video_position);
-                               delta -= one_frame;
-                       }
-
-                       if (delta > -one_frame) {
-                               /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
-                               video (image, false, _video_position);
-                       }
-                               
+                       ContentTime const t = rint ((i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset) * TIME_HZ);
+                       video (image, false, t);
                } else {
                        shared_ptr<const Film> film = _film.lock ();
                        assert (film);
@@ -529,14 +542,6 @@ FFmpegDecoder::setup_subtitle ()
        }
 }
 
-bool
-FFmpegDecoder::done () const
-{
-       bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
-       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
-       return vd && ad;
-}
-       
 void
 FFmpegDecoder::decode_subtitle_packet ()
 {
@@ -562,8 +567,8 @@ FFmpegDecoder::decode_subtitle_packet ()
        double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
        
        /* hence start time for this sub */
-       Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+       DCPTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+       DCPTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 
        AVSubtitleRect const * rect = sub.rects[0];
 
index 11f83ed97c9808aeaff603f110814463b51234a6..e50b248cff35992ad3da52c37e6b9fb97ecf12d5 100644 (file)
@@ -51,15 +51,12 @@ public:
        FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
        ~FFmpegDecoder ();
 
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
+       void seek (ContentTime time, bool);
 
 private:
        friend class ::ffmpeg_pts_offset_test;
 
-       static double compute_pts_offset (double, double, float);
-
+       bool pass ();
        void flush ();
 
        void setup_subtitle ();
@@ -74,6 +71,11 @@ private:
        void maybe_add_subtitle ();
        boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
 
+       bool seek_overrun_finished (ContentTime, ContentTime, ContentTime) const;
+       bool seek_final_finished (int, int) const;
+       int minimal_run (boost::function<bool (ContentTime, ContentTime, int)>);
+       void seek_and_flush (int64_t);
+
        AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
        AVCodec* _subtitle_codec;                ///< may be 0 if there is no subtitle
        
@@ -85,5 +87,4 @@ private:
 
        double _video_pts_offset;
        double _audio_pts_offset;
-       bool _just_sought;
 };
index 5946d5bec8ce3db40f8c53d06259ae521f8dd170..8e93667d5e54ad1ccb7474257e6536ce97a1bd27 100644 (file)
@@ -842,7 +842,7 @@ Film::move_content_later (shared_ptr<Content> c)
        _playlist->move_later (c);
 }
 
-Time
+DCPTime
 Film::length () const
 {
        return _playlist->length ();
@@ -866,6 +866,12 @@ Film::content_paths_valid () const
        return _playlist->content_paths_valid ();
 }
 
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+       return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
 void
 Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
 {
@@ -885,24 +891,24 @@ Film::playlist_changed ()
 }      
 
 OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
+Film::time_to_audio_frames (DCPTime t) const
 {
        return t * audio_frame_rate () / TIME_HZ;
 }
 
 OutputVideoFrame
-Film::time_to_video_frames (Time t) const
+Film::time_to_video_frames (DCPTime t) const
 {
        return t * video_frame_rate () / TIME_HZ;
 }
 
-Time
+DCPTime
 Film::audio_frames_to_time (OutputAudioFrame f) const
 {
        return f * TIME_HZ / audio_frame_rate ();
 }
 
-Time
+DCPTime
 Film::video_frames_to_time (OutputVideoFrame f) const
 {
        return f * TIME_HZ / video_frame_rate ();
index 4b07f84a928503befb9fd231ab2f4a0a8efd97e9..a0ace99811005d5b93bffcffa6d7cbb039f7a539 100644 (file)
@@ -103,18 +103,19 @@ public:
 
        OutputAudioFrame audio_frame_rate () const;
 
-       OutputAudioFrame time_to_audio_frames (Time) const;
-       OutputVideoFrame time_to_video_frames (Time) const;
-       Time video_frames_to_time (OutputVideoFrame) const;
-       Time audio_frames_to_time (OutputAudioFrame) const;
+       OutputAudioFrame time_to_audio_frames (DCPTime) const;
+       OutputVideoFrame time_to_video_frames (DCPTime) const;
+       DCPTime video_frames_to_time (OutputVideoFrame) const;
+       DCPTime audio_frames_to_time (OutputAudioFrame) const;
 
        /* Proxies for some Playlist methods */
 
        ContentList content () const;
-       Time length () const;
+       DCPTime length () const;
        bool has_subtitles () const;
        OutputVideoFrame best_video_frame_rate () const;
        bool content_paths_valid () const;
+       FrameRateChange active_frame_rate_change (DCPTime) const;
 
        libdcp::KDM
        make_kdm (
index b05fa6b8d69ce8599503e3d9b3c1e6c5179e6c9a..0f952607159f99e4da16b6990f7efac0a32a541b 100644 (file)
@@ -120,13 +120,13 @@ ImageContent::set_video_length (VideoContent::Frame len)
        signal_changed (ContentProperty::LENGTH);
 }
 
-Time
+DCPTime
 ImageContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
        return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
 }
 
index 47c5a20e3486298e2e49d307a0136a21a4b80243..88c178faad35578cc5e7143f334c9feb1d22dbef 100644 (file)
@@ -41,7 +41,7 @@ public:
        std::string summary () const;
        std::string technical_summary () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        std::string identifier () const;
        
index fb6053ae5226188d91f31809133df65aec47d011..9e90b5bc8317cccd5d46b4c36912fd6872653337 100644 (file)
@@ -36,20 +36,22 @@ ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageCont
        : Decoder (f)
        , VideoDecoder (f, c)
        , _image_content (c)
+       , _video_position (0)
 {
 
 }
 
-void
+bool
 ImageDecoder::pass ()
 {
        if (_video_position >= _image_content->video_length ()) {
-               return;
+               return true;
        }
 
        if (_image && _image_content->still ()) {
-               video (_image, true, _video_position);
-               return;
+               video (_image, true, _video_position * TIME_HZ / _video_content->video_frame_rate ());
+               ++_video_position;
+               return false;
        }
 
        Magick::Image* magick_image = new Magick::Image (_image_content->path (_image_content->still() ? 0 : _video_position).string ());
@@ -73,17 +75,16 @@ ImageDecoder::pass ()
 
        delete magick_image;
 
-       video (_image, false, _video_position);
-}
+       video (_image, false, _video_position * TIME_HZ / _video_content->video_frame_rate ());
+       ++_video_position;
 
-void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
-       _video_position = frame;
+       return false;
 }
 
-bool
-ImageDecoder::done () const
+void
+ImageDecoder::seek (ContentTime time, bool accurate)
 {
-       return _video_position >= _image_content->video_length ();
+       Decoder::seek (time, accurate);
+       
+       _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ);
 }
index c7500243e08091ec3d6669f3c14273dfb93454e4..f4d81dfb507a5ef545c0f573542904472d023956 100644 (file)
@@ -34,14 +34,13 @@ public:
                return _image_content;
        }
 
-       /* Decoder */
-
-       void pass ();
-       void seek (VideoContent::Frame, bool);
-       bool done () const;
+       void seek (ContentTime, bool);
 
 private:
+       bool pass ();
+       
        boost::shared_ptr<const ImageContent> _image_content;
        boost::shared_ptr<Image> _image;
+       ContentTime _video_position;
 };
 
index 9981934ec93767d1286ff459acbe3845de9af821..05a90524c9818615112b77464da5a56bd154ec69 100644 (file)
@@ -198,7 +198,7 @@ Job::set_state (State s)
        }
 }
 
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
 int
 Job::elapsed_time () const
 {
index 09a4374943a7dabaf189f2e179d13a904aec848a..f5bc68565935c953f076d57752574935f85e91e1 100644 (file)
@@ -45,69 +45,20 @@ using std::map;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
+using boost::optional;
 
 class Piece
 {
 public:
-       Piece (shared_ptr<Content> c)
-               : content (c)
-               , video_position (c->position ())
-               , audio_position (c->position ())
-               , repeat_to_do (0)
-               , repeat_done (0)
-       {}
-       
-       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+       Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
                : content (c)
                , decoder (d)
-               , video_position (c->position ())
-               , audio_position (c->position ())
+               , frc (f)
        {}
 
-       /** Set this piece to repeat a video frame a given number of times */
-       void set_repeat (IncomingVideo video, int num)
-       {
-               repeat_video = video;
-               repeat_to_do = num;
-               repeat_done = 0;
-       }
-
-       void reset_repeat ()
-       {
-               repeat_video.image.reset ();
-               repeat_to_do = 0;
-               repeat_done = 0;
-       }
-
-       bool repeating () const
-       {
-               return repeat_done != repeat_to_do;
-       }
-
-       void repeat (Player* player)
-       {
-               player->process_video (
-                       repeat_video.weak_piece,
-                       repeat_video.image,
-                       repeat_video.eyes,
-                       repeat_done > 0,
-                       repeat_video.frame,
-                       (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
-                       );
-
-               ++repeat_done;
-       }
-       
        shared_ptr<Content> content;
        shared_ptr<Decoder> decoder;
-       /** Time of the last video we emitted relative to the start of the DCP */
-       Time video_position;
-       /** Time of the last audio we emitted relative to the start of the DCP */
-       Time audio_position;
-
-       IncomingVideo repeat_video;
-       int repeat_to_do;
-       int repeat_done;
+       FrameRateChange frc;
 };
 
 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
@@ -120,6 +71,7 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        , _audio_position (0)
        , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
        , _last_emit_was_black (false)
+       , _just_did_inaccurate_seek (false)
 {
        _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -146,110 +98,109 @@ Player::pass ()
                setup_pieces ();
        }
 
-       Time earliest_t = TIME_MAX;
-       shared_ptr<Piece> earliest;
-       enum {
-               VIDEO,
-               AUDIO
-       } type = VIDEO;
+       /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+       shared_ptr<Piece> earliest_piece;
+       shared_ptr<Decoded> earliest_decoded;
+       DCPTime earliest_time = TIME_MAX;
+       DCPTime earliest_audio = TIME_MAX;
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               if ((*i)->decoder->done ()) {
-                       continue;
-               }
 
-               shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
-               shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+               shared_ptr<Decoded> dec = (*i)->decoder->peek ();
 
-               if (_video && vd) {
-                       if ((*i)->video_position < earliest_t) {
-                               earliest_t = (*i)->video_position;
-                               earliest = *i;
-                               type = VIDEO;
-                       }
+               if (dec) {
+                       dec->set_dcp_times ((*i)->frc.speed_up, (*i)->content->position());
                }
 
-               if (_audio && ad && ad->has_audio ()) {
-                       if ((*i)->audio_position < earliest_t) {
-                               earliest_t = (*i)->audio_position;
-                               earliest = *i;
-                               type = AUDIO;
+               /* XXX: don't know what to do with this */
+#if 0          
+               if (ad->done()) {
+                       shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> ((*i)->content);
+                       assert (ac);
+                       shared_ptr<Resampler> re = resampler (ac, false);
+                       if (re) {
+                               shared_ptr<const AudioBuffers> b = re->flush ();
+                               if (b->frames ()) {
+                                       process_audio (earliest, b, ac->audio_length ());
+                               }
                        }
                }
-       }
+#endif         
 
-       if (!earliest) {
+               if (dec && dec->dcp_time < earliest_time) {
+                       earliest_piece = *i;
+                       earliest_decoded = dec;
+                       earliest_time = dec->dcp_time;
+               }
+
+               if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+                       earliest_audio = dec->dcp_time;
+               }
+       }
+               
+       if (!earliest_piece) {
                flush ();
                return true;
        }
 
-       switch (type) {
-       case VIDEO:
-               if (earliest_t > _video_position) {
-                       emit_black ();
-               } else {
-                       if (earliest->repeating ()) {
-                               earliest->repeat (this);
-                       } else {
-                               earliest->decoder->pass ();
-                       }
-               }
-               break;
-
-       case AUDIO:
-               if (earliest_t > _audio_position) {
-                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
-               } else {
-                       earliest->decoder->pass ();
-
-                       if (earliest->decoder->done()) {
-                               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
-                               assert (ac);
-                               shared_ptr<Resampler> re = resampler (ac, false);
-                               if (re) {
-                                       shared_ptr<const AudioBuffers> b = re->flush ();
-                                       if (b->frames ()) {
-                                               process_audio (earliest, b, ac->audio_length ());
-                                       }
-                               }
-                       }
-               }
-               break;
+       if (earliest_audio != TIME_MAX) {
+               TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (earliest_audio);
+               Audio (tb.audio, tb.time);
+               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
        }
 
-       if (_audio) {
-               boost::optional<Time> audio_done_up_to;
-               for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-                       if ((*i)->decoder->done ()) {
-                               continue;
+       /* Emit the earliest thing */
+
+       shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+       shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+       shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
+
+       if (dv) {
+               if (!_just_did_inaccurate_seek && earliest_time > _video_position) {
+
+                       /* See if we're inside some video content */
+                       list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+                       while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+                               ++i;
                        }
 
-                       if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
-                               audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
+                       if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+                               /* We're outside all video content */
+                               emit_black ();
+                       } else {
+                               _last_incoming_video.video->dcp_time = _video_position;
+                               emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
                        }
+               } else {
+                       emit_video (earliest_piece, dv);
+                       earliest_piece->decoder->get ();
                }
-
-               if (audio_done_up_to) {
-                       TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
-                       Audio (tb.audio, tb.time);
-                       _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+       } else if (da) {
+               if (!_just_did_inaccurate_seek && earliest_time > _audio_position) {
+                       emit_silence (earliest_time - _audio_position);
+               } else {
+                       emit_audio (earliest_piece, da);
+                       earliest_piece->decoder->get ();
                }
+       } else if (ds) {
+               _in_subtitle.piece = earliest_piece;
+               _in_subtitle.subtitle = ds;
+               update_subtitle ();
+               earliest_piece->decoder->get ();
        }
-               
+
+       _just_did_inaccurate_seek = false;
+
        return false;
 }
 
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
 void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
 {
        /* Keep a note of what came in so that we can repeat it if required */
        _last_incoming_video.weak_piece = weak_piece;
-       _last_incoming_video.image = image;
-       _last_incoming_video.eyes = eyes;
-       _last_incoming_video.same = same;
-       _last_incoming_video.frame = frame;
-       _last_incoming_video.extra = extra;
+       _last_incoming_video.video = video;
        
        shared_ptr<Piece> piece = weak_piece.lock ();
        if (!piece) {
@@ -259,23 +210,24 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
        shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
        assert (content);
 
-       FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
+       FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
+#if 0
+       XXX
        if (frc.skip && (frame % 2) == 1) {
                return;
        }
+#endif 
 
-       Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
-       if (content->trimmed (relative_time)) {
+       if (content->trimmed (video->dcp_time - content->position ())) {
                return;
        }
 
-       Time const time = content->position() + relative_time + extra - content->trim_start ();
        float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
        libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
 
        shared_ptr<PlayerImage> pi (
                new PlayerImage (
-                       image,
+                       video->image,
                        content->crop(),
                        image_size,
                        _video_container_size,
@@ -283,32 +235,32 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
                        )
                );
        
-       if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+       if (
+               _film->with_subtitles () &&
+               _out_subtitle.subtitle->image &&
+               video->dcp_time >= _out_subtitle.subtitle->dcp_time && video->dcp_time <= _out_subtitle.subtitle->dcp_time_to
+               ) {
 
                Position<int> const container_offset (
                        (_video_container_size.width - image_size.width) / 2,
                        (_video_container_size.height - image_size.width) / 2
                        );
 
-               pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
+               pi->set_subtitle (_out_subtitle.subtitle->image, _out_subtitle.position + container_offset);
        }
                                            
 #ifdef DCPOMATIC_DEBUG
        _last_video = piece->content;
 #endif
 
-       Video (pi, eyes, content->colour_conversion(), same, time);
+       Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
 
        _last_emit_was_black = false;
-       _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
-
-       if (frc.repeat > 1 && !piece->repeating ()) {
-               piece->set_repeat (_last_incoming_video, frc.repeat - 1);
-       }
+       _video_position = rint (video->dcp_time + TIME_HZ / _film->video_frame_rate());
 }
 
 void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
 {
        shared_ptr<Piece> piece = weak_piece.lock ();
        if (!piece) {
@@ -320,61 +272,54 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
 
        /* Gain */
        if (content->audio_gain() != 0) {
-               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+               shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
                gain->apply_gain (content->audio_gain ());
-               audio = gain;
+               audio->data = gain;
        }
 
        /* Resample */
        if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
-               shared_ptr<Resampler> r = resampler (content, true);
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
-               audio = ro.first;
-               frame = ro.second;
+               audio->data = resampler(content, true)->run (audio->data);
        }
        
-       Time const relative_time = _film->audio_frames_to_time (frame);
-
-       if (content->trimmed (relative_time)) {
+       if (content->trimmed (audio->dcp_time - content->position ())) {
                return;
        }
 
-       Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-       
        /* Remap channels */
-       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
        dcp_mapped->make_silent ();
        list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
        for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
-               if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
-                       dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+               if (i->first < audio->data->channels() && i->second < dcp_mapped->channels()) {
+                       dcp_mapped->accumulate_channel (audio->data.get(), i->first, i->second);
                }
        }
 
-       audio = dcp_mapped;
+       audio->data = dcp_mapped;
 
-       /* We must cut off anything that comes before the start of all time */
-       if (time < 0) {
-               int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
-               if (frames >= audio->frames ()) {
+       /* Delay */
+       audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+       if (audio->dcp_time < 0) {
+               int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+               if (frames >= audio->data->frames ()) {
                        return;
                }
 
-               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
-               trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+               shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+               trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
 
-               audio = trimmed;
-               time = 0;
+               audio->data = trimmed;
+               audio->dcp_time = 0;
        }
 
-       _audio_merger.push (audio, time);
-       piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+       _audio_merger.push (audio->data, audio->dcp_time);
 }
 
 void
 Player::flush ()
 {
-       TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+       TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
        if (tb.audio) {
                Audio (tb.audio, tb.time);
                _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -385,7 +330,7 @@ Player::flush ()
        }
 
        while (_audio_position < _video_position) {
-               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+               emit_silence (_video_position - _audio_position);
        }
        
 }
@@ -395,7 +340,7 @@ Player::flush ()
  *  @return true on error
  */
 void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
 {
        if (!_have_valid_pieces) {
                setup_pieces ();
@@ -406,89 +351,105 @@ Player::seek (Time t, bool accurate)
        }
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
-               if (!vc) {
-                       continue;
-               }
-
                /* s is the offset of t from the start position of this content */
-               Time s = t - vc->position ();
-               s = max (static_cast<Time> (0), s);
-               s = min (vc->length_after_trim(), s);
+               DCPTime s = t - (*i)->content->position ();
+               s = max (static_cast<DCPTime> (0), s);
+               s = min ((*i)->content->length_after_trim(), s);
 
-               /* Hence set the piece positions to the `global' time */
-               (*i)->video_position = (*i)->audio_position = vc->position() + s;
+               /* Convert this to the content time */
+               ContentTime ct = (s * (*i)->frc.speed_up) + (*i)->content->trim_start ();
 
                /* And seek the decoder */
-               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
-                       vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
-                       );
-
-               (*i)->reset_repeat ();
+               (*i)->decoder->seek (ct, accurate);
        }
 
-       _video_position = _audio_position = t;
+       _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
+       _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
 
-       /* XXX: don't seek audio because we don't need to... */
+       _audio_merger.clear (_audio_position);
+
+       if (!accurate) {
+               /* We just did an inaccurate seek, so it's likely that the next thing seen
+                  out of pass() will be a fair distance from _{video,audio}_position.  Setting
+                  this flag stops pass() from trying to fix that: we assume that if it
+                  was an inaccurate seek then the caller does not care too much about
+                  inserting black/silence to keep the time tidy.
+               */
+               _just_did_inaccurate_seek = true;
+       }
 }
 
 void
 Player::setup_pieces ()
 {
        list<shared_ptr<Piece> > old_pieces = _pieces;
-
        _pieces.clear ();
 
        ContentList content = _playlist->content ();
-       sort (content.begin(), content.end(), ContentSorter ());
 
        for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 
-               shared_ptr<Piece> piece (new Piece (*i));
-
-               /* XXX: into content? */
+               shared_ptr<Decoder> decoder;
+               optional<FrameRateChange> frc;
 
                shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
                if (fc) {
-                       shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-                       
-                       fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                       fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
-                       fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
-                       fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
-                       piece->decoder = fd;
+                       decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
+                       frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
                }
                
                shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
                if (ic) {
-                       bool reusing = false;
-                       
                        /* See if we can re-use an old ImageDecoder */
                        for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
                                shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
                                if (imd && imd->content() == ic) {
-                                       piece = *j;
-                                       reusing = true;
+                                       decoder = imd;
                                }
                        }
 
-                       if (!reusing) {
-                               shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
-                               id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
-                               piece->decoder = id;
+                       if (!decoder) {
+                               decoder.reset (new ImageDecoder (_film, ic));
                        }
+
+                       frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
                }
 
                shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
                if (sc) {
-                       shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
-                       sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+                       decoder.reset (new SndfileDecoder (_film, sc));
+
+                       /* Working out the frc for this content is a bit tricky: what if it overlaps
+                          two pieces of video content with different frame rates?  For now, use
+                          the one with the best overlap.
+                       */
+
+                       DCPTime best_overlap_t = 0;
+                       shared_ptr<VideoContent> best_overlap;
+                       for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+                               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+                               if (!vc) {
+                                       continue;
+                               }
+
+                               DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
+                               if (overlap > best_overlap_t) {
+                                       best_overlap = vc;
+                                       best_overlap_t = overlap;
+                               }
+                       }
 
-                       piece->decoder = sd;
+                       if (best_overlap) {
+                               frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+                       } else {
+                               /* No video overlap; e.g. if the DCP is just audio */
+                               frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+                       }
                }
 
-               _pieces.push_back (piece);
+               decoder->seek ((*i)->trim_start (), true);
+               
+               _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
        }
 
        _have_valid_pieces = true;
@@ -588,17 +549,17 @@ Player::emit_black ()
 }
 
 void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
 {
        if (most == 0) {
                return;
        }
        
-       OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
-       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+       DCPTime t = min (most, TIME_HZ / 2);
+       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
        silence->make_silent ();
        Audio (silence, _audio_position);
-       _audio_position += _film->audio_frames_to_time (N);
+       _audio_position += t;
 }
 
 void
@@ -614,18 +575,6 @@ Player::film_changed (Film::Property p)
        }
 }
 
-void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-{
-       _in_subtitle.piece = weak_piece;
-       _in_subtitle.image = image;
-       _in_subtitle.rect = rect;
-       _in_subtitle.from = from;
-       _in_subtitle.to = to;
-
-       update_subtitle ();
-}
-
 void
 Player::update_subtitle ()
 {
@@ -634,15 +583,15 @@ Player::update_subtitle ()
                return;
        }
 
-       if (!_in_subtitle.image) {
-               _out_subtitle.image.reset ();
+       if (!_in_subtitle.subtitle->image) {
+               _out_subtitle.subtitle->image.reset ();
                return;
        }
 
        shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
        assert (sc);
 
-       dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+       dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
        libdcp::Size scaled_size;
 
        in_rect.y += sc->subtitle_offset ();
@@ -666,14 +615,15 @@ Player::update_subtitle ()
        _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
        _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
        
-       _out_subtitle.image = _in_subtitle.image->scale (
+       _out_subtitle.subtitle->image = _in_subtitle.subtitle->image->scale (
                scaled_size,
                Scaler::from_id ("bicubic"),
-               _in_subtitle.image->pixel_format (),
+               _in_subtitle.subtitle->image->pixel_format (),
                true
                );
-       _out_subtitle.from = _in_subtitle.from + piece->content->position ();
-       _out_subtitle.to = _in_subtitle.to + piece->content->position ();
+       
+       _out_subtitle.subtitle->dcp_time = _in_subtitle.subtitle->dcp_time;
+       _out_subtitle.subtitle->dcp_time = _in_subtitle.subtitle->dcp_time;
 }
 
 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -682,17 +632,13 @@ Player::update_subtitle ()
 bool
 Player::repeat_last_video ()
 {
-       if (!_last_incoming_video.image || !_have_valid_pieces) {
+       if (!_last_incoming_video.video || !_have_valid_pieces) {
                return false;
        }
 
-       process_video (
+       emit_video (
                _last_incoming_video.weak_piece,
-               _last_incoming_video.image,
-               _last_incoming_video.eyes,
-               _last_incoming_video.same,
-               _last_incoming_video.frame,
-               _last_incoming_video.extra
+               _last_incoming_video.video
                );
 
        return true;
index 11cc99e7793005630a5e9ce1014e5827bdf55ed4..6e3f8187f3ea4b1afd4ad95853ae8a3aebd3b6b3 100644 (file)
@@ -29,6 +29,7 @@
 #include "rect.h"
 #include "audio_merger.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class Job;
 class Film;
@@ -38,22 +39,8 @@ class Piece;
 class Image;
 class Resampler;
 
-/** @class Player
- *  @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
-{
-public:
-       boost::weak_ptr<Piece> weak_piece;
-       boost::shared_ptr<const Image> image;
-       Eyes eyes;
-       bool same;
-       VideoContent::Frame frame;
-       Time extra;
-};
-
-/** A wrapper for an Image which contains some pending operations; these may
+/** @class PlayerImage
+ *  @brief A wrapper for an Image which contains some pending operations; these may
  *  not be necessary if the receiver of the PlayerImage throws it away.
  */
 class PlayerImage
@@ -75,6 +62,10 @@ private:
        Position<int> _subtitle_position;
 };
  
+/** @class Player
+ *  @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+
 class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
 {
 public:
@@ -84,9 +75,9 @@ public:
        void disable_audio ();
 
        bool pass ();
-       void seek (Time, bool);
+       void seek (DCPTime, bool);
 
-       Time video_position () const {
+       DCPTime video_position () const {
                return _video_position;
        }
 
@@ -101,10 +92,10 @@ public:
         *  Fourth parameter is true if the image is the same as the last one that was emitted.
         *  Fifth parameter is the time.
         */
-       boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video;
+       boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video;
        
        /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio;
 
        /** Emitted when something has changed such that if we went back and emitted
         *  the last frame again it would look different.  This is not emitted after
@@ -118,19 +109,18 @@ private:
        friend class PlayerWrapper;
        friend class Piece;
 
-       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
-       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
-       void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
        void setup_pieces ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int, bool);
-       void do_seek (Time, bool);
+       void do_seek (DCPTime, bool);
        void flush ();
        void emit_black ();
        void emit_silence (OutputAudioFrame);
        boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
        void film_changed (Film::Property);
        void update_subtitle ();
+       void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
+       void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -143,11 +133,11 @@ private:
        std::list<boost::shared_ptr<Piece> > _pieces;
 
        /** The time after the last video that we emitted */
-       Time _video_position;
+       DCPTime _video_position;
        /** The time after the last audio that we emitted */
-       Time _audio_position;
+       DCPTime _audio_position;
 
-       AudioMerger<Time, AudioContent::Frame> _audio_merger;
+       AudioMerger<DCPTime, AudioContent::Frame> _audio_merger;
 
        libdcp::Size _video_container_size;
        boost::shared_ptr<PlayerImage> _black_frame;
@@ -155,17 +145,12 @@ private:
 
        struct {
                boost::weak_ptr<Piece> piece;
-               boost::shared_ptr<Image> image;
-               dcpomatic::Rect<double> rect;
-               Time from;
-               Time to;
+               boost::shared_ptr<DecodedSubtitle> subtitle;
        } _in_subtitle;
 
        struct {
-               boost::shared_ptr<Image> image;
                Position<int> position;
-               Time from;
-               Time to;
+               boost::shared_ptr<DecodedSubtitle> subtitle;
        } _out_subtitle;
 
 #ifdef DCPOMATIC_DEBUG
@@ -174,7 +159,12 @@ private:
 
        bool _last_emit_was_black;
 
-       IncomingVideo _last_incoming_video;
+       struct {
+               boost::weak_ptr<Piece> weak_piece;
+               boost::shared_ptr<DecodedVideo> video;
+       } _last_incoming_video;
+
+       bool _just_did_inaccurate_seek;
 
        boost::signals2::scoped_connection _playlist_changed_connection;
        boost::signals2::scoped_connection _playlist_content_changed_connection;
index 37b2902189f2b32d61a94a57bbc0baa36c7f0a28..9fc62f8be6e5345a1bebecca6d3cd9293e6712a2 100644 (file)
@@ -81,7 +81,7 @@ Playlist::maybe_sequence_video ()
        _sequencing_video = true;
        
        ContentList cl = _content;
-       Time next = 0;
+       DCPTime next = 0;
        for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
                if (!dynamic_pointer_cast<VideoContent> (*i)) {
                        continue;
@@ -254,10 +254,10 @@ Playlist::best_dcp_frame_rate () const
        return best->dcp;
 }
 
-Time
+DCPTime
 Playlist::length () const
 {
-       Time len = 0;
+       DCPTime len = 0;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                len = max (len, (*i)->end() + 1);
        }
@@ -279,10 +279,10 @@ Playlist::reconnect ()
        }
 }
 
-Time
+DCPTime
 Playlist::video_end () const
 {
-       Time end = 0;
+       DCPTime end = 0;
        for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
                if (dynamic_pointer_cast<const VideoContent> (*i)) {
                        end = max (end, (*i)->end ());
@@ -292,6 +292,23 @@ Playlist::video_end () const
        return end;
 }
 
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+       for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+               shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+               if (!vc) {
+                       break;
+               }
+
+               if (vc->position() >= t && t < vc->end()) {
+                       return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+               }
+       }
+
+       return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
 void
 Playlist::set_sequence_video (bool s)
 {
@@ -314,7 +331,7 @@ Playlist::content () const
 void
 Playlist::repeat (ContentList c, int n)
 {
-       pair<Time, Time> range (TIME_MAX, 0);
+       pair<DCPTime, DCPTime> range (TIME_MAX, 0);
        for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                range.first = min (range.first, (*i)->position ());
                range.second = max (range.second, (*i)->position ());
@@ -322,7 +339,7 @@ Playlist::repeat (ContentList c, int n)
                range.second = max (range.second, (*i)->end ());
        }
 
-       Time pos = range.second;
+       DCPTime pos = range.second;
        for (int i = 0; i < n; ++i) {
                for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
                        shared_ptr<Content> copy = (*i)->clone ();
@@ -355,7 +372,7 @@ Playlist::move_earlier (shared_ptr<Content> c)
                return;
        }
        
-       Time const p = (*previous)->position ();
+       DCPTime const p = (*previous)->position ();
        (*previous)->set_position (p + c->length_after_trim ());
        c->set_position (p);
        sort (_content.begin(), _content.end(), ContentSorter ());
@@ -382,7 +399,7 @@ Playlist::move_later (shared_ptr<Content> c)
                return;
        }
 
-       Time const p = (*next)->position ();
+       DCPTime const p = (*next)->position ();
        (*next)->set_position (c->position ());
        c->set_position (p + c->length_after_trim ());
        sort (_content.begin(), _content.end(), ContentSorter ());
index f87b3397b4b68e388f3f5e0f37b512c385c4bcf5..a985bf93da07669795394e468d4c682c2d2ea3e2 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/enable_shared_from_this.hpp>
 #include "ffmpeg_content.h"
 #include "audio_mapping.h"
+#include "util.h"
 
 class Content;
 class FFmpegContent;
@@ -70,10 +71,11 @@ public:
 
        std::string video_identifier () const;
 
-       Time length () const;
+       DCPTime length () const;
        
        int best_dcp_frame_rate () const;
-       Time video_end () const;
+       DCPTime video_end () const;
+       FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
 
        void set_sequence_video (bool);
        void maybe_sequence_video ();
index d897bf562dc4a97dcf5c314a8e7242e050f93e2f..00121384dce7617ab59f0ee85ea0373a7ab0c440 100644 (file)
@@ -64,11 +64,9 @@ Resampler::~Resampler ()
        swr_free (&_swr_context);
 }
 
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
 {
-       AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-               
        /* Compute the resampled frames count and add 32 for luck */
        int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
        shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
@@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
        }
        
        resampled->set_frames (resampled_frames);
-       return make_pair (resampled, resamp_time);
+       return resampled;
 }      
 
 shared_ptr<const AudioBuffers>
index 69ec83ba93047f3493cd23005487b7845580944e..4ee11a7f0954958e69a99bf09be75196394838c3 100644 (file)
@@ -33,7 +33,7 @@ public:
        Resampler (int, int, int);
        ~Resampler ();
 
-       std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
        boost::shared_ptr<const AudioBuffers> flush ();
 
 private:       
index 89db865d530b2d721cff9d0b82a392d16ec792a8..1c872cf96ef165d6685c8fe01caadbce36ba2509 100644 (file)
@@ -141,7 +141,7 @@ SndfileContent::as_xml (xmlpp::Node* node) const
        _audio_mapping.as_xml (node->add_child("AudioMapping"));
 }
 
-Time
+DCPTime
 SndfileContent::full_length () const
 {
        shared_ptr<const Film> film = _film.lock ();
index 701ff16b24bd0dbbdf2d75e5708e2be3c7ce9e45..c88764c1b22808e953d696d55f3cc50d2ce017de 100644 (file)
@@ -44,7 +44,7 @@ public:
        std::string technical_summary () const;
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
-       Time full_length () const;
+       DCPTime full_length () const;
 
        /* AudioContent */
        int audio_channels () const {
index e10f4f568430d08dd86d1d5db38d2116606fbbbb..3af683c570a89b21a59cf6fd68d8a9f6d37d3fb7 100644 (file)
@@ -55,9 +55,13 @@ SndfileDecoder::~SndfileDecoder ()
        delete[] _deinterleave_buffer;
 }
 
-void
+bool
 SndfileDecoder::pass ()
 {
+       if (_remaining == 0) {
+               return true;
+       }
+       
        /* Do things in half second blocks as I think there may be limits
           to what FFmpeg (and in particular the resampler) can cope with.
        */
@@ -93,6 +97,8 @@ SndfileDecoder::pass ()
        audio (data, _done);
        _done += this_time;
        _remaining -= this_time;
+
+       return true;
 }
 
 int
@@ -113,8 +119,10 @@ SndfileDecoder::audio_frame_rate () const
        return _info.samplerate;
 }
 
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
 {
-       return _audio_position >= _sndfile_content->audio_length ();
+       Decoder::seek (t, accurate);
+       
+       /* XXX */
 }
index 77fa6d17734da4096757dae504a759c9925e0e8b..63f2f7dc49f7553b9c265732dc683f79c58c718d 100644 (file)
@@ -29,14 +29,18 @@ public:
        SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
        ~SndfileDecoder ();
 
-       void pass ();
-       bool done () const;
+       void seek (ContentTime, bool);
 
        int audio_channels () const;
        AudioContent::Frame audio_length () const;
        int audio_frame_rate () const;
 
 private:
+       bool has_audio () const {
+               return true;
+       }
+       bool pass ();
+       
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
        SF_INFO _info;
index c06f3d718a812e9403266e3ccf91f5c494fa69b8..389e4b13a18e0d106808927ae102028150a70cea 100644 (file)
@@ -21,6 +21,7 @@
 #include "subtitle_decoder.h"
 
 using boost::shared_ptr;
+using boost::optional;
 
 SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
        : Decoder (f)
@@ -33,7 +34,7 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
  *  Image may be 0 to say that there is no current subtitle.
  */
 void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, DCPTime from, DCPTime to)
 {
-       Subtitle (image, rect, from, to);
+       _pending.push_back (shared_ptr<DecodedSubtitle> (new DecodedSubtitle (image, rect, from, to)));
 }
index eeeadbd3f65fdda2f9ee46b90c9745f2ce713f8f..4836e31fa980af02589b330eadddbddb8b45b16d 100644 (file)
 #include "decoder.h"
 #include "rect.h"
 #include "types.h"
+#include "decoded.h"
 
 class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
 class Image;
 
 class SubtitleDecoder : public virtual Decoder
@@ -31,8 +32,6 @@ class SubtitleDecoder : public virtual Decoder
 public:
        SubtitleDecoder (boost::shared_ptr<const Film>);
 
-       boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
-
 protected:
-       void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+       void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, DCPTime, DCPTime);
 };
index 69a7e3aa94b29e4988051f6ca61898d996b94f09..a20a08b072de8b0a0d7aca9f9a6c3aaf79bc018c 100644 (file)
 using namespace std;
 
 /** @param n Name to use when giving output */
-PeriodTimer::PeriodTimer (string n)
+PeriodDCPTimer::PeriodDCPTimer (string n)
        : _name (n)
 {
        gettimeofday (&_start, 0);
 }
 
-/** Destroy PeriodTimer and output the time elapsed since its construction */
-PeriodTimer::~PeriodTimer ()
+/** Destroy PeriodDCPTimer and output the time elapsed since its construction */
+PeriodDCPTimer::~PeriodDCPTimer ()
 {
        struct timeval stop;
        gettimeofday (&stop, 0);
@@ -48,7 +48,7 @@ PeriodTimer::~PeriodTimer ()
 /** @param n Name to use when giving output.
  *  @param s Initial state.
  */
-StateTimer::StateTimer (string n, string s)
+StateDCPTimer::StateDCPTimer (string n, string s)
        : _name (n)
 {
        struct timeval t;
@@ -59,7 +59,7 @@ StateTimer::StateTimer (string n, string s)
 
 /** @param s New state that the caller is in */
 void
-StateTimer::set_state (string s)
+StateDCPTimer::set_state (string s)
 {
        double const last = _time;
        struct timeval t;
@@ -74,8 +74,8 @@ StateTimer::set_state (string s)
        _state = s;
 }
 
-/** Destroy StateTimer and generate a summary of the state timings on cout */
-StateTimer::~StateTimer ()
+/** Destroy StateDCPTimer and generate a summary of the state timings on cout */
+StateDCPTimer::~StateDCPTimer ()
 {
        if (_state.empty ()) {
                return;
index 4a5aa12de331c0fe5b024144bc18923e76ccc8eb..8ec392aa63a212dc708356d819f58d1676393e2e 100644 (file)
 #include <map>
 #include <sys/time.h>
 
-/** @class PeriodTimer
+/** @class PeriodDCPTimer
  *  @brief A class to allow timing of a period within the caller.
  *
  *  On destruction, it will output the time since its construction.
  */
-class PeriodTimer
+class PeriodDCPTimer
 {
 public:
-       PeriodTimer (std::string n);
-       ~PeriodTimer ();
+       PeriodDCPTimer (std::string n);
+       ~PeriodDCPTimer ();
        
 private:
 
@@ -48,19 +48,19 @@ private:
        struct timeval _start;
 };
 
-/** @class StateTimer
+/** @class StateDCPTimer
  *  @brief A class to allow measurement of the amount of time a program
  *  spends in one of a set of states.
  *
  *  Once constructed, the caller can call set_state() whenever
- *  its state changes. When StateTimer is destroyed, it will
+ *  its state changes. When StateDCPTimer is destroyed, it will
  *  output (to cout) a summary of the time spent in each state.
  */
-class StateTimer
+class StateDCPTimer
 {
 public:
-       StateTimer (std::string n, std::string s);
-       ~StateTimer ();
+       StateDCPTimer (std::string n, std::string s);
+       ~StateDCPTimer ();
 
        void set_state (std::string s);
 
index 448b6c154bac00faae280e78a5c927f1bf1ea5a9..1ab6a94d4244f18c38ca291e3e546e40ed703f56 100644 (file)
@@ -38,9 +38,10 @@ class AudioBuffers;
  */
 #define SERVER_LINK_VERSION 1
 
-typedef int64_t Time;
+typedef int64_t DCPTime;
 #define TIME_MAX INT64_MAX
-#define TIME_HZ         ((Time) 96000)
+#define TIME_HZ         ((DCPTime) 96000)
+typedef int64_t ContentTime;
 typedef int64_t OutputAudioFrame;
 typedef int    OutputVideoFrame;
 typedef std::vector<boost::shared_ptr<Content> > ContentList;
index ddc0a297459cef504b49300b25f874752ec2ba64..381c47a9ae606253a7207a492351965987638c58 100644 (file)
@@ -772,7 +772,7 @@ audio_channel_name (int c)
        return channels[c];
 }
 
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
        : skip (false)
        , repeat (1)
        , change_speed (false)
@@ -790,7 +790,8 @@ FrameRateConversion::FrameRateConversion (float source, int dcp)
                repeat = round (dcp / source);
        }
 
-       change_speed = !about_equal (source * factor(), dcp);
+       speed_up = dcp / (source * factor());
+       change_speed = !about_equal (speed_up, 1.0);
 
        if (!skip && repeat == 1 && !change_speed) {
                description = _("Content and DCP have the same rate.\n");
@@ -914,3 +915,10 @@ fit_ratio_within (float ratio, libdcp::Size full_frame)
        
        return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
 }
+
+DCPTime
+time_round_up (DCPTime t, DCPTime nearest)
+{
+       DCPTime const a = t + nearest - 1;
+       return a - (a % nearest);
+}
index 7dcd920b7cac3d72974332eda264466faf0cc42a..892b473f739a21de88827d8aecd94682b3f2fe24 100644 (file)
@@ -79,9 +79,9 @@ extern std::string tidy_for_filename (std::string);
 extern boost::shared_ptr<const libdcp::Signer> make_signer ();
 extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
 
-struct FrameRateConversion
+struct FrameRateChange
 {
-       FrameRateConversion (float, int);
+       FrameRateChange (float, int);
 
        /** @return factor by which to multiply a source frame rate
            to get the effective rate after any skip or repeat has happened.
@@ -109,11 +109,17 @@ struct FrameRateConversion
         */
        bool change_speed;
 
+       /** Amount by which the video is being sped-up in the DCP; e.g. for a
+        *  24fps source in a 25fps DCP this would be 25/24.
+        */
+       float speed_up;
+
        std::string description;
 };
 
 extern int dcp_audio_frame_rate (int);
 extern int stride_round_up (int, int const *, int);
+extern DCPTime time_round_up (DCPTime, DCPTime);
 extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
 extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
 extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
index 0a19ffd692463ac4f2923cd106ea54ddfccd4de1..ebec8b6dbe3ce3f40d1ee184d461d0e494af3f6e 100644 (file)
@@ -59,7 +59,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
        setup_default_colour_conversion ();
 }
 
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, VideoContent::Frame len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@ -351,19 +351,21 @@ VideoContent::video_size_after_crop () const
 }
 
 /** @param t A time offset from the start of this piece of content.
- *  @return Corresponding frame index.
+ *  @return Corresponding frame index, rounded up so that the frame index
+ *  is that of the next complete frame which starts after `t'.
  */
 VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+VideoContent::time_to_content_video_frames (DCPTime t) const
 {
        shared_ptr<const Film> film = _film.lock ();
        assert (film);
        
-       FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
        /* 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.
+          the source's rate; source rate will be equal to DCP rate if we ignore
+          double/skip.  There's no need to call Film::active_frame_rate_change() here
+          as we know that we are it (since we're video).
        */
-       return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+       FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
+       return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ));
 }
index effca5c61c314c50c4328d02adb5db7ce92dd48e..f008143fa2a7460aa6b37abc09e1b7fd9269b1e6 100644 (file)
@@ -43,7 +43,7 @@ public:
        typedef int Frame;
 
        VideoContent (boost::shared_ptr<const Film>);
-       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+       VideoContent (boost::shared_ptr<const Film>, DCPTime, VideoContent::Frame);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -123,7 +123,7 @@ public:
        libdcp::Size video_size_after_3d_split () const;
        libdcp::Size video_size_after_crop () const;
 
-       VideoContent::Frame time_to_content_video_frames (Time) const;
+       VideoContent::Frame time_to_content_video_frames (DCPTime) const;
 
 protected:
        void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
index eaa4534e44b64ab6c6e503b06bfae280e8dde08e..3a88911118e4054bf2c2d2eecae6b59a50fa055a 100644 (file)
 
 using std::cout;
 using boost::shared_ptr;
+using boost::optional;
 
 VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
        : Decoder (f)
        , _video_content (c)
-       , _video_position (0)
 {
 
 }
 
+/** Called by subclasses when they have a video frame ready */
 void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, ContentTime time)
 {
        switch (_video_content->video_frame_type ()) {
        case VIDEO_FRAME_TYPE_2D:
-               Video (image, EYES_BOTH, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image, EYES_BOTH, same, time)));
                break;
        case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
        {
                int const half = image->size().width / 2;
-               Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, time)));
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, time)));
                break;
        }
        }
-       
-       _video_position = frame + 1;
 }
-
index 142320a049c8e7a23bf7aff2d618149d6259941d..d8c362354ee02b2f9614b265c6159ee1b200bf7d 100644 (file)
@@ -25,6 +25,7 @@
 #include "decoder.h"
 #include "video_content.h"
 #include "util.h"
+#include "decoded.h"
 
 class VideoContent;
 class Image;
@@ -34,24 +35,14 @@ class VideoDecoder : public virtual Decoder
 public:
        VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
 
-       /** Seek so that the next pass() will yield (approximately) the requested frame.
-        *  Pass accurate = true to try harder to get close to the request.
-        */
-       virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is the eye(s) which should see this image.
-        *  Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
-        *  Fourth parameter is the frame within our source.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-       
+       boost::shared_ptr<const VideoContent> video_content () const {
+               return _video_content;
+       }
+
 protected:
 
-       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void video (boost::shared_ptr<const Image>, bool, ContentTime);
        boost::shared_ptr<const VideoContent> _video_content;
-       VideoContent::Frame _video_position;
 };
 
 #endif
index f0a44954ff2ee2ed88d9dcaef2de1044a7f42f09..3c40139b80560d85ec948d5ca5b5fbf7dd6eb8e4 100644 (file)
@@ -47,7 +47,7 @@ static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log"));
 static int frame = 0;
 
 void
-process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time)
+process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, DCPTime)
 {
        shared_ptr<DCPVideoFrame> local  (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, log_));
        shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, log_));
index f78885772ce033e682a1e9d178d85c93441b2b45..96de34d406744960db60c4aa072a0673699c9c2d 100644 (file)
@@ -145,7 +145,7 @@ AudioPlot::paint ()
        gc->SetPen (*wxLIGHT_GREY_PEN);
        gc->StrokePath (grid);
 
-       gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2);
+       gc->DrawText (_("DCPTime"), data_width, _height - _y_origin + db_label_height / 2);
        
        if (_type_visible[AudioPoint::PEAK]) {
                for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) {
index 564b90d28ab63295e675f594c71981d6a2297faf..728098b934ab331e3c10956f8abc22c973df9c90 100644 (file)
@@ -936,7 +936,7 @@ FilmEditor::content_timeline_clicked ()
                _timeline_dialog = 0;
        }
        
-       _timeline_dialog = new TimelineDialog (this, _film);
+       _timeline_dialog = new DCPTimelineDialog (this, _film);
        _timeline_dialog->Show ();
 }
 
index 23c87e6784510288c54c8e9e96484b520e05bccc..dadb583ae07b028f1c8324b1a0f30db6513c426e 100644 (file)
@@ -33,9 +33,9 @@ class wxNotebook;
 class wxListCtrl;
 class wxListEvent;
 class Film;
-class TimelineDialog;
+class DCPTimelineDialog;
 class Ratio;
-class Timecode;
+class DCPTimecode;
 class FilmEditorPanel;
 class SubtitleContent;
 
@@ -156,5 +156,5 @@ private:
        std::vector<Ratio const *> _ratios;
 
        bool _generally_sensitive;
-       TimelineDialog* _timeline_dialog;
+       DCPTimelineDialog* _timeline_dialog;
 };
index fbca835c2bc2126513b9eb1b0eb49efce3fa1e29..cc2f67b15c06216584b9ce82fff0b47d7d8ff2d6 100644 (file)
@@ -164,7 +164,7 @@ FilmViewer::timer ()
        
        fetch_next_frame ();
 
-       Time const len = _film->length ();
+       DCPTime const len = _film->length ();
 
        if (len) {
                int const new_slider_position = 4096 * _player->video_position() / len;
@@ -275,7 +275,7 @@ FilmViewer::check_play_state ()
 }
 
 void
-FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
+FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t)
 {
        if (eyes == EYES_RIGHT) {
                return;
@@ -288,7 +288,7 @@ FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t)
 }
 
 void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text (DCPTime t)
 {
        if (!_film) {
                _frame_number->SetLabel ("0");
@@ -371,7 +371,7 @@ FilmViewer::back_clicked ()
           We want to see the one before it, so we need to go back 2.
        */
 
-       Time p = _player->video_position() - _film->video_frames_to_time (2);
+       DCPTime p = _player->video_position() - _film->video_frames_to_time (2);
        if (p < 0) {
                p = 0;
        }
index c99c7344047448b088115a9e085bb5eb389937fa..0e5c49c6d8ad91968e439a011ebea6844f7b2472 100644 (file)
@@ -59,7 +59,7 @@ private:
        void slider_moved ();
        void play_clicked ();
        void timer ();
-       void process_video (boost::shared_ptr<PlayerImage>, Eyes, Time);
+       void process_video (boost::shared_ptr<PlayerImage>, Eyes, DCPTime);
        void calculate_sizes ();
        void check_play_state ();
        void fetch_current_frame_again ();
@@ -68,7 +68,7 @@ private:
        void back_clicked ();
        void forward_clicked ();
        void player_changed (bool);
-       void set_position_text (Time);
+       void set_position_text (DCPTime);
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
index 033bd2bd01e97f29965829c8c28c20bd37fa2a3b..b23ff2a39203842feac9805fb811d7b0e351bbfd 100644 (file)
@@ -26,7 +26,7 @@ using std::string;
 using std::cout;
 using boost::lexical_cast;
 
-Timecode::Timecode (wxWindow* parent)
+DCPTimecode::DCPTimecode (wxWindow* parent)
        : wxPanel (parent)
 {
        wxClientDC dc (parent);
@@ -69,11 +69,11 @@ Timecode::Timecode (wxWindow* parent)
 
        _fixed = add_label_to_sizer (_sizer, this, wxT ("42"), false);
        
-       _hours->Bind      (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
-       _minutes->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
-       _seconds->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
-       _frames->Bind     (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&Timecode::changed, this));
-       _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&Timecode::set_clicked, this));
+       _hours->Bind      (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&DCPTimecode::changed, this));
+       _minutes->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&DCPTimecode::changed, this));
+       _seconds->Bind    (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&DCPTimecode::changed, this));
+       _frames->Bind     (wxEVT_COMMAND_TEXT_UPDATED,   boost::bind (&DCPTimecode::changed, this));
+       _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DCPTimecode::set_clicked, this));
 
        _set_button->Enable (false);
 
@@ -83,7 +83,7 @@ Timecode::Timecode (wxWindow* parent)
 }
 
 void
-Timecode::set (Time t, int fps)
+DCPTimecode::set (DCPTime t, int fps)
 {
        int const h = t / (3600 * TIME_HZ);
        t -= h * 3600 * TIME_HZ;
@@ -101,10 +101,10 @@ Timecode::set (Time t, int fps)
        _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f));
 }
 
-Time
-Timecode::get (int fps) const
+DCPTime
+DCPTimecode::get (int fps) const
 {
-       Time t = 0;
+       DCPTime t = 0;
        string const h = wx_to_std (_hours->GetValue ());
        t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ;
        string const m = wx_to_std (_minutes->GetValue());
@@ -118,20 +118,20 @@ Timecode::get (int fps) const
 }
 
 void
-Timecode::changed ()
+DCPTimecode::changed ()
 {
        _set_button->Enable (true);
 }
 
 void
-Timecode::set_clicked ()
+DCPTimecode::set_clicked ()
 {
        Changed ();
        _set_button->Enable (false);
 }
 
 void
-Timecode::set_editable (bool e)
+DCPTimecode::set_editable (bool e)
 {
        _editable->Show (e);
        _fixed->Show (!e);
index 5b094e39f0c2c3cf6d67d36eba70a804acf73017..6a8bf03df3d19a33206b68098bb9790e34a0345b 100644 (file)
 #include <wx/wx.h>
 #include "lib/types.h"
 
-class Timecode : public wxPanel
+class DCPTimecode : public wxPanel
 {
 public:
-       Timecode (wxWindow *);
+       DCPTimecode (wxWindow *);
 
-       void set (Time, int);
-       Time get (int) const;
+       void set (DCPTime, int);
+       DCPTime get (int) const;
 
        void set_editable (bool);
 
index 0ac9a1d4bb6d513e6fa3bedbfe96eeae66441777..9d2aee76c00f7660d424a5ef9c5275e5514a1cd2 100644 (file)
@@ -39,7 +39,7 @@ using boost::optional;
 class View : public boost::noncopyable
 {
 public:
-       View (Timeline& t)
+       View (DCPTimeline& t)
                : _timeline (t)
        {
 
@@ -64,12 +64,12 @@ public:
 protected:
        virtual void do_paint (wxGraphicsContext *) = 0;
        
-       int time_x (Time t) const
+       int time_x (DCPTime t) const
        {
                return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
        }
        
-       Timeline& _timeline;
+       DCPTimeline& _timeline;
 
 private:
        dcpomatic::Rect<int> _last_paint_bbox;
@@ -80,7 +80,7 @@ private:
 class ContentView : public View
 {
 public:
-       ContentView (Timeline& tl, shared_ptr<Content> c)
+       ContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : View (tl)
                , _content (c)
                , _track (0)
@@ -139,8 +139,8 @@ private:
                        return;
                }
 
-               Time const position = cont->position ();
-               Time const len = cont->length_after_trim ();
+               DCPTime const position = cont->position ();
+               DCPTime const len = cont->length_after_trim ();
 
                wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
 
@@ -203,7 +203,7 @@ private:
 class AudioContentView : public ContentView
 {
 public:
-       AudioContentView (Timeline& tl, shared_ptr<Content> c)
+       AudioContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : ContentView (tl, c)
        {}
        
@@ -222,7 +222,7 @@ private:
 class VideoContentView : public ContentView
 {
 public:
-       VideoContentView (Timeline& tl, shared_ptr<Content> c)
+       VideoContentView (DCPTimeline& tl, shared_ptr<Content> c)
                : ContentView (tl, c)
        {}
 
@@ -243,10 +243,10 @@ private:
        }
 };
 
-class TimeAxisView : public View
+class DCPTimeAxisView : public View
 {
 public:
-       TimeAxisView (Timeline& tl, int y)
+       DCPTimeAxisView (DCPTimeline& tl, int y)
                : View (tl)
                , _y (y)
        {}
@@ -291,7 +291,7 @@ private:
                path.AddLineToPoint (_timeline.width(), _y);
                gc->StrokePath (path);
 
-               Time t = 0;
+               DCPTime t = 0;
                while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
                        wxGraphicsPath path = gc->CreatePath ();
                        path.MoveToPoint (time_x (t), _y - 4);
@@ -326,11 +326,11 @@ private:
 };
 
 
-Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
+DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
        , _film_editor (ed)
        , _film (film)
-       , _time_axis_view (new TimeAxisView (*this, 32))
+       , _time_axis_view (new DCPTimeAxisView (*this, 32))
        , _tracks (0)
        , _pixels_per_time_unit (0)
        , _left_down (false)
@@ -343,22 +343,22 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
        SetDoubleBuffered (true);
 #endif 
 
-       Bind (wxEVT_PAINT,      boost::bind (&Timeline::paint,       this));
-       Bind (wxEVT_LEFT_DOWN,  boost::bind (&Timeline::left_down,   this, _1));
-       Bind (wxEVT_LEFT_UP,    boost::bind (&Timeline::left_up,     this, _1));
-       Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down,  this, _1));
-       Bind (wxEVT_MOTION,     boost::bind (&Timeline::mouse_moved, this, _1));
-       Bind (wxEVT_SIZE,       boost::bind (&Timeline::resized,     this));
+       Bind (wxEVT_PAINT,      boost::bind (&DCPTimeline::paint,       this));
+       Bind (wxEVT_LEFT_DOWN,  boost::bind (&DCPTimeline::left_down,   this, _1));
+       Bind (wxEVT_LEFT_UP,    boost::bind (&DCPTimeline::left_up,     this, _1));
+       Bind (wxEVT_RIGHT_DOWN, boost::bind (&DCPTimeline::right_down,  this, _1));
+       Bind (wxEVT_MOTION,     boost::bind (&DCPTimeline::mouse_moved, this, _1));
+       Bind (wxEVT_SIZE,       boost::bind (&DCPTimeline::resized,     this));
 
        playlist_changed ();
 
        SetMinSize (wxSize (640, tracks() * track_height() + 96));
 
-       _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+       _playlist_connection = film->playlist()->Changed.connect (bind (&DCPTimeline::playlist_changed, this));
 }
 
 void
-Timeline::paint ()
+DCPTimeline::paint ()
 {
        wxPaintDC dc (this);
 
@@ -377,7 +377,7 @@ Timeline::paint ()
 }
 
 void
-Timeline::playlist_changed ()
+DCPTimeline::playlist_changed ()
 {
        ensure_ui_thread ();
        
@@ -406,7 +406,7 @@ Timeline::playlist_changed ()
 }
 
 void
-Timeline::assign_tracks ()
+DCPTimeline::assign_tracks ()
 {
        for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
                shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
@@ -465,13 +465,13 @@ Timeline::assign_tracks ()
 }
 
 int
-Timeline::tracks () const
+DCPTimeline::tracks () const
 {
        return _tracks;
 }
 
 void
-Timeline::setup_pixels_per_time_unit ()
+DCPTimeline::setup_pixels_per_time_unit ()
 {
        shared_ptr<const Film> film = _film.lock ();
        if (!film) {
@@ -482,7 +482,7 @@ Timeline::setup_pixels_per_time_unit ()
 }
 
 shared_ptr<View>
-Timeline::event_to_view (wxMouseEvent& ev)
+DCPTimeline::event_to_view (wxMouseEvent& ev)
 {
        ViewList::iterator i = _views.begin();
        Position<int> const p (ev.GetX(), ev.GetY());
@@ -498,7 +498,7 @@ Timeline::event_to_view (wxMouseEvent& ev)
 }
 
 void
-Timeline::left_down (wxMouseEvent& ev)
+DCPTimeline::left_down (wxMouseEvent& ev)
 {
        shared_ptr<View> view = event_to_view (ev);
        shared_ptr<ContentView> content_view = dynamic_pointer_cast<ContentView> (view);
@@ -539,7 +539,7 @@ Timeline::left_down (wxMouseEvent& ev)
 }
 
 void
-Timeline::left_up (wxMouseEvent& ev)
+DCPTimeline::left_up (wxMouseEvent& ev)
 {
        _left_down = false;
 
@@ -551,7 +551,7 @@ Timeline::left_up (wxMouseEvent& ev)
 }
 
 void
-Timeline::mouse_moved (wxMouseEvent& ev)
+DCPTimeline::mouse_moved (wxMouseEvent& ev)
 {
        if (!_left_down) {
                return;
@@ -561,7 +561,7 @@ Timeline::mouse_moved (wxMouseEvent& ev)
 }
 
 void
-Timeline::right_down (wxMouseEvent& ev)
+DCPTimeline::right_down (wxMouseEvent& ev)
 {
        shared_ptr<View> view = event_to_view (ev);
        shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (view);
@@ -578,7 +578,7 @@ Timeline::right_down (wxMouseEvent& ev)
 }
 
 void
-Timeline::set_position_from_event (wxMouseEvent& ev)
+DCPTimeline::set_position_from_event (wxMouseEvent& ev)
 {
        wxPoint const p = ev.GetPosition();
 
@@ -597,13 +597,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                return;
        }
        
-       Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
+       DCPTime new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit;
        
        if (_snap) {
                
                bool first = true;
-               Time nearest_distance = TIME_MAX;
-               Time nearest_new_position = TIME_MAX;
+               DCPTime nearest_distance = TIME_MAX;
+               DCPTime nearest_new_position = TIME_MAX;
                
                /* Find the nearest content edge; this is inefficient */
                for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
@@ -614,7 +614,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap starts to ends */
-                               Time const d = abs (cv->content()->end() - new_position);
+                               DCPTime const d = abs (cv->content()->end() - new_position);
                                if (first || d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->end();
@@ -623,7 +623,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
                        
                        {
                                /* Snap ends to starts */
-                               Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
+                               DCPTime const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim()));
                                if (d < nearest_distance) {
                                        nearest_distance = d;
                                        nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim ();
@@ -653,25 +653,25 @@ Timeline::set_position_from_event (wxMouseEvent& ev)
 }
 
 void
-Timeline::force_redraw (dcpomatic::Rect<int> const & r)
+DCPTimeline::force_redraw (dcpomatic::Rect<int> const & r)
 {
        RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
 }
 
 shared_ptr<const Film>
-Timeline::film () const
+DCPTimeline::film () const
 {
        return _film.lock ();
 }
 
 void
-Timeline::resized ()
+DCPTimeline::resized ()
 {
        setup_pixels_per_time_unit ();
 }
 
 void
-Timeline::clear_selection ()
+DCPTimeline::clear_selection ()
 {
        for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
                shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
@@ -681,8 +681,8 @@ Timeline::clear_selection ()
        }
 }
 
-Timeline::ContentViewList
-Timeline::selected_views () const
+DCPTimeline::ContentViewList
+DCPTimeline::selected_views () const
 {
        ContentViewList sel;
        
@@ -697,7 +697,7 @@ Timeline::selected_views () const
 }
 
 ContentList
-Timeline::selected_content () const
+DCPTimeline::selected_content () const
 {
        ContentList sel;
        ContentViewList views = selected_views ();
index ef1d10797c6e75d114c9d55d7b5d97ff40777279..b3ee77fff84644a14eb2158ab38fd64778b19a6d 100644 (file)
@@ -29,12 +29,12 @@ class Film;
 class View;
 class ContentView;
 class FilmEditor;
-class TimeAxisView;
+class DCPTimeAxisView;
 
-class Timeline : public wxPanel
+class DCPTimeline : public wxPanel
 {
 public:
-       Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
+       DCPTimeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>);
 
        boost::shared_ptr<const Film> film () const;
 
@@ -94,13 +94,13 @@ private:
        FilmEditor* _film_editor;
        boost::weak_ptr<Film> _film;
        ViewList _views;
-       boost::shared_ptr<TimeAxisView> _time_axis_view;
+       boost::shared_ptr<DCPTimeAxisView> _time_axis_view;
        int _tracks;
        double _pixels_per_time_unit;
        bool _left_down;
        wxPoint _down_point;
        boost::shared_ptr<ContentView> _down_view;
-       Time _down_view_position;
+       DCPTime _down_view_position;
        bool _first_move;
        ContentMenu _menu;
        bool _snap;
index dbf7ae232be2cd5f72dd8a247e98a80fff968fa7..a63c219dbfb43ca3966cd076653f6a9afd18e44c 100644 (file)
@@ -28,8 +28,8 @@ using std::list;
 using std::cout;
 using boost::shared_ptr;
 
-TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
-       : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
+DCPTimelineDialog::DCPTimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
+       : wxDialog (ed, wxID_ANY, _("DCPTimeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
        , _timeline (this, ed, film)
 {
        wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
@@ -46,11 +46,11 @@ TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film)
        sizer->SetSizeHints (this);
 
        _snap->SetValue (_timeline.snap ());
-       _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&TimelineDialog::snap_toggled, this));
+       _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPTimelineDialog::snap_toggled, this));
 }
 
 void
-TimelineDialog::snap_toggled ()
+DCPTimelineDialog::snap_toggled ()
 {
        _timeline.set_snap (_snap->GetValue ());
 }
index 1e595500303b5b964b0ba54157c67d47f260625b..b64445d9cf88447a8b5638f1d579641864700879 100644 (file)
 
 class Playlist;
 
-class TimelineDialog : public wxDialog
+class DCPTimelineDialog : public wxDialog
 {
 public:
-       TimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
+       DCPTimelineDialog (FilmEditor *, boost::shared_ptr<Film>);
 
 private:
        void snap_toggled ();
        
-       Timeline _timeline;
+       DCPTimeline _timeline;
        wxCheckBox* _snap;
 };
index 79099b1680a5450c71ac469fbaa0ebe9be2a54a1..08334da4bfef8142a8425e6ee88485b0de47bd4f 100644 (file)
@@ -35,19 +35,19 @@ TimingPanel::TimingPanel (FilmEditor* e)
        _sizer->Add (grid, 0, wxALL, 8);
 
        add_label_to_sizer (grid, this, _("Position"), true);
-       _position = new Timecode (this);
+       _position = new DCPTimecode (this);
        grid->Add (_position);
        add_label_to_sizer (grid, this, _("Full length"), true);
-       _full_length = new Timecode (this);
+       _full_length = new DCPTimecode (this);
        grid->Add (_full_length);
        add_label_to_sizer (grid, this, _("Trim from start"), true);
-       _trim_start = new Timecode (this);
+       _trim_start = new DCPTimecode (this);
        grid->Add (_trim_start);
        add_label_to_sizer (grid, this, _("Trim from end"), true);
-       _trim_end = new Timecode (this);
+       _trim_end = new DCPTimecode (this);
        grid->Add (_trim_end);
        add_label_to_sizer (grid, this, _("Play length"), true);
-       _play_length = new Timecode (this);
+       _play_length = new DCPTimecode (this);
        grid->Add (_play_length);
 
        _position->Changed.connect    (boost::bind (&TimingPanel::position_changed, this));
index ab859a1befc1cfb37acdaabee33d88d5bf367156..7fea45eb5c4982ead55940d3cced32f83a978053 100644 (file)
@@ -19,7 +19,7 @@
 
 #include "film_editor_panel.h"
 
-class Timecode;
+class DCPTimecode;
 
 class TimingPanel : public FilmEditorPanel
 {
@@ -36,9 +36,9 @@ private:
        void trim_end_changed ();
        void play_length_changed ();
        
-       Timecode* _position;
-       Timecode* _full_length;
-       Timecode* _trim_start;
-       Timecode* _trim_end;
-       Timecode* _play_length;
+       DCPTimecode* _position;
+       DCPTimecode* _full_length;
+       DCPTimecode* _trim_start;
+       DCPTimecode* _trim_end;
+       DCPTimecode* _play_length;
 };
index 8609322ceb6bbeb895f602bae030011aa26cedc2..1bf0927865f1f50e1a102214471e74754b7ddf70 100644 (file)
@@ -332,7 +332,7 @@ VideoPanel::setup_description ()
 
        d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ());
        ++lines;
-       FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
+       FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ());
        d << frc.description << "\n";
        ++lines;
 
diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc
new file mode 100644 (file)
index 0000000..e66a191
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+    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 <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+using boost::optional;
+
+#define FFMPEG_SEEK_TEST_DEBUG 1
+
+optional<DCPTime> first_video;
+optional<DCPTime> first_audio;
+shared_ptr<Film> film;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime t)
+{
+       if (!first_video) {
+               first_video = t;
+       }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, DCPTime t)
+{
+       if (!first_audio) {
+               first_audio = t;
+       }
+}
+
+static string
+print_time (DCPTime t, float fps)
+{
+       stringstream s;
+       s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+       return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, DCPTime t)
+{
+       first_video.reset ();
+       first_audio.reset ();
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif 
+       
+       p->seek (t, true);
+       while (!first_video || !first_audio) {
+               p->pass ();
+       }
+
+#if FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "First video " << print_time (first_video.get(), 24) << "\n";
+       cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif 
+
+       /* Outputs should be on or after seek time */
+       BOOST_CHECK (first_video.get() >= t);
+       BOOST_CHECK (first_audio.get() >= t);
+       /* And should be rounded to frame boundaries */
+       BOOST_CHECK ((first_video.get() % (TIME_HZ / film->video_frame_rate())) == 0);
+       BOOST_CHECK ((first_audio.get() % (TIME_HZ / film->audio_frame_rate())) == 0);
+}
+
+BOOST_AUTO_TEST_CASE (ffmpeg_seek_test)
+{
+       film = new_test_film ("ffmpeg_audio_test");
+       film->set_name ("ffmpeg_audio_test");
+       film->set_container (Ratio::from_id ("185"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/staircase.mov"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       shared_ptr<Player> player = film->make_player ();
+       player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+       player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+       check (player, 0);
+       check (player, 0.1 * TIME_HZ);
+       check (player, 0.2 * TIME_HZ);
+       check (player, 0.3 * TIME_HZ);
+}
+
+
index fdfdcf4529739f126ffab9da192aafeec44f8503..2135b3738f50a669b39b2678a51bd404eb755a2f 100644 (file)
@@ -47,91 +47,102 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        int best = film->playlist()->best_dcp_frame_rate ();
-       FrameRateConversion frc = FrameRateConversion (60, best);
+       FrameRateChange frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, true);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 30;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (30, best);
+       frc = FrameRateChange (30, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 29.97;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (29.97, best);
+       frc = FrameRateChange (29.97, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 30 / 29.97, 0.1);
        
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 24;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (24, best);
+       frc = FrameRateChange (24, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 14.5;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (14.5, best);
+       frc = FrameRateChange (14.5, best);
        BOOST_CHECK_EQUAL (best, 30);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 15 / 14.5, 0.1);
 
        content->_video_frame_rate = 12.6;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.6, best);
+       frc = FrameRateChange (12.6, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 25.2, 0.1);
 
        content->_video_frame_rate = 12.4;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12.4, best);
+       frc = FrameRateChange (12.4, best);
        BOOST_CHECK_EQUAL (best, 25);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 25 / 24.8, 0.1);
 
        content->_video_frame_rate = 12;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (12, best);
+       frc = FrameRateChange (12, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Now add some more rates and see if it will use them
           in preference to skip/repeat.
@@ -144,34 +155,38 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 60;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (60, best);
+       frc = FrameRateChange (60, best);
        BOOST_CHECK_EQUAL (best, 60);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
        
        content->_video_frame_rate = 50;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (50, best);
+       frc = FrameRateChange (50, best);
        BOOST_CHECK_EQUAL (best, 50);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        content->_video_frame_rate = 48;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (48, best);
+       frc = FrameRateChange (48, best);
        BOOST_CHECK_EQUAL (best, 48);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, false);
+       BOOST_CHECK_CLOSE (frc.speed_up, 1, 0.1);
 
        /* Check some out-there conversions (not the best) */
        
-       frc = FrameRateConversion (14.99, 24);
+       frc = FrameRateChange (14.99, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 2);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24 / (2 * 14.99), 0.1);
 
        /* Check some conversions with limited DCP targets */
 
@@ -181,14 +196,15 @@ BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_single)
 
        content->_video_frame_rate = 25;
        best = film->playlist()->best_dcp_frame_rate ();
-       frc = FrameRateConversion (25, best);
+       frc = FrameRateChange (25, best);
        BOOST_CHECK_EQUAL (best, 24);
        BOOST_CHECK_EQUAL (frc.skip, false);
        BOOST_CHECK_EQUAL (frc.repeat, 1);
        BOOST_CHECK_EQUAL (frc.change_speed, true);
+       BOOST_CHECK_CLOSE (frc.speed_up, 24.0 / 25, 0.1);
 }
 
-/* Test Playlist::best_dcp_frame_rate and FrameRateConversion
+/* Test Playlist::best_dcp_frame_rate and FrameRateChange
    with two pieces of content.
 */
 BOOST_AUTO_TEST_CASE (best_dcp_frame_rate_test_double)
@@ -266,7 +282,7 @@ BOOST_AUTO_TEST_CASE (audio_sampling_rate_test)
        content->_video_frame_rate = 14.99;
        film->set_video_frame_rate (25);
        content->set_audio_stream (shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream ("a", 42, 16000, 0)));
-       /* The FrameRateConversion within output_audio_frame_rate should choose to double-up
+       /* The FrameRateChange within output_audio_frame_rate should choose to double-up
           the 14.99 fps video to 30 and then run it slow at 25.
        */
        BOOST_CHECK_EQUAL (content->output_audio_frame_rate(), rint (48000 * 2 * 14.99 / 25));
diff --git a/test/long_ffmpeg_seek_test.cc b/test/long_ffmpeg_seek_test.cc
new file mode 100644 (file)
index 0000000..5285d44
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+    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 <boost/test/unit_test.hpp>
+#include "lib/player.h"
+#include "lib/ffmpeg_decoder.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "test.h"
+
+using std::cout;
+using std::string;
+using std::stringstream;
+using boost::shared_ptr;
+
+#define LONG_FFMPEG_SEEK_TEST_DEBUG 1
+
+boost::optional<DCPTime> first_video;
+boost::optional<DCPTime> first_audio;
+
+static void
+process_video (shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime t)
+{
+       if (!first_video) {
+               first_video = t;
+       }
+}
+
+static void
+process_audio (shared_ptr<const AudioBuffers>, DCPTime t)
+{
+       if (!first_audio) {
+               first_audio = t;
+       }
+}
+
+static string
+print_time (DCPTime t, float fps)
+{
+       stringstream s;
+       s << t << " " << (float(t) / TIME_HZ) << "s " << (float(t) * fps / TIME_HZ) << "f";
+       return s.str ();
+}
+
+static void
+check (shared_ptr<Player> p, DCPTime t)
+{
+       first_video.reset ();
+       first_audio.reset ();
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "\n-- Seek to " << print_time (t, 24) << "\n";
+#endif 
+       
+       p->seek (t, true);
+       while (!first_video || !first_audio) {
+               p->pass ();
+       }
+
+#if LONG_FFMPEG_SEEK_TEST_DEBUG == 1
+       cout << "First video " << print_time (first_video.get(), 24) << "\n";
+       cout << "First audio " << print_time (first_audio.get(), 24) << "\n";
+#endif 
+       
+       BOOST_CHECK (first_video.get() >= t);
+       BOOST_CHECK (first_audio.get() >= t);
+}
+
+BOOST_AUTO_TEST_CASE (long_ffmpeg_seek_test)
+{
+       shared_ptr<Film> film = new_test_film ("long_ffmpeg_audio_test");
+       film->set_name ("long_ffmpeg_audio_test");
+       film->set_container (Ratio::from_id ("185"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/long_data/dolby_aurora.vob"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       shared_ptr<Player> player = film->make_player ();
+       player->Video.connect (boost::bind (&process_video, _1, _2, _3, _4, _5));
+       player->Audio.connect (boost::bind (&process_audio, _1, _2));
+
+       for (float i = 0; i < 10; i += 0.1) {
+               check (player, i * TIME_HZ);
+       }
+}
+
+
index dfa597431fee6b818d42e195f3676af6bf6229e8..7beee6f43df86453e316e052a8fc3e83bb32a7c5 100644 (file)
@@ -34,7 +34,7 @@ struct Video
 {
        boost::shared_ptr<Content> content;
        boost::shared_ptr<const Image> image;
-       Time time;
+       DCPTime time;
 };
 
 class PlayerWrapper
@@ -46,7 +46,7 @@ public:
                _player->Video.connect (bind (&PlayerWrapper::process_video, this, _1, _2, _5));
        }
 
-       void process_video (shared_ptr<PlayerImage> i, bool, Time t)
+       void process_video (shared_ptr<PlayerImage> i, bool, DCPTime t)
        {
                Video v;
                v.content = _player->_last_video;
@@ -67,7 +67,7 @@ public:
                return v;
        }
 
-       void seek (Time t, bool ac)
+       void seek (DCPTime t, bool ac)
        {
                _player->seek (t, ac);
                _queue.clear ();
@@ -106,8 +106,6 @@ BOOST_AUTO_TEST_CASE (play_test)
 
        shared_ptr<Player> player = film->make_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 ();
@@ -119,10 +117,10 @@ BOOST_AUTO_TEST_CASE (play_test)
                }
        }
 
-       player->seek (10 * TIME_HZ / 25, true);
+       player->seek (6 * TIME_HZ / 25, true);
        optional<Video> v = wrap.get_video ();
        BOOST_CHECK (v);
-       BOOST_CHECK_EQUAL (v.get().time, 10 * TIME_HZ / 25);
+       BOOST_CHECK_EQUAL (v.get().time, 6 * TIME_HZ / 25);
 }
 
 #endif
index 1ef69b0c26bbb71f0851b15808c0ce1482ad0d9b..5bee3603b2e8c71df95ab07cf5681a9dd8478a25 100644 (file)
@@ -34,13 +34,14 @@ resampler_test_one (int from, int to)
 
        /* 3 hours */
        int64_t const N = from * 60 * 60 * 3;
+
+       /* XXX: no longer checks anything */
        
        for (int64_t i = 0; i < N; i += 1000) {
                shared_ptr<AudioBuffers> a (new AudioBuffers (1, 1000));
                a->make_silent ();
-               pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> r = resamp.run (a, i);
-               BOOST_CHECK_EQUAL (r.second, total_out);
-               total_out += r.first->frames ();
+               shared_ptr<const AudioBuffers> r = resamp.run (a);
+               total_out += r->frames ();
        }
 }      
                
index 22dea1fc4ea537b705d36084731305b808c63df3..df2cb1c4b03b256da63861c589dc267d024f203f 100644 (file)
@@ -53,9 +53,9 @@ public:
 
 struct TestConfig
 {
-       TestConfig()
+       TestConfig ()
        {
-               dcpomatic_setup();
+               dcpomatic_setup ();
 
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
@@ -95,10 +95,10 @@ new_test_film (string name)
 }
 
 static void
-check_file (string ref, string check)
+check_file (boost::filesystem::path ref, boost::filesystem::path check)
 {
        uintmax_t N = boost::filesystem::file_size (ref);
-       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size(check));
+       BOOST_CHECK_EQUAL (N, boost::filesystem::file_size (check));
        FILE* ref_file = fopen (ref.c_str(), "rb");
        BOOST_CHECK (ref_file);
        FILE* check_file = fopen (check.c_str(), "rb");
@@ -135,7 +135,7 @@ note (libdcp::NoteType t, string n)
 }
 
 void
-check_dcp (string ref, string check)
+check_dcp (boost::filesystem::path ref, boost::filesystem::path check)
 {
        libdcp::DCP ref_dcp (ref);
        ref_dcp.read ();
index e49dfc276fb58db43109bf27cb360354e28b734a..c582966655ddc6c6ed4b6f8e2ceb0cefb14f240f 100644 (file)
@@ -23,6 +23,6 @@ class Film;
 
 extern void wait_for_jobs ();
 extern boost::shared_ptr<Film> new_test_film (std::string);
-extern void check_dcp (std::string, std::string);
+extern void check_dcp (boost::filesystem::path, boost::filesystem::path);
 extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list<std::string>);
 extern boost::filesystem::path test_film_dir (std::string);
index 4dccb49c6c1d95302fb031794b90d1ba97217357..5733c7d03d69070b2fa195e43665c1b337f146c7 100644 (file)
@@ -53,3 +53,17 @@ BOOST_AUTO_TEST_CASE (md5_digest_test)
        p.push_back ("foobar");
        BOOST_CHECK_THROW (md5_digest (p, shared_ptr<Job> ()), std::runtime_error);
 }
+
+/* Straightforward test of time_round_up_test */
+BOOST_AUTO_TEST_CASE (time_round_up_test)
+{
+       BOOST_CHECK_EQUAL (time_round_up (0, 2), 0);
+       BOOST_CHECK_EQUAL (time_round_up (1, 2), 2);
+       BOOST_CHECK_EQUAL (time_round_up (2, 2), 2);
+       BOOST_CHECK_EQUAL (time_round_up (3, 2), 4);
+       
+       BOOST_CHECK_EQUAL (time_round_up (0, 42), 0);
+       BOOST_CHECK_EQUAL (time_round_up (1, 42), 42);
+       BOOST_CHECK_EQUAL (time_round_up (42, 42), 42);
+       BOOST_CHECK_EQUAL (time_round_up (43, 42), 84);
+}
index 4ae49b087a05b32c3dfac078a4dbfaaddcb4896b..1a924ba0d0c5f8ea18fff197d5f9c70223c98291 100644 (file)
@@ -10,7 +10,7 @@ def configure(conf):
                               """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST')
 
 def build(bld):
-    obj = bld(features = 'cxx cxxprogram')
+    obj = bld(features='cxx cxxprogram')
     obj.name   = 'unit-tests'
     obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
     obj.use    = 'libdcpomatic'
@@ -26,6 +26,7 @@ def build(bld):
                  ffmpeg_dcp_test.cc
                  ffmpeg_examiner_test.cc
                  ffmpeg_pts_offset.cc
+                 ffmpeg_seek_test.cc
                  file_group_test.cc
                  film_metadata_test.cc
                  frame_rate_test.cc
@@ -35,6 +36,7 @@ def build(bld):
                  pixel_formats_test.cc
                  play_test.cc
                  ratio_test.cc
+                 repeat_frame_test.cc
                  resampler_test.cc
                  scaling_test.cc
                  silence_padding_test.cc
@@ -46,3 +48,15 @@ def build(bld):
 
     obj.target = 'unit-tests'
     obj.install_path = ''
+
+    obj = bld(features='cxx cxxprogram')
+    obj.name   = 'long-unit-tests'
+    obj.uselib = 'BOOST_TEST DCP OPENJPEG AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML'
+    obj.use    = 'libdcpomatic'
+    obj.source = """
+                 test.cc
+                 long_ffmpeg_seek_test.cc
+                 """
+
+    obj.target = 'long-unit-tests'
+    obj.install_path = ''