diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/ab_transcoder.cc | 33 | ||||
| -rw-r--r-- | src/lib/audio_decoder.h | 2 | ||||
| -rw-r--r-- | src/lib/audio_sink.h | 7 | ||||
| -rw-r--r-- | src/lib/audio_source.cc | 6 | ||||
| -rw-r--r-- | src/lib/audio_source.h | 12 | ||||
| -rw-r--r-- | src/lib/combiner.cc | 8 | ||||
| -rw-r--r-- | src/lib/combiner.h | 6 | ||||
| -rw-r--r-- | src/lib/delay_line.cc | 70 | ||||
| -rw-r--r-- | src/lib/delay_line.h | 15 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.cc | 141 | ||||
| -rw-r--r-- | src/lib/ffmpeg_decoder.h | 7 | ||||
| -rw-r--r-- | src/lib/imagemagick_decoder.cc | 14 | ||||
| -rw-r--r-- | src/lib/imagemagick_decoder.h | 7 | ||||
| -rw-r--r-- | src/lib/job.cc | 14 | ||||
| -rw-r--r-- | src/lib/matcher.cc | 159 | ||||
| -rw-r--r-- | src/lib/matcher.h | 29 | ||||
| -rw-r--r-- | src/lib/processor.h | 17 | ||||
| -rw-r--r-- | src/lib/sndfile_decoder.cc | 5 | ||||
| -rw-r--r-- | src/lib/transcoder.cc | 45 | ||||
| -rw-r--r-- | src/lib/video_decoder.cc | 38 | ||||
| -rw-r--r-- | src/lib/video_decoder.h | 10 | ||||
| -rw-r--r-- | src/lib/video_sink.h | 12 | ||||
| -rw-r--r-- | src/lib/video_source.cc | 6 | ||||
| -rw-r--r-- | src/lib/video_source.h | 23 |
24 files changed, 352 insertions, 334 deletions
diff --git a/src/lib/ab_transcoder.cc b/src/lib/ab_transcoder.cc index 3a1cd83d7..6eef397c2 100644 --- a/src/lib/ab_transcoder.cc +++ b/src/lib/ab_transcoder.cc @@ -60,34 +60,27 @@ ABTranscoder::ABTranscoder ( _da = decoder_factory (_film_a, o); _db = decoder_factory (_film_b, o); - 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->source_frame_rate())); - _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())); - } + shared_ptr<AudioStream> st = _film_a->audio_stream(); + _matcher.reset (new Matcher (_film_a->log(), st->sample_rate(), _film_a->source_frame_rate())); + _delay_line.reset (new DelayLine (_film_a->log(), _film_a->audio_delay() / 1000.0f)); + _gain.reset (new Gain (_film_a->log(), _film_a->audio_gain())); /* Set up the decoder to use the film's set streams */ _da.video->set_subtitle_stream (_film_a->subtitle_stream ()); _db.video->set_subtitle_stream (_film_a->subtitle_stream ()); _da.audio->set_audio_stream (_film_a->audio_stream ()); - _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3)); - _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3)); + _da.video->Video.connect (bind (&Combiner::process_video, _combiner, _1, _2, _3, _4)); + _db.video->Video.connect (bind (&Combiner::process_video_b, _combiner, _1, _2, _3, _4)); - if (_matcher) { - _combiner->connect_video (_matcher); - _matcher->connect_video (_encoder); - } else { - _combiner->connect_video (_encoder); - } + _combiner->connect_video (_delay_line); + _delay_line->connect_video (_matcher); + _matcher->connect_video (_encoder); - if (_matcher && _delay_line) { - _da.audio->connect_audio (_delay_line); - _delay_line->connect_audio (_matcher); - _matcher->connect_audio (_gain); - _gain->connect_audio (_encoder); - } + _da.audio->connect_audio (_delay_line); + _delay_line->connect_audio (_matcher); + _matcher->connect_audio (_gain); + _gain->connect_audio (_encoder); } void diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 9bef8e0e7..cfe94b528 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -31,7 +31,7 @@ /** @class AudioDecoder. * @brief Parent class for audio decoders. */ -class AudioDecoder : public AudioSource, public virtual Decoder +class AudioDecoder : public TimedAudioSource, public virtual Decoder { public: AudioDecoder (boost::shared_ptr<Film>, DecodeOptions); diff --git a/src/lib/audio_sink.h b/src/lib/audio_sink.h index 11d578a60..f34b24f88 100644 --- a/src/lib/audio_sink.h +++ b/src/lib/audio_sink.h @@ -27,4 +27,11 @@ public: virtual void process_audio (boost::shared_ptr<AudioBuffers>) = 0; }; +class TimedAudioSink +{ +public: + /** Call with some audio data */ + virtual void process_audio (boost::shared_ptr<AudioBuffers>, double t) = 0; +}; + #endif diff --git a/src/lib/audio_source.cc b/src/lib/audio_source.cc index 53b0dda15..bca3562cf 100644 --- a/src/lib/audio_source.cc +++ b/src/lib/audio_source.cc @@ -28,3 +28,9 @@ AudioSource::connect_audio (shared_ptr<AudioSink> s) { Audio.connect (bind (&AudioSink::process_audio, s, _1)); } + +void +TimedAudioSource::connect_audio (shared_ptr<TimedAudioSink> s) +{ + Audio.connect (bind (&TimedAudioSink::process_audio, s, _1, _2)); +} diff --git a/src/lib/audio_source.h b/src/lib/audio_source.h index 5a1510d3c..3dc998cca 100644 --- a/src/lib/audio_source.h +++ b/src/lib/audio_source.h @@ -28,6 +28,7 @@ class AudioBuffers; class AudioSink; +class TimedAudioSink; /** A class that emits audio data */ class AudioSource @@ -39,4 +40,15 @@ public: void connect_audio (boost::shared_ptr<AudioSink>); }; + +/** A class that emits audio data with timestamps */ +class TimedAudioSource +{ +public: + /** Emitted when some audio data is ready */ + boost::signals2::signal<void (boost::shared_ptr<AudioBuffers>, double)> Audio; + + void connect_audio (boost::shared_ptr<TimedAudioSink>); +}; + #endif diff --git a/src/lib/combiner.cc b/src/lib/combiner.cc index 12ce4a96e..006dd2697 100644 --- a/src/lib/combiner.cc +++ b/src/lib/combiner.cc @@ -23,7 +23,7 @@ using boost::shared_ptr; Combiner::Combiner (shared_ptr<Log> log) - : VideoProcessor (log) + : TimedVideoProcessor (log) { } @@ -33,7 +33,7 @@ Combiner::Combiner (shared_ptr<Log> log) * @param image Frame image. */ void -Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>) +Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>, double t) { _image = image; } @@ -43,7 +43,7 @@ Combiner::process_video (shared_ptr<Image> image, bool, shared_ptr<Subtitle>) * @param sub Subtitle (which will be put onto the whole frame) */ void -Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub) +Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> sub, double t) { /* Copy the right half of this image into our _image */ /* XXX: this should probably be in the Image class */ @@ -62,6 +62,6 @@ Combiner::process_video_b (shared_ptr<Image> image, bool, shared_ptr<Subtitle> s } } - Video (_image, false, sub); + Video (_image, false, sub, t); _image.reset (); } diff --git a/src/lib/combiner.h b/src/lib/combiner.h index 68026eaff..a8f1fa804 100644 --- a/src/lib/combiner.h +++ b/src/lib/combiner.h @@ -28,13 +28,13 @@ * one image used for the left half of the screen and the other for * the right. */ -class Combiner : public VideoProcessor +class Combiner : public TimedVideoProcessor { public: Combiner (boost::shared_ptr<Log> log); - void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); - void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); + void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double); + void process_video_b (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double); private: /** The image that we are currently working on */ diff --git a/src/lib/delay_line.cc b/src/lib/delay_line.cc index 53da9a412..9e6baeba8 100644 --- a/src/lib/delay_line.cc +++ b/src/lib/delay_line.cc @@ -27,67 +27,31 @@ using std::min; using boost::shared_ptr; -/** @param channels Number of channels of audio. - * @param frames Delay in frames, +ve to move audio later. +/* @param seconds Delay in seconds, +ve to move audio later. */ -DelayLine::DelayLine (shared_ptr<Log> log, int channels, int frames) - : AudioProcessor (log) - , _negative_delay_remaining (0) - , _frames (frames) +DelayLine::DelayLine (shared_ptr<Log> log, double seconds) + : TimedAudioVideoProcessor (log) + , _seconds (seconds) { - if (_frames > 0) { - /* We need a buffer to keep some data in */ - _buffers.reset (new AudioBuffers (channels, _frames)); - _buffers->make_silent (); - } else if (_frames < 0) { - /* We can do -ve delays just by chopping off - the start, so no buffer needed. - */ - _negative_delay_remaining = -_frames; - } + } void -DelayLine::process_audio (shared_ptr<AudioBuffers> data) +DelayLine::process_audio (shared_ptr<AudioBuffers> data, double t) { - if (_buffers) { - /* We have some buffers, so we are moving the audio later */ - - /* Copy the input data */ - AudioBuffers input (*data.get ()); - - int to_do = data->frames (); - - /* Write some of our buffer to the output */ - int const from_buffer = min (to_do, _buffers->frames()); - data->copy_from (_buffers.get(), from_buffer, 0, 0); - to_do -= from_buffer; - - /* Write some of the input to the output */ - int const from_input = to_do; - data->copy_from (&input, from_input, 0, from_buffer); - - int const left_in_buffer = _buffers->frames() - from_buffer; - - /* Shuffle our buffer down */ - _buffers->move (from_buffer, 0, left_in_buffer); - - /* Copy remaining input data to our buffer */ - _buffers->copy_from (&input, input.frames() - from_input, from_input, left_in_buffer); - - } else { + if (_seconds > 0) { + t += _seconds; + } - /* Chop the initial data off until _negative_delay_remaining - is zero, then just pass data. - */ + Audio (data, t); +} - int const to_do = min (data->frames(), _negative_delay_remaining); - if (to_do) { - data->move (to_do, 0, data->frames() - to_do); - data->set_frames (data->frames() - to_do); - _negative_delay_remaining -= to_do; - } +void +DelayLine::process_video (boost::shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) +{ + if (_seconds < 0) { + t += _seconds; } - Audio (data); + Video (image, same, sub, t); } diff --git a/src/lib/delay_line.h b/src/lib/delay_line.h index c51784f35..90f1dcfa7 100644 --- a/src/lib/delay_line.h +++ b/src/lib/delay_line.h @@ -20,18 +20,15 @@ #include <boost/shared_ptr.hpp> #include "processor.h" -class AudioBuffers; - -/** A delay line for audio */ -class DelayLine : public AudioProcessor +/** A delay line */ +class DelayLine : public TimedAudioVideoProcessor { public: - DelayLine (boost::shared_ptr<Log> log, int channels, int frames); + DelayLine (boost::shared_ptr<Log> log, double); - void process_audio (boost::shared_ptr<AudioBuffers>); + void process_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double); + void process_audio (boost::shared_ptr<AudioBuffers>, double); private: - boost::shared_ptr<AudioBuffers> _buffers; - int _negative_delay_remaining; ///< number of frames of negative delay that remain to emit - int _frames; + double _seconds; }; diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index ac25844e3..2d7092789 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -80,10 +80,6 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<Film> f, DecodeOptions o) setup_video (); setup_audio (); setup_subtitle (); - - if (!o.video_sync) { - _first_video = 0; - } } FFmpegDecoder::~FFmpegDecoder () @@ -228,26 +224,26 @@ FFmpegDecoder::pass () av_strerror (r, buf, sizeof(buf)); _film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r)); } - + /* Get any remaining frames */ _packet.data = 0; _packet.size = 0; - + /* XXX: should we reset _packet.data and size after each *_decode_* call? */ - + int frame_finished; - + if (_opt.decode_video) { while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - filter_and_emit_video (_frame); + filter_and_emit_video (); } } - + if (_audio_stream && _opt.decode_audio) { decode_audio_packet (); } - + return true; } @@ -265,16 +261,12 @@ FFmpegDecoder::pass () _film->log()->log (String::compose (N_("Used only %1 bytes of %2 in packet"), r, _packet.size)); } - if (_opt.video_sync) { - out_with_sync (); - } else { - filter_and_emit_video (_frame); - } + filter_and_emit_video (); } } else if (ffa && _packet.stream_index == ffa->id() && _opt.decode_audio) { decode_audio_packet (); - } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles && _first_video) { + } else if (_subtitle_stream && _packet.stream_index == _subtitle_stream->id() && _opt.decode_subtitles) { int got_subtitle; AVSubtitle sub; @@ -504,29 +496,34 @@ FFmpegDecoder::set_subtitle_stream (shared_ptr<SubtitleStream> s) } void -FFmpegDecoder::filter_and_emit_video (AVFrame* frame) +FFmpegDecoder::filter_and_emit_video () { boost::mutex::scoped_lock lm (_filter_graphs_mutex); shared_ptr<FilterGraph> graph; list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin(); - while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format)) { + while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) { ++i; } if (i == _filter_graphs.end ()) { - graph.reset (new FilterGraph (_film, this, libdcp::Size (frame->width, frame->height), (AVPixelFormat) frame->format)); + graph.reset (new FilterGraph (_film, this, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)); _filter_graphs.push_back (graph); - _film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), frame->width, frame->height, frame->format)); + _film->log()->log (String::compose (N_("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); + list<shared_ptr<Image> > images = graph->process (_frame); for (list<shared_ptr<Image> >::iterator i = images.begin(); i != images.end(); ++i) { - emit_video (*i, frame_time ()); + int64_t const bet = av_frame_get_best_effort_timestamp (_frame); + if (bet != AV_NOPTS_VALUE) { + emit_video (*i, false, bet * av_q2d (_format_context->streams[_video_stream]->time_base)); + } else { + _film->log()->log ("Dropping frame without PTS"); + } } } @@ -614,52 +611,6 @@ FFmpegAudioStream::to_string () const } void -FFmpegDecoder::out_with_sync () -{ - /* Where we are in the output, in seconds */ - double const out_pts_seconds = video_frame() / frames_per_second(); - - /* Where we are in the source, in seconds */ - double const source_pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame); - - _film->log()->log ( - String::compose (N_("Source video frame ready; source at %1, output at %2"), source_pts_seconds, out_pts_seconds), - Log::VERBOSE - ); - - if (!_first_video) { - _first_video = source_pts_seconds; - } - - /* Difference between where we are and where we should be */ - double const delta = source_pts_seconds - _first_video.get() - out_pts_seconds; - double const one_frame = 1 / frames_per_second(); - - /* Insert frames if required to get out_pts_seconds up to pts_seconds */ - if (delta > one_frame) { - int const extra = rint (delta / one_frame); - for (int i = 0; i < extra; ++i) { - repeat_last_video (); - _film->log()->log ( - String::compose ( - N_("Extra video frame inserted at %1s; source frame %2, source PTS %3 (at %4 fps)"), - out_pts_seconds, video_frame(), source_pts_seconds, frames_per_second() - ) - ); - } - } - - if (delta > -one_frame) { - /* Process this frame */ - filter_and_emit_video (_frame); - } else { - /* Otherwise we are omitting a frame to keep things right */ - _film->log()->log (String::compose (N_("Frame removed at %1s"), out_pts_seconds)); - } -} - -void FFmpegDecoder::film_changed (Film::Property p) { switch (p) { @@ -684,12 +635,6 @@ FFmpegDecoder::length () const return (double(_format_context->duration) / AV_TIME_BASE) * frames_per_second(); } -double -FFmpegDecoder::frame_time () const -{ - return av_frame_get_best_effort_timestamp(_frame) * av_q2d (_format_context->streams[_video_stream]->time_base); -} - void FFmpegDecoder::decode_audio_packet () { @@ -706,53 +651,21 @@ FFmpegDecoder::decode_audio_packet () int frame_finished; int const decode_result = avcodec_decode_audio4 (_audio_codec_context, _frame, &frame_finished, ©_packet); - if (decode_result >= 0 && frame_finished) { - - /* Where we are in the source, in seconds */ - double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) - * av_frame_get_best_effort_timestamp(_frame); - - /* We only decode audio if we've had our first video packet through, and if it - was before this packet. Until then audio is thrown away. - */ + if (decode_result >= 0) { + if (frame_finished) { - if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) { - - if (!_first_audio && _opt.decode_video) { - _first_audio = source_pts_seconds; - - /* This is our first audio frame, and if we've arrived here we must have had our - first video frame. Push some silence to make up any gap between our first - video frame and our first audio. - */ - - /* frames of silence that we must push */ - int const s = rint ((_first_audio.get() - _first_video.get()) * ffa->sample_rate ()); - - _film->log()->log ( - String::compose ( - N_("First video at %1, first audio at %2, pushing %3 audio frames of silence for %4 channels (%5 bytes per sample)"), - _first_video.get(), _first_audio.get(), s, ffa->channels(), bytes_per_audio_sample() - ) - ); - - if (s) { - shared_ptr<AudioBuffers> audio (new AudioBuffers (ffa->channels(), s)); - audio->make_silent (); - Audio (audio); - } - } + /* Where we are in the source, in seconds */ + double const source_pts_seconds = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base) + * av_frame_get_best_effort_timestamp(_frame); 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()); - Audio (deinterleave_audio (_frame->data, data_size)); + Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds); } - } - - if (decode_result >= 0) { + copy_packet.data += decode_result; copy_packet.size -= decode_result; } diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h index 1bb14ce9c..2a4d40b1d 100644 --- a/src/lib/ffmpeg_decoder.h +++ b/src/lib/ffmpeg_decoder.h @@ -111,9 +111,7 @@ private: AVSampleFormat audio_sample_format () const; int bytes_per_audio_sample () const; - void out_with_sync (); - void filter_and_emit_video (AVFrame *); - double frame_time () const; + void filter_and_emit_video (); void setup_general (); void setup_video (); @@ -143,9 +141,6 @@ private: AVPacket _packet; - boost::optional<double> _first_video; - boost::optional<double> _first_audio; - std::list<boost::shared_ptr<FilterGraph> > _filter_graphs; boost::mutex _filter_graphs_mutex; }; diff --git a/src/lib/imagemagick_decoder.cc b/src/lib/imagemagick_decoder.cc index 5dc0b7b06..5ce22c296 100644 --- a/src/lib/imagemagick_decoder.cc +++ b/src/lib/imagemagick_decoder.cc @@ -77,7 +77,7 @@ ImageMagickDecoder::pass () return true; } - repeat_last_video (); + emit_video (_image, true, double (video_frame()) / frames_per_second()); return false; } @@ -100,9 +100,9 @@ ImageMagickDecoder::pass () delete magick_image; - image = image->crop (_film->crop(), true); - - emit_video (image, 0); + _image = image->crop (_film->crop(), true); + + emit_video (_image, false, double (video_frame()) / frames_per_second()); ++_iter; return false; @@ -150,3 +150,9 @@ ImageMagickDecoder::film_changed (Film::Property p) OutputChanged (); } } + +float +ImageMagickDecoder::frames_per_second () const +{ + return _film->source_frame_rate (); +} diff --git a/src/lib/imagemagick_decoder.h b/src/lib/imagemagick_decoder.h index 2f4e2c967..80a08f81f 100644 --- a/src/lib/imagemagick_decoder.h +++ b/src/lib/imagemagick_decoder.h @@ -28,10 +28,7 @@ class ImageMagickDecoder : public VideoDecoder public: ImageMagickDecoder (boost::shared_ptr<Film>, DecodeOptions); - float frames_per_second () const { - /* We don't know */ - return 0; - } + float frames_per_second () const; libdcp::Size native_size () const; @@ -82,4 +79,6 @@ private: std::list<std::string> _files; std::list<std::string>::iterator _iter; + + boost::shared_ptr<Image> _image; }; diff --git a/src/lib/job.cc b/src/lib/job.cc index ace02b8b3..1c66d87d3 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -70,11 +70,15 @@ Job::run_wrapper () set_state (FINISHED_ERROR); string m = String::compose (_("An error occurred whilst handling the file %1."), boost::filesystem::path (e.filename()).leaf()); - - boost::filesystem::space_info const s = boost::filesystem::space (e.filename()); - if (s.available < pow (1024, 3)) { - m += N_("\n\n"); - m += _("The drive that the film is stored on is low in disc space. Free some more space and try again."); + + try { + boost::filesystem::space_info const s = boost::filesystem::space (e.filename()); + if (s.available < pow (1024, 3)) { + m += N_("\n\n"); + m += _("The drive that the film is stored on is low in disc space. Free some more space and try again."); + } + } catch (...) { + } set_error (e.what(), m); diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc index 48f6ed912..34ddc86d6 100644 --- a/src/lib/matcher.cc +++ b/src/lib/matcher.cc @@ -24,35 +24,97 @@ #include "i18n.h" using std::min; +using std::cout; +using std::list; using boost::shared_ptr; Matcher::Matcher (shared_ptr<Log> log, int sample_rate, float frames_per_second) - : AudioVideoProcessor (log) + : Processor (log) , _sample_rate (sample_rate) , _frames_per_second (frames_per_second) , _video_frames (0) , _audio_frames (0) + , _had_first_video (false) + , _had_first_audio (false) { } void -Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) +Matcher::process_video (boost::shared_ptr<Image> image, bool same, boost::shared_ptr<Subtitle> sub, double t) { - Video (i, same, s); - _video_frames++; + _pixel_format = image->pixel_format (); + _size = image->size (); - _pixel_format = i->pixel_format (); - _size = i->size (); + _log->log(String::compose("Matcher video @ %1 [audio=%2, video=%3, pending_audio=%4]", t, _audio_frames, _video_frames, _pending_audio.size())); + + if (!_first_input) { + _first_input = t; + } + + bool const this_is_first_video = !_had_first_video; + _had_first_video = true; + + if (this_is_first_video && _had_first_audio) { + /* First video since we got audio */ + fix_start (t); + } + + /* Video before audio is fine, since we can make up an arbitrary difference + with audio samples (contrasting with video which is quantised to frames) + */ + + /* Difference between where this video is and where it should be */ + double const delta = t - _first_input.get() - _video_frames / _frames_per_second; + double const one_frame = 1 / _frames_per_second; + + if (delta > one_frame) { + /* Insert frames to make up the difference */ + int const extra = rint (delta / one_frame); + for (int i = 0; i < extra; ++i) { + repeat_last_video (); + _log->log (String::compose ("Extra video frame inserted at %1s", _video_frames / _frames_per_second)); + } + } + + if (delta > -one_frame) { + Video (image, same, sub); + ++_video_frames; + } else { + /* We are omitting a frame to keep things right */ + _log->log (String::compose ("Frame removed at %1s", t)); + } + + _last_image = image; + _last_subtitle = sub; } void -Matcher::process_audio (boost::shared_ptr<AudioBuffers> b) +Matcher::process_audio (boost::shared_ptr<AudioBuffers> b, double t) { - Audio (b); - _audio_frames += b->frames (); - _channels = b->channels (); + + _log->log (String::compose ("Matcher audio @ %1 [video=%2, audio=%3, pending_audio=%4]", t, _video_frames, _audio_frames, _pending_audio.size())); + + if (!_first_input) { + _first_input = t; + } + + bool const this_is_first_audio = _had_first_audio; + _had_first_audio = true; + + if (!_had_first_video) { + /* No video yet; we must postpone these data until we have some */ + _pending_audio.push_back (AudioRecord (b, t)); + } else if (this_is_first_audio && !_had_first_video) { + /* First audio since we got video */ + _pending_audio.push_back (AudioRecord (b, t)); + fix_start (_first_input.get ()); + } else { + /* Normal running. We assume audio time stamps are consecutive */ + Audio (b); + _audio_frames += b->frames (); + } } void @@ -62,39 +124,58 @@ Matcher::process_end () /* We won't do anything */ return; } + + _log->log (String::compose ("Matcher 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)); - int64_t audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames; - - _log->log ( - String::compose ( - N_("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 - ) - ); + match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second)); +} + +void +Matcher::fix_start (double first_video) +{ + assert (!_pending_audio.empty ()); + + _log->log (String::compose ("Fixing start; video at %1, audio at %2", first_video, _pending_audio.front().time)); + + match (first_video - _pending_audio.front().time); + + for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) { + process_audio (i->audio, i->time); + } - if (audio_short_by_frames < 0) { - - _log->log (String::compose (N_("%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); + _pending_audio.clear (); +} + +void +Matcher::match (double extra_video_needed) +{ + _log->log (String::compose ("Match %1", extra_video_needed)); + + if (extra_video_needed > 0) { + + /* Emit black video frames */ + int const black_video_frames = ceil (extra_video_needed * _frames_per_second); + _log->log (String::compose (N_("Emitting %1 frames of black video"), black_video_frames)); shared_ptr<Image> black (new SimpleImage (_pixel_format.get(), _size.get(), true)); black->make_black (); for (int i = 0; i < black_video_frames; ++i) { Video (black, i != 0, shared_ptr<Subtitle>()); + ++_video_frames; } - - /* Now recompute our check value */ - audio_short_by_frames = video_frames_to_audio_frames (_video_frames, _sample_rate, _frames_per_second) - _audio_frames; + + extra_video_needed -= black_video_frames / _frames_per_second; } - - if (audio_short_by_frames > 0) { - _log->log (String::compose (N_("Emitted %1 too few audio frames"), audio_short_by_frames)); + + if (extra_video_needed < 0) { + + /* Emit silence */ + + int64_t to_do = -extra_video_needed * _sample_rate; + _log->log (String::compose (N_("Emitting %1 frames of silence"), to_do)); /* Do things in half second blocks as I think there may be limits to what FFmpeg (and in particular the resampler) can cope with. @@ -103,7 +184,6 @@ Matcher::process_end () 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); @@ -113,3 +193,16 @@ Matcher::process_end () } } } + +void +Matcher::repeat_last_video () +{ + if (!_last_image) { + _last_image.reset (new SimpleImage (_pixel_format.get(), _size.get(), true)); + _last_image->make_black (); + } + + Video (_last_image, true, _last_subtitle); + ++_video_frames; +} + diff --git a/src/lib/matcher.h b/src/lib/matcher.h index b1680e131..f54aa4b6a 100644 --- a/src/lib/matcher.h +++ b/src/lib/matcher.h @@ -21,15 +21,19 @@ #include "processor.h" #include "ffmpeg_compatibility.h" -class Matcher : public AudioVideoProcessor +class Matcher : public Processor, public TimedAudioSink, public TimedVideoSink, public AudioSource, public VideoSource { public: Matcher (boost::shared_ptr<Log> log, int sample_rate, float frames_per_second); - void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s); - void process_audio (boost::shared_ptr<AudioBuffers>); + void process_video (boost::shared_ptr<Image> i, bool, boost::shared_ptr<Subtitle> s, double); + void process_audio (boost::shared_ptr<AudioBuffers>, double); void process_end (); private: + void fix_start (double); + void match (double); + void repeat_last_video (); + int _sample_rate; float _frames_per_second; int _video_frames; @@ -37,4 +41,23 @@ private: boost::optional<AVPixelFormat> _pixel_format; boost::optional<libdcp::Size> _size; boost::optional<int> _channels; + + struct AudioRecord { + AudioRecord (boost::shared_ptr<AudioBuffers> a, double t) + : audio (a) + , time (t) + {} + + boost::shared_ptr<AudioBuffers> audio; + double time; + }; + + std::list<AudioRecord> _pending_audio; + + boost::optional<double> _first_input; + boost::shared_ptr<Image> _last_image; + boost::shared_ptr<Subtitle> _last_subtitle; + + bool _had_first_video; + bool _had_first_audio; }; diff --git a/src/lib/processor.h b/src/lib/processor.h index 1ba396f2f..603239f8f 100644 --- a/src/lib/processor.h +++ b/src/lib/processor.h @@ -67,6 +67,15 @@ public: {} }; +class TimedAudioVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink, public TimedAudioSource, public TimedAudioSink +{ +public: + TimedAudioVideoProcessor (boost::shared_ptr<Log> log) + : Processor (log) + {} +}; + + /** @class AudioProcessor * @brief A processor which handles just audio data. */ @@ -95,4 +104,12 @@ public: {} }; +class TimedVideoProcessor : public Processor, public TimedVideoSource, public TimedVideoSink +{ +public: + TimedVideoProcessor (boost::shared_ptr<Log> log) + : Processor (log) + {} +}; + #endif diff --git a/src/lib/sndfile_decoder.cc b/src/lib/sndfile_decoder.cc index 0e3e5e234..af59c049c 100644 --- a/src/lib/sndfile_decoder.cc +++ b/src/lib/sndfile_decoder.cc @@ -113,8 +113,8 @@ SndfileDecoder::pass () 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)); + sf_count_t done = 0; while (frames > 0) { sf_count_t const this_time = min (block, frames); for (size_t i = 0; i < sndfiles.size(); ++i) { @@ -126,7 +126,8 @@ SndfileDecoder::pass () } audio->set_frames (this_time); - Audio (audio); + Audio (audio, double(done) / _audio_stream->sample_rate()); + done += this_time; frames -= this_time; } diff --git a/src/lib/transcoder.cc b/src/lib/transcoder.cc index e0f3a03a2..23fb5b788 100644 --- a/src/lib/transcoder.cc +++ b/src/lib/transcoder.cc @@ -54,32 +54,23 @@ Transcoder::Transcoder (shared_ptr<Film> f, DecodeOptions o, Job* j, shared_ptr< { assert (_encoder); - if (f->audio_stream()) { - shared_ptr<AudioStream> st = f->audio_stream(); - _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate())); - _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())); - } + shared_ptr<AudioStream> st = f->audio_stream(); + _matcher.reset (new Matcher (f->log(), st->sample_rate(), f->source_frame_rate())); + _delay_line.reset (new DelayLine (f->log(), f->audio_delay() / 1000.0f)); + _gain.reset (new Gain (f->log(), f->audio_gain())); /* Set up the decoder to use the film's set streams */ _decoders.video->set_subtitle_stream (f->subtitle_stream ()); - if (_decoders.audio) { - _decoders.audio->set_audio_stream (f->audio_stream ()); - } + _decoders.audio->set_audio_stream (f->audio_stream ()); - if (_matcher) { - _decoders.video->connect_video (_matcher); - _matcher->connect_video (_encoder); - } else { - _decoders.video->connect_video (_encoder); - } + _decoders.video->connect_video (_delay_line); + _delay_line->connect_video (_matcher); + _matcher->connect_video (_encoder); - if (_matcher && _delay_line && _decoders.audio) { - _decoders.audio->connect_audio (_delay_line); - _delay_line->connect_audio (_matcher); - _matcher->connect_audio (_gain); - _gain->connect_audio (_encoder); - } + _decoders.audio->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 @@ -116,14 +107,8 @@ Transcoder::go () throw; } - if (_delay_line) { - _delay_line->process_end (); - } - if (_matcher) { - _matcher->process_end (); - } - if (_gain) { - _gain->process_end (); - } + _delay_line->process_end (); + _matcher->process_end (); + _gain->process_end (); _encoder->process_end (); } diff --git a/src/lib/video_decoder.cc b/src/lib/video_decoder.cc index 891720f6b..16a076698 100644 --- a/src/lib/video_decoder.cc +++ b/src/lib/video_decoder.cc @@ -27,6 +27,7 @@ #include "i18n.h" +using std::cout; using boost::shared_ptr; using boost::optional; @@ -44,46 +45,17 @@ VideoDecoder::VideoDecoder (shared_ptr<Film> f, DecodeOptions o) * @param t Time of the frame within the source, in seconds. */ void -VideoDecoder::emit_video (shared_ptr<Image> image, double t) +VideoDecoder::emit_video (shared_ptr<Image> image, bool same, double t) { shared_ptr<Subtitle> sub; if (_timed_subtitle && _timed_subtitle->displayed_at (t)) { sub = _timed_subtitle->subtitle (); } - signal_video (image, false, sub); - _last_source_time = t; -} - -/** Called by subclasses to repeat the last video frame that we - * passed to emit_video(). If emit_video hasn't yet been called, - * we will generate a black frame. - */ -void -VideoDecoder::repeat_last_video () -{ - if (!_last_image) { - _last_image.reset (new SimpleImage (pixel_format(), native_size(), true)); - _last_image->make_black (); - } - - signal_video (_last_image, true, _last_subtitle); -} - -/** Emit our signal to say that some video data is ready. - * @param image Video frame. - * @param same true if `image' is the same as the last one we emitted. - * @param sub Subtitle for this frame, or 0. - */ -void -VideoDecoder::signal_video (shared_ptr<Image> image, bool same, shared_ptr<Subtitle> sub) -{ - TIMING (N_("Decoder emits %1"), _video_frame); - Video (image, same, sub); + Video (image, same, sub, t); ++_video_frame; - - _last_image = image; - _last_subtitle = sub; + + _last_source_time = t; } /** Set up the current subtitle. This will be put onto frames that diff --git a/src/lib/video_decoder.h b/src/lib/video_decoder.h index 283ab5d88..6e4fd48c0 100644 --- a/src/lib/video_decoder.h +++ b/src/lib/video_decoder.h @@ -24,7 +24,7 @@ #include "stream.h" #include "decoder.h" -class VideoDecoder : public VideoSource, public virtual Decoder +class VideoDecoder : public TimedVideoSource, public virtual Decoder { public: VideoDecoder (boost::shared_ptr<Film>, DecodeOptions); @@ -65,9 +65,8 @@ protected: virtual PixelFormat pixel_format () const = 0; - void emit_video (boost::shared_ptr<Image>, double); + void emit_video (boost::shared_ptr<Image>, bool, double); void emit_subtitle (boost::shared_ptr<TimedSubtitle>); - void repeat_last_video (); /** Subtitle stream to use when decoding */ boost::shared_ptr<SubtitleStream> _subtitle_stream; @@ -75,15 +74,10 @@ protected: std::vector<boost::shared_ptr<SubtitleStream> > _subtitle_streams; private: - void signal_video (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>); - int _video_frame; double _last_source_time; 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 index 7c128cf73..32c7f3b38 100644 --- a/src/lib/video_sink.h +++ b/src/lib/video_sink.h @@ -37,4 +37,16 @@ public: virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s) = 0; }; +class TimedVideoSink +{ +public: + /** Call with a frame of video. + * @param i Video frame image. + * @param same true if i is the same as last time we were called. + * @param s A subtitle that should be on this frame, or 0. + * @param t Source timestamp. + */ + virtual void process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double t) = 0; +}; + #endif diff --git a/src/lib/video_source.cc b/src/lib/video_source.cc index 56742e2b4..af6f941fd 100644 --- a/src/lib/video_source.cc +++ b/src/lib/video_source.cc @@ -28,3 +28,9 @@ VideoSource::connect_video (shared_ptr<VideoSink> s) { Video.connect (bind (&VideoSink::process_video, s, _1, _2, _3)); } + +void +TimedVideoSource::connect_video (shared_ptr<TimedVideoSink> s) +{ + Video.connect (bind (&TimedVideoSink::process_video, s, _1, _2, _3, _4)); +} diff --git a/src/lib/video_source.h b/src/lib/video_source.h index 893629160..705b0023a 100644 --- a/src/lib/video_source.h +++ b/src/lib/video_source.h @@ -29,11 +29,12 @@ #include "util.h" class VideoSink; +class TimedVideoSink; class Subtitle; class Image; -/** @class VideoSink - * @param A class that emits video data. +/** @class VideoSource + * @param A class that emits video data without timestamps. */ class VideoSource { @@ -49,4 +50,22 @@ public: void connect_video (boost::shared_ptr<VideoSink>); }; +/** @class TimedVideoSource + * @param A class that emits video data with timestamps. + */ +class TimedVideoSource +{ +public: + + /** Emitted when a video frame is ready. + * First parameter is the video image. + * Second parameter is true if the image is the same as the last one that was emitted. + * Third parameter is either 0 or a subtitle that should be on this frame. + * Fourth parameter is the source timestamp of this frame. + */ + boost::signals2::signal<void (boost::shared_ptr<Image>, bool, boost::shared_ptr<Subtitle>, double)> Video; + + void connect_video (boost::shared_ptr<TimedVideoSink>); +}; + #endif |
