X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Flib%2Fffmpeg_decoder.cc;h=30701797affc9f3f74837378b79735d19b66e08a;hb=bd8fa9a370f1739952c83107352baa08c79d095e;hp=cf17bbfb7e83932c363ca9e217ba3c62e55dfa1a;hpb=76740ecc18a5e896c7d05f3d71f865105c3248c7;p=dcpomatic.git diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index cf17bbfb7..30701797a 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -27,6 +27,7 @@ #include #include #include +#include extern "C" { #include #include @@ -40,19 +41,19 @@ extern "C" { #include "transcoder.h" #include "job.h" #include "filter.h" -#include "film_state.h" #include "options.h" #include "exceptions.h" #include "image.h" #include "util.h" #include "log.h" #include "ffmpeg_decoder.h" +#include "subtitle.h" using namespace std; using namespace boost; -FFmpegDecoder::FFmpegDecoder (boost::shared_ptr s, boost::shared_ptr o, Job* j, Log* l, bool minimal, bool ignore_length) - : Decoder (s, o, j, l, minimal, ignore_length) +FFmpegDecoder::FFmpegDecoder (boost::shared_ptr f, boost::shared_ptr o, Job* j, bool minimal, bool ignore_length) + : Decoder (f, o, j, minimal, ignore_length) , _format_context (0) , _video_stream (-1) , _audio_stream (-1) @@ -64,7 +65,6 @@ FFmpegDecoder::FFmpegDecoder (boost::shared_ptr s, boost::share , _audio_codec (0) , _subtitle_codec_context (0) , _subtitle_codec (0) - , _have_subtitle (false) { setup_general (); setup_video (); @@ -82,10 +82,6 @@ FFmpegDecoder::~FFmpegDecoder () avcodec_close (_video_codec_context); } - if (_have_subtitle) { - avsubtitle_free (&_subtitle); - } - if (_subtitle_codec_context) { avcodec_close (_subtitle_codec_context); } @@ -101,24 +97,43 @@ FFmpegDecoder::setup_general () av_register_all (); - if ((r = avformat_open_input (&_format_context, _fs->content_path().c_str(), 0, 0)) != 0) { - throw OpenFileError (_fs->content_path ()); + if ((r = avformat_open_input (&_format_context, _film->content_path().c_str(), 0, 0)) != 0) { + throw OpenFileError (_film->content_path ()); } if (avformat_find_stream_info (_format_context, 0) < 0) { throw DecodeError ("could not find stream information"); } + /* Find video, audio and subtitle streams and choose the first of each */ + for (uint32_t i = 0; i < _format_context->nb_streams; ++i) { - if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + AVStream* s = _format_context->streams[i]; + if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) { _video_stream = i; - } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - _audio_stream = i; - } else if (_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { - _subtitle_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)); + } 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)); } } + /* 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"); } @@ -195,6 +210,7 @@ bool FFmpegDecoder::do_pass () { int r = av_read_frame (_format_context, &_packet); + if (r < 0) { if (r != AVERROR_EOF) { throw DecodeError ("error on av_read_frame"); @@ -205,12 +221,12 @@ FFmpegDecoder::do_pass () _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) { - process_video (_frame); - } + while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { + process_video (_frame); } if (_audio_stream >= 0 && _opt->decode_audio) { @@ -218,8 +234,8 @@ FFmpegDecoder::do_pass () 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 == _fs->audio_channels); + + assert (_audio_codec_context->channels == _film->audio_channels()); process_audio (_frame->data[0], data_size); } } @@ -227,15 +243,53 @@ FFmpegDecoder::do_pass () return true; } - if (_packet.stream_index == _video_stream && _opt->decode_video) { + double const pts_seconds = av_q2d (_format_context->streams[_packet.stream_index]->time_base) * _packet.pts; + + if (_packet.stream_index == _video_stream) { + if (!_first_video) { + _first_video = pts_seconds; + } + int frame_finished; if (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) { - maybe_add_subtitle (); process_video (_frame); } - } else if (_audio_stream >= 0 && _packet.stream_index == _audio_stream && _opt->decode_audio) { + } else if (_audio_stream >= 0 && _packet.stream_index == _audio_stream && _opt->decode_audio && _first_video && _first_video.get() <= pts_seconds) { + + /* Note: 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 (!_first_audio) { + _first_audio = pts_seconds; + + /* This is our first audio packet, and if we've arrived here we must have had our + first video packet. Push some silence to make up the gap between our first + video packet and our first audio. + */ + + /* frames of silence that we must push */ + int const s = rint ((_first_audio.get() - _first_video.get()) * audio_sample_rate ()); + + _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() + ) + ); + + /* 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); + } avcodec_get_frame_defaults (_frame); @@ -244,21 +298,25 @@ FFmpegDecoder::do_pass () 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 == _fs->audio_channels); + + assert (_audio_codec_context->channels == _film->audio_channels()); process_audio (_frame->data[0], data_size); } - - } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream) { - - if (_have_subtitle) { - avsubtitle_free (&_subtitle); - _have_subtitle = false; - } + + } else if (_subtitle_stream >= 0 && _packet.stream_index == _subtitle_stream && _opt->decode_subtitles && _first_video) { int got_subtitle; - if (avcodec_decode_subtitle2 (_subtitle_codec_context, &_subtitle, &got_subtitle, &_packet) && got_subtitle) { - _have_subtitle = true; + AVSubtitle sub; + if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) && got_subtitle) { + /* Sometimes we get an empty AVSubtitle, which is used by some codecs to + indicate that the previous subtitle should stop. + */ + if (sub.num_rects > 0) { + process_subtitle (shared_ptr (new TimedSubtitle (sub, _first_video.get()))); + } else { + process_subtitle (shared_ptr ()); + } + avsubtitle_free (&sub); } } @@ -266,16 +324,16 @@ FFmpegDecoder::do_pass () return false; } -int -FFmpegDecoder::length_in_frames () const -{ - return (_format_context->duration / AV_TIME_BASE) * frames_per_second (); -} - float FFmpegDecoder::frames_per_second () const { - return av_q2d (_format_context->streams[_video_stream]->avg_frame_rate); + 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 av_q2d (s->r_frame_rate); } int @@ -354,95 +412,46 @@ FFmpegDecoder::sample_aspect_ratio_denominator () const return _video_codec_context->sample_aspect_ratio.den; } -void -FFmpegDecoder::maybe_add_subtitle () +bool +FFmpegDecoder::has_subtitles () const { - if (!_have_subtitle) { - return; - } - - /* subtitle PTS in seconds */ - float const packet_time = (_subtitle.pts / AV_TIME_BASE) + float (_subtitle.pts % AV_TIME_BASE) / 1e6; - /* hence start time for this sub */ - float const from = packet_time + (float (_subtitle.start_display_time) / 1e3); - float const to = packet_time + (float (_subtitle.end_display_time) / 1e3); - - float const video_frame_time = float (last_video_frame ()) / rint (_fs->frames_per_second); + return (_subtitle_stream != -1); +} + +vector +FFmpegDecoder::audio_streams () const +{ + return _audio_streams; +} + +vector +FFmpegDecoder::subtitle_streams () const +{ + return _subtitle_streams; +} + +string +FFmpegDecoder::stream_name (AVStream* s) const +{ + stringstream n; - if (from < video_frame_time || video_frame_time > to) { - return; + AVDictionaryEntry const * lang = av_dict_get (s->metadata, "language", 0, 0); + if (lang) { + n << lang->value; } - for (unsigned int i = 0; i < _subtitle.num_rects; ++i) { - AVSubtitleRect* rect = _subtitle.rects[i]; - if (rect->type != SUBTITLE_BITMAP) { - throw DecodeError ("non-bitmap subtitles not yet supported"); + AVDictionaryEntry const * title = av_dict_get (s->metadata, "title", 0, 0); + if (title) { + if (!n.str().empty()) { + n << " "; } - - /* XXX: all this assumes YUV420 in _frame */ - - assert (rect->pict.data[0]); + n << title->value; + } - /* Start of the first line in the target frame */ - uint8_t* frame_y_p = _frame->data[0] + rect->y * _frame->linesize[0]; - uint8_t* frame_u_p = _frame->data[1] + (rect->y / 2) * _frame->linesize[1]; - uint8_t* frame_v_p = _frame->data[2] + (rect->y / 2) * _frame->linesize[2]; - - /* Start of the first line in the subtitle */ - uint8_t* sub_p = rect->pict.data[0]; - /* sub_p looks up into a RGB palette which is here */ - uint32_t const * palette = (uint32_t *) rect->pict.data[1]; - - for (int sub_y = 0; sub_y < rect->h; ++sub_y) { - /* Pointers to the start of this line */ - uint8_t* sub_line_p = sub_p; - uint8_t* frame_line_y_p = frame_y_p + rect->x; - uint8_t* frame_line_u_p = frame_u_p + (rect->x / 2); - uint8_t* frame_line_v_p = frame_v_p + (rect->x / 2); - - /* U and V are subsampled */ - uint8_t current_u = 0; - uint8_t current_v = 0; - int subsample_step = 0; - - for (int sub_x = 0; sub_x < rect->w; ++sub_x) { - - /* RGB value for this subtitle pixel */ - uint32_t const val = palette[*sub_line_p++]; - - int const red = (val & 0xff); - int const green = (val & 0xff00) >> 8; - int const blue = (val & 0xff0000) >> 16; - float const alpha = ((val & 0xff000000) >> 24) / 255.0; - - /* Alpha-blend Y */ - int const cy = *frame_line_y_p; - *frame_line_y_p++ = int (cy * (1 - alpha)) + int (RGB_TO_Y_CCIR (red, green, blue) * alpha); - - /* Store up U and V */ - current_u |= ((RGB_TO_U_CCIR (red, green, blue, 0) & 0xf0) >> 4) << (4 * subsample_step); - current_v |= ((RGB_TO_V_CCIR (red, green, blue, 0) & 0xf0) >> 4) << (4 * subsample_step); - - if (subsample_step == 1 && (sub_y % 2) == 0) { - /* We have complete U and V bytes, so alpha-blend them into the frame */ - int const cu = *frame_line_u_p; - int const cv = *frame_line_v_p; - *frame_line_u_p++ = int (cu * (1 - alpha)) + int (current_u * alpha); - *frame_line_v_p++ = int (cv * (1 - alpha)) + int (current_v * alpha); - current_u = current_v = 0; - } - - subsample_step = (subsample_step + 1) % 2; - } - - sub_p += rect->pict.linesize[0]; - frame_y_p += _frame->linesize[0]; - if ((sub_y % 2) == 0) { - frame_u_p += _frame->linesize[1]; - frame_v_p += _frame->linesize[2]; - } - } + if (n.str().empty()) { + n << "unknown"; } + + return n.str (); } -