From ea5b901bcce20949a8d1fd622a7b03ff6f56ae20 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Thu, 23 Jan 2014 23:49:50 +0100
Subject: output/*: move to output/plugins/

---
 src/CommandLine.cxx                         |   4 +-
 src/Main.cxx                                |   2 +-
 src/MixerAll.cxx                            |   4 +-
 src/OutputAPI.hxx                           |  33 --
 src/OutputAll.cxx                           | 589 ------------------
 src/OutputAll.hxx                           | 174 ------
 src/OutputCommand.cxx                       | 112 ----
 src/OutputCommand.hxx                       |  51 --
 src/OutputControl.cxx                       | 325 ----------
 src/OutputControl.hxx                       |  94 ---
 src/OutputError.cxx                         |  23 -
 src/OutputError.hxx                         |  25 -
 src/OutputFinish.cxx                        |  51 --
 src/OutputInit.cxx                          | 329 ----------
 src/OutputInternal.hxx                      | 301 ----------
 src/OutputList.cxx                          | 100 ----
 src/OutputList.hxx                          |  33 --
 src/OutputPlugin.cxx                        | 109 ----
 src/OutputPlugin.hxx                        | 202 -------
 src/OutputPrint.cxx                         |  45 --
 src/OutputPrint.hxx                         |  34 --
 src/OutputState.cxx                         |  92 ---
 src/OutputState.hxx                         |  44 --
 src/OutputThread.cxx                        | 690 ---------------------
 src/OutputThread.hxx                        |  28 -
 src/PlayerThread.cxx                        |   2 +-
 src/StateFile.cxx                           |   2 +-
 src/command/OutputCommands.cxx              |   4 +-
 src/command/PlayerCommands.cxx              |   2 +-
 src/mixer/AlsaMixerPlugin.cxx               |   2 +-
 src/mixer/PulseMixerPlugin.cxx              |   2 +-
 src/mixer/RoarMixerPlugin.cxx               |   2 +-
 src/mixer/WinmmMixerPlugin.cxx              |   4 +-
 src/output/AlsaOutputPlugin.cxx             | 868 ---------------------------
 src/output/AlsaOutputPlugin.hxx             |  25 -
 src/output/AoOutputPlugin.cxx               | 286 ---------
 src/output/AoOutputPlugin.hxx               |  25 -
 src/output/FifoOutputPlugin.cxx             | 313 ----------
 src/output/FifoOutputPlugin.hxx             |  25 -
 src/output/HttpdClient.cxx                  | 485 ---------------
 src/output/HttpdClient.hxx                  | 193 ------
 src/output/HttpdInternal.hxx                | 279 ---------
 src/output/HttpdOutputPlugin.cxx            | 601 -------------------
 src/output/HttpdOutputPlugin.hxx            |  25 -
 src/output/JackOutputPlugin.cxx             | 765 ------------------------
 src/output/JackOutputPlugin.hxx             |  25 -
 src/output/NullOutputPlugin.cxx             | 141 -----
 src/output/NullOutputPlugin.hxx             |  25 -
 src/output/OSXOutputPlugin.cxx              | 428 -------------
 src/output/OSXOutputPlugin.hxx              |  25 -
 src/output/OpenALOutputPlugin.cxx           | 285 ---------
 src/output/OpenALOutputPlugin.hxx           |  25 -
 src/output/OssOutputPlugin.cxx              | 776 ------------------------
 src/output/OssOutputPlugin.hxx              |  25 -
 src/output/OutputAPI.hxx                    |  33 ++
 src/output/OutputAll.cxx                    | 589 ++++++++++++++++++
 src/output/OutputAll.hxx                    | 174 ++++++
 src/output/OutputCommand.cxx                | 112 ++++
 src/output/OutputCommand.hxx                |  51 ++
 src/output/OutputControl.cxx                | 325 ++++++++++
 src/output/OutputControl.hxx                |  94 +++
 src/output/OutputError.cxx                  |  23 +
 src/output/OutputError.hxx                  |  25 +
 src/output/OutputFinish.cxx                 |  51 ++
 src/output/OutputInit.cxx                   | 329 ++++++++++
 src/output/OutputInternal.hxx               | 301 ++++++++++
 src/output/OutputList.cxx                   | 100 ++++
 src/output/OutputList.hxx                   |  33 ++
 src/output/OutputPlugin.cxx                 | 109 ++++
 src/output/OutputPlugin.hxx                 | 202 +++++++
 src/output/OutputPrint.cxx                  |  45 ++
 src/output/OutputPrint.hxx                  |  34 ++
 src/output/OutputState.cxx                  |  92 +++
 src/output/OutputState.hxx                  |  44 ++
 src/output/OutputThread.cxx                 | 690 +++++++++++++++++++++
 src/output/OutputThread.hxx                 |  28 +
 src/output/PipeOutputPlugin.cxx             | 147 -----
 src/output/PipeOutputPlugin.hxx             |  25 -
 src/output/PulseOutputPlugin.cxx            | 889 ----------------------------
 src/output/PulseOutputPlugin.hxx            |  46 --
 src/output/RecorderOutputPlugin.cxx         | 262 --------
 src/output/RecorderOutputPlugin.hxx         |  25 -
 src/output/RoarOutputPlugin.cxx             | 428 -------------
 src/output/RoarOutputPlugin.hxx             |  33 --
 src/output/ShoutOutputPlugin.cxx            | 544 -----------------
 src/output/ShoutOutputPlugin.hxx            |  25 -
 src/output/SolarisOutputPlugin.cxx          | 201 -------
 src/output/SolarisOutputPlugin.hxx          |  25 -
 src/output/WinmmOutputPlugin.cxx            | 353 -----------
 src/output/WinmmOutputPlugin.hxx            |  42 --
 src/output/plugins/AlsaOutputPlugin.cxx     | 868 +++++++++++++++++++++++++++
 src/output/plugins/AlsaOutputPlugin.hxx     |  25 +
 src/output/plugins/AoOutputPlugin.cxx       | 286 +++++++++
 src/output/plugins/AoOutputPlugin.hxx       |  25 +
 src/output/plugins/FifoOutputPlugin.cxx     | 313 ++++++++++
 src/output/plugins/FifoOutputPlugin.hxx     |  25 +
 src/output/plugins/HttpdClient.cxx          | 485 +++++++++++++++
 src/output/plugins/HttpdClient.hxx          | 193 ++++++
 src/output/plugins/HttpdInternal.hxx        | 279 +++++++++
 src/output/plugins/HttpdOutputPlugin.cxx    | 601 +++++++++++++++++++
 src/output/plugins/HttpdOutputPlugin.hxx    |  25 +
 src/output/plugins/JackOutputPlugin.cxx     | 765 ++++++++++++++++++++++++
 src/output/plugins/JackOutputPlugin.hxx     |  25 +
 src/output/plugins/NullOutputPlugin.cxx     | 141 +++++
 src/output/plugins/NullOutputPlugin.hxx     |  25 +
 src/output/plugins/OSXOutputPlugin.cxx      | 428 +++++++++++++
 src/output/plugins/OSXOutputPlugin.hxx      |  25 +
 src/output/plugins/OpenALOutputPlugin.cxx   | 285 +++++++++
 src/output/plugins/OpenALOutputPlugin.hxx   |  25 +
 src/output/plugins/OssOutputPlugin.cxx      | 776 ++++++++++++++++++++++++
 src/output/plugins/OssOutputPlugin.hxx      |  25 +
 src/output/plugins/PipeOutputPlugin.cxx     | 147 +++++
 src/output/plugins/PipeOutputPlugin.hxx     |  25 +
 src/output/plugins/PulseOutputPlugin.cxx    | 889 ++++++++++++++++++++++++++++
 src/output/plugins/PulseOutputPlugin.hxx    |  46 ++
 src/output/plugins/RecorderOutputPlugin.cxx | 262 ++++++++
 src/output/plugins/RecorderOutputPlugin.hxx |  25 +
 src/output/plugins/RoarOutputPlugin.cxx     | 428 +++++++++++++
 src/output/plugins/RoarOutputPlugin.hxx     |  33 ++
 src/output/plugins/ShoutOutputPlugin.cxx    | 544 +++++++++++++++++
 src/output/plugins/ShoutOutputPlugin.hxx    |  25 +
 src/output/plugins/SolarisOutputPlugin.cxx  | 201 +++++++
 src/output/plugins/SolarisOutputPlugin.hxx  |  25 +
 src/output/plugins/WinmmOutputPlugin.cxx    | 353 +++++++++++
 src/output/plugins/WinmmOutputPlugin.hxx    |  42 ++
 125 files changed, 12189 insertions(+), 12189 deletions(-)
 delete mode 100644 src/OutputAPI.hxx
 delete mode 100644 src/OutputAll.cxx
 delete mode 100644 src/OutputAll.hxx
 delete mode 100644 src/OutputCommand.cxx
 delete mode 100644 src/OutputCommand.hxx
 delete mode 100644 src/OutputControl.cxx
 delete mode 100644 src/OutputControl.hxx
 delete mode 100644 src/OutputError.cxx
 delete mode 100644 src/OutputError.hxx
 delete mode 100644 src/OutputFinish.cxx
 delete mode 100644 src/OutputInit.cxx
 delete mode 100644 src/OutputInternal.hxx
 delete mode 100644 src/OutputList.cxx
 delete mode 100644 src/OutputList.hxx
 delete mode 100644 src/OutputPlugin.cxx
 delete mode 100644 src/OutputPlugin.hxx
 delete mode 100644 src/OutputPrint.cxx
 delete mode 100644 src/OutputPrint.hxx
 delete mode 100644 src/OutputState.cxx
 delete mode 100644 src/OutputState.hxx
 delete mode 100644 src/OutputThread.cxx
 delete mode 100644 src/OutputThread.hxx
 delete mode 100644 src/output/AlsaOutputPlugin.cxx
 delete mode 100644 src/output/AlsaOutputPlugin.hxx
 delete mode 100644 src/output/AoOutputPlugin.cxx
 delete mode 100644 src/output/AoOutputPlugin.hxx
 delete mode 100644 src/output/FifoOutputPlugin.cxx
 delete mode 100644 src/output/FifoOutputPlugin.hxx
 delete mode 100644 src/output/HttpdClient.cxx
 delete mode 100644 src/output/HttpdClient.hxx
 delete mode 100644 src/output/HttpdInternal.hxx
 delete mode 100644 src/output/HttpdOutputPlugin.cxx
 delete mode 100644 src/output/HttpdOutputPlugin.hxx
 delete mode 100644 src/output/JackOutputPlugin.cxx
 delete mode 100644 src/output/JackOutputPlugin.hxx
 delete mode 100644 src/output/NullOutputPlugin.cxx
 delete mode 100644 src/output/NullOutputPlugin.hxx
 delete mode 100644 src/output/OSXOutputPlugin.cxx
 delete mode 100644 src/output/OSXOutputPlugin.hxx
 delete mode 100644 src/output/OpenALOutputPlugin.cxx
 delete mode 100644 src/output/OpenALOutputPlugin.hxx
 delete mode 100644 src/output/OssOutputPlugin.cxx
 delete mode 100644 src/output/OssOutputPlugin.hxx
 create mode 100644 src/output/OutputAPI.hxx
 create mode 100644 src/output/OutputAll.cxx
 create mode 100644 src/output/OutputAll.hxx
 create mode 100644 src/output/OutputCommand.cxx
 create mode 100644 src/output/OutputCommand.hxx
 create mode 100644 src/output/OutputControl.cxx
 create mode 100644 src/output/OutputControl.hxx
 create mode 100644 src/output/OutputError.cxx
 create mode 100644 src/output/OutputError.hxx
 create mode 100644 src/output/OutputFinish.cxx
 create mode 100644 src/output/OutputInit.cxx
 create mode 100644 src/output/OutputInternal.hxx
 create mode 100644 src/output/OutputList.cxx
 create mode 100644 src/output/OutputList.hxx
 create mode 100644 src/output/OutputPlugin.cxx
 create mode 100644 src/output/OutputPlugin.hxx
 create mode 100644 src/output/OutputPrint.cxx
 create mode 100644 src/output/OutputPrint.hxx
 create mode 100644 src/output/OutputState.cxx
 create mode 100644 src/output/OutputState.hxx
 create mode 100644 src/output/OutputThread.cxx
 create mode 100644 src/output/OutputThread.hxx
 delete mode 100644 src/output/PipeOutputPlugin.cxx
 delete mode 100644 src/output/PipeOutputPlugin.hxx
 delete mode 100644 src/output/PulseOutputPlugin.cxx
 delete mode 100644 src/output/PulseOutputPlugin.hxx
 delete mode 100644 src/output/RecorderOutputPlugin.cxx
 delete mode 100644 src/output/RecorderOutputPlugin.hxx
 delete mode 100644 src/output/RoarOutputPlugin.cxx
 delete mode 100644 src/output/RoarOutputPlugin.hxx
 delete mode 100644 src/output/ShoutOutputPlugin.cxx
 delete mode 100644 src/output/ShoutOutputPlugin.hxx
 delete mode 100644 src/output/SolarisOutputPlugin.cxx
 delete mode 100644 src/output/SolarisOutputPlugin.hxx
 delete mode 100644 src/output/WinmmOutputPlugin.cxx
 delete mode 100644 src/output/WinmmOutputPlugin.hxx
 create mode 100644 src/output/plugins/AlsaOutputPlugin.cxx
 create mode 100644 src/output/plugins/AlsaOutputPlugin.hxx
 create mode 100644 src/output/plugins/AoOutputPlugin.cxx
 create mode 100644 src/output/plugins/AoOutputPlugin.hxx
 create mode 100644 src/output/plugins/FifoOutputPlugin.cxx
 create mode 100644 src/output/plugins/FifoOutputPlugin.hxx
 create mode 100644 src/output/plugins/HttpdClient.cxx
 create mode 100644 src/output/plugins/HttpdClient.hxx
 create mode 100644 src/output/plugins/HttpdInternal.hxx
 create mode 100644 src/output/plugins/HttpdOutputPlugin.cxx
 create mode 100644 src/output/plugins/HttpdOutputPlugin.hxx
 create mode 100644 src/output/plugins/JackOutputPlugin.cxx
 create mode 100644 src/output/plugins/JackOutputPlugin.hxx
 create mode 100644 src/output/plugins/NullOutputPlugin.cxx
 create mode 100644 src/output/plugins/NullOutputPlugin.hxx
 create mode 100644 src/output/plugins/OSXOutputPlugin.cxx
 create mode 100644 src/output/plugins/OSXOutputPlugin.hxx
 create mode 100644 src/output/plugins/OpenALOutputPlugin.cxx
 create mode 100644 src/output/plugins/OpenALOutputPlugin.hxx
 create mode 100644 src/output/plugins/OssOutputPlugin.cxx
 create mode 100644 src/output/plugins/OssOutputPlugin.hxx
 create mode 100644 src/output/plugins/PipeOutputPlugin.cxx
 create mode 100644 src/output/plugins/PipeOutputPlugin.hxx
 create mode 100644 src/output/plugins/PulseOutputPlugin.cxx
 create mode 100644 src/output/plugins/PulseOutputPlugin.hxx
 create mode 100644 src/output/plugins/RecorderOutputPlugin.cxx
 create mode 100644 src/output/plugins/RecorderOutputPlugin.hxx
 create mode 100644 src/output/plugins/RoarOutputPlugin.cxx
 create mode 100644 src/output/plugins/RoarOutputPlugin.hxx
 create mode 100644 src/output/plugins/ShoutOutputPlugin.cxx
 create mode 100644 src/output/plugins/ShoutOutputPlugin.hxx
 create mode 100644 src/output/plugins/SolarisOutputPlugin.cxx
 create mode 100644 src/output/plugins/SolarisOutputPlugin.hxx
 create mode 100644 src/output/plugins/WinmmOutputPlugin.cxx
 create mode 100644 src/output/plugins/WinmmOutputPlugin.hxx

(limited to 'src')

diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index 0667844f6..97cc2b18e 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -27,8 +27,8 @@
 #include "DatabasePlugin.hxx"
 #include "DecoderList.hxx"
 #include "DecoderPlugin.hxx"
-#include "OutputList.hxx"
-#include "OutputPlugin.hxx"
+#include "output/OutputList.hxx"
+#include "output/OutputPlugin.hxx"
 #include "InputRegistry.hxx"
 #include "InputPlugin.hxx"
 #include "playlist/PlaylistRegistry.hxx"
diff --git a/src/Main.cxx b/src/Main.cxx
index 8808a78d9..9eb39a7e2 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -37,7 +37,7 @@
 #include "command/AllCommands.hxx"
 #include "Partition.hxx"
 #include "Volume.hxx"
-#include "OutputAll.hxx"
+#include "output/OutputAll.hxx"
 #include "tag/TagConfig.hxx"
 #include "ReplayGainConfig.hxx"
 #include "Idle.hxx"
diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx
index 2310048c4..3cc92baee 100644
--- a/src/MixerAll.cxx
+++ b/src/MixerAll.cxx
@@ -22,9 +22,9 @@
 #include "MixerControl.hxx"
 #include "MixerInternal.hxx"
 #include "MixerList.hxx"
-#include "OutputAll.hxx"
+#include "output/OutputAll.hxx"
+#include "output/OutputInternal.hxx"
 #include "pcm/Volume.hxx"
-#include "OutputInternal.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
 #include "Log.hxx"
diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx
deleted file mode 100644
index 322ed3971..000000000
--- a/src/OutputAPI.hxx
+++ /dev/null
@@ -1,33 +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_API_HXX
-#define MPD_OUTPUT_API_HXX
-
-// IWYU pragma: begin_exports
-
-#include "OutputPlugin.hxx"
-#include "OutputInternal.hxx"
-#include "AudioFormat.hxx"
-#include "tag/Tag.hxx"
-#include "ConfigData.hxx"
-
-// IWYU pragma: end_exports
-
-#endif
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
deleted file mode 100644
index b3623f1af..000000000
--- a/src/OutputAll.cxx
+++ /dev/null
@@ -1,589 +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.
- */
-
-#include "config.h"
-#include "OutputAll.hxx"
-#include "PlayerControl.hxx"
-#include "OutputInternal.hxx"
-#include "OutputControl.hxx"
-#include "OutputError.hxx"
-#include "MusicBuffer.hxx"
-#include "MusicPipe.hxx"
-#include "MusicChunk.hxx"
-#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "notify.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-static AudioFormat input_audio_format;
-
-static struct audio_output **audio_outputs;
-static unsigned int num_audio_outputs;
-
-/**
- * The #MusicBuffer object where consumed chunks are returned.
- */
-static MusicBuffer *g_music_buffer;
-
-/**
- * The #MusicPipe object which feeds all audio outputs.  It is filled
- * by audio_output_all_play().
- */
-static MusicPipe *g_mp;
-
-/**
- * The "elapsed_time" stamp of the most recently finished chunk.
- */
-static float audio_output_all_elapsed_time = -1.0;
-
-unsigned int audio_output_count(void)
-{
-	return num_audio_outputs;
-}
-
-struct audio_output *
-audio_output_get(unsigned i)
-{
-	assert(i < num_audio_outputs);
-
-	assert(audio_outputs[i] != nullptr);
-
-	return audio_outputs[i];
-}
-
-struct audio_output *
-audio_output_find(const char *name)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i) {
-		struct audio_output *ao = audio_output_get(i);
-
-		if (strcmp(ao->name, name) == 0)
-			return ao;
-	}
-
-	/* name not found */
-	return nullptr;
-}
-
-gcc_const
-static unsigned
-audio_output_config_count(void)
-{
-	unsigned int nr = 0;
-	const struct config_param *param = nullptr;
-
-	while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
-		nr++;
-	if (!nr)
-		nr = 1; /* we'll always have at least one device  */
-	return nr;
-}
-
-void
-audio_output_all_init(PlayerControl &pc)
-{
-	const struct config_param *param = nullptr;
-	unsigned int i;
-	Error error;
-
-	num_audio_outputs = audio_output_config_count();
-	audio_outputs = new audio_output *[num_audio_outputs];
-
-	const config_param empty;
-
-	for (i = 0; i < num_audio_outputs; i++)
-	{
-		unsigned int j;
-
-		param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
-		if (param == nullptr) {
-			/* only allow param to be nullptr if there
-			   just one audio output */
-			assert(i == 0);
-			assert(num_audio_outputs == 1);
-
-			param = &empty;
-		}
-
-		audio_output *output = audio_output_new(*param, pc, error);
-		if (output == nullptr) {
-			if (param != nullptr)
-				FormatFatalError("line %i: %s",
-						 param->line,
-						 error.GetMessage());
-			else
-				FatalError(error);
-		}
-
-		audio_outputs[i] = output;
-
-		/* require output names to be unique: */
-		for (j = 0; j < i; j++) {
-			if (!strcmp(output->name, audio_outputs[j]->name)) {
-				FormatFatalError("output devices with identical "
-						 "names: %s", output->name);
-			}
-		}
-	}
-}
-
-void
-audio_output_all_finish(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < num_audio_outputs; i++) {
-		audio_output_disable(audio_outputs[i]);
-		audio_output_finish(audio_outputs[i]);
-	}
-
-	delete[] audio_outputs;
-	audio_outputs = nullptr;
-	num_audio_outputs = 0;
-}
-
-void
-audio_output_all_enable_disable(void)
-{
-	for (unsigned i = 0; i < num_audio_outputs; i++) {
-		struct audio_output *ao = audio_outputs[i];
-		bool enabled;
-
-		ao->mutex.lock();
-		enabled = ao->really_enabled;
-		ao->mutex.unlock();
-
-		if (ao->enabled != enabled) {
-			if (ao->enabled)
-				audio_output_enable(ao);
-			else
-				audio_output_disable(ao);
-		}
-	}
-}
-
-/**
- * Determine if all (active) outputs have finished the current
- * command.
- */
-static bool
-audio_output_all_finished(void)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i) {
-		struct audio_output *ao = audio_outputs[i];
-
-		const ScopeLock protect(ao->mutex);
-		if (audio_output_is_open(ao) &&
-		    !audio_output_command_is_finished(ao))
-			return false;
-	}
-
-	return true;
-}
-
-static void audio_output_wait_all(void)
-{
-	while (!audio_output_all_finished())
-		audio_output_client_notify.Wait();
-}
-
-/**
- * Signals all audio outputs which are open.
- */
-static void
-audio_output_allow_play_all(void)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i)
-		audio_output_allow_play(audio_outputs[i]);
-}
-
-static void
-audio_output_reset_reopen(struct audio_output *ao)
-{
-	const ScopeLock protect(ao->mutex);
-
-	ao->fail_timer.Reset();
-}
-
-/**
- * Resets the "reopen" flag on all audio devices.  MPD should
- * immediately retry to open the device instead of waiting for the
- * timeout when the user wants to start playback.
- */
-static void
-audio_output_all_reset_reopen(void)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i) {
-		struct audio_output *ao = audio_outputs[i];
-
-		audio_output_reset_reopen(ao);
-	}
-}
-
-/**
- * Opens all output devices which are enabled, but closed.
- *
- * @return true if there is at least open output device which is open
- */
-static bool
-audio_output_all_update(void)
-{
-	unsigned int i;
-	bool ret = false;
-
-	if (!input_audio_format.IsDefined())
-		return false;
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		ret = audio_output_update(audio_outputs[i],
-					  input_audio_format, *g_mp) || ret;
-
-	return ret;
-}
-
-void
-audio_output_all_set_replay_gain_mode(ReplayGainMode mode)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i)
-		audio_output_set_replay_gain_mode(audio_outputs[i], mode);
-}
-
-bool
-audio_output_all_play(struct music_chunk *chunk, Error &error)
-{
-	bool ret;
-	unsigned int i;
-
-	assert(g_music_buffer != nullptr);
-	assert(g_mp != nullptr);
-	assert(chunk != nullptr);
-	assert(chunk->CheckFormat(input_audio_format));
-
-	ret = audio_output_all_update();
-	if (!ret) {
-		/* TODO: obtain real error */
-		error.Set(output_domain, "Failed to open audio output");
-		return false;
-	}
-
-	g_mp->Push(chunk);
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		audio_output_play(audio_outputs[i]);
-
-	return true;
-}
-
-bool
-audio_output_all_open(const AudioFormat audio_format,
-		      MusicBuffer &buffer,
-		      Error &error)
-{
-	bool ret = false, enabled = false;
-	unsigned int i;
-
-	assert(g_music_buffer == nullptr || g_music_buffer == &buffer);
-	assert((g_mp == nullptr) == (g_music_buffer == nullptr));
-
-	g_music_buffer = &buffer;
-
-	/* the audio format must be the same as existing chunks in the
-	   pipe */
-	assert(g_mp == nullptr || g_mp->CheckFormat(audio_format));
-
-	if (g_mp == nullptr)
-		g_mp = new MusicPipe();
-	else
-		/* if the pipe hasn't been cleared, the the audio
-		   format must not have changed */
-		assert(g_mp->IsEmpty() || audio_format == input_audio_format);
-
-	input_audio_format = audio_format;
-
-	audio_output_all_reset_reopen();
-	audio_output_all_enable_disable();
-	audio_output_all_update();
-
-	for (i = 0; i < num_audio_outputs; ++i) {
-		if (audio_outputs[i]->enabled)
-			enabled = true;
-
-		if (audio_outputs[i]->open)
-			ret = true;
-	}
-
-	if (!enabled)
-		error.Set(output_domain, "All audio outputs are disabled");
-	else if (!ret)
-		/* TODO: obtain real error */
-		error.Set(output_domain, "Failed to open audio output");
-
-	if (!ret)
-		/* close all devices if there was an error */
-		audio_output_all_close();
-
-	return ret;
-}
-
-/**
- * Has the specified audio output already consumed this chunk?
- */
-static bool
-chunk_is_consumed_in(const struct audio_output *ao,
-		     const struct music_chunk *chunk)
-{
-	if (!ao->open)
-		return true;
-
-	if (ao->chunk == nullptr)
-		return false;
-
-	assert(chunk == ao->chunk || g_mp->Contains(ao->chunk));
-
-	if (chunk != ao->chunk) {
-		assert(chunk->next != nullptr);
-		return true;
-	}
-
-	return ao->chunk_finished && chunk->next == nullptr;
-}
-
-/**
- * Has this chunk been consumed by all audio outputs?
- */
-static bool
-chunk_is_consumed(const struct music_chunk *chunk)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i) {
-		struct audio_output *ao = audio_outputs[i];
-
-		const ScopeLock protect(ao->mutex);
-		if (!chunk_is_consumed_in(ao, chunk))
-			return false;
-	}
-
-	return true;
-}
-
-/**
- * There's only one chunk left in the pipe (#g_mp), and all audio
- * outputs have consumed it already.  Clear the reference.
- */
-static void
-clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked)
-{
-	assert(chunk->next == nullptr);
-	assert(g_mp->Contains(chunk));
-
-	for (unsigned i = 0; i < num_audio_outputs; ++i) {
-		struct audio_output *ao = audio_outputs[i];
-
-		/* this mutex will be unlocked by the caller when it's
-		   ready */
-		ao->mutex.lock();
-		locked[i] = ao->open;
-
-		if (!locked[i]) {
-			ao->mutex.unlock();
-			continue;
-		}
-
-		assert(ao->chunk == chunk);
-		assert(ao->chunk_finished);
-		ao->chunk = nullptr;
-	}
-}
-
-unsigned
-audio_output_all_check(void)
-{
-	const struct music_chunk *chunk;
-	bool is_tail;
-	struct music_chunk *shifted;
-	bool locked[num_audio_outputs];
-
-	assert(g_music_buffer != nullptr);
-	assert(g_mp != nullptr);
-
-	while ((chunk = g_mp->Peek()) != nullptr) {
-		assert(!g_mp->IsEmpty());
-
-		if (!chunk_is_consumed(chunk))
-			/* at least one output is not finished playing
-			   this chunk */
-			return g_mp->GetSize();
-
-		if (chunk->length > 0 && chunk->times >= 0.0)
-			/* only update elapsed_time if the chunk
-			   provides a defined value */
-			audio_output_all_elapsed_time = chunk->times;
-
-		is_tail = chunk->next == nullptr;
-		if (is_tail)
-			/* this is the tail of the pipe - clear the
-			   chunk reference in all outputs */
-			clear_tail_chunk(chunk, locked);
-
-		/* remove the chunk from the pipe */
-		shifted = g_mp->Shift();
-		assert(shifted == chunk);
-
-		if (is_tail)
-			/* unlock all audio outputs which were locked
-			   by clear_tail_chunk() */
-			for (unsigned i = 0; i < num_audio_outputs; ++i)
-				if (locked[i])
-					audio_outputs[i]->mutex.unlock();
-
-		/* return the chunk to the buffer */
-		g_music_buffer->Return(shifted);
-	}
-
-	return 0;
-}
-
-bool
-audio_output_all_wait(PlayerControl &pc, unsigned threshold)
-{
-	pc.Lock();
-
-	if (audio_output_all_check() < threshold) {
-		pc.Unlock();
-		return true;
-	}
-
-	pc.Wait();
-	pc.Unlock();
-
-	return audio_output_all_check() < threshold;
-}
-
-void
-audio_output_all_pause(void)
-{
-	unsigned int i;
-
-	audio_output_all_update();
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		audio_output_pause(audio_outputs[i]);
-
-	audio_output_wait_all();
-}
-
-void
-audio_output_all_drain(void)
-{
-	for (unsigned i = 0; i < num_audio_outputs; ++i)
-		audio_output_drain_async(audio_outputs[i]);
-
-	audio_output_wait_all();
-}
-
-void
-audio_output_all_cancel(void)
-{
-	unsigned int i;
-
-	/* send the cancel() command to all audio outputs */
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		audio_output_cancel(audio_outputs[i]);
-
-	audio_output_wait_all();
-
-	/* clear the music pipe and return all chunks to the buffer */
-
-	if (g_mp != nullptr)
-		g_mp->Clear(*g_music_buffer);
-
-	/* the audio outputs are now waiting for a signal, to
-	   synchronize the cleared music pipe */
-
-	audio_output_allow_play_all();
-
-	/* invalidate elapsed_time */
-
-	audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_close(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		audio_output_close(audio_outputs[i]);
-
-	if (g_mp != nullptr) {
-		assert(g_music_buffer != nullptr);
-
-		g_mp->Clear(*g_music_buffer);
-		delete g_mp;
-		g_mp = nullptr;
-	}
-
-	g_music_buffer = nullptr;
-
-	input_audio_format.Clear();
-
-	audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_release(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < num_audio_outputs; ++i)
-		audio_output_release(audio_outputs[i]);
-
-	if (g_mp != nullptr) {
-		assert(g_music_buffer != nullptr);
-
-		g_mp->Clear(*g_music_buffer);
-		delete g_mp;
-		g_mp = nullptr;
-	}
-
-	g_music_buffer = nullptr;
-
-	input_audio_format.Clear();
-
-	audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_song_border(void)
-{
-	/* clear the elapsed_time pointer at the beginning of a new
-	   song */
-	audio_output_all_elapsed_time = 0.0;
-}
-
-float
-audio_output_all_get_elapsed_time(void)
-{
-	return audio_output_all_elapsed_time;
-}
diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx
deleted file mode 100644
index b6166eb48..000000000
--- a/src/OutputAll.hxx
+++ /dev/null
@@ -1,174 +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.
- */
-
-/*
- * Functions for dealing with all configured (enabled) audion outputs
- * at once.
- *
- */
-
-#ifndef OUTPUT_ALL_H
-#define OUTPUT_ALL_H
-
-#include "ReplayGainInfo.hxx"
-#include "Compiler.h"
-
-struct AudioFormat;
-class MusicBuffer;
-struct music_chunk;
-struct PlayerControl;
-class Error;
-
-/**
- * Global initialization: load audio outputs from the configuration
- * file and initialize them.
- */
-void
-audio_output_all_init(PlayerControl &pc);
-
-/**
- * Global finalization: free memory occupied by audio outputs.  All
- */
-void
-audio_output_all_finish(void);
-
-/**
- * Returns the total number of audio output devices, including those
- * who are disabled right now.
- */
-gcc_const
-unsigned int audio_output_count(void);
-
-/**
- * Returns the "i"th audio output device.
- */
-gcc_const
-struct audio_output *
-audio_output_get(unsigned i);
-
-/**
- * Returns the audio output device with the specified name.  Returns
- * NULL if the name does not exist.
- */
-gcc_pure
-struct audio_output *
-audio_output_find(const char *name);
-
-/**
- * Checks the "enabled" flag of all audio outputs, and if one has
- * changed, commit the change.
- */
-void
-audio_output_all_enable_disable(void);
-
-/**
- * Opens all audio outputs which are not disabled.
- *
- * @param audio_format the preferred audio format
- * @param buffer the #music_buffer where consumed #music_chunk objects
- * should be returned
- * @return true on success, false on failure
- */
-bool
-audio_output_all_open(AudioFormat audio_format,
-		      MusicBuffer &buffer,
-		      Error &error);
-
-/**
- * Closes all audio outputs.
- */
-void
-audio_output_all_close(void);
-
-/**
- * Closes all audio outputs.  Outputs with the "always_on" flag are
- * put into pause mode.
- */
-void
-audio_output_all_release(void);
-
-void
-audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
-
-/**
- * Enqueue a #music_chunk object for playing, i.e. pushes it to a
- * #MusicPipe.
- *
- * @param chunk the #music_chunk object to be played
- * @return true on success, false if no audio output was able to play
- * (all closed then)
- */
-bool
-audio_output_all_play(music_chunk *chunk, Error &error);
-
-/**
- * Checks if the output devices have drained their music pipe, and
- * returns the consumed music chunks to the #music_buffer.
- *
- * @return the number of chunks to play left in the #MusicPipe
- */
-unsigned
-audio_output_all_check(void);
-
-/**
- * Checks if the size of the #MusicPipe is below the #threshold.  If
- * not, it attempts to synchronize with all output threads, and waits
- * until another #music_chunk is finished.
- *
- * @param threshold the maximum number of chunks in the pipe
- * @return true if there are less than #threshold chunks in the pipe
- */
-bool
-audio_output_all_wait(PlayerControl &pc, unsigned threshold);
-
-/**
- * Puts all audio outputs into pause mode.  Most implementations will
- * simply close it then.
- */
-void
-audio_output_all_pause(void);
-
-/**
- * Drain all audio outputs.
- */
-void
-audio_output_all_drain(void);
-
-/**
- * Try to cancel data which may still be in the device's buffers.
- */
-void
-audio_output_all_cancel(void);
-
-/**
- * Indicate that a new song will begin now.
- */
-void
-audio_output_all_song_border(void);
-
-/**
- * Returns the "elapsed_time" stamp of the most recently finished
- * chunk.  A negative value is returned when no chunk has been
- * finished yet.
- */
-gcc_pure
-float
-audio_output_all_get_elapsed_time(void);
-
-#endif
diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx
deleted file mode 100644
index 839e9bd88..000000000
--- a/src/OutputCommand.cxx
+++ /dev/null
@@ -1,112 +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.
- */
-
-/*
- * Glue functions for controlling the audio outputs over the MPD
- * protocol.  These functions perform extra validation on all
- * parameters, because they might be from an untrusted source.
- *
- */
-
-#include "config.h"
-#include "OutputCommand.hxx"
-#include "OutputAll.hxx"
-#include "OutputInternal.hxx"
-#include "PlayerControl.hxx"
-#include "MixerControl.hxx"
-#include "Idle.hxx"
-
-extern unsigned audio_output_state_version;
-
-bool
-audio_output_enable_index(unsigned idx)
-{
-	struct audio_output *ao;
-
-	if (idx >= audio_output_count())
-		return false;
-
-	ao = audio_output_get(idx);
-	if (ao->enabled)
-		return true;
-
-	ao->enabled = true;
-	idle_add(IDLE_OUTPUT);
-
-	ao->player_control->UpdateAudio();
-
-	++audio_output_state_version;
-
-	return true;
-}
-
-bool
-audio_output_disable_index(unsigned idx)
-{
-	struct audio_output *ao;
-
-	if (idx >= audio_output_count())
-		return false;
-
-	ao = audio_output_get(idx);
-	if (!ao->enabled)
-		return true;
-
-	ao->enabled = false;
-	idle_add(IDLE_OUTPUT);
-
-	Mixer *mixer = ao->mixer;
-	if (mixer != nullptr) {
-		mixer_close(mixer);
-		idle_add(IDLE_MIXER);
-	}
-
-	ao->player_control->UpdateAudio();
-
-	++audio_output_state_version;
-
-	return true;
-}
-
-bool
-audio_output_toggle_index(unsigned idx)
-{
-	struct audio_output *ao;
-
-	if (idx >= audio_output_count())
-		return false;
-
-	ao = audio_output_get(idx);
-	const bool enabled = ao->enabled = !ao->enabled;
-	idle_add(IDLE_OUTPUT);
-
-	if (!enabled) {
-		Mixer *mixer = ao->mixer;
-		if (mixer != nullptr) {
-			mixer_close(mixer);
-			idle_add(IDLE_MIXER);
-		}
-	}
-
-	ao->player_control->UpdateAudio();
-
-	++audio_output_state_version;
-
-	return true;
-}
diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx
deleted file mode 100644
index 4c44dff53..000000000
--- a/src/OutputCommand.hxx
+++ /dev/null
@@ -1,51 +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.
- */
-
-/*
- * Glue functions for controlling the audio outputs over the MPD
- * protocol.  These functions perform extra validation on all
- * parameters, because they might be from an untrusted source.
- *
- */
-
-#ifndef MPD_OUTPUT_COMMAND_HXX
-#define MPD_OUTPUT_COMMAND_HXX
-
-/**
- * Enables an audio output.  Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_enable_index(unsigned idx);
-
-/**
- * Disables an audio output.  Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_disable_index(unsigned idx);
-
-/**
- * Toggles an audio output.  Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_toggle_index(unsigned idx);
-
-#endif
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
deleted file mode 100644
index b938754fd..000000000
--- a/src/OutputControl.cxx
+++ /dev/null
@@ -1,325 +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.
- */
-
-#include "config.h"
-#include "OutputControl.hxx"
-#include "OutputThread.hxx"
-#include "OutputInternal.hxx"
-#include "OutputPlugin.hxx"
-#include "OutputError.hxx"
-#include "MixerControl.hxx"
-#include "notify.hxx"
-#include "filter/ReplayGainFilterPlugin.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-/** after a failure, wait this number of seconds before
-    automatically reopening the device */
-static constexpr unsigned REOPEN_AFTER = 10;
-
-struct notify audio_output_client_notify;
-
-/**
- * Waits for command completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_wait(struct audio_output *ao)
-{
-	while (ao->command != AO_COMMAND_NONE) {
-		ao->mutex.unlock();
-		audio_output_client_notify.Wait();
-		ao->mutex.lock();
-	}
-}
-
-/**
- * Sends a command to the #audio_output object, but does not wait for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_async(struct audio_output *ao,
-			     enum audio_output_command cmd)
-{
-	assert(ao->command == AO_COMMAND_NONE);
-	ao->command = cmd;
-	ao->cond.signal();
-}
-
-/**
- * Sends a command to the #audio_output object and waits for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void
-ao_command(struct audio_output *ao, enum audio_output_command cmd)
-{
-	ao_command_async(ao, cmd);
-	ao_command_wait(ao);
-}
-
-/**
- * Lock the #audio_output object and execute the command
- * synchronously.
- */
-static void
-ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
-{
-	const ScopeLock protect(ao->mutex);
-	ao_command(ao, cmd);
-}
-
-void
-audio_output_set_replay_gain_mode(struct audio_output *ao,
-				  ReplayGainMode mode)
-{
-	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);
-}
-
-void
-audio_output_enable(struct audio_output *ao)
-{
-	if (!ao->thread.IsDefined()) {
-		if (ao->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;
-			return;
-		}
-
-		audio_output_thread_start(ao);
-	}
-
-	ao_lock_command(ao, AO_COMMAND_ENABLE);
-}
-
-void
-audio_output_disable(struct audio_output *ao)
-{
-	if (!ao->thread.IsDefined()) {
-		if (ao->plugin->disable == nullptr)
-			ao->really_enabled = false;
-		else
-			/* if there's no thread yet, the device cannot
-			   be enabled */
-			assert(!ao->really_enabled);
-
-		return;
-	}
-
-	ao_lock_command(ao, AO_COMMAND_DISABLE);
-}
-
-/**
- * Object must be locked (and unlocked) by the caller.
- */
-static bool
-audio_output_open(struct audio_output *ao,
-		  const AudioFormat audio_format,
-		  const MusicPipe &mp)
-{
-	bool open;
-
-	assert(ao != nullptr);
-	assert(ao->allow_play);
-	assert(audio_format.IsValid());
-
-	ao->fail_timer.Reset();
-
-	if (ao->open && audio_format == ao->in_audio_format) {
-		assert(ao->pipe == &mp ||
-		       (ao->always_on && ao->pause));
-
-		if (ao->pause) {
-			ao->chunk = nullptr;
-			ao->pipe = &mp;
-
-			/* unpause with the CANCEL command; this is a
-			   hack, but suits well for forcing the thread
-			   to leave the ao_pause() thread, and we need
-			   to flush the device buffer anyway */
-
-			/* we're not using audio_output_cancel() here,
-			   because that function is asynchronous */
-			ao_command(ao, AO_COMMAND_CANCEL);
-		}
-
-		return true;
-	}
-
-	ao->in_audio_format = audio_format;
-	ao->chunk = nullptr;
-
-	ao->pipe = &mp;
-
-	if (!ao->thread.IsDefined())
-		audio_output_thread_start(ao);
-
-	ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
-	open = ao->open;
-
-	if (open && ao->mixer != nullptr) {
-		Error error;
-		if (!mixer_open(ao->mixer, error))
-			FormatWarning(output_domain,
-				      "Failed to open mixer for '%s'",
-				      ao->name);
-	}
-
-	return open;
-}
-
-/**
- * Same as audio_output_close(), but expects the lock to be held by
- * the caller.
- */
-static void
-audio_output_close_locked(struct audio_output *ao)
-{
-	assert(ao != nullptr);
-	assert(ao->allow_play);
-
-	if (ao->mixer != nullptr)
-		mixer_auto_close(ao->mixer);
-
-	assert(!ao->open || !ao->fail_timer.IsDefined());
-
-	if (ao->open)
-		ao_command(ao, AO_COMMAND_CLOSE);
-	else
-		ao->fail_timer.Reset();
-}
-
-bool
-audio_output_update(struct audio_output *ao,
-		    const AudioFormat audio_format,
-		    const MusicPipe &mp)
-{
-	const ScopeLock protect(ao->mutex);
-
-	if (ao->enabled && ao->really_enabled) {
-		if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) {
-			return audio_output_open(ao, audio_format, mp);
-		}
-	} else if (audio_output_is_open(ao))
-		audio_output_close_locked(ao);
-
-	return false;
-}
-
-void
-audio_output_play(struct audio_output *ao)
-{
-	const ScopeLock protect(ao->mutex);
-
-	assert(ao->allow_play);
-
-	if (audio_output_is_open(ao) && !ao->in_playback_loop &&
-	    !ao->woken_for_play) {
-		ao->woken_for_play = true;
-		ao->cond.signal();
-	}
-}
-
-void audio_output_pause(struct audio_output *ao)
-{
-	if (ao->mixer != nullptr && ao->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);
-
-	const ScopeLock protect(ao->mutex);
-
-	assert(ao->allow_play);
-	if (audio_output_is_open(ao))
-		ao_command_async(ao, AO_COMMAND_PAUSE);
-}
-
-void
-audio_output_drain_async(struct audio_output *ao)
-{
-	const ScopeLock protect(ao->mutex);
-
-	assert(ao->allow_play);
-	if (audio_output_is_open(ao))
-		ao_command_async(ao, AO_COMMAND_DRAIN);
-}
-
-void audio_output_cancel(struct audio_output *ao)
-{
-	const ScopeLock protect(ao->mutex);
-
-	if (audio_output_is_open(ao)) {
-		ao->allow_play = false;
-		ao_command_async(ao, AO_COMMAND_CANCEL);
-	}
-}
-
-void
-audio_output_allow_play(struct audio_output *ao)
-{
-	const ScopeLock protect(ao->mutex);
-
-	ao->allow_play = true;
-	if (audio_output_is_open(ao))
-		ao->cond.signal();
-}
-
-void
-audio_output_release(struct audio_output *ao)
-{
-	if (ao->always_on)
-		audio_output_pause(ao);
-	else
-		audio_output_close(ao);
-}
-
-void audio_output_close(struct audio_output *ao)
-{
-	assert(ao != nullptr);
-	assert(!ao->open || !ao->fail_timer.IsDefined());
-
-	const ScopeLock protect(ao->mutex);
-	audio_output_close_locked(ao);
-}
-
-void audio_output_finish(struct audio_output *ao)
-{
-	audio_output_close(ao);
-
-	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);
-}
diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx
deleted file mode 100644
index 7195412ef..000000000
--- a/src/OutputControl.hxx
+++ /dev/null
@@ -1,94 +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_CONTROL_HXX
-#define MPD_OUTPUT_CONTROL_HXX
-
-#include "ReplayGainInfo.hxx"
-
-#include <stddef.h>
-
-struct audio_output;
-struct AudioFormat;
-struct config_param;
-class MusicPipe;
-
-void
-audio_output_set_replay_gain_mode(audio_output *ao,
-				  ReplayGainMode mode);
-
-/**
- * Enables the device.
- */
-void
-audio_output_enable(audio_output *ao);
-
-/**
- * Disables the device.
- */
-void
-audio_output_disable(audio_output *ao);
-
-/**
- * Opens or closes the device, depending on the "enabled" flag.
- *
- * @return true if the device is open
- */
-bool
-audio_output_update(audio_output *ao,
-		    AudioFormat audio_format,
-		    const MusicPipe &mp);
-
-void
-audio_output_play(audio_output *ao);
-
-void
-audio_output_pause(audio_output *ao);
-
-void
-audio_output_drain_async(audio_output *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(audio_output *ao);
-
-/**
- * Set the "allow_play" and signal the thread.
- */
-void
-audio_output_allow_play(audio_output *ao);
-
-void
-audio_output_close(audio_output *ao);
-
-/**
- * Closes the audio output, but if the "always_on" flag is set, put it
- * into pause mode instead.
- */
-void
-audio_output_release(audio_output *ao);
-
-void
-audio_output_finish(audio_output *ao);
-
-#endif
diff --git a/src/OutputError.cxx b/src/OutputError.cxx
deleted file mode 100644
index 9d4128912..000000000
--- a/src/OutputError.cxx
+++ /dev/null
@@ -1,23 +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.
- */
-
-#include "OutputError.hxx"
-#include "util/Domain.hxx"
-
-const Domain output_domain("output");
diff --git a/src/OutputError.hxx b/src/OutputError.hxx
deleted file mode 100644
index e3a20142f..000000000
--- a/src/OutputError.hxx
+++ /dev/null
@@ -1,25 +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_ERROR_HXX
-#define MPD_OUTPUT_ERROR_HXX
-
-extern const class Domain output_domain;
-
-#endif
diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx
deleted file mode 100644
index 43f0dd1ec..000000000
--- a/src/OutputFinish.cxx
+++ /dev/null
@@ -1,51 +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.
- */
-
-#include "config.h"
-#include "OutputInternal.hxx"
-#include "OutputPlugin.hxx"
-#include "MixerControl.hxx"
-#include "FilterInternal.hxx"
-
-#include <assert.h>
-
-void
-ao_base_finish(struct audio_output *ao)
-{
-	assert(!ao->open);
-	assert(!ao->fail_timer.IsDefined());
-	assert(!ao->thread.IsDefined());
-
-	if (ao->mixer != nullptr)
-		mixer_free(ao->mixer);
-
-	delete ao->replay_gain_filter;
-	delete ao->other_replay_gain_filter;
-	delete ao->filter;
-}
-
-void
-audio_output_free(struct audio_output *ao)
-{
-	assert(!ao->open);
-	assert(!ao->fail_timer.IsDefined());
-	assert(!ao->thread.IsDefined());
-
-	ao_plugin_finish(ao);
-}
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
deleted file mode 100644
index f5b1bdc81..000000000
--- a/src/OutputInit.cxx
+++ /dev/null
@@ -1,329 +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.
- */
-
-#include "config.h"
-#include "OutputInternal.hxx"
-#include "OutputList.hxx"
-#include "OutputError.hxx"
-#include "OutputAPI.hxx"
-#include "FilterConfig.hxx"
-#include "AudioParser.hxx"
-#include "MixerList.hxx"
-#include "MixerType.hxx"
-#include "MixerControl.hxx"
-#include "mixer/SoftwareMixerPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterRegistry.hxx"
-#include "filter/AutoConvertFilterPlugin.hxx"
-#include "filter/ReplayGainFilterPlugin.hxx"
-#include "filter/ChainFilterPlugin.hxx"
-#include "ConfigError.hxx"
-#include "ConfigGlobal.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-#define AUDIO_OUTPUT_TYPE	"type"
-#define AUDIO_OUTPUT_NAME	"name"
-#define AUDIO_OUTPUT_FORMAT	"format"
-#define AUDIO_FILTERS		"filters"
-
-static const struct audio_output_plugin *
-audio_output_detect(Error &error)
-{
-	LogDefault(output_domain, "Attempt to detect audio output device");
-
-	audio_output_plugins_for_each(plugin) {
-		if (plugin->test_default_device == nullptr)
-			continue;
-
-		FormatDefault(output_domain,
-			      "Attempting to detect a %s audio device",
-			      plugin->name);
-		if (ao_plugin_test_default_device(plugin))
-			return plugin;
-	}
-
-	error.Set(output_domain, "Unable to detect an audio device");
-	return nullptr;
-}
-
-/**
- * Determines the mixer type which should be used for the specified
- * configuration block.
- *
- * This handles the deprecated options mixer_type (global) and
- * mixer_enabled, if the mixer_type setting is not configured.
- */
-gcc_pure
-static enum mixer_type
-audio_output_mixer_type(const config_param &param)
-{
-	/* read the local "mixer_type" setting */
-	const char *p = param.GetBlockValue("mixer_type");
-	if (p != nullptr)
-		return mixer_type_parse(p);
-
-	/* try the local "mixer_enabled" setting next (deprecated) */
-	if (!param.GetBlockValue("mixer_enabled", true))
-		return MIXER_TYPE_NONE;
-
-	/* fall back to the global "mixer_type" setting (also
-	   deprecated) */
-	return mixer_type_parse(config_get_string(CONF_MIXER_TYPE,
-						  "hardware"));
-}
-
-static Mixer *
-audio_output_load_mixer(struct audio_output *ao,
-			const config_param &param,
-			const struct mixer_plugin *plugin,
-			Filter &filter_chain,
-			Error &error)
-{
-	Mixer *mixer;
-
-	switch (audio_output_mixer_type(param)) {
-	case MIXER_TYPE_NONE:
-	case MIXER_TYPE_UNKNOWN:
-		return nullptr;
-
-	case MIXER_TYPE_HARDWARE:
-		if (plugin == nullptr)
-			return nullptr;
-
-		return mixer_new(plugin, ao, param, error);
-
-	case MIXER_TYPE_SOFTWARE:
-		mixer = mixer_new(&software_mixer_plugin, nullptr,
-				  config_param(),
-				  IgnoreError());
-		assert(mixer != nullptr);
-
-		filter_chain_append(filter_chain, "software_mixer",
-				    software_mixer_get_filter(mixer));
-		return mixer;
-	}
-
-	assert(false);
-	gcc_unreachable();
-}
-
-bool
-ao_base_init(struct audio_output *ao,
-	     const struct audio_output_plugin *plugin,
-	     const config_param &param, Error &error)
-{
-	assert(ao != nullptr);
-	assert(plugin != nullptr);
-	assert(plugin->finish != nullptr);
-	assert(plugin->open != nullptr);
-	assert(plugin->close != nullptr);
-	assert(plugin->play != nullptr);
-
-	if (!param.IsNull()) {
-		ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
-		if (ao->name == nullptr) {
-			error.Set(config_domain,
-				  "Missing \"name\" configuration");
-			return false;
-		}
-
-		const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
-		if (p != nullptr) {
-			bool success =
-				audio_format_parse(ao->config_audio_format,
-						   p, true, error);
-			if (!success)
-				return false;
-		} else
-			ao->config_audio_format.Clear();
-	} else {
-		ao->name = "default detected output";
-
-		ao->config_audio_format.Clear();
-	}
-
-	ao->plugin = plugin;
-	ao->tags = param.GetBlockValue("tags", true);
-	ao->always_on = param.GetBlockValue("always_on", false);
-	ao->enabled = param.GetBlockValue("enabled", true);
-	ao->really_enabled = false;
-	ao->open = false;
-	ao->pause = false;
-	ao->allow_play = true;
-	ao->in_playback_loop = false;
-	ao->woken_for_play = false;
-
-	/* set up the filter chain */
-
-	ao->filter = filter_chain_new();
-	assert(ao->filter != nullptr);
-
-	/* create the normalization filter (if configured) */
-
-	if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
-		Filter *normalize_filter =
-			filter_new(&normalize_filter_plugin, config_param(),
-				   IgnoreError());
-		assert(normalize_filter != nullptr);
-
-		filter_chain_append(*ao->filter, "normalize",
-				    autoconvert_filter_new(normalize_filter));
-	}
-
-	Error filter_error;
-	filter_chain_parse(*ao->filter,
-			   param.GetBlockValue(AUDIO_FILTERS, ""),
-			   filter_error);
-
-	// It's not really fatal - Part of the filter chain has been set up already
-	// and even an empty one will work (if only with unexpected behaviour)
-	if (filter_error.IsDefined())
-		FormatError(filter_error,
-			    "Failed to initialize filter chain for '%s'",
-			    ao->name);
-
-	ao->command = AO_COMMAND_NONE;
-
-	ao->mixer = nullptr;
-	ao->replay_gain_filter = nullptr;
-	ao->other_replay_gain_filter = nullptr;
-
-	/* done */
-
-	return true;
-}
-
-static bool
-audio_output_setup(struct audio_output *ao, const config_param &param,
-		   Error &error)
-{
-
-	/* create the replay_gain filter */
-
-	const char *replay_gain_handler =
-		param.GetBlockValue("replay_gain_handler", "software");
-
-	if (strcmp(replay_gain_handler, "none") != 0) {
-		ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
-						    param, IgnoreError());
-		assert(ao->replay_gain_filter != nullptr);
-
-		ao->replay_gain_serial = 0;
-
-		ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
-							  param,
-							  IgnoreError());
-		assert(ao->other_replay_gain_filter != nullptr);
-
-		ao->other_replay_gain_serial = 0;
-	} else {
-		ao->replay_gain_filter = nullptr;
-		ao->other_replay_gain_filter = nullptr;
-	}
-
-	/* set up the mixer */
-
-	Error mixer_error;
-	ao->mixer = audio_output_load_mixer(ao, param,
-					    ao->plugin->mixer_plugin,
-					    *ao->filter, mixer_error);
-	if (ao->mixer == nullptr && mixer_error.IsDefined())
-		FormatError(mixer_error,
-			    "Failed to initialize hardware mixer for '%s'",
-			    ao->name);
-
-	/* use the hardware mixer for replay gain? */
-
-	if (strcmp(replay_gain_handler, "mixer") == 0) {
-		if (ao->mixer != nullptr)
-			replay_gain_filter_set_mixer(ao->replay_gain_filter,
-						     ao->mixer, 100);
-		else
-			FormatError(output_domain,
-				    "No such mixer for output '%s'", ao->name);
-	} else if (strcmp(replay_gain_handler, "software") != 0 &&
-		   ao->replay_gain_filter != nullptr) {
-		error.Set(config_domain,
-			  "Invalid \"replay_gain_handler\" value");
-		return false;
-	}
-
-	/* the "convert" filter must be the last one in the chain */
-
-	ao->convert_filter = filter_new(&convert_filter_plugin, config_param(),
-					IgnoreError());
-	assert(ao->convert_filter != nullptr);
-
-	filter_chain_append(*ao->filter, "convert", ao->convert_filter);
-
-	return true;
-}
-
-struct audio_output *
-audio_output_new(const config_param &param,
-		 PlayerControl &pc,
-		 Error &error)
-{
-	const struct audio_output_plugin *plugin;
-
-	if (!param.IsNull()) {
-		const char *p;
-
-		p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
-		if (p == nullptr) {
-			error.Set(config_domain,
-				  "Missing \"type\" configuration");
-			return nullptr;
-		}
-
-		plugin = audio_output_plugin_get(p);
-		if (plugin == nullptr) {
-			error.Format(config_domain,
-				     "No such audio output plugin: %s", p);
-			return nullptr;
-		}
-	} else {
-		LogWarning(output_domain,
-			   "No 'audio_output' defined in config file");
-
-		plugin = audio_output_detect(error);
-		if (plugin == nullptr)
-			return nullptr;
-
-		FormatDefault(output_domain,
-			      "Successfully detected a %s audio device",
-			      plugin->name);
-	}
-
-	struct audio_output *ao = ao_plugin_init(plugin, param, error);
-	if (ao == nullptr)
-		return nullptr;
-
-	if (!audio_output_setup(ao, param, error)) {
-		ao_plugin_finish(ao);
-		return nullptr;
-	}
-
-	ao->player_control = &pc;
-	return ao;
-}
diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx
deleted file mode 100644
index 18404dc01..000000000
--- a/src/OutputInternal.hxx
+++ /dev/null
@@ -1,301 +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_INTERNAL_HXX
-#define MPD_OUTPUT_INTERNAL_HXX
-
-#include "AudioFormat.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "pcm/PcmDither.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "thread/Thread.hxx"
-#include "system/PeriodClock.hxx"
-
-class Error;
-class Filter;
-class MusicPipe;
-struct config_param;
-struct PlayerControl;
-
-enum audio_output_command {
-	AO_COMMAND_NONE = 0,
-	AO_COMMAND_ENABLE,
-	AO_COMMAND_DISABLE,
-	AO_COMMAND_OPEN,
-
-	/**
-	 * This command is invoked when the input audio format
-	 * changes.
-	 */
-	AO_COMMAND_REOPEN,
-
-	AO_COMMAND_CLOSE,
-	AO_COMMAND_PAUSE,
-
-	/**
-	 * Drains the internal (hardware) buffers of the device.  This
-	 * operation may take a while to complete.
-	 */
-	AO_COMMAND_DRAIN,
-
-	AO_COMMAND_CANCEL,
-	AO_COMMAND_KILL
-};
-
-struct audio_output {
-	/**
-	 * The device's configured display name.
-	 */
-	const char *name;
-
-	/**
-	 * The plugin which implements this output device.
-	 */
-	const struct audio_output_plugin *plugin;
-
-	/**
-	 * The #mixer object associated with this audio output device.
-	 * May be nullptr if none is available, or if software volume is
-	 * configured.
-	 */
-	class Mixer *mixer;
-
-	/**
-	 * Will this output receive tags from the decoder?  The
-	 * default is true, but it may be configured to false to
-	 * suppress sending tags to the output.
-	 */
-	bool tags;
-
-	/**
-	 * Shall this output always play something (i.e. silence),
-	 * even when playback is stopped?
-	 */
-	bool always_on;
-
-	/**
-	 * Has the user enabled this device?
-	 */
-	bool enabled;
-
-	/**
-	 * Is this device actually enabled, i.e. the "enable" method
-	 * has succeeded?
-	 */
-	bool really_enabled;
-
-	/**
-	 * Is the device (already) open and functional?
-	 *
-	 * This attribute may only be modified by the output thread.
-	 * It is protected with #mutex: write accesses inside the
-	 * output thread and read accesses outside of it may only be
-	 * performed while the lock is held.
-	 */
-	bool open;
-
-	/**
-	 * Is the device paused?  i.e. the output thread is in the
-	 * ao_pause() loop.
-	 */
-	bool pause;
-
-	/**
-	 * When this flag is set, the output thread will not do any
-	 * playback.  It will wait until the flag is cleared.
-	 *
-	 * This is used to synchronize the "clear" operation on the
-	 * shared music pipe during the CANCEL command.
-	 */
-	bool allow_play;
-
-	/**
-	 * True while the OutputThread is inside ao_play().  This
-	 * means the PlayerThread does not need to wake up the
-	 * OutputThread when new chunks are added to the MusicPipe,
-	 * because the OutputThread is already watching that.
-	 */
-	bool in_playback_loop;
-
-	/**
-	 * Has the OutputThread been woken up to play more chunks?
-	 * This is set by audio_output_play() and reset by ao_play()
-	 * to reduce the number of duplicate wakeups.
-	 */
-	bool woken_for_play;
-
-	/**
-	 * If not nullptr, the device has failed, and this timer is used
-	 * to estimate how long it should stay disabled (unless
-	 * explicitly reopened with "play").
-	 */
-	PeriodClock fail_timer;
-
-	/**
-	 * The configured audio format.
-	 */
-	AudioFormat config_audio_format;
-
-	/**
-	 * The audio_format in which audio data is received from the
-	 * player thread (which in turn receives it from the decoder).
-	 */
-	AudioFormat in_audio_format;
-
-	/**
-	 * The audio_format which is really sent to the device.  This
-	 * is basically config_audio_format (if configured) or
-	 * in_audio_format, but may have been modified by
-	 * plugin->open().
-	 */
-	AudioFormat out_audio_format;
-
-	/**
-	 * The buffer used to allocate the cross-fading result.
-	 */
-	PcmBuffer cross_fade_buffer;
-
-	/**
-	 * The dithering state for cross-fading two streams.
-	 */
-	PcmDither cross_fade_dither;
-
-	/**
-	 * The filter object of this audio output.  This is an
-	 * instance of chain_filter_plugin.
-	 */
-	Filter *filter;
-
-	/**
-	 * The replay_gain_filter_plugin instance of this audio
-	 * output.
-	 */
-	Filter *replay_gain_filter;
-
-	/**
-	 * The serial number of the last replay gain info.  0 means no
-	 * replay gain info was available.
-	 */
-	unsigned replay_gain_serial;
-
-	/**
-	 * The replay_gain_filter_plugin instance of this audio
-	 * output, to be applied to the second chunk during
-	 * cross-fading.
-	 */
-	Filter *other_replay_gain_filter;
-
-	/**
-	 * The serial number of the last replay gain info by the
-	 * "other" chunk during cross-fading.
-	 */
-	unsigned other_replay_gain_serial;
-
-	/**
-	 * The convert_filter_plugin instance of this audio output.
-	 * It is the last item in the filter chain, and is responsible
-	 * for converting the input data into the appropriate format
-	 * for this audio output.
-	 */
-	Filter *convert_filter;
-
-	/**
-	 * The thread handle, or nullptr if the output thread isn't
-	 * running.
-	 */
-	Thread thread;
-
-	/**
-	 * The next command to be performed by the output thread.
-	 */
-	enum audio_output_command command;
-
-	/**
-	 * The music pipe which provides music chunks to be played.
-	 */
-	const MusicPipe *pipe;
-
-	/**
-	 * This mutex protects #open, #fail_timer, #chunk and
-	 * #chunk_finished.
-	 */
-	Mutex mutex;
-
-	/**
-	 * This condition object wakes up the output thread after
-	 * #command has been set.
-	 */
-	Cond cond;
-
-	/**
-	 * The PlayerControl object which "owns" this output.  This
-	 * object is needed to signal command completion.
-	 */
-	PlayerControl *player_control;
-
-	/**
-	 * The #music_chunk which is currently being played.  All
-	 * chunks before this one may be returned to the
-	 * #music_buffer, because they are not going to be used by
-	 * this output anymore.
-	 */
-	const struct music_chunk *chunk;
-
-	/**
-	 * Has the output finished playing #chunk?
-	 */
-	bool chunk_finished;
-};
-
-/**
- * Notify object used by the thread's client, i.e. we will send a
- * notify signal to this object, expecting the caller to wait on it.
- */
-extern struct notify audio_output_client_notify;
-
-static inline bool
-audio_output_is_open(const struct audio_output *ao)
-{
-	return ao->open;
-}
-
-static inline bool
-audio_output_command_is_finished(const struct audio_output *ao)
-{
-	return ao->command == AO_COMMAND_NONE;
-}
-
-struct audio_output *
-audio_output_new(const config_param &param,
-		 PlayerControl &pc,
-		 Error &error);
-
-bool
-ao_base_init(struct audio_output *ao,
-	     const struct audio_output_plugin *plugin,
-	     const config_param &param, Error &error);
-
-void
-ao_base_finish(struct audio_output *ao);
-
-void
-audio_output_free(struct audio_output *ao);
-
-#endif
diff --git a/src/OutputList.cxx b/src/OutputList.cxx
deleted file mode 100644
index a190da543..000000000
--- a/src/OutputList.cxx
+++ /dev/null
@@ -1,100 +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.
- */
-
-#include "config.h"
-#include "OutputList.hxx"
-#include "OutputAPI.hxx"
-#include "output/AlsaOutputPlugin.hxx"
-#include "output/AoOutputPlugin.hxx"
-#include "output/FifoOutputPlugin.hxx"
-#include "output/HttpdOutputPlugin.hxx"
-#include "output/JackOutputPlugin.hxx"
-#include "output/NullOutputPlugin.hxx"
-#include "output/OpenALOutputPlugin.hxx"
-#include "output/OssOutputPlugin.hxx"
-#include "output/OSXOutputPlugin.hxx"
-#include "output/PipeOutputPlugin.hxx"
-#include "output/PulseOutputPlugin.hxx"
-#include "output/RecorderOutputPlugin.hxx"
-#include "output/RoarOutputPlugin.hxx"
-#include "output/ShoutOutputPlugin.hxx"
-#include "output/SolarisOutputPlugin.hxx"
-#include "output/WinmmOutputPlugin.hxx"
-
-#include <string.h>
-
-const struct audio_output_plugin *const audio_output_plugins[] = {
-#ifdef HAVE_SHOUT
-	&shout_output_plugin,
-#endif
-	&null_output_plugin,
-#ifdef HAVE_FIFO
-	&fifo_output_plugin,
-#endif
-#ifdef ENABLE_PIPE_OUTPUT
-	&pipe_output_plugin,
-#endif
-#ifdef HAVE_ALSA
-	&alsa_output_plugin,
-#endif
-#ifdef HAVE_ROAR
-	&roar_output_plugin,
-#endif
-#ifdef HAVE_AO
-	&ao_output_plugin,
-#endif
-#ifdef HAVE_OSS
-	&oss_output_plugin,
-#endif
-#ifdef HAVE_OPENAL
-	&openal_output_plugin,
-#endif
-#ifdef HAVE_OSX
-	&osx_output_plugin,
-#endif
-#ifdef ENABLE_SOLARIS_OUTPUT
-	&solaris_output_plugin,
-#endif
-#ifdef HAVE_PULSE
-	&pulse_output_plugin,
-#endif
-#ifdef HAVE_JACK
-	&jack_output_plugin,
-#endif
-#ifdef ENABLE_HTTPD_OUTPUT
-	&httpd_output_plugin,
-#endif
-#ifdef ENABLE_RECORDER_OUTPUT
-	&recorder_output_plugin,
-#endif
-#ifdef ENABLE_WINMM_OUTPUT
-	&winmm_output_plugin,
-#endif
-	nullptr
-};
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name)
-{
-	audio_output_plugins_for_each(plugin)
-		if (strcmp(plugin->name, name) == 0)
-			return plugin;
-
-	return nullptr;
-}
diff --git a/src/OutputList.hxx b/src/OutputList.hxx
deleted file mode 100644
index dfcf7487c..000000000
--- a/src/OutputList.hxx
+++ /dev/null
@@ -1,33 +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_LIST_HXX
-#define MPD_OUTPUT_LIST_HXX
-
-extern const struct audio_output_plugin *const audio_output_plugins[];
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name);
-
-#define audio_output_plugins_for_each(plugin) \
-	for (const struct audio_output_plugin *plugin, \
-		*const*output_plugin_iterator = &audio_output_plugins[0]; \
-		(plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator)
-
-#endif
diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx
deleted file mode 100644
index 29fd6455a..000000000
--- a/src/OutputPlugin.cxx
+++ /dev/null
@@ -1,109 +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.
- */
-
-#include "config.h"
-#include "OutputPlugin.hxx"
-#include "OutputInternal.hxx"
-
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
-	       const config_param &param,
-	       Error &error)
-{
-	assert(plugin != nullptr);
-	assert(plugin->init != nullptr);
-
-	return plugin->init(param, error);
-}
-
-void
-ao_plugin_finish(struct audio_output *ao)
-{
-	ao->plugin->finish(ao);
-}
-
-bool
-ao_plugin_enable(struct audio_output *ao, Error &error_r)
-{
-	return ao->plugin->enable != nullptr
-		? ao->plugin->enable(ao, error_r)
-		: true;
-}
-
-void
-ao_plugin_disable(struct audio_output *ao)
-{
-	if (ao->plugin->disable != nullptr)
-		ao->plugin->disable(ao);
-}
-
-bool
-ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
-	       Error &error)
-{
-	return ao->plugin->open(ao, audio_format, error);
-}
-
-void
-ao_plugin_close(struct audio_output *ao)
-{
-	ao->plugin->close(ao);
-}
-
-unsigned
-ao_plugin_delay(struct audio_output *ao)
-{
-	return ao->plugin->delay != nullptr
-		? ao->plugin->delay(ao)
-		: 0;
-}
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const Tag *tag)
-{
-	if (ao->plugin->send_tag != nullptr)
-		ao->plugin->send_tag(ao, tag);
-}
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
-	       Error &error)
-{
-	return ao->plugin->play(ao, chunk, size, error);
-}
-
-void
-ao_plugin_drain(struct audio_output *ao)
-{
-	if (ao->plugin->drain != nullptr)
-		ao->plugin->drain(ao);
-}
-
-void
-ao_plugin_cancel(struct audio_output *ao)
-{
-	if (ao->plugin->cancel != nullptr)
-		ao->plugin->cancel(ao);
-}
-
-bool
-ao_plugin_pause(struct audio_output *ao)
-{
-	return ao->plugin->pause != nullptr && ao->plugin->pause(ao);
-}
diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx
deleted file mode 100644
index 7d77c92b2..000000000
--- a/src/OutputPlugin.hxx
+++ /dev/null
@@ -1,202 +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_PLUGIN_HXX
-#define MPD_OUTPUT_PLUGIN_HXX
-
-#include "Compiler.h"
-
-#include <stddef.h>
-
-struct config_param;
-struct AudioFormat;
-struct Tag;
-class Error;
-
-/**
- * A plugin which controls an audio output device.
- */
-struct audio_output_plugin {
-	/**
-	 * the plugin's name
-	 */
-	const char *name;
-
-	/**
-	 * Test if this plugin can provide a default output, in case
-	 * none has been configured.  This method is optional.
-	 */
-	bool (*test_default_device)(void);
-
-	/**
-	 * Configure and initialize the device, but do not open it
-	 * yet.
-	 *
-	 * @param param the configuration section, or nullptr if there is
-	 * no configuration
-	 * @return nullptr on error, or an opaque pointer to the plugin's
-	 * data
-	 */
-	struct audio_output *(*init)(const config_param &param,
-				     Error &error);
-
-	/**
-	 * Free resources allocated by this device.
-	 */
-	void (*finish)(struct audio_output *data);
-
-	/**
-	 * Enable the device.  This may allocate resources, preparing
-	 * for the device to be opened.  Enabling a device cannot
-	 * fail: if an error occurs during that, it should be reported
-	 * by the open() method.
-	 *
-	 * @return true on success, false on error
-	 */
-	bool (*enable)(struct audio_output *data, Error &error);
-
-	/**
-	 * Disables the device.  It is closed before this method is
-	 * called.
-	 */
-	void (*disable)(struct audio_output *data);
-
-	/**
-	 * Really open the device.
-	 *
-	 * @param audio_format the audio format in which data is going
-	 * to be delivered; may be modified by the plugin
-	 */
-	bool (*open)(struct audio_output *data, AudioFormat &audio_format,
-		     Error &error);
-
-	/**
-	 * Close the device.
-	 */
-	void (*close)(struct audio_output *data);
-
-	/**
-	 * Returns a positive number if the output thread shall delay
-	 * the next call to play() or pause().  This should be
-	 * implemented instead of doing a sleep inside the plugin,
-	 * because this allows MPD to listen to commands meanwhile.
-	 *
-	 * @return the number of milliseconds to wait
-	 */
-	unsigned (*delay)(struct audio_output *data);
-
-	/**
-	 * Display metadata for the next chunk.  Optional method,
-	 * because not all devices can display metadata.
-	 */
-	void (*send_tag)(struct audio_output *data, const Tag *tag);
-
-	/**
-	 * Play a chunk of audio data.
-	 *
-	 * @return the number of bytes played, or 0 on error
-	 */
-	size_t (*play)(struct audio_output *data,
-		       const void *chunk, size_t size,
-		       Error &error);
-
-	/**
-	 * Wait until the device has finished playing.
-	 */
-	void (*drain)(struct audio_output *data);
-
-	/**
-	 * Try to cancel data which may still be in the device's
-	 * buffers.
-	 */
-	void (*cancel)(struct audio_output *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 then), true
-	 * for continue to pause
-	 */
-	bool (*pause)(struct audio_output *data);
-
-	/**
-	 * The mixer plugin associated with this output plugin.  This
-	 * may be nullptr if no mixer plugin is implemented.  When
-	 * created, this mixer plugin gets the same #config_param as
-	 * this audio output device.
-	 */
-	const struct mixer_plugin *mixer_plugin;
-};
-
-static inline bool
-ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
-{
-	return plugin->test_default_device != nullptr
-		? plugin->test_default_device()
-		: false;
-}
-
-gcc_malloc
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
-	       const config_param &param,
-	       Error &error);
-
-void
-ao_plugin_finish(struct audio_output *ao);
-
-bool
-ao_plugin_enable(struct audio_output *ao, Error &error);
-
-void
-ao_plugin_disable(struct audio_output *ao);
-
-bool
-ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
-	       Error &error);
-
-void
-ao_plugin_close(struct audio_output *ao);
-
-gcc_pure
-unsigned
-ao_plugin_delay(struct audio_output *ao);
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const Tag *tag);
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
-	       Error &error);
-
-void
-ao_plugin_drain(struct audio_output *ao);
-
-void
-ao_plugin_cancel(struct audio_output *ao);
-
-bool
-ao_plugin_pause(struct audio_output *ao);
-
-#endif
diff --git a/src/OutputPrint.cxx b/src/OutputPrint.cxx
deleted file mode 100644
index ee4424df2..000000000
--- a/src/OutputPrint.cxx
+++ /dev/null
@@ -1,45 +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.
- */
-
-/*
- * Protocol specific code for the audio output library.
- *
- */
-
-#include "config.h"
-#include "OutputPrint.hxx"
-#include "OutputAll.hxx"
-#include "OutputInternal.hxx"
-#include "Client.hxx"
-
-void
-printAudioDevices(Client &client)
-{
-	const unsigned n = audio_output_count();
-
-	for (unsigned i = 0; i < n; ++i) {
-		const struct audio_output *ao = audio_output_get(i);
-
-		client_printf(client,
-			      "outputid: %i\n"
-			      "outputname: %s\n"
-			      "outputenabled: %i\n",
-			      i, ao->name, ao->enabled);
-	}
-}
diff --git a/src/OutputPrint.hxx b/src/OutputPrint.hxx
deleted file mode 100644
index 2d94226fd..000000000
--- a/src/OutputPrint.hxx
+++ /dev/null
@@ -1,34 +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.
- */
-
-/*
- * Protocol specific code for the audio output library.
- *
- */
-
-#ifndef MPD_OUTPUT_PRINT_HXX
-#define MPD_OUTPUT_PRINT_HXX
-
-class Client;
-
-void
-printAudioDevices(Client &client);
-
-#endif
diff --git a/src/OutputState.cxx b/src/OutputState.cxx
deleted file mode 100644
index 1141abeda..000000000
--- a/src/OutputState.cxx
+++ /dev/null
@@ -1,92 +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.
- */
-
-/*
- * Saving and loading the audio output states to/from the state file.
- *
- */
-
-#include "config.h"
-#include "OutputState.hxx"
-#include "OutputAll.hxx"
-#include "OutputInternal.hxx"
-#include "OutputError.hxx"
-#include "Log.hxx"
-#include "util/StringUtil.hxx"
-
-#include <assert.h>
-#include <stdlib.h>
-
-#define AUDIO_DEVICE_STATE "audio_device_state:"
-
-unsigned audio_output_state_version;
-
-void
-audio_output_state_save(FILE *fp)
-{
-	unsigned n = audio_output_count();
-
-	assert(n > 0);
-
-	for (unsigned i = 0; i < n; ++i) {
-		const struct audio_output *ao = audio_output_get(i);
-
-		fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
-			ao->enabled, ao->name);
-	}
-}
-
-bool
-audio_output_state_read(const char *line)
-{
-	long value;
-	char *endptr;
-	const char *name;
-	struct audio_output *ao;
-
-	if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
-		return false;
-
-	line += sizeof(AUDIO_DEVICE_STATE) - 1;
-
-	value = strtol(line, &endptr, 10);
-	if (*endptr != ':' || (value != 0 && value != 1))
-		return false;
-
-	if (value != 0)
-		/* state is "enabled": no-op */
-		return true;
-
-	name = endptr + 1;
-	ao = audio_output_find(name);
-	if (ao == NULL) {
-		FormatDebug(output_domain,
-			    "Ignoring device state for '%s'", name);
-		return true;
-	}
-
-	ao->enabled = false;
-	return true;
-}
-
-unsigned
-audio_output_state_get_version(void)
-{
-	return audio_output_state_version;
-}
diff --git a/src/OutputState.hxx b/src/OutputState.hxx
deleted file mode 100644
index a68180ae4..000000000
--- a/src/OutputState.hxx
+++ /dev/null
@@ -1,44 +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.
- */
-
-/*
- * Saving and loading the audio output states to/from the state file.
- *
- */
-
-#ifndef MPD_OUTPUT_STATE_HXX
-#define MPD_OUTPUT_STATE_HXX
-
-#include <stdio.h>
-
-bool
-audio_output_state_read(const char *line);
-
-void
-audio_output_state_save(FILE *fp);
-
-/**
- * Generates a version number for the current state of the audio
- * outputs.  This is used by timer_save_state_file() to determine
- * whether the state has changed and the state file should be saved.
- */
-unsigned
-audio_output_state_get_version(void);
-
-#endif
diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx
deleted file mode 100644
index f7e28b5f4..000000000
--- a/src/OutputThread.cxx
+++ /dev/null
@@ -1,690 +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.
- */
-
-#include "config.h"
-#include "OutputThread.hxx"
-#include "OutputInternal.hxx"
-#include "OutputAPI.hxx"
-#include "OutputError.hxx"
-#include "pcm/PcmMix.hxx"
-#include "notify.hxx"
-#include "FilterInternal.hxx"
-#include "filter/ConvertFilterPlugin.hxx"
-#include "filter/ReplayGainFilterPlugin.hxx"
-#include "PlayerControl.hxx"
-#include "MusicPipe.hxx"
-#include "MusicChunk.hxx"
-#include "thread/Util.hxx"
-#include "thread/Name.hxx"
-#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-#include <assert.h>
-#include <string.h>
-
-static void ao_command_finished(struct audio_output *ao)
-{
-	assert(ao->command != AO_COMMAND_NONE);
-	ao->command = AO_COMMAND_NONE;
-
-	ao->mutex.unlock();
-	audio_output_client_notify.Signal();
-	ao->mutex.lock();
-}
-
-static bool
-ao_enable(struct audio_output *ao)
-{
-	Error error;
-	bool success;
-
-	if (ao->really_enabled)
-		return true;
-
-	ao->mutex.unlock();
-	success = ao_plugin_enable(ao, error);
-	ao->mutex.lock();
-	if (!success) {
-		FormatError(error,
-			    "Failed to enable \"%s\" [%s]",
-			    ao->name, ao->plugin->name);
-		return false;
-	}
-
-	ao->really_enabled = true;
-	return true;
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain);
-
-static void
-ao_disable(struct audio_output *ao)
-{
-	if (ao->open)
-		ao_close(ao, false);
-
-	if (ao->really_enabled) {
-		ao->really_enabled = false;
-
-		ao->mutex.unlock();
-		ao_plugin_disable(ao);
-		ao->mutex.lock();
-	}
-}
-
-static AudioFormat
-ao_filter_open(struct audio_output *ao, AudioFormat &format,
-	       Error &error_r)
-{
-	assert(format.IsValid());
-
-	/* the replay_gain filter cannot fail here */
-	if (ao->replay_gain_filter != nullptr &&
-	    !ao->replay_gain_filter->Open(format, error_r).IsDefined())
-		return AudioFormat::Undefined();
-
-	if (ao->other_replay_gain_filter != nullptr &&
-	    !ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) {
-		if (ao->replay_gain_filter != nullptr)
-			ao->replay_gain_filter->Close();
-		return AudioFormat::Undefined();
-	}
-
-	const AudioFormat af = ao->filter->Open(format, error_r);
-	if (!af.IsDefined()) {
-		if (ao->replay_gain_filter != nullptr)
-			ao->replay_gain_filter->Close();
-		if (ao->other_replay_gain_filter != nullptr)
-			ao->other_replay_gain_filter->Close();
-	}
-
-	return af;
-}
-
-static void
-ao_filter_close(struct audio_output *ao)
-{
-	if (ao->replay_gain_filter != nullptr)
-		ao->replay_gain_filter->Close();
-	if (ao->other_replay_gain_filter != nullptr)
-		ao->other_replay_gain_filter->Close();
-
-	ao->filter->Close();
-}
-
-static void
-ao_open(struct audio_output *ao)
-{
-	bool success;
-	Error error;
-	struct audio_format_string af_string;
-
-	assert(!ao->open);
-	assert(ao->pipe != nullptr);
-	assert(ao->chunk == nullptr);
-	assert(ao->in_audio_format.IsValid());
-
-	ao->fail_timer.Reset();
-
-	/* enable the device (just in case the last enable has failed) */
-
-	if (!ao_enable(ao))
-		/* still no luck */
-		return;
-
-	/* open the filter */
-
-	const AudioFormat filter_audio_format =
-		ao_filter_open(ao, ao->in_audio_format, error);
-	if (!filter_audio_format.IsDefined()) {
-		FormatError(error, "Failed to open filter for \"%s\" [%s]",
-			    ao->name, ao->plugin->name);
-
-		ao->fail_timer.Update();
-		return;
-	}
-
-	assert(filter_audio_format.IsValid());
-
-	ao->out_audio_format = filter_audio_format;
-	ao->out_audio_format.ApplyMask(ao->config_audio_format);
-
-	ao->mutex.unlock();
-	success = ao_plugin_open(ao, ao->out_audio_format, error);
-	ao->mutex.lock();
-
-	assert(!ao->open);
-
-	if (!success) {
-		FormatError(error, "Failed to open \"%s\" [%s]",
-			    ao->name, ao->plugin->name);
-
-		ao_filter_close(ao);
-		ao->fail_timer.Update();
-		return;
-	}
-
-	if (!convert_filter_set(ao->convert_filter, ao->out_audio_format,
-				error)) {
-		FormatError(error, "Failed to convert for \"%s\" [%s]",
-			    ao->name, ao->plugin->name);
-
-		ao_filter_close(ao);
-		ao->fail_timer.Update();
-		return;
-	}
-
-	ao->open = true;
-
-	FormatDebug(output_domain,
-		    "opened plugin=%s name=\"%s\" audio_format=%s",
-		    ao->plugin->name, ao->name,
-		    audio_format_to_string(ao->out_audio_format, &af_string));
-
-	if (ao->in_audio_format != ao->out_audio_format)
-		FormatDebug(output_domain, "converting from %s",
-			    audio_format_to_string(ao->in_audio_format,
-						   &af_string));
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain)
-{
-	assert(ao->open);
-
-	ao->pipe = nullptr;
-
-	ao->chunk = nullptr;
-	ao->open = false;
-
-	ao->mutex.unlock();
-
-	if (drain)
-		ao_plugin_drain(ao);
-	else
-		ao_plugin_cancel(ao);
-
-	ao_plugin_close(ao);
-	ao_filter_close(ao);
-
-	ao->mutex.lock();
-
-	FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
-		    ao->plugin->name, ao->name);
-}
-
-static void
-ao_reopen_filter(struct audio_output *ao)
-{
-	Error error;
-
-	ao_filter_close(ao);
-	const AudioFormat filter_audio_format =
-		ao_filter_open(ao, ao->in_audio_format, error);
-	if (!filter_audio_format.IsDefined() ||
-	    !convert_filter_set(ao->convert_filter, ao->out_audio_format,
-				error)) {
-		FormatError(error,
-			    "Failed to open filter for \"%s\" [%s]",
-			    ao->name, ao->plugin->name);
-
-		/* this is a little code duplication fro ao_close(),
-		   but we cannot call this function because we must
-		   not call filter_close(ao->filter) again */
-
-		ao->pipe = nullptr;
-
-		ao->chunk = nullptr;
-		ao->open = false;
-		ao->fail_timer.Update();
-
-		ao->mutex.unlock();
-		ao_plugin_close(ao);
-		ao->mutex.lock();
-
-		return;
-	}
-}
-
-static void
-ao_reopen(struct audio_output *ao)
-{
-	if (!ao->config_audio_format.IsFullyDefined()) {
-		if (ao->open) {
-			const MusicPipe *mp = ao->pipe;
-			ao_close(ao, true);
-			ao->pipe = mp;
-		}
-
-		/* no audio format is configured: copy in->out, let
-		   the output's open() method determine the effective
-		   out_audio_format */
-		ao->out_audio_format = ao->in_audio_format;
-		ao->out_audio_format.ApplyMask(ao->config_audio_format);
-	}
-
-	if (ao->open)
-		/* the audio format has changed, and all filters have
-		   to be reconfigured */
-		ao_reopen_filter(ao);
-	else
-		ao_open(ao);
-}
-
-/**
- * Wait until the output's delay reaches zero.
- *
- * @return true if playback should be continued, false if a command
- * was issued
- */
-static bool
-ao_wait(struct audio_output *ao)
-{
-	while (true) {
-		unsigned delay = ao_plugin_delay(ao);
-		if (delay == 0)
-			return true;
-
-		(void)ao->cond.timed_wait(ao->mutex, delay);
-
-		if (ao->command != AO_COMMAND_NONE)
-			return false;
-	}
-}
-
-static const void *
-ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
-	      Filter *replay_gain_filter,
-	      unsigned *replay_gain_serial_p,
-	      size_t *length_r)
-{
-	assert(chunk != nullptr);
-	assert(!chunk->IsEmpty());
-	assert(chunk->CheckFormat(ao->in_audio_format));
-
-	const void *data = chunk->data;
-	size_t length = chunk->length;
-
-	(void)ao;
-
-	assert(length % ao->in_audio_format.GetFrameSize() == 0);
-
-	if (length > 0 && replay_gain_filter != nullptr) {
-		if (chunk->replay_gain_serial != *replay_gain_serial_p) {
-			replay_gain_filter_set_info(replay_gain_filter,
-						    chunk->replay_gain_serial != 0
-						    ? &chunk->replay_gain_info
-						    : nullptr);
-			*replay_gain_serial_p = chunk->replay_gain_serial;
-		}
-
-		Error error;
-		data = replay_gain_filter->FilterPCM(data, length,
-						     &length, error);
-		if (data == nullptr) {
-			FormatError(error, "\"%s\" [%s] failed to filter",
-				    ao->name, ao->plugin->name);
-			return nullptr;
-		}
-	}
-
-	*length_r = length;
-	return data;
-}
-
-static const void *
-ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
-		size_t *length_r)
-{
-	size_t length;
-	const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
-					 &ao->replay_gain_serial, &length);
-	if (data == nullptr)
-		return nullptr;
-
-	if (length == 0) {
-		/* empty chunk, nothing to do */
-		*length_r = 0;
-		return data;
-	}
-
-	/* cross-fade */
-
-	if (chunk->other != nullptr) {
-		size_t other_length;
-		const void *other_data =
-			ao_chunk_data(ao, chunk->other,
-				      ao->other_replay_gain_filter,
-				      &ao->other_replay_gain_serial,
-				      &other_length);
-		if (other_data == nullptr)
-			return nullptr;
-
-		if (other_length == 0) {
-			*length_r = 0;
-			return data;
-		}
-
-		/* if the "other" chunk is longer, then that trailer
-		   is used as-is, without mixing; it is part of the
-		   "next" song being faded in, and if there's a rest,
-		   it means cross-fading ends here */
-
-		if (length > other_length)
-			length = other_length;
-
-		void *dest = ao->cross_fade_buffer.Get(other_length);
-		memcpy(dest, other_data, other_length);
-		if (!pcm_mix(ao->cross_fade_dither, dest, data, length,
-			     ao->in_audio_format.format,
-			     1.0 - chunk->mix_ratio)) {
-			FormatError(output_domain,
-				    "Cannot cross-fade format %s",
-				    sample_format_to_string(ao->in_audio_format.format));
-			return nullptr;
-		}
-
-		data = dest;
-		length = other_length;
-	}
-
-	/* apply filter chain */
-
-	Error error;
-	data = ao->filter->FilterPCM(data, length, &length, error);
-	if (data == nullptr) {
-		FormatError(error, "\"%s\" [%s] failed to filter",
-			    ao->name, ao->plugin->name);
-		return nullptr;
-	}
-
-	*length_r = length;
-	return data;
-}
-
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
-{
-	assert(ao != nullptr);
-	assert(ao->filter != nullptr);
-
-	if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) {
-		ao->mutex.unlock();
-		ao_plugin_send_tag(ao, chunk->tag);
-		ao->mutex.lock();
-	}
-
-	size_t size;
-#if GCC_CHECK_VERSION(4,7)
-	/* workaround -Wmaybe-uninitialized false positive */
-	size = 0;
-#endif
-	const char *data = (const char *)ao_filter_chunk(ao, chunk, &size);
-	if (data == nullptr) {
-		ao_close(ao, false);
-
-		/* don't automatically reopen this device for 10
-		   seconds */
-		ao->fail_timer.Update();
-		return false;
-	}
-
-	Error error;
-
-	while (size > 0 && ao->command == AO_COMMAND_NONE) {
-		size_t nbytes;
-
-		if (!ao_wait(ao))
-			break;
-
-		ao->mutex.unlock();
-		nbytes = ao_plugin_play(ao, data, size, error);
-		ao->mutex.lock();
-		if (nbytes == 0) {
-			/* play()==0 means failure */
-			FormatError(error, "\"%s\" [%s] failed to play",
-				    ao->name, ao->plugin->name);
-
-			ao_close(ao, false);
-
-			/* don't automatically reopen this device for
-			   10 seconds */
-			assert(!ao->fail_timer.IsDefined());
-			ao->fail_timer.Update();
-
-			return false;
-		}
-
-		assert(nbytes <= size);
-		assert(nbytes % ao->out_audio_format.GetFrameSize() == 0);
-
-		data += nbytes;
-		size -= nbytes;
-	}
-
-	return true;
-}
-
-static const struct music_chunk *
-ao_next_chunk(struct audio_output *ao)
-{
-	return ao->chunk != nullptr
-		/* continue the previous play() call */
-		? ao->chunk->next
-		/* get the first chunk from the pipe */
-		: ao->pipe->Peek();
-}
-
-/**
- * Plays all remaining chunks, until the tail of the pipe has been
- * reached (and no more chunks are queued), or until a command is
- * received.
- *
- * @return true if at least one chunk has been available, false if the
- * tail of the pipe was already reached
- */
-static bool
-ao_play(struct audio_output *ao)
-{
-	bool success;
-	const struct music_chunk *chunk;
-
-	assert(ao->pipe != nullptr);
-
-	chunk = ao_next_chunk(ao);
-	if (chunk == nullptr)
-		/* no chunk available */
-		return false;
-
-	ao->chunk_finished = false;
-
-	assert(!ao->in_playback_loop);
-	ao->in_playback_loop = true;
-
-	while (chunk != nullptr && ao->command == AO_COMMAND_NONE) {
-		assert(!ao->chunk_finished);
-
-		ao->chunk = chunk;
-
-		success = ao_play_chunk(ao, chunk);
-		if (!success) {
-			assert(ao->chunk == nullptr);
-			break;
-		}
-
-		assert(ao->chunk == chunk);
-		chunk = chunk->next;
-	}
-
-	assert(ao->in_playback_loop);
-	ao->in_playback_loop = false;
-
-	ao->chunk_finished = true;
-
-	ao->mutex.unlock();
-	ao->player_control->LockSignal();
-	ao->mutex.lock();
-
-	return true;
-}
-
-static void ao_pause(struct audio_output *ao)
-{
-	bool ret;
-
-	ao->mutex.unlock();
-	ao_plugin_cancel(ao);
-	ao->mutex.lock();
-
-	ao->pause = true;
-	ao_command_finished(ao);
-
-	do {
-		if (!ao_wait(ao))
-			break;
-
-		ao->mutex.unlock();
-		ret = ao_plugin_pause(ao);
-		ao->mutex.lock();
-
-		if (!ret) {
-			ao_close(ao, false);
-			break;
-		}
-	} while (ao->command == AO_COMMAND_NONE);
-
-	ao->pause = false;
-}
-
-static void
-audio_output_task(void *arg)
-{
-	struct audio_output *ao = (struct audio_output *)arg;
-
-	FormatThreadName("output:%s", ao->name);
-
-	SetThreadRealtime();
-
-	ao->mutex.lock();
-
-	while (1) {
-		switch (ao->command) {
-		case AO_COMMAND_NONE:
-			break;
-
-		case AO_COMMAND_ENABLE:
-			ao_enable(ao);
-			ao_command_finished(ao);
-			break;
-
-		case AO_COMMAND_DISABLE:
-			ao_disable(ao);
-			ao_command_finished(ao);
-			break;
-
-		case AO_COMMAND_OPEN:
-			ao_open(ao);
-			ao_command_finished(ao);
-			break;
-
-		case AO_COMMAND_REOPEN:
-			ao_reopen(ao);
-			ao_command_finished(ao);
-			break;
-
-		case AO_COMMAND_CLOSE:
-			assert(ao->open);
-			assert(ao->pipe != nullptr);
-
-			ao_close(ao, false);
-			ao_command_finished(ao);
-			break;
-
-		case AO_COMMAND_PAUSE:
-			if (!ao->open) {
-				/* the output has failed after
-				   audio_output_all_pause() has
-				   submitted the PAUSE command; bail
-				   out */
-				ao_command_finished(ao);
-				break;
-			}
-
-			ao_pause(ao);
-			/* don't "break" here: this might cause
-			   ao_play() to be called when command==CLOSE
-			   ends the paused state - "continue" checks
-			   the new command first */
-			continue;
-
-		case AO_COMMAND_DRAIN:
-			if (ao->open) {
-				assert(ao->chunk == nullptr);
-				assert(ao->pipe->Peek() == nullptr);
-
-				ao->mutex.unlock();
-				ao_plugin_drain(ao);
-				ao->mutex.lock();
-			}
-
-			ao_command_finished(ao);
-			continue;
-
-		case AO_COMMAND_CANCEL:
-			ao->chunk = nullptr;
-
-			if (ao->open) {
-				ao->mutex.unlock();
-				ao_plugin_cancel(ao);
-				ao->mutex.lock();
-			}
-
-			ao_command_finished(ao);
-			continue;
-
-		case AO_COMMAND_KILL:
-			ao->chunk = nullptr;
-			ao_command_finished(ao);
-			ao->mutex.unlock();
-			return;
-		}
-
-		if (ao->open && ao->allow_play && ao_play(ao))
-			/* don't wait for an event if there are more
-			   chunks in the pipe */
-			continue;
-
-		if (ao->command == AO_COMMAND_NONE) {
-			ao->woken_for_play = false;
-			ao->cond.wait(ao->mutex);
-		}
-	}
-}
-
-void audio_output_thread_start(struct audio_output *ao)
-{
-	assert(ao->command == AO_COMMAND_NONE);
-
-	Error error;
-	if (!ao->thread.Start(audio_output_task, ao, error))
-		FatalError(error);
-}
diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx
deleted file mode 100644
index 1cdbd65f2..000000000
--- a/src/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 audio_output;
-
-void
-audio_output_thread_start(audio_output *ao);
-
-#endif
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
index 750a1732a..bde3c1372 100644
--- a/src/PlayerThread.cxx
+++ b/src/PlayerThread.cxx
@@ -28,7 +28,7 @@
 #include "system/FatalError.hxx"
 #include "CrossFade.hxx"
 #include "PlayerControl.hxx"
-#include "OutputAll.hxx"
+#include "output/OutputAll.hxx"
 #include "tag/Tag.hxx"
 #include "Idle.hxx"
 #include "GlobalEvents.hxx"
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 392b3454b..06b7f0779 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "StateFile.hxx"
-#include "OutputState.hxx"
+#include "output/OutputState.hxx"
 #include "PlaylistState.hxx"
 #include "fs/TextFile.hxx"
 #include "Partition.hxx"
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index 5f708bf0d..4ed3b89f1 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -19,8 +19,8 @@
 
 #include "config.h"
 #include "OutputCommands.hxx"
-#include "OutputPrint.hxx"
-#include "OutputCommand.hxx"
+#include "output/OutputPrint.hxx"
+#include "output/OutputCommand.hxx"
 #include "protocol/Result.hxx"
 #include "protocol/ArgParser.hxx"
 
diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx
index 196fcc0bd..ae7a7ba89 100644
--- a/src/command/PlayerCommands.cxx
+++ b/src/command/PlayerCommands.cxx
@@ -25,7 +25,7 @@
 #include "UpdateGlue.hxx"
 #include "Client.hxx"
 #include "Volume.hxx"
-#include "OutputAll.hxx"
+#include "output/OutputAll.hxx"
 #include "Partition.hxx"
 #include "protocol/Result.hxx"
 #include "protocol/ArgParser.hxx"
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx
index 86a949a8f..9d04ad98b 100644
--- a/src/mixer/AlsaMixerPlugin.cxx
+++ b/src/mixer/AlsaMixerPlugin.cxx
@@ -19,7 +19,7 @@
 
 #include "config.h"
 #include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
+#include "output/OutputAPI.hxx"
 #include "GlobalEvents.hxx"
 #include "Main.hxx"
 #include "event/MultiSocketMonitor.hxx"
diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx
index 4d07a0b68..a379cebae 100644
--- a/src/mixer/PulseMixerPlugin.cxx
+++ b/src/mixer/PulseMixerPlugin.cxx
@@ -20,7 +20,7 @@
 #include "config.h"
 #include "PulseMixerPlugin.hxx"
 #include "MixerInternal.hxx"
-#include "output/PulseOutputPlugin.hxx"
+#include "output/plugins/PulseOutputPlugin.hxx"
 #include "GlobalEvents.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx
index 839b35a6b..18f8c14f8 100644
--- a/src/mixer/RoarMixerPlugin.cxx
+++ b/src/mixer/RoarMixerPlugin.cxx
@@ -21,7 +21,7 @@
 
 #include "config.h"
 #include "MixerInternal.hxx"
-#include "output/RoarOutputPlugin.hxx"
+#include "output/plugins/RoarOutputPlugin.hxx"
 #include "Compiler.h"
 
 struct RoarMixer final : public Mixer {
diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx
index bd0ec4b09..6f10fd71b 100644
--- a/src/mixer/WinmmMixerPlugin.cxx
+++ b/src/mixer/WinmmMixerPlugin.cxx
@@ -19,8 +19,8 @@
 
 #include "config.h"
 #include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
-#include "output/WinmmOutputPlugin.hxx"
+#include "output/OutputAPI.hxx"
+#include "output/plugins/WinmmOutputPlugin.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
 
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
deleted file mode 100644
index 87370ba99..000000000
--- a/src/output/AlsaOutputPlugin.cxx
+++ /dev/null
@@ -1,868 +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.
- */
-
-#include "config.h"
-#include "AlsaOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "pcm/PcmExport.hxx"
-#include "util/Manual.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <alsa/asoundlib.h>
-
-#include <string>
-
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-
-static const char default_device[] = "default";
-
-static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
-
-#define MPD_ALSA_RETRY_NR 5
-
-typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
-					snd_pcm_uframes_t size);
-
-struct AlsaOutput {
-	struct audio_output base;
-
-	Manual<PcmExport> pcm_export;
-
-	/**
-	 * The configured name of the ALSA device; empty for the
-	 * default device
-	 */
-	std::string device;
-
-	/** use memory mapped I/O? */
-	bool use_mmap;
-
-	/**
-	 * Enable DSD over USB according to the dCS suggested
-	 * standard?
-	 *
-	 * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
-	 */
-	bool dsd_usb;
-
-	/** libasound's buffer_time setting (in microseconds) */
-	unsigned int buffer_time;
-
-	/** libasound's period_time setting (in microseconds) */
-	unsigned int period_time;
-
-	/** the mode flags passed to snd_pcm_open */
-	int mode;
-
-	/** the libasound PCM device handle */
-	snd_pcm_t *pcm;
-
-	/**
-	 * a pointer to the libasound writei() function, which is
-	 * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
-	 * use_mmap configuration
-	 */
-	alsa_writei_t *writei;
-
-	/**
-	 * The size of one audio frame passed to method play().
-	 */
-	size_t in_frame_size;
-
-	/**
-	 * The size of one audio frame passed to libasound.
-	 */
-	size_t out_frame_size;
-
-	/**
-	 * The size of one period, in number of frames.
-	 */
-	snd_pcm_uframes_t period_frames;
-
-	/**
-	 * The number of frames written in the current period.
-	 */
-	snd_pcm_uframes_t period_position;
-
-	/**
-	 * Set to non-zero when the Raspberry Pi workaround has been
-	 * activated in alsa_recover(); decremented by each write.
-	 * This will avoid activating it again, leading to an endless
-	 * loop.  This problem was observed with a "RME Digi9636/52".
-	 */
-	unsigned pi_workaround;
-
-	/**
-	 * This buffer gets allocated after opening the ALSA device.
-	 * It contains silence samples, enough to fill one period (see
-	 * #period_frames).
-	 */
-	uint8_t *silence;
-
-	AlsaOutput():mode(0), writei(snd_pcm_writei) {
-	}
-
-	bool Init(const config_param &param, Error &error) {
-		return ao_base_init(&base, &alsa_output_plugin,
-				    param, error);
-	}
-
-	void Deinit() {
-		ao_base_finish(&base);
-	}
-};
-
-static constexpr Domain alsa_output_domain("alsa_output");
-
-static const char *
-alsa_device(const AlsaOutput *ad)
-{
-	return ad->device.empty() ? default_device : ad->device.c_str();
-}
-
-static void
-alsa_configure(AlsaOutput *ad, const config_param &param)
-{
-	ad->device = param.GetBlockValue("device", "");
-
-	ad->use_mmap = param.GetBlockValue("use_mmap", false);
-
-	ad->dsd_usb = param.GetBlockValue("dsd_usb", false);
-
-	ad->buffer_time = param.GetBlockValue("buffer_time",
-					      MPD_ALSA_BUFFER_TIME_US);
-	ad->period_time = param.GetBlockValue("period_time", 0u);
-
-#ifdef SND_PCM_NO_AUTO_RESAMPLE
-	if (!param.GetBlockValue("auto_resample", true))
-		ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_CHANNELS
-	if (!param.GetBlockValue("auto_channels", true))
-		ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_FORMAT
-	if (!param.GetBlockValue("auto_format", true))
-		ad->mode |= SND_PCM_NO_AUTO_FORMAT;
-#endif
-}
-
-static struct audio_output *
-alsa_init(const config_param &param, Error &error)
-{
-	AlsaOutput *ad = new AlsaOutput();
-
-	if (!ad->Init(param, error)) {
-		delete ad;
-		return nullptr;
-	}
-
-	alsa_configure(ad, param);
-
-	return &ad->base;
-}
-
-static void
-alsa_finish(struct audio_output *ao)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	ad->Deinit();
-	delete ad;
-
-	/* free libasound's config cache */
-	snd_config_update_free_global();
-}
-
-static bool
-alsa_output_enable(struct audio_output *ao, gcc_unused Error &error)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	ad->pcm_export.Construct();
-	return true;
-}
-
-static void
-alsa_output_disable(struct audio_output *ao)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	ad->pcm_export.Destruct();
-}
-
-static bool
-alsa_test_default_device(void)
-{
-	snd_pcm_t *handle;
-
-	int ret = snd_pcm_open(&handle, default_device,
-	                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
-	if (ret) {
-		FormatError(alsa_output_domain,
-			    "Error opening default ALSA device: %s",
-			    snd_strerror(-ret));
-		return false;
-	} else
-		snd_pcm_close(handle);
-
-	return true;
-}
-
-static snd_pcm_format_t
-get_bitformat(SampleFormat sample_format)
-{
-	switch (sample_format) {
-	case SampleFormat::UNDEFINED:
-	case SampleFormat::DSD:
-		return SND_PCM_FORMAT_UNKNOWN;
-
-	case SampleFormat::S8:
-		return SND_PCM_FORMAT_S8;
-
-	case SampleFormat::S16:
-		return SND_PCM_FORMAT_S16;
-
-	case SampleFormat::S24_P32:
-		return SND_PCM_FORMAT_S24;
-
-	case SampleFormat::S32:
-		return SND_PCM_FORMAT_S32;
-
-	case SampleFormat::FLOAT:
-		return SND_PCM_FORMAT_FLOAT;
-	}
-
-	assert(false);
-	gcc_unreachable();
-}
-
-static snd_pcm_format_t
-byteswap_bitformat(snd_pcm_format_t fmt)
-{
-	switch(fmt) {
-	case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
-	case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
-	case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
-	case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
-	case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
-
-	case SND_PCM_FORMAT_S24_3BE:
-		return SND_PCM_FORMAT_S24_3LE;
-
-	case SND_PCM_FORMAT_S24_3LE:
-		return SND_PCM_FORMAT_S24_3BE;
-
-	case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
-	default: return SND_PCM_FORMAT_UNKNOWN;
-	}
-}
-
-static snd_pcm_format_t
-alsa_to_packed_format(snd_pcm_format_t fmt)
-{
-	switch (fmt) {
-	case SND_PCM_FORMAT_S24_LE:
-		return SND_PCM_FORMAT_S24_3LE;
-
-	case SND_PCM_FORMAT_S24_BE:
-		return SND_PCM_FORMAT_S24_3BE;
-
-	default:
-		return SND_PCM_FORMAT_UNKNOWN;
-	}
-}
-
-static int
-alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
-			  snd_pcm_format_t fmt, bool *packed_r)
-{
-	int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
-	if (err == 0)
-		*packed_r = false;
-
-	if (err != -EINVAL)
-		return err;
-
-	fmt = alsa_to_packed_format(fmt);
-	if (fmt == SND_PCM_FORMAT_UNKNOWN)
-		return -EINVAL;
-
-	err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
-	if (err == 0)
-		*packed_r = true;
-
-	return err;
-}
-
-/**
- * Attempts to configure the specified sample format, and tries the
- * reversed host byte order if was not supported.
- */
-static int
-alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
-		       SampleFormat sample_format,
-		       bool *packed_r, bool *reverse_endian_r)
-{
-	snd_pcm_format_t alsa_format = get_bitformat(sample_format);
-	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
-		return -EINVAL;
-
-	int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
-					    packed_r);
-	if (err == 0)
-		*reverse_endian_r = false;
-
-	if (err != -EINVAL)
-		return err;
-
-	alsa_format = byteswap_bitformat(alsa_format);
-	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
-		return -EINVAL;
-
-	err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
-	if (err == 0)
-		*reverse_endian_r = true;
-
-	return err;
-}
-
-/**
- * Configure a sample format, and probe other formats if that fails.
- */
-static int
-alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
-			 AudioFormat &audio_format,
-			 bool *packed_r, bool *reverse_endian_r)
-{
-	/* try the input format first */
-
-	int err = alsa_output_try_format(pcm, hwparams,
-					 audio_format.format,
-					 packed_r, reverse_endian_r);
-
-	/* if unsupported by the hardware, try other formats */
-
-	static const SampleFormat probe_formats[] = {
-		SampleFormat::S24_P32,
-		SampleFormat::S32,
-		SampleFormat::S16,
-		SampleFormat::S8,
-		SampleFormat::UNDEFINED,
-	};
-
-	for (unsigned i = 0;
-	     err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
-	     ++i) {
-		const SampleFormat mpd_format = probe_formats[i];
-		if (mpd_format == audio_format.format)
-			continue;
-
-		err = alsa_output_try_format(pcm, hwparams, mpd_format,
-					     packed_r, reverse_endian_r);
-		if (err == 0)
-			audio_format.format = mpd_format;
-	}
-
-	return err;
-}
-
-/**
- * Set up the snd_pcm_t object which was opened by the caller.  Set up
- * the configured settings and the audio format.
- */
-static bool
-alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
-	   bool *packed_r, bool *reverse_endian_r, Error &error)
-{
-	unsigned int sample_rate = audio_format.sample_rate;
-	unsigned int channels = audio_format.channels;
-	int err;
-	const char *cmd = nullptr;
-	int retry = MPD_ALSA_RETRY_NR;
-	unsigned int period_time, period_time_ro;
-	unsigned int buffer_time;
-
-	period_time_ro = period_time = ad->period_time;
-configure_hw:
-	/* configure HW params */
-	snd_pcm_hw_params_t *hwparams;
-	snd_pcm_hw_params_alloca(&hwparams);
-	cmd = "snd_pcm_hw_params_any";
-	err = snd_pcm_hw_params_any(ad->pcm, hwparams);
-	if (err < 0)
-		goto error;
-
-	if (ad->use_mmap) {
-		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
-						   SND_PCM_ACCESS_MMAP_INTERLEAVED);
-		if (err < 0) {
-			FormatWarning(alsa_output_domain,
-				      "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
-				      alsa_device(ad), snd_strerror(-err));
-			LogWarning(alsa_output_domain,
-				   "Falling back to direct write mode");
-			ad->use_mmap = false;
-		} else
-			ad->writei = snd_pcm_mmap_writei;
-	}
-
-	if (!ad->use_mmap) {
-		cmd = "snd_pcm_hw_params_set_access";
-		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
-						   SND_PCM_ACCESS_RW_INTERLEAVED);
-		if (err < 0)
-			goto error;
-		ad->writei = snd_pcm_writei;
-	}
-
-	err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
-				       packed_r, reverse_endian_r);
-	if (err < 0) {
-		error.Format(alsa_output_domain, err,
-			     "ALSA device \"%s\" does not support format %s: %s",
-			     alsa_device(ad),
-			     sample_format_to_string(audio_format.format),
-			     snd_strerror(-err));
-		return false;
-	}
-
-	snd_pcm_format_t format;
-	if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
-		FormatDebug(alsa_output_domain,
-			    "format=%s (%s)", snd_pcm_format_name(format),
-			    snd_pcm_format_description(format));
-
-	err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
-						  &channels);
-	if (err < 0) {
-		error.Format(alsa_output_domain, err,
-			     "ALSA device \"%s\" does not support %i channels: %s",
-			     alsa_device(ad), (int)audio_format.channels,
-			     snd_strerror(-err));
-		return false;
-	}
-	audio_format.channels = (int8_t)channels;
-
-	err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
-					      &sample_rate, nullptr);
-	if (err < 0 || sample_rate == 0) {
-		error.Format(alsa_output_domain, err,
-			     "ALSA device \"%s\" does not support %u Hz audio",
-			     alsa_device(ad), audio_format.sample_rate);
-		return false;
-	}
-	audio_format.sample_rate = sample_rate;
-
-	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
-	snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
-	snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
-	unsigned buffer_time_min, buffer_time_max;
-	snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
-	snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
-	FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
-		    (unsigned)buffer_size_min, (unsigned)buffer_size_max,
-		    buffer_time_min, buffer_time_max);
-
-	snd_pcm_uframes_t period_size_min, period_size_max;
-	snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
-	snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
-	unsigned period_time_min, period_time_max;
-	snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
-	snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
-	FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
-		    (unsigned)period_size_min, (unsigned)period_size_max,
-		    period_time_min, period_time_max);
-
-	if (ad->buffer_time > 0) {
-		buffer_time = ad->buffer_time;
-		cmd = "snd_pcm_hw_params_set_buffer_time_near";
-		err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
-							     &buffer_time, nullptr);
-		if (err < 0)
-			goto error;
-	} else {
-		err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
-							nullptr);
-		if (err < 0)
-			buffer_time = 0;
-	}
-
-	if (period_time_ro == 0 && buffer_time >= 10000) {
-		period_time_ro = period_time = buffer_time / 4;
-
-		FormatDebug(alsa_output_domain,
-			    "default period_time = buffer_time/4 = %u/4 = %u",
-			    buffer_time, period_time);
-	}
-
-	if (period_time_ro > 0) {
-		period_time = period_time_ro;
-		cmd = "snd_pcm_hw_params_set_period_time_near";
-		err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
-							     &period_time, nullptr);
-		if (err < 0)
-			goto error;
-	}
-
-	cmd = "snd_pcm_hw_params";
-	err = snd_pcm_hw_params(ad->pcm, hwparams);
-	if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
-		period_time_ro = period_time_ro >> 1;
-		goto configure_hw;
-	} else if (err < 0)
-		goto error;
-	if (retry != MPD_ALSA_RETRY_NR)
-		FormatDebug(alsa_output_domain,
-			    "ALSA period_time set to %d", period_time);
-
-	snd_pcm_uframes_t alsa_buffer_size;
-	cmd = "snd_pcm_hw_params_get_buffer_size";
-	err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
-	if (err < 0)
-		goto error;
-
-	snd_pcm_uframes_t alsa_period_size;
-	cmd = "snd_pcm_hw_params_get_period_size";
-	err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
-						nullptr);
-	if (err < 0)
-		goto error;
-
-	/* configure SW params */
-	snd_pcm_sw_params_t *swparams;
-	snd_pcm_sw_params_alloca(&swparams);
-
-	cmd = "snd_pcm_sw_params_current";
-	err = snd_pcm_sw_params_current(ad->pcm, swparams);
-	if (err < 0)
-		goto error;
-
-	cmd = "snd_pcm_sw_params_set_start_threshold";
-	err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
-						    alsa_buffer_size -
-						    alsa_period_size);
-	if (err < 0)
-		goto error;
-
-	cmd = "snd_pcm_sw_params_set_avail_min";
-	err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
-					      alsa_period_size);
-	if (err < 0)
-		goto error;
-
-	cmd = "snd_pcm_sw_params";
-	err = snd_pcm_sw_params(ad->pcm, swparams);
-	if (err < 0)
-		goto error;
-
-	FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
-		    (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
-
-	if (alsa_period_size == 0)
-		/* this works around a SIGFPE bug that occurred when
-		   an ALSA driver indicated period_size==0; this
-		   caused a division by zero in alsa_play().  By using
-		   the fallback "1", we make sure that this won't
-		   happen again. */
-		alsa_period_size = 1;
-
-	ad->period_frames = alsa_period_size;
-	ad->period_position = 0;
-
-	ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
-							  alsa_period_size)];
-	snd_pcm_format_set_silence(format, ad->silence,
-				   alsa_period_size * channels);
-
-	return true;
-
-error:
-	error.Format(alsa_output_domain, err,
-		     "Error opening ALSA device \"%s\" (%s): %s",
-		     alsa_device(ad), cmd, snd_strerror(-err));
-	return false;
-}
-
-static bool
-alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
-	       bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
-	       Error &error)
-{
-	assert(ad->dsd_usb);
-	assert(audio_format.format == SampleFormat::DSD);
-
-	/* pass 24 bit to alsa_setup() */
-
-	AudioFormat usb_format = audio_format;
-	usb_format.format = SampleFormat::S24_P32;
-	usb_format.sample_rate /= 2;
-
-	const AudioFormat check = usb_format;
-
-	if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error))
-		return false;
-
-	/* if the device allows only 32 bit, shift all DSD-over-USB
-	   samples left by 8 bit and leave the lower 8 bit cleared;
-	   the DSD-over-USB documentation does not specify whether
-	   this is legal, but there is anecdotical evidence that this
-	   is possible (and the only option for some devices) */
-	*shift8_r = usb_format.format == SampleFormat::S32;
-	if (usb_format.format == SampleFormat::S32)
-		usb_format.format = SampleFormat::S24_P32;
-
-	if (usb_format != check) {
-		/* no bit-perfect playback, which is required
-		   for DSD over USB */
-		error.Format(alsa_output_domain,
-			     "Failed to configure DSD-over-USB on ALSA device \"%s\"",
-			     alsa_device(ad));
-		delete[] ad->silence;
-		return false;
-	}
-
-	return true;
-}
-
-static bool
-alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
-		  Error &error)
-{
-	bool shift8 = false, packed, reverse_endian;
-
-	const bool dsd_usb = ad->dsd_usb &&
-		audio_format.format == SampleFormat::DSD;
-	const bool success = dsd_usb
-		? alsa_setup_dsd(ad, audio_format,
-				 &shift8, &packed, &reverse_endian,
-				 error)
-		: alsa_setup(ad, audio_format, &packed, &reverse_endian,
-			     error);
-	if (!success)
-		return false;
-
-	ad->pcm_export->Open(audio_format.format,
-			     audio_format.channels,
-			     dsd_usb, shift8, packed, reverse_endian);
-	return true;
-}
-
-static bool
-alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	ad->pi_workaround = 0;
-
-	int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
-			       SND_PCM_STREAM_PLAYBACK, ad->mode);
-	if (err < 0) {
-		error.Format(alsa_output_domain, err,
-			    "Failed to open ALSA device \"%s\": %s",
-			    alsa_device(ad), snd_strerror(err));
-		return false;
-	}
-
-	FormatDebug(alsa_output_domain, "opened %s type=%s",
-		    snd_pcm_name(ad->pcm),
-		    snd_pcm_type_name(snd_pcm_type(ad->pcm)));
-
-	if (!alsa_setup_or_dsd(ad, audio_format, error)) {
-		snd_pcm_close(ad->pcm);
-		return false;
-	}
-
-	ad->in_frame_size = audio_format.GetFrameSize();
-	ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
-
-	return true;
-}
-
-/**
- * Write silence to the ALSA device.
- */
-static void
-alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
-{
-	ad->writei(ad->pcm, ad->silence, nframes);
-}
-
-static int
-alsa_recover(AlsaOutput *ad, int err)
-{
-	if (err == -EPIPE) {
-		FormatDebug(alsa_output_domain,
-			    "Underrun on ALSA device \"%s\"", alsa_device(ad));
-	} else if (err == -ESTRPIPE) {
-		FormatDebug(alsa_output_domain,
-			    "ALSA device \"%s\" was suspended",
-			    alsa_device(ad));
-	}
-
-	switch (snd_pcm_state(ad->pcm)) {
-	case SND_PCM_STATE_PAUSED:
-		err = snd_pcm_pause(ad->pcm, /* disable */ 0);
-		break;
-	case SND_PCM_STATE_SUSPENDED:
-		err = snd_pcm_resume(ad->pcm);
-		if (err == -EAGAIN)
-			return 0;
-		/* fall-through to snd_pcm_prepare: */
-	case SND_PCM_STATE_SETUP:
-	case SND_PCM_STATE_XRUN:
-		ad->period_position = 0;
-		err = snd_pcm_prepare(ad->pcm);
-
-		if (err == 0 && ad->pi_workaround == 0) {
-			/* this works around a driver bug observed on
-			   the Raspberry Pi: after snd_pcm_drop(), the
-			   whole ring buffer must be invalidated, but
-			   the snd_pcm_prepare() call above makes the
-			   driver play random data that just happens
-			   to be still in the buffer; by adding and
-			   cancelling some silence, this bug does not
-			   occur */
-			alsa_write_silence(ad, ad->period_frames);
-
-			/* cancel the silence data right away to avoid
-			   increasing latency; even though this
-			   function call invalidates the portion of
-			   silence, the driver seems to avoid the
-			   bug */
-			snd_pcm_reset(ad->pcm);
-
-			/* disable the workaround for some time */
-			ad->pi_workaround = 8;
-		}
-
-		break;
-	case SND_PCM_STATE_DISCONNECTED:
-		break;
-	/* this is no error, so just keep running */
-	case SND_PCM_STATE_RUNNING:
-		err = 0;
-		break;
-	default:
-		/* unknown state, do nothing */
-		break;
-	}
-
-	return err;
-}
-
-static void
-alsa_drain(struct audio_output *ao)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
-		return;
-
-	if (ad->period_position > 0) {
-		/* generate some silence to finish the partial
-		   period */
-		snd_pcm_uframes_t nframes =
-			ad->period_frames - ad->period_position;
-		alsa_write_silence(ad, nframes);
-	}
-
-	snd_pcm_drain(ad->pcm);
-
-	ad->period_position = 0;
-}
-
-static void
-alsa_cancel(struct audio_output *ao)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	ad->period_position = 0;
-
-	snd_pcm_drop(ad->pcm);
-}
-
-static void
-alsa_close(struct audio_output *ao)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	snd_pcm_close(ad->pcm);
-	delete[] ad->silence;
-}
-
-static size_t
-alsa_play(struct audio_output *ao, const void *chunk, size_t size,
-	  Error &error)
-{
-	AlsaOutput *ad = (AlsaOutput *)ao;
-
-	assert(size % ad->in_frame_size == 0);
-
-	chunk = ad->pcm_export->Export(chunk, size, size);
-
-	assert(size % ad->out_frame_size == 0);
-
-	size /= ad->out_frame_size;
-
-	while (true) {
-		snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
-		if (ret > 0) {
-			ad->period_position = (ad->period_position + ret)
-				% ad->period_frames;
-
-			if (ad->pi_workaround > 0)
-				--ad->pi_workaround;
-
-			size_t bytes_written = ret * ad->out_frame_size;
-			return ad->pcm_export->CalcSourceSize(bytes_written);
-		}
-
-		if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
-		    alsa_recover(ad, ret) < 0) {
-			error.Set(alsa_output_domain, ret, snd_strerror(-ret));
-			return 0;
-		}
-	}
-}
-
-const struct audio_output_plugin alsa_output_plugin = {
-	"alsa",
-	alsa_test_default_device,
-	alsa_init,
-	alsa_finish,
-	alsa_output_enable,
-	alsa_output_disable,
-	alsa_open,
-	alsa_close,
-	nullptr,
-	nullptr,
-	alsa_play,
-	alsa_drain,
-	alsa_cancel,
-	nullptr,
-
-	&alsa_mixer_plugin,
-};
diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx
deleted file mode 100644
index 63508e041..000000000
--- a/src/output/AlsaOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_ALSA_OUTPUT_PLUGIN_HXX
-#define MPD_ALSA_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin alsa_output_plugin;
-
-#endif
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx
deleted file mode 100644
index e6cd0b916..000000000
--- a/src/output/AoOutputPlugin.cxx
+++ /dev/null
@@ -1,286 +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.
- */
-
-#include "config.h"
-#include "AoOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <ao/ao.h>
-#include <glib.h>
-
-#include <string.h>
-
-/* An ao_sample_format, with all fields set to zero: */
-static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
-
-static unsigned ao_output_ref;
-
-struct AoOutput {
-	struct audio_output base;
-
-	size_t write_size;
-	int driver;
-	ao_option *options;
-	ao_device *device;
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &ao_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain ao_output_domain("ao_output");
-
-static void
-ao_output_error(Error &error_r)
-{
-	const char *error;
-
-	switch (errno) {
-	case AO_ENODRIVER:
-		error = "No such libao driver";
-		break;
-
-	case AO_ENOTLIVE:
-		error = "This driver is not a libao live device";
-		break;
-
-	case AO_EBADOPTION:
-		error = "Invalid libao option";
-		break;
-
-	case AO_EOPENDEVICE:
-		error = "Cannot open the libao device";
-		break;
-
-	case AO_EFAIL:
-		error = "Generic libao failure";
-		break;
-
-	default:
-		error_r.SetErrno();
-		return;
-	}
-
-	error_r.Set(ao_output_domain, errno, error);
-}
-
-inline bool
-AoOutput::Configure(const config_param &param, Error &error)
-{
-	const char *value;
-
-	options = nullptr;
-
-	write_size = param.GetBlockValue("write_size", 1024u);
-
-	if (ao_output_ref == 0) {
-		ao_initialize();
-	}
-	ao_output_ref++;
-
-	value = param.GetBlockValue("driver", "default");
-	if (0 == strcmp(value, "default"))
-		driver = ao_default_driver_id();
-	else
-		driver = ao_driver_id(value);
-
-	if (driver < 0) {
-		error.Format(ao_output_domain,
-			     "\"%s\" is not a valid ao driver",
-			     value);
-		return false;
-	}
-
-	ao_info *ai = ao_driver_info(driver);
-	if (ai == nullptr) {
-		error.Set(ao_output_domain, "problems getting driver info");
-		return false;
-	}
-
-	FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
-		    ai->short_name, param.GetBlockValue("name", nullptr));
-
-	value = param.GetBlockValue("options", nullptr);
-	if (value != nullptr) {
-		gchar **_options = g_strsplit(value, ";", 0);
-
-		for (unsigned i = 0; _options[i] != nullptr; ++i) {
-			gchar **key_value = g_strsplit(_options[i], "=", 2);
-
-			if (key_value[0] == nullptr || key_value[1] == nullptr) {
-				error.Format(ao_output_domain,
-					     "problems parsing options \"%s\"",
-					     _options[i]);
-				return false;
-			}
-
-			ao_append_option(&options, key_value[0],
-					 key_value[1]);
-
-			g_strfreev(key_value);
-		}
-
-		g_strfreev(_options);
-	}
-
-	return true;
-}
-
-static struct audio_output *
-ao_output_init(const config_param &param, Error &error)
-{
-	AoOutput *ad = new AoOutput();
-
-	if (!ad->Initialize(param, error)) {
-		delete ad;
-		return nullptr;
-	}
-
-	if (!ad->Configure(param, error)) {
-		ad->Deinitialize();
-		delete ad;
-		return nullptr;
-	}
-
-	return &ad->base;
-}
-
-static void
-ao_output_finish(struct audio_output *ao)
-{
-	AoOutput *ad = (AoOutput *)ao;
-
-	ao_free_options(ad->options);
-	ad->Deinitialize();
-	delete ad;
-
-	ao_output_ref--;
-
-	if (ao_output_ref == 0)
-		ao_shutdown();
-}
-
-static void
-ao_output_close(struct audio_output *ao)
-{
-	AoOutput *ad = (AoOutput *)ao;
-
-	ao_close(ad->device);
-}
-
-static bool
-ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
-	       Error &error)
-{
-	ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
-	AoOutput *ad = (AoOutput *)ao;
-
-	switch (audio_format.format) {
-	case SampleFormat::S8:
-		format.bits = 8;
-		break;
-
-	case SampleFormat::S16:
-		format.bits = 16;
-		break;
-
-	default:
-		/* support for 24 bit samples in libao is currently
-		   dubious, and until we have sorted that out,
-		   convert everything to 16 bit */
-		audio_format.format = SampleFormat::S16;
-		format.bits = 16;
-		break;
-	}
-
-	format.rate = audio_format.sample_rate;
-	format.byte_format = AO_FMT_NATIVE;
-	format.channels = audio_format.channels;
-
-	ad->device = ao_open_live(ad->driver, &format, ad->options);
-
-	if (ad->device == nullptr) {
-		ao_output_error(error);
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * For whatever reason, libao wants a non-const pointer.  Let's hope
- * it does not write to the buffer, and use the union deconst hack to
- * work around this API misdesign.
- */
-static int ao_play_deconst(ao_device *device, const void *output_samples,
-			   uint_32 num_bytes)
-{
-	union {
-		const void *in;
-		char *out;
-	} u;
-
-	u.in = output_samples;
-	return ao_play(device, u.out, num_bytes);
-}
-
-static size_t
-ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
-	       Error &error)
-{
-	AoOutput *ad = (AoOutput *)ao;
-
-	if (size > ad->write_size)
-		size = ad->write_size;
-
-	if (ao_play_deconst(ad->device, chunk, size) == 0) {
-		ao_output_error(error);
-		return 0;
-	}
-
-	return size;
-}
-
-const struct audio_output_plugin ao_output_plugin = {
-	"ao",
-	nullptr,
-	ao_output_init,
-	ao_output_finish,
-	nullptr,
-	nullptr,
-	ao_output_open,
-	ao_output_close,
-	nullptr,
-	nullptr,
-	ao_output_play,
-	nullptr,
-	nullptr,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/AoOutputPlugin.hxx b/src/output/AoOutputPlugin.hxx
deleted file mode 100644
index cbf2fd589..000000000
--- a/src/output/AoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_AO_OUTPUT_PLUGIN_HXX
-#define MPD_AO_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin ao_output_plugin;
-
-#endif
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
deleted file mode 100644
index 9e5a1d5d2..000000000
--- a/src/output/FifoOutputPlugin.cxx
+++ /dev/null
@@ -1,313 +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.
- */
-
-#include "config.h"
-#include "FifoOutputPlugin.hxx"
-#include "ConfigError.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-#include "open.h"
-
-#include <sys/stat.h>
-#include <errno.h>
-#include <unistd.h>
-
-#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
-
-struct FifoOutput {
-	struct audio_output base;
-
-	AllocatedPath path;
-	std::string path_utf8;
-
-	int input;
-	int output;
-	bool created;
-	Timer *timer;
-
-	FifoOutput()
-		:path(AllocatedPath::Null()), input(-1), output(-1),
-		 created(false) {}
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &fifo_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	bool Create(Error &error);
-	bool Check(Error &error);
-	void Delete();
-
-	bool Open(Error &error);
-	void Close();
-};
-
-static constexpr Domain fifo_output_domain("fifo_output");
-
-inline void
-FifoOutput::Delete()
-{
-	FormatDebug(fifo_output_domain,
-		    "Removing FIFO \"%s\"", path_utf8.c_str());
-
-	if (!RemoveFile(path)) {
-		FormatErrno(fifo_output_domain,
-			    "Could not remove FIFO \"%s\"",
-			    path_utf8.c_str());
-		return;
-	}
-
-	created = false;
-}
-
-void
-FifoOutput::Close()
-{
-	if (input >= 0) {
-		close(input);
-		input = -1;
-	}
-
-	if (output >= 0) {
-		close(output);
-		output = -1;
-	}
-
-	struct stat st;
-	if (created && StatFile(path, st))
-		Delete();
-}
-
-inline bool
-FifoOutput::Create(Error &error)
-{
-	if (!MakeFifo(path, 0666)) {
-		error.FormatErrno("Couldn't create FIFO \"%s\"",
-				  path_utf8.c_str());
-		return false;
-	}
-
-	created = true;
-	return true;
-}
-
-inline bool
-FifoOutput::Check(Error &error)
-{
-	struct stat st;
-	if (!StatFile(path, st)) {
-		if (errno == ENOENT) {
-			/* Path doesn't exist */
-			return Create(error);
-		}
-
-		error.FormatErrno("Failed to stat FIFO \"%s\"",
-				  path_utf8.c_str());
-		return false;
-	}
-
-	if (!S_ISFIFO(st.st_mode)) {
-		error.Format(fifo_output_domain,
-			     "\"%s\" already exists, but is not a FIFO",
-			     path_utf8.c_str());
-		return false;
-	}
-
-	return true;
-}
-
-inline bool
-FifoOutput::Open(Error &error)
-{
-	if (!Check(error))
-		return false;
-
-	input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
-	if (input < 0) {
-		error.FormatErrno("Could not open FIFO \"%s\" for reading",
-				  path_utf8.c_str());
-		Close();
-		return false;
-	}
-
-	output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
-	if (output < 0) {
-		error.FormatErrno("Could not open FIFO \"%s\" for writing",
-				  path_utf8.c_str());
-		Close();
-		return false;
-	}
-
-	return true;
-}
-
-static bool
-fifo_open(FifoOutput *fd, Error &error)
-{
-	return fd->Open(error);
-}
-
-static struct audio_output *
-fifo_output_init(const config_param &param, Error &error)
-{
-	FifoOutput *fd = new FifoOutput();
-
-	fd->path = param.GetBlockPath("path", error);
-	if (fd->path.IsNull()) {
-		delete fd;
-
-		if (!error.IsDefined())
-			error.Set(config_domain,
-				  "No \"path\" parameter specified");
-		return nullptr;
-	}
-
-	fd->path_utf8 = fd->path.ToUTF8();
-
-	if (!fd->Initialize(param, error)) {
-		delete fd;
-		return nullptr;
-	}
-
-	if (!fifo_open(fd, error)) {
-		fd->Deinitialize();
-		delete fd;
-		return nullptr;
-	}
-
-	return &fd->base;
-}
-
-static void
-fifo_output_finish(struct audio_output *ao)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-
-	fd->Close();
-	fd->Deinitialize();
-	delete fd;
-}
-
-static bool
-fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		 gcc_unused Error &error)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-
-	fd->timer = new Timer(audio_format);
-
-	return true;
-}
-
-static void
-fifo_output_close(struct audio_output *ao)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-
-	delete fd->timer;
-}
-
-static void
-fifo_output_cancel(struct audio_output *ao)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-	char buf[FIFO_BUFFER_SIZE];
-	int bytes = 1;
-
-	fd->timer->Reset();
-
-	while (bytes > 0 && errno != EINTR)
-		bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
-
-	if (bytes < 0 && errno != EAGAIN) {
-		FormatErrno(fifo_output_domain,
-			    "Flush of FIFO \"%s\" failed",
-			    fd->path_utf8.c_str());
-	}
-}
-
-static unsigned
-fifo_output_delay(struct audio_output *ao)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-
-	return fd->timer->IsStarted()
-		? fd->timer->GetDelay()
-		: 0;
-}
-
-static size_t
-fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		 Error &error)
-{
-	FifoOutput *fd = (FifoOutput *)ao;
-	ssize_t bytes;
-
-	if (!fd->timer->IsStarted())
-		fd->timer->Start();
-	fd->timer->Add(size);
-
-	while (true) {
-		bytes = write(fd->output, chunk, size);
-		if (bytes > 0)
-			return (size_t)bytes;
-
-		if (bytes < 0) {
-			switch (errno) {
-			case EAGAIN:
-				/* The pipe is full, so empty it */
-				fifo_output_cancel(&fd->base);
-				continue;
-			case EINTR:
-				continue;
-			}
-
-			error.FormatErrno("Failed to write to FIFO %s",
-					  fd->path_utf8.c_str());
-			return 0;
-		}
-	}
-}
-
-const struct audio_output_plugin fifo_output_plugin = {
-	"fifo",
-	nullptr,
-	fifo_output_init,
-	fifo_output_finish,
-	nullptr,
-	nullptr,
-	fifo_output_open,
-	fifo_output_close,
-	fifo_output_delay,
-	nullptr,
-	fifo_output_play,
-	nullptr,
-	fifo_output_cancel,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/FifoOutputPlugin.hxx
deleted file mode 100644
index 394ec3ae9..000000000
--- a/src/output/FifoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_FIFO_OUTPUT_PLUGIN_HXX
-#define MPD_FIFO_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin fifo_output_plugin;
-
-#endif
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
deleted file mode 100644
index d761bdf57..000000000
--- a/src/output/HttpdClient.cxx
+++ /dev/null
@@ -1,485 +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.
- */
-
-#include "config.h"
-#include "HttpdClient.hxx"
-#include "HttpdInternal.hxx"
-#include "util/ASCII.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/SocketError.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-HttpdClient::~HttpdClient()
-{
-	if (state == RESPONSE) {
-		if (current_page != nullptr)
-			current_page->Unref();
-
-		ClearQueue();
-	}
-
-	if (metadata)
-		metadata->Unref();
-
-	if (IsDefined())
-		BufferedSocket::Close();
-}
-
-void
-HttpdClient::Close()
-{
-	httpd.RemoveClient(*this);
-}
-
-void
-HttpdClient::LockClose()
-{
-	const ScopeLock protect(httpd.mutex);
-	Close();
-}
-
-void
-HttpdClient::BeginResponse()
-{
-	assert(state != RESPONSE);
-
-	state = RESPONSE;
-	current_page = nullptr;
-
-	if (!head_method)
-		httpd.SendHeader(*this);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-bool
-HttpdClient::HandleLine(const char *line)
-{
-	assert(state != RESPONSE);
-
-	if (state == REQUEST) {
-		if (memcmp(line, "HEAD /", 6) == 0) {
-			line += 6;
-			head_method = true;
-		} else if (memcmp(line, "GET /", 5) == 0) {
-			line += 5;
-		} else {
-			/* only GET is supported */
-			LogWarning(httpd_output_domain,
-				   "malformed request line from client");
-			return false;
-		}
-
-		line = strchr(line, ' ');
-		if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
-			/* HTTP/0.9 without request headers */
-
-			if (head_method)
-				return false;
-
-			BeginResponse();
-			return true;
-		}
-
-		/* after the request line, request headers follow */
-		state = HEADERS;
-		return true;
-	} else {
-		if (*line == 0) {
-			/* empty line: request is finished */
-
-			BeginResponse();
-			return true;
-		}
-
-		if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
-		    StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
-			/* Send icy metadata */
-			metadata_requested = metadata_supported;
-			return true;
-		}
-
-		if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
-			/* Send as dlna */
-			dlna_streaming_requested = true;
-			/* metadata is not supported by dlna streaming, so disable it */
-			metadata_supported = false;
-			metadata_requested = false;
-			return true;
-		}
-
-		/* expect more request headers */
-		return true;
-	}
-}
-
-/**
- * Sends the status line and response headers to the client.
- */
-bool
-HttpdClient::SendResponse()
-{
-	char buffer[1024];
-	assert(state == RESPONSE);
-
-	if (dlna_streaming_requested) {
-		snprintf(buffer, sizeof(buffer),
-			 "HTTP/1.1 206 OK\r\n"
-			 "Content-Type: %s\r\n"
-			 "Content-Length: 10000\r\n"
-			 "Content-RangeX: 0-1000000/1000000\r\n"
-			 "transferMode.dlna.org: Streaming\r\n"
-			 "Accept-Ranges: bytes\r\n"
-			 "Connection: close\r\n"
-			 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
-			 "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
-			 "\r\n",
-			 httpd.content_type);
-
-	} else if (metadata_requested) {
-		char *metadata_header =
-			icy_server_metadata_header(httpd.name, httpd.genre,
-						   httpd.website,
-						   httpd.content_type,
-						   metaint);
-
-		g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
-		delete[] metadata_header;
-
-       } else { /* revert to a normal HTTP request */
-		snprintf(buffer, sizeof(buffer),
-			 "HTTP/1.1 200 OK\r\n"
-			 "Content-Type: %s\r\n"
-			 "Connection: close\r\n"
-			 "Pragma: no-cache\r\n"
-			 "Cache-Control: no-cache, no-store\r\n"
-			 "\r\n",
-			 httpd.content_type);
-	}
-
-	ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
-	if (gcc_unlikely(nbytes < 0)) {
-		const SocketErrorMessage msg;
-		FormatWarning(httpd_output_domain,
-			      "failed to write to client: %s",
-			      (const char *)msg);
-		Close();
-		return false;
-	}
-
-	return true;
-}
-
-HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
-			 bool _metadata_supported)
-	:BufferedSocket(_fd, _loop),
-	 httpd(_httpd),
-	 state(REQUEST),
-	 queue_size(0),
-	 head_method(false),
-	 dlna_streaming_requested(false),
-	 metadata_supported(_metadata_supported),
-	 metadata_requested(false), metadata_sent(true),
-	 metaint(8192), /*TODO: just a std value */
-	 metadata(nullptr),
-	 metadata_current_position(0), metadata_fill(0)
-{
-}
-
-void
-HttpdClient::ClearQueue()
-{
-	assert(state == RESPONSE);
-
-	while (!pages.empty()) {
-		Page *page = pages.front();
-		pages.pop();
-
-#ifndef NDEBUG
-		assert(queue_size >= page->size);
-		queue_size -= page->size;
-#endif
-
-		page->Unref();
-	}
-
-	assert(queue_size == 0);
-}
-
-void
-HttpdClient::CancelQueue()
-{
-	if (state != RESPONSE)
-		return;
-
-	ClearQueue();
-
-	if (current_page == nullptr)
-		CancelWrite();
-}
-
-ssize_t
-HttpdClient::TryWritePage(const Page &page, size_t position)
-{
-	assert(position < page.size);
-
-	return Write(page.data + position, page.size - position);
-}
-
-ssize_t
-HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
-{
-	return n >= 0
-		? Write(page.data + position, n)
-		: TryWritePage(page, position);
-}
-
-ssize_t
-HttpdClient::GetBytesTillMetaData() const
-{
-	if (metadata_requested &&
-	    current_page->size - current_position > metaint - metadata_fill)
-		return metaint - metadata_fill;
-
-	return -1;
-}
-
-inline bool
-HttpdClient::TryWrite()
-{
-	const ScopeLock protect(httpd.mutex);
-
-	assert(state == RESPONSE);
-
-	if (current_page == nullptr) {
-		if (pages.empty()) {
-			/* another thread has removed the event source
-			   while this thread was waiting for
-			   httpd.mutex */
-			CancelWrite();
-			return true;
-		}
-
-		current_page = pages.front();
-		pages.pop();
-		current_position = 0;
-
-		assert(queue_size >= current_page->size);
-		queue_size -= current_page->size;
-	}
-
-	const ssize_t bytes_to_write = GetBytesTillMetaData();
-	if (bytes_to_write == 0) {
-		if (!metadata_sent) {
-			ssize_t nbytes = TryWritePage(*metadata,
-						      metadata_current_position);
-			if (nbytes < 0) {
-				auto e = GetSocketError();
-				if (IsSocketErrorAgain(e))
-					return true;
-
-				if (!IsSocketErrorClosed(e)) {
-					SocketErrorMessage msg(e);
-					FormatWarning(httpd_output_domain,
-						      "failed to write to client: %s",
-						      (const char *)msg);
-				}
-
-				Close();
-				return false;
-			}
-
-			metadata_current_position += nbytes;
-
-			if (metadata->size - metadata_current_position == 0) {
-				metadata_fill = 0;
-				metadata_current_position = 0;
-				metadata_sent = true;
-			}
-		} else {
-			guchar empty_data = 0;
-
-			ssize_t nbytes = Write(&empty_data, 1);
-			if (nbytes < 0) {
-				auto e = GetSocketError();
-				if (IsSocketErrorAgain(e))
-					return true;
-
-				if (!IsSocketErrorClosed(e)) {
-					SocketErrorMessage msg(e);
-					FormatWarning(httpd_output_domain,
-						      "failed to write to client: %s",
-						      (const char *)msg);
-				}
-
-				Close();
-				return false;
-			}
-
-			metadata_fill = 0;
-			metadata_current_position = 0;
-		}
-	} else {
-		ssize_t nbytes =
-			TryWritePageN(*current_page, current_position,
-				      bytes_to_write);
-		if (nbytes < 0) {
-			auto e = GetSocketError();
-			if (IsSocketErrorAgain(e))
-				return true;
-
-			if (!IsSocketErrorClosed(e)) {
-				SocketErrorMessage msg(e);
-				FormatWarning(httpd_output_domain,
-					      "failed to write to client: %s",
-					      (const char *)msg);
-			}
-
-			Close();
-			return false;
-		}
-
-		current_position += nbytes;
-		assert(current_position <= current_page->size);
-
-		if (metadata_requested)
-			metadata_fill += nbytes;
-
-		if (current_position >= current_page->size) {
-			current_page->Unref();
-			current_page = nullptr;
-
-			if (pages.empty())
-				/* all pages are sent: remove the
-				   event source */
-				CancelWrite();
-		}
-	}
-
-	return true;
-}
-
-void
-HttpdClient::PushPage(Page *page)
-{
-	if (state != RESPONSE)
-		/* the client is still writing the HTTP request */
-		return;
-
-	if (queue_size > 256 * 1024) {
-		FormatDebug(httpd_output_domain,
-			    "client is too slow, flushing its queue");
-		ClearQueue();
-	}
-
-	page->Ref();
-	pages.push(page);
-	queue_size += page->size;
-
-	ScheduleWrite();
-}
-
-void
-HttpdClient::PushMetaData(Page *page)
-{
-	if (metadata) {
-		metadata->Unref();
-		metadata = nullptr;
-	}
-
-	g_return_if_fail (page);
-
-	page->Ref();
-	metadata = page;
-	metadata_sent = false;
-}
-
-bool
-HttpdClient::OnSocketReady(unsigned flags)
-{
-	if (!BufferedSocket::OnSocketReady(flags))
-		return false;
-
-	if (flags & WRITE)
-		if (!TryWrite())
-			return false;
-
-	return true;
-}
-
-BufferedSocket::InputResult
-HttpdClient::OnSocketInput(void *data, size_t length)
-{
-	if (state == RESPONSE) {
-		LogWarning(httpd_output_domain,
-			   "unexpected input from client");
-		LockClose();
-		return InputResult::CLOSED;
-	}
-
-	char *line = (char *)data;
-	char *newline = (char *)memchr(line, '\n', length);
-	if (newline == nullptr)
-		return InputResult::MORE;
-
-	ConsumeInput(newline + 1 - line);
-
-	if (newline > line && newline[-1] == '\r')
-		--newline;
-
-	/* terminate the string at the end of the line */
-	*newline = 0;
-
-	if (!HandleLine(line)) {
-		LockClose();
-		return InputResult::CLOSED;
-	}
-
-	if (state == RESPONSE) {
-		if (!SendResponse())
-			return InputResult::CLOSED;
-
-		if (head_method) {
-			LockClose();
-			return InputResult::CLOSED;
-		}
-	}
-
-	return InputResult::AGAIN;
-}
-
-void
-HttpdClient::OnSocketError(Error &&error)
-{
-	LogError(error);
-}
-
-void
-HttpdClient::OnSocketClosed()
-{
-	LockClose();
-}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
deleted file mode 100644
index f94f05769..000000000
--- a/src/output/HttpdClient.hxx
+++ /dev/null
@@ -1,193 +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_HTTPD_CLIENT_HXX
-#define MPD_OUTPUT_HTTPD_CLIENT_HXX
-
-#include "event/BufferedSocket.hxx"
-#include "Compiler.h"
-
-#include <queue>
-#include <list>
-
-#include <stddef.h>
-
-class HttpdOutput;
-class Page;
-
-class HttpdClient final : BufferedSocket {
-	/**
-	 * The httpd output object this client is connected to.
-	 */
-	HttpdOutput &httpd;
-
-	/**
-	 * The current state of the client.
-	 */
-	enum {
-		/** reading the request line */
-		REQUEST,
-
-		/** reading the request headers */
-		HEADERS,
-
-		/** sending the HTTP response */
-		RESPONSE,
-	} state;
-
-	/**
-	 * A queue of #Page objects to be sent to the client.
-	 */
-	std::queue<Page *, std::list<Page *>> pages;
-
-	/**
-	 * The sum of all page sizes in #pages.
-	 */
-	size_t queue_size;
-
-	/**
-	 * The #page which is currently being sent to the client.
-	 */
-	Page *current_page;
-
-	/**
-	 * The amount of bytes which were already sent from
-	 * #current_page.
-	 */
-	size_t current_position;
-
-	/**
-	 * Is this a HEAD request?
-	 */
-	bool head_method;
-
-	/**
-         * If DLNA streaming was an option.
-         */
-	bool dlna_streaming_requested;
-
-	/* ICY */
-
-	/**
-	 * Do we support sending Icy-Metadata to the client?  This is
-	 * disabled if the httpd audio output uses encoder tags.
-	 */
-	bool metadata_supported;
-
-	/**
-	 * If we should sent icy metadata.
-	 */
-	bool metadata_requested;
-
-	/**
-	 * If the current metadata was already sent to the client.
-	 */
-	bool metadata_sent;
-
-	/**
-	 * The amount of streaming data between each metadata block
-	 */
-	unsigned metaint;
-
-	/**
-	 * The metadata as #Page which is currently being sent to the client.
-	 */
-	Page *metadata;
-
-	/*
-	 * The amount of bytes which were already sent from the metadata.
-	 */
-	size_t metadata_current_position;
-
-	/**
-	 * The amount of streaming data sent to the client
-	 * since the last icy information was sent.
-	 */
-	unsigned metadata_fill;
-
-public:
-	/**
-	 * @param httpd the HTTP output device
-	 * @param fd the socket file descriptor
-	 */
-	HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
-		    bool _metadata_supported);
-
-	/**
-	 * Note: this does not remove the client from the
-	 * #HttpdOutput object.
-	 */
-	~HttpdClient();
-
-	/**
-	 * Frees the client and removes it from the server's client list.
-	 */
-	void Close();
-
-	void LockClose();
-
-	/**
-	 * Clears the page queue.
-	 */
-	void CancelQueue();
-
-	/**
-	 * Handle a line of the HTTP request.
-	 */
-	bool HandleLine(const char *line);
-
-	/**
-	 * Switch the client to the "RESPONSE" state.
-	 */
-	void BeginResponse();
-
-	/**
-	 * Sends the status line and response headers to the client.
-	 */
-	bool SendResponse();
-
-	gcc_pure
-	ssize_t GetBytesTillMetaData() const;
-
-	ssize_t TryWritePage(const Page &page, size_t position);
-	ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n);
-
-	bool TryWrite();
-
-	/**
-	 * Appends a page to the client's queue.
-	 */
-	void PushPage(Page *page);
-
-	/**
-	 * Sends the passed metadata.
-	 */
-	void PushMetaData(Page *page);
-
-private:
-	void ClearQueue();
-
-protected:
-	virtual bool OnSocketReady(unsigned flags) override;
-	virtual InputResult OnSocketInput(void *data, size_t length) override;
-	virtual void OnSocketError(Error &&error) override;
-	virtual void OnSocketClosed() override;
-};
-
-#endif
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
deleted file mode 100644
index 2ef0831ba..000000000
--- a/src/output/HttpdInternal.hxx
+++ /dev/null
@@ -1,279 +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.
- */
-
-/** \file
- *
- * Internal declarations for the "httpd" audio output plugin.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
-#define MPD_OUTPUT_HTTPD_INTERNAL_H
-
-#include "OutputInternal.hxx"
-#include "Timer.hxx"
-#include "thread/Mutex.hxx"
-#include "event/ServerSocket.hxx"
-#include "event/DeferredMonitor.hxx"
-#include "util/Cast.hxx"
-
-#ifdef _LIBCPP_VERSION
-/* can't use incomplete template arguments with libc++ */
-#include "HttpdClient.hxx"
-#endif
-
-#include <forward_list>
-#include <queue>
-#include <list>
-
-struct config_param;
-class Error;
-class EventLoop;
-class ServerSocket;
-class HttpdClient;
-class Page;
-struct Encoder;
-struct Tag;
-
-class HttpdOutput final : ServerSocket, DeferredMonitor {
-	struct audio_output base;
-
-	/**
-	 * True if the audio output is open and accepts client
-	 * connections.
-	 */
-	bool open;
-
-	/**
-	 * The configured encoder plugin.
-	 */
-	Encoder *encoder;
-
-	/**
-	 * Number of bytes which were fed into the encoder, without
-	 * ever receiving new output.  This is used to estimate
-	 * whether MPD should manually flush the encoder, to avoid
-	 * buffer underruns in the client.
-	 */
-	size_t unflushed_input;
-
-public:
-	/**
-	 * The MIME type produced by the #encoder.
-	 */
-	const char *content_type;
-
-	/**
-	 * This mutex protects the listener socket and the client
-	 * list.
-	 */
-	mutable Mutex mutex;
-
-	/**
-	 * This condition gets signalled when an item is removed from
-	 * #pages.
-	 */
-	Cond cond;
-
-private:
-	/**
-	 * A #Timer object to synchronize this output with the
-	 * wallclock.
-	 */
-	Timer *timer;
-
-	/**
-	 * The header page, which is sent to every client on connect.
-	 */
-	Page *header;
-
-	/**
-	 * The metadata, which is sent to every client.
-	 */
-	Page *metadata;
-
-	/**
-	 * The page queue, i.e. pages from the encoder to be
-	 * broadcasted to all clients.  This container is necessary to
-	 * pass pages from the OutputThread to the IOThread.  It is
-	 * protected by #mutex, and removing signals #cond.
-	 */
-	std::queue<Page *, std::list<Page *>> pages;
-
- public:
-	/**
-	 * The configured name.
-	 */
-	char const *name;
-	/**
-	 * The configured genre.
-	 */
-	char const *genre;
-	/**
-	 * The configured website address.
-	 */
-	char const *website;
-
-private:
-	/**
-	 * A linked list containing all clients which are currently
-	 * connected.
-	 */
-	std::forward_list<HttpdClient> clients;
-
-	/**
-	 * A temporary buffer for the httpd_output_read_page()
-	 * function.
-	 */
-	char buffer[32768];
-
-	/**
-	 * The maximum and current number of clients connected
-	 * at the same time.
-	 */
-	unsigned clients_max, clients_cnt;
-
-public:
-	HttpdOutput(EventLoop &_loop);
-	~HttpdOutput();
-
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Winvalid-offsetof"
-#endif
-
-	static constexpr HttpdOutput *Cast(audio_output *ao) {
-		return ContainerCast(ao, HttpdOutput, base);
-	}
-
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-
-	using DeferredMonitor::GetEventLoop;
-
-	bool Init(const config_param &param, Error &error);
-
-	void Finish() {
-		ao_base_finish(&base);
-	}
-
-	bool Configure(const config_param &param, Error &error);
-
-	audio_output *InitAndConfigure(const config_param &param,
-				       Error &error) {
-		if (!Init(param, error))
-			return nullptr;
-
-		if (!Configure(param, error)) {
-			Finish();
-			return nullptr;
-		}
-
-		return &base;
-	}
-
-	bool Bind(Error &error);
-	void Unbind();
-
-	/**
-	 * Caller must lock the mutex.
-	 */
-	bool OpenEncoder(AudioFormat &audio_format, Error &error);
-
-	/**
-	 * Caller must lock the mutex.
-	 */
-	bool Open(AudioFormat &audio_format, Error &error);
-
-	/**
-	 * Caller must lock the mutex.
-	 */
-	void Close();
-
-	/**
-	 * Check whether there is at least one client.
-	 *
-	 * Caller must lock the mutex.
-	 */
-	gcc_pure
-	bool HasClients() const {
-		return !clients.empty();
-	}
-
-	/**
-	 * Check whether there is at least one client.
-	 */
-	gcc_pure
-	bool LockHasClients() const {
-		const ScopeLock protect(mutex);
-		return HasClients();
-	}
-
-	void AddClient(int fd);
-
-	/**
-	 * Removes a client from the httpd_output.clients linked list.
-	 */
-	void RemoveClient(HttpdClient &client);
-
-	/**
-	 * Sends the encoder header to the client.  This is called
-	 * right after the response headers have been sent.
-	 */
-	void SendHeader(HttpdClient &client) const;
-
-	gcc_pure
-	unsigned Delay() const;
-
-	/**
-	 * Reads data from the encoder (as much as available) and
-	 * returns it as a new #page object.
-	 */
-	Page *ReadPage();
-
-	/**
-	 * Broadcasts a page struct to all clients.
-	 *
-	 * Mutext must not be locked.
-	 */
-	void BroadcastPage(Page *page);
-
-	/**
-	 * Broadcasts data from the encoder to all clients.
-	 */
-	void BroadcastFromEncoder();
-
-	bool EncodeAndPlay(const void *chunk, size_t size, Error &error);
-
-	void SendTag(const Tag *tag);
-
-	size_t Play(const void *chunk, size_t size, Error &error);
-
-	void CancelAllClients();
-
-private:
-	virtual void RunDeferred() override;
-
-	virtual void OnAccept(int fd, const sockaddr &address,
-			      size_t address_length, int uid) override;
-};
-
-extern const class Domain httpd_output_domain;
-
-#endif
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
deleted file mode 100644
index 322c9a61e..000000000
--- a/src/output/HttpdOutputPlugin.cxx
+++ /dev/null
@@ -1,601 +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.
- */
-
-#include "config.h"
-#include "HttpdOutputPlugin.hxx"
-#include "HttpdInternal.hxx"
-#include "HttpdClient.hxx"
-#include "OutputAPI.hxx"
-#include "encoder/EncoderPlugin.hxx"
-#include "encoder/EncoderList.hxx"
-#include "system/Resolver.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/fd_util.h"
-#include "IOThread.hxx"
-#include "event/Call.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-
-#ifdef HAVE_LIBWRAP
-#include <sys/socket.h> /* needed for AF_UNIX */
-#include <tcpd.h>
-#endif
-
-const Domain httpd_output_domain("httpd_output");
-
-inline
-HttpdOutput::HttpdOutput(EventLoop &_loop)
-	:ServerSocket(_loop), DeferredMonitor(_loop),
-	 encoder(nullptr), unflushed_input(0),
-	 metadata(nullptr)
-{
-}
-
-HttpdOutput::~HttpdOutput()
-{
-	if (metadata != nullptr)
-		metadata->Unref();
-
-	if (encoder != nullptr)
-		encoder_finish(encoder);
-
-}
-
-inline bool
-HttpdOutput::Bind(Error &error)
-{
-	open = false;
-
-	bool result = false;
-	BlockingCall(GetEventLoop(), [this, &error, &result](){
-			result = ServerSocket::Open(error);
-		});
-	return result;
-}
-
-inline void
-HttpdOutput::Unbind()
-{
-	assert(!open);
-
-	BlockingCall(GetEventLoop(), [this](){
-			ServerSocket::Close();
-		});
-}
-
-inline bool
-HttpdOutput::Configure(const config_param &param, Error &error)
-{
-	/* read configuration */
-	name = param.GetBlockValue("name", "Set name in config");
-	genre = param.GetBlockValue("genre", "Set genre in config");
-	website = param.GetBlockValue("website", "Set website in config");
-
-	unsigned port = param.GetBlockValue("port", 8000u);
-
-	const char *encoder_name =
-		param.GetBlockValue("encoder", "vorbis");
-	const auto encoder_plugin = encoder_plugin_get(encoder_name);
-	if (encoder_plugin == nullptr) {
-		error.Format(httpd_output_domain,
-			     "No such encoder: %s", encoder_name);
-		return false;
-	}
-
-	clients_max = param.GetBlockValue("max_clients", 0u);
-
-	/* set up bind_to_address */
-
-	const char *bind_to_address = param.GetBlockValue("bind_to_address");
-	bool success = bind_to_address != nullptr &&
-		strcmp(bind_to_address, "any") != 0
-		? AddHost(bind_to_address, port, error)
-		: AddPort(port, error);
-	if (!success)
-		return false;
-
-	/* initialize encoder */
-
-	encoder = encoder_init(*encoder_plugin, param, error);
-	if (encoder == nullptr)
-		return false;
-
-	/* determine content type */
-	content_type = encoder_get_mime_type(encoder);
-	if (content_type == nullptr)
-		content_type = "application/octet-stream";
-
-	return true;
-}
-
-inline bool
-HttpdOutput::Init(const config_param &param, Error &error)
-{
-	return ao_base_init(&base, &httpd_output_plugin, param, error);
-}
-
-static struct audio_output *
-httpd_output_init(const config_param &param, Error &error)
-{
-	HttpdOutput *httpd = new HttpdOutput(io_thread_get());
-
-	audio_output *result = httpd->InitAndConfigure(param, error);
-	if (result == nullptr)
-		delete httpd;
-
-	return result;
-}
-
-static void
-httpd_output_finish(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	httpd->Finish();
-	delete httpd;
-}
-
-/**
- * Creates a new #HttpdClient object and adds it into the
- * HttpdOutput.clients linked list.
- */
-inline void
-HttpdOutput::AddClient(int fd)
-{
-	clients.emplace_front(*this, fd, GetEventLoop(),
-			      encoder->plugin.tag == nullptr);
-	++clients_cnt;
-
-	/* pass metadata to client */
-	if (metadata != nullptr)
-		clients.front().PushMetaData(metadata);
-}
-
-void
-HttpdOutput::RunDeferred()
-{
-	/* this method runs in the IOThread; it broadcasts pages from
-	   our own queue to all clients */
-
-	const ScopeLock protect(mutex);
-
-	while (!pages.empty()) {
-		Page *page = pages.front();
-		pages.pop();
-
-		for (auto &client : clients)
-			client.PushPage(page);
-
-		page->Unref();
-	}
-
-	/* wake up the client that may be waiting for the queue to be
-	   flushed */
-	cond.broadcast();
-}
-
-void
-HttpdOutput::OnAccept(int fd, const sockaddr &address,
-		      size_t address_length, gcc_unused int uid)
-{
-	/* the listener socket has become readable - a client has
-	   connected */
-
-#ifdef HAVE_LIBWRAP
-	if (address.sa_family != AF_UNIX) {
-		const auto hostaddr = sockaddr_to_string(&address,
-							 address_length);
-		// TODO: shall we obtain the program name from argv[0]?
-		const char *progname = "mpd";
-
-		struct request_info req;
-		request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
-
-		fromhost(&req);
-
-		if (!hosts_access(&req)) {
-			/* tcp wrappers says no */
-			FormatWarning(httpd_output_domain,
-				      "libwrap refused connection (libwrap=%s) from %s",
-				      progname, hostaddr.c_str());
-			close_socket(fd);
-			return;
-		}
-	}
-#else
-	(void)address;
-	(void)address_length;
-#endif	/* HAVE_WRAP */
-
-	const ScopeLock protect(mutex);
-
-	if (fd >= 0) {
-		/* can we allow additional client */
-		if (open && (clients_max == 0 ||  clients_cnt < clients_max))
-			AddClient(fd);
-		else
-			close_socket(fd);
-	} else if (fd < 0 && errno != EINTR) {
-		LogErrno(httpd_output_domain, "accept() failed");
-	}
-}
-
-Page *
-HttpdOutput::ReadPage()
-{
-	if (unflushed_input >= 65536) {
-		/* we have fed a lot of input into the encoder, but it
-		   didn't give anything back yet - flush now to avoid
-		   buffer underruns */
-		encoder_flush(encoder, IgnoreError());
-		unflushed_input = 0;
-	}
-
-	size_t size = 0;
-	do {
-		size_t nbytes = encoder_read(encoder,
-					     buffer + size,
-					     sizeof(buffer) - size);
-		if (nbytes == 0)
-			break;
-
-		unflushed_input = 0;
-
-		size += nbytes;
-	} while (size < sizeof(buffer));
-
-	if (size == 0)
-		return nullptr;
-
-	return Page::Copy(buffer, size);
-}
-
-static bool
-httpd_output_enable(struct audio_output *ao, Error &error)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	return httpd->Bind(error);
-}
-
-static void
-httpd_output_disable(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	httpd->Unbind();
-}
-
-inline bool
-HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
-{
-	if (!encoder_open(encoder, audio_format, error))
-		return false;
-
-	/* we have to remember the encoder header, i.e. the first
-	   bytes of encoder output after opening it, because it has to
-	   be sent to every new client */
-	header = ReadPage();
-
-	unflushed_input = 0;
-
-	return true;
-}
-
-inline bool
-HttpdOutput::Open(AudioFormat &audio_format, Error &error)
-{
-	assert(!open);
-	assert(clients.empty());
-
-	/* open the encoder */
-
-	if (!OpenEncoder(audio_format, error))
-		return false;
-
-	/* initialize other attributes */
-
-	clients_cnt = 0;
-	timer = new Timer(audio_format);
-
-	open = true;
-
-	return true;
-}
-
-static bool
-httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		  Error &error)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	const ScopeLock protect(httpd->mutex);
-	return httpd->Open(audio_format, error);
-}
-
-inline void
-HttpdOutput::Close()
-{
-	assert(open);
-
-	open = false;
-
-	delete timer;
-
-	BlockingCall(GetEventLoop(), [this](){
-			clients.clear();
-		});
-
-	if (header != nullptr)
-		header->Unref();
-
-	encoder_close(encoder);
-}
-
-static void
-httpd_output_close(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	const ScopeLock protect(httpd->mutex);
-	httpd->Close();
-}
-
-void
-HttpdOutput::RemoveClient(HttpdClient &client)
-{
-	assert(clients_cnt > 0);
-
-	for (auto prev = clients.before_begin(), i = std::next(prev);;
-	     prev = i, i = std::next(prev)) {
-		assert(i != clients.end());
-		if (&*i == &client) {
-			clients.erase_after(prev);
-			clients_cnt--;
-			break;
-		}
-	}
-}
-
-void
-HttpdOutput::SendHeader(HttpdClient &client) const
-{
-	if (header != nullptr)
-		client.PushPage(header);
-}
-
-inline unsigned
-HttpdOutput::Delay() const
-{
-	if (!LockHasClients() && base.pause) {
-		/* if there's no client and this output is paused,
-		   then httpd_output_pause() will not do anything, it
-		   will not fill the buffer and it will not update the
-		   timer; therefore, we reset the timer here */
-		timer->Reset();
-
-		/* some arbitrary delay that is long enough to avoid
-		   consuming too much CPU, and short enough to notice
-		   new clients quickly enough */
-		return 1000;
-	}
-
-	return timer->IsStarted()
-		? timer->GetDelay()
-		: 0;
-}
-
-static unsigned
-httpd_output_delay(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	return httpd->Delay();
-}
-
-void
-HttpdOutput::BroadcastPage(Page *page)
-{
-	assert(page != nullptr);
-
-	mutex.lock();
-	pages.push(page);
-	page->Ref();
-	mutex.unlock();
-
-	DeferredMonitor::Schedule();
-}
-
-void
-HttpdOutput::BroadcastFromEncoder()
-{
-	/* synchronize with the IOThread */
-	mutex.lock();
-	while (!pages.empty())
-		cond.wait(mutex);
-
-	Page *page;
-	while ((page = ReadPage()) != nullptr)
-		pages.push(page);
-
-	mutex.unlock();
-
-	DeferredMonitor::Schedule();
-}
-
-inline bool
-HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
-{
-	if (!encoder_write(encoder, chunk, size, error))
-		return false;
-
-	unflushed_input += size;
-
-	BroadcastFromEncoder();
-	return true;
-}
-
-inline size_t
-HttpdOutput::Play(const void *chunk, size_t size, Error &error)
-{
-	if (LockHasClients()) {
-		if (!EncodeAndPlay(chunk, size, error))
-			return 0;
-	}
-
-	if (!timer->IsStarted())
-		timer->Start();
-	timer->Add(size);
-
-	return size;
-}
-
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		  Error &error)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	return httpd->Play(chunk, size, error);
-}
-
-static bool
-httpd_output_pause(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	if (httpd->LockHasClients()) {
-		static const char silence[1020] = { 0 };
-		return httpd_output_play(ao, silence, sizeof(silence),
-					 IgnoreError()) > 0;
-	} else {
-		return true;
-	}
-}
-
-inline void
-HttpdOutput::SendTag(const Tag *tag)
-{
-	assert(tag != nullptr);
-
-	if (encoder->plugin.tag != nullptr) {
-		/* embed encoder tags */
-
-		/* flush the current stream, and end it */
-
-		encoder_pre_tag(encoder, IgnoreError());
-		BroadcastFromEncoder();
-
-		/* send the tag to the encoder - which starts a new
-		   stream now */
-
-		encoder_tag(encoder, tag, IgnoreError());
-
-		/* the first page generated by the encoder will now be
-		   used as the new "header" page, which is sent to all
-		   new clients */
-
-		Page *page = ReadPage();
-		if (page != nullptr) {
-			if (header != nullptr)
-				header->Unref();
-			header = page;
-			BroadcastPage(page);
-		}
-	} else {
-		/* use Icy-Metadata */
-
-		if (metadata != nullptr)
-			metadata->Unref();
-
-		static constexpr TagType types[] = {
-			TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
-			TAG_NUM_OF_ITEM_TYPES
-		};
-
-		metadata = icy_server_metadata_page(*tag, &types[0]);
-		if (metadata != nullptr) {
-			const ScopeLock protect(mutex);
-			for (auto &client : clients)
-				client.PushMetaData(metadata);
-		}
-	}
-}
-
-static void
-httpd_output_tag(struct audio_output *ao, const Tag *tag)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	httpd->SendTag(tag);
-}
-
-inline void
-HttpdOutput::CancelAllClients()
-{
-	const ScopeLock protect(mutex);
-
-	while (!pages.empty()) {
-		Page *page = pages.front();
-		pages.pop();
-		page->Unref();
-	}
-
-	for (auto &client : clients)
-		client.CancelQueue();
-
-	cond.broadcast();
-}
-
-static void
-httpd_output_cancel(struct audio_output *ao)
-{
-	HttpdOutput *httpd = HttpdOutput::Cast(ao);
-
-	BlockingCall(io_thread_get(), [httpd](){
-			httpd->CancelAllClients();
-		});
-}
-
-const struct audio_output_plugin httpd_output_plugin = {
-	"httpd",
-	nullptr,
-	httpd_output_init,
-	httpd_output_finish,
-	httpd_output_enable,
-	httpd_output_disable,
-	httpd_output_open,
-	httpd_output_close,
-	httpd_output_delay,
-	httpd_output_tag,
-	httpd_output_play,
-	nullptr,
-	httpd_output_cancel,
-	httpd_output_pause,
-	nullptr,
-};
diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx
deleted file mode 100644
index 78218e5f0..000000000
--- a/src/output/HttpdOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_HTTPD_OUTPUT_PLUGIN_HXX
-#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin httpd_output_plugin;
-
-#endif
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
deleted file mode 100644
index c65ae5225..000000000
--- a/src/output/JackOutputPlugin.cxx
+++ /dev/null
@@ -1,765 +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.
- */
-
-#include "config.h"
-#include "JackOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <string.h>
-
-enum {
-	MAX_PORTS = 16,
-};
-
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
-
-struct JackOutput {
-	struct audio_output base;
-
-	/**
-	 * libjack options passed to jack_client_open().
-	 */
-	jack_options_t options;
-
-	const char *name;
-
-	const char *server_name;
-
-	/* configuration */
-
-	char *source_ports[MAX_PORTS];
-	unsigned num_source_ports;
-
-	char *destination_ports[MAX_PORTS];
-	unsigned num_destination_ports;
-
-	size_t ringbuffer_size;
-
-	/* the current audio format */
-	AudioFormat audio_format;
-
-	/* jack library stuff */
-	jack_port_t *ports[MAX_PORTS];
-	jack_client_t *client;
-	jack_ringbuffer_t *ringbuffer[MAX_PORTS];
-
-	bool shutdown;
-
-	/**
-	 * While this flag is set, the "process" callback generates
-	 * silence.
-	 */
-	bool pause;
-
-	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, &jack_output_plugin, param,
-				    error_r);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-};
-
-static constexpr Domain jack_output_domain("jack_output");
-
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const JackOutput *jd)
-{
-	size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
-
-	for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
-		size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
-		if (current < min)
-			min = current;
-	}
-
-	assert(min % jack_sample_size == 0);
-
-	return min / jack_sample_size;
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
-	JackOutput *jd = (JackOutput *) arg;
-
-	if (nframes <= 0)
-		return 0;
-
-	if (jd->pause) {
-		/* empty the ring buffers */
-
-		const jack_nframes_t available = mpd_jack_available(jd);
-		for (unsigned i = 0; i < jd->audio_format.channels; ++i)
-			jack_ringbuffer_read_advance(jd->ringbuffer[i],
-						     available * jack_sample_size);
-
-		/* generate silence while MPD is paused */
-
-		for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
-			jack_default_audio_sample_t *out =
-				(jack_default_audio_sample_t *)
-				jack_port_get_buffer(jd->ports[i], nframes);
-
-			for (jack_nframes_t f = 0; f < nframes; ++f)
-				out[f] = 0.0;
-		}
-
-		return 0;
-	}
-
-	jack_nframes_t available = mpd_jack_available(jd);
-	if (available > nframes)
-		available = nframes;
-
-	for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
-		jack_default_audio_sample_t *out =
-			(jack_default_audio_sample_t *)
-			jack_port_get_buffer(jd->ports[i], nframes);
-		if (out == nullptr)
-			/* workaround for libjack1 bug: if the server
-			   connection fails, the process callback is
-			   invoked anyway, but unable to get a
-			   buffer */
-			continue;
-
-		jack_ringbuffer_read(jd->ringbuffer[i],
-				     (char *)out, available * jack_sample_size);
-
-		for (jack_nframes_t f = available; f < nframes; ++f)
-			/* ringbuffer underrun, fill with silence */
-			out[f] = 0.0;
-	}
-
-	/* generate silence for the unused source ports */
-
-	for (unsigned i = jd->audio_format.channels;
-	     i < jd->num_source_ports; ++i) {
-		jack_default_audio_sample_t *out =
-			(jack_default_audio_sample_t *)
-			jack_port_get_buffer(jd->ports[i], nframes);
-		if (out == nullptr)
-			/* workaround for libjack1 bug: if the server
-			   connection fails, the process callback is
-			   invoked anyway, but unable to get a
-			   buffer */
-			continue;
-
-		for (jack_nframes_t f = 0; f < nframes; ++f)
-			out[f] = 0.0;
-	}
-
-	return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
-	JackOutput *jd = (JackOutput *) arg;
-	jd->shutdown = true;
-}
-
-static void
-set_audioformat(JackOutput *jd, AudioFormat &audio_format)
-{
-	audio_format.sample_rate = jack_get_sample_rate(jd->client);
-
-	if (jd->num_source_ports == 1)
-		audio_format.channels = 1;
-	else if (audio_format.channels > jd->num_source_ports)
-		audio_format.channels = 2;
-
-	if (audio_format.format != SampleFormat::S16 &&
-	    audio_format.format != SampleFormat::S24_P32)
-		audio_format.format = SampleFormat::S24_P32;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
-	LogError(jack_output_domain, msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
-	LogDefault(jack_output_domain, msg);
-}
-#endif
-
-/**
- * Disconnect the JACK client.
- */
-static void
-mpd_jack_disconnect(JackOutput *jd)
-{
-	assert(jd != nullptr);
-	assert(jd->client != nullptr);
-
-	jack_deactivate(jd->client);
-	jack_client_close(jd->client);
-	jd->client = nullptr;
-}
-
-/**
- * Connect the JACK client and performs some basic setup
- * (e.g. register callbacks).
- */
-static bool
-mpd_jack_connect(JackOutput *jd, Error &error)
-{
-	jack_status_t status;
-
-	assert(jd != nullptr);
-
-	jd->shutdown = false;
-
-	jd->client = jack_client_open(jd->name, jd->options, &status,
-				      jd->server_name);
-	if (jd->client == nullptr) {
-		error.Format(jack_output_domain, status,
-			     "Failed to connect to JACK server, status=%d",
-			     status);
-		return false;
-	}
-
-	jack_set_process_callback(jd->client, mpd_jack_process, jd);
-	jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
-	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
-		jd->ports[i] = jack_port_register(jd->client,
-						  jd->source_ports[i],
-						  JACK_DEFAULT_AUDIO_TYPE,
-						  JackPortIsOutput, 0);
-		if (jd->ports[i] == nullptr) {
-			error.Format(jack_output_domain,
-				     "Cannot register output port \"%s\"",
-				     jd->source_ports[i]);
-			mpd_jack_disconnect(jd);
-			return false;
-		}
-	}
-
-	return true;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
-	return true;
-}
-
-static unsigned
-parse_port_list(const char *source, char **dest, Error &error)
-{
-	char **list = g_strsplit(source, ",", 0);
-	unsigned n = 0;
-
-	for (n = 0; list[n] != nullptr; ++n) {
-		if (n >= MAX_PORTS) {
-			error.Set(config_domain,
-				  "too many port names");
-			return 0;
-		}
-
-		dest[n] = list[n];
-	}
-
-	g_free(list);
-
-	if (n == 0) {
-		error.Format(config_domain,
-			     "at least one port name expected");
-		return 0;
-	}
-
-	return n;
-}
-
-static struct audio_output *
-mpd_jack_init(const config_param &param, Error &error)
-{
-	JackOutput *jd = new JackOutput();
-
-	if (!jd->Initialize(param, error)) {
-		delete jd;
-		return nullptr;
-	}
-
-	const char *value;
-
-	jd->options = JackNullOption;
-
-	jd->name = param.GetBlockValue("client_name", nullptr);
-	if (jd->name != nullptr)
-		jd->options = jack_options_t(jd->options | JackUseExactName);
-	else
-		/* if there's a no configured client name, we don't
-		   care about the JackUseExactName option */
-		jd->name = "Music Player Daemon";
-
-	jd->server_name = param.GetBlockValue("server_name", nullptr);
-	if (jd->server_name != nullptr)
-		jd->options = jack_options_t(jd->options | JackServerName);
-
-	if (!param.GetBlockValue("autostart", false))
-		jd->options = jack_options_t(jd->options | JackNoStartServer);
-
-	/* configure the source ports */
-
-	value = param.GetBlockValue("source_ports", "left,right");
-	jd->num_source_ports = parse_port_list(value,
-					       jd->source_ports, error);
-	if (jd->num_source_ports == 0)
-		return nullptr;
-
-	/* configure the destination ports */
-
-	value = param.GetBlockValue("destination_ports", nullptr);
-	if (value == nullptr) {
-		/* compatibility with MPD < 0.16 */
-		value = param.GetBlockValue("ports", nullptr);
-		if (value != nullptr)
-			FormatWarning(jack_output_domain,
-				      "deprecated option 'ports' in line %d",
-				      param.line);
-	}
-
-	if (value != nullptr) {
-		jd->num_destination_ports =
-			parse_port_list(value,
-					jd->destination_ports, error);
-		if (jd->num_destination_ports == 0)
-			return nullptr;
-	} else {
-		jd->num_destination_ports = 0;
-	}
-
-	if (jd->num_destination_ports > 0 &&
-	    jd->num_destination_ports != jd->num_source_ports)
-		FormatWarning(jack_output_domain,
-			      "number of source ports (%u) mismatches the "
-			      "number of destination ports (%u) in line %d",
-			      jd->num_source_ports, jd->num_destination_ports,
-			      param.line);
-
-	jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
-
-	jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-	jack_set_info_function(mpd_jack_info);
-#endif
-
-	return &jd->base;
-}
-
-static void
-mpd_jack_finish(struct audio_output *ao)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	for (unsigned i = 0; i < jd->num_source_ports; ++i)
-		g_free(jd->source_ports[i]);
-
-	for (unsigned i = 0; i < jd->num_destination_ports; ++i)
-		g_free(jd->destination_ports[i]);
-
-	jd->Deinitialize();
-	delete jd;
-}
-
-static bool
-mpd_jack_enable(struct audio_output *ao, Error &error)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	for (unsigned i = 0; i < jd->num_source_ports; ++i)
-		jd->ringbuffer[i] = nullptr;
-
-	return mpd_jack_connect(jd, error);
-}
-
-static void
-mpd_jack_disable(struct audio_output *ao)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	if (jd->client != nullptr)
-		mpd_jack_disconnect(jd);
-
-	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
-		if (jd->ringbuffer[i] != nullptr) {
-			jack_ringbuffer_free(jd->ringbuffer[i]);
-			jd->ringbuffer[i] = nullptr;
-		}
-	}
-}
-
-/**
- * Stops the playback on the JACK connection.
- */
-static void
-mpd_jack_stop(JackOutput *jd)
-{
-	assert(jd != nullptr);
-
-	if (jd->client == nullptr)
-		return;
-
-	if (jd->shutdown)
-		/* the connection has failed; close it */
-		mpd_jack_disconnect(jd);
-	else
-		/* the connection is alive: just stop playback */
-		jack_deactivate(jd->client);
-}
-
-static bool
-mpd_jack_start(JackOutput *jd, Error &error)
-{
-	const char *destination_ports[MAX_PORTS], **jports;
-	const char *duplicate_port = nullptr;
-	unsigned num_destination_ports;
-
-	assert(jd->client != nullptr);
-	assert(jd->audio_format.channels <= jd->num_source_ports);
-
-	/* allocate the ring buffers on the first open(); these
-	   persist until MPD exits.  It's too unsafe to delete them
-	   because we can never know when mpd_jack_process() gets
-	   called */
-	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
-		if (jd->ringbuffer[i] == nullptr)
-			jd->ringbuffer[i] =
-				jack_ringbuffer_create(jd->ringbuffer_size);
-
-		/* clear the ring buffer to be sure that data from
-		   previous playbacks are gone */
-		jack_ringbuffer_reset(jd->ringbuffer[i]);
-	}
-
-	if ( jack_activate(jd->client) ) {
-		error.Set(jack_output_domain, "cannot activate client");
-		mpd_jack_stop(jd);
-		return false;
-	}
-
-	if (jd->num_destination_ports == 0) {
-		/* no output ports were configured - ask libjack for
-		   defaults */
-		jports = jack_get_ports(jd->client, nullptr, nullptr,
-					JackPortIsPhysical | JackPortIsInput);
-		if (jports == nullptr) {
-			error.Set(jack_output_domain, "no ports found");
-			mpd_jack_stop(jd);
-			return false;
-		}
-
-		assert(*jports != nullptr);
-
-		for (num_destination_ports = 0;
-		     num_destination_ports < MAX_PORTS &&
-			     jports[num_destination_ports] != nullptr;
-		     ++num_destination_ports) {
-			FormatDebug(jack_output_domain,
-				    "destination_port[%u] = '%s'\n",
-				    num_destination_ports,
-				    jports[num_destination_ports]);
-			destination_ports[num_destination_ports] =
-				jports[num_destination_ports];
-		}
-	} else {
-		/* use the configured output ports */
-
-		num_destination_ports = jd->num_destination_ports;
-		memcpy(destination_ports, jd->destination_ports,
-		       num_destination_ports * sizeof(*destination_ports));
-
-		jports = nullptr;
-	}
-
-	assert(num_destination_ports > 0);
-
-	if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
-		/* mix stereo signal on one speaker */
-
-		while (num_destination_ports < jd->audio_format.channels)
-			destination_ports[num_destination_ports++] =
-				destination_ports[0];
-	} else if (num_destination_ports > jd->audio_format.channels) {
-		if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
-			/* mono input file: connect the one source
-			   channel to the both destination channels */
-			duplicate_port = destination_ports[1];
-			num_destination_ports = 1;
-		} else
-			/* connect only as many ports as we need */
-			num_destination_ports = jd->audio_format.channels;
-	}
-
-	assert(num_destination_ports <= jd->num_source_ports);
-
-	for (unsigned i = 0; i < num_destination_ports; ++i) {
-		int ret;
-
-		ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
-				   destination_ports[i]);
-		if (ret != 0) {
-			error.Format(jack_output_domain,
-				     "Not a valid JACK port: %s",
-				     destination_ports[i]);
-
-			if (jports != nullptr)
-				free(jports);
-
-			mpd_jack_stop(jd);
-			return false;
-		}
-	}
-
-	if (duplicate_port != nullptr) {
-		/* mono input file: connect the one source channel to
-		   the both destination channels */
-		int ret;
-
-		ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
-				   duplicate_port);
-		if (ret != 0) {
-			error.Format(jack_output_domain,
-				     "Not a valid JACK port: %s",
-				     duplicate_port);
-
-			if (jports != nullptr)
-				free(jports);
-
-			mpd_jack_stop(jd);
-			return false;
-		}
-	}
-
-	if (jports != nullptr)
-		free(jports);
-
-	return true;
-}
-
-static bool
-mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
-	      Error &error)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	assert(jd != nullptr);
-
-	jd->pause = false;
-
-	if (jd->client != nullptr && jd->shutdown)
-		mpd_jack_disconnect(jd);
-
-	if (jd->client == nullptr && !mpd_jack_connect(jd, error))
-		return false;
-
-	set_audioformat(jd, audio_format);
-	jd->audio_format = audio_format;
-
-	if (!mpd_jack_start(jd, error))
-		return false;
-
-	return true;
-}
-
-static void
-mpd_jack_close(gcc_unused struct audio_output *ao)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	mpd_jack_stop(jd);
-}
-
-static unsigned
-mpd_jack_delay(struct audio_output *ao)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	return jd->base.pause && jd->pause && !jd->shutdown
-		? 1000
-		: 0;
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
-	return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
-			  unsigned num_samples)
-{
-	jack_default_audio_sample_t sample;
-	unsigned i;
-
-	while (num_samples-- > 0) {
-		for (i = 0; i < jd->audio_format.channels; ++i) {
-			sample = sample_16_to_jack(*src++);
-			jack_ringbuffer_write(jd->ringbuffer[i],
-					      (const char *)&sample,
-					      sizeof(sample));
-		}
-	}
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
-	return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
-			  unsigned num_samples)
-{
-	jack_default_audio_sample_t sample;
-	unsigned i;
-
-	while (num_samples-- > 0) {
-		for (i = 0; i < jd->audio_format.channels; ++i) {
-			sample = sample_24_to_jack(*src++);
-			jack_ringbuffer_write(jd->ringbuffer[i],
-					      (const char *)&sample,
-					      sizeof(sample));
-		}
-	}
-}
-
-static void
-mpd_jack_write_samples(JackOutput *jd, const void *src,
-		       unsigned num_samples)
-{
-	switch (jd->audio_format.format) {
-	case SampleFormat::S16:
-		mpd_jack_write_samples_16(jd, (const int16_t*)src,
-					  num_samples);
-		break;
-
-	case SampleFormat::S24_P32:
-		mpd_jack_write_samples_24(jd, (const int32_t*)src,
-					  num_samples);
-		break;
-
-	default:
-		assert(false);
-		gcc_unreachable();
-	}
-}
-
-static size_t
-mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
-	      Error &error)
-{
-	JackOutput *jd = (JackOutput *)ao;
-	const size_t frame_size = jd->audio_format.GetFrameSize();
-	size_t space = 0, space1;
-
-	jd->pause = false;
-
-	assert(size % frame_size == 0);
-	size /= frame_size;
-
-	while (true) {
-		if (jd->shutdown) {
-			error.Set(jack_output_domain,
-				  "Refusing to play, because "
-				  "there is no client thread");
-			return 0;
-		}
-
-		space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
-		for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
-			space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
-			if (space > space1)
-				/* send data symmetrically */
-				space = space1;
-		}
-
-		if (space >= jack_sample_size)
-			break;
-
-		/* XXX do something more intelligent to
-		   synchronize */
-		g_usleep(1000);
-	}
-
-	space /= jack_sample_size;
-	if (space < size)
-		size = space;
-
-	mpd_jack_write_samples(jd, chunk, size);
-	return size * frame_size;
-}
-
-static bool
-mpd_jack_pause(struct audio_output *ao)
-{
-	JackOutput *jd = (JackOutput *)ao;
-
-	if (jd->shutdown)
-		return false;
-
-	jd->pause = true;
-
-	return true;
-}
-
-const struct audio_output_plugin jack_output_plugin = {
-	"jack",
-	mpd_jack_test_default_device,
-	mpd_jack_init,
-	mpd_jack_finish,
-	mpd_jack_enable,
-	mpd_jack_disable,
-	mpd_jack_open,
-	mpd_jack_close,
-	mpd_jack_delay,
-	nullptr,
-	mpd_jack_play,
-	nullptr,
-	nullptr,
-	mpd_jack_pause,
-	nullptr,
-};
diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx
deleted file mode 100644
index ee3fe9238..000000000
--- a/src/output/JackOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_JACK_OUTPUT_PLUGIN_HXX
-#define MPD_JACK_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin jack_output_plugin;
-
-#endif
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
deleted file mode 100644
index 0b6476239..000000000
--- a/src/output/NullOutputPlugin.cxx
+++ /dev/null
@@ -1,141 +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.
- */
-
-#include "config.h"
-#include "NullOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-
-struct NullOutput {
-	struct audio_output base;
-
-	bool sync;
-
-	Timer *timer;
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &null_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-};
-
-static struct audio_output *
-null_init(const config_param &param, Error &error)
-{
-	NullOutput *nd = new NullOutput();
-
-	if (!nd->Initialize(param, error)) {
-		delete nd;
-		return nullptr;
-	}
-
-	nd->sync = param.GetBlockValue("sync", true);
-
-	return &nd->base;
-}
-
-static void
-null_finish(struct audio_output *ao)
-{
-	NullOutput *nd = (NullOutput *)ao;
-
-	nd->Deinitialize();
-	delete nd;
-}
-
-static bool
-null_open(struct audio_output *ao, AudioFormat &audio_format,
-	  gcc_unused Error &error)
-{
-	NullOutput *nd = (NullOutput *)ao;
-
-	if (nd->sync)
-		nd->timer = new Timer(audio_format);
-
-	return true;
-}
-
-static void
-null_close(struct audio_output *ao)
-{
-	NullOutput *nd = (NullOutput *)ao;
-
-	if (nd->sync)
-		delete nd->timer;
-}
-
-static unsigned
-null_delay(struct audio_output *ao)
-{
-	NullOutput *nd = (NullOutput *)ao;
-
-	return nd->sync && nd->timer->IsStarted()
-		? nd->timer->GetDelay()
-		: 0;
-}
-
-static size_t
-null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
-	  gcc_unused Error &error)
-{
-	NullOutput *nd = (NullOutput *)ao;
-	Timer *timer = nd->timer;
-
-	if (!nd->sync)
-		return size;
-
-	if (!timer->IsStarted())
-		timer->Start();
-	timer->Add(size);
-
-	return size;
-}
-
-static void
-null_cancel(struct audio_output *ao)
-{
-	NullOutput *nd = (NullOutput *)ao;
-
-	if (!nd->sync)
-		return;
-
-	nd->timer->Reset();
-}
-
-const struct audio_output_plugin null_output_plugin = {
-	"null",
-	nullptr,
-	null_init,
-	null_finish,
-	nullptr,
-	nullptr,
-	null_open,
-	null_close,
-	null_delay,
-	nullptr,
-	null_play,
-	nullptr,
-	null_cancel,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx
deleted file mode 100644
index 05b8ef3d8..000000000
--- a/src/output/NullOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_NULL_OUTPUT_PLUGIN_HXX
-#define MPD_NULL_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin null_output_plugin;
-
-#endif
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
deleted file mode 100644
index 6c8467752..000000000
--- a/src/output/OSXOutputPlugin.cxx
+++ /dev/null
@@ -1,428 +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.
- */
-
-#include "config.h"
-#include "OSXOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/DynamicFifoBuffer.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <CoreAudio/AudioHardware.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-struct OSXOutput {
-	struct audio_output base;
-
-	/* configuration settings */
-	OSType component_subtype;
-	/* only applicable with kAudioUnitSubType_HALOutput */
-	const char *device_name;
-
-	AudioUnit au;
-	Mutex mutex;
-	Cond condition;
-
-	DynamicFifoBuffer<uint8_t> *buffer;
-};
-
-static constexpr Domain osx_output_domain("osx_output");
-
-static bool
-osx_output_test_default_device(void)
-{
-	/* on a Mac, this is always the default plugin, if nothing
-	   else is configured */
-	return true;
-}
-
-static void
-osx_output_configure(OSXOutput *oo, const config_param &param)
-{
-	const char *device = param.GetBlockValue("device");
-
-	if (device == NULL || 0 == strcmp(device, "default")) {
-		oo->component_subtype = kAudioUnitSubType_DefaultOutput;
-		oo->device_name = NULL;
-	}
-	else if (0 == strcmp(device, "system")) {
-		oo->component_subtype = kAudioUnitSubType_SystemOutput;
-		oo->device_name = NULL;
-	}
-	else {
-		oo->component_subtype = kAudioUnitSubType_HALOutput;
-		/* XXX am I supposed to strdup() this? */
-		oo->device_name = device;
-	}
-}
-
-static struct audio_output *
-osx_output_init(const config_param &param, Error &error)
-{
-	OSXOutput *oo = new OSXOutput();
-	if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) {
-		delete oo;
-		return NULL;
-	}
-
-	osx_output_configure(oo, param);
-
-	return &oo->base;
-}
-
-static void
-osx_output_finish(struct audio_output *ao)
-{
-	OSXOutput *oo = (OSXOutput *)ao;
-
-	delete oo;
-}
-
-static bool
-osx_output_set_device(OSXOutput *oo, Error &error)
-{
-	bool ret = true;
-	OSStatus status;
-	UInt32 size, numdevices;
-	AudioDeviceID *deviceids = NULL;
-	char name[256];
-	unsigned int i;
-
-	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
-		goto done;
-
-	/* how many audio devices are there? */
-	status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
-					      &size,
-					      NULL);
-	if (status != noErr) {
-		error.Format(osx_output_domain, status,
-			     "Unable to determine number of OS X audio devices: %s",
-			     GetMacOSStatusCommentString(status));
-		ret = false;
-		goto done;
-	}
-
-	/* what are the available audio device IDs? */
-	numdevices = size / sizeof(AudioDeviceID);
-	deviceids = new AudioDeviceID[numdevices];
-	status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
-					  &size,
-					  deviceids);
-	if (status != noErr) {
-		error.Format(osx_output_domain, status,
-			     "Unable to determine OS X audio device IDs: %s",
-			     GetMacOSStatusCommentString(status));
-		ret = false;
-		goto done;
-	}
-
-	/* which audio device matches oo->device_name? */
-	for (i = 0; i < numdevices; i++) {
-		size = sizeof(name);
-		status = AudioDeviceGetProperty(deviceids[i], 0, false,
-						kAudioDevicePropertyDeviceName,
-						&size, name);
-		if (status != noErr) {
-			error.Format(osx_output_domain, status,
-				     "Unable to determine OS X device name "
-				     "(device %u): %s",
-				     (unsigned int) deviceids[i],
-				     GetMacOSStatusCommentString(status));
-			ret = false;
-			goto done;
-		}
-		if (strcmp(oo->device_name, name) == 0) {
-			FormatDebug(osx_output_domain,
-				    "found matching device: ID=%u, name=%s",
-				    (unsigned)deviceids[i], name);
-			break;
-		}
-	}
-	if (i == numdevices) {
-		FormatWarning(osx_output_domain,
-			      "Found no audio device with name '%s' "
-			      "(will use default audio device)",
-			      oo->device_name);
-		goto done;
-	}
-
-	status = AudioUnitSetProperty(oo->au,
-				      kAudioOutputUnitProperty_CurrentDevice,
-				      kAudioUnitScope_Global,
-				      0,
-				      &(deviceids[i]),
-				      sizeof(AudioDeviceID));
-	if (status != noErr) {
-		error.Format(osx_output_domain, status,
-			     "Unable to set OS X audio output device: %s",
-			     GetMacOSStatusCommentString(status));
-		ret = false;
-		goto done;
-	}
-
-	FormatDebug(osx_output_domain,
-		    "set OS X audio output device ID=%u, name=%s",
-		    (unsigned)deviceids[i], name);
-
-done:
-	delete[] deviceids;
-	return ret;
-}
-
-static OSStatus
-osx_render(void *vdata,
-	   gcc_unused AudioUnitRenderActionFlags *io_action_flags,
-	   gcc_unused const AudioTimeStamp *in_timestamp,
-	   gcc_unused UInt32 in_bus_number,
-	   gcc_unused UInt32 in_number_frames,
-	   AudioBufferList *buffer_list)
-{
-	OSXOutput *od = (OSXOutput *) vdata;
-	AudioBuffer *buffer = &buffer_list->mBuffers[0];
-	size_t buffer_size = buffer->mDataByteSize;
-
-	assert(od->buffer != NULL);
-
-	od->mutex.lock();
-
-	auto src = od->buffer->Read();
-	if (!src.IsEmpty()) {
-		if (src.size > buffer_size)
-			src.size = buffer_size;
-
-		memcpy(buffer->mData, src.data, src.size);
-		od->buffer->Consume(src.size);
-	}
-
-	od->condition.signal();
-	od->mutex.unlock();
-
-	buffer->mDataByteSize = src.size;
-
-	unsigned i;
-	for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
-		buffer = &buffer_list->mBuffers[i];
-		buffer->mDataByteSize = 0;
-	}
-
-	return 0;
-}
-
-static bool
-osx_output_enable(struct audio_output *ao, Error &error)
-{
-	OSXOutput *oo = (OSXOutput *)ao;
-
-	ComponentDescription desc;
-	desc.componentType = kAudioUnitType_Output;
-	desc.componentSubType = oo->component_subtype;
-	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
-	desc.componentFlags = 0;
-	desc.componentFlagsMask = 0;
-
-	Component comp = FindNextComponent(NULL, &desc);
-	if (comp == 0) {
-		error.Set(osx_output_domain,
-			  "Error finding OS X component");
-		return false;
-	}
-
-	OSStatus status = OpenAComponent(comp, &oo->au);
-	if (status != noErr) {
-		error.Format(osx_output_domain, status,
-			     "Unable to open OS X component: %s",
-			     GetMacOSStatusCommentString(status));
-		return false;
-	}
-
-	if (!osx_output_set_device(oo, error)) {
-		CloseComponent(oo->au);
-		return false;
-	}
-
-	AURenderCallbackStruct callback;
-	callback.inputProc = osx_render;
-	callback.inputProcRefCon = oo;
-
-	ComponentResult result =
-		AudioUnitSetProperty(oo->au,
-				     kAudioUnitProperty_SetRenderCallback,
-				     kAudioUnitScope_Input, 0,
-				     &callback, sizeof(callback));
-	if (result != noErr) {
-		CloseComponent(oo->au);
-		error.Set(osx_output_domain, result,
-			  "unable to set callback for OS X audio unit");
-		return false;
-	}
-
-	return true;
-}
-
-static void
-osx_output_disable(struct audio_output *ao)
-{
-	OSXOutput *oo = (OSXOutput *)ao;
-
-	CloseComponent(oo->au);
-}
-
-static void
-osx_output_cancel(struct audio_output *ao)
-{
-	OSXOutput *od = (OSXOutput *)ao;
-
-	const ScopeLock protect(od->mutex);
-	od->buffer->Clear();
-}
-
-static void
-osx_output_close(struct audio_output *ao)
-{
-	OSXOutput *od = (OSXOutput *)ao;
-
-	AudioOutputUnitStop(od->au);
-	AudioUnitUninitialize(od->au);
-
-	delete od->buffer;
-}
-
-static bool
-osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		Error &error)
-{
-	OSXOutput *od = (OSXOutput *)ao;
-
-	AudioStreamBasicDescription stream_description;
-	stream_description.mSampleRate = audio_format.sample_rate;
-	stream_description.mFormatID = kAudioFormatLinearPCM;
-	stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
-	switch (audio_format.format) {
-	case SampleFormat::S8:
-		stream_description.mBitsPerChannel = 8;
-		break;
-
-	case SampleFormat::S16:
-		stream_description.mBitsPerChannel = 16;
-		break;
-
-	case SampleFormat::S32:
-		stream_description.mBitsPerChannel = 32;
-		break;
-
-	default:
-		audio_format.format = SampleFormat::S32;
-		stream_description.mBitsPerChannel = 32;
-		break;
-	}
-
-	if (IsBigEndian())
-		stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-
-	stream_description.mBytesPerPacket = audio_format.GetFrameSize();
-	stream_description.mFramesPerPacket = 1;
-	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
-	stream_description.mChannelsPerFrame = audio_format.channels;
-
-	ComponentResult result =
-		AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
-				     kAudioUnitScope_Input, 0,
-				     &stream_description,
-				     sizeof(stream_description));
-	if (result != noErr) {
-		error.Set(osx_output_domain, result,
-			  "Unable to set format on OS X device");
-		return false;
-	}
-
-	OSStatus status = AudioUnitInitialize(od->au);
-	if (status != noErr) {
-		error.Format(osx_output_domain, status,
-			     "Unable to initialize OS X audio unit: %s",
-			     GetMacOSStatusCommentString(status));
-		return false;
-	}
-
-	/* create a buffer of 1s */
-	od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
-						    audio_format.GetFrameSize());
-
-	status = AudioOutputUnitStart(od->au);
-	if (status != 0) {
-		AudioUnitUninitialize(od->au);
-		error.Format(osx_output_domain, status,
-			     "unable to start audio output: %s",
-			     GetMacOSStatusCommentString(status));
-		return false;
-	}
-
-	return true;
-}
-
-static size_t
-osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		gcc_unused Error &error)
-{
-	OSXOutput *od = (OSXOutput *)ao;
-
-	const ScopeLock protect(od->mutex);
-
-	DynamicFifoBuffer<uint8_t>::Range dest;
-	while (true) {
-		dest = od->buffer->Write();
-		if (!dest.IsEmpty())
-			break;
-
-		/* wait for some free space in the buffer */
-		od->condition.wait(od->mutex);
-	}
-
-	if (size > dest.size)
-		size = dest.size;
-
-	memcpy(dest.data, chunk, size);
-	od->buffer->Append(size);
-
-	return size;
-}
-
-const struct audio_output_plugin 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,
-	nullptr,
-	nullptr,
-	osx_output_play,
-	nullptr,
-	osx_output_cancel,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx
deleted file mode 100644
index 0de10f83e..000000000
--- a/src/output/OSXOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_OSX_OUTPUT_PLUGIN_HXX
-#define MPD_OSX_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin osx_output_plugin;
-
-#endif
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx
deleted file mode 100644
index 52a2c9070..000000000
--- a/src/output/OpenALOutputPlugin.cxx
+++ /dev/null
@@ -1,285 +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.
- */
-
-#include "config.h"
-#include "OpenALOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-
-#ifndef __APPLE__
-#include <AL/al.h>
-#include <AL/alc.h>
-#else
-#include <OpenAL/al.h>
-#include <OpenAL/alc.h>
-#endif
-
-/* should be enough for buffer size = 2048 */
-#define NUM_BUFFERS 16
-
-struct OpenALOutput {
-	struct audio_output base;
-
-	const char *device_name;
-	ALCdevice *device;
-	ALCcontext *context;
-	ALuint buffers[NUM_BUFFERS];
-	unsigned filled;
-	ALuint source;
-	ALenum format;
-	ALuint frequency;
-
-	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, &openal_output_plugin, param,
-				    error_r);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-};
-
-static constexpr Domain openal_output_domain("openal_output");
-
-static ALenum
-openal_audio_format(AudioFormat &audio_format)
-{
-	/* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
-	   AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
-	   samples, while MPD uses signed samples */
-
-	switch (audio_format.format) {
-	case SampleFormat::S16:
-		if (audio_format.channels == 2)
-			return AL_FORMAT_STEREO16;
-		if (audio_format.channels == 1)
-			return AL_FORMAT_MONO16;
-
-		/* fall back to mono */
-		audio_format.channels = 1;
-		return openal_audio_format(audio_format);
-
-	default:
-		/* fall back to 16 bit */
-		audio_format.format = SampleFormat::S16;
-		return openal_audio_format(audio_format);
-	}
-}
-
-gcc_pure
-static inline ALint
-openal_get_source_i(const OpenALOutput *od, ALenum param)
-{
-	ALint value;
-	alGetSourcei(od->source, param, &value);
-	return value;
-}
-
-gcc_pure
-static inline bool
-openal_has_processed(const OpenALOutput *od)
-{
-	return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
-}
-
-gcc_pure
-static inline ALint
-openal_is_playing(const OpenALOutput *od)
-{
-	return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
-}
-
-static bool
-openal_setup_context(OpenALOutput *od, Error &error)
-{
-	od->device = alcOpenDevice(od->device_name);
-
-	if (od->device == nullptr) {
-		error.Format(openal_output_domain,
-			     "Error opening OpenAL device \"%s\"",
-			     od->device_name);
-		return false;
-	}
-
-	od->context = alcCreateContext(od->device, nullptr);
-
-	if (od->context == nullptr) {
-		error.Format(openal_output_domain,
-			     "Error creating context for \"%s\"",
-			     od->device_name);
-		alcCloseDevice(od->device);
-		return false;
-	}
-
-	return true;
-}
-
-static struct audio_output *
-openal_init(const config_param &param, Error &error)
-{
-	const char *device_name = param.GetBlockValue("device");
-	if (device_name == nullptr) {
-		device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
-	}
-
-	OpenALOutput *od = new OpenALOutput();
-	if (!od->Initialize(param, error)) {
-		delete od;
-		return nullptr;
-	}
-
-	od->device_name = device_name;
-
-	return &od->base;
-}
-
-static void
-openal_finish(struct audio_output *ao)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-
-	od->Deinitialize();
-	delete od;
-}
-
-static bool
-openal_open(struct audio_output *ao, AudioFormat &audio_format,
-	    Error &error)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-
-	od->format = openal_audio_format(audio_format);
-
-	if (!openal_setup_context(od, error)) {
-		return false;
-	}
-
-	alcMakeContextCurrent(od->context);
-	alGenBuffers(NUM_BUFFERS, od->buffers);
-
-	if (alGetError() != AL_NO_ERROR) {
-		error.Set(openal_output_domain, "Failed to generate buffers");
-		return false;
-	}
-
-	alGenSources(1, &od->source);
-
-	if (alGetError() != AL_NO_ERROR) {
-		error.Set(openal_output_domain, "Failed to generate source");
-		alDeleteBuffers(NUM_BUFFERS, od->buffers);
-		return false;
-	}
-
-	od->filled = 0;
-	od->frequency = audio_format.sample_rate;
-
-	return true;
-}
-
-static void
-openal_close(struct audio_output *ao)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-
-	alcMakeContextCurrent(od->context);
-	alDeleteSources(1, &od->source);
-	alDeleteBuffers(NUM_BUFFERS, od->buffers);
-	alcDestroyContext(od->context);
-	alcCloseDevice(od->device);
-}
-
-static unsigned
-openal_delay(struct audio_output *ao)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-
-	return od->filled < NUM_BUFFERS || openal_has_processed(od)
-		? 0
-		/* we don't know exactly how long we must wait for the
-		   next buffer to finish, so this is a random
-		   guess: */
-		: 50;
-}
-
-static size_t
-openal_play(struct audio_output *ao, const void *chunk, size_t size,
-	    gcc_unused Error &error)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-	ALuint buffer;
-
-	if (alcGetCurrentContext() != od->context) {
-		alcMakeContextCurrent(od->context);
-	}
-
-	if (od->filled < NUM_BUFFERS) {
-		/* fill all buffers */
-		buffer = od->buffers[od->filled];
-		od->filled++;
-	} else {
-		/* wait for processed buffer */
-		while (!openal_has_processed(od))
-			g_usleep(10);
-
-		alSourceUnqueueBuffers(od->source, 1, &buffer);
-	}
-
-	alBufferData(buffer, od->format, chunk, size, od->frequency);
-	alSourceQueueBuffers(od->source, 1, &buffer);
-
-	if (!openal_is_playing(od))
-		alSourcePlay(od->source);
-
-	return size;
-}
-
-static void
-openal_cancel(struct audio_output *ao)
-{
-	OpenALOutput *od = (OpenALOutput *)ao;
-
-	od->filled = 0;
-	alcMakeContextCurrent(od->context);
-	alSourceStop(od->source);
-
-	/* force-unqueue all buffers */
-	alSourcei(od->source, AL_BUFFER, 0);
-	od->filled = 0;
-}
-
-const struct audio_output_plugin openal_output_plugin = {
-	"openal",
-	nullptr,
-	openal_init,
-	openal_finish,
-	nullptr,
-	nullptr,
-	openal_open,
-	openal_close,
-	openal_delay,
-	nullptr,
-	openal_play,
-	nullptr,
-	openal_cancel,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/OpenALOutputPlugin.hxx
deleted file mode 100644
index eb43d1aa5..000000000
--- a/src/output/OpenALOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_OPENAL_OUTPUT_PLUGIN_HXX
-#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin openal_output_plugin;
-
-#endif
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx
deleted file mode 100644
index 24f3f48b5..000000000
--- a/src/output/OssOutputPlugin.cxx
+++ /dev/null
@@ -1,776 +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.
- */
-
-#include "config.h"
-#include "OssOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <assert.h>
-
-#if defined(__OpenBSD__) || defined(__NetBSD__)
-# include <soundcard.h>
-#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-# include <sys/soundcard.h>
-#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-
-/* We got bug reports from FreeBSD users who said that the two 24 bit
-   formats generate white noise on FreeBSD, but 32 bit works.  This is
-   a workaround until we know what exactly is expected by the kernel
-   audio drivers. */
-#ifndef __linux__
-#undef AFMT_S24_PACKED
-#undef AFMT_S24_NE
-#endif
-
-#ifdef AFMT_S24_PACKED
-#include "pcm/PcmExport.hxx"
-#include "util/Manual.hxx"
-#endif
-
-struct OssOutput {
-	struct audio_output base;
-
-#ifdef AFMT_S24_PACKED
-	Manual<PcmExport> pcm_export;
-#endif
-
-	int fd;
-	const char *device;
-
-	/**
-	 * The current input audio format.  This is needed to reopen
-	 * the device after cancel().
-	 */
-	AudioFormat audio_format;
-
-	/**
-	 * The current OSS audio format.  This is needed to reopen the
-	 * device after cancel().
-	 */
-	int oss_format;
-
-	OssOutput():fd(-1), device(nullptr) {}
-
-	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, &oss_output_plugin, param,
-				    error_r);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-};
-
-static constexpr Domain oss_output_domain("oss_output");
-
-enum oss_stat {
-	OSS_STAT_NO_ERROR = 0,
-	OSS_STAT_NOT_CHAR_DEV = -1,
-	OSS_STAT_NO_PERMS = -2,
-	OSS_STAT_DOESN_T_EXIST = -3,
-	OSS_STAT_OTHER = -4,
-};
-
-static enum oss_stat
-oss_stat_device(const char *device, int *errno_r)
-{
-	struct stat st;
-
-	if (0 == stat(device, &st)) {
-		if (!S_ISCHR(st.st_mode)) {
-			return OSS_STAT_NOT_CHAR_DEV;
-		}
-	} else {
-		*errno_r = errno;
-
-		switch (errno) {
-		case ENOENT:
-		case ENOTDIR:
-			return OSS_STAT_DOESN_T_EXIST;
-		case EACCES:
-			return OSS_STAT_NO_PERMS;
-		default:
-			return OSS_STAT_OTHER;
-		}
-	}
-
-	return OSS_STAT_NO_ERROR;
-}
-
-static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
-
-static bool
-oss_output_test_default_device(void)
-{
-	int fd, i;
-
-	for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
-		fd = open_cloexec(default_devices[i], O_WRONLY, 0);
-
-		if (fd >= 0) {
-			close(fd);
-			return true;
-		}
-
-		FormatErrno(oss_output_domain,
-			    "Error opening OSS device \"%s\"",
-			    default_devices[i]);
-	}
-
-	return false;
-}
-
-static struct audio_output *
-oss_open_default(Error &error)
-{
-	int err[ARRAY_SIZE(default_devices)];
-	enum oss_stat ret[ARRAY_SIZE(default_devices)];
-
-	const config_param empty;
-	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
-		ret[i] = oss_stat_device(default_devices[i], &err[i]);
-		if (ret[i] == OSS_STAT_NO_ERROR) {
-			OssOutput *od = new OssOutput();
-			if (!od->Initialize(empty, error)) {
-				delete od;
-				return NULL;
-			}
-
-			od->device = default_devices[i];
-			return &od->base;
-		}
-	}
-
-	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
-		const char *dev = default_devices[i];
-		switch(ret[i]) {
-		case OSS_STAT_NO_ERROR:
-			/* never reached */
-			break;
-		case OSS_STAT_DOESN_T_EXIST:
-			FormatWarning(oss_output_domain,
-				      "%s not found", dev);
-			break;
-		case OSS_STAT_NOT_CHAR_DEV:
-			FormatWarning(oss_output_domain,
-				      "%s is not a character device", dev);
-			break;
-		case OSS_STAT_NO_PERMS:
-			FormatWarning(oss_output_domain,
-				      "%s: permission denied", dev);
-			break;
-		case OSS_STAT_OTHER:
-			FormatErrno(oss_output_domain, err[i],
-				    "Error accessing %s", dev);
-		}
-	}
-
-	error.Set(oss_output_domain,
-		  "error trying to open default OSS device");
-	return NULL;
-}
-
-static struct audio_output *
-oss_output_init(const config_param &param, Error &error)
-{
-	const char *device = param.GetBlockValue("device");
-	if (device != NULL) {
-		OssOutput *od = new OssOutput();
-		if (!od->Initialize(param, error)) {
-			delete od;
-			return NULL;
-		}
-
-		od->device = device;
-		return &od->base;
-	}
-
-	return oss_open_default(error);
-}
-
-static void
-oss_output_finish(struct audio_output *ao)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	ao_base_finish(&od->base);
-	delete od;
-}
-
-#ifdef AFMT_S24_PACKED
-
-static bool
-oss_output_enable(struct audio_output *ao, gcc_unused Error &error)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	od->pcm_export.Construct();
-	return true;
-}
-
-static void
-oss_output_disable(struct audio_output *ao)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	od->pcm_export.Destruct();
-}
-
-#endif
-
-static void
-oss_close(OssOutput *od)
-{
-	if (od->fd >= 0)
-		close(od->fd);
-	od->fd = -1;
-}
-
-/**
- * A tri-state type for oss_try_ioctl().
- */
-enum oss_setup_result {
-	SUCCESS,
-	ERROR,
-	UNSUPPORTED,
-};
-
-/**
- * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
- * returned.  If the parameter is not supported, UNSUPPORTED is
- * returned.  Any other failure returns ERROR and allocates an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
-		const char *msg, Error &error)
-{
-	assert(fd >= 0);
-	assert(value_r != NULL);
-	assert(msg != NULL);
-	assert(!error.IsDefined());
-
-	int ret = ioctl(fd, request, value_r);
-	if (ret >= 0)
-		return SUCCESS;
-
-	if (errno == EINVAL)
-		return UNSUPPORTED;
-
-	error.SetErrno(msg);
-	return ERROR;
-}
-
-/**
- * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
- * returned.  If the parameter is not supported, UNSUPPORTED is
- * returned.  Any other failure returns ERROR and allocates an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl(int fd, unsigned long request, int value,
-	      const char *msg, Error &error_r)
-{
-	return oss_try_ioctl_r(fd, request, &value, msg, error_r);
-}
-
-/**
- * Set up the channel number, and attempts to find alternatives if the
- * specified number is not supported.
- */
-static bool
-oss_setup_channels(int fd, AudioFormat &audio_format, Error &error)
-{
-	const char *const msg = "Failed to set channel count";
-	int channels = audio_format.channels;
-	enum oss_setup_result result =
-		oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error);
-	switch (result) {
-	case SUCCESS:
-		if (!audio_valid_channel_count(channels))
-		    break;
-
-		audio_format.channels = channels;
-		return true;
-
-	case ERROR:
-		return false;
-
-	case UNSUPPORTED:
-		break;
-	}
-
-	for (unsigned i = 1; i < 2; ++i) {
-		if (i == audio_format.channels)
-			/* don't try that again */
-			continue;
-
-		channels = i;
-		result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
-					 msg, error);
-		switch (result) {
-		case SUCCESS:
-			if (!audio_valid_channel_count(channels))
-			    break;
-
-			audio_format.channels = channels;
-			return true;
-
-		case ERROR:
-			return false;
-
-		case UNSUPPORTED:
-			break;
-		}
-	}
-
-	error.Set(oss_output_domain, msg);
-	return false;
-}
-
-/**
- * Set up the sample rate, and attempts to find alternatives if the
- * specified sample rate is not supported.
- */
-static bool
-oss_setup_sample_rate(int fd, AudioFormat &audio_format,
-		      Error &error)
-{
-	const char *const msg = "Failed to set sample rate";
-	int sample_rate = audio_format.sample_rate;
-	enum oss_setup_result result =
-		oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
-				msg, error);
-	switch (result) {
-	case SUCCESS:
-		if (!audio_valid_sample_rate(sample_rate))
-			break;
-
-		audio_format.sample_rate = sample_rate;
-		return true;
-
-	case ERROR:
-		return false;
-
-	case UNSUPPORTED:
-		break;
-	}
-
-	static const int sample_rates[] = { 48000, 44100, 0 };
-	for (unsigned i = 0; sample_rates[i] != 0; ++i) {
-		sample_rate = sample_rates[i];
-		if (sample_rate == (int)audio_format.sample_rate)
-			continue;
-
-		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
-					 msg, error);
-		switch (result) {
-		case SUCCESS:
-			if (!audio_valid_sample_rate(sample_rate))
-				break;
-
-			audio_format.sample_rate = sample_rate;
-			return true;
-
-		case ERROR:
-			return false;
-
-		case UNSUPPORTED:
-			break;
-		}
-	}
-
-	error.Set(oss_output_domain, msg);
-	return false;
-}
-
-/**
- * Convert a MPD sample format to its OSS counterpart.  Returns
- * AFMT_QUERY if there is no direct counterpart.
- */
-static int
-sample_format_to_oss(SampleFormat format)
-{
-	switch (format) {
-	case SampleFormat::UNDEFINED:
-	case SampleFormat::FLOAT:
-	case SampleFormat::DSD:
-		return AFMT_QUERY;
-
-	case SampleFormat::S8:
-		return AFMT_S8;
-
-	case SampleFormat::S16:
-		return AFMT_S16_NE;
-
-	case SampleFormat::S24_P32:
-#ifdef AFMT_S24_NE
-		return AFMT_S24_NE;
-#else
-		return AFMT_QUERY;
-#endif
-
-	case SampleFormat::S32:
-#ifdef AFMT_S32_NE
-		return AFMT_S32_NE;
-#else
-		return AFMT_QUERY;
-#endif
-	}
-
-	return AFMT_QUERY;
-}
-
-/**
- * Convert an OSS sample format to its MPD counterpart.  Returns
- * SampleFormat::UNDEFINED if there is no direct counterpart.
- */
-static SampleFormat
-sample_format_from_oss(int format)
-{
-	switch (format) {
-	case AFMT_S8:
-		return SampleFormat::S8;
-
-	case AFMT_S16_NE:
-		return SampleFormat::S16;
-
-#ifdef AFMT_S24_PACKED
-	case AFMT_S24_PACKED:
-		return SampleFormat::S24_P32;
-#endif
-
-#ifdef AFMT_S24_NE
-	case AFMT_S24_NE:
-		return SampleFormat::S24_P32;
-#endif
-
-#ifdef AFMT_S32_NE
-	case AFMT_S32_NE:
-		return SampleFormat::S32;
-#endif
-
-	default:
-		return SampleFormat::UNDEFINED;
-	}
-}
-
-/**
- * Probe one sample format.
- *
- * @return the selected sample format or SampleFormat::UNDEFINED on
- * error
- */
-static enum oss_setup_result
-oss_probe_sample_format(int fd, SampleFormat sample_format,
-			SampleFormat *sample_format_r,
-			int *oss_format_r,
-#ifdef AFMT_S24_PACKED
-			PcmExport &pcm_export,
-#endif
-			Error &error)
-{
-	int oss_format = sample_format_to_oss(sample_format);
-	if (oss_format == AFMT_QUERY)
-		return UNSUPPORTED;
-
-	enum oss_setup_result result =
-		oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
-				&oss_format,
-				"Failed to set sample format", error);
-
-#ifdef AFMT_S24_PACKED
-	if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
-		/* if the driver doesn't support padded 24 bit, try
-		   packed 24 bit */
-		oss_format = AFMT_S24_PACKED;
-		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
-					 &oss_format,
-					 "Failed to set sample format", error);
-	}
-#endif
-
-	if (result != SUCCESS)
-		return result;
-
-	sample_format = sample_format_from_oss(oss_format);
-	if (sample_format == SampleFormat::UNDEFINED)
-		return UNSUPPORTED;
-
-	*sample_format_r = sample_format;
-	*oss_format_r = oss_format;
-
-#ifdef AFMT_S24_PACKED
-	pcm_export.Open(sample_format, 0, false, false,
-			oss_format == AFMT_S24_PACKED,
-			oss_format == AFMT_S24_PACKED &&
-			!IsLittleEndian());
-#endif
-
-	return SUCCESS;
-}
-
-/**
- * Set up the sample format, and attempts to find alternatives if the
- * specified format is not supported.
- */
-static bool
-oss_setup_sample_format(int fd, AudioFormat &audio_format,
-			int *oss_format_r,
-#ifdef AFMT_S24_PACKED
-			PcmExport &pcm_export,
-#endif
-			Error &error)
-{
-	SampleFormat mpd_format;
-	enum oss_setup_result result =
-		oss_probe_sample_format(fd, audio_format.format,
-					&mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
-					pcm_export,
-#endif
-					error);
-	switch (result) {
-	case SUCCESS:
-		audio_format.format = mpd_format;
-		return true;
-
-	case ERROR:
-		return false;
-
-	case UNSUPPORTED:
-		break;
-	}
-
-	if (result != UNSUPPORTED)
-		return result == SUCCESS;
-
-	/* the requested sample format is not available - probe for
-	   other formats supported by MPD */
-
-	static const SampleFormat sample_formats[] = {
-		SampleFormat::S24_P32,
-		SampleFormat::S32,
-		SampleFormat::S16,
-		SampleFormat::S8,
-		SampleFormat::UNDEFINED /* sentinel */
-	};
-
-	for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
-		mpd_format = sample_formats[i];
-		if (mpd_format == audio_format.format)
-			/* don't try that again */
-			continue;
-
-		result = oss_probe_sample_format(fd, mpd_format,
-						 &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
-						 pcm_export,
-#endif
-						 error);
-		switch (result) {
-		case SUCCESS:
-			audio_format.format = mpd_format;
-			return true;
-
-		case ERROR:
-			return false;
-
-		case UNSUPPORTED:
-			break;
-		}
-	}
-
-	error.Set(oss_output_domain, "Failed to set sample format");
-	return false;
-}
-
-/**
- * Sets up the OSS device which was opened before.
- */
-static bool
-oss_setup(OssOutput *od, AudioFormat &audio_format,
-	  Error &error)
-{
-	return oss_setup_channels(od->fd, audio_format, error) &&
-		oss_setup_sample_rate(od->fd, audio_format, error) &&
-		oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
-#ifdef AFMT_S24_PACKED
-					od->pcm_export,
-#endif
-					error);
-}
-
-/**
- * Reopen the device with the saved audio_format, without any probing.
- */
-static bool
-oss_reopen(OssOutput *od, Error &error)
-{
-	assert(od->fd < 0);
-
-	od->fd = open_cloexec(od->device, O_WRONLY, 0);
-	if (od->fd < 0) {
-		error.FormatErrno("Error opening OSS device \"%s\"",
-				  od->device);
-		return false;
-	}
-
-	enum oss_setup_result result;
-
-	const char *const msg1 = "Failed to set channel count";
-	result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
-			       od->audio_format.channels, msg1, error);
-	if (result != SUCCESS) {
-		oss_close(od);
-		if (result == UNSUPPORTED)
-			error.Set(oss_output_domain, msg1);
-		return false;
-	}
-
-	const char *const msg2 = "Failed to set sample rate";
-	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
-			       od->audio_format.sample_rate, msg2, error);
-	if (result != SUCCESS) {
-		oss_close(od);
-		if (result == UNSUPPORTED)
-			error.Set(oss_output_domain, msg2);
-		return false;
-	}
-
-	const char *const msg3 = "Failed to set sample format";
-	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
-			       od->oss_format,
-			       msg3, error);
-	if (result != SUCCESS) {
-		oss_close(od);
-		if (result == UNSUPPORTED)
-			error.Set(oss_output_domain, msg3);
-		return false;
-	}
-
-	return true;
-}
-
-static bool
-oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		Error &error)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	od->fd = open_cloexec(od->device, O_WRONLY, 0);
-	if (od->fd < 0) {
-		error.FormatErrno("Error opening OSS device \"%s\"",
-				  od->device);
-		return false;
-	}
-
-	if (!oss_setup(od, audio_format, error)) {
-		oss_close(od);
-		return false;
-	}
-
-	od->audio_format = audio_format;
-	return true;
-}
-
-static void
-oss_output_close(struct audio_output *ao)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	oss_close(od);
-}
-
-static void
-oss_output_cancel(struct audio_output *ao)
-{
-	OssOutput *od = (OssOutput *)ao;
-
-	if (od->fd >= 0) {
-		ioctl(od->fd, SNDCTL_DSP_RESET, 0);
-		oss_close(od);
-	}
-}
-
-static size_t
-oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		Error &error)
-{
-	OssOutput *od = (OssOutput *)ao;
-	ssize_t ret;
-
-	/* reopen the device since it was closed by dropBufferedAudio */
-	if (od->fd < 0 && !oss_reopen(od, error))
-		return 0;
-
-#ifdef AFMT_S24_PACKED
-	chunk = od->pcm_export->Export(chunk, size, size);
-#endif
-
-	while (true) {
-		ret = write(od->fd, chunk, size);
-		if (ret > 0) {
-#ifdef AFMT_S24_PACKED
-			ret = od->pcm_export->CalcSourceSize(ret);
-#endif
-			return ret;
-		}
-
-		if (ret < 0 && errno != EINTR) {
-			error.FormatErrno("Write error on %s", od->device);
-			return 0;
-		}
-	}
-}
-
-const struct audio_output_plugin oss_output_plugin = {
-	"oss",
-	oss_output_test_default_device,
-	oss_output_init,
-	oss_output_finish,
-#ifdef AFMT_S24_PACKED
-	oss_output_enable,
-	oss_output_disable,
-#else
-	nullptr,
-	nullptr,
-#endif
-	oss_output_open,
-	oss_output_close,
-	nullptr,
-	nullptr,
-	oss_output_play,
-	nullptr,
-	oss_output_cancel,
-	nullptr,
-
-	&oss_mixer_plugin,
-};
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx
deleted file mode 100644
index 4762fa652..000000000
--- a/src/output/OssOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_OSS_OUTPUT_PLUGIN_HXX
-#define MPD_OSS_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin oss_output_plugin;
-
-#endif
diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx
new file mode 100644
index 000000000..322ed3971
--- /dev/null
+++ b/src/output/OutputAPI.hxx
@@ -0,0 +1,33 @@
+/*
+ * 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_API_HXX
+#define MPD_OUTPUT_API_HXX
+
+// IWYU pragma: begin_exports
+
+#include "OutputPlugin.hxx"
+#include "OutputInternal.hxx"
+#include "AudioFormat.hxx"
+#include "tag/Tag.hxx"
+#include "ConfigData.hxx"
+
+// IWYU pragma: end_exports
+
+#endif
diff --git a/src/output/OutputAll.cxx b/src/output/OutputAll.cxx
new file mode 100644
index 000000000..b3623f1af
--- /dev/null
+++ b/src/output/OutputAll.cxx
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputAll.hxx"
+#include "PlayerControl.hxx"
+#include "OutputInternal.hxx"
+#include "OutputControl.hxx"
+#include "OutputError.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "ConfigData.hxx"
+#include "ConfigGlobal.hxx"
+#include "ConfigOption.hxx"
+#include "notify.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static AudioFormat input_audio_format;
+
+static struct audio_output **audio_outputs;
+static unsigned int num_audio_outputs;
+
+/**
+ * The #MusicBuffer object where consumed chunks are returned.
+ */
+static MusicBuffer *g_music_buffer;
+
+/**
+ * The #MusicPipe object which feeds all audio outputs.  It is filled
+ * by audio_output_all_play().
+ */
+static MusicPipe *g_mp;
+
+/**
+ * The "elapsed_time" stamp of the most recently finished chunk.
+ */
+static float audio_output_all_elapsed_time = -1.0;
+
+unsigned int audio_output_count(void)
+{
+	return num_audio_outputs;
+}
+
+struct audio_output *
+audio_output_get(unsigned i)
+{
+	assert(i < num_audio_outputs);
+
+	assert(audio_outputs[i] != nullptr);
+
+	return audio_outputs[i];
+}
+
+struct audio_output *
+audio_output_find(const char *name)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i) {
+		struct audio_output *ao = audio_output_get(i);
+
+		if (strcmp(ao->name, name) == 0)
+			return ao;
+	}
+
+	/* name not found */
+	return nullptr;
+}
+
+gcc_const
+static unsigned
+audio_output_config_count(void)
+{
+	unsigned int nr = 0;
+	const struct config_param *param = nullptr;
+
+	while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
+		nr++;
+	if (!nr)
+		nr = 1; /* we'll always have at least one device  */
+	return nr;
+}
+
+void
+audio_output_all_init(PlayerControl &pc)
+{
+	const struct config_param *param = nullptr;
+	unsigned int i;
+	Error error;
+
+	num_audio_outputs = audio_output_config_count();
+	audio_outputs = new audio_output *[num_audio_outputs];
+
+	const config_param empty;
+
+	for (i = 0; i < num_audio_outputs; i++)
+	{
+		unsigned int j;
+
+		param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
+		if (param == nullptr) {
+			/* only allow param to be nullptr if there
+			   just one audio output */
+			assert(i == 0);
+			assert(num_audio_outputs == 1);
+
+			param = &empty;
+		}
+
+		audio_output *output = audio_output_new(*param, pc, error);
+		if (output == nullptr) {
+			if (param != nullptr)
+				FormatFatalError("line %i: %s",
+						 param->line,
+						 error.GetMessage());
+			else
+				FatalError(error);
+		}
+
+		audio_outputs[i] = output;
+
+		/* require output names to be unique: */
+		for (j = 0; j < i; j++) {
+			if (!strcmp(output->name, audio_outputs[j]->name)) {
+				FormatFatalError("output devices with identical "
+						 "names: %s", output->name);
+			}
+		}
+	}
+}
+
+void
+audio_output_all_finish(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < num_audio_outputs; i++) {
+		audio_output_disable(audio_outputs[i]);
+		audio_output_finish(audio_outputs[i]);
+	}
+
+	delete[] audio_outputs;
+	audio_outputs = nullptr;
+	num_audio_outputs = 0;
+}
+
+void
+audio_output_all_enable_disable(void)
+{
+	for (unsigned i = 0; i < num_audio_outputs; i++) {
+		struct audio_output *ao = audio_outputs[i];
+		bool enabled;
+
+		ao->mutex.lock();
+		enabled = ao->really_enabled;
+		ao->mutex.unlock();
+
+		if (ao->enabled != enabled) {
+			if (ao->enabled)
+				audio_output_enable(ao);
+			else
+				audio_output_disable(ao);
+		}
+	}
+}
+
+/**
+ * Determine if all (active) outputs have finished the current
+ * command.
+ */
+static bool
+audio_output_all_finished(void)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i) {
+		struct audio_output *ao = audio_outputs[i];
+
+		const ScopeLock protect(ao->mutex);
+		if (audio_output_is_open(ao) &&
+		    !audio_output_command_is_finished(ao))
+			return false;
+	}
+
+	return true;
+}
+
+static void audio_output_wait_all(void)
+{
+	while (!audio_output_all_finished())
+		audio_output_client_notify.Wait();
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_allow_play_all(void)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i)
+		audio_output_allow_play(audio_outputs[i]);
+}
+
+static void
+audio_output_reset_reopen(struct audio_output *ao)
+{
+	const ScopeLock protect(ao->mutex);
+
+	ao->fail_timer.Reset();
+}
+
+/**
+ * Resets the "reopen" flag on all audio devices.  MPD should
+ * immediately retry to open the device instead of waiting for the
+ * timeout when the user wants to start playback.
+ */
+static void
+audio_output_all_reset_reopen(void)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i) {
+		struct audio_output *ao = audio_outputs[i];
+
+		audio_output_reset_reopen(ao);
+	}
+}
+
+/**
+ * Opens all output devices which are enabled, but closed.
+ *
+ * @return true if there is at least open output device which is open
+ */
+static bool
+audio_output_all_update(void)
+{
+	unsigned int i;
+	bool ret = false;
+
+	if (!input_audio_format.IsDefined())
+		return false;
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		ret = audio_output_update(audio_outputs[i],
+					  input_audio_format, *g_mp) || ret;
+
+	return ret;
+}
+
+void
+audio_output_all_set_replay_gain_mode(ReplayGainMode mode)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i)
+		audio_output_set_replay_gain_mode(audio_outputs[i], mode);
+}
+
+bool
+audio_output_all_play(struct music_chunk *chunk, Error &error)
+{
+	bool ret;
+	unsigned int i;
+
+	assert(g_music_buffer != nullptr);
+	assert(g_mp != nullptr);
+	assert(chunk != nullptr);
+	assert(chunk->CheckFormat(input_audio_format));
+
+	ret = audio_output_all_update();
+	if (!ret) {
+		/* TODO: obtain real error */
+		error.Set(output_domain, "Failed to open audio output");
+		return false;
+	}
+
+	g_mp->Push(chunk);
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		audio_output_play(audio_outputs[i]);
+
+	return true;
+}
+
+bool
+audio_output_all_open(const AudioFormat audio_format,
+		      MusicBuffer &buffer,
+		      Error &error)
+{
+	bool ret = false, enabled = false;
+	unsigned int i;
+
+	assert(g_music_buffer == nullptr || g_music_buffer == &buffer);
+	assert((g_mp == nullptr) == (g_music_buffer == nullptr));
+
+	g_music_buffer = &buffer;
+
+	/* the audio format must be the same as existing chunks in the
+	   pipe */
+	assert(g_mp == nullptr || g_mp->CheckFormat(audio_format));
+
+	if (g_mp == nullptr)
+		g_mp = new MusicPipe();
+	else
+		/* if the pipe hasn't been cleared, the the audio
+		   format must not have changed */
+		assert(g_mp->IsEmpty() || audio_format == input_audio_format);
+
+	input_audio_format = audio_format;
+
+	audio_output_all_reset_reopen();
+	audio_output_all_enable_disable();
+	audio_output_all_update();
+
+	for (i = 0; i < num_audio_outputs; ++i) {
+		if (audio_outputs[i]->enabled)
+			enabled = true;
+
+		if (audio_outputs[i]->open)
+			ret = true;
+	}
+
+	if (!enabled)
+		error.Set(output_domain, "All audio outputs are disabled");
+	else if (!ret)
+		/* TODO: obtain real error */
+		error.Set(output_domain, "Failed to open audio output");
+
+	if (!ret)
+		/* close all devices if there was an error */
+		audio_output_all_close();
+
+	return ret;
+}
+
+/**
+ * Has the specified audio output already consumed this chunk?
+ */
+static bool
+chunk_is_consumed_in(const struct audio_output *ao,
+		     const struct music_chunk *chunk)
+{
+	if (!ao->open)
+		return true;
+
+	if (ao->chunk == nullptr)
+		return false;
+
+	assert(chunk == ao->chunk || g_mp->Contains(ao->chunk));
+
+	if (chunk != ao->chunk) {
+		assert(chunk->next != nullptr);
+		return true;
+	}
+
+	return ao->chunk_finished && chunk->next == nullptr;
+}
+
+/**
+ * Has this chunk been consumed by all audio outputs?
+ */
+static bool
+chunk_is_consumed(const struct music_chunk *chunk)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i) {
+		struct audio_output *ao = audio_outputs[i];
+
+		const ScopeLock protect(ao->mutex);
+		if (!chunk_is_consumed_in(ao, chunk))
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * There's only one chunk left in the pipe (#g_mp), and all audio
+ * outputs have consumed it already.  Clear the reference.
+ */
+static void
+clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked)
+{
+	assert(chunk->next == nullptr);
+	assert(g_mp->Contains(chunk));
+
+	for (unsigned i = 0; i < num_audio_outputs; ++i) {
+		struct audio_output *ao = audio_outputs[i];
+
+		/* this mutex will be unlocked by the caller when it's
+		   ready */
+		ao->mutex.lock();
+		locked[i] = ao->open;
+
+		if (!locked[i]) {
+			ao->mutex.unlock();
+			continue;
+		}
+
+		assert(ao->chunk == chunk);
+		assert(ao->chunk_finished);
+		ao->chunk = nullptr;
+	}
+}
+
+unsigned
+audio_output_all_check(void)
+{
+	const struct music_chunk *chunk;
+	bool is_tail;
+	struct music_chunk *shifted;
+	bool locked[num_audio_outputs];
+
+	assert(g_music_buffer != nullptr);
+	assert(g_mp != nullptr);
+
+	while ((chunk = g_mp->Peek()) != nullptr) {
+		assert(!g_mp->IsEmpty());
+
+		if (!chunk_is_consumed(chunk))
+			/* at least one output is not finished playing
+			   this chunk */
+			return g_mp->GetSize();
+
+		if (chunk->length > 0 && chunk->times >= 0.0)
+			/* only update elapsed_time if the chunk
+			   provides a defined value */
+			audio_output_all_elapsed_time = chunk->times;
+
+		is_tail = chunk->next == nullptr;
+		if (is_tail)
+			/* this is the tail of the pipe - clear the
+			   chunk reference in all outputs */
+			clear_tail_chunk(chunk, locked);
+
+		/* remove the chunk from the pipe */
+		shifted = g_mp->Shift();
+		assert(shifted == chunk);
+
+		if (is_tail)
+			/* unlock all audio outputs which were locked
+			   by clear_tail_chunk() */
+			for (unsigned i = 0; i < num_audio_outputs; ++i)
+				if (locked[i])
+					audio_outputs[i]->mutex.unlock();
+
+		/* return the chunk to the buffer */
+		g_music_buffer->Return(shifted);
+	}
+
+	return 0;
+}
+
+bool
+audio_output_all_wait(PlayerControl &pc, unsigned threshold)
+{
+	pc.Lock();
+
+	if (audio_output_all_check() < threshold) {
+		pc.Unlock();
+		return true;
+	}
+
+	pc.Wait();
+	pc.Unlock();
+
+	return audio_output_all_check() < threshold;
+}
+
+void
+audio_output_all_pause(void)
+{
+	unsigned int i;
+
+	audio_output_all_update();
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		audio_output_pause(audio_outputs[i]);
+
+	audio_output_wait_all();
+}
+
+void
+audio_output_all_drain(void)
+{
+	for (unsigned i = 0; i < num_audio_outputs; ++i)
+		audio_output_drain_async(audio_outputs[i]);
+
+	audio_output_wait_all();
+}
+
+void
+audio_output_all_cancel(void)
+{
+	unsigned int i;
+
+	/* send the cancel() command to all audio outputs */
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		audio_output_cancel(audio_outputs[i]);
+
+	audio_output_wait_all();
+
+	/* clear the music pipe and return all chunks to the buffer */
+
+	if (g_mp != nullptr)
+		g_mp->Clear(*g_music_buffer);
+
+	/* the audio outputs are now waiting for a signal, to
+	   synchronize the cleared music pipe */
+
+	audio_output_allow_play_all();
+
+	/* invalidate elapsed_time */
+
+	audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_close(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		audio_output_close(audio_outputs[i]);
+
+	if (g_mp != nullptr) {
+		assert(g_music_buffer != nullptr);
+
+		g_mp->Clear(*g_music_buffer);
+		delete g_mp;
+		g_mp = nullptr;
+	}
+
+	g_music_buffer = nullptr;
+
+	input_audio_format.Clear();
+
+	audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_release(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < num_audio_outputs; ++i)
+		audio_output_release(audio_outputs[i]);
+
+	if (g_mp != nullptr) {
+		assert(g_music_buffer != nullptr);
+
+		g_mp->Clear(*g_music_buffer);
+		delete g_mp;
+		g_mp = nullptr;
+	}
+
+	g_music_buffer = nullptr;
+
+	input_audio_format.Clear();
+
+	audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_song_border(void)
+{
+	/* clear the elapsed_time pointer at the beginning of a new
+	   song */
+	audio_output_all_elapsed_time = 0.0;
+}
+
+float
+audio_output_all_get_elapsed_time(void)
+{
+	return audio_output_all_elapsed_time;
+}
diff --git a/src/output/OutputAll.hxx b/src/output/OutputAll.hxx
new file mode 100644
index 000000000..b6166eb48
--- /dev/null
+++ b/src/output/OutputAll.hxx
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+/*
+ * Functions for dealing with all configured (enabled) audion outputs
+ * at once.
+ *
+ */
+
+#ifndef OUTPUT_ALL_H
+#define OUTPUT_ALL_H
+
+#include "ReplayGainInfo.hxx"
+#include "Compiler.h"
+
+struct AudioFormat;
+class MusicBuffer;
+struct music_chunk;
+struct PlayerControl;
+class Error;
+
+/**
+ * Global initialization: load audio outputs from the configuration
+ * file and initialize them.
+ */
+void
+audio_output_all_init(PlayerControl &pc);
+
+/**
+ * Global finalization: free memory occupied by audio outputs.  All
+ */
+void
+audio_output_all_finish(void);
+
+/**
+ * Returns the total number of audio output devices, including those
+ * who are disabled right now.
+ */
+gcc_const
+unsigned int audio_output_count(void);
+
+/**
+ * Returns the "i"th audio output device.
+ */
+gcc_const
+struct audio_output *
+audio_output_get(unsigned i);
+
+/**
+ * Returns the audio output device with the specified name.  Returns
+ * NULL if the name does not exist.
+ */
+gcc_pure
+struct audio_output *
+audio_output_find(const char *name);
+
+/**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+void
+audio_output_all_enable_disable(void);
+
+/**
+ * Opens all audio outputs which are not disabled.
+ *
+ * @param audio_format the preferred audio format
+ * @param buffer the #music_buffer where consumed #music_chunk objects
+ * should be returned
+ * @return true on success, false on failure
+ */
+bool
+audio_output_all_open(AudioFormat audio_format,
+		      MusicBuffer &buffer,
+		      Error &error);
+
+/**
+ * Closes all audio outputs.
+ */
+void
+audio_output_all_close(void);
+
+/**
+ * Closes all audio outputs.  Outputs with the "always_on" flag are
+ * put into pause mode.
+ */
+void
+audio_output_all_release(void);
+
+void
+audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
+
+/**
+ * Enqueue a #music_chunk object for playing, i.e. pushes it to a
+ * #MusicPipe.
+ *
+ * @param chunk the #music_chunk object to be played
+ * @return true on success, false if no audio output was able to play
+ * (all closed then)
+ */
+bool
+audio_output_all_play(music_chunk *chunk, Error &error);
+
+/**
+ * Checks if the output devices have drained their music pipe, and
+ * returns the consumed music chunks to the #music_buffer.
+ *
+ * @return the number of chunks to play left in the #MusicPipe
+ */
+unsigned
+audio_output_all_check(void);
+
+/**
+ * Checks if the size of the #MusicPipe is below the #threshold.  If
+ * not, it attempts to synchronize with all output threads, and waits
+ * until another #music_chunk is finished.
+ *
+ * @param threshold the maximum number of chunks in the pipe
+ * @return true if there are less than #threshold chunks in the pipe
+ */
+bool
+audio_output_all_wait(PlayerControl &pc, unsigned threshold);
+
+/**
+ * Puts all audio outputs into pause mode.  Most implementations will
+ * simply close it then.
+ */
+void
+audio_output_all_pause(void);
+
+/**
+ * Drain all audio outputs.
+ */
+void
+audio_output_all_drain(void);
+
+/**
+ * Try to cancel data which may still be in the device's buffers.
+ */
+void
+audio_output_all_cancel(void);
+
+/**
+ * Indicate that a new song will begin now.
+ */
+void
+audio_output_all_song_border(void);
+
+/**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk.  A negative value is returned when no chunk has been
+ * finished yet.
+ */
+gcc_pure
+float
+audio_output_all_get_elapsed_time(void);
+
+#endif
diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx
new file mode 100644
index 000000000..839e9bd88
--- /dev/null
+++ b/src/output/OutputCommand.cxx
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+/*
+ * Glue functions for controlling the audio outputs over the MPD
+ * protocol.  These functions perform extra validation on all
+ * parameters, because they might be from an untrusted source.
+ *
+ */
+
+#include "config.h"
+#include "OutputCommand.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+#include "PlayerControl.hxx"
+#include "MixerControl.hxx"
+#include "Idle.hxx"
+
+extern unsigned audio_output_state_version;
+
+bool
+audio_output_enable_index(unsigned idx)
+{
+	struct audio_output *ao;
+
+	if (idx >= audio_output_count())
+		return false;
+
+	ao = audio_output_get(idx);
+	if (ao->enabled)
+		return true;
+
+	ao->enabled = true;
+	idle_add(IDLE_OUTPUT);
+
+	ao->player_control->UpdateAudio();
+
+	++audio_output_state_version;
+
+	return true;
+}
+
+bool
+audio_output_disable_index(unsigned idx)
+{
+	struct audio_output *ao;
+
+	if (idx >= audio_output_count())
+		return false;
+
+	ao = audio_output_get(idx);
+	if (!ao->enabled)
+		return true;
+
+	ao->enabled = false;
+	idle_add(IDLE_OUTPUT);
+
+	Mixer *mixer = ao->mixer;
+	if (mixer != nullptr) {
+		mixer_close(mixer);
+		idle_add(IDLE_MIXER);
+	}
+
+	ao->player_control->UpdateAudio();
+
+	++audio_output_state_version;
+
+	return true;
+}
+
+bool
+audio_output_toggle_index(unsigned idx)
+{
+	struct audio_output *ao;
+
+	if (idx >= audio_output_count())
+		return false;
+
+	ao = audio_output_get(idx);
+	const bool enabled = ao->enabled = !ao->enabled;
+	idle_add(IDLE_OUTPUT);
+
+	if (!enabled) {
+		Mixer *mixer = ao->mixer;
+		if (mixer != nullptr) {
+			mixer_close(mixer);
+			idle_add(IDLE_MIXER);
+		}
+	}
+
+	ao->player_control->UpdateAudio();
+
+	++audio_output_state_version;
+
+	return true;
+}
diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx
new file mode 100644
index 000000000..4c44dff53
--- /dev/null
+++ b/src/output/OutputCommand.hxx
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*
+ * Glue functions for controlling the audio outputs over the MPD
+ * protocol.  These functions perform extra validation on all
+ * parameters, because they might be from an untrusted source.
+ *
+ */
+
+#ifndef MPD_OUTPUT_COMMAND_HXX
+#define MPD_OUTPUT_COMMAND_HXX
+
+/**
+ * Enables an audio output.  Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_enable_index(unsigned idx);
+
+/**
+ * Disables an audio output.  Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_disable_index(unsigned idx);
+
+/**
+ * Toggles an audio output.  Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_toggle_index(unsigned idx);
+
+#endif
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
new file mode 100644
index 000000000..b938754fd
--- /dev/null
+++ b/src/output/OutputControl.cxx
@@ -0,0 +1,325 @@
+
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputControl.hxx"
+#include "OutputThread.hxx"
+#include "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "OutputError.hxx"
+#include "MixerControl.hxx"
+#include "notify.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+/** after a failure, wait this number of seconds before
+    automatically reopening the device */
+static constexpr unsigned REOPEN_AFTER = 10;
+
+struct notify audio_output_client_notify;
+
+/**
+ * Waits for command completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void ao_command_wait(struct audio_output *ao)
+{
+	while (ao->command != AO_COMMAND_NONE) {
+		ao->mutex.unlock();
+		audio_output_client_notify.Wait();
+		ao->mutex.lock();
+	}
+}
+
+/**
+ * Sends a command to the #audio_output object, but does not wait for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void ao_command_async(struct audio_output *ao,
+			     enum audio_output_command cmd)
+{
+	assert(ao->command == AO_COMMAND_NONE);
+	ao->command = cmd;
+	ao->cond.signal();
+}
+
+/**
+ * Sends a command to the #audio_output object and waits for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void
+ao_command(struct audio_output *ao, enum audio_output_command cmd)
+{
+	ao_command_async(ao, cmd);
+	ao_command_wait(ao);
+}
+
+/**
+ * Lock the #audio_output object and execute the command
+ * synchronously.
+ */
+static void
+ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
+{
+	const ScopeLock protect(ao->mutex);
+	ao_command(ao, cmd);
+}
+
+void
+audio_output_set_replay_gain_mode(struct audio_output *ao,
+				  ReplayGainMode mode)
+{
+	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);
+}
+
+void
+audio_output_enable(struct audio_output *ao)
+{
+	if (!ao->thread.IsDefined()) {
+		if (ao->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;
+			return;
+		}
+
+		audio_output_thread_start(ao);
+	}
+
+	ao_lock_command(ao, AO_COMMAND_ENABLE);
+}
+
+void
+audio_output_disable(struct audio_output *ao)
+{
+	if (!ao->thread.IsDefined()) {
+		if (ao->plugin->disable == nullptr)
+			ao->really_enabled = false;
+		else
+			/* if there's no thread yet, the device cannot
+			   be enabled */
+			assert(!ao->really_enabled);
+
+		return;
+	}
+
+	ao_lock_command(ao, AO_COMMAND_DISABLE);
+}
+
+/**
+ * Object must be locked (and unlocked) by the caller.
+ */
+static bool
+audio_output_open(struct audio_output *ao,
+		  const AudioFormat audio_format,
+		  const MusicPipe &mp)
+{
+	bool open;
+
+	assert(ao != nullptr);
+	assert(ao->allow_play);
+	assert(audio_format.IsValid());
+
+	ao->fail_timer.Reset();
+
+	if (ao->open && audio_format == ao->in_audio_format) {
+		assert(ao->pipe == &mp ||
+		       (ao->always_on && ao->pause));
+
+		if (ao->pause) {
+			ao->chunk = nullptr;
+			ao->pipe = &mp;
+
+			/* unpause with the CANCEL command; this is a
+			   hack, but suits well for forcing the thread
+			   to leave the ao_pause() thread, and we need
+			   to flush the device buffer anyway */
+
+			/* we're not using audio_output_cancel() here,
+			   because that function is asynchronous */
+			ao_command(ao, AO_COMMAND_CANCEL);
+		}
+
+		return true;
+	}
+
+	ao->in_audio_format = audio_format;
+	ao->chunk = nullptr;
+
+	ao->pipe = &mp;
+
+	if (!ao->thread.IsDefined())
+		audio_output_thread_start(ao);
+
+	ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+	open = ao->open;
+
+	if (open && ao->mixer != nullptr) {
+		Error error;
+		if (!mixer_open(ao->mixer, error))
+			FormatWarning(output_domain,
+				      "Failed to open mixer for '%s'",
+				      ao->name);
+	}
+
+	return open;
+}
+
+/**
+ * Same as audio_output_close(), but expects the lock to be held by
+ * the caller.
+ */
+static void
+audio_output_close_locked(struct audio_output *ao)
+{
+	assert(ao != nullptr);
+	assert(ao->allow_play);
+
+	if (ao->mixer != nullptr)
+		mixer_auto_close(ao->mixer);
+
+	assert(!ao->open || !ao->fail_timer.IsDefined());
+
+	if (ao->open)
+		ao_command(ao, AO_COMMAND_CLOSE);
+	else
+		ao->fail_timer.Reset();
+}
+
+bool
+audio_output_update(struct audio_output *ao,
+		    const AudioFormat audio_format,
+		    const MusicPipe &mp)
+{
+	const ScopeLock protect(ao->mutex);
+
+	if (ao->enabled && ao->really_enabled) {
+		if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) {
+			return audio_output_open(ao, audio_format, mp);
+		}
+	} else if (audio_output_is_open(ao))
+		audio_output_close_locked(ao);
+
+	return false;
+}
+
+void
+audio_output_play(struct audio_output *ao)
+{
+	const ScopeLock protect(ao->mutex);
+
+	assert(ao->allow_play);
+
+	if (audio_output_is_open(ao) && !ao->in_playback_loop &&
+	    !ao->woken_for_play) {
+		ao->woken_for_play = true;
+		ao->cond.signal();
+	}
+}
+
+void audio_output_pause(struct audio_output *ao)
+{
+	if (ao->mixer != nullptr && ao->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);
+
+	const ScopeLock protect(ao->mutex);
+
+	assert(ao->allow_play);
+	if (audio_output_is_open(ao))
+		ao_command_async(ao, AO_COMMAND_PAUSE);
+}
+
+void
+audio_output_drain_async(struct audio_output *ao)
+{
+	const ScopeLock protect(ao->mutex);
+
+	assert(ao->allow_play);
+	if (audio_output_is_open(ao))
+		ao_command_async(ao, AO_COMMAND_DRAIN);
+}
+
+void audio_output_cancel(struct audio_output *ao)
+{
+	const ScopeLock protect(ao->mutex);
+
+	if (audio_output_is_open(ao)) {
+		ao->allow_play = false;
+		ao_command_async(ao, AO_COMMAND_CANCEL);
+	}
+}
+
+void
+audio_output_allow_play(struct audio_output *ao)
+{
+	const ScopeLock protect(ao->mutex);
+
+	ao->allow_play = true;
+	if (audio_output_is_open(ao))
+		ao->cond.signal();
+}
+
+void
+audio_output_release(struct audio_output *ao)
+{
+	if (ao->always_on)
+		audio_output_pause(ao);
+	else
+		audio_output_close(ao);
+}
+
+void audio_output_close(struct audio_output *ao)
+{
+	assert(ao != nullptr);
+	assert(!ao->open || !ao->fail_timer.IsDefined());
+
+	const ScopeLock protect(ao->mutex);
+	audio_output_close_locked(ao);
+}
+
+void audio_output_finish(struct audio_output *ao)
+{
+	audio_output_close(ao);
+
+	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);
+}
diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx
new file mode 100644
index 000000000..7195412ef
--- /dev/null
+++ b/src/output/OutputControl.hxx
@@ -0,0 +1,94 @@
+/*
+ * 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_CONTROL_HXX
+#define MPD_OUTPUT_CONTROL_HXX
+
+#include "ReplayGainInfo.hxx"
+
+#include <stddef.h>
+
+struct audio_output;
+struct AudioFormat;
+struct config_param;
+class MusicPipe;
+
+void
+audio_output_set_replay_gain_mode(audio_output *ao,
+				  ReplayGainMode mode);
+
+/**
+ * Enables the device.
+ */
+void
+audio_output_enable(audio_output *ao);
+
+/**
+ * Disables the device.
+ */
+void
+audio_output_disable(audio_output *ao);
+
+/**
+ * Opens or closes the device, depending on the "enabled" flag.
+ *
+ * @return true if the device is open
+ */
+bool
+audio_output_update(audio_output *ao,
+		    AudioFormat audio_format,
+		    const MusicPipe &mp);
+
+void
+audio_output_play(audio_output *ao);
+
+void
+audio_output_pause(audio_output *ao);
+
+void
+audio_output_drain_async(audio_output *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(audio_output *ao);
+
+/**
+ * Set the "allow_play" and signal the thread.
+ */
+void
+audio_output_allow_play(audio_output *ao);
+
+void
+audio_output_close(audio_output *ao);
+
+/**
+ * Closes the audio output, but if the "always_on" flag is set, put it
+ * into pause mode instead.
+ */
+void
+audio_output_release(audio_output *ao);
+
+void
+audio_output_finish(audio_output *ao);
+
+#endif
diff --git a/src/output/OutputError.cxx b/src/output/OutputError.cxx
new file mode 100644
index 000000000..9d4128912
--- /dev/null
+++ b/src/output/OutputError.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "OutputError.hxx"
+#include "util/Domain.hxx"
+
+const Domain output_domain("output");
diff --git a/src/output/OutputError.hxx b/src/output/OutputError.hxx
new file mode 100644
index 000000000..e3a20142f
--- /dev/null
+++ b/src/output/OutputError.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_ERROR_HXX
+#define MPD_OUTPUT_ERROR_HXX
+
+extern const class Domain output_domain;
+
+#endif
diff --git a/src/output/OutputFinish.cxx b/src/output/OutputFinish.cxx
new file mode 100644
index 000000000..43f0dd1ec
--- /dev/null
+++ b/src/output/OutputFinish.cxx
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputInternal.hxx"
+#include "OutputPlugin.hxx"
+#include "MixerControl.hxx"
+#include "FilterInternal.hxx"
+
+#include <assert.h>
+
+void
+ao_base_finish(struct audio_output *ao)
+{
+	assert(!ao->open);
+	assert(!ao->fail_timer.IsDefined());
+	assert(!ao->thread.IsDefined());
+
+	if (ao->mixer != nullptr)
+		mixer_free(ao->mixer);
+
+	delete ao->replay_gain_filter;
+	delete ao->other_replay_gain_filter;
+	delete ao->filter;
+}
+
+void
+audio_output_free(struct audio_output *ao)
+{
+	assert(!ao->open);
+	assert(!ao->fail_timer.IsDefined());
+	assert(!ao->thread.IsDefined());
+
+	ao_plugin_finish(ao);
+}
diff --git a/src/output/OutputInit.cxx b/src/output/OutputInit.cxx
new file mode 100644
index 000000000..f5b1bdc81
--- /dev/null
+++ b/src/output/OutputInit.cxx
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputInternal.hxx"
+#include "OutputList.hxx"
+#include "OutputError.hxx"
+#include "OutputAPI.hxx"
+#include "FilterConfig.hxx"
+#include "AudioParser.hxx"
+#include "MixerList.hxx"
+#include "MixerType.hxx"
+#include "MixerControl.hxx"
+#include "mixer/SoftwareMixerPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "FilterRegistry.hxx"
+#include "filter/AutoConvertFilterPlugin.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "filter/ChainFilterPlugin.hxx"
+#include "ConfigError.hxx"
+#include "ConfigGlobal.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#define AUDIO_OUTPUT_TYPE	"type"
+#define AUDIO_OUTPUT_NAME	"name"
+#define AUDIO_OUTPUT_FORMAT	"format"
+#define AUDIO_FILTERS		"filters"
+
+static const struct audio_output_plugin *
+audio_output_detect(Error &error)
+{
+	LogDefault(output_domain, "Attempt to detect audio output device");
+
+	audio_output_plugins_for_each(plugin) {
+		if (plugin->test_default_device == nullptr)
+			continue;
+
+		FormatDefault(output_domain,
+			      "Attempting to detect a %s audio device",
+			      plugin->name);
+		if (ao_plugin_test_default_device(plugin))
+			return plugin;
+	}
+
+	error.Set(output_domain, "Unable to detect an audio device");
+	return nullptr;
+}
+
+/**
+ * Determines the mixer type which should be used for the specified
+ * configuration block.
+ *
+ * This handles the deprecated options mixer_type (global) and
+ * mixer_enabled, if the mixer_type setting is not configured.
+ */
+gcc_pure
+static enum mixer_type
+audio_output_mixer_type(const config_param &param)
+{
+	/* read the local "mixer_type" setting */
+	const char *p = param.GetBlockValue("mixer_type");
+	if (p != nullptr)
+		return mixer_type_parse(p);
+
+	/* try the local "mixer_enabled" setting next (deprecated) */
+	if (!param.GetBlockValue("mixer_enabled", true))
+		return MIXER_TYPE_NONE;
+
+	/* fall back to the global "mixer_type" setting (also
+	   deprecated) */
+	return mixer_type_parse(config_get_string(CONF_MIXER_TYPE,
+						  "hardware"));
+}
+
+static Mixer *
+audio_output_load_mixer(struct audio_output *ao,
+			const config_param &param,
+			const struct mixer_plugin *plugin,
+			Filter &filter_chain,
+			Error &error)
+{
+	Mixer *mixer;
+
+	switch (audio_output_mixer_type(param)) {
+	case MIXER_TYPE_NONE:
+	case MIXER_TYPE_UNKNOWN:
+		return nullptr;
+
+	case MIXER_TYPE_HARDWARE:
+		if (plugin == nullptr)
+			return nullptr;
+
+		return mixer_new(plugin, ao, param, error);
+
+	case MIXER_TYPE_SOFTWARE:
+		mixer = mixer_new(&software_mixer_plugin, nullptr,
+				  config_param(),
+				  IgnoreError());
+		assert(mixer != nullptr);
+
+		filter_chain_append(filter_chain, "software_mixer",
+				    software_mixer_get_filter(mixer));
+		return mixer;
+	}
+
+	assert(false);
+	gcc_unreachable();
+}
+
+bool
+ao_base_init(struct audio_output *ao,
+	     const struct audio_output_plugin *plugin,
+	     const config_param &param, Error &error)
+{
+	assert(ao != nullptr);
+	assert(plugin != nullptr);
+	assert(plugin->finish != nullptr);
+	assert(plugin->open != nullptr);
+	assert(plugin->close != nullptr);
+	assert(plugin->play != nullptr);
+
+	if (!param.IsNull()) {
+		ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
+		if (ao->name == nullptr) {
+			error.Set(config_domain,
+				  "Missing \"name\" configuration");
+			return false;
+		}
+
+		const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
+		if (p != nullptr) {
+			bool success =
+				audio_format_parse(ao->config_audio_format,
+						   p, true, error);
+			if (!success)
+				return false;
+		} else
+			ao->config_audio_format.Clear();
+	} else {
+		ao->name = "default detected output";
+
+		ao->config_audio_format.Clear();
+	}
+
+	ao->plugin = plugin;
+	ao->tags = param.GetBlockValue("tags", true);
+	ao->always_on = param.GetBlockValue("always_on", false);
+	ao->enabled = param.GetBlockValue("enabled", true);
+	ao->really_enabled = false;
+	ao->open = false;
+	ao->pause = false;
+	ao->allow_play = true;
+	ao->in_playback_loop = false;
+	ao->woken_for_play = false;
+
+	/* set up the filter chain */
+
+	ao->filter = filter_chain_new();
+	assert(ao->filter != nullptr);
+
+	/* create the normalization filter (if configured) */
+
+	if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
+		Filter *normalize_filter =
+			filter_new(&normalize_filter_plugin, config_param(),
+				   IgnoreError());
+		assert(normalize_filter != nullptr);
+
+		filter_chain_append(*ao->filter, "normalize",
+				    autoconvert_filter_new(normalize_filter));
+	}
+
+	Error filter_error;
+	filter_chain_parse(*ao->filter,
+			   param.GetBlockValue(AUDIO_FILTERS, ""),
+			   filter_error);
+
+	// It's not really fatal - Part of the filter chain has been set up already
+	// and even an empty one will work (if only with unexpected behaviour)
+	if (filter_error.IsDefined())
+		FormatError(filter_error,
+			    "Failed to initialize filter chain for '%s'",
+			    ao->name);
+
+	ao->command = AO_COMMAND_NONE;
+
+	ao->mixer = nullptr;
+	ao->replay_gain_filter = nullptr;
+	ao->other_replay_gain_filter = nullptr;
+
+	/* done */
+
+	return true;
+}
+
+static bool
+audio_output_setup(struct audio_output *ao, const config_param &param,
+		   Error &error)
+{
+
+	/* create the replay_gain filter */
+
+	const char *replay_gain_handler =
+		param.GetBlockValue("replay_gain_handler", "software");
+
+	if (strcmp(replay_gain_handler, "none") != 0) {
+		ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+						    param, IgnoreError());
+		assert(ao->replay_gain_filter != nullptr);
+
+		ao->replay_gain_serial = 0;
+
+		ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+							  param,
+							  IgnoreError());
+		assert(ao->other_replay_gain_filter != nullptr);
+
+		ao->other_replay_gain_serial = 0;
+	} else {
+		ao->replay_gain_filter = nullptr;
+		ao->other_replay_gain_filter = nullptr;
+	}
+
+	/* set up the mixer */
+
+	Error mixer_error;
+	ao->mixer = audio_output_load_mixer(ao, param,
+					    ao->plugin->mixer_plugin,
+					    *ao->filter, mixer_error);
+	if (ao->mixer == nullptr && mixer_error.IsDefined())
+		FormatError(mixer_error,
+			    "Failed to initialize hardware mixer for '%s'",
+			    ao->name);
+
+	/* use the hardware mixer for replay gain? */
+
+	if (strcmp(replay_gain_handler, "mixer") == 0) {
+		if (ao->mixer != nullptr)
+			replay_gain_filter_set_mixer(ao->replay_gain_filter,
+						     ao->mixer, 100);
+		else
+			FormatError(output_domain,
+				    "No such mixer for output '%s'", ao->name);
+	} else if (strcmp(replay_gain_handler, "software") != 0 &&
+		   ao->replay_gain_filter != nullptr) {
+		error.Set(config_domain,
+			  "Invalid \"replay_gain_handler\" value");
+		return false;
+	}
+
+	/* the "convert" filter must be the last one in the chain */
+
+	ao->convert_filter = filter_new(&convert_filter_plugin, config_param(),
+					IgnoreError());
+	assert(ao->convert_filter != nullptr);
+
+	filter_chain_append(*ao->filter, "convert", ao->convert_filter);
+
+	return true;
+}
+
+struct audio_output *
+audio_output_new(const config_param &param,
+		 PlayerControl &pc,
+		 Error &error)
+{
+	const struct audio_output_plugin *plugin;
+
+	if (!param.IsNull()) {
+		const char *p;
+
+		p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
+		if (p == nullptr) {
+			error.Set(config_domain,
+				  "Missing \"type\" configuration");
+			return nullptr;
+		}
+
+		plugin = audio_output_plugin_get(p);
+		if (plugin == nullptr) {
+			error.Format(config_domain,
+				     "No such audio output plugin: %s", p);
+			return nullptr;
+		}
+	} else {
+		LogWarning(output_domain,
+			   "No 'audio_output' defined in config file");
+
+		plugin = audio_output_detect(error);
+		if (plugin == nullptr)
+			return nullptr;
+
+		FormatDefault(output_domain,
+			      "Successfully detected a %s audio device",
+			      plugin->name);
+	}
+
+	struct audio_output *ao = ao_plugin_init(plugin, param, error);
+	if (ao == nullptr)
+		return nullptr;
+
+	if (!audio_output_setup(ao, param, error)) {
+		ao_plugin_finish(ao);
+		return nullptr;
+	}
+
+	ao->player_control = &pc;
+	return ao;
+}
diff --git a/src/output/OutputInternal.hxx b/src/output/OutputInternal.hxx
new file mode 100644
index 000000000..18404dc01
--- /dev/null
+++ b/src/output/OutputInternal.hxx
@@ -0,0 +1,301 @@
+/*
+ * 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_INTERNAL_HXX
+#define MPD_OUTPUT_INTERNAL_HXX
+
+#include "AudioFormat.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "pcm/PcmDither.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "system/PeriodClock.hxx"
+
+class Error;
+class Filter;
+class MusicPipe;
+struct config_param;
+struct PlayerControl;
+
+enum audio_output_command {
+	AO_COMMAND_NONE = 0,
+	AO_COMMAND_ENABLE,
+	AO_COMMAND_DISABLE,
+	AO_COMMAND_OPEN,
+
+	/**
+	 * This command is invoked when the input audio format
+	 * changes.
+	 */
+	AO_COMMAND_REOPEN,
+
+	AO_COMMAND_CLOSE,
+	AO_COMMAND_PAUSE,
+
+	/**
+	 * Drains the internal (hardware) buffers of the device.  This
+	 * operation may take a while to complete.
+	 */
+	AO_COMMAND_DRAIN,
+
+	AO_COMMAND_CANCEL,
+	AO_COMMAND_KILL
+};
+
+struct audio_output {
+	/**
+	 * The device's configured display name.
+	 */
+	const char *name;
+
+	/**
+	 * The plugin which implements this output device.
+	 */
+	const struct audio_output_plugin *plugin;
+
+	/**
+	 * The #mixer object associated with this audio output device.
+	 * May be nullptr if none is available, or if software volume is
+	 * configured.
+	 */
+	class Mixer *mixer;
+
+	/**
+	 * Will this output receive tags from the decoder?  The
+	 * default is true, but it may be configured to false to
+	 * suppress sending tags to the output.
+	 */
+	bool tags;
+
+	/**
+	 * Shall this output always play something (i.e. silence),
+	 * even when playback is stopped?
+	 */
+	bool always_on;
+
+	/**
+	 * Has the user enabled this device?
+	 */
+	bool enabled;
+
+	/**
+	 * Is this device actually enabled, i.e. the "enable" method
+	 * has succeeded?
+	 */
+	bool really_enabled;
+
+	/**
+	 * Is the device (already) open and functional?
+	 *
+	 * This attribute may only be modified by the output thread.
+	 * It is protected with #mutex: write accesses inside the
+	 * output thread and read accesses outside of it may only be
+	 * performed while the lock is held.
+	 */
+	bool open;
+
+	/**
+	 * Is the device paused?  i.e. the output thread is in the
+	 * ao_pause() loop.
+	 */
+	bool pause;
+
+	/**
+	 * When this flag is set, the output thread will not do any
+	 * playback.  It will wait until the flag is cleared.
+	 *
+	 * This is used to synchronize the "clear" operation on the
+	 * shared music pipe during the CANCEL command.
+	 */
+	bool allow_play;
+
+	/**
+	 * True while the OutputThread is inside ao_play().  This
+	 * means the PlayerThread does not need to wake up the
+	 * OutputThread when new chunks are added to the MusicPipe,
+	 * because the OutputThread is already watching that.
+	 */
+	bool in_playback_loop;
+
+	/**
+	 * Has the OutputThread been woken up to play more chunks?
+	 * This is set by audio_output_play() and reset by ao_play()
+	 * to reduce the number of duplicate wakeups.
+	 */
+	bool woken_for_play;
+
+	/**
+	 * If not nullptr, the device has failed, and this timer is used
+	 * to estimate how long it should stay disabled (unless
+	 * explicitly reopened with "play").
+	 */
+	PeriodClock fail_timer;
+
+	/**
+	 * The configured audio format.
+	 */
+	AudioFormat config_audio_format;
+
+	/**
+	 * The audio_format in which audio data is received from the
+	 * player thread (which in turn receives it from the decoder).
+	 */
+	AudioFormat in_audio_format;
+
+	/**
+	 * The audio_format which is really sent to the device.  This
+	 * is basically config_audio_format (if configured) or
+	 * in_audio_format, but may have been modified by
+	 * plugin->open().
+	 */
+	AudioFormat out_audio_format;
+
+	/**
+	 * The buffer used to allocate the cross-fading result.
+	 */
+	PcmBuffer cross_fade_buffer;
+
+	/**
+	 * The dithering state for cross-fading two streams.
+	 */
+	PcmDither cross_fade_dither;
+
+	/**
+	 * The filter object of this audio output.  This is an
+	 * instance of chain_filter_plugin.
+	 */
+	Filter *filter;
+
+	/**
+	 * The replay_gain_filter_plugin instance of this audio
+	 * output.
+	 */
+	Filter *replay_gain_filter;
+
+	/**
+	 * The serial number of the last replay gain info.  0 means no
+	 * replay gain info was available.
+	 */
+	unsigned replay_gain_serial;
+
+	/**
+	 * The replay_gain_filter_plugin instance of this audio
+	 * output, to be applied to the second chunk during
+	 * cross-fading.
+	 */
+	Filter *other_replay_gain_filter;
+
+	/**
+	 * The serial number of the last replay gain info by the
+	 * "other" chunk during cross-fading.
+	 */
+	unsigned other_replay_gain_serial;
+
+	/**
+	 * The convert_filter_plugin instance of this audio output.
+	 * It is the last item in the filter chain, and is responsible
+	 * for converting the input data into the appropriate format
+	 * for this audio output.
+	 */
+	Filter *convert_filter;
+
+	/**
+	 * The thread handle, or nullptr if the output thread isn't
+	 * running.
+	 */
+	Thread thread;
+
+	/**
+	 * The next command to be performed by the output thread.
+	 */
+	enum audio_output_command command;
+
+	/**
+	 * The music pipe which provides music chunks to be played.
+	 */
+	const MusicPipe *pipe;
+
+	/**
+	 * This mutex protects #open, #fail_timer, #chunk and
+	 * #chunk_finished.
+	 */
+	Mutex mutex;
+
+	/**
+	 * This condition object wakes up the output thread after
+	 * #command has been set.
+	 */
+	Cond cond;
+
+	/**
+	 * The PlayerControl object which "owns" this output.  This
+	 * object is needed to signal command completion.
+	 */
+	PlayerControl *player_control;
+
+	/**
+	 * The #music_chunk which is currently being played.  All
+	 * chunks before this one may be returned to the
+	 * #music_buffer, because they are not going to be used by
+	 * this output anymore.
+	 */
+	const struct music_chunk *chunk;
+
+	/**
+	 * Has the output finished playing #chunk?
+	 */
+	bool chunk_finished;
+};
+
+/**
+ * Notify object used by the thread's client, i.e. we will send a
+ * notify signal to this object, expecting the caller to wait on it.
+ */
+extern struct notify audio_output_client_notify;
+
+static inline bool
+audio_output_is_open(const struct audio_output *ao)
+{
+	return ao->open;
+}
+
+static inline bool
+audio_output_command_is_finished(const struct audio_output *ao)
+{
+	return ao->command == AO_COMMAND_NONE;
+}
+
+struct audio_output *
+audio_output_new(const config_param &param,
+		 PlayerControl &pc,
+		 Error &error);
+
+bool
+ao_base_init(struct audio_output *ao,
+	     const struct audio_output_plugin *plugin,
+	     const config_param &param, Error &error);
+
+void
+ao_base_finish(struct audio_output *ao);
+
+void
+audio_output_free(struct audio_output *ao);
+
+#endif
diff --git a/src/output/OutputList.cxx b/src/output/OutputList.cxx
new file mode 100644
index 000000000..b914e6d2e
--- /dev/null
+++ b/src/output/OutputList.cxx
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputList.hxx"
+#include "OutputAPI.hxx"
+#include "plugins/AlsaOutputPlugin.hxx"
+#include "plugins/AoOutputPlugin.hxx"
+#include "plugins/FifoOutputPlugin.hxx"
+#include "plugins/HttpdOutputPlugin.hxx"
+#include "plugins/JackOutputPlugin.hxx"
+#include "plugins/NullOutputPlugin.hxx"
+#include "plugins/OpenALOutputPlugin.hxx"
+#include "plugins/OssOutputPlugin.hxx"
+#include "plugins/OSXOutputPlugin.hxx"
+#include "plugins/PipeOutputPlugin.hxx"
+#include "plugins/PulseOutputPlugin.hxx"
+#include "plugins/RecorderOutputPlugin.hxx"
+#include "plugins/RoarOutputPlugin.hxx"
+#include "plugins/ShoutOutputPlugin.hxx"
+#include "plugins/SolarisOutputPlugin.hxx"
+#include "plugins/WinmmOutputPlugin.hxx"
+
+#include <string.h>
+
+const struct audio_output_plugin *const audio_output_plugins[] = {
+#ifdef HAVE_SHOUT
+	&shout_output_plugin,
+#endif
+	&null_output_plugin,
+#ifdef HAVE_FIFO
+	&fifo_output_plugin,
+#endif
+#ifdef ENABLE_PIPE_OUTPUT
+	&pipe_output_plugin,
+#endif
+#ifdef HAVE_ALSA
+	&alsa_output_plugin,
+#endif
+#ifdef HAVE_ROAR
+	&roar_output_plugin,
+#endif
+#ifdef HAVE_AO
+	&ao_output_plugin,
+#endif
+#ifdef HAVE_OSS
+	&oss_output_plugin,
+#endif
+#ifdef HAVE_OPENAL
+	&openal_output_plugin,
+#endif
+#ifdef HAVE_OSX
+	&osx_output_plugin,
+#endif
+#ifdef ENABLE_SOLARIS_OUTPUT
+	&solaris_output_plugin,
+#endif
+#ifdef HAVE_PULSE
+	&pulse_output_plugin,
+#endif
+#ifdef HAVE_JACK
+	&jack_output_plugin,
+#endif
+#ifdef ENABLE_HTTPD_OUTPUT
+	&httpd_output_plugin,
+#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+	&recorder_output_plugin,
+#endif
+#ifdef ENABLE_WINMM_OUTPUT
+	&winmm_output_plugin,
+#endif
+	nullptr
+};
+
+const struct audio_output_plugin *
+audio_output_plugin_get(const char *name)
+{
+	audio_output_plugins_for_each(plugin)
+		if (strcmp(plugin->name, name) == 0)
+			return plugin;
+
+	return nullptr;
+}
diff --git a/src/output/OutputList.hxx b/src/output/OutputList.hxx
new file mode 100644
index 000000000..dfcf7487c
--- /dev/null
+++ b/src/output/OutputList.hxx
@@ -0,0 +1,33 @@
+/*
+ * 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_LIST_HXX
+#define MPD_OUTPUT_LIST_HXX
+
+extern const struct audio_output_plugin *const audio_output_plugins[];
+
+const struct audio_output_plugin *
+audio_output_plugin_get(const char *name);
+
+#define audio_output_plugins_for_each(plugin) \
+	for (const struct audio_output_plugin *plugin, \
+		*const*output_plugin_iterator = &audio_output_plugins[0]; \
+		(plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator)
+
+#endif
diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx
new file mode 100644
index 000000000..29fd6455a
--- /dev/null
+++ b/src/output/OutputPlugin.cxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputPlugin.hxx"
+#include "OutputInternal.hxx"
+
+struct audio_output *
+ao_plugin_init(const struct audio_output_plugin *plugin,
+	       const config_param &param,
+	       Error &error)
+{
+	assert(plugin != nullptr);
+	assert(plugin->init != nullptr);
+
+	return plugin->init(param, error);
+}
+
+void
+ao_plugin_finish(struct audio_output *ao)
+{
+	ao->plugin->finish(ao);
+}
+
+bool
+ao_plugin_enable(struct audio_output *ao, Error &error_r)
+{
+	return ao->plugin->enable != nullptr
+		? ao->plugin->enable(ao, error_r)
+		: true;
+}
+
+void
+ao_plugin_disable(struct audio_output *ao)
+{
+	if (ao->plugin->disable != nullptr)
+		ao->plugin->disable(ao);
+}
+
+bool
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
+	       Error &error)
+{
+	return ao->plugin->open(ao, audio_format, error);
+}
+
+void
+ao_plugin_close(struct audio_output *ao)
+{
+	ao->plugin->close(ao);
+}
+
+unsigned
+ao_plugin_delay(struct audio_output *ao)
+{
+	return ao->plugin->delay != nullptr
+		? ao->plugin->delay(ao)
+		: 0;
+}
+
+void
+ao_plugin_send_tag(struct audio_output *ao, const Tag *tag)
+{
+	if (ao->plugin->send_tag != nullptr)
+		ao->plugin->send_tag(ao, tag);
+}
+
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+	       Error &error)
+{
+	return ao->plugin->play(ao, chunk, size, error);
+}
+
+void
+ao_plugin_drain(struct audio_output *ao)
+{
+	if (ao->plugin->drain != nullptr)
+		ao->plugin->drain(ao);
+}
+
+void
+ao_plugin_cancel(struct audio_output *ao)
+{
+	if (ao->plugin->cancel != nullptr)
+		ao->plugin->cancel(ao);
+}
+
+bool
+ao_plugin_pause(struct audio_output *ao)
+{
+	return ao->plugin->pause != nullptr && ao->plugin->pause(ao);
+}
diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx
new file mode 100644
index 000000000..7d77c92b2
--- /dev/null
+++ b/src/output/OutputPlugin.hxx
@@ -0,0 +1,202 @@
+/*
+ * 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_PLUGIN_HXX
+#define MPD_OUTPUT_PLUGIN_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+struct config_param;
+struct AudioFormat;
+struct Tag;
+class Error;
+
+/**
+ * A plugin which controls an audio output device.
+ */
+struct audio_output_plugin {
+	/**
+	 * the plugin's name
+	 */
+	const char *name;
+
+	/**
+	 * Test if this plugin can provide a default output, in case
+	 * none has been configured.  This method is optional.
+	 */
+	bool (*test_default_device)(void);
+
+	/**
+	 * Configure and initialize the device, but do not open it
+	 * yet.
+	 *
+	 * @param param the configuration section, or nullptr if there is
+	 * no configuration
+	 * @return nullptr on error, or an opaque pointer to the plugin's
+	 * data
+	 */
+	struct audio_output *(*init)(const config_param &param,
+				     Error &error);
+
+	/**
+	 * Free resources allocated by this device.
+	 */
+	void (*finish)(struct audio_output *data);
+
+	/**
+	 * Enable the device.  This may allocate resources, preparing
+	 * for the device to be opened.  Enabling a device cannot
+	 * fail: if an error occurs during that, it should be reported
+	 * by the open() method.
+	 *
+	 * @return true on success, false on error
+	 */
+	bool (*enable)(struct audio_output *data, Error &error);
+
+	/**
+	 * Disables the device.  It is closed before this method is
+	 * called.
+	 */
+	void (*disable)(struct audio_output *data);
+
+	/**
+	 * Really open the device.
+	 *
+	 * @param audio_format the audio format in which data is going
+	 * to be delivered; may be modified by the plugin
+	 */
+	bool (*open)(struct audio_output *data, AudioFormat &audio_format,
+		     Error &error);
+
+	/**
+	 * Close the device.
+	 */
+	void (*close)(struct audio_output *data);
+
+	/**
+	 * Returns a positive number if the output thread shall delay
+	 * the next call to play() or pause().  This should be
+	 * implemented instead of doing a sleep inside the plugin,
+	 * because this allows MPD to listen to commands meanwhile.
+	 *
+	 * @return the number of milliseconds to wait
+	 */
+	unsigned (*delay)(struct audio_output *data);
+
+	/**
+	 * Display metadata for the next chunk.  Optional method,
+	 * because not all devices can display metadata.
+	 */
+	void (*send_tag)(struct audio_output *data, const Tag *tag);
+
+	/**
+	 * Play a chunk of audio data.
+	 *
+	 * @return the number of bytes played, or 0 on error
+	 */
+	size_t (*play)(struct audio_output *data,
+		       const void *chunk, size_t size,
+		       Error &error);
+
+	/**
+	 * Wait until the device has finished playing.
+	 */
+	void (*drain)(struct audio_output *data);
+
+	/**
+	 * Try to cancel data which may still be in the device's
+	 * buffers.
+	 */
+	void (*cancel)(struct audio_output *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 then), true
+	 * for continue to pause
+	 */
+	bool (*pause)(struct audio_output *data);
+
+	/**
+	 * The mixer plugin associated with this output plugin.  This
+	 * may be nullptr if no mixer plugin is implemented.  When
+	 * created, this mixer plugin gets the same #config_param as
+	 * this audio output device.
+	 */
+	const struct mixer_plugin *mixer_plugin;
+};
+
+static inline bool
+ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
+{
+	return plugin->test_default_device != nullptr
+		? plugin->test_default_device()
+		: false;
+}
+
+gcc_malloc
+struct audio_output *
+ao_plugin_init(const struct audio_output_plugin *plugin,
+	       const config_param &param,
+	       Error &error);
+
+void
+ao_plugin_finish(struct audio_output *ao);
+
+bool
+ao_plugin_enable(struct audio_output *ao, Error &error);
+
+void
+ao_plugin_disable(struct audio_output *ao);
+
+bool
+ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
+	       Error &error);
+
+void
+ao_plugin_close(struct audio_output *ao);
+
+gcc_pure
+unsigned
+ao_plugin_delay(struct audio_output *ao);
+
+void
+ao_plugin_send_tag(struct audio_output *ao, const Tag *tag);
+
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+	       Error &error);
+
+void
+ao_plugin_drain(struct audio_output *ao);
+
+void
+ao_plugin_cancel(struct audio_output *ao);
+
+bool
+ao_plugin_pause(struct audio_output *ao);
+
+#endif
diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx
new file mode 100644
index 000000000..ee4424df2
--- /dev/null
+++ b/src/output/OutputPrint.cxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#include "config.h"
+#include "OutputPrint.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+#include "Client.hxx"
+
+void
+printAudioDevices(Client &client)
+{
+	const unsigned n = audio_output_count();
+
+	for (unsigned i = 0; i < n; ++i) {
+		const struct audio_output *ao = audio_output_get(i);
+
+		client_printf(client,
+			      "outputid: %i\n"
+			      "outputname: %s\n"
+			      "outputenabled: %i\n",
+			      i, ao->name, ao->enabled);
+	}
+}
diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx
new file mode 100644
index 000000000..2d94226fd
--- /dev/null
+++ b/src/output/OutputPrint.hxx
@@ -0,0 +1,34 @@
+
+/*
+ * 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.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#ifndef MPD_OUTPUT_PRINT_HXX
+#define MPD_OUTPUT_PRINT_HXX
+
+class Client;
+
+void
+printAudioDevices(Client &client);
+
+#endif
diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx
new file mode 100644
index 000000000..1141abeda
--- /dev/null
+++ b/src/output/OutputState.cxx
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "OutputState.hxx"
+#include "OutputAll.hxx"
+#include "OutputInternal.hxx"
+#include "OutputError.hxx"
+#include "Log.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#define AUDIO_DEVICE_STATE "audio_device_state:"
+
+unsigned audio_output_state_version;
+
+void
+audio_output_state_save(FILE *fp)
+{
+	unsigned n = audio_output_count();
+
+	assert(n > 0);
+
+	for (unsigned i = 0; i < n; ++i) {
+		const struct audio_output *ao = audio_output_get(i);
+
+		fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
+			ao->enabled, ao->name);
+	}
+}
+
+bool
+audio_output_state_read(const char *line)
+{
+	long value;
+	char *endptr;
+	const char *name;
+	struct audio_output *ao;
+
+	if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
+		return false;
+
+	line += sizeof(AUDIO_DEVICE_STATE) - 1;
+
+	value = strtol(line, &endptr, 10);
+	if (*endptr != ':' || (value != 0 && value != 1))
+		return false;
+
+	if (value != 0)
+		/* state is "enabled": no-op */
+		return true;
+
+	name = endptr + 1;
+	ao = audio_output_find(name);
+	if (ao == NULL) {
+		FormatDebug(output_domain,
+			    "Ignoring device state for '%s'", name);
+		return true;
+	}
+
+	ao->enabled = false;
+	return true;
+}
+
+unsigned
+audio_output_state_get_version(void)
+{
+	return audio_output_state_version;
+}
diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx
new file mode 100644
index 000000000..a68180ae4
--- /dev/null
+++ b/src/output/OutputState.hxx
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#ifndef MPD_OUTPUT_STATE_HXX
+#define MPD_OUTPUT_STATE_HXX
+
+#include <stdio.h>
+
+bool
+audio_output_state_read(const char *line);
+
+void
+audio_output_state_save(FILE *fp);
+
+/**
+ * Generates a version number for the current state of the audio
+ * outputs.  This is used by timer_save_state_file() to determine
+ * whether the state has changed and the state file should be saved.
+ */
+unsigned
+audio_output_state_get_version(void);
+
+#endif
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
new file mode 100644
index 000000000..f7e28b5f4
--- /dev/null
+++ b/src/output/OutputThread.cxx
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputThread.hxx"
+#include "OutputInternal.hxx"
+#include "OutputAPI.hxx"
+#include "OutputError.hxx"
+#include "pcm/PcmMix.hxx"
+#include "notify.hxx"
+#include "FilterInternal.hxx"
+#include "filter/ConvertFilterPlugin.hxx"
+#include "filter/ReplayGainFilterPlugin.hxx"
+#include "PlayerControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "thread/Util.hxx"
+#include "thread/Name.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+#include <string.h>
+
+static void ao_command_finished(struct audio_output *ao)
+{
+	assert(ao->command != AO_COMMAND_NONE);
+	ao->command = AO_COMMAND_NONE;
+
+	ao->mutex.unlock();
+	audio_output_client_notify.Signal();
+	ao->mutex.lock();
+}
+
+static bool
+ao_enable(struct audio_output *ao)
+{
+	Error error;
+	bool success;
+
+	if (ao->really_enabled)
+		return true;
+
+	ao->mutex.unlock();
+	success = ao_plugin_enable(ao, error);
+	ao->mutex.lock();
+	if (!success) {
+		FormatError(error,
+			    "Failed to enable \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
+		return false;
+	}
+
+	ao->really_enabled = true;
+	return true;
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain);
+
+static void
+ao_disable(struct audio_output *ao)
+{
+	if (ao->open)
+		ao_close(ao, false);
+
+	if (ao->really_enabled) {
+		ao->really_enabled = false;
+
+		ao->mutex.unlock();
+		ao_plugin_disable(ao);
+		ao->mutex.lock();
+	}
+}
+
+static AudioFormat
+ao_filter_open(struct audio_output *ao, AudioFormat &format,
+	       Error &error_r)
+{
+	assert(format.IsValid());
+
+	/* the replay_gain filter cannot fail here */
+	if (ao->replay_gain_filter != nullptr &&
+	    !ao->replay_gain_filter->Open(format, error_r).IsDefined())
+		return AudioFormat::Undefined();
+
+	if (ao->other_replay_gain_filter != nullptr &&
+	    !ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) {
+		if (ao->replay_gain_filter != nullptr)
+			ao->replay_gain_filter->Close();
+		return AudioFormat::Undefined();
+	}
+
+	const AudioFormat af = ao->filter->Open(format, error_r);
+	if (!af.IsDefined()) {
+		if (ao->replay_gain_filter != nullptr)
+			ao->replay_gain_filter->Close();
+		if (ao->other_replay_gain_filter != nullptr)
+			ao->other_replay_gain_filter->Close();
+	}
+
+	return af;
+}
+
+static void
+ao_filter_close(struct audio_output *ao)
+{
+	if (ao->replay_gain_filter != nullptr)
+		ao->replay_gain_filter->Close();
+	if (ao->other_replay_gain_filter != nullptr)
+		ao->other_replay_gain_filter->Close();
+
+	ao->filter->Close();
+}
+
+static void
+ao_open(struct audio_output *ao)
+{
+	bool success;
+	Error error;
+	struct audio_format_string af_string;
+
+	assert(!ao->open);
+	assert(ao->pipe != nullptr);
+	assert(ao->chunk == nullptr);
+	assert(ao->in_audio_format.IsValid());
+
+	ao->fail_timer.Reset();
+
+	/* enable the device (just in case the last enable has failed) */
+
+	if (!ao_enable(ao))
+		/* still no luck */
+		return;
+
+	/* open the filter */
+
+	const AudioFormat filter_audio_format =
+		ao_filter_open(ao, ao->in_audio_format, error);
+	if (!filter_audio_format.IsDefined()) {
+		FormatError(error, "Failed to open filter for \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
+
+		ao->fail_timer.Update();
+		return;
+	}
+
+	assert(filter_audio_format.IsValid());
+
+	ao->out_audio_format = filter_audio_format;
+	ao->out_audio_format.ApplyMask(ao->config_audio_format);
+
+	ao->mutex.unlock();
+	success = ao_plugin_open(ao, ao->out_audio_format, error);
+	ao->mutex.lock();
+
+	assert(!ao->open);
+
+	if (!success) {
+		FormatError(error, "Failed to open \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
+
+		ao_filter_close(ao);
+		ao->fail_timer.Update();
+		return;
+	}
+
+	if (!convert_filter_set(ao->convert_filter, ao->out_audio_format,
+				error)) {
+		FormatError(error, "Failed to convert for \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
+
+		ao_filter_close(ao);
+		ao->fail_timer.Update();
+		return;
+	}
+
+	ao->open = true;
+
+	FormatDebug(output_domain,
+		    "opened plugin=%s name=\"%s\" audio_format=%s",
+		    ao->plugin->name, ao->name,
+		    audio_format_to_string(ao->out_audio_format, &af_string));
+
+	if (ao->in_audio_format != ao->out_audio_format)
+		FormatDebug(output_domain, "converting from %s",
+			    audio_format_to_string(ao->in_audio_format,
+						   &af_string));
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain)
+{
+	assert(ao->open);
+
+	ao->pipe = nullptr;
+
+	ao->chunk = nullptr;
+	ao->open = false;
+
+	ao->mutex.unlock();
+
+	if (drain)
+		ao_plugin_drain(ao);
+	else
+		ao_plugin_cancel(ao);
+
+	ao_plugin_close(ao);
+	ao_filter_close(ao);
+
+	ao->mutex.lock();
+
+	FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
+		    ao->plugin->name, ao->name);
+}
+
+static void
+ao_reopen_filter(struct audio_output *ao)
+{
+	Error error;
+
+	ao_filter_close(ao);
+	const AudioFormat filter_audio_format =
+		ao_filter_open(ao, ao->in_audio_format, error);
+	if (!filter_audio_format.IsDefined() ||
+	    !convert_filter_set(ao->convert_filter, ao->out_audio_format,
+				error)) {
+		FormatError(error,
+			    "Failed to open filter for \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
+
+		/* this is a little code duplication fro ao_close(),
+		   but we cannot call this function because we must
+		   not call filter_close(ao->filter) again */
+
+		ao->pipe = nullptr;
+
+		ao->chunk = nullptr;
+		ao->open = false;
+		ao->fail_timer.Update();
+
+		ao->mutex.unlock();
+		ao_plugin_close(ao);
+		ao->mutex.lock();
+
+		return;
+	}
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+	if (!ao->config_audio_format.IsFullyDefined()) {
+		if (ao->open) {
+			const MusicPipe *mp = ao->pipe;
+			ao_close(ao, true);
+			ao->pipe = mp;
+		}
+
+		/* no audio format is configured: copy in->out, let
+		   the output's open() method determine the effective
+		   out_audio_format */
+		ao->out_audio_format = ao->in_audio_format;
+		ao->out_audio_format.ApplyMask(ao->config_audio_format);
+	}
+
+	if (ao->open)
+		/* the audio format has changed, and all filters have
+		   to be reconfigured */
+		ao_reopen_filter(ao);
+	else
+		ao_open(ao);
+}
+
+/**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a command
+ * was issued
+ */
+static bool
+ao_wait(struct audio_output *ao)
+{
+	while (true) {
+		unsigned delay = ao_plugin_delay(ao);
+		if (delay == 0)
+			return true;
+
+		(void)ao->cond.timed_wait(ao->mutex, delay);
+
+		if (ao->command != AO_COMMAND_NONE)
+			return false;
+	}
+}
+
+static const void *
+ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
+	      Filter *replay_gain_filter,
+	      unsigned *replay_gain_serial_p,
+	      size_t *length_r)
+{
+	assert(chunk != nullptr);
+	assert(!chunk->IsEmpty());
+	assert(chunk->CheckFormat(ao->in_audio_format));
+
+	const void *data = chunk->data;
+	size_t length = chunk->length;
+
+	(void)ao;
+
+	assert(length % ao->in_audio_format.GetFrameSize() == 0);
+
+	if (length > 0 && replay_gain_filter != nullptr) {
+		if (chunk->replay_gain_serial != *replay_gain_serial_p) {
+			replay_gain_filter_set_info(replay_gain_filter,
+						    chunk->replay_gain_serial != 0
+						    ? &chunk->replay_gain_info
+						    : nullptr);
+			*replay_gain_serial_p = chunk->replay_gain_serial;
+		}
+
+		Error error;
+		data = replay_gain_filter->FilterPCM(data, length,
+						     &length, error);
+		if (data == nullptr) {
+			FormatError(error, "\"%s\" [%s] failed to filter",
+				    ao->name, ao->plugin->name);
+			return nullptr;
+		}
+	}
+
+	*length_r = length;
+	return data;
+}
+
+static const void *
+ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
+		size_t *length_r)
+{
+	size_t length;
+	const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+					 &ao->replay_gain_serial, &length);
+	if (data == nullptr)
+		return nullptr;
+
+	if (length == 0) {
+		/* empty chunk, nothing to do */
+		*length_r = 0;
+		return data;
+	}
+
+	/* cross-fade */
+
+	if (chunk->other != nullptr) {
+		size_t other_length;
+		const void *other_data =
+			ao_chunk_data(ao, chunk->other,
+				      ao->other_replay_gain_filter,
+				      &ao->other_replay_gain_serial,
+				      &other_length);
+		if (other_data == nullptr)
+			return nullptr;
+
+		if (other_length == 0) {
+			*length_r = 0;
+			return data;
+		}
+
+		/* if the "other" chunk is longer, then that trailer
+		   is used as-is, without mixing; it is part of the
+		   "next" song being faded in, and if there's a rest,
+		   it means cross-fading ends here */
+
+		if (length > other_length)
+			length = other_length;
+
+		void *dest = ao->cross_fade_buffer.Get(other_length);
+		memcpy(dest, other_data, other_length);
+		if (!pcm_mix(ao->cross_fade_dither, dest, data, length,
+			     ao->in_audio_format.format,
+			     1.0 - chunk->mix_ratio)) {
+			FormatError(output_domain,
+				    "Cannot cross-fade format %s",
+				    sample_format_to_string(ao->in_audio_format.format));
+			return nullptr;
+		}
+
+		data = dest;
+		length = other_length;
+	}
+
+	/* apply filter chain */
+
+	Error error;
+	data = ao->filter->FilterPCM(data, length, &length, error);
+	if (data == nullptr) {
+		FormatError(error, "\"%s\" [%s] failed to filter",
+			    ao->name, ao->plugin->name);
+		return nullptr;
+	}
+
+	*length_r = length;
+	return data;
+}
+
+static bool
+ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
+{
+	assert(ao != nullptr);
+	assert(ao->filter != nullptr);
+
+	if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) {
+		ao->mutex.unlock();
+		ao_plugin_send_tag(ao, chunk->tag);
+		ao->mutex.lock();
+	}
+
+	size_t size;
+#if GCC_CHECK_VERSION(4,7)
+	/* workaround -Wmaybe-uninitialized false positive */
+	size = 0;
+#endif
+	const char *data = (const char *)ao_filter_chunk(ao, chunk, &size);
+	if (data == nullptr) {
+		ao_close(ao, false);
+
+		/* don't automatically reopen this device for 10
+		   seconds */
+		ao->fail_timer.Update();
+		return false;
+	}
+
+	Error error;
+
+	while (size > 0 && ao->command == AO_COMMAND_NONE) {
+		size_t nbytes;
+
+		if (!ao_wait(ao))
+			break;
+
+		ao->mutex.unlock();
+		nbytes = ao_plugin_play(ao, data, size, error);
+		ao->mutex.lock();
+		if (nbytes == 0) {
+			/* play()==0 means failure */
+			FormatError(error, "\"%s\" [%s] failed to play",
+				    ao->name, ao->plugin->name);
+
+			ao_close(ao, false);
+
+			/* don't automatically reopen this device for
+			   10 seconds */
+			assert(!ao->fail_timer.IsDefined());
+			ao->fail_timer.Update();
+
+			return false;
+		}
+
+		assert(nbytes <= size);
+		assert(nbytes % ao->out_audio_format.GetFrameSize() == 0);
+
+		data += nbytes;
+		size -= nbytes;
+	}
+
+	return true;
+}
+
+static const struct music_chunk *
+ao_next_chunk(struct audio_output *ao)
+{
+	return ao->chunk != nullptr
+		/* continue the previous play() call */
+		? ao->chunk->next
+		/* get the first chunk from the pipe */
+		: ao->pipe->Peek();
+}
+
+/**
+ * Plays all remaining chunks, until the tail of the pipe has been
+ * reached (and no more chunks are queued), or until a command is
+ * received.
+ *
+ * @return true if at least one chunk has been available, false if the
+ * tail of the pipe was already reached
+ */
+static bool
+ao_play(struct audio_output *ao)
+{
+	bool success;
+	const struct music_chunk *chunk;
+
+	assert(ao->pipe != nullptr);
+
+	chunk = ao_next_chunk(ao);
+	if (chunk == nullptr)
+		/* no chunk available */
+		return false;
+
+	ao->chunk_finished = false;
+
+	assert(!ao->in_playback_loop);
+	ao->in_playback_loop = true;
+
+	while (chunk != nullptr && ao->command == AO_COMMAND_NONE) {
+		assert(!ao->chunk_finished);
+
+		ao->chunk = chunk;
+
+		success = ao_play_chunk(ao, chunk);
+		if (!success) {
+			assert(ao->chunk == nullptr);
+			break;
+		}
+
+		assert(ao->chunk == chunk);
+		chunk = chunk->next;
+	}
+
+	assert(ao->in_playback_loop);
+	ao->in_playback_loop = false;
+
+	ao->chunk_finished = true;
+
+	ao->mutex.unlock();
+	ao->player_control->LockSignal();
+	ao->mutex.lock();
+
+	return true;
+}
+
+static void ao_pause(struct audio_output *ao)
+{
+	bool ret;
+
+	ao->mutex.unlock();
+	ao_plugin_cancel(ao);
+	ao->mutex.lock();
+
+	ao->pause = true;
+	ao_command_finished(ao);
+
+	do {
+		if (!ao_wait(ao))
+			break;
+
+		ao->mutex.unlock();
+		ret = ao_plugin_pause(ao);
+		ao->mutex.lock();
+
+		if (!ret) {
+			ao_close(ao, false);
+			break;
+		}
+	} while (ao->command == AO_COMMAND_NONE);
+
+	ao->pause = false;
+}
+
+static void
+audio_output_task(void *arg)
+{
+	struct audio_output *ao = (struct audio_output *)arg;
+
+	FormatThreadName("output:%s", ao->name);
+
+	SetThreadRealtime();
+
+	ao->mutex.lock();
+
+	while (1) {
+		switch (ao->command) {
+		case AO_COMMAND_NONE:
+			break;
+
+		case AO_COMMAND_ENABLE:
+			ao_enable(ao);
+			ao_command_finished(ao);
+			break;
+
+		case AO_COMMAND_DISABLE:
+			ao_disable(ao);
+			ao_command_finished(ao);
+			break;
+
+		case AO_COMMAND_OPEN:
+			ao_open(ao);
+			ao_command_finished(ao);
+			break;
+
+		case AO_COMMAND_REOPEN:
+			ao_reopen(ao);
+			ao_command_finished(ao);
+			break;
+
+		case AO_COMMAND_CLOSE:
+			assert(ao->open);
+			assert(ao->pipe != nullptr);
+
+			ao_close(ao, false);
+			ao_command_finished(ao);
+			break;
+
+		case AO_COMMAND_PAUSE:
+			if (!ao->open) {
+				/* the output has failed after
+				   audio_output_all_pause() has
+				   submitted the PAUSE command; bail
+				   out */
+				ao_command_finished(ao);
+				break;
+			}
+
+			ao_pause(ao);
+			/* don't "break" here: this might cause
+			   ao_play() to be called when command==CLOSE
+			   ends the paused state - "continue" checks
+			   the new command first */
+			continue;
+
+		case AO_COMMAND_DRAIN:
+			if (ao->open) {
+				assert(ao->chunk == nullptr);
+				assert(ao->pipe->Peek() == nullptr);
+
+				ao->mutex.unlock();
+				ao_plugin_drain(ao);
+				ao->mutex.lock();
+			}
+
+			ao_command_finished(ao);
+			continue;
+
+		case AO_COMMAND_CANCEL:
+			ao->chunk = nullptr;
+
+			if (ao->open) {
+				ao->mutex.unlock();
+				ao_plugin_cancel(ao);
+				ao->mutex.lock();
+			}
+
+			ao_command_finished(ao);
+			continue;
+
+		case AO_COMMAND_KILL:
+			ao->chunk = nullptr;
+			ao_command_finished(ao);
+			ao->mutex.unlock();
+			return;
+		}
+
+		if (ao->open && ao->allow_play && ao_play(ao))
+			/* don't wait for an event if there are more
+			   chunks in the pipe */
+			continue;
+
+		if (ao->command == AO_COMMAND_NONE) {
+			ao->woken_for_play = false;
+			ao->cond.wait(ao->mutex);
+		}
+	}
+}
+
+void audio_output_thread_start(struct audio_output *ao)
+{
+	assert(ao->command == AO_COMMAND_NONE);
+
+	Error error;
+	if (!ao->thread.Start(audio_output_task, ao, error))
+		FatalError(error);
+}
diff --git a/src/output/OutputThread.hxx b/src/output/OutputThread.hxx
new file mode 100644
index 000000000..1cdbd65f2
--- /dev/null
+++ b/src/output/OutputThread.hxx
@@ -0,0 +1,28 @@
+/*
+ * 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 audio_output;
+
+void
+audio_output_thread_start(audio_output *ao);
+
+#endif
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
deleted file mode 100644
index 66d5a28ae..000000000
--- a/src/output/PipeOutputPlugin.cxx
+++ /dev/null
@@ -1,147 +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.
- */
-
-#include "config.h"
-#include "PipeOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <string>
-
-#include <stdio.h>
-
-struct PipeOutput {
-	struct audio_output base;
-
-	std::string cmd;
-	FILE *fh;
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &pipe_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain pipe_output_domain("pipe_output");
-
-inline bool
-PipeOutput::Configure(const config_param &param, Error &error)
-{
-	cmd = param.GetBlockValue("command", "");
-	if (cmd.empty()) {
-		error.Set(config_domain,
-			  "No \"command\" parameter specified");
-		return false;
-	}
-
-	return true;
-}
-
-static struct audio_output *
-pipe_output_init(const config_param &param, Error &error)
-{
-	PipeOutput *pd = new PipeOutput();
-
-	if (!pd->Initialize(param, error)) {
-		delete pd;
-		return nullptr;
-	}
-
-	if (!pd->Configure(param, error)) {
-		pd->Deinitialize();
-		delete pd;
-		return nullptr;
-	}
-
-	return &pd->base;
-}
-
-static void
-pipe_output_finish(struct audio_output *ao)
-{
-	PipeOutput *pd = (PipeOutput *)ao;
-
-	pd->Deinitialize();
-	delete pd;
-}
-
-static bool
-pipe_output_open(struct audio_output *ao,
-		 gcc_unused AudioFormat &audio_format,
-		 Error &error)
-{
-	PipeOutput *pd = (PipeOutput *)ao;
-
-	pd->fh = popen(pd->cmd.c_str(), "w");
-	if (pd->fh == nullptr) {
-		error.FormatErrno("Error opening pipe \"%s\"",
-				  pd->cmd.c_str());
-		return false;
-	}
-
-	return true;
-}
-
-static void
-pipe_output_close(struct audio_output *ao)
-{
-	PipeOutput *pd = (PipeOutput *)ao;
-
-	pclose(pd->fh);
-}
-
-static size_t
-pipe_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		 Error &error)
-{
-	PipeOutput *pd = (PipeOutput *)ao;
-	size_t ret;
-
-	ret = fwrite(chunk, 1, size, pd->fh);
-	if (ret == 0)
-		error.SetErrno("Write error on pipe");
-
-	return ret;
-}
-
-const struct audio_output_plugin pipe_output_plugin = {
-	"pipe",
-	nullptr,
-	pipe_output_init,
-	pipe_output_finish,
-	nullptr,
-	nullptr,
-	pipe_output_open,
-	pipe_output_close,
-	nullptr,
-	nullptr,
-	pipe_output_play,
-	nullptr,
-	nullptr,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/PipeOutputPlugin.hxx
deleted file mode 100644
index 42b01b9f7..000000000
--- a/src/output/PipeOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_PIPE_OUTPUT_PLUGIN_HXX
-#define MPD_PIPE_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin pipe_output_plugin;
-
-#endif
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx
deleted file mode 100644
index dd1906f53..000000000
--- a/src/output/PulseOutputPlugin.cxx
+++ /dev/null
@@ -1,889 +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.
- */
-
-#include "config.h"
-#include "PulseOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "mixer/PulseMixerPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <pulse/thread-mainloop.h>
-#include <pulse/context.h>
-#include <pulse/stream.h>
-#include <pulse/introspect.h>
-#include <pulse/subscribe.h>
-#include <pulse/error.h>
-#include <pulse/version.h>
-
-#include <assert.h>
-#include <stddef.h>
-
-#define MPD_PULSE_NAME "Music Player Daemon"
-
-struct PulseOutput {
-	struct audio_output base;
-
-	const char *name;
-	const char *server;
-	const char *sink;
-
-	PulseMixer *mixer;
-
-	struct pa_threaded_mainloop *mainloop;
-	struct pa_context *context;
-	struct pa_stream *stream;
-
-	size_t writable;
-};
-
-static constexpr Domain pulse_output_domain("pulse_output");
-
-static void
-SetError(Error &error, pa_context *context, const char *msg)
-{
-	const int e = pa_context_errno(context);
-	error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
-}
-
-void
-pulse_output_lock(PulseOutput *po)
-{
-	pa_threaded_mainloop_lock(po->mainloop);
-}
-
-void
-pulse_output_unlock(PulseOutput *po)
-{
-	pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm)
-{
-	assert(po != nullptr);
-	assert(po->mixer == nullptr);
-	assert(pm != nullptr);
-
-	po->mixer = pm;
-
-	if (po->mainloop == nullptr)
-		return;
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (po->context != nullptr &&
-	    pa_context_get_state(po->context) == PA_CONTEXT_READY) {
-		pulse_mixer_on_connect(pm, po->context);
-
-		if (po->stream != nullptr &&
-		    pa_stream_get_state(po->stream) == PA_STREAM_READY)
-			pulse_mixer_on_change(pm, po->context, po->stream);
-	}
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm)
-{
-	assert(po != nullptr);
-	assert(pm != nullptr);
-	assert(po->mixer == pm);
-
-	po->mixer = nullptr;
-}
-
-bool
-pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume,
-			Error &error)
-{
-	pa_operation *o;
-
-	if (po->context == nullptr || po->stream == nullptr ||
-	    pa_stream_get_state(po->stream) != PA_STREAM_READY) {
-		error.Set(pulse_output_domain, "disconnected");
-		return false;
-	}
-
-	o = pa_context_set_sink_input_volume(po->context,
-					     pa_stream_get_index(po->stream),
-					     volume, nullptr, nullptr);
-	if (o == nullptr) {
-		SetError(error, po->context,
-			 "failed to set PulseAudio volume");
-		return false;
-	}
-
-	pa_operation_unref(o);
-	return true;
-}
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- *     unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- *     false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
-			 struct pa_operation *operation)
-{
-	pa_operation_state_t state;
-
-	assert(mainloop != nullptr);
-	assert(operation != nullptr);
-
-	state = pa_operation_get_state(operation);
-	while (state == PA_OPERATION_RUNNING) {
-		pa_threaded_mainloop_wait(mainloop);
-		state = pa_operation_get_state(operation);
-	}
-
-	pa_operation_unref(operation);
-
-	return state == PA_OPERATION_DONE;
-}
-
-/**
- * Callback function for stream operation.  It just sends a signal to
- * the caller thread, to wake pulse_wait_for_operation() up.
- */
-static void
-pulse_output_stream_success_cb(gcc_unused pa_stream *s,
-			       gcc_unused int success, void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-
-	pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_context_state_cb(struct pa_context *context, void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-
-	switch (pa_context_get_state(context)) {
-	case PA_CONTEXT_READY:
-		if (po->mixer != nullptr)
-			pulse_mixer_on_connect(po->mixer, context);
-
-		pa_threaded_mainloop_signal(po->mainloop, 0);
-		break;
-
-	case PA_CONTEXT_TERMINATED:
-	case PA_CONTEXT_FAILED:
-		if (po->mixer != nullptr)
-			pulse_mixer_on_disconnect(po->mixer);
-
-		/* the caller thread might be waiting for these
-		   states */
-		pa_threaded_mainloop_signal(po->mainloop, 0);
-		break;
-
-	case PA_CONTEXT_UNCONNECTED:
-	case PA_CONTEXT_CONNECTING:
-	case PA_CONTEXT_AUTHORIZING:
-	case PA_CONTEXT_SETTING_NAME:
-		break;
-	}
-}
-
-static void
-pulse_output_subscribe_cb(pa_context *context,
-			  pa_subscription_event_type_t t,
-			  uint32_t idx, void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-	pa_subscription_event_type_t facility =
-		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
-	pa_subscription_event_type_t type =
-		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
-
-	if (po->mixer != nullptr &&
-	    facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
-	    po->stream != nullptr &&
-	    pa_stream_get_state(po->stream) == PA_STREAM_READY &&
-	    idx == pa_stream_get_index(po->stream) &&
-	    (type == PA_SUBSCRIPTION_EVENT_NEW ||
-	     type == PA_SUBSCRIPTION_EVENT_CHANGE))
-		pulse_mixer_on_change(po->mixer, context, po->stream);
-}
-
-/**
- * Attempt to connect asynchronously to the PulseAudio server.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_connect(PulseOutput *po, Error &error)
-{
-	assert(po != nullptr);
-	assert(po->context != nullptr);
-
-	if (pa_context_connect(po->context, po->server,
-			       (pa_context_flags_t)0, nullptr) < 0) {
-		SetError(error, po->context,
-			 "pa_context_connect() has failed");
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * Frees and clears the stream.
- */
-static void
-pulse_output_delete_stream(PulseOutput *po)
-{
-	assert(po != nullptr);
-	assert(po->stream != nullptr);
-
-	pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
-
-	pa_stream_set_state_callback(po->stream, nullptr, nullptr);
-	pa_stream_set_write_callback(po->stream, nullptr, nullptr);
-
-	pa_stream_disconnect(po->stream);
-	pa_stream_unref(po->stream);
-	po->stream = nullptr;
-}
-
-/**
- * Frees and clears the context.
- *
- * Caller must lock the main loop.
- */
-static void
-pulse_output_delete_context(PulseOutput *po)
-{
-	assert(po != nullptr);
-	assert(po->context != nullptr);
-
-	pa_context_set_state_callback(po->context, nullptr, nullptr);
-	pa_context_set_subscribe_callback(po->context, nullptr, nullptr);
-
-	pa_context_disconnect(po->context);
-	pa_context_unref(po->context);
-	po->context = nullptr;
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_context(PulseOutput *po, Error &error)
-{
-	assert(po != nullptr);
-	assert(po->mainloop != nullptr);
-
-	po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
-				     MPD_PULSE_NAME);
-	if (po->context == nullptr) {
-		error.Set(pulse_output_domain, "pa_context_new() has failed");
-		return false;
-	}
-
-	pa_context_set_state_callback(po->context,
-				      pulse_output_context_state_cb, po);
-	pa_context_set_subscribe_callback(po->context,
-					  pulse_output_subscribe_cb, po);
-
-	if (!pulse_output_connect(po, error)) {
-		pulse_output_delete_context(po);
-		return false;
-	}
-
-	return true;
-}
-
-static struct audio_output *
-pulse_output_init(const config_param &param, Error &error)
-{
-	PulseOutput *po;
-
-	g_setenv("PULSE_PROP_media.role", "music", true);
-
-	po = new PulseOutput();
-	if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) {
-		delete po;
-		return nullptr;
-	}
-
-	po->name = param.GetBlockValue("name", "mpd_pulse");
-	po->server = param.GetBlockValue("server");
-	po->sink = param.GetBlockValue("sink");
-
-	po->mixer = nullptr;
-	po->mainloop = nullptr;
-	po->context = nullptr;
-	po->stream = nullptr;
-
-	return &po->base;
-}
-
-static void
-pulse_output_finish(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-
-	ao_base_finish(&po->base);
-	delete po;
-}
-
-static bool
-pulse_output_enable(struct audio_output *ao, Error &error)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-
-	assert(po->mainloop == nullptr);
-	assert(po->context == nullptr);
-
-	/* create the libpulse mainloop and start the thread */
-
-	po->mainloop = pa_threaded_mainloop_new();
-	if (po->mainloop == nullptr) {
-		g_free(po);
-
-		error.Set(pulse_output_domain,
-			  "pa_threaded_mainloop_new() has failed");
-		return false;
-	}
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (pa_threaded_mainloop_start(po->mainloop) < 0) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		pa_threaded_mainloop_free(po->mainloop);
-		po->mainloop = nullptr;
-
-		error.Set(pulse_output_domain,
-			  "pa_threaded_mainloop_start() has failed");
-		return false;
-	}
-
-	/* create the libpulse context and connect it */
-
-	if (!pulse_output_setup_context(po, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		pa_threaded_mainloop_stop(po->mainloop);
-		pa_threaded_mainloop_free(po->mainloop);
-		po->mainloop = nullptr;
-		return false;
-	}
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-
-	return true;
-}
-
-static void
-pulse_output_disable(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-
-	assert(po->mainloop != nullptr);
-
-	pa_threaded_mainloop_stop(po->mainloop);
-	if (po->context != nullptr)
-		pulse_output_delete_context(po);
-	pa_threaded_mainloop_free(po->mainloop);
-	po->mainloop = nullptr;
-}
-
-/**
- * Check if the context is (already) connected, and waits if not.  If
- * the context has been disconnected, retry to connect.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_connection(PulseOutput *po, Error &error)
-{
-	assert(po->mainloop != nullptr);
-
-	pa_context_state_t state;
-
-	if (po->context == nullptr && !pulse_output_setup_context(po, error))
-		return false;
-
-	while (true) {
-		state = pa_context_get_state(po->context);
-		switch (state) {
-		case PA_CONTEXT_READY:
-			/* nothing to do */
-			return true;
-
-		case PA_CONTEXT_UNCONNECTED:
-		case PA_CONTEXT_TERMINATED:
-		case PA_CONTEXT_FAILED:
-			/* failure */
-			SetError(error, po->context, "failed to connect");
-			pulse_output_delete_context(po);
-			return false;
-
-		case PA_CONTEXT_CONNECTING:
-		case PA_CONTEXT_AUTHORIZING:
-		case PA_CONTEXT_SETTING_NAME:
-			/* wait some more */
-			pa_threaded_mainloop_wait(po->mainloop);
-			break;
-		}
-	}
-}
-
-static void
-pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-
-	assert(stream == po->stream || po->stream == nullptr);
-	assert(po->mainloop != nullptr);
-
-	/* wake up the main loop to break out of the loop in
-	   pulse_output_play() */
-	pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-
-	assert(stream == po->stream || po->stream == nullptr);
-	assert(po->mainloop != nullptr);
-	assert(po->context != nullptr);
-
-	switch (pa_stream_get_state(stream)) {
-	case PA_STREAM_READY:
-		if (po->mixer != nullptr)
-			pulse_mixer_on_change(po->mixer, po->context, stream);
-
-		pa_threaded_mainloop_signal(po->mainloop, 0);
-		break;
-
-	case PA_STREAM_FAILED:
-	case PA_STREAM_TERMINATED:
-		if (po->mixer != nullptr)
-			pulse_mixer_on_disconnect(po->mixer);
-
-		pa_threaded_mainloop_signal(po->mainloop, 0);
-		break;
-
-	case PA_STREAM_UNCONNECTED:
-	case PA_STREAM_CREATING:
-		break;
-	}
-}
-
-static void
-pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes,
-			     void *userdata)
-{
-	PulseOutput *po = (PulseOutput *)userdata;
-
-	assert(po->mainloop != nullptr);
-
-	po->writable = nbytes;
-	pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
-			  Error &error)
-{
-	assert(po != nullptr);
-	assert(po->context != nullptr);
-
-	po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
-	if (po->stream == nullptr) {
-		SetError(error, po->context, "pa_stream_new() has failed");
-		return false;
-	}
-
-	pa_stream_set_suspended_callback(po->stream,
-					 pulse_output_stream_suspended_cb, po);
-
-	pa_stream_set_state_callback(po->stream,
-				     pulse_output_stream_state_cb, po);
-	pa_stream_set_write_callback(po->stream,
-				     pulse_output_stream_write_cb, po);
-
-	return true;
-}
-
-static bool
-pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		  Error &error)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-	pa_sample_spec ss;
-
-	assert(po->mainloop != nullptr);
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (po->context != nullptr) {
-		switch (pa_context_get_state(po->context)) {
-		case PA_CONTEXT_UNCONNECTED:
-		case PA_CONTEXT_TERMINATED:
-		case PA_CONTEXT_FAILED:
-			/* the connection was closed meanwhile; delete
-			   it, and pulse_output_wait_connection() will
-			   reopen it */
-			pulse_output_delete_context(po);
-			break;
-
-		case PA_CONTEXT_READY:
-		case PA_CONTEXT_CONNECTING:
-		case PA_CONTEXT_AUTHORIZING:
-		case PA_CONTEXT_SETTING_NAME:
-			break;
-		}
-	}
-
-	if (!pulse_output_wait_connection(po, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return false;
-	}
-
-	/* MPD doesn't support the other pulseaudio sample formats, so
-	   we just force MPD to send us everything as 16 bit */
-	audio_format.format = SampleFormat::S16;
-
-	ss.format = PA_SAMPLE_S16NE;
-	ss.rate = audio_format.sample_rate;
-	ss.channels = audio_format.channels;
-
-	/* create a stream .. */
-
-	if (!pulse_output_setup_stream(po, &ss, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return false;
-	}
-
-	/* .. and connect it (asynchronously) */
-
-	if (pa_stream_connect_playback(po->stream, po->sink,
-				       nullptr, pa_stream_flags_t(0),
-				       nullptr, nullptr) < 0) {
-		pulse_output_delete_stream(po);
-
-		SetError(error, po->context,
-			 "pa_stream_connect_playback() has failed");
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return false;
-	}
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-
-	return true;
-}
-
-static void
-pulse_output_close(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-	pa_operation *o;
-
-	assert(po->mainloop != nullptr);
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
-		o = pa_stream_drain(po->stream,
-				    pulse_output_stream_success_cb, po);
-		if (o == nullptr) {
-			FormatWarning(pulse_output_domain,
-				      "pa_stream_drain() has failed: %s",
-				      pa_strerror(pa_context_errno(po->context)));
-		} else
-			pulse_wait_for_operation(po->mainloop, o);
-	}
-
-	pulse_output_delete_stream(po);
-
-	if (po->context != nullptr &&
-	    pa_context_get_state(po->context) != PA_CONTEXT_READY)
-		pulse_output_delete_context(po);
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-/**
- * Check if the stream is (already) connected, and waits if not.  The
- * mainloop must be locked before calling this function.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_stream(PulseOutput *po, Error &error)
-{
-	while (true) {
-		switch (pa_stream_get_state(po->stream)) {
-		case PA_STREAM_READY:
-			return true;
-
-		case PA_STREAM_FAILED:
-		case PA_STREAM_TERMINATED:
-		case PA_STREAM_UNCONNECTED:
-			SetError(error, po->context,
-				 "failed to connect the stream");
-			return false;
-
-		case PA_STREAM_CREATING:
-			pa_threaded_mainloop_wait(po->mainloop);
-			break;
-		}
-	}
-}
-
-/**
- * Sets cork mode on the stream.
- */
-static bool
-pulse_output_stream_pause(PulseOutput *po, bool pause,
-			  Error &error)
-{
-	pa_operation *o;
-
-	assert(po->mainloop != nullptr);
-	assert(po->context != nullptr);
-	assert(po->stream != nullptr);
-
-	o = pa_stream_cork(po->stream, pause,
-			   pulse_output_stream_success_cb, po);
-	if (o == nullptr) {
-		SetError(error, po->context, "pa_stream_cork() has failed");
-		return false;
-	}
-
-	if (!pulse_wait_for_operation(po->mainloop, o)) {
-		SetError(error, po->context, "pa_stream_cork() has failed");
-		return false;
-	}
-
-	return true;
-}
-
-static unsigned
-pulse_output_delay(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-	unsigned result = 0;
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (po->base.pause && pa_stream_is_corked(po->stream) &&
-	    pa_stream_get_state(po->stream) == PA_STREAM_READY)
-		/* idle while paused */
-		result = 1000;
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-
-	return result;
-}
-
-static size_t
-pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		  Error &error)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-
-	assert(po->mainloop != nullptr);
-	assert(po->stream != nullptr);
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	/* check if the stream is (already) connected */
-
-	if (!pulse_output_wait_stream(po, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return 0;
-	}
-
-	assert(po->context != nullptr);
-
-	/* unpause if previously paused */
-
-	if (pa_stream_is_corked(po->stream) &&
-	    !pulse_output_stream_pause(po, false, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return 0;
-	}
-
-	/* wait until the server allows us to write */
-
-	while (po->writable == 0) {
-		if (pa_stream_is_suspended(po->stream)) {
-			pa_threaded_mainloop_unlock(po->mainloop);
-			error.Set(pulse_output_domain, "suspended");
-			return 0;
-		}
-
-		pa_threaded_mainloop_wait(po->mainloop);
-
-		if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
-			pa_threaded_mainloop_unlock(po->mainloop);
-			error.Set(pulse_output_domain, "disconnected");
-			return 0;
-		}
-	}
-
-	/* now write */
-
-	if (size > po->writable)
-		/* don't send more than possible */
-		size = po->writable;
-
-	po->writable -= size;
-
-	int result = pa_stream_write(po->stream, chunk, size, nullptr,
-				     0, PA_SEEK_RELATIVE);
-	pa_threaded_mainloop_unlock(po->mainloop);
-	if (result < 0) {
-		SetError(error, po->context, "pa_stream_write() failed");
-		return 0;
-	}
-
-	return size;
-}
-
-static void
-pulse_output_cancel(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-	pa_operation *o;
-
-	assert(po->mainloop != nullptr);
-	assert(po->stream != nullptr);
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
-		/* no need to flush when the stream isn't connected
-		   yet */
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return;
-	}
-
-	assert(po->context != nullptr);
-
-	o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
-	if (o == nullptr) {
-		FormatWarning(pulse_output_domain,
-			      "pa_stream_flush() has failed: %s",
-			      pa_strerror(pa_context_errno(po->context)));
-		pa_threaded_mainloop_unlock(po->mainloop);
-		return;
-	}
-
-	pulse_wait_for_operation(po->mainloop, o);
-	pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-static bool
-pulse_output_pause(struct audio_output *ao)
-{
-	PulseOutput *po = (PulseOutput *)ao;
-
-	assert(po->mainloop != nullptr);
-	assert(po->stream != nullptr);
-
-	pa_threaded_mainloop_lock(po->mainloop);
-
-	/* check if the stream is (already/still) connected */
-
-	Error error;
-	if (!pulse_output_wait_stream(po, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		LogError(error);
-		return false;
-	}
-
-	assert(po->context != nullptr);
-
-	/* cork the stream */
-
-	if (!pa_stream_is_corked(po->stream) &&
-	    !pulse_output_stream_pause(po, true, error)) {
-		pa_threaded_mainloop_unlock(po->mainloop);
-		LogError(error);
-		return false;
-	}
-
-	pa_threaded_mainloop_unlock(po->mainloop);
-
-	return true;
-}
-
-static bool
-pulse_output_test_default_device(void)
-{
-	bool success;
-
-	const config_param empty;
-	PulseOutput *po = (PulseOutput *)
-		pulse_output_init(empty, IgnoreError());
-	if (po == nullptr)
-		return false;
-
-	success = pulse_output_wait_connection(po, IgnoreError());
-	pulse_output_finish(&po->base);
-
-	return success;
-}
-
-const struct audio_output_plugin pulse_output_plugin = {
-	"pulse",
-	pulse_output_test_default_device,
-	pulse_output_init,
-	pulse_output_finish,
-	pulse_output_enable,
-	pulse_output_disable,
-	pulse_output_open,
-	pulse_output_close,
-	pulse_output_delay,
-	nullptr,
-	pulse_output_play,
-	nullptr,
-	pulse_output_cancel,
-	pulse_output_pause,
-
-	&pulse_mixer_plugin,
-};
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx
deleted file mode 100644
index 9df557282..000000000
--- a/src/output/PulseOutputPlugin.hxx
+++ /dev/null
@@ -1,46 +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_PULSE_OUTPUT_PLUGIN_HXX
-#define MPD_PULSE_OUTPUT_PLUGIN_HXX
-
-struct PulseOutput;
-struct PulseMixer;
-struct pa_cvolume;
-class Error;
-
-extern const struct audio_output_plugin pulse_output_plugin;
-
-void
-pulse_output_lock(PulseOutput *po);
-
-void
-pulse_output_unlock(PulseOutput *po);
-
-void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm);
-
-void
-pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
-
-bool
-pulse_output_set_volume(PulseOutput *po,
-			const pa_cvolume *volume, Error &error);
-
-#endif
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
deleted file mode 100644
index cebbc2cec..000000000
--- a/src/output/RecorderOutputPlugin.cxx
+++ /dev/null
@@ -1,262 +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.
- */
-
-#include "config.h"
-#include "RecorderOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "encoder/EncoderPlugin.hxx"
-#include "encoder/EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/fd_util.h"
-#include "open.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-struct RecorderOutput {
-	struct audio_output base;
-
-	/**
-	 * The configured encoder plugin.
-	 */
-	Encoder *encoder;
-
-	/**
-	 * The destination file name.
-	 */
-	const char *path;
-
-	/**
-	 * The destination file descriptor.
-	 */
-	int fd;
-
-	/**
-	 * The buffer for encoder_read().
-	 */
-	char buffer[32768];
-
-	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, &recorder_output_plugin, param,
-				    error_r);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	bool Configure(const config_param &param, Error &error);
-
-	bool WriteToFile(const void *data, size_t length, Error &error);
-
-	/**
-	 * Writes pending data from the encoder to the output file.
-	 */
-	bool EncoderToFile(Error &error);
-};
-
-static constexpr Domain recorder_output_domain("recorder_output");
-
-inline bool
-RecorderOutput::Configure(const config_param &param, Error &error)
-{
-	/* read configuration */
-
-	const char *encoder_name =
-		param.GetBlockValue("encoder", "vorbis");
-	const auto encoder_plugin = encoder_plugin_get(encoder_name);
-	if (encoder_plugin == nullptr) {
-		error.Format(config_domain,
-			     "No such encoder: %s", encoder_name);
-		return false;
-	}
-
-	path = param.GetBlockValue("path");
-	if (path == nullptr) {
-		error.Set(config_domain, "'path' not configured");
-		return false;
-	}
-
-	/* initialize encoder */
-
-	encoder = encoder_init(*encoder_plugin, param, error);
-	if (encoder == nullptr)
-		return false;
-
-	return true;
-}
-
-static audio_output *
-recorder_output_init(const config_param &param, Error &error)
-{
-	RecorderOutput *recorder = new RecorderOutput();
-
-	if (!recorder->Initialize(param, error)) {
-		delete recorder;
-		return nullptr;
-	}
-
-	if (!recorder->Configure(param, error)) {
-		recorder->Deinitialize();
-		delete recorder;
-		return nullptr;
-	}
-
-	return &recorder->base;
-}
-
-static void
-recorder_output_finish(struct audio_output *ao)
-{
-	RecorderOutput *recorder = (RecorderOutput *)ao;
-
-	encoder_finish(recorder->encoder);
-	recorder->Deinitialize();
-	delete recorder;
-}
-
-inline bool
-RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
-{
-	assert(length > 0);
-
-	const uint8_t *data = (const uint8_t *)_data, *end = data + length;
-
-	while (true) {
-		ssize_t nbytes = write(fd, data, end - data);
-		if (nbytes > 0) {
-			data += nbytes;
-			if (data == end)
-				return true;
-		} else if (nbytes == 0) {
-			/* shouldn't happen for files */
-			error.Set(recorder_output_domain,
-				  "write() returned 0");
-			return false;
-		} else if (errno != EINTR) {
-			error.FormatErrno("Failed to write to '%s'", path);
-			return false;
-		}
-	}
-}
-
-inline bool
-RecorderOutput::EncoderToFile(Error &error)
-{
-	assert(fd >= 0);
-
-	while (true) {
-		/* read from the encoder */
-
-		size_t size = encoder_read(encoder, buffer, sizeof(buffer));
-		if (size == 0)
-			return true;
-
-		/* write everything into the file */
-
-		if (!WriteToFile(buffer, size, error))
-			return false;
-	}
-}
-
-static bool
-recorder_output_open(struct audio_output *ao,
-		     AudioFormat &audio_format,
-		     Error &error)
-{
-	RecorderOutput *recorder = (RecorderOutput *)ao;
-
-	/* create the output file */
-
-	recorder->fd = open_cloexec(recorder->path,
-				    O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
-				    0666);
-	if (recorder->fd < 0) {
-		error.FormatErrno("Failed to create '%s'", recorder->path);
-		return false;
-	}
-
-	/* open the encoder */
-
-	if (!encoder_open(recorder->encoder, audio_format, error)) {
-		close(recorder->fd);
-		unlink(recorder->path);
-		return false;
-	}
-
-	if (!recorder->EncoderToFile(error)) {
-		encoder_close(recorder->encoder);
-		close(recorder->fd);
-		unlink(recorder->path);
-		return false;
-	}
-
-	return true;
-}
-
-static void
-recorder_output_close(struct audio_output *ao)
-{
-	RecorderOutput *recorder = (RecorderOutput *)ao;
-
-	/* flush the encoder and write the rest to the file */
-
-	if (encoder_end(recorder->encoder, IgnoreError()))
-		recorder->EncoderToFile(IgnoreError());
-
-	/* now really close everything */
-
-	encoder_close(recorder->encoder);
-
-	close(recorder->fd);
-}
-
-static size_t
-recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		     Error &error)
-{
-	RecorderOutput *recorder = (RecorderOutput *)ao;
-
-	return encoder_write(recorder->encoder, chunk, size, error) &&
-		recorder->EncoderToFile(error)
-		? size : 0;
-}
-
-const struct audio_output_plugin recorder_output_plugin = {
-	"recorder",
-	nullptr,
-	recorder_output_init,
-	recorder_output_finish,
-	nullptr,
-	nullptr,
-	recorder_output_open,
-	recorder_output_close,
-	nullptr,
-	nullptr,
-	recorder_output_play,
-	nullptr,
-	nullptr,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/RecorderOutputPlugin.hxx
deleted file mode 100644
index 4fac911a1..000000000
--- a/src/output/RecorderOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_RECORDER_OUTPUT_PLUGIN_HXX
-#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin recorder_output_plugin;
-
-#endif
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
deleted file mode 100644
index 9634379c5..000000000
--- a/src/output/RoarOutputPlugin.cxx
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2003-2014 The Music Player Daemon Project
- * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
- * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RoarOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "thread/Mutex.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <string>
-
-/* libroar/services.h declares roar_service_stream::new - work around
-   this C++ problem */
-#define new _new
-#include <roaraudio.h>
-#undef new
-
-class RoarOutput {
-	struct audio_output base;
-
-	std::string host, name;
-
-	roar_vs_t * vss;
-	int err;
-	int role;
-	struct roar_connection con;
-	struct roar_audio_info info;
-	mutable Mutex mutex;
-	volatile bool alive;
-
-public:
-	RoarOutput()
-		:err(ROAR_ERROR_NONE) {}
-
-	operator audio_output *() {
-		return &base;
-	}
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &roar_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	void Configure(const config_param &param);
-
-	bool Open(AudioFormat &audio_format, Error &error);
-	void Close();
-
-	void SendTag(const Tag &tag);
-	size_t Play(const void *chunk, size_t size, Error &error);
-	void Cancel();
-
-	int GetVolume() const;
-	bool SetVolume(unsigned volume);
-};
-
-static constexpr Domain roar_output_domain("roar_output");
-
-inline int
-RoarOutput::GetVolume() const
-{
-	const ScopeLock protect(mutex);
-
-	if (vss == nullptr || !alive)
-		return -1;
-
-	float l, r;
-	int error;
-	if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
-		return -1;
-
-	return (l + r) * 50;
-}
-
-int
-roar_output_get_volume(RoarOutput *roar)
-{
-	return roar->GetVolume();
-}
-
-bool
-RoarOutput::SetVolume(unsigned volume)
-{
-	assert(volume <= 100);
-
-	const ScopeLock protect(mutex);
-	if (vss == nullptr || !alive)
-		return false;
-
-	int error;
-	float level = volume / 100.0;
-
-	roar_vs_volume_mono(vss, level, &error);
-	return true;
-}
-
-bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume)
-{
-	return roar->SetVolume(volume);
-}
-
-inline void
-RoarOutput::Configure(const config_param &param)
-{
-	host = param.GetBlockValue("server", "");
-	name = param.GetBlockValue("name", "MPD");
-
-	const char *_role = param.GetBlockValue("role", "music");
-	role = _role != nullptr
-		? roar_str2role(_role)
-		: ROAR_ROLE_MUSIC;
-}
-
-static struct audio_output *
-roar_init(const config_param &param, Error &error)
-{
-	RoarOutput *self = new RoarOutput();
-
-	if (!self->Initialize(param, error)) {
-		delete self;
-		return nullptr;
-	}
-
-	self->Configure(param);
-	return *self;
-}
-
-static void
-roar_finish(struct audio_output *ao)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-
-	self->Deinitialize();
-	delete self;
-}
-
-static void
-roar_use_audio_format(struct roar_audio_info *info,
-		      AudioFormat &audio_format)
-{
-	info->rate = audio_format.sample_rate;
-	info->channels = audio_format.channels;
-	info->codec = ROAR_CODEC_PCM_S;
-
-	switch (audio_format.format) {
-	case SampleFormat::UNDEFINED:
-	case SampleFormat::FLOAT:
-	case SampleFormat::DSD:
-		info->bits = 16;
-		audio_format.format = SampleFormat::S16;
-		break;
-
-	case SampleFormat::S8:
-		info->bits = 8;
-		break;
-
-	case SampleFormat::S16:
-		info->bits = 16;
-		break;
-
-	case SampleFormat::S24_P32:
-		info->bits = 32;
-		audio_format.format = SampleFormat::S32;
-		break;
-
-	case SampleFormat::S32:
-		info->bits = 32;
-		break;
-	}
-}
-
-inline bool
-RoarOutput::Open(AudioFormat &audio_format, Error &error)
-{
-	const ScopeLock protect(mutex);
-
-	if (roar_simple_connect(&con,
-				host.empty() ? nullptr : host.c_str(),
-				name.c_str()) < 0) {
-		error.Set(roar_output_domain,
-			  "Failed to connect to Roar server");
-		return false;
-	}
-
-	vss = roar_vs_new_from_con(&con, &err);
-
-	if (vss == nullptr || err != ROAR_ERROR_NONE) {
-		error.Set(roar_output_domain, "Failed to connect to server");
-		return false;
-	}
-
-	roar_use_audio_format(&info, audio_format);
-
-	if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
-		error.Set(roar_output_domain, "Failed to start stream");
-		return false;
-	}
-
-	roar_vs_role(vss, role, &err);
-	alive = true;
-	return true;
-}
-
-static bool
-roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-
-	return self->Open(audio_format, error);
-}
-
-inline void
-RoarOutput::Close()
-{
-	const ScopeLock protect(mutex);
-
-	alive = false;
-
-	if (vss != nullptr)
-		roar_vs_close(vss, ROAR_VS_TRUE, &err);
-	vss = nullptr;
-	roar_disconnect(&con);
-}
-
-static void
-roar_close(struct audio_output *ao)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-	self->Close();
-}
-
-inline void
-RoarOutput::Cancel()
-{
-	const ScopeLock protect(mutex);
-
-	if (vss == nullptr)
-		return;
-
-	roar_vs_t *_vss = vss;
-	vss = nullptr;
-	roar_vs_close(_vss, ROAR_VS_TRUE, &err);
-	alive = false;
-
-	_vss = roar_vs_new_from_con(&con, &err);
-	if (_vss == nullptr)
-		return;
-
-	if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
-		roar_vs_close(_vss, ROAR_VS_TRUE, &err);
-		LogError(roar_output_domain, "Failed to start stream");
-		return;
-	}
-
-	roar_vs_role(_vss, role, &err);
-	vss = _vss;
-	alive = true;
-}
-
-static void
-roar_cancel(struct audio_output *ao)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-
-	self->Cancel();
-}
-
-inline size_t
-RoarOutput::Play(const void *chunk, size_t size, Error &error)
-{
-	if (vss == nullptr) {
-		error.Set(roar_output_domain, "Connection is invalid");
-		return 0;
-	}
-
-	ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
-	if (nbytes <= 0) {
-		error.Set(roar_output_domain, "Failed to play data");
-		return 0;
-	}
-
-	return nbytes;
-}
-
-static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size,
-	  Error &error)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-	return self->Play(chunk, size, error);
-}
-
-static const char*
-roar_tag_convert(TagType type, bool *is_uuid)
-{
-	*is_uuid = false;
-	switch (type)
-	{
-		case TAG_ARTIST:
-		case TAG_ALBUM_ARTIST:
-			return "AUTHOR";
-		case TAG_ALBUM:
-			return "ALBUM";
-		case TAG_TITLE:
-			return "TITLE";
-		case TAG_TRACK:
-			return "TRACK";
-		case TAG_NAME:
-			return "NAME";
-		case TAG_GENRE:
-			return "GENRE";
-		case TAG_DATE:
-			return "DATE";
-		case TAG_PERFORMER:
-			return "PERFORMER";
-		case TAG_COMMENT:
-			return "COMMENT";
-		case TAG_DISC:
-			return "DISCID";
-		case TAG_COMPOSER:
-#ifdef ROAR_META_TYPE_COMPOSER
-			return "COMPOSER";
-#else
-			return "AUTHOR";
-#endif
-		case TAG_MUSICBRAINZ_ARTISTID:
-		case TAG_MUSICBRAINZ_ALBUMID:
-		case TAG_MUSICBRAINZ_ALBUMARTISTID:
-		case TAG_MUSICBRAINZ_TRACKID:
-			*is_uuid = true;
-			return "HASH";
-
-		default:
-			return nullptr;
-	}
-}
-
-inline void
-RoarOutput::SendTag(const Tag &tag)
-{
-	if (vss == nullptr)
-		return;
-
-	const ScopeLock protect(mutex);
-
-	size_t cnt = 1;
-	struct roar_keyval vals[32];
-	char uuid_buf[32][64];
-
-	char timebuf[16];
-	snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
-		 tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60);
-
-	vals[0].key = const_cast<char *>("LENGTH");
-	vals[0].value = timebuf;
-
-	for (unsigned i = 0; i < tag.num_items && cnt < 32; i++)
-	{
-		bool is_uuid = false;
-		const char *key = roar_tag_convert(tag.items[i]->type,
-						   &is_uuid);
-		if (key != nullptr) {
-			vals[cnt].key = const_cast<char *>(key);
-
-			if (is_uuid) {
-				snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
-					 tag.items[i]->value);
-				vals[cnt].value = uuid_buf[cnt];
-			} else {
-				vals[cnt].value = tag.items[i]->value;
-			}
-
-			cnt++;
-		}
-	}
-
-	roar_vs_meta(vss, vals, cnt, &(err));
-}
-
-static void
-roar_send_tag(struct audio_output *ao, const Tag *meta)
-{
-	RoarOutput *self = (RoarOutput *)ao;
-	self->SendTag(*meta);
-}
-
-const struct audio_output_plugin roar_output_plugin = {
-	"roar",
-	nullptr,
-	roar_init,
-	roar_finish,
-	nullptr,
-	nullptr,
-	roar_open,
-	roar_close,
-	nullptr,
-	roar_send_tag,
-	roar_play,
-	nullptr,
-	roar_cancel,
-	nullptr,
-	&roar_mixer_plugin,
-};
diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx
deleted file mode 100644
index 27c5dc420..000000000
--- a/src/output/RoarOutputPlugin.hxx
+++ /dev/null
@@ -1,33 +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_ROAR_OUTPUT_PLUGIN_H
-#define MPD_ROAR_OUTPUT_PLUGIN_H
-
-class RoarOutput;
-
-extern const struct audio_output_plugin roar_output_plugin;
-
-int
-roar_output_get_volume(RoarOutput *roar);
-
-bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume);
-
-#endif
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx
deleted file mode 100644
index cccd3e0ba..000000000
--- a/src/output/ShoutOutputPlugin.cxx
+++ /dev/null
@@ -1,544 +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.
- */
-
-#include "config.h"
-#include "ShoutOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "encoder/EncoderPlugin.hxx"
-#include "encoder/EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <shout/shout.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
-
-struct ShoutOutput final {
-	struct audio_output base;
-
-	shout_t *shout_conn;
-	shout_metadata_t *shout_meta;
-
-	Encoder *encoder;
-
-	float quality;
-	int bitrate;
-
-	int timeout;
-
-	uint8_t buffer[32768];
-
-	ShoutOutput()
-		:shout_conn(shout_new()),
-		shout_meta(shout_metadata_new()),
-		quality(-2.0),
-		bitrate(-1),
-		timeout(DEFAULT_CONN_TIMEOUT) {}
-
-	~ShoutOutput() {
-		if (shout_meta != nullptr)
-			shout_metadata_free(shout_meta);
-		if (shout_conn != nullptr)
-			shout_free(shout_conn);
-	}
-
-	bool Initialize(const config_param &param, Error &error) {
-		return ao_base_init(&base, &shout_output_plugin, param,
-				    error);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-
-	bool Configure(const config_param &param, Error &error);
-};
-
-static int shout_init_count;
-
-static constexpr Domain shout_output_domain("shout_output");
-
-static const EncoderPlugin *
-shout_encoder_plugin_get(const char *name)
-{
-	if (strcmp(name, "ogg") == 0)
-		name = "vorbis";
-	else if (strcmp(name, "mp3") == 0)
-		name = "lame";
-
-	return encoder_plugin_get(name);
-}
-
-gcc_pure
-static const char *
-require_block_string(const config_param &param, const char *name)
-{
-	const char *value = param.GetBlockValue(name);
-	if (value == nullptr)
-		FormatFatalError("no \"%s\" defined for shout device defined "
-				 "at line %u\n", name, param.line);
-
-	return value;
-}
-
-inline bool
-ShoutOutput::Configure(const config_param &param, Error &error)
-{
-
-	const AudioFormat audio_format = base.config_audio_format;
-	if (!audio_format.IsFullyDefined()) {
-		error.Set(config_domain,
-			  "Need full audio format specification");
-		return nullptr;
-	}
-
-	const char *host = require_block_string(param, "host");
-	const char *mount = require_block_string(param, "mount");
-	unsigned port = param.GetBlockValue("port", 0u);
-	if (port == 0) {
-		error.Set(config_domain, "shout port must be configured");
-		return false;
-	}
-
-	const char *passwd = require_block_string(param, "password");
-	const char *name = require_block_string(param, "name");
-
-	bool is_public = param.GetBlockValue("public", false);
-
-	const char *user = param.GetBlockValue("user", "source");
-
-	const char *value = param.GetBlockValue("quality");
-	if (value != nullptr) {
-		char *test;
-		quality = strtod(value, &test);
-
-		if (*test != '\0' || quality < -1.0 || quality > 10.0) {
-			error.Format(config_domain,
-				     "shout quality \"%s\" is not a number in the "
-				     "range -1 to 10",
-				     value);
-			return false;
-		}
-
-		if (param.GetBlockValue("bitrate") != nullptr) {
-			error.Set(config_domain,
-				  "quality and bitrate are "
-				  "both defined");
-			return false;
-		}
-	} else {
-		value = param.GetBlockValue("bitrate");
-		if (value == nullptr) {
-			error.Set(config_domain,
-				  "neither bitrate nor quality defined");
-			return false;
-		}
-
-		char *test;
-		bitrate = strtol(value, &test, 10);
-
-		if (*test != '\0' || bitrate <= 0) {
-			error.Set(config_domain,
-				  "bitrate must be a positive integer");
-			return false;
-		}
-	}
-
-	const char *encoding = param.GetBlockValue("encoding", "ogg");
-	const auto encoder_plugin = shout_encoder_plugin_get(encoding);
-	if (encoder_plugin == nullptr) {
-		error.Format(config_domain,
-			     "couldn't find shout encoder plugin \"%s\"",
-			     encoding);
-		return false;
-	}
-
-	encoder = encoder_init(*encoder_plugin, param, error);
-	if (encoder == nullptr)
-		return false;
-
-	unsigned shout_format;
-	if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
-		shout_format = SHOUT_FORMAT_MP3;
-	else
-		shout_format = SHOUT_FORMAT_OGG;
-
-	unsigned protocol;
-	value = param.GetBlockValue("protocol");
-	if (value != nullptr) {
-		if (0 == strcmp(value, "shoutcast") &&
-		    0 != strcmp(encoding, "mp3")) {
-			error.Format(config_domain,
-				     "you cannot stream \"%s\" to shoutcast, use mp3",
-				     encoding);
-			return false;
-		} else if (0 == strcmp(value, "shoutcast"))
-			protocol = SHOUT_PROTOCOL_ICY;
-		else if (0 == strcmp(value, "icecast1"))
-			protocol = SHOUT_PROTOCOL_XAUDIOCAST;
-		else if (0 == strcmp(value, "icecast2"))
-			protocol = SHOUT_PROTOCOL_HTTP;
-		else {
-			error.Format(config_domain,
-				     "shout protocol \"%s\" is not \"shoutcast\" or "
-				     "\"icecast1\"or \"icecast2\"",
-				     value);
-			return false;
-		}
-	} else {
-		protocol = SHOUT_PROTOCOL_HTTP;
-	}
-
-	if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
-	    shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
-	    shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
-	    shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS ||
-	    shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS ||
-	    shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS ||
-	    shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS ||
-	    shout_set_format(shout_conn, shout_format)
-	    != SHOUTERR_SUCCESS ||
-	    shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
-	    shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) {
-		error.Set(shout_output_domain, shout_get_error(shout_conn));
-		return false;
-	}
-
-	/* optional paramters */
-	timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
-
-	value = param.GetBlockValue("genre");
-	if (value != nullptr && shout_set_genre(shout_conn, value)) {
-		error.Set(shout_output_domain, shout_get_error(shout_conn));
-		return false;
-	}
-
-	value = param.GetBlockValue("description");
-	if (value != nullptr && shout_set_description(shout_conn, value)) {
-		error.Set(shout_output_domain, shout_get_error(shout_conn));
-		return false;
-	}
-
-	value = param.GetBlockValue("url");
-	if (value != nullptr && shout_set_url(shout_conn, value)) {
-		error.Set(shout_output_domain, shout_get_error(shout_conn));
-		return false;
-	}
-
-	{
-		char temp[11];
-		memset(temp, 0, sizeof(temp));
-
-		snprintf(temp, sizeof(temp), "%u", audio_format.channels);
-		shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
-
-		snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
-
-		shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
-
-		if (quality >= -1.0) {
-			snprintf(temp, sizeof(temp), "%2.2f", quality);
-			shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY,
-					     temp);
-		} else {
-			snprintf(temp, sizeof(temp), "%d", bitrate);
-			shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE,
-					     temp);
-		}
-	}
-
-	return true;
-}
-
-static struct audio_output *
-my_shout_init_driver(const config_param &param, Error &error)
-{
-	ShoutOutput *sd = new ShoutOutput();
-	if (!sd->Initialize(param, error)) {
-		delete sd;
-		return nullptr;
-	}
-
-	if (!sd->Configure(param, error)) {
-		sd->Deinitialize();
-		delete sd;
-		return nullptr;
-	}
-
-	if (shout_init_count == 0)
-		shout_init();
-
-	shout_init_count++;
-
-	return &sd->base;
-}
-
-static bool
-handle_shout_error(ShoutOutput *sd, int err, Error &error)
-{
-	switch (err) {
-	case SHOUTERR_SUCCESS:
-		break;
-
-	case SHOUTERR_UNCONNECTED:
-	case SHOUTERR_SOCKET:
-		error.Format(shout_output_domain, err,
-			     "Lost shout connection to %s:%i: %s",
-			     shout_get_host(sd->shout_conn),
-			     shout_get_port(sd->shout_conn),
-			     shout_get_error(sd->shout_conn));
-		return false;
-
-	default:
-		error.Format(shout_output_domain, err,
-			     "connection to %s:%i error: %s",
-			     shout_get_host(sd->shout_conn),
-			     shout_get_port(sd->shout_conn),
-			     shout_get_error(sd->shout_conn));
-		return false;
-	}
-
-	return true;
-}
-
-static bool
-write_page(ShoutOutput *sd, Error &error)
-{
-	assert(sd->encoder != nullptr);
-
-	while (true) {
-		size_t nbytes = encoder_read(sd->encoder,
-					     sd->buffer, sizeof(sd->buffer));
-		if (nbytes == 0)
-			return true;
-
-		int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
-		if (!handle_shout_error(sd, err, error))
-			return false;
-	}
-
-	return true;
-}
-
-static void close_shout_conn(ShoutOutput * sd)
-{
-	if (sd->encoder != nullptr) {
-		if (encoder_end(sd->encoder, IgnoreError()))
-			write_page(sd, IgnoreError());
-
-		encoder_close(sd->encoder);
-	}
-
-	if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
-	    shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
-		FormatWarning(shout_output_domain,
-			      "problem closing connection to shout server: %s",
-			      shout_get_error(sd->shout_conn));
-	}
-}
-
-static void
-my_shout_finish_driver(struct audio_output *ao)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	encoder_finish(sd->encoder);
-
-	sd->Deinitialize();
-	delete sd;
-
-	shout_init_count--;
-
-	if (shout_init_count == 0)
-		shout_shutdown();
-}
-
-static void
-my_shout_drop_buffered_audio(struct audio_output *ao)
-{
-	gcc_unused
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	/* needs to be implemented for shout */
-}
-
-static void
-my_shout_close_device(struct audio_output *ao)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	close_shout_conn(sd);
-}
-
-static bool
-shout_connect(ShoutOutput *sd, Error &error)
-{
-	switch (shout_open(sd->shout_conn)) {
-	case SHOUTERR_SUCCESS:
-	case SHOUTERR_CONNECTED:
-		return true;
-
-	default:
-		error.Format(shout_output_domain,
-			     "problem opening connection to shout server %s:%i: %s",
-			     shout_get_host(sd->shout_conn),
-			     shout_get_port(sd->shout_conn),
-			     shout_get_error(sd->shout_conn));
-		return false;
-	}
-}
-
-static bool
-my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
-		     Error &error)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	if (!shout_connect(sd, error))
-		return false;
-
-	if (!encoder_open(sd->encoder, audio_format, error)) {
-		shout_close(sd->shout_conn);
-		return false;
-	}
-
-	if (!write_page(sd, error)) {
-		encoder_close(sd->encoder);
-		shout_close(sd->shout_conn);
-		return false;
-	}
-
-	return true;
-}
-
-static unsigned
-my_shout_delay(struct audio_output *ao)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	int delay = shout_delay(sd->shout_conn);
-	if (delay < 0)
-		delay = 0;
-
-	return delay;
-}
-
-static size_t
-my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
-	      Error &error)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	return encoder_write(sd->encoder, chunk, size, error) &&
-		write_page(sd, error)
-		? size
-		: 0;
-}
-
-static bool
-my_shout_pause(struct audio_output *ao)
-{
-	static char silence[1020];
-
-	return my_shout_play(ao, silence, sizeof(silence), IgnoreError());
-}
-
-static void
-shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
-{
-	char artist[size];
-	char title[size];
-
-	artist[0] = 0;
-	title[0] = 0;
-
-	for (unsigned i = 0; i < tag->num_items; i++) {
-		switch (tag->items[i]->type) {
-		case TAG_ARTIST:
-			strncpy(artist, tag->items[i]->value, size);
-			break;
-		case TAG_TITLE:
-			strncpy(title, tag->items[i]->value, size);
-			break;
-
-		default:
-			break;
-		}
-	}
-
-	snprintf(dest, size, "%s - %s", artist, title);
-}
-
-static void my_shout_set_tag(struct audio_output *ao,
-			     const Tag *tag)
-{
-	ShoutOutput *sd = (ShoutOutput *)ao;
-
-	if (sd->encoder->plugin.tag != nullptr) {
-		/* encoder plugin supports stream tags */
-
-		Error error;
-		if (!encoder_pre_tag(sd->encoder, error) ||
-		    !write_page(sd, error) ||
-		    !encoder_tag(sd->encoder, tag, error)) {
-			LogError(error);
-			return;
-		}
-	} else {
-		/* no stream tag support: fall back to icy-metadata */
-		char song[1024];
-		shout_tag_to_metadata(tag, song, sizeof(song));
-
-		shout_metadata_add(sd->shout_meta, "song", song);
-		if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
-							   sd->shout_meta)) {
-			LogWarning(shout_output_domain,
-				   "error setting shout metadata");
-		}
-	}
-
-	write_page(sd, IgnoreError());
-}
-
-const struct audio_output_plugin shout_output_plugin = {
-	"shout",
-	nullptr,
-	my_shout_init_driver,
-	my_shout_finish_driver,
-	nullptr,
-	nullptr,
-	my_shout_open_device,
-	my_shout_close_device,
-	my_shout_delay,
-	my_shout_set_tag,
-	my_shout_play,
-	nullptr,
-	my_shout_drop_buffered_audio,
-	my_shout_pause,
-	nullptr,
-};
diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/ShoutOutputPlugin.hxx
deleted file mode 100644
index d437e0b0d..000000000
--- a/src/output/ShoutOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_SHOUT_OUTPUT_PLUGIN_HXX
-#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin shout_output_plugin;
-
-#endif
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx
deleted file mode 100644
index c06c848d1..000000000
--- a/src/output/SolarisOutputPlugin.cxx
+++ /dev/null
@@ -1,201 +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.
- */
-
-#include "config.h"
-#include "SolarisOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-
-#include <sys/stropts.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#ifdef __sun
-#include <sys/audio.h>
-#else
-
-/* some fake declarations that allow build this plugin on systems
-   other than Solaris, just to see if it compiles */
-
-#define AUDIO_GETINFO 0
-#define AUDIO_SETINFO 0
-#define AUDIO_ENCODING_LINEAR 0
-
-struct audio_info {
-	struct {
-		unsigned sample_rate, channels, precision, encoding;
-	} play;
-};
-
-#endif
-
-struct SolarisOutput {
-	struct audio_output base;
-
-	/* configuration */
-	const char *device;
-
-	int fd;
-
-	bool Initialize(const config_param &param, Error &error_r) {
-		return ao_base_init(&base, &solaris_output_plugin, param,
-				    error_r);
-	}
-
-	void Deinitialize() {
-		ao_base_finish(&base);
-	}
-};
-
-static bool
-solaris_output_test_default_device(void)
-{
-	struct stat st;
-
-	return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
-		access("/dev/audio", W_OK) == 0;
-}
-
-static struct audio_output *
-solaris_output_init(const config_param &param, Error &error_r)
-{
-	SolarisOutput *so = new SolarisOutput();
-	if (!so->Initialize(param, error_r)) {
-		delete so;
-		return nullptr;
-	}
-
-	so->device = param.GetBlockValue("device", "/dev/audio");
-
-	return &so->base;
-}
-
-static void
-solaris_output_finish(struct audio_output *ao)
-{
-	SolarisOutput *so = (SolarisOutput *)ao;
-
-	so->Deinitialize();
-	delete so;
-}
-
-static bool
-solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		    Error &error)
-{
-	SolarisOutput *so = (SolarisOutput *)ao;
-	struct audio_info info;
-	int ret, flags;
-
-	/* support only 16 bit mono/stereo for now; nothing else has
-	   been tested */
-	audio_format.format = SampleFormat::S16;
-
-	/* open the device in non-blocking mode */
-
-	so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
-	if (so->fd < 0) {
-		error.FormatErrno("Failed to open %s",
-				  so->device);
-		return false;
-	}
-
-	/* restore blocking mode */
-
-	flags = fcntl(so->fd, F_GETFL);
-	if (flags > 0 && (flags & O_NONBLOCK) != 0)
-		fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
-
-	/* configure the audio device */
-
-	ret = ioctl(so->fd, AUDIO_GETINFO, &info);
-	if (ret < 0) {
-		error.SetErrno("AUDIO_GETINFO failed");
-		close(so->fd);
-		return false;
-	}
-
-	info.play.sample_rate = audio_format.sample_rate;
-	info.play.channels = audio_format.channels;
-	info.play.precision = 16;
-	info.play.encoding = AUDIO_ENCODING_LINEAR;
-
-	ret = ioctl(so->fd, AUDIO_SETINFO, &info);
-	if (ret < 0) {
-		error.SetErrno("AUDIO_SETINFO failed");
-		close(so->fd);
-		return false;
-	}
-
-	return true;
-}
-
-static void
-solaris_output_close(struct audio_output *ao)
-{
-	SolarisOutput *so = (SolarisOutput *)ao;
-
-	close(so->fd);
-}
-
-static size_t
-solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
-		    Error &error)
-{
-	SolarisOutput *so = (SolarisOutput *)ao;
-	ssize_t nbytes;
-
-	nbytes = write(so->fd, chunk, size);
-	if (nbytes <= 0) {
-		error.SetErrno("Write failed");
-		return 0;
-	}
-
-	return nbytes;
-}
-
-static void
-solaris_output_cancel(struct audio_output *ao)
-{
-	SolarisOutput *so = (SolarisOutput *)ao;
-
-	ioctl(so->fd, I_FLUSH);
-}
-
-const struct audio_output_plugin solaris_output_plugin = {
-	"solaris",
-	solaris_output_test_default_device,
-	solaris_output_init,
-	solaris_output_finish,
-	nullptr,
-	nullptr,
-	solaris_output_open,
-	solaris_output_close,
-	nullptr,
-	nullptr,
-	solaris_output_play,
-	nullptr,
-	solaris_output_cancel,
-	nullptr,
-	nullptr,
-};
diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/SolarisOutputPlugin.hxx
deleted file mode 100644
index 9ce848a40..000000000
--- a/src/output/SolarisOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +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_SOLARIS_OUTPUT_PLUGIN_HXX
-#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin solaris_output_plugin;
-
-#endif
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
deleted file mode 100644
index bc4ca9386..000000000
--- a/src/output/WinmmOutputPlugin.cxx
+++ /dev/null
@@ -1,353 +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.
- */
-
-#include "config.h"
-#include "WinmmOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "MixerList.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-
-#include <glib.h>
-
-#include <stdlib.h>
-#include <string.h>
-
-struct WinmmBuffer {
-	PcmBuffer buffer;
-
-	WAVEHDR hdr;
-};
-
-struct WinmmOutput {
-	struct audio_output base;
-
-	UINT device_id;
-	HWAVEOUT handle;
-
-	/**
-	 * This event is triggered by Windows when a buffer is
-	 * finished.
-	 */
-	HANDLE event;
-
-	WinmmBuffer buffers[8];
-	unsigned next_buffer;
-};
-
-static constexpr Domain winmm_output_domain("winmm_output");
-
-HWAVEOUT
-winmm_output_get_handle(WinmmOutput *output)
-{
-	return output->handle;
-}
-
-static bool
-winmm_output_test_default_device(void)
-{
-	return waveOutGetNumDevs() > 0;
-}
-
-static bool
-get_device_id(const char *device_name, UINT *device_id, Error &error)
-{
-	/* if device is not specified use wave mapper */
-	if (device_name == nullptr) {
-		*device_id = WAVE_MAPPER;
-		return true;
-	}
-
-	UINT numdevs = waveOutGetNumDevs();
-
-	/* check for device id */
-	char *endptr;
-	UINT id = strtoul(device_name, &endptr, 0);
-	if (endptr > device_name && *endptr == 0) {
-		if (id >= numdevs)
-			goto fail;
-		*device_id = id;
-		return true;
-	}
-
-	/* check for device name */
-	for (UINT i = 0; i < numdevs; i++) {
-		WAVEOUTCAPS caps;
-		MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
-		if (result != MMSYSERR_NOERROR)
-			continue;
-		/* szPname is only 32 chars long, so it is often truncated.
-		   Use partial match to work around this. */
-		if (strstr(device_name, caps.szPname) == device_name) {
-			*device_id = i;
-			return true;
-		}
-	}
-
-fail:
-	error.Format(winmm_output_domain,
-		     "device \"%s\" is not found", device_name);
-	return false;
-}
-
-static struct audio_output *
-winmm_output_init(const config_param &param, Error &error)
-{
-	WinmmOutput *wo = new WinmmOutput();
-	if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
-		delete wo;
-		return nullptr;
-	}
-
-	const char *device = param.GetBlockValue("device");
-	if (!get_device_id(device, &wo->device_id, error)) {
-		ao_base_finish(&wo->base);
-		delete wo;
-		return nullptr;
-	}
-
-	return &wo->base;
-}
-
-static void
-winmm_output_finish(struct audio_output *ao)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	ao_base_finish(&wo->base);
-	delete wo;
-}
-
-static bool
-winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
-		  Error &error)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	wo->event = CreateEvent(nullptr, false, false, nullptr);
-	if (wo->event == nullptr) {
-		error.Set(winmm_output_domain, "CreateEvent() failed");
-		return false;
-	}
-
-	switch (audio_format.format) {
-	case SampleFormat::S8:
-	case SampleFormat::S16:
-		break;
-
-	case SampleFormat::S24_P32:
-	case SampleFormat::S32:
-	case SampleFormat::FLOAT:
-	case SampleFormat::DSD:
-	case SampleFormat::UNDEFINED:
-		/* we havn't tested formats other than S16 */
-		audio_format.format = SampleFormat::S16;
-		break;
-	}
-
-	if (audio_format.channels > 2)
-		/* same here: more than stereo was not tested */
-		audio_format.channels = 2;
-
-	WAVEFORMATEX format;
-	format.wFormatTag = WAVE_FORMAT_PCM;
-	format.nChannels = audio_format.channels;
-	format.nSamplesPerSec = audio_format.sample_rate;
-	format.nBlockAlign = audio_format.GetFrameSize();
-	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
-	format.wBitsPerSample = audio_format.GetSampleSize() * 8;
-	format.cbSize = 0;
-
-	MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
-				      (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
-	if (result != MMSYSERR_NOERROR) {
-		CloseHandle(wo->event);
-		error.Set(winmm_output_domain, "waveOutOpen() failed");
-		return false;
-	}
-
-	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
-		memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
-	}
-
-	wo->next_buffer = 0;
-
-	return true;
-}
-
-static void
-winmm_output_close(struct audio_output *ao)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i)
-		wo->buffers[i].buffer.Clear();
-
-	waveOutClose(wo->handle);
-
-	CloseHandle(wo->event);
-}
-
-/**
- * Copy data into a buffer, and prepare the wave header.
- */
-static bool
-winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
-		 const void *data, size_t size,
-		 Error &error)
-{
-	void *dest = buffer->buffer.Get(size);
-	assert(dest != nullptr);
-
-	memcpy(dest, data, size);
-
-	memset(&buffer->hdr, 0, sizeof(buffer->hdr));
-	buffer->hdr.lpData = (LPSTR)dest;
-	buffer->hdr.dwBufferLength = size;
-
-	MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
-					       sizeof(buffer->hdr));
-	if (result != MMSYSERR_NOERROR) {
-		error.Set(winmm_output_domain, result,
-			  "waveOutPrepareHeader() failed");
-		return false;
-	}
-
-	return true;
-}
-
-/**
- * Wait until the buffer is finished.
- */
-static bool
-winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
-		   Error &error)
-{
-	if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
-		/* already finished */
-		return true;
-
-	while (true) {
-		MMRESULT result = waveOutUnprepareHeader(wo->handle,
-							 &buffer->hdr,
-							 sizeof(buffer->hdr));
-		if (result == MMSYSERR_NOERROR)
-			return true;
-		else if (result != WAVERR_STILLPLAYING) {
-			error.Set(winmm_output_domain, result,
-				  "waveOutUnprepareHeader() failed");
-			return false;
-		}
-
-		/* wait some more */
-		WaitForSingleObject(wo->event, INFINITE);
-	}
-}
-
-static size_t
-winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	/* get the next buffer from the ring and prepare it */
-	WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
-	if (!winmm_drain_buffer(wo, buffer, error) ||
-	    !winmm_set_buffer(wo, buffer, chunk, size, error))
-		return 0;
-
-	/* enqueue the buffer */
-	MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
-				       sizeof(buffer->hdr));
-	if (result != MMSYSERR_NOERROR) {
-		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
-				       sizeof(buffer->hdr));
-		error.Set(winmm_output_domain, result,
-			  "waveOutWrite() failed");
-		return 0;
-	}
-
-	/* mark our buffer as "used" */
-	wo->next_buffer = (wo->next_buffer + 1) %
-		ARRAY_SIZE(wo->buffers);
-
-	return size;
-}
-
-static bool
-winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
-{
-	for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
-		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
-			return false;
-
-	for (unsigned i = 0; i < wo->next_buffer; ++i)
-		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
-			return false;
-
-	return true;
-}
-
-static void
-winmm_stop(WinmmOutput *wo)
-{
-	waveOutReset(wo->handle);
-
-	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
-		WinmmBuffer *buffer = &wo->buffers[i];
-		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
-				       sizeof(buffer->hdr));
-	}
-}
-
-static void
-winmm_output_drain(struct audio_output *ao)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	if (!winmm_drain_all_buffers(wo, IgnoreError()))
-		winmm_stop(wo);
-}
-
-static void
-winmm_output_cancel(struct audio_output *ao)
-{
-	WinmmOutput *wo = (WinmmOutput *)ao;
-
-	winmm_stop(wo);
-}
-
-const struct audio_output_plugin winmm_output_plugin = {
-	"winmm",
-	winmm_output_test_default_device,
-	winmm_output_init,
-	winmm_output_finish,
-	nullptr,
-	nullptr,
-	winmm_output_open,
-	winmm_output_close,
-	nullptr,
-	nullptr,
-	winmm_output_play,
-	winmm_output_drain,
-	winmm_output_cancel,
-	nullptr,
-	&winmm_mixer_plugin,
-};
diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/WinmmOutputPlugin.hxx
deleted file mode 100644
index 1409a2e8c..000000000
--- a/src/output/WinmmOutputPlugin.hxx
+++ /dev/null
@@ -1,42 +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_WINMM_OUTPUT_PLUGIN_HXX
-#define MPD_WINMM_OUTPUT_PLUGIN_HXX
-
-#include "check.h"
-
-#ifdef ENABLE_WINMM_OUTPUT
-
-#include "Compiler.h"
-
-#include <windows.h>
-#include <mmsystem.h>
-
-struct WinmmOutput;
-
-extern const struct audio_output_plugin winmm_output_plugin;
-
-gcc_pure
-HWAVEOUT
-winmm_output_get_handle(WinmmOutput *);
-
-#endif
-
-#endif
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
new file mode 100644
index 000000000..f2e4fc643
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -0,0 +1,868 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AlsaOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "pcm/PcmExport.hxx"
+#include "util/Manual.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <alsa/asoundlib.h>
+
+#include <string>
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+static const char default_device[] = "default";
+
+static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
+
+#define MPD_ALSA_RETRY_NR 5
+
+typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
+					snd_pcm_uframes_t size);
+
+struct AlsaOutput {
+	struct audio_output base;
+
+	Manual<PcmExport> pcm_export;
+
+	/**
+	 * The configured name of the ALSA device; empty for the
+	 * default device
+	 */
+	std::string device;
+
+	/** use memory mapped I/O? */
+	bool use_mmap;
+
+	/**
+	 * Enable DSD over USB according to the dCS suggested
+	 * standard?
+	 *
+	 * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
+	 */
+	bool dsd_usb;
+
+	/** libasound's buffer_time setting (in microseconds) */
+	unsigned int buffer_time;
+
+	/** libasound's period_time setting (in microseconds) */
+	unsigned int period_time;
+
+	/** the mode flags passed to snd_pcm_open */
+	int mode;
+
+	/** the libasound PCM device handle */
+	snd_pcm_t *pcm;
+
+	/**
+	 * a pointer to the libasound writei() function, which is
+	 * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
+	 * use_mmap configuration
+	 */
+	alsa_writei_t *writei;
+
+	/**
+	 * The size of one audio frame passed to method play().
+	 */
+	size_t in_frame_size;
+
+	/**
+	 * The size of one audio frame passed to libasound.
+	 */
+	size_t out_frame_size;
+
+	/**
+	 * The size of one period, in number of frames.
+	 */
+	snd_pcm_uframes_t period_frames;
+
+	/**
+	 * The number of frames written in the current period.
+	 */
+	snd_pcm_uframes_t period_position;
+
+	/**
+	 * Set to non-zero when the Raspberry Pi workaround has been
+	 * activated in alsa_recover(); decremented by each write.
+	 * This will avoid activating it again, leading to an endless
+	 * loop.  This problem was observed with a "RME Digi9636/52".
+	 */
+	unsigned pi_workaround;
+
+	/**
+	 * This buffer gets allocated after opening the ALSA device.
+	 * It contains silence samples, enough to fill one period (see
+	 * #period_frames).
+	 */
+	uint8_t *silence;
+
+	AlsaOutput():mode(0), writei(snd_pcm_writei) {
+	}
+
+	bool Init(const config_param &param, Error &error) {
+		return ao_base_init(&base, &alsa_output_plugin,
+				    param, error);
+	}
+
+	void Deinit() {
+		ao_base_finish(&base);
+	}
+};
+
+static constexpr Domain alsa_output_domain("alsa_output");
+
+static const char *
+alsa_device(const AlsaOutput *ad)
+{
+	return ad->device.empty() ? default_device : ad->device.c_str();
+}
+
+static void
+alsa_configure(AlsaOutput *ad, const config_param &param)
+{
+	ad->device = param.GetBlockValue("device", "");
+
+	ad->use_mmap = param.GetBlockValue("use_mmap", false);
+
+	ad->dsd_usb = param.GetBlockValue("dsd_usb", false);
+
+	ad->buffer_time = param.GetBlockValue("buffer_time",
+					      MPD_ALSA_BUFFER_TIME_US);
+	ad->period_time = param.GetBlockValue("period_time", 0u);
+
+#ifdef SND_PCM_NO_AUTO_RESAMPLE
+	if (!param.GetBlockValue("auto_resample", true))
+		ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_CHANNELS
+	if (!param.GetBlockValue("auto_channels", true))
+		ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_FORMAT
+	if (!param.GetBlockValue("auto_format", true))
+		ad->mode |= SND_PCM_NO_AUTO_FORMAT;
+#endif
+}
+
+static struct audio_output *
+alsa_init(const config_param &param, Error &error)
+{
+	AlsaOutput *ad = new AlsaOutput();
+
+	if (!ad->Init(param, error)) {
+		delete ad;
+		return nullptr;
+	}
+
+	alsa_configure(ad, param);
+
+	return &ad->base;
+}
+
+static void
+alsa_finish(struct audio_output *ao)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	ad->Deinit();
+	delete ad;
+
+	/* free libasound's config cache */
+	snd_config_update_free_global();
+}
+
+static bool
+alsa_output_enable(struct audio_output *ao, gcc_unused Error &error)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	ad->pcm_export.Construct();
+	return true;
+}
+
+static void
+alsa_output_disable(struct audio_output *ao)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	ad->pcm_export.Destruct();
+}
+
+static bool
+alsa_test_default_device(void)
+{
+	snd_pcm_t *handle;
+
+	int ret = snd_pcm_open(&handle, default_device,
+	                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+	if (ret) {
+		FormatError(alsa_output_domain,
+			    "Error opening default ALSA device: %s",
+			    snd_strerror(-ret));
+		return false;
+	} else
+		snd_pcm_close(handle);
+
+	return true;
+}
+
+static snd_pcm_format_t
+get_bitformat(SampleFormat sample_format)
+{
+	switch (sample_format) {
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::DSD:
+		return SND_PCM_FORMAT_UNKNOWN;
+
+	case SampleFormat::S8:
+		return SND_PCM_FORMAT_S8;
+
+	case SampleFormat::S16:
+		return SND_PCM_FORMAT_S16;
+
+	case SampleFormat::S24_P32:
+		return SND_PCM_FORMAT_S24;
+
+	case SampleFormat::S32:
+		return SND_PCM_FORMAT_S32;
+
+	case SampleFormat::FLOAT:
+		return SND_PCM_FORMAT_FLOAT;
+	}
+
+	assert(false);
+	gcc_unreachable();
+}
+
+static snd_pcm_format_t
+byteswap_bitformat(snd_pcm_format_t fmt)
+{
+	switch(fmt) {
+	case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+	case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+	case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+	case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+	case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+
+	case SND_PCM_FORMAT_S24_3BE:
+		return SND_PCM_FORMAT_S24_3LE;
+
+	case SND_PCM_FORMAT_S24_3LE:
+		return SND_PCM_FORMAT_S24_3BE;
+
+	case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+	default: return SND_PCM_FORMAT_UNKNOWN;
+	}
+}
+
+static snd_pcm_format_t
+alsa_to_packed_format(snd_pcm_format_t fmt)
+{
+	switch (fmt) {
+	case SND_PCM_FORMAT_S24_LE:
+		return SND_PCM_FORMAT_S24_3LE;
+
+	case SND_PCM_FORMAT_S24_BE:
+		return SND_PCM_FORMAT_S24_3BE;
+
+	default:
+		return SND_PCM_FORMAT_UNKNOWN;
+	}
+}
+
+static int
+alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+			  snd_pcm_format_t fmt, bool *packed_r)
+{
+	int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+	if (err == 0)
+		*packed_r = false;
+
+	if (err != -EINVAL)
+		return err;
+
+	fmt = alsa_to_packed_format(fmt);
+	if (fmt == SND_PCM_FORMAT_UNKNOWN)
+		return -EINVAL;
+
+	err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+	if (err == 0)
+		*packed_r = true;
+
+	return err;
+}
+
+/**
+ * Attempts to configure the specified sample format, and tries the
+ * reversed host byte order if was not supported.
+ */
+static int
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+		       SampleFormat sample_format,
+		       bool *packed_r, bool *reverse_endian_r)
+{
+	snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+		return -EINVAL;
+
+	int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
+					    packed_r);
+	if (err == 0)
+		*reverse_endian_r = false;
+
+	if (err != -EINVAL)
+		return err;
+
+	alsa_format = byteswap_bitformat(alsa_format);
+	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+		return -EINVAL;
+
+	err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
+	if (err == 0)
+		*reverse_endian_r = true;
+
+	return err;
+}
+
+/**
+ * Configure a sample format, and probe other formats if that fails.
+ */
+static int
+alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+			 AudioFormat &audio_format,
+			 bool *packed_r, bool *reverse_endian_r)
+{
+	/* try the input format first */
+
+	int err = alsa_output_try_format(pcm, hwparams,
+					 audio_format.format,
+					 packed_r, reverse_endian_r);
+
+	/* if unsupported by the hardware, try other formats */
+
+	static const SampleFormat probe_formats[] = {
+		SampleFormat::S24_P32,
+		SampleFormat::S32,
+		SampleFormat::S16,
+		SampleFormat::S8,
+		SampleFormat::UNDEFINED,
+	};
+
+	for (unsigned i = 0;
+	     err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
+	     ++i) {
+		const SampleFormat mpd_format = probe_formats[i];
+		if (mpd_format == audio_format.format)
+			continue;
+
+		err = alsa_output_try_format(pcm, hwparams, mpd_format,
+					     packed_r, reverse_endian_r);
+		if (err == 0)
+			audio_format.format = mpd_format;
+	}
+
+	return err;
+}
+
+/**
+ * Set up the snd_pcm_t object which was opened by the caller.  Set up
+ * the configured settings and the audio format.
+ */
+static bool
+alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
+	   bool *packed_r, bool *reverse_endian_r, Error &error)
+{
+	unsigned int sample_rate = audio_format.sample_rate;
+	unsigned int channels = audio_format.channels;
+	int err;
+	const char *cmd = nullptr;
+	int retry = MPD_ALSA_RETRY_NR;
+	unsigned int period_time, period_time_ro;
+	unsigned int buffer_time;
+
+	period_time_ro = period_time = ad->period_time;
+configure_hw:
+	/* configure HW params */
+	snd_pcm_hw_params_t *hwparams;
+	snd_pcm_hw_params_alloca(&hwparams);
+	cmd = "snd_pcm_hw_params_any";
+	err = snd_pcm_hw_params_any(ad->pcm, hwparams);
+	if (err < 0)
+		goto error;
+
+	if (ad->use_mmap) {
+		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+						   SND_PCM_ACCESS_MMAP_INTERLEAVED);
+		if (err < 0) {
+			FormatWarning(alsa_output_domain,
+				      "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
+				      alsa_device(ad), snd_strerror(-err));
+			LogWarning(alsa_output_domain,
+				   "Falling back to direct write mode");
+			ad->use_mmap = false;
+		} else
+			ad->writei = snd_pcm_mmap_writei;
+	}
+
+	if (!ad->use_mmap) {
+		cmd = "snd_pcm_hw_params_set_access";
+		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+						   SND_PCM_ACCESS_RW_INTERLEAVED);
+		if (err < 0)
+			goto error;
+		ad->writei = snd_pcm_writei;
+	}
+
+	err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
+				       packed_r, reverse_endian_r);
+	if (err < 0) {
+		error.Format(alsa_output_domain, err,
+			     "ALSA device \"%s\" does not support format %s: %s",
+			     alsa_device(ad),
+			     sample_format_to_string(audio_format.format),
+			     snd_strerror(-err));
+		return false;
+	}
+
+	snd_pcm_format_t format;
+	if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
+		FormatDebug(alsa_output_domain,
+			    "format=%s (%s)", snd_pcm_format_name(format),
+			    snd_pcm_format_description(format));
+
+	err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
+						  &channels);
+	if (err < 0) {
+		error.Format(alsa_output_domain, err,
+			     "ALSA device \"%s\" does not support %i channels: %s",
+			     alsa_device(ad), (int)audio_format.channels,
+			     snd_strerror(-err));
+		return false;
+	}
+	audio_format.channels = (int8_t)channels;
+
+	err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
+					      &sample_rate, nullptr);
+	if (err < 0 || sample_rate == 0) {
+		error.Format(alsa_output_domain, err,
+			     "ALSA device \"%s\" does not support %u Hz audio",
+			     alsa_device(ad), audio_format.sample_rate);
+		return false;
+	}
+	audio_format.sample_rate = sample_rate;
+
+	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
+	snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
+	snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
+	unsigned buffer_time_min, buffer_time_max;
+	snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
+	snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
+	FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
+		    (unsigned)buffer_size_min, (unsigned)buffer_size_max,
+		    buffer_time_min, buffer_time_max);
+
+	snd_pcm_uframes_t period_size_min, period_size_max;
+	snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
+	snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
+	unsigned period_time_min, period_time_max;
+	snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
+	snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
+	FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
+		    (unsigned)period_size_min, (unsigned)period_size_max,
+		    period_time_min, period_time_max);
+
+	if (ad->buffer_time > 0) {
+		buffer_time = ad->buffer_time;
+		cmd = "snd_pcm_hw_params_set_buffer_time_near";
+		err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
+							     &buffer_time, nullptr);
+		if (err < 0)
+			goto error;
+	} else {
+		err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
+							nullptr);
+		if (err < 0)
+			buffer_time = 0;
+	}
+
+	if (period_time_ro == 0 && buffer_time >= 10000) {
+		period_time_ro = period_time = buffer_time / 4;
+
+		FormatDebug(alsa_output_domain,
+			    "default period_time = buffer_time/4 = %u/4 = %u",
+			    buffer_time, period_time);
+	}
+
+	if (period_time_ro > 0) {
+		period_time = period_time_ro;
+		cmd = "snd_pcm_hw_params_set_period_time_near";
+		err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
+							     &period_time, nullptr);
+		if (err < 0)
+			goto error;
+	}
+
+	cmd = "snd_pcm_hw_params";
+	err = snd_pcm_hw_params(ad->pcm, hwparams);
+	if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
+		period_time_ro = period_time_ro >> 1;
+		goto configure_hw;
+	} else if (err < 0)
+		goto error;
+	if (retry != MPD_ALSA_RETRY_NR)
+		FormatDebug(alsa_output_domain,
+			    "ALSA period_time set to %d", period_time);
+
+	snd_pcm_uframes_t alsa_buffer_size;
+	cmd = "snd_pcm_hw_params_get_buffer_size";
+	err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
+	if (err < 0)
+		goto error;
+
+	snd_pcm_uframes_t alsa_period_size;
+	cmd = "snd_pcm_hw_params_get_period_size";
+	err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
+						nullptr);
+	if (err < 0)
+		goto error;
+
+	/* configure SW params */
+	snd_pcm_sw_params_t *swparams;
+	snd_pcm_sw_params_alloca(&swparams);
+
+	cmd = "snd_pcm_sw_params_current";
+	err = snd_pcm_sw_params_current(ad->pcm, swparams);
+	if (err < 0)
+		goto error;
+
+	cmd = "snd_pcm_sw_params_set_start_threshold";
+	err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
+						    alsa_buffer_size -
+						    alsa_period_size);
+	if (err < 0)
+		goto error;
+
+	cmd = "snd_pcm_sw_params_set_avail_min";
+	err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
+					      alsa_period_size);
+	if (err < 0)
+		goto error;
+
+	cmd = "snd_pcm_sw_params";
+	err = snd_pcm_sw_params(ad->pcm, swparams);
+	if (err < 0)
+		goto error;
+
+	FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
+		    (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+
+	if (alsa_period_size == 0)
+		/* this works around a SIGFPE bug that occurred when
+		   an ALSA driver indicated period_size==0; this
+		   caused a division by zero in alsa_play().  By using
+		   the fallback "1", we make sure that this won't
+		   happen again. */
+		alsa_period_size = 1;
+
+	ad->period_frames = alsa_period_size;
+	ad->period_position = 0;
+
+	ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
+							  alsa_period_size)];
+	snd_pcm_format_set_silence(format, ad->silence,
+				   alsa_period_size * channels);
+
+	return true;
+
+error:
+	error.Format(alsa_output_domain, err,
+		     "Error opening ALSA device \"%s\" (%s): %s",
+		     alsa_device(ad), cmd, snd_strerror(-err));
+	return false;
+}
+
+static bool
+alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
+	       bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+	       Error &error)
+{
+	assert(ad->dsd_usb);
+	assert(audio_format.format == SampleFormat::DSD);
+
+	/* pass 24 bit to alsa_setup() */
+
+	AudioFormat usb_format = audio_format;
+	usb_format.format = SampleFormat::S24_P32;
+	usb_format.sample_rate /= 2;
+
+	const AudioFormat check = usb_format;
+
+	if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error))
+		return false;
+
+	/* if the device allows only 32 bit, shift all DSD-over-USB
+	   samples left by 8 bit and leave the lower 8 bit cleared;
+	   the DSD-over-USB documentation does not specify whether
+	   this is legal, but there is anecdotical evidence that this
+	   is possible (and the only option for some devices) */
+	*shift8_r = usb_format.format == SampleFormat::S32;
+	if (usb_format.format == SampleFormat::S32)
+		usb_format.format = SampleFormat::S24_P32;
+
+	if (usb_format != check) {
+		/* no bit-perfect playback, which is required
+		   for DSD over USB */
+		error.Format(alsa_output_domain,
+			     "Failed to configure DSD-over-USB on ALSA device \"%s\"",
+			     alsa_device(ad));
+		delete[] ad->silence;
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
+		  Error &error)
+{
+	bool shift8 = false, packed, reverse_endian;
+
+	const bool dsd_usb = ad->dsd_usb &&
+		audio_format.format == SampleFormat::DSD;
+	const bool success = dsd_usb
+		? alsa_setup_dsd(ad, audio_format,
+				 &shift8, &packed, &reverse_endian,
+				 error)
+		: alsa_setup(ad, audio_format, &packed, &reverse_endian,
+			     error);
+	if (!success)
+		return false;
+
+	ad->pcm_export->Open(audio_format.format,
+			     audio_format.channels,
+			     dsd_usb, shift8, packed, reverse_endian);
+	return true;
+}
+
+static bool
+alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	ad->pi_workaround = 0;
+
+	int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
+			       SND_PCM_STREAM_PLAYBACK, ad->mode);
+	if (err < 0) {
+		error.Format(alsa_output_domain, err,
+			    "Failed to open ALSA device \"%s\": %s",
+			    alsa_device(ad), snd_strerror(err));
+		return false;
+	}
+
+	FormatDebug(alsa_output_domain, "opened %s type=%s",
+		    snd_pcm_name(ad->pcm),
+		    snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+	if (!alsa_setup_or_dsd(ad, audio_format, error)) {
+		snd_pcm_close(ad->pcm);
+		return false;
+	}
+
+	ad->in_frame_size = audio_format.GetFrameSize();
+	ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
+
+	return true;
+}
+
+/**
+ * Write silence to the ALSA device.
+ */
+static void
+alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
+{
+	ad->writei(ad->pcm, ad->silence, nframes);
+}
+
+static int
+alsa_recover(AlsaOutput *ad, int err)
+{
+	if (err == -EPIPE) {
+		FormatDebug(alsa_output_domain,
+			    "Underrun on ALSA device \"%s\"", alsa_device(ad));
+	} else if (err == -ESTRPIPE) {
+		FormatDebug(alsa_output_domain,
+			    "ALSA device \"%s\" was suspended",
+			    alsa_device(ad));
+	}
+
+	switch (snd_pcm_state(ad->pcm)) {
+	case SND_PCM_STATE_PAUSED:
+		err = snd_pcm_pause(ad->pcm, /* disable */ 0);
+		break;
+	case SND_PCM_STATE_SUSPENDED:
+		err = snd_pcm_resume(ad->pcm);
+		if (err == -EAGAIN)
+			return 0;
+		/* fall-through to snd_pcm_prepare: */
+	case SND_PCM_STATE_SETUP:
+	case SND_PCM_STATE_XRUN:
+		ad->period_position = 0;
+		err = snd_pcm_prepare(ad->pcm);
+
+		if (err == 0 && ad->pi_workaround == 0) {
+			/* this works around a driver bug observed on
+			   the Raspberry Pi: after snd_pcm_drop(), the
+			   whole ring buffer must be invalidated, but
+			   the snd_pcm_prepare() call above makes the
+			   driver play random data that just happens
+			   to be still in the buffer; by adding and
+			   cancelling some silence, this bug does not
+			   occur */
+			alsa_write_silence(ad, ad->period_frames);
+
+			/* cancel the silence data right away to avoid
+			   increasing latency; even though this
+			   function call invalidates the portion of
+			   silence, the driver seems to avoid the
+			   bug */
+			snd_pcm_reset(ad->pcm);
+
+			/* disable the workaround for some time */
+			ad->pi_workaround = 8;
+		}
+
+		break;
+	case SND_PCM_STATE_DISCONNECTED:
+		break;
+	/* this is no error, so just keep running */
+	case SND_PCM_STATE_RUNNING:
+		err = 0;
+		break;
+	default:
+		/* unknown state, do nothing */
+		break;
+	}
+
+	return err;
+}
+
+static void
+alsa_drain(struct audio_output *ao)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+		return;
+
+	if (ad->period_position > 0) {
+		/* generate some silence to finish the partial
+		   period */
+		snd_pcm_uframes_t nframes =
+			ad->period_frames - ad->period_position;
+		alsa_write_silence(ad, nframes);
+	}
+
+	snd_pcm_drain(ad->pcm);
+
+	ad->period_position = 0;
+}
+
+static void
+alsa_cancel(struct audio_output *ao)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	ad->period_position = 0;
+
+	snd_pcm_drop(ad->pcm);
+}
+
+static void
+alsa_close(struct audio_output *ao)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	snd_pcm_close(ad->pcm);
+	delete[] ad->silence;
+}
+
+static size_t
+alsa_play(struct audio_output *ao, const void *chunk, size_t size,
+	  Error &error)
+{
+	AlsaOutput *ad = (AlsaOutput *)ao;
+
+	assert(size % ad->in_frame_size == 0);
+
+	chunk = ad->pcm_export->Export(chunk, size, size);
+
+	assert(size % ad->out_frame_size == 0);
+
+	size /= ad->out_frame_size;
+
+	while (true) {
+		snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
+		if (ret > 0) {
+			ad->period_position = (ad->period_position + ret)
+				% ad->period_frames;
+
+			if (ad->pi_workaround > 0)
+				--ad->pi_workaround;
+
+			size_t bytes_written = ret * ad->out_frame_size;
+			return ad->pcm_export->CalcSourceSize(bytes_written);
+		}
+
+		if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
+		    alsa_recover(ad, ret) < 0) {
+			error.Set(alsa_output_domain, ret, snd_strerror(-ret));
+			return 0;
+		}
+	}
+}
+
+const struct audio_output_plugin alsa_output_plugin = {
+	"alsa",
+	alsa_test_default_device,
+	alsa_init,
+	alsa_finish,
+	alsa_output_enable,
+	alsa_output_disable,
+	alsa_open,
+	alsa_close,
+	nullptr,
+	nullptr,
+	alsa_play,
+	alsa_drain,
+	alsa_cancel,
+	nullptr,
+
+	&alsa_mixer_plugin,
+};
diff --git a/src/output/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx
new file mode 100644
index 000000000..63508e041
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_ALSA_OUTPUT_PLUGIN_HXX
+#define MPD_ALSA_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
new file mode 100644
index 000000000..efc1e0c6e
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AoOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <ao/ao.h>
+#include <glib.h>
+
+#include <string.h>
+
+/* An ao_sample_format, with all fields set to zero: */
+static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
+
+static unsigned ao_output_ref;
+
+struct AoOutput {
+	struct audio_output base;
+
+	size_t write_size;
+	int driver;
+	ao_option *options;
+	ao_device *device;
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &ao_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain ao_output_domain("ao_output");
+
+static void
+ao_output_error(Error &error_r)
+{
+	const char *error;
+
+	switch (errno) {
+	case AO_ENODRIVER:
+		error = "No such libao driver";
+		break;
+
+	case AO_ENOTLIVE:
+		error = "This driver is not a libao live device";
+		break;
+
+	case AO_EBADOPTION:
+		error = "Invalid libao option";
+		break;
+
+	case AO_EOPENDEVICE:
+		error = "Cannot open the libao device";
+		break;
+
+	case AO_EFAIL:
+		error = "Generic libao failure";
+		break;
+
+	default:
+		error_r.SetErrno();
+		return;
+	}
+
+	error_r.Set(ao_output_domain, errno, error);
+}
+
+inline bool
+AoOutput::Configure(const config_param &param, Error &error)
+{
+	const char *value;
+
+	options = nullptr;
+
+	write_size = param.GetBlockValue("write_size", 1024u);
+
+	if (ao_output_ref == 0) {
+		ao_initialize();
+	}
+	ao_output_ref++;
+
+	value = param.GetBlockValue("driver", "default");
+	if (0 == strcmp(value, "default"))
+		driver = ao_default_driver_id();
+	else
+		driver = ao_driver_id(value);
+
+	if (driver < 0) {
+		error.Format(ao_output_domain,
+			     "\"%s\" is not a valid ao driver",
+			     value);
+		return false;
+	}
+
+	ao_info *ai = ao_driver_info(driver);
+	if (ai == nullptr) {
+		error.Set(ao_output_domain, "problems getting driver info");
+		return false;
+	}
+
+	FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
+		    ai->short_name, param.GetBlockValue("name", nullptr));
+
+	value = param.GetBlockValue("options", nullptr);
+	if (value != nullptr) {
+		gchar **_options = g_strsplit(value, ";", 0);
+
+		for (unsigned i = 0; _options[i] != nullptr; ++i) {
+			gchar **key_value = g_strsplit(_options[i], "=", 2);
+
+			if (key_value[0] == nullptr || key_value[1] == nullptr) {
+				error.Format(ao_output_domain,
+					     "problems parsing options \"%s\"",
+					     _options[i]);
+				return false;
+			}
+
+			ao_append_option(&options, key_value[0],
+					 key_value[1]);
+
+			g_strfreev(key_value);
+		}
+
+		g_strfreev(_options);
+	}
+
+	return true;
+}
+
+static struct audio_output *
+ao_output_init(const config_param &param, Error &error)
+{
+	AoOutput *ad = new AoOutput();
+
+	if (!ad->Initialize(param, error)) {
+		delete ad;
+		return nullptr;
+	}
+
+	if (!ad->Configure(param, error)) {
+		ad->Deinitialize();
+		delete ad;
+		return nullptr;
+	}
+
+	return &ad->base;
+}
+
+static void
+ao_output_finish(struct audio_output *ao)
+{
+	AoOutput *ad = (AoOutput *)ao;
+
+	ao_free_options(ad->options);
+	ad->Deinitialize();
+	delete ad;
+
+	ao_output_ref--;
+
+	if (ao_output_ref == 0)
+		ao_shutdown();
+}
+
+static void
+ao_output_close(struct audio_output *ao)
+{
+	AoOutput *ad = (AoOutput *)ao;
+
+	ao_close(ad->device);
+}
+
+static bool
+ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
+	       Error &error)
+{
+	ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
+	AoOutput *ad = (AoOutput *)ao;
+
+	switch (audio_format.format) {
+	case SampleFormat::S8:
+		format.bits = 8;
+		break;
+
+	case SampleFormat::S16:
+		format.bits = 16;
+		break;
+
+	default:
+		/* support for 24 bit samples in libao is currently
+		   dubious, and until we have sorted that out,
+		   convert everything to 16 bit */
+		audio_format.format = SampleFormat::S16;
+		format.bits = 16;
+		break;
+	}
+
+	format.rate = audio_format.sample_rate;
+	format.byte_format = AO_FMT_NATIVE;
+	format.channels = audio_format.channels;
+
+	ad->device = ao_open_live(ad->driver, &format, ad->options);
+
+	if (ad->device == nullptr) {
+		ao_output_error(error);
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * For whatever reason, libao wants a non-const pointer.  Let's hope
+ * it does not write to the buffer, and use the union deconst hack to
+ * work around this API misdesign.
+ */
+static int ao_play_deconst(ao_device *device, const void *output_samples,
+			   uint_32 num_bytes)
+{
+	union {
+		const void *in;
+		char *out;
+	} u;
+
+	u.in = output_samples;
+	return ao_play(device, u.out, num_bytes);
+}
+
+static size_t
+ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
+	       Error &error)
+{
+	AoOutput *ad = (AoOutput *)ao;
+
+	if (size > ad->write_size)
+		size = ad->write_size;
+
+	if (ao_play_deconst(ad->device, chunk, size) == 0) {
+		ao_output_error(error);
+		return 0;
+	}
+
+	return size;
+}
+
+const struct audio_output_plugin ao_output_plugin = {
+	"ao",
+	nullptr,
+	ao_output_init,
+	ao_output_finish,
+	nullptr,
+	nullptr,
+	ao_output_open,
+	ao_output_close,
+	nullptr,
+	nullptr,
+	ao_output_play,
+	nullptr,
+	nullptr,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx
new file mode 100644
index 000000000..cbf2fd589
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_AO_OUTPUT_PLUGIN_HXX
+#define MPD_AO_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin ao_output_plugin;
+
+#endif
diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
new file mode 100644
index 000000000..5f14bcbbe
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FifoOutputPlugin.hxx"
+#include "ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "Timer.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "open.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+
+struct FifoOutput {
+	struct audio_output base;
+
+	AllocatedPath path;
+	std::string path_utf8;
+
+	int input;
+	int output;
+	bool created;
+	Timer *timer;
+
+	FifoOutput()
+		:path(AllocatedPath::Null()), input(-1), output(-1),
+		 created(false) {}
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &fifo_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	bool Create(Error &error);
+	bool Check(Error &error);
+	void Delete();
+
+	bool Open(Error &error);
+	void Close();
+};
+
+static constexpr Domain fifo_output_domain("fifo_output");
+
+inline void
+FifoOutput::Delete()
+{
+	FormatDebug(fifo_output_domain,
+		    "Removing FIFO \"%s\"", path_utf8.c_str());
+
+	if (!RemoveFile(path)) {
+		FormatErrno(fifo_output_domain,
+			    "Could not remove FIFO \"%s\"",
+			    path_utf8.c_str());
+		return;
+	}
+
+	created = false;
+}
+
+void
+FifoOutput::Close()
+{
+	if (input >= 0) {
+		close(input);
+		input = -1;
+	}
+
+	if (output >= 0) {
+		close(output);
+		output = -1;
+	}
+
+	struct stat st;
+	if (created && StatFile(path, st))
+		Delete();
+}
+
+inline bool
+FifoOutput::Create(Error &error)
+{
+	if (!MakeFifo(path, 0666)) {
+		error.FormatErrno("Couldn't create FIFO \"%s\"",
+				  path_utf8.c_str());
+		return false;
+	}
+
+	created = true;
+	return true;
+}
+
+inline bool
+FifoOutput::Check(Error &error)
+{
+	struct stat st;
+	if (!StatFile(path, st)) {
+		if (errno == ENOENT) {
+			/* Path doesn't exist */
+			return Create(error);
+		}
+
+		error.FormatErrno("Failed to stat FIFO \"%s\"",
+				  path_utf8.c_str());
+		return false;
+	}
+
+	if (!S_ISFIFO(st.st_mode)) {
+		error.Format(fifo_output_domain,
+			     "\"%s\" already exists, but is not a FIFO",
+			     path_utf8.c_str());
+		return false;
+	}
+
+	return true;
+}
+
+inline bool
+FifoOutput::Open(Error &error)
+{
+	if (!Check(error))
+		return false;
+
+	input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
+	if (input < 0) {
+		error.FormatErrno("Could not open FIFO \"%s\" for reading",
+				  path_utf8.c_str());
+		Close();
+		return false;
+	}
+
+	output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
+	if (output < 0) {
+		error.FormatErrno("Could not open FIFO \"%s\" for writing",
+				  path_utf8.c_str());
+		Close();
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+fifo_open(FifoOutput *fd, Error &error)
+{
+	return fd->Open(error);
+}
+
+static struct audio_output *
+fifo_output_init(const config_param &param, Error &error)
+{
+	FifoOutput *fd = new FifoOutput();
+
+	fd->path = param.GetBlockPath("path", error);
+	if (fd->path.IsNull()) {
+		delete fd;
+
+		if (!error.IsDefined())
+			error.Set(config_domain,
+				  "No \"path\" parameter specified");
+		return nullptr;
+	}
+
+	fd->path_utf8 = fd->path.ToUTF8();
+
+	if (!fd->Initialize(param, error)) {
+		delete fd;
+		return nullptr;
+	}
+
+	if (!fifo_open(fd, error)) {
+		fd->Deinitialize();
+		delete fd;
+		return nullptr;
+	}
+
+	return &fd->base;
+}
+
+static void
+fifo_output_finish(struct audio_output *ao)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+
+	fd->Close();
+	fd->Deinitialize();
+	delete fd;
+}
+
+static bool
+fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		 gcc_unused Error &error)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+
+	fd->timer = new Timer(audio_format);
+
+	return true;
+}
+
+static void
+fifo_output_close(struct audio_output *ao)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+
+	delete fd->timer;
+}
+
+static void
+fifo_output_cancel(struct audio_output *ao)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+	char buf[FIFO_BUFFER_SIZE];
+	int bytes = 1;
+
+	fd->timer->Reset();
+
+	while (bytes > 0 && errno != EINTR)
+		bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
+
+	if (bytes < 0 && errno != EAGAIN) {
+		FormatErrno(fifo_output_domain,
+			    "Flush of FIFO \"%s\" failed",
+			    fd->path_utf8.c_str());
+	}
+}
+
+static unsigned
+fifo_output_delay(struct audio_output *ao)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+
+	return fd->timer->IsStarted()
+		? fd->timer->GetDelay()
+		: 0;
+}
+
+static size_t
+fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		 Error &error)
+{
+	FifoOutput *fd = (FifoOutput *)ao;
+	ssize_t bytes;
+
+	if (!fd->timer->IsStarted())
+		fd->timer->Start();
+	fd->timer->Add(size);
+
+	while (true) {
+		bytes = write(fd->output, chunk, size);
+		if (bytes > 0)
+			return (size_t)bytes;
+
+		if (bytes < 0) {
+			switch (errno) {
+			case EAGAIN:
+				/* The pipe is full, so empty it */
+				fifo_output_cancel(&fd->base);
+				continue;
+			case EINTR:
+				continue;
+			}
+
+			error.FormatErrno("Failed to write to FIFO %s",
+					  fd->path_utf8.c_str());
+			return 0;
+		}
+	}
+}
+
+const struct audio_output_plugin fifo_output_plugin = {
+	"fifo",
+	nullptr,
+	fifo_output_init,
+	fifo_output_finish,
+	nullptr,
+	nullptr,
+	fifo_output_open,
+	fifo_output_close,
+	fifo_output_delay,
+	nullptr,
+	fifo_output_play,
+	nullptr,
+	fifo_output_cancel,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx
new file mode 100644
index 000000000..394ec3ae9
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_FIFO_OUTPUT_PLUGIN_HXX
+#define MPD_FIFO_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/plugins/HttpdClient.cxx b/src/output/plugins/HttpdClient.cxx
new file mode 100644
index 000000000..d761bdf57
--- /dev/null
+++ b/src/output/plugins/HttpdClient.cxx
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdClient.hxx"
+#include "HttpdInternal.hxx"
+#include "util/ASCII.hxx"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/SocketError.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+HttpdClient::~HttpdClient()
+{
+	if (state == RESPONSE) {
+		if (current_page != nullptr)
+			current_page->Unref();
+
+		ClearQueue();
+	}
+
+	if (metadata)
+		metadata->Unref();
+
+	if (IsDefined())
+		BufferedSocket::Close();
+}
+
+void
+HttpdClient::Close()
+{
+	httpd.RemoveClient(*this);
+}
+
+void
+HttpdClient::LockClose()
+{
+	const ScopeLock protect(httpd.mutex);
+	Close();
+}
+
+void
+HttpdClient::BeginResponse()
+{
+	assert(state != RESPONSE);
+
+	state = RESPONSE;
+	current_page = nullptr;
+
+	if (!head_method)
+		httpd.SendHeader(*this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+	assert(state != RESPONSE);
+
+	if (state == REQUEST) {
+		if (memcmp(line, "HEAD /", 6) == 0) {
+			line += 6;
+			head_method = true;
+		} else if (memcmp(line, "GET /", 5) == 0) {
+			line += 5;
+		} else {
+			/* only GET is supported */
+			LogWarning(httpd_output_domain,
+				   "malformed request line from client");
+			return false;
+		}
+
+		line = strchr(line, ' ');
+		if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
+			/* HTTP/0.9 without request headers */
+
+			if (head_method)
+				return false;
+
+			BeginResponse();
+			return true;
+		}
+
+		/* after the request line, request headers follow */
+		state = HEADERS;
+		return true;
+	} else {
+		if (*line == 0) {
+			/* empty line: request is finished */
+
+			BeginResponse();
+			return true;
+		}
+
+		if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
+		    StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
+			/* Send icy metadata */
+			metadata_requested = metadata_supported;
+			return true;
+		}
+
+		if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
+			/* Send as dlna */
+			dlna_streaming_requested = true;
+			/* metadata is not supported by dlna streaming, so disable it */
+			metadata_supported = false;
+			metadata_requested = false;
+			return true;
+		}
+
+		/* expect more request headers */
+		return true;
+	}
+}
+
+/**
+ * Sends the status line and response headers to the client.
+ */
+bool
+HttpdClient::SendResponse()
+{
+	char buffer[1024];
+	assert(state == RESPONSE);
+
+	if (dlna_streaming_requested) {
+		snprintf(buffer, sizeof(buffer),
+			 "HTTP/1.1 206 OK\r\n"
+			 "Content-Type: %s\r\n"
+			 "Content-Length: 10000\r\n"
+			 "Content-RangeX: 0-1000000/1000000\r\n"
+			 "transferMode.dlna.org: Streaming\r\n"
+			 "Accept-Ranges: bytes\r\n"
+			 "Connection: close\r\n"
+			 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+			 "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
+			 "\r\n",
+			 httpd.content_type);
+
+	} else if (metadata_requested) {
+		char *metadata_header =
+			icy_server_metadata_header(httpd.name, httpd.genre,
+						   httpd.website,
+						   httpd.content_type,
+						   metaint);
+
+		g_strlcpy(buffer, metadata_header, sizeof(buffer));
+
+		delete[] metadata_header;
+
+       } else { /* revert to a normal HTTP request */
+		snprintf(buffer, sizeof(buffer),
+			 "HTTP/1.1 200 OK\r\n"
+			 "Content-Type: %s\r\n"
+			 "Connection: close\r\n"
+			 "Pragma: no-cache\r\n"
+			 "Cache-Control: no-cache, no-store\r\n"
+			 "\r\n",
+			 httpd.content_type);
+	}
+
+	ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
+	if (gcc_unlikely(nbytes < 0)) {
+		const SocketErrorMessage msg;
+		FormatWarning(httpd_output_domain,
+			      "failed to write to client: %s",
+			      (const char *)msg);
+		Close();
+		return false;
+	}
+
+	return true;
+}
+
+HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
+			 bool _metadata_supported)
+	:BufferedSocket(_fd, _loop),
+	 httpd(_httpd),
+	 state(REQUEST),
+	 queue_size(0),
+	 head_method(false),
+	 dlna_streaming_requested(false),
+	 metadata_supported(_metadata_supported),
+	 metadata_requested(false), metadata_sent(true),
+	 metaint(8192), /*TODO: just a std value */
+	 metadata(nullptr),
+	 metadata_current_position(0), metadata_fill(0)
+{
+}
+
+void
+HttpdClient::ClearQueue()
+{
+	assert(state == RESPONSE);
+
+	while (!pages.empty()) {
+		Page *page = pages.front();
+		pages.pop();
+
+#ifndef NDEBUG
+		assert(queue_size >= page->size);
+		queue_size -= page->size;
+#endif
+
+		page->Unref();
+	}
+
+	assert(queue_size == 0);
+}
+
+void
+HttpdClient::CancelQueue()
+{
+	if (state != RESPONSE)
+		return;
+
+	ClearQueue();
+
+	if (current_page == nullptr)
+		CancelWrite();
+}
+
+ssize_t
+HttpdClient::TryWritePage(const Page &page, size_t position)
+{
+	assert(position < page.size);
+
+	return Write(page.data + position, page.size - position);
+}
+
+ssize_t
+HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
+{
+	return n >= 0
+		? Write(page.data + position, n)
+		: TryWritePage(page, position);
+}
+
+ssize_t
+HttpdClient::GetBytesTillMetaData() const
+{
+	if (metadata_requested &&
+	    current_page->size - current_position > metaint - metadata_fill)
+		return metaint - metadata_fill;
+
+	return -1;
+}
+
+inline bool
+HttpdClient::TryWrite()
+{
+	const ScopeLock protect(httpd.mutex);
+
+	assert(state == RESPONSE);
+
+	if (current_page == nullptr) {
+		if (pages.empty()) {
+			/* another thread has removed the event source
+			   while this thread was waiting for
+			   httpd.mutex */
+			CancelWrite();
+			return true;
+		}
+
+		current_page = pages.front();
+		pages.pop();
+		current_position = 0;
+
+		assert(queue_size >= current_page->size);
+		queue_size -= current_page->size;
+	}
+
+	const ssize_t bytes_to_write = GetBytesTillMetaData();
+	if (bytes_to_write == 0) {
+		if (!metadata_sent) {
+			ssize_t nbytes = TryWritePage(*metadata,
+						      metadata_current_position);
+			if (nbytes < 0) {
+				auto e = GetSocketError();
+				if (IsSocketErrorAgain(e))
+					return true;
+
+				if (!IsSocketErrorClosed(e)) {
+					SocketErrorMessage msg(e);
+					FormatWarning(httpd_output_domain,
+						      "failed to write to client: %s",
+						      (const char *)msg);
+				}
+
+				Close();
+				return false;
+			}
+
+			metadata_current_position += nbytes;
+
+			if (metadata->size - metadata_current_position == 0) {
+				metadata_fill = 0;
+				metadata_current_position = 0;
+				metadata_sent = true;
+			}
+		} else {
+			guchar empty_data = 0;
+
+			ssize_t nbytes = Write(&empty_data, 1);
+			if (nbytes < 0) {
+				auto e = GetSocketError();
+				if (IsSocketErrorAgain(e))
+					return true;
+
+				if (!IsSocketErrorClosed(e)) {
+					SocketErrorMessage msg(e);
+					FormatWarning(httpd_output_domain,
+						      "failed to write to client: %s",
+						      (const char *)msg);
+				}
+
+				Close();
+				return false;
+			}
+
+			metadata_fill = 0;
+			metadata_current_position = 0;
+		}
+	} else {
+		ssize_t nbytes =
+			TryWritePageN(*current_page, current_position,
+				      bytes_to_write);
+		if (nbytes < 0) {
+			auto e = GetSocketError();
+			if (IsSocketErrorAgain(e))
+				return true;
+
+			if (!IsSocketErrorClosed(e)) {
+				SocketErrorMessage msg(e);
+				FormatWarning(httpd_output_domain,
+					      "failed to write to client: %s",
+					      (const char *)msg);
+			}
+
+			Close();
+			return false;
+		}
+
+		current_position += nbytes;
+		assert(current_position <= current_page->size);
+
+		if (metadata_requested)
+			metadata_fill += nbytes;
+
+		if (current_position >= current_page->size) {
+			current_page->Unref();
+			current_page = nullptr;
+
+			if (pages.empty())
+				/* all pages are sent: remove the
+				   event source */
+				CancelWrite();
+		}
+	}
+
+	return true;
+}
+
+void
+HttpdClient::PushPage(Page *page)
+{
+	if (state != RESPONSE)
+		/* the client is still writing the HTTP request */
+		return;
+
+	if (queue_size > 256 * 1024) {
+		FormatDebug(httpd_output_domain,
+			    "client is too slow, flushing its queue");
+		ClearQueue();
+	}
+
+	page->Ref();
+	pages.push(page);
+	queue_size += page->size;
+
+	ScheduleWrite();
+}
+
+void
+HttpdClient::PushMetaData(Page *page)
+{
+	if (metadata) {
+		metadata->Unref();
+		metadata = nullptr;
+	}
+
+	g_return_if_fail (page);
+
+	page->Ref();
+	metadata = page;
+	metadata_sent = false;
+}
+
+bool
+HttpdClient::OnSocketReady(unsigned flags)
+{
+	if (!BufferedSocket::OnSocketReady(flags))
+		return false;
+
+	if (flags & WRITE)
+		if (!TryWrite())
+			return false;
+
+	return true;
+}
+
+BufferedSocket::InputResult
+HttpdClient::OnSocketInput(void *data, size_t length)
+{
+	if (state == RESPONSE) {
+		LogWarning(httpd_output_domain,
+			   "unexpected input from client");
+		LockClose();
+		return InputResult::CLOSED;
+	}
+
+	char *line = (char *)data;
+	char *newline = (char *)memchr(line, '\n', length);
+	if (newline == nullptr)
+		return InputResult::MORE;
+
+	ConsumeInput(newline + 1 - line);
+
+	if (newline > line && newline[-1] == '\r')
+		--newline;
+
+	/* terminate the string at the end of the line */
+	*newline = 0;
+
+	if (!HandleLine(line)) {
+		LockClose();
+		return InputResult::CLOSED;
+	}
+
+	if (state == RESPONSE) {
+		if (!SendResponse())
+			return InputResult::CLOSED;
+
+		if (head_method) {
+			LockClose();
+			return InputResult::CLOSED;
+		}
+	}
+
+	return InputResult::AGAIN;
+}
+
+void
+HttpdClient::OnSocketError(Error &&error)
+{
+	LogError(error);
+}
+
+void
+HttpdClient::OnSocketClosed()
+{
+	LockClose();
+}
diff --git a/src/output/plugins/HttpdClient.hxx b/src/output/plugins/HttpdClient.hxx
new file mode 100644
index 000000000..f94f05769
--- /dev/null
+++ b/src/output/plugins/HttpdClient.hxx
@@ -0,0 +1,193 @@
+/*
+ * 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_HTTPD_CLIENT_HXX
+#define MPD_OUTPUT_HTTPD_CLIENT_HXX
+
+#include "event/BufferedSocket.hxx"
+#include "Compiler.h"
+
+#include <queue>
+#include <list>
+
+#include <stddef.h>
+
+class HttpdOutput;
+class Page;
+
+class HttpdClient final : BufferedSocket {
+	/**
+	 * The httpd output object this client is connected to.
+	 */
+	HttpdOutput &httpd;
+
+	/**
+	 * The current state of the client.
+	 */
+	enum {
+		/** reading the request line */
+		REQUEST,
+
+		/** reading the request headers */
+		HEADERS,
+
+		/** sending the HTTP response */
+		RESPONSE,
+	} state;
+
+	/**
+	 * A queue of #Page objects to be sent to the client.
+	 */
+	std::queue<Page *, std::list<Page *>> pages;
+
+	/**
+	 * The sum of all page sizes in #pages.
+	 */
+	size_t queue_size;
+
+	/**
+	 * The #page which is currently being sent to the client.
+	 */
+	Page *current_page;
+
+	/**
+	 * The amount of bytes which were already sent from
+	 * #current_page.
+	 */
+	size_t current_position;
+
+	/**
+	 * Is this a HEAD request?
+	 */
+	bool head_method;
+
+	/**
+         * If DLNA streaming was an option.
+         */
+	bool dlna_streaming_requested;
+
+	/* ICY */
+
+	/**
+	 * Do we support sending Icy-Metadata to the client?  This is
+	 * disabled if the httpd audio output uses encoder tags.
+	 */
+	bool metadata_supported;
+
+	/**
+	 * If we should sent icy metadata.
+	 */
+	bool metadata_requested;
+
+	/**
+	 * If the current metadata was already sent to the client.
+	 */
+	bool metadata_sent;
+
+	/**
+	 * The amount of streaming data between each metadata block
+	 */
+	unsigned metaint;
+
+	/**
+	 * The metadata as #Page which is currently being sent to the client.
+	 */
+	Page *metadata;
+
+	/*
+	 * The amount of bytes which were already sent from the metadata.
+	 */
+	size_t metadata_current_position;
+
+	/**
+	 * The amount of streaming data sent to the client
+	 * since the last icy information was sent.
+	 */
+	unsigned metadata_fill;
+
+public:
+	/**
+	 * @param httpd the HTTP output device
+	 * @param fd the socket file descriptor
+	 */
+	HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
+		    bool _metadata_supported);
+
+	/**
+	 * Note: this does not remove the client from the
+	 * #HttpdOutput object.
+	 */
+	~HttpdClient();
+
+	/**
+	 * Frees the client and removes it from the server's client list.
+	 */
+	void Close();
+
+	void LockClose();
+
+	/**
+	 * Clears the page queue.
+	 */
+	void CancelQueue();
+
+	/**
+	 * Handle a line of the HTTP request.
+	 */
+	bool HandleLine(const char *line);
+
+	/**
+	 * Switch the client to the "RESPONSE" state.
+	 */
+	void BeginResponse();
+
+	/**
+	 * Sends the status line and response headers to the client.
+	 */
+	bool SendResponse();
+
+	gcc_pure
+	ssize_t GetBytesTillMetaData() const;
+
+	ssize_t TryWritePage(const Page &page, size_t position);
+	ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n);
+
+	bool TryWrite();
+
+	/**
+	 * Appends a page to the client's queue.
+	 */
+	void PushPage(Page *page);
+
+	/**
+	 * Sends the passed metadata.
+	 */
+	void PushMetaData(Page *page);
+
+private:
+	void ClearQueue();
+
+protected:
+	virtual bool OnSocketReady(unsigned flags) override;
+	virtual InputResult OnSocketInput(void *data, size_t length) override;
+	virtual void OnSocketError(Error &&error) override;
+	virtual void OnSocketClosed() override;
+};
+
+#endif
diff --git a/src/output/plugins/HttpdInternal.hxx b/src/output/plugins/HttpdInternal.hxx
new file mode 100644
index 000000000..506730d11
--- /dev/null
+++ b/src/output/plugins/HttpdInternal.hxx
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Internal declarations for the "httpd" audio output plugin.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
+#define MPD_OUTPUT_HTTPD_INTERNAL_H
+
+#include "../OutputInternal.hxx"
+#include "Timer.hxx"
+#include "thread/Mutex.hxx"
+#include "event/ServerSocket.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Cast.hxx"
+
+#ifdef _LIBCPP_VERSION
+/* can't use incomplete template arguments with libc++ */
+#include "HttpdClient.hxx"
+#endif
+
+#include <forward_list>
+#include <queue>
+#include <list>
+
+struct config_param;
+class Error;
+class EventLoop;
+class ServerSocket;
+class HttpdClient;
+class Page;
+struct Encoder;
+struct Tag;
+
+class HttpdOutput final : ServerSocket, DeferredMonitor {
+	struct audio_output base;
+
+	/**
+	 * True if the audio output is open and accepts client
+	 * connections.
+	 */
+	bool open;
+
+	/**
+	 * The configured encoder plugin.
+	 */
+	Encoder *encoder;
+
+	/**
+	 * Number of bytes which were fed into the encoder, without
+	 * ever receiving new output.  This is used to estimate
+	 * whether MPD should manually flush the encoder, to avoid
+	 * buffer underruns in the client.
+	 */
+	size_t unflushed_input;
+
+public:
+	/**
+	 * The MIME type produced by the #encoder.
+	 */
+	const char *content_type;
+
+	/**
+	 * This mutex protects the listener socket and the client
+	 * list.
+	 */
+	mutable Mutex mutex;
+
+	/**
+	 * This condition gets signalled when an item is removed from
+	 * #pages.
+	 */
+	Cond cond;
+
+private:
+	/**
+	 * A #Timer object to synchronize this output with the
+	 * wallclock.
+	 */
+	Timer *timer;
+
+	/**
+	 * The header page, which is sent to every client on connect.
+	 */
+	Page *header;
+
+	/**
+	 * The metadata, which is sent to every client.
+	 */
+	Page *metadata;
+
+	/**
+	 * The page queue, i.e. pages from the encoder to be
+	 * broadcasted to all clients.  This container is necessary to
+	 * pass pages from the OutputThread to the IOThread.  It is
+	 * protected by #mutex, and removing signals #cond.
+	 */
+	std::queue<Page *, std::list<Page *>> pages;
+
+ public:
+	/**
+	 * The configured name.
+	 */
+	char const *name;
+	/**
+	 * The configured genre.
+	 */
+	char const *genre;
+	/**
+	 * The configured website address.
+	 */
+	char const *website;
+
+private:
+	/**
+	 * A linked list containing all clients which are currently
+	 * connected.
+	 */
+	std::forward_list<HttpdClient> clients;
+
+	/**
+	 * A temporary buffer for the httpd_output_read_page()
+	 * function.
+	 */
+	char buffer[32768];
+
+	/**
+	 * The maximum and current number of clients connected
+	 * at the same time.
+	 */
+	unsigned clients_max, clients_cnt;
+
+public:
+	HttpdOutput(EventLoop &_loop);
+	~HttpdOutput();
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+	static constexpr HttpdOutput *Cast(audio_output *ao) {
+		return ContainerCast(ao, HttpdOutput, base);
+	}
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+	using DeferredMonitor::GetEventLoop;
+
+	bool Init(const config_param &param, Error &error);
+
+	void Finish() {
+		ao_base_finish(&base);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+
+	audio_output *InitAndConfigure(const config_param &param,
+				       Error &error) {
+		if (!Init(param, error))
+			return nullptr;
+
+		if (!Configure(param, error)) {
+			Finish();
+			return nullptr;
+		}
+
+		return &base;
+	}
+
+	bool Bind(Error &error);
+	void Unbind();
+
+	/**
+	 * Caller must lock the mutex.
+	 */
+	bool OpenEncoder(AudioFormat &audio_format, Error &error);
+
+	/**
+	 * Caller must lock the mutex.
+	 */
+	bool Open(AudioFormat &audio_format, Error &error);
+
+	/**
+	 * Caller must lock the mutex.
+	 */
+	void Close();
+
+	/**
+	 * Check whether there is at least one client.
+	 *
+	 * Caller must lock the mutex.
+	 */
+	gcc_pure
+	bool HasClients() const {
+		return !clients.empty();
+	}
+
+	/**
+	 * Check whether there is at least one client.
+	 */
+	gcc_pure
+	bool LockHasClients() const {
+		const ScopeLock protect(mutex);
+		return HasClients();
+	}
+
+	void AddClient(int fd);
+
+	/**
+	 * Removes a client from the httpd_output.clients linked list.
+	 */
+	void RemoveClient(HttpdClient &client);
+
+	/**
+	 * Sends the encoder header to the client.  This is called
+	 * right after the response headers have been sent.
+	 */
+	void SendHeader(HttpdClient &client) const;
+
+	gcc_pure
+	unsigned Delay() const;
+
+	/**
+	 * Reads data from the encoder (as much as available) and
+	 * returns it as a new #page object.
+	 */
+	Page *ReadPage();
+
+	/**
+	 * Broadcasts a page struct to all clients.
+	 *
+	 * Mutext must not be locked.
+	 */
+	void BroadcastPage(Page *page);
+
+	/**
+	 * Broadcasts data from the encoder to all clients.
+	 */
+	void BroadcastFromEncoder();
+
+	bool EncodeAndPlay(const void *chunk, size_t size, Error &error);
+
+	void SendTag(const Tag *tag);
+
+	size_t Play(const void *chunk, size_t size, Error &error);
+
+	void CancelAllClients();
+
+private:
+	virtual void RunDeferred() override;
+
+	virtual void OnAccept(int fd, const sockaddr &address,
+			      size_t address_length, int uid) override;
+};
+
+extern const class Domain httpd_output_domain;
+
+#endif
diff --git a/src/output/plugins/HttpdOutputPlugin.cxx b/src/output/plugins/HttpdOutputPlugin.cxx
new file mode 100644
index 000000000..6921cb808
--- /dev/null
+++ b/src/output/plugins/HttpdOutputPlugin.cxx
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdOutputPlugin.hxx"
+#include "HttpdInternal.hxx"
+#include "HttpdClient.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "system/Resolver.hxx"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/fd_util.h"
+#include "IOThread.hxx"
+#include "event/Call.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_LIBWRAP
+#include <sys/socket.h> /* needed for AF_UNIX */
+#include <tcpd.h>
+#endif
+
+const Domain httpd_output_domain("httpd_output");
+
+inline
+HttpdOutput::HttpdOutput(EventLoop &_loop)
+	:ServerSocket(_loop), DeferredMonitor(_loop),
+	 encoder(nullptr), unflushed_input(0),
+	 metadata(nullptr)
+{
+}
+
+HttpdOutput::~HttpdOutput()
+{
+	if (metadata != nullptr)
+		metadata->Unref();
+
+	if (encoder != nullptr)
+		encoder_finish(encoder);
+
+}
+
+inline bool
+HttpdOutput::Bind(Error &error)
+{
+	open = false;
+
+	bool result = false;
+	BlockingCall(GetEventLoop(), [this, &error, &result](){
+			result = ServerSocket::Open(error);
+		});
+	return result;
+}
+
+inline void
+HttpdOutput::Unbind()
+{
+	assert(!open);
+
+	BlockingCall(GetEventLoop(), [this](){
+			ServerSocket::Close();
+		});
+}
+
+inline bool
+HttpdOutput::Configure(const config_param &param, Error &error)
+{
+	/* read configuration */
+	name = param.GetBlockValue("name", "Set name in config");
+	genre = param.GetBlockValue("genre", "Set genre in config");
+	website = param.GetBlockValue("website", "Set website in config");
+
+	unsigned port = param.GetBlockValue("port", 8000u);
+
+	const char *encoder_name =
+		param.GetBlockValue("encoder", "vorbis");
+	const auto encoder_plugin = encoder_plugin_get(encoder_name);
+	if (encoder_plugin == nullptr) {
+		error.Format(httpd_output_domain,
+			     "No such encoder: %s", encoder_name);
+		return false;
+	}
+
+	clients_max = param.GetBlockValue("max_clients", 0u);
+
+	/* set up bind_to_address */
+
+	const char *bind_to_address = param.GetBlockValue("bind_to_address");
+	bool success = bind_to_address != nullptr &&
+		strcmp(bind_to_address, "any") != 0
+		? AddHost(bind_to_address, port, error)
+		: AddPort(port, error);
+	if (!success)
+		return false;
+
+	/* initialize encoder */
+
+	encoder = encoder_init(*encoder_plugin, param, error);
+	if (encoder == nullptr)
+		return false;
+
+	/* determine content type */
+	content_type = encoder_get_mime_type(encoder);
+	if (content_type == nullptr)
+		content_type = "application/octet-stream";
+
+	return true;
+}
+
+inline bool
+HttpdOutput::Init(const config_param &param, Error &error)
+{
+	return ao_base_init(&base, &httpd_output_plugin, param, error);
+}
+
+static struct audio_output *
+httpd_output_init(const config_param &param, Error &error)
+{
+	HttpdOutput *httpd = new HttpdOutput(io_thread_get());
+
+	audio_output *result = httpd->InitAndConfigure(param, error);
+	if (result == nullptr)
+		delete httpd;
+
+	return result;
+}
+
+static void
+httpd_output_finish(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	httpd->Finish();
+	delete httpd;
+}
+
+/**
+ * Creates a new #HttpdClient object and adds it into the
+ * HttpdOutput.clients linked list.
+ */
+inline void
+HttpdOutput::AddClient(int fd)
+{
+	clients.emplace_front(*this, fd, GetEventLoop(),
+			      encoder->plugin.tag == nullptr);
+	++clients_cnt;
+
+	/* pass metadata to client */
+	if (metadata != nullptr)
+		clients.front().PushMetaData(metadata);
+}
+
+void
+HttpdOutput::RunDeferred()
+{
+	/* this method runs in the IOThread; it broadcasts pages from
+	   our own queue to all clients */
+
+	const ScopeLock protect(mutex);
+
+	while (!pages.empty()) {
+		Page *page = pages.front();
+		pages.pop();
+
+		for (auto &client : clients)
+			client.PushPage(page);
+
+		page->Unref();
+	}
+
+	/* wake up the client that may be waiting for the queue to be
+	   flushed */
+	cond.broadcast();
+}
+
+void
+HttpdOutput::OnAccept(int fd, const sockaddr &address,
+		      size_t address_length, gcc_unused int uid)
+{
+	/* the listener socket has become readable - a client has
+	   connected */
+
+#ifdef HAVE_LIBWRAP
+	if (address.sa_family != AF_UNIX) {
+		const auto hostaddr = sockaddr_to_string(&address,
+							 address_length);
+		// TODO: shall we obtain the program name from argv[0]?
+		const char *progname = "mpd";
+
+		struct request_info req;
+		request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+		fromhost(&req);
+
+		if (!hosts_access(&req)) {
+			/* tcp wrappers says no */
+			FormatWarning(httpd_output_domain,
+				      "libwrap refused connection (libwrap=%s) from %s",
+				      progname, hostaddr.c_str());
+			close_socket(fd);
+			return;
+		}
+	}
+#else
+	(void)address;
+	(void)address_length;
+#endif	/* HAVE_WRAP */
+
+	const ScopeLock protect(mutex);
+
+	if (fd >= 0) {
+		/* can we allow additional client */
+		if (open && (clients_max == 0 ||  clients_cnt < clients_max))
+			AddClient(fd);
+		else
+			close_socket(fd);
+	} else if (fd < 0 && errno != EINTR) {
+		LogErrno(httpd_output_domain, "accept() failed");
+	}
+}
+
+Page *
+HttpdOutput::ReadPage()
+{
+	if (unflushed_input >= 65536) {
+		/* we have fed a lot of input into the encoder, but it
+		   didn't give anything back yet - flush now to avoid
+		   buffer underruns */
+		encoder_flush(encoder, IgnoreError());
+		unflushed_input = 0;
+	}
+
+	size_t size = 0;
+	do {
+		size_t nbytes = encoder_read(encoder,
+					     buffer + size,
+					     sizeof(buffer) - size);
+		if (nbytes == 0)
+			break;
+
+		unflushed_input = 0;
+
+		size += nbytes;
+	} while (size < sizeof(buffer));
+
+	if (size == 0)
+		return nullptr;
+
+	return Page::Copy(buffer, size);
+}
+
+static bool
+httpd_output_enable(struct audio_output *ao, Error &error)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	return httpd->Bind(error);
+}
+
+static void
+httpd_output_disable(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	httpd->Unbind();
+}
+
+inline bool
+HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
+{
+	if (!encoder_open(encoder, audio_format, error))
+		return false;
+
+	/* we have to remember the encoder header, i.e. the first
+	   bytes of encoder output after opening it, because it has to
+	   be sent to every new client */
+	header = ReadPage();
+
+	unflushed_input = 0;
+
+	return true;
+}
+
+inline bool
+HttpdOutput::Open(AudioFormat &audio_format, Error &error)
+{
+	assert(!open);
+	assert(clients.empty());
+
+	/* open the encoder */
+
+	if (!OpenEncoder(audio_format, error))
+		return false;
+
+	/* initialize other attributes */
+
+	clients_cnt = 0;
+	timer = new Timer(audio_format);
+
+	open = true;
+
+	return true;
+}
+
+static bool
+httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		  Error &error)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	const ScopeLock protect(httpd->mutex);
+	return httpd->Open(audio_format, error);
+}
+
+inline void
+HttpdOutput::Close()
+{
+	assert(open);
+
+	open = false;
+
+	delete timer;
+
+	BlockingCall(GetEventLoop(), [this](){
+			clients.clear();
+		});
+
+	if (header != nullptr)
+		header->Unref();
+
+	encoder_close(encoder);
+}
+
+static void
+httpd_output_close(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	const ScopeLock protect(httpd->mutex);
+	httpd->Close();
+}
+
+void
+HttpdOutput::RemoveClient(HttpdClient &client)
+{
+	assert(clients_cnt > 0);
+
+	for (auto prev = clients.before_begin(), i = std::next(prev);;
+	     prev = i, i = std::next(prev)) {
+		assert(i != clients.end());
+		if (&*i == &client) {
+			clients.erase_after(prev);
+			clients_cnt--;
+			break;
+		}
+	}
+}
+
+void
+HttpdOutput::SendHeader(HttpdClient &client) const
+{
+	if (header != nullptr)
+		client.PushPage(header);
+}
+
+inline unsigned
+HttpdOutput::Delay() const
+{
+	if (!LockHasClients() && base.pause) {
+		/* if there's no client and this output is paused,
+		   then httpd_output_pause() will not do anything, it
+		   will not fill the buffer and it will not update the
+		   timer; therefore, we reset the timer here */
+		timer->Reset();
+
+		/* some arbitrary delay that is long enough to avoid
+		   consuming too much CPU, and short enough to notice
+		   new clients quickly enough */
+		return 1000;
+	}
+
+	return timer->IsStarted()
+		? timer->GetDelay()
+		: 0;
+}
+
+static unsigned
+httpd_output_delay(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	return httpd->Delay();
+}
+
+void
+HttpdOutput::BroadcastPage(Page *page)
+{
+	assert(page != nullptr);
+
+	mutex.lock();
+	pages.push(page);
+	page->Ref();
+	mutex.unlock();
+
+	DeferredMonitor::Schedule();
+}
+
+void
+HttpdOutput::BroadcastFromEncoder()
+{
+	/* synchronize with the IOThread */
+	mutex.lock();
+	while (!pages.empty())
+		cond.wait(mutex);
+
+	Page *page;
+	while ((page = ReadPage()) != nullptr)
+		pages.push(page);
+
+	mutex.unlock();
+
+	DeferredMonitor::Schedule();
+}
+
+inline bool
+HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
+{
+	if (!encoder_write(encoder, chunk, size, error))
+		return false;
+
+	unflushed_input += size;
+
+	BroadcastFromEncoder();
+	return true;
+}
+
+inline size_t
+HttpdOutput::Play(const void *chunk, size_t size, Error &error)
+{
+	if (LockHasClients()) {
+		if (!EncodeAndPlay(chunk, size, error))
+			return 0;
+	}
+
+	if (!timer->IsStarted())
+		timer->Start();
+	timer->Add(size);
+
+	return size;
+}
+
+static size_t
+httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		  Error &error)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	return httpd->Play(chunk, size, error);
+}
+
+static bool
+httpd_output_pause(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	if (httpd->LockHasClients()) {
+		static const char silence[1020] = { 0 };
+		return httpd_output_play(ao, silence, sizeof(silence),
+					 IgnoreError()) > 0;
+	} else {
+		return true;
+	}
+}
+
+inline void
+HttpdOutput::SendTag(const Tag *tag)
+{
+	assert(tag != nullptr);
+
+	if (encoder->plugin.tag != nullptr) {
+		/* embed encoder tags */
+
+		/* flush the current stream, and end it */
+
+		encoder_pre_tag(encoder, IgnoreError());
+		BroadcastFromEncoder();
+
+		/* send the tag to the encoder - which starts a new
+		   stream now */
+
+		encoder_tag(encoder, tag, IgnoreError());
+
+		/* the first page generated by the encoder will now be
+		   used as the new "header" page, which is sent to all
+		   new clients */
+
+		Page *page = ReadPage();
+		if (page != nullptr) {
+			if (header != nullptr)
+				header->Unref();
+			header = page;
+			BroadcastPage(page);
+		}
+	} else {
+		/* use Icy-Metadata */
+
+		if (metadata != nullptr)
+			metadata->Unref();
+
+		static constexpr TagType types[] = {
+			TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
+			TAG_NUM_OF_ITEM_TYPES
+		};
+
+		metadata = icy_server_metadata_page(*tag, &types[0]);
+		if (metadata != nullptr) {
+			const ScopeLock protect(mutex);
+			for (auto &client : clients)
+				client.PushMetaData(metadata);
+		}
+	}
+}
+
+static void
+httpd_output_tag(struct audio_output *ao, const Tag *tag)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	httpd->SendTag(tag);
+}
+
+inline void
+HttpdOutput::CancelAllClients()
+{
+	const ScopeLock protect(mutex);
+
+	while (!pages.empty()) {
+		Page *page = pages.front();
+		pages.pop();
+		page->Unref();
+	}
+
+	for (auto &client : clients)
+		client.CancelQueue();
+
+	cond.broadcast();
+}
+
+static void
+httpd_output_cancel(struct audio_output *ao)
+{
+	HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+	BlockingCall(io_thread_get(), [httpd](){
+			httpd->CancelAllClients();
+		});
+}
+
+const struct audio_output_plugin httpd_output_plugin = {
+	"httpd",
+	nullptr,
+	httpd_output_init,
+	httpd_output_finish,
+	httpd_output_enable,
+	httpd_output_disable,
+	httpd_output_open,
+	httpd_output_close,
+	httpd_output_delay,
+	httpd_output_tag,
+	httpd_output_play,
+	nullptr,
+	httpd_output_cancel,
+	httpd_output_pause,
+	nullptr,
+};
diff --git a/src/output/plugins/HttpdOutputPlugin.hxx b/src/output/plugins/HttpdOutputPlugin.hxx
new file mode 100644
index 000000000..78218e5f0
--- /dev/null
+++ b/src/output/plugins/HttpdOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_HTTPD_OUTPUT_PLUGIN_HXX
+#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin httpd_output_plugin;
+
+#endif
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
new file mode 100644
index 000000000..5a0d2bf16
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -0,0 +1,765 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "JackOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+enum {
+	MAX_PORTS = 16,
+};
+
+static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+
+struct JackOutput {
+	struct audio_output base;
+
+	/**
+	 * libjack options passed to jack_client_open().
+	 */
+	jack_options_t options;
+
+	const char *name;
+
+	const char *server_name;
+
+	/* configuration */
+
+	char *source_ports[MAX_PORTS];
+	unsigned num_source_ports;
+
+	char *destination_ports[MAX_PORTS];
+	unsigned num_destination_ports;
+
+	size_t ringbuffer_size;
+
+	/* the current audio format */
+	AudioFormat audio_format;
+
+	/* jack library stuff */
+	jack_port_t *ports[MAX_PORTS];
+	jack_client_t *client;
+	jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+	bool shutdown;
+
+	/**
+	 * While this flag is set, the "process" callback generates
+	 * silence.
+	 */
+	bool pause;
+
+	bool Initialize(const config_param &param, Error &error_r) {
+		return ao_base_init(&base, &jack_output_plugin, param,
+				    error_r);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+};
+
+static constexpr Domain jack_output_domain("jack_output");
+
+/**
+ * Determine the number of frames guaranteed to be available on all
+ * channels.
+ */
+static jack_nframes_t
+mpd_jack_available(const JackOutput *jd)
+{
+	size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
+
+	for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+		size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+		if (current < min)
+			min = current;
+	}
+
+	assert(min % jack_sample_size == 0);
+
+	return min / jack_sample_size;
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+	JackOutput *jd = (JackOutput *) arg;
+
+	if (nframes <= 0)
+		return 0;
+
+	if (jd->pause) {
+		/* empty the ring buffers */
+
+		const jack_nframes_t available = mpd_jack_available(jd);
+		for (unsigned i = 0; i < jd->audio_format.channels; ++i)
+			jack_ringbuffer_read_advance(jd->ringbuffer[i],
+						     available * jack_sample_size);
+
+		/* generate silence while MPD is paused */
+
+		for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+			jack_default_audio_sample_t *out =
+				(jack_default_audio_sample_t *)
+				jack_port_get_buffer(jd->ports[i], nframes);
+
+			for (jack_nframes_t f = 0; f < nframes; ++f)
+				out[f] = 0.0;
+		}
+
+		return 0;
+	}
+
+	jack_nframes_t available = mpd_jack_available(jd);
+	if (available > nframes)
+		available = nframes;
+
+	for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+		jack_default_audio_sample_t *out =
+			(jack_default_audio_sample_t *)
+			jack_port_get_buffer(jd->ports[i], nframes);
+		if (out == nullptr)
+			/* workaround for libjack1 bug: if the server
+			   connection fails, the process callback is
+			   invoked anyway, but unable to get a
+			   buffer */
+			continue;
+
+		jack_ringbuffer_read(jd->ringbuffer[i],
+				     (char *)out, available * jack_sample_size);
+
+		for (jack_nframes_t f = available; f < nframes; ++f)
+			/* ringbuffer underrun, fill with silence */
+			out[f] = 0.0;
+	}
+
+	/* generate silence for the unused source ports */
+
+	for (unsigned i = jd->audio_format.channels;
+	     i < jd->num_source_ports; ++i) {
+		jack_default_audio_sample_t *out =
+			(jack_default_audio_sample_t *)
+			jack_port_get_buffer(jd->ports[i], nframes);
+		if (out == nullptr)
+			/* workaround for libjack1 bug: if the server
+			   connection fails, the process callback is
+			   invoked anyway, but unable to get a
+			   buffer */
+			continue;
+
+		for (jack_nframes_t f = 0; f < nframes; ++f)
+			out[f] = 0.0;
+	}
+
+	return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+	JackOutput *jd = (JackOutput *) arg;
+	jd->shutdown = true;
+}
+
+static void
+set_audioformat(JackOutput *jd, AudioFormat &audio_format)
+{
+	audio_format.sample_rate = jack_get_sample_rate(jd->client);
+
+	if (jd->num_source_ports == 1)
+		audio_format.channels = 1;
+	else if (audio_format.channels > jd->num_source_ports)
+		audio_format.channels = 2;
+
+	if (audio_format.format != SampleFormat::S16 &&
+	    audio_format.format != SampleFormat::S24_P32)
+		audio_format.format = SampleFormat::S24_P32;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+	LogError(jack_output_domain, msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+	LogDefault(jack_output_domain, msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(JackOutput *jd)
+{
+	assert(jd != nullptr);
+	assert(jd->client != nullptr);
+
+	jack_deactivate(jd->client);
+	jack_client_close(jd->client);
+	jd->client = nullptr;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(JackOutput *jd, Error &error)
+{
+	jack_status_t status;
+
+	assert(jd != nullptr);
+
+	jd->shutdown = false;
+
+	jd->client = jack_client_open(jd->name, jd->options, &status,
+				      jd->server_name);
+	if (jd->client == nullptr) {
+		error.Format(jack_output_domain, status,
+			     "Failed to connect to JACK server, status=%d",
+			     status);
+		return false;
+	}
+
+	jack_set_process_callback(jd->client, mpd_jack_process, jd);
+	jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+		jd->ports[i] = jack_port_register(jd->client,
+						  jd->source_ports[i],
+						  JACK_DEFAULT_AUDIO_TYPE,
+						  JackPortIsOutput, 0);
+		if (jd->ports[i] == nullptr) {
+			error.Format(jack_output_domain,
+				     "Cannot register output port \"%s\"",
+				     jd->source_ports[i]);
+			mpd_jack_disconnect(jd);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+	return true;
+}
+
+static unsigned
+parse_port_list(const char *source, char **dest, Error &error)
+{
+	char **list = g_strsplit(source, ",", 0);
+	unsigned n = 0;
+
+	for (n = 0; list[n] != nullptr; ++n) {
+		if (n >= MAX_PORTS) {
+			error.Set(config_domain,
+				  "too many port names");
+			return 0;
+		}
+
+		dest[n] = list[n];
+	}
+
+	g_free(list);
+
+	if (n == 0) {
+		error.Format(config_domain,
+			     "at least one port name expected");
+		return 0;
+	}
+
+	return n;
+}
+
+static struct audio_output *
+mpd_jack_init(const config_param &param, Error &error)
+{
+	JackOutput *jd = new JackOutput();
+
+	if (!jd->Initialize(param, error)) {
+		delete jd;
+		return nullptr;
+	}
+
+	const char *value;
+
+	jd->options = JackNullOption;
+
+	jd->name = param.GetBlockValue("client_name", nullptr);
+	if (jd->name != nullptr)
+		jd->options = jack_options_t(jd->options | JackUseExactName);
+	else
+		/* if there's a no configured client name, we don't
+		   care about the JackUseExactName option */
+		jd->name = "Music Player Daemon";
+
+	jd->server_name = param.GetBlockValue("server_name", nullptr);
+	if (jd->server_name != nullptr)
+		jd->options = jack_options_t(jd->options | JackServerName);
+
+	if (!param.GetBlockValue("autostart", false))
+		jd->options = jack_options_t(jd->options | JackNoStartServer);
+
+	/* configure the source ports */
+
+	value = param.GetBlockValue("source_ports", "left,right");
+	jd->num_source_ports = parse_port_list(value,
+					       jd->source_ports, error);
+	if (jd->num_source_ports == 0)
+		return nullptr;
+
+	/* configure the destination ports */
+
+	value = param.GetBlockValue("destination_ports", nullptr);
+	if (value == nullptr) {
+		/* compatibility with MPD < 0.16 */
+		value = param.GetBlockValue("ports", nullptr);
+		if (value != nullptr)
+			FormatWarning(jack_output_domain,
+				      "deprecated option 'ports' in line %d",
+				      param.line);
+	}
+
+	if (value != nullptr) {
+		jd->num_destination_ports =
+			parse_port_list(value,
+					jd->destination_ports, error);
+		if (jd->num_destination_ports == 0)
+			return nullptr;
+	} else {
+		jd->num_destination_ports = 0;
+	}
+
+	if (jd->num_destination_ports > 0 &&
+	    jd->num_destination_ports != jd->num_source_ports)
+		FormatWarning(jack_output_domain,
+			      "number of source ports (%u) mismatches the "
+			      "number of destination ports (%u) in line %d",
+			      jd->num_source_ports, jd->num_destination_ports,
+			      param.line);
+
+	jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+
+	jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+	jack_set_info_function(mpd_jack_info);
+#endif
+
+	return &jd->base;
+}
+
+static void
+mpd_jack_finish(struct audio_output *ao)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	for (unsigned i = 0; i < jd->num_source_ports; ++i)
+		g_free(jd->source_ports[i]);
+
+	for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+		g_free(jd->destination_ports[i]);
+
+	jd->Deinitialize();
+	delete jd;
+}
+
+static bool
+mpd_jack_enable(struct audio_output *ao, Error &error)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	for (unsigned i = 0; i < jd->num_source_ports; ++i)
+		jd->ringbuffer[i] = nullptr;
+
+	return mpd_jack_connect(jd, error);
+}
+
+static void
+mpd_jack_disable(struct audio_output *ao)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	if (jd->client != nullptr)
+		mpd_jack_disconnect(jd);
+
+	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+		if (jd->ringbuffer[i] != nullptr) {
+			jack_ringbuffer_free(jd->ringbuffer[i]);
+			jd->ringbuffer[i] = nullptr;
+		}
+	}
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(JackOutput *jd)
+{
+	assert(jd != nullptr);
+
+	if (jd->client == nullptr)
+		return;
+
+	if (jd->shutdown)
+		/* the connection has failed; close it */
+		mpd_jack_disconnect(jd);
+	else
+		/* the connection is alive: just stop playback */
+		jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(JackOutput *jd, Error &error)
+{
+	const char *destination_ports[MAX_PORTS], **jports;
+	const char *duplicate_port = nullptr;
+	unsigned num_destination_ports;
+
+	assert(jd->client != nullptr);
+	assert(jd->audio_format.channels <= jd->num_source_ports);
+
+	/* allocate the ring buffers on the first open(); these
+	   persist until MPD exits.  It's too unsafe to delete them
+	   because we can never know when mpd_jack_process() gets
+	   called */
+	for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+		if (jd->ringbuffer[i] == nullptr)
+			jd->ringbuffer[i] =
+				jack_ringbuffer_create(jd->ringbuffer_size);
+
+		/* clear the ring buffer to be sure that data from
+		   previous playbacks are gone */
+		jack_ringbuffer_reset(jd->ringbuffer[i]);
+	}
+
+	if ( jack_activate(jd->client) ) {
+		error.Set(jack_output_domain, "cannot activate client");
+		mpd_jack_stop(jd);
+		return false;
+	}
+
+	if (jd->num_destination_ports == 0) {
+		/* no output ports were configured - ask libjack for
+		   defaults */
+		jports = jack_get_ports(jd->client, nullptr, nullptr,
+					JackPortIsPhysical | JackPortIsInput);
+		if (jports == nullptr) {
+			error.Set(jack_output_domain, "no ports found");
+			mpd_jack_stop(jd);
+			return false;
+		}
+
+		assert(*jports != nullptr);
+
+		for (num_destination_ports = 0;
+		     num_destination_ports < MAX_PORTS &&
+			     jports[num_destination_ports] != nullptr;
+		     ++num_destination_ports) {
+			FormatDebug(jack_output_domain,
+				    "destination_port[%u] = '%s'\n",
+				    num_destination_ports,
+				    jports[num_destination_ports]);
+			destination_ports[num_destination_ports] =
+				jports[num_destination_ports];
+		}
+	} else {
+		/* use the configured output ports */
+
+		num_destination_ports = jd->num_destination_ports;
+		memcpy(destination_ports, jd->destination_ports,
+		       num_destination_ports * sizeof(*destination_ports));
+
+		jports = nullptr;
+	}
+
+	assert(num_destination_ports > 0);
+
+	if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+		/* mix stereo signal on one speaker */
+
+		while (num_destination_ports < jd->audio_format.channels)
+			destination_ports[num_destination_ports++] =
+				destination_ports[0];
+	} else if (num_destination_ports > jd->audio_format.channels) {
+		if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+			/* mono input file: connect the one source
+			   channel to the both destination channels */
+			duplicate_port = destination_ports[1];
+			num_destination_ports = 1;
+		} else
+			/* connect only as many ports as we need */
+			num_destination_ports = jd->audio_format.channels;
+	}
+
+	assert(num_destination_ports <= jd->num_source_ports);
+
+	for (unsigned i = 0; i < num_destination_ports; ++i) {
+		int ret;
+
+		ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+				   destination_ports[i]);
+		if (ret != 0) {
+			error.Format(jack_output_domain,
+				     "Not a valid JACK port: %s",
+				     destination_ports[i]);
+
+			if (jports != nullptr)
+				free(jports);
+
+			mpd_jack_stop(jd);
+			return false;
+		}
+	}
+
+	if (duplicate_port != nullptr) {
+		/* mono input file: connect the one source channel to
+		   the both destination channels */
+		int ret;
+
+		ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+				   duplicate_port);
+		if (ret != 0) {
+			error.Format(jack_output_domain,
+				     "Not a valid JACK port: %s",
+				     duplicate_port);
+
+			if (jports != nullptr)
+				free(jports);
+
+			mpd_jack_stop(jd);
+			return false;
+		}
+	}
+
+	if (jports != nullptr)
+		free(jports);
+
+	return true;
+}
+
+static bool
+mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
+	      Error &error)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	assert(jd != nullptr);
+
+	jd->pause = false;
+
+	if (jd->client != nullptr && jd->shutdown)
+		mpd_jack_disconnect(jd);
+
+	if (jd->client == nullptr && !mpd_jack_connect(jd, error))
+		return false;
+
+	set_audioformat(jd, audio_format);
+	jd->audio_format = audio_format;
+
+	if (!mpd_jack_start(jd, error))
+		return false;
+
+	return true;
+}
+
+static void
+mpd_jack_close(gcc_unused struct audio_output *ao)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	mpd_jack_stop(jd);
+}
+
+static unsigned
+mpd_jack_delay(struct audio_output *ao)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	return jd->base.pause && jd->pause && !jd->shutdown
+		? 1000
+		: 0;
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+	return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
+			  unsigned num_samples)
+{
+	jack_default_audio_sample_t sample;
+	unsigned i;
+
+	while (num_samples-- > 0) {
+		for (i = 0; i < jd->audio_format.channels; ++i) {
+			sample = sample_16_to_jack(*src++);
+			jack_ringbuffer_write(jd->ringbuffer[i],
+					      (const char *)&sample,
+					      sizeof(sample));
+		}
+	}
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+	return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
+			  unsigned num_samples)
+{
+	jack_default_audio_sample_t sample;
+	unsigned i;
+
+	while (num_samples-- > 0) {
+		for (i = 0; i < jd->audio_format.channels; ++i) {
+			sample = sample_24_to_jack(*src++);
+			jack_ringbuffer_write(jd->ringbuffer[i],
+					      (const char *)&sample,
+					      sizeof(sample));
+		}
+	}
+}
+
+static void
+mpd_jack_write_samples(JackOutput *jd, const void *src,
+		       unsigned num_samples)
+{
+	switch (jd->audio_format.format) {
+	case SampleFormat::S16:
+		mpd_jack_write_samples_16(jd, (const int16_t*)src,
+					  num_samples);
+		break;
+
+	case SampleFormat::S24_P32:
+		mpd_jack_write_samples_24(jd, (const int32_t*)src,
+					  num_samples);
+		break;
+
+	default:
+		assert(false);
+		gcc_unreachable();
+	}
+}
+
+static size_t
+mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
+	      Error &error)
+{
+	JackOutput *jd = (JackOutput *)ao;
+	const size_t frame_size = jd->audio_format.GetFrameSize();
+	size_t space = 0, space1;
+
+	jd->pause = false;
+
+	assert(size % frame_size == 0);
+	size /= frame_size;
+
+	while (true) {
+		if (jd->shutdown) {
+			error.Set(jack_output_domain,
+				  "Refusing to play, because "
+				  "there is no client thread");
+			return 0;
+		}
+
+		space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+		for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+			space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+			if (space > space1)
+				/* send data symmetrically */
+				space = space1;
+		}
+
+		if (space >= jack_sample_size)
+			break;
+
+		/* XXX do something more intelligent to
+		   synchronize */
+		g_usleep(1000);
+	}
+
+	space /= jack_sample_size;
+	if (space < size)
+		size = space;
+
+	mpd_jack_write_samples(jd, chunk, size);
+	return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(struct audio_output *ao)
+{
+	JackOutput *jd = (JackOutput *)ao;
+
+	if (jd->shutdown)
+		return false;
+
+	jd->pause = true;
+
+	return true;
+}
+
+const struct audio_output_plugin jack_output_plugin = {
+	"jack",
+	mpd_jack_test_default_device,
+	mpd_jack_init,
+	mpd_jack_finish,
+	mpd_jack_enable,
+	mpd_jack_disable,
+	mpd_jack_open,
+	mpd_jack_close,
+	mpd_jack_delay,
+	nullptr,
+	mpd_jack_play,
+	nullptr,
+	nullptr,
+	mpd_jack_pause,
+	nullptr,
+};
diff --git a/src/output/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx
new file mode 100644
index 000000000..ee3fe9238
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_JACK_OUTPUT_PLUGIN_HXX
+#define MPD_JACK_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin jack_output_plugin;
+
+#endif
diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
new file mode 100644
index 000000000..c336d86e6
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NullOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "Timer.hxx"
+
+struct NullOutput {
+	struct audio_output base;
+
+	bool sync;
+
+	Timer *timer;
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &null_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+};
+
+static struct audio_output *
+null_init(const config_param &param, Error &error)
+{
+	NullOutput *nd = new NullOutput();
+
+	if (!nd->Initialize(param, error)) {
+		delete nd;
+		return nullptr;
+	}
+
+	nd->sync = param.GetBlockValue("sync", true);
+
+	return &nd->base;
+}
+
+static void
+null_finish(struct audio_output *ao)
+{
+	NullOutput *nd = (NullOutput *)ao;
+
+	nd->Deinitialize();
+	delete nd;
+}
+
+static bool
+null_open(struct audio_output *ao, AudioFormat &audio_format,
+	  gcc_unused Error &error)
+{
+	NullOutput *nd = (NullOutput *)ao;
+
+	if (nd->sync)
+		nd->timer = new Timer(audio_format);
+
+	return true;
+}
+
+static void
+null_close(struct audio_output *ao)
+{
+	NullOutput *nd = (NullOutput *)ao;
+
+	if (nd->sync)
+		delete nd->timer;
+}
+
+static unsigned
+null_delay(struct audio_output *ao)
+{
+	NullOutput *nd = (NullOutput *)ao;
+
+	return nd->sync && nd->timer->IsStarted()
+		? nd->timer->GetDelay()
+		: 0;
+}
+
+static size_t
+null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
+	  gcc_unused Error &error)
+{
+	NullOutput *nd = (NullOutput *)ao;
+	Timer *timer = nd->timer;
+
+	if (!nd->sync)
+		return size;
+
+	if (!timer->IsStarted())
+		timer->Start();
+	timer->Add(size);
+
+	return size;
+}
+
+static void
+null_cancel(struct audio_output *ao)
+{
+	NullOutput *nd = (NullOutput *)ao;
+
+	if (!nd->sync)
+		return;
+
+	nd->timer->Reset();
+}
+
+const struct audio_output_plugin null_output_plugin = {
+	"null",
+	nullptr,
+	null_init,
+	null_finish,
+	nullptr,
+	nullptr,
+	null_open,
+	null_close,
+	null_delay,
+	nullptr,
+	null_play,
+	nullptr,
+	null_cancel,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx
new file mode 100644
index 000000000..05b8ef3d8
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_NULL_OUTPUT_PLUGIN_HXX
+#define MPD_NULL_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin null_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
new file mode 100644
index 000000000..c247336d7
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OSXOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+struct OSXOutput {
+	struct audio_output base;
+
+	/* configuration settings */
+	OSType component_subtype;
+	/* only applicable with kAudioUnitSubType_HALOutput */
+	const char *device_name;
+
+	AudioUnit au;
+	Mutex mutex;
+	Cond condition;
+
+	DynamicFifoBuffer<uint8_t> *buffer;
+};
+
+static constexpr Domain osx_output_domain("osx_output");
+
+static bool
+osx_output_test_default_device(void)
+{
+	/* on a Mac, this is always the default plugin, if nothing
+	   else is configured */
+	return true;
+}
+
+static void
+osx_output_configure(OSXOutput *oo, const config_param &param)
+{
+	const char *device = param.GetBlockValue("device");
+
+	if (device == NULL || 0 == strcmp(device, "default")) {
+		oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+		oo->device_name = NULL;
+	}
+	else if (0 == strcmp(device, "system")) {
+		oo->component_subtype = kAudioUnitSubType_SystemOutput;
+		oo->device_name = NULL;
+	}
+	else {
+		oo->component_subtype = kAudioUnitSubType_HALOutput;
+		/* XXX am I supposed to strdup() this? */
+		oo->device_name = device;
+	}
+}
+
+static struct audio_output *
+osx_output_init(const config_param &param, Error &error)
+{
+	OSXOutput *oo = new OSXOutput();
+	if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) {
+		delete oo;
+		return NULL;
+	}
+
+	osx_output_configure(oo, param);
+
+	return &oo->base;
+}
+
+static void
+osx_output_finish(struct audio_output *ao)
+{
+	OSXOutput *oo = (OSXOutput *)ao;
+
+	delete oo;
+}
+
+static bool
+osx_output_set_device(OSXOutput *oo, Error &error)
+{
+	bool ret = true;
+	OSStatus status;
+	UInt32 size, numdevices;
+	AudioDeviceID *deviceids = NULL;
+	char name[256];
+	unsigned int i;
+
+	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+		goto done;
+
+	/* how many audio devices are there? */
+	status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+					      &size,
+					      NULL);
+	if (status != noErr) {
+		error.Format(osx_output_domain, status,
+			     "Unable to determine number of OS X audio devices: %s",
+			     GetMacOSStatusCommentString(status));
+		ret = false;
+		goto done;
+	}
+
+	/* what are the available audio device IDs? */
+	numdevices = size / sizeof(AudioDeviceID);
+	deviceids = new AudioDeviceID[numdevices];
+	status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+					  &size,
+					  deviceids);
+	if (status != noErr) {
+		error.Format(osx_output_domain, status,
+			     "Unable to determine OS X audio device IDs: %s",
+			     GetMacOSStatusCommentString(status));
+		ret = false;
+		goto done;
+	}
+
+	/* which audio device matches oo->device_name? */
+	for (i = 0; i < numdevices; i++) {
+		size = sizeof(name);
+		status = AudioDeviceGetProperty(deviceids[i], 0, false,
+						kAudioDevicePropertyDeviceName,
+						&size, name);
+		if (status != noErr) {
+			error.Format(osx_output_domain, status,
+				     "Unable to determine OS X device name "
+				     "(device %u): %s",
+				     (unsigned int) deviceids[i],
+				     GetMacOSStatusCommentString(status));
+			ret = false;
+			goto done;
+		}
+		if (strcmp(oo->device_name, name) == 0) {
+			FormatDebug(osx_output_domain,
+				    "found matching device: ID=%u, name=%s",
+				    (unsigned)deviceids[i], name);
+			break;
+		}
+	}
+	if (i == numdevices) {
+		FormatWarning(osx_output_domain,
+			      "Found no audio device with name '%s' "
+			      "(will use default audio device)",
+			      oo->device_name);
+		goto done;
+	}
+
+	status = AudioUnitSetProperty(oo->au,
+				      kAudioOutputUnitProperty_CurrentDevice,
+				      kAudioUnitScope_Global,
+				      0,
+				      &(deviceids[i]),
+				      sizeof(AudioDeviceID));
+	if (status != noErr) {
+		error.Format(osx_output_domain, status,
+			     "Unable to set OS X audio output device: %s",
+			     GetMacOSStatusCommentString(status));
+		ret = false;
+		goto done;
+	}
+
+	FormatDebug(osx_output_domain,
+		    "set OS X audio output device ID=%u, name=%s",
+		    (unsigned)deviceids[i], name);
+
+done:
+	delete[] deviceids;
+	return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+	   gcc_unused AudioUnitRenderActionFlags *io_action_flags,
+	   gcc_unused const AudioTimeStamp *in_timestamp,
+	   gcc_unused UInt32 in_bus_number,
+	   gcc_unused UInt32 in_number_frames,
+	   AudioBufferList *buffer_list)
+{
+	OSXOutput *od = (OSXOutput *) vdata;
+	AudioBuffer *buffer = &buffer_list->mBuffers[0];
+	size_t buffer_size = buffer->mDataByteSize;
+
+	assert(od->buffer != NULL);
+
+	od->mutex.lock();
+
+	auto src = od->buffer->Read();
+	if (!src.IsEmpty()) {
+		if (src.size > buffer_size)
+			src.size = buffer_size;
+
+		memcpy(buffer->mData, src.data, src.size);
+		od->buffer->Consume(src.size);
+	}
+
+	od->condition.signal();
+	od->mutex.unlock();
+
+	buffer->mDataByteSize = src.size;
+
+	unsigned i;
+	for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
+		buffer = &buffer_list->mBuffers[i];
+		buffer->mDataByteSize = 0;
+	}
+
+	return 0;
+}
+
+static bool
+osx_output_enable(struct audio_output *ao, Error &error)
+{
+	OSXOutput *oo = (OSXOutput *)ao;
+
+	ComponentDescription desc;
+	desc.componentType = kAudioUnitType_Output;
+	desc.componentSubType = oo->component_subtype;
+	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+	desc.componentFlags = 0;
+	desc.componentFlagsMask = 0;
+
+	Component comp = FindNextComponent(NULL, &desc);
+	if (comp == 0) {
+		error.Set(osx_output_domain,
+			  "Error finding OS X component");
+		return false;
+	}
+
+	OSStatus status = OpenAComponent(comp, &oo->au);
+	if (status != noErr) {
+		error.Format(osx_output_domain, status,
+			     "Unable to open OS X component: %s",
+			     GetMacOSStatusCommentString(status));
+		return false;
+	}
+
+	if (!osx_output_set_device(oo, error)) {
+		CloseComponent(oo->au);
+		return false;
+	}
+
+	AURenderCallbackStruct callback;
+	callback.inputProc = osx_render;
+	callback.inputProcRefCon = oo;
+
+	ComponentResult result =
+		AudioUnitSetProperty(oo->au,
+				     kAudioUnitProperty_SetRenderCallback,
+				     kAudioUnitScope_Input, 0,
+				     &callback, sizeof(callback));
+	if (result != noErr) {
+		CloseComponent(oo->au);
+		error.Set(osx_output_domain, result,
+			  "unable to set callback for OS X audio unit");
+		return false;
+	}
+
+	return true;
+}
+
+static void
+osx_output_disable(struct audio_output *ao)
+{
+	OSXOutput *oo = (OSXOutput *)ao;
+
+	CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(struct audio_output *ao)
+{
+	OSXOutput *od = (OSXOutput *)ao;
+
+	const ScopeLock protect(od->mutex);
+	od->buffer->Clear();
+}
+
+static void
+osx_output_close(struct audio_output *ao)
+{
+	OSXOutput *od = (OSXOutput *)ao;
+
+	AudioOutputUnitStop(od->au);
+	AudioUnitUninitialize(od->au);
+
+	delete od->buffer;
+}
+
+static bool
+osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		Error &error)
+{
+	OSXOutput *od = (OSXOutput *)ao;
+
+	AudioStreamBasicDescription stream_description;
+	stream_description.mSampleRate = audio_format.sample_rate;
+	stream_description.mFormatID = kAudioFormatLinearPCM;
+	stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+	switch (audio_format.format) {
+	case SampleFormat::S8:
+		stream_description.mBitsPerChannel = 8;
+		break;
+
+	case SampleFormat::S16:
+		stream_description.mBitsPerChannel = 16;
+		break;
+
+	case SampleFormat::S32:
+		stream_description.mBitsPerChannel = 32;
+		break;
+
+	default:
+		audio_format.format = SampleFormat::S32;
+		stream_description.mBitsPerChannel = 32;
+		break;
+	}
+
+	if (IsBigEndian())
+		stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+
+	stream_description.mBytesPerPacket = audio_format.GetFrameSize();
+	stream_description.mFramesPerPacket = 1;
+	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
+	stream_description.mChannelsPerFrame = audio_format.channels;
+
+	ComponentResult result =
+		AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+				     kAudioUnitScope_Input, 0,
+				     &stream_description,
+				     sizeof(stream_description));
+	if (result != noErr) {
+		error.Set(osx_output_domain, result,
+			  "Unable to set format on OS X device");
+		return false;
+	}
+
+	OSStatus status = AudioUnitInitialize(od->au);
+	if (status != noErr) {
+		error.Format(osx_output_domain, status,
+			     "Unable to initialize OS X audio unit: %s",
+			     GetMacOSStatusCommentString(status));
+		return false;
+	}
+
+	/* create a buffer of 1s */
+	od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
+						    audio_format.GetFrameSize());
+
+	status = AudioOutputUnitStart(od->au);
+	if (status != 0) {
+		AudioUnitUninitialize(od->au);
+		error.Format(osx_output_domain, status,
+			     "unable to start audio output: %s",
+			     GetMacOSStatusCommentString(status));
+		return false;
+	}
+
+	return true;
+}
+
+static size_t
+osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		gcc_unused Error &error)
+{
+	OSXOutput *od = (OSXOutput *)ao;
+
+	const ScopeLock protect(od->mutex);
+
+	DynamicFifoBuffer<uint8_t>::Range dest;
+	while (true) {
+		dest = od->buffer->Write();
+		if (!dest.IsEmpty())
+			break;
+
+		/* wait for some free space in the buffer */
+		od->condition.wait(od->mutex);
+	}
+
+	if (size > dest.size)
+		size = dest.size;
+
+	memcpy(dest.data, chunk, size);
+	od->buffer->Append(size);
+
+	return size;
+}
+
+const struct audio_output_plugin 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,
+	nullptr,
+	nullptr,
+	osx_output_play,
+	nullptr,
+	osx_output_cancel,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
new file mode 100644
index 000000000..0de10f83e
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_OSX_OUTPUT_PLUGIN_HXX
+#define MPD_OSX_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin osx_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
new file mode 100644
index 000000000..f590f0ea0
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpenALOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <glib.h>
+
+#ifndef __APPLE__
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct OpenALOutput {
+	struct audio_output base;
+
+	const char *device_name;
+	ALCdevice *device;
+	ALCcontext *context;
+	ALuint buffers[NUM_BUFFERS];
+	unsigned filled;
+	ALuint source;
+	ALenum format;
+	ALuint frequency;
+
+	bool Initialize(const config_param &param, Error &error_r) {
+		return ao_base_init(&base, &openal_output_plugin, param,
+				    error_r);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+};
+
+static constexpr Domain openal_output_domain("openal_output");
+
+static ALenum
+openal_audio_format(AudioFormat &audio_format)
+{
+	/* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
+	   AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
+	   samples, while MPD uses signed samples */
+
+	switch (audio_format.format) {
+	case SampleFormat::S16:
+		if (audio_format.channels == 2)
+			return AL_FORMAT_STEREO16;
+		if (audio_format.channels == 1)
+			return AL_FORMAT_MONO16;
+
+		/* fall back to mono */
+		audio_format.channels = 1;
+		return openal_audio_format(audio_format);
+
+	default:
+		/* fall back to 16 bit */
+		audio_format.format = SampleFormat::S16;
+		return openal_audio_format(audio_format);
+	}
+}
+
+gcc_pure
+static inline ALint
+openal_get_source_i(const OpenALOutput *od, ALenum param)
+{
+	ALint value;
+	alGetSourcei(od->source, param, &value);
+	return value;
+}
+
+gcc_pure
+static inline bool
+openal_has_processed(const OpenALOutput *od)
+{
+	return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+gcc_pure
+static inline ALint
+openal_is_playing(const OpenALOutput *od)
+{
+	return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
+}
+
+static bool
+openal_setup_context(OpenALOutput *od, Error &error)
+{
+	od->device = alcOpenDevice(od->device_name);
+
+	if (od->device == nullptr) {
+		error.Format(openal_output_domain,
+			     "Error opening OpenAL device \"%s\"",
+			     od->device_name);
+		return false;
+	}
+
+	od->context = alcCreateContext(od->device, nullptr);
+
+	if (od->context == nullptr) {
+		error.Format(openal_output_domain,
+			     "Error creating context for \"%s\"",
+			     od->device_name);
+		alcCloseDevice(od->device);
+		return false;
+	}
+
+	return true;
+}
+
+static struct audio_output *
+openal_init(const config_param &param, Error &error)
+{
+	const char *device_name = param.GetBlockValue("device");
+	if (device_name == nullptr) {
+		device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
+	}
+
+	OpenALOutput *od = new OpenALOutput();
+	if (!od->Initialize(param, error)) {
+		delete od;
+		return nullptr;
+	}
+
+	od->device_name = device_name;
+
+	return &od->base;
+}
+
+static void
+openal_finish(struct audio_output *ao)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+
+	od->Deinitialize();
+	delete od;
+}
+
+static bool
+openal_open(struct audio_output *ao, AudioFormat &audio_format,
+	    Error &error)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+
+	od->format = openal_audio_format(audio_format);
+
+	if (!openal_setup_context(od, error)) {
+		return false;
+	}
+
+	alcMakeContextCurrent(od->context);
+	alGenBuffers(NUM_BUFFERS, od->buffers);
+
+	if (alGetError() != AL_NO_ERROR) {
+		error.Set(openal_output_domain, "Failed to generate buffers");
+		return false;
+	}
+
+	alGenSources(1, &od->source);
+
+	if (alGetError() != AL_NO_ERROR) {
+		error.Set(openal_output_domain, "Failed to generate source");
+		alDeleteBuffers(NUM_BUFFERS, od->buffers);
+		return false;
+	}
+
+	od->filled = 0;
+	od->frequency = audio_format.sample_rate;
+
+	return true;
+}
+
+static void
+openal_close(struct audio_output *ao)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+
+	alcMakeContextCurrent(od->context);
+	alDeleteSources(1, &od->source);
+	alDeleteBuffers(NUM_BUFFERS, od->buffers);
+	alcDestroyContext(od->context);
+	alcCloseDevice(od->device);
+}
+
+static unsigned
+openal_delay(struct audio_output *ao)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+
+	return od->filled < NUM_BUFFERS || openal_has_processed(od)
+		? 0
+		/* we don't know exactly how long we must wait for the
+		   next buffer to finish, so this is a random
+		   guess: */
+		: 50;
+}
+
+static size_t
+openal_play(struct audio_output *ao, const void *chunk, size_t size,
+	    gcc_unused Error &error)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+	ALuint buffer;
+
+	if (alcGetCurrentContext() != od->context) {
+		alcMakeContextCurrent(od->context);
+	}
+
+	if (od->filled < NUM_BUFFERS) {
+		/* fill all buffers */
+		buffer = od->buffers[od->filled];
+		od->filled++;
+	} else {
+		/* wait for processed buffer */
+		while (!openal_has_processed(od))
+			g_usleep(10);
+
+		alSourceUnqueueBuffers(od->source, 1, &buffer);
+	}
+
+	alBufferData(buffer, od->format, chunk, size, od->frequency);
+	alSourceQueueBuffers(od->source, 1, &buffer);
+
+	if (!openal_is_playing(od))
+		alSourcePlay(od->source);
+
+	return size;
+}
+
+static void
+openal_cancel(struct audio_output *ao)
+{
+	OpenALOutput *od = (OpenALOutput *)ao;
+
+	od->filled = 0;
+	alcMakeContextCurrent(od->context);
+	alSourceStop(od->source);
+
+	/* force-unqueue all buffers */
+	alSourcei(od->source, AL_BUFFER, 0);
+	od->filled = 0;
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+	"openal",
+	nullptr,
+	openal_init,
+	openal_finish,
+	nullptr,
+	nullptr,
+	openal_open,
+	openal_close,
+	openal_delay,
+	nullptr,
+	openal_play,
+	nullptr,
+	openal_cancel,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx
new file mode 100644
index 000000000..eb43d1aa5
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_OPENAL_OUTPUT_PLUGIN_HXX
+#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin openal_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
new file mode 100644
index 000000000..cdf055df9
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OssOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+/* We got bug reports from FreeBSD users who said that the two 24 bit
+   formats generate white noise on FreeBSD, but 32 bit works.  This is
+   a workaround until we know what exactly is expected by the kernel
+   audio drivers. */
+#ifndef __linux__
+#undef AFMT_S24_PACKED
+#undef AFMT_S24_NE
+#endif
+
+#ifdef AFMT_S24_PACKED
+#include "pcm/PcmExport.hxx"
+#include "util/Manual.hxx"
+#endif
+
+struct OssOutput {
+	struct audio_output base;
+
+#ifdef AFMT_S24_PACKED
+	Manual<PcmExport> pcm_export;
+#endif
+
+	int fd;
+	const char *device;
+
+	/**
+	 * The current input audio format.  This is needed to reopen
+	 * the device after cancel().
+	 */
+	AudioFormat audio_format;
+
+	/**
+	 * The current OSS audio format.  This is needed to reopen the
+	 * device after cancel().
+	 */
+	int oss_format;
+
+	OssOutput():fd(-1), device(nullptr) {}
+
+	bool Initialize(const config_param &param, Error &error_r) {
+		return ao_base_init(&base, &oss_output_plugin, param,
+				    error_r);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+};
+
+static constexpr Domain oss_output_domain("oss_output");
+
+enum oss_stat {
+	OSS_STAT_NO_ERROR = 0,
+	OSS_STAT_NOT_CHAR_DEV = -1,
+	OSS_STAT_NO_PERMS = -2,
+	OSS_STAT_DOESN_T_EXIST = -3,
+	OSS_STAT_OTHER = -4,
+};
+
+static enum oss_stat
+oss_stat_device(const char *device, int *errno_r)
+{
+	struct stat st;
+
+	if (0 == stat(device, &st)) {
+		if (!S_ISCHR(st.st_mode)) {
+			return OSS_STAT_NOT_CHAR_DEV;
+		}
+	} else {
+		*errno_r = errno;
+
+		switch (errno) {
+		case ENOENT:
+		case ENOTDIR:
+			return OSS_STAT_DOESN_T_EXIST;
+		case EACCES:
+			return OSS_STAT_NO_PERMS;
+		default:
+			return OSS_STAT_OTHER;
+		}
+	}
+
+	return OSS_STAT_NO_ERROR;
+}
+
+static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
+
+static bool
+oss_output_test_default_device(void)
+{
+	int fd, i;
+
+	for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+		fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+		if (fd >= 0) {
+			close(fd);
+			return true;
+		}
+
+		FormatErrno(oss_output_domain,
+			    "Error opening OSS device \"%s\"",
+			    default_devices[i]);
+	}
+
+	return false;
+}
+
+static struct audio_output *
+oss_open_default(Error &error)
+{
+	int err[ARRAY_SIZE(default_devices)];
+	enum oss_stat ret[ARRAY_SIZE(default_devices)];
+
+	const config_param empty;
+	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+		ret[i] = oss_stat_device(default_devices[i], &err[i]);
+		if (ret[i] == OSS_STAT_NO_ERROR) {
+			OssOutput *od = new OssOutput();
+			if (!od->Initialize(empty, error)) {
+				delete od;
+				return NULL;
+			}
+
+			od->device = default_devices[i];
+			return &od->base;
+		}
+	}
+
+	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+		const char *dev = default_devices[i];
+		switch(ret[i]) {
+		case OSS_STAT_NO_ERROR:
+			/* never reached */
+			break;
+		case OSS_STAT_DOESN_T_EXIST:
+			FormatWarning(oss_output_domain,
+				      "%s not found", dev);
+			break;
+		case OSS_STAT_NOT_CHAR_DEV:
+			FormatWarning(oss_output_domain,
+				      "%s is not a character device", dev);
+			break;
+		case OSS_STAT_NO_PERMS:
+			FormatWarning(oss_output_domain,
+				      "%s: permission denied", dev);
+			break;
+		case OSS_STAT_OTHER:
+			FormatErrno(oss_output_domain, err[i],
+				    "Error accessing %s", dev);
+		}
+	}
+
+	error.Set(oss_output_domain,
+		  "error trying to open default OSS device");
+	return NULL;
+}
+
+static struct audio_output *
+oss_output_init(const config_param &param, Error &error)
+{
+	const char *device = param.GetBlockValue("device");
+	if (device != NULL) {
+		OssOutput *od = new OssOutput();
+		if (!od->Initialize(param, error)) {
+			delete od;
+			return NULL;
+		}
+
+		od->device = device;
+		return &od->base;
+	}
+
+	return oss_open_default(error);
+}
+
+static void
+oss_output_finish(struct audio_output *ao)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	ao_base_finish(&od->base);
+	delete od;
+}
+
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(struct audio_output *ao, gcc_unused Error &error)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	od->pcm_export.Construct();
+	return true;
+}
+
+static void
+oss_output_disable(struct audio_output *ao)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	od->pcm_export.Destruct();
+}
+
+#endif
+
+static void
+oss_close(OssOutput *od)
+{
+	if (od->fd >= 0)
+		close(od->fd);
+	od->fd = -1;
+}
+
+/**
+ * A tri-state type for oss_try_ioctl().
+ */
+enum oss_setup_result {
+	SUCCESS,
+	ERROR,
+	UNSUPPORTED,
+};
+
+/**
+ * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
+ * returned.  If the parameter is not supported, UNSUPPORTED is
+ * returned.  Any other failure returns ERROR and allocates an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
+		const char *msg, Error &error)
+{
+	assert(fd >= 0);
+	assert(value_r != NULL);
+	assert(msg != NULL);
+	assert(!error.IsDefined());
+
+	int ret = ioctl(fd, request, value_r);
+	if (ret >= 0)
+		return SUCCESS;
+
+	if (errno == EINVAL)
+		return UNSUPPORTED;
+
+	error.SetErrno(msg);
+	return ERROR;
+}
+
+/**
+ * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
+ * returned.  If the parameter is not supported, UNSUPPORTED is
+ * returned.  Any other failure returns ERROR and allocates an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+	      const char *msg, Error &error_r)
+{
+	return oss_try_ioctl_r(fd, request, &value, msg, error_r);
+}
+
+/**
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
+ */
+static bool
+oss_setup_channels(int fd, AudioFormat &audio_format, Error &error)
+{
+	const char *const msg = "Failed to set channel count";
+	int channels = audio_format.channels;
+	enum oss_setup_result result =
+		oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error);
+	switch (result) {
+	case SUCCESS:
+		if (!audio_valid_channel_count(channels))
+		    break;
+
+		audio_format.channels = channels;
+		return true;
+
+	case ERROR:
+		return false;
+
+	case UNSUPPORTED:
+		break;
+	}
+
+	for (unsigned i = 1; i < 2; ++i) {
+		if (i == audio_format.channels)
+			/* don't try that again */
+			continue;
+
+		channels = i;
+		result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
+					 msg, error);
+		switch (result) {
+		case SUCCESS:
+			if (!audio_valid_channel_count(channels))
+			    break;
+
+			audio_format.channels = channels;
+			return true;
+
+		case ERROR:
+			return false;
+
+		case UNSUPPORTED:
+			break;
+		}
+	}
+
+	error.Set(oss_output_domain, msg);
+	return false;
+}
+
+/**
+ * Set up the sample rate, and attempts to find alternatives if the
+ * specified sample rate is not supported.
+ */
+static bool
+oss_setup_sample_rate(int fd, AudioFormat &audio_format,
+		      Error &error)
+{
+	const char *const msg = "Failed to set sample rate";
+	int sample_rate = audio_format.sample_rate;
+	enum oss_setup_result result =
+		oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+				msg, error);
+	switch (result) {
+	case SUCCESS:
+		if (!audio_valid_sample_rate(sample_rate))
+			break;
+
+		audio_format.sample_rate = sample_rate;
+		return true;
+
+	case ERROR:
+		return false;
+
+	case UNSUPPORTED:
+		break;
+	}
+
+	static const int sample_rates[] = { 48000, 44100, 0 };
+	for (unsigned i = 0; sample_rates[i] != 0; ++i) {
+		sample_rate = sample_rates[i];
+		if (sample_rate == (int)audio_format.sample_rate)
+			continue;
+
+		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+					 msg, error);
+		switch (result) {
+		case SUCCESS:
+			if (!audio_valid_sample_rate(sample_rate))
+				break;
+
+			audio_format.sample_rate = sample_rate;
+			return true;
+
+		case ERROR:
+			return false;
+
+		case UNSUPPORTED:
+			break;
+		}
+	}
+
+	error.Set(oss_output_domain, msg);
+	return false;
+}
+
+/**
+ * Convert a MPD sample format to its OSS counterpart.  Returns
+ * AFMT_QUERY if there is no direct counterpart.
+ */
+static int
+sample_format_to_oss(SampleFormat format)
+{
+	switch (format) {
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+		return AFMT_QUERY;
+
+	case SampleFormat::S8:
+		return AFMT_S8;
+
+	case SampleFormat::S16:
+		return AFMT_S16_NE;
+
+	case SampleFormat::S24_P32:
+#ifdef AFMT_S24_NE
+		return AFMT_S24_NE;
+#else
+		return AFMT_QUERY;
+#endif
+
+	case SampleFormat::S32:
+#ifdef AFMT_S32_NE
+		return AFMT_S32_NE;
+#else
+		return AFMT_QUERY;
+#endif
+	}
+
+	return AFMT_QUERY;
+}
+
+/**
+ * Convert an OSS sample format to its MPD counterpart.  Returns
+ * SampleFormat::UNDEFINED if there is no direct counterpart.
+ */
+static SampleFormat
+sample_format_from_oss(int format)
+{
+	switch (format) {
+	case AFMT_S8:
+		return SampleFormat::S8;
+
+	case AFMT_S16_NE:
+		return SampleFormat::S16;
+
+#ifdef AFMT_S24_PACKED
+	case AFMT_S24_PACKED:
+		return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S24_NE
+	case AFMT_S24_NE:
+		return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S32_NE
+	case AFMT_S32_NE:
+		return SampleFormat::S32;
+#endif
+
+	default:
+		return SampleFormat::UNDEFINED;
+	}
+}
+
+/**
+ * Probe one sample format.
+ *
+ * @return the selected sample format or SampleFormat::UNDEFINED on
+ * error
+ */
+static enum oss_setup_result
+oss_probe_sample_format(int fd, SampleFormat sample_format,
+			SampleFormat *sample_format_r,
+			int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+			PcmExport &pcm_export,
+#endif
+			Error &error)
+{
+	int oss_format = sample_format_to_oss(sample_format);
+	if (oss_format == AFMT_QUERY)
+		return UNSUPPORTED;
+
+	enum oss_setup_result result =
+		oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+				&oss_format,
+				"Failed to set sample format", error);
+
+#ifdef AFMT_S24_PACKED
+	if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
+		/* if the driver doesn't support padded 24 bit, try
+		   packed 24 bit */
+		oss_format = AFMT_S24_PACKED;
+		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+					 &oss_format,
+					 "Failed to set sample format", error);
+	}
+#endif
+
+	if (result != SUCCESS)
+		return result;
+
+	sample_format = sample_format_from_oss(oss_format);
+	if (sample_format == SampleFormat::UNDEFINED)
+		return UNSUPPORTED;
+
+	*sample_format_r = sample_format;
+	*oss_format_r = oss_format;
+
+#ifdef AFMT_S24_PACKED
+	pcm_export.Open(sample_format, 0, false, false,
+			oss_format == AFMT_S24_PACKED,
+			oss_format == AFMT_S24_PACKED &&
+			!IsLittleEndian());
+#endif
+
+	return SUCCESS;
+}
+
+/**
+ * Set up the sample format, and attempts to find alternatives if the
+ * specified format is not supported.
+ */
+static bool
+oss_setup_sample_format(int fd, AudioFormat &audio_format,
+			int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+			PcmExport &pcm_export,
+#endif
+			Error &error)
+{
+	SampleFormat mpd_format;
+	enum oss_setup_result result =
+		oss_probe_sample_format(fd, audio_format.format,
+					&mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+					pcm_export,
+#endif
+					error);
+	switch (result) {
+	case SUCCESS:
+		audio_format.format = mpd_format;
+		return true;
+
+	case ERROR:
+		return false;
+
+	case UNSUPPORTED:
+		break;
+	}
+
+	if (result != UNSUPPORTED)
+		return result == SUCCESS;
+
+	/* the requested sample format is not available - probe for
+	   other formats supported by MPD */
+
+	static const SampleFormat sample_formats[] = {
+		SampleFormat::S24_P32,
+		SampleFormat::S32,
+		SampleFormat::S16,
+		SampleFormat::S8,
+		SampleFormat::UNDEFINED /* sentinel */
+	};
+
+	for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
+		mpd_format = sample_formats[i];
+		if (mpd_format == audio_format.format)
+			/* don't try that again */
+			continue;
+
+		result = oss_probe_sample_format(fd, mpd_format,
+						 &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+						 pcm_export,
+#endif
+						 error);
+		switch (result) {
+		case SUCCESS:
+			audio_format.format = mpd_format;
+			return true;
+
+		case ERROR:
+			return false;
+
+		case UNSUPPORTED:
+			break;
+		}
+	}
+
+	error.Set(oss_output_domain, "Failed to set sample format");
+	return false;
+}
+
+/**
+ * Sets up the OSS device which was opened before.
+ */
+static bool
+oss_setup(OssOutput *od, AudioFormat &audio_format,
+	  Error &error)
+{
+	return oss_setup_channels(od->fd, audio_format, error) &&
+		oss_setup_sample_rate(od->fd, audio_format, error) &&
+		oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+					od->pcm_export,
+#endif
+					error);
+}
+
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(OssOutput *od, Error &error)
+{
+	assert(od->fd < 0);
+
+	od->fd = open_cloexec(od->device, O_WRONLY, 0);
+	if (od->fd < 0) {
+		error.FormatErrno("Error opening OSS device \"%s\"",
+				  od->device);
+		return false;
+	}
+
+	enum oss_setup_result result;
+
+	const char *const msg1 = "Failed to set channel count";
+	result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
+			       od->audio_format.channels, msg1, error);
+	if (result != SUCCESS) {
+		oss_close(od);
+		if (result == UNSUPPORTED)
+			error.Set(oss_output_domain, msg1);
+		return false;
+	}
+
+	const char *const msg2 = "Failed to set sample rate";
+	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
+			       od->audio_format.sample_rate, msg2, error);
+	if (result != SUCCESS) {
+		oss_close(od);
+		if (result == UNSUPPORTED)
+			error.Set(oss_output_domain, msg2);
+		return false;
+	}
+
+	const char *const msg3 = "Failed to set sample format";
+	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
+			       od->oss_format,
+			       msg3, error);
+	if (result != SUCCESS) {
+		oss_close(od);
+		if (result == UNSUPPORTED)
+			error.Set(oss_output_domain, msg3);
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		Error &error)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	od->fd = open_cloexec(od->device, O_WRONLY, 0);
+	if (od->fd < 0) {
+		error.FormatErrno("Error opening OSS device \"%s\"",
+				  od->device);
+		return false;
+	}
+
+	if (!oss_setup(od, audio_format, error)) {
+		oss_close(od);
+		return false;
+	}
+
+	od->audio_format = audio_format;
+	return true;
+}
+
+static void
+oss_output_close(struct audio_output *ao)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	oss_close(od);
+}
+
+static void
+oss_output_cancel(struct audio_output *ao)
+{
+	OssOutput *od = (OssOutput *)ao;
+
+	if (od->fd >= 0) {
+		ioctl(od->fd, SNDCTL_DSP_RESET, 0);
+		oss_close(od);
+	}
+}
+
+static size_t
+oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		Error &error)
+{
+	OssOutput *od = (OssOutput *)ao;
+	ssize_t ret;
+
+	/* reopen the device since it was closed by dropBufferedAudio */
+	if (od->fd < 0 && !oss_reopen(od, error))
+		return 0;
+
+#ifdef AFMT_S24_PACKED
+	chunk = od->pcm_export->Export(chunk, size, size);
+#endif
+
+	while (true) {
+		ret = write(od->fd, chunk, size);
+		if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+			ret = od->pcm_export->CalcSourceSize(ret);
+#endif
+			return ret;
+		}
+
+		if (ret < 0 && errno != EINTR) {
+			error.FormatErrno("Write error on %s", od->device);
+			return 0;
+		}
+	}
+}
+
+const struct audio_output_plugin oss_output_plugin = {
+	"oss",
+	oss_output_test_default_device,
+	oss_output_init,
+	oss_output_finish,
+#ifdef AFMT_S24_PACKED
+	oss_output_enable,
+	oss_output_disable,
+#else
+	nullptr,
+	nullptr,
+#endif
+	oss_output_open,
+	oss_output_close,
+	nullptr,
+	nullptr,
+	oss_output_play,
+	nullptr,
+	oss_output_cancel,
+	nullptr,
+
+	&oss_mixer_plugin,
+};
diff --git a/src/output/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx
new file mode 100644
index 000000000..4762fa652
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_OSS_OUTPUT_PLUGIN_HXX
+#define MPD_OSS_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin oss_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
new file mode 100644
index 000000000..802e1ba4d
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PipeOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string>
+
+#include <stdio.h>
+
+struct PipeOutput {
+	struct audio_output base;
+
+	std::string cmd;
+	FILE *fh;
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &pipe_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain pipe_output_domain("pipe_output");
+
+inline bool
+PipeOutput::Configure(const config_param &param, Error &error)
+{
+	cmd = param.GetBlockValue("command", "");
+	if (cmd.empty()) {
+		error.Set(config_domain,
+			  "No \"command\" parameter specified");
+		return false;
+	}
+
+	return true;
+}
+
+static struct audio_output *
+pipe_output_init(const config_param &param, Error &error)
+{
+	PipeOutput *pd = new PipeOutput();
+
+	if (!pd->Initialize(param, error)) {
+		delete pd;
+		return nullptr;
+	}
+
+	if (!pd->Configure(param, error)) {
+		pd->Deinitialize();
+		delete pd;
+		return nullptr;
+	}
+
+	return &pd->base;
+}
+
+static void
+pipe_output_finish(struct audio_output *ao)
+{
+	PipeOutput *pd = (PipeOutput *)ao;
+
+	pd->Deinitialize();
+	delete pd;
+}
+
+static bool
+pipe_output_open(struct audio_output *ao,
+		 gcc_unused AudioFormat &audio_format,
+		 Error &error)
+{
+	PipeOutput *pd = (PipeOutput *)ao;
+
+	pd->fh = popen(pd->cmd.c_str(), "w");
+	if (pd->fh == nullptr) {
+		error.FormatErrno("Error opening pipe \"%s\"",
+				  pd->cmd.c_str());
+		return false;
+	}
+
+	return true;
+}
+
+static void
+pipe_output_close(struct audio_output *ao)
+{
+	PipeOutput *pd = (PipeOutput *)ao;
+
+	pclose(pd->fh);
+}
+
+static size_t
+pipe_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		 Error &error)
+{
+	PipeOutput *pd = (PipeOutput *)ao;
+	size_t ret;
+
+	ret = fwrite(chunk, 1, size, pd->fh);
+	if (ret == 0)
+		error.SetErrno("Write error on pipe");
+
+	return ret;
+}
+
+const struct audio_output_plugin pipe_output_plugin = {
+	"pipe",
+	nullptr,
+	pipe_output_init,
+	pipe_output_finish,
+	nullptr,
+	nullptr,
+	pipe_output_open,
+	pipe_output_close,
+	nullptr,
+	nullptr,
+	pipe_output_play,
+	nullptr,
+	nullptr,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx
new file mode 100644
index 000000000..42b01b9f7
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_PIPE_OUTPUT_PLUGIN_HXX
+#define MPD_PIPE_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
new file mode 100644
index 000000000..c133d9796
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -0,0 +1,889 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PulseOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "mixer/PulseMixerPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+#include <pulse/version.h>
+
+#include <assert.h>
+#include <stddef.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+struct PulseOutput {
+	struct audio_output base;
+
+	const char *name;
+	const char *server;
+	const char *sink;
+
+	PulseMixer *mixer;
+
+	struct pa_threaded_mainloop *mainloop;
+	struct pa_context *context;
+	struct pa_stream *stream;
+
+	size_t writable;
+};
+
+static constexpr Domain pulse_output_domain("pulse_output");
+
+static void
+SetError(Error &error, pa_context *context, const char *msg)
+{
+	const int e = pa_context_errno(context);
+	error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
+}
+
+void
+pulse_output_lock(PulseOutput *po)
+{
+	pa_threaded_mainloop_lock(po->mainloop);
+}
+
+void
+pulse_output_unlock(PulseOutput *po)
+{
+	pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm)
+{
+	assert(po != nullptr);
+	assert(po->mixer == nullptr);
+	assert(pm != nullptr);
+
+	po->mixer = pm;
+
+	if (po->mainloop == nullptr)
+		return;
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (po->context != nullptr &&
+	    pa_context_get_state(po->context) == PA_CONTEXT_READY) {
+		pulse_mixer_on_connect(pm, po->context);
+
+		if (po->stream != nullptr &&
+		    pa_stream_get_state(po->stream) == PA_STREAM_READY)
+			pulse_mixer_on_change(pm, po->context, po->stream);
+	}
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm)
+{
+	assert(po != nullptr);
+	assert(pm != nullptr);
+	assert(po->mixer == pm);
+
+	po->mixer = nullptr;
+}
+
+bool
+pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume,
+			Error &error)
+{
+	pa_operation *o;
+
+	if (po->context == nullptr || po->stream == nullptr ||
+	    pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+		error.Set(pulse_output_domain, "disconnected");
+		return false;
+	}
+
+	o = pa_context_set_sink_input_volume(po->context,
+					     pa_stream_get_index(po->stream),
+					     volume, nullptr, nullptr);
+	if (o == nullptr) {
+		SetError(error, po->context,
+			 "failed to set PulseAudio volume");
+		return false;
+	}
+
+	pa_operation_unref(o);
+	return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ *     unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ *     false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+			 struct pa_operation *operation)
+{
+	pa_operation_state_t state;
+
+	assert(mainloop != nullptr);
+	assert(operation != nullptr);
+
+	state = pa_operation_get_state(operation);
+	while (state == PA_OPERATION_RUNNING) {
+		pa_threaded_mainloop_wait(mainloop);
+		state = pa_operation_get_state(operation);
+	}
+
+	pa_operation_unref(operation);
+
+	return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation.  It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(gcc_unused pa_stream *s,
+			       gcc_unused int success, void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+
+	pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+
+	switch (pa_context_get_state(context)) {
+	case PA_CONTEXT_READY:
+		if (po->mixer != nullptr)
+			pulse_mixer_on_connect(po->mixer, context);
+
+		pa_threaded_mainloop_signal(po->mainloop, 0);
+		break;
+
+	case PA_CONTEXT_TERMINATED:
+	case PA_CONTEXT_FAILED:
+		if (po->mixer != nullptr)
+			pulse_mixer_on_disconnect(po->mixer);
+
+		/* the caller thread might be waiting for these
+		   states */
+		pa_threaded_mainloop_signal(po->mainloop, 0);
+		break;
+
+	case PA_CONTEXT_UNCONNECTED:
+	case PA_CONTEXT_CONNECTING:
+	case PA_CONTEXT_AUTHORIZING:
+	case PA_CONTEXT_SETTING_NAME:
+		break;
+	}
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+			  pa_subscription_event_type_t t,
+			  uint32_t idx, void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+	pa_subscription_event_type_t facility =
+		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
+	pa_subscription_event_type_t type =
+		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
+
+	if (po->mixer != nullptr &&
+	    facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+	    po->stream != nullptr &&
+	    pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+	    idx == pa_stream_get_index(po->stream) &&
+	    (type == PA_SUBSCRIPTION_EVENT_NEW ||
+	     type == PA_SUBSCRIPTION_EVENT_CHANGE))
+		pulse_mixer_on_change(po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(PulseOutput *po, Error &error)
+{
+	assert(po != nullptr);
+	assert(po->context != nullptr);
+
+	if (pa_context_connect(po->context, po->server,
+			       (pa_context_flags_t)0, nullptr) < 0) {
+		SetError(error, po->context,
+			 "pa_context_connect() has failed");
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Frees and clears the stream.
+ */
+static void
+pulse_output_delete_stream(PulseOutput *po)
+{
+	assert(po != nullptr);
+	assert(po->stream != nullptr);
+
+	pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
+
+	pa_stream_set_state_callback(po->stream, nullptr, nullptr);
+	pa_stream_set_write_callback(po->stream, nullptr, nullptr);
+
+	pa_stream_disconnect(po->stream);
+	pa_stream_unref(po->stream);
+	po->stream = nullptr;
+}
+
+/**
+ * Frees and clears the context.
+ *
+ * Caller must lock the main loop.
+ */
+static void
+pulse_output_delete_context(PulseOutput *po)
+{
+	assert(po != nullptr);
+	assert(po->context != nullptr);
+
+	pa_context_set_state_callback(po->context, nullptr, nullptr);
+	pa_context_set_subscribe_callback(po->context, nullptr, nullptr);
+
+	pa_context_disconnect(po->context);
+	pa_context_unref(po->context);
+	po->context = nullptr;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(PulseOutput *po, Error &error)
+{
+	assert(po != nullptr);
+	assert(po->mainloop != nullptr);
+
+	po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+				     MPD_PULSE_NAME);
+	if (po->context == nullptr) {
+		error.Set(pulse_output_domain, "pa_context_new() has failed");
+		return false;
+	}
+
+	pa_context_set_state_callback(po->context,
+				      pulse_output_context_state_cb, po);
+	pa_context_set_subscribe_callback(po->context,
+					  pulse_output_subscribe_cb, po);
+
+	if (!pulse_output_connect(po, error)) {
+		pulse_output_delete_context(po);
+		return false;
+	}
+
+	return true;
+}
+
+static struct audio_output *
+pulse_output_init(const config_param &param, Error &error)
+{
+	PulseOutput *po;
+
+	g_setenv("PULSE_PROP_media.role", "music", true);
+
+	po = new PulseOutput();
+	if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) {
+		delete po;
+		return nullptr;
+	}
+
+	po->name = param.GetBlockValue("name", "mpd_pulse");
+	po->server = param.GetBlockValue("server");
+	po->sink = param.GetBlockValue("sink");
+
+	po->mixer = nullptr;
+	po->mainloop = nullptr;
+	po->context = nullptr;
+	po->stream = nullptr;
+
+	return &po->base;
+}
+
+static void
+pulse_output_finish(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+
+	ao_base_finish(&po->base);
+	delete po;
+}
+
+static bool
+pulse_output_enable(struct audio_output *ao, Error &error)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+
+	assert(po->mainloop == nullptr);
+	assert(po->context == nullptr);
+
+	/* create the libpulse mainloop and start the thread */
+
+	po->mainloop = pa_threaded_mainloop_new();
+	if (po->mainloop == nullptr) {
+		g_free(po);
+
+		error.Set(pulse_output_domain,
+			  "pa_threaded_mainloop_new() has failed");
+		return false;
+	}
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		pa_threaded_mainloop_free(po->mainloop);
+		po->mainloop = nullptr;
+
+		error.Set(pulse_output_domain,
+			  "pa_threaded_mainloop_start() has failed");
+		return false;
+	}
+
+	/* create the libpulse context and connect it */
+
+	if (!pulse_output_setup_context(po, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		pa_threaded_mainloop_stop(po->mainloop);
+		pa_threaded_mainloop_free(po->mainloop);
+		po->mainloop = nullptr;
+		return false;
+	}
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+
+	return true;
+}
+
+static void
+pulse_output_disable(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+
+	assert(po->mainloop != nullptr);
+
+	pa_threaded_mainloop_stop(po->mainloop);
+	if (po->context != nullptr)
+		pulse_output_delete_context(po);
+	pa_threaded_mainloop_free(po->mainloop);
+	po->mainloop = nullptr;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not.  If
+ * the context has been disconnected, retry to connect.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(PulseOutput *po, Error &error)
+{
+	assert(po->mainloop != nullptr);
+
+	pa_context_state_t state;
+
+	if (po->context == nullptr && !pulse_output_setup_context(po, error))
+		return false;
+
+	while (true) {
+		state = pa_context_get_state(po->context);
+		switch (state) {
+		case PA_CONTEXT_READY:
+			/* nothing to do */
+			return true;
+
+		case PA_CONTEXT_UNCONNECTED:
+		case PA_CONTEXT_TERMINATED:
+		case PA_CONTEXT_FAILED:
+			/* failure */
+			SetError(error, po->context, "failed to connect");
+			pulse_output_delete_context(po);
+			return false;
+
+		case PA_CONTEXT_CONNECTING:
+		case PA_CONTEXT_AUTHORIZING:
+		case PA_CONTEXT_SETTING_NAME:
+			/* wait some more */
+			pa_threaded_mainloop_wait(po->mainloop);
+			break;
+		}
+	}
+}
+
+static void
+pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+
+	assert(stream == po->stream || po->stream == nullptr);
+	assert(po->mainloop != nullptr);
+
+	/* wake up the main loop to break out of the loop in
+	   pulse_output_play() */
+	pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+
+	assert(stream == po->stream || po->stream == nullptr);
+	assert(po->mainloop != nullptr);
+	assert(po->context != nullptr);
+
+	switch (pa_stream_get_state(stream)) {
+	case PA_STREAM_READY:
+		if (po->mixer != nullptr)
+			pulse_mixer_on_change(po->mixer, po->context, stream);
+
+		pa_threaded_mainloop_signal(po->mainloop, 0);
+		break;
+
+	case PA_STREAM_FAILED:
+	case PA_STREAM_TERMINATED:
+		if (po->mixer != nullptr)
+			pulse_mixer_on_disconnect(po->mixer);
+
+		pa_threaded_mainloop_signal(po->mainloop, 0);
+		break;
+
+	case PA_STREAM_UNCONNECTED:
+	case PA_STREAM_CREATING:
+		break;
+	}
+}
+
+static void
+pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes,
+			     void *userdata)
+{
+	PulseOutput *po = (PulseOutput *)userdata;
+
+	assert(po->mainloop != nullptr);
+
+	po->writable = nbytes;
+	pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
+			  Error &error)
+{
+	assert(po != nullptr);
+	assert(po->context != nullptr);
+
+	po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+	if (po->stream == nullptr) {
+		SetError(error, po->context, "pa_stream_new() has failed");
+		return false;
+	}
+
+	pa_stream_set_suspended_callback(po->stream,
+					 pulse_output_stream_suspended_cb, po);
+
+	pa_stream_set_state_callback(po->stream,
+				     pulse_output_stream_state_cb, po);
+	pa_stream_set_write_callback(po->stream,
+				     pulse_output_stream_write_cb, po);
+
+	return true;
+}
+
+static bool
+pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		  Error &error)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+	pa_sample_spec ss;
+
+	assert(po->mainloop != nullptr);
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (po->context != nullptr) {
+		switch (pa_context_get_state(po->context)) {
+		case PA_CONTEXT_UNCONNECTED:
+		case PA_CONTEXT_TERMINATED:
+		case PA_CONTEXT_FAILED:
+			/* the connection was closed meanwhile; delete
+			   it, and pulse_output_wait_connection() will
+			   reopen it */
+			pulse_output_delete_context(po);
+			break;
+
+		case PA_CONTEXT_READY:
+		case PA_CONTEXT_CONNECTING:
+		case PA_CONTEXT_AUTHORIZING:
+		case PA_CONTEXT_SETTING_NAME:
+			break;
+		}
+	}
+
+	if (!pulse_output_wait_connection(po, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return false;
+	}
+
+	/* MPD doesn't support the other pulseaudio sample formats, so
+	   we just force MPD to send us everything as 16 bit */
+	audio_format.format = SampleFormat::S16;
+
+	ss.format = PA_SAMPLE_S16NE;
+	ss.rate = audio_format.sample_rate;
+	ss.channels = audio_format.channels;
+
+	/* create a stream .. */
+
+	if (!pulse_output_setup_stream(po, &ss, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return false;
+	}
+
+	/* .. and connect it (asynchronously) */
+
+	if (pa_stream_connect_playback(po->stream, po->sink,
+				       nullptr, pa_stream_flags_t(0),
+				       nullptr, nullptr) < 0) {
+		pulse_output_delete_stream(po);
+
+		SetError(error, po->context,
+			 "pa_stream_connect_playback() has failed");
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return false;
+	}
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+
+	return true;
+}
+
+static void
+pulse_output_close(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+	pa_operation *o;
+
+	assert(po->mainloop != nullptr);
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+		o = pa_stream_drain(po->stream,
+				    pulse_output_stream_success_cb, po);
+		if (o == nullptr) {
+			FormatWarning(pulse_output_domain,
+				      "pa_stream_drain() has failed: %s",
+				      pa_strerror(pa_context_errno(po->context)));
+		} else
+			pulse_wait_for_operation(po->mainloop, o);
+	}
+
+	pulse_output_delete_stream(po);
+
+	if (po->context != nullptr &&
+	    pa_context_get_state(po->context) != PA_CONTEXT_READY)
+		pulse_output_delete_context(po);
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not.  The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(PulseOutput *po, Error &error)
+{
+	while (true) {
+		switch (pa_stream_get_state(po->stream)) {
+		case PA_STREAM_READY:
+			return true;
+
+		case PA_STREAM_FAILED:
+		case PA_STREAM_TERMINATED:
+		case PA_STREAM_UNCONNECTED:
+			SetError(error, po->context,
+				 "failed to connect the stream");
+			return false;
+
+		case PA_STREAM_CREATING:
+			pa_threaded_mainloop_wait(po->mainloop);
+			break;
+		}
+	}
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(PulseOutput *po, bool pause,
+			  Error &error)
+{
+	pa_operation *o;
+
+	assert(po->mainloop != nullptr);
+	assert(po->context != nullptr);
+	assert(po->stream != nullptr);
+
+	o = pa_stream_cork(po->stream, pause,
+			   pulse_output_stream_success_cb, po);
+	if (o == nullptr) {
+		SetError(error, po->context, "pa_stream_cork() has failed");
+		return false;
+	}
+
+	if (!pulse_wait_for_operation(po->mainloop, o)) {
+		SetError(error, po->context, "pa_stream_cork() has failed");
+		return false;
+	}
+
+	return true;
+}
+
+static unsigned
+pulse_output_delay(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+	unsigned result = 0;
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (po->base.pause && pa_stream_is_corked(po->stream) &&
+	    pa_stream_get_state(po->stream) == PA_STREAM_READY)
+		/* idle while paused */
+		result = 1000;
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+
+	return result;
+}
+
+static size_t
+pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		  Error &error)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+
+	assert(po->mainloop != nullptr);
+	assert(po->stream != nullptr);
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	/* check if the stream is (already) connected */
+
+	if (!pulse_output_wait_stream(po, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return 0;
+	}
+
+	assert(po->context != nullptr);
+
+	/* unpause if previously paused */
+
+	if (pa_stream_is_corked(po->stream) &&
+	    !pulse_output_stream_pause(po, false, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return 0;
+	}
+
+	/* wait until the server allows us to write */
+
+	while (po->writable == 0) {
+		if (pa_stream_is_suspended(po->stream)) {
+			pa_threaded_mainloop_unlock(po->mainloop);
+			error.Set(pulse_output_domain, "suspended");
+			return 0;
+		}
+
+		pa_threaded_mainloop_wait(po->mainloop);
+
+		if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+			pa_threaded_mainloop_unlock(po->mainloop);
+			error.Set(pulse_output_domain, "disconnected");
+			return 0;
+		}
+	}
+
+	/* now write */
+
+	if (size > po->writable)
+		/* don't send more than possible */
+		size = po->writable;
+
+	po->writable -= size;
+
+	int result = pa_stream_write(po->stream, chunk, size, nullptr,
+				     0, PA_SEEK_RELATIVE);
+	pa_threaded_mainloop_unlock(po->mainloop);
+	if (result < 0) {
+		SetError(error, po->context, "pa_stream_write() failed");
+		return 0;
+	}
+
+	return size;
+}
+
+static void
+pulse_output_cancel(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+	pa_operation *o;
+
+	assert(po->mainloop != nullptr);
+	assert(po->stream != nullptr);
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+		/* no need to flush when the stream isn't connected
+		   yet */
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return;
+	}
+
+	assert(po->context != nullptr);
+
+	o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+	if (o == nullptr) {
+		FormatWarning(pulse_output_domain,
+			      "pa_stream_flush() has failed: %s",
+			      pa_strerror(pa_context_errno(po->context)));
+		pa_threaded_mainloop_unlock(po->mainloop);
+		return;
+	}
+
+	pulse_wait_for_operation(po->mainloop, o);
+	pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(struct audio_output *ao)
+{
+	PulseOutput *po = (PulseOutput *)ao;
+
+	assert(po->mainloop != nullptr);
+	assert(po->stream != nullptr);
+
+	pa_threaded_mainloop_lock(po->mainloop);
+
+	/* check if the stream is (already/still) connected */
+
+	Error error;
+	if (!pulse_output_wait_stream(po, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		LogError(error);
+		return false;
+	}
+
+	assert(po->context != nullptr);
+
+	/* cork the stream */
+
+	if (!pa_stream_is_corked(po->stream) &&
+	    !pulse_output_stream_pause(po, true, error)) {
+		pa_threaded_mainloop_unlock(po->mainloop);
+		LogError(error);
+		return false;
+	}
+
+	pa_threaded_mainloop_unlock(po->mainloop);
+
+	return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+	bool success;
+
+	const config_param empty;
+	PulseOutput *po = (PulseOutput *)
+		pulse_output_init(empty, IgnoreError());
+	if (po == nullptr)
+		return false;
+
+	success = pulse_output_wait_connection(po, IgnoreError());
+	pulse_output_finish(&po->base);
+
+	return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+	"pulse",
+	pulse_output_test_default_device,
+	pulse_output_init,
+	pulse_output_finish,
+	pulse_output_enable,
+	pulse_output_disable,
+	pulse_output_open,
+	pulse_output_close,
+	pulse_output_delay,
+	nullptr,
+	pulse_output_play,
+	nullptr,
+	pulse_output_cancel,
+	pulse_output_pause,
+
+	&pulse_mixer_plugin,
+};
diff --git a/src/output/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx
new file mode 100644
index 000000000..9df557282
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.hxx
@@ -0,0 +1,46 @@
+/*
+ * 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_PULSE_OUTPUT_PLUGIN_HXX
+#define MPD_PULSE_OUTPUT_PLUGIN_HXX
+
+struct PulseOutput;
+struct PulseMixer;
+struct pa_cvolume;
+class Error;
+
+extern const struct audio_output_plugin pulse_output_plugin;
+
+void
+pulse_output_lock(PulseOutput *po);
+
+void
+pulse_output_unlock(PulseOutput *po);
+
+void
+pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm);
+
+void
+pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
+
+bool
+pulse_output_set_volume(PulseOutput *po,
+			const pa_cvolume *volume, Error &error);
+
+#endif
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
new file mode 100644
index 000000000..16fe2c692
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RecorderOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/fd_util.h"
+#include "open.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+struct RecorderOutput {
+	struct audio_output base;
+
+	/**
+	 * The configured encoder plugin.
+	 */
+	Encoder *encoder;
+
+	/**
+	 * The destination file name.
+	 */
+	const char *path;
+
+	/**
+	 * The destination file descriptor.
+	 */
+	int fd;
+
+	/**
+	 * The buffer for encoder_read().
+	 */
+	char buffer[32768];
+
+	bool Initialize(const config_param &param, Error &error_r) {
+		return ao_base_init(&base, &recorder_output_plugin, param,
+				    error_r);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+
+	bool WriteToFile(const void *data, size_t length, Error &error);
+
+	/**
+	 * Writes pending data from the encoder to the output file.
+	 */
+	bool EncoderToFile(Error &error);
+};
+
+static constexpr Domain recorder_output_domain("recorder_output");
+
+inline bool
+RecorderOutput::Configure(const config_param &param, Error &error)
+{
+	/* read configuration */
+
+	const char *encoder_name =
+		param.GetBlockValue("encoder", "vorbis");
+	const auto encoder_plugin = encoder_plugin_get(encoder_name);
+	if (encoder_plugin == nullptr) {
+		error.Format(config_domain,
+			     "No such encoder: %s", encoder_name);
+		return false;
+	}
+
+	path = param.GetBlockValue("path");
+	if (path == nullptr) {
+		error.Set(config_domain, "'path' not configured");
+		return false;
+	}
+
+	/* initialize encoder */
+
+	encoder = encoder_init(*encoder_plugin, param, error);
+	if (encoder == nullptr)
+		return false;
+
+	return true;
+}
+
+static audio_output *
+recorder_output_init(const config_param &param, Error &error)
+{
+	RecorderOutput *recorder = new RecorderOutput();
+
+	if (!recorder->Initialize(param, error)) {
+		delete recorder;
+		return nullptr;
+	}
+
+	if (!recorder->Configure(param, error)) {
+		recorder->Deinitialize();
+		delete recorder;
+		return nullptr;
+	}
+
+	return &recorder->base;
+}
+
+static void
+recorder_output_finish(struct audio_output *ao)
+{
+	RecorderOutput *recorder = (RecorderOutput *)ao;
+
+	encoder_finish(recorder->encoder);
+	recorder->Deinitialize();
+	delete recorder;
+}
+
+inline bool
+RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
+{
+	assert(length > 0);
+
+	const uint8_t *data = (const uint8_t *)_data, *end = data + length;
+
+	while (true) {
+		ssize_t nbytes = write(fd, data, end - data);
+		if (nbytes > 0) {
+			data += nbytes;
+			if (data == end)
+				return true;
+		} else if (nbytes == 0) {
+			/* shouldn't happen for files */
+			error.Set(recorder_output_domain,
+				  "write() returned 0");
+			return false;
+		} else if (errno != EINTR) {
+			error.FormatErrno("Failed to write to '%s'", path);
+			return false;
+		}
+	}
+}
+
+inline bool
+RecorderOutput::EncoderToFile(Error &error)
+{
+	assert(fd >= 0);
+
+	while (true) {
+		/* read from the encoder */
+
+		size_t size = encoder_read(encoder, buffer, sizeof(buffer));
+		if (size == 0)
+			return true;
+
+		/* write everything into the file */
+
+		if (!WriteToFile(buffer, size, error))
+			return false;
+	}
+}
+
+static bool
+recorder_output_open(struct audio_output *ao,
+		     AudioFormat &audio_format,
+		     Error &error)
+{
+	RecorderOutput *recorder = (RecorderOutput *)ao;
+
+	/* create the output file */
+
+	recorder->fd = open_cloexec(recorder->path,
+				    O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
+				    0666);
+	if (recorder->fd < 0) {
+		error.FormatErrno("Failed to create '%s'", recorder->path);
+		return false;
+	}
+
+	/* open the encoder */
+
+	if (!encoder_open(recorder->encoder, audio_format, error)) {
+		close(recorder->fd);
+		unlink(recorder->path);
+		return false;
+	}
+
+	if (!recorder->EncoderToFile(error)) {
+		encoder_close(recorder->encoder);
+		close(recorder->fd);
+		unlink(recorder->path);
+		return false;
+	}
+
+	return true;
+}
+
+static void
+recorder_output_close(struct audio_output *ao)
+{
+	RecorderOutput *recorder = (RecorderOutput *)ao;
+
+	/* flush the encoder and write the rest to the file */
+
+	if (encoder_end(recorder->encoder, IgnoreError()))
+		recorder->EncoderToFile(IgnoreError());
+
+	/* now really close everything */
+
+	encoder_close(recorder->encoder);
+
+	close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		     Error &error)
+{
+	RecorderOutput *recorder = (RecorderOutput *)ao;
+
+	return encoder_write(recorder->encoder, chunk, size, error) &&
+		recorder->EncoderToFile(error)
+		? size : 0;
+}
+
+const struct audio_output_plugin recorder_output_plugin = {
+	"recorder",
+	nullptr,
+	recorder_output_init,
+	recorder_output_finish,
+	nullptr,
+	nullptr,
+	recorder_output_open,
+	recorder_output_close,
+	nullptr,
+	nullptr,
+	recorder_output_play,
+	nullptr,
+	nullptr,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx
new file mode 100644
index 000000000..4fac911a1
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_RECORDER_OUTPUT_PLUGIN_HXX
+#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
new file mode 100644
index 000000000..7c1c41b47
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RoarOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "MixerList.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <string>
+
+/* libroar/services.h declares roar_service_stream::new - work around
+   this C++ problem */
+#define new _new
+#include <roaraudio.h>
+#undef new
+
+class RoarOutput {
+	struct audio_output base;
+
+	std::string host, name;
+
+	roar_vs_t * vss;
+	int err;
+	int role;
+	struct roar_connection con;
+	struct roar_audio_info info;
+	mutable Mutex mutex;
+	volatile bool alive;
+
+public:
+	RoarOutput()
+		:err(ROAR_ERROR_NONE) {}
+
+	operator audio_output *() {
+		return &base;
+	}
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &roar_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	void Configure(const config_param &param);
+
+	bool Open(AudioFormat &audio_format, Error &error);
+	void Close();
+
+	void SendTag(const Tag &tag);
+	size_t Play(const void *chunk, size_t size, Error &error);
+	void Cancel();
+
+	int GetVolume() const;
+	bool SetVolume(unsigned volume);
+};
+
+static constexpr Domain roar_output_domain("roar_output");
+
+inline int
+RoarOutput::GetVolume() const
+{
+	const ScopeLock protect(mutex);
+
+	if (vss == nullptr || !alive)
+		return -1;
+
+	float l, r;
+	int error;
+	if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
+		return -1;
+
+	return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(RoarOutput *roar)
+{
+	return roar->GetVolume();
+}
+
+bool
+RoarOutput::SetVolume(unsigned volume)
+{
+	assert(volume <= 100);
+
+	const ScopeLock protect(mutex);
+	if (vss == nullptr || !alive)
+		return false;
+
+	int error;
+	float level = volume / 100.0;
+
+	roar_vs_volume_mono(vss, level, &error);
+	return true;
+}
+
+bool
+roar_output_set_volume(RoarOutput *roar, unsigned volume)
+{
+	return roar->SetVolume(volume);
+}
+
+inline void
+RoarOutput::Configure(const config_param &param)
+{
+	host = param.GetBlockValue("server", "");
+	name = param.GetBlockValue("name", "MPD");
+
+	const char *_role = param.GetBlockValue("role", "music");
+	role = _role != nullptr
+		? roar_str2role(_role)
+		: ROAR_ROLE_MUSIC;
+}
+
+static struct audio_output *
+roar_init(const config_param &param, Error &error)
+{
+	RoarOutput *self = new RoarOutput();
+
+	if (!self->Initialize(param, error)) {
+		delete self;
+		return nullptr;
+	}
+
+	self->Configure(param);
+	return *self;
+}
+
+static void
+roar_finish(struct audio_output *ao)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+
+	self->Deinitialize();
+	delete self;
+}
+
+static void
+roar_use_audio_format(struct roar_audio_info *info,
+		      AudioFormat &audio_format)
+{
+	info->rate = audio_format.sample_rate;
+	info->channels = audio_format.channels;
+	info->codec = ROAR_CODEC_PCM_S;
+
+	switch (audio_format.format) {
+	case SampleFormat::UNDEFINED:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+		info->bits = 16;
+		audio_format.format = SampleFormat::S16;
+		break;
+
+	case SampleFormat::S8:
+		info->bits = 8;
+		break;
+
+	case SampleFormat::S16:
+		info->bits = 16;
+		break;
+
+	case SampleFormat::S24_P32:
+		info->bits = 32;
+		audio_format.format = SampleFormat::S32;
+		break;
+
+	case SampleFormat::S32:
+		info->bits = 32;
+		break;
+	}
+}
+
+inline bool
+RoarOutput::Open(AudioFormat &audio_format, Error &error)
+{
+	const ScopeLock protect(mutex);
+
+	if (roar_simple_connect(&con,
+				host.empty() ? nullptr : host.c_str(),
+				name.c_str()) < 0) {
+		error.Set(roar_output_domain,
+			  "Failed to connect to Roar server");
+		return false;
+	}
+
+	vss = roar_vs_new_from_con(&con, &err);
+
+	if (vss == nullptr || err != ROAR_ERROR_NONE) {
+		error.Set(roar_output_domain, "Failed to connect to server");
+		return false;
+	}
+
+	roar_use_audio_format(&info, audio_format);
+
+	if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+		error.Set(roar_output_domain, "Failed to start stream");
+		return false;
+	}
+
+	roar_vs_role(vss, role, &err);
+	alive = true;
+	return true;
+}
+
+static bool
+roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+
+	return self->Open(audio_format, error);
+}
+
+inline void
+RoarOutput::Close()
+{
+	const ScopeLock protect(mutex);
+
+	alive = false;
+
+	if (vss != nullptr)
+		roar_vs_close(vss, ROAR_VS_TRUE, &err);
+	vss = nullptr;
+	roar_disconnect(&con);
+}
+
+static void
+roar_close(struct audio_output *ao)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+	self->Close();
+}
+
+inline void
+RoarOutput::Cancel()
+{
+	const ScopeLock protect(mutex);
+
+	if (vss == nullptr)
+		return;
+
+	roar_vs_t *_vss = vss;
+	vss = nullptr;
+	roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+	alive = false;
+
+	_vss = roar_vs_new_from_con(&con, &err);
+	if (_vss == nullptr)
+		return;
+
+	if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+		roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+		LogError(roar_output_domain, "Failed to start stream");
+		return;
+	}
+
+	roar_vs_role(_vss, role, &err);
+	vss = _vss;
+	alive = true;
+}
+
+static void
+roar_cancel(struct audio_output *ao)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+
+	self->Cancel();
+}
+
+inline size_t
+RoarOutput::Play(const void *chunk, size_t size, Error &error)
+{
+	if (vss == nullptr) {
+		error.Set(roar_output_domain, "Connection is invalid");
+		return 0;
+	}
+
+	ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
+	if (nbytes <= 0) {
+		error.Set(roar_output_domain, "Failed to play data");
+		return 0;
+	}
+
+	return nbytes;
+}
+
+static size_t
+roar_play(struct audio_output *ao, const void *chunk, size_t size,
+	  Error &error)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+	return self->Play(chunk, size, error);
+}
+
+static const char*
+roar_tag_convert(TagType type, bool *is_uuid)
+{
+	*is_uuid = false;
+	switch (type)
+	{
+		case TAG_ARTIST:
+		case TAG_ALBUM_ARTIST:
+			return "AUTHOR";
+		case TAG_ALBUM:
+			return "ALBUM";
+		case TAG_TITLE:
+			return "TITLE";
+		case TAG_TRACK:
+			return "TRACK";
+		case TAG_NAME:
+			return "NAME";
+		case TAG_GENRE:
+			return "GENRE";
+		case TAG_DATE:
+			return "DATE";
+		case TAG_PERFORMER:
+			return "PERFORMER";
+		case TAG_COMMENT:
+			return "COMMENT";
+		case TAG_DISC:
+			return "DISCID";
+		case TAG_COMPOSER:
+#ifdef ROAR_META_TYPE_COMPOSER
+			return "COMPOSER";
+#else
+			return "AUTHOR";
+#endif
+		case TAG_MUSICBRAINZ_ARTISTID:
+		case TAG_MUSICBRAINZ_ALBUMID:
+		case TAG_MUSICBRAINZ_ALBUMARTISTID:
+		case TAG_MUSICBRAINZ_TRACKID:
+			*is_uuid = true;
+			return "HASH";
+
+		default:
+			return nullptr;
+	}
+}
+
+inline void
+RoarOutput::SendTag(const Tag &tag)
+{
+	if (vss == nullptr)
+		return;
+
+	const ScopeLock protect(mutex);
+
+	size_t cnt = 1;
+	struct roar_keyval vals[32];
+	char uuid_buf[32][64];
+
+	char timebuf[16];
+	snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+		 tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60);
+
+	vals[0].key = const_cast<char *>("LENGTH");
+	vals[0].value = timebuf;
+
+	for (unsigned i = 0; i < tag.num_items && cnt < 32; i++)
+	{
+		bool is_uuid = false;
+		const char *key = roar_tag_convert(tag.items[i]->type,
+						   &is_uuid);
+		if (key != nullptr) {
+			vals[cnt].key = const_cast<char *>(key);
+
+			if (is_uuid) {
+				snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+					 tag.items[i]->value);
+				vals[cnt].value = uuid_buf[cnt];
+			} else {
+				vals[cnt].value = tag.items[i]->value;
+			}
+
+			cnt++;
+		}
+	}
+
+	roar_vs_meta(vss, vals, cnt, &(err));
+}
+
+static void
+roar_send_tag(struct audio_output *ao, const Tag *meta)
+{
+	RoarOutput *self = (RoarOutput *)ao;
+	self->SendTag(*meta);
+}
+
+const struct audio_output_plugin roar_output_plugin = {
+	"roar",
+	nullptr,
+	roar_init,
+	roar_finish,
+	nullptr,
+	nullptr,
+	roar_open,
+	roar_close,
+	nullptr,
+	roar_send_tag,
+	roar_play,
+	nullptr,
+	roar_cancel,
+	nullptr,
+	&roar_mixer_plugin,
+};
diff --git a/src/output/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx
new file mode 100644
index 000000000..27c5dc420
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * 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_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+class RoarOutput;
+
+extern const struct audio_output_plugin roar_output_plugin;
+
+int
+roar_output_get_volume(RoarOutput *roar);
+
+bool
+roar_output_set_volume(RoarOutput *roar, unsigned volume);
+
+#endif
diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
new file mode 100644
index 000000000..e0ec6ce3d
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ShoutOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+#include <shout/shout.h>
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
+
+struct ShoutOutput final {
+	struct audio_output base;
+
+	shout_t *shout_conn;
+	shout_metadata_t *shout_meta;
+
+	Encoder *encoder;
+
+	float quality;
+	int bitrate;
+
+	int timeout;
+
+	uint8_t buffer[32768];
+
+	ShoutOutput()
+		:shout_conn(shout_new()),
+		shout_meta(shout_metadata_new()),
+		quality(-2.0),
+		bitrate(-1),
+		timeout(DEFAULT_CONN_TIMEOUT) {}
+
+	~ShoutOutput() {
+		if (shout_meta != nullptr)
+			shout_metadata_free(shout_meta);
+		if (shout_conn != nullptr)
+			shout_free(shout_conn);
+	}
+
+	bool Initialize(const config_param &param, Error &error) {
+		return ao_base_init(&base, &shout_output_plugin, param,
+				    error);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+};
+
+static int shout_init_count;
+
+static constexpr Domain shout_output_domain("shout_output");
+
+static const EncoderPlugin *
+shout_encoder_plugin_get(const char *name)
+{
+	if (strcmp(name, "ogg") == 0)
+		name = "vorbis";
+	else if (strcmp(name, "mp3") == 0)
+		name = "lame";
+
+	return encoder_plugin_get(name);
+}
+
+gcc_pure
+static const char *
+require_block_string(const config_param &param, const char *name)
+{
+	const char *value = param.GetBlockValue(name);
+	if (value == nullptr)
+		FormatFatalError("no \"%s\" defined for shout device defined "
+				 "at line %u\n", name, param.line);
+
+	return value;
+}
+
+inline bool
+ShoutOutput::Configure(const config_param &param, Error &error)
+{
+
+	const AudioFormat audio_format = base.config_audio_format;
+	if (!audio_format.IsFullyDefined()) {
+		error.Set(config_domain,
+			  "Need full audio format specification");
+		return nullptr;
+	}
+
+	const char *host = require_block_string(param, "host");
+	const char *mount = require_block_string(param, "mount");
+	unsigned port = param.GetBlockValue("port", 0u);
+	if (port == 0) {
+		error.Set(config_domain, "shout port must be configured");
+		return false;
+	}
+
+	const char *passwd = require_block_string(param, "password");
+	const char *name = require_block_string(param, "name");
+
+	bool is_public = param.GetBlockValue("public", false);
+
+	const char *user = param.GetBlockValue("user", "source");
+
+	const char *value = param.GetBlockValue("quality");
+	if (value != nullptr) {
+		char *test;
+		quality = strtod(value, &test);
+
+		if (*test != '\0' || quality < -1.0 || quality > 10.0) {
+			error.Format(config_domain,
+				     "shout quality \"%s\" is not a number in the "
+				     "range -1 to 10",
+				     value);
+			return false;
+		}
+
+		if (param.GetBlockValue("bitrate") != nullptr) {
+			error.Set(config_domain,
+				  "quality and bitrate are "
+				  "both defined");
+			return false;
+		}
+	} else {
+		value = param.GetBlockValue("bitrate");
+		if (value == nullptr) {
+			error.Set(config_domain,
+				  "neither bitrate nor quality defined");
+			return false;
+		}
+
+		char *test;
+		bitrate = strtol(value, &test, 10);
+
+		if (*test != '\0' || bitrate <= 0) {
+			error.Set(config_domain,
+				  "bitrate must be a positive integer");
+			return false;
+		}
+	}
+
+	const char *encoding = param.GetBlockValue("encoding", "ogg");
+	const auto encoder_plugin = shout_encoder_plugin_get(encoding);
+	if (encoder_plugin == nullptr) {
+		error.Format(config_domain,
+			     "couldn't find shout encoder plugin \"%s\"",
+			     encoding);
+		return false;
+	}
+
+	encoder = encoder_init(*encoder_plugin, param, error);
+	if (encoder == nullptr)
+		return false;
+
+	unsigned shout_format;
+	if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
+		shout_format = SHOUT_FORMAT_MP3;
+	else
+		shout_format = SHOUT_FORMAT_OGG;
+
+	unsigned protocol;
+	value = param.GetBlockValue("protocol");
+	if (value != nullptr) {
+		if (0 == strcmp(value, "shoutcast") &&
+		    0 != strcmp(encoding, "mp3")) {
+			error.Format(config_domain,
+				     "you cannot stream \"%s\" to shoutcast, use mp3",
+				     encoding);
+			return false;
+		} else if (0 == strcmp(value, "shoutcast"))
+			protocol = SHOUT_PROTOCOL_ICY;
+		else if (0 == strcmp(value, "icecast1"))
+			protocol = SHOUT_PROTOCOL_XAUDIOCAST;
+		else if (0 == strcmp(value, "icecast2"))
+			protocol = SHOUT_PROTOCOL_HTTP;
+		else {
+			error.Format(config_domain,
+				     "shout protocol \"%s\" is not \"shoutcast\" or "
+				     "\"icecast1\"or \"icecast2\"",
+				     value);
+			return false;
+		}
+	} else {
+		protocol = SHOUT_PROTOCOL_HTTP;
+	}
+
+	if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
+	    shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
+	    shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
+	    shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS ||
+	    shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS ||
+	    shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS ||
+	    shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS ||
+	    shout_set_format(shout_conn, shout_format)
+	    != SHOUTERR_SUCCESS ||
+	    shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
+	    shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) {
+		error.Set(shout_output_domain, shout_get_error(shout_conn));
+		return false;
+	}
+
+	/* optional paramters */
+	timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
+
+	value = param.GetBlockValue("genre");
+	if (value != nullptr && shout_set_genre(shout_conn, value)) {
+		error.Set(shout_output_domain, shout_get_error(shout_conn));
+		return false;
+	}
+
+	value = param.GetBlockValue("description");
+	if (value != nullptr && shout_set_description(shout_conn, value)) {
+		error.Set(shout_output_domain, shout_get_error(shout_conn));
+		return false;
+	}
+
+	value = param.GetBlockValue("url");
+	if (value != nullptr && shout_set_url(shout_conn, value)) {
+		error.Set(shout_output_domain, shout_get_error(shout_conn));
+		return false;
+	}
+
+	{
+		char temp[11];
+		memset(temp, 0, sizeof(temp));
+
+		snprintf(temp, sizeof(temp), "%u", audio_format.channels);
+		shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
+
+		snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
+
+		shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
+
+		if (quality >= -1.0) {
+			snprintf(temp, sizeof(temp), "%2.2f", quality);
+			shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY,
+					     temp);
+		} else {
+			snprintf(temp, sizeof(temp), "%d", bitrate);
+			shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE,
+					     temp);
+		}
+	}
+
+	return true;
+}
+
+static struct audio_output *
+my_shout_init_driver(const config_param &param, Error &error)
+{
+	ShoutOutput *sd = new ShoutOutput();
+	if (!sd->Initialize(param, error)) {
+		delete sd;
+		return nullptr;
+	}
+
+	if (!sd->Configure(param, error)) {
+		sd->Deinitialize();
+		delete sd;
+		return nullptr;
+	}
+
+	if (shout_init_count == 0)
+		shout_init();
+
+	shout_init_count++;
+
+	return &sd->base;
+}
+
+static bool
+handle_shout_error(ShoutOutput *sd, int err, Error &error)
+{
+	switch (err) {
+	case SHOUTERR_SUCCESS:
+		break;
+
+	case SHOUTERR_UNCONNECTED:
+	case SHOUTERR_SOCKET:
+		error.Format(shout_output_domain, err,
+			     "Lost shout connection to %s:%i: %s",
+			     shout_get_host(sd->shout_conn),
+			     shout_get_port(sd->shout_conn),
+			     shout_get_error(sd->shout_conn));
+		return false;
+
+	default:
+		error.Format(shout_output_domain, err,
+			     "connection to %s:%i error: %s",
+			     shout_get_host(sd->shout_conn),
+			     shout_get_port(sd->shout_conn),
+			     shout_get_error(sd->shout_conn));
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+write_page(ShoutOutput *sd, Error &error)
+{
+	assert(sd->encoder != nullptr);
+
+	while (true) {
+		size_t nbytes = encoder_read(sd->encoder,
+					     sd->buffer, sizeof(sd->buffer));
+		if (nbytes == 0)
+			return true;
+
+		int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
+		if (!handle_shout_error(sd, err, error))
+			return false;
+	}
+
+	return true;
+}
+
+static void close_shout_conn(ShoutOutput * sd)
+{
+	if (sd->encoder != nullptr) {
+		if (encoder_end(sd->encoder, IgnoreError()))
+			write_page(sd, IgnoreError());
+
+		encoder_close(sd->encoder);
+	}
+
+	if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
+	    shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
+		FormatWarning(shout_output_domain,
+			      "problem closing connection to shout server: %s",
+			      shout_get_error(sd->shout_conn));
+	}
+}
+
+static void
+my_shout_finish_driver(struct audio_output *ao)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	encoder_finish(sd->encoder);
+
+	sd->Deinitialize();
+	delete sd;
+
+	shout_init_count--;
+
+	if (shout_init_count == 0)
+		shout_shutdown();
+}
+
+static void
+my_shout_drop_buffered_audio(struct audio_output *ao)
+{
+	gcc_unused
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	/* needs to be implemented for shout */
+}
+
+static void
+my_shout_close_device(struct audio_output *ao)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	close_shout_conn(sd);
+}
+
+static bool
+shout_connect(ShoutOutput *sd, Error &error)
+{
+	switch (shout_open(sd->shout_conn)) {
+	case SHOUTERR_SUCCESS:
+	case SHOUTERR_CONNECTED:
+		return true;
+
+	default:
+		error.Format(shout_output_domain,
+			     "problem opening connection to shout server %s:%i: %s",
+			     shout_get_host(sd->shout_conn),
+			     shout_get_port(sd->shout_conn),
+			     shout_get_error(sd->shout_conn));
+		return false;
+	}
+}
+
+static bool
+my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
+		     Error &error)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	if (!shout_connect(sd, error))
+		return false;
+
+	if (!encoder_open(sd->encoder, audio_format, error)) {
+		shout_close(sd->shout_conn);
+		return false;
+	}
+
+	if (!write_page(sd, error)) {
+		encoder_close(sd->encoder);
+		shout_close(sd->shout_conn);
+		return false;
+	}
+
+	return true;
+}
+
+static unsigned
+my_shout_delay(struct audio_output *ao)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	int delay = shout_delay(sd->shout_conn);
+	if (delay < 0)
+		delay = 0;
+
+	return delay;
+}
+
+static size_t
+my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
+	      Error &error)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	return encoder_write(sd->encoder, chunk, size, error) &&
+		write_page(sd, error)
+		? size
+		: 0;
+}
+
+static bool
+my_shout_pause(struct audio_output *ao)
+{
+	static char silence[1020];
+
+	return my_shout_play(ao, silence, sizeof(silence), IgnoreError());
+}
+
+static void
+shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
+{
+	char artist[size];
+	char title[size];
+
+	artist[0] = 0;
+	title[0] = 0;
+
+	for (unsigned i = 0; i < tag->num_items; i++) {
+		switch (tag->items[i]->type) {
+		case TAG_ARTIST:
+			strncpy(artist, tag->items[i]->value, size);
+			break;
+		case TAG_TITLE:
+			strncpy(title, tag->items[i]->value, size);
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	snprintf(dest, size, "%s - %s", artist, title);
+}
+
+static void my_shout_set_tag(struct audio_output *ao,
+			     const Tag *tag)
+{
+	ShoutOutput *sd = (ShoutOutput *)ao;
+
+	if (sd->encoder->plugin.tag != nullptr) {
+		/* encoder plugin supports stream tags */
+
+		Error error;
+		if (!encoder_pre_tag(sd->encoder, error) ||
+		    !write_page(sd, error) ||
+		    !encoder_tag(sd->encoder, tag, error)) {
+			LogError(error);
+			return;
+		}
+	} else {
+		/* no stream tag support: fall back to icy-metadata */
+		char song[1024];
+		shout_tag_to_metadata(tag, song, sizeof(song));
+
+		shout_metadata_add(sd->shout_meta, "song", song);
+		if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
+							   sd->shout_meta)) {
+			LogWarning(shout_output_domain,
+				   "error setting shout metadata");
+		}
+	}
+
+	write_page(sd, IgnoreError());
+}
+
+const struct audio_output_plugin shout_output_plugin = {
+	"shout",
+	nullptr,
+	my_shout_init_driver,
+	my_shout_finish_driver,
+	nullptr,
+	nullptr,
+	my_shout_open_device,
+	my_shout_close_device,
+	my_shout_delay,
+	my_shout_set_tag,
+	my_shout_play,
+	nullptr,
+	my_shout_drop_buffered_audio,
+	my_shout_pause,
+	nullptr,
+};
diff --git a/src/output/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx
new file mode 100644
index 000000000..d437e0b0d
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_SHOUT_OUTPUT_PLUGIN_HXX
+#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin shout_output_plugin;
+
+#endif
diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
new file mode 100644
index 000000000..38ed2e314
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SolarisOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#include <sys/stropts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef __sun
+#include <sys/audio.h>
+#else
+
+/* some fake declarations that allow build this plugin on systems
+   other than Solaris, just to see if it compiles */
+
+#define AUDIO_GETINFO 0
+#define AUDIO_SETINFO 0
+#define AUDIO_ENCODING_LINEAR 0
+
+struct audio_info {
+	struct {
+		unsigned sample_rate, channels, precision, encoding;
+	} play;
+};
+
+#endif
+
+struct SolarisOutput {
+	struct audio_output base;
+
+	/* configuration */
+	const char *device;
+
+	int fd;
+
+	bool Initialize(const config_param &param, Error &error_r) {
+		return ao_base_init(&base, &solaris_output_plugin, param,
+				    error_r);
+	}
+
+	void Deinitialize() {
+		ao_base_finish(&base);
+	}
+};
+
+static bool
+solaris_output_test_default_device(void)
+{
+	struct stat st;
+
+	return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
+		access("/dev/audio", W_OK) == 0;
+}
+
+static struct audio_output *
+solaris_output_init(const config_param &param, Error &error_r)
+{
+	SolarisOutput *so = new SolarisOutput();
+	if (!so->Initialize(param, error_r)) {
+		delete so;
+		return nullptr;
+	}
+
+	so->device = param.GetBlockValue("device", "/dev/audio");
+
+	return &so->base;
+}
+
+static void
+solaris_output_finish(struct audio_output *ao)
+{
+	SolarisOutput *so = (SolarisOutput *)ao;
+
+	so->Deinitialize();
+	delete so;
+}
+
+static bool
+solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		    Error &error)
+{
+	SolarisOutput *so = (SolarisOutput *)ao;
+	struct audio_info info;
+	int ret, flags;
+
+	/* support only 16 bit mono/stereo for now; nothing else has
+	   been tested */
+	audio_format.format = SampleFormat::S16;
+
+	/* open the device in non-blocking mode */
+
+	so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
+	if (so->fd < 0) {
+		error.FormatErrno("Failed to open %s",
+				  so->device);
+		return false;
+	}
+
+	/* restore blocking mode */
+
+	flags = fcntl(so->fd, F_GETFL);
+	if (flags > 0 && (flags & O_NONBLOCK) != 0)
+		fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
+
+	/* configure the audio device */
+
+	ret = ioctl(so->fd, AUDIO_GETINFO, &info);
+	if (ret < 0) {
+		error.SetErrno("AUDIO_GETINFO failed");
+		close(so->fd);
+		return false;
+	}
+
+	info.play.sample_rate = audio_format.sample_rate;
+	info.play.channels = audio_format.channels;
+	info.play.precision = 16;
+	info.play.encoding = AUDIO_ENCODING_LINEAR;
+
+	ret = ioctl(so->fd, AUDIO_SETINFO, &info);
+	if (ret < 0) {
+		error.SetErrno("AUDIO_SETINFO failed");
+		close(so->fd);
+		return false;
+	}
+
+	return true;
+}
+
+static void
+solaris_output_close(struct audio_output *ao)
+{
+	SolarisOutput *so = (SolarisOutput *)ao;
+
+	close(so->fd);
+}
+
+static size_t
+solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
+		    Error &error)
+{
+	SolarisOutput *so = (SolarisOutput *)ao;
+	ssize_t nbytes;
+
+	nbytes = write(so->fd, chunk, size);
+	if (nbytes <= 0) {
+		error.SetErrno("Write failed");
+		return 0;
+	}
+
+	return nbytes;
+}
+
+static void
+solaris_output_cancel(struct audio_output *ao)
+{
+	SolarisOutput *so = (SolarisOutput *)ao;
+
+	ioctl(so->fd, I_FLUSH);
+}
+
+const struct audio_output_plugin solaris_output_plugin = {
+	"solaris",
+	solaris_output_test_default_device,
+	solaris_output_init,
+	solaris_output_finish,
+	nullptr,
+	nullptr,
+	solaris_output_open,
+	solaris_output_close,
+	nullptr,
+	nullptr,
+	solaris_output_play,
+	nullptr,
+	solaris_output_cancel,
+	nullptr,
+	nullptr,
+};
diff --git a/src/output/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx
new file mode 100644
index 000000000..9ce848a40
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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_SOLARIS_OUTPUT_PLUGIN_HXX
+#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+
+extern const struct audio_output_plugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
new file mode 100644
index 000000000..87861180f
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WinmmOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "MixerList.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+
+#include <glib.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+struct WinmmBuffer {
+	PcmBuffer buffer;
+
+	WAVEHDR hdr;
+};
+
+struct WinmmOutput {
+	struct audio_output base;
+
+	UINT device_id;
+	HWAVEOUT handle;
+
+	/**
+	 * This event is triggered by Windows when a buffer is
+	 * finished.
+	 */
+	HANDLE event;
+
+	WinmmBuffer buffers[8];
+	unsigned next_buffer;
+};
+
+static constexpr Domain winmm_output_domain("winmm_output");
+
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput *output)
+{
+	return output->handle;
+}
+
+static bool
+winmm_output_test_default_device(void)
+{
+	return waveOutGetNumDevs() > 0;
+}
+
+static bool
+get_device_id(const char *device_name, UINT *device_id, Error &error)
+{
+	/* if device is not specified use wave mapper */
+	if (device_name == nullptr) {
+		*device_id = WAVE_MAPPER;
+		return true;
+	}
+
+	UINT numdevs = waveOutGetNumDevs();
+
+	/* check for device id */
+	char *endptr;
+	UINT id = strtoul(device_name, &endptr, 0);
+	if (endptr > device_name && *endptr == 0) {
+		if (id >= numdevs)
+			goto fail;
+		*device_id = id;
+		return true;
+	}
+
+	/* check for device name */
+	for (UINT i = 0; i < numdevs; i++) {
+		WAVEOUTCAPS caps;
+		MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
+		if (result != MMSYSERR_NOERROR)
+			continue;
+		/* szPname is only 32 chars long, so it is often truncated.
+		   Use partial match to work around this. */
+		if (strstr(device_name, caps.szPname) == device_name) {
+			*device_id = i;
+			return true;
+		}
+	}
+
+fail:
+	error.Format(winmm_output_domain,
+		     "device \"%s\" is not found", device_name);
+	return false;
+}
+
+static struct audio_output *
+winmm_output_init(const config_param &param, Error &error)
+{
+	WinmmOutput *wo = new WinmmOutput();
+	if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
+		delete wo;
+		return nullptr;
+	}
+
+	const char *device = param.GetBlockValue("device");
+	if (!get_device_id(device, &wo->device_id, error)) {
+		ao_base_finish(&wo->base);
+		delete wo;
+		return nullptr;
+	}
+
+	return &wo->base;
+}
+
+static void
+winmm_output_finish(struct audio_output *ao)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	ao_base_finish(&wo->base);
+	delete wo;
+}
+
+static bool
+winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
+		  Error &error)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	wo->event = CreateEvent(nullptr, false, false, nullptr);
+	if (wo->event == nullptr) {
+		error.Set(winmm_output_domain, "CreateEvent() failed");
+		return false;
+	}
+
+	switch (audio_format.format) {
+	case SampleFormat::S8:
+	case SampleFormat::S16:
+		break;
+
+	case SampleFormat::S24_P32:
+	case SampleFormat::S32:
+	case SampleFormat::FLOAT:
+	case SampleFormat::DSD:
+	case SampleFormat::UNDEFINED:
+		/* we havn't tested formats other than S16 */
+		audio_format.format = SampleFormat::S16;
+		break;
+	}
+
+	if (audio_format.channels > 2)
+		/* same here: more than stereo was not tested */
+		audio_format.channels = 2;
+
+	WAVEFORMATEX format;
+	format.wFormatTag = WAVE_FORMAT_PCM;
+	format.nChannels = audio_format.channels;
+	format.nSamplesPerSec = audio_format.sample_rate;
+	format.nBlockAlign = audio_format.GetFrameSize();
+	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+	format.wBitsPerSample = audio_format.GetSampleSize() * 8;
+	format.cbSize = 0;
+
+	MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
+				      (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
+	if (result != MMSYSERR_NOERROR) {
+		CloseHandle(wo->event);
+		error.Set(winmm_output_domain, "waveOutOpen() failed");
+		return false;
+	}
+
+	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
+		memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
+	}
+
+	wo->next_buffer = 0;
+
+	return true;
+}
+
+static void
+winmm_output_close(struct audio_output *ao)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i)
+		wo->buffers[i].buffer.Clear();
+
+	waveOutClose(wo->handle);
+
+	CloseHandle(wo->event);
+}
+
+/**
+ * Copy data into a buffer, and prepare the wave header.
+ */
+static bool
+winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+		 const void *data, size_t size,
+		 Error &error)
+{
+	void *dest = buffer->buffer.Get(size);
+	assert(dest != nullptr);
+
+	memcpy(dest, data, size);
+
+	memset(&buffer->hdr, 0, sizeof(buffer->hdr));
+	buffer->hdr.lpData = (LPSTR)dest;
+	buffer->hdr.dwBufferLength = size;
+
+	MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
+					       sizeof(buffer->hdr));
+	if (result != MMSYSERR_NOERROR) {
+		error.Set(winmm_output_domain, result,
+			  "waveOutPrepareHeader() failed");
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+		   Error &error)
+{
+	if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
+		/* already finished */
+		return true;
+
+	while (true) {
+		MMRESULT result = waveOutUnprepareHeader(wo->handle,
+							 &buffer->hdr,
+							 sizeof(buffer->hdr));
+		if (result == MMSYSERR_NOERROR)
+			return true;
+		else if (result != WAVERR_STILLPLAYING) {
+			error.Set(winmm_output_domain, result,
+				  "waveOutUnprepareHeader() failed");
+			return false;
+		}
+
+		/* wait some more */
+		WaitForSingleObject(wo->event, INFINITE);
+	}
+}
+
+static size_t
+winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	/* get the next buffer from the ring and prepare it */
+	WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
+	if (!winmm_drain_buffer(wo, buffer, error) ||
+	    !winmm_set_buffer(wo, buffer, chunk, size, error))
+		return 0;
+
+	/* enqueue the buffer */
+	MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
+				       sizeof(buffer->hdr));
+	if (result != MMSYSERR_NOERROR) {
+		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+				       sizeof(buffer->hdr));
+		error.Set(winmm_output_domain, result,
+			  "waveOutWrite() failed");
+		return 0;
+	}
+
+	/* mark our buffer as "used" */
+	wo->next_buffer = (wo->next_buffer + 1) %
+		ARRAY_SIZE(wo->buffers);
+
+	return size;
+}
+
+static bool
+winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
+{
+	for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
+		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+			return false;
+
+	for (unsigned i = 0; i < wo->next_buffer; ++i)
+		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+			return false;
+
+	return true;
+}
+
+static void
+winmm_stop(WinmmOutput *wo)
+{
+	waveOutReset(wo->handle);
+
+	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
+		WinmmBuffer *buffer = &wo->buffers[i];
+		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+				       sizeof(buffer->hdr));
+	}
+}
+
+static void
+winmm_output_drain(struct audio_output *ao)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	if (!winmm_drain_all_buffers(wo, IgnoreError()))
+		winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(struct audio_output *ao)
+{
+	WinmmOutput *wo = (WinmmOutput *)ao;
+
+	winmm_stop(wo);
+}
+
+const struct audio_output_plugin winmm_output_plugin = {
+	"winmm",
+	winmm_output_test_default_device,
+	winmm_output_init,
+	winmm_output_finish,
+	nullptr,
+	nullptr,
+	winmm_output_open,
+	winmm_output_close,
+	nullptr,
+	nullptr,
+	winmm_output_play,
+	winmm_output_drain,
+	winmm_output_cancel,
+	nullptr,
+	&winmm_mixer_plugin,
+};
diff --git a/src/output/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx
new file mode 100644
index 000000000..1409a2e8c
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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_WINMM_OUTPUT_PLUGIN_HXX
+#define MPD_WINMM_OUTPUT_PLUGIN_HXX
+
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
+#include "Compiler.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+
+struct WinmmOutput;
+
+extern const struct audio_output_plugin winmm_output_plugin;
+
+gcc_pure
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput *);
+
+#endif
+
+#endif
-- 
cgit v1.2.3