setup_video ();
setup_audio ();
setup_subtitle ();
-
- if (!o.video_sync) {
- _first_video = 0;
- }
}
FFmpegDecoder::~FFmpegDecoder ()
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 ();
}
}
_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;
}
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 ());
+ emit_video (*i, av_frame_get_best_effort_timestamp (_frame) * av_q2d (_format_context->streams[_video_stream]->time_base));
}
}
return String::compose (N_("ffmpeg %1 %2 %3 %4"), _id, _sample_rate, _channel_layout, _name);
}
-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) {
- /* XXX: timestamp is wrong */
- repeat_last_video (source_pts_seconds);
- _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)
{
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 ()
{
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 ();
- /* XXX: this time stamp is wrong */
- Audio (audio, source_pts_seconds);
- }
- }
+ /* 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), source_pts_seconds );
+ Audio (deinterleave_audio (_frame->data, data_size), source_pts_seconds);
}
- }
-
- if (decode_result >= 0) {
+
copy_packet.data += decode_result;
copy_packet.size -= decode_result;
}
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 ();
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;
};
using boost::shared_ptr;
using libdcp::Size;
+/* XXX: reads a directory and then ignores it */
+
ImageMagickDecoder::ImageMagickDecoder (
boost::shared_ptr<Film> f, DecodeOptions o)
: Decoder (f, o)
return true;
}
- /* XXX: timestamp is wrong */
- repeat_last_video (0);
+ /* XXX: timestamp */
+ emit_video (_image, 0);
return false;
}
delete magick_image;
- image = image->crop (_film->crop(), true);
-
- emit_video (image, 0);
+ _image = image->crop (_film->crop(), true);
+
+ /* XXX: timestamp */
+ emit_video (_image, 0);
++_iter;
return false;
bool
ImageMagickDecoder::seek (double t)
{
+ /* XXX: frames_per_second == 0 */
int const f = t * frames_per_second();
_iter = _files.begin ();
std::list<std::string> _files;
std::list<std::string>::iterator _iter;
+
+ boost::shared_ptr<Image> _image;
+
};
#include "i18n.h"
using std::min;
+using std::cout;
+using std::list;
using boost::shared_ptr;
Matcher::Matcher (Log* log, int sample_rate, float frames_per_second)
}
void
-Matcher::process_video (boost::shared_ptr<Image> i, bool same, boost::shared_ptr<Subtitle> s, double)
+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 ();
+ if (!_first_input) {
+ _first_input = t;
+ }
+
+ if (_audio_frames == 0 && _pending_audio.empty ()) {
+ /* No audio yet; we must postpone this frame until we have some */
+ _pending_video.push_back (VideoRecord (image, same, sub, t));
+ } else if (!_pending_audio.empty() && _pending_video.empty ()) {
+ /* 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", t));
+ }
+ }
+
+ _last_image = image;
+ _last_subtitle = sub;
}
void
-Matcher::process_audio (boost::shared_ptr<AudioBuffers> b, double)
+Matcher::process_audio (boost::shared_ptr<AudioBuffers> b, double t)
{
- Audio (b);
- _audio_frames += b->frames ();
-
_channels = b->channels ();
+
+ if (!_first_input) {
+ _first_input = t;
+ }
+
+ if (_video_frames == 0 && _pending_video.empty ()) {
+ /* No video yet; we must postpone these data until we have some */
+ _pending_audio.push_back (AudioRecord (b, t));
+ } else if (!_pending_video.empty() && _pending_audio.empty ()) {
+ /* 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 */
+ Audio (b);
+ _audio_frames += b->frames ();
+ }
}
void
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 (
- 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 ()
+{
+ 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) {
+ Video (i->image, i->same, i->subtitle);
+ }
+
+ for (list<AudioRecord>::iterator i = _pending_audio.begin(); i != _pending_audio.end(); ++i) {
+ Audio (i->audio);
+ }
- 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_video.clear ();
+ _pending_audio.clear ();
+}
+
+void
+Matcher::match (double extra_video_needed)
+{
+ if (extra_video_needed) {
+
+ /* 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 = rint (-extra_video_needed * _sample_rate);
+ _log->log (String::compose (N_("Emitted %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.
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);
}
}
}
+
+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;
+}
+
void process_end ();
private:
+ void fix_start ();
+ void match (double);
+ void repeat_last_video ();
+
int _sample_rate;
float _frames_per_second;
int _video_frames;
boost::optional<AVPixelFormat> _pixel_format;
boost::optional<libdcp::Size> _size;
boost::optional<int> _channels;
+
+ struct VideoRecord {
+ VideoRecord (boost::shared_ptr<Image> i, bool s, boost::shared_ptr<Subtitle> sub, double t)
+ : image (i)
+ , same (s)
+ , subtitle (sub)
+ , time (t)
+ {}
+
+ boost::shared_ptr<Image> image;
+ bool same;
+ boost::shared_ptr<Subtitle> subtitle;
+ double time;
+ };
+
+ std::list<VideoRecord> _pending_video;
+
+ 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;
};
#include "i18n.h"
+using std::cout;
using boost::shared_ptr;
using boost::optional;
_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 (double t)
-{
- if (!_last_image) {
- _last_image.reset (new SimpleImage (pixel_format(), native_size(), true));
- _last_image->make_black ();
- }
-
- signal_video (_last_image, true, _last_subtitle, t);
-}
-
/** 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.
TIMING (N_("Decoder emits %1"), _video_frame);
Video (image, same, sub, t);
++_video_frame;
-
- _last_image = image;
- _last_subtitle = sub;
}
/** Set up the current subtitle. This will be put onto frames that
void emit_video (boost::shared_ptr<Image>, double);
void emit_subtitle (boost::shared_ptr<TimedSubtitle>);
- void repeat_last_video (double t);
/** Subtitle stream to use when decoding */
boost::shared_ptr<SubtitleStream> _subtitle_stream;
double _last_source_time;
boost::shared_ptr<TimedSubtitle> _timed_subtitle;
-
- boost::shared_ptr<Image> _last_image;
- boost::shared_ptr<Subtitle> _last_subtitle;
};
#endif