diff --git a/Makefile.am b/Makefile.am
index 35fdbe6cc5ef8c22aa26ce91188179c94dd126d9..f30e3fafa4a8a0629418fafaa1d082bc0c3c1498 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1375,7 +1375,6 @@ OUTPUT_API_SRC = \
 	src/output/OutputAPI.hxx \
 	src/output/Interface.hxx \
 	src/output/Filtered.cxx src/output/Filtered.hxx \
-	src/output/Wrapper.hxx \
 	src/output/Registry.cxx src/output/Registry.hxx \
 	src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \
 	src/output/SharedPipeConsumer.cxx src/output/SharedPipeConsumer.hxx \
diff --git a/src/output/Filtered.cxx b/src/output/Filtered.cxx
index 723f5565535cd379829fb7d28b5c66d7f79c88d3..45d2d92bebc5fb9c529111afb1c26f0f39f8f23e 100644
--- a/src/output/Filtered.cxx
+++ b/src/output/Filtered.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "Filtered.hxx"
 #include "Interface.hxx"
-#include "OutputPlugin.hxx"
 #include "Domain.hxx"
 #include "Log.hxx"
 #include "mixer/MixerInternal.hxx"
@@ -32,22 +31,20 @@
 bool
 FilteredAudioOutput::SupportsEnableDisable() const noexcept
 {
-	assert((output->GetPlugin().enable == nullptr) == (output->GetPlugin().disable == nullptr));
-
-	return output->GetPlugin().enable != nullptr;
+	return output->SupportsEnableDisable();
 }
 
 bool
 FilteredAudioOutput::SupportsPause() const noexcept
 {
-	return output->GetPlugin().pause != nullptr;
+	return output->SupportsPause();
 }
 
 void
 FilteredAudioOutput::Enable()
 {
 	try {
-		ao_plugin_enable(*output);
+		output->Enable();
 	} catch (const std::runtime_error &e) {
 		std::throw_with_nested(FormatRuntimeError("Failed to enable output %s",
 							  GetLogName()));
@@ -57,7 +54,7 @@ FilteredAudioOutput::Enable()
 void
 FilteredAudioOutput::Disable() noexcept
 {
-	ao_plugin_disable(*output);
+	output->Disable();
 }
 
 void
@@ -77,7 +74,7 @@ FilteredAudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
 	out_audio_format = desired_audio_format;
 
 	try {
-		ao_plugin_open(*output, out_audio_format);
+		output->Open(out_audio_format);
 	} catch (const std::runtime_error &e) {
 		std::throw_with_nested(FormatRuntimeError("Failed to open %s",
 							  GetLogName()));
@@ -91,7 +88,7 @@ FilteredAudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
 	try {
 		ConfigureConvertFilter();
 	} catch (const std::runtime_error &e) {
-		ao_plugin_close(*output);
+		output->Close();
 
 		if (out_audio_format.format == SampleFormat::DSD) {
 			/* if the audio output supports DSD, but not
@@ -120,7 +117,7 @@ FilteredAudioOutput::CloseOutput(bool drain) noexcept
 	else
 		Cancel();
 
-	ao_plugin_close(*output);
+	output->Close();
 }
 
 void
@@ -149,31 +146,31 @@ FilteredAudioOutput::Close(bool drain) noexcept
 std::chrono::steady_clock::duration
 FilteredAudioOutput::Delay() noexcept
 {
-	return ao_plugin_delay(*output);
+	return output->Delay();
 }
 
 void
 FilteredAudioOutput::SendTag(const Tag &tag)
 {
-	ao_plugin_send_tag(*output, tag);
+	output->SendTag(tag);
 }
 
 size_t
 FilteredAudioOutput::Play(const void *data, size_t size)
 {
-	return ao_plugin_play(*output, data, size);
+	return output->Play(data, size);
 }
 
 void
 FilteredAudioOutput::Drain()
 {
-	ao_plugin_drain(*output);
+	output->Drain();
 }
 
 void
 FilteredAudioOutput::Cancel() noexcept
 {
-	ao_plugin_cancel(*output);
+	output->Cancel();
 }
 
 void
@@ -186,7 +183,7 @@ bool
 FilteredAudioOutput::IteratePause() noexcept
 {
 	try {
-		return ao_plugin_pause(*output);
+		return output->Pause();
 	} catch (const std::runtime_error &e) {
 		FormatError(e, "Failed to pause %s",
 			    GetLogName());
diff --git a/src/output/Filtered.hxx b/src/output/Filtered.hxx
index 9717c484c61a08af5a908a867b02604d07e439b7..4f0a8713d8fb71cc07bb169649309254dd57a3a4 100644
--- a/src/output/Filtered.hxx
+++ b/src/output/Filtered.hxx
@@ -23,6 +23,7 @@
 #include "AudioFormat.hxx"
 #include "filter/Observer.hxx"
 
+#include <memory>
 #include <string>
 #include <chrono>
 
@@ -31,6 +32,7 @@ class MusicPipe;
 class EventLoop;
 class Mixer;
 class MixerListener;
+struct MixerPlugin;
 struct MusicChunk;
 struct ConfigBlock;
 class AudioOutput;
@@ -38,6 +40,8 @@ struct ReplayGainConfig;
 struct Tag;
 
 struct FilteredAudioOutput {
+	const char *const plugin_name;
+
 	/**
 	 * The device's configured display name.
 	 */
@@ -54,7 +58,7 @@ public:
 	/**
 	 * The plugin which implements this output device.
 	 */
-	AudioOutput *const output;
+	std::unique_ptr<AudioOutput> output;
 
 	/**
 	 * The #mixer object associated with this audio output device.
@@ -120,7 +124,8 @@ public:
 	/**
 	 * Throws #std::runtime_error on error.
 	 */
-	FilteredAudioOutput(AudioOutput &_output,
+	FilteredAudioOutput(const char *_plugin_name,
+			    std::unique_ptr<AudioOutput> &&_output,
 			    const ConfigBlock &block);
 
 	~FilteredAudioOutput();
@@ -131,6 +136,7 @@ private:
 public:
 	void Setup(EventLoop &event_loop,
 		   const ReplayGainConfig &replay_gain_config,
+		   const MixerPlugin *mixer_plugin,
 		   MixerListener &mixer_listener,
 		   const ConfigBlock &block);
 
diff --git a/src/output/Finish.cxx b/src/output/Finish.cxx
index 9db4d50fea64c09fa4d1108c7c9988af6bab1ea8..9e1c4eb46f4649967bdf52b512657531a0c6aaeb 100644
--- a/src/output/Finish.cxx
+++ b/src/output/Finish.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "Filtered.hxx"
-#include "OutputPlugin.hxx"
+#include "Interface.hxx"
 #include "mixer/MixerControl.hxx"
 #include "filter/FilterInternal.hxx"
 
@@ -31,8 +31,6 @@ FilteredAudioOutput::~FilteredAudioOutput()
 	delete prepared_replay_gain_filter;
 	delete prepared_other_replay_gain_filter;
 	delete prepared_filter;
-
-	ao_plugin_finish(output);
 }
 
 void
@@ -45,5 +43,5 @@ FilteredAudioOutput::BeginDestroy() noexcept
 void
 FilteredAudioOutput::FinishDestroy() noexcept
 {
-	ao_plugin_finish(output);
+	output.reset();
 }
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
index fb66b4a5d984ce395af6dcbd6557a5b35adf03f3..c8756bb1825928d7a38d233a8de2287f04fc323d 100644
--- a/src/output/Init.cxx
+++ b/src/output/Init.cxx
@@ -50,18 +50,11 @@
 #define AUDIO_OUTPUT_FORMAT	"format"
 #define AUDIO_FILTERS		"filters"
 
-FilteredAudioOutput::FilteredAudioOutput(AudioOutput &_output,
+FilteredAudioOutput::FilteredAudioOutput(const char *_plugin_name,
+					 std::unique_ptr<AudioOutput> &&_output,
 					 const ConfigBlock &block)
-	:output(&_output)
+	:plugin_name(_plugin_name), output(std::move(_output))
 {
-#ifndef NDEBUG
-	const auto &plugin = output->GetPlugin();
-	assert(plugin.finish != nullptr);
-	assert(plugin.open != nullptr);
-	assert(plugin.close != nullptr);
-	assert(plugin.play != nullptr);
-#endif
-
 	Configure(block);
 }
 
@@ -172,7 +165,7 @@ FilteredAudioOutput::Configure(const ConfigBlock &block)
 	{
 		char buffer[64];
 		snprintf(buffer, sizeof(buffer), "\"%s\" (%s)",
-			 name, output->GetPlugin().name);
+			 name, plugin_name);
 		log_name = buffer;
 	}
 
@@ -204,6 +197,7 @@ FilteredAudioOutput::Configure(const ConfigBlock &block)
 inline void
 FilteredAudioOutput::Setup(EventLoop &event_loop,
 			   const ReplayGainConfig &replay_gain_config,
+			   const MixerPlugin *mixer_plugin,
 			   MixerListener &mixer_listener,
 			   const ConfigBlock &block)
 {
@@ -233,7 +227,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
 
 	try {
 		mixer = audio_output_load_mixer(event_loop, *this, block,
-						output->GetPlugin().mixer_plugin,
+						mixer_plugin,
 						*prepared_filter,
 						mixer_listener);
 	} catch (const std::runtime_error &e) {
@@ -291,20 +285,15 @@ audio_output_new(EventLoop &event_loop,
 			      plugin->name);
 	}
 
-	auto *ao = ao_plugin_init(event_loop, *plugin, block);
+	std::unique_ptr<AudioOutput> ao(ao_plugin_init(event_loop, *plugin,
+						       block));
 	assert(ao != nullptr);
 
-	FilteredAudioOutput *f;
-
-	try {
-		f = new FilteredAudioOutput(*ao, block);
-	} catch (...) {
-		ao_plugin_finish(ao);
-		throw;
-	}
+	auto *f = new FilteredAudioOutput(plugin->name, std::move(ao), block);
 
 	try {
 		f->Setup(event_loop, replay_gain_config,
+			 plugin->mixer_plugin,
 			 mixer_listener, block);
 	} catch (...) {
 		delete f;
diff --git a/src/output/Interface.hxx b/src/output/Interface.hxx
index 645a94b2eeded999c43a4a2ea61152d6e07ff80a..83737df5b8c0cb407dfd021ca1fec3375c38152f 100644
--- a/src/output/Interface.hxx
+++ b/src/output/Interface.hxx
@@ -20,22 +20,33 @@
 #ifndef MPD_AUDIO_OUTPUT_INTERFACE_HXX
 #define MPD_AUDIO_OUTPUT_INTERFACE_HXX
 
-struct AudioOutputPlugin;
+#include <chrono>
+
+struct AudioFormat;
+struct Tag;
 
 class AudioOutput {
-	/**
-	 * The plugin which implements this output device.
-	 */
-	const AudioOutputPlugin &plugin;
+	const unsigned flags;
 
 	bool need_fully_defined_audio_format = false;
 
+protected:
+	static constexpr unsigned FLAG_ENABLE_DISABLE = 0x1;
+	static constexpr unsigned FLAG_PAUSE = 0x2;
+
 public:
-	AudioOutput(const AudioOutputPlugin &_plugin)
-		:plugin(_plugin) {}
+	explicit AudioOutput(unsigned _flags):flags(_flags) {}
+	virtual ~AudioOutput() = default;
+
+	AudioOutput(const AudioOutput &) = delete;
+	AudioOutput &operator=(const AudioOutput &) = delete;
 
-	const AudioOutputPlugin &GetPlugin() const {
-		return plugin;
+	bool SupportsEnableDisable() const {
+		return flags & FLAG_ENABLE_DISABLE;
+	}
+
+	bool SupportsPause() const {
+		return flags & FLAG_PAUSE;
 	}
 
 	bool GetNeedFullyDefinedAudioFormat() const {
@@ -50,6 +61,87 @@ public:
 	void NeedFullyDefinedAudioFormat() {
 		need_fully_defined_audio_format = true;
 	}
+
+	/**
+	 * Enable the device.  This may allocate resources, preparing
+	 * for the device to be opened.
+	 *
+	 * Throws #std::runtime_error on error.
+	 */
+	virtual void Enable() {}
+
+	/**
+	 * Disables the device.  It is closed before this method is
+	 * called.
+	 */
+	virtual void Disable() noexcept {}
+
+	/**
+	 * Really open the device.
+	 *
+	 * Throws #std::runtime_error on error.
+	 *
+	 * @param audio_format the audio format in which data is going
+	 * to be delivered; may be modified by the plugin
+	 */
+	virtual void Open(AudioFormat &audio_format) = 0;
+
+	/**
+	 * Close the device.
+	 */
+	virtual void Close() noexcept = 0;
+
+	/**
+	 * Returns a positive number if the output thread shall further
+	 * delay the next call to Play() or Pause(), which will happen
+	 * until this function returns 0.  This should be implemented
+	 * instead of doing a sleep inside the plugin, because this
+	 * allows MPD to listen to commands meanwhile.
+	 *
+	 * @return the duration to wait
+	 */
+	virtual std::chrono::steady_clock::duration Delay() const noexcept {
+		return std::chrono::steady_clock::duration::zero();
+	}
+
+	/**
+	 * Display metadata for the next chunk.  Optional method,
+	 * because not all devices can display metadata.
+	 */
+	virtual void SendTag(const Tag &) {}
+
+	/**
+	 * Play a chunk of audio data.
+	 *
+	 * Throws #std::runtime_error on error.
+	 *
+	 * @return the number of bytes played
+	 */
+	virtual size_t Play(const void *chunk, size_t size) = 0;
+
+	/**
+	 * Wait until the device has finished playing.
+	 */
+	virtual void Drain() {}
+
+	/**
+	 * Try to cancel data which may still be in the device's
+	 * buffers.
+	 */
+	virtual void Cancel() noexcept {}
+
+	/**
+	 * Pause the device.  If supported, it may perform a special
+	 * action, which keeps the device open, but does not play
+	 * anything.  Output plugins like "shout" might want to play
+	 * silence during pause, so their clients won't be
+	 * disconnected.  Plugins which do not support pausing will
+	 * simply be closed, and have to be reopened when unpaused.
+	 *
+	 * @return false on error (output will be closed by caller),
+	 * true for continue to pause
+	 */
+	virtual bool Pause() noexcept { return true; }
 };
 
 #endif
diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx
index e0a42e7e56b206d092dc72ebc212854a6e9851ec..40ca82be06f731aef023e3494e13ae26c068efd1 100644
--- a/src/output/OutputPlugin.cxx
+++ b/src/output/OutputPlugin.cxx
@@ -19,7 +19,6 @@
 
 #include "config.h"
 #include "OutputPlugin.hxx"
-#include "Interface.hxx"
 
 #include <assert.h>
 
@@ -32,76 +31,3 @@ ao_plugin_init(EventLoop &event_loop,
 
 	return plugin.init(event_loop, block);
 }
-
-void
-ao_plugin_finish(AudioOutput *ao) noexcept
-{
-	ao->GetPlugin().finish(ao);
-}
-
-void
-ao_plugin_enable(AudioOutput &ao)
-{
-	if (ao.GetPlugin().enable != nullptr)
-		ao.GetPlugin().enable(&ao);
-}
-
-void
-ao_plugin_disable(AudioOutput &ao) noexcept
-{
-	if (ao.GetPlugin().disable != nullptr)
-		ao.GetPlugin().disable(&ao);
-}
-
-void
-ao_plugin_open(AudioOutput &ao, AudioFormat &audio_format)
-{
-	ao.GetPlugin().open(&ao, audio_format);
-}
-
-void
-ao_plugin_close(AudioOutput &ao) noexcept
-{
-	ao.GetPlugin().close(&ao);
-}
-
-std::chrono::steady_clock::duration
-ao_plugin_delay(AudioOutput &ao) noexcept
-{
-	return ao.GetPlugin().delay != nullptr
-		? ao.GetPlugin().delay(&ao)
-		: std::chrono::steady_clock::duration::zero();
-}
-
-void
-ao_plugin_send_tag(AudioOutput &ao, const Tag &tag)
-{
-	if (ao.GetPlugin().send_tag != nullptr)
-		ao.GetPlugin().send_tag(&ao, tag);
-}
-
-size_t
-ao_plugin_play(AudioOutput &ao, const void *chunk, size_t size)
-{
-	return ao.GetPlugin().play(&ao, chunk, size);
-}
-
-void
-ao_plugin_drain(AudioOutput &ao)
-{
-	if (ao.GetPlugin().drain != nullptr)
-		ao.GetPlugin().drain(&ao);
-}
-
-void
-ao_plugin_cancel(AudioOutput &ao) noexcept
-{
-	if (ao.GetPlugin().cancel != nullptr)
-		ao.GetPlugin().cancel(&ao);
-}
-
-bool
-ao_plugin_pause(AudioOutput &ao)
-{
-	return ao.GetPlugin().pause != nullptr && ao.GetPlugin().pause(&ao);
-}
diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx
index 254f783fe713ad15877ca13a729ef2ac7ecc0fee..8738131d09649533ec11593534d6a5c8c517a89a 100644
--- a/src/output/OutputPlugin.hxx
+++ b/src/output/OutputPlugin.hxx
@@ -59,91 +59,6 @@ struct AudioOutputPlugin {
 	 */
 	AudioOutput *(*init)(EventLoop &event_loop, const ConfigBlock &block);
 
-	/**
-	 * Free resources allocated by this device.
-	 */
-	void (*finish)(AudioOutput *data);
-
-	/**
-	 * Enable the device.  This may allocate resources, preparing
-	 * for the device to be opened.
-	 *
-	 * Throws #std::runtime_error on error.
-	 */
-	void (*enable)(AudioOutput *data);
-
-	/**
-	 * Disables the device.  It is closed before this method is
-	 * called.
-	 */
-	void (*disable)(AudioOutput *data);
-
-	/**
-	 * Really open the device.
-	 *
-	 * Throws #std::runtime_error on error.
-	 *
-	 * @param audio_format the audio format in which data is going
-	 * to be delivered; may be modified by the plugin
-	 */
-	void (*open)(AudioOutput *data, AudioFormat &audio_format);
-
-	/**
-	 * Close the device.
-	 */
-	void (*close)(AudioOutput *data);
-
-	/**
-	 * Returns a positive number if the output thread shall further
-	 * delay the next call to play() or pause(), which will happen
-	 * until this function returns 0.  This should be implemented
-	 * instead of doing a sleep inside the plugin, because this
-	 * allows MPD to listen to commands meanwhile.
-	 *
-	 * @return the duration to wait
-	 */
-	std::chrono::steady_clock::duration (*delay)(AudioOutput *data) noexcept;
-
-	/**
-	 * Display metadata for the next chunk.  Optional method,
-	 * because not all devices can display metadata.
-	 */
-	void (*send_tag)(AudioOutput *data, const Tag &tag);
-
-	/**
-	 * Play a chunk of audio data.
-	 *
-	 * Throws #std::runtime_error on error.
-	 *
-	 * @return the number of bytes played
-	 */
-	size_t (*play)(AudioOutput *data,
-		       const void *chunk, size_t size);
-
-	/**
-	 * Wait until the device has finished playing.
-	 */
-	void (*drain)(AudioOutput *data);
-
-	/**
-	 * Try to cancel data which may still be in the device's
-	 * buffers.
-	 */
-	void (*cancel)(AudioOutput *data);
-
-	/**
-	 * Pause the device.  If supported, it may perform a special
-	 * action, which keeps the device open, but does not play
-	 * anything.  Output plugins like "shout" might want to play
-	 * silence during pause, so their clients won't be
-	 * disconnected.  Plugins which do not support pausing will
-	 * simply be closed, and have to be reopened when unpaused.
-	 *
-	 * @return false on error (output will be closed by caller),
-	 * true for continue to pause
-	 */
-	bool (*pause)(AudioOutput *data);
-
 	/**
 	 * The mixer plugin associated with this output plugin.  This
 	 * may be nullptr if no mixer plugin is implemented.  When
@@ -167,38 +82,4 @@ ao_plugin_init(EventLoop &event_loop,
 	       const AudioOutputPlugin &plugin,
 	       const ConfigBlock &block);
 
-void
-ao_plugin_finish(AudioOutput *ao) noexcept;
-
-void
-ao_plugin_enable(AudioOutput &ao);
-
-void
-ao_plugin_disable(AudioOutput &ao) noexcept;
-
-void
-ao_plugin_open(AudioOutput &ao, AudioFormat &audio_format);
-
-void
-ao_plugin_close(AudioOutput &ao) noexcept;
-
-gcc_pure
-std::chrono::steady_clock::duration
-ao_plugin_delay(AudioOutput &ao) noexcept;
-
-void
-ao_plugin_send_tag(AudioOutput &ao, const Tag &tag);
-
-size_t
-ao_plugin_play(AudioOutput &ao, const void *chunk, size_t size);
-
-void
-ao_plugin_drain(AudioOutput &ao);
-
-void
-ao_plugin_cancel(AudioOutput &ao) noexcept;
-
-bool
-ao_plugin_pause(AudioOutput &ao);
-
 #endif
diff --git a/src/output/Wrapper.hxx b/src/output/Wrapper.hxx
deleted file mode 100644
index ac3e9f1684b21375ab3e1d06ce6e441b82f38b78..0000000000000000000000000000000000000000
--- a/src/output/Wrapper.hxx
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2003-2017 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_OUTPUT_WRAPPER_HXX
-#define MPD_OUTPUT_WRAPPER_HXX
-
-#include "Interface.hxx"
-#include "util/Cast.hxx"
-
-#include <chrono>
-
-struct ConfigBlock;
-struct AudioFormat;
-struct Tag;
-
-template<class T>
-struct AudioOutputWrapper {
-	static T &Cast(AudioOutput &ao) {
-		return ContainerCast(ao, &T::base);
-	}
-
-	static AudioOutput *Init(EventLoop &event_loop,
-					 const ConfigBlock &block) {
-		T *t = T::Create(event_loop, block);
-		return &t->base;
-	}
-
-	static void Finish(AudioOutput *ao) {
-		T *t = &Cast(*ao);
-		delete t;
-	}
-
-	static void Enable(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		t.Enable();
-	}
-
-	static void Disable(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		t.Disable();
-	}
-
-	static void Open(AudioOutput *ao, AudioFormat &audio_format) {
-		T &t = Cast(*ao);
-		t.Open(audio_format);
-	}
-
-	static void Close(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		t.Close();
-	}
-
-	gcc_pure
-	static std::chrono::steady_clock::duration Delay(AudioOutput *ao) noexcept {
-		T &t = Cast(*ao);
-		return t.Delay();
-	}
-
-	static void SendTag(AudioOutput *ao, const Tag &tag) {
-		T &t = Cast(*ao);
-		t.SendTag(tag);
-	}
-
-	static size_t Play(AudioOutput *ao, const void *chunk, size_t size) {
-		T &t = Cast(*ao);
-		return t.Play(chunk, size);
-	}
-
-	static void Drain(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		t.Drain();
-	}
-
-	static void Cancel(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		t.Cancel();
-	}
-
-	static bool Pause(AudioOutput *ao) {
-		T &t = Cast(*ao);
-		return t.Pause();
-	}
-};
-
-#endif
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index b1b44b8bcd674749cf292c9f93b7c8027ed9016b..60c738466010513d9e222e6523eac0a619f13d08 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -22,7 +22,6 @@
 #include "lib/alsa/NonBlock.hxx"
 #include "lib/alsa/Version.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "pcm/PcmExport.hxx"
 #include "system/ByteOrder.hxx"
@@ -60,11 +59,7 @@ static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
 static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
 
 class AlsaOutput final
-	: MultiSocketMonitor, DeferredMonitor {
-
-	friend struct AudioOutputWrapper<AlsaOutput>;
-
-	AudioOutput base;
+	: AudioOutput, MultiSocketMonitor, DeferredMonitor {
 
 	Manual<PcmExport> pcm_export;
 
@@ -290,20 +285,22 @@ public:
 		return device.empty() ? default_device : device.c_str();
 	}
 
-	static AlsaOutput *Create(EventLoop &event_loop,
-				  const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &event_loop,
+				   const ConfigBlock &block) {
+		return new AlsaOutput(event_loop, block);
+	}
 
-	void Enable();
-	void Disable();
+private:
+	void Enable() override;
+	void Disable() noexcept override;
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	size_t Play(const void *chunk, size_t size);
-	void Drain();
-	void Cancel();
+	size_t Play(const void *chunk, size_t size) override;
+	void Drain() override;
+	void Cancel() noexcept override;
 
-private:
 	/**
 	 * Set up the snd_pcm_t object which was opened by the caller.
 	 * Set up the configured settings and the audio format.
@@ -413,8 +410,8 @@ private:
 static constexpr Domain alsa_output_domain("alsa_output");
 
 AlsaOutput::AlsaOutput(EventLoop &loop, const ConfigBlock &block)
-	:MultiSocketMonitor(loop), DeferredMonitor(loop),
-	 base(alsa_output_plugin),
+	:AudioOutput(FLAG_ENABLE_DISABLE),
+	 MultiSocketMonitor(loop), DeferredMonitor(loop),
 	 device(block.GetBlockValue("device", "")),
 #ifdef ENABLE_DSD
 	 dop(block.GetBlockValue("dop", false) ||
@@ -441,20 +438,14 @@ AlsaOutput::AlsaOutput(EventLoop &loop, const ConfigBlock &block)
 #endif
 }
 
-inline AlsaOutput *
-AlsaOutput::Create(EventLoop &event_loop, const ConfigBlock &block)
-{
-	return new AlsaOutput(event_loop, block);
-}
-
-inline void
+void
 AlsaOutput::Enable()
 {
 	pcm_export.Construct();
 }
 
-inline void
-AlsaOutput::Disable()
+void
+AlsaOutput::Disable() noexcept
 {
 	pcm_export.Destruct();
 }
@@ -1015,7 +1006,7 @@ MaybeDmix(snd_pcm_t *pcm) noexcept
 	return MaybeDmix(snd_pcm_type(pcm));
 }
 
-inline void
+void
 AlsaOutput::Open(AudioFormat &audio_format)
 {
 	int err = snd_pcm_open(&pcm, GetDevice(),
@@ -1158,7 +1149,7 @@ AlsaOutput::DrainInternal()
 	return snd_pcm_drain(pcm) != -EAGAIN;
 }
 
-inline void
+void
 AlsaOutput::Drain()
 {
 	const std::lock_guard<Mutex> lock(mutex);
@@ -1183,8 +1174,8 @@ AlsaOutput::CancelInternal()
 	ClearRingBuffer();
 }
 
-inline void
-AlsaOutput::Cancel()
+void
+AlsaOutput::Cancel() noexcept
 {
 	if (!active) {
 		/* early cancel, quick code path without thread
@@ -1202,8 +1193,8 @@ AlsaOutput::Cancel()
 		});
 }
 
-inline void
-AlsaOutput::Close()
+void
+AlsaOutput::Close() noexcept
 {
 	/* make sure the I/O thread isn't inside DispatchSockets() */
 	BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
@@ -1217,7 +1208,7 @@ AlsaOutput::Close()
 	delete[] silence;
 }
 
-inline size_t
+size_t
 AlsaOutput::Play(const void *chunk, size_t size)
 {
 	assert(size > 0);
@@ -1331,23 +1322,9 @@ try {
 	cond.signal();
 }
 
-typedef AudioOutputWrapper<AlsaOutput> Wrapper;
-
 const struct AudioOutputPlugin alsa_output_plugin = {
 	"alsa",
 	alsa_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	&Wrapper::Enable,
-	&Wrapper::Disable,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	&Wrapper::Drain,
-	&Wrapper::Cancel,
-	nullptr,
-
+	&AlsaOutput::Create,
 	&alsa_mixer_plugin,
 };
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
index ed8b359aa8679f3de00b248a82754da68f326e58..30de2ead6138e5301b508a677e6cd9c2c8d3af08 100644
--- a/src/output/plugins/AoOutputPlugin.cxx
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "AoOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "system/Error.hxx"
 #include "util/DivideString.hxx"
 #include "util/SplitString.hxx"
@@ -37,11 +36,7 @@ static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
 
 static unsigned ao_output_ref;
 
-class AoOutput {
-	friend struct AudioOutputWrapper<AoOutput>;
-
-	AudioOutput base;
-
+class AoOutput final : AudioOutput {
 	const size_t write_size;
 	int driver;
 	ao_option *options = nullptr;
@@ -51,14 +46,14 @@ class AoOutput {
 	~AoOutput();
 
 public:
-	static AoOutput *Create(EventLoop &, const ConfigBlock &block) {
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
 		return new AoOutput(block);
 	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 };
 
 static constexpr Domain ao_output_domain("ao_output");
@@ -95,7 +90,7 @@ MakeAoError()
 }
 
 AoOutput::AoOutput(const ConfigBlock &block)
-	:base(ao_output_plugin),
+	:AudioOutput(0),
 	 write_size(block.GetBlockValue("write_size", 1024u))
 {
 	if (ao_output_ref == 0) {
@@ -177,7 +172,7 @@ AoOutput::Open(AudioFormat &audio_format)
 }
 
 void
-AoOutput::Close()
+AoOutput::Close() noexcept
 {
 	ao_close(device);
 }
@@ -199,22 +194,9 @@ AoOutput::Play(const void *chunk, size_t size)
 	return size;
 }
 
-typedef AudioOutputWrapper<AoOutput> Wrapper;
-
 const struct AudioOutputPlugin ao_output_plugin = {
 	"ao",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	nullptr,
+	&AoOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
index 788ad85e6168a96a14baa6a3f3d75ffebf67430c..da7b6de67207977810b272a106f9551a816fe7d4 100644
--- a/src/output/plugins/FifoOutputPlugin.cxx
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "FifoOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "../Timer.hxx"
 #include "fs/AllocatedPath.hxx"
 #include "fs/FileSystem.hxx"
@@ -34,11 +33,7 @@
 #include <errno.h>
 #include <unistd.h>
 
-class FifoOutput {
-	friend struct AudioOutputWrapper<FifoOutput>;
-
-	AudioOutput base;
-
+class FifoOutput final : AudioOutput {
 	const AllocatedPath path;
 	std::string path_utf8;
 
@@ -54,9 +49,12 @@ public:
 		CloseFifo();
 	}
 
-	static FifoOutput *Create(EventLoop &event_loop,
-				  const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new FifoOutput(block);
+	}
 
+private:
 	void Create();
 	void Check();
 	void Delete();
@@ -64,18 +62,18 @@ public:
 	void OpenFifo();
 	void CloseFifo();
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	std::chrono::steady_clock::duration Delay() const noexcept;
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
+	std::chrono::steady_clock::duration Delay() const noexcept override;
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
 };
 
 static constexpr Domain fifo_output_domain("fifo_output");
 
 FifoOutput::FifoOutput(const ConfigBlock &block)
-	:base(fifo_output_plugin),
+	:AudioOutput(0),
 	 path(block.GetPath("path"))
 {
 	if (path.IsNull())
@@ -169,12 +167,6 @@ try {
 	throw;
 }
 
-inline FifoOutput *
-FifoOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new FifoOutput(block);
-}
-
 void
 FifoOutput::Open(AudioFormat &audio_format)
 {
@@ -182,13 +174,13 @@ FifoOutput::Open(AudioFormat &audio_format)
 }
 
 void
-FifoOutput::Close()
+FifoOutput::Close() noexcept
 {
 	delete timer;
 }
 
-inline void
-FifoOutput::Cancel()
+void
+FifoOutput::Cancel() noexcept
 {
 	timer->Reset();
 
@@ -205,7 +197,7 @@ FifoOutput::Cancel()
 	}
 }
 
-inline std::chrono::steady_clock::duration
+std::chrono::steady_clock::duration
 FifoOutput::Delay() const noexcept
 {
 	return timer->IsStarted()
@@ -213,7 +205,7 @@ FifoOutput::Delay() const noexcept
 		: std::chrono::steady_clock::duration::zero();
 }
 
-inline size_t
+size_t
 FifoOutput::Play(const void *chunk, size_t size)
 {
 	if (!timer->IsStarted())
@@ -241,22 +233,9 @@ FifoOutput::Play(const void *chunk, size_t size)
 	}
 }
 
-typedef AudioOutputWrapper<FifoOutput> Wrapper;
-
 const struct AudioOutputPlugin fifo_output_plugin = {
 	"fifo",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
+	&FifoOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/HaikuOutputPlugin.cxx b/src/output/plugins/HaikuOutputPlugin.cxx
index bb8d9a378c0c0fb5287507f8cd39cab6e2d67b24..ff9c23f94e6d445a9f6b1c8fbf93a0d33846520f 100644
--- a/src/output/plugins/HaikuOutputPlugin.cxx
+++ b/src/output/plugins/HaikuOutputPlugin.cxx
@@ -21,7 +21,6 @@
 #include "config.h"
 #include "HaikuOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "util/Domain.hxx"
 #include "system/Error.hxx"
@@ -43,13 +42,10 @@
 
 #define UTF8_PLAY "\xE2\x96\xB6"
 
-class HaikuOutput {
-	friend struct AudioOutputWrapper<HaikuOutput>;
+class HaikuOutput final: AudioOutput {
 	friend int haiku_output_get_volume(HaikuOutput &haiku);
 	friend bool haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
 
-	AudioOutput base;
-
 	size_t write_size;
 
 	media_raw_audio_format format;
@@ -66,27 +62,28 @@ class HaikuOutput {
 
 public:
 	HaikuOutput(const ConfigBlock &block)
-		:base(haiku_output_plugin),
+		:AudioOutput(0),
 		 /* XXX: by default we should let the MediaKit propose the buffer size */
 		 write_size(block.GetBlockValue("write_size", 4096u)) {}
 
 	~HaikuOutput();
 
-	static HaikuOutput *Create(EventLoop &event_loop,
+	static AudioOutput *Create(EventLoop &event_loop,
 				   const ConfigBlock &block);
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
 
-	std::chrono::steady_clock::duration Delay() noexcept;
+	std::chrono::steady_clock::duration Delay() const noexcept override;
 
 	void FillBuffer(void* _buffer, size_t size,
 		gcc_unused const media_raw_audio_format& _format);
 
-	void SendTag(const Tag &tag);
+	void SendTag(const Tag &tag) override;
 };
 
 static constexpr Domain haiku_output_domain("haiku_output");
@@ -120,7 +117,7 @@ haiku_test_default_device(void)
 
 }
 
-inline HaikuOutput *
+inline AudioOutput *
 HaikuOutput::Create(EventLoop &, const ConfigBlock &block)
 {
 	initialize_application();
@@ -129,7 +126,7 @@ HaikuOutput::Create(EventLoop &, const ConfigBlock &block)
 }
 
 void
-HaikuOutput::Close()
+HaikuOutput::Close() noexcept
 {
 	sound_player->SetHasData(false);
 	delete_sem(new_buffer);
@@ -139,8 +136,6 @@ HaikuOutput::Close()
 	sound_player = nullptr;
 }
 
-
-
 HaikuOutput::~HaikuOutput()
 {
 	delete_sem(new_buffer);
@@ -186,7 +181,7 @@ HaikuOutput::FillBuffer(void* _buffer, size_t size,
 	}
 }
 
-inline void
+void
 HaikuOutput::Open(AudioFormat &audio_format)
 {
 	status_t err;
@@ -265,7 +260,7 @@ HaikuOutput::Open(AudioFormat &audio_format)
 	sound_player->SetHasData(false);
 }
 
-inline size_t
+size_t
 HaikuOutput::Play(const void *chunk, size_t size)
 {
 	BSoundPlayer* const soundPlayer = sound_player;
@@ -311,7 +306,7 @@ HaikuOutput::Play(const void *chunk, size_t size)
 }
 
 inline std::chrono::steady_clock::duration
-HaikuOutput::Delay() noexcept
+HaikuOutput::Delay() const noexcept
 {
 	unsigned delay = buffer_filled ? 0 : buffer_delay;
 
@@ -324,7 +319,7 @@ HaikuOutput::Delay() noexcept
 	return std::chrono::steady_clock::duration::zero();
 }
 
-inline void
+void
 HaikuOutput::SendTag(const Tag &tag)
 {
 	status_t err;
@@ -468,18 +463,6 @@ typedef AudioOutputWrapper<HaikuOutput> Wrapper;
 const struct AudioOutputPlugin haiku_output_plugin = {
 	"haiku",
 	haiku_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	&Wrapper::SendTag,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	nullptr,
-
+	&HaikuOutput::Create,
 	&haiku_mixer_plugin,
 };
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index e1884a95cd598284e2798e98ab416059c9694183..d0b62240fcadb11be8b0f66d5339b5e417c88922 100644
--- a/src/output/plugins/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "JackOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "config/ConfigError.hxx"
 #include "util/ScopeExit.hxx"
 #include "util/ConstBuffer.hxx"
@@ -42,9 +41,7 @@ static constexpr unsigned MAX_PORTS = 16;
 
 static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
 
-struct JackOutput {
-	AudioOutput base;
-
+struct JackOutput final : AudioOutput {
 	/**
 	 * libjack options passed to jack_client_open().
 	 */
@@ -99,12 +96,12 @@ struct JackOutput {
 		shutdown = true;
 	}
 
-	void Enable();
-	void Disable();
+	void Enable() override;
+	void Disable() noexcept override;
 
-	void Open(AudioFormat &new_audio_format);
+	void Open(AudioFormat &new_audio_format) override;
 
-	void Close() {
+	void Close() noexcept override {
 		Stop();
 	}
 
@@ -128,15 +125,15 @@ struct JackOutput {
 	 */
 	size_t WriteSamples(const float *src, size_t n_frames);
 
-	std::chrono::steady_clock::duration Delay() const noexcept {
+	std::chrono::steady_clock::duration Delay() const noexcept override {
 		return pause && !shutdown
 			? std::chrono::seconds(1)
 			: std::chrono::steady_clock::duration::zero();
 	}
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 
-	bool Pause();
+	bool Pause() noexcept override;
 };
 
 static constexpr Domain jack_output_domain("jack_output");
@@ -162,7 +159,7 @@ parse_port_list(const char *source, std::string dest[])
 }
 
 JackOutput::JackOutput(const ConfigBlock &block)
-	:base(jack_output_plugin),
+	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
 	 name(block.GetBlockValue("client_name", nullptr)),
 	 server_name(block.GetBlockValue("server_name", nullptr))
 {
@@ -430,7 +427,7 @@ JackOutput::Enable()
 }
 
 inline void
-JackOutput::Disable()
+JackOutput::Disable() noexcept
 {
 	if (client != nullptr)
 		Disconnect();
@@ -452,8 +449,7 @@ mpd_jack_init(EventLoop &, const ConfigBlock &block)
 	jack_set_info_function(mpd_jack_info);
 #endif
 
-	auto *jd = new JackOutput(block);
-	return &jd->base;
+	return new JackOutput(block);
 }
 
 /**
@@ -663,7 +659,7 @@ JackOutput::Play(const void *chunk, size_t size)
 }
 
 inline bool
-JackOutput::Pause()
+JackOutput::Pause() noexcept
 {
 	if (shutdown)
 		return false;
@@ -673,22 +669,9 @@ JackOutput::Pause()
 	return true;
 }
 
-typedef AudioOutputWrapper<JackOutput> Wrapper;
-
 const struct AudioOutputPlugin jack_output_plugin = {
 	"jack",
 	mpd_jack_test_default_device,
 	mpd_jack_init,
-	&Wrapper::Finish,
-	&Wrapper::Enable,
-	&Wrapper::Disable,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	&Wrapper::Pause,
 	nullptr,
 };
diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
index 4b28751d5c88a5fbff22788cfafa653f325f1cc4..bc87c690d8dd94fe89443187c9951bca2dedae2f 100644
--- a/src/output/plugins/NullOutputPlugin.cxx
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -20,43 +20,41 @@
 #include "config.h"
 #include "NullOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "../Timer.hxx"
 
-class NullOutput {
-	friend struct AudioOutputWrapper<NullOutput>;
-
-	AudioOutput base;
-
+class NullOutput final  : AudioOutput {
 	const bool sync;
 
 	Timer *timer;
 
 public:
 	NullOutput(const ConfigBlock &block)
-		:base(null_output_plugin),
+		:AudioOutput(0),
 		 sync(block.GetBlockValue("sync", true)) {}
 
-	static NullOutput *Create(EventLoop &event_loop,
-				  const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new NullOutput(block);
+	}
 
-	void Open(AudioFormat &audio_format) {
+private:
+	void Open(AudioFormat &audio_format) override {
 		if (sync)
 			timer = new Timer(audio_format);
 	}
 
-	void Close() {
+	void Close() noexcept override {
 		if (sync)
 			delete timer;
 	}
 
-	std::chrono::steady_clock::duration Delay() const noexcept {
+	std::chrono::steady_clock::duration Delay() const noexcept override {
 		return sync && timer->IsStarted()
 			? timer->GetDelay()
 			: std::chrono::steady_clock::duration::zero();
 	}
 
-	size_t Play(gcc_unused const void *chunk, size_t size) {
+	size_t Play(gcc_unused const void *chunk, size_t size) override {
 		if (sync) {
 			if (!timer->IsStarted())
 				timer->Start();
@@ -66,34 +64,15 @@ public:
 		return size;
 	}
 
-	void Cancel() {
+	void Cancel() noexcept override {
 		if (sync)
 			timer->Reset();
 	}
 };
 
-inline NullOutput *
-NullOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new NullOutput(block);
-}
-
-typedef AudioOutputWrapper<NullOutput> Wrapper;
-
 const struct AudioOutputPlugin null_output_plugin = {
 	"null",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
+	&NullOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index 03c1bc6f1f0b15c4b65d201cb5e8fcadd0185cde..eed0552da60ab392fc4ebba7ac6b7a020ac37113 100644
--- a/src/output/plugins/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -35,9 +35,7 @@
 
 #include <memory>
 
-struct OSXOutput {
-	AudioOutput base;
-
+struct OSXOutput final : AudioOutput {
 	/* configuration settings */
 	OSType component_subtype;
 	/* only applicable with kAudioUnitSubType_HALOutput */
@@ -53,6 +51,18 @@ struct OSXOutput {
 	boost::lockfree::spsc_queue<uint8_t> *ring_buffer;
 
 	OSXOutput(const ConfigBlock &block);
+
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
+
+private:
+	void Enable() override;
+	void Disable() noexcept override;
+
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
+
+	std::chrono::steady_clock::duration Delay() const noexcept override;
+	size_t Play(const void *chunk, size_t size) override;
 };
 
 static constexpr Domain osx_output_domain("osx_output");
@@ -80,7 +90,7 @@ osx_output_test_default_device(void)
 }
 
 OSXOutput::OSXOutput(const ConfigBlock &block)
-	:base(osx_output_plugin)
+	:AudioOutput(FLAG_ENABLE_DISABLE)
 {
 	const char *device = block.GetBlockValue("device");
 
@@ -103,8 +113,8 @@ OSXOutput::OSXOutput(const ConfigBlock &block)
 	sync_sample_rate = block.GetBlockValue("sync_sample_rate", false);
 }
 
-static AudioOutput *
-osx_output_init(EventLoop &, const ConfigBlock &block)
+AudioOutput *
+OSXOutput::Create(EventLoop &, const ConfigBlock &block)
 {
 	OSXOutput *oo = new OSXOutput(block);
 
@@ -124,15 +134,7 @@ osx_output_init(EventLoop &, const ConfigBlock &block)
 				   &dev_id);
 	oo->dev_id = dev_id;
 
-	return &oo->base;
-}
-
-static void
-osx_output_finish(AudioOutput *ao)
-{
-	OSXOutput *oo = (OSXOutput *)ao;
-
-	delete oo;
+	return oo;
 }
 
 static void
@@ -513,15 +515,14 @@ osx_render(void *vdata,
  	return noErr;
 }
 
-static void
-osx_output_enable(AudioOutput *ao)
+void
+OSXOutput::Enable()
 {
 	char errormsg[1024];
-	OSXOutput *oo = (OSXOutput *)ao;
 
 	AudioComponentDescription desc;
 	desc.componentType = kAudioUnitType_Output;
-	desc.componentSubType = oo->component_subtype;
+	desc.componentSubType = component_subtype;
 	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
 	desc.componentFlags = 0;
 	desc.componentFlagsMask = 0;
@@ -530,7 +531,7 @@ osx_output_enable(AudioOutput *ao)
 	if (comp == 0)
 		throw std::runtime_error("Error finding OS X component");
 
-	OSStatus status = AudioComponentInstanceNew(comp, &oo->au);
+	OSStatus status = AudioComponentInstanceNew(comp, &au);
 	if (status != noErr) {
 		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
 		throw FormatRuntimeError("Unable to open OS X component: %s",
@@ -538,105 +539,97 @@ osx_output_enable(AudioOutput *ao)
 	}
 
 	try {
-		osx_output_set_device(oo);
+		osx_output_set_device(this);
 	} catch (...) {
-		AudioComponentInstanceDispose(oo->au);
+		AudioComponentInstanceDispose(au);
 		throw;
 	}
 
-	if (oo->hog_device) {
-		osx_output_hog_device(oo->dev_id, true);
-	}
+	if (hog_device)
+		osx_output_hog_device(dev_id, true);
 }
 
-static void
-osx_output_disable(AudioOutput *ao)
+void
+OSXOutput::Disable() noexcept
 {
-	OSXOutput *oo = (OSXOutput *)ao;
-
-	AudioComponentInstanceDispose(oo->au);
+	AudioComponentInstanceDispose(au);
 
-	if (oo->hog_device) {
-		osx_output_hog_device(oo->dev_id, false);
-	}
+	if (hog_device)
+		osx_output_hog_device(dev_id, false);
 }
 
-static void
-osx_output_close(AudioOutput *ao)
+void
+OSXOutput::Close() noexcept
 {
-	OSXOutput *od = (OSXOutput *)ao;
-
-	AudioOutputUnitStop(od->au);
-	AudioUnitUninitialize(od->au);
+	AudioOutputUnitStop(au);
+	AudioUnitUninitialize(au);
 
-	delete od->ring_buffer;
+	delete ring_buffer;
 }
 
-static void
-osx_output_open(AudioOutput *ao, AudioFormat &audio_format)
+void
+OSXOutput::Open(AudioFormat &audio_format)
 {
 	char errormsg[1024];
-	OSXOutput *od = (OSXOutput *)ao;
 
-	memset(&od->asbd, 0, sizeof(od->asbd));
-	od->asbd.mSampleRate = audio_format.sample_rate;
-	od->asbd.mFormatID = kAudioFormatLinearPCM;
-	od->asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+	memset(&asbd, 0, sizeof(asbd));
+	asbd.mSampleRate = audio_format.sample_rate;
+	asbd.mFormatID = kAudioFormatLinearPCM;
+	asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
 
 	switch (audio_format.format) {
 	case SampleFormat::S8:
-		od->asbd.mBitsPerChannel = 8;
+		asbd.mBitsPerChannel = 8;
 		break;
 
 	case SampleFormat::S16:
-		od->asbd.mBitsPerChannel = 16;
+		asbd.mBitsPerChannel = 16;
 		break;
 
 	case SampleFormat::S32:
-		od->asbd.mBitsPerChannel = 32;
+		asbd.mBitsPerChannel = 32;
 		break;
 
 	default:
 		audio_format.format = SampleFormat::S32;
-		od->asbd.mBitsPerChannel = 32;
+		asbd.mBitsPerChannel = 32;
 		break;
 	}
 
 	if (IsBigEndian())
-		od->asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+		asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
 
-	od->asbd.mBytesPerPacket = audio_format.GetFrameSize();
-	od->asbd.mFramesPerPacket = 1;
-	od->asbd.mBytesPerFrame = od->asbd.mBytesPerPacket;
-	od->asbd.mChannelsPerFrame = audio_format.channels;
+	asbd.mBytesPerPacket = audio_format.GetFrameSize();
+	asbd.mFramesPerPacket = 1;
+	asbd.mBytesPerFrame = asbd.mBytesPerPacket;
+	asbd.mChannelsPerFrame = audio_format.channels;
 
-	if (od->sync_sample_rate) {
-		osx_output_sync_device_sample_rate(od->dev_id, od->asbd);
-	}
+	if (sync_sample_rate)
+		osx_output_sync_device_sample_rate(dev_id, asbd);
 
 	OSStatus status =
-		AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+		AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat,
 				     kAudioUnitScope_Input, 0,
-				     &od->asbd,
-				     sizeof(od->asbd));
+				     &asbd,
+				     sizeof(asbd));
 	if (status != noErr)
 		throw std::runtime_error("Unable to set format on OS X device");
 
 	AURenderCallbackStruct callback;
 	callback.inputProc = osx_render;
-	callback.inputProcRefCon = od;
+	callback.inputProcRefCon = this;
 
 	status =
-		AudioUnitSetProperty(od->au,
+		AudioUnitSetProperty(au,
 				     kAudioUnitProperty_SetRenderCallback,
 				     kAudioUnitScope_Input, 0,
 				     &callback, sizeof(callback));
 	if (status != noErr) {
-		AudioComponentInstanceDispose(od->au);
+		AudioComponentInstanceDispose(au);
 		throw std::runtime_error("unable to set callback for OS X audio unit");
 	}
 
-	status = AudioUnitInitialize(od->au);
+	status = AudioUnitInitialize(au);
 	if (status != noErr) {
 		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
 		throw FormatRuntimeError("Unable to initialize OS X audio unit: %s",
@@ -644,36 +637,34 @@ osx_output_open(AudioOutput *ao, AudioFormat &audio_format)
 	}
 
 	UInt32 buffer_frame_size = 1;
-	status = osx_output_set_buffer_size(od->au, od->asbd, &buffer_frame_size);
+	status = osx_output_set_buffer_size(au, asbd, &buffer_frame_size);
 	if (status != noErr) {
 		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
 		throw FormatRuntimeError("Unable to set frame size: %s",
 					 errormsg);
 	}
 
-	od->ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(buffer_frame_size);
+	ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(buffer_frame_size);
 
-	status = AudioOutputUnitStart(od->au);
+	status = AudioOutputUnitStart(au);
 	if (status != 0) {
-		AudioUnitUninitialize(od->au);
+		AudioUnitUninitialize(au);
 		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
 		throw FormatRuntimeError("unable to start audio output: %s",
 					 errormsg);
 	}
 }
 
-static size_t
-osx_output_play(AudioOutput *ao, const void *chunk, size_t size)
+size_t
+OSXOutput::Play(const void *chunk, size_t size)
 {
-	OSXOutput *od = (OSXOutput *)ao;
-	return od->ring_buffer->push((uint8_t *)chunk, size);
+	return ring_buffer->push((uint8_t *)chunk, size);
 }
 
-static std::chrono::steady_clock::duration
-osx_output_delay(AudioOutput *ao) noexcept
+std::chrono::steady_clock::duration
+OSXOutput::Delay() const noexcept
 {
-	OSXOutput *od = (OSXOutput *)ao;
-	return od->ring_buffer->write_available()
+	return ring_buffer->write_available()
 		? std::chrono::steady_clock::duration::zero()
 		: std::chrono::milliseconds(25);
 }
@@ -681,17 +672,6 @@ osx_output_delay(AudioOutput *ao) noexcept
 const struct AudioOutputPlugin osx_output_plugin = {
 	"osx",
 	osx_output_test_default_device,
-	osx_output_init,
-	osx_output_finish,
-	osx_output_enable,
-	osx_output_disable,
-	osx_output_open,
-	osx_output_close,
-	osx_output_delay,
-	nullptr,
-	osx_output_play,
-	nullptr,
-	nullptr,
-	nullptr,
+	&OSXOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
index 0bdd98038921835fc35985fa65a94037790c364b..c96f14ededa17f67b5c4977f34f05584238b6e98 100644
--- a/src/output/plugins/OpenALOutputPlugin.cxx
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "OpenALOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "util/RuntimeError.hxx"
 
 #include <unistd.h>
@@ -33,14 +32,10 @@
 #include <OpenAL/alc.h>
 #endif
 
-class OpenALOutput {
-	friend struct AudioOutputWrapper<OpenALOutput>;
-
+class OpenALOutput final : AudioOutput {
 	/* should be enough for buffer size = 2048 */
 	static constexpr unsigned NUM_BUFFERS = 16;
 
-	AudioOutput base;
-
 	const char *device_name;
 	ALCdevice *device;
 	ALCcontext *context;
@@ -52,14 +47,18 @@ class OpenALOutput {
 
 	OpenALOutput(const ConfigBlock &block);
 
-	static OpenALOutput *Create(EventLoop &event_loop,
-				    const ConfigBlock &block);
+public:
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new OpenALOutput(block);
+	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
 	gcc_pure
-	std::chrono::steady_clock::duration Delay() const noexcept {
+	std::chrono::steady_clock::duration Delay() const noexcept override {
 		return filled < NUM_BUFFERS || HasProcessed()
 			? std::chrono::steady_clock::duration::zero()
 			/* we don't know exactly how long we must wait
@@ -68,9 +67,9 @@ class OpenALOutput {
 			: std::chrono::milliseconds(50);
 	}
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 
-	void Cancel();
+	void Cancel() noexcept override;
 
 private:
 	gcc_pure
@@ -138,7 +137,7 @@ OpenALOutput::SetupContext()
 }
 
 OpenALOutput::OpenALOutput(const ConfigBlock &block)
-	:base(openal_output_plugin),
+	:AudioOutput(0),
 	 device_name(block.GetBlockValue("device"))
 {
 	if (device_name == nullptr)
@@ -146,13 +145,7 @@ OpenALOutput::OpenALOutput(const ConfigBlock &block)
 					   ALC_DEFAULT_DEVICE_SPECIFIER);
 }
 
-inline OpenALOutput *
-OpenALOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new OpenALOutput(block);
-}
-
-inline void
+void
 OpenALOutput::Open(AudioFormat &audio_format)
 {
 	format = openal_audio_format(audio_format);
@@ -176,8 +169,8 @@ OpenALOutput::Open(AudioFormat &audio_format)
 	frequency = audio_format.sample_rate;
 }
 
-inline void
-OpenALOutput::Close()
+void
+OpenALOutput::Close() noexcept
 {
 	alcMakeContextCurrent(context);
 	alDeleteSources(1, &source);
@@ -186,7 +179,7 @@ OpenALOutput::Close()
 	alcCloseDevice(device);
 }
 
-inline size_t
+size_t
 OpenALOutput::Play(const void *chunk, size_t size)
 {
 	if (alcGetCurrentContext() != context)
@@ -214,8 +207,8 @@ OpenALOutput::Play(const void *chunk, size_t size)
 	return size;
 }
 
-inline void
-OpenALOutput::Cancel()
+void
+OpenALOutput::Cancel() noexcept
 {
 	filled = 0;
 	alcMakeContextCurrent(context);
@@ -226,22 +219,9 @@ OpenALOutput::Cancel()
 	filled = 0;
 }
 
-typedef AudioOutputWrapper<OpenALOutput> Wrapper;
-
 const struct AudioOutputPlugin openal_output_plugin = {
 	"openal",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
+	OpenALOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
index 6581237fa423d3624c66e1168cb696f6aff4d8ea..512ec67bc6a152239f84f30d7d3f29590a75290a 100644
--- a/src/output/plugins/OssOutputPlugin.cxx
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "OssOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "system/fd_util.h"
 #include "system/Error.hxx"
@@ -60,11 +59,7 @@
 #include "util/Manual.hxx"
 #endif
 
-class OssOutput {
-	friend struct AudioOutputWrapper<OssOutput>;
-
-	AudioOutput base;
-
+class OssOutput final : AudioOutput {
 #ifdef AFMT_S24_PACKED
 	Manual<PcmExport> pcm_export;
 #endif
@@ -84,32 +79,38 @@ class OssOutput {
 	 */
 	int oss_format;
 
+#ifdef AFMT_S24_PACKED
+	static constexpr unsigned oss_flags = FLAG_ENABLE_DISABLE;
+#else
+	static constexpr unsigned oss_flags = 0;
+#endif
+
 public:
 	explicit OssOutput(const char *_device=nullptr)
-		:base(oss_output_plugin),
+		:AudioOutput(oss_flags),
 		 fd(-1), device(_device) {}
 
-	static OssOutput *Create(EventLoop &event_loop,
-				 const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &event_loop,
+				   const ConfigBlock &block);
 
 #ifdef AFMT_S24_PACKED
-	void Enable() {
+	void Enable() override {
 		pcm_export.Construct();
 	}
 
-	void Disable() {
+	void Disable() noexcept override {
 		pcm_export.Destruct();
 	}
 #endif
 
-	void Open(AudioFormat &audio_format);
+	void Open(AudioFormat &audio_format) override;
 
-	void Close() {
+	void Close() noexcept override {
 		DoClose();
 	}
 
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
 
 private:
 	/**
@@ -225,7 +226,7 @@ oss_open_default()
 	throw std::runtime_error("error trying to open default OSS device");
 }
 
-inline OssOutput *
+AudioOutput *
 OssOutput::Create(EventLoop &, const ConfigBlock &block)
 {
 	const char *device = block.GetBlockValue("device");
@@ -637,7 +638,7 @@ try {
 	throw;
 }
 
-inline void
+void
 OssOutput::Open(AudioFormat &_audio_format)
 try {
 	fd = open_cloexec(device, O_WRONLY, 0);
@@ -652,8 +653,8 @@ try {
 	throw;
 }
 
-inline void
-OssOutput::Cancel()
+void
+OssOutput::Cancel() noexcept
 {
 	if (fd >= 0) {
 		ioctl(fd, SNDCTL_DSP_RESET, 0);
@@ -665,7 +666,7 @@ OssOutput::Cancel()
 #endif
 }
 
-inline size_t
+size_t
 OssOutput::Play(const void *chunk, size_t size)
 {
 	ssize_t ret;
@@ -698,28 +699,9 @@ OssOutput::Play(const void *chunk, size_t size)
 	}
 }
 
-typedef AudioOutputWrapper<OssOutput> Wrapper;
-
 const struct AudioOutputPlugin oss_output_plugin = {
 	"oss",
 	oss_output_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-#ifdef AFMT_S24_PACKED
-	&Wrapper::Enable,
-	&Wrapper::Disable,
-#else
-	nullptr,
-	nullptr,
-#endif
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
-
+	OssOutput::Create,
 	&oss_mixer_plugin,
 };
diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
index b0f7f6492796b4cf82213602ec066f4357627850..623b88a9a14050d84d069b68d76f59cdfd1967fa 100644
--- a/src/output/plugins/PipeOutputPlugin.cxx
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "PipeOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "system/Error.hxx"
 
 #include <string>
@@ -28,43 +27,36 @@
 
 #include <stdio.h>
 
-class PipeOutput {
-	friend struct AudioOutputWrapper<PipeOutput>;
-
-	AudioOutput base;
-
+class PipeOutput final : AudioOutput {
 	const std::string cmd;
 	FILE *fh;
 
 	PipeOutput(const ConfigBlock &block);
 
 public:
-	static PipeOutput *Create(EventLoop &event_loop,
-				  const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new PipeOutput(block);
+	}
 
-	void Open(AudioFormat &audio_format);
+private:
+	void Open(AudioFormat &audio_format) override;
 
-	void Close() {
+	void Close() noexcept override {
 		pclose(fh);
 	}
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 };
 
 PipeOutput::PipeOutput(const ConfigBlock &block)
-	:base(pipe_output_plugin),
+	:AudioOutput(0),
 	 cmd(block.GetBlockValue("command", ""))
 {
 	if (cmd.empty())
 		throw std::runtime_error("No \"command\" parameter specified");
 }
 
-inline PipeOutput *
-PipeOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new PipeOutput(block);
-}
-
 inline void
 PipeOutput::Open(gcc_unused AudioFormat &audio_format)
 {
@@ -83,22 +75,9 @@ PipeOutput::Play(const void *chunk, size_t size)
 	return nbytes;
 }
 
-typedef AudioOutputWrapper<PipeOutput> Wrapper;
-
 const struct AudioOutputPlugin pipe_output_plugin = {
 	"pipe",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	nullptr,
+	&PipeOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index da18bfc4b0c7e47f8e8b8780baac4e0bc78866de..fa603d5f7f6cb55741904853e4235478931bcc56 100644
--- a/src/output/plugins/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -24,7 +24,6 @@
 #include "lib/pulse/LogError.hxx"
 #include "lib/pulse/LockGuard.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "mixer/plugins/PulseMixerPlugin.hxx"
 #include "Log.hxx"
@@ -44,11 +43,7 @@
 
 #define MPD_PULSE_NAME "Music Player Daemon"
 
-class PulseOutput {
-	friend struct AudioOutputWrapper<PulseOutput>;
-
-	AudioOutput base;
-
+class PulseOutput final : AudioOutput {
 	const char *name;
 	const char *server;
 	const char *sink;
@@ -94,19 +89,21 @@ public:
 
 	static bool TestDefaultDevice();
 
-	static PulseOutput *Create(EventLoop &event_loop,
-				   const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new PulseOutput(block);
+	}
 
-	void Enable();
-	void Disable();
+	void Enable() override;
+	void Disable() noexcept override;
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	std::chrono::steady_clock::duration Delay() noexcept;
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
-	bool Pause();
+	std::chrono::steady_clock::duration Delay() const noexcept override;
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
+	bool Pause() noexcept override;
 
 private:
 	/**
@@ -179,7 +176,7 @@ private:
 };
 
 PulseOutput::PulseOutput(const ConfigBlock &block)
-	:base(pulse_output_plugin),
+	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
 	 name(block.GetBlockValue("name", "mpd_pulse")),
 	 server(block.GetBlockValue("server")),
 	 sink(block.GetBlockValue("sink"))
@@ -416,13 +413,7 @@ PulseOutput::SetupContext()
 	}
 }
 
-PulseOutput *
-PulseOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new PulseOutput(block);
-}
-
-inline void
+void
 PulseOutput::Enable()
 {
 	assert(mainloop == nullptr);
@@ -458,8 +449,8 @@ PulseOutput::Enable()
 	pa_threaded_mainloop_unlock(mainloop);
 }
 
-inline void
-PulseOutput::Disable()
+void
+PulseOutput::Disable() noexcept
 {
 	assert(mainloop != nullptr);
 
@@ -607,7 +598,7 @@ PulseOutput::SetupStream(const pa_sample_spec &ss)
 				     pulse_output_stream_write_cb, this);
 }
 
-inline void
+void
 PulseOutput::Open(AudioFormat &audio_format)
 {
 	assert(mainloop != nullptr);
@@ -680,8 +671,8 @@ PulseOutput::Open(AudioFormat &audio_format)
 	pause = false;
 }
 
-inline void
-PulseOutput::Close()
+void
+PulseOutput::Close() noexcept
 {
 	assert(mainloop != nullptr);
 
@@ -744,8 +735,8 @@ PulseOutput::StreamPause(bool _pause)
 				     "pa_stream_cork() has failed");
 }
 
-inline std::chrono::steady_clock::duration
-PulseOutput::Delay() noexcept
+std::chrono::steady_clock::duration
+PulseOutput::Delay() const noexcept
 {
 	Pulse::LockGuard lock(mainloop);
 
@@ -758,7 +749,7 @@ PulseOutput::Delay() noexcept
 	return result;
 }
 
-inline size_t
+size_t
 PulseOutput::Play(const void *chunk, size_t size)
 {
 	assert(mainloop != nullptr);
@@ -807,8 +798,8 @@ PulseOutput::Play(const void *chunk, size_t size)
 	return size;
 }
 
-inline void
-PulseOutput::Cancel()
+void
+PulseOutput::Cancel() noexcept
 {
 	assert(mainloop != nullptr);
 	assert(stream != nullptr);
@@ -834,8 +825,8 @@ PulseOutput::Cancel()
 	pulse_wait_for_operation(mainloop, o);
 }
 
-inline bool
-PulseOutput::Pause()
+bool
+PulseOutput::Pause() noexcept
 {
 	assert(mainloop != nullptr);
 	assert(stream != nullptr);
@@ -875,23 +866,9 @@ pulse_output_test_default_device(void)
 	return PulseOutput::TestDefaultDevice();
 }
 
-typedef AudioOutputWrapper<PulseOutput> Wrapper;
-
 const struct AudioOutputPlugin pulse_output_plugin = {
 	"pulse",
 	pulse_output_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	&Wrapper::Enable,
-	&Wrapper::Disable,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	&Wrapper::Pause,
-
+	PulseOutput::Create,
 	&pulse_mixer_plugin,
 };
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
index a57a5477382c86139ca80bf54e9f7ba53e900208..91a88c7a2cde4a3b5d2ca9cb7a3391d9800e5232 100644
--- a/src/output/plugins/RecorderOutputPlugin.cxx
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "RecorderOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "tag/Format.hxx"
 #include "encoder/ToOutputStream.hxx"
 #include "encoder/EncoderInterface.hxx"
@@ -42,11 +41,7 @@
 
 static constexpr Domain recorder_domain("recorder");
 
-class RecorderOutput {
-	friend struct AudioOutputWrapper<RecorderOutput>;
-
-	AudioOutput base;
-
+class RecorderOutput final : AudioOutput {
 	/**
 	 * The configured encoder plugin.
 	 */
@@ -81,20 +76,23 @@ class RecorderOutput {
 		delete prepared_encoder;
 	}
 
-	static RecorderOutput *Create(EventLoop &event_loop,
-				      const ConfigBlock &block);
+public:
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
+		return new RecorderOutput(block);
+	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
 	/**
 	 * Writes pending data from the encoder to the output file.
 	 */
 	void EncoderToFile();
 
-	void SendTag(const Tag &tag);
+	void SendTag(const Tag &tag) override;
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 
 private:
 	gcc_pure
@@ -114,7 +112,7 @@ private:
 };
 
 RecorderOutput::RecorderOutput(const ConfigBlock &block)
-	:base(recorder_output_plugin)
+	:AudioOutput(0)
 {
 	/* read configuration */
 
@@ -141,12 +139,6 @@ RecorderOutput::RecorderOutput(const ConfigBlock &block)
 	prepared_encoder = encoder_init(*encoder_plugin, block);
 }
 
-RecorderOutput *
-RecorderOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new RecorderOutput(block);
-}
-
 inline void
 RecorderOutput::EncoderToFile()
 {
@@ -155,7 +147,7 @@ RecorderOutput::EncoderToFile()
 	EncoderToOutputStream(*file, *encoder);
 }
 
-inline void
+void
 RecorderOutput::Open(AudioFormat &audio_format)
 {
 	/* create the output file */
@@ -227,8 +219,8 @@ RecorderOutput::Commit()
 	delete file;
 }
 
-inline void
-RecorderOutput::Close()
+void
+RecorderOutput::Close() noexcept
 {
 	if (file == nullptr) {
 		/* not currently encoding to a file; nothing needs to
@@ -305,7 +297,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
 		    path.ToUTF8().c_str());
 }
 
-inline void
+void
 RecorderOutput::SendTag(const Tag &tag)
 {
 	if (HasDynamicPath()) {
@@ -347,7 +339,7 @@ RecorderOutput::SendTag(const Tag &tag)
 	encoder->SendTag(tag);
 }
 
-inline size_t
+size_t
 RecorderOutput::Play(const void *chunk, size_t size)
 {
 	if (file == nullptr) {
@@ -365,22 +357,9 @@ RecorderOutput::Play(const void *chunk, size_t size)
 	return size;
 }
 
-typedef AudioOutputWrapper<RecorderOutput> Wrapper;
-
 const struct AudioOutputPlugin recorder_output_plugin = {
 	"recorder",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	&Wrapper::SendTag,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	nullptr,
+	&RecorderOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
index 6f0a31d22fe20a83d01f27663d569166adfb5d39..6be6f24715799f7f1a073e0bc7af8cfae362f08b 100644
--- a/src/output/plugins/RoarOutputPlugin.cxx
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -21,7 +21,6 @@
 #include "config.h"
 #include "RoarOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "thread/Mutex.hxx"
 #include "util/Domain.hxx"
@@ -36,11 +35,7 @@
 #include <roaraudio.h>
 #undef new
 
-class RoarOutput {
-	friend struct AudioOutputWrapper<RoarOutput>;
-
-	AudioOutput base;
-
+class RoarOutput final : AudioOutput {
 	const std::string host, name;
 
 	roar_vs_t * vss;
@@ -54,23 +49,20 @@ class RoarOutput {
 public:
 	RoarOutput(const ConfigBlock &block);
 
-	operator AudioOutput *() {
-		return &base;
-	}
-
-	static RoarOutput *Create(EventLoop &, const ConfigBlock &block) {
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
 		return new RoarOutput(block);
 	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
-
-	void SendTag(const Tag &tag);
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
-
 	int GetVolume() const;
 	void SetVolume(unsigned volume);
+
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
+
+	void SendTag(const Tag &tag) override;
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
 };
 
 static constexpr Domain roar_output_domain("roar_output");
@@ -86,7 +78,7 @@ GetConfiguredRole(const ConfigBlock &block) noexcept
 }
 
 RoarOutput::RoarOutput(const ConfigBlock &block)
-	:base(roar_output_plugin),
+	:AudioOutput(0),
 	 host(block.GetBlockValue("server", "")),
 	 name(block.GetBlockValue("name", "MPD")),
 	 role(GetConfiguredRole(block))
@@ -172,7 +164,7 @@ roar_use_audio_format(struct roar_audio_info *info,
 	}
 }
 
-inline void
+void
 RoarOutput::Open(AudioFormat &audio_format)
 {
 	const std::lock_guard<Mutex> protect(mutex);
@@ -196,8 +188,8 @@ RoarOutput::Open(AudioFormat &audio_format)
 	alive = true;
 }
 
-inline void
-RoarOutput::Close()
+void
+RoarOutput::Close() noexcept
 {
 	const std::lock_guard<Mutex> protect(mutex);
 
@@ -209,8 +201,8 @@ RoarOutput::Close()
 	roar_disconnect(&con);
 }
 
-inline void
-RoarOutput::Cancel()
+void
+RoarOutput::Cancel() noexcept
 {
 	const std::lock_guard<Mutex> protect(mutex);
 
@@ -237,7 +229,7 @@ RoarOutput::Cancel()
 	alive = true;
 }
 
-inline size_t
+size_t
 RoarOutput::Play(const void *chunk, size_t size)
 {
 	if (vss == nullptr)
@@ -296,7 +288,7 @@ roar_tag_convert(TagType type, bool *is_uuid)
 	}
 }
 
-inline void
+void
 RoarOutput::SendTag(const Tag &tag)
 {
 	if (vss == nullptr)
@@ -344,22 +336,9 @@ RoarOutput::SendTag(const Tag &tag)
 	roar_vs_meta(vss, vals, cnt, &(err));
 }
 
-typedef AudioOutputWrapper<RoarOutput> Wrapper;
-
 const struct AudioOutputPlugin roar_output_plugin = {
 	"roar",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	&Wrapper::SendTag,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
+	&RoarOutput::Create,
 	&roar_mixer_plugin,
 };
diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
index ecde49c81b98d9d8b55579fb59db3d772671b876..b2aa904b4ae693683cab8a8267384229767a1f48 100644
--- a/src/output/plugins/ShoutOutputPlugin.cxx
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "ShoutOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "encoder/EncoderInterface.hxx"
 #include "encoder/EncoderPlugin.hxx"
 #include "encoder/EncoderList.hxx"
@@ -39,9 +38,7 @@
 
 static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
 
-struct ShoutOutput final {
-	AudioOutput base;
-
+struct ShoutOutput final : AudioOutput {
 	shout_t *shout_conn;
 	shout_metadata_t *shout_meta;
 
@@ -58,17 +55,17 @@ struct ShoutOutput final {
 	explicit ShoutOutput(const ConfigBlock &block);
 	~ShoutOutput();
 
-	static ShoutOutput *Create(EventLoop &event_loop,
+	static AudioOutput *Create(EventLoop &event_loop,
 				   const ConfigBlock &block);
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	std::chrono::steady_clock::duration Delay() const noexcept;
-	void SendTag(const Tag &tag);
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
-	bool Pause();
+	std::chrono::steady_clock::duration Delay() const noexcept override;
+	void SendTag(const Tag &tag) override;
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
+	bool Pause() noexcept override;
 
 private:
 	void WritePage();
@@ -113,11 +110,11 @@ ShoutSetAudioInfo(shout_t *shout_conn, const AudioFormat &audio_format)
 }
 
 ShoutOutput::ShoutOutput(const ConfigBlock &block)
-	:base(shout_output_plugin),
+	:AudioOutput(FLAG_PAUSE),
 	 shout_conn(shout_new()),
 	 shout_meta(shout_metadata_new())
 {
-	base.NeedFullyDefinedAudioFormat();
+	NeedFullyDefinedAudioFormat();
 
 	const char *host = require_block_string(block, "host");
 	const char *mount = require_block_string(block, "mount");
@@ -250,7 +247,7 @@ ShoutOutput::~ShoutOutput()
 	delete prepared_encoder;
 }
 
-ShoutOutput *
+AudioOutput *
 ShoutOutput::Create(EventLoop &, const ConfigBlock &block)
 {
 	if (shout_init_count == 0)
@@ -306,7 +303,7 @@ ShoutOutput::WritePage()
 }
 
 void
-ShoutOutput::Close()
+ShoutOutput::Close() noexcept
 {
 	try {
 		encoder->End();
@@ -326,7 +323,7 @@ ShoutOutput::Close()
 }
 
 void
-ShoutOutput::Cancel()
+ShoutOutput::Cancel() noexcept
 {
 	/* needs to be implemented for shout */
 }
@@ -381,7 +378,7 @@ ShoutOutput::Play(const void *chunk, size_t size)
 }
 
 bool
-ShoutOutput::Pause()
+ShoutOutput::Pause() noexcept
 {
 	static char silence[1020];
 
@@ -446,22 +443,9 @@ ShoutOutput::SendTag(const Tag &tag)
 	WritePage();
 }
 
-typedef AudioOutputWrapper<ShoutOutput> Wrapper;
-
 const struct AudioOutputPlugin shout_output_plugin = {
 	"shout",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	&Wrapper::SendTag,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	&Wrapper::Pause,
+	&ShoutOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/SndioOutputPlugin.cxx b/src/output/plugins/SndioOutputPlugin.cxx
index 7fa48fc1a41ca4ebac8b60d994a1ac5357f05e5b..5f3ab23c5af78d42e567302bb84590194d71ea74 100644
--- a/src/output/plugins/SndioOutputPlugin.cxx
+++ b/src/output/plugins/SndioOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "SndioOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "util/Domain.hxx"
 #include "Log.hxx"
 
@@ -45,9 +44,7 @@ static constexpr unsigned MPD_SNDIO_BUFFER_TIME_MS = 250;
 
 static constexpr Domain sndio_output_domain("sndio_output");
 
-class SndioOutput {
-	friend struct AudioOutputWrapper<SndioOutput>;
-	AudioOutput base;
+class SndioOutput final : AudioOutput {
 	const char *const device;
 	const unsigned buffer_time; /* in ms */
 	struct sio_hdl *sio_hdl;
@@ -55,29 +52,25 @@ class SndioOutput {
 public:
 	SndioOutput(const ConfigBlock &block);
 
-	static SndioOutput *Create(EventLoop &event_loop,
-				   const ConfigBlock &block);
+	static AudioOutput *Create(EventLoop &,
+				   const ConfigBlock &block) {
+		return new SndioOutput(block);
+	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
+	size_t Play(const void *chunk, size_t size) override;
 };
 
 SndioOutput::SndioOutput(const ConfigBlock &block)
-	:base(sndio_output_plugin),
+	:AudioOutput(0),
 	 device(block.GetBlockValue("device", SIO_DEVANY)),
 	 buffer_time(block.GetBlockValue("buffer_time",
 					 MPD_SNDIO_BUFFER_TIME_MS))
 {
 }
 
-SndioOutput *
-SndioOutput::Create(EventLoop &, const ConfigBlock &block)
-{
-	return new SndioOutput(block);
-}
-
 static bool
 sndio_test_default_device()
 {
@@ -154,7 +147,7 @@ SndioOutput::Open(AudioFormat &audio_format)
 }
 
 void
-SndioOutput::Close()
+SndioOutput::Close()  noexcept
 {
 	sio_close(sio_hdl);
 }
@@ -170,22 +163,9 @@ SndioOutput::Play(const void *chunk, size_t size)
 	return n;
 }
 
-typedef AudioOutputWrapper<SndioOutput> Wrapper;
-
 const struct AudioOutputPlugin sndio_output_plugin = {
 	"sndio",
 	sndio_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	nullptr,
-	nullptr,
+	&SndioOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
index 33309dec7be84348de862b463eba64347be45436..7ac2b07b6725cc8c867a5d2f2fe7f3ade6aab570 100644
--- a/src/output/plugins/SolarisOutputPlugin.cxx
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "SolarisOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "system/FileDescriptor.hxx"
 #include "system/Error.hxx"
 
@@ -50,30 +49,27 @@ struct audio_info {
 
 #endif
 
-class SolarisOutput {
-	friend struct AudioOutputWrapper<SolarisOutput>;
-
-	AudioOutput base;
-
+class SolarisOutput final : AudioOutput {
 	/* configuration */
 	const char *const device;
 
 	FileDescriptor fd;
 
 	explicit SolarisOutput(const ConfigBlock &block)
-		:base(solaris_output_plugin),
+		:AudioOutput(0),
 		 device(block.GetBlockValue("device", "/dev/audio")) {}
 
 public:
-	static SolarisOutput *Create(EventLoop &, const ConfigBlock &block) {
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
 		return new SolarisOutput(block);
 	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	size_t Play(const void *chunk, size_t size);
-	void Cancel();
+	size_t Play(const void *chunk, size_t size) override;
+	void Cancel() noexcept override;
 };
 
 static bool
@@ -128,7 +124,7 @@ SolarisOutput::Open(AudioFormat &audio_format)
 }
 
 void
-SolarisOutput::Close()
+SolarisOutput::Close() noexcept
 {
 	fd.Close();
 }
@@ -144,27 +140,14 @@ SolarisOutput::Play(const void *chunk, size_t size)
 }
 
 void
-SolarisOutput::Cancel()
+SolarisOutput::Cancel() noexcept
 {
 	ioctl(fd.Get(), I_FLUSH);
 }
 
-typedef AudioOutputWrapper<SolarisOutput> Wrapper;
-
 const struct AudioOutputPlugin solaris_output_plugin = {
 	"solaris",
 	solaris_output_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	nullptr,
+	&SolarisOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
index 25372edf73713ed2d118f4a0b1e52c28fefcd4ce..b3897b53bccd2f7da216ad9d4003305bab580c9c 100644
--- a/src/output/plugins/WinmmOutputPlugin.cxx
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "WinmmOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
-#include "../Wrapper.hxx"
 #include "pcm/PcmBuffer.hxx"
 #include "mixer/MixerList.hxx"
 #include "fs/AllocatedPath.hxx"
@@ -39,11 +38,7 @@ struct WinmmBuffer {
 	WAVEHDR hdr;
 };
 
-class WinmmOutput {
-	friend struct AudioOutputWrapper<WinmmOutput>;
-
-	AudioOutput base;
-
+class WinmmOutput final : AudioOutput {
 	const UINT device_id;
 	HWAVEOUT handle;
 
@@ -63,16 +58,17 @@ public:
 		return handle;
 	}
 
-	static WinmmOutput *Create(EventLoop &, const ConfigBlock &block) {
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &block) {
 		return new WinmmOutput(block);
 	}
 
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	size_t Play(const void *chunk, size_t size);
-	void Drain();
-	void Cancel();
+	size_t Play(const void *chunk, size_t size) override;
+	void Drain() override;
+	void Cancel() noexcept override;
 
 private:
 	/**
@@ -82,7 +78,7 @@ private:
 
 	void DrainAllBuffers();
 
-	void Stop();
+	void Stop() noexcept;
 
 };
 
@@ -148,7 +144,7 @@ get_device_id(const char *device_name)
 }
 
 WinmmOutput::WinmmOutput(const ConfigBlock &block)
-	:base(winmm_output_plugin),
+	:AudioOutput(0),
 	 device_id(get_device_id(block.GetBlockValue("device")))
 {
 }
@@ -202,7 +198,7 @@ WinmmOutput::Open(AudioFormat &audio_format)
 }
 
 void
-WinmmOutput::Close()
+WinmmOutput::Close() noexcept
 {
 	for (auto &i : buffers)
 		i.buffer.Clear();
@@ -291,7 +287,7 @@ WinmmOutput::DrainAllBuffers()
 }
 
 void
-WinmmOutput::Stop()
+WinmmOutput::Stop() noexcept
 {
 	waveOutReset(handle);
 
@@ -311,27 +307,14 @@ WinmmOutput::Drain()
 }
 
 void
-WinmmOutput::Cancel()
+WinmmOutput::Cancel() noexcept
 {
 	Stop();
 }
 
-typedef AudioOutputWrapper<WinmmOutput> Wrapper;
-
 const struct AudioOutputPlugin winmm_output_plugin = {
 	"winmm",
 	winmm_output_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	nullptr,
-	nullptr,
-	&Wrapper::Play,
-	&Wrapper::Drain,
-	&Wrapper::Cancel,
-	nullptr,
+	WinmmOutput::Create,
 	&winmm_mixer_plugin,
 };
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
index 45b6edde68c1f5d513686919578b91327dbc9fa8..8ee7759fa05476d6db2abd48fff95a25f5cfff46 100644
--- a/src/output/plugins/httpd/HttpdInternal.hxx
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -26,8 +26,7 @@
 #define MPD_OUTPUT_HTTPD_INTERNAL_H
 
 #include "HttpdClient.hxx"
-#include "output/Wrapper.hxx"
-#include "output/Filtered.hxx"
+#include "output/Interface.hxx"
 #include "output/Timer.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
@@ -49,11 +48,7 @@ class PreparedEncoder;
 class Encoder;
 struct Tag;
 
-class HttpdOutput final : ServerSocket, DeferredMonitor {
-	friend struct AudioOutputWrapper<HttpdOutput>;
-
-	AudioOutput base;
-
+class HttpdOutput final : AudioOutput, ServerSocket, DeferredMonitor {
 	/**
 	 * True if the audio output is open and accepts client
 	 * connections.
@@ -157,11 +152,9 @@ public:
 	HttpdOutput(EventLoop &_loop, const ConfigBlock &block);
 	~HttpdOutput();
 
-	static HttpdOutput *Create(EventLoop &event_loop,
-				   const ConfigBlock &block);
-
-	static constexpr HttpdOutput *Cast(AudioOutput *ao) {
-		return &ContainerCast(*ao, &HttpdOutput::base);
+	static AudioOutput *Create(EventLoop &event_loop,
+				   const ConfigBlock &block) {
+		return new HttpdOutput(event_loop, block);
 	}
 
 	using DeferredMonitor::GetEventLoop;
@@ -169,11 +162,11 @@ public:
 	void Bind();
 	void Unbind();
 
-	void Enable() {
+	void Enable() override {
 		Bind();
 	}
 
-	void Disable() {
+	void Disable() noexcept override {
 		Unbind();
 	}
 
@@ -187,12 +180,12 @@ public:
 	/**
 	 * Caller must lock the mutex.
 	 */
-	void Open(AudioFormat &audio_format);
+	void Open(AudioFormat &audio_format) override;
 
 	/**
 	 * Caller must lock the mutex.
 	 */
-	void Close();
+	void Close() noexcept override;
 
 	/**
 	 * Check whether there is at least one client.
@@ -227,7 +220,7 @@ public:
 	void SendHeader(HttpdClient &client) const;
 
 	gcc_pure
-	std::chrono::steady_clock::duration Delay() const noexcept;
+	std::chrono::steady_clock::duration Delay() const noexcept override;
 
 	/**
 	 * Reads data from the encoder (as much as available) and
@@ -252,14 +245,14 @@ public:
 	 */
 	void EncodeAndPlay(const void *chunk, size_t size);
 
-	void SendTag(const Tag &tag);
+	void SendTag(const Tag &tag) override;
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 
 	void CancelAllClients();
 
-	void Cancel();
-	bool Pause();
+	void Cancel() noexcept override;
+	bool Pause() noexcept override;
 
 private:
 	virtual void RunDeferred() override;
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
index e65a6982dda6b6f8b1dfac7672540c77b63fdc97..fa5d38ef06e61d0775accac3bced83ca6c200533 100644
--- a/src/output/plugins/httpd/HttpdOutputPlugin.cxx
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
@@ -50,8 +50,8 @@ const Domain httpd_output_domain("httpd_output");
 
 inline
 HttpdOutput::HttpdOutput(EventLoop &_loop, const ConfigBlock &block)
-	:ServerSocket(_loop), DeferredMonitor(_loop),
-	 base(httpd_output_plugin),
+	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
+	 ServerSocket(_loop), DeferredMonitor(_loop),
 	 encoder(nullptr), unflushed_input(0),
 	 metadata(nullptr)
 {
@@ -113,12 +113,6 @@ HttpdOutput::Unbind()
 		});
 }
 
-HttpdOutput *
-HttpdOutput::Create(EventLoop &event_loop, const ConfigBlock &block)
-{
-	return new HttpdOutput(event_loop, block);
-}
-
 /**
  * Creates a new #HttpdClient object and adds it into the
  * HttpdOutput.clients linked list.
@@ -246,7 +240,7 @@ HttpdOutput::OpenEncoder(AudioFormat &audio_format)
 	unflushed_input = 0;
 }
 
-inline void
+void
 HttpdOutput::Open(AudioFormat &audio_format)
 {
 	assert(!open);
@@ -264,8 +258,8 @@ HttpdOutput::Open(AudioFormat &audio_format)
 	pause = false;
 }
 
-inline void
-HttpdOutput::Close()
+void
+HttpdOutput::Close() noexcept
 {
 	assert(open);
 
@@ -300,7 +294,7 @@ HttpdOutput::SendHeader(HttpdClient &client) const
 		client.PushPage(header);
 }
 
-inline std::chrono::steady_clock::duration
+std::chrono::steady_clock::duration
 HttpdOutput::Delay() const noexcept
 {
 	if (!LockHasClients() && pause) {
@@ -367,7 +361,7 @@ HttpdOutput::EncodeAndPlay(const void *chunk, size_t size)
 	BroadcastFromEncoder();
 }
 
-inline size_t
+size_t
 HttpdOutput::Play(const void *chunk, size_t size)
 {
 	pause = false;
@@ -383,7 +377,7 @@ HttpdOutput::Play(const void *chunk, size_t size)
 }
 
 bool
-HttpdOutput::Pause()
+HttpdOutput::Pause() noexcept
 {
 	pause = true;
 
@@ -395,7 +389,7 @@ HttpdOutput::Pause()
 	return true;
 }
 
-inline void
+void
 HttpdOutput::SendTag(const Tag &tag)
 {
 	if (encoder->ImplementsTag()) {
@@ -463,29 +457,16 @@ HttpdOutput::CancelAllClients()
 }
 
 void
-HttpdOutput::Cancel()
+HttpdOutput::Cancel() noexcept
 {
 	BlockingCall(GetEventLoop(), [this](){
 			CancelAllClients();
 		});
 }
 
-typedef AudioOutputWrapper<HttpdOutput> Wrapper;
-
 const struct AudioOutputPlugin httpd_output_plugin = {
 	"httpd",
 	nullptr,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	&Wrapper::Enable,
-	&Wrapper::Disable,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	&Wrapper::SendTag,
-	&Wrapper::Play,
-	nullptr,
-	&Wrapper::Cancel,
-	&Wrapper::Pause,
+	&HttpdOutput::Create,
 	nullptr,
 };
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
index 279d29c4dcf2497e5e5c08068a52bcbe8d533807..d1ba818f70b99525d8b3210d0c3aee614139864c 100644
--- a/src/output/plugins/sles/SlesOutputPlugin.cxx
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -24,7 +24,6 @@
 #include "Play.hxx"
 #include "AndroidSimpleBufferQueue.hxx"
 #include "../../OutputAPI.hxx"
-#include "../../Wrapper.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 #include "util/Macros.hxx"
@@ -37,14 +36,10 @@
 
 #include <stdexcept>
 
-class SlesOutput {
-	friend struct AudioOutputWrapper<SlesOutput>;
-
+class SlesOutput final : AudioOutput  {
 	static constexpr unsigned N_BUFFERS = 3;
 	static constexpr size_t BUFFER_SIZE = 65536;
 
-	AudioOutput base;
-
 	SLES::Object engine_object, mix_object, play_object;
 	SLES::Play play;
 	SLES::AndroidSimpleBufferQueue queue;
@@ -86,30 +81,28 @@ class SlesOutput {
 	 */
 	uint8_t buffers[N_BUFFERS][BUFFER_SIZE];
 
-public:
-	SlesOutput();
+	SlesOutput():AudioOutput(FLAG_PAUSE) {}
 
-	operator AudioOutput *() {
-		return &base;
+public:
+	static AudioOutput *Create(EventLoop &, const ConfigBlock &) {
+		return new SlesOutput();
 	}
 
-	static SlesOutput *Create(EventLoop &event_loop,
-				  const ConfigBlock &block);
-
-	void Open(AudioFormat &audio_format);
-	void Close();
+private:
+	void Open(AudioFormat &audio_format) override;
+	void Close() noexcept override;
 
-	std::chrono::steady_clock::duration Delay() noexcept {
+	std::chrono::steady_clock::duration Delay() const noexcept override {
 		return pause && !cancel
 			? std::chrono::milliseconds(100)
 			: std::chrono::steady_clock::duration::zero();
 	}
 
-	size_t Play(const void *chunk, size_t size);
+	size_t Play(const void *chunk, size_t size) override;
 
-	void Drain();
-	void Cancel();
-	bool Pause();
+	void Drain() override;
+	void Cancel() noexcept override;
+	bool Pause() noexcept override;
 
 private:
 	void PlayedCallback();
@@ -129,12 +122,7 @@ private:
 
 static constexpr Domain sles_domain("sles");
 
-SlesOutput::SlesOutput()
-	:base(sles_output_plugin)
-{
-}
-
-inline void
+void
 SlesOutput::Open(AudioFormat &audio_format)
 {
 	SLresult result;
@@ -300,8 +288,8 @@ SlesOutput::Open(AudioFormat &audio_format)
 	audio_format.format = SampleFormat::S16;
 }
 
-inline void
-SlesOutput::Close()
+void
+SlesOutput::Close() noexcept
 {
 	play.SetPlayState(SL_PLAYSTATE_STOPPED);
 	play_object.Destroy();
@@ -309,7 +297,7 @@ SlesOutput::Close()
 	engine_object.Destroy();
 }
 
-inline size_t
+size_t
 SlesOutput::Play(const void *chunk, size_t size)
 {
 	cancel = false;
@@ -348,7 +336,7 @@ SlesOutput::Play(const void *chunk, size_t size)
 	return nbytes;
 }
 
-inline void
+void
 SlesOutput::Drain()
 {
 	const std::lock_guard<Mutex> protect(mutex);
@@ -359,8 +347,8 @@ SlesOutput::Drain()
 		cond.wait(mutex);
 }
 
-inline void
-SlesOutput::Cancel()
+void
+SlesOutput::Cancel() noexcept
 {
 	pause = true;
 	cancel = true;
@@ -379,8 +367,8 @@ SlesOutput::Cancel()
 	filled = 0;
 }
 
-inline bool
-SlesOutput::Pause()
+bool
+SlesOutput::Pause() noexcept
 {
 	cancel = false;
 
@@ -415,28 +403,9 @@ sles_test_default_device()
 	return true;
 }
 
-inline SlesOutput *
-SlesOutput::Create(EventLoop &, const ConfigBlock &)
-{
-	return new SlesOutput();
-}
-
-typedef AudioOutputWrapper<SlesOutput> Wrapper;
-
 const struct AudioOutputPlugin sles_output_plugin = {
 	"sles",
 	sles_test_default_device,
-	&Wrapper::Init,
-	&Wrapper::Finish,
-	nullptr,
-	nullptr,
-	&Wrapper::Open,
-	&Wrapper::Close,
-	&Wrapper::Delay,
-	nullptr,
-	&Wrapper::Play,
-	&Wrapper::Drain,
-	&Wrapper::Cancel,
-	&Wrapper::Pause,
+	SlesOutput::Create,
 	nullptr,
 };
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 2f7f757748858fc89284910f53b57f478f5aa817..74bfc7aab9ad67fa756f503206621e40455f81d5 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -34,13 +34,15 @@
 #include "util/ScopeExit.hxx"
 #include "Log.hxx"
 
+#include <memory>
+
 #include <assert.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 
-static AudioOutput *
+static std::unique_ptr<AudioOutput>
 load_audio_output(EventLoop &event_loop, const char *name)
 {
 	const auto *block = config_find_block(ConfigBlockOption::AUDIO_OUTPUT,
@@ -58,7 +60,8 @@ load_audio_output(EventLoop &event_loop, const char *name)
 		throw FormatRuntimeError("No such audio output plugin: %s",
 					 plugin_name);
 
-	return ao_plugin_init(event_loop, *plugin, *block);
+	return std::unique_ptr<AudioOutput>(ao_plugin_init(event_loop, *plugin,
+							   *block));
 }
 
 static void
@@ -66,11 +69,11 @@ run_output(AudioOutput &ao, AudioFormat audio_format)
 {
 	/* open the audio output */
 
-	ao_plugin_enable(ao);
-	AtScopeExit(&ao) { ao_plugin_disable(ao); };
+	ao.Enable();
+	AtScopeExit(&ao) { ao.Disable(); };
 
-	ao_plugin_open(ao, audio_format);
-	AtScopeExit(&ao) { ao_plugin_close(ao); };
+	ao.Open(audio_format);
+	AtScopeExit(&ao) { ao.Close(); };
 
 	fprintf(stderr, "audio_format=%s\n",
 		ToString(audio_format).c_str());
@@ -93,8 +96,7 @@ run_output(AudioOutput &ao, AudioFormat audio_format)
 
 		size_t play_length = (length / frame_size) * frame_size;
 		if (play_length > 0) {
-			size_t consumed = ao_plugin_play(ao,
-							 buffer, play_length);
+			size_t consumed = ao.Play(buffer, play_length);
 
 			assert(consumed <= length);
 			assert(consumed % frame_size == 0);
@@ -126,7 +128,7 @@ try {
 
 	/* initialize the audio output */
 
-	auto *ao = load_audio_output(io_thread.GetEventLoop(), argv[2]);
+	auto ao = load_audio_output(io_thread.GetEventLoop(), argv[2]);
 
 	/* parse the audio format */
 
@@ -139,7 +141,7 @@ try {
 
 	/* cleanup and exit */
 
-	ao_plugin_finish(ao);
+	ao.reset();
 
 	config_global_finish();