Another try at sorting out the thorny question of timing.
authorCarl Hetherington <cth@carlh.net>
Wed, 26 Jun 2013 00:21:21 +0000 (01:21 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 26 Jun 2013 16:04:31 +0000 (17:04 +0100)
60 files changed:
doc/design/timing.tex [new file with mode: 0644]
src/lib/audio_content.h
src/lib/audio_decoder.cc
src/lib/audio_decoder.h
src/lib/audio_sink.h [deleted file]
src/lib/audio_source.cc [deleted file]
src/lib/audio_source.h [deleted file]
src/lib/black_decoder.cc [deleted file]
src/lib/black_decoder.h [deleted file]
src/lib/combiner.cc [deleted file]
src/lib/combiner.h [deleted file]
src/lib/decoder.h
src/lib/encoder.cc
src/lib/encoder.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_decoder.h
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/image.cc
src/lib/imagemagick_content.cc
src/lib/imagemagick_content.h
src/lib/imagemagick_decoder.cc
src/lib/imagemagick_decoder.h
src/lib/imagemagick_examiner.h
src/lib/matcher.cc [deleted file]
src/lib/matcher.h [deleted file]
src/lib/null_content.cc [deleted file]
src/lib/null_content.h [deleted file]
src/lib/player.cc
src/lib/player.h
src/lib/playlist.h
src/lib/resampler.cc [new file with mode: 0644]
src/lib/resampler.h [new file with mode: 0644]
src/lib/silence_decoder.cc [deleted file]
src/lib/silence_decoder.h [deleted file]
src/lib/sndfile_content.cc
src/lib/sndfile_content.h
src/lib/sndfile_decoder.cc
src/lib/sndfile_decoder.h
src/lib/subtitle.cc
src/lib/transcoder.cc
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/lib/video_examiner.h
src/lib/video_sink.h [deleted file]
src/lib/video_source.cc [deleted file]
src/lib/video_source.h [deleted file]
src/lib/writer.cc
src/lib/wscript
src/wx/film_viewer.cc
test/ffmpeg_examiner_test.cc
test/ffmpeg_pts_offset.cc [new file with mode: 0644]
test/test.cc

diff --git a/doc/design/timing.tex b/doc/design/timing.tex
new file mode 100644 (file)
index 0000000..567ba02
--- /dev/null
@@ -0,0 +1,42 @@
+\documentclass{article}
+\begin{document}
+
+We are trying to implement full-ish playlist based content specification.  The timing is awkward.
+
+\section{Reference timing}
+
+Frame rates of things can vary a lot; content can be in pretty much
+anything, and DCP video and audio frame rates may change on a whim
+depending on what is best for a given set of content.  This suggests
+(albeit without strong justification) the need for a frame-rate-independent unit of time.
+
+So far we've been using a time type called \texttt{Time} expressed in
+$\mathtt{TIME\_HZ}^{-1}$; e.g. \texttt{TIME\_HZ} units is 1 second.
+\texttt{TIME\_HZ} is chosen to be divisible by lots of frame and
+sample rates.
+
+We express content start time as a \texttt{Time}.
+
+
+\section{Timing at different stages of the chain}
+
+Let's try this: decoders produce sequences of (perhaps) video frames
+and (perhaps) audio frames.  There are no gaps.  They are at the
+content's native frame rates and are synchronised (meaning that if
+they are played together, at the content's frame rates, they will be
+in sync).  The decoders give timestamps for each piece of their
+output, which are \emph{simple indices} (\texttt{ContentVideoFrame}
+and \texttt{ContentAudioFrame}).  Decoders know nothing of \texttt{Time}.
+
+
+\section{Split of stuff between decoders and player}
+
+In some ways it seems nice to have decoders which produce the rawest
+possible data and make the player sort it out (e.g.\ cropping and
+scaling video, resampling audio).  The resampling is awkward, though,
+as you really need one resampler per source.  So it might make more sense
+to put stuff in the decoder.  But then, what's one map of resamplers between friends?
+
+
+
+\end{document}
index 73a00ca7d70f3b7862e93accf6168129b1fba4ea..9bf53e0ab4d044ac379c6f45decd7d00ba4f932f 100644 (file)
@@ -41,6 +41,8 @@ public:
 class AudioContent : public virtual Content
 {
 public:
+       typedef int64_t Frame;
+       
        AudioContent (boost::shared_ptr<const Film>, Time);
        AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
@@ -49,7 +51,7 @@ public:
        void as_xml (xmlpp::Node *) const;
 
         virtual int audio_channels () const = 0;
-        virtual ContentAudioFrame audio_length () const = 0;
+        virtual AudioContent::Frame audio_length () const = 0;
         virtual int content_audio_frame_rate () const = 0;
        virtual int output_audio_frame_rate () const = 0;
        virtual AudioMapping audio_mapping () const = 0;
index a9e01908c4782a6095821d080800b0568ce71214..dc49a1846e74ada429e20603d213666d858ecd52 100644 (file)
@@ -31,57 +31,12 @@ using std::cout;
 using boost::optional;
 using boost::shared_ptr;
 
-AudioDecoder::AudioDecoder (shared_ptr<const Film> f, shared_ptr<const AudioContent> c)
+AudioDecoder::AudioDecoder (shared_ptr<const Film> f)
        : Decoder (f)
-       , _next_audio (0)
-       , _audio_content (c)
+       , _audio_position (0)
 {
-       if (_audio_content->content_audio_frame_rate() != _audio_content->output_audio_frame_rate()) {
-
-               shared_ptr<const Film> film = _film.lock ();
-               assert (film);
-
-               stringstream s;
-               s << String::compose (
-                       "Will resample audio from %1 to %2",
-                       _audio_content->content_audio_frame_rate(), _audio_content->output_audio_frame_rate()
-                       );
-               
-               film->log()->log (s.str ());
-
-               /* We will be using planar float data when we call the
-                  resampler.  As far as I can see, the audio channel
-                  layout is not necessary for our purposes; it seems
-                  only to be used get the number of channels and
-                  decide if rematrixing is needed.  It won't be, since
-                  input and output layouts are the same.
-               */
-
-               _swr_context = swr_alloc_set_opts (
-                       0,
-                       av_get_default_channel_layout (_audio_content->audio_channels ()),
-                       AV_SAMPLE_FMT_FLTP,
-                       _audio_content->output_audio_frame_rate(),
-                       av_get_default_channel_layout (_audio_content->audio_channels ()),
-                       AV_SAMPLE_FMT_FLTP,
-                       _audio_content->content_audio_frame_rate(),
-                       0, 0
-                       );
-               
-               swr_init (_swr_context);
-       } else {
-               _swr_context = 0;
-       }
 }
 
-AudioDecoder::~AudioDecoder ()
-{
-       if (_swr_context) {
-               swr_free (&_swr_context);
-       }
-}
-       
-
 #if 0
 void
 AudioDecoder::process_end ()
@@ -113,54 +68,8 @@ AudioDecoder::process_end ()
 #endif
 
 void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, Time time)
-{
-       /* Maybe resample */
-       if (_swr_context) {
-
-               /* Compute the resampled frames count and add 32 for luck */
-               int const max_resampled_frames = ceil (
-                       (int64_t) data->frames() * _audio_content->output_audio_frame_rate() / _audio_content->content_audio_frame_rate()
-                       ) + 32;
-
-               shared_ptr<AudioBuffers> resampled (new AudioBuffers (data->channels(), max_resampled_frames));
-
-               /* Resample audio */
-               int const resampled_frames = swr_convert (
-                       _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) data->data(), data->frames()
-                       );
-               
-               if (resampled_frames < 0) {
-                       throw EncodeError (_("could not run sample-rate converter"));
-               }
-
-               resampled->set_frames (resampled_frames);
-               
-               /* And point our variables at the resampled audio */
-               data = resampled;
-       }
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       /* Remap channels */
-       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (film->dcp_audio_channels(), data->frames()));
-       dcp_mapped->make_silent ();
-       list<pair<int, libdcp::Channel> > map = _audio_content->audio_mapping().content_to_dcp ();
-       for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
-               dcp_mapped->accumulate_channel (data.get(), i->first, i->second);
-       }
-
-       Audio (dcp_mapped, time);
-       _next_audio = time + film->audio_frames_to_time (data->frames());
-}
-
-bool
-AudioDecoder::audio_done () const
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
 {
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       return (_audio_content->length() - _next_audio) < film->audio_frames_to_time (1);
+       Audio (data, frame);
+       _audio_position = frame + data->frames ();
 }
-       
index 1da8a676f37c7f59c3deeb46a166829e60ff1687..ddfb296c9a9b4fffbf8e2b94928594907566fb68 100644 (file)
 #ifndef DCPOMATIC_AUDIO_DECODER_H
 #define DCPOMATIC_AUDIO_DECODER_H
 
-#include "audio_source.h"
 #include "decoder.h"
-extern "C" {
-#include <libswresample/swresample.h>
-}
+#include "content.h"
 
-class AudioContent;
+class AudioBuffers;
 
 /** @class AudioDecoder.
  *  @brief Parent class for audio decoders.
  */
-class AudioDecoder : public AudioSource, public virtual Decoder
+class AudioDecoder : public virtual Decoder
 {
 public:
-       AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
-       ~AudioDecoder ();
+       AudioDecoder (boost::shared_ptr<const Film>);
 
-protected:
-
-       void audio (boost::shared_ptr<const AudioBuffers>, Time);
-       bool audio_done () const;
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
 
-       Time _next_audio;
-       boost::shared_ptr<const AudioContent> _audio_content;
+protected:
 
-private:       
-       SwrContext* _swr_context;
+       void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+       AudioContent::Frame _audio_position;
 };
 
 #endif
diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h
deleted file mode 100644 (file)
index 1aad5ed..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-    Copyright (C) 2012 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_AUDIO_SINK_H
-#define DCPOMATIC_AUDIO_SINK_H
-
-class AudioBuffers;
-
-class AudioSink
-{
-public:
-       /** Call with some audio data */
-       virtual void process_audio (boost::shared_ptr<const AudioBuffers>, Time) = 0;
-};
-
-#endif
diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc
deleted file mode 100644 (file)
index e617216..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-    Copyright (C) 2012 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 "audio_source.h"
-#include "audio_sink.h"
-
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::bind;
-
-static void
-process_audio_proxy (weak_ptr<AudioSink> sink, shared_ptr<const AudioBuffers> audio, Time time)
-{
-       shared_ptr<AudioSink> p = sink.lock ();
-       if (p) {
-               p->process_audio (audio, time);
-       }
-}
-
-void
-AudioSource::connect_audio (shared_ptr<AudioSink> s)
-{
-       Audio.connect (bind (process_audio_proxy, weak_ptr<AudioSink> (s), _1, _2));
-}
-
-
diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h
deleted file mode 100644 (file)
index ef47e96..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-    Copyright (C) 2012 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  src/audio_source.h
- *  @brief Parent class for classes which emit audio data.
- */
-
-#ifndef DCPOMATIC_AUDIO_SOURCE_H
-#define DCPOMATIC_AUDIO_SOURCE_H
-
-#include <boost/signals2.hpp>
-#include "types.h"
-
-class AudioBuffers;
-class AudioSink;
-
-/** A class that emits audio data */
-class AudioSource
-{
-public:
-       /** Emitted when some audio data is ready */
-       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
-
-       void connect_audio (boost::shared_ptr<AudioSink>);
-};
-
-#endif
diff --git a/src/lib/black_decoder.cc b/src/lib/black_decoder.cc
deleted file mode 100644 (file)
index 0b231ed..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
-    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 "black_decoder.h"
-#include "image.h"
-#include "null_content.h"
-
-using boost::shared_ptr;
-
-BlackDecoder::BlackDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
-       : Decoder (f)
-       , VideoDecoder (f, c)
-{
-
-}
-
-void
-BlackDecoder::pass ()
-{
-       if (!_image) {
-               _image.reset (new SimpleImage (AV_PIX_FMT_RGB24, video_size(), true));
-               _image->make_black ();
-               video (_image, false, _next_video);
-       } else {
-               video (_image, true, _next_video);
-       }
-}
-
-float
-BlackDecoder::video_frame_rate () const
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return 24;
-       }
-
-       return f->dcp_video_frame_rate ();
-}
-
-ContentVideoFrame
-BlackDecoder::video_length () const
-{
-       return _video_content->length() * video_frame_rate() / TIME_HZ;
-}
-
-Time
-BlackDecoder::position () const
-{
-       return _next_video;
-}
-
-void
-BlackDecoder::seek (Time t)
-{
-       _next_video = t;
-}
-
-void
-BlackDecoder::seek_back ()
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
-       }
-
-       _next_video -= f->video_frames_to_time (2);
-}
-
-void
-BlackDecoder::seek_forward ()
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
-       }
-
-       _next_video += f->video_frames_to_time (1);
-}
-
-bool
-BlackDecoder::done () const
-{
-       return video_done ();
-}
-
-       
diff --git a/src/lib/black_decoder.h b/src/lib/black_decoder.h
deleted file mode 100644 (file)
index 4591881..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-    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 "video_decoder.h"
-
-class NullContent;
-
-class BlackDecoder : public VideoDecoder
-{
-public:
-       BlackDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<NullContent>);
-
-       /* Decoder */
-       
-       void pass ();
-       void seek (Time);
-       void seek_back ();
-       void seek_forward ();
-       Time position () const;
-       bool done () const;
-
-       /* VideoDecoder */
-
-       float video_frame_rate () const;
-       libdcp::Size video_size () const {
-               return libdcp::Size (256, 256);
-       }
-       ContentVideoFrame video_length () const;
-
-private:
-       boost::shared_ptr<Image> _image;
-};
diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc
deleted file mode 100644 (file)
index 44971d1..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-    Copyright (C) 2012 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 "combiner.h"
-#include "image.h"
-
-using boost::shared_ptr;
-
-Combiner::Combiner ()
-{
-
-}
-
-/** Process video for the left half of the frame.
- *  Subtitle parameter will be ignored.
- *  @param image Frame image.
- */
-void
-Combiner::process_video (shared_ptr<const Image> image, bool, Time)
-{
-       _image.reset (new SimpleImage (image, true));
-}
-
-/** Process video for the right half of the frame.
- *  @param image Frame image.
- *  @param sub Subtitle (which will be put onto the whole frame)
- */
-void
-Combiner::process_video_b (shared_ptr<const Image> image, bool, Time t)
-{
-       /* Copy the right half of this image into our _image */
-       /* XXX: this should probably be in the Image class */
-       for (int i = 0; i < image->components(); ++i) {
-               int const line_size = image->line_size()[i];
-               int const half_line_size = line_size / 2;
-
-               uint8_t* p = _image->data()[i];
-               uint8_t* q = image->data()[i];
-                       
-               for (int j = 0; j < image->lines (i); ++j) {
-                       memcpy (p + half_line_size, q + half_line_size, half_line_size);
-                       p += _image->stride()[i];
-                       q += image->stride()[i];
-               }
-       }
-
-       Video (_image, false, t);
-       _image.reset ();
-}
diff --git a/src/lib/combiner.h b/src/lib/combiner.h
deleted file mode 100644 (file)
index 46c90b4..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-    Copyright (C) 2012 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 src/lib/combiner.h
- *  @brief Class for combining two video streams.
- */
-
-#include "video_source.h"
-#include "video_sink.h"
-
-/** @class Combiner
- *  @brief A class which can combine two video streams into one, with
- *  one image used for the left half of the screen and the other for
- *  the right.
- */
-class Combiner : public VideoSource, public VideoSink
-{
-public:
-       Combiner ();
-
-       void process_video (boost::shared_ptr<const Image> i, bool, Time);
-       void process_video_b (boost::shared_ptr<const Image> i, bool, Time);
-
-private:
-       /** The image that we are currently working on */
-       boost::shared_ptr<Image> _image;
-};
index 391b9d19aa17380289db9f310f1174e9d2eaee81..cfca6867f652d3fc9d2d5be7320d75128060cffe 100644 (file)
@@ -29,8 +29,6 @@
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
-#include "video_source.h"
-#include "audio_source.h"
 #include "film.h"
 
 class Image;
@@ -54,24 +52,6 @@ public:
         */
        virtual void pass () = 0;
 
-       /** Seek this decoder to as close as possible to some time,
-        *  expressed relative to our source's start.
-        *  @param t Time.
-        *  @param a true to try hard to be accurate, otherwise false.
-        */
-       virtual void seek (Time) = 0;
-
-       /** Seek back one video frame */
-       virtual void seek_back () = 0;
-
-       /** Seek forward one video frame */
-       virtual void seek_forward () = 0;
-
-       /** @return Approximate time of the next content that we will emit,
-        *  expressed relative to the start of our source.
-        */
-       virtual Time position () const = 0;
-
        virtual bool done () const = 0;
 
 protected:
index 8b2db0eb3113767594264b6ca8367e2022bbbe8b..c3865d2c16ebf44852cede103a97d94f93c3c022 100644 (file)
@@ -169,7 +169,7 @@ Encoder::frame_done ()
 }
 
 void
-Encoder::process_video (shared_ptr<const Image> image, bool same, Time)
+Encoder::process_video (shared_ptr<const Image> image, bool same)
 {
        boost::mutex::scoped_lock lock (_mutex);
 
@@ -215,7 +215,7 @@ Encoder::process_video (shared_ptr<const Image> image, bool same, Time)
 }
 
 void
-Encoder::process_audio (shared_ptr<const AudioBuffers> data, Time)
+Encoder::process_audio (shared_ptr<const AudioBuffers> data)
 {
        _writer->write (data);
 }
index 3fe707b511e717b38b001788db4a35d601ae8dfd..b5a641f50d40492caa008115cf294ea4ec084de5 100644 (file)
@@ -36,8 +36,6 @@ extern "C" {
 #include <libswresample/swresample.h>
 }
 #include "util.h"
-#include "video_sink.h"
-#include "audio_sink.h"
 
 class Image;
 class AudioBuffers;
@@ -55,7 +53,7 @@ class Job;
  *  is supplied as uncompressed PCM in blocks of various sizes.
  */
 
-class Encoder : public VideoSink, public AudioSink
+class Encoder
 {
 public:
        Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<Job>);
@@ -68,10 +66,10 @@ public:
         *  @param i Video frame image.
         *  @param same true if i is the same as the last time we were called.
         */
-       void process_video (boost::shared_ptr<const Image> i, bool same, Time);
+       void process_video (boost::shared_ptr<const Image> i, bool same);
 
        /** Call with some audio data */
-       void process_audio (boost::shared_ptr<const AudioBuffers>, Time);
+       void process_audio (boost::shared_ptr<const AudioBuffers>);
 
        /** Called when a processing run has finished */
        void process_end ();
index 68132c5abf94bea8aedd52196b8520712b0c1e8c..1135cc9a3add1f07e2416bba28a279748120de8c 100644 (file)
@@ -139,7 +139,7 @@ FFmpegContent::examine (shared_ptr<Job> job)
 
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
 
-       ContentVideoFrame video_length = 0;
+       VideoContent::Frame video_length = 0;
        video_length = examiner->video_length ();
        film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
 
@@ -214,12 +214,12 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
         signal_changed (FFmpegContentProperty::AUDIO_STREAM);
 }
 
-ContentAudioFrame
+AudioContent::Frame
 FFmpegContent::audio_length () const
 {
        int const cafr = content_audio_frame_rate ();
        int const vfr  = video_frame_rate ();
-       ContentVideoFrame const vl = video_length ();
+       VideoContent::Frame const vl = video_length ();
 
        boost::mutex::scoped_lock lm (_mutex);
         if (!_audio_stream) {
@@ -296,7 +296,7 @@ FFmpegAudioStream::FFmpegAudioStream (shared_ptr<const cxml::Node> node)
        frame_rate = node->number_child<int> ("FrameRate");
        channels = node->number_child<int64_t> ("Channels");
        mapping = AudioMapping (node->node_child ("Mapping"));
-       start = node->optional_number_child<Time> ("Start");
+       first_audio = node->optional_number_child<Time> ("FirstAudio");
 }
 
 void
@@ -306,8 +306,8 @@ FFmpegAudioStream::as_xml (xmlpp::Node* root) const
        root->add_child("Id")->add_child_text (lexical_cast<string> (id));
        root->add_child("FrameRate")->add_child_text (lexical_cast<string> (frame_rate));
        root->add_child("Channels")->add_child_text (lexical_cast<string> (channels));
-       if (start) {
-               root->add_child("Start")->add_child_text (lexical_cast<string> (start));
+       if (first_audio) {
+               root->add_child("FirstAudio")->add_child_text (lexical_cast<string> (first_audio));
        }
        mapping.as_xml (root->add_child("Mapping"));
 }
index 36c24c2b3353629614ada8999c0c1cb970b56674..c5ccee77a4509c55b87f374147a5333bdcd0685b 100644 (file)
@@ -46,7 +46,7 @@ public:
         int frame_rate;
        int channels;
        AudioMapping mapping;
-       boost::optional<Time> start;
+       boost::optional<double> first_audio;
 };
 
 extern bool operator== (FFmpegAudioStream const & a, FFmpegAudioStream const & b);
@@ -99,7 +99,7 @@ public:
 
         /* AudioContent */
         int audio_channels () const;
-        ContentAudioFrame audio_length () const;
+        AudioContent::Frame audio_length () const;
         int content_audio_frame_rate () const;
         int output_audio_frame_rate () const;
        AudioMapping audio_mapping () const;
@@ -134,6 +134,11 @@ public:
 
         void set_subtitle_stream (boost::shared_ptr<FFmpegSubtitleStream>);
         void set_audio_stream (boost::shared_ptr<FFmpegAudioStream>);
+
+       boost::optional<Time> first_video () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _first_video;
+       }
        
 private:
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
index f1d984ee15967aa1576426127769fc0bddb23da3..2d1792390dbdbb2e743124d5b6d0c8d87c832afd 100644 (file)
@@ -59,15 +59,37 @@ using libdcp::Size;
 
 FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
        : Decoder (f)
-       , VideoDecoder (f, c)
-       , AudioDecoder (f, c)
+       , VideoDecoder (f)
+       , AudioDecoder (f)
        , FFmpeg (c)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
        , _decode_video (video)
        , _decode_audio (audio)
+       , _pts_offset (0)
 {
        setup_subtitle ();
+
+       if (video && audio && c->audio_stream() && c->first_video() && c->audio_stream()->first_audio) {
+               _pts_offset = compute_pts_offset (c->first_video().get(), c->audio_stream()->first_audio.get(), c->video_frame_rate());
+       }
+}
+
+double
+FFmpegDecoder::compute_pts_offset (double first_video, double first_audio, float video_frame_rate)
+{
+       assert (first_video >= 0);
+       assert (first_audio >= 0);
+       
+       double const old_first_video = first_video;
+       
+       /* Round the first video to a frame boundary */
+       if (fabs (rint (first_video * video_frame_rate) - first_video * video_frame_rate) > 1e-6) {
+               first_video = ceil (first_video * video_frame_rate) / video_frame_rate;
+       }
+
+       /* Compute the required offset (also removing any common start delay) */
+       return first_video - old_first_video - min (first_video, first_audio);
 }
 
 FFmpegDecoder::~FFmpegDecoder ()
@@ -108,7 +130,8 @@ FFmpegDecoder::pass ()
                }
 
                /* Stop us being asked for any more data */
-               _next_video = _next_audio = _ffmpeg_content->length ();
+               _video_position = _ffmpeg_content->video_length ();
+               _audio_position = _ffmpeg_content->audio_length ();
                return;
        }
 
@@ -119,6 +142,7 @@ FFmpegDecoder::pass ()
        } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->id && _decode_audio) {
                decode_audio_packet ();
        } else if (_ffmpeg_content->subtitle_stream() && _packet.stream_index == _ffmpeg_content->subtitle_stream()->id) {
+#if 0          
 
                int got_subtitle;
                AVSubtitle sub;
@@ -138,6 +162,7 @@ FFmpegDecoder::pass ()
                        }
                        avsubtitle_free (&sub);
                }
+#endif         
        }
 
        av_free_packet (&_packet);
@@ -256,38 +281,25 @@ FFmpegDecoder::bytes_per_audio_sample () const
 }
 
 void
-FFmpegDecoder::seek (Time t)
+FFmpegDecoder::seek (VideoContent::Frame frame)
 {
-       do_seek (t, false, false);
-       VideoDecoder::seek (t);
+       do_seek (frame, false, false);
 }
 
 void
 FFmpegDecoder::seek_back ()
 {
-       if (position() < (2.5 * TIME_HZ / _ffmpeg_content->video_frame_rate())) {
-               return;
-       }
-       
-       do_seek (position() - 2.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
-       VideoDecoder::seek_back ();
-}
-
-void
-FFmpegDecoder::seek_forward ()
-{
-       if (position() >= (_ffmpeg_content->length() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate())) {
+       if (_video_position == 0) {
                return;
        }
        
-       do_seek (position() - 0.5 * TIME_HZ / _ffmpeg_content->video_frame_rate(), true, true);
-       VideoDecoder::seek_forward ();
+       do_seek (_video_position - 1, true, true);
 }
 
 void
-FFmpegDecoder::do_seek (Time t, bool backwards, bool accurate)
+FFmpegDecoder::do_seek (VideoContent::Frame frame, bool backwards, bool accurate)
 {
-       int64_t const vt = t / (av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ);
+       int64_t const vt = frame * _ffmpeg_content->video_frame_rate() / av_q2d (_format_context->streams[_video_stream]->time_base);
        av_seek_frame (_format_context, _video_stream, vt, backwards ? AVSEEK_FLAG_BACKWARD : 0);
 
        avcodec_flush_buffers (video_codec_context());
@@ -337,17 +349,33 @@ FFmpegDecoder::decode_audio_packet ()
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
                if (decode_result >= 0) {
                        if (frame_finished) {
-                       
-                               /* Where we are in the source, in seconds */
-                               double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                       * av_frame_get_best_effort_timestamp(_frame);
+
+                               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) - _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);
+                                       }
+                               }
+                                       
                                
                                int const data_size = av_samples_get_buffer_size (
                                        0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                        );
                                
                                assert (audio_codec_context()->channels == _ffmpeg_content->audio_channels());
-                               audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds * TIME_HZ);
+                               audio (deinterleave_audio (_frame->data, data_size), _audio_position);
                        }
                        
                        copy_packet.data += decode_result;
@@ -398,11 +426,33 @@ FFmpegDecoder::decode_video_packet ()
                
                int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
                if (bet != AV_NOPTS_VALUE) {
-                       /* XXX: may need to insert extra frames / remove frames here ...
-                          (as per old Matcher)
-                       */
-                       Time const t = bet * av_q2d (_format_context->streams[_video_stream]->time_base) * TIME_HZ;
-                       video (image, false, t);
+
+                       double const pts = bet * av_q2d (_format_context->streams[_video_stream]->time_base) - _pts_offset;
+                       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.
+                               */
+                               boost::shared_ptr<Image> black (
+                                       new SimpleImage (
+                                               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);
+                       }
                } else {
                        shared_ptr<const Film> film = _film.lock ();
                        assert (film);
@@ -413,27 +463,6 @@ FFmpegDecoder::decode_video_packet ()
        return true;
 }
 
-Time
-FFmpegDecoder::position () const
-{
-       if (_decode_video && _decode_audio && _ffmpeg_content->audio_stream()) {
-               return min (_next_video, _next_audio);
-       }
-
-       if (_decode_audio && _ffmpeg_content->audio_stream()) {
-               return _next_audio;
-       }
-
-       return _next_video;
-}
-
-bool
-FFmpegDecoder::done () const
-{
-       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || audio_done();
-       bool const vd = !_decode_video || video_done();
-       return ad && vd;
-}
        
 void
 FFmpegDecoder::setup_subtitle ()
@@ -455,3 +484,12 @@ FFmpegDecoder::setup_subtitle ()
                throw DecodeError (N_("could not open subtitle decoder"));
        }
 }
+
+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;
+}
+       
index 331d9be70ffb5cfb55e247583c4055ef65bb2268..8f0482aad8331dcd12685e1d4724e08469b20eb8 100644 (file)
@@ -38,6 +38,7 @@ extern "C" {
 #include "ffmpeg.h"
 
 class Film;
+class ffmpeg_pts_offset_test;
 
 /** @class FFmpegDecoder
  *  @brief A decoder using FFmpeg to decode content.
@@ -49,23 +50,24 @@ public:
        ~FFmpegDecoder ();
 
        void pass ();
-       void seek (Time);
+       void seek (VideoContent::Frame);
        void seek_back ();
-       void seek_forward ();
-       Time position () const;
        bool done () const;
 
 private:
+       friend class ::ffmpeg_pts_offset_test;
 
        /* No copy construction */
        FFmpegDecoder (FFmpegDecoder const &);
        FFmpegDecoder& operator= (FFmpegDecoder const &);
 
+       static double compute_pts_offset (double, double, float);
+
        void setup_subtitle ();
 
        AVSampleFormat audio_sample_format () const;
        int bytes_per_audio_sample () const;
-       void do_seek (Time, bool, bool);
+       void do_seek (VideoContent::Frame, bool, bool);
 
        bool decode_video_packet ();
        void decode_audio_packet ();
@@ -81,4 +83,6 @@ private:
 
        bool _decode_video;
        bool _decode_audio;
+
+       double _pts_offset;
 };
index c09395e76609086f33579b63b18c008cf4cc3744..f45b0fe52e6dad125de259ca773d2f2c45eb8689 100644 (file)
@@ -79,9 +79,9 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                        }
                } else {
                        for (size_t i = 0; i < _audio_streams.size(); ++i) {
-                               if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->start) {
+                               if (_packet.stream_index == _audio_streams[i]->id && !_audio_streams[i]->first_audio) {
                                        if (avcodec_decode_audio4 (context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                                               _audio_streams[i]->start = frame_time (_audio_streams[i]->id);
+                                               _audio_streams[i]->first_audio = frame_time (_audio_streams[i]->id);
                                        }
                                }
                        }
@@ -90,7 +90,7 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
                bool have_all_audio = true;
                size_t i = 0;
                while (i < _audio_streams.size() && have_all_audio) {
-                       have_all_audio = _audio_streams[i]->start;
+                       have_all_audio = _audio_streams[i]->first_audio;
                        ++i;
                }
 
@@ -102,14 +102,14 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c)
        }
 }
 
-optional<Time>
+optional<double>
 FFmpegExaminer::frame_time (int stream) const
 {
-       optional<Time> t;
+       optional<double> t;
        
        int64_t const bet = av_frame_get_best_effort_timestamp (_frame);
        if (bet != AV_NOPTS_VALUE) {
-               t = bet * av_q2d (_format_context->streams[stream]->time_base) * TIME_HZ;
+               t = bet * av_q2d (_format_context->streams[stream]->time_base);
        }
 
        return t;
@@ -134,7 +134,7 @@ FFmpegExaminer::video_size () const
 }
 
 /** @return Length (in video frames) according to our content's header */
-ContentVideoFrame
+VideoContent::Frame
 FFmpegExaminer::video_length () const
 {
        return (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
index 57b7775d4b84942c8aa065e891e8da230dcd5179..ec84865ed7cce45fdcc40999e5bb4f197fb55ce2 100644 (file)
@@ -31,7 +31,7 @@ public:
        
        float video_frame_rate () const;
        libdcp::Size video_size () const;
-       ContentVideoFrame video_length () const;
+       VideoContent::Frame video_length () const;
 
        std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
                return _subtitle_streams;
@@ -41,15 +41,15 @@ public:
                return _audio_streams;
        }
 
-       boost::optional<Time> first_video () const {
+       boost::optional<double> first_video () const {
                return _first_video;
        }
        
 private:
        std::string stream_name (AVStream* s) const;
-       boost::optional<Time> frame_time (int) const;
+       boost::optional<double> frame_time (int) const;
        
         std::vector<boost::shared_ptr<FFmpegSubtitleStream> > _subtitle_streams;
         std::vector<boost::shared_ptr<FFmpegAudioStream> > _audio_streams;
-       boost::optional<Time> _first_video;
+       boost::optional<double> _first_video;
 };
index 0bff1a7ccfca18d9912b33e44dffa28e532c5f3c..722ff5d3ceff24509edeb49bb21ca9c575148abb 100644 (file)
@@ -43,8 +43,9 @@ extern "C" {
 
 #include "i18n.h"
 
-using namespace std;
-using namespace boost;
+using std::string;
+using std::min;
+using boost::shared_ptr;
 using libdcp::Size;
 
 void
index f9ee8cc84029d0cceb01ce845d272ceda8a62484..2fd65ffa0d403d607fd7b2a835d9b71710516746 100644 (file)
@@ -87,7 +87,7 @@ ImageMagickContent::clone () const
 }
 
 void
-ImageMagickContent::set_video_length (ContentVideoFrame len)
+ImageMagickContent::set_video_length (VideoContent::Frame len)
 {
        {
                boost::mutex::scoped_lock lm (_mutex);
index d7673d870730ec3dd411e3b875a386f454652c6f..04425af080111aebb54cb0a81bb6b8a906d63ddc 100644 (file)
@@ -43,7 +43,7 @@ public:
        boost::shared_ptr<Content> clone () const;
        Time length () const;
 
-       void set_video_length (ContentVideoFrame);
+       void set_video_length (VideoContent::Frame);
 
        static bool valid_file (boost::filesystem::path);
 };
index c9123c77c288404e31783f41d6c26e51895a2674..04d3d9df7c47d166c52e7692b3977d550993b547 100644 (file)
@@ -34,7 +34,7 @@ using libdcp::Size;
 
 ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<const ImageMagickContent> c)
        : Decoder (f)
-       , VideoDecoder (f, c)
+       , VideoDecoder (f)
        , ImageMagick (c)
 {
 
@@ -43,12 +43,12 @@ ImageMagickDecoder::ImageMagickDecoder (shared_ptr<const Film> f, shared_ptr<con
 void
 ImageMagickDecoder::pass ()
 {
-       if (_next_video >= _imagemagick_content->length ()) {
+       if (_video_position >= _imagemagick_content->video_length ()) {
                return;
        }
 
        if (_image) {
-               video (_image, true, _next_video);
+               video (_image, true, _video_position);
                return;
        }
 
@@ -71,48 +71,25 @@ ImageMagickDecoder::pass ()
 
        delete magick_image;
 
-       _image = _image->crop (_imagemagick_content->crop(), true);
-       video (_image, false, _next_video);
+       video (_image, false, _video_position);
 }
 
 void
-ImageMagickDecoder::seek (Time t)
+ImageMagickDecoder::seek (VideoContent::Frame frame)
 {
-       _next_video = t;
+       _video_position = frame;
 }
 
 void
 ImageMagickDecoder::seek_back ()
 {
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
-       }
-       
-       _next_video -= f->video_frames_to_time (2);
-}
-
-void
-ImageMagickDecoder::seek_forward ()
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
+       if (_video_position > 0) {
+               _video_position--;
        }
-       
-       _next_video += f->video_frames_to_time (1);
 }
 
-Time
-ImageMagickDecoder::position () const
-{
-       return _next_video;
-}
-
-
 bool
 ImageMagickDecoder::done () const
 {
-       return video_done ();
+       return _video_position >= _imagemagick_content->video_length ();
 }
-       
index e169f9bc384a9a3b79a59657ab356c1e8b94438e..286f473378396f9bbd44f39f28f08c7eeefaf0dd 100644 (file)
@@ -34,10 +34,8 @@ public:
        /* Decoder */
 
        void pass ();
-       void seek (Time);
+       void seek (VideoContent::Frame);
        void seek_back ();
-       void seek_forward ();
-       Time position () const;
        bool done () const;
 
 private:
index 827dad67e5c9dd85bbdfc92c8b8de68d46c7093a..801ede44248348ec26f6287542c54f66a672d96d 100644 (file)
@@ -33,7 +33,7 @@ public:
 
        float video_frame_rate () const;
        libdcp::Size video_size () const;
-       ContentVideoFrame video_length () const;
+       VideoContent::Frame video_length () const;
 
 private:
        boost::weak_ptr<const Film> _film;
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc
deleted file mode 100644 (file)
index 4acb82a..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
-    Copyright (C) 2012 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 "matcher.h"
-#include "image.h"
-#include "log.h"
-
-#include "i18n.h"
-
-using std::min;
-using std::cout;
-using std::list;
-using boost::shared_ptr;
-
-Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second)
-       : Processor (log)
-       , _sample_rate (sample_rate)
-       , _frames_per_second (frames_per_second)
-       , _video_frames (0)
-       , _audio_frames (0)
-       , _had_first_video (false)
-       , _had_first_audio (false)
-{
-
-}
-
-void
-Matcher::process_video (boost::shared_ptr<const Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t)
-{
-       _pixel_format = image->pixel_format ();
-       _size = image->size ();
-
-       _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size()));
-
-       if (!_first_input || t < _first_input.get()) {
-               _first_input = t;
-       }
-
-       bool const this_is_first_video = !_had_first_video;
-       _had_first_video = true;
-
-       if (!_had_first_audio) {
-               /* No audio yet; we must postpone these data until we have some */
-               _pending_video.push_back (VideoRecord (image, same, sub, t));
-       } else if (this_is_first_video && _had_first_audio) {
-               /* First video since we got audio */
-               _pending_video.push_back (VideoRecord (image, same, sub, t));
-               fix_start ();
-       } else {
-               /* Normal running */
-
-               /* Difference between where this video is and where it should be */
-               double const delta = t - _first_input.get() - _video_frames / _frames_per_second;
-               double const one_frame = 1 / _frames_per_second;
-               
-               if (delta > one_frame) {
-                       /* Insert frames to make up the difference */
-                       int const extra = rint (delta / one_frame);
-                       for (int i = 0; i < extra; ++i) {
-                               repeat_last_video ();
-                               _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second));
-                       }
-               }
-               
-               if (delta > -one_frame) {
-                       Video (image, same, sub);
-                       ++_video_frames;
-               } else {
-                       /* We are omitting a frame to keep things right */
-                       _log->log (String::compose ("Frame removed at %1s; delta %2; first input was at %3", t, delta, _first_input.get()));
-               }
-               
-               _last_image = image;
-               _last_subtitle = sub;
-       }
-}
-
-void
-Matcher::process_audio (boost::shared_ptr<const AudioBuffers> b, double t)
-{
-       _channels = b->channels ();
-
-       _log->log (String::compose (
-                          "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]",
-                          b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size()
-                          )
-               );
-
-       if (!_first_input || t < _first_input.get()) {
-               _first_input = t;
-       }
-
-       bool const this_is_first_audio = !_had_first_audio;
-       _had_first_audio = true;
-       
-       if (!_had_first_video) {
-               /* No video yet; we must postpone these data until we have some */
-               _pending_audio.push_back (AudioRecord (b, t));
-       } else if (this_is_first_audio && _had_first_video) {
-               /* First audio since we got video */
-               _pending_audio.push_back (AudioRecord (b, t));
-               fix_start ();
-       } else {
-               /* Normal running.  We assume audio time stamps are consecutive, so there's no equivalent of
-                  the checking / insertion of repeat frames that there is for video.
-               */
-               Audio (b);
-               _audio_frames += b->frames ();
-       }
-}
-
-void
-Matcher::process_end ()
-{
-       if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) {
-               /* We won't do anything */
-               return;
-       }
-
-       _log->log (String::compose ("Matcher has seen %1 video frames (which equals %2 audio frames) and %3 audio frames",
-                                   _video_frames, video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), _audio_frames));
-       
-       match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second));
-}
-
-void
-Matcher::fix_start ()
-{
-       assert (!_pending_video.empty ());
-       assert (!_pending_audio.empty ());
-       
-       _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time));
-
-       match (_pending_video.front().time - _pending_audio.front().time);
-
-       for (list<VideoRecord>::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) {
-               process_video (i->image, i->same, i->subtitle, i->time);
-       }
-
-       _pending_video.clear ();
-
-       for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
-               process_audio (i->audio, i->time);
-       }
-       
-       _pending_audio.clear ();
-}
-
-void
-Matcher::match (double extra_video_needed)
-{
-       _log->log (String::compose ("Match %1", extra_video_needed));
-       
-       if (extra_video_needed > 0) {
-
-               /* Emit black video frames */
-               
-               int const black_video_frames = ceil (extra_video_needed * _frames_per_second);
-
-               _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames));
-
-               shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true));
-               black->make_black ();
-               for (int i = 0; i < black_video_frames; ++i) {
-                       Video (black, i != 0, shared_ptr<Subtitle>());
-                       ++_video_frames;
-               }
-
-               extra_video_needed -= black_video_frames / _frames_per_second;
-       }
-
-       if (extra_video_needed < 0) {
-               
-               /* Emit silence */
-               
-               int64_t to_do = -extra_video_needed * _sample_rate;
-               _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do));
-
-               /* Do things in half second blocks as I think there may be limits
-                  to what FFmpeg (and in particular the resampler) can cope with.
-               */
-               int64_t const block = _sample_rate / 2;
-               shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block));
-               b->make_silent ();
-               
-               while (to_do > 0) {
-                       int64_t const this_time = min (to_do, block);
-                       b->set_frames (this_time);
-                       Audio (b);
-                       _audio_frames += b->frames ();
-                       to_do -= this_time;
-               }
-       }
-}
-
-void
-Matcher::repeat_last_video ()
-{
-       if (!_last_image) {
-               shared_ptr<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true));
-               im->make_black ();
-               _last_image = im;
-       }
-
-       Video (_last_image, true, _last_subtitle);
-       ++_video_frames;
-}
-
diff --git a/src/lib/matcher.h b/src/lib/matcher.h
deleted file mode 100644 (file)
index 61fd814..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-    Copyright (C) 2012 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/optional.hpp>
-#include "processor.h"
-
-class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource 
-{
-public:
-       Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second);
-       void process_video (boost::shared_ptr<const Image> i, bool, boost::shared_ptr<Subtitle> s, double);
-       void process_audio (boost::shared_ptr<const AudioBuffers>, double);
-       void process_end ();
-
-private:
-       void fix_start ();
-       void match (double);
-       void repeat_last_video ();
-       
-       int _sample_rate;
-       float _frames_per_second;
-       int _video_frames;
-       int64_t _audio_frames;
-       boost::optional<AVPixelFormat> _pixel_format;
-       boost::optional<libdcp::Size> _size;
-       boost::optional<int> _channels;
-
-       struct VideoRecord {
-               VideoRecord (boost::shared_ptr<const Image> i, bool s, boost::shared_ptr<Subtitle> u, double t)
-                       : image (i)
-                       , same (s)
-                       , subtitle (u)
-                       , time (t)
-               {}
-
-               boost::shared_ptr<const Image> image;
-               bool same;
-               boost::shared_ptr<Subtitle> subtitle;
-               double time;
-       };
-
-       struct AudioRecord {
-               AudioRecord (boost::shared_ptr<const AudioBuffers> a, double t)
-                       : audio (a)
-                       , time (t)
-               {}
-               
-               boost::shared_ptr<const AudioBuffers> audio;
-               double time;
-       };
-
-       std::list<VideoRecord> _pending_video;
-       std::list<AudioRecord> _pending_audio;
-
-       boost::optional<double> _first_input;
-       boost::shared_ptr<const Image> _last_image;
-       boost::shared_ptr<Subtitle> _last_subtitle;
-
-       bool _had_first_video;
-       bool _had_first_audio;
-};
diff --git a/src/lib/null_content.cc b/src/lib/null_content.cc
deleted file mode 100644 (file)
index 3bbda92..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-    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 "null_content.h"
-#include "film.h"
-
-using boost::shared_ptr;
-
-NullContent::NullContent (shared_ptr<const Film> f, Time s, Time len)
-       : Content (f, s)
-       , VideoContent (f, s, f->time_to_video_frames (len))
-       , AudioContent (f, s)
-       , _audio_length (f->time_to_audio_frames (len))
-       , _length (len)
-{
-
-}
-
-int
-NullContent::content_audio_frame_rate () const
-{
-       return output_audio_frame_rate ();
-}
-       
-
-int
-NullContent::output_audio_frame_rate () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       return film->dcp_audio_frame_rate ();
-}
-
-int
-NullContent::audio_channels () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       return film->dcp_audio_channels ();
-}
-       
diff --git a/src/lib/null_content.h b/src/lib/null_content.h
deleted file mode 100644 (file)
index 889ff7a..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-    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 <string>
-#include <boost/shared_ptr.hpp>
-#include "video_content.h"
-#include "audio_content.h"
-
-class NullContent : public VideoContent, public AudioContent
-{
-public:
-       NullContent (boost::shared_ptr<const Film>, Time, Time);
-
-       std::string summary () const {
-               return "";
-       }
-       
-       std::string information () const {
-               return "";
-       }
-
-       void as_xml (xmlpp::Node *) const {}
-       
-       boost::shared_ptr<Content> clone () const {
-               return boost::shared_ptr<Content> ();
-       }
-
-        int audio_channels () const;
-       
-        ContentAudioFrame audio_length () const {
-               return _audio_length;
-       }
-       
-        int content_audio_frame_rate () const;
-       
-       int output_audio_frame_rate () const;
-       
-       AudioMapping audio_mapping () const {
-               return AudioMapping ();
-       }
-
-       void set_audio_mapping (AudioMapping) {}
-       
-       Time length () const {
-               return _length;
-       }
-
-private:
-       ContentAudioFrame _audio_length;
-       Time _length;
-};
index cd1c54d5bd466e78834d36d87c10476e180e6410..9969fbf9e163ab1d9428ffdace1a0911f435e805 100644 (file)
 #include "playlist.h"
 #include "job.h"
 #include "image.h"
-#include "null_content.h"
-#include "black_decoder.h"
-#include "silence_decoder.h"
+#include "ratio.h"
+#include "resampler.h"
 
 using std::list;
 using std::cout;
 using std::min;
 using std::max;
 using std::vector;
+using std::pair;
+using std::map;
 using boost::shared_ptr;
 using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
 #define DEBUG_PLAYER 1
 
-struct Piece
+class Piece
 {
+public:
+       Piece (shared_ptr<Content> c)
+               : content (c)
+               , video_position (c->start ())
+               , audio_position (c->start ())
+       {}
+       
        Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
                : content (c)
                , decoder (d)
+               , video_position (c->start ())
+               , audio_position (c->start ())
        {}
        
        shared_ptr<Content> content;
        shared_ptr<Decoder> decoder;
+       Time video_position;
+       Time audio_position;
 };
-       
 
 #ifdef DEBUG_PLAYER
 std::ostream& operator<<(std::ostream& s, Piece const & p)
 {
-       if (dynamic_pointer_cast<NullContent> (p.content)) {
-               if (dynamic_pointer_cast<SilenceDecoder> (p.decoder)) {
-                       s << "\tsilence    ";
-               } else {
-                       s << "\tblack      ";
-               }
-       } else if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
+       if (dynamic_pointer_cast<FFmpegContent> (p.content)) {
                s << "\tffmpeg     ";
        } else if (dynamic_pointer_cast<ImageMagickContent> (p.content)) {
                s << "\timagemagick";
@@ -85,12 +90,13 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
        , _video (true)
        , _audio (true)
        , _have_valid_pieces (false)
-       , _position (0)
+       , _video_position (0)
+       , _audio_position (0)
        , _audio_buffers (f->dcp_audio_channels(), 0)
-       , _next_audio (0)
 {
        _playlist->Changed.connect (bind (&Player::playlist_changed, this));
        _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2));
+       set_video_container_size (_film->container()->size (_film->full_frame ()));
 }
 
 void
@@ -113,77 +119,186 @@ Player::pass ()
                _have_valid_pieces = true;
        }
 
-        /* Here we are just finding the active decoder with the earliest last emission time, then
-           calling pass on it.
-        */
+#ifdef DEBUG_PLAYER
+       cout << "= PASS\n";
+#endif 
 
         Time earliest_t = TIME_MAX;
         shared_ptr<Piece> earliest;
+       enum {
+               VIDEO,
+               AUDIO
+       } type = VIDEO;
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
                if ((*i)->decoder->done ()) {
                        continue;
                }
 
-               if (!_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder) && !dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
-                       continue;
+               if (dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
+                       if ((*i)->video_position < earliest_t) {
+                               earliest_t = (*i)->video_position;
+                               earliest = *i;
+                               type = VIDEO;
+                       }
                }
-               
-               Time const t = (*i)->content->start() + (*i)->decoder->position();
-               if (t < earliest_t) {
-                       earliest_t = t;
-                       earliest = *i;
+
+               if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
+                       if ((*i)->audio_position < earliest_t) {
+                               earliest_t = (*i)->audio_position;
+                               earliest = *i;
+                               type = AUDIO;
+                       }
                }
        }
 
        if (!earliest) {
+#ifdef DEBUG_PLAYER
+               cout << "no earliest piece.\n";
+#endif         
+               
                flush ();
                return true;
        }
 
-       earliest->decoder->pass ();
-       _position = earliest->content->start() + earliest->decoder->position ();
+       switch (type) {
+       case VIDEO:
+               if (earliest_t > _video_position) {
+#ifdef DEBUG_PLAYER
+                       cout << "no video here; emitting black frame.\n";
+#endif
+                       emit_black ();
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass " << *earliest << "\n";
+#endif                 
+                       earliest->decoder->pass ();
+               }
+               break;
+
+       case AUDIO:
+               if (earliest_t > _audio_position) {
+#ifdef DEBUG_PLAYER
+                       cout << "no audio here; emitting silence.\n";
+#endif
+                       emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+               } else {
+#ifdef DEBUG_PLAYER
+                       cout << "Pass " << *earliest << "\n";
+#endif                 
+                       earliest->decoder->pass ();
+               }
+               break;
+       }
+
+#ifdef DEBUG_PLAYER
+       cout << "\tpost pass " << _video_position << " " << _audio_position << "\n";
+#endif 
 
         return false;
 }
 
 void
-Player::process_video (weak_ptr<Content> weak_content, shared_ptr<const Image> image, bool same, Time time)
+Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
 {
-       shared_ptr<Content> content = weak_content.lock ();
-       if (!content) {
+       shared_ptr<Piece> piece = weak_piece.lock ();
+       if (!piece) {
                return;
        }
+
+       shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
+       assert (content);
+
+       FrameRateConversion frc (content->video_frame_rate(), _film->dcp_video_frame_rate());
+       if (frc.skip && (frame % 2) == 1) {
+               return;
+       }
+
+       image = image->crop (content->crop(), true);
+
+       libdcp::Size const image_size = content->ratio()->size (_video_container_size);
        
-       time += content->start ();
+       image = image->scale_and_convert_to_rgb (image_size, _film->scaler(), true);
+
+#if 0  
+       if (film->with_subtitles ()) {
+               shared_ptr<Subtitle> sub;
+               if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
+                       sub = _timed_subtitle->subtitle ();
+               }
+               
+               if (sub) {
+                       dcpomatic::Rect const tx = subtitle_transformed_area (
+                               float (image_size.width) / content->video_size().width,
+                               float (image_size.height) / content->video_size().height,
+                               sub->area(), film->subtitle_offset(), film->subtitle_scale()
+                               );
+                       
+                       shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
+                       image->alpha_blend (im, tx.position());
+               }
+       }
+#endif 
+
+       if (image_size != _video_container_size) {
+               assert (image_size.width <= _video_container_size.width);
+               assert (image_size.height <= _video_container_size.height);
+               shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+               im->make_black ();
+               im->copy (image, Position ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
+               image = im;
+       }
+
+       Time time = content->start() + (frame * frc.factor() * TIME_HZ / _film->dcp_video_frame_rate());
        
         Video (image, same, time);
+       time += TIME_HZ / _film->dcp_video_frame_rate();
+
+       if (frc.repeat) {
+               Video (image, true, time);
+               time += TIME_HZ / _film->dcp_video_frame_rate();
+       }
+
+       _video_position = piece->video_position = time;
 }
 
 void
-Player::process_audio (weak_ptr<Content> weak_content, shared_ptr<const AudioBuffers> audio, Time time)
+Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
 {
-       shared_ptr<Content> content = weak_content.lock ();
-       if (!content) {
+       shared_ptr<Piece> piece = weak_piece.lock ();
+       if (!piece) {
                return;
        }
-       
+
+       shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
+       assert (content);
+
+       if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
+               audio = resampler(content)->run (audio);
+       }
+
+       /* Remap channels */
+       shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->dcp_audio_channels(), audio->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) {
+               dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+       }
+
         /* The time of this audio may indicate that some of our buffered audio is not going to
            be added to any more, so it can be emitted.
         */
 
-       time += content->start ();
+       Time const time = content->start() + (frame * TIME_HZ / _film->dcp_audio_frame_rate());
 
-       cout << "Player gets " << audio->frames() << " @ " << time << " cf " << _next_audio << "\n";
-
-        if (time > _next_audio) {
+        if (time > _audio_position) {
                 /* We can emit some audio from our buffers */
-                OutputAudioFrame const N = _film->time_to_audio_frames (time - _next_audio);
+                OutputAudioFrame const N = _film->time_to_audio_frames (time - _audio_position);
                assert (N <= _audio_buffers.frames());
                 shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), N));
                 emit->copy_from (&_audio_buffers, N, 0, 0);
-                Audio (emit, _next_audio);
-                _next_audio += _film->audio_frames_to_time (N);
+                Audio (emit, _audio_position);
+                _audio_position = piece->audio_position = time + _film->audio_frames_to_time (N);
 
                 /* And remove it from our buffers */
                 if (_audio_buffers.frames() > N) {
@@ -204,10 +319,19 @@ Player::flush ()
        if (_audio_buffers.frames() > 0) {
                shared_ptr<AudioBuffers> emit (new AudioBuffers (_audio_buffers.channels(), _audio_buffers.frames()));
                emit->copy_from (&_audio_buffers, _audio_buffers.frames(), 0, 0);
-               Audio (emit, _next_audio);
-               _next_audio += _film->audio_frames_to_time (_audio_buffers.frames ());
+               Audio (emit, _audio_position);
+               _audio_position += _film->audio_frames_to_time (_audio_buffers.frames ());
                _audio_buffers.set_frames (0);
        }
+
+       while (_video_position < _audio_position) {
+               emit_black ();
+       }
+
+       while (_audio_position < _video_position) {
+               emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+       }
+       
 }
 
 /** @return true on error */
@@ -224,10 +348,18 @@ Player::seek (Time t)
        }
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               Time s = t - (*i)->content->start ();
+               shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
+               if (!vc) {
+                       continue;
+               }
+               
+               Time s = t - vc->start ();
                s = max (static_cast<Time> (0), s);
-               s = min ((*i)->content->length(), s);
-               (*i)->decoder->seek (s);
+               s = min (vc->length(), s);
+
+               FrameRateConversion frc (vc->video_frame_rate(), _film->dcp_video_frame_rate());
+               VideoContent::Frame f = s * _film->dcp_video_frame_rate() / (frc.factor() * TIME_HZ);
+               dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (f);
        }
 
        /* XXX: don't seek audio because we don't need to... */
@@ -240,32 +372,6 @@ Player::seek_back ()
 
 }
 
-void
-Player::seek_forward ()
-{
-
-}
-
-void
-Player::add_black_piece (Time s, Time len)
-{
-       shared_ptr<NullContent> nc (new NullContent (_film, s, len));
-       nc->set_ratio (_film->container ());
-       shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
-       bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3));
-       _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
-}
-
-void
-Player::add_silent_piece (Time s, Time len)
-{
-       shared_ptr<NullContent> nc (new NullContent (_film, s, len));
-       shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
-       sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
-       _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
-}
-
-
 void
 Player::setup_pieces ()
 {
@@ -278,21 +384,18 @@ Player::setup_pieces ()
        
        for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 
-               shared_ptr<Decoder> decoder;
-               
+               shared_ptr<Piece> piece (new Piece (*i));
+
                 /* XXX: into content? */
 
                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, *i, _1, _2, _3));
-                       fd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
-                       if (_video_container_size) {
-                               fd->set_video_container_size (_video_container_size.get ());
-                       }
+                       fd->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
+                       fd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
 
-                       decoder = fd;
+                       piece->decoder = fd;
                }
                
                shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
@@ -309,54 +412,21 @@ Player::setup_pieces ()
 
                        if (!id) {
                                id.reset (new ImageMagickDecoder (_film, ic));
-                               id->Video.connect (bind (&Player::process_video, this, *i, _1, _2, _3));
-                               if (_video_container_size) {
-                                       id->set_video_container_size (_video_container_size.get ());
-                               }
+                               id->Video.connect (bind (&Player::process_video, this, piece, _1, _2, _3));
                        }
 
-                       decoder = id;
+                       piece->decoder = id;
                }
 
                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, *i, _1, _2));
+                       sd->Audio.connect (bind (&Player::process_audio, this, piece, _1, _2));
 
-                       decoder = sd;
+                       piece->decoder = sd;
                }
 
-               _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder)));
-       }
-
-       /* Fill in visual gaps with black and audio gaps with silence */
-
-       Time video_pos = 0;
-       Time audio_pos = 0;
-       list<shared_ptr<Piece> > pieces_copy = _pieces;
-       for (list<shared_ptr<Piece> >::iterator i = pieces_copy.begin(); i != pieces_copy.end(); ++i) {
-               if (dynamic_pointer_cast<VideoContent> ((*i)->content)) {
-                       Time const diff = (*i)->content->start() - video_pos;
-                       if (diff > 0) {
-                               add_black_piece (video_pos, diff);
-                       }
-                       video_pos = (*i)->content->end();
-               }
-
-               shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> ((*i)->content);
-               if (ac && ac->audio_channels()) {
-                       Time const diff = (*i)->content->start() - audio_pos;
-                       if (diff > 0) {
-                               add_silent_piece (video_pos, diff);
-                       }
-                       audio_pos = (*i)->content->end();
-               }
-       }
-
-       if (video_pos < audio_pos) {
-               add_black_piece (video_pos, audio_pos - video_pos);
-       } else if (audio_pos < video_pos) {
-               add_silent_piece (audio_pos, video_pos - audio_pos);
+               _pieces.push_back (piece);
        }
 
 #ifdef DEBUG_PLAYER
@@ -390,10 +460,40 @@ void
 Player::set_video_container_size (libdcp::Size s)
 {
        _video_container_size = s;
-       for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
-               if (vd) {
-                       vd->set_video_container_size (s);
-               }
+       _black_frame.reset (new SimpleImage (PIX_FMT_RGB24, _video_container_size, true));
+       _black_frame->make_black ();
+}
+
+shared_ptr<Resampler>
+Player::resampler (shared_ptr<AudioContent> c)
+{
+       map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
+       if (i != _resamplers.end ()) {
+               return i->second;
        }
+       
+       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 ()
+{
+       /* XXX: use same here */
+       Video (_black_frame, false, _video_position);
+       _video_position += _film->video_frames_to_time (1);
+}
+
+void
+Player::emit_silence (OutputAudioFrame most)
+{
+       OutputAudioFrame N = min (most, _film->dcp_audio_frame_rate() / 2);
+       shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->dcp_audio_channels(), N));
+       silence->make_silent ();
+       Audio (silence, _audio_position);
+       _audio_position += _film->audio_frames_to_time (N);
+}
+
+       
+       
index e4fb832209025117a3f5c02414a3d429bf62bc5a..bbdb14ec240ace9494dc1f973869c65a81c91a85 100644 (file)
 #include <list>
 #include <boost/shared_ptr.hpp>
 #include <boost/enable_shared_from_this.hpp>
-#include "video_source.h"
-#include "audio_source.h"
-#include "video_sink.h"
-#include "audio_sink.h"
 #include "playlist.h"
 #include "audio_buffers.h"
+#include "content.h"
 
 class Job;
 class Film;
 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.
  */
  
-class Player : public VideoSource, public AudioSource, public boost::enable_shared_from_this<Player>
+class Player : public boost::enable_shared_from_this<Player>
 {
 public:
        Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>);
@@ -51,26 +50,35 @@ public:
        bool pass ();
        void seek (Time);
        void seek_back ();
-       void seek_forward ();
 
-       /** @return position that we are at; ie the time of the next thing we will emit on pass() */
-       Time position () const {
-               return _position;
+       Time video_position () const {
+               return _video_position;
        }
 
        void set_video_container_size (libdcp::Size);
 
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is true if the image is the same as the last one that was emitted.
+        *  Third parameter is the time.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
+       
+       /** Emitted when some audio data is ready */
+       boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+
 private:
 
-       void process_video (boost::weak_ptr<Content>, boost::shared_ptr<const Image>, bool, Time);
-       void process_audio (boost::weak_ptr<Content>, boost::shared_ptr<const AudioBuffers>, Time);
+       void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
        void setup_pieces ();
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int);
        void do_seek (Time, bool);
-       void add_black_piece (Time, Time);
-       void add_silent_piece (Time, Time);
        void flush ();
+       void emit_black ();
+       void emit_silence (OutputAudioFrame);
+       boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -81,10 +89,17 @@ private:
        /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
        bool _have_valid_pieces;
        std::list<boost::shared_ptr<Piece> > _pieces;
-       Time _position;
+
+       /** The time after the last video that we emitted */
+       Time _video_position;
+       /** The time after the last audio that we emitted */
+       Time _audio_position;
+
        AudioBuffers _audio_buffers;
-       Time _next_audio;
-       boost::optional<libdcp::Size> _video_container_size;
+
+       libdcp::Size _video_container_size;
+       boost::shared_ptr<Image> _black_frame;
+       std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
 };
 
 #endif
index e4f4c79f00ee5c8fcaa46e0a7ad17ca014105e81..2d243fe8f0e84060b0ffa20b93a96d08fbf7af31 100644 (file)
 #include <list>
 #include <boost/shared_ptr.hpp>
 #include <boost/enable_shared_from_this.hpp>
-#include "video_source.h"
-#include "audio_source.h"
-#include "video_sink.h"
-#include "audio_sink.h"
 #include "ffmpeg_content.h"
 #include "audio_mapping.h"
 
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
new file mode 100644 (file)
index 0000000..1235b90
--- /dev/null
@@ -0,0 +1,61 @@
+extern "C" {
+#include "libavutil/channel_layout.h"
+}      
+#include "resampler.h"
+#include "audio_buffers.h"
+#include "exceptions.h"
+
+#include "i18n.h"
+
+using boost::shared_ptr;
+
+Resampler::Resampler (int in, int out, int channels)
+       : _in_rate (in)
+       , _out_rate (out)
+       , _channels (channels)
+{
+       /* We will be using planar float data when we call the
+          resampler.  As far as I can see, the audio channel
+          layout is not necessary for our purposes; it seems
+          only to be used get the number of channels and
+          decide if rematrixing is needed.  It won't be, since
+          input and output layouts are the same.
+       */
+
+       _swr_context = swr_alloc_set_opts (
+               0,
+               av_get_default_channel_layout (_channels),
+               AV_SAMPLE_FMT_FLTP,
+               _out_rate,
+               av_get_default_channel_layout (_channels),
+               AV_SAMPLE_FMT_FLTP,
+               _in_rate,
+               0, 0
+               );
+       
+       swr_init (_swr_context);
+}
+
+Resampler::~Resampler ()
+{
+       swr_free (&_swr_context);
+}
+
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
+{
+       /* 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));
+
+       int const resampled_frames = swr_convert (
+               _swr_context, (uint8_t **) resampled->data(), max_resampled_frames, (uint8_t const **) in->data(), in->frames()
+               );
+       
+       if (resampled_frames < 0) {
+               throw EncodeError (_("could not run sample-rate converter"));
+       }
+       
+       resampled->set_frames (resampled_frames);
+       return resampled;
+}      
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
new file mode 100644 (file)
index 0000000..cda7189
--- /dev/null
@@ -0,0 +1,21 @@
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <libswresample/swresample.h>
+}
+
+class AudioBuffers;
+
+class Resampler
+{
+public:
+       Resampler (int, int, int);
+       ~Resampler ();
+
+       boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
+
+private:       
+       SwrContext* _swr_context;
+       int _in_rate;
+       int _out_rate;
+       int _channels;
+};
diff --git a/src/lib/silence_decoder.cc b/src/lib/silence_decoder.cc
deleted file mode 100644 (file)
index 0380117..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-    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 "silence_decoder.h"
-#include "null_content.h"
-#include "audio_buffers.h"
-
-using std::min;
-using std::cout;
-using boost::shared_ptr;
-
-SilenceDecoder::SilenceDecoder (shared_ptr<const Film> f, shared_ptr<NullContent> c)
-       : Decoder (f)
-       , AudioDecoder (f, c)
-{
-       
-}
-
-void
-SilenceDecoder::pass ()
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
-       Time const this_time = min (_audio_content->length() - _next_audio, TIME_HZ / 2);
-       cout << "silence emit " << this_time << " from " << _audio_content->length() << "\n";
-       shared_ptr<AudioBuffers> data (new AudioBuffers (film->dcp_audio_channels(), film->time_to_audio_frames (this_time)));
-       data->make_silent ();
-       audio (data, _next_audio);
-}
-
-void
-SilenceDecoder::seek (Time t)
-{
-       _next_audio = t;
-}
-
-void
-SilenceDecoder::seek_back ()
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
-       }
-
-       _next_audio -= f->video_frames_to_time (2);
-}
-
-void
-SilenceDecoder::seek_forward ()
-{
-       boost::shared_ptr<const Film> f = _film.lock ();
-       if (!f) {
-               return;
-       }
-
-       _next_audio += f->video_frames_to_time (1);
-}
-
-Time
-SilenceDecoder::position () const
-{
-       return _next_audio;
-}
-
-bool
-SilenceDecoder::done () const
-{
-       return audio_done ();
-}
-
diff --git a/src/lib/silence_decoder.h b/src/lib/silence_decoder.h
deleted file mode 100644 (file)
index a8a335d..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-    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/shared_ptr.hpp>
-#include "audio_decoder.h"
-
-class Film;
-class NullContent;
-
-class SilenceDecoder : public AudioDecoder
-{
-public:
-       SilenceDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<NullContent>);
-
-       void pass ();
-       void seek (Time);
-       void seek_back ();
-       void seek_forward ();
-       Time position () const;
-       bool done () const;
-};
index 8eede89f4365241821ccf38a6db5b9c16acba08d..beee7cd9d0aa33c37dfa31d789b4ef076d3eacfa 100644 (file)
@@ -46,7 +46,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
        , AudioContent (f, node)
 {
        _audio_channels = node->number_child<int> ("AudioChannels");
-       _audio_length = node->number_child<ContentAudioFrame> ("AudioLength");
+       _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
        _audio_frame_rate = node->number_child<int> ("AudioFrameRate");
        _audio_mapping = AudioMapping (node->node_child ("AudioMapping"));
 }
index 30eb23a4ed38e89bea2883274306647e9568d9a5..876d66088c6f7a5a84f4b289de622d43256d1d97 100644 (file)
@@ -49,7 +49,7 @@ public:
                return _audio_channels;
        }
        
-        ContentAudioFrame audio_length () const {
+        AudioContent::Frame audio_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _audio_length;
        }
@@ -72,7 +72,7 @@ public:
 
 private:
        int _audio_channels;
-       ContentAudioFrame _audio_length;
+       AudioContent::Frame _audio_length;
        int _audio_frame_rate;
        AudioMapping _audio_mapping;
 };
index ff56f106260330f5b7d7c9401ed5cf6c4658e7a9..80a6afd2baf79eae384164a310119a127e21c5bf 100644 (file)
@@ -35,7 +35,7 @@ using boost::shared_ptr;
 
 SndfileDecoder::SndfileDecoder (shared_ptr<const Film> f, shared_ptr<const SndfileContent> c)
        : Decoder (f)
-       , AudioDecoder (f, c)
+       , AudioDecoder (f)
        , _sndfile_content (c)
        , _deinterleave_buffer (0)
 {
@@ -100,7 +100,7 @@ SndfileDecoder::audio_channels () const
        return _info.channels;
 }
 
-ContentAudioFrame
+AudioContent::Frame
 SndfileDecoder::audio_length () const
 {
        return _info.frames;
@@ -112,14 +112,8 @@ SndfileDecoder::audio_frame_rate () const
        return _info.samplerate;
 }
 
-Time
-SndfileDecoder::position () const
-{
-       return _next_audio;
-}
-
 bool
 SndfileDecoder::done () const
 {
-       return audio_done ();
+       return _audio_position >= _sndfile_content->audio_length ();
 }
index e904340b320fb612de841d6a0cc4e19690973770..77fa6d17734da4096757dae504a759c9925e0e8b 100644 (file)
@@ -30,21 +30,17 @@ public:
        ~SndfileDecoder ();
 
        void pass ();
-       void seek (Time) {}
-       void seek_back () {}
-       void seek_forward () {}
-       Time position () const;
        bool done () const;
 
        int audio_channels () const;
-       ContentAudioFrame audio_length () const;
+       AudioContent::Frame audio_length () const;
        int audio_frame_rate () const;
 
 private:
        boost::shared_ptr<const SndfileContent> _sndfile_content;
        SNDFILE* _sndfile;
        SF_INFO _info;
-       ContentAudioFrame _done;
-       ContentAudioFrame _remaining;
+       AudioContent::Frame _done;
+       AudioContent::Frame _remaining;
        float* _deinterleave_buffer;
 };
index 5e719f9776774237de7b8d982fc002fb20b2d07e..7013f1d7d9004a635ac0732a8f77c638f92f6550 100644 (file)
@@ -27,8 +27,7 @@
 
 #include "i18n.h"
 
-using namespace std;
-using namespace boost;
+using boost::shared_ptr;
 using libdcp::Size;
 
 /** Construct a TimedSubtitle.  This is a subtitle image, position,
index f4637a05cf1c0f983d9a26bff6edca680b6f5d39..f4a52639a3cc5a887f3ae7c79991171e41527ac4 100644 (file)
 
 using std::string;
 using boost::shared_ptr;
+using boost::weak_ptr;
 using boost::dynamic_pointer_cast;
 
+static void
+video_proxy (weak_ptr<Encoder> encoder, shared_ptr<const Image> image, bool same)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->process_video (image, same);
+       }
+}
+
+static void
+audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->process_audio (audio);
+       }
+}
+
 /** Construct a transcoder using a Decoder that we create and a supplied Encoder.
  *  @param f Film that we are transcoding.
  *  @param j Job that we are running under, or 0.
@@ -48,8 +67,8 @@ Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
        , _player (f->player ())
        , _encoder (new Encoder (f, j))
 {
-       _player->connect_video (_encoder);
-       _player->connect_audio (_encoder);
+       _player->Video.connect (bind (video_proxy, _encoder, _1, _2));
+       _player->Audio.connect (bind (audio_proxy, _encoder, _1));
 }
 
 void
index 70262afb0a7c670abf0fd9bc3f23eda9f072b17b..33f8239d88cbee89ace3e07b51acd94d67b27fd9 100644 (file)
@@ -27,8 +27,6 @@
 
 class Content;
 
-typedef int64_t ContentAudioFrame;
-typedef int     ContentVideoFrame;
 typedef int64_t Time;
 #define TIME_MAX INT64_MAX
 #define TIME_HZ  ((Time) 96000)
index eda0d0236767300f349c146660d29a1f24a24595..d425fc8fefe009c3f0ee44ed94eab18c1d969285 100644 (file)
@@ -710,7 +710,7 @@ ensure_ui_thread ()
  *  @return Equivalent number of audio frames for `v'.
  */
 int64_t
-video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
 {
        return ((int64_t) v * audio_sample_rate / frames_per_second);
 }
index 42514a12c92f9f863944ab0018d16b5b8e573533..c68bb4f1619614b29e91b9ceb7f45c88898547b1 100644 (file)
@@ -38,6 +38,7 @@ extern "C" {
 }
 #include "compose.hpp"
 #include "types.h"
+#include "video_content.h"
 
 #ifdef DCPOMATIC_DEBUG
 #define TIMING(...) _film->log()->microsecond_log (String::compose (__VA_ARGS__), Log::TIMING);
@@ -152,7 +153,7 @@ private:
        int _timeout;
 };
 
-extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
 
 class LocaleGuard
 {
index f9de6aa2146b0f0ff5a91de4b214ddc37b83442a..3818fa7920fb4b86d6cbf990858a192473428a1b 100644 (file)
@@ -22,6 +22,7 @@
 #include "video_content.h"
 #include "video_examiner.h"
 #include "ratio.h"
+#include "compose.hpp"
 
 #include "i18n.h"
 
@@ -37,7 +38,7 @@ using boost::shared_ptr;
 using boost::lexical_cast;
 using boost::optional;
 
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, ContentVideoFrame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
        : Content (f, s)
        , _video_length (len)
        , _video_frame_rate (0)
@@ -58,7 +59,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, boost::filesystem::path p)
 VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
        : Content (f, node)
 {
-       _video_length = node->number_child<ContentVideoFrame> ("VideoLength");
+       _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
        _video_size.width = node->number_child<int> ("VideoWidth");
        _video_size.height = node->number_child<int> ("VideoHeight");
        _video_frame_rate = node->number_child<float> ("VideoFrameRate");
index 23bcaa89bad7b945a041b4c039c54bdc36dd3f69..372cab3bd3c48ab5874188800cf7b41e541ebea0 100644 (file)
@@ -21,7 +21,6 @@
 #define DCPOMATIC_VIDEO_CONTENT_H
 
 #include "content.h"
-#include "util.h"
 
 class VideoExaminer;
 class Ratio;
@@ -38,7 +37,9 @@ public:
 class VideoContent : public virtual Content
 {
 public:
-       VideoContent (boost::shared_ptr<const Film>, Time, ContentVideoFrame);
+       typedef int Frame;
+
+       VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
        VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
        VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
        VideoContent (VideoContent const &);
@@ -46,7 +47,7 @@ public:
        void as_xml (xmlpp::Node *) const;
        virtual std::string information () const;
 
-       ContentVideoFrame video_length () const {
+       VideoContent::Frame video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
                return _video_length;
        }
@@ -82,7 +83,7 @@ public:
 protected:
        void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
 
-       ContentVideoFrame _video_length;
+       VideoContent::Frame _video_length;
 
 private:
        libdcp::Size _video_size;
index fcc0ccf41eeafcc5491e2fd6f7569bd3250d7131..f61e63d4d1a4c312c99054c436bfee462f2afdab 100644 (file)
 using std::cout;
 using boost::shared_ptr;
 
-VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
+VideoDecoder::VideoDecoder (shared_ptr<const Film> f)
        : Decoder (f)
-       , _next_video (0)
-       , _video_content (c)
-       , _frame_rate_conversion (c->video_frame_rate(), f->dcp_video_frame_rate())
-       , _odd (false)
+       , _video_position (0)
 {
 
 }
 
-/** Called by subclasses when some video is ready.
- *  @param image frame to emit.
- *  @param same true if this frame is the same as the last one passed to this call.
- *  @param t Time of the frame within the source.
- */
 void
-VideoDecoder::video (shared_ptr<Image> image, bool same, Time t)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
 {
-       if (_frame_rate_conversion.skip && _odd) {
-               _odd = !_odd;
-               return;
-       }
-
-       image = image->crop (_video_content->crop(), true);
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       libdcp::Size const container_size = _video_container_size.get_value_or (film->container()->size (film->full_frame ()));
-       libdcp::Size const image_size = _video_content->ratio()->size (container_size);
-       
-       shared_ptr<Image> out = image->scale_and_convert_to_rgb (image_size, film->scaler(), true);
-
-       if (film->with_subtitles ()) {
-               shared_ptr<Subtitle> sub;
-               if (_timed_subtitle && _timed_subtitle->displayed_at (t)) {
-                       sub = _timed_subtitle->subtitle ();
-               }
-               
-               if (sub) {
-                       dcpomatic::Rect const tx = subtitle_transformed_area (
-                               float (image_size.width) / _video_content->video_size().width,
-                               float (image_size.height) / _video_content->video_size().height,
-                               sub->area(), film->subtitle_offset(), film->subtitle_scale()
-                               );
-                       
-                       shared_ptr<Image> im = sub->image()->scale (tx.size(), film->scaler(), true);
-                       out->alpha_blend (im, tx.position());
-               }
-       }
-
-       if (image_size != container_size) {
-               assert (image_size.width <= container_size.width);
-               assert (image_size.height <= container_size.height);
-               shared_ptr<Image> im (new SimpleImage (PIX_FMT_RGB24, container_size, true));
-               im->make_black ();
-               im->copy (out, Position ((container_size.width - image_size.width) / 2, (container_size.height - image_size.height) / 2));
-               out = im;
-       }
-               
-       Video (out, same, t);
-
-       if (_frame_rate_conversion.repeat) {
-               Video (image, true, t + film->video_frames_to_time (1));
-               _next_video = t + film->video_frames_to_time (2);
-       } else {
-               _next_video = t + film->video_frames_to_time (1);
-       }
-
-       _odd = !_odd;
+        Video (image, same, frame);
+       _video_position = frame + 1;
 }
 
+#if 0
+
 /** Called by subclasses when a subtitle is ready.
  *  s may be 0 to say that there is no current subtitle.
  *  @param s New current subtitle, or 0.
@@ -114,40 +58,5 @@ VideoDecoder::subtitle (shared_ptr<TimedSubtitle> s)
                _timed_subtitle->subtitle()->set_position (Position (p.x - _video_content->crop().left, p.y - _video_content->crop().top));
        }
 }
+#endif
 
-bool
-VideoDecoder::video_done () const
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       
-       return (_video_content->length() - _next_video) < film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::seek (Time t)
-{
-       _next_video = t;
-}
-
-void
-VideoDecoder::seek_back ()
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       _next_video -= film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::seek_forward ()
-{
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-       _next_video += film->video_frames_to_time (1);
-}
-
-void
-VideoDecoder::set_video_container_size (libdcp::Size s)
-{
-       _video_container_size = s;
-}
index 8de76c10f7a2653b2d79702b9a10301bfce5b221..d24219d956cba2df1b92b8a58e2d3f5bc8d24d78 100644 (file)
 #ifndef DCPOMATIC_VIDEO_DECODER_H
 #define DCPOMATIC_VIDEO_DECODER_H
 
-#include "video_source.h"
 #include "decoder.h"
 #include "util.h"
 
 class VideoContent;
 
-class VideoDecoder : public VideoSource, public virtual Decoder
+class VideoDecoder : public virtual Decoder
 {
 public:
-       VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
+       VideoDecoder (boost::shared_ptr<const Film>);
 
-       virtual void seek (Time);
-       virtual void seek_back ();
-       virtual void seek_forward ();
-       
-       void set_video_container_size (libdcp::Size);
+       virtual void seek (VideoContent::Frame) = 0;
+       virtual void seek_back () = 0;
 
-protected:
+       /** Emitted when a video frame is ready.
+        *  First parameter is the video image.
+        *  Second parameter is true if the image is the same as the last one that was emitted.
+        *  Third parameter is the frame within our source.
+        */
+       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, VideoContent::Frame)> Video;
        
-       void video (boost::shared_ptr<Image>, bool, Time);
-       void subtitle (boost::shared_ptr<TimedSubtitle>);
-       bool video_done () const;
-
-       Time _next_video;
-       boost::shared_ptr<const VideoContent> _video_content;
-
-private:
-       boost::shared_ptr<TimedSubtitle> _timed_subtitle;
-       FrameRateConversion _frame_rate_conversion;
-       bool _odd;
-       boost::optional<libdcp::Size> _video_container_size;
+protected:
+
+       void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+       VideoContent::Frame _video_position;
 };
 
 #endif
index 2f713291b5a5165d42976196607ab396dd7fe50f..72f6ccc12cab575f2a20e297ecdfed5bbb967344 100644 (file)
 
 #include <libdcp/types.h>
 #include "types.h"
+#include "video_content.h"
 
 class VideoExaminer
 {
 public:
        virtual float video_frame_rate () const = 0;
        virtual libdcp::Size video_size () const = 0;
-       virtual ContentVideoFrame video_length () const = 0;
+       virtual VideoContent::Frame video_length () const = 0;
 };
diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h
deleted file mode 100644 (file)
index 957aeb4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-    Copyright (C) 2012 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_VIDEO_SINK_H
-#define DCPOMATIC_VIDEO_SINK_H
-
-#include <boost/shared_ptr.hpp>
-#include "util.h"
-
-class Subtitle;
-class Image;
-
-class VideoSink
-{
-public:
-       /** Call with a frame of video.
-        *  @param i Video frame image.
-        *  @param same true if i is the same as last time we were called.
-        */
-       virtual void process_video (boost::shared_ptr<const Image> i, bool same, Time) = 0;
-};
-
-#endif
diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc
deleted file mode 100644 (file)
index 824587b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-    Copyright (C) 2012 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 "video_source.h"
-#include "video_sink.h"
-
-using boost::shared_ptr;
-using boost::weak_ptr;
-using boost::bind;
-
-static void
-process_video_proxy (weak_ptr<VideoSink> sink, shared_ptr<const Image> image, bool same, Time time)
-{
-       shared_ptr<VideoSink> p = sink.lock ();
-       if (p) {
-               p->process_video (image, same, time);
-       }
-}
-
-void
-VideoSource::connect_video (shared_ptr<VideoSink> s)
-{
-       /* If we bind, say, a Player (as the VideoSink) to a Decoder (which is owned
-          by the Player) we create a cycle.  Use a weak_ptr to break it.
-       */
-       Video.connect (bind (process_video_proxy, weak_ptr<VideoSink> (s), _1, _2, _3));
-}
-
diff --git a/src/lib/video_source.h b/src/lib/video_source.h
deleted file mode 100644 (file)
index 9242af4..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-    Copyright (C) 2012 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  src/video_source.h
- *  @brief Parent class for classes which emit video data.
- */
-
-#ifndef DCPOMATIC_VIDEO_SOURCE_H
-#define DCPOMATIC_VIDEO_SOURCE_H
-
-#include <boost/shared_ptr.hpp>
-#include <boost/signals2.hpp>
-#include "util.h"
-
-class VideoSink;
-class Subtitle;
-class Image;
-
-/** @class VideoSource
- *  @param A class that emits video data.
- */
-class VideoSource
-{
-public:
-
-       /** Emitted when a video frame is ready.
-        *  First parameter is the video image.
-        *  Second parameter is true if the image is the same as the last one that was emitted.
-        *  Third parameter is the time relative to the start of this source's content.
-        */
-       boost::signals2::signal<void (boost::shared_ptr<const Image>, bool, Time)> Video;
-
-       void connect_video (boost::shared_ptr<VideoSink>);
-};
-
-#endif
index 35874581b62d829b74d9b1399f6fce36a20ee620..cf81d5b70a8e4a902a697a7b94899b2a20f52a7a 100644 (file)
@@ -133,7 +133,6 @@ Writer::fake_write (int frame)
 void
 Writer::write (shared_ptr<const AudioBuffers> audio)
 {
-       cout << "W: audio " << audio->frames() << "\n";
        _sound_asset_writer->write (audio->data(), audio->frames());
 }
 
index d0f10299821d95778631e943c8d4b2e57ef0a6e1..3e7f2e33be88ada8822bfd2b8e5312f02f1173cb 100644 (file)
@@ -8,10 +8,7 @@ sources = """
           audio_content.cc
           audio_decoder.cc
           audio_mapping.cc
-          audio_source.cc
-          black_decoder.cc
           config.cc
-          combiner.cc
           content.cc
           cross.cc
           dci_metadata.cc
@@ -37,14 +34,13 @@ sources = """
           job_manager.cc
           log.cc
           lut.cc
-          null_content.cc
           player.cc
           playlist.cc
           ratio.cc
+          resampler.cc
           scp_dcp_job.cc
           scaler.cc
           server.cc
-          silence_decoder.cc
           sndfile_content.cc
           sndfile_decoder.cc
           sound_processor.cc
@@ -57,7 +53,6 @@ sources = """
           util.cc
           video_content.cc
           video_decoder.cc
-          video_source.cc
           writer.cc
           """
 
index db2ab9dfe2e6009decb499f40559fb3c36e9795a..8ef64d509c9534907e6f22f47f8e9e4dd2537d80 100644 (file)
@@ -181,7 +181,7 @@ FilmViewer::update_from_decoder ()
                return;
        }
 
-       _player->seek (_player->position() - _film->video_frames_to_time (1));
+       _player->seek (_player->video_position() - _film->video_frames_to_time (1));
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
@@ -197,7 +197,7 @@ FilmViewer::timer (wxTimerEvent &)
        get_frame ();
 
        if (_film->length()) {
-               int const new_slider_position = 4096 * _player->position() / _film->length();
+               int const new_slider_position = 4096 * _player->video_position() / _film->length();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@ -445,7 +445,6 @@ FilmViewer::forward_clicked (wxCommandEvent &)
                return;
        }
 
-       _player->seek_forward ();
        get_frame ();
        _panel->Refresh ();
        _panel->Update ();
index 6e73038f10c6e125640317f9b73132d594982593..296444ba76bcda955a23da23211603b831ed465e 100644 (file)
@@ -25,7 +25,7 @@ BOOST_AUTO_TEST_CASE (ffmpeg_examiner_test)
        shared_ptr<FFmpegContent> content (new FFmpegContent (film, "test/data/count300bd24.m2ts"));
        shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (content));
 
-       BOOST_CHECK_EQUAL (examiner->first_video().get(), 57604000);
+       BOOST_CHECK_EQUAL (examiner->first_video().get(), 600.04166666666674);
        BOOST_CHECK_EQUAL (examiner->audio_streams().size(), 1);
-       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->start.get(), 57600000);
+       BOOST_CHECK_EQUAL (examiner->audio_streams()[0]->first_audio.get(), 600);
 }
diff --git a/test/ffmpeg_pts_offset.cc b/test/ffmpeg_pts_offset.cc
new file mode 100644 (file)
index 0000000..15c2b38
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    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.
+
+*/
+
+BOOST_AUTO_TEST_CASE (ffmpeg_pts_offset_test)
+{
+       /* Sound == video so no offset required */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (0, 0, 24), 0);
+
+       /* Common offset should be removed */
+       BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (42, 42, 24), -42, 1e-9);
+
+       /* Video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 24.0, 0, 24), 0);
+
+       /* Again, video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (1.0 / 23.97, 0, 23.97), 0);
+
+       /* And again, video is on a frame boundary */
+       BOOST_CHECK_EQUAL (FFmpegDecoder::compute_pts_offset (3.0 / 23.97, 0, 23.97), 0);
+
+       /* Off a frame boundary */
+       BOOST_CHECK_CLOSE (FFmpegDecoder::compute_pts_offset (1.0 / 24.0 - 0.0215, 0, 24), 0.0215, 1e-9);
+}
index 3434a3f09af9c4a79384643f50dc6b06e43dddec..4494540a2ac31e5069f1a6c3f192b4c8be837b14 100644 (file)
@@ -154,6 +154,7 @@ check_dcp (string ref, string check)
        BOOST_CHECK (ref_dcp.equals (check_dcp, options, boost::bind (note, _1, _2)));
 }
 
+#include "ffmpeg_pts_offset.cc"
 #include "ffmpeg_examiner_test.cc"
 #include "black_fill_test.cc"
 #include "scaling_test.cc"