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 | |
| parent | 0db016f90ae722fc8b72d465e21d9f153f72b340 (diff) | |
Compiles; strange hang on adding content to a film.
Diffstat (limited to 'src')
52 files changed, 972 insertions, 1337 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 diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc index d1d13ab78..f508b8943 100644 --- a/src/wx/audio_dialog.cc +++ b/src/wx/audio_dialog.cc @@ -90,10 +90,6 @@ AudioDialog::set_film (shared_ptr<Film> f) _film = f; - for (int i = 0; i < MAX_AUDIO_CHANNELS; ++i) { - _channel_checkbox[i]->Show (!_film->audio_mapping().dcp_to_content (static_cast<libdcp::Channel> (i)).empty()); - } - try_to_load_analysis (); _plot->set_gain (_film->audio_gain ()); diff --git a/src/wx/audio_mapping_view.cc b/src/wx/audio_mapping_view.cc index 093d25b7c..10157eb2c 100644 --- a/src/wx/audio_mapping_view.cc +++ b/src/wx/audio_mapping_view.cc @@ -140,7 +140,7 @@ AudioMappingView::set_mapping (AudioMapping map) _grid->DeleteRows (0, _grid->GetNumberRows ()); } - list<AudioMapping::Channel> content_channels = map.content_channels (); + list<int> content_channels = map.content_channels (); _grid->InsertRows (0, content_channels.size ()); for (size_t r = 0; r < content_channels.size(); ++r) { @@ -150,10 +150,8 @@ AudioMappingView::set_mapping (AudioMapping map) } int n = 0; - for (list<AudioMapping::Channel>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) { - shared_ptr<const AudioContent> ac = i->content.lock (); - assert (ac); - _grid->SetCellValue (n, 0, wxString::Format (wxT("%s %d"), std_to_wx (ac->file().filename().string()).data(), i->index + 1)); + for (list<int>::iterator i = content_channels.begin(); i != content_channels.end(); ++i) { + _grid->SetCellValue (n, 0, wxString::Format (wxT("%d"), *i + 1)); list<libdcp::Channel> const d = map.content_to_dcp (*i); for (list<libdcp::Channel>::const_iterator j = d.begin(); j != d.end(); ++j) { diff --git a/src/wx/ffmpeg_content_dialog.cc b/src/wx/ffmpeg_content_dialog.cc index 8adff59f4..0949d02a6 100644 --- a/src/wx/ffmpeg_content_dialog.cc +++ b/src/wx/ffmpeg_content_dialog.cc @@ -113,7 +113,7 @@ FFmpegContentDialog::audio_stream_changed (wxCommandEvent &) } else { s << c->audio_channels() << wxT (" ") << _("channels"); } - s << wxT (", ") << c->audio_frame_rate() << _("Hz"); + s << wxT (", ") << c->content_audio_frame_rate() << _("Hz"); _audio_description->SetLabel (s); } } diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc index 528e3840f..32def4641 100644 --- a/src/wx/film_editor.cc +++ b/src/wx/film_editor.cc @@ -163,21 +163,6 @@ FilmEditor::make_film_panel () grid->Add (_length, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); ++r; - - { - add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0)); - wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL); - add_label_to_sizer (s, _film_panel, _("Start")); - _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_trim_start); - add_label_to_sizer (s, _film_panel, _("End")); - _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1)); - s->Add (_trim_end); - - grid->Add (s, wxGBPosition (r, 1)); - } - ++r; - add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim method"), wxGBPosition (r, 0)); _trim_type = new wxChoice (_film_panel, wxID_ANY); grid->Add (_trim_type, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); @@ -215,8 +200,6 @@ FilmEditor::connect_to_widgets () _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this); _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this); _content_properties->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_properties_clicked), 0, this); - _content_earlier->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this); - _content_later->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this); _content_timeline->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this); _loop_content->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this); _loop_count->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::loop_count_changed), 0, this); @@ -230,8 +213,6 @@ FilmEditor::connect_to_widgets () _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this); _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this); _ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::ab_toggled), 0, this); - _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this); - _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this); _trim_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::trim_type_changed), 0, this); _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this); _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this); @@ -335,8 +316,6 @@ FilmEditor::make_video_panel () _top_crop->SetRange (0, 1024); _right_crop->SetRange (0, 1024); _bottom_crop->SetRange (0, 1024); - _trim_start->SetRange (0, 100); - _trim_end->SetRange (0, 100); _j2k_bandwidth->SetRange (50, 250); } @@ -363,10 +342,6 @@ FilmEditor::make_content_panel () b->Add (_content_remove); _content_properties = new wxButton (_content_panel, wxID_ANY, _("Properties...")); b->Add (_content_properties); - _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Earlier")); - b->Add (_content_earlier); - _content_later = new wxButton (_content_panel, wxID_ANY, _("Later")); - b->Add (_content_later); _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline...")); b->Add (_content_timeline); @@ -589,7 +564,7 @@ FilmEditor::dcp_frame_rate_changed (wxCommandEvent &) return; } - _film->set_dcp_frame_rate ( + _film->set_dcp_video_frame_rate ( boost::lexical_cast<int> ( wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ())) ) @@ -667,12 +642,6 @@ FilmEditor::film_changed (Film::Property p) case Film::SCALER: checked_set (_scaler, Scaler::as_index (_film->scaler ())); break; - case Film::TRIM_START: - checked_set (_trim_start, _film->trim_start()); - break; - case Film::TRIM_END: - checked_set (_trim_end, _film->trim_end()); - break; case Film::TRIM_TYPE: checked_set (_trim_type, _film->trim_type() == Film::CPL ? 0 : 1); break; @@ -706,11 +675,11 @@ FilmEditor::film_changed (Film::Property p) case Film::DCI_METADATA: setup_dcp_name (); break; - case Film::DCP_FRAME_RATE: + case Film::DCP_VIDEO_FRAME_RATE: { bool done = false; for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) { - if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) { + if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) { checked_set (_dcp_frame_rate, i); done = true; break; @@ -721,17 +690,10 @@ FilmEditor::film_changed (Film::Property p) checked_set (_dcp_frame_rate, -1); } - if (_film->video_frame_rate()) { - _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->video_frame_rate ()) != _film->dcp_frame_rate ()); - } else { - _best_dcp_frame_rate->Disable (); - } + _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ()); setup_frame_rate_description (); break; } - case Film::AUDIO_MAPPING: - _audio_mapping->set_mapping (_film->audio_mapping ()); - break; } } @@ -785,20 +747,10 @@ void FilmEditor::setup_length () { stringstream s; - ContentVideoFrame const frames = _film->content_length (); + Time const length = _film->length (); - if (frames && _film->video_frame_rate()) { - s << frames << " " << wx_to_std (_("frames")) << "; " << seconds_to_hms (frames / _film->video_frame_rate()); - } else if (frames) { - s << frames << " " << wx_to_std (_("frames")); - } - + s << time_to_hms (length); _length->SetLabel (std_to_wx (s.str ())); - - if (frames) { - _trim_start->SetRange (0, frames); - _trim_end->SetRange (0, frames); - } } void @@ -806,7 +758,10 @@ FilmEditor::setup_frame_rate_description () { wxString d; int lines = 0; - + +#if 0 + XXX + if (_film->video_frame_rate()) { d << std_to_wx (FrameRateConversion (_film->video_frame_rate(), _film->dcp_frame_rate()).description); ++lines; @@ -819,6 +774,7 @@ FilmEditor::setup_frame_rate_description () ++lines; } } +#endif for (int i = lines; i < 2; ++i) { d << wxT ("\n "); @@ -905,7 +861,7 @@ FilmEditor::set_film (shared_ptr<Film> f) film_changed (Film::COLOUR_LUT); film_changed (Film::J2K_BANDWIDTH); film_changed (Film::DCI_METADATA); - film_changed (Film::DCP_FRAME_RATE); + film_changed (Film::DCP_VIDEO_FRAME_RATE); film_changed (Film::AUDIO_MAPPING); film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAMS); @@ -940,8 +896,6 @@ FilmEditor::set_things_sensitive (bool s) _dcp_content_type->Enable (s); _best_dcp_frame_rate->Enable (s); _dcp_frame_rate->Enable (s); - _trim_start->Enable (s); - _trim_end->Enable (s); _ab->Enable (s); _trim_type->Enable (s); _colour_lut->Enable (s); @@ -1019,26 +973,6 @@ FilmEditor::setup_notebook_size () } void -FilmEditor::trim_start_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_trim_start (_trim_start->GetValue ()); -} - -void -FilmEditor::trim_end_changed (wxCommandEvent &) -{ - if (!_film) { - return; - } - - _film->set_trim_end (_trim_end->GetValue ()); -} - -void FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &) { GainCalculatorDialog* d = new GainCalculatorDialog (this); @@ -1168,13 +1102,13 @@ FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &) return; } - _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->video_frame_rate ())); + _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ()); } void FilmEditor::setup_show_audio_sensitivity () { - _show_audio->Enable (_film && _film->has_audio ()); + _show_audio->Enable (_film); } void @@ -1188,17 +1122,17 @@ FilmEditor::setup_content () _content->DeleteAllItems (); - ContentList content = _film->content (); - for (ContentList::iterator i = content.begin(); i != content.end(); ++i) { + Playlist::RegionList regions = _film->regions (); + for (Playlist::RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { int const t = _content->GetItemCount (); - _content->InsertItem (t, std_to_wx ((*i)->summary ())); - if ((*i)->summary() == selected_summary) { + _content->InsertItem (t, std_to_wx (i->content->summary ())); + if (i->content->summary() == selected_summary) { _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } } - if (selected_summary.empty () && !content.empty ()) { - /* Select the first item of content if non was selected before */ + if (selected_summary.empty () && !regions.empty ()) { + /* Select the item of content if non was selected before */ _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } @@ -1244,10 +1178,10 @@ FilmEditor::content_remove_clicked (wxCommandEvent &) void FilmEditor::content_activated (wxListEvent& ev) { - ContentList c = _film->content (); - assert (ev.GetIndex() >= 0 && size_t (ev.GetIndex()) < c.size ()); + Playlist::RegionList r = _film->regions (); + assert (ev.GetIndex() >= 0 && size_t (ev.GetIndex()) < r.size ()); - content_properties (c[ev.GetIndex()]); + content_properties (r[ev.GetIndex()].content); } void @@ -1262,16 +1196,16 @@ FilmEditor::content_properties_clicked (wxCommandEvent &) } void -FilmEditor::content_properties (shared_ptr<Content> c) +FilmEditor::content_properties (shared_ptr<Content> content) { - shared_ptr<ImageMagickContent> im = dynamic_pointer_cast<ImageMagickContent> (c); + shared_ptr<ImageMagickContent> im = dynamic_pointer_cast<ImageMagickContent> (content); if (im) { ImageMagickContentDialog* d = new ImageMagickContentDialog (this, im); d->ShowModal (); d->Destroy (); } - shared_ptr<FFmpegContent> ff = dynamic_pointer_cast<FFmpegContent> (c); + shared_ptr<FFmpegContent> ff = dynamic_pointer_cast<FFmpegContent> (content); if (ff) { FFmpegContentDialog* d = new FFmpegContentDialog (this, ff); d->ShowModal (); @@ -1280,24 +1214,6 @@ FilmEditor::content_properties (shared_ptr<Content> c) } void -FilmEditor::content_earlier_clicked (wxCommandEvent &) -{ - shared_ptr<Content> c = selected_content (); - if (c) { - _film->move_content_earlier (c); - } -} - -void -FilmEditor::content_later_clicked (wxCommandEvent &) -{ - shared_ptr<Content> c = selected_content (); - if (c) { - _film->move_content_later (c); - } -} - -void FilmEditor::content_selection_changed (wxListEvent &) { setup_content_button_sensitivity (); @@ -1329,8 +1245,6 @@ FilmEditor::setup_content_button_sensitivity () ); _content_remove->Enable (selection && _generally_sensitive); - _content_earlier->Enable (selection && _generally_sensitive); - _content_later->Enable (selection && _generally_sensitive); _content_timeline->Enable (_generally_sensitive); } @@ -1342,12 +1256,12 @@ FilmEditor::selected_content () return shared_ptr<Content> (); } - ContentList c = _film->content (); - if (s < 0 || size_t (s) >= c.size ()) { + Playlist::RegionList r = _film->regions (); + if (s < 0 || size_t (s) >= r.size ()) { return shared_ptr<Content> (); } - return c[s]; + return r[s].content; } void @@ -1355,6 +1269,8 @@ FilmEditor::setup_scaling_description () { wxString d; +#if 0 +XXX int lines = 0; if (_film->video_size().width && _film->video_size().height) { @@ -1403,6 +1319,7 @@ FilmEditor::setup_scaling_description () d << wxT ("\n "); } +#endif _scaling_description->SetLabel (d); } @@ -1444,6 +1361,6 @@ FilmEditor::content_timeline_clicked (wxCommandEvent &) _timeline_dialog = 0; } - _timeline_dialog = new TimelineDialog (this, _film->playlist ()); + _timeline_dialog = new TimelineDialog (this, _film); _timeline_dialog->Show (); } diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h index 41f7bfd1b..c791ad064 100644 --- a/src/wx/film_editor.h +++ b/src/wx/film_editor.h @@ -70,12 +70,8 @@ private: void content_add_clicked (wxCommandEvent &); void content_remove_clicked (wxCommandEvent &); void content_properties_clicked (wxCommandEvent &); - void content_earlier_clicked (wxCommandEvent &); - void content_later_clicked (wxCommandEvent &); void imagemagick_video_length_changed (wxCommandEvent &); void format_changed (wxCommandEvent &); - void trim_start_changed (wxCommandEvent &); - void trim_end_changed (wxCommandEvent &); void trim_type_changed (wxCommandEvent &); void dcp_content_type_changed (wxCommandEvent &); void ab_toggled (wxCommandEvent &); @@ -177,8 +173,6 @@ private: /** The Film's audio details */ wxStaticText* _audio; - wxSpinCtrl* _trim_start; - wxSpinCtrl* _trim_end; wxChoice* _trim_type; /** Selector to generate an A/B comparison DCP */ wxCheckBox* _ab; diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index 03f0ca646..11c826a26 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -180,7 +180,7 @@ FilmViewer::set_film (shared_ptr<Film> f) void FilmViewer::update_from_decoder () { - if (!_player || _player->seek (_player->last_video_time ())) { + if (!_player || _player->seek (_player->last_video ())) { return; } @@ -201,8 +201,8 @@ FilmViewer::timer (wxTimerEvent &) get_frame (); - if (_film->video_length()) { - int const new_slider_position = 4096 * _player->last_video_time() / (_film->video_length() / _film->video_frame_rate()); + if (_film->length()) { + int const new_slider_position = 4096 * _player->last_video() / _film->length(); if (new_slider_position != _slider->GetValue()) { _slider->SetValue (new_slider_position); } @@ -259,7 +259,7 @@ void FilmViewer::slider_moved (wxScrollEvent &) { if (_film && _player) { - _player->seek (_slider->GetValue() * _film->video_length() / (4096 * _film->video_frame_rate())); + _player->seek (_slider->GetValue() * _film->length() / 4096); } get_frame (); @@ -312,7 +312,7 @@ FilmViewer::raw_to_display () when working out the scale that we are applying. */ - Size const cropped_size = _film->cropped_size (_film->video_size ()); + Size const cropped_size = _film->cropped_size (_raw_frame->size ()); Rect tx = subtitle_transformed_area ( float (_film_size.width) / cropped_size.width, @@ -381,7 +381,7 @@ FilmViewer::check_play_state () } if (_play_button->GetValue()) { - _timer.Start (1000 / _film->video_frame_rate()); + _timer.Start (1000 / _film->dcp_video_frame_rate()); } else { _timer.Stop (); } @@ -397,7 +397,7 @@ FilmViewer::process_video (shared_ptr<const Image> image, bool, shared_ptr<Subti _got_frame = true; - double const fps = _film->video_frame_rate (); + double const fps = _film->dcp_video_frame_rate (); _frame->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps)))); double w = t; @@ -425,6 +425,8 @@ FilmViewer::get_frame () return; } + cout << "-> FilmViewer::get_frame()\n"; + try { _got_frame = false; while (!_got_frame) { @@ -441,6 +443,8 @@ FilmViewer::get_frame () check_play_state (); error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data())); } + + cout << "<- FilmViewer::get_frame()\n"; } void diff --git a/src/wx/properties_dialog.cc b/src/wx/properties_dialog.cc index d20a58ca8..40527ded7 100644 --- a/src/wx/properties_dialog.cc +++ b/src/wx/properties_dialog.cc @@ -50,10 +50,8 @@ PropertiesDialog::PropertiesDialog (wxWindow* parent, shared_ptr<Film> film) _encoded = new ThreadedStaticText (this, _("counting..."), boost::bind (&PropertiesDialog::frames_already_encoded, this)); table->Add (_encoded, 1, wxALIGN_CENTER_VERTICAL); - _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->video_length()))); - FrameRateConversion frc (_film->video_frame_rate(), _film->dcp_frame_rate()); - int const dcp_length = _film->video_length() * frc.factor(); - double const disk = ((double) _film->j2k_bandwidth() / 8) * dcp_length / (_film->dcp_frame_rate() * 1073741824.0f); + _frames->SetLabel (std_to_wx (lexical_cast<string> (_film->time_to_video_frames (_film->length())))); + double const disk = ((double) _film->j2k_bandwidth() / 8) * _film->length() / (TIME_HZ * 1073741824.0f); stringstream s; s << fixed << setprecision (1) << disk << wx_to_std (_("Gb")); _disk->SetLabel (std_to_wx (s.str ())); @@ -80,9 +78,9 @@ PropertiesDialog::frames_already_encoded () const return ""; } - if (_film->video_length()) { + if (_film->length()) { /* XXX: encoded_frames() should check which frames have been encoded */ - u << " (" << (_film->encoded_frames() * 100 / _film->video_length()) << "%)"; + u << " (" << (_film->encoded_frames() * 100 / _film->time_to_video_frames (_film->length())) << "%)"; } return u.str (); } diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index 731e72168..f6d41dcb9 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -19,6 +19,7 @@ #include <list> #include <wx/graphics.h> +#include "film.h" #include "timeline.h" #include "wx_util.h" #include "playlist.h" @@ -45,7 +46,7 @@ public: protected: int time_x (Time t) const { - return _timeline.tracks_position().x + t * _timeline.pixels_per_second(); + return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit(); } Timeline& _timeline; @@ -54,7 +55,7 @@ protected: class ContentView : public View { public: - ContentView (Timeline& tl, boost::shared_ptr<const Content> c, Time s, int t) + ContentView (Timeline& tl, shared_ptr<const Content> c, Time s, int t) : View (tl) , _content (c) , _start (s) @@ -66,12 +67,13 @@ public: void paint (wxGraphicsContext* gc) { + shared_ptr<const Film> film = _timeline.film (); shared_ptr<const Content> content = _content.lock (); - if (!content) { + if (!film || !content) { return; } - - Time const len = content->temporal_length (); + + Time const len = content->length (film); gc->SetPen (*wxBLACK_PEN); @@ -107,19 +109,20 @@ public: wxDouble name_leading; gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading); - gc->Clip (wxRegion (time_x (_start), y_pos (_track), len * _timeline.pixels_per_second(), _timeline.track_height())); + gc->Clip (wxRegion (time_x (_start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height())); gc->DrawText (name, time_x (_start) + 12, y_pos (_track + 1) - name_height - 4); gc->ResetClip (); } Rect bbox () const { + shared_ptr<const Film> film = _timeline.film (); shared_ptr<const Content> content = _content.lock (); - if (!content) { + if (!film || !content) { return Rect (); } - return Rect (time_x (_start), y_pos (_track), content->temporal_length() * _timeline.pixels_per_second(), _timeline.track_height()); + return Rect (time_x (_start), y_pos (_track), content->length (film) * _timeline.pixels_per_time_unit(), _timeline.track_height()); } void set_selected (bool s) { @@ -150,7 +153,7 @@ private: class AudioContentView : public ContentView { public: - AudioContentView (Timeline& tl, boost::shared_ptr<const Content> c, Time s, int t) + AudioContentView (Timeline& tl, shared_ptr<const Content> c, Time s, int t) : ContentView (tl, c, s, t) {} @@ -169,7 +172,7 @@ private: class VideoContentView : public ContentView { public: - VideoContentView (Timeline& tl, boost::shared_ptr<const Content> c, Time s, int t) + VideoContentView (Timeline& tl, shared_ptr<const Content> c, Time s, int t) : ContentView (tl, c, s, t) {} @@ -202,7 +205,7 @@ public: gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxSOLID)); #endif - int mark_interval = rint (128 / _timeline.pixels_per_second ()); + int mark_interval = rint (128 * TIME_HZ / _timeline.pixels_per_time_unit ()); if (mark_interval > 5) { mark_interval -= mark_interval % 5; } @@ -226,7 +229,7 @@ public: gc->StrokePath (path); Time t = 0; - while ((t * _timeline.pixels_per_second()) < _timeline.width()) { + while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) { wxGraphicsPath path = gc->CreatePath (); path.MoveToPoint (time_x (t), _y - 4); path.AddLineToPoint (time_x (t), _y + 4); @@ -246,7 +249,7 @@ public: wxDouble str_leading; gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading); - int const tx = _timeline.x_offset() + t * _timeline.pixels_per_second(); + int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit(); if ((tx + str_width) < _timeline.width()) { gc->DrawText (str, time_x (t), _y + 16); } @@ -263,14 +266,14 @@ private: int _y; }; -Timeline::Timeline (wxWindow* parent, shared_ptr<Playlist> pl) +Timeline::Timeline (wxWindow* parent, shared_ptr<const Film> film) : wxPanel (parent) - , _playlist (pl) - , _pixels_per_second (0) + , _film (film) + , _pixels_per_time_unit (0) { SetDoubleBuffered (true); - setup_pixels_per_second (); + setup_pixels_per_time_unit (); Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this); Connect (wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler (Timeline::left_down), 0, this); @@ -279,8 +282,8 @@ Timeline::Timeline (wxWindow* parent, shared_ptr<Playlist> pl) playlist_changed (); - pl->Changed.connect (bind (&Timeline::playlist_changed, this)); - pl->ContentChanged.connect (bind (&Timeline::playlist_changed, this)); + film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this)); + film->playlist()->ContentChanged.connect (bind (&Timeline::playlist_changed, this)); } void @@ -288,11 +291,6 @@ Timeline::paint (wxPaintEvent &) { wxPaintDC dc (this); - shared_ptr<Playlist> pl = _playlist.lock (); - if (!pl) { - return; - } - wxGraphicsContext* gc = wxGraphicsContext::Create (dc); if (!gc) { return; @@ -300,9 +298,6 @@ Timeline::paint (wxPaintEvent &) gc->SetFont (gc->CreateFont (*wxNORMAL_FONT)); - /* XXX */ - _pixels_per_second = (width() - x_offset() * 2) / (pl->content_length() / pl->video_frame_rate()); - for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) { (*i)->paint (gc); } @@ -313,30 +308,20 @@ Timeline::paint (wxPaintEvent &) void Timeline::playlist_changed () { - shared_ptr<Playlist> pl = _playlist.lock (); - if (!pl) { + shared_ptr<const Film> fl = _film.lock (); + if (!fl) { return; } _views.clear (); - - int track = 0; - Time time = 0; - list<shared_ptr<const VideoContent> > vc = pl->video (); - for (list<shared_ptr<const VideoContent> >::const_iterator i = vc.begin(); i != vc.end(); ++i) { - _views.push_back (shared_ptr<View> (new VideoContentView (*this, *i, time, track))); - time += (*i)->temporal_length (); - } - ++track; - time = 0; - list<shared_ptr<const AudioContent> > ac = pl->audio (); - for (list<shared_ptr<const AudioContent> >::const_iterator i = ac.begin(); i != ac.end(); ++i) { - _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i, time, track))); - if (pl->audio_from() != Playlist::AUDIO_FFMPEG) { - ++track; + Playlist::RegionList regions = fl->playlist()->regions (); + + for (Playlist::RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { + if (dynamic_pointer_cast<VideoContent> (i->content)) { + _views.push_back (shared_ptr<View> (new VideoContentView (*this, i->content, i->time, 0))); } else { - time += (*i)->temporal_length (); + _views.push_back (shared_ptr<View> (new AudioContentView (*this, i->content, i->time, 1))); } } @@ -348,28 +333,19 @@ Timeline::playlist_changed () int Timeline::tracks () const { - shared_ptr<Playlist> pl = _playlist.lock (); - if (!pl) { - return 0; - } - - if (pl->audio_from() == Playlist::AUDIO_FFMPEG) { - return 2; - } - - return 1 + max (size_t (1), pl->audio().size()); + /* XXX */ + return 2; } void -Timeline::setup_pixels_per_second () +Timeline::setup_pixels_per_time_unit () { - shared_ptr<Playlist> pl = _playlist.lock (); - if (!pl) { + shared_ptr<const Film> film = _film.lock (); + if (!film) { return; } - /* XXX */ - _pixels_per_second = (width() - x_offset() * 2) / (pl->content_length() / pl->video_frame_rate()); + _pixels_per_time_unit = (width() - x_offset() * 2) / film->length(); } void @@ -394,3 +370,9 @@ Timeline::force_redraw (Rect const & r) { RefreshRect (wxRect (r.x, r.y, r.width, r.height), false); } + +shared_ptr<const Film> +Timeline::film () const +{ + return _film.lock (); +} diff --git a/src/wx/timeline.h b/src/wx/timeline.h index 59800e7ad..4214ee3a8 100644 --- a/src/wx/timeline.h +++ b/src/wx/timeline.h @@ -22,13 +22,15 @@ #include <wx/wx.h> #include "util.h" -class Playlist; +class Film; class View; class Timeline : public wxPanel { public: - Timeline (wxWindow *, boost::shared_ptr<Playlist>); + Timeline (wxWindow *, boost::shared_ptr<const Film>); + + boost::shared_ptr<const Film> film () const; void force_redraw (Rect const &); @@ -44,8 +46,8 @@ public: return 64; } - double pixels_per_second () const { - return _pixels_per_second; + double pixels_per_time_unit () const { + return _pixels_per_time_unit; } Position tracks_position () const { @@ -58,9 +60,9 @@ private: void paint (wxPaintEvent &); void left_down (wxMouseEvent &); void playlist_changed (); - void setup_pixels_per_second (); + void setup_pixels_per_time_unit (); - boost::weak_ptr<Playlist> _playlist; + boost::weak_ptr<const Film> _film; std::list<boost::shared_ptr<View> > _views; - double _pixels_per_second; + double _pixels_per_time_unit; }; diff --git a/src/wx/timeline_dialog.cc b/src/wx/timeline_dialog.cc index 7a75044c9..91d1f7b07 100644 --- a/src/wx/timeline_dialog.cc +++ b/src/wx/timeline_dialog.cc @@ -27,9 +27,9 @@ using std::list; using std::cout; using boost::shared_ptr; -TimelineDialog::TimelineDialog (wxWindow* parent, shared_ptr<Playlist> pl) +TimelineDialog::TimelineDialog (wxWindow* parent, shared_ptr<const Film> film) : wxDialog (parent, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) - , _timeline (this, pl) + , _timeline (this, film) { wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL); diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h index e58de5540..bc6b83eb5 100644 --- a/src/wx/timeline_dialog.h +++ b/src/wx/timeline_dialog.h @@ -27,7 +27,7 @@ class Playlist; class TimelineDialog : public wxDialog { public: - TimelineDialog (wxWindow *, boost::shared_ptr<Playlist>); + TimelineDialog (wxWindow *, boost::shared_ptr<const Film>); private: Timeline _timeline; |
