From 4fed0b991c039f7c296445ca2207b779fdb736ce Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 15:07:15 +0200 Subject: [PATCH 01/32] configure.ac: prepare for 0.19.18 --- NEWS | 2 ++ configure.ac | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index df7493f80..a9e424aca 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +ver 0.19.18 (not yet released) + ver 0.19.17 (2016/07/09) * decoder - flac: fix assertion failure while seeking diff --git a/configure.ac b/configure.ac index b5d59c216..ccb49ddfd 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,10 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.19.17, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.19.18, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 VERSION_MINOR=19 -VERSION_REVISION=17 +VERSION_REVISION=18 VERSION_EXTRA=0 AC_CONFIG_SRCDIR([src/Main.cxx]) From 923c402f6934b6f135d5c6e27a587055eb799d90 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 10 Dec 2014 09:35:28 +0100 Subject: [PATCH 02/32] decoder/ffmpeg: optimize ffmpeg_scan_dictionary() Don't scan tag items if the handler doesn't implement the tag() method. --- src/decoder/plugins/FfmpegMetaData.cxx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx index a39466945..7b223b6be 100644 --- a/src/decoder/plugins/FfmpegMetaData.cxx +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -62,14 +62,16 @@ void ffmpeg_scan_dictionary(AVDictionary *dict, const struct tag_handler *handler, void *handler_ctx) { - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); + if (handler->tag != nullptr) { + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); - for (const struct tag_table *i = ffmpeg_tags; - i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); + for (const struct tag_table *i = ffmpeg_tags; + i->name != nullptr; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + } if (handler->pair != nullptr) ffmpeg_scan_pairs(dict, handler, handler_ctx); From 5e3f3b0400f1572225baae0ba6b993759ffb1611 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 10 Dec 2014 13:05:28 +0100 Subject: [PATCH 03/32] decoder/ffpmeg: rename functions to CamelCase --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 6 +++--- src/decoder/plugins/FfmpegMetaData.cxx | 24 ++++++++++----------- src/decoder/plugins/FfmpegMetaData.hxx | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 640445007..5dfe347f7 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -711,11 +711,11 @@ ffmpeg_scan_stream(InputStream &is, tag_handler_invoke_duration(handler, handler_ctx, duration); } - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); + FfmpegScanDictionary(f->metadata, handler, handler_ctx); int idx = ffmpeg_find_audio_stream(*f); if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); + FfmpegScanDictionary(f->streams[idx]->metadata, + handler, handler_ctx); avformat_close_input(&f); return true; diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx index 7b223b6be..5a5c9e1ef 100644 --- a/src/decoder/plugins/FfmpegMetaData.cxx +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -36,9 +36,9 @@ static const struct tag_table ffmpeg_tags[] = { }; static void -ffmpeg_copy_metadata(TagType type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanTag(TagType type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) { AVDictionaryEntry *mt = nullptr; @@ -48,8 +48,8 @@ ffmpeg_copy_metadata(TagType type, } static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanPairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) { AVDictionaryEntry *i = nullptr; @@ -59,20 +59,20 @@ ffmpeg_scan_pairs(AVDictionary *dict, } void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanDictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) { if (handler->tag != nullptr) { for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); + FfmpegScanTag(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); for (const struct tag_table *i = ffmpeg_tags; i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); + FfmpegScanTag(i->type, dict, i->name, + handler, handler_ctx); } if (handler->pair != nullptr) - ffmpeg_scan_pairs(dict, handler, handler_ctx); + FfmpegScanPairs(dict, handler, handler_ctx); } diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx index 5eb41db68..1f233fb6e 100644 --- a/src/decoder/plugins/FfmpegMetaData.hxx +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -32,7 +32,7 @@ extern "C" { struct tag_handler; void -ffmpeg_scan_dictionary(AVDictionary *dict, - const tag_handler *handler, void *handler_ctx); +FfmpegScanDictionary(AVDictionary *dict, + const tag_handler *handler, void *handler_ctx); #endif From 6eeec6cbfaffa50bef619ef691cfc16fee1b8512 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 11 Dec 2014 10:58:06 +0100 Subject: [PATCH 04/32] decoder/ffpmeg: simplify ffmpeg_send_packet() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 23 ++++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 5dfe347f7..b7ba4c00c 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -370,7 +370,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, time_from_ffmpeg(pts, stream.time_base)); } - uint8_t *output_buffer; + uint8_t *output_buffer = nullptr; DecoderCommand cmd = DecoderCommand::NONE; while (packet.size > 0 && cmd == DecoderCommand::NONE) { @@ -379,15 +379,6 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, int len = avcodec_decode_audio4(&codec_context, frame, &got_frame, &packet); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - *frame, - &output_buffer, - buffer, buffer_size); - if (audio_size < 0) - len = audio_size; - } - if (len < 0) { /* if error, we skip the frame */ LogDefault(ffmpeg_domain, @@ -395,6 +386,18 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, break; } + if (got_frame) { + audio_size = copy_interleave_frame(codec_context, + *frame, + &output_buffer, + buffer, buffer_size); + if (audio_size < 0) { + /* this must be a serious error, + e.g. OOM */ + return DecoderCommand::STOP; + } + } + packet.data += len; packet.size -= len; From a271a55da7071674b9e6c344588008501030ecb0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 11 Dec 2014 10:58:33 +0100 Subject: [PATCH 05/32] decoder/ffpmeg: make variables more local --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index b7ba4c00c..9c7ce58cb 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -370,11 +370,8 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, time_from_ffmpeg(pts, stream.time_base)); } - uint8_t *output_buffer = nullptr; - DecoderCommand cmd = DecoderCommand::NONE; while (packet.size > 0 && cmd == DecoderCommand::NONE) { - int audio_size = 0; int got_frame = 0; int len = avcodec_decode_audio4(&codec_context, frame, &got_frame, @@ -386,6 +383,8 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, break; } + uint8_t *output_buffer = nullptr; + int audio_size = 0; if (got_frame) { audio_size = copy_interleave_frame(codec_context, *frame, From 6637db086bc73308711a1bd0c8fce0e5a0f2b42d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 20:17:15 +0100 Subject: [PATCH 06/32] decoder/ffmpeg: add "pure" attributes --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 9c7ce58cb..1f9f583e7 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -198,6 +198,7 @@ ffmpeg_init(gcc_unused const config_param ¶m) return true; } +gcc_pure static int ffmpeg_find_audio_stream(const AVFormatContext &format_context) { @@ -252,7 +253,7 @@ timestamp_fallback(int64_t t, int64_t fallback) * assume that the stream's start time is zero, which appears to be * the best way out of that situation. */ -static int64_t +static constexpr int64_t start_time_fallback(const AVStream &stream) { return timestamp_fallback(stream.start_time, 0); From 5e77a8199d593f66e7bc4b0b2ad90452c52380ab Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 20:19:40 +0100 Subject: [PATCH 07/32] decoder/ffmpeg: remove obsolete comment --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 1f9f583e7..ce1b7143a 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -682,7 +682,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) avformat_close_input(&format_context); } -//no tag reading in ffmpeg, check if playable static bool ffmpeg_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) From 710b48d410af50474a81f5a39c64d97ed79a1016 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 22:07:38 +0100 Subject: [PATCH 08/32] decoder/ffmpeg: log detailed error message --- Makefile.am | 1 + NEWS | 2 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 5 ++- src/lib/ffmpeg/LogError.cxx | 45 +++++++++++++++++++++ src/lib/ffmpeg/LogError.hxx | 29 +++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/lib/ffmpeg/LogError.cxx create mode 100644 src/lib/ffmpeg/LogError.hxx diff --git a/Makefile.am b/Makefile.am index 7cd68f86e..ff2d1fbf8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -799,6 +799,7 @@ endif if HAVE_FFMPEG noinst_LIBRARIES += libffmpeg.a libffmpeg_a_SOURCES = \ + src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \ src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \ src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \ diff --git a/NEWS b/NEWS index a9e424aca..2e526a841 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.19.18 (not yet released) +* decoder + - ffmpeg: log detailed error message ver 0.19.17 (2016/07/09) * decoder diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index ce1b7143a..1743ec621 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -23,6 +23,7 @@ #include "config.h" #include "FfmpegDecoderPlugin.hxx" #include "lib/ffmpeg/Domain.hxx" +#include "lib/ffmpeg/LogError.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" #include "tag/TagHandler.hxx" @@ -379,8 +380,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, &packet); if (len < 0) { /* if error, we skip the frame */ - LogDefault(ffmpeg_domain, - "decoding failed, frame skipped"); + LogFfmpegError(len, "decoding failed, frame skipped"); break; } @@ -394,6 +394,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, if (audio_size < 0) { /* this must be a serious error, e.g. OOM */ + LogFfmpegError(audio_size); return DecoderCommand::STOP; } } diff --git a/src/lib/ffmpeg/LogError.cxx b/src/lib/ffmpeg/LogError.cxx new file mode 100644 index 000000000..da761f35a --- /dev/null +++ b/src/lib/ffmpeg/LogError.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LogError.hxx" +#include "Domain.hxx" +#include "Log.hxx" + +#include /* needed due to libavutil bug */ + +extern "C" { +#include +} + +void +LogFfmpegError(int errnum) +{ + char msg[256]; + av_strerror(errnum, msg, sizeof(msg)); + LogError(ffmpeg_domain, msg); +} + +void +LogFfmpegError(int errnum, const char *prefix) +{ + char msg[256]; + av_strerror(errnum, msg, sizeof(msg)); + FormatError(ffmpeg_domain, "%s: %s", prefix, msg); +} diff --git a/src/lib/ffmpeg/LogError.hxx b/src/lib/ffmpeg/LogError.hxx new file mode 100644 index 000000000..ccafc6f94 --- /dev/null +++ b/src/lib/ffmpeg/LogError.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_LOG_ERROR_HXX +#define MPD_FFMPEG_LOG_ERROR_HXX + +void +LogFfmpegError(int errnum); + +void +LogFfmpegError(int errnum, const char *prefix); + +#endif From c25b464f3730dbc2e09560404e6ed8b75188753a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 21:28:50 +0100 Subject: [PATCH 09/32] decoder/ffmpeg: move code to class FfmpegBuffer --- Makefile.am | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 27 +++----- src/lib/ffmpeg/Buffer.hxx | 72 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 src/lib/ffmpeg/Buffer.hxx diff --git a/Makefile.am b/Makefile.am index ff2d1fbf8..ec01fd845 100644 --- a/Makefile.am +++ b/Makefile.am @@ -799,6 +799,7 @@ endif if HAVE_FFMPEG noinst_LIBRARIES += libffmpeg.a libffmpeg_a_SOURCES = \ + src/lib/ffmpeg/Buffer.hxx \ src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \ src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \ src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 1743ec621..95aa2905d 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -24,6 +24,7 @@ #include "FfmpegDecoderPlugin.hxx" #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/LogError.hxx" +#include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" #include "tag/TagHandler.hxx" @@ -281,7 +282,7 @@ static int copy_interleave_frame(const AVCodecContext &codec_context, const AVFrame &frame, uint8_t **output_buffer, - uint8_t **global_buffer, int *global_buffer_size) + FfmpegBuffer &global_buffer) { int plane_size; const int data_size = @@ -294,17 +295,11 @@ copy_interleave_frame(const AVCodecContext &codec_context, if (av_sample_fmt_is_planar(codec_context.sample_fmt) && codec_context.channels > 1) { - if(*global_buffer_size < data_size) { - av_freep(global_buffer); + *output_buffer = global_buffer.GetT(data_size); + if (*output_buffer == nullptr) + /* Not enough memory - shouldn't happen */ + return AVERROR(ENOMEM); - *global_buffer = (uint8_t*)av_malloc(data_size); - - if (!*global_buffer) - /* Not enough memory - shouldn't happen */ - return AVERROR(ENOMEM); - *global_buffer_size = data_size; - } - *output_buffer = *global_buffer; copy_interleave_frame2(*output_buffer, frame.extended_data, frame.nb_samples, codec_context.channels, @@ -356,7 +351,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, const AVStream &stream, AVFrame *frame, uint64_t min_frame, size_t pcm_frame_size, - uint8_t **buffer, int *buffer_size) + FfmpegBuffer &buffer) { size_t skip_bytes = 0; @@ -390,7 +385,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, audio_size = copy_interleave_frame(codec_context, *frame, &output_buffer, - buffer, buffer_size); + buffer); if (audio_size < 0) { /* this must be a serious error, e.g. OOM */ @@ -619,8 +614,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) return; } - uint8_t *interleaved_buffer = nullptr; - int interleaved_buffer_size = 0; + FfmpegBuffer interleaved_buffer; uint64_t min_frame = 0; @@ -638,7 +632,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) *av_stream, frame, min_frame, audio_format.GetFrameSize(), - &interleaved_buffer, &interleaved_buffer_size); + interleaved_buffer); min_frame = 0; } else cmd = decoder_get_command(decoder); @@ -677,7 +671,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #else av_freep(&frame); #endif - av_freep(&interleaved_buffer); avcodec_close(codec_context); avformat_close_input(&format_context); diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx new file mode 100644 index 000000000..50a702f59 --- /dev/null +++ b/src/lib/ffmpeg/Buffer.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_BUFFER_HXX +#define MPD_FFMPEG_BUFFER_HXX + +extern "C" { +#include + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0) +#define HAVE_AV_FAST_MALLOC +#else +#include +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0) +#define HAVE_AV_FAST_MALLOC +#endif +#endif +} + +#include + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +class FfmpegBuffer { + void *data; + unsigned size; + +public: + FfmpegBuffer():data(nullptr), size(0) {} + + ~FfmpegBuffer() { + av_free(data); + } + + void *Get(size_t min_size) { +#ifdef HAVE_AV_FAST_MALLOC + av_fast_malloc(&data, &size, min_size); +#else + void *new_data = av_fast_realloc(data, &size, min_size); + if (new_data == nullptr) + return AVERROR(ENOMEM); + data = new_data; +#endif + return data; + } + + template + T *GetT(size_t n) { + return (T *)Get(n * sizeof(T)); + } +}; + +#endif From eb192137d61b0c62541904351660f9cd63bc90d5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 28 Jul 2016 19:41:05 +0200 Subject: [PATCH 10/32] decoder/ffmpeg: copy the AVPacket in ffmpeg_send_packet() Revert commit 70495aad by rewriting it. Turns out, in old FFmpeg versions, copying the AVPacket is necessary. --- NEWS | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 2e526a841..5470a208c 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ ver 0.19.18 (not yet released) * decoder + - ffmpeg: fix crash with older FFmpeg versions (< 3.0) - ffmpeg: log detailed error message ver 0.19.17 (2016/07/09) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 95aa2905d..5d3ccb3fa 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -419,6 +419,24 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, 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) @@ -627,7 +645,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (packet.stream_index == audio_stream) { cmd = ffmpeg_send_packet(decoder, input, - std::move(packet), + packet, *codec_context, *av_stream, frame, From f8a9a7a10882e42fcfd834fc702b7ebe26d5ca79 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 23:21:48 +0100 Subject: [PATCH 11/32] decoder/ffmpeg: simplify ffmpeg_send_packet() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 5d3ccb3fa..61220be8e 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -379,24 +379,24 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, break; } - uint8_t *output_buffer = nullptr; - int audio_size = 0; - if (got_frame) { - audio_size = copy_interleave_frame(codec_context, - *frame, - &output_buffer, - buffer); - if (audio_size < 0) { - /* this must be a serious error, - e.g. OOM */ - LogFfmpegError(audio_size); - return DecoderCommand::STOP; - } - } - packet.data += len; packet.size -= len; + if (!got_frame) + continue; + + uint8_t *output_buffer = nullptr; + int audio_size = + copy_interleave_frame(codec_context, *frame, + &output_buffer, + buffer); + if (audio_size < 0) { + /* this must be a serious error, + e.g. OOM */ + LogFfmpegError(audio_size); + return DecoderCommand::STOP; + } + if (audio_size <= 0) continue; From 750ae1d3f3a90ae505c01ab86ac4f0c219ec2845 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 23:39:56 +0100 Subject: [PATCH 12/32] decoder/ffmpeg: copy_interleave_frame() returns Error --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 38 +++++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 61220be8e..c038e769c 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -23,6 +23,7 @@ #include "config.h" #include "FfmpegDecoderPlugin.hxx" #include "lib/ffmpeg/Domain.hxx" +#include "lib/ffmpeg/Error.hxx" #include "lib/ffmpeg/LogError.hxx" #include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" @@ -278,27 +279,35 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src, /** * Copy PCM data from a AVFrame to an interleaved buffer. */ -static int +static size_t copy_interleave_frame(const AVCodecContext &codec_context, const AVFrame &frame, uint8_t **output_buffer, - FfmpegBuffer &global_buffer) + FfmpegBuffer &global_buffer, + Error &error) { + assert(frame.nb_samples > 0); + int plane_size; const int data_size = av_samples_get_buffer_size(&plane_size, codec_context.channels, frame.nb_samples, codec_context.sample_fmt, 1); - if (data_size <= 0) - return data_size; + assert(data_size != 0); + if (data_size < 0) { + SetFfmpegError(error, data_size); + return 0; + } if (av_sample_fmt_is_planar(codec_context.sample_fmt) && codec_context.channels > 1) { *output_buffer = global_buffer.GetT(data_size); - if (*output_buffer == nullptr) + if (*output_buffer == nullptr) { /* Not enough memory - shouldn't happen */ - return AVERROR(ENOMEM); + error.SetErrno(ENOMEM); + return 0; + } copy_interleave_frame2(*output_buffer, frame.extended_data, frame.nb_samples, @@ -367,6 +376,8 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, time_from_ffmpeg(pts, stream.time_base)); } + Error error; + DecoderCommand cmd = DecoderCommand::NONE; while (packet.size > 0 && cmd == DecoderCommand::NONE) { int got_frame = 0; @@ -382,27 +393,24 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, packet.data += len; packet.size -= len; - if (!got_frame) + if (!got_frame || frame->nb_samples <= 0) continue; uint8_t *output_buffer = nullptr; - int audio_size = + size_t audio_size = copy_interleave_frame(codec_context, *frame, &output_buffer, - buffer); - if (audio_size < 0) { + buffer, error); + if (audio_size == 0) { /* this must be a serious error, e.g. OOM */ - LogFfmpegError(audio_size); + LogError(error); return DecoderCommand::STOP; } - if (audio_size <= 0) - continue; - const uint8_t *data = output_buffer; if (skip_bytes > 0) { - if (skip_bytes >= size_t(audio_size)) { + if (skip_bytes >= audio_size) { skip_bytes -= audio_size; continue; } From 26d8e41a6b0602a290c49a9bcac4517f9a1c7512 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Dec 2014 23:41:08 +0100 Subject: [PATCH 13/32] decoder/ffmpeg: copy_interleave_frame() returns ConstBuffer --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 34 ++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index c038e769c..41dfbb8a8 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -31,6 +31,7 @@ #include "tag/TagHandler.hxx" #include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "LogV.hxx" @@ -279,10 +280,9 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src, /** * Copy PCM data from a AVFrame to an interleaved buffer. */ -static size_t +static ConstBuffer copy_interleave_frame(const AVCodecContext &codec_context, const AVFrame &frame, - uint8_t **output_buffer, FfmpegBuffer &global_buffer, Error &error) { @@ -300,24 +300,26 @@ copy_interleave_frame(const AVCodecContext &codec_context, return 0; } + void *output_buffer; if (av_sample_fmt_is_planar(codec_context.sample_fmt) && codec_context.channels > 1) { - *output_buffer = global_buffer.GetT(data_size); - if (*output_buffer == nullptr) { + output_buffer = global_buffer.GetT(data_size); + if (output_buffer == nullptr) { /* Not enough memory - shouldn't happen */ error.SetErrno(ENOMEM); return 0; } - copy_interleave_frame2(*output_buffer, frame.extended_data, + copy_interleave_frame2((uint8_t *)output_buffer, + frame.extended_data, frame.nb_samples, codec_context.channels, av_get_bytes_per_sample(codec_context.sample_fmt)); } else { - *output_buffer = frame.extended_data[0]; + output_buffer = frame.extended_data[0]; } - return data_size; + return { output_buffer, (size_t)data_size }; } /** @@ -396,32 +398,30 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, if (!got_frame || frame->nb_samples <= 0) continue; - uint8_t *output_buffer = nullptr; - size_t audio_size = + auto output_buffer = copy_interleave_frame(codec_context, *frame, - &output_buffer, buffer, error); - if (audio_size == 0) { + if (output_buffer.IsNull()) { /* this must be a serious error, e.g. OOM */ LogError(error); return DecoderCommand::STOP; } - const uint8_t *data = output_buffer; if (skip_bytes > 0) { - if (skip_bytes >= audio_size) { - skip_bytes -= audio_size; + if (skip_bytes >= output_buffer.size) { + skip_bytes -= output_buffer.size; continue; } - data += skip_bytes; - audio_size -= skip_bytes; + output_buffer.data = + (const uint8_t *)output_buffer.data + skip_bytes; + output_buffer.size -= skip_bytes; skip_bytes = 0; } cmd = decoder_data(decoder, is, - data, audio_size, + output_buffer.data, output_buffer.size, codec_context.bit_rate / 1000); } return cmd; From 087a9938d2dc84573d9efd9f92ec7ef88661c5ff Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 06:41:50 +0100 Subject: [PATCH 14/32] decoder/ffmpeg: add API documentation --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 41dfbb8a8..70534f218 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -278,7 +278,7 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src, } /** - * Copy PCM data from a AVFrame to an interleaved buffer. + * Copy PCM data from a non-empty AVFrame to an interleaved buffer. */ static ConstBuffer copy_interleave_frame(const AVCodecContext &codec_context, @@ -351,6 +351,9 @@ PtsToPcmFrame(uint64_t pts, const AVStream &stream, } /** + * Decode an #AVPacket and send the resulting PCM data to the decoder + * API. + * * @param min_frame skip all data before this PCM frame number; this * is used after seeking to skip data in an AVPacket until the exact * desired time stamp has been reached From 47360ec906b8d7354ce985a973252aa6194706ac Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 09:23:22 +0100 Subject: [PATCH 15/32] decoder/ffmpeg: use av_free() instead of av_freep() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 70534f218..25293ad51 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -698,7 +698,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) avcodec_free_frame(&frame); #else - av_freep(&frame); + av_free(frame); #endif avcodec_close(codec_context); From 0ff22a16fa890a35df45d140aa77334459c19235 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 09:41:21 +0100 Subject: [PATCH 16/32] decoder/ffmpeg: move code to lib/ffmpeg/Time.hxx --- Makefile.am | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 53 ++------------ src/lib/ffmpeg/Time.hxx | 76 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 src/lib/ffmpeg/Time.hxx diff --git a/Makefile.am b/Makefile.am index ec01fd845..15a66c113 100644 --- a/Makefile.am +++ b/Makefile.am @@ -799,6 +799,7 @@ endif if HAVE_FFMPEG noinst_LIBRARIES += libffmpeg.a libffmpeg_a_SOURCES = \ + src/lib/ffmpeg/Time.hxx \ src/lib/ffmpeg/Buffer.hxx \ src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \ src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \ diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 25293ad51..4355090ae 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -20,6 +20,7 @@ /* necessary because libavutil/common.h uses UINT64_C */ #define __STDC_CONSTANT_MACROS +#include "lib/ffmpeg/Time.hxx" #include "config.h" #include "FfmpegDecoderPlugin.hxx" #include "lib/ffmpeg/Domain.hxx" @@ -42,7 +43,6 @@ extern "C" { #include #include #include -#include #if LIBAVUTIL_VERSION_MAJOR >= 53 #include @@ -52,11 +52,6 @@ extern "C" { #include #include -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - static LogLevel import_ffmpeg_level(int level) { @@ -214,43 +209,6 @@ ffmpeg_find_audio_stream(const AVFormatContext &format_context) return -1; } -gcc_const -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -template -static constexpr AVRational -RatioToAVRational() -{ - return { Ratio::num, Ratio::den }; -} - -gcc_const -static int64_t -time_to_ffmpeg(SongTime t, const AVRational time_base) -{ - return av_rescale_q(t.count(), - RatioToAVRational(), - time_base); -} - -/** - * Replace #AV_NOPTS_VALUE with the given fallback. - */ -static constexpr int64_t -timestamp_fallback(int64_t t, int64_t fallback) -{ - return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) - ? t - : fallback; -} - /** * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply @@ -260,7 +218,7 @@ timestamp_fallback(int64_t t, int64_t fallback) static constexpr int64_t start_time_fallback(const AVStream &stream) { - return timestamp_fallback(stream.start_time, 0); + return FfmpegTimestampFallback(stream.start_time, 0); } static void @@ -378,7 +336,8 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, skip_bytes = pcm_frame_size * (min_frame - cur_frame); } else decoder_timestamp(decoder, - time_from_ffmpeg(pts, stream.time_base)); + FfmpegTimeToDouble(pts, + stream.time_base)); } Error error; @@ -674,8 +633,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (cmd == DecoderCommand::SEEK) { int64_t where = - time_to_ffmpeg(decoder_seek_time(decoder), - av_stream->time_base) + + ToFfmpegTime(decoder_seek_time(decoder), + av_stream->time_base) + start_time_fallback(*av_stream); /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to diff --git a/src/lib/ffmpeg/Time.hxx b/src/lib/ffmpeg/Time.hxx new file mode 100644 index 000000000..fb845d7d9 --- /dev/null +++ b/src/lib/ffmpeg/Time.hxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_TIME_HXX +#define MPD_FFMPEG_TIME_HXX + +#include "Chrono.hxx" +#include "Compiler.h" + +extern "C" { +#include +#include +} + +#include +#include + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +gcc_const +static inline double +FfmpegTimeToDouble(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +template +static inline constexpr AVRational +RatioToAVRational() +{ + return { Ratio::num, Ratio::den }; +} + +gcc_const +static inline int64_t +ToFfmpegTime(SongTime t, const AVRational time_base) +{ + return av_rescale_q(t.count(), + RatioToAVRational(), + time_base); +} + +/** + * Replace #AV_NOPTS_VALUE with the given fallback. + */ +static constexpr int64_t +FfmpegTimestampFallback(int64_t t, int64_t fallback) +{ + return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) + ? t + : fallback; +} + +#endif From cc19e760cfece9783603112bb6f5149bcd1352a5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 10:03:35 +0100 Subject: [PATCH 17/32] decoder/ffmpeg: use more references --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 4355090ae..aba4e95b8 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -321,7 +321,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, AVPacket &&packet, AVCodecContext &codec_context, const AVStream &stream, - AVFrame *frame, + AVFrame &frame, uint64_t min_frame, size_t pcm_frame_size, FfmpegBuffer &buffer) { @@ -346,7 +346,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, while (packet.size > 0 && cmd == DecoderCommand::NONE) { int got_frame = 0; int len = avcodec_decode_audio4(&codec_context, - frame, &got_frame, + &frame, &got_frame, &packet); if (len < 0) { /* if error, we skip the frame */ @@ -357,11 +357,11 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, packet.data += len; packet.size -= len; - if (!got_frame || frame->nb_samples <= 0) + if (!got_frame || frame.nb_samples <= 0) continue; auto output_buffer = - copy_interleave_frame(codec_context, *frame, + copy_interleave_frame(codec_context, frame, buffer, error); if (output_buffer.IsNull()) { /* this must be a serious error, @@ -394,7 +394,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is, const AVPacket &packet, AVCodecContext &codec_context, const AVStream &stream, - AVFrame *frame, + AVFrame &frame, uint64_t min_frame, size_t pcm_frame_size, FfmpegBuffer &buffer) { @@ -527,9 +527,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) return; } - AVStream *av_stream = format_context->streams[audio_stream]; + AVStream &av_stream = *format_context->streams[audio_stream]; - AVCodecContext *codec_context = av_stream->codec; + AVCodecContext *codec_context = av_stream.codec; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) const AVCodecDescriptor *codec_descriptor = @@ -617,8 +617,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) cmd = ffmpeg_send_packet(decoder, input, packet, *codec_context, - *av_stream, - frame, + av_stream, + *frame, min_frame, audio_format.GetFrameSize(), interleaved_buffer); min_frame = 0; @@ -634,8 +634,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (cmd == DecoderCommand::SEEK) { int64_t where = ToFfmpegTime(decoder_seek_time(decoder), - av_stream->time_base) + - start_time_fallback(*av_stream); + av_stream.time_base) + + start_time_fallback(av_stream); /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to the packet boundary before the seek time From 42c5f6836222d078ca8300832ccefb48a95df2f6 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 10:12:01 +0100 Subject: [PATCH 18/32] decoder/ffmpeg: use AVStream::duration Use the duration of the stream we're actually decoding - not the "global" attribute AVFormatContext::duration which may differ. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 5 +--- src/lib/ffmpeg/Time.hxx | 28 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index aba4e95b8..53e3c74be 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -583,10 +583,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) } const SignedSongTime total_time = - format_context->duration != (int64_t)AV_NOPTS_VALUE - ? SignedSongTime::FromScale(format_context->duration, - AV_TIME_BASE) - : SignedSongTime::Negative(); + FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base); decoder_initialized(decoder, audio_format, input.IsSeekable(), total_time); diff --git a/src/lib/ffmpeg/Time.hxx b/src/lib/ffmpeg/Time.hxx index fb845d7d9..9325434b5 100644 --- a/src/lib/ffmpeg/Time.hxx +++ b/src/lib/ffmpeg/Time.hxx @@ -53,6 +53,34 @@ RatioToAVRational() return { Ratio::num, Ratio::den }; } +/** + * Convert a FFmpeg time stamp to a #SongTime. + */ +gcc_const +static inline SongTime +FromFfmpegTime(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return SongTime::FromMS(av_rescale_q(t, time_base, + (AVRational){1, 1000})); +} + +/** + * Convert a FFmpeg time stamp to a #SignedSongTime. + */ +gcc_const +static inline SignedSongTime +FromFfmpegTimeChecked(int64_t t, const AVRational time_base) +{ + return t != (int64_t)AV_NOPTS_VALUE + ? SignedSongTime(FromFfmpegTime(t, time_base)) + : SignedSongTime::Negative(); +} + +/** + * Convert a #SongTime to a FFmpeg time stamp with the given base. + */ gcc_const static inline int64_t ToFfmpegTime(SongTime t, const AVRational time_base) From df97049647a82f4da4f4fa0faacb8cb2ab5a96ac Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 10:35:10 +0100 Subject: [PATCH 19/32] decoder/ffmpeg: move struct AvioStream to FfmpegIo.hxx --- Makefile.am | 2 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 85 +---------------- src/decoder/plugins/FfmpegIo.cxx | 100 ++++++++++++++++++++ src/decoder/plugins/FfmpegIo.hxx | 48 ++++++++++ 4 files changed, 151 insertions(+), 84 deletions(-) create mode 100644 src/decoder/plugins/FfmpegIo.cxx create mode 100644 src/decoder/plugins/FfmpegIo.hxx diff --git a/Makefile.am b/Makefile.am index 15a66c113..1ca69db07 100644 --- a/Makefile.am +++ b/Makefile.am @@ -983,6 +983,8 @@ endif if HAVE_FFMPEG libdecoder_a_SOURCES += \ + src/decoder/plugins/FfmpegIo.cxx \ + src/decoder/plugins/FfmpegIo.hxx \ src/decoder/plugins/FfmpegMetaData.cxx \ src/decoder/plugins/FfmpegMetaData.hxx \ src/decoder/plugins/FfmpegDecoderPlugin.cxx \ diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 53e3c74be..a3419d0ac 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -29,6 +29,7 @@ #include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" +#include "FfmpegIo.hxx" #include "tag/TagHandler.hxx" #include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" @@ -85,90 +86,6 @@ mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, } } -struct AvioStream { - Decoder *const decoder; - InputStream &input; - - AVIOContext *io; - - AvioStream(Decoder *_decoder, InputStream &_input) - :decoder(_decoder), input(_input), io(nullptr) {} - - ~AvioStream() { - if (io != nullptr) { - av_free(io->buffer); - av_free(io); - } - } - - bool Open(); -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - AvioStream *stream = (AvioStream *)opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - AvioStream *stream = (AvioStream *)opaque; - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - pos += stream->input.GetOffset(); - break; - - case SEEK_END: - if (!stream->input.KnownSize()) - return -1; - - pos += stream->input.GetSize(); - break; - - case AVSEEK_SIZE: - if (!stream->input.KnownSize()) - return -1; - - return stream->input.GetSize(); - - default: - return -1; - } - - if (!stream->input.LockSeek(pos, IgnoreError())) - return -1; - - return stream->input.GetOffset(); -} - -bool -AvioStream::Open() -{ - constexpr size_t BUFFER_SIZE = 8192; - auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE); - if (buffer == nullptr) - return false; - - io = avio_alloc_context(buffer, BUFFER_SIZE, - false, this, - mpd_ffmpeg_stream_read, nullptr, - input.IsSeekable() - ? mpd_ffmpeg_stream_seek : nullptr); - /* If avio_alloc_context() fails, who frees the buffer? The - libavformat API documentation does not specify this, it - only says that AVIOContext.buffer must be freed in the end, - however no AVIOContext exists in that failure code path. */ - return io != nullptr; -} - /** * API compatibility wrapper for av_open_input_stream() and * avformat_open_input(). diff --git a/src/decoder/plugins/FfmpegIo.cxx b/src/decoder/plugins/FfmpegIo.cxx new file mode 100644 index 000000000..5f8452c68 --- /dev/null +++ b/src/decoder/plugins/FfmpegIo.cxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2016 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegIo.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" + +AvioStream::~AvioStream() +{ + if (io != nullptr) { + av_free(io->buffer); + av_free(io); + } +} + +static int +mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) +{ + AvioStream *stream = (AvioStream *)opaque; + + return decoder_read(stream->decoder, stream->input, + (void *)buf, size); +} + +static int64_t +mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) +{ + AvioStream *stream = (AvioStream *)opaque; + + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + pos += stream->input.GetOffset(); + break; + + case SEEK_END: + if (!stream->input.KnownSize()) + return -1; + + pos += stream->input.GetSize(); + break; + + case AVSEEK_SIZE: + if (!stream->input.KnownSize()) + return -1; + + return stream->input.GetSize(); + + default: + return -1; + } + + if (!stream->input.LockSeek(pos, IgnoreError())) + return -1; + + return stream->input.GetOffset(); +} + +bool +AvioStream::Open() +{ + constexpr size_t BUFFER_SIZE = 8192; + auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE); + if (buffer == nullptr) + return false; + + io = avio_alloc_context(buffer, BUFFER_SIZE, + false, this, + mpd_ffmpeg_stream_read, nullptr, + input.IsSeekable() + ? mpd_ffmpeg_stream_seek : nullptr); + /* If avio_alloc_context() fails, who frees the buffer? The + libavformat API documentation does not specify this, it + only says that AVIOContext.buffer must be freed in the end, + however no AVIOContext exists in that failure code path. */ + return io != nullptr; +} diff --git a/src/decoder/plugins/FfmpegIo.hxx b/src/decoder/plugins/FfmpegIo.hxx new file mode 100644 index 000000000..7c400de89 --- /dev/null +++ b/src/decoder/plugins/FfmpegIo.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2016 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_IO_HXX +#define MPD_FFMPEG_IO_HXX + +#include "check.h" + +extern "C" { +#include "libavformat/avio.h" +} + +#include + +class InputStream; +struct Decoder; + +struct AvioStream { + Decoder *const decoder; + InputStream &input; + + AVIOContext *io; + + AvioStream(Decoder *_decoder, InputStream &_input) + :decoder(_decoder), input(_input), io(nullptr) {} + + ~AvioStream(); + + bool Open(); +}; + +#endif From dbe3b6eee43bb92b1cc291bee640cfdddcd30507 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 11:47:25 +0100 Subject: [PATCH 20/32] decoder/ffmpeg: convert enums to constexpr --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index a3419d0ac..1b86f0d56 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -362,10 +362,8 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt) static AVInputFormat * ffmpeg_probe(Decoder *decoder, InputStream &is) { - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; + constexpr size_t BUFFER_SIZE = 16384; + constexpr size_t PADDING = 16; unsigned char buffer[BUFFER_SIZE]; size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); From 073facea703bc95a65944d0394e299a7f9db558e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 11:51:16 +0100 Subject: [PATCH 21/32] decoder/ffmpeg: remove obsolete comment --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 1b86f0d56..88b50d430 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -418,7 +418,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) return; } - //ffmpeg works with ours "fileops" helper AVFormatContext *format_context = nullptr; if (mpd_ffmpeg_open_input(&format_context, stream.io, input.GetURI(), From 5fee130d00ec186963ba32f60f6b453457e33fe0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 20 Dec 2014 18:46:29 +0100 Subject: [PATCH 22/32] decoder/ffmpeg: move code to lib/ffmpeg/LogCallback.cxx --- Makefile.am | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 36 +---------- src/lib/ffmpeg/LogCallback.cxx | 66 +++++++++++++++++++++ src/lib/ffmpeg/LogCallback.hxx | 30 ++++++++++ 4 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 src/lib/ffmpeg/LogCallback.cxx create mode 100644 src/lib/ffmpeg/LogCallback.hxx diff --git a/Makefile.am b/Makefile.am index 1ca69db07..3590cd8a4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -802,6 +802,7 @@ libffmpeg_a_SOURCES = \ src/lib/ffmpeg/Time.hxx \ src/lib/ffmpeg/Buffer.hxx \ src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \ + src/lib/ffmpeg/LogCallback.cxx src/lib/ffmpeg/LogCallback.hxx \ src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \ src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \ diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 88b50d430..06fb949be 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -26,6 +26,7 @@ #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/Error.hxx" #include "lib/ffmpeg/LogError.hxx" +#include "lib/ffmpeg/LogCallback.hxx" #include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" @@ -53,39 +54,6 @@ extern "C" { #include #include -static LogLevel -import_ffmpeg_level(int level) -{ - if (level <= AV_LOG_FATAL) - return LogLevel::ERROR; - - if (level <= AV_LOG_WARNING) - return LogLevel::WARNING; - - if (level <= AV_LOG_INFO) - return LogLevel::INFO; - - return LogLevel::DEBUG; -} - -static void -mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, - const char *fmt, va_list vl) -{ - const AVClass * cls = nullptr; - - if (ptr != nullptr) - cls = *(const AVClass *const*)ptr; - - if (cls != nullptr) { - char domain[64]; - snprintf(domain, sizeof(domain), "%s/%s", - ffmpeg_domain.GetName(), cls->item_name(ptr)); - const Domain d(domain); - LogFormatV(d, import_ffmpeg_level(level), fmt, vl); - } -} - /** * API compatibility wrapper for av_open_input_stream() and * avformat_open_input(). @@ -108,7 +76,7 @@ mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, static bool ffmpeg_init(gcc_unused const config_param ¶m) { - av_log_set_callback(mpd_ffmpeg_log_callback); + av_log_set_callback(FfmpegLogCallback); av_register_all(); return true; diff --git a/src/lib/ffmpeg/LogCallback.cxx b/src/lib/ffmpeg/LogCallback.cxx new file mode 100644 index 000000000..799ba2f34 --- /dev/null +++ b/src/lib/ffmpeg/LogCallback.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "LogCallback.hxx" +#include "Domain.hxx" +#include "LogV.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include +} + +#include + +gcc_const +static LogLevel +FfmpegImportLogLevel(int level) +{ + if (level <= AV_LOG_FATAL) + return LogLevel::ERROR; + + if (level <= AV_LOG_WARNING) + return LogLevel::WARNING; + + if (level <= AV_LOG_INFO) + return LogLevel::INFO; + + return LogLevel::DEBUG; +} + +void +FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl) +{ + const AVClass * cls = nullptr; + + if (ptr != nullptr) + cls = *(const AVClass *const*)ptr; + + if (cls != nullptr) { + char domain[64]; + snprintf(domain, sizeof(domain), "%s/%s", + ffmpeg_domain.GetName(), cls->item_name(ptr)); + const Domain d(domain); + LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl); + } +} diff --git a/src/lib/ffmpeg/LogCallback.hxx b/src/lib/ffmpeg/LogCallback.hxx new file mode 100644 index 000000000..cb4b2ccf5 --- /dev/null +++ b/src/lib/ffmpeg/LogCallback.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_LOG_CALLBACK_HXX +#define MPD_FFMPEG_LOG_CALLBACK_HXX + +#include "check.h" + +#include + +void +FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl); + +#endif From 543296b5ba3ecc943db3d49fe8a94cdd52b8f7cf Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 21 Dec 2014 20:51:41 +0100 Subject: [PATCH 23/32] decoder/ffmpeg: move code to lib/ffmpeg/Init.cxx --- Makefile.am | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 6 ++-- src/lib/ffmpeg/Init.cxx | 38 +++++++++++++++++++++ src/lib/ffmpeg/Init.hxx | 26 ++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/lib/ffmpeg/Init.cxx create mode 100644 src/lib/ffmpeg/Init.hxx diff --git a/Makefile.am b/Makefile.am index 3590cd8a4..262557485 100644 --- a/Makefile.am +++ b/Makefile.am @@ -799,6 +799,7 @@ endif if HAVE_FFMPEG noinst_LIBRARIES += libffmpeg.a libffmpeg_a_SOURCES = \ + src/lib/ffmpeg/Init.cxx src/lib/ffmpeg/Init.hxx \ src/lib/ffmpeg/Time.hxx \ src/lib/ffmpeg/Buffer.hxx \ src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \ diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 06fb949be..993bd6fd7 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -26,7 +26,7 @@ #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/Error.hxx" #include "lib/ffmpeg/LogError.hxx" -#include "lib/ffmpeg/LogCallback.hxx" +#include "lib/ffmpeg/Init.hxx" #include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" @@ -76,9 +76,7 @@ mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, static bool ffmpeg_init(gcc_unused const config_param ¶m) { - av_log_set_callback(FfmpegLogCallback); - - av_register_all(); + FfmpegInit(); return true; } diff --git a/src/lib/ffmpeg/Init.cxx b/src/lib/ffmpeg/Init.cxx new file mode 100644 index 000000000..24f4ab238 --- /dev/null +++ b/src/lib/ffmpeg/Init.cxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "Init.hxx" +#include "LogCallback.hxx" + +extern "C" { +#include +} + +void +FfmpegInit() +{ + av_log_set_callback(FfmpegLogCallback); + + av_register_all(); +} + diff --git a/src/lib/ffmpeg/Init.hxx b/src/lib/ffmpeg/Init.hxx new file mode 100644 index 000000000..bcc4805a9 --- /dev/null +++ b/src/lib/ffmpeg/Init.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INIT_HXX +#define MPD_FFMPEG_INIT_HXX + +void +FfmpegInit(); + +#endif From 62f73758042b82be93a43f545a64308a7ee20ce2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Dec 2014 21:58:01 +0100 Subject: [PATCH 24/32] decoder/ffmpeg: simplify mpd_ffmpeg_open_input() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 33 +++++++++------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 993bd6fd7..e637914b9 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -54,23 +54,19 @@ extern "C" { #include #include -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, - AVIOContext *pb, - const char *filename, - AVInputFormat *fmt) +static AVFormatContext * +FfmpegOpenInput(AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) { AVFormatContext *context = avformat_alloc_context(); if (context == nullptr) - return AVERROR(ENOMEM); + return nullptr; context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, nullptr); + + avformat_open_input(&context, filename, fmt, nullptr); + return context; } static bool @@ -384,10 +380,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) return; } - AVFormatContext *format_context = nullptr; - if (mpd_ffmpeg_open_input(&format_context, stream.io, - input.GetURI(), - input_format) != 0) { + AVFormatContext *format_context = + FfmpegOpenInput(stream.io, input.GetURI(), input_format); + if (format_context == nullptr) { LogError(ffmpeg_domain, "Open failed"); return; } @@ -553,9 +548,9 @@ ffmpeg_scan_stream(InputStream &is, if (!stream.Open()) return false; - AVFormatContext *f = nullptr; - if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(), - input_format) != 0) + AVFormatContext *f = + FfmpegOpenInput(stream.io, is.GetURI(), input_format); + if (f == nullptr) return false; const int find_result = From 4dd2ad9b2782c828c97eb7c3ea41ebc759950cd5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Jun 2015 14:41:44 +0200 Subject: [PATCH 25/32] decoder/ffmpeg: check for commands earlier Improve initial seek by not reading/decoding the first frame before checking for the seek command. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 45 ++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index e637914b9..95cbed5a5 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -478,8 +478,27 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) uint64_t min_frame = 0; - DecoderCommand cmd; - do { + DecoderCommand cmd = decoder_get_command(decoder); + while (cmd != DecoderCommand::STOP) { + if (cmd == DecoderCommand::SEEK) { + int64_t where = + ToFfmpegTime(decoder_seek_time(decoder), + av_stream.time_base) + + start_time_fallback(av_stream); + + /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to + the packet boundary before the seek time + stamp, not after */ + if (av_seek_frame(format_context, audio_stream, where, + AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0) + decoder_seek_error(decoder); + else { + avcodec_flush_buffers(codec_context); + min_frame = decoder_seek_where_frame(decoder); + decoder_command_finished(decoder); + } + } + AVPacket packet; if (av_read_frame(format_context, &packet) < 0) /* end of file */ @@ -502,27 +521,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #else av_free_packet(&packet); #endif - - if (cmd == DecoderCommand::SEEK) { - int64_t where = - ToFfmpegTime(decoder_seek_time(decoder), - av_stream.time_base) + - start_time_fallback(av_stream); - - /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to - the packet boundary before the seek time - stamp, not after */ - - if (av_seek_frame(format_context, audio_stream, where, - AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0) - decoder_seek_error(decoder); - else { - avcodec_flush_buffers(codec_context); - min_frame = decoder_seek_where_frame(decoder); - decoder_command_finished(decoder); - } - } - } while (cmd != DecoderCommand::STOP); + } #if LIBAVUTIL_VERSION_MAJOR >= 53 av_frame_free(&frame); From 142a9fe5302bd87c6f85a98537eefc06d5f825db Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 22 Jun 2015 14:28:23 +0200 Subject: [PATCH 26/32] decoder/ffmpeg: move code to pcm/Interleave.cxx --- Makefile.am | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 25 +++-------- src/pcm/Interleave.cxx | 47 +++++++++++++++++++++ src/pcm/Interleave.hxx | 33 +++++++++++++++ 4 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 src/pcm/Interleave.cxx create mode 100644 src/pcm/Interleave.hxx diff --git a/Makefile.am b/Makefile.am index 262557485..2eed31d9f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -462,6 +462,7 @@ ICU_LDADD = libicu.a $(ICU_LIBS) libpcm_a_SOURCES = \ src/pcm/Domain.cxx src/pcm/Domain.hxx \ src/pcm/Traits.hxx \ + src/pcm/Interleave.cxx src/pcm/Interleave.hxx \ src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \ src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \ src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \ diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 95cbed5a5..7f259a1fa 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -31,6 +31,7 @@ #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" #include "FfmpegIo.hxx" +#include "pcm/Interleave.hxx" #include "tag/TagHandler.hxx" #include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" @@ -100,20 +101,6 @@ start_time_fallback(const AVStream &stream) return FfmpegTimestampFallback(stream.start_time, 0); } -static void -copy_interleave_frame2(uint8_t *dest, uint8_t **src, - unsigned nframes, unsigned nchannels, - unsigned sample_size) -{ - for (unsigned frame = 0; frame < nframes; ++frame) { - for (unsigned channel = 0; channel < nchannels; ++channel) { - memcpy(dest, src[channel] + frame * sample_size, - sample_size); - dest += sample_size; - } - } -} - /** * Copy PCM data from a non-empty AVFrame to an interleaved buffer. */ @@ -147,11 +134,11 @@ copy_interleave_frame(const AVCodecContext &codec_context, return 0; } - copy_interleave_frame2((uint8_t *)output_buffer, - frame.extended_data, - frame.nb_samples, - codec_context.channels, - av_get_bytes_per_sample(codec_context.sample_fmt)); + PcmInterleave(output_buffer, + ConstBuffer((const void *const*)frame.extended_data, + codec_context.channels), + frame.nb_samples, + av_get_bytes_per_sample(codec_context.sample_fmt)); } else { output_buffer = frame.extended_data[0]; } diff --git a/src/pcm/Interleave.cxx b/src/pcm/Interleave.cxx new file mode 100644 index 000000000..8f2b11bd7 --- /dev/null +++ b/src/pcm/Interleave.cxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Interleave.hxx" + +#include +#include + +static void +GenericPcmInterleave(uint8_t *dest, ConstBuffer src, + size_t n_frames, size_t sample_size) +{ + for (size_t frame = 0; frame < n_frames; ++frame) { + for (size_t channel = 0; channel < src.size; ++channel) { + memcpy(dest, src[channel] + frame * sample_size, + sample_size); + dest += sample_size; + } + } +} + +void +PcmInterleave(void *dest, ConstBuffer src, + size_t n_frames, size_t sample_size) +{ + GenericPcmInterleave((uint8_t *)dest, + ConstBuffer((const uint8_t *const*)src.data, + src.size), + n_frames, sample_size); +} diff --git a/src/pcm/Interleave.hxx b/src/pcm/Interleave.hxx new file mode 100644 index 000000000..c8acbb3e8 --- /dev/null +++ b/src/pcm/Interleave.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2015 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_INTERLEAVE_HXX +#define MPD_PCM_INTERLEAVE_HXX + +#include "check.h" +#include "util/ConstBuffer.hxx" + +/** + * Interleave planar PCM samples from #src to #dest. + */ +void +PcmInterleave(void *dest, ConstBuffer src, + size_t n_frames, size_t sample_size); + +#endif From f31fe8b865fc98d3911cbc8041529cab61852cdc Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 18:08:08 +0200 Subject: [PATCH 27/32] decoder/ffmpeg: include cleanup --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 7f259a1fa..2daa90ce5 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -37,7 +37,6 @@ #include "CheckAudioFormat.hxx" #include "util/ConstBuffer.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "LogV.hxx" extern "C" { From 70986bc12013af7b2b8ed883d0e923eddd4e3a50 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 17:10:39 +0200 Subject: [PATCH 28/32] decoder/ffmpeg: move code to FfmpegSendFrame() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 68 +++++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 2daa90ce5..bfaca8033 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -173,6 +173,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. @@ -205,8 +242,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; @@ -225,32 +260,11 @@ 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; } From d1c5bb956ade7fc414f20c986d3a764c24b86e03 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 14:58:41 +0200 Subject: [PATCH 29/32] decoder/ffmpeg: move code to IsAudio() --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index bfaca8033..3d2f90bc3 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -76,13 +76,19 @@ ffmpeg_init(gcc_unused const config_param ¶m) return true; } +gcc_pure +static bool +IsAudio(const AVStream &stream) +{ + return stream.codec->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; From 8412d94d0580924655eb2f6345be7959166a1d92 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 15:22:40 +0200 Subject: [PATCH 30/32] decoder/ffmpeg: add GetCodecParameters() Preparing for FFmpeg 3.1 support. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 3d2f90bc3..ad6657a2d 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -76,11 +76,25 @@ ffmpeg_init(gcc_unused const config_param ¶m) return true; } +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; +} + gcc_pure static bool IsAudio(const AVStream &stream) { - return stream.codec->codec_type == AVMEDIA_TYPE_AUDIO; + return GetCodecParameters(stream).codec_type == AVMEDIA_TYPE_AUDIO; } gcc_pure @@ -411,10 +425,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) AVStream &av_stream = *format_context->streams[audio_stream]; 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); @@ -424,7 +439,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) 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"); @@ -433,7 +448,7 @@ ffmpeg_decode(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()) avformat_close_input(&format_context); @@ -443,9 +458,9 @@ ffmpeg_decode(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); avformat_close_input(&format_context); return; From a3d020eff9ee88f5f990dbf914c037e285cdba8e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 27 Jul 2016 15:06:42 +0200 Subject: [PATCH 31/32] decoder/ffmpeg: use AVCodecParameters on FFmpeg 3.1 The AVCodecContext attribute is deprecated. --- NEWS | 1 + src/decoder/plugins/FfmpegDecoderPlugin.cxx | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/NEWS b/NEWS index 5470a208c..88ee88970 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ 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 diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index ad6657a2d..92ac764e8 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -76,6 +76,24 @@ ffmpeg_init(gcc_unused const config_param ¶m) 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) @@ -90,6 +108,8 @@ GetSampleFormat(const AVCodecContext &codec_context) return codec_context.sample_fmt; } +#endif + gcc_pure static bool IsAudio(const AVStream &stream) From cafc266e0b54c2ef02ce4529c31d22f9543a3c1f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 19 Dec 2014 09:57:29 +0100 Subject: [PATCH 32/32] decoder/ffmpeg: merge avformat_close_input() calls --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 115 +++++++++++--------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 92ac764e8..b1cb32bed 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -405,44 +405,23 @@ ffmpeg_probe(Decoder *decoder, InputStream &is) } static void -ffmpeg_decode(Decoder &decoder, InputStream &input) +FfmpegDecode(Decoder &decoder, InputStream &input, + AVFormatContext &format_context) { - AVInputFormat *input_format = ffmpeg_probe(&decoder, input); - if (input_format == nullptr) - return; - - FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", - input_format->name, input_format->long_name); - - AvioStream stream(&decoder, input); - if (!stream.Open()) { - LogError(ffmpeg_domain, "Failed to open stream"); - return; - } - - AVFormatContext *format_context = - FfmpegOpenInput(stream.io, input.GetURI(), input_format); - if (format_context == nullptr) { - LogError(ffmpeg_domain, "Open failed"); - return; - } - const int find_result = - avformat_find_stream_info(format_context, nullptr); + avformat_find_stream_info(&format_context, nullptr); if (find_result < 0) { LogError(ffmpeg_domain, "Couldn't find stream info"); - avformat_close_input(&format_context); return; } - int audio_stream = ffmpeg_find_audio_stream(*format_context); + int audio_stream = ffmpeg_find_audio_stream(format_context); if (audio_stream == -1) { LogError(ffmpeg_domain, "No audio stream inside"); - avformat_close_input(&format_context); return; } - AVStream &av_stream = *format_context->streams[audio_stream]; + AVStream &av_stream = *format_context.streams[audio_stream]; AVCodecContext *codec_context = av_stream.codec; const auto &codec_params = GetCodecParameters(av_stream); @@ -463,7 +442,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (!codec) { LogError(ffmpeg_domain, "Unsupported audio codec"); - avformat_close_input(&format_context); return; } @@ -471,7 +449,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) ffmpeg_sample_format(GetSampleFormat(codec_params)); if (sample_format == SampleFormat::UNDEFINED) { // (error message already done by ffmpeg_sample_format()) - avformat_close_input(&format_context); return; } @@ -482,7 +459,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) sample_format, codec_params.channels, error)) { LogError(error); - avformat_close_input(&format_context); return; } @@ -494,7 +470,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) const int open_result = avcodec_open2(codec_context, codec, nullptr); if (open_result < 0) { LogError(ffmpeg_domain, "Could not open codec"); - avformat_close_input(&format_context); return; } @@ -511,7 +486,6 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #endif if (!frame) { LogError(ffmpeg_domain, "Could not allocate frame"); - avformat_close_input(&format_context); return; } @@ -530,7 +504,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) /* AVSEEK_FLAG_BACKWARD asks FFmpeg to seek to the packet boundary before the seek time stamp, not after */ - if (av_seek_frame(format_context, audio_stream, where, + if (av_seek_frame(&format_context, audio_stream, where, AVSEEK_FLAG_ANY|AVSEEK_FLAG_BACKWARD) < 0) decoder_seek_error(decoder); else { @@ -541,7 +515,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) } AVPacket packet; - if (av_read_frame(format_context, &packet) < 0) + if (av_read_frame(&format_context, &packet) < 0) /* end of file */ break; @@ -573,9 +547,61 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #endif avcodec_close(codec_context); +} + +static void +ffmpeg_decode(Decoder &decoder, InputStream &input) +{ + AVInputFormat *input_format = ffmpeg_probe(&decoder, input); + if (input_format == nullptr) + return; + + FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + AvioStream stream(&decoder, input); + if (!stream.Open()) { + LogError(ffmpeg_domain, "Failed to open stream"); + return; + } + + AVFormatContext *format_context = + FfmpegOpenInput(stream.io, input.GetURI(), input_format); + if (format_context == nullptr) { + LogError(ffmpeg_domain, "Open failed"); + return; + } + + FfmpegDecode(decoder, input, *format_context); + avformat_close_input(&format_context); } +static bool +FfmpegScanStream(AVFormatContext &format_context, + const struct tag_handler &handler, void *handler_ctx) +{ + const int find_result = + avformat_find_stream_info(&format_context, nullptr); + if (find_result < 0) + return false; + + if (format_context.duration != (int64_t)AV_NOPTS_VALUE) { + const auto duration = + SongTime::FromScale(format_context.duration, + AV_TIME_BASE); + tag_handler_invoke_duration(&handler, handler_ctx, duration); + } + + FfmpegScanDictionary(format_context.metadata, &handler, handler_ctx); + int idx = ffmpeg_find_audio_stream(format_context); + if (idx >= 0) + FfmpegScanDictionary(format_context.streams[idx]->metadata, + &handler, handler_ctx); + + return FfmpegScanStream(format_context, handler, handler_ctx); +} + static bool ffmpeg_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) @@ -593,28 +619,9 @@ ffmpeg_scan_stream(InputStream &is, if (f == nullptr) return false; - const int find_result = - avformat_find_stream_info(f, nullptr); - if (find_result < 0) { - avformat_close_input(&f); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) { - const auto duration = - SongTime::FromScale(f->duration, - AV_TIME_BASE); - tag_handler_invoke_duration(handler, handler_ctx, duration); - } - - FfmpegScanDictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(*f); - if (idx >= 0) - FfmpegScanDictionary(f->streams[idx]->metadata, - handler, handler_ctx); - + bool result = FfmpegScanStream(*f, *handler, handler_ctx); avformat_close_input(&f); - return true; + return result; } /**