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