diff --git a/Makefile.am b/Makefile.am
index d50a17264..23ae8d6ea 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -963,9 +963,9 @@ OUTPUT_API_SRC = \
 	src/output/Internal.hxx \
 	src/output/Registry.cxx src/output/Registry.hxx \
 	src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \
-	src/output/OutputThread.cxx src/output/OutputThread.hxx \
+	src/output/OutputThread.cxx \
 	src/output/Domain.cxx src/output/Domain.hxx \
-	src/output/OutputControl.cxx src/output/OutputControl.hxx \
+	src/output/OutputControl.cxx \
 	src/output/OutputState.cxx src/output/OutputState.hxx \
 	src/output/OutputPrint.cxx src/output/OutputPrint.hxx \
 	src/output/OutputCommand.cxx src/output/OutputCommand.hxx \
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
index 3ba191ed7..372bb4276 100644
--- a/src/output/Init.cxx
+++ b/src/output/Init.cxx
@@ -147,14 +147,11 @@ audio_output_load_mixer(AudioOutput *ao,
 }
 
 bool
-ao_base_init(AudioOutput *ao,
-	     const config_param &param, Error &error)
+AudioOutput::Configure(const config_param &param, Error &error)
 {
-	assert(ao != nullptr);
-
 	if (!param.IsNull()) {
-		ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
-		if (ao->name == nullptr) {
+		name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
+		if (name == nullptr) {
 			error.Set(config_domain,
 				  "Missing \"name\" configuration");
 			return false;
@@ -163,26 +160,26 @@ ao_base_init(AudioOutput *ao,
 		const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
 		if (p != nullptr) {
 			bool success =
-				audio_format_parse(ao->config_audio_format,
+				audio_format_parse(config_audio_format,
 						   p, true, error);
 			if (!success)
 				return false;
 		} else
-			ao->config_audio_format.Clear();
+			config_audio_format.Clear();
 	} else {
-		ao->name = "default detected output";
+		name = "default detected output";
 
-		ao->config_audio_format.Clear();
+		config_audio_format.Clear();
 	}
 
-	ao->tags = param.GetBlockValue("tags", true);
-	ao->always_on = param.GetBlockValue("always_on", false);
-	ao->enabled = param.GetBlockValue("enabled", true);
+	tags = param.GetBlockValue("tags", true);
+	always_on = param.GetBlockValue("always_on", false);
+	enabled = param.GetBlockValue("enabled", true);
 
 	/* set up the filter chain */
 
-	ao->filter = filter_chain_new();
-	assert(ao->filter != nullptr);
+	filter = filter_chain_new();
+	assert(filter != nullptr);
 
 	/* create the normalization filter (if configured) */
 
@@ -192,12 +189,12 @@ ao_base_init(AudioOutput *ao,
 				   IgnoreError());
 		assert(normalize_filter != nullptr);
 
-		filter_chain_append(*ao->filter, "normalize",
+		filter_chain_append(*filter, "normalize",
 				    autoconvert_filter_new(normalize_filter));
 	}
 
 	Error filter_error;
-	filter_chain_parse(*ao->filter,
+	filter_chain_parse(*filter,
 			   param.GetBlockValue(AUDIO_FILTERS, ""),
 			   filter_error);
 
@@ -206,7 +203,7 @@ ao_base_init(AudioOutput *ao,
 	if (filter_error.IsDefined())
 		FormatError(filter_error,
 			    "Failed to initialize filter chain for '%s'",
-			    ao->name);
+			    name);
 
 	/* done */
 
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
index 5c5455648..443d8c6cc 100644
--- a/src/output/Internal.hxx
+++ b/src/output/Internal.hxx
@@ -23,6 +23,7 @@
 #include "AudioFormat.hxx"
 #include "pcm/PcmBuffer.hxx"
 #include "pcm/PcmDither.hxx"
+#include "ReplayGainInfo.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
 #include "thread/Thread.hxx"
@@ -266,6 +267,106 @@ struct AudioOutput {
 
 	AudioOutput(const AudioOutputPlugin &_plugin);
 	~AudioOutput();
+
+	bool Configure(const config_param &param, Error &error);
+
+	void StartThread();
+	void StopThread();
+
+	void Finish();
+
+	bool IsOpen() const {
+		return open;
+	}
+
+	bool IsCommandFinished() const {
+		return command == AO_COMMAND_NONE;
+	}
+
+	/**
+	 * Waits for command completion.
+	 *
+	 * Caller must lock the mutex.
+	 */
+	void WaitForCommand();
+
+	/**
+	 * Sends a command, but does not wait for completion.
+	 *
+	 * Caller must lock the mutex.
+	 */
+	void CommandAsync(audio_output_command cmd);
+
+	/**
+	 * Sends a command to the #AudioOutput object and waits for
+	 * completion.
+	 *
+	 * Caller must lock the mutex.
+	 */
+	void CommandWait(audio_output_command cmd);
+
+	/**
+	 * Lock the #AudioOutput object and execute the command
+	 * synchronously.
+	 */
+	void LockCommandWait(audio_output_command cmd);
+
+	/**
+	 * Enables the device.
+	 */
+	void LockEnableWait();
+
+	/**
+	 * Disables the device.
+	 */
+	void LockDisableWait();
+
+	void LockPauseAsync();
+
+	/**
+	 * Same LockCloseWait(), but expects the lock to be
+	 * held by the caller.
+	 */
+	void CloseWait();
+	void LockCloseWait();
+
+	/**
+	 * Closes the audio output, but if the "always_on" flag is set, put it
+	 * into pause mode instead.
+	 */
+	void LockRelease();
+
+	void SetReplayGainMode(ReplayGainMode mode);
+
+	/**
+	 * Caller must lock the mutex.
+	 */
+	bool Open(const AudioFormat audio_format, const MusicPipe &mp);
+
+	/**
+	 * Opens or closes the device, depending on the "enabled"
+	 * flag.
+	 *
+	 * @return true if the device is open
+	 */
+	bool LockUpdate(const AudioFormat audio_format,
+			const MusicPipe &mp);
+
+	void LockPlay();
+
+	void LockDrainAsync();
+
+	/**
+	 * Clear the "allow_play" flag and send the "CANCEL" command
+	 * asynchronously.  To finish the operation, the caller has to
+	 * call LockAllowPlay().
+	 */
+	void LockCancelAsync();
+
+	/**
+	 * Set the "allow_play" and signal the thread.
+	 */
+	void LockAllowPlay();
 };
 
 /**
@@ -274,27 +375,11 @@ struct AudioOutput {
  */
 extern struct notify audio_output_client_notify;
 
-static inline bool
-audio_output_is_open(const AudioOutput *ao)
-{
-	return ao->open;
-}
-
-static inline bool
-audio_output_command_is_finished(const AudioOutput *ao)
-{
-	return ao->command == AO_COMMAND_NONE;
-}
-
 AudioOutput *
 audio_output_new(const config_param &param,
 		 PlayerControl &pc,
 		 Error &error);
 
-bool
-ao_base_init(AudioOutput *ao,
-	     const config_param &param, Error &error);
-
 void
 audio_output_free(AudioOutput *ao);
 
diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx
index c83d8f02d..5d09b1733 100644
--- a/src/output/MultipleOutputs.cxx
+++ b/src/output/MultipleOutputs.cxx
@@ -21,7 +21,6 @@
 #include "MultipleOutputs.hxx"
 #include "PlayerControl.hxx"
 #include "Internal.hxx"
-#include "OutputControl.hxx"
 #include "Domain.hxx"
 #include "MusicBuffer.hxx"
 #include "MusicPipe.hxx"
@@ -45,8 +44,8 @@ MultipleOutputs::MultipleOutputs()
 MultipleOutputs::~MultipleOutputs()
 {
 	for (auto i : outputs) {
-		audio_output_disable(i);
-		audio_output_finish(i);
+		i->LockDisableWait();
+		i->Finish();
 	}
 }
 
@@ -111,9 +110,9 @@ MultipleOutputs::EnableDisable()
 
 		if (ao->enabled != enabled) {
 			if (ao->enabled)
-				audio_output_enable(ao);
+				ao->LockEnableWait();
 			else
-				audio_output_disable(ao);
+				ao->LockDisableWait();
 		}
 	}
 }
@@ -123,8 +122,7 @@ MultipleOutputs::AllFinished() const
 {
 	for (auto ao : outputs) {
 		const ScopeLock protect(ao->mutex);
-		if (audio_output_is_open(ao) &&
-		    !audio_output_command_is_finished(ao))
+		if (ao->IsOpen() && !ao->IsCommandFinished())
 			return false;
 	}
 
@@ -142,7 +140,7 @@ void
 MultipleOutputs::AllowPlay()
 {
 	for (auto ao : outputs)
-		audio_output_allow_play(ao);
+		ao->LockAllowPlay();
 }
 
 static void
@@ -169,7 +167,7 @@ MultipleOutputs::Update()
 		return false;
 
 	for (auto ao : outputs)
-		ret = audio_output_update(ao, input_audio_format, *pipe)
+		ret = ao->LockUpdate(input_audio_format, *pipe)
 			|| ret;
 
 	return ret;
@@ -179,7 +177,7 @@ void
 MultipleOutputs::SetReplayGainMode(ReplayGainMode mode)
 {
 	for (auto ao : outputs)
-		audio_output_set_replay_gain_mode(ao, mode);
+		ao->SetReplayGainMode(mode);
 }
 
 bool
@@ -199,7 +197,7 @@ MultipleOutputs::Play(music_chunk *chunk, Error &error)
 	pipe->Push(chunk);
 
 	for (auto ao : outputs)
-		audio_output_play(ao);
+		ao->LockPlay();
 
 	return true;
 }
@@ -387,7 +385,7 @@ MultipleOutputs::Pause()
 	Update();
 
 	for (auto ao : outputs)
-		audio_output_pause(ao);
+		ao->LockPauseAsync();
 
 	WaitAll();
 }
@@ -396,7 +394,7 @@ void
 MultipleOutputs::Drain()
 {
 	for (auto ao : outputs)
-		audio_output_drain_async(ao);
+		ao->LockDrainAsync();
 
 	WaitAll();
 }
@@ -407,7 +405,7 @@ MultipleOutputs::Cancel()
 	/* send the cancel() command to all audio outputs */
 
 	for (auto ao : outputs)
-		audio_output_cancel(ao);
+		ao->LockCancelAsync();
 
 	WaitAll();
 
@@ -430,7 +428,7 @@ void
 MultipleOutputs::Close()
 {
 	for (auto ao : outputs)
-		audio_output_close(ao);
+		ao->LockCloseWait();
 
 	if (pipe != nullptr) {
 		assert(buffer != nullptr);
@@ -451,7 +449,7 @@ void
 MultipleOutputs::Release()
 {
 	for (auto ao : outputs)
-		audio_output_release(ao);
+		ao->LockRelease();
 
 	if (pipe != nullptr) {
 		assert(buffer != nullptr);
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
index a956faed3..921369722 100644
--- a/src/output/OutputControl.cxx
+++ b/src/output/OutputControl.cxx
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2003-2014 The Music Player Daemon Project
  * http://www.musicpd.org
@@ -19,8 +18,6 @@
  */
 
 #include "config.h"
-#include "OutputControl.hxx"
-#include "OutputThread.hxx"
 #include "Internal.hxx"
 #include "OutputPlugin.hxx"
 #include "Domain.hxx"
@@ -38,126 +35,97 @@ static constexpr unsigned REOPEN_AFTER = 10;
 
 struct notify audio_output_client_notify;
 
-/**
- * Waits for command completion.
- *
- * @param ao the #AudioOutput instance; must be locked
- */
-static void ao_command_wait(AudioOutput *ao)
+void
+AudioOutput::WaitForCommand()
 {
-	while (ao->command != AO_COMMAND_NONE) {
-		ao->mutex.unlock();
+	while (!IsCommandFinished()) {
+		mutex.unlock();
 		audio_output_client_notify.Wait();
-		ao->mutex.lock();
+		mutex.lock();
 	}
 }
 
-/**
- * Sends a command to the #AudioOutput object, but does not wait for
- * completion.
- *
- * @param ao the #AudioOutput instance; must be locked
- */
-static void ao_command_async(AudioOutput *ao,
-			     enum audio_output_command cmd)
+void
+AudioOutput::CommandAsync(audio_output_command cmd)
 {
-	assert(ao->command == AO_COMMAND_NONE);
-	ao->command = cmd;
-	ao->cond.signal();
-}
+	assert(IsCommandFinished());
 
-/**
- * Sends a command to the #AudioOutput object and waits for
- * completion.
- *
- * @param ao the #AudioOutput instance; must be locked
- */
-static void
-ao_command(AudioOutput *ao, enum audio_output_command cmd)
-{
-	ao_command_async(ao, cmd);
-	ao_command_wait(ao);
-}
-
-/**
- * Lock the #AudioOutput object and execute the command
- * synchronously.
- */
-static void
-ao_lock_command(AudioOutput *ao, enum audio_output_command cmd)
-{
-	const ScopeLock protect(ao->mutex);
-	ao_command(ao, cmd);
+	command = cmd;
+	cond.signal();
 }
 
 void
-audio_output_set_replay_gain_mode(AudioOutput *ao,
-				  ReplayGainMode mode)
+AudioOutput::CommandWait(audio_output_command cmd)
 {
-	if (ao->replay_gain_filter != nullptr)
-		replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
-	if (ao->other_replay_gain_filter != nullptr)
-		replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode);
+	CommandAsync(cmd);
+	WaitForCommand();
 }
 
 void
-audio_output_enable(AudioOutput *ao)
+AudioOutput::LockCommandWait(audio_output_command cmd)
 {
-	if (!ao->thread.IsDefined()) {
-		if (ao->plugin.enable == nullptr) {
+	const ScopeLock protect(mutex);
+	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()
+{
+	if (!thread.IsDefined()) {
+		if (plugin.enable == nullptr) {
 			/* don't bother to start the thread now if the
 			   device doesn't even have a enable() method;
 			   just assign the variable and we're done */
-			ao->really_enabled = true;
+			really_enabled = true;
 			return;
 		}
 
-		audio_output_thread_start(ao);
+		StartThread();
 	}
 
-	ao_lock_command(ao, AO_COMMAND_ENABLE);
+	LockCommandWait(AO_COMMAND_ENABLE);
 }
 
 void
-audio_output_disable(AudioOutput *ao)
+AudioOutput::LockDisableWait()
 {
-	if (!ao->thread.IsDefined()) {
-		if (ao->plugin.disable == nullptr)
-			ao->really_enabled = false;
+	if (!thread.IsDefined()) {
+		if (plugin.disable == nullptr)
+			really_enabled = false;
 		else
 			/* if there's no thread yet, the device cannot
 			   be enabled */
-			assert(!ao->really_enabled);
+			assert(!really_enabled);
 
 		return;
 	}
 
-	ao_lock_command(ao, AO_COMMAND_DISABLE);
+	LockCommandWait(AO_COMMAND_DISABLE);
 }
 
-/**
- * Object must be locked (and unlocked) by the caller.
- */
-static bool
-audio_output_open(AudioOutput *ao,
-		  const AudioFormat audio_format,
-		  const MusicPipe &mp)
+inline bool
+AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
 {
-	bool open;
-
-	assert(ao != nullptr);
-	assert(ao->allow_play);
+	assert(allow_play);
 	assert(audio_format.IsValid());
 
-	ao->fail_timer.Reset();
+	fail_timer.Reset();
 
-	if (ao->open && audio_format == ao->in_audio_format) {
-		assert(ao->pipe == &mp ||
-		       (ao->always_on && ao->pause));
+	if (open && audio_format == in_audio_format) {
+		assert(pipe == &mp || (always_on && pause));
 
-		if (ao->pause) {
-			ao->chunk = nullptr;
-			ao->pipe = ∓
+		if (pause) {
+			chunk = nullptr;
+			pipe = ∓
 
 			/* unpause with the CANCEL command; this is a
 			   hack, but suits well for forcing the thread
@@ -166,160 +134,162 @@ audio_output_open(AudioOutput *ao,
 
 			/* we're not using audio_output_cancel() here,
 			   because that function is asynchronous */
-			ao_command(ao, AO_COMMAND_CANCEL);
+			CommandWait(AO_COMMAND_CANCEL);
 		}
 
 		return true;
 	}
 
-	ao->in_audio_format = audio_format;
-	ao->chunk = nullptr;
+	in_audio_format = audio_format;
+	chunk = nullptr;
 
-	ao->pipe = ∓
+	pipe = ∓
 
-	if (!ao->thread.IsDefined())
-		audio_output_thread_start(ao);
+	if (!thread.IsDefined())
+		StartThread();
 
-	ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
-	open = ao->open;
+	CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+	const bool open2 = open;
 
-	if (open && ao->mixer != nullptr) {
+	if (open2 && mixer != nullptr) {
 		Error error;
-		if (!mixer_open(ao->mixer, error))
+		if (!mixer_open(mixer, error))
 			FormatWarning(output_domain,
-				      "Failed to open mixer for '%s'",
-				      ao->name);
+				      "Failed to open mixer for '%s'", name);
 	}
 
-	return open;
+	return open2;
 }
 
-/**
- * Same as audio_output_close(), but expects the lock to be held by
- * the caller.
- */
-static void
-audio_output_close_locked(AudioOutput *ao)
+void
+AudioOutput::CloseWait()
 {
-	assert(ao != nullptr);
-	assert(ao->allow_play);
+	assert(allow_play);
 
-	if (ao->mixer != nullptr)
-		mixer_auto_close(ao->mixer);
+	if (mixer != nullptr)
+		mixer_auto_close(mixer);
 
-	assert(!ao->open || !ao->fail_timer.IsDefined());
+	assert(!open || !fail_timer.IsDefined());
 
-	if (ao->open)
-		ao_command(ao, AO_COMMAND_CLOSE);
+	if (open)
+		CommandWait(AO_COMMAND_CLOSE);
 	else
-		ao->fail_timer.Reset();
+		fail_timer.Reset();
 }
 
 bool
-audio_output_update(AudioOutput *ao,
-		    const AudioFormat audio_format,
-		    const MusicPipe &mp)
+AudioOutput::LockUpdate(const AudioFormat audio_format,
+			const MusicPipe &mp)
 {
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	if (ao->enabled && ao->really_enabled) {
-		if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) {
-			return audio_output_open(ao, audio_format, mp);
+	if (enabled && really_enabled) {
+		if (fail_timer.Check(REOPEN_AFTER * 1000)) {
+			return Open(audio_format, mp);
 		}
-	} else if (audio_output_is_open(ao))
-		audio_output_close_locked(ao);
+	} else if (IsOpen())
+		CloseWait();
 
 	return false;
 }
 
 void
-audio_output_play(AudioOutput *ao)
+AudioOutput::LockPlay()
 {
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	assert(ao->allow_play);
+	assert(allow_play);
 
-	if (audio_output_is_open(ao) && !ao->in_playback_loop &&
-	    !ao->woken_for_play) {
-		ao->woken_for_play = true;
-		ao->cond.signal();
+	if (IsOpen() && !in_playback_loop && !woken_for_play) {
+		woken_for_play = true;
+		cond.signal();
 	}
 }
 
-void audio_output_pause(AudioOutput *ao)
+void
+AudioOutput::LockPauseAsync()
 {
-	if (ao->mixer != nullptr && ao->plugin.pause == nullptr)
+	if (mixer != nullptr && plugin.pause == nullptr)
 		/* the device has no pause mode: close the mixer,
 		   unless its "global" flag is set (checked by
 		   mixer_auto_close()) */
-		mixer_auto_close(ao->mixer);
+		mixer_auto_close(mixer);
 
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	assert(ao->allow_play);
-	if (audio_output_is_open(ao))
-		ao_command_async(ao, AO_COMMAND_PAUSE);
+	assert(allow_play);
+	if (IsOpen())
+		CommandAsync(AO_COMMAND_PAUSE);
 }
 
 void
-audio_output_drain_async(AudioOutput *ao)
+AudioOutput::LockDrainAsync()
 {
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	assert(ao->allow_play);
-	if (audio_output_is_open(ao))
-		ao_command_async(ao, AO_COMMAND_DRAIN);
+	assert(allow_play);
+	if (IsOpen())
+		CommandAsync(AO_COMMAND_DRAIN);
 }
 
-void audio_output_cancel(AudioOutput *ao)
+void
+AudioOutput::LockCancelAsync()
 {
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	if (audio_output_is_open(ao)) {
-		ao->allow_play = false;
-		ao_command_async(ao, AO_COMMAND_CANCEL);
+	if (IsOpen()) {
+		allow_play = false;
+		CommandAsync(AO_COMMAND_CANCEL);
 	}
 }
 
 void
-audio_output_allow_play(AudioOutput *ao)
+AudioOutput::LockAllowPlay()
 {
-	const ScopeLock protect(ao->mutex);
+	const ScopeLock protect(mutex);
 
-	ao->allow_play = true;
-	if (audio_output_is_open(ao))
-		ao->cond.signal();
+	allow_play = true;
+	if (IsOpen())
+		cond.signal();
 }
 
 void
-audio_output_release(AudioOutput *ao)
+AudioOutput::LockRelease()
 {
-	if (ao->always_on)
-		audio_output_pause(ao);
+	if (always_on)
+		LockPauseAsync();
 	else
-		audio_output_close(ao);
+		LockCloseWait();
 }
 
-void audio_output_close(AudioOutput *ao)
+void
+AudioOutput::LockCloseWait()
 {
-	assert(ao != nullptr);
-	assert(!ao->open || !ao->fail_timer.IsDefined());
+	assert(!open || !fail_timer.IsDefined());
 
-	const ScopeLock protect(ao->mutex);
-	audio_output_close_locked(ao);
+	const ScopeLock protect(mutex);
+	CloseWait();
 }
 
-void audio_output_finish(AudioOutput *ao)
+void
+AudioOutput::StopThread()
 {
-	audio_output_close(ao);
+	assert(thread.IsDefined());
+	assert(allow_play);
 
-	assert(!ao->fail_timer.IsDefined());
-
-	if (ao->thread.IsDefined()) {
-		assert(ao->allow_play);
-		ao_lock_command(ao, AO_COMMAND_KILL);
-		ao->thread.Join();
-	}
-
-	audio_output_free(ao);
+	LockCommandWait(AO_COMMAND_KILL);
+	thread.Join();
+}
+
+void
+AudioOutput::Finish()
+{
+	LockCloseWait();
+
+	assert(!fail_timer.IsDefined());
+
+	if (thread.IsDefined())
+		StopThread();
+
+	audio_output_free(this);
 }
diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx
index 08d1cbd24..fff3fe406 100644
--- a/src/output/OutputControl.hxx
+++ b/src/output/OutputControl.hxx
@@ -20,75 +20,6 @@
 #ifndef MPD_OUTPUT_CONTROL_HXX
 #define MPD_OUTPUT_CONTROL_HXX
 
-#include "ReplayGainInfo.hxx"
-
-#include <stddef.h>
-
 struct AudioOutput;
-struct AudioFormat;
-struct config_param;
-class MusicPipe;
-
-void
-audio_output_set_replay_gain_mode(AudioOutput *ao,
-				  ReplayGainMode mode);
-
-/**
- * Enables the device.
- */
-void
-audio_output_enable(AudioOutput *ao);
-
-/**
- * Disables the device.
- */
-void
-audio_output_disable(AudioOutput *ao);
-
-/**
- * Opens or closes the device, depending on the "enabled" flag.
- *
- * @return true if the device is open
- */
-bool
-audio_output_update(AudioOutput *ao,
-		    AudioFormat audio_format,
-		    const MusicPipe &mp);
-
-void
-audio_output_play(AudioOutput *ao);
-
-void
-audio_output_pause(AudioOutput *ao);
-
-void
-audio_output_drain_async(AudioOutput *ao);
-
-/**
- * Clear the "allow_play" flag and send the "CANCEL" command
- * asynchronously.  To finish the operation, the caller has to call
- * audio_output_allow_play().
- */
-void
-audio_output_cancel(AudioOutput *ao);
-
-/**
- * Set the "allow_play" and signal the thread.
- */
-void
-audio_output_allow_play(AudioOutput *ao);
-
-void
-audio_output_close(AudioOutput *ao);
-
-/**
- * Closes the audio output, but if the "always_on" flag is set, put it
- * into pause mode instead.
- */
-void
-audio_output_release(AudioOutput *ao);
-
-void
-audio_output_finish(AudioOutput *ao);
 
 #endif
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
index 95702d183..fcbd22c6a 100644
--- a/src/output/OutputThread.cxx
+++ b/src/output/OutputThread.cxx
@@ -18,7 +18,6 @@
  */
 
 #include "config.h"
-#include "OutputThread.hxx"
 #include "Internal.hxx"
 #include "OutputAPI.hxx"
 #include "Domain.hxx"
@@ -680,11 +679,12 @@ audio_output_task(void *arg)
 	}
 }
 
-void audio_output_thread_start(AudioOutput *ao)
+void
+AudioOutput::StartThread()
 {
-	assert(ao->command == AO_COMMAND_NONE);
+	assert(command == AO_COMMAND_NONE);
 
 	Error error;
-	if (!ao->thread.Start(audio_output_task, ao, error))
+	if (!thread.Start(audio_output_task, this, error))
 		FatalError(error);
 }
diff --git a/src/output/OutputThread.hxx b/src/output/OutputThread.hxx
deleted file mode 100644
index 716fe8319..000000000
--- a/src/output/OutputThread.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2014 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_THREAD_HXX
-#define MPD_OUTPUT_THREAD_HXX
-
-struct AudioOutput;
-
-void
-audio_output_thread_start(AudioOutput *ao);
-
-#endif
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 7b4156803..bea96d09f 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -125,7 +125,7 @@ struct AlsaOutput {
 	}
 
 	bool Init(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 };
 
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
index d0020229f..af8c88fa1 100644
--- a/src/output/plugins/AoOutputPlugin.cxx
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -46,7 +46,7 @@ struct AoOutput {
 		:base(ao_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 
 	bool Configure(const config_param &param, Error &error);
diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
index 2c5151519..0c7fb421e 100644
--- a/src/output/plugins/FifoOutputPlugin.cxx
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -52,7 +52,7 @@ struct FifoOutput {
 		 created(false) {}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 
 	bool Create(Error &error);
diff --git a/src/output/plugins/HttpdOutputPlugin.cxx b/src/output/plugins/HttpdOutputPlugin.cxx
index dc9e7e0b4..17a3df59a 100644
--- a/src/output/plugins/HttpdOutputPlugin.cxx
+++ b/src/output/plugins/HttpdOutputPlugin.cxx
@@ -137,7 +137,7 @@ HttpdOutput::Configure(const config_param &param, Error &error)
 inline bool
 HttpdOutput::Init(const config_param &param, Error &error)
 {
-	return ao_base_init(&base, param, error);
+	return base.Configure(param, error);
 }
 
 static AudioOutput *
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index 42179e813..e1dad7893 100644
--- a/src/output/plugins/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -83,7 +83,7 @@ struct JackOutput {
 		:base(jack_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, param, error_r);
+		return base.Configure(param, error_r);
 	}
 };
 
diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
index 3c13e700d..53140a475 100644
--- a/src/output/plugins/NullOutputPlugin.cxx
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -33,7 +33,7 @@ struct NullOutput {
 		:base(null_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 };
 
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index 6918b3378..13ac7b35e 100644
--- a/src/output/plugins/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -84,7 +84,7 @@ static AudioOutput *
 osx_output_init(const config_param &param, Error &error)
 {
 	OSXOutput *oo = new OSXOutput();
-	if (!ao_base_init(&oo->base, param, error)) {
+	if (!oo->base.Configure(param, error)) {
 		delete oo;
 		return NULL;
 	}
diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
index b0f863374..344f23f4f 100644
--- a/src/output/plugins/OpenALOutputPlugin.cxx
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -52,7 +52,7 @@ struct OpenALOutput {
 		:base(openal_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, param, error_r);
+		return base.Configure(param, error_r);
 	}
 };
 
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
index 69018e723..05b14b29f 100644
--- a/src/output/plugins/OssOutputPlugin.cxx
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -83,7 +83,7 @@ struct OssOutput {
 		 fd(-1), device(nullptr) {}
 
 	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, param, error_r);
+		return base.Configure(param, error_r);
 	}
 };
 
diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
index 36ad0aa48..7a1f32258 100644
--- a/src/output/plugins/PipeOutputPlugin.cxx
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -38,7 +38,7 @@ struct PipeOutput {
 		:base(pipe_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 
 	bool Configure(const config_param &param, Error &error);
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index dc1fc23b8..1f255718f 100644
--- a/src/output/plugins/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -334,7 +334,7 @@ pulse_output_init(const config_param &param, Error &error)
 	g_setenv("PULSE_PROP_media.role", "music", true);
 
 	po = new PulseOutput();
-	if (!ao_base_init(&po->base, param, error)) {
+	if (!po->base.Configure(param, error)) {
 		delete po;
 		return nullptr;
 	}
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
index 0ef693d84..87e23f55a 100644
--- a/src/output/plugins/RecorderOutputPlugin.cxx
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -61,7 +61,7 @@ struct RecorderOutput {
 		:base(recorder_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, param, error_r);
+		return base.Configure(param, error_r);
 	}
 
 	bool Configure(const config_param &param, Error &error);
diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
index 0fabecb31..dca9fa346 100644
--- a/src/output/plugins/RoarOutputPlugin.cxx
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -58,7 +58,7 @@ public:
 	}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 
 	void Configure(const config_param &param);
diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
index 91dfd2477..e693274a7 100644
--- a/src/output/plugins/ShoutOutputPlugin.cxx
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -69,7 +69,7 @@ struct ShoutOutput final {
 	}
 
 	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, param, error);
+		return base.Configure(param, error);
 	}
 
 	bool Configure(const config_param &param, Error &error);
diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
index a7cab2e1f..30745f97c 100644
--- a/src/output/plugins/SolarisOutputPlugin.cxx
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -61,7 +61,7 @@ struct SolarisOutput {
 		:base(solaris_output_plugin) {}
 
 	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, param, error_r);
+		return base.Configure(param, error_r);
 	}
 };
 
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
index b09d2e081..d61f2055a 100644
--- a/src/output/plugins/WinmmOutputPlugin.cxx
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -52,7 +52,7 @@ struct WinmmOutput {
 	WinmmBuffer buffers[8];
 	unsigned next_buffer;
 
-	WinmmBuffer()
+	WinmmOutput()
 		:base(winmm_output_plugin) {}
 };
 
@@ -115,7 +115,7 @@ static AudioOutput *
 winmm_output_init(const config_param &param, Error &error)
 {
 	WinmmOutput *wo = new WinmmOutput();
-	if (!ao_base_init(&wo->base, param, error)) {
+	if (!wo->base.Configure(param, error)) {
 		delete wo;
 		return nullptr;
 	}
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 2664b7150..55fcf3698 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -18,7 +18,6 @@
  */
 
 #include "config.h"
-#include "output/OutputControl.hxx"
 #include "output/Internal.hxx"
 #include "output/OutputPlugin.hxx"
 #include "config/ConfigData.hxx"