/* Copyright (C) 2012 Carl Hetherington This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "matcher.h" #include "image.h" #include "log.h" #include "i18n.h" using std::min; using std::cout; using std::list; using boost::shared_ptr; Matcher::Matcher (shared_ptr log, int sample_rate, float frames_per_second) : 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, bool same, boost::shared_ptr sub, double t) { _pixel_format = image->pixel_format (); _size = image->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 || t < _first_input.get()) { _first_input = t; } bool const this_is_first_video = !_had_first_video; _had_first_video = true; if (!_had_first_audio) { /* No audio yet; we must postpone these data until we have some */ _pending_video.push_back (VideoRecord (image, same, sub, t)); } else if (this_is_first_video && _had_first_audio) { /* First video since we got audio */ _pending_video.push_back (VideoRecord (image, same, sub, t)); fix_start (); } else { /* Normal running */ /* 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; delta %2; first input was at %3", t, delta, _first_input.get())); } _last_image = image; _last_subtitle = sub; } } void Matcher::process_audio (boost::shared_ptr b, double t) { _channels = b->channels (); _log->log (String::compose ( "Matcher audio (%1 frames) @ %2 [video=%3, audio=%4, pending_video=%5, pending_audio=%6]", b->frames(), t, _video_frames, _audio_frames, _pending_video.size(), _pending_audio.size() ) ); if (!_first_input || t < _first_input.get()) { _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 (); } else { /* Normal running. We assume audio time stamps are consecutive, so there's no equivalent of the checking / insertion of repeat frames that there is for video. */ Audio (b); _audio_frames += b->frames (); } } void Matcher::process_end () { if (_audio_frames == 0 || !_pixel_format || !_size || !_channels) { /* 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)); match ((double (_audio_frames) / _sample_rate) - (double (_video_frames) / _frames_per_second)); } void Matcher::fix_start () { assert (!_pending_video.empty ()); assert (!_pending_audio.empty ()); _log->log (String::compose ("Fixing start; video at %1, audio at %2", _pending_video.front().time, _pending_audio.front().time)); match (_pending_video.front().time - _pending_audio.front().time); for (list::iterator i = _pending_video.begin(); i != _pending_video.end(); ++i) { process_video (i->image, i->same, i->subtitle, i->time); } _pending_video.clear (); for (list::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) { process_audio (i->audio, i->time); } _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 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()); ++_video_frames; } extra_video_needed -= black_video_frames / _frames_per_second; } 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. */ int64_t const block = _sample_rate / 2; shared_ptr b (new AudioBuffers (_channels.get(), block)); b->make_silent (); 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; } } } void Matcher::repeat_last_video () { if (!_last_image) { shared_ptr im (new SimpleImage (_pixel_format.get(), _size.get(), true)); im->make_black (); _last_image = im; } Video (_last_image, true, _last_subtitle); ++_video_frames; }