From 3a212412487e8868946e7c845b6830f880790f91 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 22 Jun 2016 11:15:49 +0200
Subject: [PATCH] filter/FilterInternal: split class Filter, add class
 PreparedFilter

For easier state management inside filter plugins.
---
 Makefile.am                                   |   2 +
 src/filter/FilterConfig.cxx                   |   6 +-
 src/filter/FilterConfig.hxx                   |   4 +-
 src/filter/FilterInternal.hxx                 |  50 ++++---
 src/filter/FilterPlugin.cxx                   |   4 +-
 src/filter/FilterPlugin.hxx                   |   8 +-
 src/filter/Observer.cxx                       | 115 ++++++++++++++++
 src/filter/Observer.hxx                       |  47 +++++++
 .../plugins/AutoConvertFilterPlugin.cxx       |  85 +++++-------
 .../plugins/AutoConvertFilterPlugin.hxx       |   6 +-
 src/filter/plugins/ChainFilterPlugin.cxx      | 126 +++++++++---------
 src/filter/plugins/ChainFilterPlugin.hxx      |   7 +-
 src/filter/plugins/ConvertFilterPlugin.cxx    |  78 ++++++-----
 src/filter/plugins/ConvertFilterPlugin.hxx    |   5 +
 src/filter/plugins/NormalizeFilterPlugin.cxx  |  37 ++---
 src/filter/plugins/NullFilterPlugin.cxx       |  19 +--
 src/filter/plugins/ReplayGainFilterPlugin.cxx |  72 ++++++----
 src/filter/plugins/ReplayGainFilterPlugin.hxx |   3 +-
 src/filter/plugins/RouteFilterPlugin.cxx      | 108 ++++++++-------
 src/filter/plugins/VolumeFilterPlugin.cxx     |  40 +++---
 src/output/Finish.cxx                         |   6 +-
 src/output/Init.cxx                           |  56 ++++----
 src/output/Internal.hxx                       |  21 ++-
 src/output/OutputControl.cxx                  |   9 --
 src/output/OutputThread.cxx                   |  64 +++++----
 test/run_filter.cxx                           |  25 ++--
 26 files changed, 615 insertions(+), 388 deletions(-)
 create mode 100644 src/filter/Observer.cxx
 create mode 100644 src/filter/Observer.hxx

diff --git a/Makefile.am b/Makefile.am
index dd47df7e8..4345a65b4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -108,6 +108,7 @@ libmpd_a_SOURCES = \
 	src/filter/FilterPlugin.cxx src/filter/FilterPlugin.hxx \
 	src/filter/FilterInternal.hxx \
 	src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
+	src/filter/Observer.cxx src/filter/Observer.hxx \
 	src/client/Client.cxx src/client/Client.hxx \
 	src/client/ClientInternal.hxx \
 	src/client/ClientEvent.cxx \
@@ -2068,6 +2069,7 @@ test_run_output_SOURCES = test/run_output.cxx \
 	src/mixer/MixerType.cxx \
 	src/filter/FilterPlugin.cxx \
 	src/filter/FilterConfig.cxx \
+	src/filter/Observer.cxx \
 	src/ReplayGainInfo.cxx
 
 test_read_mixer_LDADD = \
diff --git a/src/filter/FilterConfig.cxx b/src/filter/FilterConfig.cxx
index fa5fcc8ff..236297eba 100644
--- a/src/filter/FilterConfig.cxx
+++ b/src/filter/FilterConfig.cxx
@@ -33,7 +33,7 @@
 #include <string.h>
 
 static bool
-filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
+filter_chain_append_new(PreparedFilter &chain, const char *template_name, Error &error)
 {
 	const auto *cfg = config_find_block(ConfigBlockOption::AUDIO_FILTER,
 					    "name", template_name);
@@ -45,7 +45,7 @@ filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
 	}
 
 	// Instantiate one of those filter plugins with the template name as a hint
-	Filter *f = filter_configured_new(*cfg, error);
+	PreparedFilter *f = filter_configured_new(*cfg, error);
 	if (f == nullptr)
 		// The error has already been set, just stop.
 		return false;
@@ -58,7 +58,7 @@ filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
 }
 
 bool
-filter_chain_parse(Filter &chain, const char *spec, Error &error)
+filter_chain_parse(PreparedFilter &chain, const char *spec, Error &error)
 {
 	const char *const end = spec + strlen(spec);
 
diff --git a/src/filter/FilterConfig.hxx b/src/filter/FilterConfig.hxx
index cd0455fef..6d6fb4744 100644
--- a/src/filter/FilterConfig.hxx
+++ b/src/filter/FilterConfig.hxx
@@ -25,7 +25,7 @@
 #ifndef MPD_FILTER_CONFIG_HXX
 #define MPD_FILTER_CONFIG_HXX
 
-class Filter;
+class PreparedFilter;
 class Error;
 
 /**
@@ -38,6 +38,6 @@ class Error;
  * @return true on success
  */
 bool
-filter_chain_parse(Filter &chain, const char *spec, Error &error);
+filter_chain_parse(PreparedFilter &chain, const char *spec, Error &error);
 
 #endif
diff --git a/src/filter/FilterInternal.hxx b/src/filter/FilterInternal.hxx
index 5b204b7b0..701f73b5e 100644
--- a/src/filter/FilterInternal.hxx
+++ b/src/filter/FilterInternal.hxx
@@ -25,6 +25,8 @@
 #ifndef MPD_FILTER_INTERNAL_HXX
 #define MPD_FILTER_INTERNAL_HXX
 
+#include "AudioFormat.hxx"
+
 #include <stddef.h>
 
 struct AudioFormat;
@@ -32,9 +34,39 @@ class Error;
 template<typename T> struct ConstBuffer;
 
 class Filter {
+protected:
+	AudioFormat out_audio_format;
+
+	Filter() = default;
+	explicit Filter(AudioFormat _out_audio_format)
+		:out_audio_format(_out_audio_format) {}
+
 public:
 	virtual ~Filter() {}
 
+	/**
+	 * Returns the #AudioFormat produced by FilterPCM().
+	 */
+	const AudioFormat &GetOutAudioFormat() const {
+		return out_audio_format;
+	}
+
+	/**
+	 * Filters a block of PCM data.
+	 *
+	 * @param src the input buffer
+	 * @param error location to store the error occurring
+	 * @return the destination buffer on success (will be
+	 * invalidated by deleting this object or the next FilterPCM()
+	 * call), nullptr on error
+	 */
+	virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0;
+};
+
+class PreparedFilter {
+public:
+	virtual ~PreparedFilter() {}
+
 	/**
 	 * Opens the filter, preparing it for FilterPCM().
 	 *
@@ -45,23 +77,7 @@ public:
 	 * @return the format of outgoing data or
 	 * AudioFormat::Undefined() on error
 	 */
-	virtual AudioFormat Open(AudioFormat &af, Error &error) = 0;
-
-	/**
-	 * Closes the filter.  After that, you may call Open() again.
-	 */
-	virtual void Close() = 0;
-
-	/**
-	 * Filters a block of PCM data.
-	 *
-	 * @param src the input buffer
-	 * @param error location to store the error occurring
-	 * @return the destination buffer on success (will be
-	 * invalidated by Close() or FilterPCM()), nullptr on
-	 * error
-	 */
-	virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0;
+	virtual Filter *Open(AudioFormat &af, Error &error) = 0;
 };
 
 #endif
diff --git a/src/filter/FilterPlugin.cxx b/src/filter/FilterPlugin.cxx
index 6e21ca662..f43fee06c 100644
--- a/src/filter/FilterPlugin.cxx
+++ b/src/filter/FilterPlugin.cxx
@@ -26,7 +26,7 @@
 
 #include <assert.h>
 
-Filter *
+PreparedFilter *
 filter_new(const struct filter_plugin *plugin,
 	   const ConfigBlock &block, Error &error)
 {
@@ -36,7 +36,7 @@ filter_new(const struct filter_plugin *plugin,
 	return plugin->init(block, error);
 }
 
-Filter *
+PreparedFilter *
 filter_configured_new(const ConfigBlock &block, Error &error)
 {
 	assert(!error.IsDefined());
diff --git a/src/filter/FilterPlugin.hxx b/src/filter/FilterPlugin.hxx
index 8c1f9da35..2ebca232b 100644
--- a/src/filter/FilterPlugin.hxx
+++ b/src/filter/FilterPlugin.hxx
@@ -27,7 +27,7 @@
 #define MPD_FILTER_PLUGIN_HXX
 
 struct ConfigBlock;
-class Filter;
+class PreparedFilter;
 class Error;
 
 struct filter_plugin {
@@ -36,7 +36,7 @@ struct filter_plugin {
 	/**
          * Allocates and configures a filter.
 	 */
-	Filter *(*init)(const ConfigBlock &block, Error &error);
+	PreparedFilter *(*init)(const ConfigBlock &block, Error &error);
 };
 
 /**
@@ -48,7 +48,7 @@ struct filter_plugin {
  * ignore errors.
  * @return a new filter object, or nullptr on error
  */
-Filter *
+PreparedFilter *
 filter_new(const struct filter_plugin *plugin,
 	   const ConfigBlock &block, Error &error);
 
@@ -61,7 +61,7 @@ filter_new(const struct filter_plugin *plugin,
  * ignore errors.
  * @return a new filter object, or nullptr on error
  */
-Filter *
+PreparedFilter *
 filter_configured_new(const ConfigBlock &block, Error &error);
 
 #endif
diff --git a/src/filter/Observer.cxx b/src/filter/Observer.cxx
new file mode 100644
index 000000000..b3cadfb30
--- /dev/null
+++ b/src/filter/Observer.cxx
@@ -0,0 +1,115 @@
+/*
+ * Copyright 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.
+ */
+
+#include "config.h"
+#include "Observer.hxx"
+#include "FilterInternal.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <assert.h>
+
+class FilterObserver::PreparedProxy final : public PreparedFilter {
+	FilterObserver &observer;
+
+	PreparedFilter *const prepared_filter;
+	Proxy *child = nullptr;
+
+public:
+	PreparedProxy(FilterObserver &_observer,
+		      PreparedFilter *_prepared_filter)
+		:observer(_observer),
+		 prepared_filter(_prepared_filter) {}
+
+	~PreparedProxy() {
+		assert(child == nullptr);
+		assert(observer.proxy == this);
+
+		observer.proxy = nullptr;
+	}
+
+	void Clear(gcc_unused Proxy *_child) {
+		assert(child == _child);
+		child = nullptr;
+	}
+
+	Filter *Get();
+
+	Filter *Open(AudioFormat &af, Error &error) override;
+};
+
+class FilterObserver::Proxy final : public Filter {
+	PreparedProxy &parent;
+
+	Filter *const filter;
+
+public:
+	Proxy(PreparedProxy &_parent, Filter *_filter)
+		:Filter(_filter->GetOutAudioFormat()),
+		 parent(_parent), filter(_filter) {}
+
+	~Proxy() {
+		parent.Clear(this);
+		delete filter;
+	}
+
+	Filter *Get() {
+		return filter;
+	}
+
+	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+				    Error &error) override {
+		return filter->FilterPCM(src, error);
+	}
+};
+
+Filter *
+FilterObserver::PreparedProxy::Get()
+{
+	return child != nullptr
+		? child->Get()
+		: nullptr;
+}
+
+Filter *
+FilterObserver::PreparedProxy::Open(AudioFormat &af, Error &error)
+{
+	assert(child == nullptr);
+
+	Filter *f = prepared_filter->Open(af, error);
+	if (f == nullptr)
+		return f;
+
+	return child = new Proxy(*this, f);
+}
+
+PreparedFilter *
+FilterObserver::Set(PreparedFilter *pf)
+{
+	assert(proxy == nullptr);
+
+	return proxy = new PreparedProxy(*this, pf);
+}
+
+Filter *
+FilterObserver::Get()
+{
+	return proxy != nullptr
+		? proxy->Get()
+		: nullptr;
+}
diff --git a/src/filter/Observer.hxx b/src/filter/Observer.hxx
new file mode 100644
index 000000000..aad0c7927
--- /dev/null
+++ b/src/filter/Observer.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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_FILTER_OBSERVER_HXX
+#define MPD_FILTER_OBSERVER_HXX
+
+#include "check.h"
+
+class PreparedFilter;
+class Filter;
+
+/**
+ * A helper class which observes calls to a #PreparedFilter and allows
+ * the caller to access the #Filter instances created by it.
+ */
+class FilterObserver {
+	class PreparedProxy;
+	class Proxy;
+
+	PreparedProxy *proxy = nullptr;
+
+public:
+	/**
+	 * @return a proxy object
+	 */
+	PreparedFilter *Set(PreparedFilter *pf);
+
+	Filter *Get();
+};
+
+#endif
diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx
index 2400d6dd4..e3639e181 100644
--- a/src/filter/plugins/AutoConvertFilterPlugin.cxx
+++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx
@@ -24,7 +24,6 @@
 #include "filter/FilterInternal.hxx"
 #include "filter/FilterRegistry.hxx"
 #include "AudioFormat.hxx"
-#include "config/Block.hxx"
 #include "util/ConstBuffer.hxx"
 
 #include <assert.h>
@@ -33,82 +32,70 @@ class AutoConvertFilter final : public Filter {
 	/**
 	 * The underlying filter.
 	 */
-	Filter *filter;
+	Filter *const filter;
 
 	/**
 	 * A convert_filter, just in case conversion is needed.  nullptr
 	 * if unused.
 	 */
-	Filter *convert;
+	Filter *const convert;
 
 public:
-	AutoConvertFilter(Filter *_filter):filter(_filter) {}
+	AutoConvertFilter(Filter *_filter, Filter *_convert)
+		:filter(_filter), convert(_convert) {}
+
 	~AutoConvertFilter() {
+		delete convert;
 		delete filter;
 	}
 
-	virtual AudioFormat Open(AudioFormat &af, Error &error) override;
-	virtual void Close() override;
 	virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 					    Error &error) override;
 };
 
-AudioFormat
-AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
+class PreparedAutoConvertFilter final : public PreparedFilter {
+	/**
+	 * The underlying filter.
+	 */
+	PreparedFilter *const filter;
+
+public:
+	PreparedAutoConvertFilter(PreparedFilter *_filter):filter(_filter) {}
+	~PreparedAutoConvertFilter() {
+		delete filter;
+	}
+
+	virtual Filter *Open(AudioFormat &af, Error &error) override;
+};
+
+Filter *
+PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
 {
 	assert(in_audio_format.IsValid());
 
 	/* open the "real" filter */
 
 	AudioFormat child_audio_format = in_audio_format;
-	AudioFormat out_audio_format = filter->Open(child_audio_format, error);
-	if (!out_audio_format.IsDefined())
-		return out_audio_format;
+	auto *new_filter = filter->Open(child_audio_format, error);
+	if (new_filter == nullptr)
+		return nullptr;
 
 	/* need to convert? */
 
+	Filter *convert = nullptr;
 	if (in_audio_format != child_audio_format) {
 		/* yes - create a convert_filter */
 
-		const ConfigBlock empty;
-		convert = filter_new(&convert_filter_plugin, empty, error);
+		convert = convert_filter_new(in_audio_format,
+					     child_audio_format,
+					     error);
 		if (convert == nullptr) {
-			filter->Close();
-			return AudioFormat::Undefined();
+			delete new_filter;
+			return nullptr;
 		}
-
-		AudioFormat audio_format2 = in_audio_format;
-		AudioFormat audio_format3 =
-			convert->Open(audio_format2, error);
-		if (!audio_format3.IsDefined()) {
-			delete convert;
-			filter->Close();
-			return AudioFormat::Undefined();
-		}
-
-		assert(audio_format2 == in_audio_format);
-
-		if (!convert_filter_set(convert, child_audio_format, error)) {
-			delete convert;
-			filter->Close();
-			return AudioFormat::Undefined();
-		}
-	} else
-		/* no */
-		convert = nullptr;
-
-	return out_audio_format;
-}
-
-void
-AutoConvertFilter::Close()
-{
-	if (convert != nullptr) {
-		convert->Close();
-		delete convert;
 	}
 
-	filter->Close();
+	return new AutoConvertFilter(new_filter, convert);
 }
 
 ConstBuffer<void>
@@ -123,8 +110,8 @@ AutoConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
 	return filter->FilterPCM(src, error);
 }
 
-Filter *
-autoconvert_filter_new(Filter *filter)
+PreparedFilter *
+autoconvert_filter_new(PreparedFilter *filter)
 {
-	return new AutoConvertFilter(filter);
+	return new PreparedAutoConvertFilter(filter);
 }
diff --git a/src/filter/plugins/AutoConvertFilterPlugin.hxx b/src/filter/plugins/AutoConvertFilterPlugin.hxx
index acec195a1..6b831d3ce 100644
--- a/src/filter/plugins/AutoConvertFilterPlugin.hxx
+++ b/src/filter/plugins/AutoConvertFilterPlugin.hxx
@@ -20,7 +20,7 @@
 #ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
 #define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
 
-class Filter;
+class PreparedFilter;
 
 /**
  * Creates a new "autoconvert" filter.  When opened, it ensures that
@@ -28,7 +28,7 @@ class Filter;
  * requests a different format, it automatically creates a
  * convert_filter.
  */
-Filter *
-autoconvert_filter_new(Filter *filter);
+PreparedFilter *
+autoconvert_filter_new(PreparedFilter *filter);
 
 #endif
diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx
index 8bacc2a1f..4b79baf5b 100644
--- a/src/filter/plugins/ChainFilterPlugin.cxx
+++ b/src/filter/plugins/ChainFilterPlugin.cxx
@@ -27,6 +27,7 @@
 #include "util/Domain.hxx"
 #include "util/ConstBuffer.hxx"
 
+#include <memory>
 #include <list>
 
 #include <assert.h>
@@ -49,100 +50,98 @@ class ChainFilter final : public Filter {
 	std::list<Child> children;
 
 public:
+	explicit ChainFilter(AudioFormat _audio_format)
+		:Filter(_audio_format) {}
+
 	void Append(const char *name, Filter *filter) {
+		assert(out_audio_format.IsValid());
+		out_audio_format = filter->GetOutAudioFormat();
+		assert(out_audio_format.IsValid());
+
 		children.emplace_back(name, filter);
 	}
 
 	/* virtual methods from class Filter */
-	AudioFormat Open(AudioFormat &af, Error &error) override;
-	void Close() override;
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 					    Error &error) override;
+};
 
-private:
-	/**
-	 * Close all filters in the chain until #until is reached.
-	 * #until itself is not closed.
-	 */
-	void CloseUntil(const Filter *until);
+class PreparedChainFilter final : public PreparedFilter {
+	struct Child {
+		const char *name;
+		PreparedFilter *filter;
+
+		Child(const char *_name, PreparedFilter *_filter)
+			:name(_name), filter(_filter) {}
+		~Child() {
+			delete filter;
+		}
+
+		Child(const Child &) = delete;
+		Child &operator=(const Child &) = delete;
+
+		Filter *Open(const AudioFormat &prev_audio_format,
+			     Error &error);
+	};
+
+	std::list<Child> children;
+
+public:
+	void Append(const char *name, PreparedFilter *filter) {
+		children.emplace_back(name, filter);
+	}
+
+	/* virtual methods from class PreparedFilter */
+	Filter *Open(AudioFormat &af, Error &error) override;
 };
 
 static constexpr Domain chain_filter_domain("chain_filter");
 
-static Filter *
+static PreparedFilter *
 chain_filter_init(gcc_unused const ConfigBlock &block,
 		  gcc_unused Error &error)
 {
-	return new ChainFilter();
+	return new PreparedChainFilter();
 }
 
-void
-ChainFilter::CloseUntil(const Filter *until)
-{
-	for (auto &child : children) {
-		if (child.filter == until)
-			/* don't close this filter */
-			return;
-
-		/* close this filter */
-		child.filter->Close();
-	}
-
-	/* this assertion fails if #until does not exist (anymore) */
-	assert(false);
-	gcc_unreachable();
-}
-
-static AudioFormat
-chain_open_child(const char *name, Filter *filter,
-		 const AudioFormat &prev_audio_format,
-		 Error &error)
+Filter *
+PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format,
+				 Error &error)
 {
 	AudioFormat conv_audio_format = prev_audio_format;
-	const AudioFormat next_audio_format =
-		filter->Open(conv_audio_format, error);
-	if (!next_audio_format.IsDefined())
-		return next_audio_format;
+	Filter *new_filter = filter->Open(conv_audio_format, error);
+	if (new_filter == nullptr)
+		return nullptr;
 
 	if (conv_audio_format != prev_audio_format) {
+		delete new_filter;
+
 		struct audio_format_string s;
-
-		filter->Close();
-
 		error.Format(chain_filter_domain,
 			     "Audio format not supported by filter '%s': %s",
 			     name,
 			     audio_format_to_string(prev_audio_format, &s));
-		return AudioFormat::Undefined();
+		return nullptr;
 	}
 
-	return next_audio_format;
+	return new_filter;
 }
 
-AudioFormat
-ChainFilter::Open(AudioFormat &in_audio_format, Error &error)
+Filter *
+PreparedChainFilter::Open(AudioFormat &in_audio_format, Error &error)
 {
-	AudioFormat audio_format = in_audio_format;
+	std::unique_ptr<ChainFilter> chain(new ChainFilter(in_audio_format));
 
 	for (auto &child : children) {
-		audio_format = chain_open_child(child.name, child.filter,
-						audio_format, error);
-		if (!audio_format.IsDefined()) {
-			/* rollback, close all children */
-			CloseUntil(child.filter);
-			break;
-		}
+		AudioFormat audio_format = chain->GetOutAudioFormat();
+		auto *filter = child.Open(audio_format, error);
+		if (filter == nullptr)
+			return nullptr;
+
+		chain->Append(child.name, filter);
 	}
 
-	/* return the output format of the last filter */
-	return audio_format;
-}
-
-void
-ChainFilter::Close()
-{
-	for (auto &child : children)
-		child.filter->Close();
+	return chain.release();
 }
 
 ConstBuffer<void>
@@ -165,16 +164,17 @@ const struct filter_plugin chain_filter_plugin = {
 	chain_filter_init,
 };
 
-Filter *
+PreparedFilter *
 filter_chain_new(void)
 {
-	return new ChainFilter();
+	return new PreparedChainFilter();
 }
 
 void
-filter_chain_append(Filter &_chain, const char *name, Filter *filter)
+filter_chain_append(PreparedFilter &_chain, const char *name,
+		    PreparedFilter *filter)
 {
-	ChainFilter &chain = (ChainFilter &)_chain;
+	PreparedChainFilter &chain = (PreparedChainFilter &)_chain;
 
 	chain.Append(name, filter);
 }
diff --git a/src/filter/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx
index 6667127a2..5b29db920 100644
--- a/src/filter/plugins/ChainFilterPlugin.hxx
+++ b/src/filter/plugins/ChainFilterPlugin.hxx
@@ -27,12 +27,12 @@
 #ifndef MPD_FILTER_CHAIN_HXX
 #define MPD_FILTER_CHAIN_HXX
 
-class Filter;
+class PreparedFilter;
 
 /**
  * Creates a new filter chain.
  */
-Filter *
+PreparedFilter *
 filter_chain_new();
 
 /**
@@ -43,6 +43,7 @@ filter_chain_new();
  * @param filter the filter to be appended to #chain
  */
 void
-filter_chain_append(Filter &chain, const char *name, Filter *filter);
+filter_chain_append(PreparedFilter &chain, const char *name,
+		    PreparedFilter *filter);
 
 #endif
diff --git a/src/filter/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx
index e84cdf14b..b8a1b905d 100644
--- a/src/filter/plugins/ConvertFilterPlugin.cxx
+++ b/src/filter/plugins/ConvertFilterPlugin.cxx
@@ -38,31 +38,33 @@ class ConvertFilter final : public Filter {
 	AudioFormat in_audio_format;
 
 	/**
-	 * The output audio format; the consumer of this plugin
-	 * expects PCM data in this format.
-	 *
-	 * If this is AudioFormat::Undefined(), then the #PcmConvert
-	 * attribute is not open.  This can mean that Set() has failed
-	 * or that no conversion is necessary.
+	 * This object is only "open" if #in_audio_format !=
+	 * #out_audio_format.
 	 */
-	AudioFormat out_audio_format;
-
-	Manual<PcmConvert> state;
+	PcmConvert state;
 
 public:
+	ConvertFilter(const AudioFormat &audio_format);
+	~ConvertFilter();
+
 	bool Set(const AudioFormat &_out_audio_format, Error &error);
 
-	virtual AudioFormat Open(AudioFormat &af, Error &error) override;
-	virtual void Close() override;
 	virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 					    Error &error) override;
 };
 
-static Filter *
+class PreparedConvertFilter final : public PreparedFilter {
+public:
+	bool Set(const AudioFormat &_out_audio_format, Error &error);
+
+	Filter *Open(AudioFormat &af, Error &error) override;
+};
+
+static PreparedFilter *
 convert_filter_init(gcc_unused const ConfigBlock &block,
 		    gcc_unused Error &error)
 {
-	return new ConvertFilter();
+	return new PreparedConvertFilter();
 }
 
 bool
@@ -75,47 +77,41 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error)
 		/* no change */
 		return true;
 
-	if (out_audio_format.IsValid()) {
-		out_audio_format.Clear();
-		state->Close();
+	if (out_audio_format != in_audio_format) {
+		out_audio_format = in_audio_format;
+		state.Close();
 	}
 
 	if (_out_audio_format == in_audio_format)
 		/* optimized special case: no-op */
 		return true;
 
-	if (!state->Open(in_audio_format, _out_audio_format, error))
+	if (!state.Open(in_audio_format, _out_audio_format, error))
 		return false;
 
 	out_audio_format = _out_audio_format;
 	return true;
 }
 
-AudioFormat
-ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+ConvertFilter::ConvertFilter(const AudioFormat &audio_format)
+	:Filter(audio_format), in_audio_format(audio_format)
+{
+}
+
+Filter *
+PreparedConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
 {
 	assert(audio_format.IsValid());
 
-	in_audio_format = audio_format;
-	out_audio_format.Clear();
-
-	state.Construct();
-
-	return in_audio_format;
+	return new ConvertFilter(audio_format);
 }
 
-void
-ConvertFilter::Close()
+ConvertFilter::~ConvertFilter()
 {
 	assert(in_audio_format.IsValid());
 
 	if (out_audio_format.IsValid())
-		state->Close();
-
-	state.Destruct();
-
-	poison_undefined(&in_audio_format, sizeof(in_audio_format));
-	poison_undefined(&out_audio_format, sizeof(out_audio_format));
+		state.Close();
 }
 
 ConstBuffer<void>
@@ -127,7 +123,7 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
 		/* optimized special case: no-op */
 		return src;
 
-	return state->Convert(src, error);
+	return state.Convert(src, error);
 }
 
 const struct filter_plugin convert_filter_plugin = {
@@ -135,6 +131,20 @@ const struct filter_plugin convert_filter_plugin = {
 	convert_filter_init,
 };
 
+Filter *
+convert_filter_new(const AudioFormat in_audio_format,
+		   const AudioFormat out_audio_format,
+		   Error &error)
+{
+	auto *filter = new ConvertFilter(in_audio_format);
+	if (!filter->Set(out_audio_format, error)) {
+		delete filter;
+		return nullptr;
+	}
+
+	return filter;
+}
+
 bool
 convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
 		   Error &error)
diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx
index a514ff388..6bdd5ef1b 100644
--- a/src/filter/plugins/ConvertFilterPlugin.hxx
+++ b/src/filter/plugins/ConvertFilterPlugin.hxx
@@ -24,6 +24,11 @@ class Filter;
 class Error;
 struct AudioFormat;
 
+Filter *
+convert_filter_new(AudioFormat in_audio_format,
+		   AudioFormat out_audio_format,
+		   Error &error);
+
 /**
  * Sets the output audio format for the specified filter.  You must
  * call this after the filter has been opened.  Since this audio
diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx
index 5f66330bf..e83b6553d 100644
--- a/src/filter/plugins/NormalizeFilterPlugin.cxx
+++ b/src/filter/plugins/NormalizeFilterPlugin.cxx
@@ -29,40 +29,43 @@
 #include <string.h>
 
 class NormalizeFilter final : public Filter {
-	struct Compressor *compressor;
+	Compressor *const compressor;
 
 	PcmBuffer buffer;
 
 public:
+	NormalizeFilter(const AudioFormat &audio_format)
+		:Filter(audio_format), compressor(Compressor_new(0)) {
+	}
+
+	~NormalizeFilter() {
+		Compressor_delete(compressor);
+	}
+
 	/* virtual methods from class Filter */
-	AudioFormat Open(AudioFormat &af, Error &error) override;
-	void Close() override;
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 				    Error &error) override;
 };
 
-static Filter *
+class PreparedNormalizeFilter final : public PreparedFilter {
+public:
+	/* virtual methods from class PreparedFilter */
+	Filter *Open(AudioFormat &af, Error &error) override;
+};
+
+static PreparedFilter *
 normalize_filter_init(gcc_unused const ConfigBlock &block,
 		      gcc_unused Error &error)
 {
-	return new NormalizeFilter();
+	return new PreparedNormalizeFilter();
 }
 
-AudioFormat
-NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+Filter *
+PreparedNormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
 {
 	audio_format.format = SampleFormat::S16;
 
-	compressor = Compressor_new(0);
-
-	return audio_format;
-}
-
-void
-NormalizeFilter::Close()
-{
-	buffer.Clear();
-	Compressor_delete(compressor);
+	return new NormalizeFilter(audio_format);
 }
 
 ConstBuffer<void>
diff --git a/src/filter/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx
index 26926f752..fc43bb7d1 100644
--- a/src/filter/plugins/NullFilterPlugin.cxx
+++ b/src/filter/plugins/NullFilterPlugin.cxx
@@ -34,12 +34,7 @@
 
 class NullFilter final : public Filter {
 public:
-	virtual AudioFormat Open(AudioFormat &af,
-				 gcc_unused Error &error) override {
-		return af;
-	}
-
-	virtual void Close() override {}
+	explicit NullFilter(const AudioFormat &af):Filter(af) {}
 
 	virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 					    gcc_unused Error &error) override {
@@ -47,11 +42,19 @@ public:
 	}
 };
 
-static Filter *
+class PreparedNullFilter final : public PreparedFilter {
+public:
+	virtual Filter *Open(AudioFormat &af,
+			     gcc_unused Error &error) override {
+		return new NullFilter(af);
+	}
+};
+
+static PreparedFilter *
 null_filter_init(gcc_unused const ConfigBlock &block,
 		 gcc_unused Error &error)
 {
-	return new NullFilter();
+	return new PreparedNullFilter();
 }
 
 const struct filter_plugin null_filter_plugin = {
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx
index b9527531e..7512c180d 100644
--- a/src/filter/plugins/ReplayGainFilterPlugin.cxx
+++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx
@@ -41,13 +41,13 @@ class ReplayGainFilter final : public Filter {
 	 * If set, then this hardware mixer is used for applying
 	 * replay gain, instead of the software volume library.
 	 */
-	Mixer *mixer = nullptr;
+	Mixer *const mixer;
 
 	/**
 	 * The base volume level for scale=1.0, between 1 and 100
 	 * (including).
 	 */
-	unsigned base;
+	const unsigned base;
 
 	ReplayGainMode mode = REPLAY_GAIN_OFF;
 
@@ -68,17 +68,15 @@ class ReplayGainFilter final : public Filter {
 	PcmVolume pv;
 
 public:
-	ReplayGainFilter() {
+	ReplayGainFilter(const AudioFormat &audio_format,
+			 Mixer *_mixer, unsigned _base)
+		:Filter(audio_format),
+		 mixer(_mixer), base(_base), mode(REPLAY_GAIN_OFF) {
 		info.Clear();
 	}
 
-	void SetMixer(Mixer *_mixer, unsigned _base) {
-		assert(_mixer == nullptr || (_base > 0 && _base <= 100));
-
-		mixer = _mixer;
-		base = _base;
-
-		Update();
+	bool Open(Error &error) {
+		return pv.Open(out_audio_format.format, error);
 	}
 
 	void SetInfo(const ReplayGainInfo *_info) {
@@ -110,12 +108,35 @@ public:
 	void Update();
 
 	/* virtual methods from class Filter */
-	AudioFormat Open(AudioFormat &af, Error &error) override;
-	void Close() override;
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 				    Error &error) override;
 };
 
+class PreparedReplayGainFilter final : public PreparedFilter {
+	/**
+	 * If set, then this hardware mixer is used for applying
+	 * replay gain, instead of the software volume library.
+	 */
+	Mixer *mixer = nullptr;
+
+	/**
+	 * The base volume level for scale=1.0, between 1 and 100
+	 * (including).
+	 */
+	unsigned base;
+
+public:
+	void SetMixer(Mixer *_mixer, unsigned _base) {
+		assert(_mixer == nullptr || (_base > 0 && _base <= 100));
+
+		mixer = _mixer;
+		base = _base;
+	}
+
+	/* virtual methods from class Filter */
+	Filter *Open(AudioFormat &af, Error &error) override;
+};
+
 void
 ReplayGainFilter::Update()
 {
@@ -146,26 +167,23 @@ ReplayGainFilter::Update()
 	}
 }
 
-static Filter *
+static PreparedFilter *
 replay_gain_filter_init(gcc_unused const ConfigBlock &block,
 			gcc_unused Error &error)
 {
-	return new ReplayGainFilter();
+	return new PreparedReplayGainFilter();
 }
 
-AudioFormat
-ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
+Filter *
+PreparedReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
 {
-	if (!pv.Open(af.format, error))
-		return AudioFormat::Undefined();
+	auto *filter = new ReplayGainFilter(af, mixer, base);
+	if (!filter->Open(error)) {
+		delete filter;
+		return nullptr;
+	}
 
-	return af;
-}
-
-void
-ReplayGainFilter::Close()
-{
-	pv.Close();
+	return filter;
 }
 
 ConstBuffer<void>
@@ -180,10 +198,10 @@ const struct filter_plugin replay_gain_filter_plugin = {
 };
 
 void
-replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+replay_gain_filter_set_mixer(PreparedFilter *_filter, Mixer *mixer,
 			     unsigned base)
 {
-	ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+	PreparedReplayGainFilter *filter = (PreparedReplayGainFilter *)_filter;
 
 	filter->SetMixer(mixer, base);
 }
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.hxx b/src/filter/plugins/ReplayGainFilterPlugin.hxx
index 90accd8ea..734f864af 100644
--- a/src/filter/plugins/ReplayGainFilterPlugin.hxx
+++ b/src/filter/plugins/ReplayGainFilterPlugin.hxx
@@ -23,6 +23,7 @@
 #include "ReplayGainInfo.hxx"
 
 class Filter;
+class PreparedFilter;
 class Mixer;
 
 /**
@@ -34,7 +35,7 @@ class Mixer;
  * (including).
  */
 void
-replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+replay_gain_filter_set_mixer(PreparedFilter *_filter, Mixer *mixer,
 			     unsigned base);
 
 /**
diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx
index 0f061e597..39986a376 100644
--- a/src/filter/plugins/RouteFilterPlugin.cxx
+++ b/src/filter/plugins/RouteFilterPlugin.cxx
@@ -59,6 +59,46 @@
 #include <stdlib.h>
 
 class RouteFilter final : public Filter {
+	/**
+	 * The set of copy operations to perform on each sample
+	 * The index is an output channel to use, the value is
+	 * a corresponding input channel from which to take the
+	 * data. A -1 means "no source"
+	 */
+	const std::array<int8_t, MAX_CHANNELS> sources;
+
+	/**
+	 * The actual input format of our signal, once opened
+	 */
+	const AudioFormat input_format;
+
+	/**
+	 * The size, in bytes, of each multichannel frame in the
+	 * input buffer
+	 */
+	const size_t input_frame_size;
+
+	/**
+	 * The size, in bytes, of each multichannel frame in the
+	 * output buffer
+	 */
+	size_t output_frame_size;
+
+	/**
+	 * The output buffer used last time around, can be reused if the size doesn't differ.
+	 */
+	PcmBuffer output_buffer;
+
+public:
+	RouteFilter(const AudioFormat &audio_format, unsigned out_channels,
+		    const std::array<int8_t, MAX_CHANNELS> &_sources);
+
+	/* virtual methods from class Filter */
+	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+				    Error &error) override;
+};
+
+class PreparedRouteFilter final : public PreparedFilter {
 	/**
 	 * The minimum number of channels we need for output
 	 * to be able to perform all the copies the user has specified
@@ -81,33 +121,6 @@ class RouteFilter final : public Filter {
 	 */
 	std::array<int8_t, MAX_CHANNELS> sources;
 
-	/**
-	 * The actual input format of our signal, once opened
-	 */
-	AudioFormat input_format;
-
-	/**
-	 * The decided upon output format, once opened
-	 */
-	AudioFormat output_format;
-
-	/**
-	 * The size, in bytes, of each multichannel frame in the
-	 * input buffer
-	 */
-	size_t input_frame_size;
-
-	/**
-	 * The size, in bytes, of each multichannel frame in the
-	 * output buffer
-	 */
-	size_t output_frame_size;
-
-	/**
-	 * The output buffer used last time around, can be reused if the size doesn't differ.
-	 */
-	PcmBuffer output_buffer;
-
 public:
 	/**
 	 * Parse the "routes" section, a string on the form
@@ -120,16 +133,13 @@ public:
 	 */
 	bool Configure(const ConfigBlock &block, Error &error);
 
-	/* virtual methods from class Filter */
-	AudioFormat Open(AudioFormat &af, Error &error) override;
-	void Close() override;
-	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
-				    Error &error) override;
+	/* virtual methods from class PreparedFilter */
+	Filter *Open(AudioFormat &af, Error &error) override;
 };
 
 bool
-RouteFilter::Configure(const ConfigBlock &block, Error &error) {
-
+PreparedRouteFilter::Configure(const ConfigBlock &block, Error &error)
+{
 	/* TODO:
 	 * With a more clever way of marking "don't copy to output N",
 	 * This could easily be merged into a single loop with some
@@ -204,10 +214,10 @@ RouteFilter::Configure(const ConfigBlock &block, Error &error) {
 	return true;
 }
 
-static Filter *
+static PreparedFilter *
 route_filter_init(const ConfigBlock &block, Error &error)
 {
-	RouteFilter *filter = new RouteFilter();
+	auto *filter = new PreparedRouteFilter();
 	if (!filter->Configure(block, error)) {
 		delete filter;
 		return nullptr;
@@ -216,28 +226,24 @@ route_filter_init(const ConfigBlock &block, Error &error)
 	return filter;
 }
 
-AudioFormat
-RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+RouteFilter::RouteFilter(const AudioFormat &audio_format,
+			 unsigned out_channels,
+			 const std::array<int8_t, MAX_CHANNELS> &_sources)
+	:Filter(audio_format), sources(_sources), input_format(audio_format),
+	 input_frame_size(input_format.GetFrameSize())
 {
-	// Copy the input format for later reference
-	input_format = audio_format;
-	input_frame_size = input_format.GetFrameSize();
-
 	// Decide on an output format which has enough channels,
 	// and is otherwise identical
-	output_format = audio_format;
-	output_format.channels = min_output_channels;
+	out_audio_format.channels = out_channels;
 
 	// Precalculate this simple value, to speed up allocation later
-	output_frame_size = output_format.GetFrameSize();
-
-	return output_format;
+	output_frame_size = out_audio_format.GetFrameSize();
 }
 
-void
-RouteFilter::Close()
+Filter *
+PreparedRouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
 {
-	output_buffer.Clear();
+	return new RouteFilter(audio_format, min_output_channels, sources);
 }
 
 ConstBuffer<void>
@@ -261,7 +267,7 @@ RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
 	for (unsigned int s=0; s<number_of_frames; ++s) {
 
 		// Need to perform one copy per output channel
-		for (unsigned int c=0; c<min_output_channels; ++c) {
+		for (unsigned c = 0; c < out_audio_format.channels; ++c) {
 			if (sources[c] == -1 ||
 			    (unsigned)sources[c] >= input_format.channels) {
 				// No source for this destination output,
diff --git a/src/filter/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx
index c29c92886..7e7fa2a4a 100644
--- a/src/filter/plugins/VolumeFilterPlugin.cxx
+++ b/src/filter/plugins/VolumeFilterPlugin.cxx
@@ -30,6 +30,13 @@ class VolumeFilter final : public Filter {
 	PcmVolume pv;
 
 public:
+	explicit VolumeFilter(const AudioFormat &audio_format)
+		:Filter(audio_format) {}
+
+	bool Open(Error &error) {
+		return pv.Open(out_audio_format.format, error);
+	}
+
 	unsigned GetVolume() const {
 		return pv.GetVolume();
 	}
@@ -39,32 +46,35 @@ public:
 	}
 
 	/* virtual methods from class Filter */
-	AudioFormat Open(AudioFormat &af, Error &error) override;
-	void Close() override;
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
 				    Error &error) override;
 };
 
-static Filter *
+class PreparedVolumeFilter final : public PreparedFilter {
+	PcmVolume pv;
+
+public:
+	/* virtual methods from class Filter */
+	Filter *Open(AudioFormat &af, Error &error) override;
+};
+
+static PreparedFilter *
 volume_filter_init(gcc_unused const ConfigBlock &block,
 		   gcc_unused Error &error)
 {
-	return new VolumeFilter();
+	return new PreparedVolumeFilter();
 }
 
-AudioFormat
-VolumeFilter::Open(AudioFormat &audio_format, Error &error)
+Filter *
+PreparedVolumeFilter::Open(AudioFormat &audio_format, Error &error)
 {
-	if (!pv.Open(audio_format.format, error))
-		return AudioFormat::Undefined();
+	auto *filter = new VolumeFilter(audio_format);
+	if (!filter->Open(error)) {
+		delete filter;
+		return nullptr;
+	}
 
-	return audio_format;
-}
-
-void
-VolumeFilter::Close()
-{
-	pv.Close();
+	return filter;
 }
 
 ConstBuffer<void>
diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx
index a92718734..13c1c82bc 100644
--- a/src/output/Finish.cxx
+++ b/src/output/Finish.cxx
@@ -34,9 +34,9 @@ AudioOutput::~AudioOutput()
 	if (mixer != nullptr)
 		mixer_free(mixer);
 
-	delete replay_gain_filter;
-	delete other_replay_gain_filter;
-	delete filter;
+	delete prepared_replay_gain_filter;
+	delete prepared_other_replay_gain_filter;
+	delete prepared_filter;
 }
 
 void
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
index 5bfb4d109..00dc9d569 100644
--- a/src/output/Init.cxx
+++ b/src/output/Init.cxx
@@ -102,7 +102,7 @@ audio_output_mixer_type(const ConfigBlock &block)
 						  "hardware"));
 }
 
-static Filter *
+static PreparedFilter *
 CreateVolumeFilter()
 {
 	return filter_new(&volume_filter_plugin, ConfigBlock(),
@@ -113,12 +113,10 @@ static Mixer *
 audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
 			const ConfigBlock &block,
 			const MixerPlugin *plugin,
-			Filter &filter_chain,
+			PreparedFilter &filter_chain,
 			MixerListener &listener,
 			Error &error)
 {
-	assert(ao.volume_filter == nullptr);
-
 	Mixer *mixer;
 
 	switch (audio_output_mixer_type(block)) {
@@ -144,13 +142,8 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
 				  IgnoreError());
 		assert(mixer != nullptr);
 
-		ao.volume_filter = CreateVolumeFilter();
-		assert(ao.volume_filter != nullptr);
-
 		filter_chain_append(filter_chain, "software_mixer",
-				    ao.volume_filter);
-
-		software_mixer_set_filter(*mixer, ao.volume_filter);
+				    ao.volume_filter.Set(CreateVolumeFilter()));
 		return mixer;
 	}
 
@@ -190,23 +183,23 @@ AudioOutput::Configure(const ConfigBlock &block, Error &error)
 
 	/* set up the filter chain */
 
-	filter = filter_chain_new();
-	assert(filter != nullptr);
+	prepared_filter = filter_chain_new();
+	assert(prepared_filter != nullptr);
 
 	/* create the normalization filter (if configured) */
 
 	if (config_get_bool(ConfigOption::VOLUME_NORMALIZATION, false)) {
-		Filter *normalize_filter =
+		auto *normalize_filter =
 			filter_new(&normalize_filter_plugin, ConfigBlock(),
 				   IgnoreError());
 		assert(normalize_filter != nullptr);
 
-		filter_chain_append(*filter, "normalize",
+		filter_chain_append(*prepared_filter, "normalize",
 				    autoconvert_filter_new(normalize_filter));
 	}
 
 	Error filter_error;
-	filter_chain_parse(*filter,
+	filter_chain_parse(*prepared_filter,
 			   block.GetBlockValue(AUDIO_FILTERS, ""),
 			   filter_error);
 
@@ -235,21 +228,21 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
 		block.GetBlockValue("replay_gain_handler", "software");
 
 	if (strcmp(replay_gain_handler, "none") != 0) {
-		ao.replay_gain_filter = filter_new(&replay_gain_filter_plugin,
-						   block, IgnoreError());
-		assert(ao.replay_gain_filter != nullptr);
+		ao.prepared_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+							    block, IgnoreError());
+		assert(ao.prepared_replay_gain_filter != nullptr);
 
 		ao.replay_gain_serial = 0;
 
-		ao.other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
-							 block,
-							 IgnoreError());
-		assert(ao.other_replay_gain_filter != nullptr);
+		ao.prepared_other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+								  block,
+								  IgnoreError());
+		assert(ao.prepared_other_replay_gain_filter != nullptr);
 
 		ao.other_replay_gain_serial = 0;
 	} else {
-		ao.replay_gain_filter = nullptr;
-		ao.other_replay_gain_filter = nullptr;
+		ao.prepared_replay_gain_filter = nullptr;
+		ao.prepared_other_replay_gain_filter = nullptr;
 	}
 
 	/* set up the mixer */
@@ -257,7 +250,7 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
 	Error mixer_error;
 	ao.mixer = audio_output_load_mixer(event_loop, ao, block,
 					   ao.plugin.mixer_plugin,
-					   *ao.filter,
+					   *ao.prepared_filter,
 					   mixer_listener,
 					   mixer_error);
 	if (ao.mixer == nullptr && mixer_error.IsDefined())
@@ -269,13 +262,13 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
 
 	if (strcmp(replay_gain_handler, "mixer") == 0) {
 		if (ao.mixer != nullptr)
-			replay_gain_filter_set_mixer(ao.replay_gain_filter,
+			replay_gain_filter_set_mixer(ao.prepared_replay_gain_filter,
 						     ao.mixer, 100);
 		else
 			FormatError(output_domain,
 				    "No such mixer for output '%s'", ao.name);
 	} else if (strcmp(replay_gain_handler, "software") != 0 &&
-		   ao.replay_gain_filter != nullptr) {
+		   ao.prepared_replay_gain_filter != nullptr) {
 		error.Set(config_domain,
 			  "Invalid \"replay_gain_handler\" value");
 		return false;
@@ -283,11 +276,12 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
 
 	/* the "convert" filter must be the last one in the chain */
 
-	ao.convert_filter = filter_new(&convert_filter_plugin, ConfigBlock(),
-					IgnoreError());
-	assert(ao.convert_filter != nullptr);
+	auto *f = filter_new(&convert_filter_plugin, ConfigBlock(),
+			     IgnoreError());
+	assert(f != nullptr);
 
-	filter_chain_append(*ao.filter, "convert", ao.convert_filter);
+	filter_chain_append(*ao.prepared_filter, "convert",
+			    ao.convert_filter.Set(f));
 
 	return true;
 }
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
index 0caa1235a..c9b0be5ed 100644
--- a/src/output/Internal.hxx
+++ b/src/output/Internal.hxx
@@ -24,12 +24,14 @@
 #include "pcm/PcmBuffer.hxx"
 #include "pcm/PcmDither.hxx"
 #include "ReplayGainInfo.hxx"
+#include "filter/Observer.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 #include "thread/Thread.hxx"
 #include "system/PeriodClock.hxx"
 
 class Error;
+class PreparedFilter;
 class Filter;
 class MusicPipe;
 class EventLoop;
@@ -147,6 +149,8 @@ struct AudioOutput {
 	 */
 	bool woken_for_play = false;
 
+	ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF;
+
 	/**
 	 * If not nullptr, the device has failed, and this timer is used
 	 * to estimate how long it should stay disabled (unless
@@ -187,19 +191,21 @@ struct AudioOutput {
 	 * The filter object of this audio output.  This is an
 	 * instance of chain_filter_plugin.
 	 */
-	Filter *filter = nullptr;
+	PreparedFilter *prepared_filter = nullptr;
+	Filter *filter_instance;
 
 	/**
 	 * The #VolumeFilter instance of this audio output.  It is
 	 * used by the #SoftwareMixer.
 	 */
-	Filter *volume_filter = nullptr;
+	FilterObserver volume_filter;
 
 	/**
 	 * The replay_gain_filter_plugin instance of this audio
 	 * output.
 	 */
-	Filter *replay_gain_filter = nullptr;
+	PreparedFilter *prepared_replay_gain_filter = nullptr;
+	Filter *replay_gain_filter_instance;
 
 	/**
 	 * The serial number of the last replay gain info.  0 means no
@@ -212,7 +218,8 @@ struct AudioOutput {
 	 * output, to be applied to the second chunk during
 	 * cross-fading.
 	 */
-	Filter *other_replay_gain_filter = nullptr;
+	PreparedFilter *prepared_other_replay_gain_filter = nullptr;
+	Filter *other_replay_gain_filter_instance;
 
 	/**
 	 * The serial number of the last replay gain info by the
@@ -226,7 +233,7 @@ struct AudioOutput {
 	 * for converting the input data into the appropriate format
 	 * for this audio output.
 	 */
-	Filter *convert_filter;
+	FilterObserver convert_filter;
 
 	/**
 	 * The thread handle, or nullptr if the output thread isn't
@@ -346,7 +353,9 @@ struct AudioOutput {
 	 */
 	void LockRelease();
 
-	void SetReplayGainMode(ReplayGainMode mode);
+	void SetReplayGainMode(ReplayGainMode _mode) {
+		replay_gain_mode = _mode;
+	}
 
 	/**
 	 * Caller must lock the mutex.
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
index 47d1be300..090db35d9 100644
--- a/src/output/OutputControl.cxx
+++ b/src/output/OutputControl.cxx
@@ -68,15 +68,6 @@ AudioOutput::LockCommandWait(Command cmd)
 	CommandWait(cmd);
 }
 
-void
-AudioOutput::SetReplayGainMode(ReplayGainMode mode)
-{
-	if (replay_gain_filter != nullptr)
-		replay_gain_filter_set_mode(replay_gain_filter, mode);
-	if (other_replay_gain_filter != nullptr)
-		replay_gain_filter_set_mode(other_replay_gain_filter, mode);
-}
-
 void
 AudioOutput::LockEnableWait()
 {
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
index 4b2f336e8..53fc2edfb 100644
--- a/src/output/OutputThread.cxx
+++ b/src/output/OutputThread.cxx
@@ -27,6 +27,8 @@
 #include "filter/FilterInternal.hxx"
 #include "filter/plugins/ConvertFilterPlugin.hxx"
 #include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "mixer/plugins/SoftwareMixerPlugin.hxx"
 #include "player/Control.hxx"
 #include "MusicPipe.hxx"
 #include "MusicChunk.hxx"
@@ -94,37 +96,44 @@ AudioOutput::OpenFilter(AudioFormat &format, Error &error_r)
 	assert(format.IsValid());
 
 	/* the replay_gain filter cannot fail here */
-	if (replay_gain_filter != nullptr &&
-	    !replay_gain_filter->Open(format, error_r).IsDefined())
-		return AudioFormat::Undefined();
+	if (prepared_replay_gain_filter != nullptr) {
+		replay_gain_filter_instance =
+			prepared_replay_gain_filter->Open(format, error_r);
+		if (replay_gain_filter_instance == nullptr)
+			return AudioFormat::Undefined();
+	}
 
-	if (other_replay_gain_filter != nullptr &&
-	    !other_replay_gain_filter->Open(format, error_r).IsDefined()) {
-		if (replay_gain_filter != nullptr)
-			replay_gain_filter->Close();
+	if (prepared_other_replay_gain_filter != nullptr) {
+		other_replay_gain_filter_instance =
+			prepared_other_replay_gain_filter->Open(format, error_r);
+		if (other_replay_gain_filter_instance == nullptr) {
+			delete replay_gain_filter_instance;
+			return AudioFormat::Undefined();
+		}
+	}
+
+	filter_instance = prepared_filter->Open(format, error_r);
+	if (filter_instance == nullptr) {
+		delete other_replay_gain_filter_instance;
+		delete replay_gain_filter_instance;
 		return AudioFormat::Undefined();
 	}
 
-	const AudioFormat af = filter->Open(format, error_r);
-	if (!af.IsDefined()) {
-		if (replay_gain_filter != nullptr)
-			replay_gain_filter->Close();
-		if (other_replay_gain_filter != nullptr)
-			other_replay_gain_filter->Close();
-	}
+	if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
+		software_mixer_set_filter(*mixer, volume_filter.Get());
 
-	return af;
+	return filter_instance->GetOutAudioFormat();
 }
 
 void
 AudioOutput::CloseFilter()
 {
-	if (replay_gain_filter != nullptr)
-		replay_gain_filter->Close();
-	if (other_replay_gain_filter != nullptr)
-		other_replay_gain_filter->Close();
+	if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
+		software_mixer_set_filter(*mixer, nullptr);
 
-	filter->Close();
+	delete replay_gain_filter_instance;
+	delete other_replay_gain_filter_instance;
+	delete filter_instance;
 }
 
 inline void
@@ -186,7 +195,7 @@ AudioOutput::Open()
 		return;
 	}
 
-	if (!convert_filter_set(convert_filter, out_audio_format,
+	if (!convert_filter_set(convert_filter.Get(), out_audio_format,
 				error)) {
 		FormatError(error, "Failed to convert for \"%s\" [%s]",
 			    name, plugin.name);
@@ -282,7 +291,7 @@ AudioOutput::ReopenFilter()
 	const AudioFormat filter_audio_format =
 		OpenFilter(in_audio_format, error);
 	if (!filter_audio_format.IsDefined() ||
-	    !convert_filter_set(convert_filter, out_audio_format,
+	    !convert_filter_set(convert_filter.Get(), out_audio_format,
 				error)) {
 		FormatError(error,
 			    "Failed to open filter for \"%s\" [%s]",
@@ -368,6 +377,9 @@ ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk,
 	assert(data.size % ao->in_audio_format.GetFrameSize() == 0);
 
 	if (!data.IsEmpty() && replay_gain_filter != nullptr) {
+		replay_gain_filter_set_mode(replay_gain_filter,
+					    ao->replay_gain_mode);
+
 		if (chunk->replay_gain_serial != *replay_gain_serial_p) {
 			replay_gain_filter_set_info(replay_gain_filter,
 						    chunk->replay_gain_serial != 0
@@ -390,7 +402,7 @@ static ConstBuffer<void>
 ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
 {
 	ConstBuffer<void> data =
-		ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+		ao_chunk_data(ao, chunk, ao->replay_gain_filter_instance,
 			      &ao->replay_gain_serial);
 	if (data.IsEmpty())
 		return data;
@@ -400,7 +412,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
 	if (chunk->other != nullptr) {
 		ConstBuffer<void> other_data =
 			ao_chunk_data(ao, chunk->other,
-				      ao->other_replay_gain_filter,
+				      ao->other_replay_gain_filter_instance,
 				      &ao->other_replay_gain_serial);
 		if (other_data.IsNull())
 			return nullptr;
@@ -443,7 +455,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
 	/* apply filter chain */
 
 	Error error;
-	data = ao->filter->FilterPCM(data, error);
+	data = ao->filter_instance->FilterPCM(data, error);
 	if (data.IsNull()) {
 		FormatError(error, "\"%s\" [%s] failed to filter",
 			    ao->name, ao->plugin.name);
@@ -456,7 +468,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
 inline bool
 AudioOutput::PlayChunk(const MusicChunk *chunk)
 {
-	assert(filter != nullptr);
+	assert(filter_instance != nullptr);
 
 	if (tags && gcc_unlikely(chunk->tag != nullptr)) {
 		mutex.unlock();
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index a4e9c3653..0fa414e39 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -32,6 +32,8 @@
 #include "system/FatalError.hxx"
 #include "Log.hxx"
 
+#include <memory>
+
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
@@ -46,7 +48,7 @@ mixer_set_volume(gcc_unused Mixer *mixer,
 	return true;
 }
 
-static Filter *
+static PreparedFilter *
 load_filter(const char *name)
 {
 	const auto *param = config_find_block(ConfigBlockOption::AUDIO_FILTER,
@@ -57,7 +59,7 @@ load_filter(const char *name)
 	}
 
 	Error error;
-	Filter *filter = filter_configured_new(*param, error);
+	auto *filter = filter_configured_new(*param, error);
 	if (filter == NULL) {
 		LogError(error, "Failed to load filter");
 		return NULL;
@@ -97,20 +99,22 @@ try {
 
 	/* initialize the filter */
 
-	Filter *filter = load_filter(argv[2]);
-	if (filter == NULL)
+	std::unique_ptr<PreparedFilter> prepared_filter(load_filter(argv[2]));
+	if (!prepared_filter)
 		return EXIT_FAILURE;
 
 	/* open the filter */
 
 	Error error;
-	const AudioFormat out_audio_format = filter->Open(audio_format, error);
-	if (!out_audio_format.IsDefined()) {
+	std::unique_ptr<Filter> filter(prepared_filter->Open(audio_format,
+							     error));
+	if (!filter) {
 		LogError(error, "Failed to open filter");
-		delete filter;
 		return EXIT_FAILURE;
 	}
 
+	const AudioFormat out_audio_format = filter->GetOutAudioFormat();
+
 	fprintf(stderr, "audio_format=%s\n",
 		audio_format_to_string(out_audio_format, &af_string));
 
@@ -127,8 +131,6 @@ try {
 					      error);
 		if (dest.IsNull()) {
 			LogError(error, "filter/Filter failed");
-			filter->Close();
-			delete filter;
 			return EXIT_FAILURE;
 		}
 
@@ -136,17 +138,12 @@ try {
 		if (nbytes < 0) {
 			fprintf(stderr, "Failed to write: %s\n",
 				strerror(errno));
-			filter->Close();
-			delete filter;
 			return 1;
 		}
 	}
 
 	/* cleanup and exit */
 
-	filter->Close();
-	delete filter;
-
 	config_global_finish();
 
 	return EXIT_SUCCESS;