diff options
Diffstat (limited to 'src/lib')
60 files changed, 2351 insertions, 1297 deletions
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc index c40c0916e..537cb4dd7 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -21,12 +21,17 @@ #include <boost/shared_ptr.hpp> #include "ab_transcoder.h" #include "film.h" -#include "decoder.h" +#include "video_decoder.h" +#include "audio_decoder.h" #include "encoder.h" #include "job.h" #include "options.h" #include "image.h" #include "decoder_factory.h" +#include "matcher.h" +#include "delay_line.h" +#include "gain.h" +#include "combiner.h" /** @file src/ab_transcoder.cc * @brief A transcoder which uses one Film for the left half of the screen, and a different one @@ -50,71 +55,66 @@ ABTranscoder::ABTranscoder ( , _opt (o) , _job (j) , _encoder (e) - , _last_frame (0) { _da = decoder_factory (_film_a, o, j); _db = decoder_factory (_film_b, o, j); - _da->Video.connect (bind (&ABTranscoder::process_video, this, _1, _2, _3, 0)); - _db->Video.connect (bind (&ABTranscoder::process_video, this, _1, _2, _3, 1)); - _da->Audio.connect (bind (&Encoder::process_audio, e, _1, _2)); -} + if (_film_a->audio_stream()) { + shared_ptr<AudioStream> st = _film_a->audio_stream(); + _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->frames_per_second())); + _delay_line.reset (new DelayLine (_film_a->log(), st->channels(), _film_a->audio_delay() * st->sample_rate() / 1000)); + _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain())); + } -ABTranscoder::~ABTranscoder () -{ + /* Set up the decoder to use the film's set streams */ + _da.first->set_subtitle_stream (_film_a->subtitle_stream ()); + _db.first->set_subtitle_stream (_film_a->subtitle_stream ()); + _da.second->set_audio_stream (_film_a->audio_stream ()); -} + _da.first->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2)); + _db.first->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2)); -void -ABTranscoder::process_video (shared_ptr<Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub, int index) -{ - if (index == 0) { - /* Keep this image around until we get the other half */ - _image = yuv; + if (_matcher) { + _combiner->connect_video (_matcher); + _matcher->connect_video (_encoder); } else { - /* Copy the right half of yuv into _image */ - for (int i = 0; i < yuv->components(); ++i) { - int const line_size = yuv->line_size()[i]; - int const half_line_size = line_size / 2; - int const stride = yuv->stride()[i]; - - uint8_t* p = _image->data()[i]; - uint8_t* q = yuv->data()[i]; - - for (int j = 0; j < yuv->lines (i); ++j) { - memcpy (p + half_line_size, q + half_line_size, half_line_size); - p += stride; - q += stride; - } - } - - /* And pass it to the encoder */ - _encoder->process_video (_image, frame, sub); - _image.reset (); + _combiner->connect_video (_encoder); } - _last_frame = frame; + if (_matcher && _delay_line) { + _da.second->connect_audio (_delay_line); + _delay_line->connect_audio (_matcher); + _matcher->connect_audio (_gain); + _gain->connect_audio (_encoder); + } } - void ABTranscoder::go () { - _encoder->process_begin (_da->audio_channel_layout()); - _da->process_begin (); - _db->process_begin (); + _encoder->process_begin (); while (1) { - bool const a = _da->pass (); - bool const b = _db->pass (); + bool const va = _da.first->pass (); + bool const vb = _db.first->pass (); + bool const a = _da.first->pass (); + + _da.first->set_progress (); - if (a && b) { + if (va && vb && a) { break; } } + if (_delay_line) { + _delay_line->process_end (); + } + if (_matcher) { + _matcher->process_end (); + } + if (_gain) { + _gain->process_end (); + } _encoder->process_end (); - _da->process_end (); - _db->process_end (); } diff --git a/src/lib/ab_transcoder.h b/src/lib/ab_transcoder.h index a136fd270..9b57e4f73 100644 --- a/src/lib/ab_transcoder.h +++ b/src/lib/ab_transcoder.h @@ -28,12 +28,17 @@ class Job; class Encoder; -class Decoder; +class VideoDecoder; +class AudioDecoder; class Options; class Image; class Log; class Subtitle; class Film; +class Matcher; +class DelayLine; +class Gain; +class Combiner; /** @class ABTranscoder * @brief A transcoder which uses one Film for the left half of the screen, and a different one @@ -50,20 +55,19 @@ public: boost::shared_ptr<Encoder> e ); - ~ABTranscoder (); - void go (); private: - void process_video (boost::shared_ptr<Image>, SourceFrame, boost::shared_ptr<Subtitle>, int); - boost::shared_ptr<Film> _film_a; boost::shared_ptr<Film> _film_b; boost::shared_ptr<const Options> _opt; Job* _job; boost::shared_ptr<Encoder> _encoder; - boost::shared_ptr<Decoder> _da; - boost::shared_ptr<Decoder> _db; - SourceFrame _last_frame; + std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _da; + std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _db; + boost::shared_ptr<Combiner> _combiner; + boost::shared_ptr<Matcher> _matcher; + boost::shared_ptr<DelayLine> _delay_line; + boost::shared_ptr<Gain> _gain; boost::shared_ptr<Image> _image; }; diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc new file mode 100644 index 000000000..70f0effd9 --- /dev/null +++ b/src/lib/audio_decoder.cc @@ -0,0 +1,36 @@ +/* + Copyright (C) 2012 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 "audio_decoder.h" +#include "stream.h" + +using boost::optional; +using boost::shared_ptr; + +AudioDecoder::AudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) + : Decoder (f, o, j) +{ + +} + +void +AudioDecoder::set_audio_stream (shared_ptr<AudioStream> s) +{ + _audio_stream = s; +} diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h new file mode 100644 index 000000000..1570fe3b0 --- /dev/null +++ b/src/lib/audio_decoder.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2012 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. + +*/ + +/** @file src/lib/audio_decoder.h + * @brief Parent class for audio decoders. + */ + +#ifndef DVDOMATIC_AUDIO_DECODER_H +#define DVDOMATIC_AUDIO_DECODER_H + +#include "audio_source.h" +#include "stream.h" +#include "decoder.h" + +/** @class AudioDecoder. + * @brief Parent class for audio decoders. + */ +class AudioDecoder : public AudioSource, public virtual Decoder +{ +public: + AudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); + + virtual void set_audio_stream (boost::shared_ptr<AudioStream>); + + /** @return Audio stream that we are using */ + boost::shared_ptr<AudioStream> audio_stream () const { + return _audio_stream; + } + + /** @return All available audio streams */ + std::vector<boost::shared_ptr<AudioStream> > audio_streams () const { + return _audio_streams; + } + +protected: + /** Audio stream that we are using */ + boost::shared_ptr<AudioStream> _audio_stream; + /** All available audio streams */ + std::vector<boost::shared_ptr<AudioStream> > _audio_streams; +}; + +#endif diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h new file mode 100644 index 000000000..11d578a60 --- /dev/null +++ b/src/lib/audio_sink.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_AUDIO_SINK_H +#define DVDOMATIC_AUDIO_SINK_H + +class AudioSink +{ +public: + /** Call with some audio data */ + virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0; +}; + +#endif diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc new file mode 100644 index 000000000..53b0dda15 --- /dev/null +++ b/src/lib/audio_source.cc @@ -0,0 +1,30 @@ +/* + Copyright (C) 2012 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 "audio_source.h" +#include "audio_sink.h" + +using boost::shared_ptr; +using boost::bind; + +void +AudioSource::connect_audio (shared_ptr<AudioSink> s) +{ + Audio.connect (bind (&AudioSink::process_audio, s, _1)); +} diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h new file mode 100644 index 000000000..5a1510d3c --- /dev/null +++ b/src/lib/audio_source.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2012 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. + +*/ + +/** @file src/audio_source.h + * @brief Parent class for classes which emit audio data. + */ + +#ifndef DVDOMATIC_AUDIO_SOURCE_H +#define DVDOMATIC_AUDIO_SOURCE_H + +#include <boost/signals2.hpp> + +class AudioBuffers; +class AudioSink; + +/** A class that emits audio data */ +class AudioSource +{ +public: + /** Emitted when some audio data is ready */ + boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>)> Audio; + + void connect_audio (boost::shared_ptr<AudioSink>); +}; + +#endif diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc new file mode 100644 index 000000000..b85dbf288 --- /dev/null +++ b/src/lib/combiner.cc @@ -0,0 +1,67 @@ +/* + Copyright (C) 2012 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 "combiner.h" +#include "image.h" + +using boost::shared_ptr; + +Combiner::Combiner (Log* log) + : VideoProcessor (log) +{ + +} + +/** Process video for the left half of the frame. + * @param image Frame image. + * @param sub Subtitle (which will be ignored) + */ +void +Combiner::process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +{ + _image = image; +} + +/** Process video for the right half of the frame. + * @param image Frame image. + * @param sub Subtitle (which will be put onto the whole frame) + */ +void +Combiner::process_video_b (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +{ + /* Copy the right half of this image into our _image */ + /* XXX: this should probably be in the Image class */ + for (int i = 0; i < image->components(); ++i) { + int const line_size = image->line_size()[i]; + int const half_line_size = line_size / 2; + int const stride = image->stride()[i]; + + uint8_t* p = _image->data()[i]; + uint8_t* q = image->data()[i]; + + for (int j = 0; j < image->lines (i); ++j) { + memcpy (p + half_line_size, q + half_line_size, half_line_size); + p += stride; + q += stride; + } + } + + Video (_image, sub); + _image.reset (); +} diff --git a/src/lib/combiner.h b/src/lib/combiner.h new file mode 100644 index 000000000..78c9889b5 --- /dev/null +++ b/src/lib/combiner.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2012 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. + +*/ + +/** @file src/lib/combiner.h + * @brief Class for combining two video streams. + */ + +#include "processor.h" + +/** @class Combiner + * @brief A class which can combine two video streams into one, with + * one image used for the left half of the screen and the other for + * the right. + */ +class Combiner : public VideoProcessor +{ +public: + Combiner (Log* log); + + void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + void process_video_b (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + +private: + /** The image that we are currently working on */ + boost::shared_ptr<Image> _image; +}; diff --git a/src/lib/config.h b/src/lib/config.h index 5113236d6..4575cb54d 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -102,6 +102,7 @@ public: return _tms_password; } + /** @return The sound processor that we are using */ SoundProcessor const * sound_processor () const { return _sound_processor; } diff --git a/src/lib/decoder.cc b/src/lib/decoder.cc index c9d8f063a..2bacf58e7 100644 --- a/src/lib/decoder.cc +++ b/src/lib/decoder.cc @@ -43,273 +43,16 @@ using std::min; using std::pair; using std::list; using boost::shared_ptr; +using boost::optional; /** @param f Film. * @param o Options. * @param j Job that we are running within, or 0 - * @param minimal true to do the bare minimum of work; just run through the content. Useful for acquiring - * accurate frame counts as quickly as possible. This generates no video or audio output. */ Decoder::Decoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j) : _film (f) , _opt (o) , _job (j) - , _video_frame (0) - , _audio_frame (0) - , _delay_line (0) - , _delay_in_frames (0) { } - -Decoder::~Decoder () -{ - delete _delay_line; -} - -/** Start off a decode processing run. This should only be called once on - * a given Decoder object. - */ -void -Decoder::process_begin () -{ - _delay_in_frames = _film->audio_delay() * audio_sample_rate() / 1000; - _delay_line = new DelayLine (audio_channels(), _delay_in_frames); -} - -/** Finish off a decode processing run */ -void -Decoder::process_end () -{ - if (_delay_in_frames < 0 && _opt->decode_audio && audio_channels()) { - shared_ptr<AudioBuffers> b (new AudioBuffers (audio_channels(), -_delay_in_frames)); - b->make_silent (); - emit_audio (b); - } - - if (_opt->decode_audio && audio_channels()) { - - /* Ensure that our video and audio emissions are the same length */ - - int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()) - _audio_frame; - - _film->log()->log ( - String::compose ( - "Decoder has emitted %1 video frames (which equals %2 audio frames) and %3 audio frames", - _video_frame, - video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()), - _audio_frame - ) - ); - - if (audio_short_by_frames < 0) { - - _film->log()->log (String::compose ("Emitted %1 too many audio frames", -audio_short_by_frames)); - - /* We have emitted more audio than video. Emit enough black video frames so that we reverse this */ - int const black_video_frames = ceil (-audio_short_by_frames * frames_per_second() / audio_sample_rate()); - - _film->log()->log (String::compose ("Emitting %1 frames of black video", black_video_frames)); - - shared_ptr<Image> black (new CompactImage (pixel_format(), native_size())); - black->make_black (); - for (int i = 0; i < black_video_frames; ++i) { - emit_video (black, shared_ptr<Subtitle> ()); - } - - /* Now recompute our check value */ - audio_short_by_frames = video_frames_to_audio_frames (_video_frame, audio_sample_rate(), frames_per_second()) - _audio_frame; - } - - if (audio_short_by_frames > 0) { - _film->log()->log (String::compose ("Emitted %1 too few audio frames", audio_short_by_frames)); - shared_ptr<AudioBuffers> b (new AudioBuffers (audio_channels(), audio_short_by_frames)); - b->make_silent (); - emit_audio (b); - } - } -} - -/** Start decoding */ -void -Decoder::go () -{ - process_begin (); - - if (_job && !_film->dcp_length()) { - _job->set_progress_unknown (); - } - - while (pass () == false) { - if (_job && _film->dcp_length()) { - _job->set_progress (float (_video_frame) / _film->length().get()); - } - } - - process_end (); -} - -/** Called by subclasses to tell the world that some audio data is ready - * @param data Audio data, in Film::audio_sample_format. - * @param size Number of bytes of data. - */ -void -Decoder::process_audio (uint8_t* data, int size) -{ - /* XXX: could this be removed? */ - if (size == 0) { - return; - } - - assert (_film->audio_channels()); - assert (bytes_per_audio_sample()); - - /* Deinterleave and convert to float */ - - assert ((size % (bytes_per_audio_sample() * audio_channels())) == 0); - - int const total_samples = size / bytes_per_audio_sample(); - int const frames = total_samples / _film->audio_channels(); - shared_ptr<AudioBuffers> audio (new AudioBuffers (audio_channels(), frames)); - - switch (audio_sample_format()) { - case AV_SAMPLE_FMT_S16: - { - int16_t* p = (int16_t *) data; - int sample = 0; - int channel = 0; - for (int i = 0; i < total_samples; ++i) { - audio->data(channel)[sample] = float(*p++) / (1 << 15); - - ++channel; - if (channel == _film->audio_channels()) { - channel = 0; - ++sample; - } - } - } - break; - - case AV_SAMPLE_FMT_S32: - { - int32_t* p = (int32_t *) data; - int sample = 0; - int channel = 0; - for (int i = 0; i < total_samples; ++i) { - audio->data(channel)[sample] = float(*p++) / (1 << 31); - - ++channel; - if (channel == _film->audio_channels()) { - channel = 0; - ++sample; - } - } - } - - case AV_SAMPLE_FMT_FLTP: - { - float* p = reinterpret_cast<float*> (data); - for (int i = 0; i < _film->audio_channels(); ++i) { - memcpy (audio->data(i), p, frames * sizeof(float)); - p += frames; - } - } - break; - - default: - assert (false); - } - - /* Maybe apply gain */ - if (_film->audio_gain() != 0) { - float const linear_gain = pow (10, _film->audio_gain() / 20); - for (int i = 0; i < _film->audio_channels(); ++i) { - for (int j = 0; j < frames; ++j) { - audio->data(i)[j] *= linear_gain; - } - } - } - - _delay_line->feed (audio); - emit_audio (audio); -} - -/** Called by subclasses to tell the world that some video data is ready. - * We do some post-processing / filtering then emit it for listeners. - * @param frame to decode; caller manages memory. - */ -void -Decoder::process_video (AVFrame* frame) -{ - shared_ptr<FilterGraph> graph; - - list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process (Size (frame->width, frame->height), (AVPixelFormat) frame->format)) { - ++i; - } - - if (i == _filter_graphs.end ()) { - graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format)); - _filter_graphs.push_back (graph); - _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format)); - } else { - graph = *i; - } - - list<shared_ptr<Image> > images = graph->process (frame); - - for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - shared_ptr<Subtitle> sub; - if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) { - sub = _timed_subtitle->subtitle (); - } - - emit_video (*i, sub); - } -} - -void -Decoder::repeat_last_video () -{ - if (!_last_image) { - _last_image.reset (new CompactImage (pixel_format(), native_size())); - _last_image->make_black (); - } - - emit_video (_last_image, _last_subtitle); -} - -void -Decoder::emit_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) -{ - TIMING ("Decoder emits %1", _video_frame); - Video (image, _video_frame, sub); - ++_video_frame; - - _last_image = image; - _last_subtitle = sub; -} - -void -Decoder::emit_audio (shared_ptr<AudioBuffers> audio) -{ - Audio (audio, _audio_frame); - _audio_frame += audio->frames (); -} - -void -Decoder::process_subtitle (shared_ptr<TimedSubtitle> s) -{ - _timed_subtitle = s; - - if (_timed_subtitle && _opt->apply_crop) { - Position const p = _timed_subtitle->subtitle()->position (); - _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top)); - } -} - -int -Decoder::bytes_per_audio_sample () const -{ - return av_get_bytes_per_sample (audio_sample_format ()); -} diff --git a/src/lib/decoder.h b/src/lib/decoder.h index 9f47bf425..e757e5401 100644 --- a/src/lib/decoder.h +++ b/src/lib/decoder.h @@ -31,6 +31,8 @@ #include <boost/signals2.hpp> #include "util.h" #include "stream.h" +#include "video_source.h" +#include "audio_source.h" class Job; class Options; @@ -45,97 +47,25 @@ class FilterGraph; /** @class Decoder. * @brief Parent class for decoders of content. * - * These classes can be instructed run through their content - * (by calling ::go), and they emit signals when video or audio data is ready for something else - * to process. + * These classes can be instructed run through their content (by + * calling ::go), and they emit signals when video or audio data is + * ready for something else to process. */ class Decoder { public: Decoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); - virtual ~Decoder (); + virtual ~Decoder () {} - /* Methods to query our input video */ - - /** @return video frames per second, or 0 if unknown */ - virtual float frames_per_second () const = 0; - /** @return native size in pixels */ - virtual Size native_size () const = 0; - /** @return number of audio channels */ - virtual int audio_channels () const = 0; - /** @return audio sampling rate in Hz */ - virtual int audio_sample_rate () const = 0; - /** @return format of audio samples */ - virtual AVSampleFormat audio_sample_format () const = 0; - virtual int64_t audio_channel_layout () const = 0; - virtual bool has_subtitles () const = 0; - - virtual int time_base_numerator () const = 0; - virtual int time_base_denominator () const = 0; - virtual int sample_aspect_ratio_numerator () const = 0; - virtual int sample_aspect_ratio_denominator () const = 0; - - void process_begin (); virtual bool pass () = 0; - void process_end (); - void go (); - - SourceFrame video_frame () const { - return _video_frame; - } - - virtual std::vector<AudioStream> audio_streams () const { - return std::vector<AudioStream> (); - } - - virtual std::vector<SubtitleStream> subtitle_streams () const { - return std::vector<SubtitleStream> (); - } - - /** Emitted when a video frame is ready. - * First parameter is the frame within the source. - * Second parameter is its index within the content. - * Third parameter is either 0 or a subtitle that should be on this frame. - */ - boost::signals2::signal<void (boost::shared_ptr<Image>, SourceFrame, boost::shared_ptr<Subtitle>)> Video; - - /** Emitted when some audio data is ready */ - boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>, int64_t)> Audio; protected: - - virtual PixelFormat pixel_format () const = 0; - - void process_video (AVFrame *); - void process_audio (uint8_t *, int); - void process_subtitle (boost::shared_ptr<TimedSubtitle>); - void repeat_last_video (); - - int bytes_per_audio_sample () const; - /** our Film */ boost::shared_ptr<Film> _film; /** our options */ boost::shared_ptr<const Options> _opt; /** associated Job, or 0 */ Job* _job; - -private: - void emit_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); - void emit_audio (boost::shared_ptr<AudioBuffers>); - - SourceFrame _video_frame; - int64_t _audio_frame; - - std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; - - DelayLine* _delay_line; - int _delay_in_frames; - - boost::shared_ptr<TimedSubtitle> _timed_subtitle; - - boost::shared_ptr<Image> _last_image; - boost::shared_ptr<Subtitle> _last_subtitle; }; #endif diff --git a/src/lib/decoder_factory.cc b/src/lib/decoder_factory.cc index d4a91d8d9..b2118ef74 100644 --- a/src/lib/decoder_factory.cc +++ b/src/lib/decoder_factory.cc @@ -23,26 +23,33 @@ #include <boost/filesystem.hpp> #include "ffmpeg_decoder.h" -#include "tiff_decoder.h" #include "imagemagick_decoder.h" #include "film.h" +#include "external_audio_decoder.h" using std::string; +using std::pair; +using std::make_pair; using boost::shared_ptr; +using boost::dynamic_pointer_cast; -shared_ptr<Decoder> +pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoder_factory ( shared_ptr<Film> f, shared_ptr<const Options> o, Job* j ) { - if (boost::filesystem::is_directory (f->content_path ())) { - /* Assume a directory contains TIFFs */ - return shared_ptr<Decoder> (new TIFFDecoder (f, o, j)); + if (boost::filesystem::is_directory (f->content_path()) || f->content_type() == STILL) { + /* A single image file, or a directory of them */ + return make_pair ( + shared_ptr<VideoDecoder> (new ImageMagickDecoder (f, o, j)), + shared_ptr<AudioDecoder> () + ); } - if (f->content_type() == STILL) { - return shared_ptr<Decoder> (new ImageMagickDecoder (f, o, j)); + shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (f, o, j)); + if (f->use_content_audio()) { + return make_pair (fd, fd); } - - return shared_ptr<Decoder> (new FFmpegDecoder (f, o, j)); + + return make_pair (fd, shared_ptr<AudioDecoder> (new ExternalAudioDecoder (f, o, j))); } diff --git a/src/lib/decoder_factory.h b/src/lib/decoder_factory.h index 765b38816..1f3690611 100644 --- a/src/lib/decoder_factory.h +++ b/src/lib/decoder_factory.h @@ -18,15 +18,15 @@ */ /** @file src/decoder_factory.h - * @brief A method to create an appropriate decoder for some content. + * @brief A method to create appropriate decoders for some content. */ -class Decoder; class Film; class Options; class Job; -class Log; +class VideoDecoder; +class AudioDecoder; -extern boost::shared_ptr<Decoder> decoder_factory ( +extern std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > decoder_factory ( boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job * ); diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc index e7cd8dc94..45d8e9d9d 100644 --- a/src/lib/delay_line.cc +++ b/src/lib/delay_line.cc @@ -30,28 +30,25 @@ using boost::shared_ptr; /** @param channels Number of channels of audio. * @param frames Delay in frames, +ve to move audio later. */ -DelayLine::DelayLine (int channels, int frames) - : _negative_delay_remaining (0) +DelayLine::DelayLine (Log* log, int channels, int frames) + : AudioProcessor (log) + , _negative_delay_remaining (0) + , _frames (frames) { - if (frames > 0) { + if (_frames > 0) { /* We need a buffer to keep some data in */ - _buffers.reset (new AudioBuffers (channels, frames)); + _buffers.reset (new AudioBuffers (channels, _frames)); _buffers->make_silent (); - } else if (frames < 0) { + } else if (_frames < 0) { /* We can do -ve delays just by chopping off the start, so no buffer needed. */ - _negative_delay_remaining = -frames; + _negative_delay_remaining = -_frames; } } -DelayLine::~DelayLine () -{ - -} - void -DelayLine::feed (shared_ptr<AudioBuffers> data) +DelayLine::process_audio (shared_ptr<AudioBuffers> data) { if (_buffers) { /* We have some buffers, so we are moving the audio later */ @@ -91,4 +88,15 @@ DelayLine::feed (shared_ptr<AudioBuffers> data) _negative_delay_remaining -= to_do; } } + + Audio (data); +} + +void +DelayLine::process_end () +{ + if (_frames < 0) { + _buffers->make_silent (); + Audio (_buffers); + } } diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h index e8d9560af..fa2870ae7 100644 --- a/src/lib/delay_line.h +++ b/src/lib/delay_line.h @@ -18,19 +18,21 @@ */ #include <boost/shared_ptr.hpp> +#include "processor.h" class AudioBuffers; /** A delay line for audio */ -class DelayLine +class DelayLine : public AudioProcessor { public: - DelayLine (int channels, int frames); - ~DelayLine (); + DelayLine (Log* log, int channels, int frames); - void feed (boost::shared_ptr<AudioBuffers>); + void process_audio (boost::shared_ptr<AudioBuffers>); + void process_end (); private: boost::shared_ptr<AudioBuffers> _buffers; int _negative_delay_remaining; ///< number of frames of negative delay that remain to emit + int _frames; }; diff --git a/src/lib/encoder.cc b/src/lib/encoder.cc index b322be04c..17a6726a6 100644 --- a/src/lib/encoder.cc +++ b/src/lib/encoder.cc @@ -37,7 +37,8 @@ Encoder::Encoder (shared_ptr<const Film> f, shared_ptr<const Options> o) : _film (f) , _opt (o) , _just_skipped (false) - , _last_frame (0) + , _video_frame (0) + , _audio_frame (0) { } @@ -68,23 +69,22 @@ Encoder::skipping () const return _just_skipped; } -/** @return Index of last frame to be successfully encoded */ +/** @return Number of video frames that have been received */ SourceFrame -Encoder::last_frame () const +Encoder::video_frame () const { boost::mutex::scoped_lock (_history_mutex); - return _last_frame; + return _video_frame; } /** Should be called when a frame has been encoded successfully. * @param n Source frame index. */ void -Encoder::frame_done (SourceFrame n) +Encoder::frame_done () { boost::mutex::scoped_lock lock (_history_mutex); _just_skipped = false; - _last_frame = n; struct timeval tv; gettimeofday (&tv, 0); @@ -105,24 +105,27 @@ Encoder::frame_skipped () } void -Encoder::process_video (shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s) +Encoder::process_video (shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) { - if (_opt->decode_video_skip != 0 && (f % _opt->decode_video_skip) != 0) { + if (_opt->decode_video_skip != 0 && (_video_frame % _opt->decode_video_skip) != 0) { + ++_video_frame; return; } if (_opt->video_decode_range) { pair<SourceFrame, SourceFrame> const r = _opt->video_decode_range.get(); - if (f < r.first || f >= r.second) { + if (_video_frame < r.first || _video_frame >= r.second) { + ++_video_frame; return; } } - do_process_video (i, f, s); + do_process_video (i, s); + ++_video_frame; } void -Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f) +Encoder::process_audio (shared_ptr<AudioBuffers> data) { if (_opt->audio_decode_range) { @@ -131,7 +134,7 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f) /* Range that we are encoding */ pair<int64_t, int64_t> required_range = _opt->audio_decode_range.get(); /* Range of this block of data */ - pair<int64_t, int64_t> this_range (f, f + trimmed->frames()); + pair<int64_t, int64_t> this_range (_audio_frame, _audio_frame + trimmed->frames()); if (this_range.second < required_range.first || required_range.second < this_range.first) { /* No part of this audio is within the required range */ @@ -150,4 +153,6 @@ Encoder::process_audio (shared_ptr<const AudioBuffers> data, int64_t f) } do_process_audio (data); + + _audio_frame += data->frames (); } diff --git a/src/lib/encoder.h b/src/lib/encoder.h index 20ec8fb16..b12bd0d48 100644 --- a/src/lib/encoder.h +++ b/src/lib/encoder.h @@ -32,6 +32,8 @@ extern "C" { #include <libavutil/samplefmt.h> } #include "util.h" +#include "video_sink.h" +#include "audio_sink.h" class Options; class Image; @@ -49,45 +51,43 @@ class Film; * some way and write it to disk. */ -class Encoder +class Encoder : public VideoSink, public AudioSink { public: Encoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o); virtual ~Encoder () {} /** Called to indicate that a processing run is about to begin */ - virtual void process_begin (int64_t audio_channel_layout) = 0; + virtual void process_begin () {} /** Call with a frame of video. * @param i Video frame image. - * @param f Frame number within the film's source. * @param s A subtitle that should be on this frame, or 0. */ - void process_video (boost::shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); /** Call with some audio data */ - void process_audio (boost::shared_ptr<const AudioBuffers>, int64_t); + void process_audio (boost::shared_ptr<AudioBuffers>); /** Called when a processing run has finished */ - virtual void process_end () = 0; + virtual void process_end () {} float current_frames_per_second () const; bool skipping () const; - SourceFrame last_frame () const; + SourceFrame video_frame () const; protected: /** Called with a frame of video. * @param i Video frame image. - * @param f Frame number within the film's source. * @param s A subtitle that should be on this frame, or 0. */ - virtual void do_process_video (boost::shared_ptr<const Image> i, SourceFrame f, boost::shared_ptr<Subtitle> s) = 0; + virtual void do_process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0; /** Called with some audio data */ - virtual void do_process_audio (boost::shared_ptr<const AudioBuffers>) = 0; + virtual void do_process_audio (boost::shared_ptr<AudioBuffers>) = 0; - void frame_done (SourceFrame n); + void frame_done (); void frame_skipped (); /** Film that we are encoding */ @@ -105,8 +105,11 @@ protected: static int const _history_size; /** true if the last frame we processed was skipped (because it was already done) */ bool _just_skipped; - /** Source index of the last frame to be processed */ - SourceFrame _last_frame; + + /** Number of video frames received so far */ + SourceFrame _video_frame; + /** Number of audio frames received so far */ + int64_t _audio_frame; }; #endif diff --git a/src/lib/examine_content_job.cc b/src/lib/examine_content_job.cc index 4646fe8fc..8db74801f 100644 --- a/src/lib/examine_content_job.cc +++ b/src/lib/examine_content_job.cc @@ -30,9 +30,11 @@ #include "transcoder.h" #include "log.h" #include "film.h" +#include "video_decoder.h" using std::string; using std::vector; +using std::pair; using boost::shared_ptr; ExamineContentJob::ExamineContentJob (shared_ptr<Film> f, shared_ptr<Job> req) @@ -59,17 +61,27 @@ void ExamineContentJob::run () { /* Decode the content to get an accurate length */ + + /* We don't want to use any existing length here, as progress + will be messed up. + */ + _film->unset_length (); shared_ptr<Options> o (new Options ("", "", "")); o->out_size = Size (512, 512); o->apply_crop = false; + o->decode_audio = false; descend (0.5); - _decoder = decoder_factory (_film, o, this); - _decoder->go (); + pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > decoders = decoder_factory (_film, o, this); + + set_progress_unknown (); + while (!decoders.first->pass()) { + /* keep going */ + } - _film->set_length (_decoder->video_frame()); + _film->set_length (decoders.first->video_frame()); _film->log()->log (String::compose ("Video length is %1 frames", _film->length())); diff --git a/src/lib/examine_content_job.h b/src/lib/examine_content_job.h index d87bd0876..2004aca83 100644 --- a/src/lib/examine_content_job.h +++ b/src/lib/examine_content_job.h @@ -23,8 +23,6 @@ #include "job.h" -class Decoder; - /** @class ExamineContentJob * @brief A class to run through content at high speed to find its length. */ @@ -36,8 +34,5 @@ public: std::string name () const; void run (); - -private: - boost::shared_ptr<Decoder> _decoder; }; diff --git a/src/lib/external_audio_decoder.cc b/src/lib/external_audio_decoder.cc new file mode 100644 index 000000000..136e00fb2 --- /dev/null +++ b/src/lib/external_audio_decoder.cc @@ -0,0 +1,185 @@ +/* + Copyright (C) 2012 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 <sndfile.h> +#include "external_audio_decoder.h" +#include "film.h" +#include "exceptions.h" + +using std::vector; +using std::string; +using std::stringstream; +using std::min; +using std::cout; +using boost::shared_ptr; +using boost::optional; + +ExternalAudioDecoder::ExternalAudioDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) + : Decoder (f, o, j) + , AudioDecoder (f, o, j) +{ + sf_count_t frames; + vector<SNDFILE*> sf = open_files (frames); + close_files (sf); +} + +vector<SNDFILE*> +ExternalAudioDecoder::open_files (sf_count_t & frames) +{ + vector<string> const files = _film->external_audio (); + + int N = 0; + for (size_t i = 0; i < files.size(); ++i) { + if (!files[i].empty()) { + N = i + 1; + } + } + + if (N == 0) { + return vector<SNDFILE*> (); + } + + bool first = true; + frames = 0; + + vector<SNDFILE*> sndfiles; + for (size_t i = 0; i < (size_t) N; ++i) { + if (files[i].empty ()) { + sndfiles.push_back (0); + } else { + SF_INFO info; + SNDFILE* s = sf_open (files[i].c_str(), SFM_READ, &info); + if (!s) { + throw DecodeError ("could not open external audio file for reading"); + } + + if (info.channels != 1) { + throw DecodeError ("external audio files must be mono"); + } + + sndfiles.push_back (s); + + if (first) { + shared_ptr<ExternalAudioStream> st ( + new ExternalAudioStream ( + info.samplerate, av_get_default_channel_layout (N) + ) + ); + + _audio_streams.push_back (st); + _audio_stream = st; + frames = info.frames; + first = false; + } else { + if (info.frames != frames) { + throw DecodeError ("external audio files have differing lengths"); + } + } + } + } + + return sndfiles; +} + +bool +ExternalAudioDecoder::pass () +{ + sf_count_t frames; + vector<SNDFILE*> sndfiles = open_files (frames); + if (sndfiles.empty()) { + return true; + } + + /* Do things in half second blocks as I think there may be limits + to what FFmpeg (and in particular the resampler) can cope with. + */ + sf_count_t const block = _audio_stream->sample_rate() / 2; + + shared_ptr<AudioBuffers> audio (new AudioBuffers (_audio_stream->channels(), block)); + while (frames > 0) { + sf_count_t const this_time = min (block, frames); + for (size_t i = 0; i < sndfiles.size(); ++i) { + if (!sndfiles[i]) { + audio->make_silent (i); + } else { + sf_read_float (sndfiles[i], audio->data(i), block); + } + } + + audio->set_frames (this_time); + Audio (audio); + frames -= this_time; + } + + close_files (sndfiles); + + return true; +} + +void +ExternalAudioDecoder::close_files (vector<SNDFILE*> const & sndfiles) +{ + for (size_t i = 0; i < sndfiles.size(); ++i) { + sf_close (sndfiles[i]); + } +} + +shared_ptr<ExternalAudioStream> +ExternalAudioStream::create () +{ + return shared_ptr<ExternalAudioStream> (new ExternalAudioStream); +} + +shared_ptr<ExternalAudioStream> +ExternalAudioStream::create (string t, optional<int> v) +{ + if (!v) { + /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ + return shared_ptr<ExternalAudioStream> (); + } + + stringstream s (t); + string type; + s >> type; + if (type != "external") { + return shared_ptr<ExternalAudioStream> (); + } + + return shared_ptr<ExternalAudioStream> (new ExternalAudioStream (t, v)); +} + +ExternalAudioStream::ExternalAudioStream (string t, optional<int> v) +{ + assert (v); + + stringstream s (t); + string type; + s >> type >> _sample_rate >> _channel_layout; +} + +ExternalAudioStream::ExternalAudioStream () +{ + +} + +string +ExternalAudioStream::to_string () const +{ + return String::compose ("external %1 %2", _sample_rate, _channel_layout); +} diff --git a/src/lib/external_audio_decoder.h b/src/lib/external_audio_decoder.h new file mode 100644 index 000000000..45a2a809c --- /dev/null +++ b/src/lib/external_audio_decoder.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 2012 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 <sndfile.h> +#include "decoder.h" +#include "audio_decoder.h" +#include "stream.h" + +class ExternalAudioStream : public AudioStream +{ +public: + ExternalAudioStream (int sample_rate, int64_t layout) + : AudioStream (sample_rate, layout) + {} + + std::string to_string () const; + + static boost::shared_ptr<ExternalAudioStream> create (); + static boost::shared_ptr<ExternalAudioStream> create (std::string t, boost::optional<int> v); + +private: + friend class stream_test; + + ExternalAudioStream (); + ExternalAudioStream (std::string t, boost::optional<int> v); +}; + +class ExternalAudioDecoder : public AudioDecoder +{ +public: + ExternalAudioDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); + + bool pass (); + +private: + std::vector<SNDFILE*> open_files (sf_count_t &); + void close_files (std::vector<SNDFILE*> const &); +}; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index a2ca739e2..4a6e236c3 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -47,20 +47,24 @@ extern "C" { #include "util.h" #include "log.h" #include "ffmpeg_decoder.h" +#include "filter_graph.h" #include "subtitle.h" using std::cout; using std::string; using std::vector; using std::stringstream; +using std::list; using boost::shared_ptr; +using boost::optional; +using boost::dynamic_pointer_cast; FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) : Decoder (f, o, j) + , VideoDecoder (f, o, j) + , AudioDecoder (f, o, j) , _format_context (0) , _video_stream (-1) - , _audio_stream (-1) - , _subtitle_stream (-1) , _frame (0) , _video_codec_context (0) , _video_codec (0) @@ -116,28 +120,20 @@ FFmpegDecoder::setup_general () if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) { _video_stream = i; } else if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - if (_audio_stream == -1) { - _audio_stream = i; - } - _audio_streams.push_back (AudioStream (stream_name (s), i, s->codec->channels)); + _audio_streams.push_back ( + shared_ptr<AudioStream> ( + new FFmpegAudioStream (stream_name (s), i, s->codec->sample_rate, s->codec->channel_layout) + ) + ); } else if (s->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { - if (_subtitle_stream == -1) { - _subtitle_stream = i; - } - _subtitle_streams.push_back (SubtitleStream (stream_name (s), i)); + _subtitle_streams.push_back ( + shared_ptr<SubtitleStream> ( + new SubtitleStream (stream_name (s), i) + ) + ); } } - /* Now override audio and subtitle streams with those from the Film, if it has any */ - - if (_film->audio_stream_index() != -1) { - _audio_stream = _film->audio_stream().id(); - } - - if (_film->subtitle_stream_index() != -1) { - _subtitle_stream = _film->subtitle_stream().id (); - } - if (_video_stream < 0) { throw DecodeError ("could not find video stream"); } @@ -173,11 +169,14 @@ FFmpegDecoder::setup_video () void FFmpegDecoder::setup_audio () { - if (_audio_stream < 0) { + if (!_audio_stream) { return; } + + shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); + assert (ffa); - _audio_codec_context = _format_context->streams[_audio_stream]->codec; + _audio_codec_context = _format_context->streams[ffa->id()]->codec; _audio_codec = avcodec_find_decoder (_audio_codec_context->codec_id); if (_audio_codec == 0) { @@ -193,18 +192,18 @@ FFmpegDecoder::setup_audio () */ if (_audio_codec_context->channel_layout == 0) { - _audio_codec_context->channel_layout = av_get_default_channel_layout (audio_channels ()); + _audio_codec_context->channel_layout = av_get_default_channel_layout (ffa->channels()); } } void FFmpegDecoder::setup_subtitle () { - if (_subtitle_stream < 0) { + if (!_subtitle_stream) { return; } - _subtitle_codec_context = _format_context->streams[_subtitle_stream]->codec; + _subtitle_codec_context = _format_context->streams[_subtitle_stream->id()]->codec; _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id); if (_subtitle_codec == 0) { @@ -240,17 +239,17 @@ FFmpegDecoder::pass () int frame_finished; while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - process_video (_frame); + filter_and_emit_video (_frame); } - if (_audio_stream >= 0 && _opt->decode_audio) { + if (_audio_stream && _opt->decode_audio) { while (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { int const data_size = av_samples_get_buffer_size ( 0, _audio_codec_context->channels, _frame->nb_samples, audio_sample_format (), 1 ); assert (_audio_codec_context->channels == _film->audio_channels()); - process_audio (_frame->data[0], data_size); + Audio (deinterleave_audio (_frame->data[0], data_size)); } } @@ -258,6 +257,8 @@ FFmpegDecoder::pass () } avcodec_get_frame_defaults (_frame); + + shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); if (_packet.stream_index == _video_stream) { @@ -300,14 +301,14 @@ FFmpegDecoder::pass () if (delta > -one_frame) { /* Process this frame */ - process_video (_frame); + filter_and_emit_video (_frame); } else { /* Otherwise we are omitting a frame to keep things right */ _film->log()->log (String::compose ("Frame removed at %1s", out_pts_seconds)); } } - } else if (_audio_stream >= 0 && _packet.stream_index == _audio_stream && _opt->decode_audio) { + } else if (ffa && _packet.stream_index == ffa->id() && _opt->decode_audio) { int frame_finished; if (avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { @@ -331,25 +332,19 @@ FFmpegDecoder::pass () */ /* frames of silence that we must push */ - int const s = rint ((_first_audio.get() - _first_video.get()) * audio_sample_rate ()); + int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ()); _film->log()->log ( String::compose ( "First video at %1, first audio at %2, pushing %3 frames of silence for %4 channels (%5 bytes per sample)", - _first_video.get(), _first_audio.get(), s, audio_channels(), bytes_per_audio_sample() + _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample() ) ); if (s) { - /* hence bytes */ - int const b = s * audio_channels() * bytes_per_audio_sample(); - - /* XXX: this assumes that it won't be too much, and there are shaky assumptions - that all sound representations are silent with memset()ed zero data. - */ - uint8_t silence[b]; - memset (silence, 0, b); - process_audio (silence, b); + shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s)); + audio->make_silent (); + Audio (audio); } } @@ -358,11 +353,11 @@ FFmpegDecoder::pass () ); assert (_audio_codec_context->channels == _film->audio_channels()); - process_audio (_frame->data[0], data_size); + Audio (deinterleave_audio (_frame->data[0], data_size)); } } - } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream && _opt->decode_subtitles && _first_video) { + } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt->decode_subtitles && _first_video) { int got_subtitle; AVSubtitle sub; @@ -371,9 +366,9 @@ FFmpegDecoder::pass () indicate that the previous subtitle should stop. */ if (sub.num_rects > 0) { - process_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub, _first_video.get()))); + emit_subtitle (shared_ptr<TimedSubtitle> (new TimedSubtitle (sub, _first_video.get()))); } else { - process_subtitle (shared_ptr<TimedSubtitle> ()); + emit_subtitle (shared_ptr<TimedSubtitle> ()); } avsubtitle_free (&sub); } @@ -383,36 +378,84 @@ FFmpegDecoder::pass () return false; } -float -FFmpegDecoder::frames_per_second () const +shared_ptr<AudioBuffers> +FFmpegDecoder::deinterleave_audio (uint8_t* data, int size) { - AVStream* s = _format_context->streams[_video_stream]; + assert (_film->audio_channels()); + assert (bytes_per_audio_sample()); - if (s->avg_frame_rate.num && s->avg_frame_rate.den) { - return av_q2d (s->avg_frame_rate); + shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream); + assert (ffa); + + /* Deinterleave and convert to float */ + + assert ((size % (bytes_per_audio_sample() * ffa->channels())) == 0); + + int const total_samples = size / bytes_per_audio_sample(); + int const frames = total_samples / _film->audio_channels(); + shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), frames)); + + switch (audio_sample_format()) { + case AV_SAMPLE_FMT_S16: + { + int16_t* p = (int16_t *) data; + int sample = 0; + int channel = 0; + for (int i = 0; i < total_samples; ++i) { + audio->data(channel)[sample] = float(*p++) / (1 << 15); + + ++channel; + if (channel == _film->audio_channels()) { + channel = 0; + ++sample; + } + } + } + break; + + case AV_SAMPLE_FMT_S32: + { + int32_t* p = (int32_t *) data; + int sample = 0; + int channel = 0; + for (int i = 0; i < total_samples; ++i) { + audio->data(channel)[sample] = float(*p++) / (1 << 31); + + ++channel; + if (channel == _film->audio_channels()) { + channel = 0; + ++sample; + } + } } - return av_q2d (s->r_frame_rate); -} + case AV_SAMPLE_FMT_FLTP: + { + float* p = reinterpret_cast<float*> (data); + for (int i = 0; i < _film->audio_channels(); ++i) { + memcpy (audio->data(i), p, frames * sizeof(float)); + p += frames; + } + } + break; -int -FFmpegDecoder::audio_channels () const -{ - if (_audio_codec_context == 0) { - return 0; + default: + assert (false); } - return _audio_codec_context->channels; + return audio; } -int -FFmpegDecoder::audio_sample_rate () const +float +FFmpegDecoder::frames_per_second () const { - if (_audio_codec_context == 0) { - return 0; + AVStream* s = _format_context->streams[_video_stream]; + + if (s->avg_frame_rate.num && s->avg_frame_rate.den) { + return av_q2d (s->avg_frame_rate); } - - return _audio_codec_context->sample_rate; + + return av_q2d (s->r_frame_rate); } AVSampleFormat @@ -425,16 +468,6 @@ FFmpegDecoder::audio_sample_format () const return _audio_codec_context->sample_fmt; } -int64_t -FFmpegDecoder::audio_channel_layout () const -{ - if (_audio_codec_context == 0) { - return 0; - } - - return _audio_codec_context->channel_layout; -} - Size FFmpegDecoder::native_size () const { @@ -471,24 +504,6 @@ FFmpegDecoder::sample_aspect_ratio_denominator () const return _video_codec_context->sample_aspect_ratio.den; } -bool -FFmpegDecoder::has_subtitles () const -{ - return (_subtitle_stream != -1); -} - -vector<AudioStream> -FFmpegDecoder::audio_streams () const -{ - return _audio_streams; -} - -vector<SubtitleStream> -FFmpegDecoder::subtitle_streams () const -{ - return _subtitle_streams; -} - string FFmpegDecoder::stream_name (AVStream* s) const { @@ -514,3 +529,100 @@ FFmpegDecoder::stream_name (AVStream* s) const return n.str (); } +int +FFmpegDecoder::bytes_per_audio_sample () const +{ + return av_get_bytes_per_sample (audio_sample_format ()); +} + +void +FFmpegDecoder::set_audio_stream (shared_ptr<AudioStream> s) +{ + AudioDecoder::set_audio_stream (s); + setup_audio (); +} + +void +FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) +{ + VideoDecoder::set_subtitle_stream (s); + setup_subtitle (); +} + +void +FFmpegDecoder::filter_and_emit_video (AVFrame* frame) +{ + shared_ptr<FilterGraph> graph; + + list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); + while (i != _filter_graphs.end() && !(*i)->can_process (Size (frame->width, frame->height), (AVPixelFormat) frame->format)) { + ++i; + } + + if (i == _filter_graphs.end ()) { + graph.reset (new FilterGraph (_film, this, _opt->apply_crop, Size (frame->width, frame->height), (AVPixelFormat) frame->format)); + _filter_graphs.push_back (graph); + _film->log()->log (String::compose ("New graph for %1x%2, pixel format %3", frame->width, frame->height, frame->format)); + } else { + graph = *i; + } + + list<shared_ptr<Image> > images = graph->process (frame); + + for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { + emit_video (*i); + } +} + +shared_ptr<FFmpegAudioStream> +FFmpegAudioStream::create (string t, optional<int> v) +{ + if (!v) { + /* version < 1; no type in the string, and there's only FFmpeg streams anyway */ + return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); + } + + stringstream s (t); + string type; + s >> type; + if (type != "ffmpeg") { + return shared_ptr<FFmpegAudioStream> (); + } + + return shared_ptr<FFmpegAudioStream> (new FFmpegAudioStream (t, v)); +} + +FFmpegAudioStream::FFmpegAudioStream (string t, optional<int> version) +{ + stringstream n (t); + + int name_index = 4; + if (!version) { + name_index = 2; + int channels; + n >> _id >> channels; + _channel_layout = av_get_default_channel_layout (channels); + _sample_rate = 0; + } else { + string type; + /* Current (marked version 1) */ + n >> type >> _id >> _sample_rate >> _channel_layout; + assert (type == "ffmpeg"); + } + + for (int i = 0; i < name_index; ++i) { + size_t const s = t.find (' '); + if (s != string::npos) { + t = t.substr (s + 1); + } + } + + _name = t; +} + +string +FFmpegAudioStream::to_string () const +{ + return String::compose ("ffmpeg %1 %2 %3 %4", _id, _sample_rate, _channel_layout, _name); +} + diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 0265f7478..87eebe1ec 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -32,6 +32,8 @@ extern "C" { } #include "util.h" #include "decoder.h" +#include "video_decoder.h" +#include "audio_decoder.h" struct AVFilterGraph; struct AVCodecContext; @@ -46,35 +48,63 @@ class Options; class Image; class Log; +class FFmpegAudioStream : public AudioStream +{ +public: + FFmpegAudioStream (std::string n, int i, int s, int64_t c) + : AudioStream (s, c) + , _name (n) + , _id (i) + {} + + std::string to_string () const; + + std::string name () const { + return _name; + } + + int id () const { + return _id; + } + + static boost::shared_ptr<FFmpegAudioStream> create (std::string t, boost::optional<int> v); + +private: + friend class stream_test; + + FFmpegAudioStream (std::string t, boost::optional<int> v); + + std::string _name; + int _id; +}; + /** @class FFmpegDecoder * @brief A decoder using FFmpeg to decode content. */ -class FFmpegDecoder : public Decoder +class FFmpegDecoder : public VideoDecoder, public AudioDecoder { public: FFmpegDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); ~FFmpegDecoder (); - /* Methods to query our input video */ float frames_per_second () const; Size native_size () const; - int audio_channels () const; - int audio_sample_rate () const; - AVSampleFormat audio_sample_format () const; - int64_t audio_channel_layout () const; - bool has_subtitles () const; + int time_base_numerator () const; + int time_base_denominator () const; + int sample_aspect_ratio_numerator () const; + int sample_aspect_ratio_denominator () const; - std::vector<AudioStream> audio_streams () const; - std::vector<SubtitleStream> subtitle_streams () const; + void set_audio_stream (boost::shared_ptr<AudioStream>); + void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); private: bool pass (); PixelFormat pixel_format () const; - int time_base_numerator () const; - int time_base_denominator () const; - int sample_aspect_ratio_numerator () const; - int sample_aspect_ratio_denominator () const; + AVSampleFormat audio_sample_format () const; + int bytes_per_audio_sample () const; + + void filter_and_emit_video (AVFrame *); void setup_general (); void setup_video (); @@ -82,19 +112,15 @@ private: void setup_subtitle (); void maybe_add_subtitle (); + boost::shared_ptr<AudioBuffers> deinterleave_audio (uint8_t* data, int size); std::string stream_name (AVStream* s) const; AVFormatContext* _format_context; int _video_stream; - int _audio_stream; ///< may be < 0 if there is no audio - int _subtitle_stream; ///< may be < 0 if there is no subtitle AVFrame* _frame; - std::vector<AudioStream> _audio_streams; - std::vector<SubtitleStream> _subtitle_streams; - AVCodecContext* _video_codec_context; AVCodec* _video_codec; AVCodecContext* _audio_codec_context; ///< may be 0 if there is no audio @@ -106,4 +132,6 @@ private: boost::optional<double> _first_video; boost::optional<double> _first_audio; + + std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; }; diff --git a/src/lib/film.cc b/src/lib/film.cc index 902306fb8..3f9210080 100644 --- a/src/lib/film.cc +++ b/src/lib/film.cc @@ -51,6 +51,9 @@ #include "check_hashes_job.h" #include "version.h" #include "ui_signaller.h" +#include "video_decoder.h" +#include "audio_decoder.h" +#include "external_audio_decoder.h" using std::string; using std::stringstream; @@ -63,11 +66,15 @@ using std::ofstream; using std::setfill; using std::min; using std::make_pair; +using std::cout; using boost::shared_ptr; using boost::lexical_cast; using boost::to_upper_copy; using boost::ends_with; using boost::starts_with; +using boost::optional; + +int const Film::state_version = 1; /** Construct a Film object in a given directory, reading any metadata * file that exists in that directory. An exception will be thrown if @@ -78,23 +85,20 @@ using boost::starts_with; */ Film::Film (string d, bool must_exist) - : _use_dci_name (false) + : _use_dci_name (true) , _dcp_content_type (0) , _format (0) , _scaler (Scaler::from_id ("bicubic")) , _dcp_trim_start (0) , _dcp_trim_end (0) , _dcp_ab (false) - , _audio_stream (-1) + , _use_content_audio (true) , _audio_gain (0) , _audio_delay (0) , _still_duration (10) - , _subtitle_stream (-1) , _with_subtitles (false) , _subtitle_offset (0) , _subtitle_scale (1) - , _audio_sample_rate (0) - , _has_subtitles (false) , _frames_per_second (0) , _dirty (false) { @@ -126,6 +130,8 @@ Film::Film (string d, bool must_exist) } } + _external_audio_stream = ExternalAudioStream::create (); + read_metadata (); _log = new FileLog (file ("log")); @@ -146,7 +152,9 @@ Film::Film (Film const & o) , _dcp_trim_start (o._dcp_trim_start) , _dcp_trim_end (o._dcp_trim_end) , _dcp_ab (o._dcp_ab) - , _audio_stream (o._audio_stream) + , _content_audio_stream (o._content_audio_stream) + , _external_audio (o._external_audio) + , _use_content_audio (o._use_content_audio) , _audio_gain (o._audio_gain) , _audio_delay (o._audio_delay) , _still_duration (o._still_duration) @@ -164,10 +172,9 @@ Film::Film (Film const & o) , _thumbs (o._thumbs) , _size (o._size) , _length (o._length) - , _audio_sample_rate (o._audio_sample_rate) , _content_digest (o._content_digest) - , _has_subtitles (o._has_subtitles) - , _audio_streams (o._audio_streams) + , _content_audio_streams (o._content_audio_streams) + , _external_audio_stream (o._external_audio_stream) , _subtitle_streams (o._subtitle_streams) , _frames_per_second (o._frames_per_second) , _dirty (o._dirty) @@ -259,10 +266,12 @@ Film::make_dcp (bool transcode) o->ratio = format()->ratio_as_float (shared_from_this ()); if (dcp_length ()) { o->video_decode_range = make_pair (dcp_trim_start(), dcp_trim_start() + dcp_length().get()); - o->audio_decode_range = make_pair ( - video_frames_to_audio_frames (o->video_decode_range.get().first, audio_sample_rate(), frames_per_second()), - video_frames_to_audio_frames (o->video_decode_range.get().second, audio_sample_rate(), frames_per_second()) - ); + if (audio_stream()) { + o->audio_decode_range = make_pair ( + video_frames_to_audio_frames (o->video_decode_range.get().first, audio_stream()->sample_rate(), frames_per_second()), + video_frames_to_audio_frames (o->video_decode_range.get().second, audio_stream()->sample_rate(), frames_per_second()) + ); + } } o->decode_subtitles = with_subtitles (); @@ -389,6 +398,8 @@ Film::write_metadata () const throw CreateFileError (m); } + f << "version " << state_version << "\n"; + /* User stuff */ f << "name " << _name << "\n"; f << "use_dci_name " << _use_dci_name << "\n"; @@ -410,11 +421,19 @@ Film::write_metadata () const f << "dcp_trim_start " << _dcp_trim_start << "\n"; f << "dcp_trim_end " << _dcp_trim_end << "\n"; f << "dcp_ab " << (_dcp_ab ? "1" : "0") << "\n"; - f << "selected_audio_stream " << _audio_stream << "\n"; + if (_content_audio_stream) { + f << "selected_content_audio_stream " << _content_audio_stream->to_string() << "\n"; + } + for (vector<string>::const_iterator i = _external_audio.begin(); i != _external_audio.end(); ++i) { + f << "external_audio " << *i << "\n"; + } + f << "use_content_audio " << (_use_content_audio ? "1" : "0") << "\n"; f << "audio_gain " << _audio_gain << "\n"; f << "audio_delay " << _audio_delay << "\n"; f << "still_duration " << _still_duration << "\n"; - f << "selected_subtitle_stream " << _subtitle_stream << "\n"; + if (_subtitle_stream) { + f << "selected_subtitle_stream " << _subtitle_stream->to_string() << "\n"; + } f << "with_subtitles " << _with_subtitles << "\n"; f << "subtitle_offset " << _subtitle_offset << "\n"; f << "subtitle_scale " << _subtitle_scale << "\n"; @@ -435,16 +454,16 @@ Film::write_metadata () const f << "width " << _size.width << "\n"; f << "height " << _size.height << "\n"; f << "length " << _length.get_value_or(0) << "\n"; - f << "audio_sample_rate " << _audio_sample_rate << "\n"; f << "content_digest " << _content_digest << "\n"; - f << "has_subtitles " << _has_subtitles << "\n"; - for (vector<AudioStream>::const_iterator i = _audio_streams.begin(); i != _audio_streams.end(); ++i) { - f << "audio_stream " << i->to_string () << "\n"; + for (vector<shared_ptr<AudioStream> >::const_iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { + f << "content_audio_stream " << (*i)->to_string () << "\n"; } - for (vector<SubtitleStream>::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { - f << "subtitle_stream " << i->to_string () << "\n"; + f << "external_audio_stream " << _external_audio_stream->to_string() << "\n"; + + for (vector<shared_ptr<SubtitleStream> >::const_iterator i = _subtitle_streams.begin(); i != _subtitle_streams.end(); ++i) { + f << "subtitle_stream " << (*i)->to_string () << "\n"; } f << "frames_per_second " << _frames_per_second << "\n"; @@ -457,13 +476,36 @@ void Film::read_metadata () { boost::mutex::scoped_lock lm (_state_mutex); + + _external_audio.clear (); + _thumbs.clear (); + _content_audio_streams.clear (); + _subtitle_streams.clear (); + + boost::optional<int> version; + + /* Backward compatibility things */ + boost::optional<int> audio_sample_rate; + boost::optional<int> audio_stream_index; + boost::optional<int> subtitle_stream_index; ifstream f (file ("metadata").c_str()); multimap<string, string> kv = read_key_value (f); + + /* We need version before anything else */ + multimap<string, string>::iterator v = kv.find ("version"); + if (v != kv.end ()) { + version = atoi (v->second.c_str()); + } + for (multimap<string, string>::const_iterator i = kv.begin(); i != kv.end(); ++i) { string const k = i->first; string const v = i->second; + if (k == "audio_sample_rate") { + audio_sample_rate = atoi (v.c_str()); + } + /* User-specified stuff */ if (k == "name") { _name = v; @@ -493,8 +535,16 @@ Film::read_metadata () _dcp_trim_end = atoi (v.c_str ()); } else if (k == "dcp_ab") { _dcp_ab = (v == "1"); - } else if (k == "selected_audio_stream") { - _audio_stream = atoi (v.c_str ()); + } else if (k == "selected_content_audio_stream" || (!version && k == "selected_audio_stream")) { + if (!version) { + audio_stream_index = atoi (v.c_str ()); + } else { + _content_audio_stream = audio_stream_factory (v, version); + } + } else if (k == "external_audio") { + _external_audio.push_back (v); + } else if (k == "use_content_audio") { + _use_content_audio = (v == "1"); } else if (k == "audio_gain") { _audio_gain = atof (v.c_str ()); } else if (k == "audio_delay") { @@ -502,7 +552,11 @@ Film::read_metadata () } else if (k == "still_duration") { _still_duration = atoi (v.c_str ()); } else if (k == "selected_subtitle_stream") { - _subtitle_stream = atoi (v.c_str ()); + if (!version) { + subtitle_stream_index = atoi (v.c_str ()); + } else { + _subtitle_stream = subtitle_stream_factory (v, version); + } } else if (k == "with_subtitles") { _with_subtitles = (v == "1"); } else if (k == "subtitle_offset") { @@ -541,20 +595,37 @@ Film::read_metadata () if (vv) { _length = vv; } - } else if (k == "audio_sample_rate") { - _audio_sample_rate = atoi (v.c_str ()); } else if (k == "content_digest") { _content_digest = v; - } else if (k == "has_subtitles") { - _has_subtitles = (v == "1"); - } else if (k == "audio_stream") { - _audio_streams.push_back (AudioStream (v)); + } else if (k == "content_audio_stream" || (!version && k == "audio_stream")) { + _content_audio_streams.push_back (audio_stream_factory (v, version)); + } else if (k == "external_audio_stream") { + _external_audio_stream = audio_stream_factory (v, version); } else if (k == "subtitle_stream") { - _subtitle_streams.push_back (SubtitleStream (v)); + _subtitle_streams.push_back (subtitle_stream_factory (v, version)); } else if (k == "frames_per_second") { _frames_per_second = atof (v.c_str ()); } } + + if (!version) { + if (audio_sample_rate) { + /* version < 1 didn't specify sample rate in the audio streams, so fill it in here */ + for (vector<shared_ptr<AudioStream> >::iterator i = _content_audio_streams.begin(); i != _content_audio_streams.end(); ++i) { + (*i)->set_sample_rate (audio_sample_rate.get()); + } + } + + /* also the selected stream was specified as an index */ + if (audio_stream_index && audio_stream_index.get() >= 0 && audio_stream_index.get() < (int) _content_audio_streams.size()) { + _content_audio_stream = _content_audio_streams[audio_stream_index.get()]; + } + + /* similarly the subtitle */ + if (subtitle_stream_index && subtitle_stream_index.get() >= 0 && subtitle_stream_index.get() < (int) _subtitle_streams.size()) { + _subtitle_stream = _subtitle_streams[subtitle_stream_index.get()]; + } + } _dirty = false; } @@ -667,15 +738,12 @@ Film::content_path () const ContentType Film::content_type () const { -#if BOOST_FILESYSTEM_VERSION == 3 - string ext = boost::filesystem::path(_content).extension().string(); -#else - string ext = boost::filesystem::path(_content).extension(); -#endif + if (boost::filesystem::is_directory (_content)) { + /* Directory of images, we assume */ + return VIDEO; + } - transform (ext.begin(), ext.end(), ext.begin(), ::tolower); - - if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") { + if (still_image_file (_content)) { return STILL; } @@ -686,8 +754,12 @@ Film::content_type () const int Film::target_audio_sample_rate () const { + if (!audio_stream()) { + return 0; + } + /* Resample to a DCI-approved sample rate */ - double t = dcp_audio_sample_rate (audio_sample_rate()); + double t = dcp_audio_sample_rate (audio_stream()->sample_rate()); DCPFrameRate dfr = dcp_frame_rate (frames_per_second ()); @@ -715,11 +787,9 @@ Film::dcp_length () const string Film::dci_name () const { - boost::mutex::scoped_lock lm (_state_mutex); - stringstream d; - string fixed_name = to_upper_copy (_name); + string fixed_name = to_upper_copy (name()); for (size_t i = 0; i < fixed_name.length(); ++i) { if (fixed_name[i] == ' ') { fixed_name[i] = '-'; @@ -733,18 +803,18 @@ Film::dci_name () const d << fixed_name << "_"; - if (_dcp_content_type) { - d << _dcp_content_type->dci_name() << "_"; + if (dcp_content_type()) { + d << dcp_content_type()->dci_name() << "_"; } - if (_format) { - d << _format->dci_name() << "_"; + if (format()) { + d << format()->dci_name() << "_"; } - if (!_audio_language.empty ()) { - d << _audio_language; - if (!_subtitle_language.empty() && _with_subtitles) { - d << "-" << _subtitle_language; + if (!audio_language().empty ()) { + d << audio_language(); + if (!subtitle_language().empty() && with_subtitles()) { + d << "-" << subtitle_language(); } else { d << "-XX"; } @@ -752,45 +822,43 @@ Film::dci_name () const d << "_"; } - if (!_territory.empty ()) { - d << _territory; - if (!_rating.empty ()) { - d << "-" << _rating; + if (!territory().empty ()) { + d << territory(); + if (!rating().empty ()) { + d << "-" << rating(); } d << "_"; } - if (_audio_stream != -1) { - switch (_audio_streams[_audio_stream].channels()) { - case 1: - d << "10_"; - break; - case 2: - d << "20_"; - break; - case 6: - d << "51_"; - break; - case 8: - d << "71_"; - break; - } + 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_"; - if (!_studio.empty ()) { - d << _studio << "_"; + if (!studio().empty ()) { + d << studio() << "_"; } d << boost::gregorian::to_iso_string (_dci_date) << "_"; - if (!_facility.empty ()) { - d << _facility << "_"; + if (!facility().empty ()) { + d << facility() << "_"; } - if (!_package_type.empty ()) { - d << _package_type; + if (!package_type().empty ()) { + d << package_type(); } return d.str (); @@ -872,6 +940,10 @@ Film::set_content (string c) _content = c; } + /* Reset streams here in case the new content doesn't have one or the other */ + _content_audio_stream = shared_ptr<AudioStream> (); + _subtitle_stream = shared_ptr<SubtitleStream> (); + /* Create a temporary decoder so that we can get information about the content. */ @@ -880,16 +952,21 @@ Film::set_content (string c) shared_ptr<Options> o (new Options ("", "", "")); o->out_size = Size (1024, 1024); - shared_ptr<Decoder> d = decoder_factory (shared_from_this(), o, 0); + pair<shared_ptr<VideoDecoder>, shared_ptr<AudioDecoder> > d = decoder_factory (shared_from_this(), o, 0); + + set_size (d.first->native_size ()); + set_frames_per_second (d.first->frames_per_second ()); + set_subtitle_streams (d.first->subtitle_streams ()); + set_content_audio_streams (d.second->audio_streams ()); + + /* Start off with the first audio and subtitle streams */ + if (!d.second->audio_streams().empty()) { + set_content_audio_stream (d.second->audio_streams().front()); + } - set_size (d->native_size ()); - set_frames_per_second (d->frames_per_second ()); - set_audio_sample_rate (d->audio_sample_rate ()); - set_has_subtitles (d->has_subtitles ()); - set_audio_streams (d->audio_streams ()); - set_subtitle_streams (d->subtitle_streams ()); - set_audio_stream (audio_streams().empty() ? -1 : 0); - set_subtitle_stream (subtitle_streams().empty() ? -1 : 0); + if (!d.first->subtitle_streams().empty()) { + set_subtitle_stream (d.first->subtitle_streams().front()); + } { boost::mutex::scoped_lock lm (_state_mutex); @@ -1049,13 +1126,42 @@ Film::set_dcp_ab (bool a) } void -Film::set_audio_stream (int s) +Film::set_content_audio_stream (shared_ptr<AudioStream> s) +{ + { + boost::mutex::scoped_lock lm (_state_mutex); + _content_audio_stream = s; + } + signal_changed (CONTENT_AUDIO_STREAM); +} + +void +Film::set_external_audio (vector<string> a) +{ + { + boost::mutex::scoped_lock lm (_state_mutex); + _external_audio = a; + } + + shared_ptr<Options> o (new Options ("", "", "")); + o->decode_audio = true; + shared_ptr<ExternalAudioDecoder> decoder (new ExternalAudioDecoder (shared_from_this(), o, 0)); + if (decoder->audio_stream()) { + _external_audio_stream = decoder->audio_stream (); + } + + signal_changed (EXTERNAL_AUDIO); +} + +void +Film::set_use_content_audio (bool e) { { boost::mutex::scoped_lock lm (_state_mutex); - _audio_stream = s; + _use_content_audio = e; } - signal_changed (AUDIO_STREAM); + + signal_changed (USE_CONTENT_AUDIO); } void @@ -1089,7 +1195,7 @@ Film::set_still_duration (int d) } void -Film::set_subtitle_stream (int s) +Film::set_subtitle_stream (shared_ptr<SubtitleStream> s) { { boost::mutex::scoped_lock lm (_state_mutex); @@ -1239,16 +1345,6 @@ Film::unset_length () } void -Film::set_audio_sample_rate (int r) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_sample_rate = r; - } - signal_changed (AUDIO_SAMPLE_RATE); -} - -void Film::set_content_digest (string d) { { @@ -1259,33 +1355,23 @@ Film::set_content_digest (string d) } void -Film::set_has_subtitles (bool s) +Film::set_content_audio_streams (vector<shared_ptr<AudioStream> > s) { { boost::mutex::scoped_lock lm (_state_mutex); - _has_subtitles = s; + _content_audio_streams = s; } - signal_changed (HAS_SUBTITLES); + signal_changed (CONTENT_AUDIO_STREAMS); } void -Film::set_audio_streams (vector<AudioStream> s) -{ - { - boost::mutex::scoped_lock lm (_state_mutex); - _audio_streams = s; - } - _dirty = true; -} - -void -Film::set_subtitle_streams (vector<SubtitleStream> s) +Film::set_subtitle_streams (vector<shared_ptr<SubtitleStream> > s) { { boost::mutex::scoped_lock lm (_state_mutex); _subtitle_streams = s; } - _dirty = true; + signal_changed (SUBTITLE_STREAMS); } void @@ -1314,12 +1400,12 @@ Film::signal_changed (Property p) int Film::audio_channels () const { - boost::mutex::scoped_lock lm (_state_mutex); - if (_audio_stream == -1) { + shared_ptr<AudioStream> s = audio_stream (); + if (!s) { return 0; } - - return _audio_streams[_audio_stream].channels (); + + return s->channels (); } void @@ -1328,3 +1414,12 @@ Film::set_dci_date_today () _dci_date = boost::gregorian::day_clock::local_day (); } +boost::shared_ptr<AudioStream> +Film::audio_stream () const +{ + if (use_content_audio()) { + return _content_audio_stream; + } + + return _external_audio_stream; +} diff --git a/src/lib/film.h b/src/lib/film.h index 64e7a3a70..2e81575e4 100644 --- a/src/lib/film.h +++ b/src/lib/film.h @@ -44,6 +44,7 @@ class Job; class Filter; class Log; class ExamineContentJob; +class ExternalAudioStream; /** @class Film * @brief A representation of a video with sound. @@ -117,7 +118,9 @@ public: DCP_TRIM_START, DCP_TRIM_END, DCP_AB, - AUDIO_STREAM, + CONTENT_AUDIO_STREAM, + EXTERNAL_AUDIO, + USE_CONTENT_AUDIO, AUDIO_GAIN, AUDIO_DELAY, STILL_DURATION, @@ -129,9 +132,7 @@ public: THUMBS, SIZE, LENGTH, - AUDIO_SAMPLE_RATE, - HAS_SUBTITLES, - AUDIO_STREAMS, + CONTENT_AUDIO_STREAMS, SUBTITLE_STREAMS, FRAMES_PER_SECOND, }; @@ -199,15 +200,19 @@ public: return _dcp_ab; } - int audio_stream_index () const { + boost::shared_ptr<AudioStream> content_audio_stream () const { boost::mutex::scoped_lock lm (_state_mutex); - return _audio_stream; + return _content_audio_stream; } - AudioStream audio_stream () const { + std::vector<std::string> external_audio () const { boost::mutex::scoped_lock lm (_state_mutex); - assert (_audio_stream < int (_audio_streams.size())); - return _audio_streams[_audio_stream]; + return _external_audio; + } + + bool use_content_audio () const { + boost::mutex::scoped_lock lm (_state_mutex); + return _use_content_audio; } float audio_gain () const { @@ -225,17 +230,11 @@ public: return _still_duration; } - int subtitle_stream_index () const { + boost::shared_ptr<SubtitleStream> subtitle_stream () const { boost::mutex::scoped_lock lm (_state_mutex); return _subtitle_stream; } - SubtitleStream subtitle_stream () const { - boost::mutex::scoped_lock lm (_state_mutex); - assert (_subtitle_stream < int (_subtitle_streams.size())); - return _subtitle_streams[_subtitle_stream]; - } - bool with_subtitles () const { boost::mutex::scoped_lock lm (_state_mutex); return _with_subtitles; @@ -301,27 +300,17 @@ public: return _length; } - int audio_sample_rate () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_sample_rate; - } - std::string content_digest () const { boost::mutex::scoped_lock lm (_state_mutex); return _content_digest; } - bool has_subtitles () const { + std::vector<boost::shared_ptr<AudioStream> > content_audio_streams () const { boost::mutex::scoped_lock lm (_state_mutex); - return _has_subtitles; + return _content_audio_streams; } - std::vector<AudioStream> audio_streams () const { - boost::mutex::scoped_lock lm (_state_mutex); - return _audio_streams; - } - - std::vector<SubtitleStream> subtitle_streams () const { + std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const { boost::mutex::scoped_lock lm (_state_mutex); return _subtitle_streams; } @@ -331,7 +320,9 @@ public: return _frames_per_second; } + boost::shared_ptr<AudioStream> audio_stream () const; + /* SET */ void set_directory (std::string); @@ -350,11 +341,13 @@ public: void set_dcp_trim_start (int); void set_dcp_trim_end (int); void set_dcp_ab (bool); - void set_audio_stream (int); + void set_content_audio_stream (boost::shared_ptr<AudioStream>); + void set_external_audio (std::vector<std::string>); + void set_use_content_audio (bool); void set_audio_gain (float); void set_audio_delay (int); void set_still_duration (int); - void set_subtitle_stream (int); + void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); void set_with_subtitles (bool); void set_subtitle_offset (int); void set_subtitle_scale (float); @@ -369,16 +362,17 @@ public: void set_size (Size); void set_length (SourceFrame); void unset_length (); - void set_audio_sample_rate (int); void set_content_digest (std::string); - void set_has_subtitles (bool); - void set_audio_streams (std::vector<AudioStream>); - void set_subtitle_streams (std::vector<SubtitleStream>); + void set_content_audio_streams (std::vector<boost::shared_ptr<AudioStream> >); + void set_subtitle_streams (std::vector<boost::shared_ptr<SubtitleStream> >); void set_frames_per_second (float); /** Emitted when some property has changed */ mutable boost::signals2::signal<void (Property)> Changed; - + + /** Current version number of the state file */ + static int const state_version; + private: /** Log to write to */ @@ -429,16 +423,21 @@ private: has the specified filters and post-processing. */ bool _dcp_ab; - /** An index into our _audio_streams vector for the stream to use for audio, or -1 if there is none */ - int _audio_stream; + /** The audio stream to use from our content */ + boost::shared_ptr<AudioStream> _content_audio_stream; + /** List of filenames of external audio files, in channel order + (L, R, C, Lfe, Ls, Rs) + */ + std::vector<std::string> _external_audio; + /** true to use audio from our content file; false to use external audio */ + bool _use_content_audio; /** Gain to apply to audio in dB */ float _audio_gain; /** Delay to apply to audio (positive moves audio later) in milliseconds */ int _audio_delay; /** Duration to make still-sourced films (in seconds) */ int _still_duration; - /** An index into our _subtitle_streams vector for the stream to use for subtitles, or -1 if there is none */ - int _subtitle_stream; + boost::shared_ptr<SubtitleStream> _subtitle_stream; /** True if subtitles should be shown for this film */ bool _with_subtitles; /** y offset for placing subtitles, in source pixels; +ve is further down @@ -465,19 +464,18 @@ private: Size _size; /** Actual length of the source (in video frames) from examining it */ boost::optional<SourceFrame> _length; - /** Sample rate of the source audio, in Hz */ - int _audio_sample_rate; /** MD5 digest of our content file */ std::string _content_digest; - /** true if the source has subtitles */ - bool _has_subtitles; - /** the audio streams that the source has */ - std::vector<AudioStream> _audio_streams; - /** the subtitle streams that the source has */ - std::vector<SubtitleStream> _subtitle_streams; + /** The audio streams in our content */ + std::vector<boost::shared_ptr<AudioStream> > _content_audio_streams; + /** A stream to represent possible external audio (will always exist) */ + boost::shared_ptr<AudioStream> _external_audio_stream; + /** the subtitle streams that we can use */ + std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; /** Frames per second of the source */ float _frames_per_second; + /** true if our state has changed since we last saved it */ mutable bool _dirty; /** Mutex for all state except _directory */ diff --git a/src/lib/filter_graph.cc b/src/lib/filter_graph.cc index 1418384b8..7320070fe 100644 --- a/src/lib/filter_graph.cc +++ b/src/lib/filter_graph.cc @@ -17,6 +17,10 @@ */ +/** @file src/lib/filter_graph.cc + * @brief A graph of FFmpeg filters. + */ + extern "C" { #include <libavfilter/avfiltergraph.h> #include <libavfilter/buffersrc.h> @@ -28,42 +32,43 @@ extern "C" { #endif #include <libavformat/avio.h> } -#include "film.h" #include "decoder.h" #include "filter_graph.h" #include "ffmpeg_compatibility.h" #include "filter.h" #include "exceptions.h" #include "image.h" +#include "film.h" +#include "ffmpeg_decoder.h" using std::stringstream; using std::string; using std::list; using boost::shared_ptr; -FilterGraph::FilterGraph (shared_ptr<Film> film, Decoder* decoder, bool crop, Size s, AVPixelFormat p) +/** Construct a FilterGraph for the settings in a film. + * @param film Film. + * @param decoder Decoder that we are using. + * @param crop true to apply crop, otherwise false. + * @param s Size of the images to process. + * @param p Pixel format of the images to process. + */ +FilterGraph::FilterGraph (shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p) : _buffer_src_context (0) , _buffer_sink_context (0) , _size (s) , _pixel_format (p) { - stringstream fs; - Size size_after_crop; - - if (crop) { - size_after_crop = film->cropped_size (decoder->native_size ()); - fs << crop_string (Position (film->crop().left, film->crop().top), size_after_crop); - } else { - size_after_crop = decoder->native_size (); - fs << crop_string (Position (0, 0), size_after_crop); - } - string filters = Filter::ffmpeg_strings (film->filters()).first; if (!filters.empty ()) { filters += ","; } - filters += fs.str (); + if (crop) { + filters += crop_string (Position (film->crop().left, film->crop().top), film->cropped_size (decoder->native_size())); + } else { + filters += crop_string (Position (0, 0), decoder->native_size()); + } avfilter_register_all (); @@ -133,8 +138,11 @@ FilterGraph::FilterGraph (shared_ptr<Film> film, Decoder* decoder, bool crop, Si /* XXX: leaking `inputs' / `outputs' ? */ } +/** Take an AVFrame and process it using our configured filters, returning a + * set of Images. + */ list<shared_ptr<Image> > -FilterGraph::process (AVFrame* frame) +FilterGraph::process (AVFrame const * frame) { list<shared_ptr<Image> > images; @@ -195,6 +203,10 @@ FilterGraph::process (AVFrame* frame) return images; } +/** @param s Image size. + * @param p Pixel format. + * @return true if this chain can process images with `s' and `p', otherwise false. + */ bool FilterGraph::can_process (Size s, AVPixelFormat p) const { diff --git a/src/lib/filter_graph.h b/src/lib/filter_graph.h index 53908738e..3842e9f7d 100644 --- a/src/lib/filter_graph.h +++ b/src/lib/filter_graph.h @@ -17,28 +17,35 @@ */ +/** @file src/lib/filter_graph.h + * @brief A graph of FFmpeg filters. + */ + #ifndef DVDOMATIC_FILTER_GRAPH_H #define DVDOMATIC_FILTER_GRAPH_H #include "util.h" -class Decoder; class Image; -class Film; +class VideoFilter; +class FFmpegDecoder; +/** @class FilterGraph + * @brief A graph of FFmpeg filters. + */ class FilterGraph { public: - FilterGraph (boost::shared_ptr<Film> film, Decoder* decoder, bool crop, Size s, AVPixelFormat p); + FilterGraph (boost::shared_ptr<Film> film, FFmpegDecoder* decoder, bool crop, Size s, AVPixelFormat p); bool can_process (Size s, AVPixelFormat p) const; - std::list<boost::shared_ptr<Image> > process (AVFrame* frame); + std::list<boost::shared_ptr<Image> > process (AVFrame const * frame); private: AVFilterContext* _buffer_src_context; AVFilterContext* _buffer_sink_context; - Size _size; - AVPixelFormat _pixel_format; + Size _size; ///< size of the images that this chain can process + AVPixelFormat _pixel_format; ///< pixel format of the images that this chain can process }; #endif diff --git a/src/lib/gain.cc b/src/lib/gain.cc new file mode 100644 index 000000000..cec3b3c62 --- /dev/null +++ b/src/lib/gain.cc @@ -0,0 +1,45 @@ +/* + Copyright (C) 2012 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 "gain.h" + +using boost::shared_ptr; + +/** @param gain gain in dB */ +Gain::Gain (Log* log, float gain) + : AudioProcessor (log) + , _gain (gain) +{ + +} + +void +Gain::process_audio (shared_ptr<AudioBuffers> b) +{ + if (_gain != 0) { + float const linear_gain = pow (10, _gain / 20); + for (int i = 0; i < b->channels(); ++i) { + for (int j = 0; j < b->frames(); ++j) { + b->data(i)[j] *= linear_gain; + } + } + } + + Audio (b); +} diff --git a/src/lib/gain.h b/src/lib/gain.h new file mode 100644 index 000000000..716ee9b51 --- /dev/null +++ b/src/lib/gain.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2012 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 "processor.h" + +class Gain : public AudioProcessor +{ +public: + Gain (Log* log, float gain); + + void process_audio (boost::shared_ptr<AudioBuffers>); + +private: + float _gain; +}; diff --git a/src/lib/image.cc b/src/lib/image.cc index c8303115b..72828ed46 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -293,7 +293,7 @@ Image::write_to_socket (shared_ptr<Socket> socket) const * @param p Pixel format. * @param s Size in pixels. */ -SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int)> rounder) +SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int, int const *)> stride_computer) : Image (p) , _size (s) { @@ -329,7 +329,7 @@ SimpleImage::SimpleImage (AVPixelFormat p, Size s, function<int (int)> rounder) } for (int i = 0; i < components(); ++i) { - _stride[i] = rounder (_line_size[i]); + _stride[i] = stride_computer (i, _line_size); _data[i] = (uint8_t *) av_malloc (_stride[i] * lines (i)); } } @@ -371,19 +371,39 @@ SimpleImage::size () const } AlignedImage::AlignedImage (AVPixelFormat f, Size s) - : SimpleImage (f, s, boost::bind (round_up, _1, 32)) + : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 32)) { } +AlignedImage::AlignedImage (shared_ptr<Image> im) + : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 32)) +{ + assert (components() == im->components()); + + for (int c = 0; c < components(); ++c) { + + assert (line_size()[c] == im->line_size()[c]); + + uint8_t* t = data()[c]; + uint8_t* o = im->data()[c]; + + for (int y = 0; y < lines(c); ++y) { + memcpy (t, o, line_size()[c]); + t += stride()[c]; + o += im->stride()[c]; + } + } +} + CompactImage::CompactImage (AVPixelFormat f, Size s) - : SimpleImage (f, s, boost::bind (round_up, _1, 1)) + : SimpleImage (f, s, boost::bind (stride_round_up, _1, _2, 1)) { } CompactImage::CompactImage (shared_ptr<Image> im) - : SimpleImage (im->pixel_format(), im->size(), boost::bind (round_up, _1, 1)) + : SimpleImage (im->pixel_format(), im->size(), boost::bind (stride_round_up, _1, _2, 1)) { assert (components() == im->components()); @@ -439,53 +459,3 @@ FilterBufferImage::size () const return Size (_buffer->video->w, _buffer->video->h); } -/** XXX: this could be generalised to use any format, but I don't - * understand how avpicture_fill is supposed to be called with - * multi-planar images. - */ -RGBFrameImage::RGBFrameImage (Size s) - : Image (PIX_FMT_RGB24) - , _size (s) -{ - _frame = avcodec_alloc_frame (); - if (_frame == 0) { - throw EncodeError ("could not allocate frame"); - } - - _data = (uint8_t *) av_malloc (size().width * size().height * 3); - avpicture_fill ((AVPicture *) _frame, _data, PIX_FMT_RGB24, size().width, size().height); - _frame->width = size().width; - _frame->height = size().height; - _frame->format = PIX_FMT_RGB24; -} - -RGBFrameImage::~RGBFrameImage () -{ - av_free (_data); - av_free (_frame); -} - -uint8_t ** -RGBFrameImage::data () const -{ - return _frame->data; -} - -int * -RGBFrameImage::line_size () const -{ - return _frame->linesize; -} - -int * -RGBFrameImage::stride () const -{ - /* XXX? */ - return line_size (); -} - -Size -RGBFrameImage::size () const -{ - return _size; -} diff --git a/src/lib/image.h b/src/lib/image.h index b2b987279..7c118f338 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -111,7 +111,7 @@ private: class SimpleImage : public Image { public: - SimpleImage (AVPixelFormat, Size, boost::function<int (int)> rounder); + SimpleImage (AVPixelFormat, Size, boost::function<int (int, int const *)> rounder); ~SimpleImage (); uint8_t ** data () const; @@ -127,12 +127,19 @@ private: int* _stride; ///< array of strides for each line (including any alignment padding bytes) }; +/** @class AlignedImage + * @brief An image whose pixel data is padded so that rows always start on 32-byte boundaries. + */ class AlignedImage : public SimpleImage { public: AlignedImage (AVPixelFormat, Size); + AlignedImage (boost::shared_ptr<Image>); }; +/** @class CompactImage + * @brief An image whose pixel data is not padded, so rows may start at any pixel alignment. + */ class CompactImage : public SimpleImage { public: @@ -140,27 +147,4 @@ public: CompactImage (boost::shared_ptr<Image>); }; -/** @class RGBFrameImage - * @brief An RGB image that is held within an AVFrame. - */ -class RGBFrameImage : public Image -{ -public: - RGBFrameImage (Size); - ~RGBFrameImage (); - - uint8_t ** data () const; - int * line_size () const; - int * stride () const; - Size size () const; - AVFrame * frame () const { - return _frame; - } - -private: - Size _size; - AVFrame* _frame; - uint8_t* _data; -}; - #endif diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index cc2fd9d23..d68c1648f 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -18,60 +18,90 @@ */ #include <iostream> +#include <boost/filesystem.hpp> #include <Magick++.h> #include "imagemagick_decoder.h" #include "image.h" #include "film.h" +#include "exceptions.h" -using namespace std; -using namespace boost; +using std::cout; +using boost::shared_ptr; ImageMagickDecoder::ImageMagickDecoder ( boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j) : Decoder (f, o, j) - , _done (false) + , VideoDecoder (f, o, j) { - _magick_image = new Magick::Image (_film->content_path ()); + if (boost::filesystem::is_directory (_film->content_path())) { + for ( + boost::filesystem::directory_iterator i = boost::filesystem::directory_iterator (_film->content_path()); + i != boost::filesystem::directory_iterator(); + ++i) { + + if (still_image_file (i->path().string())) { + _files.push_back (i->path().string()); + } + } + } else { + _files.push_back (_film->content_path ()); + } + + _iter = _files.begin (); } Size ImageMagickDecoder::native_size () const { - return Size (_magick_image->columns(), _magick_image->rows()); + if (_files.empty ()) { + throw DecodeError ("no still image files found"); + } + + /* Look at the first file and assume its size holds for all */ + using namespace MagickCore; + Magick::Image* image = new Magick::Image (_film->content_path ()); + Size const s = Size (image->columns(), image->rows()); + delete image; + + return s; } bool ImageMagickDecoder::pass () { - using namespace MagickCore; - - if (_done) { + if (_iter == _files.end()) { return true; } + + using namespace MagickCore; + Magick::Image* magick_image = new Magick::Image (_film->content_path ()); + Size size = native_size (); - RGBFrameImage image (size); + shared_ptr<CompactImage> image (new CompactImage (PIX_FMT_RGB24, size)); - uint8_t* p = image.data()[0]; + uint8_t* p = image->data()[0]; for (int y = 0; y < size.height; ++y) { for (int x = 0; x < size.width; ++x) { - Magick::Color c = _magick_image->pixelColor (x, y); + Magick::Color c = magick_image->pixelColor (x, y); *p++ = c.redQuantum() * 255 / QuantumRange; *p++ = c.greenQuantum() * 255 / QuantumRange; *p++ = c.blueQuantum() * 255 / QuantumRange; } - } + + delete magick_image; - process_video (image.frame ()); + emit_video (image); - _done = true; + ++_iter; return false; } PixelFormat ImageMagickDecoder::pixel_format () const { + /* XXX: always true? */ return PIX_FMT_RGB24; } diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index b7ab9af18..f636191f2 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -17,19 +17,20 @@ */ -#include "decoder.h" +#include "video_decoder.h" namespace Magick { class Image; } -class ImageMagickDecoder : public Decoder +class ImageMagickDecoder : public VideoDecoder { public: ImageMagickDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); float frames_per_second () const { - return static_frames_per_second (); + /* We don't know */ + return 0; } Size native_size () const; @@ -42,10 +43,6 @@ public: return 0; } - AVSampleFormat audio_sample_format () const { - return AV_SAMPLE_FMT_NONE; - } - int64_t audio_channel_layout () const { return 0; } @@ -54,10 +51,6 @@ public: return false; } - static float static_frames_per_second () { - return 24; - } - protected: bool pass (); PixelFormat pixel_format () const; @@ -81,6 +74,6 @@ protected: } private: - Magick::Image* _magick_image; - bool _done; + std::list<std::string> _files; + std::list<std::string>::iterator _iter; }; diff --git a/src/lib/imagemagick_encoder.cc b/src/lib/imagemagick_encoder.cc index 6c70e3749..480dec8bc 100644 --- a/src/lib/imagemagick_encoder.cc +++ b/src/lib/imagemagick_encoder.cc @@ -50,22 +50,22 @@ ImageMagickEncoder::ImageMagickEncoder (shared_ptr<const Film> f, shared_ptr<con } void -ImageMagickEncoder::do_process_video (shared_ptr<const Image> image, SourceFrame frame, shared_ptr<Subtitle> sub) +ImageMagickEncoder::do_process_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) { shared_ptr<Image> scaled = image->scale_and_convert_to_rgb (_opt->out_size, _opt->padding, _film->scaler()); shared_ptr<Image> compact (new CompactImage (scaled)); - string tmp_file = _opt->frame_out_path (frame, true); + string tmp_file = _opt->frame_out_path (_video_frame, true); Magick::Image thumb (compact->size().width, compact->size().height, "RGB", MagickCore::CharPixel, compact->data()[0]); thumb.magick ("PNG"); thumb.write (tmp_file); - boost::filesystem::rename (tmp_file, _opt->frame_out_path (frame, false)); + boost::filesystem::rename (tmp_file, _opt->frame_out_path (_video_frame, false)); if (sub) { float const x_scale = float (_opt->out_size.width) / _film->size().width; float const y_scale = float (_opt->out_size.height) / _film->size().height; - string tmp_metadata_file = _opt->frame_out_path (frame, false, ".sub"); + string tmp_metadata_file = _opt->frame_out_path (_video_frame, false, ".sub"); ofstream metadata (tmp_metadata_file.c_str ()); Size new_size = sub->image()->size (); @@ -74,18 +74,18 @@ ImageMagickEncoder::do_process_video (shared_ptr<const Image> image, SourceFrame shared_ptr<Image> scaled = sub->image()->scale (new_size, _film->scaler()); shared_ptr<Image> compact (new CompactImage (scaled)); - string tmp_sub_file = _opt->frame_out_path (frame, true, ".sub.png"); + string tmp_sub_file = _opt->frame_out_path (_video_frame, true, ".sub.png"); Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]); sub_thumb.magick ("PNG"); sub_thumb.write (tmp_sub_file); - boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (frame, false, ".sub.png")); + boost::filesystem::rename (tmp_sub_file, _opt->frame_out_path (_video_frame, false, ".sub.png")); metadata << "x " << sub->position().x << "\n" << "y " << sub->position().y << "\n"; metadata.close (); - boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (frame, false, ".sub")); + boost::filesystem::rename (tmp_metadata_file, _opt->frame_out_path (_video_frame, false, ".sub")); } - frame_done (frame); + frame_done (); } diff --git a/src/lib/imagemagick_encoder.h b/src/lib/imagemagick_encoder.h index 9adfbf56b..dfc741cb2 100644 --- a/src/lib/imagemagick_encoder.h +++ b/src/lib/imagemagick_encoder.h @@ -36,10 +36,7 @@ class ImageMagickEncoder : public Encoder public: ImageMagickEncoder (boost::shared_ptr<const Film> f, boost::shared_ptr<const Options> o); - void process_begin (int64_t audio_channel_layout) {} - void process_end () {} - private: - void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>); - void do_process_audio (boost::shared_ptr<const AudioBuffers>) {} + void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + void do_process_audio (boost::shared_ptr<AudioBuffers>) {} }; diff --git a/src/lib/j2k_still_encoder.cc b/src/lib/j2k_still_encoder.cc index 2d6c457fc..dd6ef49b2 100644 --- a/src/lib/j2k_still_encoder.cc +++ b/src/lib/j2k_still_encoder.cc @@ -49,7 +49,7 @@ J2KStillEncoder::J2KStillEncoder (shared_ptr<const Film> f, shared_ptr<const Opt } void -J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub) +J2KStillEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub) { pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); DCPVideoFrame* f = new DCPVideoFrame ( @@ -64,7 +64,7 @@ J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame fram } string const real = _opt->frame_out_path (0, false); - for (int i = 1; i < (_film->still_duration() * ImageMagickDecoder::static_frames_per_second()); ++i) { + for (int i = 1; i < (_film->still_duration() * 24); ++i) { if (!boost::filesystem::exists (_opt->frame_out_path (i, false))) { string const link = _opt->frame_out_path (i, false); #ifdef DVDOMATIC_POSIX @@ -77,6 +77,6 @@ J2KStillEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame fram boost::filesystem::copy_file (real, link); #endif } - frame_done (0); + frame_done (); } } diff --git a/src/lib/j2k_still_encoder.h b/src/lib/j2k_still_encoder.h index bc324e967..4ffe876af 100644 --- a/src/lib/j2k_still_encoder.h +++ b/src/lib/j2k_still_encoder.h @@ -36,10 +36,7 @@ class J2KStillEncoder : public Encoder public: J2KStillEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>); - void process_begin (int64_t audio_channel_layout) {} - void process_end () {} - private: - void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>); - void do_process_audio (boost::shared_ptr<const AudioBuffers>) {} + void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + void do_process_audio (boost::shared_ptr<AudioBuffers>) {} }; diff --git a/src/lib/j2k_wav_encoder.cc b/src/lib/j2k_wav_encoder.cc index 6d777babb..134d74623 100644 --- a/src/lib/j2k_wav_encoder.cc +++ b/src/lib/j2k_wav_encoder.cc @@ -46,6 +46,7 @@ using std::stringstream; using std::list; using std::vector; using std::pair; +using std::cout; using boost::shared_ptr; using boost::thread; using boost::lexical_cast; @@ -58,20 +59,22 @@ J2KWAVEncoder::J2KWAVEncoder (shared_ptr<const Film> f, shared_ptr<const Options , _audio_frames_written (0) , _process_end (false) { - /* Create sound output files with .tmp suffixes; we will rename - them if and when we complete. - */ - for (int i = 0; i < _film->audio_channels(); ++i) { - SF_INFO sf_info; - sf_info.samplerate = dcp_audio_sample_rate (_film->audio_sample_rate()); - /* We write mono files */ - sf_info.channels = 1; - sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24; - SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info); - if (f == 0) { - throw CreateFileError (_opt->multichannel_audio_out_path (i, true)); + if (_film->audio_stream()) { + /* Create sound output files with .tmp suffixes; we will rename + them if and when we complete. + */ + for (int i = 0; i < _film->audio_channels(); ++i) { + SF_INFO sf_info; + sf_info.samplerate = dcp_audio_sample_rate (_film->audio_stream()->sample_rate()); + /* We write mono files */ + sf_info.channels = 1; + sf_info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24; + SNDFILE* f = sf_open (_opt->multichannel_audio_out_path (i, true).c_str (), SFM_WRITE, &sf_info); + if (f == 0) { + throw CreateFileError (_opt->multichannel_audio_out_path (i, true)); + } + _sound_files.push_back (f); } - _sound_files.push_back (f); } } @@ -106,7 +109,7 @@ J2KWAVEncoder::close_sound_files () } void -J2KWAVEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame, shared_ptr<Subtitle> sub) +J2KWAVEncoder::do_process_video (shared_ptr<Image> yuv, shared_ptr<Subtitle> sub) { boost::mutex::scoped_lock lock (_worker_mutex); @@ -122,13 +125,13 @@ J2KWAVEncoder::do_process_video (shared_ptr<const Image> yuv, SourceFrame frame, } /* Only do the processing if we don't already have a file for this frame */ - if (!boost::filesystem::exists (_opt->frame_out_path (frame, false))) { + if (!boost::filesystem::exists (_opt->frame_out_path (_video_frame, false))) { pair<string, string> const s = Filter::ffmpeg_strings (_film->filters()); TIMING ("adding to queue of %1", _queue.size ()); _queue.push_back (boost::shared_ptr<DCPVideoFrame> ( new DCPVideoFrame ( yuv, sub, _opt->out_size, _opt->padding, _film->subtitle_offset(), _film->subtitle_scale(), - _film->scaler(), frame, _film->frames_per_second(), s.second, + _film->scaler(), _video_frame, _film->frames_per_second(), s.second, Config::instance()->colour_lut_index (), Config::instance()->j2k_bandwidth (), _film->log() ) @@ -205,7 +208,7 @@ J2KWAVEncoder::encoder_thread (ServerDescription* server) if (encoded) { encoded->write (_opt, vf->frame ()); - frame_done (vf->frame ()); + frame_done (); } else { lock.lock (); _film->log()->log ( @@ -225,24 +228,24 @@ J2KWAVEncoder::encoder_thread (ServerDescription* server) } void -J2KWAVEncoder::process_begin (int64_t audio_channel_layout) +J2KWAVEncoder::process_begin () { - if (_film->audio_sample_rate() != _film->target_audio_sample_rate()) { + if (_film->audio_stream() && _film->audio_stream()->sample_rate() != _film->target_audio_sample_rate()) { #ifdef HAVE_SWRESAMPLE stringstream s; - s << "Will resample audio from " << _film->audio_sample_rate() << " to " << _film->target_audio_sample_rate(); + s << "Will resample audio from " << _film->audio_stream()->sample_rate() << " to " << _film->target_audio_sample_rate(); _film->log()->log (s.str ()); /* We will be using planar float data when we call the resampler */ _swr_context = swr_alloc_set_opts ( 0, - audio_channel_layout, + _film->audio_stream()->channel_layout(), AV_SAMPLE_FMT_FLTP, _film->target_audio_sample_rate(), - audio_channel_layout, + _film->audio_stream()->channel_layout(), AV_SAMPLE_FMT_FLTP, - _film->audio_sample_rate(), + _film->audio_stream()->sample_rate(), 0, 0 ); @@ -303,16 +306,16 @@ J2KWAVEncoder::process_end () try { shared_ptr<EncodedData> e = (*i)->encode_locally (); e->write (_opt, (*i)->frame ()); - frame_done ((*i)->frame ()); + frame_done (); } catch (std::exception& e) { _film->log()->log (String::compose ("Local encode failed (%1)", e.what ())); } } #if HAVE_SWRESAMPLE - if (_swr_context) { + if (_film->audio_stream() && _swr_context) { - shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_channels(), 256)); + shared_ptr<AudioBuffers> out (new AudioBuffers (_film->audio_stream()->channels(), 256)); while (1) { int const frames = swr_convert (_swr_context, (uint8_t **) out->data(), 256, 0, 0); @@ -333,25 +336,21 @@ J2KWAVEncoder::process_end () } #endif - int const dcp_sr = dcp_audio_sample_rate (_film->audio_sample_rate ()); - int64_t const extra_audio_frames = dcp_sr - (_audio_frames_written % dcp_sr); - shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), extra_audio_frames)); - silence->make_silent (); - write_audio (silence); - - close_sound_files (); - - /* Rename .wav.tmp files to .wav */ - for (int i = 0; i < _film->audio_channels(); ++i) { - if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) { - boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false)); + if (_film->audio_stream()) { + close_sound_files (); + + /* Rename .wav.tmp files to .wav */ + for (int i = 0; i < _film->audio_channels(); ++i) { + if (boost::filesystem::exists (_opt->multichannel_audio_out_path (i, false))) { + boost::filesystem::remove (_opt->multichannel_audio_out_path (i, false)); + } + boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false)); } - boost::filesystem::rename (_opt->multichannel_audio_out_path (i, true), _opt->multichannel_audio_out_path (i, false)); } } void -J2KWAVEncoder::do_process_audio (shared_ptr<const AudioBuffers> audio) +J2KWAVEncoder::do_process_audio (shared_ptr<AudioBuffers> audio) { shared_ptr<AudioBuffers> resampled; @@ -360,9 +359,9 @@ J2KWAVEncoder::do_process_audio (shared_ptr<const AudioBuffers> audio) if (_swr_context) { /* Compute the resampled frames count and add 32 for luck */ - int const max_resampled_frames = ceil (audio->frames() * _film->target_audio_sample_rate() / _film->audio_sample_rate()) + 32; + int const max_resampled_frames = ceil ((int64_t) audio->frames() * _film->target_audio_sample_rate() / _film->audio_stream()->sample_rate()) + 32; - resampled.reset (new AudioBuffers (_film->audio_channels(), max_resampled_frames)); + resampled.reset (new AudioBuffers (_film->audio_stream()->channels(), max_resampled_frames)); /* Resample audio */ int const resampled_frames = swr_convert ( diff --git a/src/lib/j2k_wav_encoder.h b/src/lib/j2k_wav_encoder.h index b494be8e5..f3340ba72 100644 --- a/src/lib/j2k_wav_encoder.h +++ b/src/lib/j2k_wav_encoder.h @@ -50,13 +50,13 @@ public: J2KWAVEncoder (boost::shared_ptr<const Film>, boost::shared_ptr<const Options>); ~J2KWAVEncoder (); - void process_begin (int64_t audio_channel_layout); + void process_begin (); void process_end (); private: - void do_process_video (boost::shared_ptr<const Image>, SourceFrame, boost::shared_ptr<Subtitle>); - void do_process_audio (boost::shared_ptr<const AudioBuffers>); + void do_process_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + void do_process_audio (boost::shared_ptr<AudioBuffers>); void write_audio (boost::shared_ptr<const AudioBuffers> audio); void encoder_thread (ServerDescription *); diff --git a/src/lib/job.cc b/src/lib/job.cc index 3309fe16c..201397f08 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -33,6 +33,7 @@ using std::stringstream; using boost::shared_ptr; /** @param s Film that we are operating on. + * @param req Job that must be completed before this job is run. */ Job::Job (shared_ptr<Film> f, shared_ptr<Job> req) : _film (f) @@ -77,6 +78,7 @@ Job::run_wrapper () } } +/** @return true if this job is new (ie has not started running) */ bool Job::is_new () const { diff --git a/src/lib/make_dcp_job.cc b/src/lib/make_dcp_job.cc index a1ffd9672..026724806 100644 --- a/src/lib/make_dcp_job.cc +++ b/src/lib/make_dcp_job.cc @@ -90,7 +90,7 @@ MakeDCPJob::run () frames = _film->dcp_length().get() / dfr.skip; break; case STILL: - frames = _film->still_duration() * ImageMagickDecoder::static_frames_per_second (); + frames = _film->still_duration() * 24; break; } diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc new file mode 100644 index 000000000..7b4434539 --- /dev/null +++ b/src/lib/matcher.cc @@ -0,0 +1,113 @@ +/* + Copyright (C) 2012 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 "matcher.h" +#include "image.h" +#include "log.h" + +using std::min; +using boost::shared_ptr; + +Matcher::Matcher (Log* log, int sample_rate, float frames_per_second) + : AudioVideoProcessor (log) + , _sample_rate (sample_rate) + , _frames_per_second (frames_per_second) + , _video_frames (0) + , _audio_frames (0) +{ + +} + +void +Matcher::process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) +{ + Video (i, s); + _video_frames++; + + _pixel_format = i->pixel_format (); + _size = i->size (); +} + +void +Matcher::process_audio (boost::shared_ptr<AudioBuffers> b) +{ + Audio (b); + _audio_frames += b->frames (); + + _channels = b->channels (); +} + +void +Matcher::process_end () +{ + if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) { + /* We won't do anything */ + return; + } + + int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames; + + _log->log ( + String::compose ( + "Matching processor has seen %1 video frames (which equals %2 audio frames) and %3 audio frames", + _video_frames, + video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second), + _audio_frames + ) + ); + + if (audio_short_by_frames < 0) { + + _log->log (String::compose ("%1 too many audio frames", -audio_short_by_frames)); + + /* We have seen more audio than video. Emit enough black video frames so that we reverse this */ + int const black_video_frames = ceil (-audio_short_by_frames * _frames_per_second / _sample_rate); + + _log->log (String::compose ("Emitting %1 frames of black video", black_video_frames)); + + shared_ptr<Image> black (new CompactImage (_pixel_format.get(), _size.get())); + black->make_black (); + for (int i = 0; i < black_video_frames; ++i) { + Video (black, shared_ptr<Subtitle>()); + } + + /* Now recompute our check value */ + audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames; + } + + if (audio_short_by_frames > 0) { + _log->log (String::compose ("Emitted %1 too few audio frames", audio_short_by_frames)); + + /* Do things in half second blocks as I think there may be limits + to what FFmpeg (and in particular the resampler) can cope with. + */ + int64_t const block = _sample_rate / 2; + shared_ptr<AudioBuffers> b (new AudioBuffers (_channels.get(), block)); + b->make_silent (); + + int64_t to_do = audio_short_by_frames; + while (to_do > 0) { + int64_t const this_time = min (to_do, block); + b->set_frames (this_time); + Audio (b); + _audio_frames += b->frames (); + to_do -= this_time; + } + } +} diff --git a/src/lib/matcher.h b/src/lib/matcher.h new file mode 100644 index 000000000..03efd5cc0 --- /dev/null +++ b/src/lib/matcher.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2012 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/optional.hpp> +#include "processor.h" + +class Matcher : public AudioVideoProcessor +{ +public: + Matcher (Log* log, int sample_rate, float frames_per_second); + void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s); + void process_audio (boost::shared_ptr<AudioBuffers>); + void process_end (); + +private: + int _sample_rate; + float _frames_per_second; + int _video_frames; + int64_t _audio_frames; + boost::optional<AVPixelFormat> _pixel_format; + boost::optional<Size> _size; + boost::optional<int> _channels; +}; diff --git a/src/lib/processor.h b/src/lib/processor.h new file mode 100644 index 000000000..e632f813f --- /dev/null +++ b/src/lib/processor.h @@ -0,0 +1,96 @@ +/* + Copyright (C) 2012 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. + +*/ + +/** @file src/processor.h + * @brief Parent class for classes which accept and then emit video or audio data. + */ + +#ifndef DVDOMATIC_PROCESSOR_H +#define DVDOMATIC_PROCESSOR_H + +#include "video_source.h" +#include "video_sink.h" +#include "audio_source.h" +#include "audio_sink.h" + +class Log; + +/** @class Processor + * @brief Base class for processors. + */ +class Processor +{ +public: + /** Construct a Processor. + * @param log Log to use. + */ + Processor (Log* log) + : _log (log) + {} + + /** Will be called at the end of a processing run */ + virtual void process_end () {} + +protected: + Log* _log; ///< log to write to +}; + +/** @class AudioVideoProcessor + * @brief A processor which handles both video and audio data. + */ +class AudioVideoProcessor : public Processor, public VideoSource, public VideoSink, public AudioSource, public AudioSink +{ +public: + /** Construct an AudioVideoProcessor. + * @param log Log to write to. + */ + AudioVideoProcessor (Log* log) + : Processor (log) + {} +}; + +/** @class AudioProcessor + * @brief A processor which handles just audio data. + */ +class AudioProcessor : public Processor, public AudioSource, public AudioSink +{ +public: + /** Construct an AudioProcessor. + * @param log Log to write to. + */ + AudioProcessor (Log* log) + : Processor (log) + {} +}; + +/** @class VideoProcessor + * @brief A processor which handles just video data. + */ +class VideoProcessor : public Processor, public VideoSource, public VideoSink +{ +public: + /** Construct an VideoProcessor. + * @param log Log to write to. + */ + VideoProcessor (Log* log) + : Processor (log) + {} +}; + +#endif diff --git a/src/lib/stream.cc b/src/lib/stream.cc index d1c2b5a9e..4f12f41b9 100644 --- a/src/lib/stream.cc +++ b/src/lib/stream.cc @@ -20,43 +20,71 @@ #include <sstream> #include "compose.hpp" #include "stream.h" +#include "ffmpeg_decoder.h" +#include "external_audio_decoder.h" -using namespace std; +using std::string; +using std::stringstream; +using boost::shared_ptr; +using boost::optional; -AudioStream::AudioStream (string t) +/** Construct a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +SubtitleStream::SubtitleStream (string t, boost::optional<int>) { stringstream n (t); - n >> _id >> _channels; + n >> _id; - for (int i = 0; i < 2; ++i) { - size_t const s = t.find (' '); - if (s != string::npos) { - t = t.substr (s + 1); - } + size_t const s = t.find (' '); + if (s != string::npos) { + _name = t.substr (s + 1); } - - _name = t; } +/** @return A canonical string representation of this stream */ string -AudioStream::to_string () const +SubtitleStream::to_string () const { - return String::compose ("%1 %2 %3", _id, _channels, _name); + return String::compose ("%1 %2", _id, _name); } -SubtitleStream::SubtitleStream (string t) +/** Create a SubtitleStream from a value returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + */ +shared_ptr<SubtitleStream> +SubtitleStream::create (string t, optional<int> v) { - stringstream n (t); - n >> _id; + return shared_ptr<SubtitleStream> (new SubtitleStream (t, v)); +} - size_t const s = t.find (' '); - if (s != string::npos) { - _name = t.substr (s + 1); +/** Create an AudioStream from a string returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + * @return AudioStream, or 0. + */ +shared_ptr<AudioStream> +audio_stream_factory (string t, optional<int> v) +{ + shared_ptr<AudioStream> s; + + s = FFmpegAudioStream::create (t, v); + if (!s) { + s = ExternalAudioStream::create (t, v); } + + return s; } -string -SubtitleStream::to_string () const +/** Create a SubtitleStream from a string returned from to_string(). + * @param t String returned from to_string(). + * @param v State file version. + * @return SubtitleStream, or 0. + */ +shared_ptr<SubtitleStream> +subtitle_stream_factory (string t, optional<int> v) { - return String::compose ("%1 %2", _id, _name); + return SubtitleStream::create (t, v); } diff --git a/src/lib/stream.h b/src/lib/stream.h index 2db63c620..c2a19e5ab 100644 --- a/src/lib/stream.h +++ b/src/lib/stream.h @@ -17,66 +17,104 @@ */ +/** @file src/lib/stream.h + * @brief Representations of audio and subtitle streams. + * + * Some content may have multiple `streams' of audio and/or subtitles; perhaps + * for multiple languages, or for stereo / surround mixes. These classes represent + * those streams, and know about their details. + */ + #ifndef DVDOMATIC_STREAM_H #define DVDOMATIC_STREAM_H +#include <stdint.h> +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> +extern "C" { +#include <libavutil/audioconvert.h> +} + +/** @class Stream + * @brief Parent class for streams. + */ class Stream { public: - Stream () - : _id (-1) - {} - - Stream (std::string n, int i) - : _name (n) - , _id (i) - {} - virtual std::string to_string () const = 0; - - std::string name () const { - return _name; - } - - int id () const { - return _id; - } - -protected: - std::string _name; - int _id; }; +/** @class AudioStream + * @brief A stream of audio data. + */ struct AudioStream : public Stream { public: - AudioStream (std::string t); - - AudioStream (std::string n, int i, int c) - : Stream (n, i) - , _channels (c) + AudioStream (int r, int64_t l) + : _sample_rate (r) + , _channel_layout (l) {} - std::string to_string () const; + /* Only used for backwards compatibility for state file version < 1 */ + void set_sample_rate (int s) { + _sample_rate = s; + } int channels () const { - return _channels; + return av_get_channel_layout_nb_channels (_channel_layout); } -private: - int _channels; + int sample_rate () const { + return _sample_rate; + } + + int64_t channel_layout () const { + return _channel_layout; + } + +protected: + AudioStream () + : _sample_rate (0) + , _channel_layout (0) + {} + + int _sample_rate; + int64_t _channel_layout; }; +/** @class SubtitleStream + * @brief A stream of subtitle data. + */ class SubtitleStream : public Stream { public: - SubtitleStream (std::string t); - SubtitleStream (std::string n, int i) - : Stream (n, i) + : _name (n) + , _id (i) {} std::string to_string () const; + + std::string name () const { + return _name; + } + + int id () const { + return _id; + } + + static boost::shared_ptr<SubtitleStream> create (std::string t, boost::optional<int> v); + +private: + friend class stream_test; + + SubtitleStream (std::string t, boost::optional<int> v); + + std::string _name; + int _id; }; +boost::shared_ptr<AudioStream> audio_stream_factory (std::string t, boost::optional<int> version); +boost::shared_ptr<SubtitleStream> subtitle_stream_factory (std::string t, boost::optional<int> version); + #endif diff --git a/src/lib/tiff_decoder.cc b/src/lib/tiff_decoder.cc deleted file mode 100644 index 7cca511dd..000000000 --- a/src/lib/tiff_decoder.cc +++ /dev/null @@ -1,220 +0,0 @@ -/* - Copyright (C) 2012 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. - -*/ - -/** @file src/tiff_decoder.cc - * @brief A decoder which reads a numbered set of TIFF files, one per frame. - */ - -#include <vector> -#include <string> -#include <iostream> -#include <stdint.h> -#include <boost/shared_ptr.hpp> -#include <boost/filesystem.hpp> -#include <tiffio.h> -extern "C" { -#include <libavformat/avformat.h> -} -#include "util.h" -#include "tiff_decoder.h" -#include "exceptions.h" -#include "image.h" -#include "options.h" -#include "film.h" - -using namespace std; -using namespace boost; - -/** @param f Our Film. - * @param o Options. - * @param j Job that we are associated with, or 0. - */ -TIFFDecoder::TIFFDecoder (boost::shared_ptr<Film> f, boost::shared_ptr<const Options> o, Job* j) - : Decoder (f, o, j) -{ - string const dir = _film->content_path (); - - if (!filesystem::is_directory (dir)) { - throw DecodeError ("TIFF content must be in a directory"); - } - - for (filesystem::directory_iterator i = filesystem::directory_iterator (dir); i != filesystem::directory_iterator(); ++i) { - /* Aah, the sweet smell of progress */ -#if BOOST_FILESYSTEM_VERSION == 3 - string const ext = filesystem::path(*i).extension().string(); - string const l = filesystem::path(*i).leaf().generic_string(); -#else - string const ext = filesystem::path(*i).extension(); - string const l = i->leaf (); -#endif - if (ext == ".tif" || ext == ".tiff") { - _files.push_back (l); - } - } - - _files.sort (); - - _iter = _files.begin (); - -} - -float -TIFFDecoder::frames_per_second () const -{ - /* We don't know */ - return 0; -} - -Size -TIFFDecoder::native_size () const -{ - if (_files.empty ()) { - throw DecodeError ("no TIFF files found"); - } - - TIFF* t = TIFFOpen (file_path (_files.front()).c_str (), "r"); - if (t == 0) { - throw DecodeError ("could not open TIFF file"); - } - - uint32_t width; - TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width); - uint32_t height; - TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height); - - return Size (width, height); -} - -int -TIFFDecoder::audio_channels () const -{ - /* No audio */ - return 0; -} - -int -TIFFDecoder::audio_sample_rate () const -{ - return 0; -} - -AVSampleFormat -TIFFDecoder::audio_sample_format () const -{ - return AV_SAMPLE_FMT_NONE; -} - - -int64_t -TIFFDecoder::audio_channel_layout () const -{ - return 0; -} - -bool -TIFFDecoder::pass () -{ - if (_iter == _files.end ()) { - return true; - } - - TIFF* t = TIFFOpen (file_path (*_iter).c_str (), "r"); - if (t == 0) { - throw DecodeError ("could not open TIFF file"); - } - - uint32_t width; - TIFFGetField (t, TIFFTAG_IMAGEWIDTH, &width); - uint32_t height; - TIFFGetField (t, TIFFTAG_IMAGELENGTH, &height); - - int const num_pixels = width * height; - uint32_t * raster = (uint32_t *) _TIFFmalloc (num_pixels * sizeof (uint32_t)); - if (raster == 0) { - throw DecodeError ("could not allocate memory to decode TIFF file"); - } - - if (TIFFReadRGBAImage (t, width, height, raster, 0) < 0) { - throw DecodeError ("could not read TIFF data"); - } - - RGBFrameImage image (Size (width, height)); - - uint8_t* p = image.data()[0]; - for (uint32_t y = 0; y < height; ++y) { - for (uint32_t x = 0; x < width; ++x) { - uint32_t const i = (height - y - 1) * width + x; - *p++ = raster[i] & 0xff; - *p++ = (raster[i] & 0xff00) >> 8; - *p++ = (raster[i] & 0xff0000) >> 16; - } - } - - _TIFFfree (raster); - TIFFClose (t); - - process_video (image.frame ()); - - ++_iter; - return false; -} - -/** @param file name within our content directory - * @return full path to the file - */ -string -TIFFDecoder::file_path (string f) const -{ - stringstream s; - s << _film->content_path() << "/" << f; - return _film->file (s.str ()); -} - -PixelFormat -TIFFDecoder::pixel_format () const -{ - return PIX_FMT_RGB24; -} - -int -TIFFDecoder::time_base_numerator () const -{ - return dcp_frame_rate(_film->frames_per_second()).frames_per_second; -} - - -int -TIFFDecoder::time_base_denominator () const -{ - return 1; -} - -int -TIFFDecoder::sample_aspect_ratio_numerator () const -{ - /* XXX */ - return 1; -} - -int -TIFFDecoder::sample_aspect_ratio_denominator () const -{ - /* XXX */ - return 1; -} diff --git a/src/lib/tiff_decoder.h b/src/lib/tiff_decoder.h deleted file mode 100644 index 1c33443cf..000000000 --- a/src/lib/tiff_decoder.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright (C) 2012 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. - -*/ - -/** @file src/tiff_decoder.h - * @brief A decoder which reads a numbered set of TIFF files, one per frame. - */ - -#ifndef DVDOMATIC_TIFF_DECODER_H -#define DVDOMATIC_TIFF_DECODER_H - -#include <vector> -#include <string> -#include <stdint.h> -#include <boost/shared_ptr.hpp> -#include "util.h" -#include "decoder.h" - -class Job; -class FilmState; -class Options; -class Image; - -/** @class TIFFDecoder. - * @brief A decoder which reads a numbered set of TIFF files, one per frame. - */ -class TIFFDecoder : public Decoder -{ -public: - TIFFDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); - - /* Methods to query our input video */ - float frames_per_second () const; - Size native_size () const; - int audio_channels () const; - int audio_sample_rate () const; - AVSampleFormat audio_sample_format () const; - int64_t audio_channel_layout () const; - bool has_subtitles () const { - return false; - } - -private: - bool pass (); - PixelFormat pixel_format () const; - int time_base_numerator () const; - int time_base_denominator () const; - int sample_aspect_ratio_numerator () const; - int sample_aspect_ratio_denominator () const; - - std::string file_path (std::string) const; - std::list<std::string> _files; - std::list<std::string>::iterator _iter; -}; - -#endif diff --git a/src/lib/transcode_job.cc b/src/lib/transcode_job.cc index 25581c8f5..081e04252 100644 --- a/src/lib/transcode_job.cc +++ b/src/lib/transcode_job.cc @@ -39,6 +39,7 @@ using boost::shared_ptr; /** @param s Film to use. * @param o Options. + * @param req Job that must be completed before this job is run. */ TranscodeJob::TranscodeJob (shared_ptr<Film> f, shared_ptr<const Options> o, shared_ptr<Job> req) : Job (f, req) @@ -116,6 +117,6 @@ TranscodeJob::remaining_time () const } /* We assume that dcp_length() is valid */ - SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->last_frame(); + SourceFrame const left = _film->dcp_trim_start() + _film->dcp_length().get() - _encoder->video_frame(); return left / fps; } diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index 66d5606af..537b9b664 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -29,9 +29,18 @@ #include "transcoder.h" #include "encoder.h" #include "decoder_factory.h" +#include "film.h" +#include "matcher.h" +#include "delay_line.h" +#include "options.h" +#include "gain.h" +#include "video_decoder.h" +#include "audio_decoder.h" using std::string; +using std::cout; using boost::shared_ptr; +using boost::dynamic_pointer_cast; /** Construct a transcoder using a Decoder that we create and a supplied Encoder. * @param f Film that we are transcoding. @@ -42,12 +51,34 @@ using boost::shared_ptr; Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, shared_ptr<Encoder> e) : _job (j) , _encoder (e) - , _decoder (decoder_factory (f, o, j)) + , _decoders (decoder_factory (f, o, j)) { assert (_encoder); + + if (f->audio_stream()) { + shared_ptr<AudioStream> st = f->audio_stream(); + _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->frames_per_second())); + _delay_line.reset (new DelayLine (f->log(), st->channels(), f->audio_delay() * st->sample_rate() / 1000)); + _gain.reset (new Gain (f->log(), f->audio_gain())); + } + + /* Set up the decoder to use the film's set streams */ + _decoders.first->set_subtitle_stream (f->subtitle_stream ()); + _decoders.second->set_audio_stream (f->audio_stream ()); + + if (_matcher) { + _decoders.first->connect_video (_matcher); + _matcher->connect_video (_encoder); + } else { + _decoders.first->connect_video (_encoder); + } - _decoder->Video.connect (bind (&Encoder::process_video, e, _1, _2, _3)); - _decoder->Audio.connect (bind (&Encoder::process_audio, e, _1, _2)); + if (_matcher && _delay_line) { + _decoders.second->connect_audio (_delay_line); + _delay_line->connect_audio (_matcher); + _matcher->connect_audio (_gain); + _gain->connect_audio (_encoder); + } } /** Run the decoder, passing its output to the encoder, until the decoder @@ -56,16 +87,40 @@ Transcoder::Transcoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j, void Transcoder::go () { - _encoder->process_begin (_decoder->audio_channel_layout()); + _encoder->process_begin (); try { - _decoder->go (); + bool done[2] = { false, false }; + + while (1) { + if (!done[0]) { + done[0] = _decoders.first->pass (); + _decoders.first->set_progress (); + } + + if (!done[1] && dynamic_pointer_cast<Decoder> (_decoders.second) != dynamic_pointer_cast<Decoder> (_decoders.first)) { + done[1] = _decoders.second->pass (); + } else { + done[1] = true; + } + + if (done[0] && done[1]) { + break; + } + } + } catch (...) { - /* process_end() is important as the decoder may have worker - threads that need to be cleaned up. - */ _encoder->process_end (); throw; } - + + if (_delay_line) { + _delay_line->process_end (); + } + if (_matcher) { + _matcher->process_end (); + } + if (_gain) { + _gain->process_end (); + } _encoder->process_end (); } diff --git a/src/lib/transcoder.h b/src/lib/transcoder.h index 79ef89a18..e3ca2bb32 100644 --- a/src/lib/transcoder.h +++ b/src/lib/transcoder.h @@ -17,8 +17,6 @@ */ -#include "decoder.h" - /** @file src/transcoder.h * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. * @@ -30,6 +28,13 @@ class Film; class Job; class Encoder; class FilmState; +class Matcher; +class VideoFilter; +class Gain; +class VideoDecoder; +class AudioDecoder; +class DelayLine; +class Options; /** @class Transcoder * @brief A class which takes a FilmState and some Options, then uses those to transcode a Film. @@ -44,16 +49,14 @@ public: void go (); - /** @return Our decoder */ - boost::shared_ptr<Decoder> decoder () { - return _decoder; - } - protected: /** A Job that is running this Transcoder, or 0 */ Job* _job; /** The encoder that we will use */ boost::shared_ptr<Encoder> _encoder; - /** The decoder that we will use */ - boost::shared_ptr<Decoder> _decoder; + /** The decoders that we will use */ + std::pair<boost::shared_ptr<VideoDecoder>, boost::shared_ptr<AudioDecoder> > _decoders; + boost::shared_ptr<Matcher> _matcher; + boost::shared_ptr<DelayLine> _delay_line; + boost::shared_ptr<Gain> _gain; }; diff --git a/src/lib/util.cc b/src/lib/util.cc index 6179eedac..b69581eba 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -37,6 +37,7 @@ #include <boost/lambda/lambda.hpp> #include <boost/lexical_cast.hpp> #include <boost/thread.hpp> +#include <boost/filesystem.hpp> #include <openjpeg.h> #include <openssl/md5.h> #include <magick/MagickCore.h> @@ -238,6 +239,10 @@ dvdomatic_setup () ui_thread = this_thread::get_id (); } +/** @param start Start position for the crop within the image. + * @param size Size of the cropped area. + * @return FFmpeg crop filter string. + */ string crop_string (Position start, Size size) { @@ -246,6 +251,9 @@ crop_string (Position start, Size size) return s.str (); } +/** @param s A string. + * @return Parts of the string split at spaces, except when a space is within quotation marks. + */ vector<string> split_at_spaces_considering_quotes (string s) { @@ -322,6 +330,9 @@ md5_digest (string file) return s.str (); } +/** @param fps Arbitrary frames-per-second value. + * @return DCPFrameRate for this frames-per-second. + */ DCPFrameRate dcp_frame_rate (float fps) { @@ -548,6 +559,9 @@ Socket::read_indefinite (uint8_t* data, int size, int timeout) memcpy (data, _buffer, size); } +/** @param other A Rect. + * @return The intersection of this with `other'. + */ Rect Rect::intersection (Rect const & other) const { @@ -566,14 +580,19 @@ Rect::intersection (Rect const & other) const * @param t Multiple to round to. * @return Rounded number. */ - int -round_up (int a, int t) +stride_round_up (int c, int const * stride, int t) { - a += (t - 1); + int const a = stride[c] + (t - 1); return a - (a % t); } +int +stride_lookup (int c, int const * stride) +{ + return stride[c]; +} + /** Read a sequence of key / value pairs from a text stream; * the keys are the first words on the line, and the values are * the remainder of the line following the key. Lines beginning @@ -591,7 +610,7 @@ read_key_value (istream &s) if (line.empty ()) { continue; } - + if (line[0] == '#') { continue; } @@ -671,6 +690,10 @@ 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) @@ -682,6 +705,9 @@ AudioBuffers::AudioBuffers (int channels, int frames) } } +/** Copy constructor. + * @param other Other AudioBuffers; data is copied. + */ AudioBuffers::AudioBuffers (AudioBuffers const & other) : _channels (other._channels) , _frames (other._frames) @@ -694,6 +720,7 @@ AudioBuffers::AudioBuffers (AudioBuffers const & other) } } +/** AudioBuffers destructor */ AudioBuffers::~AudioBuffers () { for (int i = 0; i < _channels; ++i) { @@ -703,13 +730,20 @@ AudioBuffers::~AudioBuffers () 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) { @@ -717,26 +751,54 @@ AudioBuffers::set_frames (int f) _frames = f; } +/** Make all samples on all channels silent */ void AudioBuffers::make_silent () { for (int i = 0; i < _channels; ++i) { - for (int j = 0; j < _frames; ++j) { - _data[i][j] = 0; - } + 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) { @@ -758,14 +820,37 @@ AudioBuffers::move (int from, int to, int frames) } } +/** Trip an assert if the caller is not in the UI thread */ void ensure_ui_thread () { assert (this_thread::get_id() == ui_thread); } +/** @param v Source video frame. + * @param audio_sample_rate Source audio sample rate. + * @param frames_per_second Number of video frames per second. + * @return Equivalent number of audio frames for `v'. + */ int64_t video_frames_to_audio_frames (SourceFrame v, float audio_sample_rate, float frames_per_second) { return ((int64_t) v * audio_sample_rate / frames_per_second); } + +/** @param f Filename. + * @return true if this file is a still image, false if it is something else. + */ +bool +still_image_file (string f) +{ +#if BOOST_FILESYSTEM_VERSION == 3 + string ext = boost::filesystem::path(f).extension().string(); +#else + string ext = boost::filesystem::path(f).extension(); +#endif + + transform (ext.begin(), ext.end(), ext.begin(), ::tolower); + + return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png"); +} diff --git a/src/lib/util.h b/src/lib/util.h index 7aa9f25e1..68c7bd384 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -41,6 +41,9 @@ extern "C" { #define TIMING(...) #endif +/** The maximum number of audio channels that we can cope with */ +#define MAX_AUDIO_CHANNELS 6 + class Scaler; extern std::string seconds_to_hms (int); @@ -58,14 +61,21 @@ typedef int SourceFrame; struct DCPFrameRate { + /** frames per second for the DCP */ int frames_per_second; + /** Skip every `skip' frames. e.g. if this is 1, we skip nothing; + * if it's 2, we skip every other frame. + */ int skip; + /** true if this DCP will run its video faster than the source + * (e.g. if the source is 29.97fps and we will run the DCP at 30fps) + */ bool run_fast; }; enum ContentType { - STILL, - VIDEO + STILL, ///< content is still images + VIDEO ///< content is a video }; /** @class Size @@ -94,7 +104,9 @@ struct Size extern bool operator== (Size const & a, Size const & b); -/** A description of the crop of an image or video. */ +/** @struct Crop + * @brief A description of the crop of an image or video. + */ struct Crop { Crop () : left (0), right (0), top (0), bottom (0) {} @@ -112,7 +124,9 @@ struct Crop extern bool operator== (Crop const & a, Crop const & b); extern bool operator!= (Crop const & a, Crop const & b); -/** A position */ +/** @struct Position + * @brief A position. + */ struct Position { Position () @@ -131,7 +145,9 @@ struct Position int y; }; -/** A rectangle */ +/** @struct Rect + * @brief A rectangle. + */ struct Rect { Rect () @@ -168,7 +184,8 @@ extern std::string crop_string (Position, Size); extern int dcp_audio_sample_rate (int); extern DCPFrameRate dcp_frame_rate (float); extern std::string colour_lut_index_to_name (int index); -extern int round_up (int, int); +extern int stride_round_up (int, int const *, int); +extern int stride_lookup (int c, int const * stride); extern std::multimap<std::string, std::string> read_key_value (std::istream& s); extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k); extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k); @@ -216,6 +233,9 @@ private: int _buffer_data; }; +/** @class AudioBuffers + * @brief A class to hold multi-channel audio data in float format. + */ class AudioBuffers { public: @@ -240,18 +260,24 @@ public: 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); 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 (SourceFrame v, float audio_sample_rate, float frames_per_second); +extern bool still_image_file (std::string); #endif diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc new file mode 100644 index 000000000..23a69f958 --- /dev/null +++ b/src/lib/video_decoder.cc @@ -0,0 +1,98 @@ +/* + Copyright (C) 2012 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 "video_decoder.h" +#include "subtitle.h" +#include "film.h" +#include "image.h" +#include "log.h" +#include "options.h" +#include "job.h" + +using boost::shared_ptr; +using boost::optional; + +VideoDecoder::VideoDecoder (shared_ptr<Film> f, shared_ptr<const Options> o, Job* j) + : Decoder (f, o, j) + , _video_frame (0) +{ + +} + +/** Called by subclasses to tell the world that some video data is ready. + * We find a subtitle then emit it for listeners. + * @param frame to decode; caller manages memory. + */ +void +VideoDecoder::emit_video (shared_ptr<Image> image) +{ + shared_ptr<Subtitle> sub; + if (_timed_subtitle && _timed_subtitle->displayed_at (double (video_frame()) / _film->frames_per_second())) { + sub = _timed_subtitle->subtitle (); + } + + signal_video (image, sub); +} + +void +VideoDecoder::repeat_last_video () +{ + if (!_last_image) { + _last_image.reset (new CompactImage (pixel_format(), native_size())); + _last_image->make_black (); + } + + signal_video (_last_image, _last_subtitle); +} + +void +VideoDecoder::signal_video (shared_ptr<Image> image, shared_ptr<Subtitle> sub) +{ + TIMING ("Decoder emits %1", _video_frame); + Video (image, sub); + ++_video_frame; + + _last_image = image; + _last_subtitle = sub; +} + +void +VideoDecoder::emit_subtitle (shared_ptr<TimedSubtitle> s) +{ + _timed_subtitle = s; + + if (_timed_subtitle && _opt->apply_crop) { + Position const p = _timed_subtitle->subtitle()->position (); + _timed_subtitle->subtitle()->set_position (Position (p.x - _film->crop().left, p.y - _film->crop().top)); + } +} + +void +VideoDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) +{ + _subtitle_stream = s; +} + +void +VideoDecoder::set_progress () const +{ + if (_job && _film->dcp_length()) { + _job->set_progress (float (_video_frame) / _film->length().get()); + } +} diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h new file mode 100644 index 000000000..ea1899840 --- /dev/null +++ b/src/lib/video_decoder.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_VIDEO_DECODER_H +#define DVDOMATIC_VIDEO_DECODER_H + +#include "video_source.h" +#include "stream.h" +#include "decoder.h" + +class VideoDecoder : public VideoSource, public virtual Decoder +{ +public: + VideoDecoder (boost::shared_ptr<Film>, boost::shared_ptr<const Options>, Job *); + + /** @return video frames per second, or 0 if unknown */ + virtual float frames_per_second () const = 0; + /** @return native size in pixels */ + virtual Size native_size () const = 0; + + virtual int time_base_numerator () const = 0; + virtual int time_base_denominator () const = 0; + virtual int sample_aspect_ratio_numerator () const = 0; + virtual int sample_aspect_ratio_denominator () const = 0; + + virtual void set_subtitle_stream (boost::shared_ptr<SubtitleStream>); + + void set_progress () const; + + SourceFrame video_frame () const { + return _video_frame; + } + + boost::shared_ptr<SubtitleStream> subtitle_stream () const { + return _subtitle_stream; + } + + std::vector<boost::shared_ptr<SubtitleStream> > subtitle_streams () const { + return _subtitle_streams; + } + +protected: + + virtual PixelFormat pixel_format () const = 0; + + void emit_video (boost::shared_ptr<Image>); + void emit_subtitle (boost::shared_ptr<TimedSubtitle>); + void repeat_last_video (); + + /** Subtitle stream to use when decoding */ + boost::shared_ptr<SubtitleStream> _subtitle_stream; + /** Subtitle streams that this decoder's content has */ + std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; + +private: + void signal_video (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>); + + SourceFrame _video_frame; + + boost::shared_ptr<TimedSubtitle> _timed_subtitle; + + boost::shared_ptr<Image> _last_image; + boost::shared_ptr<Subtitle> _last_subtitle; +}; + +#endif diff --git a/src/lib/video_sink.h b/src/lib/video_sink.h new file mode 100644 index 000000000..78500c643 --- /dev/null +++ b/src/lib/video_sink.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2012 Carl Hetherington <cth@carlh.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DVDOMATIC_VIDEO_SINK_H +#define DVDOMATIC_VIDEO_SINK_H + +#include <boost/shared_ptr.hpp> +#include "util.h" + +class Subtitle; +class Image; + +class VideoSink +{ +public: + /** Call with a frame of video. + * @param i Video frame image. + * @param s A subtitle that should be on this frame, or 0. + */ + virtual void process_video (boost::shared_ptr<Image> i, boost::shared_ptr<Subtitle> s) = 0; +}; + +#endif diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc new file mode 100644 index 000000000..c6bd4a5cf --- /dev/null +++ b/src/lib/video_source.cc @@ -0,0 +1,30 @@ +/* + Copyright (C) 2012 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 "video_source.h" +#include "video_sink.h" + +using boost::shared_ptr; +using boost::bind; + +void +VideoSource::connect_video (shared_ptr<VideoSink> s) +{ + Video.connect (bind (&VideoSink::process_video, s, _1, _2)); +} diff --git a/src/lib/video_source.h b/src/lib/video_source.h new file mode 100644 index 000000000..d53589e2e --- /dev/null +++ b/src/lib/video_source.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2012 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. + +*/ + +/** @file src/video_source.h + * @brief Parent class for classes which emit video data. + */ + +#ifndef DVDOMATIC_VIDEO_SOURCE_H +#define DVDOMATIC_VIDEO_SOURCE_H + +#include <boost/shared_ptr.hpp> +#include <boost/signals2.hpp> +#include "util.h" + +class VideoSink; +class Subtitle; +class Image; + +/** @class VideoSink + * @param A class that emits video data. + */ +class VideoSource +{ +public: + + /** Emitted when a video frame is ready. + * First parameter is the video image. + * Second parameter is either 0 or a subtitle that should be on this frame. + */ + boost::signals2::signal<void (boost::shared_ptr<Image>, boost::shared_ptr<Subtitle>)> Video; + + void connect_video (boost::shared_ptr<VideoSink>); +}; + +#endif diff --git a/src/lib/wscript b/src/lib/wscript index 2d31e09a2..0d3c7d99d 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -8,8 +8,11 @@ def build(bld): obj.source = """ ab_transcode_job.cc ab_transcoder.cc + audio_decoder.cc + audio_source.cc check_hashes_job.cc config.cc + combiner.cc cross.cc dcp_content_type.cc dcp_video_frame.cc @@ -20,12 +23,14 @@ def build(bld): encoder.cc encoder_factory.cc examine_content_job.cc + external_audio_decoder.cc filter_graph.cc ffmpeg_compatibility.cc ffmpeg_decoder.cc film.cc filter.cc format.cc + gain.cc image.cc imagemagick_decoder.cc imagemagick_encoder.cc @@ -36,19 +41,21 @@ def build(bld): log.cc lut.cc make_dcp_job.cc + matcher.cc scp_dcp_job.cc scaler.cc server.cc sound_processor.cc stream.cc subtitle.cc - tiff_decoder.cc timer.cc transcode_job.cc transcoder.cc ui_signaller.cc util.cc version.cc + video_decoder.cc + video_source.cc """ obj.target = 'dvdomatic' |
