diff --git a/NEWS b/NEWS
index 6a93361b1..421e70da9 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ ver 0.22 (not yet released)
 * input
   - ffmpeg: allow partial reads
 * filter
+  - ffmpeg: new plugin based on FFmpeg's libavfilter library
   - hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback
 
 ver 0.21.7 (not yet released)
diff --git a/doc/plugins.rst b/doc/plugins.rst
index 340bbb5c0..238397558 100644
--- a/doc/plugins.rst
+++ b/doc/plugins.rst
@@ -1078,6 +1078,27 @@ The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
 Filter plugins
 --------------
 
+ffmpeg
+~~~~~~
+
+Decode `HDCD
+<https://en.wikipedia.org/wiki/High_Definition_Compatible_Digital>`_.
+
+This plugin requires building with ``libavfilter`` (FFmpeg).
+
+.. list-table::
+   :widths: 20 80
+   :header-rows: 1
+
+   * - Setting
+     - Description
+   * - **graph "..."**
+     - Specifies the ``libavfilter`` graph; read the `FFmpeg
+       documentation
+       <https://libav.org/documentation/libavfilter.html#Filtergraph-syntax-1>`_
+       for details
+
+
 hdcd
 ~~~~
 
diff --git a/src/filter/FilterRegistry.cxx b/src/filter/FilterRegistry.cxx
index a9995c4ee..3cd421a81 100644
--- a/src/filter/FilterRegistry.cxx
+++ b/src/filter/FilterRegistry.cxx
@@ -22,6 +22,7 @@
 #include "plugins/NullFilterPlugin.hxx"
 #include "plugins/RouteFilterPlugin.hxx"
 #include "plugins/NormalizeFilterPlugin.hxx"
+#include "plugins/FfmpegFilterPlugin.hxx"
 #include "plugins/HdcdFilterPlugin.hxx"
 #include "config.h"
 
@@ -32,6 +33,7 @@ static const FilterPlugin *const filter_plugins[] = {
 	&route_filter_plugin,
 	&normalize_filter_plugin,
 #ifdef HAVE_LIBAVFILTER
+	&ffmpeg_filter_plugin,
 	&hdcd_filter_plugin,
 #endif
 	nullptr,
diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx
new file mode 100644
index 000000000..6efca1398
--- /dev/null
+++ b/src/filter/plugins/FfmpegFilterPlugin.cxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2003-2019 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 "FfmpegFilterPlugin.hxx"
+#include "FfmpegFilter.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/Filter.hxx"
+#include "filter/Prepared.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "lib/ffmpeg/Filter.hxx"
+#include "config/Block.hxx"
+#include "AudioFormat.hxx"
+
+class PreparedFfmpegFilter final : public PreparedFilter {
+	const char *const graph_string;
+
+public:
+	explicit PreparedFfmpegFilter(const char *_graph) noexcept
+		:graph_string(_graph) {}
+
+	/* virtual methods from class PreparedFilter */
+	std::unique_ptr<Filter> Open(AudioFormat &af) override;
+};
+
+std::unique_ptr<Filter>
+PreparedFfmpegFilter::Open(AudioFormat &in_audio_format)
+{
+	Ffmpeg::FilterGraph graph;
+
+	auto buffer_src =
+		Ffmpeg::FilterContext::MakeAudioBufferSource(in_audio_format,
+							     *graph);
+
+	auto buffer_sink = Ffmpeg::FilterContext::MakeAudioBufferSink(*graph);
+
+	Ffmpeg::FilterInOut io_sink("out", *buffer_sink);
+	Ffmpeg::FilterInOut io_src("in", *buffer_src);
+	auto io = graph.Parse(graph_string, std::move(io_sink),
+			      std::move(io_src));
+
+	if (io.first.get() != nullptr)
+		throw std::runtime_error("FFmpeg filter has an open input");
+
+	if (io.second.get() != nullptr)
+		throw std::runtime_error("FFmpeg filter has an open output");
+
+	graph.CheckAndConfigure();
+
+	auto out_audio_format = in_audio_format; // TODO
+
+	return std::make_unique<FfmpegFilter>(in_audio_format,
+					      out_audio_format,
+					      std::move(graph),
+					      std::move(buffer_src),
+					      std::move(buffer_sink));
+}
+
+static std::unique_ptr<PreparedFilter>
+ffmpeg_filter_init(const ConfigBlock &block)
+{
+	const char *graph = block.GetBlockValue("graph");
+	if (graph == nullptr)
+		throw std::runtime_error("Missing \"graph\" configuration");
+
+	/* check if the graph can be parsed (and discard the
+	   object) */
+	Ffmpeg::FilterGraph().Parse(graph);
+
+	return std::make_unique<PreparedFfmpegFilter>(graph);
+}
+
+const FilterPlugin ffmpeg_filter_plugin = {
+	"ffmpeg",
+	ffmpeg_filter_init,
+};
diff --git a/src/filter/plugins/FfmpegFilterPlugin.hxx b/src/filter/plugins/FfmpegFilterPlugin.hxx
new file mode 100644
index 000000000..92c048820
--- /dev/null
+++ b/src/filter/plugins/FfmpegFilterPlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2003-2019 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_FILTER_PLUGIN_HXX
+#define MPD_FFMPEG_FILTER_PLUGIN_HXX
+
+struct FilterPlugin;
+
+extern const FilterPlugin ffmpeg_filter_plugin;
+
+#endif
diff --git a/src/filter/plugins/meson.build b/src/filter/plugins/meson.build
index b9130a090..e25193092 100644
--- a/src/filter/plugins/meson.build
+++ b/src/filter/plugins/meson.build
@@ -4,6 +4,7 @@ filter_plugins_deps = []
 if libavfilter_dep.found()
   filter_plugins_sources += [
     'FfmpegFilter.cxx',
+    'FfmpegFilterPlugin.cxx',
     'HdcdFilterPlugin.cxx',
   ]
   filter_plugins_deps += ffmpeg_dep