Merge 1.0
authorCarl Hetherington <cth@carlh.net>
Tue, 31 Dec 2013 15:44:51 +0000 (15:44 +0000)
committerCarl Hetherington <cth@carlh.net>
Tue, 31 Dec 2013 15:44:51 +0000 (15:44 +0000)
74 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/encoder.cc
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.cc
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/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_audio_test.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/repeat_frame_test.cc [new file with mode: 0644]
test/resampler_test.cc
test/seek_zero_test.cc [new file with mode: 0644]
test/skip_frame_test.cc [new file with mode: 0644]
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 b4c4f34b6f064aaa71e822869728e266c488bdf0..83a43f3bd2eae6ee48a54594adf313403dca8df2 100644 (file)
@@ -40,7 +40,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 (Config::instance()->default_audio_delay ())
@@ -149,3 +149,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..aecf396440d63153a3c1c8a9cef4130af05502a4 100644 (file)
@@ -35,24 +35,33 @@ using boost::shared_ptr;
 AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
        : Decoder (film)
        , _audio_content (content)
-       , _audio_position (0)
+       , _last_audio (0)
 {
-
+       if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+               _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+       }
 }
 
 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 ();
+       if (_resampler) {
+               data = _resampler->run (data);
+       }
+       
+       _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, time)));
+       _last_audio = time + (data->frames() * TIME_HZ / _audio_content->output_audio_frame_rate());
 }
 
-/** 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
+void
+AudioDecoder::flush ()
 {
-       return _audio_content->audio_channels () > 0;
+       if (!_resampler) {
+               return;
+       }
+
+       shared_ptr<const AudioBuffers> b = _resampler->flush ();
+       if (b) {
+               audio (b, _last_audio);
+       }
 }
index ab6c4b8a931e12cfe892c8cb74d86ae7162d1a10..a295df0ccbbf9d770b7f60afb0926ec99c56f659 100644 (file)
 #include "decoder.h"
 #include "content.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class AudioBuffers;
+class Resampler;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
@@ -37,17 +39,20 @@ 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);
+       void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+       void flush ();
+
        boost::shared_ptr<const AudioContent> _audio_content;
-       AudioContent::Frame _audio_position;
+       boost::shared_ptr<Resampler> _resampler;
+       /* End time of last audio that we wrote to _pending; only used for flushing the resampler */
+       ContentTime _last_audio;
 };
 
 #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 d835a5b0573fd263c6e46f0be4073d830bccde06..fa0031abfe27c32bb539da792d7691136a8239b6 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 4ee7c267f354c5cf65da2f1c948f3b8d93b79424..965b33b22b98d0ebf9a81d5166aa4623e8fdb763 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..1de2ff7
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+    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)
+       {}
+
+       DecodedSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
+               : Decoded (f)
+               , image (im)
+               , rect (r)
+               , content_time_to (t)
+               , dcp_time_to (0)
+       {}
+
+       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..4e136d619e075b3d49f52e147365ded667cd3d15 100644 (file)
 
 #include "film.h"
 #include "decoder.h"
+#include "decoded.h"
 
 #include "i18n.h"
 
+using std::cout;
 using boost::shared_ptr;
 
 /** @param f Film.
@@ -33,6 +35,36 @@ using boost::shared_ptr;
  */
 Decoder::Decoder (shared_ptr<const Film> f)
        : _film (f)
+       , _done (false)
 {
 
 }
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+       while (!_done && _pending.empty ()) {
+               _done = pass ();
+       }
+
+       if (_done) {
+               return shared_ptr<Decoded> ();
+       }
+
+       return _pending.front ();
+}
+
+void
+Decoder::consume ()
+{
+       if (!_pending.empty ()) {
+               _pending.pop_front ();
+       }
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+       _pending.clear ();
+       _done = false;
+}
index d67592ed812544c644b8766bcb1b1be1c03e84de..6646b0e76acc66b14edb78d698bd9c48412e07c4 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,32 @@ 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 ();
+       void consume ();
 
 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;
+       bool _done;
 };
 
 #endif
index b78bcaeea7081bd12317608ff7e45a09695a18b7..ca9134c04d29e52400579330eb34d4fa75a5196e 100644 (file)
@@ -218,7 +218,7 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
                TIMING ("adding to queue of %1", _queue.size ());
                _queue.push_back (shared_ptr<DCPVideoFrame> (
                                          new DCPVideoFrame (
-                                                 image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+                                                 image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
                                                  _film->j2k_bandwidth(), _film->resolution(), _film->log()
                                                  )
                                          ));
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..298284512371b87c53ba21edee018434f218d009 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,10 @@ FFmpegDecoder::flush ()
                decode_audio_packet ();
        }
 
-       /* Stop us being asked for any more data */
-       _video_position = _ffmpeg_content->video_length ();
-       _audio_position = _ffmpeg_content->audio_length ();
+       AudioDecoder::flush ();
 }
 
-void
+bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
@@ -164,7 +161,7 @@ FFmpegDecoder::pass ()
                }
 
                flush ();
-               return;
+               return true;
        }
 
        avcodec_get_frame_defaults (_frame);
@@ -183,6 +180,7 @@ FFmpegDecoder::pass ()
        }
 
        av_free_packet (&_packet);
+       return false;
 }
 
 /** @param data pointer to array of pointers to buffers.
@@ -297,70 +295,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 (optional<ContentTime>, optional<ContentTime>, int)> finished)
 {
-       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+       int frames_read = 0;
+       optional<ContentTime> last_video;
+       optional<ContentTime> last_audio;
 
-       /* 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;
+
+               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+               if (_packet.stream_index == _video_stream) {
 
-       if (initial < 0) {
-               initial = 0;
+                       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, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+       return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+       int64_t s = ((double (t) / TIME_HZ) - _video_pts_offset) /
+               av_q2d (_format_context->streams[_video_stream]->time_base);
+       
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (
+                               ((double (t) / TIME_HZ) - _audio_pts_offset) /
+                               av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)
+                               )
+                       );
+       }
 
-       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+       av_seek_frame (_format_context, _video_stream, s, 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 (!accurate) {
+               /* That'll do */
+               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 +436,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 +445,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 +503,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 +537,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 +562,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;
+       ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+       ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
 
        AVSubtitleRect const * rect = sub.rects[0];
 
index 11f83ed97c9808aeaff603f110814463b51234a6..55b624bd60e1df843fbfb7eb0aacd7ef9c9746a6 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, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+       bool seek_final_finished (int, int) const;
+       int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<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 1bf35cc5fffde98ced3c2a2598c0a41926f129c4..98b7bf6b90595589e48326b0157e40739ff92e17 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 ();
@@ -860,6 +860,12 @@ Film::best_video_frame_rate () const
        return _playlist->best_dcp_frame_rate ();
 }
 
+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)
 {
@@ -879,24 +885,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 e318f772492fc83777b5169170ff822779c88c08..f1564e83bece00051f16140df7f6c8bb435e6aa2 100644 (file)
@@ -103,17 +103,18 @@ 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;
+       FrameRateChange active_frame_rate_change (DCPTime) const;
 
        libdcp::KDM
        make_kdm (
index 95bf2b04d171085615e786418600d23d858f2909..e5f626c24762bbb2e57be8e1bd06a1cada3b9efc 100644 (file)
@@ -31,6 +31,7 @@ extern "C" {
 #include "image.h"
 #include "exceptions.h"
 #include "scaler.h"
+#include "timer.h"
 
 using std::string;
 using std::min;
@@ -94,9 +95,9 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
        libdcp::Size cropped_size = crop.apply (size ());
 
        struct SwsContext* scale_context = sws_getContext (
-               cropped_size.width, cropped_size.height, pixel_format(),
-               inter_size.width, inter_size.height, out_format,
-               scaler->ffmpeg_id (), 0, 0, 0
+                       cropped_size.width, cropped_size.height, pixel_format(),
+                       inter_size.width, inter_size.height, out_format,
+                       scaler->ffmpeg_id (), 0, 0, 0
                );
 
        uint8_t* scale_in_data[components()];
@@ -375,8 +376,18 @@ Image::make_black ()
 void
 Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
 {
-       /* Only implemented for RGBA onto RGB24 so far */
-       assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+       int this_bpp = 0;
+       int other_bpp = 0;
+
+       if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 4;
+               other_bpp = 4;
+       } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+               this_bpp = 3;
+               other_bpp = 4;
+       } else {
+               assert (false);
+       }
 
        int start_tx = position.x;
        int start_ox = 0;
@@ -395,15 +406,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
        }
 
        for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
-               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+               uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
                uint8_t* op = other->data()[0] + oy * other->stride()[0];
                for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
                        float const alpha = float (op[3]) / 255;
                        tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
                        tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
                        tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
-                       tp += 3;
-                       op += 4;
+                       tp += this_bpp;
+                       op += other_bpp;
                }
        }
 }
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 a7999c02a86b5270b09dd50fd118c00280ef5761..a5ca67e0d95856c4de867b4f5ea70a17a5dd7a6a 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 = 0;
@@ -80,17 +82,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 cacb42651b1656d706773a7b335c21c054af2458..c9f9acd942179561184fb2c32bd70e2ee65a24b9 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include <stdint.h>
+#include <algorithm>
 #include "player.h"
 #include "film.h"
 #include "ffmpeg_decoder.h"
@@ -31,7 +32,6 @@
 #include "job.h"
 #include "image.h"
 #include "ratio.h"
-#include "resampler.h"
 #include "log.h"
 #include "scaler.h"
 
@@ -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,8 @@ 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)
+       , _approximate_size (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 +99,131 @@ 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() - (*i)->content->trim_start());
                }
 
-               if (_audio && ad && ad->has_audio ()) {
-                       if ((*i)->audio_position < earliest_t) {
-                               earliest_t = (*i)->audio_position;
-                               earliest = *i;
-                               type = AUDIO;
-                       }
+               if (dec && dec->dcp_time < earliest_time) {
+                       earliest_piece = *i;
+                       earliest_decoded = dec;
+                       earliest_time = dec->dcp_time;
                }
-       }
 
-       if (!earliest) {
+               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);
+       if (earliest_audio != TIME_MAX) {
+               TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
+               Audio (tb.audio, tb.time);
+               /* This assumes that the audio_frames_to_time conversion is exact
+                  so that there are no accumulated errors caused by rounding.
+               */
+               _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+       }
+
+       /* 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);
+
+       /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+       bool consume = true;
+
+       /* This is the margin either side of _{video,audio}_position that we will accept
+          as a starting point for a frame consecutive to the previous.
+       */
+       DCPTime const margin = TIME_HZ / (2 * _film->video_frame_rate ());
+       
+       if (dv && _video) {
+
+               if (_just_did_inaccurate_seek) {
+
+                       /* Just emit; no subtlety */
+                       emit_video (earliest_piece, dv);
+                       step_video_position (dv);
+                       
+               } else if (dv->dcp_time - _video_position > margin) {
+
+                       /* Too far ahead */
+
+                       list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+                       while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+                               ++i;
+                       }
+
+                       if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+                               /* We're outside all video content */
+                               emit_black ();
                        } else {
-                               earliest->decoder->pass ();
+                               /* We're inside some video; repeat the frame */
+                               _last_incoming_video.video->dcp_time = _video_position;
+                               emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+                               step_video_position (_last_incoming_video.video);
                        }
-               }
-               break;
 
-       case AUDIO:
-               if (earliest_t > _audio_position) {
-                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+                       consume = false;
+
+               } else if (abs (dv->dcp_time - _video_position) < margin) {
+                       /* We're ok */
+                       emit_video (earliest_piece, dv);
+                       step_video_position (dv);
                } 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 ());
-                                       }
-                               }
-                       }
+                       /* Too far behind: skip */
                }
-               break;
-       }
 
-       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;
-                       }
+               _just_did_inaccurate_seek = false;
 
-                       if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
-                               audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
-                       }
-               }
+       } else if (da && _audio) {
 
-               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 ());
+               if (da->dcp_time - _audio_position > margin) {
+                       /* Too far ahead */
+                       emit_silence (da->dcp_time - _audio_position);
+                       consume = false;
+               } else if (abs (da->dcp_time - _audio_position) < margin) {
+                       /* We're ok */
+                       emit_audio (earliest_piece, da);
+               } else {
+                       /* Too far behind: skip */
                }
-       }
                
+       } else if (ds && _video) {
+               _in_subtitle.piece = earliest_piece;
+               _in_subtitle.subtitle = ds;
+               update_subtitle ();
+       }
+
+       if (consume) {
+               earliest_piece->decoder->consume ();
+       }                       
+       
        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 +233,18 @@ 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());
-       if (frc.skip && (frame % 2) == 1) {
-               return;
-       }
+       FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
 
-       Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
-       if (content->trimmed (relative_time)) {
-               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);
+       libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
+       if (_approximate_size) {
+               image_size.width &= ~3;
+               image_size.height &= ~3;
+       }
 
        shared_ptr<PlayerImage> pi (
                new PlayerImage (
-                       image,
+                       video->image,
                        content->crop(),
                        image_size,
                        _video_container_size,
@@ -283,11 +252,15 @@ 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.image &&
+               video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+               ) {
 
                Position<int> const container_offset (
                        (_video_container_size.width - image_size.width) / 2,
-                       (_video_container_size.height - image_size.width) / 2
+                       (_video_container_size.height - image_size.height) / 2
                        );
 
                pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
@@ -297,18 +270,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
        _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);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+       /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+       if (video->eyes != EYES_LEFT) {
+               /* This assumes that the video_frames_to_time conversion is exact
+                  so that there are no accumulated errors caused by rounding.
+               */
+               _video_position += _film->video_frames_to_time (1);
        }
 }
 
 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 +300,49 @@ 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;
-       }
-       
-       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 (_audio && tb.audio) {
                Audio (tb.audio, tb.time);
                _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -385,7 +353,7 @@ Player::flush ()
        }
 
        while (_audio && _audio_position < _video_position) {
-               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+               emit_silence (_video_position - _audio_position);
        }
        
 }
@@ -395,7 +363,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,92 +374,113 @@ 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;
+                               }
 
-                       piece->decoder = sd;
+                               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;
+                               }
+                       }
+
+                       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;
+
+       /* The Piece for the _last_incoming_video will no longer be valid */
+       _last_incoming_video.video.reset ();
+
+       _video_position = _audio_position = 0;
 }
 
 void
@@ -552,29 +541,6 @@ Player::set_video_container_size (libdcp::Size s)
                );
 }
 
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
-       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
-       if (i != _resamplers.end ()) {
-               return i->second;
-       }
-
-       if (!create) {
-               return shared_ptr<Resampler> ();
-       }
-
-       _film->log()->log (
-               String::compose (
-                       "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
-                       )
-               );
-       
-       shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
-       _resamplers[c] = r;
-       return r;
-}
-
 void
 Player::emit_black ()
 {
@@ -588,17 +554,18 @@ 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 +581,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,7 +589,7 @@ Player::update_subtitle ()
                return;
        }
 
-       if (!_in_subtitle.image) {
+       if (!_in_subtitle.subtitle->image) {
                _out_subtitle.image.reset ();
                return;
        }
@@ -642,7 +597,7 @@ Player::update_subtitle ()
        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 ();
@@ -665,15 +620,16 @@ 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.image = _in_subtitle.subtitle->image->scale (
                scaled_size,
                Scaler::from_id ("bicubic"),
-               _in_subtitle.image->pixel_format (),
+               PIX_FMT_RGBA,
                true
                );
-       _out_subtitle.from = _in_subtitle.from + piece->content->position ();
-       _out_subtitle.to = _in_subtitle.to + piece->content->position ();
+
+       _out_subtitle.from = _in_subtitle.subtitle->dcp_time;
+       _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to;
 }
 
 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -682,22 +638,25 @@ 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;
 }
 
+void
+Player::set_approximate_size ()
+{
+       _approximate_size = true;
+}
+                             
+
 PlayerImage::PlayerImage (
        shared_ptr<const Image> in,
        Crop crop,
@@ -722,10 +681,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
 }
 
 shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
 {
-       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+       shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+       
        Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
 
        if (_subtitle_image) {
@@ -734,3 +693,4 @@ PlayerImage::image ()
 
        return out;
 }
+
index 11cc99e7793005630a5e9ce1014e5827bdf55ed4..b932f41682a78da892a675b6ac1e39aae0060fcf 100644 (file)
@@ -29,6 +29,7 @@
 #include "rect.h"
 #include "audio_merger.h"
 #include "audio_content.h"
+#include "decoded.h"
 
 class Job;
 class Film;
@@ -36,24 +37,9 @@ class Playlist;
 class AudioContent;
 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
@@ -63,7 +49,7 @@ public:
 
        void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
        
-       boost::shared_ptr<Image> image ();
+       boost::shared_ptr<Image> image (AVPixelFormat, bool);
 
 private:
        boost::shared_ptr<const Image> _in;
@@ -75,6 +61,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,13 +74,14 @@ 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;
        }
 
        void set_video_container_size (libdcp::Size);
+       void set_approximate_size ();
 
        bool repeat_last_video ();
 
@@ -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>);
+       void step_video_position (boost::shared_ptr<DecodedVideo>);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -143,29 +133,25 @@ 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;
-       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
 
        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<Image> image;
+               DCPTime from;
+               DCPTime to;
        } _out_subtitle;
 
 #ifdef DCPOMATIC_DEBUG
@@ -174,7 +160,13 @@ 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;
+       bool _approximate_size;
 
        boost::signals2::scoped_connection _playlist_changed_connection;
        boost::signals2::scoped_connection _playlist_content_changed_connection;
index daa82cb94a56e305dc3420424f15e2d7a4f1cf41..4175de4c9ce89d4f4baf8b403e92dac1ffcb05d0 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 1915e3d045a01cabe7058c0fb4af089599548842..35709f109d2bf4bb4cbcbd725f61bbce7f7bf3ae 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..432f73f0d66f5b30e6c8068a13266719cf9c197b 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.
        */
@@ -90,9 +94,11 @@ SndfileDecoder::pass ()
        }
                
        data->set_frames (this_time);
-       audio (data, _done);
+       audio (data, _done * TIME_HZ / audio_frame_rate ());
        _done += this_time;
        _remaining -= this_time;
+
+       return true;
 }
 
 int
@@ -113,8 +119,11 @@ 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);
+
+       _done = t * audio_frame_rate() / TIME_HZ;
+       _remaining = _info.frames - _done;
 }
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..7ba969933d077717ed125546657767d8fcc1aeda 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, ContentTime from, ContentTime to)
 {
-       Subtitle (image, rect, from, to);
+       _pending.push_back (shared_ptr<DecodedSubtitle> (new DecodedSubtitle (image, rect, from, to)));
 }
index eeeadbd3f65fdda2f9ee46b90c9745f2ce713f8f..fd1d71f33fdef23b3ef8ed7b5d69140baa4ed2cb 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>, ContentTime, ContentTime);
 };
index 96b993a8e15866b4674324735e05cdadd968f2f7..33c0c171feecb6758d5976203ddea1020feeac99 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 cc075a34ced0534d0e3298affe5ce840d0a8926a..404549532806fc947ecc7440d6acb85aef521bc0 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)
@@ -353,19 +353,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 e7ddec5e6cd19df910966a66fcb9e969b5c5b6ee..6a16557cdc65e8aac476eeb290a1f5b3c48d8c32 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;
        }
        case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
        {
                int const half = image->size().height / 2;
-               Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
-               Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, time)));
+               _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, half, 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 b2c5e784ba8caf2346c08b903c5a81dec8d8e296..38e4704b751e8882437b8d605e5fbb1415151f21 100644 (file)
@@ -47,10 +47,15 @@ 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, RESOLUTION_2K, log_));
-       shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_));
+       shared_ptr<DCPVideoFrame> local  (
+               new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+               );
+       
+       shared_ptr<DCPVideoFrame> remote (
+               new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)
+               );
 
        cout << "Frame " << frame << ": ";
        cout.flush ();
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 cd159189cc15a3a909324998205f94b2594049dd..e5cb5371c533c62ed755bc736199150ef41d1847 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..e6b8bf8dd06cd37b6d4e67fd41a3f3cec2c3381d 100644 (file)
@@ -36,6 +36,7 @@
 #include "lib/player.h"
 #include "lib/video_content.h"
 #include "lib/video_decoder.h"
+#include "lib/timer.h"
 #include "film_viewer.h"
 #include "wx_util.h"
 
@@ -128,6 +129,7 @@ FilmViewer::set_film (shared_ptr<Film> f)
 
        _player = f->make_player ();
        _player->disable_audio ();
+       _player->set_approximate_size ();
        _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5));
        _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 
@@ -164,7 +166,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,20 +277,24 @@ 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;
        }
-       
-       _frame = image->image ();
+
+       /* Going via BGRA here makes the scaler faster then using RGB24 directly (about
+          twice on x86 Linux).
+       */
+       shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true);
+       _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
        _got_frame = true;
 
        set_position_text (t);
 }
 
 void
-FilmViewer::set_position_text (Time t)
+FilmViewer::set_position_text (DCPTime t)
 {
        if (!_film) {
                _frame_number->SetLabel ("0");
@@ -371,7 +377,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 880b44a31be8754ca54634e542d9e739e328b745..f95740255ea44ffe105860f76cc6669e3f69b1e4 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 6cc1f79d93dd23c9d0b089648f36edcf1892c893..2119e781322f2cedae4e3083ffd20b7ead4ffda2 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 90b4adfe396a716b157538903c2542217bc6cee4..a777b3e88da8f317f9b0e8f3221850a3c6407c35 100644 (file)
@@ -334,7 +334,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;
 
index 65c29325c348178b2fd4febc13fa8ac7f01044ff..bba05ae9bef5a4db7ffbdb52f0d7860f153fd225 100644 (file)
 
 */
 
+/** @file  test/ffmpeg_audio_test.cc
+ *  @brief A simple test of reading audio from an FFmpeg file.
+ */
+
 #include <boost/test/unit_test.hpp>
 #include <libdcp/cpl.h>
 #include <libdcp/dcp.h>
diff --git a/test/ffmpeg_seek_test.cc b/test/ffmpeg_seek_test.cc
new file mode 100644 (file)
index 0000000..3c175f0
--- /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.
+
+*/
+
+/** @file  test/ffmpeg_seek_test.cc
+ *  @brief Test seek using Player with an FFmpegDecoder; note that the player
+ *  can hide problems with FFmpegDecoder seeking as it will skip frames / insert
+ *  black as it sees fit.
+ */
+
+#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);
+}
+
+/* Test basic seeking */
+BOOST_AUTO_TEST_CASE (ffmpeg_seek_test)
+{
+       film = new_test_film ("ffmpeg_seek_test");
+       film->set_name ("ffmpeg_seek_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..54fe2699f7fd47b19ad9ac666a70857e309d4567 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,11 +46,11 @@ 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;
-               v.image = i->image ();
+               v.image = i->image (PIX_FMT_RGB24, false);
                v.time = t;
                _queue.push_front (v);
        }
@@ -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
diff --git a/test/repeat_frame_test.cc b/test/repeat_frame_test.cc
new file mode 100644 (file)
index 0000000..73b9ad6
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    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 "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the repeat of frames by the player when putting a 24fps
+   source into a 48fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (repeat_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("repeat_frame_test");
+       film->set_name ("repeat_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/red_24.mp4"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+
+       film->set_video_frame_rate (48);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/repeat_frame_test", film->dir (film->dcp_name ()));
+}
+
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 ();
        }
 }      
                
diff --git a/test/seek_zero_test.cc b/test/seek_zero_test.cc
new file mode 100644 (file)
index 0000000..0987f6b
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    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.
+
+*/
+
+/** @file  test/seek_zero_test.cc
+ *  @brief Test seek to zero with a raw FFmpegDecoder (without the player
+ *  confusing things as it might in ffmpeg_seek_test).
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/film.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/ratio.h"
+#include "lib/dcp_content_type.h"
+#include "lib/ffmpeg_decoder.h"
+#include "test.h"
+
+using std::cout;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (seek_zero_test)
+{
+       shared_ptr<Film> film = new_test_film ("seek_zero_test");
+       film->set_name ("seek_zero_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       content->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (content);
+       wait_for_jobs ();
+
+       FFmpegDecoder decoder (film, content, true, false);
+       shared_ptr<Decoded> a = decoder.peek ();
+       cout << a->content_time << "\n";
+       decoder.seek (0, true);
+       shared_ptr<Decoded> b = decoder.peek ();
+       cout << b->content_time << "\n";
+
+       /* a will be after no seek, and b after a seek to zero, which should
+          have the same effect.
+       */
+       BOOST_CHECK_EQUAL (a->content_time, b->content_time);
+}
diff --git a/test/skip_frame_test.cc b/test/skip_frame_test.cc
new file mode 100644 (file)
index 0000000..4ac5192
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    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 "test.h"
+#include "lib/film.h"
+#include "lib/ratio.h"
+#include "lib/ffmpeg_content.h"
+#include "lib/dcp_content_type.h"
+
+using boost::shared_ptr;
+
+/* Test the skip of frames by the player when putting a 48fps
+   source into a 24fps DCP.
+*/
+BOOST_AUTO_TEST_CASE (skip_frame_test)
+{
+       shared_ptr<Film> film = new_test_film ("skip_frame_test");
+       film->set_name ("skip_frame_test");
+       film->set_container (Ratio::from_id ("185"));
+       film->set_dcp_content_type (DCPContentType::from_pretty_name ("Test"));
+       shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/count300bd48.m2ts"));
+       c->set_ratio (Ratio::from_id ("185"));
+       film->examine_and_add_content (c);
+
+       wait_for_jobs ();
+       film->write_metadata ();
+
+       film->set_video_frame_rate (24);
+       film->make_dcp ();
+       wait_for_jobs ();
+
+       check_dcp ("test/data/skip_frame_test", film->dir (film->dcp_name ()));
+}
+
index 22dea1fc4ea537b705d36084731305b808c63df3..c0f732c46d22c9f558e6c897772d3833cf51e39f 100644 (file)
@@ -53,15 +53,16 @@ public:
 
 struct TestConfig
 {
-       TestConfig()
+       TestConfig ()
        {
-               dcpomatic_setup();
+               dcpomatic_setup ();
 
                Config::instance()->set_num_local_encoding_threads (1);
                Config::instance()->set_server_port_base (61920);
                Config::instance()->set_default_dci_metadata (DCIMetadata ());
                Config::instance()->set_default_container (static_cast<Ratio*> (0));
                Config::instance()->set_default_dcp_content_type (static_cast<DCPContentType*> (0));
+               Config::instance()->set_default_audio_delay (0);
 
                ServerFinder::instance()->disable ();
 
@@ -95,10 +96,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 +136,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..3c7484fcdb48f17521bbf626fa8544785d8de2dd 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,9 +36,12 @@ def build(bld):
                  pixel_formats_test.cc
                  play_test.cc
                  ratio_test.cc
+                 repeat_frame_test.cc
                  resampler_test.cc
                  scaling_test.cc
+                 seek_zero_test.cc
                  silence_padding_test.cc
+                 skip_frame_test.cc
                  stream_test.cc
                  test.cc
                  threed_test.cc
@@ -46,3 +50,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 = ''