From 21ce34c2cd04a2e7e133ff693b84c054182f4f91 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 16 May 2013 08:36:47 +0100 Subject: Compiles; strange hang on adding content to a film. --- src/lib/analyse_audio_job.cc | 4 +- src/lib/audio_buffers.cc | 222 ++++++++++++++++++++++ src/lib/audio_buffers.h | 67 +++++++ src/lib/audio_content.cc | 6 - src/lib/audio_content.h | 5 +- src/lib/audio_decoder.cc | 14 +- src/lib/audio_decoder.h | 1 + src/lib/audio_mapping.cc | 51 ++--- src/lib/audio_mapping.h | 28 +-- src/lib/audio_sink.h | 2 + src/lib/combiner.cc | 4 +- src/lib/content.h | 2 +- src/lib/decoder.h | 1 - src/lib/encoder.cc | 17 +- src/lib/encoder.h | 2 - src/lib/ffmpeg_content.cc | 33 +++- src/lib/ffmpeg_content.h | 5 +- src/lib/ffmpeg_decoder.cc | 5 +- src/lib/film.cc | 188 ++++++------------ src/lib/film.h | 48 ++--- src/lib/format.cc | 3 +- src/lib/format.h | 2 +- src/lib/imagemagick_content.cc | 7 + src/lib/imagemagick_content.h | 1 + src/lib/player.cc | 338 ++++++++++++++++----------------- src/lib/player.h | 53 +++--- src/lib/playlist.cc | 419 +++++++++++++---------------------------- src/lib/playlist.h | 77 +++----- src/lib/sndfile_content.cc | 14 +- src/lib/sndfile_content.h | 5 +- src/lib/sndfile_decoder.cc | 3 +- src/lib/subtitle.h | 2 +- src/lib/transcode_job.cc | 13 +- src/lib/types.h | 10 +- src/lib/util.cc | 227 +--------------------- src/lib/util.h | 50 +---- src/lib/video_content.cc | 6 - src/lib/video_content.h | 1 - src/lib/video_decoder.cc | 4 +- src/lib/writer.cc | 47 ++--- src/lib/wscript | 1 + 41 files changed, 866 insertions(+), 1122 deletions(-) create mode 100644 src/lib/audio_buffers.cc create mode 100644 src/lib/audio_buffers.h (limited to 'src/lib') diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc index f3c55b208..0ed33827c 100644 --- a/src/lib/analyse_audio_job.cc +++ b/src/lib/analyse_audio_job.cc @@ -55,13 +55,13 @@ AnalyseAudioJob::run () player->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1)); - _samples_per_point = max (int64_t (1), _film->audio_length() / _num_points); + _samples_per_point = max (int64_t (1), _film->time_to_audio_frames (_film->length()) / _num_points); _current.resize (MAX_AUDIO_CHANNELS); _analysis.reset (new AudioAnalysis (MAX_AUDIO_CHANNELS)); while (!player->pass()) { - set_progress (float (_done) / _film->audio_length ()); + set_progress (float (_done) / _film->time_to_audio_frames (_film->length ())); } _analysis->write (_film->audio_analysis_path ()); diff --git a/src/lib/audio_buffers.cc b/src/lib/audio_buffers.cc new file mode 100644 index 000000000..cd8fcd35b --- /dev/null +++ b/src/lib/audio_buffers.cc @@ -0,0 +1,222 @@ +/* + Copyright (C) 2012-2013 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include +#include +#include "audio_buffers.h" + +using std::bad_alloc; +using boost::shared_ptr; + +/** Construct an AudioBuffers. Audio data is undefined after this constructor. + * @param channels Number of channels. + * @param frames Number of frames to reserve space for. + */ +AudioBuffers::AudioBuffers (int channels, int frames) + : _channels (channels) + , _frames (frames) + , _allocated_frames (frames) +{ + _data = static_cast (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast (malloc (frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + } +} + +/** Copy constructor. + * @param other Other AudioBuffers; data is copied. + */ +AudioBuffers::AudioBuffers (AudioBuffers const & other) + : _channels (other._channels) + , _frames (other._frames) + , _allocated_frames (other._frames) +{ + _data = static_cast (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast (malloc (_frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + memcpy (_data[i], other._data[i], _frames * sizeof (float)); + } +} + +/* XXX: it's a shame that this is a copy-and-paste of the above; + probably fixable with c++0x. +*/ +AudioBuffers::AudioBuffers (boost::shared_ptr other) + : _channels (other->_channels) + , _frames (other->_frames) + , _allocated_frames (other->_frames) +{ + _data = static_cast (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast (malloc (_frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + memcpy (_data[i], other->_data[i], _frames * sizeof (float)); + } +} + +/** AudioBuffers destructor */ +AudioBuffers::~AudioBuffers () +{ + for (int i = 0; i < _channels; ++i) { + free (_data[i]); + } + + free (_data); +} + +/** @param c Channel index. + * @return Buffer for this channel. + */ +float* +AudioBuffers::data (int c) const +{ + assert (c >= 0 && c < _channels); + return _data[c]; +} + +/** Set the number of frames that these AudioBuffers will report themselves + * as having. + * @param f Frames; must be less than or equal to the number of allocated frames. + */ +void +AudioBuffers::set_frames (int f) +{ + assert (f <= _allocated_frames); + _frames = f; +} + +/** Make all samples on all channels silent */ +void +AudioBuffers::make_silent () +{ + for (int i = 0; i < _channels; ++i) { + make_silent (i); + } +} + +/** Make all samples on a given channel silent. + * @param c Channel. + */ +void +AudioBuffers::make_silent (int c) +{ + assert (c >= 0 && c < _channels); + + for (int i = 0; i < _frames; ++i) { + _data[c][i] = 0; + } +} + +/** Copy data from another AudioBuffers to this one. All channels are copied. + * @param from AudioBuffers to copy from; must have the same number of channels as this. + * @param frames_to_copy Number of frames to copy. + * @param read_offset Offset to read from in `from'. + * @param write_offset Offset to write to in `to'. + */ +void +AudioBuffers::copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset) +{ + assert (from->channels() == channels()); + + assert (from); + assert (read_offset >= 0 && (read_offset + frames_to_copy) <= from->_allocated_frames); + assert (write_offset >= 0 && (write_offset + frames_to_copy) <= _allocated_frames); + + for (int i = 0; i < _channels; ++i) { + memcpy (_data[i] + write_offset, from->_data[i] + read_offset, frames_to_copy * sizeof(float)); + } +} + +/** Move audio data around. + * @param from Offset to move from. + * @param to Offset to move to. + * @param frames Number of frames to move. + */ + +void +AudioBuffers::move (int from, int to, int frames) +{ + if (frames == 0) { + return; + } + + assert (from >= 0); + assert (from < _frames); + assert (to >= 0); + assert (to < _frames); + assert (frames > 0); + assert (frames <= _frames); + assert ((from + frames) <= _frames); + assert ((to + frames) <= _frames); + + for (int i = 0; i < _channels; ++i) { + memmove (_data[i] + to, _data[i] + from, frames * sizeof(float)); + } +} + +/** Add data from from `from', `from_channel' to our channel `to_channel' */ +void +AudioBuffers::accumulate (AudioBuffers const * from, int from_channel, int to_channel) +{ + int const N = frames (); + assert (from->frames() == N); + + float* s = from->data (from_channel); + float* d = _data[to_channel]; + + for (int i = 0; i < N; ++i) { + *d++ += *s++; + } +} + +void +AudioBuffers::ensure_size (int frames) +{ + if (_allocated_frames >= frames) { + return; + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast (realloc (_data[i], _frames * sizeof (float))); + if (!_data[i]) { + throw bad_alloc (); + } + } +} diff --git a/src/lib/audio_buffers.h b/src/lib/audio_buffers.h new file mode 100644 index 000000000..5e7b9fda4 --- /dev/null +++ b/src/lib/audio_buffers.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2012-2013 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include + +/** @class AudioBuffers + * @brief A class to hold multi-channel audio data in float format. + */ +class AudioBuffers +{ +public: + AudioBuffers (int channels, int frames); + AudioBuffers (AudioBuffers const &); + AudioBuffers (boost::shared_ptr); + ~AudioBuffers (); + + void ensure_size (int); + + float** data () const { + return _data; + } + + float* data (int) const; + + int channels () const { + return _channels; + } + + int frames () const { + return _frames; + } + + void set_frames (int f); + + void make_silent (); + void make_silent (int c); + + void copy_from (AudioBuffers const * from, int frames_to_copy, int read_offset, int write_offset); + void move (int from, int to, int frames); + void accumulate (AudioBuffers const *, int, int); + +private: + /** Number of channels */ + int _channels; + /** Number of frames (where a frame is one sample across all channels) */ + int _frames; + /** Number of frames that _data can hold */ + int _allocated_frames; + /** Audio data (so that, e.g. _data[2][6] is channel 2, sample 6) */ + float** _data; +}; diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index dfa48d97e..9968f4725 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -43,9 +43,3 @@ AudioContent::AudioContent (AudioContent const & o) { } - -Time -AudioContent::temporal_length () const -{ - return audio_length() / audio_frame_rate(); -} diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 18107843c..87858488c 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -44,9 +44,8 @@ public: virtual int audio_channels () const = 0; virtual ContentAudioFrame audio_length () const = 0; - virtual int audio_frame_rate () const = 0; - - Time temporal_length () const; + virtual int content_audio_frame_rate () const = 0; + virtual int output_audio_frame_rate (boost::shared_ptr) const = 0; }; #endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 68554daf9..e1c93ac77 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -18,6 +18,7 @@ */ #include "audio_decoder.h" +#include "audio_buffers.h" #include "exceptions.h" #include "log.h" @@ -30,11 +31,12 @@ using boost::shared_ptr; AudioDecoder::AudioDecoder (shared_ptr f, shared_ptr c) : Decoder (f) , _audio_content (c) + , _output_audio_frame_rate (_audio_content->output_audio_frame_rate (f)) { - if (_audio_content->audio_frame_rate() != _film->target_audio_sample_rate()) { + if (_audio_content->content_audio_frame_rate() != _output_audio_frame_rate) { stringstream s; - s << String::compose ("Will resample audio from %1 to %2", _audio_content->audio_frame_rate(), _film->target_audio_sample_rate()); + s << String::compose ("Will resample audio from %1 to %2", _audio_content->content_audio_frame_rate(), _output_audio_frame_rate); _film->log()->log (s.str ()); /* We will be using planar float data when we call the @@ -49,10 +51,10 @@ AudioDecoder::AudioDecoder (shared_ptr f, shared_ptrtarget_audio_sample_rate(), + _output_audio_frame_rate, av_get_default_channel_layout (MAX_AUDIO_CHANNELS), AV_SAMPLE_FMT_FLTP, - _audio_content->audio_frame_rate(), + _audio_content->content_audio_frame_rate(), 0, 0 ); @@ -74,7 +76,7 @@ AudioDecoder::~AudioDecoder () void AudioDecoder::process_end () { - if (_film->has_audio() && _swr_context) { + if (_swr_context) { shared_ptr out (new AudioBuffers (_film->audio_mapping().dcp_channels(), 256)); @@ -106,7 +108,7 @@ AudioDecoder::emit_audio (shared_ptr data, Time time) if (_swr_context) { /* Compute the resampled frames count and add 32 for luck */ - int const max_resampled_frames = ceil ((int64_t) data->frames() * _film->target_audio_sample_rate() / _audio_content->audio_frame_rate()) + 32; + int const max_resampled_frames = ceil ((int64_t) data->frames() * _output_audio_frame_rate / _audio_content->content_audio_frame_rate()) + 32; shared_ptr resampled (new AudioBuffers (MAX_AUDIO_CHANNELS, max_resampled_frames)); diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 1c7287a55..94845ec23 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -46,6 +46,7 @@ public: private: boost::shared_ptr _audio_content; SwrContext* _swr_context; + int _output_audio_frame_rate; }; #endif diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index 7e28aa5c4..e1fa0c220 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -31,7 +31,7 @@ using boost::lexical_cast; using boost::dynamic_pointer_cast; void -AudioMapping::add (Channel c, libdcp::Channel d) +AudioMapping::add (int c, libdcp::Channel d) { _content_to_dcp.push_back (make_pair (c, d)); } @@ -40,7 +40,7 @@ AudioMapping::add (Channel c, libdcp::Channel d) int AudioMapping::dcp_channels () const { - for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { if (((int) i->second) >= 2) { return 6; } @@ -49,11 +49,11 @@ AudioMapping::dcp_channels () const return 2; } -list +list AudioMapping::dcp_to_content (libdcp::Channel d) const { - list c; - for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + list c; + for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { if (i->second == d) { c.push_back (i->first); } @@ -62,11 +62,11 @@ AudioMapping::dcp_to_content (libdcp::Channel d) const return c; } -list +list AudioMapping::content_channels () const { - list c; - for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + list c; + for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { if (find (c.begin(), c.end(), i->first) == c.end ()) { c.push_back (i->first); } @@ -76,10 +76,10 @@ AudioMapping::content_channels () const } list -AudioMapping::content_to_dcp (Channel c) const +AudioMapping::content_to_dcp (int c) const { list d; - for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { if (i->first == c) { d.push_back (i->second); } @@ -91,41 +91,18 @@ AudioMapping::content_to_dcp (Channel c) const void AudioMapping::as_xml (xmlpp::Node* node) const { - for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { xmlpp::Node* t = node->add_child ("Map"); - shared_ptr c = i->first.content.lock (); - t->add_child ("Content")->add_child_text (c->digest ()); - t->add_child ("ContentIndex")->add_child_text (lexical_cast (i->first.index)); + t->add_child ("ContentIndex")->add_child_text (lexical_cast (i->first)); t->add_child ("DCP")->add_child_text (lexical_cast (i->second)); } } void -AudioMapping::set_from_xml (ContentList const & content, shared_ptr node) +AudioMapping::set_from_xml (shared_ptr node) { list > const c = node->node_children ("Map"); for (list >::const_iterator i = c.begin(); i != c.end(); ++i) { - string const c = (*i)->string_child ("Content"); - ContentList::const_iterator j = content.begin (); - while (j != content.end() && (*j)->digest() != c) { - ++j; - } - - if (j == content.end ()) { - continue; - } - - shared_ptr ac = dynamic_pointer_cast (*j); - assert (ac); - - add (AudioMapping::Channel (ac, (*i)->number_child ("ContentIndex")), static_cast ((*i)->number_child ("DCP"))); + add ((*i)->number_child ("ContentIndex"), static_cast ((*i)->number_child ("DCP"))); } } - -bool -operator== (AudioMapping::Channel const & a, AudioMapping::Channel const & b) -{ - shared_ptr sa = a.content.lock (); - shared_ptr sb = b.content.lock (); - return sa == sb && a.index == b.index; -} diff --git a/src/lib/audio_mapping.h b/src/lib/audio_mapping.h index 248d2570e..3c471d3f4 100644 --- a/src/lib/audio_mapping.h +++ b/src/lib/audio_mapping.h @@ -30,33 +30,21 @@ class AudioMapping { public: void as_xml (xmlpp::Node *) const; - void set_from_xml (ContentList const &, boost::shared_ptr); - - struct Channel { - Channel (boost::weak_ptr c, int i) - : content (c) - , index (i) - {} - - boost::weak_ptr content; - int index; - }; - - void add (Channel, libdcp::Channel); + void set_from_xml (boost::shared_ptr); + + void add (int, libdcp::Channel); int dcp_channels () const; - std::list dcp_to_content (libdcp::Channel) const; - std::list > content_to_dcp () const { + std::list dcp_to_content (libdcp::Channel) const; + std::list > content_to_dcp () const { return _content_to_dcp; } - std::list content_channels () const; - std::list content_to_dcp (Channel) const; + std::list content_channels () const; + std::list content_to_dcp (int) const; private: - std::list > _content_to_dcp; + std::list > _content_to_dcp; }; -extern bool operator== (AudioMapping::Channel const &, AudioMapping::Channel const &); - #endif diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h index 2e3ead005..1aad5edf9 100644 --- a/src/lib/audio_sink.h +++ b/src/lib/audio_sink.h @@ -20,6 +20,8 @@ #ifndef DCPOMATIC_AUDIO_SINK_H #define DCPOMATIC_AUDIO_SINK_H +class AudioBuffers; + class AudioSink { public: diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc index 9f9461ec2..0afb9807a 100644 --- a/src/lib/combiner.cc +++ b/src/lib/combiner.cc @@ -33,7 +33,7 @@ Combiner::Combiner (shared_ptr log) * @param image Frame image. */ void -Combiner::process_video (shared_ptr image, bool, shared_ptr, double) +Combiner::process_video (shared_ptr image, bool, shared_ptr, Time) { _image.reset (new SimpleImage (image)); } @@ -43,7 +43,7 @@ Combiner::process_video (shared_ptr image, bool, shared_ptr image, bool, shared_ptr sub, double t) +Combiner::process_video_b (shared_ptr image, bool, shared_ptr sub, Time t) { /* Copy the right half of this image into our _image */ /* XXX: this should probably be in the Image class */ diff --git a/src/lib/content.h b/src/lib/content.h index e1cf41df0..9f465570b 100644 --- a/src/lib/content.h +++ b/src/lib/content.h @@ -46,7 +46,7 @@ public: virtual std::string information () const = 0; virtual void as_xml (xmlpp::Node *) const; virtual boost::shared_ptr clone () const = 0; - virtual Time temporal_length () const = 0; + virtual Time length (boost::shared_ptr) const = 0; boost::filesystem::path file () const { boost::mutex::scoped_lock lm (_mutex); diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 02ccaa42b..ae0d0c671 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -29,7 +29,6 @@ #include #include #include -#include "util.h" #include "video_source.h" #include "audio_source.h" #include "film.h" diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index 6cb384e20..95e98ab76 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -58,7 +58,6 @@ int const Encoder::_history_size = 25; Encoder::Encoder (shared_ptr f, shared_ptr j) : _film (f) , _job (j) - , _video_frames_in (0) , _video_frames_out (0) , _have_a_real_frame (false) , _terminate (false) @@ -180,13 +179,6 @@ Encoder::frame_done () void Encoder::process_video (shared_ptr image, bool same, shared_ptr sub, Time) { - FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate()); - - if (frc.skip && (_video_frames_in % 2)) { - ++_video_frames_in; - return; - } - boost::mutex::scoped_lock lock (_mutex); /* Wait until the queue has gone down a bit */ @@ -220,7 +212,7 @@ Encoder::process_video (shared_ptr image, bool same, shared_ptrformat()->dcp_size(), _film->format()->dcp_padding (_film), _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), _video_frames_out, _film->dcp_frame_rate(), s.second, + _film->scaler(), _video_frames_out, _film->dcp_video_frame_rate(), s.second, _film->colour_lut(), _film->j2k_bandwidth(), _film->log() ) @@ -230,14 +222,7 @@ Encoder::process_video (shared_ptr image, bool same, shared_ptrrepeat (_video_frames_out); - ++_video_frames_out; - frame_done (); - } } void diff --git a/src/lib/encoder.h b/src/lib/encoder.h index b6d3663fd..6815fa6f6 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -101,8 +101,6 @@ private: /** Number of frames that we should keep history for */ static int const _history_size; - /** Number of video frames received so far */ - ContentVideoFrame _video_frames_in; /** Number of video frames written for the DCP so far */ int _video_frames_out; diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index a61f777c8..55281ff9b 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -206,7 +206,7 @@ FFmpegContent::audio_length () const return 0; } - return video_frames_to_audio_frames (_video_length, audio_frame_rate(), video_frame_rate()); + return video_frames_to_audio_frames (_video_length, content_audio_frame_rate(), video_frame_rate()); } int @@ -220,7 +220,7 @@ FFmpegContent::audio_channels () const } int -FFmpegContent::audio_frame_rate () const +FFmpegContent::content_audio_frame_rate () const { if (!_audio_stream) { return 0; @@ -229,6 +229,28 @@ FFmpegContent::audio_frame_rate () const return _audio_stream->frame_rate; } +int +FFmpegContent::output_audio_frame_rate (shared_ptr film) const +{ + /* Resample to a DCI-approved sample rate */ + double t = dcp_audio_frame_rate (content_audio_frame_rate ()); + + FrameRateConversion frc (video_frame_rate(), film->dcp_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->dcp_video_frame_rate(); + cout << "-> " << t << "\n"; + } + + return rint (t); +} + bool operator== (FFmpegSubtitleStream const & a, FFmpegSubtitleStream const & b) { @@ -281,8 +303,9 @@ FFmpegContent::clone () const return shared_ptr (new FFmpegContent (*this)); } -double -FFmpegContent::temporal_length () const +Time +FFmpegContent::length (shared_ptr film) const { - return video_length() / video_frame_rate(); + FrameRateConversion frc (video_frame_rate (), film->dcp_video_frame_rate ()); + return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate (); } diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 6d7151498..540df041f 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -89,12 +89,13 @@ public: std::string information () const; void as_xml (xmlpp::Node *) const; boost::shared_ptr clone () const; - double temporal_length () const; + Time length (boost::shared_ptr) const; /* AudioContent */ int audio_channels () const; ContentAudioFrame audio_length () const; - int audio_frame_rate () const; + int content_audio_frame_rate () const; + int output_audio_frame_rate (boost::shared_ptr) const; std::vector subtitle_streams () const { boost::mutex::scoped_lock lm (_mutex); diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 0e704bb14..d21a93e29 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -48,6 +48,7 @@ extern "C" { #include "ffmpeg_decoder.h" #include "filter_graph.h" #include "subtitle.h" +#include "audio_buffers.h" #include "i18n.h" @@ -219,8 +220,10 @@ FFmpegDecoder::setup_subtitle () bool FFmpegDecoder::pass () { - int r = av_read_frame (_format_context, &_packet); + cout << "FFmpeg::pass\n"; + int r = av_read_frame (_format_context, &_packet); + if (r < 0) { if (r != AVERROR_EOF) { /* Maybe we should fail here, but for now we'll just finish off instead */ diff --git a/src/lib/film.cc b/src/lib/film.cc index b8102d315..646b114da 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -108,7 +108,7 @@ Film::Film (string d, bool must_exist) , _colour_lut (0) , _j2k_bandwidth (200000000) , _dci_metadata (Config::instance()->default_dci_metadata ()) - , _dcp_frame_rate (0) + , _dcp_video_frame_rate (0) , _dirty (false) { set_dci_date_today (); @@ -179,7 +179,7 @@ Film::Film (Film const & o) , _colour_lut (o._colour_lut) , _j2k_bandwidth (o._j2k_bandwidth) , _dci_metadata (o._dci_metadata) - , _dcp_frame_rate (o._dcp_frame_rate) + , _dcp_video_frame_rate (o._dcp_video_frame_rate) , _dci_date (o._dci_date) , _dirty (o._dirty) { @@ -198,7 +198,7 @@ Film::video_state_identifier () const s << format()->id() << "_" << _playlist->video_digest() << "_" << crop().left << "_" << crop().right << "_" << crop().top << "_" << crop().bottom - << "_" << _dcp_frame_rate + << "_" << _dcp_video_frame_rate << "_" << f.first << "_" << f.second << "_" << scaler()->id() << "_" << j2k_bandwidth() @@ -315,8 +315,8 @@ Film::make_dcp () throw MissingSettingError (_("format")); } - if (_playlist->content().empty ()) { - throw MissingSettingError (_("content")); + if (_playlist->regions().empty ()) { + throw StringError (_("You must add some content to the DCP before creating it")); } if (dcp_content_type() == 0) { @@ -450,9 +450,8 @@ Film::write_metadata () const root->add_child("ColourLUT")->add_child_text (boost::lexical_cast (_colour_lut)); root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast (_j2k_bandwidth)); _dci_metadata.as_xml (root->add_child ("DCIMetadata")); - root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast (_dcp_frame_rate)); + root->add_child("DCPVideoFrameRate")->add_child_text (boost::lexical_cast (_dcp_video_frame_rate)); root->add_child("DCIDate")->add_child_text (boost::gregorian::to_iso_string (_dci_date)); - _audio_mapping.as_xml (root->add_child("AudioMapping")); _playlist->as_xml (root->add_child ("Playlist")); doc.write_to_file_formatted (file ("metadata.xml")); @@ -524,11 +523,10 @@ Film::read_metadata () _colour_lut = f.number_child ("ColourLUT"); _j2k_bandwidth = f.number_child ("J2KBandwidth"); _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); - _dcp_frame_rate = f.number_child ("DCPFrameRate"); + _dcp_video_frame_rate = f.number_child ("DCPVideoFrameRate"); _dci_date = boost::gregorian::from_undelimited_string (f.string_child ("DCIDate")); _playlist->set_from_xml (f.node_child ("Playlist")); - _audio_mapping.set_from_xml (_playlist->content(), f.node_child ("AudioMapping")); _dirty = false; } @@ -577,32 +575,6 @@ Film::file (string f) const return p.string (); } -/** @return The sampling rate that we will resample the audio to */ -int -Film::target_audio_sample_rate () const -{ - if (!has_audio ()) { - return 0; - } - - /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_sample_rate (audio_frame_rate()); - - FrameRateConversion frc (video_frame_rate(), dcp_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() / dcp_frame_rate(); - } - - return rint (t); -} - /** @return a DCI-compliant name for a DCP of this film */ string Film::dci_name (bool if_created_now) const @@ -649,22 +621,7 @@ Film::dci_name (bool if_created_now) const } } - switch (audio_channels ()) { - case 1: - d << "_10"; - break; - case 2: - d << "_20"; - break; - case 6: - d << "_51"; - break; - case 8: - d << "_71"; - break; - } - - d << "_2K"; + d << "_51_2K"; if (!dm.studio.empty ()) { d << "_" << dm.studio; @@ -738,11 +695,11 @@ Film::set_trust_content_headers (bool t) signal_changed (TRUST_CONTENT_HEADERS); - ContentList content = _playlist->content (); - if (!_trust_content_headers && !content.empty()) { + Playlist::RegionList regions = _playlist->regions (); + if (!_trust_content_headers && !regions.empty()) { /* We just said that we don't trust the content's header */ - for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { - examine_content (*i); + for (Playlist::RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + examine_content (i->content); } } } @@ -976,13 +933,13 @@ Film::set_dci_metadata (DCIMetadata m) void -Film::set_dcp_frame_rate (int f) +Film::set_dcp_video_frame_rate (int f) { { boost::mutex::scoped_lock lm (_state_mutex); - _dcp_frame_rate = f; + _dcp_video_frame_rate = f; } - signal_changed (DCP_FRAME_RATE); + signal_changed (DCP_VIDEO_FRAME_RATE); } void @@ -995,8 +952,7 @@ Film::signal_changed (Property p) switch (p) { case Film::CONTENT: - set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); - set_audio_mapping (_playlist->default_audio_mapping ()); + set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ()); break; default: break; @@ -1082,10 +1038,10 @@ Film::playlist () const return _playlist; } -ContentList -Film::content () const +Playlist::RegionList +Film::regions () const { - return _playlist->content (); + return _playlist->regions (); } void @@ -1101,64 +1057,10 @@ Film::remove_content (shared_ptr c) _playlist->remove (c); } -void -Film::move_content_earlier (shared_ptr c) -{ - _playlist->move_earlier (c); -} - -void -Film::move_content_later (shared_ptr c) -{ - _playlist->move_later (c); -} - -ContentAudioFrame -Film::audio_length () const -{ - return _playlist->audio_length (); -} - -int -Film::audio_channels () const -{ - return _playlist->audio_channels (); -} - -int -Film::audio_frame_rate () const -{ - return _playlist->audio_frame_rate (); -} - -bool -Film::has_audio () const -{ - return _playlist->has_audio (); -} - -float -Film::video_frame_rate () const -{ - return _playlist->video_frame_rate (); -} - -libdcp::Size -Film::video_size () const +Time +Film::length () const { - return _playlist->video_size (); -} - -ContentVideoFrame -Film::video_length () const -{ - return _playlist->video_length (); -} - -ContentVideoFrame -Film::content_length () const -{ - return _playlist->content_length (); + return _playlist->length (shared_from_this ()); } bool @@ -1167,24 +1069,17 @@ Film::has_subtitles () const return _playlist->has_subtitles (); } -void -Film::set_audio_mapping (AudioMapping m) +OutputVideoFrame +Film::best_dcp_video_frame_rate () const { - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_mapping = m; - } - - signal_changed (AUDIO_MAPPING); + return _playlist->best_dcp_frame_rate (); } void Film::playlist_content_changed (boost::weak_ptr c, int p) { if (p == VideoContentProperty::VIDEO_FRAME_RATE) { - set_dcp_frame_rate (best_dcp_frame_rate (video_frame_rate ())); - } else if (p == AudioContentProperty::AUDIO_CHANNELS) { - set_audio_mapping (_playlist->default_audio_mapping ()); + set_dcp_video_frame_rate (_playlist->best_dcp_frame_rate ()); } if (ui_signaller) { @@ -1209,3 +1104,34 @@ Film::set_loop (int c) { _playlist->set_loop (c); } + +OutputAudioFrame +Film::time_to_audio_frames (Time t) const +{ + return t * dcp_audio_frame_rate () / TIME_HZ; +} + +OutputVideoFrame +Film::time_to_video_frames (Time t) const +{ + return t * dcp_video_frame_rate () / TIME_HZ; +} + +Time +Film::audio_frames_to_time (OutputAudioFrame f) const +{ + return f * TIME_HZ / dcp_audio_frame_rate (); +} + +Time +Film::video_frames_to_time (OutputVideoFrame f) const +{ + return f * TIME_HZ / dcp_video_frame_rate (); +} + +OutputAudioFrame +Film::dcp_audio_frame_rate () const +{ + /* XXX */ + return 48000; +} diff --git a/src/lib/film.h b/src/lib/film.h index 18255a15e..cfc55c0ac 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -36,7 +36,7 @@ #include "dci_metadata.h" #include "types.h" #include "ffmpeg_content.h" -#include "audio_mapping.h" +#include "playlist.h" class DCPContentType; class Format; @@ -48,7 +48,6 @@ class AnalyseAudioJob; class ExternalAudioStream; class Content; class Player; -class Playlist; /** @class Film * @brief A representation of some audio and video content, and details of @@ -87,8 +86,6 @@ public: std::string file (std::string f) const; std::string dir (std::string d) const; - int target_audio_sample_rate () const; - void write_metadata () const; libdcp::Size cropped_size (libdcp::Size) const; @@ -105,27 +102,24 @@ public: boost::shared_ptr player () const; boost::shared_ptr playlist () const; - /* Proxies for some Playlist methods */ + OutputAudioFrame dcp_audio_frame_rate () const; - ContentList content () 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; - ContentAudioFrame audio_length () const; - int audio_channels () const; - int audio_frame_rate () const; - bool has_audio () const; + /* Proxies for some Playlist methods */ - bool has_subtitles () const; - - float video_frame_rate () const; - libdcp::Size video_size () const; - ContentVideoFrame video_length () const; + Playlist::RegionList regions () const; - ContentVideoFrame content_length () const; + Time length () const; + bool has_subtitles () const; + OutputVideoFrame best_dcp_video_frame_rate () const; void set_loop (int); int loop () const; - enum TrimType { CPL, ENCODE @@ -159,7 +153,7 @@ public: COLOUR_LUT, J2K_BANDWIDTH, DCI_METADATA, - DCP_FRAME_RATE, + DCP_VIDEO_FRAME_RATE, AUDIO_MAPPING }; @@ -271,14 +265,10 @@ public: return _dci_metadata; } - int dcp_frame_rate () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _dcp_frame_rate; - } - - AudioMapping audio_mapping () const { + /* XXX: -> "video_frame_rate" */ + int dcp_video_frame_rate () const { boost::mutex::scoped_lock lm (_state_mutex); - return _audio_mapping; + return _dcp_video_frame_rate; } /* SET */ @@ -289,8 +279,6 @@ public: void set_trust_content_headers (bool); void add_content (boost::shared_ptr); void remove_content (boost::shared_ptr); - void move_content_earlier (boost::shared_ptr); - void move_content_later (boost::shared_ptr); void set_dcp_content_type (DCPContentType const *); void set_format (Format const *); void set_crop (Crop); @@ -312,9 +300,8 @@ public: void set_colour_lut (int); void set_j2k_bandwidth (int); void set_dci_metadata (DCIMetadata); - void set_dcp_frame_rate (int); + void set_dcp_video_frame_rate (int); void set_dci_date_today (); - void set_audio_mapping (AudioMapping); /** Emitted when some property has of the Film has changed */ mutable boost::signals2::signal Changed; @@ -398,10 +385,9 @@ private: /** DCI naming stuff */ DCIMetadata _dci_metadata; /** Frames per second to run our DCP at */ - int _dcp_frame_rate; + int _dcp_video_frame_rate; /** The date that we should use in a DCI name */ boost::gregorian::date _dci_date; - AudioMapping _audio_mapping; /** true if our state has changed since we last saved it */ mutable bool _dirty; diff --git a/src/lib/format.cc b/src/lib/format.cc index f5026c0da..688b22f16 100644 --- a/src/lib/format.cc +++ b/src/lib/format.cc @@ -208,7 +208,8 @@ VariableFormat::VariableFormat (libdcp::Size dcp, string id, string n, string d) float VariableFormat::ratio (shared_ptr f) const { - libdcp::Size const c = f->cropped_size (f->video_size ()); + /* XXX */ + libdcp::Size const c;// = f->cropped_size (f->video_size ()); return float (c.width) / c.height; } diff --git a/src/lib/format.h b/src/lib/format.h index d45a3a10a..29347a3fd 100644 --- a/src/lib/format.h +++ b/src/lib/format.h @@ -24,7 +24,7 @@ #include #include -#include "util.h" +#include class Film; diff --git a/src/lib/imagemagick_content.cc b/src/lib/imagemagick_content.cc index 9e5f00ba0..2e42e25f3 100644 --- a/src/lib/imagemagick_content.cc +++ b/src/lib/imagemagick_content.cc @@ -98,3 +98,10 @@ ImageMagickContent::set_video_length (ContentVideoFrame len) signal_changed (VideoContentProperty::VIDEO_LENGTH); } + +Time +ImageMagickContent::length (shared_ptr film) const +{ + FrameRateConversion frc (24, film->dcp_video_frame_rate ()); + return video_length() * frc.factor() * TIME_HZ / film->dcp_video_frame_rate (); +} diff --git a/src/lib/imagemagick_content.h b/src/lib/imagemagick_content.h index b1e7f9495..366049002 100644 --- a/src/lib/imagemagick_content.h +++ b/src/lib/imagemagick_content.h @@ -38,6 +38,7 @@ public: std::string summary () const; void as_xml (xmlpp::Node *) const; boost::shared_ptr clone () const; + Time length (boost::shared_ptr) const; void set_video_length (ContentVideoFrame); diff --git a/src/lib/player.cc b/src/lib/player.cc index 95036cfe0..9cc166204 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -27,9 +27,11 @@ #include "sndfile_content.h" #include "playlist.h" #include "job.h" +#include "image.h" using std::list; using std::cout; +using std::min; using std::vector; using boost::shared_ptr; using boost::weak_ptr; @@ -42,6 +44,11 @@ Player::Player (shared_ptr f, shared_ptr p) , _audio (true) , _subtitles (true) , _have_valid_decoders (false) + , _position (0) + , _audio_buffers (MAX_AUDIO_CHANNELS, 0) + , _last_video (0) + , _last_was_black (false) + , _last_audio (0) { _playlist->Changed.connect (bind (&Player::playlist_changed, this)); _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2)); @@ -72,118 +79,118 @@ Player::pass () setup_decoders (); _have_valid_decoders = true; } - - bool done = true; - - if (_video && _video_decoder < _video_decoders.size ()) { - - /* Run video decoder; this may also produce audio */ - - if (_video_decoders[_video_decoder]->pass ()) { - _video_decoder++; - } - - if (_video_decoder < _video_decoders.size ()) { - done = false; - } - - } - - if (!_video && _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG && _sequential_audio_decoder < _audio_decoders.size ()) { - - /* We're not producing video, so we may need to run FFmpeg content to get the audio */ - - if (_audio_decoders[_sequential_audio_decoder]->pass ()) { - _sequential_audio_decoder++; - } - - if (_sequential_audio_decoder < _audio_decoders.size ()) { - done = false; - } - - } - - if (_audio && _playlist->audio_from() == Playlist::AUDIO_SNDFILE) { - - /* We're getting audio from SndfileContent */ - - for (vector >::iterator i = _audio_decoders.begin(); i != _audio_decoders.end(); ++i) { - if (!(*i)->pass ()) { - done = false; - } - } - Audio (_audio_buffers, _audio_time.get()); - _audio_buffers.reset (); - _audio_time = boost::none; - } - - return done; + cout << "-> Player::pass\n"; + + /* Here we are just finding the active decoder with the earliest last emission time, then + calling pass on it. If there is no decoder, we skip our position on until there is. + Hence this method will cause video and audio to be emitted, and it is up to the + process_{video,audio} methods to tidy it up. + */ + + Time earliest_pos = TIME_MAX; + shared_ptr earliest; + Time next_wait = TIME_MAX; + + for (list >::iterator i = _decoders.begin(); i != _decoders.end(); ++i) { + Time const ts = (*i)->region.time; + Time const te = (*i)->region.time + (*i)->region.content->length (_film); + if (ts <= _position && te > _position) { + Time const pos = ts + (*i)->last; + if (pos < earliest_pos) { + earliest_pos = pos; + earliest = *i; + } + } + + if (ts > _position) { + next_wait = min (next_wait, ts - _position); + } + } + + if (earliest) { + earliest->decoder->pass (); + _position = earliest->last; + } else if (next_wait < TIME_MAX) { + _position += next_wait; + } else { + cout << "<- Player::pass\n"; + return true; + } + + cout << "<- Player::pass\n"; + return false; } void -Player::process_video (shared_ptr i, bool same, shared_ptr s, double t) +Player::process_video (shared_ptr rd, shared_ptr image, bool same, shared_ptr sub, Time time) { - Video (i, same, s, _video_start[_video_decoder] + t); + shared_ptr vd = dynamic_pointer_cast (rd->decoder); + + Time const global_time = rd->region.time + time; + while ((global_time - _last_video) > 1) { + /* Fill in with black */ + emit_black_frame (); + } + + Video (image, same, sub, global_time); + rd->last = time; + _last_video = global_time; + _last_was_black = false; } void -Player::process_audio (weak_ptr c, shared_ptr b, double t) +Player::process_audio (shared_ptr rd, shared_ptr audio, Time time) { - AudioMapping mapping = _film->audio_mapping (); - if (!_audio_buffers) { - _audio_buffers.reset (new AudioBuffers (mapping.dcp_channels(), b->frames ())); - _audio_buffers->make_silent (); - _audio_time = t; - if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { - _audio_time = _audio_time.get() + _audio_start[_sequential_audio_decoder]; - } - } - - for (int i = 0; i < b->channels(); ++i) { - list dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i)); - for (list::iterator j = dcp.begin(); j != dcp.end(); ++j) { - _audio_buffers->accumulate (b, i, static_cast (*j)); - } - } - - if (_playlist->audio_from() == Playlist::AUDIO_FFMPEG) { - /* We can just emit this audio now as it will all be here */ - Audio (_audio_buffers, t); - _audio_buffers.reset (); - _audio_time = boost::none; - } + /* XXX: mapping */ + + /* The time of this audio may indicate that some of our buffered audio is not going to + be added to any more, so it can be emitted. + */ + + if (time > _last_audio) { + /* We can emit some audio from our buffers */ + OutputAudioFrame const N = min (_film->time_to_audio_frames (time - _last_audio), static_cast (_audio_buffers.frames())); + shared_ptr emit (new AudioBuffers (_audio_buffers.channels(), N)); + emit->copy_from (&_audio_buffers, N, 0, 0); + Audio (emit, _last_audio); + _last_audio += _film->audio_frames_to_time (N); + + /* And remove it from our buffers */ + if (_audio_buffers.frames() > N) { + _audio_buffers.move (N, 0, _audio_buffers.frames() - N); + } + _audio_buffers.set_frames (_audio_buffers.frames() - N); + } + + /* Now accumulate the new audio into our buffers */ + + if (_audio_buffers.frames() == 0) { + /* We have no remaining data. Emit silence up to the start of this new data */ + if ((time - _last_audio) > 0) { + emit_silence (time - _last_audio); + } + } + + _audio_buffers.ensure_size (time - _last_audio + audio->frames()); + _audio_buffers.accumulate (audio.get(), 0, _film->time_to_audio_frames (time - _last_audio)); + rd->last = time + _film->audio_frames_to_time (audio->frames ()); } /** @return true on error */ bool -Player::seek (double t) +Player::seek (Time t) { if (!_have_valid_decoders) { setup_decoders (); _have_valid_decoders = true; } - if (_video_decoders.empty ()) { + if (_decoders.empty ()) { return true; } - /* Find the decoder that contains this position */ - _video_decoder = 0; - while (1) { - ++_video_decoder; - if (_video_decoder >= _video_decoders.size () || t < _video_start[_video_decoder]) { - --_video_decoder; - t -= _video_start[_video_decoder]; - break; - } - } - - if (_video_decoder < _video_decoders.size()) { - _video_decoders[_video_decoder]->seek (t); - } else { - return true; - } + /* XXX */ /* XXX: don't seek audio because we don't need to... */ @@ -207,106 +214,60 @@ Player::seek_forward () void Player::setup_decoders () { - vector > old_video_decoders = _video_decoders; + list > old_decoders = _decoders; - _video_decoders.clear (); - _video_decoder = 0; - _audio_decoders.clear (); - _sequential_audio_decoder = 0; + _decoders.clear (); - _video_start.clear(); - _audio_start.clear(); + Playlist::RegionList regions = _playlist->regions (); + for (Playlist::RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - double video_so_far = 0; - double audio_so_far = 0; + shared_ptr rd (new RegionDecoder); + rd->region = *i; + + /* XXX: into content? */ - for (int l = 0; l < _playlist->loop(); ++l) { - list > vc = _playlist->video (); - for (list >::iterator i = vc.begin(); i != vc.end(); ++i) { - - shared_ptr video_content; - shared_ptr audio_content; - shared_ptr video_decoder; - shared_ptr audio_decoder; - - /* XXX: into content? */ + shared_ptr fc = dynamic_pointer_cast (i->content); + if (fc) { + shared_ptr fd (new FFmpegDecoder (_film, fc, _video, _audio, _subtitles)); - shared_ptr fc = dynamic_pointer_cast (*i); - if (fc) { - shared_ptr fd ( - new FFmpegDecoder ( - _film, fc, _video, - _audio && _playlist->audio_from() == Playlist::AUDIO_FFMPEG, - _subtitles - ) - ); - - video_content = fc; - audio_content = fc; - video_decoder = fd; - audio_decoder = fd; - - video_decoder->connect_video (shared_from_this ()); - } - - shared_ptr ic = dynamic_pointer_cast (*i); - if (ic) { - video_content = ic; - - /* See if we can re-use an old ImageMagickDecoder */ - for (vector >::const_iterator i = old_video_decoders.begin(); i != old_video_decoders.end(); ++i) { - shared_ptr imd = dynamic_pointer_cast (*i); - if (imd && imd->content() == ic) { - video_decoder = *i; - } - } + fd->Video.connect (bind (&Player::process_video, this, rd, _1, _2, _3, _4)); + fd->Audio.connect (bind (&Player::process_audio, this, rd, _1, _2)); - if (!video_decoder) { - video_decoder.reset (new ImageMagickDecoder (_film, ic)); - video_decoder->connect_video (shared_from_this ()); - } - } - - _video_decoders.push_back (video_decoder); - _video_start.push_back (video_so_far); - video_so_far += video_content->video_length() / video_content->video_frame_rate(); - - if (audio_decoder && _playlist->audio_from() == Playlist::AUDIO_FFMPEG) { - audio_decoder->Audio.connect (bind (&Player::process_audio, this, audio_content, _1, _2)); - _audio_decoders.push_back (audio_decoder); - _audio_start.push_back (audio_so_far); - audio_so_far += double(audio_content->audio_length()) / audio_content->audio_frame_rate(); - } + rd->decoder = fd; } - _video_decoder = 0; - _sequential_audio_decoder = 0; - - if (_playlist->audio_from() == Playlist::AUDIO_SNDFILE) { + shared_ptr ic = dynamic_pointer_cast (i->content); + if (ic) { + shared_ptr id; - list > ac = _playlist->audio (); - for (list >::iterator i = ac.begin(); i != ac.end(); ++i) { - - shared_ptr sc = dynamic_pointer_cast (*i); - assert (sc); - - shared_ptr d (new SndfileDecoder (_film, sc)); - d->Audio.connect (bind (&Player::process_audio, this, sc, _1, _2)); - _audio_decoders.push_back (d); - _audio_start.push_back (audio_so_far); + /* See if we can re-use an old ImageMagickDecoder */ + for (list >::const_iterator i = old_decoders.begin(); i != old_decoders.end(); ++i) { + shared_ptr imd = dynamic_pointer_cast ((*i)->decoder); + if (imd && imd->content() == ic) { + id = imd; + } + } + + if (!id) { + id.reset (new ImageMagickDecoder (_film, ic)); + id->Video.connect (bind (&Player::process_video, this, rd, _1, _2, _3, _4)); } + + rd->decoder = id; } - } -} -double -Player::last_video_time () const -{ - if (_video_decoder >= _video_decoders.size ()) { - return 0; + shared_ptr sc = dynamic_pointer_cast (i->content); + if (sc) { + shared_ptr sd (new SndfileDecoder (_film, sc)); + sd->Audio.connect (bind (&Player::process_audio, this, rd, _1, _2)); + + rd->decoder = sd; + } + + _decoders.push_back (rd); } - - return _video_start[_video_decoder] + _video_decoders[_video_decoder]->last_content_time (); + + _position = 0; } void @@ -327,3 +288,26 @@ Player::playlist_changed () { _have_valid_decoders = false; } + +void +Player::emit_black_frame () +{ + shared_ptr image (new SimpleImage (AV_PIX_FMT_RGB24, libdcp::Size (128, 128), true)); + Video (image, _last_was_black, shared_ptr (), _last_video); + _last_video += _film->video_frames_to_time (1); +} + +void +Player::emit_silence (Time t) +{ + OutputAudioFrame frames = _film->time_to_audio_frames (t); + while (frames) { + /* Do this in half-second chunks so we don't overwhelm anybody */ + OutputAudioFrame this_time = min (_film->dcp_audio_frame_rate() / 2, frames); + shared_ptr silence (new AudioBuffers (MAX_AUDIO_CHANNELS, this_time)); + silence->make_silent (); + Audio (silence, _last_audio); + _last_audio += _film->audio_frames_to_time (this_time); + frames -= this_time; + } +} diff --git a/src/lib/player.h b/src/lib/player.h index b1be2f456..c9bf2a00b 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -27,19 +27,20 @@ #include "audio_source.h" #include "video_sink.h" #include "audio_sink.h" +#include "playlist.h" +#include "audio_buffers.h" -class VideoDecoder; -class AudioDecoder; class Job; class Film; class Playlist; class AudioContent; +class Decoder; /** @class Player * @brief A class which can `play' a Playlist; emitting its audio and video. */ -class Player : public VideoSource, public AudioSource, public VideoSink, public boost::enable_shared_from_this +class Player : public VideoSource, public AudioSource, public boost::enable_shared_from_this { public: Player (boost::shared_ptr, boost::shared_ptr); @@ -49,18 +50,34 @@ public: void disable_subtitles (); bool pass (); - bool seek (double); + bool seek (Time); void seek_back (); void seek_forward (); - double last_video_time () const; + Time last_video () const { + return _last_video; + } private: - void process_video (boost::shared_ptr i, bool same, boost::shared_ptr s, double); - void process_audio (boost::weak_ptr, boost::shared_ptr, double); + + struct RegionDecoder + { + RegionDecoder () + : last (0) + {} + + Playlist::Region region; + boost::shared_ptr decoder; + Time last; + }; + + void process_video (boost::shared_ptr, boost::shared_ptr, bool, boost::shared_ptr, Time); + void process_audio (boost::shared_ptr, boost::shared_ptr, Time); void setup_decoders (); void playlist_changed (); void content_changed (boost::weak_ptr, int); + void emit_black_frame (); + void emit_silence (Time); boost::shared_ptr _film; boost::shared_ptr _playlist; @@ -71,21 +88,13 @@ private: /** Our decoders are ready to go; if this is false the decoders must be (re-)created before they are used */ bool _have_valid_decoders; - /** Video decoders in order of presentation */ - std::vector > _video_decoders; - /** Start positions of each video decoder in seconds*/ - std::vector _video_start; - /** Index of current video decoder */ - size_t _video_decoder; - /** Audio decoders in order of presentation (if they are from FFmpeg) */ - std::vector > _audio_decoders; - /** Start positions of each audio decoder (if they are from FFmpeg) in seconds */ - std::vector _audio_start; - /** Current audio decoder index if we are running them sequentially; otherwise undefined */ - size_t _sequential_audio_decoder; - - boost::shared_ptr _audio_buffers; - boost::optional _audio_time; + std::list > _decoders; + + Time _position; + AudioBuffers _audio_buffers; + Time _last_video; + bool _last_was_black; + Time _last_audio; }; #endif diff --git a/src/lib/playlist.cc b/src/lib/playlist.cc index f1dd881b3..8f4a35ac2 100644 --- a/src/lib/playlist.cc +++ b/src/lib/playlist.cc @@ -29,6 +29,8 @@ #include "imagemagick_decoder.h" #include "imagemagick_content.h" #include "job.h" +#include "config.h" +#include "util.h" #include "i18n.h" @@ -39,171 +41,24 @@ using std::min; using std::max; using std::string; using std::stringstream; +using boost::optional; using boost::shared_ptr; using boost::weak_ptr; using boost::dynamic_pointer_cast; using boost::lexical_cast; Playlist::Playlist () - : _audio_from (AUDIO_FFMPEG) - , _loop (1) + : _loop (1) { } Playlist::Playlist (shared_ptr other) - : _audio_from (other->_audio_from) - , _loop (other->_loop) + : _loop (other->_loop) { - for (ContentList::const_iterator i = other->_content.begin(); i != other->_content.end(); ++i) { - _content.push_back ((*i)->clone ()); + for (RegionList::const_iterator i = other->_regions.begin(); i != other->_regions.end(); ++i) { + _regions.push_back (Region (i->content->clone(), i->time, this)); } - - setup (); -} - -void -Playlist::setup () -{ - _audio_from = AUDIO_FFMPEG; - - _video.clear (); - _audio.clear (); - - for (list::iterator i = _content_connections.begin(); i != _content_connections.end(); ++i) { - i->disconnect (); - } - - _content_connections.clear (); - - for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - - /* Video is video */ - shared_ptr vc = dynamic_pointer_cast (*i); - if (vc) { - _video.push_back (vc); - } - - /* FFmpegContent is audio if we are doing AUDIO_FFMPEG */ - shared_ptr fc = dynamic_pointer_cast (*i); - if (fc && _audio_from == AUDIO_FFMPEG) { - _audio.push_back (fc); - } - - /* SndfileContent trumps FFmpegContent for audio */ - shared_ptr sc = dynamic_pointer_cast (*i); - if (sc) { - if (_audio_from == AUDIO_FFMPEG) { - /* This is our fist SndfileContent; clear any FFmpegContent and - say that we are using Sndfile. - */ - _audio.clear (); - _audio_from = AUDIO_SNDFILE; - } - - _audio.push_back (sc); - } - - _content_connections.push_back ((*i)->Changed.connect (bind (&Playlist::content_changed, this, _1, _2))); - } -} - -/** @return Length of our audio */ -ContentAudioFrame -Playlist::audio_length () const -{ - ContentAudioFrame len = 0; - - switch (_audio_from) { - case AUDIO_FFMPEG: - /* FFmpeg content is sequential */ - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - len += (*i)->audio_length (); - } - break; - case AUDIO_SNDFILE: - /* Sndfile content is simultaneous */ - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - len = max (len, (*i)->audio_length ()); - } - break; - } - - return len * _loop; -} - -/** @return number of audio channels */ -int -Playlist::audio_channels () const -{ - int channels = 0; - - switch (_audio_from) { - case AUDIO_FFMPEG: - /* FFmpeg audio is sequential, so use the maximum channel count */ - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - channels = max (channels, (*i)->audio_channels ()); - } - break; - case AUDIO_SNDFILE: - /* Sndfile audio is simultaneous, so it's the sum of the channel counts */ - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - channels += (*i)->audio_channels (); - } - break; - } - - return channels; -} - -int -Playlist::audio_frame_rate () const -{ - if (_audio.empty ()) { - return 0; - } - - /* XXX: assuming that all content has the same rate */ - return _audio.front()->audio_frame_rate (); -} - -float -Playlist::video_frame_rate () const -{ - if (_video.empty ()) { - return 0; - } - - /* XXX: assuming all the same */ - return _video.front()->video_frame_rate (); -} - -libdcp::Size -Playlist::video_size () const -{ - if (_video.empty ()) { - return libdcp::Size (); - } - - /* XXX: assuming all the same */ - return _video.front()->video_size (); -} - -ContentVideoFrame -Playlist::video_length () const -{ - ContentVideoFrame len = 0; - for (list >::const_iterator i = _video.begin(); i != _video.end(); ++i) { - len += (*i)->video_length (); - } - - return len * _loop; -} - -bool -Playlist::has_audio () const -{ - return !_audio.empty (); } void @@ -212,62 +67,19 @@ Playlist::content_changed (weak_ptr c, int p) ContentChanged (c, p); } -AudioMapping -Playlist::default_audio_mapping () const -{ - AudioMapping m; - if (_audio.empty ()) { - return m; - } - - switch (_audio_from) { - case AUDIO_FFMPEG: - { - /* XXX: assumes all the same */ - if (_audio.front()->audio_channels() == 1) { - /* Map mono sources to centre */ - m.add (AudioMapping::Channel (_audio.front(), 0), libdcp::CENTRE); - } else { - int const N = min (_audio.front()->audio_channels (), MAX_AUDIO_CHANNELS); - /* Otherwise just start with a 1:1 mapping */ - for (int i = 0; i < N; ++i) { - m.add (AudioMapping::Channel (_audio.front(), i), (libdcp::Channel) i); - } - } - break; - } - - case AUDIO_SNDFILE: - { - int n = 0; - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - for (int j = 0; j < (*i)->audio_channels(); ++j) { - m.add (AudioMapping::Channel (*i, j), (libdcp::Channel) n); - ++n; - if (n >= MAX_AUDIO_CHANNELS) { - break; - } - } - if (n >= MAX_AUDIO_CHANNELS) { - break; - } - } - break; - } - } - - return m; -} - string Playlist::audio_digest () const { string t; - for (list >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - t += (*i)->digest (); + for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) { + if (!dynamic_pointer_cast (i->content)) { + continue; + } + + t += i->content->digest (); - shared_ptr fc = dynamic_pointer_cast (*i); + shared_ptr fc = dynamic_pointer_cast (i->content); if (fc) { t += lexical_cast (fc->audio_stream()->id); } @@ -283,9 +95,13 @@ Playlist::video_digest () const { string t; - for (list >::const_iterator i = _video.begin(); i != _video.end(); ++i) { - t += (*i)->digest (); - shared_ptr fc = dynamic_pointer_cast (*i); + for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) { + if (!dynamic_pointer_cast (i->content)) { + continue; + } + + t += i->content->digest (); + shared_ptr fc = dynamic_pointer_cast (i->content); if (fc && fc->subtitle_stream()) { t += fc->subtitle_stream()->id; } @@ -296,48 +112,22 @@ Playlist::video_digest () const return md5_digest (t.c_str(), t.length()); } -ContentVideoFrame -Playlist::content_length () const -{ - float const vfr = video_frame_rate() > 0 ? video_frame_rate() : 24; - int const afr = audio_frame_rate() > 0 ? audio_frame_rate() : 48000; - - return max ( - video_length(), - ContentVideoFrame (audio_length() * vfr / afr) - ); -} - void Playlist::set_from_xml (shared_ptr node) { - list > c = node->node_children ("Content"); + list > c = node->node_children ("Region"); for (list >::iterator i = c.begin(); i != c.end(); ++i) { - - string const type = (*i)->string_child ("Type"); - boost::shared_ptr c; - - if (type == "FFmpeg") { - c.reset (new FFmpegContent (*i)); - } else if (type == "ImageMagick") { - c.reset (new ImageMagickContent (*i)); - } else if (type == "Sndfile") { - c.reset (new SndfileContent (*i)); - } - - _content.push_back (c); + _regions.push_back (Region (*i, this)); } _loop = node->number_child ("Loop"); - - setup (); } void Playlist::as_xml (xmlpp::Node* node) { - for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) { - (*i)->as_xml (node->add_child ("Content")); + for (RegionList::iterator i = _regions.begin(); i != _regions.end(); ++i) { + i->as_xml (node->add_child ("Region")); } node->add_child("Loop")->add_child_text(lexical_cast (_loop)); @@ -346,87 +136,146 @@ Playlist::as_xml (xmlpp::Node* node) void Playlist::add (shared_ptr c) { - _content.push_back (c); - setup (); + _regions.push_back (Region (c, 0, this)); Changed (); } void Playlist::remove (shared_ptr c) { - ContentList::iterator i = find (_content.begin(), _content.end(), c); - if (i != _content.end ()) { - _content.erase (i); + RegionList::iterator i = _regions.begin (); + while (i != _regions.end() && i->content != c) { + ++i; } + + if (i != _regions.end ()) { + _regions.erase (i); + Changed (); + } +} - setup (); +void +Playlist::set_loop (int l) +{ + _loop = l; Changed (); } -void -Playlist::move_earlier (shared_ptr c) +bool +Playlist::has_subtitles () const { - ContentList::iterator i = find (_content.begin(), _content.end(), c); - if (i == _content.begin () || i == _content.end()) { - return; + for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) { + shared_ptr fc = dynamic_pointer_cast (i->content); + if (fc && !fc->subtitle_streams().empty()) { + return true; + } } - ContentList::iterator j = i; - --j; - - swap (*i, *j); - - setup (); - Changed (); + return false; } -void -Playlist::move_later (shared_ptr c) +Playlist::Region::Region (shared_ptr c, Time t, Playlist* p) + : content (c) + , time (t) { - ContentList::iterator i = find (_content.begin(), _content.end(), c); - if (i == _content.end()) { - return; - } + connection = c->Changed.connect (bind (&Playlist::content_changed, p, _1, _2)); +} - ContentList::iterator j = i; - ++j; - if (j == _content.end ()) { - return; +Playlist::Region::Region (shared_ptr node, Playlist* p) +{ + shared_ptr content_node = node->node_child ("Content"); + string const type = content_node->string_child ("Type"); + + if (type == "FFmpeg") { + content.reset (new FFmpegContent (content_node)); + } else if (type == "ImageMagick") { + content.reset (new ImageMagickContent (content_node)); + } else if (type == "Sndfile") { + content.reset (new SndfileContent (content_node)); } - swap (*i, *j); - - setup (); - Changed (); + time = node->number_child