aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to 'src/output')
-rw-r--r--src/output/Domain.cxx23
-rw-r--r--src/output/Domain.hxx25
-rw-r--r--src/output/Finish.cxx50
-rw-r--r--src/output/Init.cxx335
-rw-r--r--src/output/Internal.hxx436
-rw-r--r--src/output/MultipleOutputs.cxx482
-rw-r--r--src/output/MultipleOutputs.hxx276
-rw-r--r--src/output/OutputAPI.hxx33
-rw-r--r--src/output/OutputCommand.cxx106
-rw-r--r--src/output/OutputCommand.hxx53
-rw-r--r--src/output/OutputControl.cxx295
-rw-r--r--src/output/OutputControl.hxx25
-rw-r--r--src/output/OutputPlugin.cxx109
-rw-r--r--src/output/OutputPlugin.hxx204
-rw-r--r--src/output/OutputPrint.cxx43
-rw-r--r--src/output/OutputPrint.hxx34
-rw-r--r--src/output/OutputState.cxx88
-rw-r--r--src/output/OutputState.hxx46
-rw-r--r--src/output/OutputThread.cxx664
-rw-r--r--src/output/Registry.cxx104
-rw-r--r--src/output/Registry.hxx35
-rw-r--r--src/output/Timer.cxx67
-rw-r--r--src/output/Timer.hxx47
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx (renamed from src/output/AlsaOutputPlugin.cxx)174
-rw-r--r--src/output/plugins/AlsaOutputPlugin.hxx (renamed from src/output/AlsaOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx (renamed from src/output/AoOutputPlugin.cxx)30
-rw-r--r--src/output/plugins/AoOutputPlugin.hxx (renamed from src/output/AoOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/FifoOutputPlugin.cxx (renamed from src/output/FifoOutputPlugin.cxx)41
-rw-r--r--src/output/plugins/FifoOutputPlugin.hxx (renamed from src/output/FifoOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx (renamed from src/output/JackOutputPlugin.cxx)43
-rw-r--r--src/output/plugins/JackOutputPlugin.hxx (renamed from src/output/JackOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/NullOutputPlugin.cxx (renamed from src/output/NullOutputPlugin.cxx)37
-rw-r--r--src/output/plugins/NullOutputPlugin.hxx (renamed from src/output/NullOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/OSXOutputPlugin.cxx (renamed from src/output/OSXOutputPlugin.cxx)78
-rw-r--r--src/output/plugins/OSXOutputPlugin.hxx (renamed from src/output/OSXOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/OpenALOutputPlugin.cxx (renamed from src/output/OpenALOutputPlugin.cxx)37
-rw-r--r--src/output/plugins/OpenALOutputPlugin.hxx (renamed from src/output/OpenALOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx (renamed from src/output/OssOutputPlugin.cxx)45
-rw-r--r--src/output/plugins/OssOutputPlugin.hxx (renamed from src/output/OssOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/PipeOutputPlugin.cxx (renamed from src/output/PipeOutputPlugin.cxx)32
-rw-r--r--src/output/plugins/PipeOutputPlugin.hxx (renamed from src/output/PipeOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx (renamed from src/output/PulseOutputPlugin.cxx)117
-rw-r--r--src/output/plugins/PulseOutputPlugin.hxx (renamed from src/output/PulseOutputPlugin.hxx)18
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx (renamed from src/output/RecorderOutputPlugin.cxx)36
-rw-r--r--src/output/plugins/RecorderOutputPlugin.hxx (renamed from src/output/RecorderOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/RoarOutputPlugin.cxx (renamed from src/output/RoarOutputPlugin.cxx)71
-rw-r--r--src/output/plugins/RoarOutputPlugin.hxx (renamed from src/output/RoarOutputPlugin.hxx)8
-rw-r--r--src/output/plugins/ShoutOutputPlugin.cxx (renamed from src/output/ShoutOutputPlugin.cxx)53
-rw-r--r--src/output/plugins/ShoutOutputPlugin.hxx (renamed from src/output/ShoutOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/SolarisOutputPlugin.cxx (renamed from src/output/SolarisOutputPlugin.cxx)31
-rw-r--r--src/output/plugins/SolarisOutputPlugin.hxx (renamed from src/output/SolarisOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx (renamed from src/output/WinmmOutputPlugin.cxx)37
-rw-r--r--src/output/plugins/WinmmOutputPlugin.hxx (renamed from src/output/WinmmOutputPlugin.hxx)6
-rw-r--r--src/output/plugins/httpd/HttpdClient.cxx (renamed from src/output/HttpdClient.cxx)97
-rw-r--r--src/output/plugins/httpd/HttpdClient.hxx (renamed from src/output/HttpdClient.hxx)27
-rw-r--r--src/output/plugins/httpd/HttpdInternal.hxx (renamed from src/output/HttpdInternal.hxx)65
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.cxx (renamed from src/output/HttpdOutputPlugin.cxx)246
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.hxx (renamed from src/output/HttpdOutputPlugin.hxx)4
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.cxx134
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.hxx39
-rw-r--r--src/output/plugins/httpd/Page.cxx70
-rw-r--r--src/output/plugins/httpd/Page.hxx102
-rw-r--r--src/output/plugins/sles/AndroidSimpleBufferQueue.hxx67
-rw-r--r--src/output/plugins/sles/Engine.hxx68
-rw-r--r--src/output/plugins/sles/Object.hxx64
-rw-r--r--src/output/plugins/sles/Play.hxx52
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.cxx539
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.hxx25
68 files changed, 5472 insertions, 649 deletions
diff --git a/src/output/Domain.cxx b/src/output/Domain.cxx
new file mode 100644
index 000000000..878e5f3c5
--- /dev/null
+++ b/src/output/Domain.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 "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain output_domain("output");
diff --git a/src/output/Domain.hxx b/src/output/Domain.hxx
new file mode 100644
index 000000000..e3a20142f
--- /dev/null
+++ b/src/output/Domain.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/Finish.cxx b/src/output/Finish.cxx
new file mode 100644
index 000000000..be2ca463e
--- /dev/null
+++ b/src/output/Finish.cxx
@@ -0,0 +1,50 @@
+/*
+ * 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 "Internal.hxx"
+#include "OutputPlugin.hxx"
+#include "mixer/MixerControl.hxx"
+#include "filter/FilterInternal.hxx"
+
+#include <assert.h>
+
+AudioOutput::~AudioOutput()
+{
+ assert(!open);
+ assert(!fail_timer.IsDefined());
+ assert(!thread.IsDefined());
+
+ if (mixer != nullptr)
+ mixer_free(mixer);
+
+ delete replay_gain_filter;
+ delete other_replay_gain_filter;
+ delete filter;
+}
+
+void
+audio_output_free(AudioOutput *ao)
+{
+ assert(!ao->open);
+ assert(!ao->fail_timer.IsDefined());
+ assert(!ao->thread.IsDefined());
+
+ ao_plugin_finish(ao);
+}
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
new file mode 100644
index 000000000..eafcec432
--- /dev/null
+++ b/src/output/Init.cxx
@@ -0,0 +1,335 @@
+/*
+ * 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 "Internal.hxx"
+#include "Registry.hxx"
+#include "Domain.hxx"
+#include "OutputAPI.hxx"
+#include "filter/FilterConfig.hxx"
+#include "AudioParser.hxx"
+#include "mixer/MixerList.hxx"
+#include "mixer/MixerType.hxx"
+#include "mixer/MixerControl.hxx"
+#include "mixer/plugins/SoftwareMixerPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "filter/plugins/AutoConvertFilterPlugin.hxx"
+#include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "filter/plugins/ChainFilterPlugin.hxx"
+#include "config/ConfigError.hxx"
+#include "config/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"
+
+AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin)
+ :plugin(_plugin),
+ enabled(true), really_enabled(false),
+ open(false),
+ pause(false),
+ allow_play(true),
+ in_playback_loop(false),
+ woken_for_play(false),
+ filter(nullptr),
+ replay_gain_filter(nullptr),
+ other_replay_gain_filter(nullptr),
+ command(AO_COMMAND_NONE)
+{
+ assert(plugin.finish != nullptr);
+ assert(plugin.open != nullptr);
+ assert(plugin.close != nullptr);
+ assert(plugin.play != nullptr);
+}
+
+static const AudioOutputPlugin *
+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(EventLoop &event_loop, AudioOutput &ao,
+ const config_param &param,
+ const MixerPlugin *plugin,
+ Filter &filter_chain,
+ MixerListener &listener,
+ 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(event_loop, *plugin, ao, listener,
+ param, error);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(event_loop, software_mixer_plugin, ao,
+ listener,
+ 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
+AudioOutput::Configure(const config_param &param, Error &error)
+{
+ if (!param.IsNull()) {
+ name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
+ if (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(config_audio_format,
+ p, true, error);
+ if (!success)
+ return false;
+ } else
+ config_audio_format.Clear();
+ } else {
+ name = "default detected output";
+
+ config_audio_format.Clear();
+ }
+
+ tags = param.GetBlockValue("tags", true);
+ always_on = param.GetBlockValue("always_on", false);
+ enabled = param.GetBlockValue("enabled", true);
+
+ /* set up the filter chain */
+
+ filter = filter_chain_new();
+ assert(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(*filter, "normalize",
+ autoconvert_filter_new(normalize_filter));
+ }
+
+ Error filter_error;
+ filter_chain_parse(*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'",
+ name);
+
+ /* done */
+
+ return true;
+}
+
+static bool
+audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &mixer_listener,
+ 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(event_loop, ao, param,
+ ao.plugin.mixer_plugin,
+ *ao.filter,
+ mixer_listener,
+ 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;
+}
+
+AudioOutput *
+audio_output_new(EventLoop &event_loop, const config_param &param,
+ MixerListener &mixer_listener,
+ PlayerControl &pc,
+ Error &error)
+{
+ const AudioOutputPlugin *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 = AudioOutputPlugin_get(p);
+ if (plugin == nullptr) {
+ error.Format(config_domain,
+ "No such audio output plugin: %s", p);
+ return nullptr;
+ }
+ } else {
+ LogWarning(output_domain,
+ "No 'AudioOutput' 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);
+ }
+
+ AudioOutput *ao = ao_plugin_init(plugin, param, error);
+ if (ao == nullptr)
+ return nullptr;
+
+ if (!audio_output_setup(event_loop, *ao, mixer_listener,
+ param, error)) {
+ ao_plugin_finish(ao);
+ return nullptr;
+ }
+
+ ao->player_control = &pc;
+ return ao;
+}
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
new file mode 100644
index 000000000..658ebd4e1
--- /dev/null
+++ b/src/output/Internal.hxx
@@ -0,0 +1,436 @@
+/*
+ * 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 "ReplayGainInfo.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "system/PeriodClock.hxx"
+
+class Error;
+class Filter;
+class MusicPipe;
+class EventLoop;
+class Mixer;
+class MixerListener;
+struct MusicChunk;
+struct config_param;
+struct PlayerControl;
+struct AudioOutputPlugin;
+
+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 AudioOutput {
+ /**
+ * The device's configured display name.
+ */
+ const char *name;
+
+ /**
+ * The plugin which implements this output device.
+ */
+ const AudioOutputPlugin &plugin;
+
+ /**
+ * The #mixer object associated with this audio output device.
+ * May be nullptr if none is available, or if software volume is
+ * configured.
+ */
+ 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, #current_chunk and
+ * #current_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 #MusicChunk 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 MusicChunk *current_chunk;
+
+ /**
+ * Has the output finished playing #current_chunk?
+ */
+ bool current_chunk_finished;
+
+ AudioOutput(const AudioOutputPlugin &_plugin);
+ ~AudioOutput();
+
+ bool Configure(const config_param &param, Error &error);
+
+ void StartThread();
+ void StopThread();
+
+ void Finish();
+
+ bool IsOpen() const {
+ return open;
+ }
+
+ bool IsCommandFinished() const {
+ return command == AO_COMMAND_NONE;
+ }
+
+ /**
+ * Waits for command completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void WaitForCommand();
+
+ /**
+ * Sends a command, but does not wait for completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void CommandAsync(audio_output_command cmd);
+
+ /**
+ * Sends a command to the #AudioOutput object and waits for
+ * completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void CommandWait(audio_output_command cmd);
+
+ /**
+ * Lock the #AudioOutput object and execute the command
+ * synchronously.
+ */
+ void LockCommandWait(audio_output_command cmd);
+
+ /**
+ * Enables the device.
+ */
+ void LockEnableWait();
+
+ /**
+ * Disables the device.
+ */
+ void LockDisableWait();
+
+ void LockPauseAsync();
+
+ /**
+ * Same LockCloseWait(), but expects the lock to be
+ * held by the caller.
+ */
+ void CloseWait();
+ void LockCloseWait();
+
+ /**
+ * Closes the audio output, but if the "always_on" flag is set, put it
+ * into pause mode instead.
+ */
+ void LockRelease();
+
+ void SetReplayGainMode(ReplayGainMode mode);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool Open(const AudioFormat audio_format, const MusicPipe &mp);
+
+ /**
+ * Opens or closes the device, depending on the "enabled"
+ * flag.
+ *
+ * @return true if the device is open
+ */
+ bool LockUpdate(const AudioFormat audio_format,
+ const MusicPipe &mp);
+
+ void LockPlay();
+
+ void LockDrainAsync();
+
+ /**
+ * Clear the "allow_play" flag and send the "CANCEL" command
+ * asynchronously. To finish the operation, the caller has to
+ * call LockAllowPlay().
+ */
+ void LockCancelAsync();
+
+ /**
+ * Set the "allow_play" and signal the thread.
+ */
+ void LockAllowPlay();
+
+private:
+ void CommandFinished();
+
+ bool Enable();
+ void Disable();
+
+ void Open();
+ void Close(bool drain);
+ void Reopen();
+
+ AudioFormat OpenFilter(AudioFormat &format, Error &error_r);
+ void CloseFilter();
+ void ReopenFilter();
+
+ /**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a
+ * command was issued
+ */
+ bool WaitForDelay();
+
+ gcc_pure
+ const MusicChunk *GetNextChunk() const;
+
+ bool PlayChunk(const MusicChunk *chunk);
+
+ /**
+ * 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
+ */
+ bool Play();
+
+ void Pause();
+
+ /**
+ * The OutputThread.
+ */
+ void Task();
+ static void Task(void *arg);
+};
+
+/**
+ * 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;
+
+AudioOutput *
+audio_output_new(EventLoop &event_loop, const config_param &param,
+ MixerListener &mixer_listener,
+ PlayerControl &pc,
+ Error &error);
+
+void
+audio_output_free(AudioOutput *ao);
+
+#endif
diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx
new file mode 100644
index 000000000..33ab57894
--- /dev/null
+++ b/src/output/MultipleOutputs.cxx
@@ -0,0 +1,482 @@
+/*
+ * 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 "MultipleOutputs.hxx"
+#include "PlayerControl.hxx"
+#include "Internal.hxx"
+#include "Domain.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "notify.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+MultipleOutputs::MultipleOutputs(MixerListener &_mixer_listener)
+ :mixer_listener(_mixer_listener),
+ input_audio_format(AudioFormat::Undefined()),
+ buffer(nullptr), pipe(nullptr),
+ elapsed_time(SignedSongTime::Negative())
+{
+}
+
+MultipleOutputs::~MultipleOutputs()
+{
+ for (auto i : outputs) {
+ i->LockDisableWait();
+ i->Finish();
+ }
+}
+
+static AudioOutput *
+LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener,
+ PlayerControl &pc, const config_param &param)
+{
+ Error error;
+ AudioOutput *output = audio_output_new(event_loop, param,
+ mixer_listener,
+ pc, error);
+ if (output == nullptr) {
+ if (param.line > 0)
+ FormatFatalError("line %i: %s",
+ param.line,
+ error.GetMessage());
+ else
+ FatalError(error);
+ }
+
+ return output;
+}
+
+void
+MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc)
+{
+ for (const config_param *param = config_get_param(CONF_AUDIO_OUTPUT);
+ param != nullptr; param = param->next) {
+ auto output = LoadOutput(event_loop, mixer_listener,
+ pc, *param);
+ if (FindByName(output->name) != nullptr)
+ FormatFatalError("output devices with identical "
+ "names: %s", output->name);
+
+ outputs.push_back(output);
+ }
+
+ if (outputs.empty()) {
+ /* auto-detect device */
+ const config_param empty;
+ auto output = LoadOutput(event_loop, mixer_listener,
+ pc, empty);
+ outputs.push_back(output);
+ }
+}
+
+AudioOutput *
+MultipleOutputs::FindByName(const char *name) const
+{
+ for (auto i : outputs)
+ if (strcmp(i->name, name) == 0)
+ return i;
+
+ return nullptr;
+}
+
+void
+MultipleOutputs::EnableDisable()
+{
+ for (auto ao : outputs) {
+ bool enabled;
+
+ ao->mutex.lock();
+ enabled = ao->really_enabled;
+ ao->mutex.unlock();
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ ao->LockEnableWait();
+ else
+ ao->LockDisableWait();
+ }
+ }
+}
+
+bool
+MultipleOutputs::AllFinished() const
+{
+ for (auto ao : outputs) {
+ const ScopeLock protect(ao->mutex);
+ if (ao->IsOpen() && !ao->IsCommandFinished())
+ return false;
+ }
+
+ return true;
+}
+
+void
+MultipleOutputs::WaitAll()
+{
+ while (!AllFinished())
+ audio_output_client_notify.Wait();
+}
+
+void
+MultipleOutputs::AllowPlay()
+{
+ for (auto ao : outputs)
+ ao->LockAllowPlay();
+}
+
+static void
+audio_output_reset_reopen(AudioOutput *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ ao->fail_timer.Reset();
+}
+
+void
+MultipleOutputs::ResetReopen()
+{
+ for (auto ao : outputs)
+ audio_output_reset_reopen(ao);
+}
+
+bool
+MultipleOutputs::Update()
+{
+ bool ret = false;
+
+ if (!input_audio_format.IsDefined())
+ return false;
+
+ for (auto ao : outputs)
+ ret = ao->LockUpdate(input_audio_format, *pipe)
+ || ret;
+
+ return ret;
+}
+
+void
+MultipleOutputs::SetReplayGainMode(ReplayGainMode mode)
+{
+ for (auto ao : outputs)
+ ao->SetReplayGainMode(mode);
+}
+
+bool
+MultipleOutputs::Play(MusicChunk *chunk, Error &error)
+{
+ assert(buffer != nullptr);
+ assert(pipe != nullptr);
+ assert(chunk != nullptr);
+ assert(chunk->CheckFormat(input_audio_format));
+
+ if (!Update()) {
+ /* TODO: obtain real error */
+ error.Set(output_domain, "Failed to open audio output");
+ return false;
+ }
+
+ pipe->Push(chunk);
+
+ for (auto ao : outputs)
+ ao->LockPlay();
+
+ return true;
+}
+
+bool
+MultipleOutputs::Open(const AudioFormat audio_format,
+ MusicBuffer &_buffer,
+ Error &error)
+{
+ bool ret = false, enabled = false;
+
+ assert(buffer == nullptr || buffer == &_buffer);
+ assert((pipe == nullptr) == (buffer == nullptr));
+
+ buffer = &_buffer;
+
+ /* the audio format must be the same as existing chunks in the
+ pipe */
+ assert(pipe == nullptr || pipe->CheckFormat(audio_format));
+
+ if (pipe == nullptr)
+ pipe = new MusicPipe();
+ else
+ /* if the pipe hasn't been cleared, the the audio
+ format must not have changed */
+ assert(pipe->IsEmpty() || audio_format == input_audio_format);
+
+ input_audio_format = audio_format;
+
+ ResetReopen();
+ EnableDisable();
+ Update();
+
+ for (auto ao : outputs) {
+ if (ao->enabled)
+ enabled = true;
+
+ if (ao->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 */
+ Close();
+
+ return ret;
+}
+
+/**
+ * Has the specified audio output already consumed this chunk?
+ */
+gcc_pure
+static bool
+chunk_is_consumed_in(const AudioOutput *ao,
+ gcc_unused const MusicPipe *pipe,
+ const MusicChunk *chunk)
+{
+ if (!ao->open)
+ return true;
+
+ if (ao->current_chunk == nullptr)
+ return false;
+
+ assert(chunk == ao->current_chunk ||
+ pipe->Contains(ao->current_chunk));
+
+ if (chunk != ao->current_chunk) {
+ assert(chunk->next != nullptr);
+ return true;
+ }
+
+ return ao->current_chunk_finished && chunk->next == nullptr;
+}
+
+bool
+MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const
+{
+ for (auto ao : outputs) {
+ const ScopeLock protect(ao->mutex);
+ if (!chunk_is_consumed_in(ao, pipe, chunk))
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk,
+ bool *locked)
+{
+ assert(chunk->next == nullptr);
+ assert(pipe->Contains(chunk));
+
+ for (unsigned i = 0, n = outputs.size(); i != n; ++i) {
+ AudioOutput *ao = 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->current_chunk == chunk);
+ assert(ao->current_chunk_finished);
+ ao->current_chunk = nullptr;
+ }
+}
+
+unsigned
+MultipleOutputs::Check()
+{
+ const MusicChunk *chunk;
+ bool is_tail;
+ MusicChunk *shifted;
+ bool locked[outputs.size()];
+
+ assert(buffer != nullptr);
+ assert(pipe != nullptr);
+
+ while ((chunk = pipe->Peek()) != nullptr) {
+ assert(!pipe->IsEmpty());
+
+ if (!IsChunkConsumed(chunk))
+ /* at least one output is not finished playing
+ this chunk */
+ return pipe->GetSize();
+
+ if (chunk->length > 0 && !chunk->time.IsNegative())
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ elapsed_time = chunk->time;
+
+ is_tail = chunk->next == nullptr;
+ if (is_tail)
+ /* this is the tail of the pipe - clear the
+ chunk reference in all outputs */
+ ClearTailChunk(chunk, locked);
+
+ /* remove the chunk from the pipe */
+ shifted = pipe->Shift();
+ assert(shifted == chunk);
+
+ if (is_tail)
+ /* unlock all audio outputs which were locked
+ by clear_tail_chunk() */
+ for (unsigned i = 0, n = outputs.size(); i != n; ++i)
+ if (locked[i])
+ outputs[i]->mutex.unlock();
+
+ /* return the chunk to the buffer */
+ buffer->Return(shifted);
+ }
+
+ return 0;
+}
+
+bool
+MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold)
+{
+ pc.Lock();
+
+ if (Check() < threshold) {
+ pc.Unlock();
+ return true;
+ }
+
+ pc.Wait();
+ pc.Unlock();
+
+ return Check() < threshold;
+}
+
+void
+MultipleOutputs::Pause()
+{
+ Update();
+
+ for (auto ao : outputs)
+ ao->LockPauseAsync();
+
+ WaitAll();
+}
+
+void
+MultipleOutputs::Drain()
+{
+ for (auto ao : outputs)
+ ao->LockDrainAsync();
+
+ WaitAll();
+}
+
+void
+MultipleOutputs::Cancel()
+{
+ /* send the cancel() command to all audio outputs */
+
+ for (auto ao : outputs)
+ ao->LockCancelAsync();
+
+ WaitAll();
+
+ /* clear the music pipe and return all chunks to the buffer */
+
+ if (pipe != nullptr)
+ pipe->Clear(*buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ AllowPlay();
+
+ /* invalidate elapsed_time */
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::Close()
+{
+ for (auto ao : outputs)
+ ao->LockCloseWait();
+
+ if (pipe != nullptr) {
+ assert(buffer != nullptr);
+
+ pipe->Clear(*buffer);
+ delete pipe;
+ pipe = nullptr;
+ }
+
+ buffer = nullptr;
+
+ input_audio_format.Clear();
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::Release()
+{
+ for (auto ao : outputs)
+ ao->LockRelease();
+
+ if (pipe != nullptr) {
+ assert(buffer != nullptr);
+
+ pipe->Clear(*buffer);
+ delete pipe;
+ pipe = nullptr;
+ }
+
+ buffer = nullptr;
+
+ input_audio_format.Clear();
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::SongBorder()
+{
+ /* clear the elapsed_time pointer at the beginning of a new
+ song */
+ elapsed_time = SignedSongTime::zero();
+}
diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx
new file mode 100644
index 000000000..2c6536e2a
--- /dev/null
+++ b/src/output/MultipleOutputs.hxx
@@ -0,0 +1,276 @@
+/*
+ * 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 "AudioFormat.hxx"
+#include "ReplayGainInfo.hxx"
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+#include <vector>
+
+#include <assert.h>
+
+struct AudioFormat;
+class MusicBuffer;
+class MusicPipe;
+class EventLoop;
+class MixerListener;
+struct MusicChunk;
+struct PlayerControl;
+struct AudioOutput;
+class Error;
+
+class MultipleOutputs {
+ MixerListener &mixer_listener;
+
+ std::vector<AudioOutput *> outputs;
+
+ AudioFormat input_audio_format;
+
+ /**
+ * The #MusicBuffer object where consumed chunks are returned.
+ */
+ MusicBuffer *buffer;
+
+ /**
+ * The #MusicPipe object which feeds all audio outputs. It is
+ * filled by audio_output_all_play().
+ */
+ MusicPipe *pipe;
+
+ /**
+ * The "elapsed_time" stamp of the most recently finished
+ * chunk.
+ */
+ SignedSongTime elapsed_time;
+
+public:
+ /**
+ * Load audio outputs from the configuration file and
+ * initialize them.
+ */
+ MultipleOutputs(MixerListener &_mixer_listener);
+ ~MultipleOutputs();
+
+ void Configure(EventLoop &event_loop, PlayerControl &pc);
+
+ /**
+ * Returns the total number of audio output devices, including
+ * those which are disabled right now.
+ */
+ gcc_pure
+ unsigned Size() const {
+ return outputs.size();
+ }
+
+ /**
+ * Returns the "i"th audio output device.
+ */
+ const AudioOutput &Get(unsigned i) const {
+ assert(i < Size());
+
+ return *outputs[i];
+ }
+
+ AudioOutput &Get(unsigned i) {
+ assert(i < Size());
+
+ return *outputs[i];
+ }
+
+ /**
+ * Returns the audio output device with the specified name.
+ * Returns nullptr if the name does not exist.
+ */
+ gcc_pure
+ AudioOutput *FindByName(const char *name) const;
+
+ /**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+ void EnableDisable();
+
+ /**
+ * Opens all audio outputs which are not disabled.
+ *
+ * @param audio_format the preferred audio format
+ * @param buffer the #music_buffer where consumed #MusicChunk objects
+ * should be returned
+ * @return true on success, false on failure
+ */
+ bool Open(const AudioFormat audio_format, MusicBuffer &_buffer,
+ Error &error);
+
+ /**
+ * Closes all audio outputs.
+ */
+ void Close();
+
+ /**
+ * Closes all audio outputs. Outputs with the "always_on"
+ * flag are put into pause mode.
+ */
+ void Release();
+
+ void SetReplayGainMode(ReplayGainMode mode);
+
+ /**
+ * Enqueue a #MusicChunk object for playing, i.e. pushes it to a
+ * #MusicPipe.
+ *
+ * @param chunk the #MusicChunk object to be played
+ * @return true on success, false if no audio output was able to play
+ * (all closed then)
+ */
+ bool Play(MusicChunk *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 Check();
+
+ /**
+ * 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 #MusicChunk 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 Wait(PlayerControl &pc, unsigned threshold);
+
+ /**
+ * Puts all audio outputs into pause mode. Most implementations will
+ * simply close it then.
+ */
+ void Pause();
+
+ /**
+ * Drain all audio outputs.
+ */
+ void Drain();
+
+ /**
+ * Try to cancel data which may still be in the device's buffers.
+ */
+ void Cancel();
+
+ /**
+ * Indicate that a new song will begin now.
+ */
+ void SongBorder();
+
+ /**
+ * 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
+ SignedSongTime GetElapsedTime() const {
+ return elapsed_time;
+ }
+
+ /**
+ * Returns the average volume of all available mixers (range
+ * 0..100). Returns -1 if no mixer can be queried.
+ */
+ gcc_pure
+ int GetVolume() const;
+
+ /**
+ * Sets the volume on all available mixers.
+ *
+ * @param volume the volume (range 0..100)
+ * @return true on success, false on failure
+ */
+ bool SetVolume(unsigned volume);
+
+ /**
+ * Similar to GetVolume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This
+ * function fails if no software mixer is configured.
+ */
+ gcc_pure
+ int GetSoftwareVolume() const;
+
+ /**
+ * Similar to SetVolume(), but sets the volume only for
+ * software mixers. See #software_mixer_plugin. This
+ * function cannot fail, because the underlying software
+ * mixers cannot fail either.
+ */
+ void SetSoftwareVolume(unsigned volume);
+
+private:
+ /**
+ * Determine if all (active) outputs have finished the current
+ * command.
+ */
+ gcc_pure
+ bool AllFinished() const;
+
+ void WaitAll();
+
+ /**
+ * Signals all audio outputs which are open.
+ */
+ void AllowPlay();
+
+ /**
+ * 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.
+ */
+ void ResetReopen();
+
+ /**
+ * Opens all output devices which are enabled, but closed.
+ *
+ * @return true if there is at least open output device which
+ * is open
+ */
+ bool Update();
+
+ /**
+ * Has this chunk been consumed by all audio outputs?
+ */
+ bool IsChunkConsumed(const MusicChunk *chunk) const;
+
+ /**
+ * There's only one chunk left in the pipe (#pipe), and all
+ * audio outputs have consumed it already. Clear the
+ * reference.
+ */
+ void ClearTailChunk(const MusicChunk *chunk, bool *locked);
+};
+
+#endif
diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx
new file mode 100644
index 000000000..e0fd6eec8
--- /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 "Internal.hxx"
+#include "AudioFormat.hxx"
+#include "tag/Tag.hxx"
+#include "config/ConfigData.hxx"
+
+// IWYU pragma: end_exports
+
+#endif
diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx
new file mode 100644
index 000000000..6afb70cf1
--- /dev/null
+++ b/src/output/OutputCommand.cxx
@@ -0,0 +1,106 @@
+/*
+ * 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 "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "PlayerControl.hxx"
+#include "mixer/MixerControl.hxx"
+#include "Idle.hxx"
+
+extern unsigned audio_output_state_version;
+
+bool
+audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.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(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.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(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.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..53fc5c95e
--- /dev/null
+++ b/src/output/OutputCommand.hxx
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+class MultipleOutputs;
+
+/**
+ * Enables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_enable_index(MultipleOutputs &outputs, unsigned idx);
+
+/**
+ * Disables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
+
+/**
+ * Toggles an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
+
+#endif
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
new file mode 100644
index 000000000..89428fa87
--- /dev/null
+++ b/src/output/OutputControl.cxx
@@ -0,0 +1,295 @@
+/*
+ * 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 "Internal.hxx"
+#include "OutputPlugin.hxx"
+#include "Domain.hxx"
+#include "mixer/MixerControl.hxx"
+#include "notify.hxx"
+#include "filter/plugins/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;
+
+void
+AudioOutput::WaitForCommand()
+{
+ while (!IsCommandFinished()) {
+ mutex.unlock();
+ audio_output_client_notify.Wait();
+ mutex.lock();
+ }
+}
+
+void
+AudioOutput::CommandAsync(audio_output_command cmd)
+{
+ assert(IsCommandFinished());
+
+ command = cmd;
+ cond.signal();
+}
+
+void
+AudioOutput::CommandWait(audio_output_command cmd)
+{
+ CommandAsync(cmd);
+ WaitForCommand();
+}
+
+void
+AudioOutput::LockCommandWait(audio_output_command cmd)
+{
+ const ScopeLock protect(mutex);
+ CommandWait(cmd);
+}
+
+void
+AudioOutput::SetReplayGainMode(ReplayGainMode mode)
+{
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter_set_mode(replay_gain_filter, mode);
+ if (other_replay_gain_filter != nullptr)
+ replay_gain_filter_set_mode(other_replay_gain_filter, mode);
+}
+
+void
+AudioOutput::LockEnableWait()
+{
+ if (!thread.IsDefined()) {
+ if (plugin.enable == nullptr) {
+ /* don't bother to start the thread now if the
+ device doesn't even have a enable() method;
+ just assign the variable and we're done */
+ really_enabled = true;
+ return;
+ }
+
+ StartThread();
+ }
+
+ LockCommandWait(AO_COMMAND_ENABLE);
+}
+
+void
+AudioOutput::LockDisableWait()
+{
+ if (!thread.IsDefined()) {
+ if (plugin.disable == nullptr)
+ really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!really_enabled);
+
+ return;
+ }
+
+ LockCommandWait(AO_COMMAND_DISABLE);
+}
+
+inline bool
+AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
+{
+ assert(allow_play);
+ assert(audio_format.IsValid());
+
+ fail_timer.Reset();
+
+ if (open && audio_format == in_audio_format) {
+ assert(pipe == &mp || (always_on && pause));
+
+ if (pause) {
+ current_chunk = nullptr;
+ 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 */
+ CommandWait(AO_COMMAND_CANCEL);
+ }
+
+ return true;
+ }
+
+ in_audio_format = audio_format;
+ current_chunk = nullptr;
+
+ pipe = &mp;
+
+ if (!thread.IsDefined())
+ StartThread();
+
+ CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+ const bool open2 = open;
+
+ if (open2 && mixer != nullptr) {
+ Error error;
+ if (!mixer_open(mixer, error))
+ FormatWarning(output_domain,
+ "Failed to open mixer for '%s'", name);
+ }
+
+ return open2;
+}
+
+void
+AudioOutput::CloseWait()
+{
+ assert(allow_play);
+
+ if (mixer != nullptr)
+ mixer_auto_close(mixer);
+
+ assert(!open || !fail_timer.IsDefined());
+
+ if (open)
+ CommandWait(AO_COMMAND_CLOSE);
+ else
+ fail_timer.Reset();
+}
+
+bool
+AudioOutput::LockUpdate(const AudioFormat audio_format,
+ const MusicPipe &mp)
+{
+ const ScopeLock protect(mutex);
+
+ if (enabled && really_enabled) {
+ if (fail_timer.Check(REOPEN_AFTER * 1000)) {
+ return Open(audio_format, mp);
+ }
+ } else if (IsOpen())
+ CloseWait();
+
+ return false;
+}
+
+void
+AudioOutput::LockPlay()
+{
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+
+ if (IsOpen() && !in_playback_loop && !woken_for_play) {
+ woken_for_play = true;
+ cond.signal();
+ }
+}
+
+void
+AudioOutput::LockPauseAsync()
+{
+ if (mixer != nullptr && plugin.pause == nullptr)
+ /* the device has no pause mode: close the mixer,
+ unless its "global" flag is set (checked by
+ mixer_auto_close()) */
+ mixer_auto_close(mixer);
+
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+ if (IsOpen())
+ CommandAsync(AO_COMMAND_PAUSE);
+}
+
+void
+AudioOutput::LockDrainAsync()
+{
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+ if (IsOpen())
+ CommandAsync(AO_COMMAND_DRAIN);
+}
+
+void
+AudioOutput::LockCancelAsync()
+{
+ const ScopeLock protect(mutex);
+
+ if (IsOpen()) {
+ allow_play = false;
+ CommandAsync(AO_COMMAND_CANCEL);
+ }
+}
+
+void
+AudioOutput::LockAllowPlay()
+{
+ const ScopeLock protect(mutex);
+
+ allow_play = true;
+ if (IsOpen())
+ cond.signal();
+}
+
+void
+AudioOutput::LockRelease()
+{
+ if (always_on)
+ LockPauseAsync();
+ else
+ LockCloseWait();
+}
+
+void
+AudioOutput::LockCloseWait()
+{
+ assert(!open || !fail_timer.IsDefined());
+
+ const ScopeLock protect(mutex);
+ CloseWait();
+}
+
+void
+AudioOutput::StopThread()
+{
+ assert(thread.IsDefined());
+ assert(allow_play);
+
+ LockCommandWait(AO_COMMAND_KILL);
+ thread.Join();
+}
+
+void
+AudioOutput::Finish()
+{
+ LockCloseWait();
+
+ assert(!fail_timer.IsDefined());
+
+ if (thread.IsDefined())
+ StopThread();
+
+ audio_output_free(this);
+}
diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx
new file mode 100644
index 000000000..fff3fe406
--- /dev/null
+++ b/src/output/OutputControl.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_CONTROL_HXX
+#define MPD_OUTPUT_CONTROL_HXX
+
+struct AudioOutput;
+
+#endif
diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx
new file mode 100644
index 000000000..33bb854d4
--- /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 "Internal.hxx"
+
+AudioOutput *
+ao_plugin_init(const AudioOutputPlugin *plugin,
+ const config_param &param,
+ Error &error)
+{
+ assert(plugin != nullptr);
+ assert(plugin->init != nullptr);
+
+ return plugin->init(param, error);
+}
+
+void
+ao_plugin_finish(AudioOutput *ao)
+{
+ ao->plugin.finish(ao);
+}
+
+bool
+ao_plugin_enable(AudioOutput *ao, Error &error_r)
+{
+ return ao->plugin.enable != nullptr
+ ? ao->plugin.enable(ao, error_r)
+ : true;
+}
+
+void
+ao_plugin_disable(AudioOutput *ao)
+{
+ if (ao->plugin.disable != nullptr)
+ ao->plugin.disable(ao);
+}
+
+bool
+ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ return ao->plugin.open(ao, audio_format, error);
+}
+
+void
+ao_plugin_close(AudioOutput *ao)
+{
+ ao->plugin.close(ao);
+}
+
+unsigned
+ao_plugin_delay(AudioOutput *ao)
+{
+ return ao->plugin.delay != nullptr
+ ? ao->plugin.delay(ao)
+ : 0;
+}
+
+void
+ao_plugin_send_tag(AudioOutput *ao, const Tag *tag)
+{
+ if (ao->plugin.send_tag != nullptr)
+ ao->plugin.send_tag(ao, tag);
+}
+
+size_t
+ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ return ao->plugin.play(ao, chunk, size, error);
+}
+
+void
+ao_plugin_drain(AudioOutput *ao)
+{
+ if (ao->plugin.drain != nullptr)
+ ao->plugin.drain(ao);
+}
+
+void
+ao_plugin_cancel(AudioOutput *ao)
+{
+ if (ao->plugin.cancel != nullptr)
+ ao->plugin.cancel(ao);
+}
+
+bool
+ao_plugin_pause(AudioOutput *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..00fa36bc0
--- /dev/null
+++ b/src/output/OutputPlugin.hxx
@@ -0,0 +1,204 @@
+/*
+ * 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;
+struct AudioOutput;
+struct MixerPlugin;
+class Error;
+
+/**
+ * A plugin which controls an audio output device.
+ */
+struct AudioOutputPlugin {
+ /**
+ * 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
+ */
+ AudioOutput *(*init)(const config_param &param,
+ Error &error);
+
+ /**
+ * Free resources allocated by this device.
+ */
+ void (*finish)(AudioOutput *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)(AudioOutput *data, Error &error);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(AudioOutput *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)(AudioOutput *data, AudioFormat &audio_format,
+ Error &error);
+
+ /**
+ * Close the device.
+ */
+ void (*close)(AudioOutput *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)(AudioOutput *data);
+
+ /**
+ * Display metadata for the next chunk. Optional method,
+ * because not all devices can display metadata.
+ */
+ void (*send_tag)(AudioOutput *data, const Tag *tag);
+
+ /**
+ * Play a chunk of audio data.
+ *
+ * @return the number of bytes played, or 0 on error
+ */
+ size_t (*play)(AudioOutput *data,
+ const void *chunk, size_t size,
+ Error &error);
+
+ /**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(AudioOutput *data);
+
+ /**
+ * Try to cancel data which may still be in the device's
+ * buffers.
+ */
+ void (*cancel)(AudioOutput *data);
+
+ /**
+ * Pause the device. If supported, it may perform a special
+ * action, which keeps the device open, but does not play
+ * anything. Output plugins like "shout" might want to play
+ * silence during pause, so their clients won't be
+ * disconnected. Plugins which do not support pausing will
+ * simply be closed, and have to be reopened when unpaused.
+ *
+ * @return false on error (output will be closed then), true
+ * for continue to pause
+ */
+ bool (*pause)(AudioOutput *data);
+
+ /**
+ * The mixer plugin associated with this output plugin. This
+ * may be nullptr if no mixer plugin is implemented. When
+ * created, this mixer plugin gets the same #config_param as
+ * this audio output device.
+ */
+ const MixerPlugin *mixer_plugin;
+};
+
+static inline bool
+ao_plugin_test_default_device(const AudioOutputPlugin *plugin)
+{
+ return plugin->test_default_device != nullptr
+ ? plugin->test_default_device()
+ : false;
+}
+
+gcc_malloc
+AudioOutput *
+ao_plugin_init(const AudioOutputPlugin *plugin,
+ const config_param &param,
+ Error &error);
+
+void
+ao_plugin_finish(AudioOutput *ao);
+
+bool
+ao_plugin_enable(AudioOutput *ao, Error &error);
+
+void
+ao_plugin_disable(AudioOutput *ao);
+
+bool
+ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error);
+
+void
+ao_plugin_close(AudioOutput *ao);
+
+gcc_pure
+unsigned
+ao_plugin_delay(AudioOutput *ao);
+
+void
+ao_plugin_send_tag(AudioOutput *ao, const Tag *tag);
+
+size_t
+ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error);
+
+void
+ao_plugin_drain(AudioOutput *ao);
+
+void
+ao_plugin_cancel(AudioOutput *ao);
+
+bool
+ao_plugin_pause(AudioOutput *ao);
+
+#endif
diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx
new file mode 100644
index 000000000..414a86e32
--- /dev/null
+++ b/src/output/OutputPrint.cxx
@@ -0,0 +1,43 @@
+/*
+ * 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 "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "client/Client.hxx"
+
+void
+printAudioDevices(Client &client, const MultipleOutputs &outputs)
+{
+ for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
+ const AudioOutput &ao = outputs.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..29aa2b11c
--- /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;
+class MultipleOutputs;
+
+void
+printAudioDevices(Client &client, const MultipleOutputs &outputs);
+
+#endif
diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx
new file mode 100644
index 000000000..fb01b1c65
--- /dev/null
+++ b/src/output/OutputState.cxx
@@ -0,0 +1,88 @@
+/*
+ * 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 "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "Domain.hxx"
+#include "Log.hxx"
+#include "fs/io/BufferedOutputStream.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(BufferedOutputStream &os,
+ const MultipleOutputs &outputs)
+{
+ for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
+ const AudioOutput &ao = outputs.Get(i);
+
+ os.Format(AUDIO_DEVICE_STATE "%d:%s\n", ao.enabled, ao.name);
+ }
+}
+
+bool
+audio_output_state_read(const char *line, MultipleOutputs &outputs)
+{
+ long value;
+ char *endptr;
+ const char *name;
+
+ 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;
+ AudioOutput *ao = outputs.FindByName(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..47f8429d5
--- /dev/null
+++ b/src/output/OutputState.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#ifndef MPD_OUTPUT_STATE_HXX
+#define MPD_OUTPUT_STATE_HXX
+
+class MultipleOutputs;
+class BufferedOutputStream;
+
+bool
+audio_output_state_read(const char *line, MultipleOutputs &outputs);
+
+void
+audio_output_state_save(BufferedOutputStream &os,
+ const MultipleOutputs &outputs);
+
+/**
+ * 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..e46425097
--- /dev/null
+++ b/src/output/OutputThread.cxx
@@ -0,0 +1,664 @@
+/*
+ * 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 "Internal.hxx"
+#include "OutputAPI.hxx"
+#include "Domain.hxx"
+#include "pcm/PcmMix.hxx"
+#include "notify.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/plugins/ConvertFilterPlugin.hxx"
+#include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "PlayerControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "thread/Util.hxx"
+#include "thread/Slack.hxx"
+#include "thread/Name.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+#include <string.h>
+
+void
+AudioOutput::CommandFinished()
+{
+ assert(command != AO_COMMAND_NONE);
+ command = AO_COMMAND_NONE;
+
+ mutex.unlock();
+ audio_output_client_notify.Signal();
+ mutex.lock();
+}
+
+inline bool
+AudioOutput::Enable()
+{
+ if (really_enabled)
+ return true;
+
+ mutex.unlock();
+ Error error;
+ bool success = ao_plugin_enable(this, error);
+ mutex.lock();
+ if (!success) {
+ FormatError(error,
+ "Failed to enable \"%s\" [%s]",
+ name, plugin.name);
+ return false;
+ }
+
+ really_enabled = true;
+ return true;
+}
+
+inline void
+AudioOutput::Disable()
+{
+ if (open)
+ Close(false);
+
+ if (really_enabled) {
+ really_enabled = false;
+
+ mutex.unlock();
+ ao_plugin_disable(this);
+ mutex.lock();
+ }
+}
+
+inline AudioFormat
+AudioOutput::OpenFilter(AudioFormat &format, Error &error_r)
+{
+ assert(format.IsValid());
+
+ /* the replay_gain filter cannot fail here */
+ if (replay_gain_filter != nullptr &&
+ !replay_gain_filter->Open(format, error_r).IsDefined())
+ return AudioFormat::Undefined();
+
+ if (other_replay_gain_filter != nullptr &&
+ !other_replay_gain_filter->Open(format, error_r).IsDefined()) {
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ const AudioFormat af = filter->Open(format, error_r);
+ if (!af.IsDefined()) {
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ if (other_replay_gain_filter != nullptr)
+ other_replay_gain_filter->Close();
+ }
+
+ return af;
+}
+
+void
+AudioOutput::CloseFilter()
+{
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ if (other_replay_gain_filter != nullptr)
+ other_replay_gain_filter->Close();
+
+ filter->Close();
+}
+
+inline void
+AudioOutput::Open()
+{
+ bool success;
+ Error error;
+ struct audio_format_string af_string;
+
+ assert(!open);
+ assert(pipe != nullptr);
+ assert(current_chunk == nullptr);
+ assert(in_audio_format.IsValid());
+
+ fail_timer.Reset();
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!Enable())
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ const AudioFormat filter_audio_format =
+ OpenFilter(in_audio_format, error);
+ if (!filter_audio_format.IsDefined()) {
+ FormatError(error, "Failed to open filter for \"%s\" [%s]",
+ name, plugin.name);
+
+ fail_timer.Update();
+ return;
+ }
+
+ assert(filter_audio_format.IsValid());
+
+ out_audio_format = filter_audio_format;
+ out_audio_format.ApplyMask(config_audio_format);
+
+ mutex.unlock();
+ success = ao_plugin_open(this, out_audio_format, error);
+ mutex.lock();
+
+ assert(!open);
+
+ if (!success) {
+ FormatError(error, "Failed to open \"%s\" [%s]",
+ name, plugin.name);
+
+ CloseFilter();
+ fail_timer.Update();
+ return;
+ }
+
+ if (!convert_filter_set(convert_filter, out_audio_format,
+ error)) {
+ FormatError(error, "Failed to convert for \"%s\" [%s]",
+ name, plugin.name);
+
+ CloseFilter();
+ fail_timer.Update();
+ return;
+ }
+
+ open = true;
+
+ FormatDebug(output_domain,
+ "opened plugin=%s name=\"%s\" audio_format=%s",
+ plugin.name, name,
+ audio_format_to_string(out_audio_format, &af_string));
+
+ if (in_audio_format != out_audio_format)
+ FormatDebug(output_domain, "converting from %s",
+ audio_format_to_string(in_audio_format,
+ &af_string));
+}
+
+void
+AudioOutput::Close(bool drain)
+{
+ assert(open);
+
+ pipe = nullptr;
+
+ current_chunk = nullptr;
+ open = false;
+
+ mutex.unlock();
+
+ if (drain)
+ ao_plugin_drain(this);
+ else
+ ao_plugin_cancel(this);
+
+ ao_plugin_close(this);
+ CloseFilter();
+
+ mutex.lock();
+
+ FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
+ plugin.name, name);
+}
+
+void
+AudioOutput::ReopenFilter()
+{
+ Error error;
+
+ CloseFilter();
+ const AudioFormat filter_audio_format =
+ OpenFilter(in_audio_format, error);
+ if (!filter_audio_format.IsDefined() ||
+ !convert_filter_set(convert_filter, out_audio_format,
+ error)) {
+ FormatError(error,
+ "Failed to open filter for \"%s\" [%s]",
+ name, plugin.name);
+
+ /* this is a little code duplication from Close(),
+ but we cannot call this function because we must
+ not call filter_close(filter) again */
+
+ pipe = nullptr;
+
+ current_chunk = nullptr;
+ open = false;
+ fail_timer.Update();
+
+ mutex.unlock();
+ ao_plugin_close(this);
+ mutex.lock();
+
+ return;
+ }
+}
+
+void
+AudioOutput::Reopen()
+{
+ if (!config_audio_format.IsFullyDefined()) {
+ if (open) {
+ const MusicPipe *mp = pipe;
+ Close(true);
+ pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ out_audio_format = in_audio_format;
+ out_audio_format.ApplyMask(config_audio_format);
+ }
+
+ if (open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ReopenFilter();
+ else
+ Open();
+}
+
+/**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a command
+ * was issued
+ */
+inline bool
+AudioOutput::WaitForDelay()
+{
+ while (true) {
+ unsigned delay = ao_plugin_delay(this);
+ if (delay == 0)
+ return true;
+
+ (void)cond.timed_wait(mutex, delay);
+
+ if (command != AO_COMMAND_NONE)
+ return false;
+ }
+}
+
+static ConstBuffer<void>
+ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk,
+ Filter *replay_gain_filter,
+ unsigned *replay_gain_serial_p)
+{
+ assert(chunk != nullptr);
+ assert(!chunk->IsEmpty());
+ assert(chunk->CheckFormat(ao->in_audio_format));
+
+ ConstBuffer<void> data(chunk->data, chunk->length);
+
+ (void)ao;
+
+ assert(data.size % ao->in_audio_format.GetFrameSize() == 0);
+
+ if (!data.IsEmpty() && 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, error);
+ if (data.IsNull())
+ FormatError(error, "\"%s\" [%s] failed to filter",
+ ao->name, ao->plugin.name);
+ }
+
+ return data;
+}
+
+static ConstBuffer<void>
+ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
+{
+ ConstBuffer<void> data =
+ ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+ &ao->replay_gain_serial);
+ if (data.IsEmpty())
+ return data;
+
+ /* cross-fade */
+
+ if (chunk->other != nullptr) {
+ ConstBuffer<void> other_data =
+ ao_chunk_data(ao, chunk->other,
+ ao->other_replay_gain_filter,
+ &ao->other_replay_gain_serial);
+ if (other_data.IsNull())
+ return nullptr;
+
+ if (other_data.IsEmpty())
+ 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 (data.size > other_data.size)
+ data.size = other_data.size;
+
+ float mix_ratio = chunk->mix_ratio;
+ if (mix_ratio >= 0)
+ /* reverse the mix ratio (because the
+ arguments to pcm_mix() are reversed), but
+ only if the mix ratio is non-negative; a
+ negative mix ratio is a MixRamp special
+ case */
+ mix_ratio = 1.0 - mix_ratio;
+
+ void *dest = ao->cross_fade_buffer.Get(other_data.size);
+ memcpy(dest, other_data.data, other_data.size);
+ if (!pcm_mix(ao->cross_fade_dither, dest, data.data, data.size,
+ ao->in_audio_format.format,
+ mix_ratio)) {
+ FormatError(output_domain,
+ "Cannot cross-fade format %s",
+ sample_format_to_string(ao->in_audio_format.format));
+ return nullptr;
+ }
+
+ data.data = dest;
+ data.size = other_data.size;
+ }
+
+ /* apply filter chain */
+
+ Error error;
+ data = ao->filter->FilterPCM(data, error);
+ if (data.IsNull()) {
+ FormatError(error, "\"%s\" [%s] failed to filter",
+ ao->name, ao->plugin.name);
+ return nullptr;
+ }
+
+ return data;
+}
+
+inline bool
+AudioOutput::PlayChunk(const MusicChunk *chunk)
+{
+ assert(filter != nullptr);
+
+ if (tags && gcc_unlikely(chunk->tag != nullptr)) {
+ mutex.unlock();
+ ao_plugin_send_tag(this, chunk->tag);
+ mutex.lock();
+ }
+
+ auto data = ConstBuffer<char>::FromVoid(ao_filter_chunk(this, chunk));
+ if (data.IsNull()) {
+ Close(false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ fail_timer.Update();
+ return false;
+ }
+
+ Error error;
+
+ while (!data.IsEmpty() && command == AO_COMMAND_NONE) {
+ if (!WaitForDelay())
+ break;
+
+ mutex.unlock();
+ size_t nbytes = ao_plugin_play(this, data.data, data.size,
+ error);
+ mutex.lock();
+ if (nbytes == 0) {
+ /* play()==0 means failure */
+ FormatError(error, "\"%s\" [%s] failed to play",
+ name, plugin.name);
+
+ Close(false);
+
+ /* don't automatically reopen this device for
+ 10 seconds */
+ assert(!fail_timer.IsDefined());
+ fail_timer.Update();
+
+ return false;
+ }
+
+ assert(nbytes <= data.size);
+ assert(nbytes % out_audio_format.GetFrameSize() == 0);
+
+ data.data += nbytes;
+ data.size -= nbytes;
+ }
+
+ return true;
+}
+
+inline const MusicChunk *
+AudioOutput::GetNextChunk() const
+{
+ return current_chunk != nullptr
+ /* continue the previous play() call */
+ ? current_chunk->next
+ /* get the first chunk from the pipe */
+ : pipe->Peek();
+}
+
+inline bool
+AudioOutput::Play()
+{
+ assert(pipe != nullptr);
+
+ const MusicChunk *chunk = GetNextChunk();
+ if (chunk == nullptr)
+ /* no chunk available */
+ return false;
+
+ current_chunk_finished = false;
+
+ assert(!in_playback_loop);
+ in_playback_loop = true;
+
+ while (chunk != nullptr && command == AO_COMMAND_NONE) {
+ assert(!current_chunk_finished);
+
+ current_chunk = chunk;
+
+ if (!PlayChunk(chunk)) {
+ assert(current_chunk == nullptr);
+ break;
+ }
+
+ assert(current_chunk == chunk);
+ chunk = chunk->next;
+ }
+
+ assert(in_playback_loop);
+ in_playback_loop = false;
+
+ current_chunk_finished = true;
+
+ mutex.unlock();
+ player_control->LockSignal();
+ mutex.lock();
+
+ return true;
+}
+
+inline void
+AudioOutput::Pause()
+{
+ mutex.unlock();
+ ao_plugin_cancel(this);
+ mutex.lock();
+
+ pause = true;
+ CommandFinished();
+
+ do {
+ if (!WaitForDelay())
+ break;
+
+ mutex.unlock();
+ bool success = ao_plugin_pause(this);
+ mutex.lock();
+
+ if (!success) {
+ Close(false);
+ break;
+ }
+ } while (command == AO_COMMAND_NONE);
+
+ pause = false;
+}
+
+inline void
+AudioOutput::Task()
+{
+ FormatThreadName("output:%s", name);
+
+ SetThreadRealtime();
+ SetThreadTimerSlackUS(100);
+
+ mutex.lock();
+
+ while (1) {
+ switch (command) {
+ case AO_COMMAND_NONE:
+ break;
+
+ case AO_COMMAND_ENABLE:
+ Enable();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_DISABLE:
+ Disable();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_OPEN:
+ Open();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_REOPEN:
+ Reopen();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_CLOSE:
+ assert(open);
+ assert(pipe != nullptr);
+
+ Close(false);
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_PAUSE:
+ if (!open) {
+ /* the output has failed after
+ audio_output_all_pause() has
+ submitted the PAUSE command; bail
+ out */
+ CommandFinished();
+ break;
+ }
+
+ Pause();
+ /* don't "break" here: this might cause
+ Play() to be called when command==CLOSE
+ ends the paused state - "continue" checks
+ the new command first */
+ continue;
+
+ case AO_COMMAND_DRAIN:
+ if (open) {
+ assert(current_chunk == nullptr);
+ assert(pipe->Peek() == nullptr);
+
+ mutex.unlock();
+ ao_plugin_drain(this);
+ mutex.lock();
+ }
+
+ CommandFinished();
+ continue;
+
+ case AO_COMMAND_CANCEL:
+ current_chunk = nullptr;
+
+ if (open) {
+ mutex.unlock();
+ ao_plugin_cancel(this);
+ mutex.lock();
+ }
+
+ CommandFinished();
+ continue;
+
+ case AO_COMMAND_KILL:
+ current_chunk = nullptr;
+ CommandFinished();
+ mutex.unlock();
+ return;
+ }
+
+ if (open && allow_play && Play())
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
+
+ if (command == AO_COMMAND_NONE) {
+ woken_for_play = false;
+ cond.wait(mutex);
+ }
+ }
+}
+
+void
+AudioOutput::Task(void *arg)
+{
+ AudioOutput *ao = (AudioOutput *)arg;
+ ao->Task();
+}
+
+void
+AudioOutput::StartThread()
+{
+ assert(command == AO_COMMAND_NONE);
+
+ Error error;
+ if (!thread.Start(Task, this, error))
+ FatalError(error);
+}
diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx
new file mode 100644
index 000000000..566f6b6a8
--- /dev/null
+++ b/src/output/Registry.cxx
@@ -0,0 +1,104 @@
+/*
+ * 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 "Registry.hxx"
+#include "OutputAPI.hxx"
+#include "plugins/AlsaOutputPlugin.hxx"
+#include "plugins/AoOutputPlugin.hxx"
+#include "plugins/FifoOutputPlugin.hxx"
+#include "plugins/httpd/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/sles/SlesOutputPlugin.hxx"
+#include "plugins/SolarisOutputPlugin.hxx"
+#include "plugins/WinmmOutputPlugin.hxx"
+
+#include <string.h>
+
+const AudioOutputPlugin *const audio_output_plugins[] = {
+#ifdef HAVE_SHOUT
+ &shout_output_plugin,
+#endif
+ &null_output_plugin,
+#ifdef ANDROID
+ &sles_output_plugin,
+#endif
+#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 AudioOutputPlugin *
+AudioOutputPlugin_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/Registry.hxx b/src/output/Registry.hxx
new file mode 100644
index 000000000..bc9c1ae2b
--- /dev/null
+++ b/src/output/Registry.hxx
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+struct AudioOutputPlugin;
+
+extern const AudioOutputPlugin *const audio_output_plugins[];
+
+const AudioOutputPlugin *
+AudioOutputPlugin_get(const char *name);
+
+#define audio_output_plugins_for_each(plugin) \
+ for (const AudioOutputPlugin *plugin, \
+ *const*output_plugin_iterator = &audio_output_plugins[0]; \
+ (plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator)
+
+#endif
diff --git a/src/output/Timer.cxx b/src/output/Timer.cxx
new file mode 100644
index 000000000..d3dcc714d
--- /dev/null
+++ b/src/output/Timer.cxx
@@ -0,0 +1,67 @@
+/*
+ * 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 "Timer.hxx"
+#include "AudioFormat.hxx"
+#include "system/Clock.hxx"
+
+#include <limits>
+
+#include <assert.h>
+
+Timer::Timer(const AudioFormat af)
+ : time(0),
+ started(false),
+ rate(af.sample_rate * af.GetFrameSize())
+{
+}
+
+void Timer::Start()
+{
+ time = MonotonicClockUS();
+ started = true;
+}
+
+void Timer::Reset()
+{
+ time = 0;
+ started = false;
+}
+
+void Timer::Add(int size)
+{
+ assert(started);
+
+ // (size samples) / (rate samples per second) = duration seconds
+ // duration seconds * 1000000 = duration us
+ time += ((uint64_t)size * 1000000) / rate;
+}
+
+unsigned Timer::GetDelay() const
+{
+ int64_t delay = (int64_t)(time - MonotonicClockUS()) / 1000;
+ if (delay < 0)
+ return 0;
+
+ if (delay > std::numeric_limits<int>::max())
+ delay = std::numeric_limits<int>::max();
+
+ return delay;
+}
diff --git a/src/output/Timer.hxx b/src/output/Timer.hxx
new file mode 100644
index 000000000..3c935cfac
--- /dev/null
+++ b/src/output/Timer.hxx
@@ -0,0 +1,47 @@
+/*
+ * 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_TIMER_HXX
+#define MPD_TIMER_HXX
+
+#include <stdint.h>
+
+struct AudioFormat;
+
+class Timer {
+ uint64_t time;
+ bool started;
+ const int rate;
+public:
+ explicit Timer(AudioFormat af);
+
+ bool IsStarted() const { return started; }
+
+ void Start();
+ void Reset();
+
+ void Add(int size);
+
+ /**
+ * Returns the number of milliseconds to sleep to get back to sync.
+ */
+ unsigned GetDelay() const;
+};
+
+#endif
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index f8aae13a1..33a090274 100644
--- a/src/output/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,33 +19,36 @@
#include "config.h"
#include "AlsaOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
#include "pcm/PcmExport.hxx"
+#include "config/ConfigError.hxx"
#include "util/Manual.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <alsa/asoundlib.h>
#include <string>
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
+#if SND_LIB_VERSION >= 0x1001c
+/* alsa-lib supports DSD since version 1.0.27.1 */
+#define HAVE_ALSA_DSD
+#endif
static const char default_device[] = "default";
static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
-#define MPD_ALSA_RETRY_NR 5
+static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
snd_pcm_uframes_t size);
struct AlsaOutput {
- struct audio_output base;
+ AudioOutput base;
Manual<PcmExport> pcm_export;
@@ -59,12 +62,11 @@ struct AlsaOutput {
bool use_mmap;
/**
- * Enable DSD over USB according to the dCS suggested
- * standard?
+ * Enable DSD over PCM according to the DoP standard standard?
*
- * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
+ * @see http://dsd-guide.com/dop-open-standard
*/
- bool dsd_usb;
+ bool dop;
/** libasound's buffer_time setting (in microseconds) */
unsigned int buffer_time;
@@ -122,19 +124,14 @@ struct AlsaOutput {
* It contains silence samples, enough to fill one period (see
* #period_frames).
*/
- void *silence;
-
- AlsaOutput():mode(0), writei(snd_pcm_writei) {
- }
+ uint8_t *silence;
- bool Init(const config_param &param, Error &error) {
- return ao_base_init(&base, &alsa_output_plugin,
- param, error);
+ AlsaOutput()
+ :base(alsa_output_plugin),
+ mode(0), writei(snd_pcm_writei) {
}
- void Deinit() {
- ao_base_finish(&base);
- }
+ bool Configure(const config_param &param, Error &error);
};
static constexpr Domain alsa_output_domain("alsa_output");
@@ -145,56 +142,60 @@ alsa_device(const AlsaOutput *ad)
return ad->device.empty() ? default_device : ad->device.c_str();
}
-static void
-alsa_configure(AlsaOutput *ad, const config_param &param)
+inline bool
+AlsaOutput::Configure(const config_param &param, Error &error)
{
- ad->device = param.GetBlockValue("device", "");
+ if (!base.Configure(param, error))
+ return false;
- ad->use_mmap = param.GetBlockValue("use_mmap", false);
+ device = param.GetBlockValue("device", "");
- ad->dsd_usb = param.GetBlockValue("dsd_usb", false);
+ use_mmap = param.GetBlockValue("use_mmap", false);
- ad->buffer_time = param.GetBlockValue("buffer_time",
+ dop = param.GetBlockValue("dop", false) ||
+ /* legacy name from MPD 0.18 and older: */
+ param.GetBlockValue("dsd_usb", false);
+
+ buffer_time = param.GetBlockValue("buffer_time",
MPD_ALSA_BUFFER_TIME_US);
- ad->period_time = param.GetBlockValue("period_time", 0u);
+ period_time = param.GetBlockValue("period_time", 0u);
#ifdef SND_PCM_NO_AUTO_RESAMPLE
if (!param.GetBlockValue("auto_resample", true))
- ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
+ mode |= SND_PCM_NO_AUTO_RESAMPLE;
#endif
#ifdef SND_PCM_NO_AUTO_CHANNELS
if (!param.GetBlockValue("auto_channels", true))
- ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
+ mode |= SND_PCM_NO_AUTO_CHANNELS;
#endif
#ifdef SND_PCM_NO_AUTO_FORMAT
if (!param.GetBlockValue("auto_format", true))
- ad->mode |= SND_PCM_NO_AUTO_FORMAT;
+ mode |= SND_PCM_NO_AUTO_FORMAT;
#endif
+
+ return true;
}
-static struct audio_output *
+static AudioOutput *
alsa_init(const config_param &param, Error &error)
{
AlsaOutput *ad = new AlsaOutput();
- if (!ad->Init(param, error)) {
+ if (!ad->Configure(param, error)) {
delete ad;
return nullptr;
}
- alsa_configure(ad, param);
-
return &ad->base;
}
static void
-alsa_finish(struct audio_output *ao)
+alsa_finish(AudioOutput *ao)
{
AlsaOutput *ad = (AlsaOutput *)ao;
- ad->Deinit();
delete ad;
/* free libasound's config cache */
@@ -202,7 +203,7 @@ alsa_finish(struct audio_output *ao)
}
static bool
-alsa_output_enable(struct audio_output *ao, gcc_unused Error &error)
+alsa_output_enable(AudioOutput *ao, gcc_unused Error &error)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -211,7 +212,7 @@ alsa_output_enable(struct audio_output *ao, gcc_unused Error &error)
}
static void
-alsa_output_disable(struct audio_output *ao)
+alsa_output_disable(AudioOutput *ao)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -219,12 +220,12 @@ alsa_output_disable(struct audio_output *ao)
}
static bool
-alsa_test_default_device(void)
+alsa_test_default_device()
{
snd_pcm_t *handle;
int ret = snd_pcm_open(&handle, default_device,
- SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (ret) {
FormatError(alsa_output_domain,
"Error opening default ALSA device: %s",
@@ -236,13 +237,24 @@ alsa_test_default_device(void)
return true;
}
+/**
+ * Convert MPD's #SampleFormat enum to libasound's snd_pcm_format_t
+ * enum. Returns SND_PCM_FORMAT_UNKNOWN if there is no according ALSA
+ * PCM format.
+ */
static snd_pcm_format_t
get_bitformat(SampleFormat sample_format)
{
switch (sample_format) {
case SampleFormat::UNDEFINED:
+ return SND_PCM_FORMAT_UNKNOWN;
+
case SampleFormat::DSD:
+#ifdef HAVE_ALSA_DSD
+ return SND_PCM_FORMAT_DSD_U8;
+#else
return SND_PCM_FORMAT_UNKNOWN;
+#endif
case SampleFormat::S8:
return SND_PCM_FORMAT_S8;
@@ -264,10 +276,14 @@ get_bitformat(SampleFormat sample_format)
gcc_unreachable();
}
+/**
+ * Determine the byte-swapped PCM format. Returns
+ * SND_PCM_FORMAT_UNKNOWN if the format cannot be byte-swapped.
+ */
static snd_pcm_format_t
byteswap_bitformat(snd_pcm_format_t fmt)
{
- switch(fmt) {
+ switch (fmt) {
case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
@@ -285,6 +301,10 @@ byteswap_bitformat(snd_pcm_format_t fmt)
}
}
+/**
+ * Check if there is a "packed" version of the give PCM format.
+ * Returns SND_PCM_FORMAT_UNKNOWN if not.
+ */
static snd_pcm_format_t
alsa_to_packed_format(snd_pcm_format_t fmt)
{
@@ -300,6 +320,10 @@ alsa_to_packed_format(snd_pcm_format_t fmt)
}
}
+/**
+ * Attempts to configure the specified sample format. On failure,
+ * fall back to the packed version.
+ */
static int
alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
snd_pcm_format_t fmt, bool *packed_r)
@@ -370,7 +394,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
/* if unsupported by the hardware, try other formats */
- static const SampleFormat probe_formats[] = {
+ static constexpr SampleFormat probe_formats[] = {
SampleFormat::S24_P32,
SampleFormat::S32,
SampleFormat::S16,
@@ -406,7 +430,7 @@ alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
unsigned int channels = audio_format.channels;
int err;
const char *cmd = nullptr;
- int retry = MPD_ALSA_RETRY_NR;
+ unsigned retry = MPD_ALSA_RETRY_NR;
unsigned int period_time, period_time_ro;
unsigned int buffer_time;
@@ -597,8 +621,8 @@ configure_hw:
ad->period_frames = alsa_period_size;
ad->period_position = 0;
- ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm,
- alsa_period_size));
+ ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
+ alsa_period_size)];
snd_pcm_format_set_silence(format, ad->silence,
alsa_period_size * channels);
@@ -616,36 +640,36 @@ alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
Error &error)
{
- assert(ad->dsd_usb);
+ assert(ad->dop);
assert(audio_format.format == SampleFormat::DSD);
/* pass 24 bit to alsa_setup() */
- AudioFormat usb_format = audio_format;
- usb_format.format = SampleFormat::S24_P32;
- usb_format.sample_rate /= 2;
+ AudioFormat dop_format = audio_format;
+ dop_format.format = SampleFormat::S24_P32;
+ dop_format.sample_rate /= 2;
- const AudioFormat check = usb_format;
+ const AudioFormat check = dop_format;
- if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error))
+ if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error))
return false;
- /* if the device allows only 32 bit, shift all DSD-over-USB
+ /* if the device allows only 32 bit, shift all DoP
samples left by 8 bit and leave the lower 8 bit cleared;
the DSD-over-USB documentation does not specify whether
this is legal, but there is anecdotical evidence that this
is possible (and the only option for some devices) */
- *shift8_r = usb_format.format == SampleFormat::S32;
- if (usb_format.format == SampleFormat::S32)
- usb_format.format = SampleFormat::S24_P32;
+ *shift8_r = dop_format.format == SampleFormat::S32;
+ if (dop_format.format == SampleFormat::S32)
+ dop_format.format = SampleFormat::S24_P32;
- if (usb_format != check) {
+ if (dop_format != check) {
/* no bit-perfect playback, which is required
for DSD over USB */
error.Format(alsa_output_domain,
- "Failed to configure DSD-over-USB on ALSA device \"%s\"",
+ "Failed to configure DSD-over-PCM on ALSA device \"%s\"",
alsa_device(ad));
- g_free(ad->silence);
+ delete[] ad->silence;
return false;
}
@@ -658,9 +682,9 @@ alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
{
bool shift8 = false, packed, reverse_endian;
- const bool dsd_usb = ad->dsd_usb &&
+ const bool dop = ad->dop &&
audio_format.format == SampleFormat::DSD;
- const bool success = dsd_usb
+ const bool success = dop
? alsa_setup_dsd(ad, audio_format,
&shift8, &packed, &reverse_endian,
error)
@@ -671,12 +695,12 @@ alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
ad->pcm_export->Open(audio_format.format,
audio_format.channels,
- dsd_usb, shift8, packed, reverse_endian);
+ dop, shift8, packed, reverse_endian);
return true;
}
static bool
-alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
+alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -756,7 +780,7 @@ alsa_recover(AlsaOutput *ad, int err)
}
static void
-alsa_drain(struct audio_output *ao)
+alsa_drain(AudioOutput *ao)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -777,7 +801,7 @@ alsa_drain(struct audio_output *ao)
}
static void
-alsa_cancel(struct audio_output *ao)
+alsa_cancel(AudioOutput *ao)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -788,16 +812,16 @@ alsa_cancel(struct audio_output *ao)
}
static void
-alsa_close(struct audio_output *ao)
+alsa_close(AudioOutput *ao)
{
AlsaOutput *ad = (AlsaOutput *)ao;
snd_pcm_close(ad->pcm);
- g_free(ad->silence);
+ delete[] ad->silence;
}
static size_t
-alsa_play(struct audio_output *ao, const void *chunk, size_t size,
+alsa_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
AlsaOutput *ad = (AlsaOutput *)ao;
@@ -815,16 +839,18 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
}
}
- const size_t original_size = size;
- chunk = ad->pcm_export->Export(chunk, size, size);
- if (size == 0)
+ const auto e = ad->pcm_export->Export({chunk, size});
+ if (e.size == 0)
/* the DoP (DSD over PCM) filter converts two frames
at a time and ignores the last odd frame; if there
was only one frame (e.g. the last frame in the
file), the result is empty; to avoid an endless
loop, bail out here, and pretend the one frame has
been played */
- return original_size;
+ return size;
+
+ chunk = e.data;
+ size = e.size;
assert(size % ad->out_frame_size == 0);
@@ -849,7 +875,7 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
}
}
-const struct audio_output_plugin alsa_output_plugin = {
+const struct AudioOutputPlugin alsa_output_plugin = {
"alsa",
alsa_test_default_device,
alsa_init,
diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx
index dc7e639a8..f72116f91 100644
--- a/src/output/AlsaOutputPlugin.hxx
+++ b/src/output/plugins/AlsaOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX
#define MPD_ALSA_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin alsa_output_plugin;
+extern const struct AudioOutputPlugin alsa_output_plugin;
#endif
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
index e66969e20..af8c88fa1 100644
--- a/src/output/AoOutputPlugin.cxx
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -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"
@@ -35,20 +35,18 @@ static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
static unsigned ao_output_ref;
struct AoOutput {
- struct audio_output base;
+ AudioOutput base;
size_t write_size;
int driver;
ao_option *options;
ao_device *device;
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &ao_output_plugin, param,
- error);
- }
+ AoOutput()
+ :base(ao_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
}
bool Configure(const config_param &param, Error &error);
@@ -152,7 +150,7 @@ AoOutput::Configure(const config_param &param, Error &error)
return true;
}
-static struct audio_output *
+static AudioOutput *
ao_output_init(const config_param &param, Error &error)
{
AoOutput *ad = new AoOutput();
@@ -163,7 +161,6 @@ ao_output_init(const config_param &param, Error &error)
}
if (!ad->Configure(param, error)) {
- ad->Deinitialize();
delete ad;
return nullptr;
}
@@ -172,12 +169,11 @@ ao_output_init(const config_param &param, Error &error)
}
static void
-ao_output_finish(struct audio_output *ao)
+ao_output_finish(AudioOutput *ao)
{
AoOutput *ad = (AoOutput *)ao;
ao_free_options(ad->options);
- ad->Deinitialize();
delete ad;
ao_output_ref--;
@@ -187,7 +183,7 @@ ao_output_finish(struct audio_output *ao)
}
static void
-ao_output_close(struct audio_output *ao)
+ao_output_close(AudioOutput *ao)
{
AoOutput *ad = (AoOutput *)ao;
@@ -195,7 +191,7 @@ ao_output_close(struct audio_output *ao)
}
static bool
-ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ao_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
@@ -251,7 +247,7 @@ static int ao_play_deconst(ao_device *device, const void *output_samples,
}
static size_t
-ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ao_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
AoOutput *ad = (AoOutput *)ao;
@@ -267,7 +263,7 @@ ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
return size;
}
-const struct audio_output_plugin ao_output_plugin = {
+const struct AudioOutputPlugin ao_output_plugin = {
"ao",
nullptr,
ao_output_init,
diff --git a/src/output/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx
index a44885e56..07c2ba16b 100644
--- a/src/output/AoOutputPlugin.hxx
+++ b/src/output/plugins/AoOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_AO_OUTPUT_PLUGIN_HXX
#define MPD_AO_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin ao_output_plugin;
+extern const struct AudioOutputPlugin ao_output_plugin;
#endif
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
index aeb9a6a87..9df5a74dd 100644
--- a/src/output/FifoOutputPlugin.cxx
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,10 +19,9 @@
#include "config.h"
#include "FifoOutputPlugin.hxx"
-#include "ConfigError.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-#include "system/fd_util.h"
+#include "config/ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
@@ -30,16 +29,14 @@
#include "Log.hxx"
#include "open.h"
-#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
-#include <string.h>
#include <unistd.h>
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
struct FifoOutput {
- struct audio_output base;
+ AudioOutput base;
AllocatedPath path;
std::string path_utf8;
@@ -50,16 +47,12 @@ struct FifoOutput {
Timer *timer;
FifoOutput()
- :path(AllocatedPath::Null()), input(-1), output(-1),
+ :base(fifo_output_plugin),
+ path(AllocatedPath::Null()), input(-1), output(-1),
created(false) {}
bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &fifo_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
+ return base.Configure(param, error);
}
bool Create(Error &error);
@@ -175,7 +168,7 @@ fifo_open(FifoOutput *fd, Error &error)
return fd->Open(error);
}
-static struct audio_output *
+static AudioOutput *
fifo_output_init(const config_param &param, Error &error)
{
FifoOutput *fd = new FifoOutput();
@@ -198,7 +191,6 @@ fifo_output_init(const config_param &param, Error &error)
}
if (!fifo_open(fd, error)) {
- fd->Deinitialize();
delete fd;
return nullptr;
}
@@ -207,17 +199,16 @@ fifo_output_init(const config_param &param, Error &error)
}
static void
-fifo_output_finish(struct audio_output *ao)
+fifo_output_finish(AudioOutput *ao)
{
FifoOutput *fd = (FifoOutput *)ao;
fd->Close();
- fd->Deinitialize();
delete fd;
}
static bool
-fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
+fifo_output_open(AudioOutput *ao, AudioFormat &audio_format,
gcc_unused Error &error)
{
FifoOutput *fd = (FifoOutput *)ao;
@@ -228,7 +219,7 @@ fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-fifo_output_close(struct audio_output *ao)
+fifo_output_close(AudioOutput *ao)
{
FifoOutput *fd = (FifoOutput *)ao;
@@ -236,7 +227,7 @@ fifo_output_close(struct audio_output *ao)
}
static void
-fifo_output_cancel(struct audio_output *ao)
+fifo_output_cancel(AudioOutput *ao)
{
FifoOutput *fd = (FifoOutput *)ao;
char buf[FIFO_BUFFER_SIZE];
@@ -255,7 +246,7 @@ fifo_output_cancel(struct audio_output *ao)
}
static unsigned
-fifo_output_delay(struct audio_output *ao)
+fifo_output_delay(AudioOutput *ao)
{
FifoOutput *fd = (FifoOutput *)ao;
@@ -265,7 +256,7 @@ fifo_output_delay(struct audio_output *ao)
}
static size_t
-fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
+fifo_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
FifoOutput *fd = (FifoOutput *)ao;
@@ -297,7 +288,7 @@ fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
}
}
-const struct audio_output_plugin fifo_output_plugin = {
+const struct AudioOutputPlugin fifo_output_plugin = {
"fifo",
nullptr,
fifo_output_init,
diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx
index dca2886d8..f41ceded6 100644
--- a/src/output/FifoOutputPlugin.hxx
+++ b/src/output/plugins/FifoOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX
#define MPD_FIFO_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin fifo_output_plugin;
+extern const struct AudioOutputPlugin fifo_output_plugin;
#endif
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
index 7ed672f95..e1dad7893 100644
--- a/src/output/JackOutputPlugin.cxx
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "JackOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -34,10 +34,6 @@
#include <stdlib.h>
#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
enum {
MAX_PORTS = 16,
@@ -46,7 +42,7 @@ enum {
static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
struct JackOutput {
- struct audio_output base;
+ AudioOutput base;
/**
* libjack options passed to jack_client_open().
@@ -83,13 +79,11 @@ struct JackOutput {
*/
bool pause;
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &jack_output_plugin, param,
- error_r);
- }
+ JackOutput()
+ :base(jack_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
}
};
@@ -314,7 +308,7 @@ parse_port_list(const char *source, char **dest, Error &error)
return n;
}
-static struct audio_output *
+static AudioOutput *
mpd_jack_init(const config_param &param, Error &error)
{
JackOutput *jd = new JackOutput();
@@ -393,7 +387,7 @@ mpd_jack_init(const config_param &param, Error &error)
}
static void
-mpd_jack_finish(struct audio_output *ao)
+mpd_jack_finish(AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
@@ -403,12 +397,11 @@ mpd_jack_finish(struct audio_output *ao)
for (unsigned i = 0; i < jd->num_destination_ports; ++i)
g_free(jd->destination_ports[i]);
- jd->Deinitialize();
delete jd;
}
static bool
-mpd_jack_enable(struct audio_output *ao, Error &error)
+mpd_jack_enable(AudioOutput *ao, Error &error)
{
JackOutput *jd = (JackOutput *)ao;
@@ -419,7 +412,7 @@ mpd_jack_enable(struct audio_output *ao, Error &error)
}
static void
-mpd_jack_disable(struct audio_output *ao)
+mpd_jack_disable(AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
@@ -583,7 +576,7 @@ mpd_jack_start(JackOutput *jd, Error &error)
}
static bool
-mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
+mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
JackOutput *jd = (JackOutput *)ao;
@@ -608,7 +601,7 @@ mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-mpd_jack_close(gcc_unused struct audio_output *ao)
+mpd_jack_close(gcc_unused AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
@@ -616,7 +609,7 @@ mpd_jack_close(gcc_unused struct audio_output *ao)
}
static unsigned
-mpd_jack_delay(struct audio_output *ao)
+mpd_jack_delay(AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
@@ -693,7 +686,7 @@ mpd_jack_write_samples(JackOutput *jd, const void *src,
}
static size_t
-mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
+mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
JackOutput *jd = (JackOutput *)ao;
@@ -738,7 +731,7 @@ mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
}
static bool
-mpd_jack_pause(struct audio_output *ao)
+mpd_jack_pause(AudioOutput *ao)
{
JackOutput *jd = (JackOutput *)ao;
@@ -750,7 +743,7 @@ mpd_jack_pause(struct audio_output *ao)
return true;
}
-const struct audio_output_plugin jack_output_plugin = {
+const struct AudioOutputPlugin jack_output_plugin = {
"jack",
mpd_jack_test_default_device,
mpd_jack_init,
diff --git a/src/output/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx
index 908105ad2..6f1f7ecb9 100644
--- a/src/output/JackOutputPlugin.hxx
+++ b/src/output/plugins/JackOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX
#define MPD_JACK_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin jack_output_plugin;
+extern const struct AudioOutputPlugin jack_output_plugin;
#endif
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
index e2eec9dbc..098f58926 100644
--- a/src/output/NullOutputPlugin.cxx
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,29 +19,25 @@
#include "config.h"
#include "NullOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-
-#include <assert.h>
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
struct NullOutput {
- struct audio_output base;
+ AudioOutput base;
bool sync;
Timer *timer;
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &null_output_plugin, param,
- error);
- }
+ NullOutput()
+ :base(null_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
}
};
-static struct audio_output *
+static AudioOutput *
null_init(const config_param &param, Error &error)
{
NullOutput *nd = new NullOutput();
@@ -57,16 +53,15 @@ null_init(const config_param &param, Error &error)
}
static void
-null_finish(struct audio_output *ao)
+null_finish(AudioOutput *ao)
{
NullOutput *nd = (NullOutput *)ao;
- nd->Deinitialize();
delete nd;
}
static bool
-null_open(struct audio_output *ao, AudioFormat &audio_format,
+null_open(AudioOutput *ao, AudioFormat &audio_format,
gcc_unused Error &error)
{
NullOutput *nd = (NullOutput *)ao;
@@ -78,7 +73,7 @@ null_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-null_close(struct audio_output *ao)
+null_close(AudioOutput *ao)
{
NullOutput *nd = (NullOutput *)ao;
@@ -87,7 +82,7 @@ null_close(struct audio_output *ao)
}
static unsigned
-null_delay(struct audio_output *ao)
+null_delay(AudioOutput *ao)
{
NullOutput *nd = (NullOutput *)ao;
@@ -97,7 +92,7 @@ null_delay(struct audio_output *ao)
}
static size_t
-null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
+null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size,
gcc_unused Error &error)
{
NullOutput *nd = (NullOutput *)ao;
@@ -114,7 +109,7 @@ null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
}
static void
-null_cancel(struct audio_output *ao)
+null_cancel(AudioOutput *ao)
{
NullOutput *nd = (NullOutput *)ao;
@@ -124,7 +119,7 @@ null_cancel(struct audio_output *ao)
nd->timer->Reset();
}
-const struct audio_output_plugin null_output_plugin = {
+const struct AudioOutputPlugin null_output_plugin = {
"null",
nullptr,
null_init,
diff --git a/src/output/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx
index a58f1cb13..f25f5b9f3 100644
--- a/src/output/NullOutputPlugin.hxx
+++ b/src/output/plugins/NullOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX
#define MPD_NULL_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin null_output_plugin;
+extern const struct AudioOutputPlugin null_output_plugin;
#endif
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index 97ebae056..13ac7b35e 100644
--- a/src/output/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "OSXOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/fifo_buffer.h"
+#include "../OutputAPI.hxx"
+#include "util/DynamicFifoBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "thread/Mutex.hxx"
@@ -33,7 +33,7 @@
#include <CoreServices/CoreServices.h>
struct OSXOutput {
- struct audio_output base;
+ AudioOutput base;
/* configuration settings */
OSType component_subtype;
@@ -44,7 +44,10 @@ struct OSXOutput {
Mutex mutex;
Cond condition;
- struct fifo_buffer *buffer;
+ DynamicFifoBuffer<uint8_t> *buffer;
+
+ OSXOutput()
+ :base(osx_output_plugin) {}
};
static constexpr Domain osx_output_domain("osx_output");
@@ -72,16 +75,16 @@ osx_output_configure(OSXOutput *oo, const config_param &param)
}
else {
oo->component_subtype = kAudioUnitSubType_HALOutput;
- /* XXX am I supposed to g_strdup() this? */
+ /* XXX am I supposed to strdup() this? */
oo->device_name = device;
}
}
-static struct audio_output *
+static AudioOutput *
osx_output_init(const config_param &param, Error &error)
{
OSXOutput *oo = new OSXOutput();
- if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) {
+ if (!oo->base.Configure(param, error)) {
delete oo;
return NULL;
}
@@ -92,7 +95,7 @@ osx_output_init(const config_param &param, Error &error)
}
static void
-osx_output_finish(struct audio_output *ao)
+osx_output_finish(AudioOutput *ao)
{
OSXOutput *oo = (OSXOutput *)ao;
@@ -207,22 +210,19 @@ osx_render(void *vdata,
od->mutex.lock();
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
-
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
+ auto src = od->buffer->Read();
+ if (!src.IsEmpty()) {
+ if (src.size > buffer_size)
+ src.size = buffer_size;
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
+ memcpy(buffer->mData, src.data, src.size);
+ od->buffer->Consume(src.size);
+ }
od->condition.signal();
od->mutex.unlock();
- buffer->mDataByteSize = nbytes;
+ buffer->mDataByteSize = src.size;
unsigned i;
for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
@@ -234,7 +234,7 @@ osx_render(void *vdata,
}
static bool
-osx_output_enable(struct audio_output *ao, Error &error)
+osx_output_enable(AudioOutput *ao, Error &error)
{
OSXOutput *oo = (OSXOutput *)ao;
@@ -285,7 +285,7 @@ osx_output_enable(struct audio_output *ao, Error &error)
}
static void
-osx_output_disable(struct audio_output *ao)
+osx_output_disable(AudioOutput *ao)
{
OSXOutput *oo = (OSXOutput *)ao;
@@ -293,27 +293,27 @@ osx_output_disable(struct audio_output *ao)
}
static void
-osx_output_cancel(struct audio_output *ao)
+osx_output_cancel(AudioOutput *ao)
{
OSXOutput *od = (OSXOutput *)ao;
const ScopeLock protect(od->mutex);
- fifo_buffer_clear(od->buffer);
+ od->buffer->Clear();
}
static void
-osx_output_close(struct audio_output *ao)
+osx_output_close(AudioOutput *ao)
{
OSXOutput *od = (OSXOutput *)ao;
AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au);
- fifo_buffer_free(od->buffer);
+ delete od->buffer;
}
static bool
-osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
+osx_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
OSXOutput *od = (OSXOutput *)ao;
@@ -370,8 +370,8 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
/* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format.sample_rate *
- audio_format.GetFrameSize());
+ od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
+ audio_format.GetFrameSize());
status = AudioOutputUnitStart(od->au);
if (status != 0) {
@@ -386,35 +386,33 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static size_t
-osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
+osx_output_play(AudioOutput *ao, const void *chunk, size_t size,
gcc_unused Error &error)
{
OSXOutput *od = (OSXOutput *)ao;
const ScopeLock protect(od->mutex);
- void *dest;
- size_t max_length;
-
+ DynamicFifoBuffer<uint8_t>::Range dest;
while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
+ dest = od->buffer->Write();
+ if (!dest.IsEmpty())
break;
/* wait for some free space in the buffer */
od->condition.wait(od->mutex);
}
- if (size > max_length)
- size = max_length;
+ if (size > dest.size)
+ size = dest.size;
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
+ memcpy(dest.data, chunk, size);
+ od->buffer->Append(size);
return size;
}
-const struct audio_output_plugin osx_output_plugin = {
+const struct AudioOutputPlugin osx_output_plugin = {
"osx",
osx_output_test_default_device,
osx_output_init,
diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
index 2a4172880..d7aed40b6 100644
--- a/src/output/OSXOutputPlugin.hxx
+++ b/src/output/plugins/OSXOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX
#define MPD_OSX_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin osx_output_plugin;
+extern const struct AudioOutputPlugin osx_output_plugin;
#endif
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
index 268cf17cc..2f095c0a4 100644
--- a/src/output/OpenALOutputPlugin.cxx
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,11 +19,11 @@
#include "config.h"
#include "OpenALOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
+#include <unistd.h>
#ifndef __APPLE__
#include <AL/al.h>
@@ -37,7 +37,7 @@
#define NUM_BUFFERS 16
struct OpenALOutput {
- struct audio_output base;
+ AudioOutput base;
const char *device_name;
ALCdevice *device;
@@ -48,13 +48,11 @@ struct OpenALOutput {
ALenum format;
ALuint frequency;
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &openal_output_plugin, param,
- error_r);
- }
+ OpenALOutput()
+ :base(openal_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
}
};
@@ -133,7 +131,7 @@ openal_setup_context(OpenALOutput *od, Error &error)
return true;
}
-static struct audio_output *
+static AudioOutput *
openal_init(const config_param &param, Error &error)
{
const char *device_name = param.GetBlockValue("device");
@@ -153,16 +151,15 @@ openal_init(const config_param &param, Error &error)
}
static void
-openal_finish(struct audio_output *ao)
+openal_finish(AudioOutput *ao)
{
OpenALOutput *od = (OpenALOutput *)ao;
- od->Deinitialize();
delete od;
}
static bool
-openal_open(struct audio_output *ao, AudioFormat &audio_format,
+openal_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
OpenALOutput *od = (OpenALOutput *)ao;
@@ -196,7 +193,7 @@ openal_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-openal_close(struct audio_output *ao)
+openal_close(AudioOutput *ao)
{
OpenALOutput *od = (OpenALOutput *)ao;
@@ -208,7 +205,7 @@ openal_close(struct audio_output *ao)
}
static unsigned
-openal_delay(struct audio_output *ao)
+openal_delay(AudioOutput *ao)
{
OpenALOutput *od = (OpenALOutput *)ao;
@@ -221,7 +218,7 @@ openal_delay(struct audio_output *ao)
}
static size_t
-openal_play(struct audio_output *ao, const void *chunk, size_t size,
+openal_play(AudioOutput *ao, const void *chunk, size_t size,
gcc_unused Error &error)
{
OpenALOutput *od = (OpenALOutput *)ao;
@@ -238,7 +235,7 @@ openal_play(struct audio_output *ao, const void *chunk, size_t size,
} else {
/* wait for processed buffer */
while (!openal_has_processed(od))
- g_usleep(10);
+ usleep(10);
alSourceUnqueueBuffers(od->source, 1, &buffer);
}
@@ -253,7 +250,7 @@ openal_play(struct audio_output *ao, const void *chunk, size_t size,
}
static void
-openal_cancel(struct audio_output *ao)
+openal_cancel(AudioOutput *ao)
{
OpenALOutput *od = (OpenALOutput *)ao;
@@ -266,7 +263,7 @@ openal_cancel(struct audio_output *ao)
od->filled = 0;
}
-const struct audio_output_plugin openal_output_plugin = {
+const struct AudioOutputPlugin openal_output_plugin = {
"openal",
nullptr,
openal_init,
diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx
index e1ebf3d4f..a27e6b53c 100644
--- a/src/output/OpenALOutputPlugin.hxx
+++ b/src/output/plugins/OpenALOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX
#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin openal_output_plugin;
+extern const struct AudioOutputPlugin openal_output_plugin;
#endif
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
index cdde6d562..39d87fc35 100644
--- a/src/output/OssOutputPlugin.cxx
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,9 +19,10 @@
#include "config.h"
#include "OssOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
#include "system/fd_util.h"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
@@ -57,7 +58,7 @@
#endif
struct OssOutput {
- struct audio_output base;
+ AudioOutput base;
#ifdef AFMT_S24_PACKED
Manual<PcmExport> pcm_export;
@@ -78,15 +79,12 @@ struct OssOutput {
*/
int oss_format;
- OssOutput():fd(-1), device(nullptr) {}
+ OssOutput()
+ :base(oss_output_plugin),
+ fd(-1), device(nullptr) {}
bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &oss_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
+ return base.Configure(param, error_r);
}
};
@@ -149,7 +147,7 @@ oss_output_test_default_device(void)
return false;
}
-static struct audio_output *
+static AudioOutput *
oss_open_default(Error &error)
{
int err[ARRAY_SIZE(default_devices)];
@@ -199,7 +197,7 @@ oss_open_default(Error &error)
return NULL;
}
-static struct audio_output *
+static AudioOutput *
oss_output_init(const config_param &param, Error &error)
{
const char *device = param.GetBlockValue("device");
@@ -218,18 +216,17 @@ oss_output_init(const config_param &param, Error &error)
}
static void
-oss_output_finish(struct audio_output *ao)
+oss_output_finish(AudioOutput *ao)
{
OssOutput *od = (OssOutput *)ao;
- ao_base_finish(&od->base);
delete od;
}
#ifdef AFMT_S24_PACKED
static bool
-oss_output_enable(struct audio_output *ao, gcc_unused Error &error)
+oss_output_enable(AudioOutput *ao, gcc_unused Error &error)
{
OssOutput *od = (OssOutput *)ao;
@@ -238,7 +235,7 @@ oss_output_enable(struct audio_output *ao, gcc_unused Error &error)
}
static void
-oss_output_disable(struct audio_output *ao)
+oss_output_disable(AudioOutput *ao)
{
OssOutput *od = (OssOutput *)ao;
@@ -680,7 +677,7 @@ oss_reopen(OssOutput *od, Error &error)
}
static bool
-oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
+oss_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
OssOutput *od = (OssOutput *)ao;
@@ -702,7 +699,7 @@ oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-oss_output_close(struct audio_output *ao)
+oss_output_close(AudioOutput *ao)
{
OssOutput *od = (OssOutput *)ao;
@@ -710,7 +707,7 @@ oss_output_close(struct audio_output *ao)
}
static void
-oss_output_cancel(struct audio_output *ao)
+oss_output_cancel(AudioOutput *ao)
{
OssOutput *od = (OssOutput *)ao;
@@ -721,7 +718,7 @@ oss_output_cancel(struct audio_output *ao)
}
static size_t
-oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+oss_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
OssOutput *od = (OssOutput *)ao;
@@ -734,7 +731,9 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
return 0;
#ifdef AFMT_S24_PACKED
- chunk = od->pcm_export->Export(chunk, size, size);
+ const auto e = od->pcm_export->Export({chunk, size});
+ chunk = e.data;
+ size = e.size;
#endif
assert(size > 0);
@@ -755,7 +754,7 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
}
}
-const struct audio_output_plugin oss_output_plugin = {
+const struct AudioOutputPlugin oss_output_plugin = {
"oss",
oss_output_test_default_device,
oss_output_init,
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx
index 6c5c9530b..f9970c8f0 100644
--- a/src/output/OssOutputPlugin.hxx
+++ b/src/output/plugins/OssOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX
#define MPD_OSS_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin oss_output_plugin;
+extern const struct AudioOutputPlugin oss_output_plugin;
#endif
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
index 34d615284..7a1f32258 100644
--- a/src/output/PipeOutputPlugin.cxx
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "PipeOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -29,18 +29,16 @@
#include <stdio.h>
struct PipeOutput {
- struct audio_output base;
+ AudioOutput base;
std::string cmd;
FILE *fh;
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &pipe_output_plugin, param,
- error);
- }
+ PipeOutput()
+ :base(pipe_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
}
bool Configure(const config_param &param, Error &error);
@@ -61,7 +59,7 @@ PipeOutput::Configure(const config_param &param, Error &error)
return true;
}
-static struct audio_output *
+static AudioOutput *
pipe_output_init(const config_param &param, Error &error)
{
PipeOutput *pd = new PipeOutput();
@@ -72,7 +70,6 @@ pipe_output_init(const config_param &param, Error &error)
}
if (!pd->Configure(param, error)) {
- pd->Deinitialize();
delete pd;
return nullptr;
}
@@ -81,16 +78,15 @@ pipe_output_init(const config_param &param, Error &error)
}
static void
-pipe_output_finish(struct audio_output *ao)
+pipe_output_finish(AudioOutput *ao)
{
PipeOutput *pd = (PipeOutput *)ao;
- pd->Deinitialize();
delete pd;
}
static bool
-pipe_output_open(struct audio_output *ao,
+pipe_output_open(AudioOutput *ao,
gcc_unused AudioFormat &audio_format,
Error &error)
{
@@ -107,7 +103,7 @@ pipe_output_open(struct audio_output *ao,
}
static void
-pipe_output_close(struct audio_output *ao)
+pipe_output_close(AudioOutput *ao)
{
PipeOutput *pd = (PipeOutput *)ao;
@@ -115,7 +111,7 @@ pipe_output_close(struct audio_output *ao)
}
static size_t
-pipe_output_play(struct audio_output *ao, const void *chunk, size_t size,
+pipe_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
PipeOutput *pd = (PipeOutput *)ao;
@@ -128,7 +124,7 @@ pipe_output_play(struct audio_output *ao, const void *chunk, size_t size,
return ret;
}
-const struct audio_output_plugin pipe_output_plugin = {
+const struct AudioOutputPlugin pipe_output_plugin = {
"pipe",
nullptr,
pipe_output_init,
diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx
index f0c29706b..bdaf2ecfd 100644
--- a/src/output/PipeOutputPlugin.hxx
+++ b/src/output/plugins/PipeOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX
#define MPD_PIPE_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin pipe_output_plugin;
+extern const struct AudioOutputPlugin pipe_output_plugin;
#endif
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index 1eece448a..120bad090 100644
--- a/src/output/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,15 +19,13 @@
#include "config.h"
#include "PulseOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "mixer/PulseMixerPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "mixer/plugins/PulseMixerPlugin.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <pulse/thread-mainloop.h>
#include <pulse/context.h>
#include <pulse/stream.h>
@@ -38,11 +36,12 @@
#include <assert.h>
#include <stddef.h>
+#include <stdlib.h>
#define MPD_PULSE_NAME "Music Player Daemon"
struct PulseOutput {
- struct audio_output base;
+ AudioOutput base;
const char *name;
const char *server;
@@ -55,6 +54,9 @@ struct PulseOutput {
struct pa_stream *stream;
size_t writable;
+
+ PulseOutput()
+ :base(pulse_output_plugin) {}
};
static constexpr Domain pulse_output_domain("pulse_output");
@@ -67,70 +69,66 @@ SetError(Error &error, pa_context *context, const char *msg)
}
void
-pulse_output_lock(PulseOutput *po)
+pulse_output_lock(PulseOutput &po)
{
- pa_threaded_mainloop_lock(po->mainloop);
+ pa_threaded_mainloop_lock(po.mainloop);
}
void
-pulse_output_unlock(PulseOutput *po)
+pulse_output_unlock(PulseOutput &po)
{
- pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_unlock(po.mainloop);
}
void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm)
+pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm)
{
- assert(po != nullptr);
- assert(po->mixer == nullptr);
- assert(pm != nullptr);
+ assert(po.mixer == nullptr);
- po->mixer = pm;
+ po.mixer = &pm;
- if (po->mainloop == nullptr)
+ if (po.mainloop == nullptr)
return;
- pa_threaded_mainloop_lock(po->mainloop);
+ pa_threaded_mainloop_lock(po.mainloop);
- if (po->context != nullptr &&
- pa_context_get_state(po->context) == PA_CONTEXT_READY) {
- pulse_mixer_on_connect(pm, po->context);
+ if (po.context != nullptr &&
+ pa_context_get_state(po.context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po.context);
- if (po->stream != nullptr &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY)
- pulse_mixer_on_change(pm, po->context, po->stream);
+ if (po.stream != nullptr &&
+ pa_stream_get_state(po.stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po.context, po.stream);
}
- pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_unlock(po.mainloop);
}
void
-pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm)
+pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm)
{
- assert(po != nullptr);
- assert(pm != nullptr);
- assert(po->mixer == pm);
+ assert(po.mixer == &pm);
- po->mixer = nullptr;
+ po.mixer = nullptr;
}
bool
-pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume,
+pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume,
Error &error)
{
pa_operation *o;
- if (po->context == nullptr || po->stream == nullptr ||
- pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ if (po.context == nullptr || po.stream == nullptr ||
+ pa_stream_get_state(po.stream) != PA_STREAM_READY) {
error.Set(pulse_output_domain, "disconnected");
return false;
}
- o = pa_context_set_sink_input_volume(po->context,
- pa_stream_get_index(po->stream),
+ o = pa_context_set_sink_input_volume(po.context,
+ pa_stream_get_index(po.stream),
volume, nullptr, nullptr);
if (o == nullptr) {
- SetError(error, po->context,
+ SetError(error, po.context,
"failed to set PulseAudio volume");
return false;
}
@@ -150,16 +148,13 @@ static bool
pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
struct pa_operation *operation)
{
- pa_operation_state_t state;
-
assert(mainloop != nullptr);
assert(operation != nullptr);
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
+ pa_operation_state_t state;
+ while ((state = pa_operation_get_state(operation))
+ == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
pa_operation_unref(operation);
@@ -187,7 +182,7 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata)
switch (pa_context_get_state(context)) {
case PA_CONTEXT_READY:
if (po->mixer != nullptr)
- pulse_mixer_on_connect(po->mixer, context);
+ pulse_mixer_on_connect(*po->mixer, context);
pa_threaded_mainloop_signal(po->mainloop, 0);
break;
@@ -195,7 +190,7 @@ pulse_output_context_state_cb(struct pa_context *context, void *userdata)
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_FAILED:
if (po->mixer != nullptr)
- pulse_mixer_on_disconnect(po->mixer);
+ pulse_mixer_on_disconnect(*po->mixer);
/* the caller thread might be waiting for these
states */
@@ -228,7 +223,7 @@ pulse_output_subscribe_cb(pa_context *context,
idx == pa_stream_get_index(po->stream) &&
(type == PA_SUBSCRIPTION_EVENT_NEW ||
type == PA_SUBSCRIPTION_EVENT_CHANGE))
- pulse_mixer_on_change(po->mixer, context, po->stream);
+ pulse_mixer_on_change(*po->mixer, context, po->stream);
}
/**
@@ -323,15 +318,16 @@ pulse_output_setup_context(PulseOutput *po, Error &error)
return true;
}
-static struct audio_output *
+static AudioOutput *
pulse_output_init(const config_param &param, Error &error)
{
PulseOutput *po;
- g_setenv("PULSE_PROP_media.role", "music", true);
+ setenv("PULSE_PROP_media.role", "music", true);
+ setenv("PULSE_PROP_application.icon_name", "mpd", true);
po = new PulseOutput();
- if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) {
+ if (!po->base.Configure(param, error)) {
delete po;
return nullptr;
}
@@ -349,16 +345,15 @@ pulse_output_init(const config_param &param, Error &error)
}
static void
-pulse_output_finish(struct audio_output *ao)
+pulse_output_finish(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
- ao_base_finish(&po->base);
delete po;
}
static bool
-pulse_output_enable(struct audio_output *ao, Error &error)
+pulse_output_enable(AudioOutput *ao, Error &error)
{
PulseOutput *po = (PulseOutput *)ao;
@@ -402,7 +397,7 @@ pulse_output_enable(struct audio_output *ao, Error &error)
}
static void
-pulse_output_disable(struct audio_output *ao)
+pulse_output_disable(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
@@ -483,7 +478,7 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
switch (pa_stream_get_state(stream)) {
case PA_STREAM_READY:
if (po->mixer != nullptr)
- pulse_mixer_on_change(po->mixer, po->context, stream);
+ pulse_mixer_on_change(*po->mixer, po->context, stream);
pa_threaded_mainloop_signal(po->mainloop, 0);
break;
@@ -491,7 +486,7 @@ pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
if (po->mixer != nullptr)
- pulse_mixer_on_disconnect(po->mixer);
+ pulse_mixer_on_disconnect(*po->mixer);
pa_threaded_mainloop_signal(po->mainloop, 0);
break;
@@ -546,7 +541,7 @@ pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
}
static bool
-pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
+pulse_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
PulseOutput *po = (PulseOutput *)ao;
@@ -614,7 +609,7 @@ pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-pulse_output_close(struct audio_output *ao)
+pulse_output_close(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
pa_operation *o;
@@ -700,7 +695,7 @@ pulse_output_stream_pause(PulseOutput *po, bool pause,
}
static unsigned
-pulse_output_delay(struct audio_output *ao)
+pulse_output_delay(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
unsigned result = 0;
@@ -718,7 +713,7 @@ pulse_output_delay(struct audio_output *ao)
}
static size_t
-pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+pulse_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
PulseOutput *po = (PulseOutput *)ao;
@@ -783,7 +778,7 @@ pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
}
static void
-pulse_output_cancel(struct audio_output *ao)
+pulse_output_cancel(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
pa_operation *o;
@@ -816,7 +811,7 @@ pulse_output_cancel(struct audio_output *ao)
}
static bool
-pulse_output_pause(struct audio_output *ao)
+pulse_output_pause(AudioOutput *ao)
{
PulseOutput *po = (PulseOutput *)ao;
@@ -867,7 +862,7 @@ pulse_output_test_default_device(void)
return success;
}
-const struct audio_output_plugin pulse_output_plugin = {
+const struct AudioOutputPlugin pulse_output_plugin = {
"pulse",
pulse_output_test_default_device,
pulse_output_init,
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx
index 0ed8404bc..9219780a5 100644
--- a/src/output/PulseOutputPlugin.hxx
+++ b/src/output/plugins/PulseOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,26 +21,26 @@
#define MPD_PULSE_OUTPUT_PLUGIN_HXX
struct PulseOutput;
-struct PulseMixer;
+class PulseMixer;
struct pa_cvolume;
class Error;
-extern const struct audio_output_plugin pulse_output_plugin;
+extern const struct AudioOutputPlugin pulse_output_plugin;
void
-pulse_output_lock(PulseOutput *po);
+pulse_output_lock(PulseOutput &po);
void
-pulse_output_unlock(PulseOutput *po);
+pulse_output_unlock(PulseOutput &po);
void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm);
+pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm);
void
-pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
+pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm);
bool
-pulse_output_set_volume(PulseOutput *po,
- const struct pa_cvolume *volume, Error &error);
+pulse_output_set_volume(PulseOutput &po,
+ const pa_cvolume *volume, Error &error);
#endif
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
index 9a7eba01f..87e23f55a 100644
--- a/src/output/RecorderOutputPlugin.cxx
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,10 +19,10 @@
#include "config.h"
#include "RecorderOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "system/fd_util.h"
@@ -35,7 +35,7 @@
#include <errno.h>
struct RecorderOutput {
- struct audio_output base;
+ AudioOutput base;
/**
* The configured encoder plugin.
@@ -57,13 +57,11 @@ struct RecorderOutput {
*/
char buffer[32768];
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &recorder_output_plugin, param,
- error_r);
- }
+ RecorderOutput()
+ :base(recorder_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
}
bool Configure(const config_param &param, Error &error);
@@ -107,7 +105,7 @@ RecorderOutput::Configure(const config_param &param, Error &error)
return true;
}
-static audio_output *
+static AudioOutput *
recorder_output_init(const config_param &param, Error &error)
{
RecorderOutput *recorder = new RecorderOutput();
@@ -118,7 +116,6 @@ recorder_output_init(const config_param &param, Error &error)
}
if (!recorder->Configure(param, error)) {
- recorder->Deinitialize();
delete recorder;
return nullptr;
}
@@ -127,12 +124,11 @@ recorder_output_init(const config_param &param, Error &error)
}
static void
-recorder_output_finish(struct audio_output *ao)
+recorder_output_finish(AudioOutput *ao)
{
RecorderOutput *recorder = (RecorderOutput *)ao;
encoder_finish(recorder->encoder);
- recorder->Deinitialize();
delete recorder;
}
@@ -181,7 +177,7 @@ RecorderOutput::EncoderToFile(Error &error)
}
static bool
-recorder_output_open(struct audio_output *ao,
+recorder_output_open(AudioOutput *ao,
AudioFormat &audio_format,
Error &error)
{
@@ -216,7 +212,7 @@ recorder_output_open(struct audio_output *ao,
}
static void
-recorder_output_close(struct audio_output *ao)
+recorder_output_close(AudioOutput *ao)
{
RecorderOutput *recorder = (RecorderOutput *)ao;
@@ -233,7 +229,7 @@ recorder_output_close(struct audio_output *ao)
}
static size_t
-recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
+recorder_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
RecorderOutput *recorder = (RecorderOutput *)ao;
@@ -243,7 +239,7 @@ recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
? size : 0;
}
-const struct audio_output_plugin recorder_output_plugin = {
+const struct AudioOutputPlugin recorder_output_plugin = {
"recorder",
nullptr,
recorder_output_init,
diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx
index a27f51e23..ea1044e0f 100644
--- a/src/output/RecorderOutputPlugin.hxx
+++ b/src/output/plugins/RecorderOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX
#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin recorder_output_plugin;
+extern const struct AudioOutputPlugin recorder_output_plugin;
#endif
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
index 895a165d1..04836cc46 100644
--- a/src/output/RoarOutputPlugin.cxx
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
* Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
*
@@ -20,8 +20,8 @@
#include "config.h"
#include "RoarOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
#include "thread/Mutex.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -36,7 +36,7 @@
#undef new
class RoarOutput {
- struct audio_output base;
+ AudioOutput base;
std::string host, name;
@@ -50,19 +50,15 @@ class RoarOutput {
public:
RoarOutput()
- :err(ROAR_ERROR_NONE) {}
+ :base(roar_output_plugin),
+ err(ROAR_ERROR_NONE) {}
- operator audio_output *() {
+ operator AudioOutput *() {
return &base;
}
bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &roar_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
+ return base.Configure(param, error);
}
void Configure(const config_param &param);
@@ -97,9 +93,9 @@ RoarOutput::GetVolume() const
}
int
-roar_output_get_volume(RoarOutput *roar)
+roar_output_get_volume(RoarOutput &roar)
{
- return roar->GetVolume();
+ return roar.GetVolume();
}
bool
@@ -119,9 +115,9 @@ RoarOutput::SetVolume(unsigned volume)
}
bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume)
+roar_output_set_volume(RoarOutput &roar, unsigned volume)
{
- return roar->SetVolume(volume);
+ return roar.SetVolume(volume);
}
inline void
@@ -136,7 +132,7 @@ RoarOutput::Configure(const config_param &param)
: ROAR_ROLE_MUSIC;
}
-static struct audio_output *
+static AudioOutput *
roar_init(const config_param &param, Error &error)
{
RoarOutput *self = new RoarOutput();
@@ -151,11 +147,10 @@ roar_init(const config_param &param, Error &error)
}
static void
-roar_finish(struct audio_output *ao)
+roar_finish(AudioOutput *ao)
{
RoarOutput *self = (RoarOutput *)ao;
- self->Deinitialize();
delete self;
}
@@ -227,7 +222,7 @@ RoarOutput::Open(AudioFormat &audio_format, Error &error)
}
static bool
-roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
+roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
{
RoarOutput *self = (RoarOutput *)ao;
@@ -248,7 +243,7 @@ RoarOutput::Close()
}
static void
-roar_close(struct audio_output *ao)
+roar_close(AudioOutput *ao)
{
RoarOutput *self = (RoarOutput *)ao;
self->Close();
@@ -283,7 +278,7 @@ RoarOutput::Cancel()
}
static void
-roar_cancel(struct audio_output *ao)
+roar_cancel(AudioOutput *ao)
{
RoarOutput *self = (RoarOutput *)ao;
@@ -308,7 +303,7 @@ RoarOutput::Play(const void *chunk, size_t size, Error &error)
}
static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size,
+roar_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
RoarOutput *self = (RoarOutput *)ao;
@@ -368,31 +363,37 @@ RoarOutput::SendTag(const Tag &tag)
const ScopeLock protect(mutex);
- size_t cnt = 1;
+ size_t cnt = 0;
struct roar_keyval vals[32];
char uuid_buf[32][64];
char timebuf[16];
- snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
- tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60);
+ if (!tag.duration.IsNegative()) {
+ const unsigned seconds = tag.duration.ToS();
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ seconds / 3600, (seconds % 3600) / 60, seconds % 60);
+
+ vals[cnt].key = const_cast<char *>("LENGTH");
+ vals[cnt].value = timebuf;
+ ++cnt;
+ }
- vals[0].key = const_cast<char *>("LENGTH");
- vals[0].value = timebuf;
+ for (const auto &item : tag) {
+ if (cnt >= 32)
+ break;
- for (unsigned i = 0; i < tag.num_items && cnt < 32; i++)
- {
bool is_uuid = false;
- const char *key = roar_tag_convert(tag.items[i]->type,
+ const char *key = roar_tag_convert(item.type,
&is_uuid);
if (key != nullptr) {
vals[cnt].key = const_cast<char *>(key);
if (is_uuid) {
snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
- tag.items[i]->value);
+ item.value);
vals[cnt].value = uuid_buf[cnt];
} else {
- vals[cnt].value = tag.items[i]->value;
+ vals[cnt].value = const_cast<char *>(item.value);
}
cnt++;
@@ -403,13 +404,13 @@ RoarOutput::SendTag(const Tag &tag)
}
static void
-roar_send_tag(struct audio_output *ao, const Tag *meta)
+roar_send_tag(AudioOutput *ao, const Tag *meta)
{
RoarOutput *self = (RoarOutput *)ao;
self->SendTag(*meta);
}
-const struct audio_output_plugin roar_output_plugin = {
+const struct AudioOutputPlugin roar_output_plugin = {
"roar",
nullptr,
roar_init,
diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx
index 04949e421..5f5a9246e 100644
--- a/src/output/RoarOutputPlugin.hxx
+++ b/src/output/plugins/RoarOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -22,12 +22,12 @@
class RoarOutput;
-extern const struct audio_output_plugin roar_output_plugin;
+extern const struct AudioOutputPlugin roar_output_plugin;
int
-roar_output_get_volume(RoarOutput *roar);
+roar_output_get_volume(RoarOutput &roar);
bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume);
+roar_output_set_volume(RoarOutput &roar, unsigned volume);
#endif
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
index 19f2b61cd..0341e1cf7 100644
--- a/src/output/ShoutOutputPlugin.cxx
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,17 +19,16 @@
#include "config.h"
#include "ShoutOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "system/FatalError.hxx"
#include "Log.hxx"
#include <shout/shout.h>
-#include <glib.h>
#include <assert.h>
#include <stdlib.h>
@@ -39,7 +38,7 @@
static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
struct ShoutOutput final {
- struct audio_output base;
+ AudioOutput base;
shout_t *shout_conn;
shout_metadata_t *shout_meta;
@@ -54,7 +53,8 @@ struct ShoutOutput final {
uint8_t buffer[32768];
ShoutOutput()
- :shout_conn(shout_new()),
+ :base(shout_output_plugin),
+ shout_conn(shout_new()),
shout_meta(shout_metadata_new()),
quality(-2.0),
bitrate(-1),
@@ -68,12 +68,7 @@ struct ShoutOutput final {
}
bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &shout_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
+ return base.Configure(param, error);
}
bool Configure(const config_param &param, Error &error);
@@ -275,7 +270,7 @@ ShoutOutput::Configure(const config_param &param, Error &error)
return true;
}
-static struct audio_output *
+static AudioOutput *
my_shout_init_driver(const config_param &param, Error &error)
{
ShoutOutput *sd = new ShoutOutput();
@@ -285,7 +280,6 @@ my_shout_init_driver(const config_param &param, Error &error)
}
if (!sd->Configure(param, error)) {
- sd->Deinitialize();
delete sd;
return nullptr;
}
@@ -363,13 +357,12 @@ static void close_shout_conn(ShoutOutput * sd)
}
static void
-my_shout_finish_driver(struct audio_output *ao)
+my_shout_finish_driver(AudioOutput *ao)
{
ShoutOutput *sd = (ShoutOutput *)ao;
encoder_finish(sd->encoder);
- sd->Deinitialize();
delete sd;
shout_init_count--;
@@ -379,7 +372,7 @@ my_shout_finish_driver(struct audio_output *ao)
}
static void
-my_shout_drop_buffered_audio(struct audio_output *ao)
+my_shout_drop_buffered_audio(AudioOutput *ao)
{
gcc_unused
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -388,7 +381,7 @@ my_shout_drop_buffered_audio(struct audio_output *ao)
}
static void
-my_shout_close_device(struct audio_output *ao)
+my_shout_close_device(AudioOutput *ao)
{
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -414,7 +407,7 @@ shout_connect(ShoutOutput *sd, Error &error)
}
static bool
-my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
+my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -437,7 +430,7 @@ my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
}
static unsigned
-my_shout_delay(struct audio_output *ao)
+my_shout_delay(AudioOutput *ao)
{
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -449,7 +442,7 @@ my_shout_delay(struct audio_output *ao)
}
static size_t
-my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
+my_shout_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -461,7 +454,7 @@ my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
}
static bool
-my_shout_pause(struct audio_output *ao)
+my_shout_pause(AudioOutput *ao)
{
static char silence[1020];
@@ -477,13 +470,13 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
artist[0] = 0;
title[0] = 0;
- for (unsigned i = 0; i < tag->num_items; i++) {
- switch (tag->items[i]->type) {
+ for (const auto &item : *tag) {
+ switch (item.type) {
case TAG_ARTIST:
- strncpy(artist, tag->items[i]->value, size);
+ strncpy(artist, item.value, size);
break;
case TAG_TITLE:
- strncpy(title, tag->items[i]->value, size);
+ strncpy(title, item.value, size);
break;
default:
@@ -494,7 +487,7 @@ shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
snprintf(dest, size, "%s - %s", artist, title);
}
-static void my_shout_set_tag(struct audio_output *ao,
+static void my_shout_set_tag(AudioOutput *ao,
const Tag *tag)
{
ShoutOutput *sd = (ShoutOutput *)ao;
@@ -525,7 +518,7 @@ static void my_shout_set_tag(struct audio_output *ao,
write_page(sd, IgnoreError());
}
-const struct audio_output_plugin shout_output_plugin = {
+const struct AudioOutputPlugin shout_output_plugin = {
"shout",
nullptr,
my_shout_init_driver,
diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx
index 496b77975..9f706fc3b 100644
--- a/src/output/ShoutOutputPlugin.hxx
+++ b/src/output/plugins/ShoutOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX
#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin shout_output_plugin;
+extern const struct AudioOutputPlugin shout_output_plugin;
#endif
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
index 0836dc2e2..30745f97c 100644
--- a/src/output/SolarisOutputPlugin.cxx
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -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"
@@ -50,20 +50,18 @@ struct audio_info {
#endif
struct SolarisOutput {
- struct audio_output base;
+ AudioOutput base;
/* configuration */
const char *device;
int fd;
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &solaris_output_plugin, param,
- error_r);
- }
+ SolarisOutput()
+ :base(solaris_output_plugin) {}
- void Deinitialize() {
- ao_base_finish(&base);
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
}
};
@@ -76,7 +74,7 @@ solaris_output_test_default_device(void)
access("/dev/audio", W_OK) == 0;
}
-static struct audio_output *
+static AudioOutput *
solaris_output_init(const config_param &param, Error &error_r)
{
SolarisOutput *so = new SolarisOutput();
@@ -91,16 +89,15 @@ solaris_output_init(const config_param &param, Error &error_r)
}
static void
-solaris_output_finish(struct audio_output *ao)
+solaris_output_finish(AudioOutput *ao)
{
SolarisOutput *so = (SolarisOutput *)ao;
- so->Deinitialize();
delete so;
}
static bool
-solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
+solaris_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
SolarisOutput *so = (SolarisOutput *)ao;
@@ -151,7 +148,7 @@ solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-solaris_output_close(struct audio_output *ao)
+solaris_output_close(AudioOutput *ao)
{
SolarisOutput *so = (SolarisOutput *)ao;
@@ -159,7 +156,7 @@ solaris_output_close(struct audio_output *ao)
}
static size_t
-solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
+solaris_output_play(AudioOutput *ao, const void *chunk, size_t size,
Error &error)
{
SolarisOutput *so = (SolarisOutput *)ao;
@@ -175,14 +172,14 @@ solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
}
static void
-solaris_output_cancel(struct audio_output *ao)
+solaris_output_cancel(AudioOutput *ao)
{
SolarisOutput *so = (SolarisOutput *)ao;
ioctl(so->fd, I_FLUSH);
}
-const struct audio_output_plugin solaris_output_plugin = {
+const struct AudioOutputPlugin solaris_output_plugin = {
"solaris",
solaris_output_test_default_device,
solaris_output_init,
diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx
index d0fbd32c8..3f9ede7a6 100644
--- a/src/output/SolarisOutputPlugin.hxx
+++ b/src/output/plugins/SolarisOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX
#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin solaris_output_plugin;
+extern const struct AudioOutputPlugin solaris_output_plugin;
#endif
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
index d2508ee2a..e5c5a6f0c 100644
--- a/src/output/WinmmOutputPlugin.cxx
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,15 +19,13 @@
#include "config.h"
#include "WinmmOutputPlugin.hxx"
-#include "OutputAPI.hxx"
+#include "../OutputAPI.hxx"
#include "pcm/PcmBuffer.hxx"
-#include "MixerList.hxx"
+#include "mixer/MixerList.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
-#include <glib.h>
-
#include <stdlib.h>
#include <string.h>
@@ -38,7 +36,7 @@ struct WinmmBuffer {
};
struct WinmmOutput {
- struct audio_output base;
+ AudioOutput base;
UINT device_id;
HWAVEOUT handle;
@@ -51,14 +49,17 @@ struct WinmmOutput {
WinmmBuffer buffers[8];
unsigned next_buffer;
+
+ WinmmOutput()
+ :base(winmm_output_plugin) {}
};
static constexpr Domain winmm_output_domain("winmm_output");
HWAVEOUT
-winmm_output_get_handle(WinmmOutput *output)
+winmm_output_get_handle(WinmmOutput &output)
{
- return output->handle;
+ return output.handle;
}
static bool
@@ -108,18 +109,17 @@ fail:
return false;
}
-static struct audio_output *
+static AudioOutput *
winmm_output_init(const config_param &param, Error &error)
{
WinmmOutput *wo = new WinmmOutput();
- if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
+ if (!wo->base.Configure(param, error)) {
delete wo;
return nullptr;
}
const char *device = param.GetBlockValue("device");
if (!get_device_id(device, &wo->device_id, error)) {
- ao_base_finish(&wo->base);
delete wo;
return nullptr;
}
@@ -128,16 +128,15 @@ winmm_output_init(const config_param &param, Error &error)
}
static void
-winmm_output_finish(struct audio_output *ao)
+winmm_output_finish(AudioOutput *ao)
{
WinmmOutput *wo = (WinmmOutput *)ao;
- ao_base_finish(&wo->base);
delete wo;
}
static bool
-winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
+winmm_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
WinmmOutput *wo = (WinmmOutput *)ao;
@@ -194,7 +193,7 @@ winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
}
static void
-winmm_output_close(struct audio_output *ao)
+winmm_output_close(AudioOutput *ao)
{
WinmmOutput *wo = (WinmmOutput *)ao;
@@ -263,7 +262,7 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
}
static size_t
-winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error)
+winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error)
{
WinmmOutput *wo = (WinmmOutput *)ao;
@@ -318,7 +317,7 @@ winmm_stop(WinmmOutput *wo)
}
static void
-winmm_output_drain(struct audio_output *ao)
+winmm_output_drain(AudioOutput *ao)
{
WinmmOutput *wo = (WinmmOutput *)ao;
@@ -327,14 +326,14 @@ winmm_output_drain(struct audio_output *ao)
}
static void
-winmm_output_cancel(struct audio_output *ao)
+winmm_output_cancel(AudioOutput *ao)
{
WinmmOutput *wo = (WinmmOutput *)ao;
winmm_stop(wo);
}
-const struct audio_output_plugin winmm_output_plugin = {
+const struct AudioOutputPlugin winmm_output_plugin = {
"winmm",
winmm_output_test_default_device,
winmm_output_init,
diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx
index a6b7733ec..50fae4f2f 100644
--- a/src/output/WinmmOutputPlugin.hxx
+++ b/src/output/plugins/WinmmOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -31,11 +31,11 @@
struct WinmmOutput;
-extern const struct audio_output_plugin winmm_output_plugin;
+extern const struct AudioOutputPlugin winmm_output_plugin;
gcc_pure
HWAVEOUT
-winmm_output_get_handle(WinmmOutput *);
+winmm_output_get_handle(WinmmOutput &output);
#endif
diff --git a/src/output/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx
index dc337053d..3797c3d26 100644
--- a/src/output/HttpdClient.cxx
+++ b/src/output/plugins/httpd/HttpdClient.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -26,8 +26,6 @@
#include "system/SocketError.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
#include <stdio.h>
@@ -38,24 +36,26 @@ HttpdClient::~HttpdClient()
if (current_page != nullptr)
current_page->Unref();
- for (auto page : pages)
- page->Unref();
+ ClearQueue();
}
if (metadata)
metadata->Unref();
+
+ if (IsDefined())
+ BufferedSocket::Close();
}
void
HttpdClient::Close()
{
- httpd->RemoveClient(*this);
+ httpd.RemoveClient(*this);
}
void
HttpdClient::LockClose()
{
- const ScopeLock protect(httpd->mutex);
+ const ScopeLock protect(httpd.mutex);
Close();
}
@@ -68,7 +68,7 @@ HttpdClient::BeginResponse()
current_page = nullptr;
if (!head_method)
- httpd->SendHeader(*this);
+ httpd.SendHeader(*this);
}
/**
@@ -141,7 +141,9 @@ HttpdClient::HandleLine(const char *line)
bool
HttpdClient::SendResponse()
{
- char buffer[1024];
+ char buffer[1024], *allocated = nullptr;
+ const char *response;
+
assert(state == RESPONSE);
if (dlna_streaming_requested) {
@@ -156,19 +158,15 @@ HttpdClient::SendResponse()
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
- httpd->content_type);
+ httpd.content_type);
+ response = buffer;
} else if (metadata_requested) {
- char *metadata_header =
- icy_server_metadata_header(httpd->name, httpd->genre,
- httpd->website,
- httpd->content_type,
+ response = allocated =
+ icy_server_metadata_header(httpd.name, httpd.genre,
+ httpd.website,
+ httpd.content_type,
metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- delete[] metadata_header;
-
} else { /* revert to a normal HTTP request */
snprintf(buffer, sizeof(buffer),
"HTTP/1.1 200 OK\r\n"
@@ -177,10 +175,12 @@ HttpdClient::SendResponse()
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"\r\n",
- httpd->content_type);
+ httpd.content_type);
+ response = buffer;
}
- ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
+ ssize_t nbytes = SocketMonitor::Write(response, strlen(response));
+ delete[] allocated;
if (gcc_unlikely(nbytes < 0)) {
const SocketErrorMessage msg;
FormatWarning(httpd_output_domain,
@@ -193,11 +193,12 @@ HttpdClient::SendResponse()
return true;
}
-HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
+HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
bool _metadata_supported)
:BufferedSocket(_fd, _loop),
httpd(_httpd),
state(REQUEST),
+ queue_size(0),
head_method(false),
dlna_streaming_requested(false),
metadata_supported(_metadata_supported),
@@ -208,16 +209,24 @@ HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
{
}
-size_t
-HttpdClient::GetQueueSize() const
+void
+HttpdClient::ClearQueue()
{
- if (state != RESPONSE)
- return 0;
+ assert(state == RESPONSE);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
- size_t size = 0;
- for (auto page : pages)
- size += page->size;
- return size;
+#ifndef NDEBUG
+ assert(queue_size >= page->size);
+ queue_size -= page->size;
+#endif
+
+ page->Unref();
+ }
+
+ assert(queue_size == 0);
}
void
@@ -226,9 +235,7 @@ HttpdClient::CancelQueue()
if (state != RESPONSE)
return;
- for (auto page : pages)
- page->Unref();
- pages.clear();
+ ClearQueue();
if (current_page == nullptr)
CancelWrite();
@@ -263,7 +270,7 @@ HttpdClient::GetBytesTillMetaData() const
inline bool
HttpdClient::TryWrite()
{
- const ScopeLock protect(httpd->mutex);
+ const ScopeLock protect(httpd.mutex);
assert(state == RESPONSE);
@@ -271,14 +278,17 @@ HttpdClient::TryWrite()
if (pages.empty()) {
/* another thread has removed the event source
while this thread was waiting for
- httpd->mutex */
+ httpd.mutex */
CancelWrite();
return true;
}
current_page = pages.front();
- pages.pop_front();
+ pages.pop();
current_position = 0;
+
+ assert(queue_size >= current_page->size);
+ queue_size -= current_page->size;
}
const ssize_t bytes_to_write = GetBytesTillMetaData();
@@ -310,7 +320,7 @@ HttpdClient::TryWrite()
metadata_sent = true;
}
} else {
- guchar empty_data = 0;
+ char empty_data = 0;
ssize_t nbytes = Write(&empty_data, 1);
if (nbytes < 0) {
@@ -379,8 +389,15 @@ HttpdClient::PushPage(Page *page)
/* the client is still writing the HTTP request */
return;
+ if (queue_size > 256 * 1024) {
+ FormatDebug(httpd_output_domain,
+ "client is too slow, flushing its queue");
+ ClearQueue();
+ }
+
page->Ref();
- pages.push_back(page);
+ pages.push(page);
+ queue_size += page->size;
ScheduleWrite();
}
@@ -388,13 +405,13 @@ HttpdClient::PushPage(Page *page)
void
HttpdClient::PushMetaData(Page *page)
{
+ assert(page != nullptr);
+
if (metadata) {
metadata->Unref();
metadata = nullptr;
}
- g_return_if_fail (page);
-
page->Ref();
metadata = page;
metadata_sent = false;
diff --git a/src/output/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx
index 66a819232..f94f05769 100644
--- a/src/output/HttpdClient.hxx
+++ b/src/output/plugins/httpd/HttpdClient.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,18 +23,19 @@
#include "event/BufferedSocket.hxx"
#include "Compiler.h"
+#include <queue>
#include <list>
#include <stddef.h>
-struct HttpdOutput;
+class HttpdOutput;
class Page;
-class HttpdClient final : public BufferedSocket {
+class HttpdClient final : BufferedSocket {
/**
* The httpd output object this client is connected to.
*/
- HttpdOutput *const httpd;
+ HttpdOutput &httpd;
/**
* The current state of the client.
@@ -53,7 +54,12 @@ class HttpdClient final : public BufferedSocket {
/**
* A queue of #Page objects to be sent to the client.
*/
- std::list<Page *> pages;
+ std::queue<Page *, std::list<Page *>> pages;
+
+ /**
+ * The sum of all page sizes in #pages.
+ */
+ size_t queue_size;
/**
* The #page which is currently being sent to the client.
@@ -120,7 +126,7 @@ public:
* @param httpd the HTTP output device
* @param fd the socket file descriptor
*/
- HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop,
+ HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
bool _metadata_supported);
/**
@@ -137,12 +143,6 @@ public:
void LockClose();
/**
- * Returns the total size of this client's page queue.
- */
- gcc_pure
- size_t GetQueueSize() const;
-
- /**
* Clears the page queue.
*/
void CancelQueue();
@@ -180,6 +180,9 @@ public:
*/
void PushMetaData(Page *page);
+private:
+ void ClearQueue();
+
protected:
virtual bool OnSocketReady(unsigned flags) override;
virtual InputResult OnSocketInput(void *data, size_t length) override;
diff --git a/src/output/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
index b76493a44..20ff15e42 100644
--- a/src/output/HttpdInternal.hxx
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -25,10 +25,13 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
-#include "OutputInternal.hxx"
-#include "Timer.hxx"
+#include "output/Internal.hxx"
+#include "output/Timer.hxx"
#include "thread/Mutex.hxx"
#include "event/ServerSocket.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Cast.hxx"
+#include "Compiler.h"
#ifdef _LIBCPP_VERSION
/* can't use incomplete template arguments with libc++ */
@@ -36,6 +39,8 @@
#endif
#include <forward_list>
+#include <queue>
+#include <list>
struct config_param;
class Error;
@@ -46,8 +51,8 @@ class Page;
struct Encoder;
struct Tag;
-struct HttpdOutput final : private ServerSocket {
- struct audio_output base;
+class HttpdOutput final : ServerSocket, DeferredMonitor {
+ AudioOutput base;
/**
* True if the audio output is open and accepts client
@@ -68,6 +73,7 @@ struct HttpdOutput final : private ServerSocket {
*/
size_t unflushed_input;
+public:
/**
* The MIME type produced by the #encoder.
*/
@@ -80,6 +86,13 @@ struct HttpdOutput final : private ServerSocket {
mutable Mutex mutex;
/**
+ * This condition gets signalled when an item is removed from
+ * #pages.
+ */
+ Cond cond;
+
+private:
+ /**
* A #Timer object to synchronize this output with the
* wallclock.
*/
@@ -96,6 +109,15 @@ struct HttpdOutput final : private ServerSocket {
Page *metadata;
/**
+ * The page queue, i.e. pages from the encoder to be
+ * broadcasted to all clients. This container is necessary to
+ * pass pages from the OutputThread to the IOThread. It is
+ * protected by #mutex, and removing signals #cond.
+ */
+ std::queue<Page *, std::list<Page *>> pages;
+
+ public:
+ /**
* The configured name.
*/
char const *name;
@@ -108,6 +130,7 @@ struct HttpdOutput final : private ServerSocket {
*/
char const *website;
+private:
/**
* A linked list containing all clients which are currently
* connected.
@@ -126,11 +149,34 @@ struct HttpdOutput final : private ServerSocket {
*/
unsigned clients_max, clients_cnt;
+public:
HttpdOutput(EventLoop &_loop);
~HttpdOutput();
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+ constexpr
+#endif
+ static HttpdOutput *Cast(AudioOutput *ao) {
+ return &ContainerCast(*ao, &HttpdOutput::base);
+ }
+
+ using DeferredMonitor::GetEventLoop;
+
+ bool Init(const config_param &param, Error &error);
+
bool Configure(const config_param &param, Error &error);
+ AudioOutput *InitAndConfigure(const config_param &param,
+ Error &error) {
+ if (!Init(param, error))
+ return nullptr;
+
+ if (!Configure(param, error))
+ return nullptr;
+
+ return &base;
+ }
+
bool Bind(Error &error);
void Unbind();
@@ -181,6 +227,9 @@ struct HttpdOutput final : private ServerSocket {
*/
void SendHeader(HttpdClient &client) const;
+ gcc_pure
+ unsigned Delay() const;
+
/**
* Reads data from the encoder (as much as available) and
* returns it as a new #page object.
@@ -203,7 +252,13 @@ struct HttpdOutput final : private ServerSocket {
void SendTag(const Tag *tag);
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void CancelAllClients();
+
private:
+ virtual void RunDeferred() override;
+
virtual void OnAccept(int fd, const sockaddr &address,
size_t address_length, int uid) override;
};
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
index 369c06937..e3ba7727d 100644
--- a/src/output/HttpdOutputPlugin.cxx
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,20 +21,19 @@
#include "HttpdOutputPlugin.hxx"
#include "HttpdInternal.hxx"
#include "HttpdClient.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
+#include "output/OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
#include "system/Resolver.hxx"
#include "Page.hxx"
#include "IcyMetaDataServer.hxx"
#include "system/fd_util.h"
-#include "Main.hxx"
+#include "IOThread.hxx"
+#include "event/Call.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <sys/types.h>
@@ -51,7 +50,8 @@ const Domain httpd_output_domain("httpd_output");
inline
HttpdOutput::HttpdOutput(EventLoop &_loop)
- :ServerSocket(_loop),
+ :ServerSocket(_loop), DeferredMonitor(_loop),
+ base(httpd_output_plugin),
encoder(nullptr), unflushed_input(0),
metadata(nullptr)
{
@@ -72,8 +72,11 @@ HttpdOutput::Bind(Error &error)
{
open = false;
- const ScopeLock protect(mutex);
- return ServerSocket::Open(error);
+ bool result = false;
+ BlockingCall(GetEventLoop(), [this, &error, &result](){
+ result = ServerSocket::Open(error);
+ });
+ return result;
}
inline void
@@ -81,8 +84,9 @@ HttpdOutput::Unbind()
{
assert(!open);
- const ScopeLock protect(mutex);
- ServerSocket::Close();
+ BlockingCall(GetEventLoop(), [this](){
+ ServerSocket::Close();
+ });
}
inline bool
@@ -130,47 +134,29 @@ HttpdOutput::Configure(const config_param &param, Error &error)
return true;
}
-static struct audio_output *
-httpd_output_init(const config_param &param, Error &error)
+inline bool
+HttpdOutput::Init(const config_param &param, Error &error)
{
- HttpdOutput *httpd = new HttpdOutput(*main_loop);
+ return base.Configure(param, error);
+}
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param,
- error)) {
- delete httpd;
- return nullptr;
- }
+static AudioOutput *
+httpd_output_init(const config_param &param, Error &error)
+{
+ HttpdOutput *httpd = new HttpdOutput(io_thread_get());
- if (!httpd->Configure(param, error)) {
- ao_base_finish(&httpd->base);
+ AudioOutput *result = httpd->InitAndConfigure(param, error);
+ if (result == nullptr)
delete httpd;
- return nullptr;
- }
-
- return &httpd->base;
-}
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Winvalid-offsetof"
-#endif
-
-static inline constexpr HttpdOutput *
-Cast(audio_output *ao)
-{
- return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base));
+ return result;
}
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-
static void
-httpd_output_finish(struct audio_output *ao)
+httpd_output_finish(AudioOutput *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
- ao_base_finish(&httpd->base);
delete httpd;
}
@@ -181,7 +167,7 @@ httpd_output_finish(struct audio_output *ao)
inline void
HttpdOutput::AddClient(int fd)
{
- clients.emplace_front(this, fd, GetEventLoop(),
+ clients.emplace_front(*this, fd, GetEventLoop(),
encoder->plugin.tag == nullptr);
++clients_cnt;
@@ -191,6 +177,29 @@ HttpdOutput::AddClient(int fd)
}
void
+HttpdOutput::RunDeferred()
+{
+ /* this method runs in the IOThread; it broadcasts pages from
+ our own queue to all clients */
+
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+ for (auto &client : clients)
+ client.PushPage(page);
+
+ page->Unref();
+ }
+
+ /* wake up the client that may be waiting for the queue to be
+ flushed */
+ cond.broadcast();
+}
+
+void
HttpdOutput::OnAccept(int fd, const sockaddr &address,
size_t address_length, gcc_unused int uid)
{
@@ -199,9 +208,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
#ifdef HAVE_LIBWRAP
if (address.sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(&address, address_length,
- IgnoreError());
- const char *progname = g_get_prgname();
+ const auto hostaddr = sockaddr_to_string(&address,
+ address_length);
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
struct request_info req;
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
@@ -212,13 +222,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
/* tcp wrappers says no */
FormatWarning(httpd_output_domain,
"libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
- g_free(hostaddr);
+ progname, hostaddr.c_str());
close_socket(fd);
return;
}
-
- g_free(hostaddr);
}
#else
(void)address;
@@ -269,17 +276,17 @@ HttpdOutput::ReadPage()
}
static bool
-httpd_output_enable(struct audio_output *ao, Error &error)
+httpd_output_enable(AudioOutput *ao, Error &error)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
return httpd->Bind(error);
}
static void
-httpd_output_disable(struct audio_output *ao)
+httpd_output_disable(AudioOutput *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
httpd->Unbind();
}
@@ -322,12 +329,10 @@ HttpdOutput::Open(AudioFormat &audio_format, Error &error)
}
static bool
-httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
+httpd_output_open(AudioOutput *ao, AudioFormat &audio_format,
Error &error)
{
- HttpdOutput *httpd = Cast(ao);
-
- assert(httpd->clients.empty());
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
const ScopeLock protect(httpd->mutex);
return httpd->Open(audio_format, error);
@@ -342,7 +347,9 @@ HttpdOutput::Close()
delete timer;
- clients.clear();
+ BlockingCall(GetEventLoop(), [this](){
+ clients.clear();
+ });
if (header != nullptr)
header->Unref();
@@ -351,9 +358,9 @@ HttpdOutput::Close()
}
static void
-httpd_output_close(struct audio_output *ao)
+httpd_output_close(AudioOutput *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
const ScopeLock protect(httpd->mutex);
httpd->Close();
@@ -382,17 +389,15 @@ HttpdOutput::SendHeader(HttpdClient &client) const
client.PushPage(header);
}
-static unsigned
-httpd_output_delay(struct audio_output *ao)
+inline unsigned
+HttpdOutput::Delay() const
{
- HttpdOutput *httpd = Cast(ao);
-
- if (!httpd->LockHasClients() && httpd->base.pause) {
+ if (!LockHasClients() && base.pause) {
/* if there's no client and this output is paused,
then httpd_output_pause() will not do anything, it
will not fill the buffer and it will not update the
timer; therefore, we reset the timer here */
- httpd->timer->Reset();
+ timer->Reset();
/* some arbitrary delay that is long enough to avoid
consuming too much CPU, and short enough to notice
@@ -400,39 +405,47 @@ httpd_output_delay(struct audio_output *ao)
return 1000;
}
- return httpd->timer->IsStarted()
- ? httpd->timer->GetDelay()
+ return timer->IsStarted()
+ ? timer->GetDelay()
: 0;
}
+static unsigned
+httpd_output_delay(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Delay();
+}
+
void
HttpdOutput::BroadcastPage(Page *page)
{
assert(page != nullptr);
- const ScopeLock protect(mutex);
- for (auto &client : clients)
- client.PushPage(page);
+ mutex.lock();
+ pages.push(page);
+ page->Ref();
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
}
void
HttpdOutput::BroadcastFromEncoder()
{
+ /* synchronize with the IOThread */
mutex.lock();
- for (auto &client : clients) {
- if (client.GetQueueSize() > 256 * 1024) {
- FormatDebug(httpd_output_domain,
- "client is too slow, flushing its queue");
- client.CancelQueue();
- }
- }
- mutex.unlock();
+ while (!pages.empty())
+ cond.wait(mutex);
Page *page;
- while ((page = ReadPage()) != nullptr) {
- BroadcastPage(page);
- page->Unref();
- }
+ while ((page = ReadPage()) != nullptr)
+ pages.push(page);
+
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
}
inline bool
@@ -447,28 +460,34 @@ HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
return true;
}
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
+inline size_t
+HttpdOutput::Play(const void *chunk, size_t size, Error &error)
{
- HttpdOutput *httpd = Cast(ao);
-
- if (httpd->LockHasClients()) {
- if (!httpd->EncodeAndPlay(chunk, size, error))
+ if (LockHasClients()) {
+ if (!EncodeAndPlay(chunk, size, error))
return 0;
}
- if (!httpd->timer->IsStarted())
- httpd->timer->Start();
- httpd->timer->Add(size);
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
return size;
}
+static size_t
+httpd_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Play(chunk, size, error);
+}
+
static bool
-httpd_output_pause(struct audio_output *ao)
+httpd_output_pause(AudioOutput *ao)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
if (httpd->LockHasClients()) {
static const char silence[1020] = { 0 };
@@ -529,24 +548,41 @@ HttpdOutput::SendTag(const Tag *tag)
}
static void
-httpd_output_tag(struct audio_output *ao, const Tag *tag)
+httpd_output_tag(AudioOutput *ao, const Tag *tag)
{
- HttpdOutput *httpd = Cast(ao);
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
httpd->SendTag(tag);
}
-static void
-httpd_output_cancel(struct audio_output *ao)
+inline void
+HttpdOutput::CancelAllClients()
{
- HttpdOutput *httpd = Cast(ao);
+ const ScopeLock protect(mutex);
- const ScopeLock protect(httpd->mutex);
- for (auto &client : httpd->clients)
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+ page->Unref();
+ }
+
+ for (auto &client : clients)
client.CancelQueue();
+
+ cond.broadcast();
+}
+
+static void
+httpd_output_cancel(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ BlockingCall(io_thread_get(), [httpd](){
+ httpd->CancelAllClients();
+ });
}
-const struct audio_output_plugin httpd_output_plugin = {
+const struct AudioOutputPlugin httpd_output_plugin = {
"httpd",
nullptr,
httpd_output_init,
diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
index c74d2bd4a..df99e2b43 100644
--- a/src/output/HttpdOutputPlugin.hxx
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,6 @@
#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX
#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
-extern const struct audio_output_plugin httpd_output_plugin;
+extern const struct AudioOutputPlugin httpd_output_plugin;
#endif
diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx
new file mode 100644
index 000000000..146df23d1
--- /dev/null
+++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx
@@ -0,0 +1,134 @@
+/*
+ * 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 "IcyMetaDataServer.hxx"
+#include "Page.hxx"
+#include "tag/Tag.hxx"
+#include "util/FormatString.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+char*
+icy_server_metadata_header(const char *name,
+ const char *genre, const char *url,
+ const char *content_type, int metaint)
+{
+ return FormatNew("ICY 200 OK\r\n"
+ "icy-notice1:<BR>This stream requires an audio player!<BR>\r\n" /* TODO */
+ "icy-notice2:MPD - The music player daemon<BR>\r\n"
+ "icy-name: %s\r\n" /* TODO */
+ "icy-genre: %s\r\n" /* TODO */
+ "icy-url: %s\r\n" /* TODO */
+ "icy-pub:1\r\n"
+ "icy-metaint:%d\r\n"
+ /* TODO "icy-br:%d\r\n" */
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ name,
+ genre,
+ url,
+ metaint,
+ /* bitrate, */
+ content_type);
+}
+
+static char *
+icy_server_metadata_string(const char *stream_title, const char* stream_url)
+{
+ gchar *icy_metadata;
+ guint meta_length;
+
+ // The leading n is a placeholder for the length information
+ icy_metadata = FormatNew("nStreamTitle='%s';"
+ "StreamUrl='%s';",
+ stream_title,
+ stream_url);
+
+ meta_length = strlen(icy_metadata);
+
+ meta_length--; // subtract placeholder
+
+ meta_length = ((int)meta_length / 16) + 1;
+
+ icy_metadata[0] = meta_length;
+
+ if (meta_length > 255) {
+ delete[] icy_metadata;
+ return nullptr;
+ }
+
+ return icy_metadata;
+}
+
+Page *
+icy_server_metadata_page(const Tag &tag, const TagType *types)
+{
+ const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
+ gint last_item, item;
+ guint position;
+ gchar *icy_string;
+ gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
+ // "StreamTitle='';StreamUrl='';"
+ // = 4081 - 28
+ stream_title[0] = '\0';
+
+ last_item = -1;
+
+ while (*types != TAG_NUM_OF_ITEM_TYPES) {
+ const gchar *tag_item = tag.GetValue(*types++);
+ if (tag_item)
+ tag_items[++last_item] = tag_item;
+ }
+
+ position = item = 0;
+ while (position < sizeof(stream_title) && item <= last_item) {
+ gint length = 0;
+
+ length = g_strlcpy(stream_title + position,
+ tag_items[item++],
+ sizeof(stream_title) - position);
+
+ position += length;
+
+ if (item <= last_item) {
+ length = g_strlcpy(stream_title + position,
+ " - ",
+ sizeof(stream_title) - position);
+
+ position += length;
+ }
+ }
+
+ icy_string = icy_server_metadata_string(stream_title, "");
+
+ if (icy_string == nullptr)
+ return nullptr;
+
+ Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1);
+
+ delete[] icy_string;
+
+ return icy_metadata;
+}
diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx
new file mode 100644
index 000000000..773b46641
--- /dev/null
+++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx
@@ -0,0 +1,39 @@
+/*
+ * 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_ICY_META_DATA_SERVER_HXX
+#define MPD_ICY_META_DATA_SERVER_HXX
+
+#include "tag/TagType.h"
+
+struct Tag;
+class Page;
+
+/**
+ * Free the return value with delete[].
+ */
+char*
+icy_server_metadata_header(const char *name,
+ const char *genre, const char *url,
+ const char *content_type, int metaint);
+
+Page *
+icy_server_metadata_page(const Tag &tag, const TagType *types);
+
+#endif
diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx
new file mode 100644
index 000000000..e22134bbc
--- /dev/null
+++ b/src/output/plugins/httpd/Page.cxx
@@ -0,0 +1,70 @@
+/*
+ * 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 "Page.hxx"
+#include "util/Alloc.hxx"
+
+#include <new>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+Page *
+Page::Create(size_t size)
+{
+ void *p = xalloc(sizeof(Page) + size -
+ sizeof(Page::data));
+ return ::new(p) Page(size);
+}
+
+Page *
+Page::Copy(const void *data, size_t size)
+{
+ assert(data != nullptr);
+
+ Page *page = Create(size);
+ memcpy(page->data, data, size);
+ return page;
+}
+
+Page *
+Page::Concat(const Page &a, const Page &b)
+{
+ Page *page = Create(a.size + b.size);
+
+ memcpy(page->data, a.data, a.size);
+ memcpy(page->data + a.size, b.data, b.size);
+
+ return page;
+}
+
+bool
+Page::Unref()
+{
+ bool unused = ref.Decrement();
+
+ if (unused) {
+ this->Page::~Page();
+ free(this);
+ }
+
+ return unused;
+}
diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx
new file mode 100644
index 000000000..95f35d06a
--- /dev/null
+++ b/src/output/plugins/httpd/Page.hxx
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This is a library which manages reference counted buffers.
+ */
+
+#ifndef MPD_PAGE_HXX
+#define MPD_PAGE_HXX
+
+#include "util/RefCount.hxx"
+
+#include <stddef.h>
+
+/**
+ * A dynamically allocated buffer which keeps track of its reference
+ * count. This is useful for passing buffers around, when several
+ * instances hold references to one buffer.
+ */
+class Page {
+ /**
+ * The number of references to this buffer. This library uses
+ * atomic functions to access it, i.e. no locks are required.
+ * As soon as this attribute reaches zero, the buffer is
+ * freed.
+ */
+ RefCount ref;
+
+public:
+ /**
+ * The size of this buffer in bytes.
+ */
+ const size_t size;
+
+ /**
+ * Dynamic array containing the buffer data.
+ */
+ unsigned char data[sizeof(long)];
+
+protected:
+ Page(size_t _size):size(_size) {}
+ ~Page() = default;
+
+ /**
+ * Allocates a new #Page object, without filling the data
+ * element.
+ */
+ static Page *Create(size_t size);
+
+public:
+ /**
+ * Creates a new #page object, and copies data from the
+ * specified buffer. It is initialized with a reference count
+ * of 1.
+ *
+ * @param data the source buffer
+ * @param size the size of the source buffer
+ */
+ static Page *Copy(const void *data, size_t size);
+
+ /**
+ * Concatenates two pages to a new page.
+ *
+ * @param a the first page
+ * @param b the second page, which is appended
+ */
+ static Page *Concat(const Page &a, const Page &b);
+
+ /**
+ * Increases the reference counter.
+ */
+ void Ref() {
+ ref.Increment();
+ }
+
+ /**
+ * Decreases the reference counter. If it reaches zero, the #page is
+ * freed.
+ *
+ * @return true if the #page has been freed
+ */
+ bool Unref();
+};
+
+#endif
diff --git a/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
new file mode 100644
index 000000000..c7dd4ccca
--- /dev/null
+++ b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+#define SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+
+#include <SLES/OpenSLES_Android.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLAndroidSimpleBufferQueueItf
+ * variable.
+ */
+ class AndroidSimpleBufferQueue {
+ SLAndroidSimpleBufferQueueItf queue;
+
+ public:
+ AndroidSimpleBufferQueue() = default;
+ explicit AndroidSimpleBufferQueue(SLAndroidSimpleBufferQueueItf _queue)
+ :queue(_queue) {}
+
+ SLresult Enqueue(const void *pBuffer, SLuint32 size) {
+ return (*queue)->Enqueue(queue, pBuffer, size);
+ }
+
+ SLresult Clear() {
+ return (*queue)->Clear(queue);
+ }
+
+ SLresult GetState(SLAndroidSimpleBufferQueueState *pState) {
+ return (*queue)->GetState(queue, pState);
+ }
+
+ SLresult RegisterCallback(slAndroidSimpleBufferQueueCallback callback,
+ void *pContext) {
+ return (*queue)->RegisterCallback(queue, callback, pContext);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Engine.hxx b/src/output/plugins/sles/Engine.hxx
new file mode 100644
index 000000000..7c6e3cf50
--- /dev/null
+++ b/src/output/plugins/sles/Engine.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ENGINE_HPP
+#define SLES_ENGINE_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLEngineItf variable.
+ */
+ class Engine {
+ SLEngineItf engine;
+
+ public:
+ Engine() = default;
+ explicit Engine(SLEngineItf _engine):engine(_engine) {}
+
+ SLresult CreateAudioPlayer(SLObjectItf *pPlayer,
+ SLDataSource *pAudioSrc, SLDataSink *pAudioSnk,
+ SLuint32 numInterfaces,
+ const SLInterfaceID *pInterfaceIds,
+ const SLboolean *pInterfaceRequired) {
+ return (*engine)->CreateAudioPlayer(engine, pPlayer,
+ pAudioSrc, pAudioSnk,
+ numInterfaces, pInterfaceIds,
+ pInterfaceRequired);
+ }
+
+ SLresult CreateOutputMix(SLObjectItf *pMix,
+ SLuint32 numInterfaces,
+ const SLInterfaceID *pInterfaceIds,
+ const SLboolean *pInterfaceRequired) {
+ return (*engine)->CreateOutputMix(engine, pMix,
+ numInterfaces, pInterfaceIds,
+ pInterfaceRequired);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Object.hxx b/src/output/plugins/sles/Object.hxx
new file mode 100644
index 000000000..852d62d0d
--- /dev/null
+++ b/src/output/plugins/sles/Object.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_OBJECT_HPP
+#define SLES_OBJECT_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLObjectItf variable.
+ */
+ class Object {
+ SLObjectItf object;
+
+ public:
+ Object() = default;
+ explicit Object(SLObjectItf _object):object(_object) {}
+
+ operator SLObjectItf() {
+ return object;
+ }
+
+ SLresult Realize(bool async) {
+ return (*object)->Realize(object, async);
+ }
+
+ void Destroy() {
+ (*object)->Destroy(object);
+ }
+
+ SLresult GetInterface(const SLInterfaceID iid, void *pInterface) {
+ return (*object)->GetInterface(object, iid, pInterface);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Play.hxx b/src/output/plugins/sles/Play.hxx
new file mode 100644
index 000000000..c760151ef
--- /dev/null
+++ b/src/output/plugins/sles/Play.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_PLAY_HPP
+#define SLES_PLAY_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLPlayItf variable.
+ */
+ class Play {
+ SLPlayItf play;
+
+ public:
+ Play() = default;
+ explicit Play(SLPlayItf _play):play(_play) {}
+
+ SLresult SetPlayState(SLuint32 state) {
+ return (*play)->SetPlayState(play, state);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
new file mode 100644
index 000000000..85fd9f2f2
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -0,0 +1,539 @@
+/*
+ * 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 "SlesOutputPlugin.hxx"
+#include "Object.hxx"
+#include "Engine.hxx"
+#include "Play.hxx"
+#include "AndroidSimpleBufferQueue.hxx"
+#include "../../OutputAPI.hxx"
+#include "util/Macros.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+class SlesOutput {
+ static constexpr unsigned N_BUFFERS = 3;
+ static constexpr size_t BUFFER_SIZE = 65536;
+
+ AudioOutput base;
+
+ SLES::Object engine_object, mix_object, play_object;
+ SLES::Play play;
+ SLES::AndroidSimpleBufferQueue queue;
+
+ /**
+ * This mutex protects the attributes "next" and "filled". It
+ * is only needed while playback is launched, when the initial
+ * buffers are being enqueued in the caller thread, while
+ * another thread may invoke the registered callback.
+ */
+ Mutex mutex;
+
+ Cond cond;
+
+ bool pause, cancel;
+
+ /**
+ * The number of buffers queued to OpenSLES.
+ */
+ unsigned n_queued;
+
+ /**
+ * The index of the next buffer to be enqueued.
+ */
+ unsigned next;
+
+ /**
+ * Does the "next" buffer already contain synthesised samples?
+ * This can happen when PCMSynthesiser::Synthesise() has been
+ * called, but the OpenSL/ES buffer queue was full. The
+ * buffer will then be postponed.
+ */
+ unsigned filled;
+
+ /**
+ * An array of buffers. It's one more than being managed by
+ * OpenSL/ES, and the one not enqueued (see attribute #next)
+ * will be written to.
+ */
+ uint8_t buffers[N_BUFFERS][BUFFER_SIZE];
+
+public:
+ SlesOutput()
+ :base(sles_output_plugin) {}
+
+ operator AudioOutput *() {
+ return &base;
+ }
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
+
+ unsigned Delay() {
+ return pause && !cancel ? 100 : 0;
+ }
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void Drain();
+ void Cancel();
+ bool Pause();
+
+private:
+ void PlayedCallback();
+
+ /**
+ * OpenSL/ES callback which gets invoked when a buffer has
+ * been consumed. It synthesises and enqueues the next
+ * buffer.
+ */
+ static void PlayedCallback(gcc_unused SLAndroidSimpleBufferQueueItf caller,
+ void *pContext)
+ {
+ SlesOutput &sles = *(SlesOutput *)pContext;
+ sles.PlayedCallback();
+ }
+};
+
+static constexpr Domain sles_domain("sles");
+
+inline bool
+SlesOutput::Configure(const config_param &, Error &)
+{
+ return true;
+}
+
+inline bool
+SlesOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ SLresult result;
+ SLObjectItf _object;
+
+ result = slCreateEngine(&_object, 0, nullptr, 0,
+ nullptr, nullptr);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result), "slCreateEngine() failed");
+ return false;
+ }
+
+ engine_object = SLES::Object(_object);
+
+ result = engine_object.Realize(false);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result), "Engine.Realize() failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLEngineItf _engine;
+ result = engine_object.GetInterface(SL_IID_ENGINE, &_engine);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.GetInterface(IID_ENGINE) failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLES::Engine engine(_engine);
+
+ result = engine.CreateOutputMix(&_object, 0, nullptr, nullptr);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.CreateOutputMix() failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ mix_object = SLES::Object(_object);
+
+ result = mix_object.Realize(false);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Mix.Realize() failed");
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ N_BUFFERS,
+ };
+
+ if (audio_format.channels > 2)
+ audio_format.channels = 1;
+
+ SLDataFormat_PCM format_pcm;
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = audio_format.channels;
+ /* from the Android NDK docs: "Note that the field samplesPerSec is
+ actually in units of milliHz, despite the misleading name." */
+ format_pcm.samplesPerSec = audio_format.sample_rate * 1000u;
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.channelMask = audio_format.channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ format_pcm.endianness = IsLittleEndian()
+ ? SL_BYTEORDER_LITTLEENDIAN
+ : SL_BYTEORDER_BIGENDIAN;
+
+ SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+
+ SLDataLocator_OutputMix loc_outmix = {
+ SL_DATALOCATOR_OUTPUTMIX,
+ mix_object,
+ };
+
+ SLDataSink audioSnk = {
+ &loc_outmix,
+ nullptr,
+ };
+
+ const SLInterfaceID ids2[] = {
+ SL_IID_PLAY,
+ SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION,
+ };
+
+ static constexpr SLboolean req2[] = {
+ SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE,
+ };
+
+ result = engine.CreateAudioPlayer(&_object, &audioSrc, &audioSnk,
+ ARRAY_SIZE(ids2), ids2, req2);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.CreateAudioPlayer() failed");
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ play_object = SLES::Object(_object);
+
+ SLAndroidConfigurationItf android_config;
+ if (play_object.GetInterface(SL_IID_ANDROIDCONFIGURATION,
+ &android_config) == SL_RESULT_SUCCESS) {
+ SLint32 stream_type = SL_ANDROID_STREAM_MEDIA;
+ (*android_config)->SetConfiguration(android_config,
+ SL_ANDROID_KEY_STREAM_TYPE,
+ &stream_type,
+ sizeof(stream_type));
+ }
+
+ result = play_object.Realize(false);
+
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.Realize() failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLPlayItf _play;
+ result = play_object.GetInterface(SL_IID_PLAY, &_play);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.GetInterface(IID_PLAY) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ play = SLES::Play(_play);
+
+ SLAndroidSimpleBufferQueueItf _queue;
+ result = play_object.GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &_queue);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.GetInterface(IID_ANDROIDSIMPLEBUFFERQUEUE) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ queue = SLES::AndroidSimpleBufferQueue(_queue);
+ result = queue.RegisterCallback(PlayedCallback, (void *)this);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.RegisterCallback() failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.SetPlayState(PLAYING) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ pause = cancel = false;
+ n_queued = 0;
+ next = 0;
+ filled = 0;
+
+ // TODO: support other sample formats
+ audio_format.format = SampleFormat::S16;
+
+ return true;
+}
+
+inline void
+SlesOutput::Close()
+{
+ play.SetPlayState(SL_PLAYSTATE_STOPPED);
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+}
+
+inline size_t
+SlesOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ cancel = false;
+
+ if (pause) {
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.SetPlayState(PLAYING) failed");
+ return false;
+ }
+
+ pause = false;
+ }
+
+ const ScopeLock protect(mutex);
+
+ assert(filled < BUFFER_SIZE);
+
+ while (n_queued == N_BUFFERS) {
+ assert(filled == 0);
+ cond.wait(mutex);
+ }
+
+ size_t nbytes = std::min(BUFFER_SIZE - filled, size);
+ memcpy(buffers[next] + filled, chunk, nbytes);
+ filled += nbytes;
+ if (filled < BUFFER_SIZE)
+ return nbytes;
+
+ SLresult result = queue.Enqueue(buffers[next], BUFFER_SIZE);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "AndroidSimpleBufferQueue.Enqueue() failed");
+ return 0;
+ }
+
+ ++n_queued;
+ next = (next + 1) % N_BUFFERS;
+ filled = 0;
+
+ return nbytes;
+}
+
+inline void
+SlesOutput::Drain()
+{
+ const ScopeLock protect(mutex);
+
+ assert(filled < BUFFER_SIZE);
+
+ while (n_queued > 0)
+ cond.wait(mutex);
+}
+
+inline void
+SlesOutput::Cancel()
+{
+ pause = true;
+ cancel = true;
+
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+ if (result != SL_RESULT_SUCCESS)
+ FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed");
+
+ result = queue.Clear();
+ if (result != SL_RESULT_SUCCESS)
+ FormatWarning(sles_domain,
+ "AndroidSimpleBufferQueue.Clear() failed");
+
+ const ScopeLock protect(mutex);
+ n_queued = 0;
+ filled = 0;
+}
+
+inline bool
+SlesOutput::Pause()
+{
+ cancel = false;
+
+ if (pause)
+ return true;
+
+ pause = true;
+
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+ if (result != SL_RESULT_SUCCESS) {
+ FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed");
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+SlesOutput::PlayedCallback()
+{
+ const ScopeLock protect(mutex);
+ assert(n_queued > 0);
+ --n_queued;
+ cond.signal();
+}
+
+static bool
+sles_test_default_device()
+{
+ /* this is the default output plugin on Android, and it should
+ be available in any case */
+ return true;
+}
+
+static AudioOutput *
+sles_output_init(const config_param &param, Error &error)
+{
+ SlesOutput *sles = new SlesOutput();
+
+ if (!sles->Initialize(param, error) ||
+ !sles->Configure(param, error)) {
+ delete sles;
+ return nullptr;
+ }
+
+ return *sles;
+}
+
+static void
+sles_output_finish(AudioOutput *ao)
+{
+ SlesOutput *sles = (SlesOutput *)ao;
+
+ delete sles;
+}
+
+static bool
+sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Open(audio_format, error);
+}
+
+static void
+sles_output_close(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Close();
+}
+
+static unsigned
+sles_output_delay(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Delay();
+}
+
+static size_t
+sles_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Play(chunk, size, error);
+}
+
+static void
+sles_output_drain(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Drain();
+}
+
+static void
+sles_output_cancel(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Cancel();
+}
+
+static bool
+sles_output_pause(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Pause();
+}
+
+const struct AudioOutputPlugin sles_output_plugin = {
+ "sles",
+ sles_test_default_device,
+ sles_output_init,
+ sles_output_finish,
+ nullptr,
+ nullptr,
+ sles_output_open,
+ sles_output_close,
+ sles_output_delay,
+ nullptr,
+ sles_output_play,
+ sles_output_drain,
+ sles_output_cancel,
+ sles_output_pause,
+ nullptr,
+};
diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx
new file mode 100644
index 000000000..5424dec2e
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.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_SLES_OUTPUT_PLUGIN_HXX
+#define MPD_SLES_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin sles_output_plugin;
+
+#endif