From 4fed0b991c039f7c296445ca2207b779fdb736ce Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 &param)
 	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 <max@duempel.org>
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 <max@duempel.org>
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 <cstdint> /* needed due to libavutil bug */
+
+extern "C" {
+#include <libavutil/error.h>
+}
+
+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 <max@duempel.org>
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<uint8_t>(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 <libavutil/mem.h>
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0)
+#define HAVE_AV_FAST_MALLOC
+#else
+#include <libavcodec/avcodec.h>
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0)
+#define HAVE_AV_FAST_MALLOC
+#endif
+#endif
+}
+
+#include <stddef.h>
+
+/* 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<typename T>
+	T *GetT(size_t n) {
+		return (T *)Get(n * sizeof(T));
+	}
+};
+
+#endif

From eb192137d61b0c62541904351660f9cd63bc90d5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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<uint8_t>(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 <max@duempel.org>
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<void>
 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<uint8_t>(data_size);
-		if (*output_buffer == nullptr) {
+		output_buffer = global_buffer.GetT<uint8_t>(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 <max@duempel.org>
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<void>
 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 <max@duempel.org>
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 <max@duempel.org>
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 <libavformat/avio.h>
 #include <libavutil/avutil.h>
 #include <libavutil/log.h>
-#include <libavutil/mathematics.h>
 
 #if LIBAVUTIL_VERSION_MAJOR >= 53
 #include <libavutil/frame.h>
@@ -52,11 +52,6 @@ extern "C" {
 #include <assert.h>
 #include <string.h>
 
-/* 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<typename Ratio>
-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<SongTime::period>(),
-			    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 <libavutil/avutil.h>
+#include <libavutil/mathematics.h>
+}
+
+#include <assert.h>
+#include <stdint.h>
+
+/* 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<typename Ratio>
+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<SongTime::period>(),
+			    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 <max@duempel.org>
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 <max@duempel.org>
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<uint64_t>(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 <max@duempel.org>
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 <stdint.h>
+
+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 <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 <assert.h>
 #include <string.h>
 
-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 &param)
 {
-	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 <libavutil/log.h>
+}
+
+#include <stdio.h>
+
+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 <stdarg.h>
+
+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 <max@duempel.org>
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 &param)
 {
-	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 <libavformat/avformat.h>
+}
+
+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 <max@duempel.org>
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 <assert.h>
 #include <string.h>
 
-/**
- * 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 <max@duempel.org>
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 <max@duempel.org>
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 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 <stdint.h>
+#include <string.h>
+
+static void
+GenericPcmInterleave(uint8_t *dest, ConstBuffer<const uint8_t *> 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<const void *> src,
+	      size_t n_frames, size_t sample_size)
+{
+	GenericPcmInterleave((uint8_t *)dest,
+			     ConstBuffer<const uint8_t *>((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<const void *> src,
+	      size_t n_frames, size_t sample_size);
+
+#endif

From f31fe8b865fc98d3911cbc8041529cab61852cdc Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
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 <max@duempel.org>
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 <max@duempel.org>
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 &param)
 	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 <max@duempel.org>
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 &param)
 	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 <max@duempel.org>
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 &param)
 	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 <max@duempel.org>
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<uint64_t>(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<uint64_t>(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;
 }
 
 /**