diff options
Diffstat (limited to 'src')
60 files changed, 951 insertions, 718 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 b4c4f34b6..83a43f3bd 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. + */ +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..aecf39644 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -35,24 +35,33 @@ using boost::shared_ptr; AudioDecoder::AudioDecoder (shared_ptr<const Film> film, shared_ptr<const AudioContent> content) : Decoder (film) , _audio_content (content) - , _audio_position (0) + , _last_audio (0) { - + if (content->output_audio_frame_rate() != content->content_audio_frame_rate() && content->audio_channels ()) { + _resampler.reset (new Resampler (content->content_audio_frame_rate(), content->output_audio_frame_rate(), content->audio_channels ())); + } } void -AudioDecoder::audio (shared_ptr<const AudioBuffers> data, AudioContent::Frame frame) +AudioDecoder::audio (shared_ptr<const AudioBuffers> data, ContentTime time) { - Audio (data, frame); - _audio_position = frame + data->frames (); + if (_resampler) { + data = _resampler->run (data); + } + + _pending.push_back (shared_ptr<DecodedAudio> (new DecodedAudio (data, time))); + _last_audio = time + (data->frames() * TIME_HZ / _audio_content->output_audio_frame_rate()); } -/** This is a bit odd, but necessary when we have (e.g.) FFmpegDecoders with no audio. - * The player needs to know that there is no audio otherwise it will keep trying to - * pass() the decoder to get it to emit audio. - */ -bool -AudioDecoder::has_audio () const +void +AudioDecoder::flush () { - return _audio_content->audio_channels () > 0; + if (!_resampler) { + return; + } + + shared_ptr<const AudioBuffers> b = _resampler->flush (); + if (b) { + audio (b, _last_audio); + } } diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index ab6c4b8a9..a295df0cc 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,20 @@ class AudioDecoder : public virtual Decoder { public: AudioDecoder (boost::shared_ptr<const Film>, boost::shared_ptr<const AudioContent>); - - bool has_audio () const; - - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame)> Audio; + + boost::shared_ptr<const AudioContent> audio_content () const { + return _audio_content; + } protected: - void audio (boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); + void audio (boost::shared_ptr<const AudioBuffers>, ContentTime); + void flush (); + boost::shared_ptr<const AudioContent> _audio_content; - AudioContent::Frame _audio_position; + boost::shared_ptr<Resampler> _resampler; + /* End time of last audio that we wrote to _pending; only used for flushing the resampler */ + ContentTime _last_audio; }; #endif 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 d835a5b05..fa0031abf 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 4ee7c267f..965b33b22 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..1de2ff79e --- /dev/null +++ b/src/lib/decoded.h @@ -0,0 +1,116 @@ +/* + Copyright (C) 2013 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DCPOMATIC_LIB_DECODED_H +#define DCPOMATIC_LIB_DECODED_H + +#include "types.h" +#include "rect.h" + +class Image; + +class Decoded +{ +public: + Decoded () + : content_time (0) + , dcp_time (0) + {} + + Decoded (DCPTime ct) + : content_time (ct) + , dcp_time (0) + {} + + virtual ~Decoded () {} + + virtual void set_dcp_times (float speed_up, DCPTime offset) = 0; + + ContentTime content_time; + DCPTime dcp_time; +}; + +/** One frame of video from a VideoDecoder */ +class DecodedVideo : public Decoded +{ +public: + DecodedVideo () + : eyes (EYES_BOTH) + , same (false) + {} + + DecodedVideo (boost::shared_ptr<const Image> im, Eyes e, bool s, ContentTime ct) + : Decoded (ct) + , image (im) + , eyes (e) + , same (s) + {} + + void set_dcp_times (float speed_up, DCPTime offset) { + dcp_time = rint (content_time / speed_up) + offset; + } + + boost::shared_ptr<const Image> image; + Eyes eyes; + bool same; +}; + +class DecodedAudio : public Decoded +{ +public: + DecodedAudio (boost::shared_ptr<const AudioBuffers> d, ContentTime ct) + : Decoded (ct) + , data (d) + {} + + void set_dcp_times (float speed_up, DCPTime offset) { + dcp_time = rint (content_time / speed_up) + offset; + } + + boost::shared_ptr<const AudioBuffers> data; +}; + +class DecodedSubtitle : public Decoded +{ +public: + DecodedSubtitle () + : content_time_to (0) + , dcp_time_to (0) + {} + + DecodedSubtitle (boost::shared_ptr<Image> im, dcpomatic::Rect<double> r, ContentTime f, ContentTime t) + : Decoded (f) + , image (im) + , rect (r) + , content_time_to (t) + , dcp_time_to (0) + {} + + void set_dcp_times (float speed_up, DCPTime offset) { + dcp_time = rint (content_time / speed_up) + offset; + dcp_time_to = rint (content_time_to / speed_up) + offset; + } + + boost::shared_ptr<Image> image; + dcpomatic::Rect<double> rect; + ContentTime content_time_to; + DCPTime dcp_time_to; +}; + +#endif diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index 3f4cda6eb..4e136d619 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,36 @@ using boost::shared_ptr; */ Decoder::Decoder (shared_ptr<const Film> f) : _film (f) + , _done (false) { } + +shared_ptr<Decoded> +Decoder::peek () +{ + while (!_done && _pending.empty ()) { + _done = pass (); + } + + if (_done) { + return shared_ptr<Decoded> (); + } + + return _pending.front (); +} + +void +Decoder::consume () +{ + if (!_pending.empty ()) { + _pending.pop_front (); + } +} + +void +Decoder::seek (ContentTime, bool) +{ + _pending.clear (); + _done = false; +} 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 eff38b6a5..475c230da 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -216,7 +216,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 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..298284512 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,10 @@ FFmpegDecoder::flush () decode_audio_packet (); } - /* Stop us being asked for any more data */ - _video_position = _ffmpeg_content->video_length (); - _audio_position = _ffmpeg_content->audio_length (); + AudioDecoder::flush (); } -void +bool FFmpegDecoder::pass () { int r = av_read_frame (_format_context, &_packet); @@ -164,7 +161,7 @@ FFmpegDecoder::pass () } flush (); - return; + return true; } avcodec_get_frame_defaults (_frame); @@ -183,6 +180,7 @@ FFmpegDecoder::pass () } av_free_packet (&_packet); + return false; } /** @param data pointer to array of pointers to buffers. @@ -297,70 +295,131 @@ FFmpegDecoder::bytes_per_audio_sample () const return av_get_bytes_per_sample (audio_sample_format ()); } -void -FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate) +int +FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished) { - double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base); + int frames_read = 0; + optional<ContentTime> last_video; + optional<ContentTime> last_audio; - /* If we are doing an accurate seek, our initial shot will be 5 frames (5 being - a number plucked from the air) earlier than we want to end up. The loop below - will hopefully then step through to where we want to be. - */ - int initial = frame; + while (!finished (last_video, last_audio, frames_read)) { + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { + /* We should flush our decoders here, possibly yielding a few more frames, + but the consequence of having to do that is too hideous to contemplate. + Instead we give up and say that you can't seek too close to the end + of a file. + */ + return frames_read; + } - if (accurate) { - initial -= 5; - } + ++frames_read; + + double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base); + + if (_packet.stream_index == _video_stream) { - if (initial < 0) { - initial = 0; + avcodec_get_frame_defaults (_frame); + + int finished = 0; + r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); + if (r >= 0 && finished) { + last_video = rint ( + (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * TIME_HZ + ); + } + + } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) { + AVPacket copy_packet = _packet; + while (copy_packet.size > 0) { + + int finished; + r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet); + if (r >= 0 && finished) { + last_audio = rint ( + (av_frame_get_best_effort_timestamp (_frame) * time_base + _audio_pts_offset) * TIME_HZ + ); + } + + copy_packet.data += r; + copy_packet.size -= r; + } + } + + av_free_packet (&_packet); } - /* Initial seek time in the stream's timebase */ - int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _video_pts_offset) / time_base; + return frames_read; +} + +bool +FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const +{ + return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek); +} + +bool +FFmpegDecoder::seek_final_finished (int n, int done) const +{ + return n == done; +} + +void +FFmpegDecoder::seek_and_flush (ContentTime t) +{ + int64_t s = ((double (t) / TIME_HZ) - _video_pts_offset) / + av_q2d (_format_context->streams[_video_stream]->time_base); + + if (_ffmpeg_content->audio_stream ()) { + s = min ( + s, int64_t ( + ((double (t) / TIME_HZ) - _audio_pts_offset) / + av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base) + ) + ); + } - av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD); + av_seek_frame (_format_context, _video_stream, s, AVSEEK_FLAG_BACKWARD); avcodec_flush_buffers (video_codec_context()); + if (audio_codec_context ()) { + avcodec_flush_buffers (audio_codec_context ()); + } if (_subtitle_codec_context) { avcodec_flush_buffers (_subtitle_codec_context); } +} - _just_sought = true; - _video_position = frame; +void +FFmpegDecoder::seek (ContentTime time, bool accurate) +{ + Decoder::seek (time, accurate); - if (frame == 0 || !accurate) { - /* We're already there, or we're as close as we need to be */ - return; + /* If we are doing an accurate seek, our initial shot will be 200ms (200 being + a number plucked from the air) earlier than we want to end up. The loop below + will hopefully then step through to where we want to be. + */ + + ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0; + ContentTime initial_seek = time - pre_roll; + if (initial_seek < 0) { + initial_seek = 0; } + + /* Initial seek time in the video stream's timebase */ - while (1) { - int r = av_read_frame (_format_context, &_packet); - if (r < 0) { - return; - } + seek_and_flush (initial_seek); - if (_packet.stream_index != _video_stream) { - av_free_packet (&_packet); - continue; - } - - avcodec_get_frame_defaults (_frame); - - int finished = 0; - r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet); - if (r >= 0 && finished) { - _video_position = rint ( - (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * _ffmpeg_content->video_frame_rate() - ); + if (!accurate) { + /* That'll do */ + return; + } - if (_video_position >= (frame - 1)) { - av_free_packet (&_packet); - break; - } - } - - av_free_packet (&_packet); + int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2)); + + seek_and_flush (initial_seek); + if (N > 0) { + minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3)); } } @@ -377,6 +436,7 @@ FFmpegDecoder::decode_audio_packet () int frame_finished; int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, ©_packet); + if (decode_result < 0) { shared_ptr<const Film> film = _film.lock (); assert (film); @@ -385,31 +445,16 @@ FFmpegDecoder::decode_audio_packet () } if (frame_finished) { - - if (_audio_position == 0) { - /* Where we are in the source, in seconds */ - double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset; - - if (pts > 0) { - /* Emit some silence */ - shared_ptr<AudioBuffers> silence ( - new AudioBuffers ( - _ffmpeg_content->audio_channels(), - pts * _ffmpeg_content->content_audio_frame_rate() - ) - ); - - silence->make_silent (); - audio (silence, _audio_position); - } - } - + ContentTime const t = rint ( + (av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) + * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset) * TIME_HZ + ); + int const data_size = av_samples_get_buffer_size ( 0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1 ); - audio (deinterleave_audio (_frame->data, data_size), _audio_position); + audio (deinterleave_audio (_frame->data, data_size), t); } copy_packet.data += decode_result; @@ -458,45 +503,8 @@ FFmpegDecoder::decode_video_packet () } if (i->second != AV_NOPTS_VALUE) { - - double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset; - - if (_just_sought) { - /* We just did a seek, so disable any attempts to correct for where we - are / should be. - */ - _video_position = rint (pts * _ffmpeg_content->video_frame_rate ()); - _just_sought = false; - } - - double const next = _video_position / _ffmpeg_content->video_frame_rate(); - double const one_frame = 1 / _ffmpeg_content->video_frame_rate (); - double delta = pts - next; - - while (delta > one_frame) { - /* This PTS is more than one frame forward in time of where we think we should be; emit - a black frame. - */ - - /* XXX: I think this should be a copy of the last frame... */ - boost::shared_ptr<Image> black ( - new Image ( - static_cast<AVPixelFormat> (_frame->format), - libdcp::Size (video_codec_context()->width, video_codec_context()->height), - true - ) - ); - - black->make_black (); - video (image, false, _video_position); - delta -= one_frame; - } - - if (delta > -one_frame) { - /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */ - video (image, false, _video_position); - } - + ContentTime const t = rint ((i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset) * TIME_HZ); + video (image, false, t); } else { shared_ptr<const Film> film = _film.lock (); assert (film); @@ -529,14 +537,6 @@ FFmpegDecoder::setup_subtitle () } } -bool -FFmpegDecoder::done () const -{ - bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length()); - bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length()); - return vd && ad; -} - void FFmpegDecoder::decode_subtitle_packet () { @@ -562,8 +562,8 @@ FFmpegDecoder::decode_subtitle_packet () double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE; /* hence start time for this sub */ - Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; - Time const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; + ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ; + ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ; AVSubtitleRect const * rect = sub.rects[0]; diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 11f83ed97..55b624bd6 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 @@ -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 1bf35cc5f..98b7bf6b9 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 (); @@ -860,6 +860,12 @@ Film::best_video_frame_rate () const return _playlist->best_dcp_frame_rate (); } +FrameRateChange +Film::active_frame_rate_change (DCPTime t) const +{ + return _playlist->active_frame_rate_change (t, video_frame_rate ()); +} + void Film::playlist_content_changed (boost::weak_ptr<Content> c, int p) { @@ -879,24 +885,24 @@ Film::playlist_changed () } OutputAudioFrame -Film::time_to_audio_frames (Time t) const +Film::time_to_audio_frames (DCPTime t) const { return t * audio_frame_rate () / TIME_HZ; } OutputVideoFrame -Film::time_to_video_frames (Time t) const +Film::time_to_video_frames (DCPTime t) const { return t * video_frame_rate () / TIME_HZ; } -Time +DCPTime Film::audio_frames_to_time (OutputAudioFrame f) const { return f * TIME_HZ / audio_frame_rate (); } -Time +DCPTime Film::video_frames_to_time (OutputVideoFrame f) const { return f * TIME_HZ / video_frame_rate (); diff --git a/src/lib/film.h b/src/lib/film.h index e318f7724..f1564e83b 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -103,17 +103,18 @@ public: OutputAudioFrame audio_frame_rate () const; - OutputAudioFrame time_to_audio_frames (Time) const; - OutputVideoFrame time_to_video_frames (Time) const; - Time video_frames_to_time (OutputVideoFrame) const; - Time audio_frames_to_time (OutputAudioFrame) const; + OutputAudioFrame time_to_audio_frames (DCPTime) const; + OutputVideoFrame time_to_video_frames (DCPTime) const; + DCPTime video_frames_to_time (OutputVideoFrame) const; + DCPTime audio_frames_to_time (OutputAudioFrame) const; /* Proxies for some Playlist methods */ ContentList content () const; - Time length () const; + DCPTime length () const; bool has_subtitles () const; OutputVideoFrame best_video_frame_rate () const; + FrameRateChange active_frame_rate_change (DCPTime) const; libdcp::KDM make_kdm ( 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 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 a7999c02a..a5ca67e0d 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 = 0; @@ -80,17 +82,16 @@ ImageDecoder::pass () delete magick_image; - video (_image, false, _video_position); -} + video (_image, false, _video_position * TIME_HZ / _video_content->video_frame_rate ()); + ++_video_position; -void -ImageDecoder::seek (VideoContent::Frame frame, bool) -{ - _video_position = frame; + return false; } -bool -ImageDecoder::done () const +void +ImageDecoder::seek (ContentTime time, bool accurate) { - return _video_position >= _image_content->video_length (); + Decoder::seek (time, accurate); + + _video_position = rint (time * _video_content->video_frame_rate() / TIME_HZ); } 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..e3d88a54c 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -31,7 +31,6 @@ #include "job.h" #include "image.h" #include "ratio.h" -#include "resampler.h" #include "log.h" #include "scaler.h" @@ -45,69 +44,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 +70,8 @@ Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p) , _audio_position (0) , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1)) , _last_emit_was_black (false) + , _just_did_inaccurate_seek (false) + , _approximate_size (false) { _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3)); @@ -146,110 +98,131 @@ Player::pass () setup_pieces (); } - Time earliest_t = TIME_MAX; - shared_ptr<Piece> earliest; - enum { - VIDEO, - AUDIO - } type = VIDEO; + /* Interrogate all our pieces to find the one with the earliest decoded data */ + + shared_ptr<Piece> earliest_piece; + shared_ptr<Decoded> earliest_decoded; + DCPTime earliest_time = TIME_MAX; + DCPTime earliest_audio = TIME_MAX; for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { - continue; - } - shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder); - shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder); + shared_ptr<Decoded> dec = (*i)->decoder->peek (); - if (_video && vd) { - if ((*i)->video_position < earliest_t) { - earliest_t = (*i)->video_position; - earliest = *i; - type = VIDEO; - } + if (dec) { + dec->set_dcp_times ((*i)->frc.speed_up, (*i)->content->position()); } - if (_audio && ad && ad->has_audio ()) { - if ((*i)->audio_position < earliest_t) { - earliest_t = (*i)->audio_position; - earliest = *i; - type = AUDIO; - } + if (dec && dec->dcp_time < earliest_time) { + earliest_piece = *i; + earliest_decoded = dec; + earliest_time = dec->dcp_time; } - } - if (!earliest) { + if (dynamic_pointer_cast<DecodedAudio> (dec) && dec->dcp_time < earliest_audio) { + earliest_audio = dec->dcp_time; + } + } + + if (!earliest_piece) { flush (); return true; } - switch (type) { - case VIDEO: - if (earliest_t > _video_position) { - emit_black (); - } else { - if (earliest->repeating ()) { - earliest->repeat (this); + if (earliest_audio != TIME_MAX) { + TimedAudioBuffers<DCPTime> tb = _audio_merger.pull (earliest_audio); + Audio (tb.audio, tb.time); + /* This assumes that the audio_frames_to_time conversion is exact + so that there are no accumulated errors caused by rounding. + */ + _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + } + + /* Emit the earliest thing */ + + shared_ptr<DecodedVideo> dv = dynamic_pointer_cast<DecodedVideo> (earliest_decoded); + shared_ptr<DecodedAudio> da = dynamic_pointer_cast<DecodedAudio> (earliest_decoded); + shared_ptr<DecodedSubtitle> ds = dynamic_pointer_cast<DecodedSubtitle> (earliest_decoded); + + /* Will be set to false if we shouldn't consume the peeked DecodedThing */ + bool consume = true; + + /* This is the margin either side of _{video,audio}_position that we will accept + as a starting point for a frame consecutive to the previous. + */ + DCPTime const margin = TIME_HZ / (2 * _film->video_frame_rate ()); + + if (dv && _video) { + + if (_just_did_inaccurate_seek) { + + /* Just emit; no subtlety */ + emit_video (earliest_piece, dv); + step_video_position (dv); + + } else if (dv->dcp_time - _video_position > margin) { + + /* Too far ahead */ + + list<shared_ptr<Piece> >::iterator i = _pieces.begin(); + while (i != _pieces.end() && ((*i)->content->position() >= _video_position || _video_position >= (*i)->content->end())) { + ++i; + } + + if (i == _pieces.end() || !_last_incoming_video.video || !_have_valid_pieces) { + /* We're outside all video content */ + emit_black (); } else { - earliest->decoder->pass (); + /* We're inside some video; repeat the frame */ + _last_incoming_video.video->dcp_time = _video_position; + emit_video (_last_incoming_video.weak_piece, _last_incoming_video.video); + step_video_position (_last_incoming_video.video); } - } - break; - case AUDIO: - if (earliest_t > _audio_position) { - emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position)); + consume = false; + + } else if (abs (dv->dcp_time - _video_position) < margin) { + /* We're ok */ + emit_video (earliest_piece, dv); + step_video_position (dv); } else { - earliest->decoder->pass (); - - if (earliest->decoder->done()) { - shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content); - assert (ac); - shared_ptr<Resampler> re = resampler (ac, false); - if (re) { - shared_ptr<const AudioBuffers> b = re->flush (); - if (b->frames ()) { - process_audio (earliest, b, ac->audio_length ()); - } - } - } + /* Too far behind: skip */ } - break; - } - if (_audio) { - boost::optional<Time> audio_done_up_to; - for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - if ((*i)->decoder->done ()) { - continue; - } + _just_did_inaccurate_seek = false; - if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) { - audio_done_up_to = min (audio_done_up_to.get_value_or (TIME_MAX), (*i)->audio_position); - } - } + } else if (da && _audio) { - if (audio_done_up_to) { - TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to.get ()); - Audio (tb.audio, tb.time); - _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); + if (da->dcp_time - _audio_position > margin) { + /* Too far ahead */ + emit_silence (da->dcp_time - _audio_position); + consume = false; + } else if (abs (da->dcp_time - _audio_position) < margin) { + /* We're ok */ + emit_audio (earliest_piece, da); + } else { + /* Too far behind: skip */ } - } + } else if (ds && _video) { + _in_subtitle.piece = earliest_piece; + _in_subtitle.subtitle = ds; + update_subtitle (); + } + + if (consume) { + earliest_piece->decoder->consume (); + } + return false; } -/** @param extra Amount of extra time to add to the content frame's time (for repeat) */ void -Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra) +Player::emit_video (weak_ptr<Piece> weak_piece, shared_ptr<DecodedVideo> video) { /* Keep a note of what came in so that we can repeat it if required */ _last_incoming_video.weak_piece = weak_piece; - _last_incoming_video.image = image; - _last_incoming_video.eyes = eyes; - _last_incoming_video.same = same; - _last_incoming_video.frame = frame; - _last_incoming_video.extra = extra; + _last_incoming_video.video = video; shared_ptr<Piece> piece = weak_piece.lock (); if (!piece) { @@ -259,23 +232,18 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content); assert (content); - FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate()); - if (frc.skip && (frame % 2) == 1) { - return; - } + FrameRateChange frc (content->video_frame_rate(), _film->video_frame_rate()); - Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate()); - if (content->trimmed (relative_time)) { - return; - } - - Time const time = content->position() + relative_time + extra - content->trim_start (); float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio(); - libdcp::Size const image_size = fit_ratio_within (ratio, _video_container_size); + libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size); + if (_approximate_size) { + image_size.width &= ~3; + image_size.height &= ~3; + } shared_ptr<PlayerImage> pi ( new PlayerImage ( - image, + video->image, content->crop(), image_size, _video_container_size, @@ -283,11 +251,15 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image ) ); - if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) { + if ( + _film->with_subtitles () && + _out_subtitle.image && + video->dcp_time >= _out_subtitle.from && video->dcp_time <= _out_subtitle.to + ) { Position<int> const container_offset ( (_video_container_size.width - image_size.width) / 2, - (_video_container_size.height - image_size.width) / 2 + (_video_container_size.height - image_size.height) / 2 ); pi->set_subtitle (_out_subtitle.image, _out_subtitle.position + container_offset); @@ -297,18 +269,25 @@ Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image _last_video = piece->content; #endif - Video (pi, eyes, content->colour_conversion(), same, time); - + Video (pi, video->eyes, content->colour_conversion(), video->same, video->dcp_time); + _last_emit_was_black = false; - _video_position = piece->video_position = (time + TIME_HZ / _film->video_frame_rate()); +} - if (frc.repeat > 1 && !piece->repeating ()) { - piece->set_repeat (_last_incoming_video, frc.repeat - 1); +void +Player::step_video_position (shared_ptr<DecodedVideo> video) +{ + /* This is a bit of a hack; don't update _video_position if EYES_RIGHT is on its way */ + if (video->eyes != EYES_LEFT) { + /* This assumes that the video_frames_to_time conversion is exact + so that there are no accumulated errors caused by rounding. + */ + _video_position += _film->video_frames_to_time (1); } } void -Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame) +Player::emit_audio (weak_ptr<Piece> weak_piece, shared_ptr<DecodedAudio> audio) { shared_ptr<Piece> piece = weak_piece.lock (); if (!piece) { @@ -320,61 +299,49 @@ Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers /* Gain */ if (content->audio_gain() != 0) { - shared_ptr<AudioBuffers> gain (new AudioBuffers (audio)); + shared_ptr<AudioBuffers> gain (new AudioBuffers (audio->data)); gain->apply_gain (content->audio_gain ()); - audio = gain; + audio->data = gain; } - /* Resample */ - if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) { - shared_ptr<Resampler> r = resampler (content, true); - pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame); - audio = ro.first; - frame = ro.second; - } - - Time const relative_time = _film->audio_frames_to_time (frame); - - if (content->trimmed (relative_time)) { + if (content->trimmed (audio->dcp_time - content->position ())) { return; } - Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start (); - /* Remap channels */ - shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames())); + shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->data->frames())); dcp_mapped->make_silent (); list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp (); for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) { - if (i->first < audio->channels() && i->second < dcp_mapped->channels()) { - dcp_mapped->accumulate_channel (audio.get(), i->first, i->second); + if (i->first < audio->data->channels() && i->second < dcp_mapped->channels()) { + dcp_mapped->accumulate_channel (audio->data.get(), i->first, i->second); } } - audio = dcp_mapped; + audio->data = dcp_mapped; - /* We must cut off anything that comes before the start of all time */ - if (time < 0) { - int const frames = - time * _film->audio_frame_rate() / TIME_HZ; - if (frames >= audio->frames ()) { + /* Delay */ + audio->dcp_time += content->audio_delay() * TIME_HZ / 1000; + if (audio->dcp_time < 0) { + int const frames = - audio->dcp_time * _film->audio_frame_rate() / TIME_HZ; + if (frames >= audio->data->frames ()) { return; } - shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames)); - trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0); + shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->data->channels(), audio->data->frames() - frames)); + trimmed->copy_from (audio->data.get(), audio->data->frames() - frames, frames, 0); - audio = trimmed; - time = 0; + audio->data = trimmed; + audio->dcp_time = 0; } - _audio_merger.push (audio, time); - piece->audio_position += _film->audio_frames_to_time (audio->frames ()); + _audio_merger.push (audio->data, audio->dcp_time); } void Player::flush () { - TimedAudioBuffers<Time> tb = _audio_merger.flush (); + TimedAudioBuffers<DCPTime> tb = _audio_merger.flush (); if (tb.audio) { Audio (tb.audio, tb.time); _audio_position += _film->audio_frames_to_time (tb.audio->frames ()); @@ -385,7 +352,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 +362,7 @@ Player::flush () * @return true on error */ void -Player::seek (Time t, bool accurate) +Player::seek (DCPTime t, bool accurate) { if (!_have_valid_pieces) { setup_pieces (); @@ -406,92 +373,113 @@ Player::seek (Time t, bool accurate) } for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) { - shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content); - if (!vc) { - continue; - } - /* s is the offset of t from the start position of this content */ - Time s = t - vc->position (); - s = max (static_cast<Time> (0), s); - s = min (vc->length_after_trim(), s); + DCPTime s = t - (*i)->content->position (); + s = max (static_cast<DCPTime> (0), s); + s = min ((*i)->content->length_after_trim(), s); - /* Hence set the piece positions to the `global' time */ - (*i)->video_position = (*i)->audio_position = vc->position() + s; + /* Convert this to the content time */ + ContentTime ct = (s * (*i)->frc.speed_up) + (*i)->content->trim_start (); /* And seek the decoder */ - dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek ( - vc->time_to_content_video_frames (s + vc->trim_start ()), accurate - ); - - (*i)->reset_repeat (); + (*i)->decoder->seek (ct, accurate); } - _video_position = _audio_position = t; + _video_position = time_round_up (t, TIME_HZ / _film->video_frame_rate()); + _audio_position = time_round_up (t, TIME_HZ / _film->audio_frame_rate()); - /* XXX: don't seek audio because we don't need to... */ + _audio_merger.clear (_audio_position); + + if (!accurate) { + /* We just did an inaccurate seek, so it's likely that the next thing seen + out of pass() will be a fair distance from _{video,audio}_position. Setting + this flag stops pass() from trying to fix that: we assume that if it + was an inaccurate seek then the caller does not care too much about + inserting black/silence to keep the time tidy. + */ + _just_did_inaccurate_seek = true; + } } void Player::setup_pieces () { list<shared_ptr<Piece> > old_pieces = _pieces; - _pieces.clear (); ContentList content = _playlist->content (); - sort (content.begin(), content.end(), ContentSorter ()); for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - shared_ptr<Piece> piece (new Piece (*i)); - - /* XXX: into content? */ + shared_ptr<Decoder> decoder; + optional<FrameRateChange> frc; shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); if (fc) { - shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio)); - - fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0)); - fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2)); - fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4)); - - fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true); - piece->decoder = fd; + decoder.reset (new FFmpegDecoder (_film, fc, _video, _audio)); + frc = FrameRateChange (fc->video_frame_rate(), _film->video_frame_rate()); } shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (*i); if (ic) { - bool reusing = false; - /* See if we can re-use an old ImageDecoder */ for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) { shared_ptr<ImageDecoder> imd = dynamic_pointer_cast<ImageDecoder> ((*j)->decoder); if (imd && imd->content() == ic) { - piece = *j; - reusing = true; + decoder = imd; } } - if (!reusing) { - shared_ptr<ImageDecoder> id (new ImageDecoder (_film, ic)); - id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0)); - piece->decoder = id; + if (!decoder) { + decoder.reset (new ImageDecoder (_film, ic)); } + + frc = FrameRateChange (ic->video_frame_rate(), _film->video_frame_rate()); } shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); if (sc) { - shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc)); - sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2)); + decoder.reset (new SndfileDecoder (_film, sc)); + + /* Working out the frc for this content is a bit tricky: what if it overlaps + two pieces of video content with different frame rates? For now, use + the one with the best overlap. + */ + + DCPTime best_overlap_t = 0; + shared_ptr<VideoContent> best_overlap; + for (ContentList::iterator j = content.begin(); j != content.end(); ++j) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*j); + if (!vc) { + continue; + } - piece->decoder = sd; + DCPTime const overlap = max (vc->position(), sc->position()) - min (vc->end(), sc->end()); + if (overlap > best_overlap_t) { + best_overlap = vc; + best_overlap_t = overlap; + } + } + + if (best_overlap) { + frc = FrameRateChange (best_overlap->video_frame_rate(), _film->video_frame_rate ()); + } else { + /* No video overlap; e.g. if the DCP is just audio */ + frc = FrameRateChange (_film->video_frame_rate(), _film->video_frame_rate ()); + } } - _pieces.push_back (piece); + decoder->seek ((*i)->trim_start (), true); + + _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder, frc.get ()))); } _have_valid_pieces = true; + + /* The Piece for the _last_incoming_video will no longer be valid */ + _last_incoming_video.video.reset (); + + _video_position = _audio_position = 0; } void @@ -552,29 +540,6 @@ Player::set_video_container_size (libdcp::Size s) ); } -shared_ptr<Resampler> -Player::resampler (shared_ptr<AudioContent> c, bool create) -{ - map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c); - if (i != _resamplers.end ()) { - return i->second; - } - - if (!create) { - return shared_ptr<Resampler> (); - } - - _film->log()->log ( - String::compose ( - "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels() - ) - ); - - shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels())); - _resamplers[c] = r; - return r; -} - void Player::emit_black () { @@ -588,17 +553,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 @@ -615,18 +581,6 @@ Player::film_changed (Film::Property p) } void -Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to) -{ - _in_subtitle.piece = weak_piece; - _in_subtitle.image = image; - _in_subtitle.rect = rect; - _in_subtitle.from = from; - _in_subtitle.to = to; - - update_subtitle (); -} - -void Player::update_subtitle () { shared_ptr<Piece> piece = _in_subtitle.piece.lock (); @@ -634,7 +588,7 @@ Player::update_subtitle () return; } - if (!_in_subtitle.image) { + if (!_in_subtitle.subtitle->image) { _out_subtitle.image.reset (); return; } @@ -642,7 +596,7 @@ Player::update_subtitle () shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content); assert (sc); - dcpomatic::Rect<double> in_rect = _in_subtitle.rect; + dcpomatic::Rect<double> in_rect = _in_subtitle.subtitle->rect; libdcp::Size scaled_size; in_rect.y += sc->subtitle_offset (); @@ -665,15 +619,16 @@ Player::update_subtitle () _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2))); _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2))); - - _out_subtitle.image = _in_subtitle.image->scale ( + + _out_subtitle.image = _in_subtitle.subtitle->image->scale ( scaled_size, Scaler::from_id ("bicubic"), - _in_subtitle.image->pixel_format (), + PIX_FMT_RGBA, true ); - _out_subtitle.from = _in_subtitle.from + piece->content->position (); - _out_subtitle.to = _in_subtitle.to + piece->content->position (); + + _out_subtitle.from = _in_subtitle.subtitle->dcp_time; + _out_subtitle.to = _in_subtitle.subtitle->dcp_time_to; } /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles. @@ -682,22 +637,25 @@ Player::update_subtitle () bool Player::repeat_last_video () { - if (!_last_incoming_video.image || !_have_valid_pieces) { + if (!_last_incoming_video.video || !_have_valid_pieces) { return false; } - process_video ( + emit_video ( _last_incoming_video.weak_piece, - _last_incoming_video.image, - _last_incoming_video.eyes, - _last_incoming_video.same, - _last_incoming_video.frame, - _last_incoming_video.extra + _last_incoming_video.video ); return true; } +void +Player::set_approximate_size () +{ + _approximate_size = true; +} + + PlayerImage::PlayerImage ( shared_ptr<const Image> in, Crop crop, @@ -722,10 +680,10 @@ PlayerImage::set_subtitle (shared_ptr<const Image> image, Position<int> pos) } shared_ptr<Image> -PlayerImage::image () +PlayerImage::image (AVPixelFormat format, bool aligned) { - shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, PIX_FMT_RGB24, false); - + shared_ptr<Image> out = _in->crop_scale_window (_crop, _inter_size, _out_size, _scaler, format, aligned); + Position<int> const container_offset ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.width) / 2); if (_subtitle_image) { @@ -734,3 +692,4 @@ PlayerImage::image () return out; } + diff --git a/src/lib/player.h b/src/lib/player.h index 11cc99e77..b932f4168 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; @@ -36,24 +37,9 @@ class Playlist; class AudioContent; class Piece; class Image; -class Resampler; -/** @class Player - * @brief A class which can `play' a Playlist; emitting its audio and video. - */ - -struct IncomingVideo -{ -public: - boost::weak_ptr<Piece> weak_piece; - boost::shared_ptr<const Image> image; - Eyes eyes; - bool same; - VideoContent::Frame frame; - Time extra; -}; - -/** A wrapper for an Image which contains some pending operations; these may +/** @class PlayerImage + * @brief A wrapper for an Image which contains some pending operations; these may * not be necessary if the receiver of the PlayerImage throws it away. */ class PlayerImage @@ -63,7 +49,7 @@ public: void set_subtitle (boost::shared_ptr<const Image>, Position<int>); - boost::shared_ptr<Image> image (); + boost::shared_ptr<Image> image (AVPixelFormat, bool); private: boost::shared_ptr<const Image> _in; @@ -75,6 +61,10 @@ private: Position<int> _subtitle_position; }; +/** @class Player + * @brief A class which can `play' a Playlist; emitting its audio and video. + */ + class Player : public boost::enable_shared_from_this<Player>, public boost::noncopyable { public: @@ -84,13 +74,14 @@ public: void disable_audio (); bool pass (); - void seek (Time, bool); + void seek (DCPTime, bool); - Time video_position () const { + DCPTime video_position () const { return _video_position; } void set_video_container_size (libdcp::Size); + void set_approximate_size (); bool repeat_last_video (); @@ -101,10 +92,10 @@ public: * Fourth parameter is true if the image is the same as the last one that was emitted. * Fifth parameter is the time. */ - boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, Time)> Video; + boost::signals2::signal<void (boost::shared_ptr<PlayerImage>, Eyes, ColourConversion, bool, DCPTime)> Video; /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, Time)> Audio; + boost::signals2::signal<void (boost::shared_ptr<const AudioBuffers>, DCPTime)> Audio; /** Emitted when something has changed such that if we went back and emitted * the last frame again it would look different. This is not emitted after @@ -118,19 +109,18 @@ private: friend class PlayerWrapper; friend class Piece; - void process_video (boost::weak_ptr<Piece>, boost::shared_ptr<const Image>, Eyes, bool, VideoContent::Frame, Time); - void process_audio (boost::weak_ptr<Piece>, boost::shared_ptr<const AudioBuffers>, AudioContent::Frame); - void process_subtitle (boost::weak_ptr<Piece>, boost::shared_ptr<Image>, dcpomatic::Rect<double>, Time, Time); void setup_pieces (); void playlist_changed (); void content_changed (boost::weak_ptr<Content>, int, bool); - void do_seek (Time, bool); + void do_seek (DCPTime, bool); void flush (); void emit_black (); void emit_silence (OutputAudioFrame); - boost::shared_ptr<Resampler> resampler (boost::shared_ptr<AudioContent>, bool); void film_changed (Film::Property); void update_subtitle (); + void emit_video (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedVideo>); + void emit_audio (boost::weak_ptr<Piece>, boost::shared_ptr<DecodedAudio>); + void step_video_position (boost::shared_ptr<DecodedVideo>); boost::shared_ptr<const Film> _film; boost::shared_ptr<const Playlist> _playlist; @@ -143,29 +133,25 @@ private: std::list<boost::shared_ptr<Piece> > _pieces; /** The time after the last video that we emitted */ - Time _video_position; + DCPTime _video_position; /** The time after the last audio that we emitted */ - Time _audio_position; + DCPTime _audio_position; - AudioMerger<Time, AudioContent::Frame> _audio_merger; + AudioMerger<DCPTime, AudioContent::Frame> _audio_merger; libdcp::Size _video_container_size; boost::shared_ptr<PlayerImage> _black_frame; - std::map<boost::shared_ptr<AudioContent>, boost::shared_ptr<Resampler> > _resamplers; struct { boost::weak_ptr<Piece> piece; - boost::shared_ptr<Image> image; - dcpomatic::Rect<double> rect; - Time from; - Time to; + boost::shared_ptr<DecodedSubtitle> subtitle; } _in_subtitle; struct { - boost::shared_ptr<Image> image; Position<int> position; - Time from; - Time to; + boost::shared_ptr<Image> image; + DCPTime from; + DCPTime to; } _out_subtitle; #ifdef DCPOMATIC_DEBUG @@ -174,7 +160,13 @@ private: bool _last_emit_was_black; - IncomingVideo _last_incoming_video; + struct { + boost::weak_ptr<Piece> weak_piece; + boost::shared_ptr<DecodedVideo> video; + } _last_incoming_video; + + bool _just_did_inaccurate_seek; + bool _approximate_size; boost::signals2::scoped_connection _playlist_changed_connection; boost::signals2::scoped_connection _playlist_content_changed_connection; 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 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..432f73f0d 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 true; } int @@ -113,8 +119,11 @@ SndfileDecoder::audio_frame_rate () const return _info.samplerate; } -bool -SndfileDecoder::done () const +void +SndfileDecoder::seek (ContentTime t, bool accurate) { - return _audio_position >= _sndfile_content->audio_length (); + Decoder::seek (t, accurate); + + _done = t * audio_frame_rate() / TIME_HZ; + _remaining = _info.frames - _done; } 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..7ba969933 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, ContentTime from, ContentTime 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..fd1d71f33 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>, ContentTime, ContentTime); }; diff --git a/src/lib/types.h b/src/lib/types.h index 96b993a8e..33c0c171f 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 cc075a34c..404549532 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) @@ -353,19 +353,21 @@ VideoContent::video_size_after_crop () const } /** @param t A time offset from the start of this piece of content. - * @return Corresponding frame index. + * @return Corresponding frame index, rounded up so that the frame index + * is that of the next complete frame which starts after `t'. */ VideoContent::Frame -VideoContent::time_to_content_video_frames (Time t) const +VideoContent::time_to_content_video_frames (DCPTime t) const { shared_ptr<const Film> film = _film.lock (); assert (film); - FrameRateConversion frc (video_frame_rate(), film->video_frame_rate()); - /* Here we are converting from time (in the DCP) to a frame number in the content. Hence we need to use the DCP's frame rate and the double/skip correction, not - the source's rate. + the source's rate; source rate will be equal to DCP rate if we ignore + double/skip. There's no need to call Film::active_frame_rate_change() here + as we know that we are it (since we're video). */ - return t * film->video_frame_rate() / (frc.factor() * TIME_HZ); + FrameRateChange frc (video_frame_rate(), film->video_frame_rate()); + return ceil (t * film->video_frame_rate() / (frc.factor() * TIME_HZ)); } 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 e7ddec5e6..6a16557cd 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -24,38 +24,36 @@ using std::cout; using boost::shared_ptr; +using boost::optional; VideoDecoder::VideoDecoder (shared_ptr<const Film> f, shared_ptr<const VideoContent> c) : Decoder (f) , _video_content (c) - , _video_position (0) { } +/** Called by subclasses when they have a video frame ready */ void -VideoDecoder::video (shared_ptr<const Image> image, bool same, VideoContent::Frame frame) +VideoDecoder::video (shared_ptr<const Image> image, bool same, ContentTime time) { switch (_video_content->video_frame_type ()) { case VIDEO_FRAME_TYPE_2D: - Video (image, EYES_BOTH, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image, EYES_BOTH, same, time))); break; case VIDEO_FRAME_TYPE_3D_LEFT_RIGHT: { int const half = image->size().width / 2; - Video (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, frame); - Video (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, half, 0, 0), true), EYES_LEFT, same, time))); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (half, 0, 0, 0), true), EYES_RIGHT, same, time))); break; } case VIDEO_FRAME_TYPE_3D_TOP_BOTTOM: { int const half = image->size().height / 2; - Video (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, frame); - Video (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, frame); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, 0, half), true), EYES_LEFT, same, time))); + _pending.push_back (shared_ptr<DecodedVideo> (new DecodedVideo (image->crop (Crop (0, 0, half, 0), true), EYES_RIGHT, same, time))); break; } } - - _video_position = frame + 1; } - 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 diff --git a/src/tools/server_test.cc b/src/tools/server_test.cc index b2c5e784b..38e4704b7 100644 --- a/src/tools/server_test.cc +++ b/src/tools/server_test.cc @@ -47,10 +47,15 @@ static shared_ptr<FileLog> log_ (new FileLog ("servomatictest.log")); static int frame = 0; void -process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, Time) +process_video (shared_ptr<PlayerImage> image, Eyes eyes, ColourConversion conversion, DCPTime) { - shared_ptr<DCPVideoFrame> local (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)); - shared_ptr<DCPVideoFrame> remote (new DCPVideoFrame (image->image(), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_)); + shared_ptr<DCPVideoFrame> local ( + new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + ); + + shared_ptr<DCPVideoFrame> remote ( + new DCPVideoFrame (image->image (PIX_FMT_RGB24, false), frame, eyes, conversion, film->video_frame_rate(), 250000000, RESOLUTION_2K, log_) + ); cout << "Frame " << frame << ": "; cout.flush (); diff --git a/src/wx/audio_plot.cc b/src/wx/audio_plot.cc index f78885772..96de34d40 100644 --- a/src/wx/audio_plot.cc +++ b/src/wx/audio_plot.cc @@ -145,7 +145,7 @@ AudioPlot::paint () gc->SetPen (*wxLIGHT_GREY_PEN); gc->StrokePath (grid); - gc->DrawText (_("Time"), data_width, _height - _y_origin + db_label_height / 2); + gc->DrawText (_("DCPTime"), data_width, _height - _y_origin + db_label_height / 2); if (_type_visible[AudioPoint::PEAK]) { for (int c = 0; c < MAX_AUDIO_CHANNELS; ++c) { diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 04bf6d2a8..1818fb451 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -936,7 +936,7 @@ FilmEditor::content_timeline_clicked () _timeline_dialog = 0; } - _timeline_dialog = new TimelineDialog (this, _film); + _timeline_dialog = new DCPTimelineDialog (this, _film); _timeline_dialog->Show (); } diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index 23c87e678..dadb583ae 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -33,9 +33,9 @@ class wxNotebook; class wxListCtrl; class wxListEvent; class Film; -class TimelineDialog; +class DCPTimelineDialog; class Ratio; -class Timecode; +class DCPTimecode; class FilmEditorPanel; class SubtitleContent; @@ -156,5 +156,5 @@ private: std::vector<Ratio const *> _ratios; bool _generally_sensitive; - TimelineDialog* _timeline_dialog; + DCPTimelineDialog* _timeline_dialog; }; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index fbca835c2..e6b8bf8dd 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -36,6 +36,7 @@ #include "lib/player.h" #include "lib/video_content.h" #include "lib/video_decoder.h" +#include "lib/timer.h" #include "film_viewer.h" #include "wx_util.h" @@ -128,6 +129,7 @@ FilmViewer::set_film (shared_ptr<Film> f) _player = f->make_player (); _player->disable_audio (); + _player->set_approximate_size (); _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _2, _5)); _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1)); @@ -164,7 +166,7 @@ FilmViewer::timer () fetch_next_frame (); - Time const len = _film->length (); + DCPTime const len = _film->length (); if (len) { int const new_slider_position = 4096 * _player->video_position() / len; @@ -275,20 +277,24 @@ FilmViewer::check_play_state () } void -FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, Time t) +FilmViewer::process_video (shared_ptr<PlayerImage> image, Eyes eyes, DCPTime t) { if (eyes == EYES_RIGHT) { return; } - - _frame = image->image (); + + /* Going via BGRA here makes the scaler faster then using RGB24 directly (about + twice on x86 Linux). + */ + shared_ptr<Image> im = image->image (PIX_FMT_BGRA, true); + _frame = im->scale (im->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false); _got_frame = true; set_position_text (t); } void -FilmViewer::set_position_text (Time t) +FilmViewer::set_position_text (DCPTime t) { if (!_film) { _frame_number->SetLabel ("0"); @@ -371,7 +377,7 @@ FilmViewer::back_clicked () We want to see the one before it, so we need to go back 2. */ - Time p = _player->video_position() - _film->video_frames_to_time (2); + DCPTime p = _player->video_position() - _film->video_frames_to_time (2); if (p < 0) { p = 0; } diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index c99c73440..0e5c49c6d 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -59,7 +59,7 @@ private: void slider_moved (); void play_clicked (); void timer (); - void process_video (boost::shared_ptr<PlayerImage>, Eyes, Time); + void process_video (boost::shared_ptr<PlayerImage>, Eyes, DCPTime); void calculate_sizes (); void check_play_state (); void fetch_current_frame_again (); @@ -68,7 +68,7 @@ private: void back_clicked (); void forward_clicked (); void player_changed (bool); - void set_position_text (Time); + void set_position_text (DCPTime); boost::shared_ptr<Film> _film; boost::shared_ptr<Player> _player; diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc index 033bd2bd0..b23ff2a39 100644 --- a/src/wx/timecode.cc +++ b/src/wx/timecode.cc @@ -26,7 +26,7 @@ using std::string; using std::cout; using boost::lexical_cast; -Timecode::Timecode (wxWindow* parent) +DCPTimecode::DCPTimecode (wxWindow* parent) : wxPanel (parent) { wxClientDC dc (parent); @@ -69,11 +69,11 @@ Timecode::Timecode (wxWindow* parent) _fixed = add_label_to_sizer (_sizer, this, wxT ("42"), false); - _hours->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _minutes->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _seconds->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _frames->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&Timecode::changed, this)); - _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&Timecode::set_clicked, this)); + _hours->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DCPTimecode::changed, this)); + _minutes->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DCPTimecode::changed, this)); + _seconds->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DCPTimecode::changed, this)); + _frames->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DCPTimecode::changed, this)); + _set_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DCPTimecode::set_clicked, this)); _set_button->Enable (false); @@ -83,7 +83,7 @@ Timecode::Timecode (wxWindow* parent) } void -Timecode::set (Time t, int fps) +DCPTimecode::set (DCPTime t, int fps) { int const h = t / (3600 * TIME_HZ); t -= h * 3600 * TIME_HZ; @@ -101,10 +101,10 @@ Timecode::set (Time t, int fps) _fixed->SetLabel (wxString::Format ("%02d:%02d:%02d.%02d", h, m, s, f)); } -Time -Timecode::get (int fps) const +DCPTime +DCPTimecode::get (int fps) const { - Time t = 0; + DCPTime t = 0; string const h = wx_to_std (_hours->GetValue ()); t += lexical_cast<int> (h.empty() ? "0" : h) * 3600 * TIME_HZ; string const m = wx_to_std (_minutes->GetValue()); @@ -118,20 +118,20 @@ Timecode::get (int fps) const } void -Timecode::changed () +DCPTimecode::changed () { _set_button->Enable (true); } void -Timecode::set_clicked () +DCPTimecode::set_clicked () { Changed (); _set_button->Enable (false); } void -Timecode::set_editable (bool e) +DCPTimecode::set_editable (bool e) { _editable->Show (e); _fixed->Show (!e); diff --git a/src/wx/timecode.h b/src/wx/timecode.h index 880b44a31..f95740255 100644 --- a/src/wx/timecode.h +++ b/src/wx/timecode.h @@ -21,13 +21,13 @@ #include <wx/wx.h> #include "lib/types.h" -class Timecode : public wxPanel +class DCPTimecode : public wxPanel { public: - Timecode (wxWindow *); + DCPTimecode (wxWindow *); - void set (Time, int); - Time get (int) const; + void set (DCPTime, int); + DCPTime get (int) const; void set_editable (bool); diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index 0ac9a1d4b..9d2aee76c 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -39,7 +39,7 @@ using boost::optional; class View : public boost::noncopyable { public: - View (Timeline& t) + View (DCPTimeline& t) : _timeline (t) { @@ -64,12 +64,12 @@ public: protected: virtual void do_paint (wxGraphicsContext *) = 0; - int time_x (Time t) const + int time_x (DCPTime t) const { return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit(); } - Timeline& _timeline; + DCPTimeline& _timeline; private: dcpomatic::Rect<int> _last_paint_bbox; @@ -80,7 +80,7 @@ private: class ContentView : public View { public: - ContentView (Timeline& tl, shared_ptr<Content> c) + ContentView (DCPTimeline& tl, shared_ptr<Content> c) : View (tl) , _content (c) , _track (0) @@ -139,8 +139,8 @@ private: return; } - Time const position = cont->position (); - Time const len = cont->length_after_trim (); + DCPTime const position = cont->position (); + DCPTime const len = cont->length_after_trim (); wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2); @@ -203,7 +203,7 @@ private: class AudioContentView : public ContentView { public: - AudioContentView (Timeline& tl, shared_ptr<Content> c) + AudioContentView (DCPTimeline& tl, shared_ptr<Content> c) : ContentView (tl, c) {} @@ -222,7 +222,7 @@ private: class VideoContentView : public ContentView { public: - VideoContentView (Timeline& tl, shared_ptr<Content> c) + VideoContentView (DCPTimeline& tl, shared_ptr<Content> c) : ContentView (tl, c) {} @@ -243,10 +243,10 @@ private: } }; -class TimeAxisView : public View +class DCPTimeAxisView : public View { public: - TimeAxisView (Timeline& tl, int y) + DCPTimeAxisView (DCPTimeline& tl, int y) : View (tl) , _y (y) {} @@ -291,7 +291,7 @@ private: path.AddLineToPoint (_timeline.width(), _y); gc->StrokePath (path); - Time t = 0; + DCPTime t = 0; while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) { wxGraphicsPath path = gc->CreatePath (); path.MoveToPoint (time_x (t), _y - 4); @@ -326,11 +326,11 @@ private: }; -Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) +DCPTimeline::DCPTimeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE) , _film_editor (ed) , _film (film) - , _time_axis_view (new TimeAxisView (*this, 32)) + , _time_axis_view (new DCPTimeAxisView (*this, 32)) , _tracks (0) , _pixels_per_time_unit (0) , _left_down (false) @@ -343,22 +343,22 @@ Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film) SetDoubleBuffered (true); #endif - Bind (wxEVT_PAINT, boost::bind (&Timeline::paint, this)); - Bind (wxEVT_LEFT_DOWN, boost::bind (&Timeline::left_down, this, _1)); - Bind (wxEVT_LEFT_UP, boost::bind (&Timeline::left_up, this, _1)); - Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down, this, _1)); - Bind (wxEVT_MOTION, boost::bind (&Timeline::mouse_moved, this, _1)); - Bind (wxEVT_SIZE, boost::bind (&Timeline::resized, this)); + Bind (wxEVT_PAINT, boost::bind (&DCPTimeline::paint, this)); + Bind (wxEVT_LEFT_DOWN, boost::bind (&DCPTimeline::left_down, this, _1)); + Bind (wxEVT_LEFT_UP, boost::bind (&DCPTimeline::left_up, this, _1)); + Bind (wxEVT_RIGHT_DOWN, boost::bind (&DCPTimeline::right_down, this, _1)); + Bind (wxEVT_MOTION, boost::bind (&DCPTimeline::mouse_moved, this, _1)); + Bind (wxEVT_SIZE, boost::bind (&DCPTimeline::resized, this)); playlist_changed (); SetMinSize (wxSize (640, tracks() * track_height() + 96)); - _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this)); + _playlist_connection = film->playlist()->Changed.connect (bind (&DCPTimeline::playlist_changed, this)); } void -Timeline::paint () +DCPTimeline::paint () { wxPaintDC dc (this); @@ -377,7 +377,7 @@ Timeline::paint () } void -Timeline::playlist_changed () +DCPTimeline::playlist_changed () { ensure_ui_thread (); @@ -406,7 +406,7 @@ Timeline::playlist_changed () } void -Timeline::assign_tracks () +DCPTimeline::assign_tracks () { for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i); @@ -465,13 +465,13 @@ Timeline::assign_tracks () } int -Timeline::tracks () const +DCPTimeline::tracks () const { return _tracks; } void -Timeline::setup_pixels_per_time_unit () +DCPTimeline::setup_pixels_per_time_unit () { shared_ptr<const Film> film = _film.lock (); if (!film) { @@ -482,7 +482,7 @@ Timeline::setup_pixels_per_time_unit () } shared_ptr<View> -Timeline::event_to_view (wxMouseEvent& ev) +DCPTimeline::event_to_view (wxMouseEvent& ev) { ViewList::iterator i = _views.begin(); Position<int> const p (ev.GetX(), ev.GetY()); @@ -498,7 +498,7 @@ Timeline::event_to_view (wxMouseEvent& ev) } void -Timeline::left_down (wxMouseEvent& ev) +DCPTimeline::left_down (wxMouseEvent& ev) { shared_ptr<View> view = event_to_view (ev); shared_ptr<ContentView> content_view = dynamic_pointer_cast<ContentView> (view); @@ -539,7 +539,7 @@ Timeline::left_down (wxMouseEvent& ev) } void -Timeline::left_up (wxMouseEvent& ev) +DCPTimeline::left_up (wxMouseEvent& ev) { _left_down = false; @@ -551,7 +551,7 @@ Timeline::left_up (wxMouseEvent& ev) } void -Timeline::mouse_moved (wxMouseEvent& ev) +DCPTimeline::mouse_moved (wxMouseEvent& ev) { if (!_left_down) { return; @@ -561,7 +561,7 @@ Timeline::mouse_moved (wxMouseEvent& ev) } void -Timeline::right_down (wxMouseEvent& ev) +DCPTimeline::right_down (wxMouseEvent& ev) { shared_ptr<View> view = event_to_view (ev); shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (view); @@ -578,7 +578,7 @@ Timeline::right_down (wxMouseEvent& ev) } void -Timeline::set_position_from_event (wxMouseEvent& ev) +DCPTimeline::set_position_from_event (wxMouseEvent& ev) { wxPoint const p = ev.GetPosition(); @@ -597,13 +597,13 @@ Timeline::set_position_from_event (wxMouseEvent& ev) return; } - Time new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit; + DCPTime new_position = _down_view_position + (p.x - _down_point.x) / _pixels_per_time_unit; if (_snap) { bool first = true; - Time nearest_distance = TIME_MAX; - Time nearest_new_position = TIME_MAX; + DCPTime nearest_distance = TIME_MAX; + DCPTime nearest_new_position = TIME_MAX; /* Find the nearest content edge; this is inefficient */ for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { @@ -614,7 +614,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* Snap starts to ends */ - Time const d = abs (cv->content()->end() - new_position); + DCPTime const d = abs (cv->content()->end() - new_position); if (first || d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->end(); @@ -623,7 +623,7 @@ Timeline::set_position_from_event (wxMouseEvent& ev) { /* Snap ends to starts */ - Time const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim())); + DCPTime const d = abs (cv->content()->position() - (new_position + _down_view->content()->length_after_trim())); if (d < nearest_distance) { nearest_distance = d; nearest_new_position = cv->content()->position() - _down_view->content()->length_after_trim (); @@ -653,25 +653,25 @@ Timeline::set_position_from_event (wxMouseEvent& ev) } void -Timeline::force_redraw (dcpomatic::Rect<int> const & r) +DCPTimeline::force_redraw (dcpomatic::Rect<int> const & r) { RefreshRect (wxRect (r.x, r.y, r.width, r.height), false); } shared_ptr<const Film> -Timeline::film () const +DCPTimeline::film () const { return _film.lock (); } void -Timeline::resized () +DCPTimeline::resized () { setup_pixels_per_time_unit (); } void -Timeline::clear_selection () +DCPTimeline::clear_selection () { for (ViewList::iterator i = _views.begin(); i != _views.end(); ++i) { shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i); @@ -681,8 +681,8 @@ Timeline::clear_selection () } } -Timeline::ContentViewList -Timeline::selected_views () const +DCPTimeline::ContentViewList +DCPTimeline::selected_views () const { ContentViewList sel; @@ -697,7 +697,7 @@ Timeline::selected_views () const } ContentList -Timeline::selected_content () const +DCPTimeline::selected_content () const { ContentList sel; ContentViewList views = selected_views (); diff --git a/src/wx/timeline.h b/src/wx/timeline.h index ef1d10797..b3ee77fff 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -29,12 +29,12 @@ class Film; class View; class ContentView; class FilmEditor; -class TimeAxisView; +class DCPTimeAxisView; -class Timeline : public wxPanel +class DCPTimeline : public wxPanel { public: - Timeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>); + DCPTimeline (wxWindow *, FilmEditor *, boost::shared_ptr<Film>); boost::shared_ptr<const Film> film () const; @@ -94,13 +94,13 @@ private: FilmEditor* _film_editor; boost::weak_ptr<Film> _film; ViewList _views; - boost::shared_ptr<TimeAxisView> _time_axis_view; + boost::shared_ptr<DCPTimeAxisView> _time_axis_view; int _tracks; double _pixels_per_time_unit; bool _left_down; wxPoint _down_point; boost::shared_ptr<ContentView> _down_view; - Time _down_view_position; + DCPTime _down_view_position; bool _first_move; ContentMenu _menu; bool _snap; diff --git a/src/wx/timeline_dialog.cc b/src/wx/timeline_dialog.cc index dbf7ae232..a63c219db 100644 --- a/src/wx/timeline_dialog.cc +++ b/src/wx/timeline_dialog.cc @@ -28,8 +28,8 @@ using std::list; using std::cout; using boost::shared_ptr; -TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film) - : wxDialog (ed, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) +DCPTimelineDialog::DCPTimelineDialog (FilmEditor* ed, shared_ptr<Film> film) + : wxDialog (ed, wxID_ANY, _("DCPTimeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) , _timeline (this, ed, film) { wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); @@ -46,11 +46,11 @@ TimelineDialog::TimelineDialog (FilmEditor* ed, shared_ptr<Film> film) sizer->SetSizeHints (this); _snap->SetValue (_timeline.snap ()); - _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&TimelineDialog::snap_toggled, this)); + _snap->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&DCPTimelineDialog::snap_toggled, this)); } void -TimelineDialog::snap_toggled () +DCPTimelineDialog::snap_toggled () { _timeline.set_snap (_snap->GetValue ()); } diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h index 1e5955003..b64445d9c 100644 --- a/src/wx/timeline_dialog.h +++ b/src/wx/timeline_dialog.h @@ -24,14 +24,14 @@ class Playlist; -class TimelineDialog : public wxDialog +class DCPTimelineDialog : public wxDialog { public: - TimelineDialog (FilmEditor *, boost::shared_ptr<Film>); + DCPTimelineDialog (FilmEditor *, boost::shared_ptr<Film>); private: void snap_toggled (); - Timeline _timeline; + DCPTimeline _timeline; wxCheckBox* _snap; }; diff --git a/src/wx/timing_panel.cc b/src/wx/timing_panel.cc index 79099b168..08334da4b 100644 --- a/src/wx/timing_panel.cc +++ b/src/wx/timing_panel.cc @@ -35,19 +35,19 @@ TimingPanel::TimingPanel (FilmEditor* e) _sizer->Add (grid, 0, wxALL, 8); add_label_to_sizer (grid, this, _("Position"), true); - _position = new Timecode (this); + _position = new DCPTimecode (this); grid->Add (_position); add_label_to_sizer (grid, this, _("Full length"), true); - _full_length = new Timecode (this); + _full_length = new DCPTimecode (this); grid->Add (_full_length); add_label_to_sizer (grid, this, _("Trim from start"), true); - _trim_start = new Timecode (this); + _trim_start = new DCPTimecode (this); grid->Add (_trim_start); add_label_to_sizer (grid, this, _("Trim from end"), true); - _trim_end = new Timecode (this); + _trim_end = new DCPTimecode (this); grid->Add (_trim_end); add_label_to_sizer (grid, this, _("Play length"), true); - _play_length = new Timecode (this); + _play_length = new DCPTimecode (this); grid->Add (_play_length); _position->Changed.connect (boost::bind (&TimingPanel::position_changed, this)); diff --git a/src/wx/timing_panel.h b/src/wx/timing_panel.h index ab859a1be..7fea45eb5 100644 --- a/src/wx/timing_panel.h +++ b/src/wx/timing_panel.h @@ -19,7 +19,7 @@ #include "film_editor_panel.h" -class Timecode; +class DCPTimecode; class TimingPanel : public FilmEditorPanel { @@ -36,9 +36,9 @@ private: void trim_end_changed (); void play_length_changed (); - Timecode* _position; - Timecode* _full_length; - Timecode* _trim_start; - Timecode* _trim_end; - Timecode* _play_length; + DCPTimecode* _position; + DCPTimecode* _full_length; + DCPTimecode* _trim_start; + DCPTimecode* _trim_end; + DCPTimecode* _play_length; }; diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc index 90b4adfe3..a777b3e88 100644 --- a/src/wx/video_panel.cc +++ b/src/wx/video_panel.cc @@ -334,7 +334,7 @@ VideoPanel::setup_description () d << wxString::Format (_("Content frame rate %.4f\n"), vcs->video_frame_rate ()); ++lines; - FrameRateConversion frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); + FrameRateChange frc (vcs->video_frame_rate(), _editor->film()->video_frame_rate ()); d << frc.description << "\n"; ++lines; |
