diff --git a/NEWS b/NEWS index 5a1ea8edf..0882a81a1 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,12 @@ ver 0.20 (not yet released) * update - apply .mpdignore matches to subdirectories +ver 0.19.18 (not yet released) +* decoder + - ffmpeg: fix crash with older FFmpeg versions (< 3.0) + - ffmpeg: log detailed error message + - ffmpeg: support FFmpeg 3.1 + ver 0.19.17 (2016/07/09) * decoder - flac: fix assertion failure while seeking diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 5d7767500..5a5b8a190 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -78,13 +78,53 @@ ffmpeg_init(gcc_unused const ConfigBlock &block) return true; } +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0) + +gcc_pure +static const AVCodecParameters & +GetCodecParameters(const AVStream &stream) +{ + return *stream.codecpar; +} + +gcc_pure +static AVSampleFormat +GetSampleFormat(const AVCodecParameters &codec_params) +{ + return AVSampleFormat(codec_params.format); +} + +#else + +gcc_pure +static const AVCodecContext & +GetCodecParameters(const AVStream &stream) +{ + return *stream.codec; +} + +gcc_pure +static AVSampleFormat +GetSampleFormat(const AVCodecContext &codec_context) +{ + return codec_context.sample_fmt; +} + +#endif + +gcc_pure +static bool +IsAudio(const AVStream &stream) +{ + return GetCodecParameters(stream).codec_type == AVMEDIA_TYPE_AUDIO; +} + gcc_pure static int ffmpeg_find_audio_stream(const AVFormatContext &format_context) { for (unsigned i = 0; i < format_context.nb_streams; ++i) - if (format_context.streams[i]->codec->codec_type == - AVMEDIA_TYPE_AUDIO) + if (IsAudio(*format_context.streams[i])) return i; return -1; @@ -175,6 +215,43 @@ PtsToPcmFrame(uint64_t pts, const AVStream &stream, return av_rescale_q(pts, stream.time_base, codec_context.time_base); } +/** + * Invoke decoder_data() with the contents of an #AVFrame. + */ +static DecoderCommand +FfmpegSendFrame(Decoder &decoder, InputStream &is, + AVCodecContext &codec_context, + const AVFrame &frame, + size_t &skip_bytes, + FfmpegBuffer &buffer) +{ + Error error; + auto output_buffer = + copy_interleave_frame(codec_context, frame, + buffer, error); + if (output_buffer.IsNull()) { + /* this must be a serious error, e.g. OOM */ + LogError(error); + return DecoderCommand::STOP; + } + + if (skip_bytes > 0) { + if (skip_bytes >= output_buffer.size) { + skip_bytes -= output_buffer.size; + return DecoderCommand::NONE; + } + + output_buffer.data = + (const uint8_t *)output_buffer.data + skip_bytes; + output_buffer.size -= skip_bytes; + skip_bytes = 0; + } + + return decoder_data(decoder, is, + output_buffer.data, output_buffer.size, + codec_context.bit_rate / 1000); +} + /** * Decode an #AVPacket and send the resulting PCM data to the decoder * API. @@ -207,8 +284,6 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, stream.time_base)); } - Error error; - DecoderCommand cmd = DecoderCommand::NONE; while (packet.size > 0 && cmd == DecoderCommand::NONE) { int got_frame = 0; @@ -227,35 +302,32 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, if (!got_frame || frame.nb_samples <= 0) continue; - auto output_buffer = - copy_interleave_frame(codec_context, frame, - buffer, error); - if (output_buffer.IsNull()) { - /* this must be a serious error, - e.g. OOM */ - LogError(error); - return DecoderCommand::STOP; - } - - if (skip_bytes > 0) { - if (skip_bytes >= output_buffer.size) { - skip_bytes -= output_buffer.size; - continue; - } - - output_buffer.data = - (const uint8_t *)output_buffer.data + skip_bytes; - output_buffer.size -= skip_bytes; - skip_bytes = 0; - } - - cmd = decoder_data(decoder, is, - output_buffer.data, output_buffer.size, - codec_context.bit_rate / 1000); + cmd = FfmpegSendFrame(decoder, is, codec_context, + frame, skip_bytes, + buffer); } + return cmd; } +static DecoderCommand +ffmpeg_send_packet(Decoder &decoder, InputStream &is, + const AVPacket &packet, + AVCodecContext &codec_context, + const AVStream &stream, + AVFrame &frame, + uint64_t min_frame, size_t pcm_frame_size, + FfmpegBuffer &buffer) +{ + return ffmpeg_send_packet(decoder, is, + /* copy the AVPacket, because FFmpeg + < 3.0 requires this */ + AVPacket(packet), + codec_context, stream, + frame, min_frame, pcm_frame_size, + buffer); +} + gcc_const static SampleFormat ffmpeg_sample_format(enum AVSampleFormat sample_fmt) @@ -457,21 +529,22 @@ FfmpegDecode(Decoder &decoder, InputStream &input, AVStream &av_stream = *format_context.streams[audio_stream]; - AVCodecContext &codec_context = *av_stream.codec; + AVCodecContext *codec_context = av_stream.codec; + const auto &codec_params = GetCodecParameters(av_stream); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) const AVCodecDescriptor *codec_descriptor = - avcodec_descriptor_get(codec_context.codec_id); + avcodec_descriptor_get(codec_params.codec_id); if (codec_descriptor != nullptr) FormatDebug(ffmpeg_domain, "codec '%s'", codec_descriptor->name); #else - if (codec_context.codec_name[0] != 0) + if (codec_context->codec_name[0] != 0) FormatDebug(ffmpeg_domain, "codec '%s'", - codec_context.codec_name); + codec_context->codec_name); #endif - AVCodec *codec = avcodec_find_decoder(codec_context.codec_id); + AVCodec *codec = avcodec_find_decoder(codec_params.codec_id); if (!codec) { LogError(ffmpeg_domain, "Unsupported audio codec"); @@ -479,7 +552,7 @@ FfmpegDecode(Decoder &decoder, InputStream &input, } const SampleFormat sample_format = - ffmpeg_sample_format(codec_context.sample_fmt); + ffmpeg_sample_format(GetSampleFormat(codec_params)); if (sample_format == SampleFormat::UNDEFINED) { // (error message already done by ffmpeg_sample_format()) return; @@ -488,9 +561,9 @@ FfmpegDecode(Decoder &decoder, InputStream &input, Error error; AudioFormat audio_format; if (!audio_format_init_checked(audio_format, - codec_context.sample_rate, + codec_params.sample_rate, sample_format, - codec_context.channels, error)) { + codec_params.channels, error)) { LogError(error); return; } @@ -500,7 +573,7 @@ FfmpegDecode(Decoder &decoder, InputStream &input, values into AVCodecContext.channels - a change that will be reverted later by avcodec_decode_audio3() */ - const int open_result = avcodec_open2(&codec_context, codec, nullptr); + const int open_result = avcodec_open2(codec_context, codec, nullptr); if (open_result < 0) { LogError(ffmpeg_domain, "Could not open codec"); return; @@ -543,7 +616,7 @@ FfmpegDecode(Decoder &decoder, InputStream &input, AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0) decoder_seek_error(decoder); else { - avcodec_flush_buffers(&codec_context); + avcodec_flush_buffers(codec_context); min_frame = decoder_seek_where_frame(decoder); decoder_command_finished(decoder); } @@ -560,8 +633,8 @@ FfmpegDecode(Decoder &decoder, InputStream &input, if (packet.stream_index == audio_stream) { cmd = ffmpeg_send_packet(decoder, input, - std::move(packet), - codec_context, + packet, + *codec_context, av_stream, *frame, min_frame, audio_format.GetFrameSize(), @@ -585,7 +658,7 @@ FfmpegDecode(Decoder &decoder, InputStream &input, av_free(frame); #endif - avcodec_close(&codec_context); + avcodec_close(codec_context); } static void @@ -612,6 +685,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) } FfmpegDecode(decoder, input, *format_context); + avformat_close_input(&format_context); }