diff options
Diffstat (limited to 'src/lib/matcher.cc')
| -rw-r--r-- | src/lib/matcher.cc | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/src/lib/matcher.cc b/src/lib/matcher.cc new file mode 100644 index 000000000..4acb82afa --- /dev/null +++ b/src/lib/matcher.cc @@ -0,0 +1,224 @@ +/* + 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" + +#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) + : 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<const Image> image, bool same, boost::shared_ptr<Subtitle> 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<const AudioBuffers> 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<VideoRecord>::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<AudioRecord>::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<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; + } + + 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<AudioBuffers> 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<Image> im (new SimpleImage (_pixel_format.get(), _size.get(), true)); + im->make_black (); + _last_image = im; + } + + Video (_last_image, true, _last_subtitle); + ++_video_frames; +} + |
