summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2014-01-15 21:23:33 +0000
committerCarl Hetherington <cth@carlh.net>2014-01-15 21:23:33 +0000
commit8353a009aae1a604251c0160193c39741c2fa27c (patch)
tree558d2b1951f2b4f05f0ab93b46d10afd0ebee929 /src/lib
parentd0bca7d33b8101cd87f78d5a76b2512dea3988f8 (diff)
parent4e5d5c7dcc6470b8dc918d03a00e30c07df60efe (diff)
Merge 1.0-seek and subtitle-content.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/analyse_audio_job.cc4
-rw-r--r--src/lib/analyse_audio_job.h4
-rw-r--r--src/lib/audio_content.cc26
-rw-r--r--src/lib/audio_content.h6
-rw-r--r--src/lib/audio_decoder.cc53
-rw-r--r--src/lib/audio_decoder.h20
-rw-r--r--src/lib/audio_merger.h11
-rw-r--r--src/lib/content.cc25
-rw-r--r--src/lib/content.h30
-rw-r--r--src/lib/decoded.h145
-rw-r--r--src/lib/decoder.cc41
-rw-r--r--src/lib/decoder.h24
-rw-r--r--src/lib/encoder.cc2
-rw-r--r--src/lib/ffmpeg.cc4
-rw-r--r--src/lib/ffmpeg_content.cc15
-rw-r--r--src/lib/ffmpeg_content.h4
-rw-r--r--src/lib/ffmpeg_decoder.cc270
-rw-r--r--src/lib/ffmpeg_decoder.h16
-rw-r--r--src/lib/ffmpeg_examiner.cc4
-rw-r--r--src/lib/ffmpeg_examiner.h2
-rw-r--r--src/lib/film.cc28
-rw-r--r--src/lib/film.h15
-rw-r--r--src/lib/filter_graph.cc3
-rw-r--r--src/lib/image.cc27
-rw-r--r--src/lib/image_content.cc6
-rw-r--r--src/lib/image_content.h4
-rw-r--r--src/lib/image_decoder.cc23
-rw-r--r--src/lib/image_decoder.h9
-rw-r--r--src/lib/image_examiner.h4
-rw-r--r--src/lib/job.cc2
-rw-r--r--src/lib/player.cc591
-rw-r--r--src/lib/player.h116
-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.cc6
-rw-r--r--src/lib/sndfile_content.h6
-rw-r--r--src/lib/sndfile_decoder.cc22
-rw-r--r--src/lib/sndfile_decoder.h11
-rw-r--r--src/lib/subrip.cc6
-rw-r--r--src/lib/subrip.h4
-rw-r--r--src/lib/subrip_content.cc2
-rw-r--r--src/lib/subrip_content.h4
-rw-r--r--src/lib/subrip_decoder.cc13
-rw-r--r--src/lib/subrip_decoder.h3
-rw-r--r--src/lib/subrip_subtitle.h4
-rw-r--r--src/lib/subtitle_decoder.cc7
-rw-r--r--src/lib/subtitle_decoder.h8
-rw-r--r--src/lib/transcode_job.cc2
-rw-r--r--src/lib/transcoder.cc5
-rw-r--r--src/lib/transcoder.h1
-rw-r--r--src/lib/types.h9
-rw-r--r--src/lib/util.cc14
-rw-r--r--src/lib/util.h12
-rw-r--r--src/lib/video_content.cc20
-rw-r--r--src/lib/video_content.h8
-rw-r--r--src/lib/video_decoder.cc20
-rw-r--r--src/lib/video_decoder.h21
-rw-r--r--src/lib/video_examiner.h2
60 files changed, 1064 insertions, 731 deletions
diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
index 8186f9de4..872947b55 100644
--- a/src/lib/analyse_audio_job.cc
+++ b/src/lib/analyse_audio_job.cc
@@ -69,7 +69,7 @@ AnalyseAudioJob::run ()
_analysis.reset (new AudioAnalysis (_film->audio_channels ()));
_done = 0;
- OutputAudioFrame const len = _film->time_to_audio_frames (_film->length ());
+ AudioFrame const len = _film->time_to_audio_frames (_film->length ());
while (!player->pass ()) {
set_progress (double (_done) / len);
}
@@ -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..6ed236d85 100644
--- a/src/lib/analyse_audio_job.h
+++ b/src/lib/analyse_audio_job.h
@@ -33,10 +33,10 @@ 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;
+ AudioFrame _done;
int64_t _samples_per_point;
std::vector<AudioPoint> _current;
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index b4c4f34b6..3c0d13ba9 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -40,7 +40,7 @@ int const AudioContentProperty::AUDIO_GAIN = 203;
int const AudioContentProperty::AUDIO_DELAY = 204;
int const AudioContentProperty::AUDIO_MAPPING = 205;
-AudioContent::AudioContent (shared_ptr<const Film> f, Time s)
+AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
: Content (f, s)
, _audio_gain (0)
, _audio_delay (Config::instance()->default_audio_delay ())
@@ -149,3 +149,27 @@ AudioContent::technical_summary () const
{
return String::compose ("audio: channels %1, length %2, raw rate %3, out rate %4", audio_channels(), audio_length(), content_audio_frame_rate(), output_audio_frame_rate());
}
+
+/** Note: this is not particularly fast, as the FrameRateChange lookup
+ * is not very intelligent.
+ *
+ * @param t Some duration to convert.
+ * @param at The time within the DCP to get the active frame rate change from; i.e. a point at which
+ * the `controlling' video content is active.
+ */
+AudioFrame
+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..0b2ee2e46 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> >);
@@ -52,7 +52,7 @@ public:
std::string technical_summary () const;
virtual int audio_channels () const = 0;
- virtual AudioContent::Frame audio_length () const = 0;
+ virtual AudioFrame 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;
@@ -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..8d3b0e128 100644
--- a/src/lib/audio_decoder.cc
+++ b/src/lib/audio_decoder.cc
@@ -22,6 +22,8 @@
#include "exceptions.h"
#include "log.h"
#include "resampler.h"
+#include "util.h"
+#include "film.h"
#include "i18n.h"
@@ -35,24 +37,53 @@ using boost::shared_ptr;
AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content)
: Decoder (film)
, _audio_content (content)
- , _audio_position (0)
{
+ if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) {
+ _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ()));
+ }
+}
+
+/** Audio timestamping is made hard by many factors, but the final nail in the coffin is resampling.
+ * We have to assume that we are feeding continuous data into the resampler, and so we get continuous
+ * data out. Hence we do the timestamping here, post-resampler, just by counting samples.
+ *
+ * The time is passed in here so that after a seek we can set up our _audio_position. The
+ * time is ignored once this has been done.
+ */
+void
+AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time)
+{
+ if (_resampler) {
+ data = _resampler->run (data);
+ }
+ if (!_audio_position) {
+ shared_ptr<const Film> film = _film.lock ();
+ assert (film);
+ FrameRateChange frc = film->active_frame_rate_change (_audio_content->position ());
+ _audio_position = (double (time) / frc.speed_up) * film->audio_frame_rate() / TIME_HZ;
+ }
+
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, _audio_position.get ())));
+ _audio_position = _audio_position.get() + data->frames ();
}
void
-AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame)
+AudioDecoder::flush ()
{
- Audio (data, frame);
- _audio_position = frame + data->frames ();
+ if (!_resampler) {
+ return;
+ }
+
+ shared_ptr<const AudioBuffers> b = _resampler->flush ();
+ if (b) {
+ _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (b, _audio_position.get ())));
+ _audio_position = _audio_position.get() + b->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
+void
+AudioDecoder::seek (ContentTime, bool)
{
- return _audio_content->audio_channels () > 0;
+ _audio_position.reset ();
}
diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h
index ab6c4b8a9..bb3aafccd 100644
--- a/src/lib/audio_decoder.h
+++ b/src/lib/audio_decoder.h
@@ -27,8 +27,10 @@
#include "decoder.h"
#include "content.h"
#include "audio_content.h"
+#include "decoded.h"
class AudioBuffers;
+class Resampler;
/** @class AudioDecoder.
* @brief Parent class for audio decoders.
@@ -37,17 +39,21 @@ class AudioDecoder : public virtual Decoder
{
public:
AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>);
+
+ boost::shared_ptr<const AudioContent> audio_content () const {
+ return _audio_content;
+ }
- bool has_audio () const;
-
- /** Emitted when some audio data is ready */
- boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio;
-
+ void seek (ContentTime time, bool accurate);
+
protected:
- void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame);
+ void audio (boost::shared_ptr<const AudioBuffers>, ContentTime);
+ void flush ();
+
boost::shared_ptr<const AudioContent> _audio_content;
- AudioContent::Frame _audio_position;
+ boost::shared_ptr<Resampler> _resampler;
+ boost::optional<AudioFrame> _audio_position;
};
#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 ccca46bc0..ea1c19acd 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);
@@ -161,7 +161,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);
@@ -172,7 +172,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);
@@ -204,21 +204,12 @@ 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();
}
-/** @param t A time relative to the start of this content (not the position).
- * @return true if this time is trimmed by our trim settings.
- */
-bool
-Content::trimmed (Time t) const
-{
- return (t < trim_start() || t > (full_length() - trim_end ()));
-}
-
/** @return string which includes everything about how this content affects
* its playlist.
*/
diff --git a/src/lib/content.h b/src/lib/content.h
index 4ee7c267f..3172b9c8d 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,42 +92,40 @@ 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;
-
boost::signals2::signal<void (boost::weak_ptr<Content>, int, bool)> Changed;
protected:
@@ -145,9 +143,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..f4ebe0dbd
--- /dev/null
+++ b/src/lib/decoded.h
@@ -0,0 +1,145 @@
+/*
+ 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 <libdcp/subtitle_asset.h>
+#include "types.h"
+#include "rect.h"
+#include "util.h"
+
+class Image;
+
+class Decoded
+{
+public:
+ Decoded ()
+ : dcp_time (0)
+ {}
+
+ virtual ~Decoded () {}
+
+ virtual void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange, DCPTime) = 0;
+
+ DCPTime dcp_time;
+};
+
+/** One frame of video from a VideoDecoder */
+class DecodedVideo : public Decoded
+{
+public:
+ DecodedVideo ()
+ : eyes (EYES_BOTH)
+ , same (false)
+ , frame (0)
+ {}
+
+ DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, VideoFrame f)
+ : image (im)
+ , eyes (e)
+ , same (s)
+ , frame (f)
+ {}
+
+ void set_dcp_times (VideoFrame video_frame_rate, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ dcp_time = frame * TIME_HZ * frc.factor() / video_frame_rate + offset;
+ }
+
+ boost::shared_ptr<const Image> image;
+ Eyes eyes;
+ bool same;
+ VideoFrame frame;
+};
+
+class DecodedAudio : public Decoded
+{
+public:
+ DecodedAudio (boost::shared_ptr<const AudioBuffers> d, AudioFrame f)
+ : data (d)
+ , frame (f)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame audio_frame_rate, FrameRateChange, DCPTime offset)
+ {
+ dcp_time = frame * TIME_HZ / audio_frame_rate + offset;
+ }
+
+ boost::shared_ptr<const AudioBuffers> data;
+ AudioFrame frame;
+};
+
+class DecodedImageSubtitle : public Decoded
+{
+public:
+ DecodedImageSubtitle ()
+ : content_time (0)
+ , content_time_to (0)
+ , dcp_time_to (0)
+ {}
+
+ DecodedImageSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t)
+ : image (im)
+ , rect (r)
+ , content_time (f)
+ , content_time_to (t)
+ , dcp_time_to (0)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ dcp_time = rint (content_time / frc.speed_up) + offset;
+ dcp_time_to = rint (content_time_to / frc.speed_up) + offset;
+ }
+
+ boost::shared_ptr<Image> image;
+ dcpomatic::Rect<double> rect;
+ ContentTime content_time;
+ ContentTime content_time_to;
+ DCPTime dcp_time_to;
+};
+
+class DecodedTextSubtitle : public Decoded
+{
+public:
+ DecodedTextSubtitle ()
+ : dcp_time_to (0)
+ {}
+
+ DecodedTextSubtitle (std::list<libdcp::Subtitle> s)
+ : subs (s)
+ {}
+
+ void set_dcp_times (VideoFrame, AudioFrame, FrameRateChange frc, DCPTime offset)
+ {
+ if (subs.empty ()) {
+ return;
+ }
+
+ /* Assuming that all subs are at the same time */
+ dcp_time = rint (subs.front().in().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ dcp_time_to = rint (subs.front().out().to_ticks() * 4 * TIME_HZ / frc.speed_up) + offset;
+ }
+
+ std::list<libdcp::Subtitle> subs;
+ DCPTime dcp_time_to;
+};
+
+#endif
diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc
index 3f4cda6eb..53a0c31e1 100644
--- a/src/lib/decoder.cc
+++ b/src/lib/decoder.cc
@@ -23,9 +23,11 @@
#include "film.h"
#include "decoder.h"
+#include "decoded.h"
#include "i18n.h"
+using std::cout;
using boost::shared_ptr;
/** @param f Film.
@@ -33,6 +35,45 @@ using boost::shared_ptr;
*/
Decoder::Decoder (shared_ptr<const Film> f)
: _film (f)
+ , _done (false)
{
}
+
+struct DecodedSorter
+{
+ bool operator() (shared_ptr<Decoded> a, shared_ptr<Decoded> b)
+ {
+ return a->dcp_time < b->dcp_time;
+ }
+};
+
+shared_ptr<Decoded>
+Decoder::peek ()
+{
+ while (!_done && _pending.empty ()) {
+ _done = pass ();
+ }
+
+ if (_done && _pending.empty ()) {
+ return shared_ptr<Decoded> ();
+ }
+
+ _pending.sort (DecodedSorter ());
+ return _pending.front ();
+}
+
+void
+Decoder::consume ()
+{
+ if (!_pending.empty ()) {
+ _pending.pop_front ();
+ }
+}
+
+void
+Decoder::seek (ContentTime, bool)
+{
+ _pending.clear ();
+ _done = false;
+}
diff --git a/src/lib/decoder.h b/src/lib/decoder.h
index d67592ed8..6646b0e76 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,32 @@ public:
Decoder (boost::shared_ptr<const Film>);
virtual ~Decoder () {}
- /** Perform one decode pass of the content, which may or may not
- * cause the object to emit some data.
+ /** Seek so that the next get_*() will yield the next thing
+ * (video/sound frame, subtitle etc.) at or after the requested
+ * time. Pass accurate = true to try harder to get close to
+ * the request.
*/
- virtual void pass () = 0;
- virtual bool done () const = 0;
+ virtual void seek (ContentTime time, bool accurate);
+
+ boost::shared_ptr<Decoded> peek ();
+ void consume ();
protected:
+ /** Perform one decode pass of the content, which may or may not
+ * result in a complete quantum (Decoded object) of decoded stuff
+ * being made ready.
+ * @return true if the decoder is done (i.e. no more data will be
+ * produced by any future calls to pass() without a seek() first).
+ */
+ virtual bool pass () = 0;
virtual void flush () {};
/** The Film that we are decoding in */
boost::weak_ptr<const Film> _film;
+
+ std::list<boost::shared_ptr<Decoded> > _pending;
+ bool _done;
};
#endif
diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc
index fbec3e4d0..d26f77614 100644
--- a/src/lib/encoder.cc
+++ b/src/lib/encoder.cc
@@ -215,7 +215,7 @@ Encoder::process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversi
TIMING ("adding to queue of %1", _queue.size ());
_queue.push_back (shared_ptr<DCPVideoFrame> (
new DCPVideoFrame (
- image->image(), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
+ image->image(PIX_FMT_RGB24, false), _video_frames_out, eyes, conversion, _film->video_frame_rate(),
_film->j2k_bandwidth(), _film->resolution(), _film->log()
)
));
diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc
index d3653e311..4bf941523 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 f59551d1d..394c16aa5 100644
--- a/src/lib/ffmpeg_content.cc
+++ b/src/lib/ffmpeg_content.cc
@@ -163,7 +163,7 @@ FFmpegContent::examine (shared_ptr<Job> job)
shared_ptr<FFmpegExaminer> examiner (new FFmpegExaminer (shared_from_this ()));
- VideoContent::Frame video_length = 0;
+ VideoFrame video_length = 0;
video_length = examiner->video_length ();
film->log()->log (String::compose ("Video length obtained from header as %1 frames", video_length));
@@ -262,12 +262,12 @@ FFmpegContent::set_audio_stream (shared_ptr<FFmpegAudioStream> s)
signal_changed (FFmpegContentProperty::AUDIO_STREAM);
}
-AudioContent::Frame
+AudioFrame
FFmpegContent::audio_length () const
{
int const cafr = content_audio_frame_rate ();
int const vfr = video_frame_rate ();
- VideoContent::Frame const vl = video_length ();
+ VideoFrame const vl = video_length ();
boost::mutex::scoped_lock lm (_mutex);
if (!_audio_stream) {
@@ -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..d9037b0d8 100644
--- a/src/lib/ffmpeg_content.h
+++ b/src/lib/ffmpeg_content.h
@@ -134,13 +134,13 @@ 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;
/* AudioContent */
int audio_channels () const;
- AudioContent::Frame audio_length () const;
+ AudioFrame audio_length () const;
int content_audio_frame_rate () const;
int output_audio_frame_rate () const;
AudioMapping audio_mapping () const;
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index a051bcd12..55f4eb5c6 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -68,9 +68,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
, _subtitle_codec (0)
, _decode_video (video)
, _decode_audio (audio)
- , _video_pts_offset (0)
- , _audio_pts_offset (0)
- , _just_sought (false)
+ , _pts_offset (0)
{
setup_subtitle ();
@@ -83,27 +81,25 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
Then we remove big initial gaps in PTS and we allow our
insertion of black frames to work.
- We will do:
- audio_pts_to_use = audio_pts_from_ffmpeg + audio_pts_offset;
- video_pts_to_use = video_pts_from_ffmpeg + video_pts_offset;
+ We will do pts_to_use = pts_from_ffmpeg + pts_offset;
*/
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 */
if (have_audio && have_video) {
- _video_pts_offset = _audio_pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
+ _pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
} else if (have_video) {
- _video_pts_offset = - c->first_video().get();
+ _pts_offset = - c->first_video().get();
} else if (have_audio) {
- _audio_pts_offset = - c->audio_stream()->first_audio.get();
+ _pts_offset = - c->audio_stream()->first_audio.get();
}
/* Now adjust both so that the video pts starts on a frame */
if (have_video && have_audio) {
- double first_video = c->first_video().get() + _video_pts_offset;
+ double first_video = c->first_video().get() + _pts_offset;
double const old_first_video = first_video;
/* Round the first video up to a frame boundary */
@@ -111,8 +107,7 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
}
- _video_pts_offset += first_video - old_first_video;
- _audio_pts_offset += first_video - old_first_video;
+ _pts_offset += first_video - old_first_video;
}
}
@@ -143,12 +138,10 @@ FFmpegDecoder::flush ()
decode_audio_packet ();
}
- /* Stop us being asked for any more data */
- _video_position = _ffmpeg_content->video_length ();
- _audio_position = _ffmpeg_content->audio_length ();
+ AudioDecoder::flush ();
}
-void
+bool
FFmpegDecoder::pass ()
{
int r = av_read_frame (_format_context, &_packet);
@@ -164,7 +157,7 @@ FFmpegDecoder::pass ()
}
flush ();
- return;
+ return true;
}
shared_ptr<const Film> film = _film.lock ();
@@ -181,6 +174,7 @@ FFmpegDecoder::pass ()
}
av_free_packet (&_packet);
+ return false;
}
/** @param data pointer to array of pointers to buffers.
@@ -295,68 +289,135 @@ FFmpegDecoder::bytes_per_audio_sample () const
return av_get_bytes_per_sample (audio_sample_format ());
}
-void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+int
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
{
- double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
+ int frames_read = 0;
+ optional<ContentTime> last_video;
+ optional<ContentTime> last_audio;
- /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being
- a number plucked from the air) earlier than we want to end up. The loop below
- will hopefully then step through to where we want to be.
- */
- int initial = frame;
+ while (!finished (last_video, last_audio, frames_read)) {
+ int r = av_read_frame (_format_context, &_packet);
+ if (r < 0) {
+ /* We should flush our decoders here, possibly yielding a few more frames,
+ but the consequence of having to do that is too hideous to contemplate.
+ Instead we give up and say that you can't seek too close to the end
+ of a file.
+ */
+ return frames_read;
+ }
- if (accurate) {
- initial -= 5;
+ ++frames_read;
+
+ double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
+
+ if (_packet.stream_index == _video_stream) {
+
+ 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 + _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 + _pts_offset) * TIME_HZ
+ );
+ }
+
+ copy_packet.data += r;
+ copy_packet.size -= r;
+ }
+ }
+
+ av_free_packet (&_packet);
}
- if (initial < 0) {
- initial = 0;
+ return frames_read;
+}
+
+bool
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
+{
+ return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
+}
+
+bool
+FFmpegDecoder::seek_final_finished (int n, int done) const
+{
+ return n == done;
+}
+
+void
+FFmpegDecoder::seek_and_flush (ContentTime t)
+{
+ int64_t s = ((double (t) / TIME_HZ) - _pts_offset) /
+ av_q2d (_format_context->streams[_video_stream]->time_base);
+
+ if (_ffmpeg_content->audio_stream ()) {
+ s = min (
+ s, int64_t (
+ ((double (t) / TIME_HZ) - _pts_offset) /
+ av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)
+ )
+ );
}
- /* Initial seek time in the stream's timebase */
- int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base;
+ /* Ridiculous empirical hack */
+ s--;
- av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
+ av_seek_frame (_format_context, _video_stream, s, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers (video_codec_context());
+ if (audio_codec_context ()) {
+ avcodec_flush_buffers (audio_codec_context ());
+ }
if (_subtitle_codec_context) {
avcodec_flush_buffers (_subtitle_codec_context);
}
+}
- _just_sought = true;
- _video_position = frame;
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
+{
+ Decoder::seek (time, accurate);
+ AudioDecoder::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;
}
- while (1) {
- int r = av_read_frame (_format_context, &_packet);
- if (r < 0) {
- return;
- }
+ /* Initial seek time in the video stream's timebase */
- if (_packet.stream_index != _video_stream) {
- av_free_packet (&_packet);
- continue;
- }
-
- 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()
- );
+ seek_and_flush (initial_seek);
- if (_video_position >= (frame - 1)) {
- av_free_packet (&_packet);
- break;
- }
- }
-
- av_free_packet (&_packet);
+ if (!accurate) {
+ /* That'll do */
+ return;
+ }
+
+ 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));
}
}
@@ -373,6 +434,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);
@@ -381,31 +443,17 @@ 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 ct = (
+ av_frame_get_best_effort_timestamp (_frame) *
+ av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base)
+ + _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), ct);
}
copy_packet.data += decode_result;
@@ -454,45 +502,9 @@ 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);
- }
-
+ double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
+ VideoFrame const f = rint (pts * _ffmpeg_content->video_frame_rate ());
+ video (image, false, f);
} else {
shared_ptr<const Film> film = _film.lock ();
assert (film);
@@ -525,14 +537,6 @@ FFmpegDecoder::setup_subtitle ()
}
}
-bool
-FFmpegDecoder::done () const
-{
- bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
- bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
- return vd && ad;
-}
-
void
FFmpegDecoder::decode_subtitle_packet ()
{
@@ -555,11 +559,11 @@ FFmpegDecoder::decode_subtitle_packet ()
/* Subtitle PTS in seconds (within the source, not taking into account any of the
source that we may have chopped off for the DCP)
*/
- double const packet_time = (static_cast<double> (sub.pts ) / AV_TIME_BASE) + _video_pts_offset;
-
+ double const packet_time = (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
+
/* hence start time for this sub */
- Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
- Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
+ ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
AVSubtitleRect const * rect = sub.rects[0];
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index 11f83ed97..ee725b20c 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, boost::optional<ContentTime>, boost::optional<ContentTime>) const;
+ bool seek_final_finished (int, int) const;
+ int minimal_run (boost::function<bool (boost::optional<ContentTime>, boost::optional<ContentTime>, int)>);
+ void seek_and_flush (int64_t);
+
AVCodecContext* _subtitle_codec_context; ///< may be 0 if there is no subtitle
AVCodec* _subtitle_codec; ///< may be 0 if there is no subtitle
@@ -83,7 +85,5 @@ private:
bool _decode_video;
bool _decode_audio;
- double _video_pts_offset;
- double _audio_pts_offset;
- bool _just_sought;
+ double _pts_offset;
};
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
index a63090d12..38dd678bb 100644
--- a/src/lib/ffmpeg_examiner.cc
+++ b/src/lib/ffmpeg_examiner.cc
@@ -134,10 +134,10 @@ FFmpegExaminer::video_size () const
}
/** @return Length (in video frames) according to our content's header */
-VideoContent::Frame
+VideoFrame
FFmpegExaminer::video_length () const
{
- VideoContent::Frame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
+ VideoFrame const length = (double (_format_context->duration) / AV_TIME_BASE) * video_frame_rate();
return max (1, length);
}
diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h
index 4de475d2a..2dd8b2e34 100644
--- a/src/lib/ffmpeg_examiner.h
+++ b/src/lib/ffmpeg_examiner.h
@@ -31,7 +31,7 @@ public:
float video_frame_rate () const;
libdcp::Size video_size () const;
- VideoContent::Frame video_length () const;
+ VideoFrame video_length () const;
std::vector<boost::shared_ptr<FFmpegSubtitleStream> > subtitle_streams () const {
return _subtitle_streams;
diff --git a/src/lib/film.cc b/src/lib/film.cc
index 1290cbda2..099bacfdc 100644
--- a/src/lib/film.cc
+++ b/src/lib/film.cc
@@ -853,7 +853,7 @@ Film::move_content_later (shared_ptr<Content> c)
_playlist->move_later (c);
}
-Time
+DCPTime
Film::length () const
{
return _playlist->length ();
@@ -865,12 +865,18 @@ Film::has_subtitles () const
return _playlist->has_subtitles ();
}
-OutputVideoFrame
+VideoFrame
Film::best_video_frame_rate () const
{
return _playlist->best_dcp_frame_rate ();
}
+FrameRateChange
+Film::active_frame_rate_change (DCPTime t) const
+{
+ return _playlist->active_frame_rate_change (t, video_frame_rate ());
+}
+
void
Film::playlist_content_changed (boost::weak_ptr<Content> c, int p)
{
@@ -889,31 +895,31 @@ Film::playlist_changed ()
signal_changed (CONTENT);
}
-OutputAudioFrame
-Film::time_to_audio_frames (Time t) const
+AudioFrame
+Film::time_to_audio_frames (DCPTime t) const
{
return t * audio_frame_rate () / TIME_HZ;
}
-OutputVideoFrame
-Film::time_to_video_frames (Time t) const
+VideoFrame
+Film::time_to_video_frames (DCPTime t) const
{
return t * video_frame_rate () / TIME_HZ;
}
-Time
-Film::audio_frames_to_time (OutputAudioFrame f) const
+DCPTime
+Film::audio_frames_to_time (AudioFrame f) const
{
return f * TIME_HZ / audio_frame_rate ();
}
-Time
-Film::video_frames_to_time (OutputVideoFrame f) const
+DCPTime
+Film::video_frames_to_time (VideoFrame f) const
{
return f * TIME_HZ / video_frame_rate ();
}
-OutputAudioFrame
+AudioFrame
Film::audio_frame_rate () const
{
/* XXX */
diff --git a/src/lib/film.h b/src/lib/film.h
index 0a747193e..5d83ec6ed 100644
--- a/src/lib/film.h
+++ b/src/lib/film.h
@@ -101,12 +101,12 @@ public:
boost::shared_ptr<Player> make_player () const;
boost::shared_ptr<Playlist> playlist () const;
- OutputAudioFrame audio_frame_rate () const;
+ AudioFrame 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;
+ AudioFrame time_to_audio_frames (DCPTime) const;
+ VideoFrame time_to_video_frames (DCPTime) const;
+ DCPTime video_frames_to_time (VideoFrame) const;
+ DCPTime audio_frames_to_time (AudioFrame) const;
uint64_t required_disk_space () const;
bool should_be_enough_disk_space (double &, double &) const;
@@ -114,9 +114,10 @@ public:
/* Proxies for some Playlist methods */
ContentList content () const;
- Time length () const;
+ DCPTime length () const;
bool has_subtitles () const;
- OutputVideoFrame best_video_frame_rate () const;
+ VideoFrame best_video_frame_rate () const;
+ FrameRateChange active_frame_rate_change (DCPTime) const;
libdcp::KDM
make_kdm (
diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc
index cd5d19807..7c006aa58 100644
--- a/src/lib/filter_graph.cc
+++ b/src/lib/filter_graph.cc
@@ -122,7 +122,8 @@ FilterGraph::FilterGraph (shared_ptr<const FFmpegContent> content, libdcp::Size
throw DecodeError (N_("could not configure filter graph."));
}
- /* XXX: leaking `inputs' / `outputs' ? */
+ avfilter_inout_free (&inputs);
+ avfilter_inout_free (&outputs);
}
FilterGraph::~FilterGraph ()
diff --git a/src/lib/image.cc b/src/lib/image.cc
index 95bf2b04d..e5f626c24 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -31,6 +31,7 @@ extern "C" {
#include "image.h"
#include "exceptions.h"
#include "scaler.h"
+#include "timer.h"
using std::string;
using std::min;
@@ -94,9 +95,9 @@ Image::crop_scale_window (Crop crop, libdcp::Size inter_size, libdcp::Size out_s
libdcp::Size cropped_size = crop.apply (size ());
struct SwsContext* scale_context = sws_getContext (
- cropped_size.width, cropped_size.height, pixel_format(),
- inter_size.width, inter_size.height, out_format,
- scaler->ffmpeg_id (), 0, 0, 0
+ cropped_size.width, cropped_size.height, pixel_format(),
+ inter_size.width, inter_size.height, out_format,
+ scaler->ffmpeg_id (), 0, 0, 0
);
uint8_t* scale_in_data[components()];
@@ -375,8 +376,18 @@ Image::make_black ()
void
Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
{
- /* Only implemented for RGBA onto RGB24 so far */
- assert (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA);
+ int this_bpp = 0;
+ int other_bpp = 0;
+
+ if (_pixel_format == PIX_FMT_BGRA && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 4;
+ other_bpp = 4;
+ } else if (_pixel_format == PIX_FMT_RGB24 && other->pixel_format() == PIX_FMT_RGBA) {
+ this_bpp = 3;
+ other_bpp = 4;
+ } else {
+ assert (false);
+ }
int start_tx = position.x;
int start_ox = 0;
@@ -395,15 +406,15 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position)
}
for (int ty = start_ty, oy = start_oy; ty < size().height && oy < other->size().height; ++ty, ++oy) {
- uint8_t* tp = data()[0] + ty * stride()[0] + position.x * 3;
+ uint8_t* tp = data()[0] + ty * stride()[0] + position.x * this_bpp;
uint8_t* op = other->data()[0] + oy * other->stride()[0];
for (int tx = start_tx, ox = start_ox; tx < size().width && ox < other->size().width; ++tx, ++ox) {
float const alpha = float (op[3]) / 255;
tp[0] = (tp[0] * (1 - alpha)) + op[0] * alpha;
tp[1] = (tp[1] * (1 - alpha)) + op[1] * alpha;
tp[2] = (tp[2] * (1 - alpha)) + op[2] * alpha;
- tp += 3;
- op += 4;
+ tp += this_bpp;
+ op += other_bpp;
}
}
}
diff --git a/src/lib/image_content.cc b/src/lib/image_content.cc
index 2d29df0c4..a7f951bea 100644
--- a/src/lib/image_content.cc
+++ b/src/lib/image_content.cc
@@ -110,7 +110,7 @@ ImageContent::examine (shared_ptr<Job> job)
}
void
-ImageContent::set_video_length (VideoContent::Frame len)
+ImageContent::set_video_length (VideoFrame len)
{
{
boost::mutex::scoped_lock lm (_mutex);
@@ -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 e5a0311d9..f929e2b6f 100644
--- a/src/lib/image_content.h
+++ b/src/lib/image_content.h
@@ -41,11 +41,11 @@ 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;
- void set_video_length (VideoContent::Frame);
+ void set_video_length (VideoFrame);
bool still () const;
void set_video_frame_rate (float);
};
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index a7999c02a..204849ecf 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_position;
+ return false;
}
Magick::Image* magick_image = 0;
@@ -81,16 +83,15 @@ ImageDecoder::pass ()
delete magick_image;
video (_image, false, _video_position);
-}
+ ++_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..63b4c58e3 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;
+ VideoFrame _video_position;
};
diff --git a/src/lib/image_examiner.h b/src/lib/image_examiner.h
index 8887f0d3d..f5d515074 100644
--- a/src/lib/image_examiner.h
+++ b/src/lib/image_examiner.h
@@ -32,7 +32,7 @@ public:
float video_frame_rate () const;
libdcp::Size video_size () const;
- VideoContent::Frame video_length () const {
+ VideoFrame video_length () const {
return _video_length;
}
@@ -40,5 +40,5 @@ private:
boost::weak_ptr<const Film> _film;
boost::shared_ptr<const ImageContent> _image_content;
boost::optional<libdcp::Size> _video_size;
- VideoContent::Frame _video_length;
+ VideoFrame _video_length;
};
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 faf783ef6..cb6d51984 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -18,6 +18,7 @@
*/
#include <stdint.h>
+#include <algorithm>
#include "player.h"
#include "film.h"
#include "ffmpeg_decoder.h"
@@ -33,7 +34,6 @@
#include "job.h"
#include "image.h"
#include "ratio.h"
-#include "resampler.h"
#include "log.h"
#include "scaler.h"
#include "render_subtitles.h"
@@ -48,69 +48,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)
@@ -123,6 +74,8 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
, _audio_position (0)
, _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
, _last_emit_was_black (false)
+ , _just_did_inaccurate_seek (false)
+ , _approximate_size (false)
{
_playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
_playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
@@ -149,111 +102,162 @@ 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);
+ DCPTime const offset = (*i)->content->position() - (*i)->content->trim_start();
+
+ bool done = false;
+ shared_ptr<Decoded> dec;
+ while (!done) {
+ dec = (*i)->decoder->peek ();
+ if (!dec) {
+ /* Decoder has nothing else to give us */
+ break;
+ }
- if (_video && vd) {
- if ((*i)->video_position < earliest_t) {
- earliest_t = (*i)->video_position;
- earliest = *i;
- type = VIDEO;
+ dec->set_dcp_times (_film->video_frame_rate(), _film->audio_frame_rate(), (*i)->frc, offset);
+ DCPTime const t = dec->dcp_time - offset;
+ if (t >= ((*i)->content->full_length() - (*i)->content->trim_end ())) {
+ /* In the end-trimmed part; decoder has nothing else to give us */
+ dec.reset ();
+ done = true;
+ } else if (t >= (*i)->content->trim_start ()) {
+ /* Within the un-trimmed part; everything's ok */
+ done = true;
+ } else {
+ /* Within the start-trimmed part; get something else */
+ (*i)->decoder->consume ();
}
}
- if (_audio && ad && ad->has_audio ()) {
- if ((*i)->audio_position < earliest_t) {
- earliest_t = (*i)->audio_position;
- earliest = *i;
- type = AUDIO;
- }
+ if (!dec) {
+ continue;
}
- }
- if (!earliest) {
+ if (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);
+ if (earliest_audio != TIME_MAX) {
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (max (int64_t (0), earliest_audio));
+ Audio (tb.audio, tb.time);
+ /* This assumes that the audio_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ }
+
+ /* Emit the earliest thing */
+
+ shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded);
+ shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded);
+ shared_ptr<DecodedImageSubtitle> dis = dynamic_pointer_cast<DecodedImageSubtitle> (earliest_decoded);
+ shared_ptr<DecodedTextSubtitle> dts = dynamic_pointer_cast<DecodedTextSubtitle> (earliest_decoded);
+
+ /* Will be set to false if we shouldn't consume the peeked DecodedThing */
+ bool consume = true;
+
+ if (dv && _video) {
+
+ if (_just_did_inaccurate_seek) {
+
+ /* Just emit; no subtlety */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+
+ } else if (dv->dcp_time > _video_position) {
+
+ /* Too far ahead */
+
+ list<shared_ptr<Piece> >::iterator i = _pieces.begin();
+ while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) {
+ ++i;
+ }
+
+ if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) {
+ /* We're outside all video content */
+ emit_black ();
+ _statistics.video.black++;
} else {
- earliest->decoder->pass ();
+ /* We're inside some video; repeat the frame */
+ _last_incoming_video.video->dcp_time = _video_position;
+ emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video);
+ step_video_position (_last_incoming_video.video);
+ _statistics.video.repeat++;
}
- }
- break;
- case AUDIO:
- if (earliest_t > _audio_position) {
- emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
+ consume = false;
+
+ } else if (dv->dcp_time == _video_position) {
+ /* We're ok */
+ emit_video (earliest_piece, dv);
+ step_video_position (dv);
+ _statistics.video.good++;
} else {
- earliest->decoder->pass ();
-
- if (earliest->decoder->done()) {
- shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
- assert (ac);
- shared_ptr<Resampler> re = resampler (ac, false);
- if (re) {
- shared_ptr<const AudioBuffers> b = re->flush ();
- if (b->frames ()) {
- process_audio (earliest, b, ac->audio_length ());
- }
- }
- }
+ /* Too far behind: skip */
+ _statistics.video.skip++;
}
- break;
- }
- if (_audio) {
- boost::optional<Time> audio_done_up_to;
- for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
- if ((*i)->decoder->done ()) {
- continue;
- }
+ _just_did_inaccurate_seek = false;
- shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
- if (ad && ad->has_audio ()) {
- audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position);
- }
- }
+ } else if (da && _audio) {
- if (audio_done_up_to) {
- TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ());
- Audio (tb.audio, tb.time);
- _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
+ if (da->dcp_time > _audio_position) {
+ /* Too far ahead */
+ emit_silence (da->dcp_time - _audio_position);
+ consume = false;
+ _statistics.audio.silence += (da->dcp_time - _audio_position);
+ } else if (da->dcp_time == _audio_position) {
+ /* We're ok */
+ emit_audio (earliest_piece, da);
+ _statistics.audio.good += da->data->frames();
+ } else {
+ /* Too far behind: skip */
+ _statistics.audio.skip += da->data->frames();
}
- }
+ } else if (dis && _video) {
+ _image_subtitle.piece = earliest_piece;
+ _image_subtitle.subtitle = dis;
+ update_subtitle_from_image ();
+ } else if (dts && _video) {
+ _text_subtitle.piece = earliest_piece;
+ _text_subtitle.subtitle = dts;
+ update_subtitle_from_text ();
+ }
+
+ if (consume) {
+ earliest_piece->decoder->consume ();
+ }
+
return false;
}
-/** @param extra Amount of extra time to add to the content frame's time (for repeat) */
void
-Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
+Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video)
{
/* Keep a note of what came in so that we can repeat it if required */
_last_incoming_video.weak_piece = weak_piece;
- _last_incoming_video.image = image;
- _last_incoming_video.eyes = eyes;
- _last_incoming_video.same = same;
- _last_incoming_video.frame = frame;
- _last_incoming_video.extra = extra;
+ _last_incoming_video.video = video;
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -263,23 +267,18 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
assert (content);
- FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
- if (frc.skip && (frame % 2) == 1) {
- return;
- }
-
- Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
- if (content->trimmed (relative_time)) {
- return;
- }
+ FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate());
- Time const time = content->position() + relative_time + extra - content->trim_start ();
float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
- libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size);
+ libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
+ if (_approximate_size) {
+ image_size.width &= ~3;
+ image_size.height &= ~3;
+ }
shared_ptr<PlayerImage> pi (
new PlayerImage (
- image,
+ video->image,
content->crop(),
image_size,
_video_container_size,
@@ -287,11 +286,15 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
)
);
- if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
+ if (
+ _film->with_subtitles () &&
+ _out_subtitle.image &&
+ video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to
+ ) {
Position<int> const container_offset (
(_video_container_size.width - image_size.width) / 2,
- (_video_container_size.height - image_size.width) / 2
+ (_video_container_size.height - image_size.height) / 2
);
pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset);
@@ -302,18 +305,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image
_last_video = piece->content;
#endif
- Video (pi, eyes, content->colour_conversion(), same, time);
-
+ Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time);
+
_last_emit_was_black = false;
- _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate());
+}
- if (frc.repeat > 1 && !piece->repeating ()) {
- piece->set_repeat (_last_incoming_video, frc.repeat - 1);
+void
+Player::step_video_position (shared_ptr<DecodedVideo> video)
+{
+ /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */
+ if (video->eyes != EYES_LEFT) {
+ /* This assumes that the video_frames_to_time conversion is exact
+ so that there are no accumulated errors caused by rounding.
+ */
+ _video_position += _film->video_frames_to_time (1);
}
}
void
-Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
+Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio)
{
shared_ptr<Piece> piece = weak_piece.lock ();
if (!piece) {
@@ -325,37 +335,20 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
/* Gain */
if (content->audio_gain() != 0) {
- shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
+ shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data));
gain->apply_gain (content->audio_gain ());
- audio = gain;
+ audio->data = gain;
}
- /* Resample */
- if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
- shared_ptr<Resampler> r = resampler (content, true);
- pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
- audio = ro.first;
- frame = ro.second;
- }
-
- Time const relative_time = _film->audio_frames_to_time (frame);
-
- if (content->trimmed (relative_time)) {
- 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 ();
-
AudioMapping map = content->audio_mapping ();
for (int i = 0; i < map.content_channels(); ++i) {
for (int j = 0; j < _film->audio_channels(); ++j) {
if (map.get (i, static_cast<libdcp::Channel> (j)) > 0) {
dcp_mapped->accumulate_channel (
- audio.get(),
+ audio->data.get(),
i,
static_cast<libdcp::Channel> (j),
map.get (i, static_cast<libdcp::Channel> (j))
@@ -364,30 +357,30 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers
}
}
- audio = dcp_mapped;
+ audio->data = dcp_mapped;
- /* We must cut off anything that comes before the start of all time */
- if (time < 0) {
- int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
- if (frames >= audio->frames ()) {
+ /* Delay */
+ audio->dcp_time += content->audio_delay() * TIME_HZ / 1000;
+ if (audio->dcp_time < 0) {
+ int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ;
+ if (frames >= audio->data->frames ()) {
return;
}
- shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
- trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
+ shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames));
+ trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0);
- audio = trimmed;
- time = 0;
+ audio->data = trimmed;
+ audio->dcp_time = 0;
}
- _audio_merger.push (audio, time);
- piece->audio_position += _film->audio_frames_to_time (audio->frames ());
+ _audio_merger.push (audio->data, audio->dcp_time);
}
void
Player::flush ()
{
- TimedAudioBuffers<Time> tb = _audio_merger.flush ();
+ TimedAudioBuffers<DCPTime> tb = _audio_merger.flush ();
if (_audio && tb.audio) {
Audio (tb.audio, tb.time);
_audio_position += _film->audio_frames_to_time (tb.audio->frames ());
@@ -398,7 +391,7 @@ Player::flush ()
}
while (_audio && _audio_position < _video_position) {
- emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
+ emit_silence (_video_position - _audio_position);
}
}
@@ -408,7 +401,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 ();
@@ -419,101 +412,122 @@ 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)->content->trim_start()) * (*i)->frc.speed_up;
/* 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());
+
+ _audio_merger.clear (_audio_position);
- /* XXX: don't seek audio because we don't need to... */
+ 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));
+ shared_ptr<Decoder> decoder;
+ optional<FrameRateChange> frc;
- /* XXX: into content? */
+ /* Work out a FrameRateChange for the best overlap video for this content, in case we need it below */
+ 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(), (*i)->position()) - min (vc->end(), (*i)->end());
+ if (overlap > best_overlap_t) {
+ best_overlap = vc;
+ best_overlap_t = overlap;
+ }
+ }
+ optional<FrameRateChange> best_overlap_frc;
+ if (best_overlap) {
+ 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 */
+ best_overlap_frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ());
+ }
+
+ /* FFmpeg */
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->ImageSubtitle.connect (bind (&Player::process_image_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
- fd->TextSubtitle.connect (bind (&Player::process_text_subtitle, this, weak_ptr<Piece> (piece), _1));
-
- 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());
}
-
+
+ /* ImageContent */
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());
}
+ /* SndfileContent */
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));
-
- piece->decoder = sd;
+ decoder.reset (new SndfileDecoder (_film, sc));
+ frc = best_overlap_frc;
}
+ /* SubRipContent */
shared_ptr<const SubRipContent> rc = dynamic_pointer_cast<const SubRipContent> (*i);
if (rc) {
- shared_ptr<SubRipDecoder> sd (new SubRipDecoder (_film, rc));
- sd->TextSubtitle.connect (bind (&Player::process_text_subtitle, this, weak_ptr<Piece> (piece), _1));
-
- piece->decoder = sd;
+ decoder.reset (new SubRipDecoder (_film, rc));
+ frc = best_overlap_frc;
}
- _pieces.push_back (piece);
+ ContentTime st = (*i)->trim_start() * frc->speed_up;
+ decoder->seek (st, true);
+
+ _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ())));
}
_have_valid_pieces = true;
+
+ /* The Piece for the _last_incoming_video will no longer be valid */
+ _last_incoming_video.video.reset ();
+
+ _video_position = _audio_position = 0;
}
void
@@ -578,29 +592,6 @@ Player::set_video_container_size (libdcp::Size s)
);
}
-shared_ptr<Resampler>
-Player::resampler (shared_ptr<AudioContent> c, bool create)
-{
- map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
- if (i != _resamplers.end ()) {
- return i->second;
- }
-
- if (!create) {
- return shared_ptr<Resampler> ();
- }
-
- _film->log()->log (
- String::compose (
- "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
- )
- );
-
- shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
- _resamplers[c] = r;
- return r;
-}
-
void
Player::emit_black ()
{
@@ -614,17 +605,18 @@ Player::emit_black ()
}
void
-Player::emit_silence (OutputAudioFrame most)
+Player::emit_silence (DCPTime most)
{
if (most == 0) {
return;
}
- OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
- shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
+ DCPTime t = min (most, TIME_HZ / 2);
+ shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), t * _film->audio_frame_rate() / TIME_HZ));
silence->make_silent ();
Audio (silence, _audio_position);
- _audio_position += _film->audio_frames_to_time (N);
+
+ _audio_position += t;
}
void
@@ -641,27 +633,6 @@ Player::film_changed (Film::Property p)
}
void
-Player::process_image_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
-{
- _image_subtitle.piece = weak_piece;
- _image_subtitle.image = image;
- _image_subtitle.rect = rect;
- _image_subtitle.from = from;
- _image_subtitle.to = to;
-
- update_subtitle_from_image ();
-}
-
-void
-Player::process_text_subtitle (weak_ptr<Piece>, list<libdcp::Subtitle> s)
-{
- _text_subtitles = s;
-
- update_subtitle_from_text ();
-}
-
-/** Update _out_subtitle from _image_subtitle */
-void
Player::update_subtitle_from_image ()
{
shared_ptr<Piece> piece = _image_subtitle.piece.lock ();
@@ -669,7 +640,7 @@ Player::update_subtitle_from_image ()
return;
}
- if (!_image_subtitle.image) {
+ if (!_image_subtitle.subtitle->image) {
_out_subtitle.image.reset ();
return;
}
@@ -677,7 +648,7 @@ Player::update_subtitle_from_image ()
shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
assert (sc);
- dcpomatic::Rect<double> in_rect = _image_subtitle.rect;
+ dcpomatic::Rect<double> in_rect = _image_subtitle.subtitle->rect;
libdcp::Size scaled_size;
in_rect.y += sc->subtitle_offset ();
@@ -701,24 +672,15 @@ Player::update_subtitle_from_image ()
_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 = _image_subtitle.image->scale (
+ _out_subtitle.image = _image_subtitle.subtitle->image->scale (
scaled_size,
Scaler::from_id ("bicubic"),
- _image_subtitle.image->pixel_format (),
+ _image_subtitle.subtitle->image->pixel_format (),
true
);
-
- /* XXX: hack */
- Time from = _image_subtitle.from;
- Time to = _image_subtitle.to;
- shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (piece->content);
- if (vc) {
- from = rint (from * vc->video_frame_rate() / _film->video_frame_rate());
- to = rint (to * vc->video_frame_rate() / _film->video_frame_rate());
- }
- _out_subtitle.from = from * piece->content->position ();
- _out_subtitle.to = to + piece->content->position ();
+ _out_subtitle.from = _image_subtitle.subtitle->dcp_time;
+ _out_subtitle.to = _image_subtitle.subtitle->dcp_time_to;
}
/** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
@@ -727,17 +689,13 @@ Player::update_subtitle_from_image ()
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;
@@ -746,14 +704,20 @@ Player::repeat_last_video ()
void
Player::update_subtitle_from_text ()
{
- if (_text_subtitles.empty ()) {
+ if (_text_subtitle.subtitle->subs.empty ()) {
_out_subtitle.image.reset ();
return;
}
- render_subtitles (_text_subtitles, _video_container_size, _out_subtitle.image, _out_subtitle.position);
+ render_subtitles (_text_subtitle.subtitle->subs, _video_container_size, _out_subtitle.image, _out_subtitle.position);
}
+void
+Player::set_approximate_size ()
+{
+ _approximate_size = true;
+}
+
PlayerImage::PlayerImage (
shared_ptr<const Image> in,
Crop crop,
@@ -778,10 +742,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos)
}
shared_ptr<Image>
-PlayerImage::image ()
+PlayerImage::image (AVPixelFormat format, bool aligned)
{
- shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false);
-
+ shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned);
+
Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2);
if (_subtitle_image) {
@@ -790,3 +754,16 @@ PlayerImage::image ()
return out;
}
+
+void
+PlayerStatistics::dump (shared_ptr<Log> log) const
+{
+ log->log (String::compose ("Video: %1 good %2 skipped %3 black %4 repeat", video.good, video.skip, video.black, video.repeat));
+ log->log (String::compose ("Audio: %1 good %2 skipped %3 silence", audio.good, audio.skip, audio.silence));
+}
+
+PlayerStatistics const &
+Player::statistics () const
+{
+ return _statistics;
+}
diff --git a/src/lib/player.h b/src/lib/player.h
index 88d05ea64..377e8bd18 100644
--- a/src/lib/player.h
+++ b/src/lib/player.h
@@ -30,6 +30,7 @@
#include "rect.h"
#include "audio_merger.h"
#include "audio_content.h"
+#include "decoded.h"
class Job;
class Film;
@@ -37,24 +38,9 @@ class Playlist;
class AudioContent;
class Piece;
class Image;
-class Resampler;
-/** @class Player
- * @brief A class which can `play' a Playlist; emitting its audio and video.
- */
-
-struct IncomingVideo
-{
-public:
- boost::weak_ptr<Piece> weak_piece;
- boost::shared_ptr<const Image> image;
- Eyes eyes;
- bool same;
- VideoContent::Frame frame;
- Time extra;
-};
-
-/** A wrapper for an Image which contains some pending operations; these may
+/** @class PlayerImage
+ * @brief A wrapper for an Image which contains some pending operations; these may
* not be necessary if the receiver of the PlayerImage throws it away.
*/
class PlayerImage
@@ -64,7 +50,7 @@ public:
void set_subtitle (boost::shared_ptr<const Image>, Position<int>);
- boost::shared_ptr<Image> image ();
+ boost::shared_ptr<Image> image (AVPixelFormat, bool);
private:
boost::shared_ptr<const Image> _in;
@@ -75,7 +61,43 @@ private:
boost::shared_ptr<const Image> _subtitle_image;
Position<int> _subtitle_position;
};
+
+class PlayerStatistics
+{
+public:
+ struct Video {
+ Video ()
+ : black (0)
+ , repeat (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int black;
+ int repeat;
+ int good;
+ int skip;
+ } video;
+
+ struct Audio {
+ Audio ()
+ : silence (0)
+ , good (0)
+ , skip (0)
+ {}
+
+ int64_t silence;
+ int64_t good;
+ int64_t skip;
+ } audio;
+
+ void dump (boost::shared_ptr<Log>) const;
+};
+/** @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:
@@ -85,16 +107,19 @@ public:
void disable_audio ();
bool pass ();
- void seek (Time, bool);
+ void seek (DCPTime, bool);
- Time video_position () const {
+ DCPTime video_position () const {
return _video_position;
}
void set_video_container_size (libdcp::Size);
+ void set_approximate_size ();
bool repeat_last_video ();
+ PlayerStatistics const & statistics () const;
+
/** Emitted when a video frame is ready.
* First parameter is the video image.
* Second parameter is the eye(s) that should see this image.
@@ -102,10 +127,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
@@ -119,21 +144,19 @@ 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_image_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
- void process_text_subtitle (boost::weak_ptr<Piece>, std::list<libdcp::Subtitle>);
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 emit_silence (AudioFrame);
void film_changed (Film::Property);
void update_subtitle_from_image ();
void update_subtitle_from_text ();
+ void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>);
+ void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>);
+ void step_video_position (boost::shared_ptr<DecodedVideo>);
boost::shared_ptr<const Film> _film;
boost::shared_ptr<const Playlist> _playlist;
@@ -146,31 +169,30 @@ 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, AudioFrame> _audio_merger;
libdcp::Size _video_container_size;
boost::shared_ptr<PlayerImage> _black_frame;
- std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers;
struct {
boost::weak_ptr<Piece> piece;
- boost::shared_ptr<Image> image;
- dcpomatic::Rect<double> rect;
- Time from;
- Time to;
+ boost::shared_ptr<DecodedImageSubtitle> subtitle;
} _image_subtitle;
- std::list<libdcp::Subtitle> _text_subtitles;
-
struct {
- boost::shared_ptr<Image> image;
+ boost::weak_ptr<Piece> piece;
+ boost::shared_ptr<DecodedTextSubtitle> subtitle;
+ } _text_subtitle;
+
+ struct {
Position<int> position;
- Time from;
- Time to;
+ boost::shared_ptr<Image> image;
+ DCPTime from;
+ DCPTime to;
} _out_subtitle;
#ifdef DCPOMATIC_DEBUG
@@ -179,7 +201,15 @@ private:
bool _last_emit_was_black;
- IncomingVideo _last_incoming_video;
+ struct {
+ boost::weak_ptr<Piece> weak_piece;
+ boost::shared_ptr<DecodedVideo> video;
+ } _last_incoming_video;
+
+ bool _just_did_inaccurate_seek;
+ bool _approximate_size;
+
+ PlayerStatistics _statistics;
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 daa82cb94..4175de4c9 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 1915e3d04..35709f109 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 796229777..d3acc7d2e 100644
--- a/src/lib/sndfile_content.cc
+++ b/src/lib/sndfile_content.cc
@@ -49,7 +49,7 @@ SndfileContent::SndfileContent (shared_ptr<const Film> f, shared_ptr<const cxml:
, _audio_mapping (node->node_child ("AudioMapping"), version)
{
_audio_channels = node->number_child<int> ("AudioChannels");
- _audio_length = node->number_child<AudioContent::Frame> ("AudioLength");
+ _audio_length = node->number_child<AudioFrame> ("AudioLength");
_audio_frame_rate = node->number_child<int> ("AudioFrameRate");
}
@@ -141,13 +141,13 @@ 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 ();
assert (film);
- OutputAudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
+ AudioFrame const len = audio_length() * output_audio_frame_rate() / content_audio_frame_rate ();
/* XXX: this depends on whether, alongside this audio, we are running video slower or faster than
it should be. The calculation above works out the output audio frames assuming that we are just
diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h
index 701ff16b2..94f46fea3 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 {
@@ -52,7 +52,7 @@ public:
return _audio_channels;
}
- AudioContent::Frame audio_length () const {
+ AudioFrame audio_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _audio_length;
}
@@ -75,7 +75,7 @@ public:
private:
int _audio_channels;
- AudioContent::Frame _audio_length;
+ AudioFrame _audio_length;
int _audio_frame_rate;
AudioMapping _audio_mapping;
};
diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc
index e10f4f568..d6537843e 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.
*/
@@ -90,9 +94,11 @@ SndfileDecoder::pass ()
}
data->set_frames (this_time);
- audio (data, _done);
+ audio (data, _done * TIME_HZ / audio_frame_rate ());
_done += this_time;
_remaining -= this_time;
+
+ return _remaining == 0;
}
int
@@ -101,7 +107,7 @@ SndfileDecoder::audio_channels () const
return _info.channels;
}
-AudioContent::Frame
+AudioFrame
SndfileDecoder::audio_length () const
{
return _info.frames;
@@ -113,8 +119,12 @@ 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);
+ AudioDecoder::seek (t, accurate);
+
+ _done = t * audio_frame_rate() / TIME_HZ;
+ _remaining = _info.frames - _done;
}
diff --git a/src/lib/sndfile_decoder.h b/src/lib/sndfile_decoder.h
index 77fa6d177..46d9c5e5c 100644
--- a/src/lib/sndfile_decoder.h
+++ b/src/lib/sndfile_decoder.h
@@ -29,18 +29,19 @@ 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;
+ AudioFrame audio_length () const;
int audio_frame_rate () const;
private:
+ bool pass ();
+
boost::shared_ptr<const SndfileContent> _sndfile_content;
SNDFILE* _sndfile;
SF_INFO _info;
- AudioContent::Frame _done;
- AudioContent::Frame _remaining;
+ AudioFrame _done;
+ AudioFrame _remaining;
float* _deinterleave_buffer;
};
diff --git a/src/lib/subrip.cc b/src/lib/subrip.cc
index be2c6f666..380a2ce2c 100644
--- a/src/lib/subrip.cc
+++ b/src/lib/subrip.cc
@@ -123,10 +123,10 @@ SubRip::SubRip (shared_ptr<const SubRipContent> content)
fclose (f);
}
-Time
+ContentTime
SubRip::convert_time (string t)
{
- Time r = 0;
+ ContentTime r = 0;
vector<string> a;
boost::algorithm::split (a, t, boost::is_any_of (":"));
@@ -225,7 +225,7 @@ SubRip::convert_content (list<string> t)
return pieces;
}
-Time
+ContentTime
SubRip::length () const
{
if (_subtitles.empty ()) {
diff --git a/src/lib/subrip.h b/src/lib/subrip.h
index 91bc7156b..e7d21675f 100644
--- a/src/lib/subrip.h
+++ b/src/lib/subrip.h
@@ -33,7 +33,7 @@ class SubRip
public:
SubRip (boost::shared_ptr<const SubRipContent>);
- Time length () const;
+ ContentTime length () const;
protected:
std::vector<SubRipSubtitle> _subtitles;
@@ -44,7 +44,7 @@ private:
friend class subrip_content_test;
friend class subrip_parse_test;
- static Time convert_time (std::string);
+ static ContentTime convert_time (std::string);
static int convert_coordinate (std::string);
static std::list<SubRipSubtitlePiece> convert_content (std::list<std::string>);
static void maybe_content (std::list<SubRipSubtitlePiece> &, SubRipSubtitlePiece &);
diff --git a/src/lib/subrip_content.cc b/src/lib/subrip_content.cc
index b13f64ded..48d3528e1 100644
--- a/src/lib/subrip_content.cc
+++ b/src/lib/subrip_content.cc
@@ -76,7 +76,7 @@ SubRipContent::as_xml (xmlpp::Node* node)
SubtitleContent::as_xml (node);
}
-Time
+DCPTime
SubRipContent::full_length () const
{
/* XXX: this assumes that the timing of the SubRip file is appropriate
diff --git a/src/lib/subrip_content.h b/src/lib/subrip_content.h
index 30a824eec..6138c047e 100644
--- a/src/lib/subrip_content.h
+++ b/src/lib/subrip_content.h
@@ -34,9 +34,9 @@ public:
std::string technical_summary () const;
std::string information () const;
void as_xml (xmlpp::Node *);
- Time full_length () const;
+ DCPTime full_length () const;
std::string identifier () const;
private:
- Time _length;
+ DCPTime _length;
};
diff --git a/src/lib/subrip_decoder.cc b/src/lib/subrip_decoder.cc
index deee82847..aecee4e3e 100644
--- a/src/lib/subrip_decoder.cc
+++ b/src/lib/subrip_decoder.cc
@@ -31,9 +31,13 @@ SubRipDecoder::SubRipDecoder (shared_ptr<const Film> film, shared_ptr<const SubR
}
-void
+bool
SubRipDecoder::pass ()
{
+ if (_next >= _subtitles.size ()) {
+ return true;
+ }
+
list<libdcp::Subtitle> out;
for (list<SubRipSubtitlePiece>::const_iterator i = _subtitles[_next].pieces.begin(); i != _subtitles[_next].pieces.end(); ++i) {
out.push_back (
@@ -57,10 +61,5 @@ SubRipDecoder::pass ()
text_subtitle (out);
_next++;
-}
-
-bool
-SubRipDecoder::done () const
-{
- return _next == _subtitles.size ();
+ return false;
}
diff --git a/src/lib/subrip_decoder.h b/src/lib/subrip_decoder.h
index b963bfb35..26d5d5010 100644
--- a/src/lib/subrip_decoder.h
+++ b/src/lib/subrip_decoder.h
@@ -30,8 +30,7 @@ class SubRipDecoder : public SubtitleDecoder, public SubRip
public:
SubRipDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const SubRipContent>);
- void pass ();
- bool done () const;
+ bool pass ();
private:
size_t _next;
diff --git a/src/lib/subrip_subtitle.h b/src/lib/subrip_subtitle.h
index 933e0fc02..f730ee492 100644
--- a/src/lib/subrip_subtitle.h
+++ b/src/lib/subrip_subtitle.h
@@ -46,8 +46,8 @@ struct SubRipSubtitle
, to (0)
{}
- Time from;
- Time to;
+ ContentTime from;
+ ContentTime to;
boost::optional<int> x1;
boost::optional<int> x2;
boost::optional<int> y1;
diff --git a/src/lib/subtitle_decoder.cc b/src/lib/subtitle_decoder.cc
index 49c04501e..e5cadb7b4 100644
--- a/src/lib/subtitle_decoder.cc
+++ b/src/lib/subtitle_decoder.cc
@@ -22,6 +22,7 @@
using std::list;
using boost::shared_ptr;
+using boost::optional;
SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
: Decoder (f)
@@ -34,13 +35,13 @@ SubtitleDecoder::SubtitleDecoder (shared_ptr<const Film> f)
* Image may be 0 to say that there is no current subtitle.
*/
void
-SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
+SubtitleDecoder::image_subtitle (shared_ptr<Image> image, dcpomatic::Rect<double> rect, ContentTime from, ContentTime to)
{
- ImageSubtitle (image, rect, from, to);
+ _pending.push_back (shared_ptr<DecodedImageSubtitle> (new DecodedImageSubtitle (image, rect, from, to)));
}
void
SubtitleDecoder::text_subtitle (list<libdcp::Subtitle> s)
{
- TextSubtitle (s);
+ _pending.push_back (shared_ptr<DecodedTextSubtitle> (new DecodedTextSubtitle (s)));
}
diff --git a/src/lib/subtitle_decoder.h b/src/lib/subtitle_decoder.h
index 31b03b066..82662d192 100644
--- a/src/lib/subtitle_decoder.h
+++ b/src/lib/subtitle_decoder.h
@@ -25,9 +25,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
@@ -35,11 +36,8 @@ 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)> ImageSubtitle;
- boost::signals2::signal<void (std::list<libdcp::Subtitle>)> TextSubtitle;
-
protected:
- void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time);
+ void image_subtitle (boost::shared_ptr<Image>, dcpomatic::Rect<double>, ContentTime, ContentTime);
void text_subtitle (std::list<libdcp::Subtitle>);
};
diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc
index 87fd5daef..882072689 100644
--- a/src/lib/transcode_job.cc
+++ b/src/lib/transcode_job.cc
@@ -110,6 +110,6 @@ TranscodeJob::remaining_time () const
}
/* Compute approximate proposed length here, as it's only here that we need it */
- OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
+ VideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out();
return left / fps;
}
diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc
index 1c8f7e3eb..ba4d3b040 100644
--- a/src/lib/transcoder.cc
+++ b/src/lib/transcoder.cc
@@ -62,7 +62,8 @@ audio_proxy (weak_ptr<Encoder> encoder, shared_ptr<const AudioBuffers> audio)
* @param e Encoder to use.
*/
Transcoder::Transcoder (shared_ptr<const Film> f, shared_ptr<Job> j)
- : _player (f->make_player ())
+ : _film (f)
+ , _player (f->make_player ())
, _encoder (new Encoder (f, j))
, _finishing (false)
{
@@ -78,6 +79,8 @@ Transcoder::go ()
_finishing = true;
_encoder->process_end ();
+
+ _player->statistics().dump (_film->log ());
}
float
diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h
index d7736d4e8..25b2ef908 100644
--- a/src/lib/transcoder.h
+++ b/src/lib/transcoder.h
@@ -42,6 +42,7 @@ public:
}
private:
+ boost::shared_ptr<const Film> _film;
boost::shared_ptr<Player> _player;
boost::shared_ptr<Encoder> _encoder;
bool _finishing;
diff --git a/src/lib/types.h b/src/lib/types.h
index 96b993a8e..924279c25 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -38,11 +38,12 @@ 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)
-typedef int64_t OutputAudioFrame;
-typedef int OutputVideoFrame;
+#define TIME_HZ ((DCPTime) 96000)
+typedef int64_t ContentTime;
+typedef int64_t AudioFrame;
+typedef int VideoFrame;
typedef std::vector<boost::shared_ptr<Content> > ContentList;
typedef std::vector<boost::shared_ptr<VideoContent> > VideoContentList;
typedef std::vector<boost::shared_ptr<AudioContent> > AudioContentList;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 4a52cd2b7..ef203c2bd 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -751,7 +751,7 @@ ensure_ui_thread ()
* @return Equivalent number of audio frames for `v'.
*/
int64_t
-video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
+video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second)
{
return ((int64_t) v * audio_sample_rate / frames_per_second);
}
@@ -776,7 +776,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)
@@ -794,7 +794,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");
@@ -918,3 +919,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..a84e7e4cf 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);
@@ -160,7 +166,7 @@ private:
int _timeout;
};
-extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
+extern int64_t video_frames_to_audio_frames (VideoFrame v, float audio_sample_rate, float frames_per_second);
class LocaleGuard
{
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index cc075a34c..bf13e1c76 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, VideoFrame len)
: Content (f, s)
, _video_length (len)
, _video_frame_rate (0)
@@ -83,7 +83,7 @@ VideoContent::VideoContent (shared_ptr<const Film> f, shared_ptr<const cxml::Nod
: Content (f, node)
, _ratio (0)
{
- _video_length = node->number_child<VideoContent::Frame> ("VideoLength");
+ _video_length = node->number_child<VideoFrame> ("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");
@@ -353,19 +353,21 @@ VideoContent::video_size_after_crop () const
}
/** @param t A time offset from the start of this piece of content.
- * @return Corresponding frame index.
+ * @return Corresponding frame index, rounded up so that the frame index
+ * is that of the next complete frame which starts after `t'.
*/
-VideoContent::Frame
-VideoContent::time_to_content_video_frames (Time t) const
+VideoFrame
+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 141525e01..8d901cbcd 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, VideoFrame);
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> >);
@@ -53,7 +53,7 @@ public:
virtual std::string information () const;
virtual std::string identifier () const;
- VideoContent::Frame video_length () const {
+ VideoFrame video_length () const {
boost::mutex::scoped_lock lm (_mutex);
return _video_length;
}
@@ -123,12 +123,12 @@ 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;
+ VideoFrame time_to_content_video_frames (DCPTime) const;
protected:
void take_from_video_examiner (boost::shared_ptr<VideoExaminer>);
- VideoContent::Frame _video_length;
+ VideoFrame _video_length;
float _video_frame_rate;
private:
diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc
index e7ddec5e6..a3b45716c 100644
--- a/src/lib/video_decoder.cc
+++ b/src/lib/video_decoder.cc
@@ -24,38 +24,38 @@
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, VideoFrame frame)
{
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, frame)));
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, frame)));
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame)));
break;
}
case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM:
{
int const half = image->size().height / 2;
- Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame);
- Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame);
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame)));
+ _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame)));
break;
}
+ default:
+ assert (false);
}
-
- _video_position = frame + 1;
}
-
diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h
index 142320a04..8947c2708 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, VideoFrame);
boost::shared_ptr<const VideoContent> _video_content;
- VideoContent::Frame _video_position;
};
#endif
diff --git a/src/lib/video_examiner.h b/src/lib/video_examiner.h
index 039c494b5..98c0cdff1 100644
--- a/src/lib/video_examiner.h
+++ b/src/lib/video_examiner.h
@@ -27,5 +27,5 @@ public:
virtual ~VideoExaminer () {}
virtual float video_frame_rate () const = 0;
virtual libdcp::Size video_size () const = 0;
- virtual VideoContent::Frame video_length () const = 0;
+ virtual VideoFrame video_length () const = 0;
};