aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2014-01-23 23:49:50 +0100
committerMax Kellermann <max@duempel.org>2014-01-23 23:49:50 +0100
commitea5b901bcce20949a8d1fd622a7b03ff6f56ae20 (patch)
treeab37a61260fdaafb94b5e54aca54928e00c9cf01 /src/output
parentf1f19841bdd291c055f59b6603f69278c66366d8 (diff)
downloadmpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.gz
mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.xz
mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.zip
output/*: move to output/plugins/
Diffstat (limited to 'src/output')
-rw-r--r--src/output/OutputAPI.hxx33
-rw-r--r--src/output/OutputAll.cxx589
-rw-r--r--src/output/OutputAll.hxx174
-rw-r--r--src/output/OutputCommand.cxx112
-rw-r--r--src/output/OutputCommand.hxx51
-rw-r--r--src/output/OutputControl.cxx325
-rw-r--r--src/output/OutputControl.hxx94
-rw-r--r--src/output/OutputError.cxx23
-rw-r--r--src/output/OutputError.hxx25
-rw-r--r--src/output/OutputFinish.cxx51
-rw-r--r--src/output/OutputInit.cxx329
-rw-r--r--src/output/OutputInternal.hxx301
-rw-r--r--src/output/OutputList.cxx100
-rw-r--r--src/output/OutputList.hxx33
-rw-r--r--src/output/OutputPlugin.cxx109
-rw-r--r--src/output/OutputPlugin.hxx202
-rw-r--r--src/output/OutputPrint.cxx45
-rw-r--r--src/output/OutputPrint.hxx34
-rw-r--r--src/output/OutputState.cxx92
-rw-r--r--src/output/OutputState.hxx44
-rw-r--r--src/output/OutputThread.cxx690
-rw-r--r--src/output/OutputThread.hxx28
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx (renamed from src/output/AlsaOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/AlsaOutputPlugin.hxx (renamed from src/output/AlsaOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx (renamed from src/output/AoOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/AoOutputPlugin.hxx (renamed from src/output/AoOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/FifoOutputPlugin.cxx (renamed from src/output/FifoOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/FifoOutputPlugin.hxx (renamed from src/output/FifoOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/HttpdClient.cxx (renamed from src/output/HttpdClient.cxx)0
-rw-r--r--src/output/plugins/HttpdClient.hxx (renamed from src/output/HttpdClient.hxx)0
-rw-r--r--src/output/plugins/HttpdInternal.hxx (renamed from src/output/HttpdInternal.hxx)2
-rw-r--r--src/output/plugins/HttpdOutputPlugin.cxx (renamed from src/output/HttpdOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/HttpdOutputPlugin.hxx (renamed from src/output/HttpdOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx (renamed from src/output/JackOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/JackOutputPlugin.hxx (renamed from src/output/JackOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/NullOutputPlugin.cxx (renamed from src/output/NullOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/NullOutputPlugin.hxx (renamed from src/output/NullOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/OSXOutputPlugin.cxx (renamed from src/output/OSXOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/OSXOutputPlugin.hxx (renamed from src/output/OSXOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/OpenALOutputPlugin.cxx (renamed from src/output/OpenALOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/OpenALOutputPlugin.hxx (renamed from src/output/OpenALOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx (renamed from src/output/OssOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/OssOutputPlugin.hxx (renamed from src/output/OssOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/PipeOutputPlugin.cxx (renamed from src/output/PipeOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/PipeOutputPlugin.hxx (renamed from src/output/PipeOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx (renamed from src/output/PulseOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/PulseOutputPlugin.hxx (renamed from src/output/PulseOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx (renamed from src/output/RecorderOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/RecorderOutputPlugin.hxx (renamed from src/output/RecorderOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/RoarOutputPlugin.cxx (renamed from src/output/RoarOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/RoarOutputPlugin.hxx (renamed from src/output/RoarOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/ShoutOutputPlugin.cxx (renamed from src/output/ShoutOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/ShoutOutputPlugin.hxx (renamed from src/output/ShoutOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/SolarisOutputPlugin.cxx (renamed from src/output/SolarisOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/SolarisOutputPlugin.hxx (renamed from src/output/SolarisOutputPlugin.hxx)0
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx (renamed from src/output/WinmmOutputPlugin.cxx)2
-rw-r--r--src/output/plugins/WinmmOutputPlugin.hxx (renamed from src/output/WinmmOutputPlugin.hxx)0
57 files changed, 3501 insertions, 17 deletions
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/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 87370ba99..f2e4fc643 100644
--- a/src/output/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "AlsaOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "pcm/PcmExport.hxx"
#include "util/Manual.hxx"
diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx
index 63508e041..63508e041 100644
--- a/src/output/AlsaOutputPlugin.hxx
+++ b/src/output/plugins/AlsaOutputPlugin.hxx
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
index e6cd0b916..efc1e0c6e 100644
--- a/src/output/AoOutputPlugin.cxx
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "AoOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
diff --git a/src/output/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx
index cbf2fd589..cbf2fd589 100644
--- a/src/output/AoOutputPlugin.hxx
+++ b/src/output/plugins/AoOutputPlugin.hxx
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
index 9e5a1d5d2..5f14bcbbe 100644
--- a/src/output/FifoOutputPlugin.cxx
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "FifoOutputPlugin.hxx"
#include "ConfigError.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "Timer.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx
index 394ec3ae9..394ec3ae9 100644
--- a/src/output/FifoOutputPlugin.hxx
+++ b/src/output/plugins/FifoOutputPlugin.hxx
diff --git a/src/output/HttpdClient.cxx b/src/output/plugins/HttpdClient.cxx
index d761bdf57..d761bdf57 100644
--- a/src/output/HttpdClient.cxx
+++ b/src/output/plugins/HttpdClient.cxx
diff --git a/src/output/HttpdClient.hxx b/src/output/plugins/HttpdClient.hxx
index f94f05769..f94f05769 100644
--- a/src/output/HttpdClient.hxx
+++ b/src/output/plugins/HttpdClient.hxx
diff --git a/src/output/HttpdInternal.hxx b/src/output/plugins/HttpdInternal.hxx
index 2ef0831ba..506730d11 100644
--- a/src/output/HttpdInternal.hxx
+++ b/src/output/plugins/HttpdInternal.hxx
@@ -25,7 +25,7 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
-#include "OutputInternal.hxx"
+#include "../OutputInternal.hxx"
#include "Timer.hxx"
#include "thread/Mutex.hxx"
#include "event/ServerSocket.hxx"
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/plugins/HttpdOutputPlugin.cxx
index 322c9a61e..6921cb808 100644
--- a/src/output/HttpdOutputPlugin.cxx
+++ b/src/output/plugins/HttpdOutputPlugin.cxx
@@ -21,7 +21,7 @@
#include "HttpdOutputPlugin.hxx"
#include "HttpdInternal.hxx"
#include "HttpdClient.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "system/Resolver.hxx"
diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/plugins/HttpdOutputPlugin.hxx
index 78218e5f0..78218e5f0 100644
--- a/src/output/HttpdOutputPlugin.hxx
+++ b/src/output/plugins/HttpdOutputPlugin.hxx
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index c65ae5225..5a0d2bf16 100644
--- a/src/output/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "JackOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/output/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx
index ee3fe9238..ee3fe9238 100644
--- a/src/output/JackOutputPlugin.hxx
+++ b/src/output/plugins/JackOutputPlugin.hxx
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
index 0b6476239..c336d86e6 100644
--- a/src/output/NullOutputPlugin.cxx
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "NullOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "Timer.hxx"
struct NullOutput {
diff --git a/src/output/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx
index 05b8ef3d8..05b8ef3d8 100644
--- a/src/output/NullOutputPlugin.hxx
+++ b/src/output/plugins/NullOutputPlugin.hxx
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index 6c8467752..c247336d7 100644
--- a/src/output/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "OSXOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
index 0de10f83e..0de10f83e 100644
--- a/src/output/OSXOutputPlugin.hxx
+++ b/src/output/plugins/OSXOutputPlugin.hxx
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
index 52a2c9070..f590f0ea0 100644
--- a/src/output/OpenALOutputPlugin.cxx
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "OpenALOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx
index eb43d1aa5..eb43d1aa5 100644
--- a/src/output/OpenALOutputPlugin.hxx
+++ b/src/output/plugins/OpenALOutputPlugin.hxx
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
index 24f3f48b5..cdf055df9 100644
--- a/src/output/OssOutputPlugin.cxx
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "OssOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "system/fd_util.h"
#include "util/Error.hxx"
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx
index 4762fa652..4762fa652 100644
--- a/src/output/OssOutputPlugin.hxx
+++ b/src/output/plugins/OssOutputPlugin.hxx
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
index 66d5a28ae..802e1ba4d 100644
--- a/src/output/PipeOutputPlugin.cxx
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "PipeOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx
index 42b01b9f7..42b01b9f7 100644
--- a/src/output/PipeOutputPlugin.hxx
+++ b/src/output/plugins/PipeOutputPlugin.hxx
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index dd1906f53..c133d9796 100644
--- a/src/output/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "PulseOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "mixer/PulseMixerPlugin.hxx"
#include "util/Error.hxx"
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx
index 9df557282..9df557282 100644
--- a/src/output/PulseOutputPlugin.hxx
+++ b/src/output/plugins/PulseOutputPlugin.hxx
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
index cebbc2cec..16fe2c692 100644
--- a/src/output/RecorderOutputPlugin.cxx
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "RecorderOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "ConfigError.hxx"
diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx
index 4fac911a1..4fac911a1 100644
--- a/src/output/RecorderOutputPlugin.hxx
+++ b/src/output/plugins/RecorderOutputPlugin.hxx
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
index 9634379c5..7c1c41b47 100644
--- a/src/output/RoarOutputPlugin.cxx
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "RoarOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "MixerList.hxx"
#include "thread/Mutex.hxx"
#include "util/Error.hxx"
diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx
index 27c5dc420..27c5dc420 100644
--- a/src/output/RoarOutputPlugin.hxx
+++ b/src/output/plugins/RoarOutputPlugin.hxx
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
index cccd3e0ba..e0ec6ce3d 100644
--- a/src/output/ShoutOutputPlugin.cxx
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "ShoutOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "encoder/EncoderPlugin.hxx"
#include "encoder/EncoderList.hxx"
#include "ConfigError.hxx"
diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx
index d437e0b0d..d437e0b0d 100644
--- a/src/output/ShoutOutputPlugin.hxx
+++ b/src/output/plugins/ShoutOutputPlugin.hxx
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
index c06c848d1..38ed2e314 100644
--- a/src/output/SolarisOutputPlugin.cxx
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "SolarisOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "system/fd_util.h"
#include "util/Error.hxx"
diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx
index 9ce848a40..9ce848a40 100644
--- a/src/output/SolarisOutputPlugin.hxx
+++ b/src/output/plugins/SolarisOutputPlugin.hxx
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
index bc4ca9386..87861180f 100644
--- a/src/output/WinmmOutputPlugin.cxx
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "WinmmOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include "MixerList.hxx"
#include "util/Error.hxx"
diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx
index 1409a2e8c..1409a2e8c 100644
--- a/src/output/WinmmOutputPlugin.hxx
+++ b/src/output/plugins/WinmmOutputPlugin.hxx