diff options
| author | Carl Hetherington <cth@carlh.net> | 2013-05-16 08:36:47 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2013-05-16 08:36:47 +0100 |
| commit | 21ce34c2cd04a2e7e133ff693b84c054182f4f91 (patch) | |
| tree | 5bda50a34b2fa7526dcd682578247f75a85d26b1 /src/lib | |
| parent | 0db016f90ae722fc8b72d465e21d9f153f72b340 (diff) | |
Compiles; strange hang on adding content to a film.
Diffstat (limited to 'src/lib')
41 files changed, 866 insertions, 1122 deletions
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 <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. + +*/ + +#include <cassert> +#include <cstring> +#include <stdexcept> +#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<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (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<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (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<const AudioBuffers> other) + : _channels (other->_channels) + , _frames (other->_frames) + , _allocated_frames (other->_frames) +{ + _data = static_cast<float**> (malloc (_channels * sizeof (float *))); + if (!_data) { + throw bad_alloc (); + } + + for (int i = 0; i < _channels; ++i) { + _data[i] = static_cast<float*> (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<float*> (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 <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. + +*/ + +#include <boost/shared_ptr.hpp> + +/** @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<const AudioBuffers>); + ~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 Film>) 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<const Film> f, shared_ptr<const AudioContent> 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<const Film> f, shared_ptr<const AudioCont 0, av_get_default_channel_layout (MAX_AUDIO_CHANNELS), AV_SAMPLE_FMT_FLTP, - _film->target_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<AudioBuffers> out (new AudioBuffers (_film->audio_mapping().dcp_channels(), 256)); @@ -106,7 +108,7 @@ AudioDecoder::emit_audio (shared_ptr<const AudioBuffers> 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<AudioBuffers> 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<const AudioContent> _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<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list<pair<int, libdcp::Channel> >::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<AudioMapping::Channel> +list<int> AudioMapping::dcp_to_content (libdcp::Channel d) const { - list<AudioMapping::Channel> c; - for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + list<int> c; + for (list<pair<int, libdcp::Channel> >::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<AudioMapping::Channel> +list<int> AudioMapping::content_channels () const { - list<AudioMapping::Channel> c; - for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + list<int> c; + for (list<pair<int, libdcp::Channel> >::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<libdcp::Channel> -AudioMapping::content_to_dcp (Channel c) const +AudioMapping::content_to_dcp (int c) const { list<libdcp::Channel> d; - for (list<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list<pair<int, libdcp::Channel> >::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<pair<Channel, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { + for (list<pair<int, libdcp::Channel> >::const_iterator i = _content_to_dcp.begin(); i != _content_to_dcp.end(); ++i) { xmlpp::Node* t = node->add_child ("Map"); - shared_ptr<const AudioContent> c = i->first.content.lock (); - t->add_child ("Content")->add_child_text (c->digest ()); - t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first.index)); + t->add_child ("ContentIndex")->add_child_text (lexical_cast<string> (i->first)); t->add_child ("DCP")->add_child_text (lexical_cast<string> (i->second)); } } void -AudioMapping::set_from_xml (ContentList const & content, shared_ptr<const cxml::Node> node) +AudioMapping::set_from_xml (shared_ptr<const cxml::Node> node) { list<shared_ptr<cxml::Node> > const c = node->node_children ("Map"); for (list<shared_ptr<cxml::Node> >::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<const AudioContent> ac = dynamic_pointer_cast<const AudioContent> (*j); - assert (ac); - - add (AudioMapping::Channel (ac, (*i)->number_child<int> ("ContentIndex")), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP"))); + add ((*i)->number_child<int> ("ContentIndex"), static_cast<libdcp::Channel> ((*i)->number_child<int> ("DCP"))); } } - -bool -operator== (AudioMapping::Channel const & a, AudioMapping::Channel const & b) -{ - shared_ptr<const AudioContent> sa = a.content.lock (); - shared_ptr<const AudioContent> 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<const cxml::Node>); - - struct Channel { - Channel (boost::weak_ptr<const AudioContent> c, int i) - : content (c) - , index (i) - {} - - boost::weak_ptr<const AudioContent> content; - int index; - }; - - void add (Channel, libdcp::Channel); + void set_from_xml (boost::shared_ptr<const cxml::Node>); + + void add (int, libdcp::Channel); int dcp_channels () const; - std::list<Channel> dcp_to_content (libdcp::Channel) const; - std::list<std::pair<Channel, libdcp::Channel> > content_to_dcp () const { + std::list<int> dcp_to_content (libdcp::Channel) const; + std::list<std::pair<int, libdcp::Channel> > content_to_dcp () const { return _content_to_dcp; } - std::list<Channel> content_channels () const; - std::list<libdcp::Channel> content_to_dcp (Channel) const; + std::list<int> content_channels () const; + std::list<libdcp::Channel> content_to_dcp (int) const; private: - std::list<std::pair<Channel, libdcp::Channel> > _content_to_dcp; + std::list<std::pair<int, libdcp::Channel> > _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> log) * @param image Frame image. */ void -Combiner::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle>, double) +Combiner::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitle>, Time) { _image.reset (new SimpleImage (image)); } @@ -43,7 +43,7 @@ Combiner::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subtitl * @param sub Subtitle (which will be put onto the whole frame) */ void -Combiner::process_video_b (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> sub, double t) +Combiner::process_video_b (shared_ptr<const Image> image, bool, shared_ptr<Subtitle> 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<Content> clone () const = 0; - virtual Time temporal_length () const = 0; + virtual Time length (boost::shared_ptr<const Film>) 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 <stdint.h> #include <boost/shared_ptr.hpp> #include <boost/signals2.hpp> -#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<Film> f, shared_ptr<Job> 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<const Image> image, bool same, shared_ptr<Subtitle> 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<const Image> image, bool same, shared_ptr<Sub new DCPVideoFrame ( image, sub, _film->format()->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<const Image> image, bool same, shared_ptr<Sub _have_a_real_frame = true; } - ++_video_frames_in; ++_video_frames_out; - - if (frc.repeat) { - _writer->repeat (_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<const Film> 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<Content> (new FFmpegContent (*this)); } -double -FFmpegContent::temporal_length () const +Time +FFmpegContent::length (shared_ptr<const Film> 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<Content> clone () const; - double temporal_length () const; + Time length (boost::shared_ptr<const Film>) 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 Film>) const; std::vector<FFmpegSubtitleStream> 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<string> (_colour_lut)); root->add_child("J2KBandwidth")->add_child_text (boost::lexical_cast<string> (_j2k_bandwidth)); _dci_metadata.as_xml (root->add_child ("DCIMetadata")); - root->add_child("DCPFrameRate")->add_child_text (boost::lexical_cast<string> (_dcp_frame_rate)); + root->add_child("DCPVideoFrameRate")->add_child_text (boost::lexical_cast<string> (_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<int> ("ColourLUT"); _j2k_bandwidth = f.number_child<int> ("J2KBandwidth"); _dci_metadata = DCIMetadata (f.node_child ("DCIMetadata")); - _dcp_frame_rate = f.number_child<int> ("DCPFrameRate"); + _dcp_video_frame_rate = f.number_child<int> ("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<Content> c) _playlist->remove (c); } -void -Film::move_content_earlier (shared_ptr<Content> c) -{ - _playlist->move_earlier (c); -} - -void -Film::move_content_later (shared_ptr<Content> 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<Content> 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> player () const; boost::shared_ptr<Playlist> 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<Content>); void remove_content (boost::shared_ptr<Content>); - void move_content_earlier (boost::shared_ptr<Content>); - void move_content_later (boost::shared_ptr<Content>); 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<void (Property)> 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<const Film> 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 <string> #include <vector> -#include "util.h" +#include <libdcp/util.h> 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<const Film> 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<Content> clone () const; + Time length (boost::shared_ptr<const Film>) 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<const Film> f, shared_ptr<const Playlist> 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<shared_ptr<AudioDecoder> >::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<RegionDecoder> earliest; + Time next_wait = TIME_MAX; + + for (list<shared_ptr<RegionDecoder> >::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<const Image> i, bool same, shared_ptr<Subtitle> s, double t) +Player::process_video (shared_ptr<RegionDecoder> rd, shared_ptr<const Image> image, bool same, shared_ptr<Subtitle> sub, Time time) { - Video (i, same, s, _video_start[_video_decoder] + t); + shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> (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<const AudioContent> c, shared_ptr<const AudioBuffers> b, double t) +Player::process_audio (shared_ptr<RegionDecoder> rd, shared_ptr<const AudioBuffers> 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<libdcp::Channel> dcp = mapping.content_to_dcp (AudioMapping::Channel (c, i)); - for (list<libdcp::Channel>::iterator j = dcp.begin(); j != dcp.end(); ++j) { - _audio_buffers->accumulate (b, i, static_cast<int> (*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<OutputAudioFrame> (_audio_buffers.frames())); + shared_ptr<AudioBuffers> 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<shared_ptr<VideoDecoder> > old_video_decoders = _video_decoders; + list<shared_ptr<RegionDecoder> > 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<RegionDecoder> rd (new RegionDecoder); + rd->region = *i; + + /* XXX: into content? */ - for (int l = 0; l < _playlist->loop(); ++l) { - list<shared_ptr<const VideoContent> > vc = _playlist->video (); - for (list<shared_ptr<const VideoContent> >::iterator i = vc.begin(); i != vc.end(); ++i) { - - shared_ptr<const VideoContent> video_content; - shared_ptr<const AudioContent> audio_content; - shared_ptr<VideoDecoder> video_decoder; - shared_ptr<AudioDecoder> audio_decoder; - - /* XXX: into content? */ + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i->content); + if (fc) { + shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio, _subtitles)); - shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); - if (fc) { - shared_ptr<FFmpegDecoder> 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<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i); - if (ic) { - video_content = ic; - - /* See if we can re-use an old ImageMagickDecoder */ - for (vector<shared_ptr<VideoDecoder> >::const_iterator i = old_video_decoders.begin(); i != old_video_decoders.end(); ++i) { - shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> (*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<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (i->content); + if (ic) { + shared_ptr<ImageMagickDecoder> id; - list<shared_ptr<const AudioContent> > ac = _playlist->audio (); - for (list<shared_ptr<const AudioContent> >::iterator i = ac.begin(); i != ac.end(); ++i) { - - shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i); - assert (sc); - - shared_ptr<AudioDecoder> 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<shared_ptr<RegionDecoder> >::const_iterator i = old_decoders.begin(); i != old_decoders.end(); ++i) { + shared_ptr<ImageMagickDecoder> imd = dynamic_pointer_cast<ImageMagickDecoder> ((*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<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (i->content); + if (sc) { + shared_ptr<AudioDecoder> 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<SimpleImage> image (new SimpleImage (AV_PIX_FMT_RGB24, libdcp::Size (128, 128), true)); + Video (image, _last_was_black, shared_ptr<Subtitle> (), _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<AudioBuffers> 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<Player> +class Player : public VideoSource, public AudioSource, public boost::enable_shared_from_this<Player> { public: Player (boost::shared_ptr<const Film>, boost::shared_ptr<const Playlist>); @@ -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<const Image> i, bool same, boost::shared_ptr<Subtitle> s, double); - void process_audio (boost::weak_ptr<const AudioContent>, boost::shared_ptr<const AudioBuffers>, double); + + struct RegionDecoder + { + RegionDecoder () + : last (0) + {} + + Playlist::Region region; + boost::shared_ptr<Decoder> decoder; + Time last; + }; + + void process_video (boost::shared_ptr<RegionDecoder>, boost::shared_ptr<const Image>, bool, boost::shared_ptr<Subtitle>, Time); + void process_audio (boost::shared_ptr<RegionDecoder>, boost::shared_ptr<const AudioBuffers>, Time); void setup_decoders (); void playlist_changed (); void content_changed (boost::weak_ptr<Content>, int); + void emit_black_frame (); + void emit_silence (Time); boost::shared_ptr<const Film> _film; boost::shared_ptr<const Playlist> _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<boost::shared_ptr<VideoDecoder> > _video_decoders; - /** Start positions of each video decoder in seconds*/ - std::vector<double> _video_start; - /** Index of current video decoder */ - size_t _video_decoder; - /** Audio decoders in order of presentation (if they are from FFmpeg) */ - std::vector<boost::shared_ptr<AudioDecoder> > _audio_decoders; - /** Start positions of each audio decoder (if they are from FFmpeg) in seconds */ - std::vector<double> _audio_start; - /** Current audio decoder index if we are running them sequentially; otherwise undefined */ - size_t _sequential_audio_decoder; - - boost::shared_ptr<AudioBuffers> _audio_buffers; - boost::optional<double> _audio_time; + std::list<boost::shared_ptr<RegionDecoder> > _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<const Playlist> 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<boost::signals2::connection>::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<VideoContent> vc = dynamic_pointer_cast<VideoContent> (*i); - if (vc) { - _video.push_back (vc); - } - - /* FFmpegContent is audio if we are doing AUDIO_FFMPEG */ - shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); - if (fc && _audio_from == AUDIO_FFMPEG) { - _audio.push_back (fc); - } - - /* SndfileContent trumps FFmpegContent for audio */ - shared_ptr<SndfileContent> sc = dynamic_pointer_cast<SndfileContent> (*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<shared_ptr<const AudioContent> >::const_iterator i = _audio.begin(); i != _audio.end(); ++i) { - len += (*i)->audio_length (); - } - break; - case AUDIO_SNDFILE: - /* Sndfile content is simultaneous */ - for (list<shared_ptr<const AudioContent> >::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<shared_ptr<const AudioContent> >::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<shared_ptr<const AudioContent> >::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<shared_ptr<const VideoContent> >::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<Content> 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<shared_ptr<const AudioContent> >::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<shared_ptr<const AudioContent> >::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<const AudioContent> (i->content)) { + continue; + } + + t += i->content->digest (); - shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (i->content); if (fc) { t += lexical_cast<string> (fc->audio_stream()->id); } @@ -283,9 +95,13 @@ Playlist::video_digest () const { string t; - for (list<shared_ptr<const VideoContent> >::const_iterator i = _video.begin(); i != _video.end(); ++i) { - t += (*i)->digest (); - shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i); + for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) { + if (!dynamic_pointer_cast<const VideoContent> (i->content)) { + continue; + } + + t += i->content->digest (); + shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (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<const cxml::Node> node) { - list<shared_ptr<cxml::Node> > c = node->node_children ("Content"); + list<shared_ptr<cxml::Node> > c = node->node_children ("Region"); for (list<shared_ptr<cxml::Node> >::iterator i = c.begin(); i != c.end(); ++i) { - - string const type = (*i)->string_child ("Type"); - boost::shared_ptr<Content> 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<int> ("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<string> (_loop)); @@ -346,87 +136,146 @@ Playlist::as_xml (xmlpp::Node* node) void Playlist::add (shared_ptr<Content> c) { - _content.push_back (c); - setup (); + _regions.push_back (Region (c, 0, this)); Changed (); } void Playlist::remove (shared_ptr<Content> 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<Content> 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<const FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (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<Content> c) +Playlist::Region::Region (shared_ptr<Content> 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<const cxml::Node> node, Playlist* p) +{ + shared_ptr<const cxml::Node> 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<Time> ("Time"); + connection = content->Changed.connect (bind (&Playlist::content_changed, p, _1, _2)); } void -Playlist::set_loop (int l) +Playlist::Region::as_xml (xmlpp::Node* node) const { - _loop = l; - Changed (); + xmlpp::Node* sub = node->add_child ("Content"); + content->as_xml (sub); + sub->add_child ("Time")->add_child_text (lexical_cast<string> (time)); } - -shared_ptr<FFmpegContent> -Playlist::ffmpeg () const + +class FrameRateCandidate { - for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) { - shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i); - if (fc) { - return fc; +public: + FrameRateCandidate (float source_, int dcp_) + : source (source_) + , dcp (dcp_) + {} + + float source; + int dcp; +}; + +int +Playlist::best_dcp_frame_rate () const +{ + list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); + + /* Work out what rates we could manage, including those achieved by using skip / repeat. */ + list<FrameRateCandidate> candidates; + + /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (*i, *i)); + } + + /* Then the skip/repeat ones */ + for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { + candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); + candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); + } + + /* Pick the best one, bailing early if we hit an exact match */ + float error = std::numeric_limits<float>::max (); + optional<FrameRateCandidate> best; + list<FrameRateCandidate>::iterator i = candidates.begin(); + while (i != candidates.end()) { + + float this_error = std::numeric_limits<float>::max (); + for (RegionList::const_iterator j = _regions.begin(); j != _regions.end(); ++j) { + shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (j->content); + if (!vc) { + continue; + } + + this_error += fabs (i->source - vc->video_frame_rate ()); } + + if (this_error < error) { + error = this_error; + best = *i; + } + + ++i; } - return shared_ptr<FFmpegContent> (); + if (!best) { + return 24; + } + + return best->dcp; } -bool -Playlist::has_subtitles () const +Time +Playlist::length (shared_ptr<const Film> film) const { - shared_ptr<FFmpegContent> fc = ffmpeg (); - if (!fc) { - return false; + Time len = 0; + for (RegionList::const_iterator i = _regions.begin(); i != _regions.end(); ++i) { + Time const t = i->time + i->content->length (film); + len = max (len, t); } - - return !fc->subtitle_streams().empty(); + + return len; } diff --git a/src/lib/playlist.h b/src/lib/playlist.h index e6acff694..5b9299795 100644 --- a/src/lib/playlist.h +++ b/src/lib/playlist.h @@ -17,6 +17,9 @@ */ +#ifndef DCPOMATIC_PLAYLIST_H +#define DCPOMATIC_PLAYLIST_H + #include <list> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> @@ -46,7 +49,7 @@ class Film; * from the video unless any sound-only files are present. If sound-only files exist, they * are played simultaneously (i.e. they can be split up into multiple files for different channels) */ - + class Playlist { public: @@ -58,44 +61,29 @@ public: void add (boost::shared_ptr<Content>); void remove (boost::shared_ptr<Content>); - void move_earlier (boost::shared_ptr<Content>); - void move_later (boost::shared_ptr<Content>); - ContentAudioFrame audio_length () const; - int audio_channels () const; - int audio_frame_rate () const; - bool has_audio () const; - - float video_frame_rate () const; - libdcp::Size video_size () const; - ContentVideoFrame video_length () const; - - AudioMapping default_audio_mapping () const; - ContentVideoFrame content_length () const; + bool has_subtitles () const; - enum AudioFrom { - AUDIO_FFMPEG, - AUDIO_SNDFILE + struct Region + { + Region () + : time (0) + {} + + Region (boost::shared_ptr<Content> c, Time t, Playlist* p); + Region (boost::shared_ptr<const cxml::Node>, Playlist* p); + + void as_xml (xmlpp::Node *) const; + + boost::shared_ptr<Content> content; + Time time; + boost::signals2::connection connection; }; - AudioFrom audio_from () const { - return _audio_from; - } - - bool has_subtitles () const; + typedef std::vector<Region> RegionList; - ContentList content () const { - return _content; - } - - boost::shared_ptr<FFmpegContent> ffmpeg () const; - - std::list<boost::shared_ptr<const VideoContent> > video () const { - return _video; - } - - std::list<boost::shared_ptr<const AudioContent> > audio () const { - return _audio; + RegionList regions () const { + return _regions; } std::string audio_digest () const; @@ -107,26 +95,17 @@ public: void set_loop (int l); + Time length (boost::shared_ptr<const Film>) const; + int best_dcp_frame_rate () const; + mutable boost::signals2::signal<void ()> Changed; mutable boost::signals2::signal<void (boost::weak_ptr<Content>, int)> ContentChanged; private: - void setup (); void content_changed (boost::weak_ptr<Content>, int); - /** where we should get our audio from */ - AudioFrom _audio_from; - - /** all our content */ - ContentList _content; - /** all our content which contains video */ - std::list<boost::shared_ptr<const VideoContent> > _video; - /** all our content which contains audio. This may contain the same objects - * as _video for FFmpegContent. - */ - std::list<boost::shared_ptr<const AudioContent> > _audio; - + RegionList _regions; int _loop; - - std::list<boost::signals2::connection> _content_connections; }; + +#endif diff --git a/src/lib/sndfile_content.cc b/src/lib/sndfile_content.cc index 539b0dfb5..210ca1577 100644 --- a/src/lib/sndfile_content.cc +++ b/src/lib/sndfile_content.cc @@ -68,7 +68,7 @@ SndfileContent::information () const s << String::compose ( _("%1 channels, %2kHz, %3 samples"), audio_channels(), - audio_frame_rate() / 1000.0, + content_audio_frame_rate() / 1000.0, audio_length() ); @@ -120,3 +120,15 @@ SndfileContent::as_xml (xmlpp::Node* node) const node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate)); } +int +SndfileContent::output_audio_frame_rate (shared_ptr<const Film>) const +{ + /* Resample to a DCI-approved sample rate */ + return dcp_audio_frame_rate (content_audio_frame_rate ()); +} + +Time +SndfileContent::length (shared_ptr<const Film> film) const +{ + return film->audio_frames_to_time (audio_length ()); +} diff --git a/src/lib/sndfile_content.h b/src/lib/sndfile_content.h index e8e86b603..0623aa6f0 100644 --- a/src/lib/sndfile_content.h +++ b/src/lib/sndfile_content.h @@ -41,6 +41,7 @@ public: std::string information () const; void as_xml (xmlpp::Node *) const; boost::shared_ptr<Content> clone () const; + Time length (boost::shared_ptr<const Film>) const; /* AudioContent */ int audio_channels () const { @@ -53,10 +54,12 @@ public: return _audio_length; } - int audio_frame_rate () const { + int content_audio_frame_rate () const { boost::mutex::scoped_lock lm (_mutex); return _audio_frame_rate; } + + int output_audio_frame_rate (boost::shared_ptr<const Film>) const; static bool valid_file (boost::filesystem::path); diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index dc22475cd..f114979de 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -23,6 +23,7 @@ #include "sndfile_decoder.h" #include "film.h" #include "exceptions.h" +#include "audio_buffers.h" #include "i18n.h" @@ -59,7 +60,7 @@ SndfileDecoder::pass () /* Do things in half second blocks as I think there may be limits to what FFmpeg (and in particular the resampler) can cope with. */ - sf_count_t const block = _sndfile_content->audio_frame_rate() / 2; + sf_count_t const block = _sndfile_content->content_audio_frame_rate() / 2; sf_count_t const this_time = min (block, _remaining); int const channels = _sndfile_content->audio_channels (); diff --git a/src/lib/subtitle.h b/src/lib/subtitle.h index 38ba4e70e..2b77eb4cb 100644 --- a/src/lib/subtitle.h +++ b/src/lib/subtitle.h @@ -23,7 +23,7 @@ #include <list> #include <boost/shared_ptr.hpp> -#include "util.h" +#include "types.h" struct AVSubtitle; class Image; diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 0c3b8c37b..a0a9454b7 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -113,18 +113,7 @@ TranscodeJob::remaining_time () const return 0; } - if (!_film->video_length()) { - return 0; - } - /* Compute approximate proposed length here, as it's only here that we need it */ - int length = _film->video_length(); - FrameRateConversion const frc (_film->video_frame_rate(), _film->dcp_frame_rate()); - if (frc.skip) { - length /= 2; - } - /* If we are repeating it shouldn't affect transcode time, so don't take it into account */ - - int const left = length - _transcoder->video_frames_out(); + OutputVideoFrame const left = _film->time_to_video_frames (_film->length ()) - _transcoder->video_frames_out(); return left / fps; } diff --git a/src/lib/types.h b/src/lib/types.h index 5e4826918..4b8b8072d 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -21,15 +21,19 @@ #define DCPOMATIC_TYPES_H #include <vector> +#include <stdint.h> #include <boost/shared_ptr.hpp> #include <libdcp/util.h> class Content; -typedef std::vector<boost::shared_ptr<Content> > ContentList; typedef int64_t ContentAudioFrame; -typedef int ContentVideoFrame; -typedef double Time; +typedef int ContentVideoFrame; +typedef int64_t Time; +#define TIME_MAX INT64_MAX +#define TIME_HZ 96000 +typedef int64_t OutputAudioFrame; +typedef int OutputVideoFrame; /** @struct Crop * @brief A description of the crop of an image or video. diff --git a/src/lib/util.cc b/src/lib/util.cc index 5e957f923..9063b46d4 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -113,6 +113,12 @@ seconds_to_hms (int s) return hms.str (); } +string +time_to_hms (Time t) +{ + return seconds_to_hms (t / TIME_HZ); +} + /** @param s Number of seconds. * @return String containing an approximate description of s (e.g. "about 2 hours") */ @@ -428,66 +434,11 @@ about_equal (float a, float b) return (fabs (a - b) < 1e-4); } -class FrameRateCandidate -{ -public: - FrameRateCandidate (float source_, int dcp_) - : source (source_) - , dcp (dcp_) - {} - - float source; - int dcp; -}; - -int -best_dcp_frame_rate (float source_fps) -{ - list<int> const allowed_dcp_frame_rates = Config::instance()->allowed_dcp_frame_rates (); - - /* Work out what rates we could manage, including those achieved by using skip / repeat. */ - list<FrameRateCandidate> candidates; - - /* Start with the ones without skip / repeat so they will get matched in preference to skipped/repeated ones */ - for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (*i, *i)); - } - - /* Then the skip/repeat ones */ - for (list<int>::const_iterator i = allowed_dcp_frame_rates.begin(); i != allowed_dcp_frame_rates.end(); ++i) { - candidates.push_back (FrameRateCandidate (float (*i) / 2, *i)); - candidates.push_back (FrameRateCandidate (float (*i) * 2, *i)); - } - - /* Pick the best one, bailing early if we hit an exact match */ - float error = std::numeric_limits<float>::max (); - optional<FrameRateCandidate> best; - list<FrameRateCandidate>::iterator i = candidates.begin(); - while (i != candidates.end()) { - - if (about_equal (i->source, source_fps)) { - best = *i; - break; - } - - float const e = fabs (i->source - source_fps); - if (e < error) { - error = e; - best = *i; - } - - ++i; - } - - assert (best); - return best->dcp; -} - -/** @param An arbitrary sampling rate. - * @return The appropriate DCP-approved sampling rate (48kHz or 96kHz). +/** @param An arbitrary audio frame rate. + * @return The appropriate DCP-approved frame rate (48kHz or 96kHz). */ int -dcp_audio_sample_rate (int fs) +dcp_audio_frame_rate (int fs) { if (fs <= 48000) { return 48000; @@ -724,166 +675,6 @@ get_optional_int (multimap<string, string> const & kv, string k) return lexical_cast<int> (i->second); } -/** 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 = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[frames]; - } -} - -/** 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 = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[_frames]; - 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<const AudioBuffers> other) - : _channels (other->_channels) - , _frames (other->_frames) - , _allocated_frames (other->_frames) -{ - _data = new float*[_channels]; - for (int i = 0; i < _channels; ++i) { - _data[i] = new float[_frames]; - memcpy (_data[i], other->_data[i], _frames * sizeof (float)); - } -} - -/** AudioBuffers destructor */ -AudioBuffers::~AudioBuffers () -{ - for (int i = 0; i < _channels; ++i) { - delete[] _data[i]; - } - - delete[] _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* 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 (shared_ptr<const AudioBuffers> 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++; - } -} - /** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () diff --git a/src/lib/util.h b/src/lib/util.h index 51ccbba99..65859309d 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -51,6 +51,7 @@ extern "C" { class Scaler; extern std::string seconds_to_hms (int); +extern std::string time_to_hms (Time); extern std::string seconds_to_approximate_hms (int); extern void stacktrace (std::ostream &, int); extern std::string dependency_version_summary (); @@ -101,10 +102,8 @@ struct FrameRateConversion std::string description; }; -int best_dcp_frame_rate (float); - extern std::string crop_string (Position, libdcp::Size); -extern int dcp_audio_sample_rate (int); +extern int dcp_audio_frame_rate (int); extern std::string colour_lut_index_to_name (int index); extern int stride_round_up (int, int const *, int); extern int stride_lookup (int c, int const * stride); @@ -151,51 +150,6 @@ private: int _timeout; }; -/** @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<const AudioBuffers>); - ~AudioBuffers (); - - 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* from, int frames_to_copy, int read_offset, int write_offset); - void move (int from, int to, int frames); - void accumulate (boost::shared_ptr<const AudioBuffers>, 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; -}; - extern int64_t video_frames_to_audio_frames (ContentVideoFrame v, float audio_sample_rate, float frames_per_second); extern std::pair<std::string, int> cpu_info (); diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 2af6ba908..9fb2b9bce 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -104,9 +104,3 @@ VideoContent::information () const return s.str (); } - -Time -VideoContent::temporal_length () const -{ - return video_length() / video_frame_rate(); -} diff --git a/src/lib/video_content.h b/src/lib/video_content.h index b2ec87e2b..75e507d4d 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -42,7 +42,6 @@ public: void as_xml (xmlpp::Node *) const; virtual std::string information () const; - Time temporal_length () const; ContentVideoFrame video_length () const { boost::mutex::scoped_lock lm (_mutex); diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index fd8238441..a24059da2 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -79,7 +79,7 @@ VideoDecoder::set_progress (Job* j) const { assert (j); - if (_film->video_length()) { - j->set_progress (float (_video_frame) / _film->video_length()); + if (_film->length()) { + j->set_progress (float (_video_frame) / _film->time_to_video_frames (_film->length())); } } diff --git a/src/lib/writer.cc b/src/lib/writer.cc index c7d2cf8b4..f1451763e 100644 --- a/src/lib/writer.cc +++ b/src/lib/writer.cc @@ -75,26 +75,24 @@ Writer::Writer (shared_ptr<Film> f, shared_ptr<Job> j) new libdcp::MonoPictureAsset ( _film->internal_video_mxf_dir (), _film->internal_video_mxf_filename (), - _film->dcp_frame_rate (), + _film->dcp_video_frame_rate (), _film->format()->dcp_size () ) ); _picture_asset_writer = _picture_asset->start_write (_first_nonexistant_frame > 0); - if (_film->audio_channels() > 0) { - _sound_asset.reset ( - new libdcp::SoundAsset ( - _film->dir (_film->dcp_name()), - _film->dcp_audio_mxf_filename (), - _film->dcp_frame_rate (), - _film->audio_mapping().dcp_channels (), - dcp_audio_sample_rate (_film->audio_frame_rate()) - ) - ); - - _sound_asset_writer = _sound_asset->start_write (); - } + _sound_asset.reset ( + new libdcp::SoundAsset ( + _film->dir (_film->dcp_name()), + _film->dcp_audio_mxf_filename (), + _film->dcp_video_frame_rate (), + MAX_AUDIO_CHANNELS, + _film->dcp_audio_frame_rate() + ) + ); + + _sound_asset_writer = _sound_asset->start_write (); _thread = new boost::thread (boost::bind (&Writer::thread, this)); } @@ -205,8 +203,8 @@ try } lock.lock (); - if (_film->video_length ()) { - _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->video_length()); + if (_film->length ()) { + _job->set_progress (float(_full_written + _fake_written + _repeat_written) / _film->time_to_video_frames (_film->length())); } ++_last_written_frame; @@ -263,11 +261,8 @@ Writer::finish () _thread = 0; _picture_asset_writer->finalize (); - - if (_sound_asset_writer) { - _sound_asset_writer->finalize (); - } - + _sound_asset_writer->finalize (); + int const frames = _last_written_frame + 1; int duration = 0; if (_film->trim_type() == Film::CPL) { @@ -302,12 +297,10 @@ Writer::finish () _picture_asset->set_directory (_film->dir (_film->dcp_name ())); _picture_asset->set_file_name (_film->dcp_video_mxf_filename ()); - if (_sound_asset) { - if (_film->trim_type() == Film::CPL) { - _sound_asset->set_entry_point (_film->trim_start ()); - } - _sound_asset->set_duration (duration); + if (_film->trim_type() == Film::CPL) { + _sound_asset->set_entry_point (_film->trim_start ()); } + _sound_asset->set_duration (duration); libdcp::DCP dcp (_film->dir (_film->dcp_name())); @@ -317,7 +310,7 @@ Writer::finish () _film->dcp_name(), _film->dcp_content_type()->libdcp_kind (), frames, - _film->dcp_frame_rate () + _film->dcp_video_frame_rate () ) ); diff --git a/src/lib/wscript b/src/lib/wscript index bce6c644c..0d9da3a8f 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -6,6 +6,7 @@ sources = """ ab_transcoder.cc analyse_audio_job.cc audio_analysis.cc + audio_buffers.cc audio_content.cc audio_decoder.cc audio_mapping.cc |
