summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/analyse_audio_job.cc2
-rw-r--r--src/lib/analyse_audio_job.h2
-rw-r--r--src/lib/audio_content.cc26
-rw-r--r--src/lib/audio_content.h4
-rw-r--r--src/lib/audio_decoder.cc16
-rw-r--r--src/lib/audio_decoder.h14
-rw-r--r--src/lib/audio_merger.h11
-rw-r--r--src/lib/content.cc18
-rw-r--r--src/lib/content.h30
-rw-r--r--src/lib/decoded.h117
-rw-r--r--src/lib/decoder.cc29
-rw-r--r--src/lib/decoder.h23
-rw-r--r--src/lib/ffmpeg.cc4
-rw-r--r--src/lib/ffmpeg_content.cc9
-rw-r--r--src/lib/ffmpeg_content.h2
-rw-r--r--src/lib/ffmpeg_decoder.cc245
-rw-r--r--src/lib/ffmpeg_decoder.h13
-rw-r--r--src/lib/film.cc16
-rw-r--r--src/lib/film.h11
-rw-r--r--src/lib/image_content.cc4
-rw-r--r--src/lib/image_content.h2
-rw-r--r--src/lib/image_decoder.cc27
-rw-r--r--src/lib/image_decoder.h9
-rw-r--r--src/lib/job.cc2
-rw-r--r--src/lib/player.cc432
-rw-r--r--src/lib/player.h60
-rw-r--r--src/lib/playlist.cc35
-rw-r--r--src/lib/playlist.h6
-rw-r--r--src/lib/resampler.cc8
-rw-r--r--src/lib/resampler.h2
-rw-r--r--src/lib/sndfile_content.cc2
-rw-r--r--src/lib/sndfile_content.h2
-rw-r--r--src/lib/sndfile_decoder.cc16
-rw-r--r--src/lib/sndfile_decoder.h8
-rw-r--r--src/lib/subtitle_decoder.cc5
-rw-r--r--src/lib/subtitle_decoder.h7
-rw-r--r--src/lib/timer.cc14
-rw-r--r--src/lib/timer.h18
-rw-r--r--src/lib/types.h5
-rw-r--r--src/lib/util.cc12
-rw-r--r--src/lib/util.h10
-rw-r--r--src/lib/video_content.cc16
-rw-r--r--src/lib/video_content.h4
-rw-r--r--src/lib/video_decoder.cc14
-rw-r--r--src/lib/video_decoder.h21
45 files changed, 752 insertions, 581 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index 8186f9de4..3f84bf16d 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -81,7 +81,7 @@ AnalyseAudioJob::run ()
}
void
-AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, Time)
+AnalyseAudioJob::audio (shared_ptr<const AudioBuffers> b, DCPTime)
{
for (int i = 0; i < b->frames(); ++i) {
for (int j = 0; j < b->channels(); ++j) {
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
index 3d4881983..2e93ef500 100644
--- a/src/lib/analyse_audio_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -33,7 +33,7 @@ public:
void run ();
private:
- void audio (boost::shared_ptr<const AudioBuffers>, Time);
+ void audio (boost::shared_ptr<const AudioBuffers>, DCPTime);
boost::weak_ptr<AudioContent> _content;
OutputAudioFrame _done;
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index 97372b962..de743571b 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -39,7 +39,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (0)
@@ -148,3 +148,27 @@ AudioContent::technical_summary () const
{
return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
}
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ * is not very intelligent.
+ *
+ * @param t Some duration to convert.
+ * @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ * the `controlling' video content is active.
+ */
+AudioContent::Frame
+AudioContent::time_to_content_audio_frames (DCPTime t, DCPTime at) const
+{
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+
+ /* Consider the case where we're running a 25fps video at 24fps (i.e. slow)
+ Our audio is at 44.1kHz. We will resample it to 48000 * 25 / 24 and then
+ run it at 48kHz (i.e. slow, to match).
+
+ After 1 second, we'll have run the equivalent of 44.1kHz * 24 / 25 samples
+ in the source.
+ */
+
+ return rint (t * content_audio_frame_rate() * film->active_frame_rate_change(at).speed_up / TIME_HZ);
+}
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
index ca4a1f234..e1b38bd97 100644
--- a/src/lib/audio_content.h
+++ b/src/lib/audio_content.h
@@ -43,7 +43,7 @@ class AudioContent : public virtual Content
public:
typedef int64_t Frame;
- AudioContent (boost::shared_ptr<const Film>, Time);
+ AudioContent (boost::shared_ptr<const Film>, DCPTime);
AudioContent (boost::shared_ptr<const Film>, boost::filesystem::path);
AudioContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
AudioContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -74,6 +74,8 @@ public:
return _audio_delay;
}
+ Frame time_to_content_audio_frames (DCPTime, DCPTime) const;
+
private:
/** Gain to apply to audio in dB */
float _audio_gain;
diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc
index c0ef02f65..7ff8529c6 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -35,24 +35,12 @@ using boost::shared_ptr;
AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
: Decoder (film)
, _audio_content (content)
- , _audio_position (0)
{
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
-}
-
-/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio.
- * The player needs to know that there is no audio otherwise it will keep trying to
- * pass() the decoder to get it to emit audio.
- */
-bool
-AudioDecoder::has_audio () const
-{
- return _audio_content->audio_channels () > 0;
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, time)));
}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index ab6c4b8a9..0cd0e9754 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -27,6 +27,7 @@
#include "decoder.h"
#include "content.h"
#include "audio_content.h"
+#include "decoded.h"
class AudioBuffers;
@@ -38,16 +39,15 @@ class AudioDecoder : public virtual Decoder
public:
AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
- bool has_audio () const;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
+ boost::shared_ptr<const AudioContent> audio_content () const {
+ return _audio_content;
+ }
protected:
- void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
- boost::shared_ptr<const AudioContent> _audio_content;
- AudioContent::Frame _audio_position;
+ void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+
+ boost::shared_ptr<const AudioContent> _audio_content;
};
#endif
diff --git a/src/lib/audio_merger.h b/src/lib/audio_merger.h
index 226601e0e..f068b504e 100644
--- a/src/lib/audio_merger.h
+++ b/src/lib/audio_merger.h
@@ -37,6 +37,8 @@ public:
TimedAudioBuffers<T>
pull (T time)
{
+ assert (time >= _last_pull);
+
TimedAudioBuffers<T> out;
F const to_return = _t_to_f (time - _last_pull);
@@ -97,9 +99,16 @@ public:
if (_buffers->frames() == 0) {
return TimedAudioBuffers<T> ();
}
-
+
return TimedAudioBuffers<T> (_buffers, _last_pull);
}
+
+ void
+ clear (DCPTime t)
+ {
+ _last_pull = t;
+ _buffers.reset (new AudioBuffers (_buffers->channels(), 0));
+ }
private:
boost::shared_ptr<AudioBuffers> _buffers;
diff --git a/src/lib/content.cc b/src/lib/content.cc
index f09012765..7db349617 100644
--- a/src/lib/content.cc
+++ b/src/lib/content.cc
@@ -54,7 +54,7 @@ Content::Content (shared_ptr<const Film> f)
}
-Content::Content (shared_ptr<const Film> f, Time p)
+Content::Content (shared_ptr<const Film> f, DCPTime p)
: _film (f)
, _position (p)
, _trim_start (0)
@@ -83,9 +83,9 @@ Content::Content (shared_ptr<const Film> f, shared_ptr<const cxml::Node> node)
_paths.push_back ((*i)->content ());
}
_digest = node->string_child ("Digest");
- _position = node->number_child<Time> ("Position");
- _trim_start = node->number_child<Time> ("TrimStart");
- _trim_end = node->number_child<Time> ("TrimEnd");
+ _position = node->number_child<DCPTime> ("Position");
+ _trim_start = node->number_child<DCPTime> ("TrimStart");
+ _trim_end = node->number_child<DCPTime> ("TrimEnd");
}
Content::Content (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
@@ -146,7 +146,7 @@ Content::signal_changed (int p)
}
void
-Content::set_position (Time p)
+Content::set_position (DCPTime p)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -157,7 +157,7 @@ Content::set_position (Time p)
}
void
-Content::set_trim_start (Time t)
+Content::set_trim_start (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -168,7 +168,7 @@ Content::set_trim_start (Time t)
}
void
-Content::set_trim_end (Time t)
+Content::set_trim_end (DCPTime t)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -200,7 +200,7 @@ Content::technical_summary () const
return String::compose ("%1 %2 %3", path_summary(), digest(), position());
}
-Time
+DCPTime
Content::length_after_trim () const
{
return full_length() - trim_start() - trim_end();
@@ -210,7 +210,7 @@ Content::length_after_trim () const
* @return true if this time is trimmed by our trim settings.
*/
bool
-Content::trimmed (Time t) const
+Content::trimmed (DCPTime t) const
{
return (t < trim_start() || t > (full_length() - trim_end ()));
}
diff --git a/src/lib/content.h b/src/lib/content.h
index 9cf6d866a..1eee02e01 100644
--- a/src/lib/content.h
+++ b/src/lib/content.h
@@ -49,7 +49,7 @@ class Content : public boost::enable_shared_from_this<Content>, public boost::no
{
public:
Content (boost::shared_ptr<const Film>);
- Content (boost::shared_ptr<const Film>, Time);
+ Content (boost::shared_ptr<const Film>, DCPTime);
Content (boost::shared_ptr<const Film>, boost::filesystem::path);
Content (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
Content (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -60,7 +60,7 @@ public:
virtual std::string technical_summary () const;
virtual std::string information () const = 0;
virtual void as_xml (xmlpp::Node *) const;
- virtual Time full_length () const = 0;
+ virtual DCPTime full_length () const = 0;
virtual std::string identifier () const;
boost::shared_ptr<Content> clone () const;
@@ -92,41 +92,41 @@ public:
return _digest;
}
- void set_position (Time);
+ void set_position (DCPTime);
- /** Time that this content starts; i.e. the time that the first
+ /** DCPTime that this content starts; i.e. the time that the first
* bit of the content (trimmed or not) will happen.
*/
- Time position () const {
+ DCPTime position () const {
boost::mutex::scoped_lock lm (_mutex);
return _position;
}
- void set_trim_start (Time);
+ void set_trim_start (DCPTime);
- Time trim_start () const {
+ DCPTime trim_start () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_start;
}
- void set_trim_end (Time);
+ void set_trim_end (DCPTime);
- Time trim_end () const {
+ DCPTime trim_end () const {
boost::mutex::scoped_lock lm (_mutex);
return _trim_end;
}
- Time end () const {
+ DCPTime end () const {
return position() + length_after_trim() - 1;
}
- Time length_after_trim () const;
+ DCPTime length_after_trim () const;
void set_change_signals_frequent (bool f) {
_change_signals_frequent = f;
}
- bool trimmed (Time) const;
+ bool trimmed (DCPTime) const;
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
@@ -145,9 +145,9 @@ protected:
private:
std::string _digest;
- Time _position;
- Time _trim_start;
- Time _trim_end;
+ DCPTime _position;
+ DCPTime _trim_start;
+ DCPTime _trim_end;
bool _change_signals_frequent;
};
diff --git a/src/lib/decoded.h b/src/lib/decoded.h
new file mode 100644
index 000000000..a9b922769
--- /dev/null
+++ b/src/lib/decoded.h
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DCPOMATIC_LIB_DECODED_H
+#define DCPOMATIC_LIB_DECODED_H
+
+#include "types.h"
+#include "rect.h"
+
+class Image;
+
+class Decoded
+{
+public:
+ Decoded ()
+ : content_time (0)
+ , dcp_time (0)
+ {}
+
+ Decoded (DCPTime ct)
+ : content_time (ct)
+ , dcp_time (0)
+ {}
+
+ virtual ~Decoded () {}
+
+ virtual void set_dcp_times (float speed_up, DCPTime offset) = 0;
+
+ ContentTime content_time;
+ DCPTime dcp_time;
+};
+
+/** One frame of video from a VideoDecoder */
+class DecodedVideo : public Decoded
+{
+public:
+ DecodedVideo ()
+ : eyes (EYES_BOTH)
+ , same (false)
+ {}
+
+ DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, ContentTime ct)
+ : Decoded (ct)
+ , image (im)
+ , eyes (e)
+ , same (s)
+ {}
+
+ void set_dcp_times (float speed_up, DCPTime offset) {
+ dcp_time = rint (content_time / speed_up) + offset;
+ }
+
+ boost::shared_ptr<const Image> image;
+ Eyes eyes;
+ bool same;
+};
+
+class DecodedAudio : public Decoded
+{
+public:
+ DecodedAudio (boost::shared_ptr<const AudioBuffers> d, ContentTime ct)
+ : Decoded (ct)
+ , data (d)
+ {}
+
+ void set_dcp_times (float speed_up, DCPTime offset) {
+ dcp_time = rint (content_time / speed_up) + offset;
+ }
+
+ boost::shared_ptr<const AudioBuffers> data;
+};
+
+class DecodedSubtitle : public Decoded
+{
+public:
+ DecodedSubtitle ()
+ : content_time_to (0)
+ , dcp_time_to (0)
+ {}
+
+ /* XXX: content/dcp time here */
+ DecodedSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, DCPTime f, DCPTime t)
+ : Decoded (f)
+ , image (im)
+ , rect (r)
+ , content_time_to (t)
+ , dcp_time_to (t)
+ {}
+
+ void set_dcp_times (float speed_up, DCPTime offset) {
+ dcp_time = rint (content_time / speed_up) + offset;
+ dcp_time_to = rint (content_time_to / speed_up) + offset;
+ }
+
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rect;
+ ContentTime content_time_to;
+ DCPTime dcp_time_to;
+};
+
+#endif
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 3f4cda6eb..18c5acd35 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -36,3 +36,32 @@ Decoder::Decoder (shared_ptr<const Film> f)
{
}
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+ while (_pending.empty () && !pass ()) {}
+
+ if (_pending.empty ()) {
+ return shared_ptr<Decoded> ();
+ }
+
+ return _pending.front ();
+}
+
+shared_ptr<Decoded>
+Decoder::get ()
+{
+ shared_ptr<Decoded> d = peek ();
+ if (d) {
+ _pending.pop_front ();
+ }
+
+ return d;
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+ _pending.clear ();
+}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index d67592ed8..654cacad4 100644
--- a/src/lib/decoder.h
+++ b/src/lib/decoder.h
@@ -27,8 +27,10 @@
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
+#include "types.h"
class Film;
+class Decoded;
/** @class Decoder.
* @brief Parent class for decoders of content.
@@ -39,18 +41,31 @@ public:
Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- /** Perform one decode pass of the content, which may or may not
- * cause the object to emit some data.
+ /** Seek so that the next get_*() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
+ virtual void seek (ContentTime time, bool accurate);
+
+ boost::shared_ptr<Decoded> peek ();
+ boost::shared_ptr<Decoded> get ();
protected:
+ /** Perform one decode pass of the content, which may or may not
+ * result in a complete quantum (Decoded object) of decoded stuff
+ * being made ready.
+ * @return true if the decoder is done (i.e. no more data will be
+ * produced by any future calls to pass() without a seek() first).
+ */
+ virtual bool pass () = 0;
virtual void flush () {};
/** The Film that we are decoding in */
boost::weak_ptr<const Film> _film;
+
+ std::list<boost::shared_ptr<Decoded> > _pending;
};
#endif
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
index e5e5f317a..53d12419a 100644
--- a/src/lib/ffmpeg.cc
+++ b/src/lib/ffmpeg.cc
@@ -191,6 +191,10 @@ FFmpeg::video_codec_context () const
AVCodecContext *
FFmpeg::audio_codec_context () const
{
+ if (!_ffmpeg_content->audio_stream ()) {
+ return 0;
+ }
+
return _ffmpeg_content->audio_stream()->stream(_format_context)->codec;
}
diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc
index 9533315a5..65a8d24f1 100644
--- a/src/lib/ffmpeg_content.cc
+++ b/src/lib/ffmpeg_content.cc
@@ -310,16 +310,15 @@ FFmpegContent::output_audio_frame_rate () const
/* Resample to a DCI-approved sample rate */
double t = dcp_audio_frame_rate (content_audio_frame_rate ());
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
/* Compensate if the DCP is being run at a different frame rate
to the source; that is, if the video is run such that it will
look different in the DCP compared to the source (slower or faster).
- skip/repeat doesn't come into effect here.
*/
if (frc.change_speed) {
- t *= video_frame_rate() * frc.factor() / film->video_frame_rate();
+ t /= frc.speed_up;
}
return rint (t);
@@ -446,13 +445,13 @@ FFmpegSubtitleStream::as_xml (xmlpp::Node* root) const
FFmpegStream::as_xml (root);
}
-Time
+DCPTime
FFmpegContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate (), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate (), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / film->video_frame_rate ();
}
diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h
index 7ff159b85..ba73c0f9b 100644
--- a/src/lib/ffmpeg_content.h
+++ b/src/lib/ffmpeg_content.h
@@ -134,7 +134,7 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 5a1b78762..b2e8cc44a 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -70,7 +70,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
, _decode_audio (audio)
, _video_pts_offset (0)
, _audio_pts_offset (0)
- , _just_sought (false)
{
setup_subtitle ();
@@ -89,7 +88,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
*/
bool const have_video = video && c->first_video();
- bool const have_audio = audio && c->audio_stream() && c->audio_stream()->first_audio;
+ bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
/* First, make one of them start at 0 */
@@ -143,12 +142,15 @@ FFmpegDecoder::flush ()
decode_audio_packet ();
}
+#if 0
+ /* XXX */
/* Stop us being asked for any more data */
_video_position = _ffmpeg_content->video_length ();
_audio_position = _ffmpeg_content->audio_length ();
+#endif
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
@@ -164,7 +166,7 @@ FFmpegDecoder::pass ()
}
flush ();
- return;
+ return true;
}
avcodec_get_frame_defaults (_frame);
@@ -183,6 +185,7 @@ FFmpegDecoder::pass ()
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
@@ -297,70 +300,131 @@ FFmpegDecoder::bytes_per_audio_sample () const
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (ContentTime, ContentTime, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ ContentTime last_video = 0;
+ ContentTime last_audio = 0;
+ bool flushing = false;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
- if (accurate) {
- initial -= 5;
- }
+ ++frames_read;
- if (initial < 0) {
- initial = 0;
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ avcodec_get_frame_defaults (_frame);
+
+ int finished = 0;
+ r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_video = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * TIME_HZ
+ );
+ }
+
+ } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+ AVPacket copy_packet = _packet;
+ while (copy_packet.size > 0) {
+
+ int finished;
+ r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
+ if (r >= 0 && finished) {
+ last_audio = rint (
+ (av_frame_get_best_effort_timestamp (_frame) * time_base + _audio_pts_offset) * TIME_HZ
+ );
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, ContentTime last_video, ContentTime last_audio) const
+{
+ return last_video >= seek || last_audio >= seek;
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ int64_t const initial_v = ((double (t) / TIME_HZ) - _video_pts_offset) /
+ av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ av_seek_frame (_format_context, _video_stream, initial_v, AVSEEK_FLAG_BACKWARD);
+
+ shared_ptr<FFmpegAudioStream> as = _ffmpeg_content->audio_stream ();
+ if (as) {
+ int64_t initial_a = ((double (t) / TIME_HZ) - _audio_pts_offset) /
+ av_q2d (as->stream(_format_context)->time_base);
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, as->index (_format_context), initial_a, AVSEEK_FLAG_BACKWARD);
+ }
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- _just_sought = true;
- _video_position = frame;
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ Decoder::seek (time, accurate);
- if (frame == 0 || !accurate) {
- /* We're already there, or we're as close as we need to be */
- return;
+ /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+ a number plucked from the air) earlier than we want to end up. The loop below
+ will hopefully then step through to where we want to be.
+ */
+
+ ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
+ ContentTime initial_seek = time - pre_roll;
+ if (initial_seek < 0) {
+ initial_seek = 0;
}
+
+ /* Initial seek time in the video stream's timebase */
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ seek_and_flush (initial_seek);
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- avcodec_get_frame_defaults (_frame);
-
- int finished = 0;
- r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
- if (r >= 0 && finished) {
- _video_position = rint (
- (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate()
- );
+ if (time == 0 || !accurate) {
+ /* We're already there, or we're as close as we need to be */
+ return;
+ }
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
+
+ seek_and_flush (initial_seek);
+ if (N > 0) {
+ minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
}
}
@@ -377,6 +441,7 @@ FFmpegDecoder::decode_audio_packet ()
int frame_finished;
int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
if (decode_result < 0) {
shared_ptr<const Film> film = _film.lock ();
assert (film);
@@ -385,31 +450,16 @@ FFmpegDecoder::decode_audio_packet ()
}
if (frame_finished) {
-
- if (_audio_position == 0) {
- /* Where we are in the source, in seconds */
- double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
- * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset;
-
- if (pts > 0) {
- /* Emit some silence */
- shared_ptr<AudioBuffers> silence (
- new AudioBuffers (
- _ffmpeg_content->audio_channels(),
- pts * _ffmpeg_content->content_audio_frame_rate()
- )
- );
-
- silence->make_silent ();
- audio (silence, _audio_position);
- }
- }
-
+ ContentTime const t = rint (
+ (av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
+ * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset) * TIME_HZ
+ );
+
int const data_size = av_samples_get_buffer_size (
0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
);
- audio (deinterleave_audio (_frame->data, data_size), _audio_position);
+ audio (deinterleave_audio (_frame->data, data_size), t);
}
copy_packet.data += decode_result;
@@ -458,45 +508,8 @@ FFmpegDecoder::decode_video_packet ()
}
if (i->second != AV_NOPTS_VALUE) {
-
- double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset;
-
- if (_just_sought) {
- /* We just did a seek, so disable any attempts to correct for where we
- are / should be.
- */
- _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
- _just_sought = false;
- }
-
- double const next = _video_position / _ffmpeg_content->video_frame_rate();
- double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
- double delta = pts - next;
-
- while (delta > one_frame) {
- /* This PTS is more than one frame forward in time of where we think we should be; emit
- a black frame.
- */
-
- /* XXX: I think this should be a copy of the last frame... */
- boost::shared_ptr<Image> black (
- new Image (
- static_cast<AVPixelFormat> (_frame->format),
- libdcp::Size (video_codec_context()->width, video_codec_context()->height),
- true
- )
- );
-
- black->make_black ();
- video (image, false, _video_position);
- delta -= one_frame;
- }
-
- if (delta > -one_frame) {
- /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
- video (image, false, _video_position);
- }
-
+ ContentTime const t = rint ((i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset) * TIME_HZ);
+ video (image, false, t);
} else {
shared_ptr<const Film> film = _film.lock ();
assert (film);
@@ -529,14 +542,6 @@ FFmpegDecoder::setup_subtitle ()
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
@@ -562,8 +567,8 @@ FFmpegDecoder::decode_subtitle_packet ()
double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
/* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ DCPTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+ DCPTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
AVSubtitleRect const * rect = sub.rects[0];
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 11f83ed97..e50b248cf 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -51,15 +51,12 @@ public:
FFmpegDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const FFmpegContent>, bool video, bool audio);
~FFmpegDecoder ();
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
+ void seek (ContentTime time, bool);
private:
friend class ::ffmpeg_pts_offset_test;
- static double compute_pts_offset (double, double, float);
-
+ bool pass ();
void flush ();
void setup_subtitle ();
@@ -74,6 +71,11 @@ private:
void maybe_add_subtitle ();
boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t** data, int size);
+ bool seek_overrun_finished (ContentTime, ContentTime, ContentTime) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (ContentTime, ContentTime, int)>);
+ void seek_and_flush (int64_t);
+
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
@@ -85,5 +87,4 @@ private:
double _video_pts_offset;
double _audio_pts_offset;
- bool _just_sought;
};
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 5946d5bec..8e93667d5 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -842,7 +842,7 @@ Film::move_content_later (shared_ptr<Content> c)
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
@@ -866,6 +866,12 @@ Film::content_paths_valid () const
return _playlist->content_paths_valid ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
@@ -885,24 +891,24 @@ Film::playlist_changed ()
}
OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
+Film::time_to_audio_frames (DCPTime t) const
{
return t * audio_frame_rate () / TIME_HZ;
}
OutputVideoFrame
-Film::time_to_video_frames (Time t) const
+Film::time_to_video_frames (DCPTime t) const
{
return t * video_frame_rate () / TIME_HZ;
}
-Time
+DCPTime
Film::audio_frames_to_time (OutputAudioFrame f) const
{
return f * TIME_HZ / audio_frame_rate ();
}
-Time
+DCPTime
Film::video_frames_to_time (OutputVideoFrame f) const
{
return f * TIME_HZ / video_frame_rate ();
diff --git a/src/lib/film.h b/src/lib/film.h
index 4b07f84a9..a0ace9981 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -103,18 +103,19 @@ public:
OutputAudioFrame audio_frame_rate () const;
- OutputAudioFrame time_to_audio_frames (Time) const;
- OutputVideoFrame time_to_video_frames (Time) const;
- Time video_frames_to_time (OutputVideoFrame) const;
- Time audio_frames_to_time (OutputAudioFrame) const;
+ OutputAudioFrame time_to_audio_frames (DCPTime) const;
+ OutputVideoFrame time_to_video_frames (DCPTime) const;
+ DCPTime video_frames_to_time (OutputVideoFrame) const;
+ DCPTime audio_frames_to_time (OutputAudioFrame) const;
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
OutputVideoFrame best_video_frame_rate () const;
bool content_paths_valid () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
libdcp::KDM
make_kdm (
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index b05fa6b8d..0f9526071 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -120,13 +120,13 @@ ImageContent::set_video_length (VideoContent::Frame len)
signal_changed (ContentProperty::LENGTH);
}
-Time
+DCPTime
ImageContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate ());
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate ());
return video_length() * frc.factor() * TIME_HZ / video_frame_rate();
}
diff --git a/src/lib/image_content.h b/src/lib/image_content.h
index 47c5a20e3..88c178faa 100644
--- a/src/lib/image_content.h
+++ b/src/lib/image_content.h
@@ -41,7 +41,7 @@ public:
std::string summary () const;
std::string technical_summary () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index fb6053ae5..9e90b5bc8 100644
--- a/src/lib/image_decoder.cc
+++ b/src/lib/image_decoder.cc
@@ -36,20 +36,22 @@ ImageDecoder::ImageDecoder (shared_ptr<const Film> f, shared_ptr<const ImageCont
: Decoder (f)
, VideoDecoder (f, c)
, _image_content (c)
+ , _video_position (0)
{
}
-void
+bool
ImageDecoder::pass ()
{
if (_video_position >= _image_content->video_length ()) {
- return;
+ return true;
}
if (_image && _image_content->still ()) {
- video (_image, true, _video_position);
- return;
+ video (_image, true, _video_position * TIME_HZ / _video_content->video_frame_rate ());
+ ++_video_position;
+ return false;
}
Magick::Image* magick_image = new Magick::Image (_image_content->path (_image_content->still() ? 0 : _video_position).string ());
@@ -73,17 +75,16 @@ ImageDecoder::pass ()
delete magick_image;
- video (_image, false, _video_position);
-}
+ video (_image, false, _video_position * TIME_HZ / _video_content->video_frame_rate ());
+ ++_video_position;
-void
-ImageDecoder::seek (VideoContent::Frame frame, bool)
-{
- _video_position = frame;
+ return false;
}
-bool
-ImageDecoder::done () const
+void
+ImageDecoder::seek (ContentTime time, bool accurate)
{
- return _video_position >= _image_content->video_length ();
+ Decoder::seek (time, accurate);
+
+ _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ);
}
diff --git a/src/lib/image_decoder.h b/src/lib/image_decoder.h
index c7500243e..f4d81dfb5 100644
--- a/src/lib/image_decoder.h
+++ b/src/lib/image_decoder.h
@@ -34,14 +34,13 @@ public:
return _image_content;
}
- /* Decoder */
-
- void pass ();
- void seek (VideoContent::Frame, bool);
- bool done () const;
+ void seek (ContentTime, bool);
private:
+ bool pass ();
+
boost::shared_ptr<const ImageContent> _image_content;
boost::shared_ptr<Image> _image;
+ ContentTime _video_position;
};
diff --git a/src/lib/job.cc b/src/lib/job.cc
index 9981934ec..05a90524c 100644
--- a/src/lib/job.cc
+++ b/src/lib/job.cc
@@ -198,7 +198,7 @@ Job::set_state (State s)
}
}
-/** @return Time (in seconds) that this sub-job has been running */
+/** @return DCPTime (in seconds) that this sub-job has been running */
int
Job::elapsed_time () const
{
diff --git a/src/lib/player.cc b/src/lib/player.cc
index 09a437494..f5bc68565 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -45,69 +45,20 @@ using std::map;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
+using boost::optional;
class Piece
{
public:
- Piece (shared_ptr<Content> c)
- : content (c)
- , video_position (c->position ())
- , audio_position (c->position ())
- , repeat_to_do (0)
- , repeat_done (0)
- {}
-
- Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
+ Piece (shared_ptr<Content> c, shared_ptr<Decoder> d, FrameRateChange f)
: content (c)
, decoder (d)
- , video_position (c->position ())
- , audio_position (c->position ())
+ , frc (f)
{}
- /** Set this piece to repeat a video frame a given number of times */
- void set_repeat (IncomingVideo video, int num)
- {
- repeat_video = video;
- repeat_to_do = num;
- repeat_done = 0;
- }
-
- void reset_repeat ()
- {
- repeat_video.image.reset ();
- repeat_to_do = 0;
- repeat_done = 0;
- }
-
- bool repeating () const
- {
- return repeat_done != repeat_to_do;
- }
-
- void repeat (Player* player)
- {
- player->process_video (
- repeat_video.weak_piece,
- repeat_video.image,
- repeat_video.eyes,
- repeat_done > 0,
- repeat_video.frame,
- (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
- );
-
- ++repeat_done;
- }
-
shared_ptr<Content> content;
shared_ptr<Decoder> decoder;
- /** Time of the last video we emitted relative to the start of the DCP */
- Time video_position;
- /** Time of the last audio we emitted relative to the start of the DCP */
- Time audio_position;
-
- IncomingVideo repeat_video;
- int repeat_to_do;
- int repeat_done;
+ FrameRateChange frc;
};
Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
@@ -120,6 +71,7 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
, _audio_position (0)
, _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
, _last_emit_was_black (false)
+ , _just_did_inaccurate_seek (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -146,110 +98,109 @@ Player::pass ()
setup_pieces ();
}
- Time earliest_t = TIME_MAX;
- shared_ptr<Piece> earliest;
- enum {
- VIDEO,
- AUDIO
- } type = VIDEO;
+ /* Interrogate all our pieces to find the one with the earliest decoded data */
+
+ shared_ptr<Piece> earliest_piece;
+ shared_ptr<Decoded> earliest_decoded;
+ DCPTime earliest_time = TIME_MAX;
+ DCPTime earliest_audio = TIME_MAX;
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
- shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
+ shared_ptr<Decoded> dec = (*i)->decoder->peek ();
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
- }
+ if (dec) {
+ dec->set_dcp_times ((*i)->frc.speed_up, (*i)->content->position());
}
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
+ /* XXX: don't know what to do with this */
+#if 0
+ if (ad->done()) {
+ shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> ((*i)->content);
+ assert (ac);
+ shared_ptr<Resampler> re = resampler (ac, false);
+ if (re) {
+ shared_ptr<const AudioBuffers> b = re->flush ();
+ if (b->frames ()) {
+ process_audio (earliest, b, ac->audio_length ());
+ }
}
}
- }
+#endif
- if (!earliest) {
+ if (dec && dec->dcp_time < earliest_time) {
+ earliest_piece = *i;
+ earliest_decoded = dec;
+ earliest_time = dec->dcp_time;
+ }
+
+ if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) {
+ earliest_audio = dec->dcp_time;
+ }
+ }
+
+ if (!earliest_piece) {
flush ();
return true;
}
- switch (type) {
- case VIDEO:
- if (earliest_t > _video_position) {
- emit_black ();
- } else {
- if (earliest->repeating ()) {
- earliest->repeat (this);
- } else {
- earliest->decoder->pass ();
- }
- }
- break;
-
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
- } else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
- }
- break;
+ if (earliest_audio != TIME_MAX) {
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (earliest_audio);
+ Audio (tb.audio, tb.time);
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
}
- if (_audio) {
- boost::optional<Time> audio_done_up_to;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
+ /* Emit the earliest thing */
+
+ shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+ shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+ shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded);
+
+ if (dv) {
+ if (!_just_did_inaccurate_seek && earliest_time > _video_position) {
+
+ /* See if we're inside some video content */
+ list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+ while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+ ++i;
}
- if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
+ if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+ /* We're outside all video content */
+ emit_black ();
+ } else {
+ _last_incoming_video.video->dcp_time = _video_position;
+ emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
}
+ } else {
+ emit_video (earliest_piece, dv);
+ earliest_piece->decoder->get ();
}
-
- if (audio_done_up_to) {
- TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ } else if (da) {
+ if (!_just_did_inaccurate_seek && earliest_time > _audio_position) {
+ emit_silence (earliest_time - _audio_position);
+ } else {
+ emit_audio (earliest_piece, da);
+ earliest_piece->decoder->get ();
}
+ } else if (ds) {
+ _in_subtitle.piece = earliest_piece;
+ _in_subtitle.subtitle = ds;
+ update_subtitle ();
+ earliest_piece->decoder->get ();
}
-
+
+ _just_did_inaccurate_seek = false;
+
return false;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
+ _last_incoming_video.video = video;
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -259,23 +210,24 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
+#if 0
+ XXX
if (frc.skip && (frame % 2) == 1) {
return;
}
+#endif
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
+ if (content->trimmed (video->dcp_time - content->position ())) {
return;
}
- Time const time = content->position() + relative_time + extra - content->trim_start ();
float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
shared_ptr<PlayerImage> pi (
new PlayerImage (
- image,
+ video->image,
content->crop(),
image_size,
_video_container_size,
@@ -283,32 +235,32 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
)
);
- if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ if (
+ _film->with_subtitles () &&
+ _out_subtitle.subtitle->image &&
+ video->dcp_time >= _out_subtitle.subtitle->dcp_time && video->dcp_time <= _out_subtitle.subtitle->dcp_time_to
+ ) {
Position<int> const container_offset (
(_video_container_size.width - image_size.width) / 2,
(_video_container_size.height - image_size.width) / 2
);
- pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
+ pi->set_subtitle (_out_subtitle.subtitle->image, _out_subtitle.position + container_offset);
}
#ifdef DCPOMATIC_DEBUG
_last_video = piece->content;
#endif
- Video (pi, eyes, content->colour_conversion(), same, time);
+ Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
_last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
-
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
- }
+ _video_position = rint (video->dcp_time + TIME_HZ / _film->video_frame_rate());
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
{
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -320,61 +272,54 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
/* Gain */
if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
gain->apply_gain (content->audio_gain ());
- audio = gain;
+ audio->data = gain;
}
/* Resample */
if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
+ audio->data = resampler(content, true)->run (audio->data);
}
- Time const relative_time = _film->audio_frames_to_time (frame);
-
- if (content->trimmed (relative_time)) {
+ if (content->trimmed (audio->dcp_time - content->position ())) {
return;
}
- Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
-
/* Remap channels */
- shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
+ shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames()));
dcp_mapped->make_silent ();
list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
- if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
- dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
+ if (i->first < audio->data->channels() && i->second < dcp_mapped->channels()) {
+ dcp_mapped->accumulate_channel (audio->data.get(), i->first, i->second);
}
}
- audio = dcp_mapped;
+ audio->data = dcp_mapped;
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
+ /* Delay */
+ audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+ if (audio->dcp_time < 0) {
+ int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+ if (frames >= audio->data->frames ()) {
return;
}
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+ trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
- audio = trimmed;
- time = 0;
+ audio->data = trimmed;
+ audio->dcp_time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ _audio_merger.push (audio->data, audio->dcp_time);
}
void
Player::flush ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
if (tb.audio) {
Audio (tb.audio, tb.time);
_audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -385,7 +330,7 @@ Player::flush ()
}
while (_audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ emit_silence (_video_position - _audio_position);
}
}
@@ -395,7 +340,7 @@ Player::flush ()
* @return true on error
*/
void
-Player::seek (Time t, bool accurate)
+Player::seek (DCPTime t, bool accurate)
{
if (!_have_valid_pieces) {
setup_pieces ();
@@ -406,89 +351,105 @@ Player::seek (Time t, bool accurate)
}
for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
- if (!vc) {
- continue;
- }
-
/* s is the offset of t from the start position of this content */
- Time s = t - vc->position ();
- s = max (static_cast<Time> (0), s);
- s = min (vc->length_after_trim(), s);
+ DCPTime s = t - (*i)->content->position ();
+ s = max (static_cast<DCPTime> (0), s);
+ s = min ((*i)->content->length_after_trim(), s);
- /* Hence set the piece positions to the `global' time */
- (*i)->video_position = (*i)->audio_position = vc->position() + s;
+ /* Convert this to the content time */
+ ContentTime ct = (s * (*i)->frc.speed_up) + (*i)->content->trim_start ();
/* And seek the decoder */
- dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
- vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
- );
-
- (*i)->reset_repeat ();
+ (*i)->decoder->seek (ct, accurate);
}
- _video_position = _audio_position = t;
+ _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate());
+ _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate());
- /* XXX: don't seek audio because we don't need to... */
+ _audio_merger.clear (_audio_position);
+
+ if (!accurate) {
+ /* We just did an inaccurate seek, so it's likely that the next thing seen
+ out of pass() will be a fair distance from _{video,audio}_position. Setting
+ this flag stops pass() from trying to fix that: we assume that if it
+ was an inaccurate seek then the caller does not care too much about
+ inserting black/silence to keep the time tidy.
+ */
+ _just_did_inaccurate_seek = true;
+ }
}
void
Player::setup_pieces ()
{
list<shared_ptr<Piece> > old_pieces = _pieces;
-
_pieces.clear ();
ContentList content = _playlist->content ();
- sort (content.begin(), content.end(), ContentSorter ());
for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
- shared_ptr<Piece> piece (new Piece (*i));
-
- /* XXX: into content? */
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
if (fc) {
- shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
-
- fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
- fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
-
- fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
- piece->decoder = fd;
+ decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio));
+ frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate());
}
shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i);
if (ic) {
- bool reusing = false;
-
/* See if we can re-use an old ImageDecoder */
for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder);
if (imd && imd->content() == ic) {
- piece = *j;
- reusing = true;
+ decoder = imd;
}
}
- if (!reusing) {
- shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic));
- id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
- piece->decoder = id;
+ if (!decoder) {
+ decoder.reset (new ImageDecoder (_film, ic));
}
+
+ frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate());
}
shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
if (sc) {
- shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
- sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
+ decoder.reset (new SndfileDecoder (_film, sc));
+
+ /* Working out the frc for this content is a bit tricky: what if it overlaps
+ two pieces of video content with different frame rates? For now, use
+ the one with the best overlap.
+ */
+
+ DCPTime best_overlap_t = 0;
+ shared_ptr<VideoContent> best_overlap;
+ for (ContentList::iterator j = content.begin(); j != content.end(); ++j) {
+ shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j);
+ if (!vc) {
+ continue;
+ }
+
+ DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
- piece->decoder = sd;
+ if (best_overlap) {
+ frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ());
+ } else {
+ /* No video overlap; e.g. if the DCP is just audio */
+ frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
}
- _pieces.push_back (piece);
+ decoder->seek ((*i)->trim_start (), true);
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
@@ -588,17 +549,17 @@ Player::emit_black ()
}
void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
{
if (most == 0) {
return;
}
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ DCPTime t = min (most, TIME_HZ / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
silence->make_silent ();
Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+ _audio_position += t;
}
void
@@ -615,18 +576,6 @@ Player::film_changed (Film::Property p)
}
void
-Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-{
- _in_subtitle.piece = weak_piece;
- _in_subtitle.image = image;
- _in_subtitle.rect = rect;
- _in_subtitle.from = from;
- _in_subtitle.to = to;
-
- update_subtitle ();
-}
-
-void
Player::update_subtitle ()
{
shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
@@ -634,15 +583,15 @@ Player::update_subtitle ()
return;
}
- if (!_in_subtitle.image) {
- _out_subtitle.image.reset ();
+ if (!_in_subtitle.subtitle->image) {
+ _out_subtitle.subtitle->image.reset ();
return;
}
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
+ dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect;
libdcp::Size scaled_size;
in_rect.y += sc->subtitle_offset ();
@@ -666,14 +615,15 @@ Player::update_subtitle ()
_out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
_out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
- _out_subtitle.image = _in_subtitle.image->scale (
+ _out_subtitle.subtitle->image = _in_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- _in_subtitle.image->pixel_format (),
+ _in_subtitle.subtitle->image->pixel_format (),
true
);
- _out_subtitle.from = _in_subtitle.from + piece->content->position ();
- _out_subtitle.to = _in_subtitle.to + piece->content->position ();
+
+ _out_subtitle.subtitle->dcp_time = _in_subtitle.subtitle->dcp_time;
+ _out_subtitle.subtitle->dcp_time = _in_subtitle.subtitle->dcp_time;
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -682,17 +632,13 @@ Player::update_subtitle ()
bool
Player::repeat_last_video ()
{
- if (!_last_incoming_video.image || !_have_valid_pieces) {
+ if (!_last_incoming_video.video || !_have_valid_pieces) {
return false;
}
- process_video (
+ emit_video (
_last_incoming_video.weak_piece,
- _last_incoming_video.image,
- _last_incoming_video.eyes,
- _last_incoming_video.same,
- _last_incoming_video.frame,
- _last_incoming_video.extra
+ _last_incoming_video.video
);
return true;
diff --git a/src/lib/player.h b/src/lib/player.h
index 11cc99e77..6e3f8187f 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -29,6 +29,7 @@
#include "rect.h"
#include "audio_merger.h"
#include "audio_content.h"
+#include "decoded.h"
class Job;
class Film;
@@ -38,22 +39,8 @@ class Piece;
class Image;
class Resampler;
-/** @class Player
- * @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
-{
-public:
- boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const Image> image;
- Eyes eyes;
- bool same;
- VideoContent::Frame frame;
- Time extra;
-};
-
-/** A wrapper for an Image which contains some pending operations; these may
+/** @class PlayerImage
+ * @brief A wrapper for an Image which contains some pending operations; these may
* not be necessary if the receiver of the PlayerImage throws it away.
*/
class PlayerImage
@@ -75,6 +62,10 @@ private:
Position<int> _subtitle_position;
};
+/** @class Player
+ * @brief A class which can `play' a Playlist; emitting its audio and video.
+ */
+
class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable
{
public:
@@ -84,9 +75,9 @@ public:
void disable_audio ();
bool pass ();
- void seek (Time, bool);
+ void seek (DCPTime, bool);
- Time video_position () const {
+ DCPTime video_position () const {
return _video_position;
}
@@ -101,10 +92,10 @@ public:
* Fourth parameter is true if the image is the same as the last one that was emitted.
* Fifth parameter is the time.
*/
- boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video;
+ boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video;
/** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio;
+ boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio;
/** Emitted when something has changed such that if we went back and emitted
* the last frame again it would look different. This is not emitted after
@@ -118,19 +109,18 @@ private:
friend class PlayerWrapper;
friend class Piece;
- void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time);
- void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
- void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
void setup_pieces ();
void playlist_changed ();
void content_changed (boost::weak_ptr<Content>, int, bool);
- void do_seek (Time, bool);
+ void do_seek (DCPTime, bool);
void flush ();
void emit_black ();
void emit_silence (OutputAudioFrame);
boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool);
void film_changed (Film::Property);
void update_subtitle ();
+ void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
+ void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
@@ -143,11 +133,11 @@ private:
std::list<boost::shared_ptr<Piece> > _pieces;
/** The time after the last video that we emitted */
- Time _video_position;
+ DCPTime _video_position;
/** The time after the last audio that we emitted */
- Time _audio_position;
+ DCPTime _audio_position;
- AudioMerger<Time, AudioContent::Frame> _audio_merger;
+ AudioMerger<DCPTime, AudioContent::Frame> _audio_merger;
libdcp::Size _video_container_size;
boost::shared_ptr<PlayerImage> _black_frame;
@@ -155,17 +145,12 @@ private:
struct {
boost::weak_ptr<Piece> piece;
- boost::shared_ptr<Image> image;
- dcpomatic::Rect<double> rect;
- Time from;
- Time to;
+ boost::shared_ptr<DecodedSubtitle> subtitle;
} _in_subtitle;
struct {
- boost::shared_ptr<Image> image;
Position<int> position;
- Time from;
- Time to;
+ boost::shared_ptr<DecodedSubtitle> subtitle;
} _out_subtitle;
#ifdef DCPOMATIC_DEBUG
@@ -174,7 +159,12 @@ private:
bool _last_emit_was_black;
- IncomingVideo _last_incoming_video;
+ struct {
+ boost::weak_ptr<Piece> weak_piece;
+ boost::shared_ptr<DecodedVideo> video;
+ } _last_incoming_video;
+
+ bool _just_did_inaccurate_seek;
boost::signals2::scoped_connection _playlist_changed_connection;
boost::signals2::scoped_connection _playlist_content_changed_connection;
diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc
index 37b290218..9fc62f8be 100644
--- a/src/lib/playlist.cc
+++ b/src/lib/playlist.cc
@@ -81,7 +81,7 @@ Playlist::maybe_sequence_video ()
_sequencing_video = true;
ContentList cl = _content;
- Time next = 0;
+ DCPTime next = 0;
for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
if (!dynamic_pointer_cast<VideoContent> (*i)) {
continue;
@@ -254,10 +254,10 @@ Playlist::best_dcp_frame_rate () const
return best->dcp;
}
-Time
+DCPTime
Playlist::length () const
{
- Time len = 0;
+ DCPTime len = 0;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
len = max (len, (*i)->end() + 1);
}
@@ -279,10 +279,10 @@ Playlist::reconnect ()
}
}
-Time
+DCPTime
Playlist::video_end () const
{
- Time end = 0;
+ DCPTime end = 0;
for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
if (dynamic_pointer_cast<const VideoContent> (*i)) {
end = max (end, (*i)->end ());
@@ -292,6 +292,23 @@ Playlist::video_end () const
return end;
}
+FrameRateChange
+Playlist::active_frame_rate_change (DCPTime t, int dcp_video_frame_rate) const
+{
+ for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
+ shared_ptr<const VideoContent> vc = dynamic_pointer_cast<const VideoContent> (*i);
+ if (!vc) {
+ break;
+ }
+
+ if (vc->position() >= t && t < vc->end()) {
+ return FrameRateChange (vc->video_frame_rate(), dcp_video_frame_rate);
+ }
+ }
+
+ return FrameRateChange (dcp_video_frame_rate, dcp_video_frame_rate);
+}
+
void
Playlist::set_sequence_video (bool s)
{
@@ -314,7 +331,7 @@ Playlist::content () const
void
Playlist::repeat (ContentList c, int n)
{
- pair<Time, Time> range (TIME_MAX, 0);
+ pair<DCPTime, DCPTime> range (TIME_MAX, 0);
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
range.first = min (range.first, (*i)->position ());
range.second = max (range.second, (*i)->position ());
@@ -322,7 +339,7 @@ Playlist::repeat (ContentList c, int n)
range.second = max (range.second, (*i)->end ());
}
- Time pos = range.second;
+ DCPTime pos = range.second;
for (int i = 0; i < n; ++i) {
for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
shared_ptr<Content> copy = (*i)->clone ();
@@ -355,7 +372,7 @@ Playlist::move_earlier (shared_ptr<Content> c)
return;
}
- Time const p = (*previous)->position ();
+ DCPTime const p = (*previous)->position ();
(*previous)->set_position (p + c->length_after_trim ());
c->set_position (p);
sort (_content.begin(), _content.end(), ContentSorter ());
@@ -382,7 +399,7 @@ Playlist::move_later (shared_ptr<Content> c)
return;
}
- Time const p = (*next)->position ();
+ DCPTime const p = (*next)->position ();
(*next)->set_position (c->position ());
c->set_position (p + c->length_after_trim ());
sort (_content.begin(), _content.end(), ContentSorter ());
diff --git a/src/lib/playlist.h b/src/lib/playlist.h
index f87b3397b..a985bf93d 100644
--- a/src/lib/playlist.h
+++ b/src/lib/playlist.h
@@ -25,6 +25,7 @@
#include <boost/enable_shared_from_this.hpp>
#include "ffmpeg_content.h"
#include "audio_mapping.h"
+#include "util.h"
class Content;
class FFmpegContent;
@@ -70,10 +71,11 @@ public:
std::string video_identifier () const;
- Time length () const;
+ DCPTime length () const;
int best_dcp_frame_rate () const;
- Time video_end () const;
+ DCPTime video_end () const;
+ FrameRateChange active_frame_rate_change (DCPTime, int dcp_frame_rate) const;
void set_sequence_video (bool);
void maybe_sequence_video ();
diff --git a/src/lib/resampler.cc b/src/lib/resampler.cc
index d897bf562..00121384d 100644
--- a/src/lib/resampler.cc
+++ b/src/lib/resampler.cc
@@ -64,11 +64,9 @@ Resampler::~Resampler ()
swr_free (&_swr_context);
}
-pair<shared_ptr<const AudioBuffers>, AudioContent::Frame>
-Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
+shared_ptr<const AudioBuffers>
+Resampler::run (shared_ptr<const AudioBuffers> in)
{
- AudioContent::Frame const resamp_time = swr_next_pts (_swr_context, frame * _out_rate) / _in_rate;
-
/* Compute the resampled frames count and add 32 for luck */
int const max_resampled_frames = ceil ((double) in->frames() * _out_rate / _in_rate) + 32;
shared_ptr<AudioBuffers> resampled (new AudioBuffers (_channels, max_resampled_frames));
@@ -84,7 +82,7 @@ Resampler::run (shared_ptr<const AudioBuffers> in, AudioContent::Frame frame)
}
resampled->set_frames (resampled_frames);
- return make_pair (resampled, resamp_time);
+ return resampled;
}
shared_ptr<const AudioBuffers>
diff --git a/src/lib/resampler.h b/src/lib/resampler.h
index 69ec83ba9..4ee11a7f0 100644
--- a/src/lib/resampler.h
+++ b/src/lib/resampler.h
@@ -33,7 +33,7 @@ public:
Resampler (int, int, int);
~Resampler ();
- std::pair<boost::shared_ptr<const AudioBuffers>, AudioContent::Frame> run (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ boost::shared_ptr<const AudioBuffers> run (boost::shared_ptr<const AudioBuffers>);
boost::shared_ptr<const AudioBuffers> flush ();
private:
diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc
index 89db865d5..1c872cf96 100644
--- a/src/lib/sndfile_content.cc
+++ b/src/lib/sndfile_content.cc
@@ -141,7 +141,7 @@ SndfileContent::as_xml (xmlpp::Node* node) const
_audio_mapping.as_xml (node->add_child("AudioMapping"));
}
-Time
+DCPTime
SndfileContent::full_length () const
{
shared_ptr<const Film> film = _film.lock ();
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
index 701ff16b2..c88764c1b 100644
--- a/src/lib/sndfile_content.h
+++ b/src/lib/sndfile_content.h
@@ -44,7 +44,7 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *) const;
- Time full_length () const;
+ DCPTime full_length () const;
/* AudioContent */
int audio_channels () const {
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index e10f4f568..3af683c57 100644
--- a/src/lib/sndfile_decoder.cc
+++ b/src/lib/sndfile_decoder.cc
@@ -55,9 +55,13 @@ SndfileDecoder::~SndfileDecoder ()
delete[] _deinterleave_buffer;
}
-void
+bool
SndfileDecoder::pass ()
{
+ if (_remaining == 0) {
+ return true;
+ }
+
/* Do things in half second blocks as I think there may be limits
to what FFmpeg (and in particular the resampler) can cope with.
*/
@@ -93,6 +97,8 @@ SndfileDecoder::pass ()
audio (data, _done);
_done += this_time;
_remaining -= this_time;
+
+ return true;
}
int
@@ -113,8 +119,10 @@ SndfileDecoder::audio_frame_rate () const
return _info.samplerate;
}
-bool
-SndfileDecoder::done () const
+void
+SndfileDecoder::seek (ContentTime t, bool accurate)
{
- return _audio_position >= _sndfile_content->audio_length ();
+ Decoder::seek (t, accurate);
+
+ /* XXX */
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index 77fa6d177..63f2f7dc4 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -29,14 +29,18 @@ public:
SndfileDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SndfileContent>);
~SndfileDecoder ();
- void pass ();
- bool done () const;
+ void seek (ContentTime, bool);
int audio_channels () const;
AudioContent::Frame audio_length () const;
int audio_frame_rate () const;
private:
+ bool has_audio () const {
+ return true;
+ }
+ bool pass ();
+
boost::shared_ptr<const SndfileContent> _sndfile_content;
SNDFILE* _sndfile;
SF_INFO _info;
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
index c06f3d718..389e4b13a 100644
--- a/src/lib/subtitle_decoder.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -21,6 +21,7 @@
#include "subtitle_decoder.h"
using boost::shared_ptr;
+using boost::optional;
SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
: Decoder (f)
@@ -33,7 +34,7 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, DCPTime from, DCPTime to)
{
- Subtitle (image, rect, from, to);
+ _pending.push_back (shared_ptr<DecodedSubtitle> (new DecodedSubtitle (image, rect, from, to)));
}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
index eeeadbd3f..4836e31fa 100644
--- a/src/lib/subtitle_decoder.h
+++ b/src/lib/subtitle_decoder.h
@@ -21,9 +21,10 @@
#include "decoder.h"
#include "rect.h"
#include "types.h"
+#include "decoded.h"
class Film;
-class TimedSubtitle;
+class DCPTimedSubtitle;
class Image;
class SubtitleDecoder : public virtual Decoder
@@ -31,8 +32,6 @@ class SubtitleDecoder : public virtual Decoder
public:
SubtitleDecoder (boost::shared_ptr<const Film>);
- boost::signals2::signal<void (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time)> Subtitle;
-
protected:
- void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, DCPTime, DCPTime);
};
diff --git a/src/lib/timer.cc b/src/lib/timer.cc
index 69a7e3aa9..a20a08b07 100644
--- a/src/lib/timer.cc
+++ b/src/lib/timer.cc
@@ -31,14 +31,14 @@
using namespace std;
/** @param n Name to use when giving output */
-PeriodTimer::PeriodTimer (string n)
+PeriodDCPTimer::PeriodDCPTimer (string n)
: _name (n)
{
gettimeofday (&_start, 0);
}
-/** Destroy PeriodTimer and output the time elapsed since its construction */
-PeriodTimer::~PeriodTimer ()
+/** Destroy PeriodDCPTimer and output the time elapsed since its construction */
+PeriodDCPTimer::~PeriodDCPTimer ()
{
struct timeval stop;
gettimeofday (&stop, 0);
@@ -48,7 +48,7 @@ PeriodTimer::~PeriodTimer ()
/** @param n Name to use when giving output.
* @param s Initial state.
*/
-StateTimer::StateTimer (string n, string s)
+StateDCPTimer::StateDCPTimer (string n, string s)
: _name (n)
{
struct timeval t;
@@ -59,7 +59,7 @@ StateTimer::StateTimer (string n, string s)
/** @param s New state that the caller is in */
void
-StateTimer::set_state (string s)
+StateDCPTimer::set_state (string s)
{
double const last = _time;
struct timeval t;
@@ -74,8 +74,8 @@ StateTimer::set_state (string s)
_state = s;
}
-/** Destroy StateTimer and generate a summary of the state timings on cout */
-StateTimer::~StateTimer ()
+/** Destroy StateDCPTimer and generate a summary of the state timings on cout */
+StateDCPTimer::~StateDCPTimer ()
{
if (_state.empty ()) {
return;
diff --git a/src/lib/timer.h b/src/lib/timer.h
index 4a5aa12de..8ec392aa6 100644
--- a/src/lib/timer.h
+++ b/src/lib/timer.h
@@ -29,16 +29,16 @@
#include <map>
#include <sys/time.h>
-/** @class PeriodTimer
+/** @class PeriodDCPTimer
* @brief A class to allow timing of a period within the caller.
*
* On destruction, it will output the time since its construction.
*/
-class PeriodTimer
+class PeriodDCPTimer
{
public:
- PeriodTimer (std::string n);
- ~PeriodTimer ();
+ PeriodDCPTimer (std::string n);
+ ~PeriodDCPTimer ();
private:
@@ -48,19 +48,19 @@ private:
struct timeval _start;
};
-/** @class StateTimer
+/** @class StateDCPTimer
* @brief A class to allow measurement of the amount of time a program
* spends in one of a set of states.
*
* Once constructed, the caller can call set_state() whenever
- * its state changes. When StateTimer is destroyed, it will
+ * its state changes. When StateDCPTimer is destroyed, it will
* output (to cout) a summary of the time spent in each state.
*/
-class StateTimer
+class StateDCPTimer
{
public:
- StateTimer (std::string n, std::string s);
- ~StateTimer ();
+ StateDCPTimer (std::string n, std::string s);
+ ~StateDCPTimer ();
void set_state (std::string s);
diff --git a/src/lib/types.h b/src/lib/types.h
index 448b6c154..1ab6a94d4 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -38,9 +38,10 @@ class AudioBuffers;
*/
#define SERVER_LINK_VERSION 1
-typedef int64_t Time;
+typedef int64_t DCPTime;
#define TIME_MAX INT64_MAX
-#define TIME_HZ ((Time) 96000)
+#define TIME_HZ ((DCPTime) 96000)
+typedef int64_t ContentTime;
typedef int64_t OutputAudioFrame;
typedef int OutputVideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index ddc0a2974..381c47a9a 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -772,7 +772,7 @@ audio_channel_name (int c)
return channels[c];
}
-FrameRateConversion::FrameRateConversion (float source, int dcp)
+FrameRateChange::FrameRateChange (float source, int dcp)
: skip (false)
, repeat (1)
, change_speed (false)
@@ -790,7 +790,8 @@ FrameRateConversion::FrameRateConversion (float source, int dcp)
repeat = round (dcp / source);
}
- change_speed = !about_equal (source * factor(), dcp);
+ speed_up = dcp / (source * factor());
+ change_speed = !about_equal (speed_up, 1.0);
if (!skip && repeat == 1 && !change_speed) {
description = _("Content and DCP have the same rate.\n");
@@ -914,3 +915,10 @@ fit_ratio_within (float ratio, libdcp::Size full_frame)
return libdcp::Size (full_frame.width, rint (full_frame.width / ratio));
}
+
+DCPTime
+time_round_up (DCPTime t, DCPTime nearest)
+{
+ DCPTime const a = t + nearest - 1;
+ return a - (a % nearest);
+}
diff --git a/src/lib/util.h b/src/lib/util.h
index 7dcd920b7..892b473f7 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -79,9 +79,9 @@ extern std::string tidy_for_filename (std::string);
extern boost::shared_ptr<const libdcp::Signer> make_signer ();
extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
-struct FrameRateConversion
+struct FrameRateChange
{
- FrameRateConversion (float, int);
+ FrameRateChange (float, int);
/** @return factor by which to multiply a source frame rate
to get the effective rate after any skip or repeat has happened.
@@ -109,11 +109,17 @@ struct FrameRateConversion
*/
bool change_speed;
+ /** Amount by which the video is being sped-up in the DCP; e.g. for a
+ * 24fps source in a 25fps DCP this would be 25/24.
+ */
+ float speed_up;
+
std::string description;
};
extern int dcp_audio_frame_rate (int);
extern int stride_round_up (int, int const *, int);
+extern DCPTime time_round_up (DCPTime, DCPTime);
extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index 0a19ffd69..ebec8b6db 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -59,7 +59,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f)
setup_default_colour_conversion ();
}
-VideoContent::VideoContent (shared_ptr<const Film> f, Time s, VideoContent::Frame len)
+VideoContent::VideoContent (shared_ptr<const Film> f, DCPTime s, VideoContent::Frame len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
@@ -351,19 +351,21 @@ VideoContent::video_size_after_crop () const
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding frame index, rounded up so that the frame index
+ * is that of the next complete frame which starts after `t'.
*/
VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+VideoContent::time_to_content_video_frames (DCPTime t) const
{
shared_ptr<const Film> film = _film.lock ();
assert (film);
- FrameRateConversion frc (video_frame_rate(), film->video_frame_rate());
-
/* Here we are converting from time (in the DCP) to a frame number in the content.
Hence we need to use the DCP's frame rate and the double/skip correction, not
- the source's rate.
+ the source's rate; source rate will be equal to DCP rate if we ignore
+ double/skip. There's no need to call Film::active_frame_rate_change() here
+ as we know that we are it (since we're video).
*/
- return t * film->video_frame_rate() / (frc.factor() * TIME_HZ);
+ FrameRateChange frc (video_frame_rate(), film->video_frame_rate());
+ return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ));
}
diff --git a/src/lib/video_content.h b/src/lib/video_content.h
index effca5c61..f008143fa 100644
--- a/src/lib/video_content.h
+++ b/src/lib/video_content.h
@@ -43,7 +43,7 @@ public:
typedef int Frame;
VideoContent (boost::shared_ptr<const Film>);
- VideoContent (boost::shared_ptr<const Film>, Time, VideoContent::Frame);
+ VideoContent (boost::shared_ptr<const Film>, DCPTime, VideoContent::Frame);
VideoContent (boost::shared_ptr<const Film>, boost::filesystem::path);
VideoContent (boost::shared_ptr<const Film>, boost::shared_ptr<const cxml::Node>);
VideoContent (boost::shared_ptr<const Film>, std::vector<boost::shared_ptr<Content> >);
@@ -123,7 +123,7 @@ public:
libdcp::Size video_size_after_3d_split () const;
libdcp::Size video_size_after_crop () const;
- VideoContent::Frame time_to_content_video_frames (Time) const;
+ VideoContent::Frame time_to_content_video_frames (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index eaa4534e4..3a8891111 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -24,31 +24,29 @@
using std::cout;
using boost::shared_ptr;
+using boost::optional;
VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c)
: Decoder (f)
, _video_content (c)
- , _video_position (0)
{
}
+/** Called by subclasses when they have a video frame ready */
void
-VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame)
+VideoDecoder::video (shared_ptr<const Image> image, bool same, ContentTime time)
{
switch (_video_content->video_frame_type ()) {
case VIDEO_FRAME_TYPE_2D:
- Video (image, EYES_BOTH, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image, EYES_BOTH, same, time)));
break;
case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT:
{
int const half = image->size().width / 2;
- Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, time)));
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, time)));
break;
}
}
-
- _video_position = frame + 1;
}
-
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 142320a04..d8c362354 100644
--- a/src/lib/video_decoder.h
+++ b/src/lib/video_decoder.h
@@ -25,6 +25,7 @@
#include "decoder.h"
#include "video_content.h"
#include "util.h"
+#include "decoded.h"
class VideoContent;
class Image;
@@ -34,24 +35,14 @@ class VideoDecoder : public virtual Decoder
public:
VideoDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const VideoContent>);
- /** Seek so that the next pass() will yield (approximately) the requested frame.
- * Pass accurate = true to try harder to get close to the request.
- */
- virtual void seek (VideoContent::Frame frame, bool accurate) = 0;
-
- /** Emitted when a video frame is ready.
- * First parameter is the video image.
- * Second parameter is the eye(s) which should see this image.
- * Third parameter is true if the image is the same as the last one that was emitted for this Eyes value.
- * Fourth parameter is the frame within our source.
- */
- boost::signals2::signal<void (boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame)> Video;
-
+ boost::shared_ptr<const VideoContent> video_content () const {
+ return _video_content;
+ }
+
protected:
- void video (boost::shared_ptr<const Image>, bool, VideoContent::Frame);
+ void video (boost::shared_ptr<const Image>, bool, ContentTime);
boost::shared_ptr<const VideoContent> _video_content;
- VideoContent::Frame _video_position;
};
#endif