aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/output/AlsaOutputPlugin.cxx869
-rw-r--r--src/output/AlsaOutputPlugin.hxx25
-rw-r--r--src/output/AoOutputPlugin.cxx286
-rw-r--r--src/output/AoOutputPlugin.hxx25
-rw-r--r--src/output/Domain.cxx23
-rw-r--r--src/output/Domain.hxx25
-rw-r--r--src/output/FifoOutputPlugin.cxx316
-rw-r--r--src/output/FifoOutputPlugin.hxx25
-rw-r--r--src/output/Finish.cxx50
-rw-r--r--src/output/HttpdClient.cxx467
-rw-r--r--src/output/HttpdClient.hxx190
-rw-r--r--src/output/HttpdInternal.hxx213
-rw-r--r--src/output/HttpdOutputPlugin.cxx565
-rw-r--r--src/output/HttpdOutputPlugin.hxx25
-rw-r--r--src/output/Init.cxx335
-rw-r--r--src/output/Internal.hxx441
-rw-r--r--src/output/JackOutputPlugin.cxx769
-rw-r--r--src/output/JackOutputPlugin.hxx25
-rw-r--r--src/output/MultipleOutputs.cxx482
-rw-r--r--src/output/MultipleOutputs.hxx276
-rw-r--r--src/output/NullOutputPlugin.cxx143
-rw-r--r--src/output/NullOutputPlugin.hxx25
-rw-r--r--src/output/OSXOutputPlugin.cxx433
-rw-r--r--src/output/OSXOutputPlugin.hxx25
-rw-r--r--src/output/OpenALOutputPlugin.cxx285
-rw-r--r--src/output/OpenALOutputPlugin.hxx25
-rw-r--r--src/output/OssOutputPlugin.cxx780
-rw-r--r--src/output/OssOutputPlugin.hxx25
-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.cxx704
-rw-r--r--src/output/PipeOutputPlugin.cxx147
-rw-r--r--src/output/PipeOutputPlugin.hxx25
-rw-r--r--src/output/PulseOutputPlugin.cxx887
-rw-r--r--src/output/PulseOutputPlugin.hxx46
-rw-r--r--src/output/RecorderOutputPlugin.cxx262
-rw-r--r--src/output/RecorderOutputPlugin.hxx25
-rw-r--r--src/output/Registry.cxx104
-rw-r--r--src/output/Registry.hxx35
-rw-r--r--src/output/RoarOutputPlugin.cxx428
-rw-r--r--src/output/RoarOutputPlugin.hxx33
-rw-r--r--src/output/ShoutOutputPlugin.cxx544
-rw-r--r--src/output/ShoutOutputPlugin.hxx25
-rw-r--r--src/output/SolarisOutputPlugin.cxx201
-rw-r--r--src/output/SolarisOutputPlugin.hxx25
-rw-r--r--src/output/Timer.cxx67
-rw-r--r--src/output/Timer.hxx47
-rw-r--r--src/output/WinmmOutputPlugin.cxx353
-rw-r--r--src/output/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx895
-rw-r--r--src/output/plugins/AlsaOutputPlugin.hxx25
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx282
-rw-r--r--src/output/plugins/AoOutputPlugin.hxx25
-rw-r--r--src/output/plugins/FifoOutputPlugin.cxx307
-rw-r--r--src/output/plugins/FifoOutputPlugin.hxx25
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx762
-rw-r--r--src/output/plugins/JackOutputPlugin.hxx25
-rw-r--r--src/output/plugins/NullOutputPlugin.cxx138
-rw-r--r--src/output/plugins/NullOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OSXOutputPlugin.cxx431
-rw-r--r--src/output/plugins/OSXOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OpenALOutputPlugin.cxx282
-rw-r--r--src/output/plugins/OpenALOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx779
-rw-r--r--src/output/plugins/OssOutputPlugin.hxx25
-rw-r--r--src/output/plugins/PipeOutputPlugin.cxx143
-rw-r--r--src/output/plugins/PipeOutputPlugin.hxx25
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx882
-rw-r--r--src/output/plugins/PulseOutputPlugin.hxx46
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx258
-rw-r--r--src/output/plugins/RecorderOutputPlugin.hxx25
-rw-r--r--src/output/plugins/RoarOutputPlugin.cxx432
-rw-r--r--src/output/plugins/RoarOutputPlugin.hxx33
-rw-r--r--src/output/plugins/ShoutOutputPlugin.cxx537
-rw-r--r--src/output/plugins/ShoutOutputPlugin.hxx25
-rw-r--r--src/output/plugins/SolarisOutputPlugin.cxx198
-rw-r--r--src/output/plugins/SolarisOutputPlugin.hxx25
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx352
-rw-r--r--src/output/plugins/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/plugins/httpd/HttpdClient.cxx484
-rw-r--r--src/output/plugins/httpd/HttpdClient.hxx193
-rw-r--r--src/output/plugins/httpd/HttpdInternal.hxx268
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.cxx601
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.hxx25
-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
103 files changed, 13455 insertions, 8584 deletions
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
deleted file mode 100644
index f8aae13a1..000000000
--- a/src/output/AlsaOutputPlugin.cxx
+++ /dev/null
@@ -1,869 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AlsaOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "pcm/PcmExport.hxx"
-#include "util/Manual.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <alsa/asoundlib.h>
-
-#include <string>
-
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-
-static const char default_device[] = "default";
-
-static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
-
-#define MPD_ALSA_RETRY_NR 5
-
-typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
- snd_pcm_uframes_t size);
-
-struct AlsaOutput {
- struct audio_output base;
-
- Manual<PcmExport> pcm_export;
-
- /**
- * The configured name of the ALSA device; empty for the
- * default device
- */
- std::string device;
-
- /** use memory mapped I/O? */
- bool use_mmap;
-
- /**
- * Enable DSD over USB according to the dCS suggested
- * standard?
- *
- * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
- */
- bool dsd_usb;
-
- /** libasound's buffer_time setting (in microseconds) */
- unsigned int buffer_time;
-
- /** libasound's period_time setting (in microseconds) */
- unsigned int period_time;
-
- /** the mode flags passed to snd_pcm_open */
- int mode;
-
- /** the libasound PCM device handle */
- snd_pcm_t *pcm;
-
- /**
- * a pointer to the libasound writei() function, which is
- * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
- * use_mmap configuration
- */
- alsa_writei_t *writei;
-
- /**
- * The size of one audio frame passed to method play().
- */
- size_t in_frame_size;
-
- /**
- * The size of one audio frame passed to libasound.
- */
- size_t out_frame_size;
-
- /**
- * The size of one period, in number of frames.
- */
- snd_pcm_uframes_t period_frames;
-
- /**
- * The number of frames written in the current period.
- */
- snd_pcm_uframes_t period_position;
-
- /**
- * Do we need to call snd_pcm_prepare() before the next write?
- * It means that we put the device to SND_PCM_STATE_SETUP by
- * calling snd_pcm_drop().
- *
- * Without this flag, we could easily recover after a failed
- * optimistic write (returning -EBADFD), but the Raspberry Pi
- * audio driver is infamous for generating ugly artefacts from
- * this.
- */
- bool must_prepare;
-
- /**
- * This buffer gets allocated after opening the ALSA device.
- * It contains silence samples, enough to fill one period (see
- * #period_frames).
- */
- void *silence;
-
- AlsaOutput():mode(0), writei(snd_pcm_writei) {
- }
-
- bool Init(const config_param &param, Error &error) {
- return ao_base_init(&base, &alsa_output_plugin,
- param, error);
- }
-
- void Deinit() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain alsa_output_domain("alsa_output");
-
-static const char *
-alsa_device(const AlsaOutput *ad)
-{
- return ad->device.empty() ? default_device : ad->device.c_str();
-}
-
-static void
-alsa_configure(AlsaOutput *ad, const config_param &param)
-{
- ad->device = param.GetBlockValue("device", "");
-
- ad->use_mmap = param.GetBlockValue("use_mmap", false);
-
- ad->dsd_usb = param.GetBlockValue("dsd_usb", false);
-
- ad->buffer_time = param.GetBlockValue("buffer_time",
- MPD_ALSA_BUFFER_TIME_US);
- ad->period_time = param.GetBlockValue("period_time", 0u);
-
-#ifdef SND_PCM_NO_AUTO_RESAMPLE
- if (!param.GetBlockValue("auto_resample", true))
- ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_CHANNELS
- if (!param.GetBlockValue("auto_channels", true))
- ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_FORMAT
- if (!param.GetBlockValue("auto_format", true))
- ad->mode |= SND_PCM_NO_AUTO_FORMAT;
-#endif
-}
-
-static struct audio_output *
-alsa_init(const config_param &param, Error &error)
-{
- AlsaOutput *ad = new AlsaOutput();
-
- if (!ad->Init(param, error)) {
- delete ad;
- return nullptr;
- }
-
- alsa_configure(ad, param);
-
- return &ad->base;
-}
-
-static void
-alsa_finish(struct audio_output *ao)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- ad->Deinit();
- delete ad;
-
- /* free libasound's config cache */
- snd_config_update_free_global();
-}
-
-static bool
-alsa_output_enable(struct audio_output *ao, gcc_unused Error &error)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- ad->pcm_export.Construct();
- return true;
-}
-
-static void
-alsa_output_disable(struct audio_output *ao)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- ad->pcm_export.Destruct();
-}
-
-static bool
-alsa_test_default_device(void)
-{
- snd_pcm_t *handle;
-
- int ret = snd_pcm_open(&handle, default_device,
- SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
- if (ret) {
- FormatError(alsa_output_domain,
- "Error opening default ALSA device: %s",
- snd_strerror(-ret));
- return false;
- } else
- snd_pcm_close(handle);
-
- return true;
-}
-
-static snd_pcm_format_t
-get_bitformat(SampleFormat sample_format)
-{
- switch (sample_format) {
- case SampleFormat::UNDEFINED:
- case SampleFormat::DSD:
- return SND_PCM_FORMAT_UNKNOWN;
-
- case SampleFormat::S8:
- return SND_PCM_FORMAT_S8;
-
- case SampleFormat::S16:
- return SND_PCM_FORMAT_S16;
-
- case SampleFormat::S24_P32:
- return SND_PCM_FORMAT_S24;
-
- case SampleFormat::S32:
- return SND_PCM_FORMAT_S32;
-
- case SampleFormat::FLOAT:
- return SND_PCM_FORMAT_FLOAT;
- }
-
- assert(false);
- gcc_unreachable();
-}
-
-static snd_pcm_format_t
-byteswap_bitformat(snd_pcm_format_t fmt)
-{
- switch(fmt) {
- case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
- case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
- case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
- case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
- case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
-
- case SND_PCM_FORMAT_S24_3BE:
- return SND_PCM_FORMAT_S24_3LE;
-
- case SND_PCM_FORMAT_S24_3LE:
- return SND_PCM_FORMAT_S24_3BE;
-
- case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
- default: return SND_PCM_FORMAT_UNKNOWN;
- }
-}
-
-static snd_pcm_format_t
-alsa_to_packed_format(snd_pcm_format_t fmt)
-{
- switch (fmt) {
- case SND_PCM_FORMAT_S24_LE:
- return SND_PCM_FORMAT_S24_3LE;
-
- case SND_PCM_FORMAT_S24_BE:
- return SND_PCM_FORMAT_S24_3BE;
-
- default:
- return SND_PCM_FORMAT_UNKNOWN;
- }
-}
-
-static int
-alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- snd_pcm_format_t fmt, bool *packed_r)
-{
- int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
- if (err == 0)
- *packed_r = false;
-
- if (err != -EINVAL)
- return err;
-
- fmt = alsa_to_packed_format(fmt);
- if (fmt == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
- if (err == 0)
- *packed_r = true;
-
- return err;
-}
-
-/**
- * Attempts to configure the specified sample format, and tries the
- * reversed host byte order if was not supported.
- */
-static int
-alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- SampleFormat sample_format,
- bool *packed_r, bool *reverse_endian_r)
-{
- snd_pcm_format_t alsa_format = get_bitformat(sample_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
- packed_r);
- if (err == 0)
- *reverse_endian_r = false;
-
- if (err != -EINVAL)
- return err;
-
- alsa_format = byteswap_bitformat(alsa_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
-
- err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
- if (err == 0)
- *reverse_endian_r = true;
-
- return err;
-}
-
-/**
- * Configure a sample format, and probe other formats if that fails.
- */
-static int
-alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- AudioFormat &audio_format,
- bool *packed_r, bool *reverse_endian_r)
-{
- /* try the input format first */
-
- int err = alsa_output_try_format(pcm, hwparams,
- audio_format.format,
- packed_r, reverse_endian_r);
-
- /* if unsupported by the hardware, try other formats */
-
- static const SampleFormat probe_formats[] = {
- SampleFormat::S24_P32,
- SampleFormat::S32,
- SampleFormat::S16,
- SampleFormat::S8,
- SampleFormat::UNDEFINED,
- };
-
- for (unsigned i = 0;
- err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
- ++i) {
- const SampleFormat mpd_format = probe_formats[i];
- if (mpd_format == audio_format.format)
- continue;
-
- err = alsa_output_try_format(pcm, hwparams, mpd_format,
- packed_r, reverse_endian_r);
- if (err == 0)
- audio_format.format = mpd_format;
- }
-
- return err;
-}
-
-/**
- * Set up the snd_pcm_t object which was opened by the caller. Set up
- * the configured settings and the audio format.
- */
-static bool
-alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
- bool *packed_r, bool *reverse_endian_r, Error &error)
-{
- unsigned int sample_rate = audio_format.sample_rate;
- unsigned int channels = audio_format.channels;
- int err;
- const char *cmd = nullptr;
- int retry = MPD_ALSA_RETRY_NR;
- unsigned int period_time, period_time_ro;
- unsigned int buffer_time;
-
- period_time_ro = period_time = ad->period_time;
-configure_hw:
- /* configure HW params */
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_hw_params_alloca(&hwparams);
- cmd = "snd_pcm_hw_params_any";
- err = snd_pcm_hw_params_any(ad->pcm, hwparams);
- if (err < 0)
- goto error;
-
- if (ad->use_mmap) {
- err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
- SND_PCM_ACCESS_MMAP_INTERLEAVED);
- if (err < 0) {
- FormatWarning(alsa_output_domain,
- "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(-err));
- LogWarning(alsa_output_domain,
- "Falling back to direct write mode");
- ad->use_mmap = false;
- } else
- ad->writei = snd_pcm_mmap_writei;
- }
-
- if (!ad->use_mmap) {
- cmd = "snd_pcm_hw_params_set_access";
- err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
- SND_PCM_ACCESS_RW_INTERLEAVED);
- if (err < 0)
- goto error;
- ad->writei = snd_pcm_writei;
- }
-
- err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
- packed_r, reverse_endian_r);
- if (err < 0) {
- error.Format(alsa_output_domain, err,
- "ALSA device \"%s\" does not support format %s: %s",
- alsa_device(ad),
- sample_format_to_string(audio_format.format),
- snd_strerror(-err));
- return false;
- }
-
- snd_pcm_format_t format;
- if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
- FormatDebug(alsa_output_domain,
- "format=%s (%s)", snd_pcm_format_name(format),
- snd_pcm_format_description(format));
-
- err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
- &channels);
- if (err < 0) {
- error.Format(alsa_output_domain, err,
- "ALSA device \"%s\" does not support %i channels: %s",
- alsa_device(ad), (int)audio_format.channels,
- snd_strerror(-err));
- return false;
- }
- audio_format.channels = (int8_t)channels;
-
- err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
- &sample_rate, nullptr);
- if (err < 0 || sample_rate == 0) {
- error.Format(alsa_output_domain, err,
- "ALSA device \"%s\" does not support %u Hz audio",
- alsa_device(ad), audio_format.sample_rate);
- return false;
- }
- audio_format.sample_rate = sample_rate;
-
- snd_pcm_uframes_t buffer_size_min, buffer_size_max;
- snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
- snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
- unsigned buffer_time_min, buffer_time_max;
- snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
- snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
- FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
- (unsigned)buffer_size_min, (unsigned)buffer_size_max,
- buffer_time_min, buffer_time_max);
-
- snd_pcm_uframes_t period_size_min, period_size_max;
- snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
- snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
- unsigned period_time_min, period_time_max;
- snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
- snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
- FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
- (unsigned)period_size_min, (unsigned)period_size_max,
- period_time_min, period_time_max);
-
- if (ad->buffer_time > 0) {
- buffer_time = ad->buffer_time;
- cmd = "snd_pcm_hw_params_set_buffer_time_near";
- err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
- &buffer_time, nullptr);
- if (err < 0)
- goto error;
- } else {
- err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
- nullptr);
- if (err < 0)
- buffer_time = 0;
- }
-
- if (period_time_ro == 0 && buffer_time >= 10000) {
- period_time_ro = period_time = buffer_time / 4;
-
- FormatDebug(alsa_output_domain,
- "default period_time = buffer_time/4 = %u/4 = %u",
- buffer_time, period_time);
- }
-
- if (period_time_ro > 0) {
- period_time = period_time_ro;
- cmd = "snd_pcm_hw_params_set_period_time_near";
- err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
- &period_time, nullptr);
- if (err < 0)
- goto error;
- }
-
- cmd = "snd_pcm_hw_params";
- err = snd_pcm_hw_params(ad->pcm, hwparams);
- if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
- period_time_ro = period_time_ro >> 1;
- goto configure_hw;
- } else if (err < 0)
- goto error;
- if (retry != MPD_ALSA_RETRY_NR)
- FormatDebug(alsa_output_domain,
- "ALSA period_time set to %d", period_time);
-
- snd_pcm_uframes_t alsa_buffer_size;
- cmd = "snd_pcm_hw_params_get_buffer_size";
- err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
- if (err < 0)
- goto error;
-
- snd_pcm_uframes_t alsa_period_size;
- cmd = "snd_pcm_hw_params_get_period_size";
- err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
- nullptr);
- if (err < 0)
- goto error;
-
- /* configure SW params */
- snd_pcm_sw_params_t *swparams;
- snd_pcm_sw_params_alloca(&swparams);
-
- cmd = "snd_pcm_sw_params_current";
- err = snd_pcm_sw_params_current(ad->pcm, swparams);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params_set_start_threshold";
- err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
- alsa_buffer_size -
- alsa_period_size);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params_set_avail_min";
- err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
- alsa_period_size);
- if (err < 0)
- goto error;
-
- cmd = "snd_pcm_sw_params";
- err = snd_pcm_sw_params(ad->pcm, swparams);
- if (err < 0)
- goto error;
-
- FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
- (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
-
- if (alsa_period_size == 0)
- /* this works around a SIGFPE bug that occurred when
- an ALSA driver indicated period_size==0; this
- caused a division by zero in alsa_play(). By using
- the fallback "1", we make sure that this won't
- happen again. */
- alsa_period_size = 1;
-
- ad->period_frames = alsa_period_size;
- ad->period_position = 0;
-
- ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm,
- alsa_period_size));
- snd_pcm_format_set_silence(format, ad->silence,
- alsa_period_size * channels);
-
- return true;
-
-error:
- error.Format(alsa_output_domain, err,
- "Error opening ALSA device \"%s\" (%s): %s",
- alsa_device(ad), cmd, snd_strerror(-err));
- return false;
-}
-
-static bool
-alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format,
- bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
- Error &error)
-{
- assert(ad->dsd_usb);
- assert(audio_format.format == SampleFormat::DSD);
-
- /* pass 24 bit to alsa_setup() */
-
- AudioFormat usb_format = audio_format;
- usb_format.format = SampleFormat::S24_P32;
- usb_format.sample_rate /= 2;
-
- const AudioFormat check = usb_format;
-
- if (!alsa_setup(ad, usb_format, packed_r, reverse_endian_r, error))
- return false;
-
- /* if the device allows only 32 bit, shift all DSD-over-USB
- samples left by 8 bit and leave the lower 8 bit cleared;
- the DSD-over-USB documentation does not specify whether
- this is legal, but there is anecdotical evidence that this
- is possible (and the only option for some devices) */
- *shift8_r = usb_format.format == SampleFormat::S32;
- if (usb_format.format == SampleFormat::S32)
- usb_format.format = SampleFormat::S24_P32;
-
- if (usb_format != check) {
- /* no bit-perfect playback, which is required
- for DSD over USB */
- error.Format(alsa_output_domain,
- "Failed to configure DSD-over-USB on ALSA device \"%s\"",
- alsa_device(ad));
- g_free(ad->silence);
- return false;
- }
-
- return true;
-}
-
-static bool
-alsa_setup_or_dsd(AlsaOutput *ad, AudioFormat &audio_format,
- Error &error)
-{
- bool shift8 = false, packed, reverse_endian;
-
- const bool dsd_usb = ad->dsd_usb &&
- audio_format.format == SampleFormat::DSD;
- const bool success = dsd_usb
- ? alsa_setup_dsd(ad, audio_format,
- &shift8, &packed, &reverse_endian,
- error)
- : alsa_setup(ad, audio_format, &packed, &reverse_endian,
- error);
- if (!success)
- return false;
-
- ad->pcm_export->Open(audio_format.format,
- audio_format.channels,
- dsd_usb, shift8, packed, reverse_endian);
- return true;
-}
-
-static bool
-alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
- SND_PCM_STREAM_PLAYBACK, ad->mode);
- if (err < 0) {
- error.Format(alsa_output_domain, err,
- "Failed to open ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(err));
- return false;
- }
-
- FormatDebug(alsa_output_domain, "opened %s type=%s",
- snd_pcm_name(ad->pcm),
- snd_pcm_type_name(snd_pcm_type(ad->pcm)));
-
- if (!alsa_setup_or_dsd(ad, audio_format, error)) {
- snd_pcm_close(ad->pcm);
- return false;
- }
-
- ad->in_frame_size = audio_format.GetFrameSize();
- ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
-
- ad->must_prepare = false;
-
- return true;
-}
-
-/**
- * Write silence to the ALSA device.
- */
-static void
-alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
-{
- ad->writei(ad->pcm, ad->silence, nframes);
-}
-
-static int
-alsa_recover(AlsaOutput *ad, int err)
-{
- if (err == -EPIPE) {
- FormatDebug(alsa_output_domain,
- "Underrun on ALSA device \"%s\"", alsa_device(ad));
- } else if (err == -ESTRPIPE) {
- FormatDebug(alsa_output_domain,
- "ALSA device \"%s\" was suspended",
- alsa_device(ad));
- }
-
- switch (snd_pcm_state(ad->pcm)) {
- case SND_PCM_STATE_PAUSED:
- err = snd_pcm_pause(ad->pcm, /* disable */ 0);
- break;
- case SND_PCM_STATE_SUSPENDED:
- err = snd_pcm_resume(ad->pcm);
- if (err == -EAGAIN)
- return 0;
- /* fall-through to snd_pcm_prepare: */
- case SND_PCM_STATE_SETUP:
- case SND_PCM_STATE_XRUN:
- ad->period_position = 0;
- err = snd_pcm_prepare(ad->pcm);
- break;
- case SND_PCM_STATE_DISCONNECTED:
- break;
- /* this is no error, so just keep running */
- case SND_PCM_STATE_RUNNING:
- err = 0;
- break;
- default:
- /* unknown state, do nothing */
- break;
- }
-
- return err;
-}
-
-static void
-alsa_drain(struct audio_output *ao)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
- return;
-
- if (ad->period_position > 0) {
- /* generate some silence to finish the partial
- period */
- snd_pcm_uframes_t nframes =
- ad->period_frames - ad->period_position;
- alsa_write_silence(ad, nframes);
- }
-
- snd_pcm_drain(ad->pcm);
-
- ad->period_position = 0;
-}
-
-static void
-alsa_cancel(struct audio_output *ao)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- ad->period_position = 0;
- ad->must_prepare = true;
-
- snd_pcm_drop(ad->pcm);
-}
-
-static void
-alsa_close(struct audio_output *ao)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- snd_pcm_close(ad->pcm);
- g_free(ad->silence);
-}
-
-static size_t
-alsa_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- assert(size > 0);
- assert(size % ad->in_frame_size == 0);
-
- if (ad->must_prepare) {
- ad->must_prepare = false;
-
- int err = snd_pcm_prepare(ad->pcm);
- if (err < 0) {
- error.Set(alsa_output_domain, err, snd_strerror(-err));
- return 0;
- }
- }
-
- const size_t original_size = size;
- chunk = ad->pcm_export->Export(chunk, size, size);
- if (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;
-
- assert(size % ad->out_frame_size == 0);
-
- size /= ad->out_frame_size;
- assert(size > 0);
-
- while (true) {
- snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0) {
- ad->period_position = (ad->period_position + ret)
- % ad->period_frames;
-
- size_t bytes_written = ret * ad->out_frame_size;
- return ad->pcm_export->CalcSourceSize(bytes_written);
- }
-
- if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
- alsa_recover(ad, ret) < 0) {
- error.Set(alsa_output_domain, ret, snd_strerror(-ret));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin alsa_output_plugin = {
- "alsa",
- alsa_test_default_device,
- alsa_init,
- alsa_finish,
- alsa_output_enable,
- alsa_output_disable,
- alsa_open,
- alsa_close,
- nullptr,
- nullptr,
- alsa_play,
- alsa_drain,
- alsa_cancel,
- nullptr,
-
- &alsa_mixer_plugin,
-};
diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx
deleted file mode 100644
index dc7e639a8..000000000
--- a/src/output/AlsaOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX
-#define MPD_ALSA_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin alsa_output_plugin;
-
-#endif
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx
deleted file mode 100644
index e66969e20..000000000
--- a/src/output/AoOutputPlugin.cxx
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AoOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <ao/ao.h>
-#include <glib.h>
-
-#include <string.h>
-
-/* An ao_sample_format, with all fields set to zero: */
-static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
-
-static unsigned ao_output_ref;
-
-struct AoOutput {
- struct audio_output base;
-
- size_t write_size;
- int driver;
- ao_option *options;
- ao_device *device;
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &ao_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain ao_output_domain("ao_output");
-
-static void
-ao_output_error(Error &error_r)
-{
- const char *error;
-
- switch (errno) {
- case AO_ENODRIVER:
- error = "No such libao driver";
- break;
-
- case AO_ENOTLIVE:
- error = "This driver is not a libao live device";
- break;
-
- case AO_EBADOPTION:
- error = "Invalid libao option";
- break;
-
- case AO_EOPENDEVICE:
- error = "Cannot open the libao device";
- break;
-
- case AO_EFAIL:
- error = "Generic libao failure";
- break;
-
- default:
- error_r.SetErrno();
- return;
- }
-
- error_r.Set(ao_output_domain, errno, error);
-}
-
-inline bool
-AoOutput::Configure(const config_param &param, Error &error)
-{
- const char *value;
-
- options = nullptr;
-
- write_size = param.GetBlockValue("write_size", 1024u);
-
- if (ao_output_ref == 0) {
- ao_initialize();
- }
- ao_output_ref++;
-
- value = param.GetBlockValue("driver", "default");
- if (0 == strcmp(value, "default"))
- driver = ao_default_driver_id();
- else
- driver = ao_driver_id(value);
-
- if (driver < 0) {
- error.Format(ao_output_domain,
- "\"%s\" is not a valid ao driver",
- value);
- return false;
- }
-
- ao_info *ai = ao_driver_info(driver);
- if (ai == nullptr) {
- error.Set(ao_output_domain, "problems getting driver info");
- return false;
- }
-
- FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
- ai->short_name, param.GetBlockValue("name", nullptr));
-
- value = param.GetBlockValue("options", nullptr);
- if (value != nullptr) {
- gchar **_options = g_strsplit(value, ";", 0);
-
- for (unsigned i = 0; _options[i] != nullptr; ++i) {
- gchar **key_value = g_strsplit(_options[i], "=", 2);
-
- if (key_value[0] == nullptr || key_value[1] == nullptr) {
- error.Format(ao_output_domain,
- "problems parsing options \"%s\"",
- _options[i]);
- return false;
- }
-
- ao_append_option(&options, key_value[0],
- key_value[1]);
-
- g_strfreev(key_value);
- }
-
- g_strfreev(_options);
- }
-
- return true;
-}
-
-static struct audio_output *
-ao_output_init(const config_param &param, Error &error)
-{
- AoOutput *ad = new AoOutput();
-
- if (!ad->Initialize(param, error)) {
- delete ad;
- return nullptr;
- }
-
- if (!ad->Configure(param, error)) {
- ad->Deinitialize();
- delete ad;
- return nullptr;
- }
-
- return &ad->base;
-}
-
-static void
-ao_output_finish(struct audio_output *ao)
-{
- AoOutput *ad = (AoOutput *)ao;
-
- ao_free_options(ad->options);
- ad->Deinitialize();
- delete ad;
-
- ao_output_ref--;
-
- if (ao_output_ref == 0)
- ao_shutdown();
-}
-
-static void
-ao_output_close(struct audio_output *ao)
-{
- AoOutput *ad = (AoOutput *)ao;
-
- ao_close(ad->device);
-}
-
-static bool
-ao_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
- AoOutput *ad = (AoOutput *)ao;
-
- switch (audio_format.format) {
- case SampleFormat::S8:
- format.bits = 8;
- break;
-
- case SampleFormat::S16:
- format.bits = 16;
- break;
-
- default:
- /* support for 24 bit samples in libao is currently
- dubious, and until we have sorted that out,
- convert everything to 16 bit */
- audio_format.format = SampleFormat::S16;
- format.bits = 16;
- break;
- }
-
- format.rate = audio_format.sample_rate;
- format.byte_format = AO_FMT_NATIVE;
- format.channels = audio_format.channels;
-
- ad->device = ao_open_live(ad->driver, &format, ad->options);
-
- if (ad->device == nullptr) {
- ao_output_error(error);
- return false;
- }
-
- return true;
-}
-
-/**
- * For whatever reason, libao wants a non-const pointer. Let's hope
- * it does not write to the buffer, and use the union deconst hack to
- * work around this API misdesign.
- */
-static int ao_play_deconst(ao_device *device, const void *output_samples,
- uint_32 num_bytes)
-{
- union {
- const void *in;
- char *out;
- } u;
-
- u.in = output_samples;
- return ao_play(device, u.out, num_bytes);
-}
-
-static size_t
-ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- AoOutput *ad = (AoOutput *)ao;
-
- if (size > ad->write_size)
- size = ad->write_size;
-
- if (ao_play_deconst(ad->device, chunk, size) == 0) {
- ao_output_error(error);
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin ao_output_plugin = {
- "ao",
- nullptr,
- ao_output_init,
- ao_output_finish,
- nullptr,
- nullptr,
- ao_output_open,
- ao_output_close,
- nullptr,
- nullptr,
- ao_output_play,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
-};
diff --git a/src/output/AoOutputPlugin.hxx b/src/output/AoOutputPlugin.hxx
deleted file mode 100644
index a44885e56..000000000
--- a/src/output/AoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_AO_OUTPUT_PLUGIN_HXX
-#define MPD_AO_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin ao_output_plugin;
-
-#endif
diff --git a/src/output/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/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
deleted file mode 100644
index aeb9a6a87..000000000
--- a/src/output/FifoOutputPlugin.cxx
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FifoOutputPlugin.hxx"
-#include "ConfigError.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-#include "system/fd_util.h"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#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;
-
- AllocatedPath path;
- std::string path_utf8;
-
- int input;
- int output;
- bool created;
- Timer *timer;
-
- FifoOutput()
- :path(AllocatedPath::Null()), input(-1), output(-1),
- created(false) {}
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &fifo_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Create(Error &error);
- bool Check(Error &error);
- void Delete();
-
- bool Open(Error &error);
- void Close();
-};
-
-static constexpr Domain fifo_output_domain("fifo_output");
-
-inline void
-FifoOutput::Delete()
-{
- FormatDebug(fifo_output_domain,
- "Removing FIFO \"%s\"", path_utf8.c_str());
-
- if (!RemoveFile(path)) {
- FormatErrno(fifo_output_domain,
- "Could not remove FIFO \"%s\"",
- path_utf8.c_str());
- return;
- }
-
- created = false;
-}
-
-void
-FifoOutput::Close()
-{
- if (input >= 0) {
- close(input);
- input = -1;
- }
-
- if (output >= 0) {
- close(output);
- output = -1;
- }
-
- struct stat st;
- if (created && StatFile(path, st))
- Delete();
-}
-
-inline bool
-FifoOutput::Create(Error &error)
-{
- if (!MakeFifo(path, 0666)) {
- error.FormatErrno("Couldn't create FIFO \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- created = true;
- return true;
-}
-
-inline bool
-FifoOutput::Check(Error &error)
-{
- struct stat st;
- if (!StatFile(path, st)) {
- if (errno == ENOENT) {
- /* Path doesn't exist */
- return Create(error);
- }
-
- error.FormatErrno("Failed to stat FIFO \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- if (!S_ISFIFO(st.st_mode)) {
- error.Format(fifo_output_domain,
- "\"%s\" already exists, but is not a FIFO",
- path_utf8.c_str());
- return false;
- }
-
- return true;
-}
-
-inline bool
-FifoOutput::Open(Error &error)
-{
- if (!Check(error))
- return false;
-
- input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
- if (input < 0) {
- error.FormatErrno("Could not open FIFO \"%s\" for reading",
- path_utf8.c_str());
- Close();
- return false;
- }
-
- output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
- if (output < 0) {
- error.FormatErrno("Could not open FIFO \"%s\" for writing",
- path_utf8.c_str());
- Close();
- return false;
- }
-
- return true;
-}
-
-static bool
-fifo_open(FifoOutput *fd, Error &error)
-{
- return fd->Open(error);
-}
-
-static struct audio_output *
-fifo_output_init(const config_param &param, Error &error)
-{
- FifoOutput *fd = new FifoOutput();
-
- fd->path = param.GetBlockPath("path", error);
- if (fd->path.IsNull()) {
- delete fd;
-
- if (!error.IsDefined())
- error.Set(config_domain,
- "No \"path\" parameter specified");
- return nullptr;
- }
-
- fd->path_utf8 = fd->path.ToUTF8();
-
- if (!fd->Initialize(param, error)) {
- delete fd;
- return nullptr;
- }
-
- if (!fifo_open(fd, error)) {
- fd->Deinitialize();
- delete fd;
- return nullptr;
- }
-
- return &fd->base;
-}
-
-static void
-fifo_output_finish(struct audio_output *ao)
-{
- FifoOutput *fd = (FifoOutput *)ao;
-
- fd->Close();
- fd->Deinitialize();
- delete fd;
-}
-
-static bool
-fifo_output_open(struct audio_output *ao, AudioFormat &audio_format,
- gcc_unused Error &error)
-{
- FifoOutput *fd = (FifoOutput *)ao;
-
- fd->timer = new Timer(audio_format);
-
- return true;
-}
-
-static void
-fifo_output_close(struct audio_output *ao)
-{
- FifoOutput *fd = (FifoOutput *)ao;
-
- delete fd->timer;
-}
-
-static void
-fifo_output_cancel(struct audio_output *ao)
-{
- FifoOutput *fd = (FifoOutput *)ao;
- char buf[FIFO_BUFFER_SIZE];
- int bytes = 1;
-
- fd->timer->Reset();
-
- while (bytes > 0 && errno != EINTR)
- bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
-
- if (bytes < 0 && errno != EAGAIN) {
- FormatErrno(fifo_output_domain,
- "Flush of FIFO \"%s\" failed",
- fd->path_utf8.c_str());
- }
-}
-
-static unsigned
-fifo_output_delay(struct audio_output *ao)
-{
- FifoOutput *fd = (FifoOutput *)ao;
-
- return fd->timer->IsStarted()
- ? fd->timer->GetDelay()
- : 0;
-}
-
-static size_t
-fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- FifoOutput *fd = (FifoOutput *)ao;
- ssize_t bytes;
-
- if (!fd->timer->IsStarted())
- fd->timer->Start();
- fd->timer->Add(size);
-
- while (true) {
- bytes = write(fd->output, chunk, size);
- if (bytes > 0)
- return (size_t)bytes;
-
- if (bytes < 0) {
- switch (errno) {
- case EAGAIN:
- /* The pipe is full, so empty it */
- fifo_output_cancel(&fd->base);
- continue;
- case EINTR:
- continue;
- }
-
- error.FormatErrno("Failed to write to FIFO %s",
- fd->path_utf8.c_str());
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin fifo_output_plugin = {
- "fifo",
- nullptr,
- fifo_output_init,
- fifo_output_finish,
- nullptr,
- nullptr,
- fifo_output_open,
- fifo_output_close,
- fifo_output_delay,
- nullptr,
- fifo_output_play,
- nullptr,
- fifo_output_cancel,
- nullptr,
- nullptr,
-};
diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/FifoOutputPlugin.hxx
deleted file mode 100644
index dca2886d8..000000000
--- a/src/output/FifoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX
-#define MPD_FIFO_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin fifo_output_plugin;
-
-#endif
diff --git a/src/output/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/HttpdClient.cxx b/src/output/HttpdClient.cxx
deleted file mode 100644
index dc337053d..000000000
--- a/src/output/HttpdClient.cxx
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "HttpdClient.hxx"
-#include "HttpdInternal.hxx"
-#include "util/ASCII.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/SocketError.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-HttpdClient::~HttpdClient()
-{
- if (state == RESPONSE) {
- if (current_page != nullptr)
- current_page->Unref();
-
- for (auto page : pages)
- page->Unref();
- }
-
- if (metadata)
- metadata->Unref();
-}
-
-void
-HttpdClient::Close()
-{
- httpd->RemoveClient(*this);
-}
-
-void
-HttpdClient::LockClose()
-{
- const ScopeLock protect(httpd->mutex);
- Close();
-}
-
-void
-HttpdClient::BeginResponse()
-{
- assert(state != RESPONSE);
-
- state = RESPONSE;
- current_page = nullptr;
-
- if (!head_method)
- httpd->SendHeader(*this);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-bool
-HttpdClient::HandleLine(const char *line)
-{
- assert(state != RESPONSE);
-
- if (state == REQUEST) {
- if (memcmp(line, "HEAD /", 6) == 0) {
- line += 6;
- head_method = true;
- } else if (memcmp(line, "GET /", 5) == 0) {
- line += 5;
- } else {
- /* only GET is supported */
- LogWarning(httpd_output_domain,
- "malformed request line from client");
- return false;
- }
-
- line = strchr(line, ' ');
- if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
- /* HTTP/0.9 without request headers */
-
- if (head_method)
- return false;
-
- BeginResponse();
- return true;
- }
-
- /* after the request line, request headers follow */
- state = HEADERS;
- return true;
- } else {
- if (*line == 0) {
- /* empty line: request is finished */
-
- BeginResponse();
- return true;
- }
-
- if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
- StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
- /* Send icy metadata */
- metadata_requested = metadata_supported;
- return true;
- }
-
- if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
- /* Send as dlna */
- dlna_streaming_requested = true;
- /* metadata is not supported by dlna streaming, so disable it */
- metadata_supported = false;
- metadata_requested = false;
- return true;
- }
-
- /* expect more request headers */
- return true;
- }
-}
-
-/**
- * Sends the status line and response headers to the client.
- */
-bool
-HttpdClient::SendResponse()
-{
- char buffer[1024];
- assert(state == RESPONSE);
-
- if (dlna_streaming_requested) {
- snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 206 OK\r\n"
- "Content-Type: %s\r\n"
- "Content-Length: 10000\r\n"
- "Content-RangeX: 0-1000000/1000000\r\n"
- "transferMode.dlna.org: Streaming\r\n"
- "Accept-Ranges: bytes\r\n"
- "Connection: close\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
- "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
- "\r\n",
- httpd->content_type);
-
- } else if (metadata_requested) {
- char *metadata_header =
- icy_server_metadata_header(httpd->name, httpd->genre,
- httpd->website,
- httpd->content_type,
- metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- delete[] metadata_header;
-
- } else { /* revert to a normal HTTP request */
- snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 200 OK\r\n"
- "Content-Type: %s\r\n"
- "Connection: close\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache, no-store\r\n"
- "\r\n",
- httpd->content_type);
- }
-
- ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
- if (gcc_unlikely(nbytes < 0)) {
- const SocketErrorMessage msg;
- FormatWarning(httpd_output_domain,
- "failed to write to client: %s",
- (const char *)msg);
- Close();
- return false;
- }
-
- return true;
-}
-
-HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
- bool _metadata_supported)
- :BufferedSocket(_fd, _loop),
- httpd(_httpd),
- state(REQUEST),
- head_method(false),
- dlna_streaming_requested(false),
- metadata_supported(_metadata_supported),
- metadata_requested(false), metadata_sent(true),
- metaint(8192), /*TODO: just a std value */
- metadata(nullptr),
- metadata_current_position(0), metadata_fill(0)
-{
-}
-
-size_t
-HttpdClient::GetQueueSize() const
-{
- if (state != RESPONSE)
- return 0;
-
- size_t size = 0;
- for (auto page : pages)
- size += page->size;
- return size;
-}
-
-void
-HttpdClient::CancelQueue()
-{
- if (state != RESPONSE)
- return;
-
- for (auto page : pages)
- page->Unref();
- pages.clear();
-
- if (current_page == nullptr)
- CancelWrite();
-}
-
-ssize_t
-HttpdClient::TryWritePage(const Page &page, size_t position)
-{
- assert(position < page.size);
-
- return Write(page.data + position, page.size - position);
-}
-
-ssize_t
-HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
-{
- return n >= 0
- ? Write(page.data + position, n)
- : TryWritePage(page, position);
-}
-
-ssize_t
-HttpdClient::GetBytesTillMetaData() const
-{
- if (metadata_requested &&
- current_page->size - current_position > metaint - metadata_fill)
- return metaint - metadata_fill;
-
- return -1;
-}
-
-inline bool
-HttpdClient::TryWrite()
-{
- const ScopeLock protect(httpd->mutex);
-
- assert(state == RESPONSE);
-
- if (current_page == nullptr) {
- if (pages.empty()) {
- /* another thread has removed the event source
- while this thread was waiting for
- httpd->mutex */
- CancelWrite();
- return true;
- }
-
- current_page = pages.front();
- pages.pop_front();
- current_position = 0;
- }
-
- const ssize_t bytes_to_write = GetBytesTillMetaData();
- if (bytes_to_write == 0) {
- if (!metadata_sent) {
- ssize_t nbytes = TryWritePage(*metadata,
- metadata_current_position);
- if (nbytes < 0) {
- auto e = GetSocketError();
- if (IsSocketErrorAgain(e))
- return true;
-
- if (!IsSocketErrorClosed(e)) {
- SocketErrorMessage msg(e);
- FormatWarning(httpd_output_domain,
- "failed to write to client: %s",
- (const char *)msg);
- }
-
- Close();
- return false;
- }
-
- metadata_current_position += nbytes;
-
- if (metadata->size - metadata_current_position == 0) {
- metadata_fill = 0;
- metadata_current_position = 0;
- metadata_sent = true;
- }
- } else {
- guchar empty_data = 0;
-
- ssize_t nbytes = Write(&empty_data, 1);
- if (nbytes < 0) {
- auto e = GetSocketError();
- if (IsSocketErrorAgain(e))
- return true;
-
- if (!IsSocketErrorClosed(e)) {
- SocketErrorMessage msg(e);
- FormatWarning(httpd_output_domain,
- "failed to write to client: %s",
- (const char *)msg);
- }
-
- Close();
- return false;
- }
-
- metadata_fill = 0;
- metadata_current_position = 0;
- }
- } else {
- ssize_t nbytes =
- TryWritePageN(*current_page, current_position,
- bytes_to_write);
- if (nbytes < 0) {
- auto e = GetSocketError();
- if (IsSocketErrorAgain(e))
- return true;
-
- if (!IsSocketErrorClosed(e)) {
- SocketErrorMessage msg(e);
- FormatWarning(httpd_output_domain,
- "failed to write to client: %s",
- (const char *)msg);
- }
-
- Close();
- return false;
- }
-
- current_position += nbytes;
- assert(current_position <= current_page->size);
-
- if (metadata_requested)
- metadata_fill += nbytes;
-
- if (current_position >= current_page->size) {
- current_page->Unref();
- current_page = nullptr;
-
- if (pages.empty())
- /* all pages are sent: remove the
- event source */
- CancelWrite();
- }
- }
-
- return true;
-}
-
-void
-HttpdClient::PushPage(Page *page)
-{
- if (state != RESPONSE)
- /* the client is still writing the HTTP request */
- return;
-
- page->Ref();
- pages.push_back(page);
-
- ScheduleWrite();
-}
-
-void
-HttpdClient::PushMetaData(Page *page)
-{
- if (metadata) {
- metadata->Unref();
- metadata = nullptr;
- }
-
- g_return_if_fail (page);
-
- page->Ref();
- metadata = page;
- metadata_sent = false;
-}
-
-bool
-HttpdClient::OnSocketReady(unsigned flags)
-{
- if (!BufferedSocket::OnSocketReady(flags))
- return false;
-
- if (flags & WRITE)
- if (!TryWrite())
- return false;
-
- return true;
-}
-
-BufferedSocket::InputResult
-HttpdClient::OnSocketInput(void *data, size_t length)
-{
- if (state == RESPONSE) {
- LogWarning(httpd_output_domain,
- "unexpected input from client");
- LockClose();
- return InputResult::CLOSED;
- }
-
- char *line = (char *)data;
- char *newline = (char *)memchr(line, '\n', length);
- if (newline == nullptr)
- return InputResult::MORE;
-
- ConsumeInput(newline + 1 - line);
-
- if (newline > line && newline[-1] == '\r')
- --newline;
-
- /* terminate the string at the end of the line */
- *newline = 0;
-
- if (!HandleLine(line)) {
- LockClose();
- return InputResult::CLOSED;
- }
-
- if (state == RESPONSE) {
- if (!SendResponse())
- return InputResult::CLOSED;
-
- if (head_method) {
- LockClose();
- return InputResult::CLOSED;
- }
- }
-
- return InputResult::AGAIN;
-}
-
-void
-HttpdClient::OnSocketError(Error &&error)
-{
- LogError(error);
-}
-
-void
-HttpdClient::OnSocketClosed()
-{
- LockClose();
-}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
deleted file mode 100644
index 66a819232..000000000
--- a/src/output/HttpdClient.hxx
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
-#define MPD_OUTPUT_HTTPD_CLIENT_HXX
-
-#include "event/BufferedSocket.hxx"
-#include "Compiler.h"
-
-#include <list>
-
-#include <stddef.h>
-
-struct HttpdOutput;
-class Page;
-
-class HttpdClient final : public BufferedSocket {
- /**
- * The httpd output object this client is connected to.
- */
- HttpdOutput *const httpd;
-
- /**
- * The current state of the client.
- */
- enum {
- /** reading the request line */
- REQUEST,
-
- /** reading the request headers */
- HEADERS,
-
- /** sending the HTTP response */
- RESPONSE,
- } state;
-
- /**
- * A queue of #Page objects to be sent to the client.
- */
- std::list<Page *> pages;
-
- /**
- * The #page which is currently being sent to the client.
- */
- Page *current_page;
-
- /**
- * The amount of bytes which were already sent from
- * #current_page.
- */
- size_t current_position;
-
- /**
- * Is this a HEAD request?
- */
- bool head_method;
-
- /**
- * If DLNA streaming was an option.
- */
- bool dlna_streaming_requested;
-
- /* ICY */
-
- /**
- * Do we support sending Icy-Metadata to the client? This is
- * disabled if the httpd audio output uses encoder tags.
- */
- bool metadata_supported;
-
- /**
- * If we should sent icy metadata.
- */
- bool metadata_requested;
-
- /**
- * If the current metadata was already sent to the client.
- */
- bool metadata_sent;
-
- /**
- * The amount of streaming data between each metadata block
- */
- unsigned metaint;
-
- /**
- * The metadata as #Page which is currently being sent to the client.
- */
- Page *metadata;
-
- /*
- * The amount of bytes which were already sent from the metadata.
- */
- size_t metadata_current_position;
-
- /**
- * The amount of streaming data sent to the client
- * since the last icy information was sent.
- */
- unsigned metadata_fill;
-
-public:
- /**
- * @param httpd the HTTP output device
- * @param fd the socket file descriptor
- */
- HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop,
- bool _metadata_supported);
-
- /**
- * Note: this does not remove the client from the
- * #HttpdOutput object.
- */
- ~HttpdClient();
-
- /**
- * Frees the client and removes it from the server's client list.
- */
- void Close();
-
- void LockClose();
-
- /**
- * Returns the total size of this client's page queue.
- */
- gcc_pure
- size_t GetQueueSize() const;
-
- /**
- * Clears the page queue.
- */
- void CancelQueue();
-
- /**
- * Handle a line of the HTTP request.
- */
- bool HandleLine(const char *line);
-
- /**
- * Switch the client to the "RESPONSE" state.
- */
- void BeginResponse();
-
- /**
- * Sends the status line and response headers to the client.
- */
- bool SendResponse();
-
- gcc_pure
- ssize_t GetBytesTillMetaData() const;
-
- ssize_t TryWritePage(const Page &page, size_t position);
- ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n);
-
- bool TryWrite();
-
- /**
- * Appends a page to the client's queue.
- */
- void PushPage(Page *page);
-
- /**
- * Sends the passed metadata.
- */
- void PushMetaData(Page *page);
-
-protected:
- virtual bool OnSocketReady(unsigned flags) override;
- virtual InputResult OnSocketInput(void *data, size_t length) override;
- virtual void OnSocketError(Error &&error) override;
- virtual void OnSocketClosed() override;
-};
-
-#endif
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
deleted file mode 100644
index b76493a44..000000000
--- a/src/output/HttpdInternal.hxx
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Internal declarations for the "httpd" audio output plugin.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
-#define MPD_OUTPUT_HTTPD_INTERNAL_H
-
-#include "OutputInternal.hxx"
-#include "Timer.hxx"
-#include "thread/Mutex.hxx"
-#include "event/ServerSocket.hxx"
-
-#ifdef _LIBCPP_VERSION
-/* can't use incomplete template arguments with libc++ */
-#include "HttpdClient.hxx"
-#endif
-
-#include <forward_list>
-
-struct config_param;
-class Error;
-class EventLoop;
-class ServerSocket;
-class HttpdClient;
-class Page;
-struct Encoder;
-struct Tag;
-
-struct HttpdOutput final : private ServerSocket {
- struct audio_output base;
-
- /**
- * True if the audio output is open and accepts client
- * connections.
- */
- bool open;
-
- /**
- * The configured encoder plugin.
- */
- Encoder *encoder;
-
- /**
- * Number of bytes which were fed into the encoder, without
- * ever receiving new output. This is used to estimate
- * whether MPD should manually flush the encoder, to avoid
- * buffer underruns in the client.
- */
- size_t unflushed_input;
-
- /**
- * The MIME type produced by the #encoder.
- */
- const char *content_type;
-
- /**
- * This mutex protects the listener socket and the client
- * list.
- */
- mutable Mutex mutex;
-
- /**
- * A #Timer object to synchronize this output with the
- * wallclock.
- */
- Timer *timer;
-
- /**
- * The header page, which is sent to every client on connect.
- */
- Page *header;
-
- /**
- * The metadata, which is sent to every client.
- */
- Page *metadata;
-
- /**
- * The configured name.
- */
- char const *name;
- /**
- * The configured genre.
- */
- char const *genre;
- /**
- * The configured website address.
- */
- char const *website;
-
- /**
- * A linked list containing all clients which are currently
- * connected.
- */
- std::forward_list<HttpdClient> clients;
-
- /**
- * A temporary buffer for the httpd_output_read_page()
- * function.
- */
- char buffer[32768];
-
- /**
- * The maximum and current number of clients connected
- * at the same time.
- */
- unsigned clients_max, clients_cnt;
-
- HttpdOutput(EventLoop &_loop);
- ~HttpdOutput();
-
- bool Configure(const config_param &param, Error &error);
-
- bool Bind(Error &error);
- void Unbind();
-
- /**
- * Caller must lock the mutex.
- */
- bool OpenEncoder(AudioFormat &audio_format, Error &error);
-
- /**
- * Caller must lock the mutex.
- */
- bool Open(AudioFormat &audio_format, Error &error);
-
- /**
- * Caller must lock the mutex.
- */
- void Close();
-
- /**
- * Check whether there is at least one client.
- *
- * Caller must lock the mutex.
- */
- gcc_pure
- bool HasClients() const {
- return !clients.empty();
- }
-
- /**
- * Check whether there is at least one client.
- */
- gcc_pure
- bool LockHasClients() const {
- const ScopeLock protect(mutex);
- return HasClients();
- }
-
- void AddClient(int fd);
-
- /**
- * Removes a client from the httpd_output.clients linked list.
- */
- void RemoveClient(HttpdClient &client);
-
- /**
- * Sends the encoder header to the client. This is called
- * right after the response headers have been sent.
- */
- void SendHeader(HttpdClient &client) const;
-
- /**
- * Reads data from the encoder (as much as available) and
- * returns it as a new #page object.
- */
- Page *ReadPage();
-
- /**
- * Broadcasts a page struct to all clients.
- *
- * Mutext must not be locked.
- */
- void BroadcastPage(Page *page);
-
- /**
- * Broadcasts data from the encoder to all clients.
- */
- void BroadcastFromEncoder();
-
- bool EncodeAndPlay(const void *chunk, size_t size, Error &error);
-
- void SendTag(const Tag *tag);
-
-private:
- virtual void OnAccept(int fd, const sockaddr &address,
- size_t address_length, int uid) override;
-};
-
-extern const class Domain httpd_output_domain;
-
-#endif
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
deleted file mode 100644
index 369c06937..000000000
--- a/src/output/HttpdOutputPlugin.cxx
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "HttpdOutputPlugin.hxx"
-#include "HttpdInternal.hxx"
-#include "HttpdClient.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "system/Resolver.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/fd_util.h"
-#include "Main.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-
-#ifdef HAVE_LIBWRAP
-#include <sys/socket.h> /* needed for AF_UNIX */
-#include <tcpd.h>
-#endif
-
-const Domain httpd_output_domain("httpd_output");
-
-inline
-HttpdOutput::HttpdOutput(EventLoop &_loop)
- :ServerSocket(_loop),
- encoder(nullptr), unflushed_input(0),
- metadata(nullptr)
-{
-}
-
-HttpdOutput::~HttpdOutput()
-{
- if (metadata != nullptr)
- metadata->Unref();
-
- if (encoder != nullptr)
- encoder_finish(encoder);
-
-}
-
-inline bool
-HttpdOutput::Bind(Error &error)
-{
- open = false;
-
- const ScopeLock protect(mutex);
- return ServerSocket::Open(error);
-}
-
-inline void
-HttpdOutput::Unbind()
-{
- assert(!open);
-
- const ScopeLock protect(mutex);
- ServerSocket::Close();
-}
-
-inline bool
-HttpdOutput::Configure(const config_param &param, Error &error)
-{
- /* read configuration */
- name = param.GetBlockValue("name", "Set name in config");
- genre = param.GetBlockValue("genre", "Set genre in config");
- website = param.GetBlockValue("website", "Set website in config");
-
- unsigned port = param.GetBlockValue("port", 8000u);
-
- const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
- const auto encoder_plugin = encoder_plugin_get(encoder_name);
- if (encoder_plugin == nullptr) {
- error.Format(httpd_output_domain,
- "No such encoder: %s", encoder_name);
- return false;
- }
-
- clients_max = param.GetBlockValue("max_clients", 0u);
-
- /* set up bind_to_address */
-
- const char *bind_to_address = param.GetBlockValue("bind_to_address");
- bool success = bind_to_address != nullptr &&
- strcmp(bind_to_address, "any") != 0
- ? AddHost(bind_to_address, port, error)
- : AddPort(port, error);
- if (!success)
- return false;
-
- /* initialize encoder */
-
- encoder = encoder_init(*encoder_plugin, param, error);
- if (encoder == nullptr)
- return false;
-
- /* determine content type */
- content_type = encoder_get_mime_type(encoder);
- if (content_type == nullptr)
- content_type = "application/octet-stream";
-
- return true;
-}
-
-static struct audio_output *
-httpd_output_init(const config_param &param, Error &error)
-{
- HttpdOutput *httpd = new HttpdOutput(*main_loop);
-
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param,
- error)) {
- delete httpd;
- return nullptr;
- }
-
- if (!httpd->Configure(param, error)) {
- ao_base_finish(&httpd->base);
- 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));
-}
-
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-
-static void
-httpd_output_finish(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- ao_base_finish(&httpd->base);
- delete httpd;
-}
-
-/**
- * Creates a new #HttpdClient object and adds it into the
- * HttpdOutput.clients linked list.
- */
-inline void
-HttpdOutput::AddClient(int fd)
-{
- clients.emplace_front(this, fd, GetEventLoop(),
- encoder->plugin.tag == nullptr);
- ++clients_cnt;
-
- /* pass metadata to client */
- if (metadata != nullptr)
- clients.front().PushMetaData(metadata);
-}
-
-void
-HttpdOutput::OnAccept(int fd, const sockaddr &address,
- size_t address_length, gcc_unused int uid)
-{
- /* the listener socket has become readable - a client has
- connected */
-
-#ifdef HAVE_LIBWRAP
- if (address.sa_family != AF_UNIX) {
- char *hostaddr = sockaddr_to_string(&address, address_length,
- IgnoreError());
- const char *progname = g_get_prgname();
-
- struct request_info req;
- request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
-
- fromhost(&req);
-
- if (!hosts_access(&req)) {
- /* tcp wrappers says no */
- FormatWarning(httpd_output_domain,
- "libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
- g_free(hostaddr);
- close_socket(fd);
- return;
- }
-
- g_free(hostaddr);
- }
-#else
- (void)address;
- (void)address_length;
-#endif /* HAVE_WRAP */
-
- const ScopeLock protect(mutex);
-
- if (fd >= 0) {
- /* can we allow additional client */
- if (open && (clients_max == 0 || clients_cnt < clients_max))
- AddClient(fd);
- else
- close_socket(fd);
- } else if (fd < 0 && errno != EINTR) {
- LogErrno(httpd_output_domain, "accept() failed");
- }
-}
-
-Page *
-HttpdOutput::ReadPage()
-{
- if (unflushed_input >= 65536) {
- /* we have fed a lot of input into the encoder, but it
- didn't give anything back yet - flush now to avoid
- buffer underruns */
- encoder_flush(encoder, IgnoreError());
- unflushed_input = 0;
- }
-
- size_t size = 0;
- do {
- size_t nbytes = encoder_read(encoder,
- buffer + size,
- sizeof(buffer) - size);
- if (nbytes == 0)
- break;
-
- unflushed_input = 0;
-
- size += nbytes;
- } while (size < sizeof(buffer));
-
- if (size == 0)
- return nullptr;
-
- return Page::Copy(buffer, size);
-}
-
-static bool
-httpd_output_enable(struct audio_output *ao, Error &error)
-{
- HttpdOutput *httpd = Cast(ao);
-
- return httpd->Bind(error);
-}
-
-static void
-httpd_output_disable(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- httpd->Unbind();
-}
-
-inline bool
-HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
-{
- if (!encoder_open(encoder, audio_format, error))
- return false;
-
- /* we have to remember the encoder header, i.e. the first
- bytes of encoder output after opening it, because it has to
- be sent to every new client */
- header = ReadPage();
-
- unflushed_input = 0;
-
- return true;
-}
-
-inline bool
-HttpdOutput::Open(AudioFormat &audio_format, Error &error)
-{
- assert(!open);
- assert(clients.empty());
-
- /* open the encoder */
-
- if (!OpenEncoder(audio_format, error))
- return false;
-
- /* initialize other attributes */
-
- clients_cnt = 0;
- timer = new Timer(audio_format);
-
- open = true;
-
- return true;
-}
-
-static bool
-httpd_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- HttpdOutput *httpd = Cast(ao);
-
- assert(httpd->clients.empty());
-
- const ScopeLock protect(httpd->mutex);
- return httpd->Open(audio_format, error);
-}
-
-inline void
-HttpdOutput::Close()
-{
- assert(open);
-
- open = false;
-
- delete timer;
-
- clients.clear();
-
- if (header != nullptr)
- header->Unref();
-
- encoder_close(encoder);
-}
-
-static void
-httpd_output_close(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- const ScopeLock protect(httpd->mutex);
- httpd->Close();
-}
-
-void
-HttpdOutput::RemoveClient(HttpdClient &client)
-{
- assert(clients_cnt > 0);
-
- for (auto prev = clients.before_begin(), i = std::next(prev);;
- prev = i, i = std::next(prev)) {
- assert(i != clients.end());
- if (&*i == &client) {
- clients.erase_after(prev);
- clients_cnt--;
- break;
- }
- }
-}
-
-void
-HttpdOutput::SendHeader(HttpdClient &client) const
-{
- if (header != nullptr)
- client.PushPage(header);
-}
-
-static unsigned
-httpd_output_delay(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- if (!httpd->LockHasClients() && httpd->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();
-
- /* some arbitrary delay that is long enough to avoid
- consuming too much CPU, and short enough to notice
- new clients quickly enough */
- return 1000;
- }
-
- return httpd->timer->IsStarted()
- ? httpd->timer->GetDelay()
- : 0;
-}
-
-void
-HttpdOutput::BroadcastPage(Page *page)
-{
- assert(page != nullptr);
-
- const ScopeLock protect(mutex);
- for (auto &client : clients)
- client.PushPage(page);
-}
-
-void
-HttpdOutput::BroadcastFromEncoder()
-{
- 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();
-
- Page *page;
- while ((page = ReadPage()) != nullptr) {
- BroadcastPage(page);
- page->Unref();
- }
-}
-
-inline bool
-HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
-{
- if (!encoder_write(encoder, chunk, size, error))
- return false;
-
- unflushed_input += size;
-
- BroadcastFromEncoder();
- return true;
-}
-
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- HttpdOutput *httpd = Cast(ao);
-
- if (httpd->LockHasClients()) {
- if (!httpd->EncodeAndPlay(chunk, size, error))
- return 0;
- }
-
- if (!httpd->timer->IsStarted())
- httpd->timer->Start();
- httpd->timer->Add(size);
-
- return size;
-}
-
-static bool
-httpd_output_pause(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- if (httpd->LockHasClients()) {
- static const char silence[1020] = { 0 };
- return httpd_output_play(ao, silence, sizeof(silence),
- IgnoreError()) > 0;
- } else {
- return true;
- }
-}
-
-inline void
-HttpdOutput::SendTag(const Tag *tag)
-{
- assert(tag != nullptr);
-
- if (encoder->plugin.tag != nullptr) {
- /* embed encoder tags */
-
- /* flush the current stream, and end it */
-
- encoder_pre_tag(encoder, IgnoreError());
- BroadcastFromEncoder();
-
- /* send the tag to the encoder - which starts a new
- stream now */
-
- encoder_tag(encoder, tag, IgnoreError());
-
- /* the first page generated by the encoder will now be
- used as the new "header" page, which is sent to all
- new clients */
-
- Page *page = ReadPage();
- if (page != nullptr) {
- if (header != nullptr)
- header->Unref();
- header = page;
- BroadcastPage(page);
- }
- } else {
- /* use Icy-Metadata */
-
- if (metadata != nullptr)
- metadata->Unref();
-
- static constexpr TagType types[] = {
- TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
- TAG_NUM_OF_ITEM_TYPES
- };
-
- metadata = icy_server_metadata_page(*tag, &types[0]);
- if (metadata != nullptr) {
- const ScopeLock protect(mutex);
- for (auto &client : clients)
- client.PushMetaData(metadata);
- }
- }
-}
-
-static void
-httpd_output_tag(struct audio_output *ao, const Tag *tag)
-{
- HttpdOutput *httpd = Cast(ao);
-
- httpd->SendTag(tag);
-}
-
-static void
-httpd_output_cancel(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- const ScopeLock protect(httpd->mutex);
- for (auto &client : httpd->clients)
- client.CancelQueue();
-}
-
-const struct audio_output_plugin httpd_output_plugin = {
- "httpd",
- nullptr,
- httpd_output_init,
- httpd_output_finish,
- httpd_output_enable,
- httpd_output_disable,
- httpd_output_open,
- httpd_output_close,
- httpd_output_delay,
- httpd_output_tag,
- httpd_output_play,
- nullptr,
- httpd_output_cancel,
- httpd_output_pause,
- nullptr,
-};
diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx
deleted file mode 100644
index c74d2bd4a..000000000
--- a/src/output/HttpdOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX
-#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin httpd_output_plugin;
-
-#endif
diff --git a/src/output/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..6e6ffb442
--- /dev/null
+++ b/src/output/Internal.hxx
@@ -0,0 +1,441 @@
+/*
+ * 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);
+
+ /**
+ * Mutex must not be locked.
+ */
+ 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/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
deleted file mode 100644
index 7ed672f95..000000000
--- a/src/output/JackOutputPlugin.cxx
+++ /dev/null
@@ -1,769 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "JackOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-enum {
- MAX_PORTS = 16,
-};
-
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
-
-struct JackOutput {
- struct audio_output base;
-
- /**
- * libjack options passed to jack_client_open().
- */
- jack_options_t options;
-
- const char *name;
-
- const char *server_name;
-
- /* configuration */
-
- char *source_ports[MAX_PORTS];
- unsigned num_source_ports;
-
- char *destination_ports[MAX_PORTS];
- unsigned num_destination_ports;
-
- size_t ringbuffer_size;
-
- /* the current audio format */
- AudioFormat audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[MAX_PORTS];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[MAX_PORTS];
-
- bool shutdown;
-
- /**
- * While this flag is set, the "process" callback generates
- * silence.
- */
- bool pause;
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &jack_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain jack_output_domain("jack_output");
-
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const JackOutput *jd)
-{
- size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
-
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- if (current < min)
- min = current;
- }
-
- assert(min % jack_sample_size == 0);
-
- return min / jack_sample_size;
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
- JackOutput *jd = (JackOutput *) arg;
-
- if (nframes <= 0)
- return 0;
-
- if (jd->pause) {
- /* empty the ring buffers */
-
- const jack_nframes_t available = mpd_jack_available(jd);
- for (unsigned i = 0; i < jd->audio_format.channels; ++i)
- jack_ringbuffer_read_advance(jd->ringbuffer[i],
- available * jack_sample_size);
-
- /* generate silence while MPD is paused */
-
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
-
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- return 0;
- }
-
- jack_nframes_t available = mpd_jack_available(jd);
- if (available > nframes)
- available = nframes;
-
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
- if (out == nullptr)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * jack_sample_size);
-
- for (jack_nframes_t f = available; f < nframes; ++f)
- /* ringbuffer underrun, fill with silence */
- out[f] = 0.0;
- }
-
- /* generate silence for the unused source ports */
-
- for (unsigned i = jd->audio_format.channels;
- i < jd->num_source_ports; ++i) {
- jack_default_audio_sample_t *out =
- (jack_default_audio_sample_t *)
- jack_port_get_buffer(jd->ports[i], nframes);
- if (out == nullptr)
- /* workaround for libjack1 bug: if the server
- connection fails, the process callback is
- invoked anyway, but unable to get a
- buffer */
- continue;
-
- for (jack_nframes_t f = 0; f < nframes; ++f)
- out[f] = 0.0;
- }
-
- return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
- JackOutput *jd = (JackOutput *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(JackOutput *jd, AudioFormat &audio_format)
-{
- audio_format.sample_rate = jack_get_sample_rate(jd->client);
-
- if (jd->num_source_ports == 1)
- audio_format.channels = 1;
- else if (audio_format.channels > jd->num_source_ports)
- audio_format.channels = 2;
-
- if (audio_format.format != SampleFormat::S16 &&
- audio_format.format != SampleFormat::S24_P32)
- audio_format.format = SampleFormat::S24_P32;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- LogError(jack_output_domain, msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- LogDefault(jack_output_domain, msg);
-}
-#endif
-
-/**
- * Disconnect the JACK client.
- */
-static void
-mpd_jack_disconnect(JackOutput *jd)
-{
- assert(jd != nullptr);
- assert(jd->client != nullptr);
-
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = nullptr;
-}
-
-/**
- * Connect the JACK client and performs some basic setup
- * (e.g. register callbacks).
- */
-static bool
-mpd_jack_connect(JackOutput *jd, Error &error)
-{
- jack_status_t status;
-
- assert(jd != nullptr);
-
- jd->shutdown = false;
-
- jd->client = jack_client_open(jd->name, jd->options, &status,
- jd->server_name);
- if (jd->client == nullptr) {
- error.Format(jack_output_domain, status,
- "Failed to connect to JACK server, status=%d",
- status);
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- jd->ports[i] = jack_port_register(jd->client,
- jd->source_ports[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == nullptr) {
- error.Format(jack_output_domain,
- "Cannot register output port \"%s\"",
- jd->source_ports[i]);
- mpd_jack_disconnect(jd);
- return false;
- }
- }
-
- return true;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static unsigned
-parse_port_list(const char *source, char **dest, Error &error)
-{
- char **list = g_strsplit(source, ",", 0);
- unsigned n = 0;
-
- for (n = 0; list[n] != nullptr; ++n) {
- if (n >= MAX_PORTS) {
- error.Set(config_domain,
- "too many port names");
- return 0;
- }
-
- dest[n] = list[n];
- }
-
- g_free(list);
-
- if (n == 0) {
- error.Format(config_domain,
- "at least one port name expected");
- return 0;
- }
-
- return n;
-}
-
-static struct audio_output *
-mpd_jack_init(const config_param &param, Error &error)
-{
- JackOutput *jd = new JackOutput();
-
- if (!jd->Initialize(param, error)) {
- delete jd;
- return nullptr;
- }
-
- const char *value;
-
- jd->options = JackNullOption;
-
- jd->name = param.GetBlockValue("client_name", nullptr);
- if (jd->name != nullptr)
- jd->options = jack_options_t(jd->options | JackUseExactName);
- else
- /* if there's a no configured client name, we don't
- care about the JackUseExactName option */
- jd->name = "Music Player Daemon";
-
- jd->server_name = param.GetBlockValue("server_name", nullptr);
- if (jd->server_name != nullptr)
- jd->options = jack_options_t(jd->options | JackServerName);
-
- if (!param.GetBlockValue("autostart", false))
- jd->options = jack_options_t(jd->options | JackNoStartServer);
-
- /* configure the source ports */
-
- value = param.GetBlockValue("source_ports", "left,right");
- jd->num_source_ports = parse_port_list(value,
- jd->source_ports, error);
- if (jd->num_source_ports == 0)
- return nullptr;
-
- /* configure the destination ports */
-
- value = param.GetBlockValue("destination_ports", nullptr);
- if (value == nullptr) {
- /* compatibility with MPD < 0.16 */
- value = param.GetBlockValue("ports", nullptr);
- if (value != nullptr)
- FormatWarning(jack_output_domain,
- "deprecated option 'ports' in line %d",
- param.line);
- }
-
- if (value != nullptr) {
- jd->num_destination_ports =
- parse_port_list(value,
- jd->destination_ports, error);
- if (jd->num_destination_ports == 0)
- return nullptr;
- } else {
- jd->num_destination_ports = 0;
- }
-
- if (jd->num_destination_ports > 0 &&
- jd->num_destination_ports != jd->num_source_ports)
- FormatWarning(jack_output_domain,
- "number of source ports (%u) mismatches the "
- "number of destination ports (%u) in line %d",
- jd->num_source_ports, jd->num_destination_ports,
- param.line);
-
- jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
-
- jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
- jack_set_info_function(mpd_jack_info);
-#endif
-
- return &jd->base;
-}
-
-static void
-mpd_jack_finish(struct audio_output *ao)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- g_free(jd->source_ports[i]);
-
- for (unsigned i = 0; i < jd->num_destination_ports; ++i)
- g_free(jd->destination_ports[i]);
-
- jd->Deinitialize();
- delete jd;
-}
-
-static bool
-mpd_jack_enable(struct audio_output *ao, Error &error)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = nullptr;
-
- return mpd_jack_connect(jd, error);
-}
-
-static void
-mpd_jack_disable(struct audio_output *ao)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- if (jd->client != nullptr)
- mpd_jack_disconnect(jd);
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] != nullptr) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = nullptr;
- }
- }
-}
-
-/**
- * Stops the playback on the JACK connection.
- */
-static void
-mpd_jack_stop(JackOutput *jd)
-{
- assert(jd != nullptr);
-
- if (jd->client == nullptr)
- return;
-
- if (jd->shutdown)
- /* the connection has failed; close it */
- mpd_jack_disconnect(jd);
- else
- /* the connection is alive: just stop playback */
- jack_deactivate(jd->client);
-}
-
-static bool
-mpd_jack_start(JackOutput *jd, Error &error)
-{
- const char *destination_ports[MAX_PORTS], **jports;
- const char *duplicate_port = nullptr;
- unsigned num_destination_ports;
-
- assert(jd->client != nullptr);
- assert(jd->audio_format.channels <= jd->num_source_ports);
-
- /* allocate the ring buffers on the first open(); these
- persist until MPD exits. It's too unsafe to delete them
- because we can never know when mpd_jack_process() gets
- called */
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] == nullptr)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- /* clear the ring buffer to be sure that data from
- previous playbacks are gone */
- jack_ringbuffer_reset(jd->ringbuffer[i]);
- }
-
- if ( jack_activate(jd->client) ) {
- error.Set(jack_output_domain, "cannot activate client");
- mpd_jack_stop(jd);
- return false;
- }
-
- if (jd->num_destination_ports == 0) {
- /* no output ports were configured - ask libjack for
- defaults */
- jports = jack_get_ports(jd->client, nullptr, nullptr,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == nullptr) {
- error.Set(jack_output_domain, "no ports found");
- mpd_jack_stop(jd);
- return false;
- }
-
- assert(*jports != nullptr);
-
- for (num_destination_ports = 0;
- num_destination_ports < MAX_PORTS &&
- jports[num_destination_ports] != nullptr;
- ++num_destination_ports) {
- FormatDebug(jack_output_domain,
- "destination_port[%u] = '%s'\n",
- num_destination_ports,
- jports[num_destination_ports]);
- destination_ports[num_destination_ports] =
- jports[num_destination_ports];
- }
- } else {
- /* use the configured output ports */
-
- num_destination_ports = jd->num_destination_ports;
- memcpy(destination_ports, jd->destination_ports,
- num_destination_ports * sizeof(*destination_ports));
-
- jports = nullptr;
- }
-
- assert(num_destination_ports > 0);
-
- if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
- /* mix stereo signal on one speaker */
-
- while (num_destination_ports < jd->audio_format.channels)
- destination_ports[num_destination_ports++] =
- destination_ports[0];
- } else if (num_destination_ports > jd->audio_format.channels) {
- if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
- /* mono input file: connect the one source
- channel to the both destination channels */
- duplicate_port = destination_ports[1];
- num_destination_ports = 1;
- } else
- /* connect only as many ports as we need */
- num_destination_ports = jd->audio_format.channels;
- }
-
- assert(num_destination_ports <= jd->num_source_ports);
-
- for (unsigned i = 0; i < num_destination_ports; ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- destination_ports[i]);
- if (ret != 0) {
- error.Format(jack_output_domain,
- "Not a valid JACK port: %s",
- destination_ports[i]);
-
- if (jports != nullptr)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (duplicate_port != nullptr) {
- /* mono input file: connect the one source channel to
- the both destination channels */
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
- duplicate_port);
- if (ret != 0) {
- error.Format(jack_output_domain,
- "Not a valid JACK port: %s",
- duplicate_port);
-
- if (jports != nullptr)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (jports != nullptr)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- assert(jd != nullptr);
-
- jd->pause = false;
-
- if (jd->client != nullptr && jd->shutdown)
- mpd_jack_disconnect(jd);
-
- if (jd->client == nullptr && !mpd_jack_connect(jd, error))
- return false;
-
- set_audioformat(jd, audio_format);
- jd->audio_format = audio_format;
-
- if (!mpd_jack_start(jd, error))
- return false;
-
- return true;
-}
-
-static void
-mpd_jack_close(gcc_unused struct audio_output *ao)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- mpd_jack_stop(jd);
-}
-
-static unsigned
-mpd_jack_delay(struct audio_output *ao)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- return jd->base.pause && jd->pause && !jd->shutdown
- ? 1000
- : 0;
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i],
- (const char *)&sample,
- sizeof(sample));
- }
- }
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
- unsigned i;
-
- while (num_samples-- > 0) {
- for (i = 0; i < jd->audio_format.channels; ++i) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[i],
- (const char *)&sample,
- sizeof(sample));
- }
- }
-}
-
-static void
-mpd_jack_write_samples(JackOutput *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.format) {
- case SampleFormat::S16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case SampleFormat::S24_P32:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- gcc_unreachable();
- }
-}
-
-static size_t
-mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- JackOutput *jd = (JackOutput *)ao;
- const size_t frame_size = jd->audio_format.GetFrameSize();
- size_t space = 0, space1;
-
- jd->pause = false;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- error.Set(jack_output_domain,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
- }
-
- if (space >= jack_sample_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= jack_sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-static bool
-mpd_jack_pause(struct audio_output *ao)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- if (jd->shutdown)
- return false;
-
- jd->pause = true;
-
- return true;
-}
-
-const struct audio_output_plugin jack_output_plugin = {
- "jack",
- mpd_jack_test_default_device,
- mpd_jack_init,
- mpd_jack_finish,
- mpd_jack_enable,
- mpd_jack_disable,
- mpd_jack_open,
- mpd_jack_close,
- mpd_jack_delay,
- nullptr,
- mpd_jack_play,
- nullptr,
- nullptr,
- mpd_jack_pause,
- nullptr,
-};
diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx
deleted file mode 100644
index 908105ad2..000000000
--- a/src/output/JackOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX
-#define MPD_JACK_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin jack_output_plugin;
-
-#endif
diff --git a/src/output/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/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
deleted file mode 100644
index e2eec9dbc..000000000
--- a/src/output/NullOutputPlugin.cxx
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "NullOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-
-#include <assert.h>
-
-struct NullOutput {
- struct audio_output base;
-
- bool sync;
-
- Timer *timer;
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &null_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static struct audio_output *
-null_init(const config_param &param, Error &error)
-{
- NullOutput *nd = new NullOutput();
-
- if (!nd->Initialize(param, error)) {
- delete nd;
- return nullptr;
- }
-
- nd->sync = param.GetBlockValue("sync", true);
-
- return &nd->base;
-}
-
-static void
-null_finish(struct audio_output *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- nd->Deinitialize();
- delete nd;
-}
-
-static bool
-null_open(struct audio_output *ao, AudioFormat &audio_format,
- gcc_unused Error &error)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- if (nd->sync)
- nd->timer = new Timer(audio_format);
-
- return true;
-}
-
-static void
-null_close(struct audio_output *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- if (nd->sync)
- delete nd->timer;
-}
-
-static unsigned
-null_delay(struct audio_output *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- return nd->sync && nd->timer->IsStarted()
- ? nd->timer->GetDelay()
- : 0;
-}
-
-static size_t
-null_play(struct audio_output *ao, gcc_unused const void *chunk, size_t size,
- gcc_unused Error &error)
-{
- NullOutput *nd = (NullOutput *)ao;
- Timer *timer = nd->timer;
-
- if (!nd->sync)
- return size;
-
- if (!timer->IsStarted())
- timer->Start();
- timer->Add(size);
-
- return size;
-}
-
-static void
-null_cancel(struct audio_output *ao)
-{
- NullOutput *nd = (NullOutput *)ao;
-
- if (!nd->sync)
- return;
-
- nd->timer->Reset();
-}
-
-const struct audio_output_plugin null_output_plugin = {
- "null",
- nullptr,
- null_init,
- null_finish,
- nullptr,
- nullptr,
- null_open,
- null_close,
- null_delay,
- nullptr,
- null_play,
- nullptr,
- null_cancel,
- nullptr,
- nullptr,
-};
diff --git a/src/output/NullOutputPlugin.hxx b/src/output/NullOutputPlugin.hxx
deleted file mode 100644
index a58f1cb13..000000000
--- a/src/output/NullOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX
-#define MPD_NULL_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin null_output_plugin;
-
-#endif
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
deleted file mode 100644
index 97ebae056..000000000
--- a/src/output/OSXOutputPlugin.cxx
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OSXOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/fifo_buffer.h"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <CoreAudio/AudioHardware.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-struct OSXOutput {
- struct audio_output base;
-
- /* configuration settings */
- OSType component_subtype;
- /* only applicable with kAudioUnitSubType_HALOutput */
- const char *device_name;
-
- AudioUnit au;
- Mutex mutex;
- Cond condition;
-
- struct fifo_buffer *buffer;
-};
-
-static constexpr Domain osx_output_domain("osx_output");
-
-static bool
-osx_output_test_default_device(void)
-{
- /* on a Mac, this is always the default plugin, if nothing
- else is configured */
- return true;
-}
-
-static void
-osx_output_configure(OSXOutput *oo, const config_param &param)
-{
- const char *device = param.GetBlockValue("device");
-
- if (device == NULL || 0 == strcmp(device, "default")) {
- oo->component_subtype = kAudioUnitSubType_DefaultOutput;
- oo->device_name = NULL;
- }
- else if (0 == strcmp(device, "system")) {
- oo->component_subtype = kAudioUnitSubType_SystemOutput;
- oo->device_name = NULL;
- }
- else {
- oo->component_subtype = kAudioUnitSubType_HALOutput;
- /* XXX am I supposed to g_strdup() this? */
- oo->device_name = device;
- }
-}
-
-static struct audio_output *
-osx_output_init(const config_param &param, Error &error)
-{
- OSXOutput *oo = new OSXOutput();
- if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) {
- delete oo;
- return NULL;
- }
-
- osx_output_configure(oo, param);
-
- return &oo->base;
-}
-
-static void
-osx_output_finish(struct audio_output *ao)
-{
- OSXOutput *oo = (OSXOutput *)ao;
-
- delete oo;
-}
-
-static bool
-osx_output_set_device(OSXOutput *oo, Error &error)
-{
- bool ret = true;
- OSStatus status;
- UInt32 size, numdevices;
- AudioDeviceID *deviceids = NULL;
- char name[256];
- unsigned int i;
-
- if (oo->component_subtype != kAudioUnitSubType_HALOutput)
- goto done;
-
- /* how many audio devices are there? */
- status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
- &size,
- NULL);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to determine number of OS X audio devices: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- /* what are the available audio device IDs? */
- numdevices = size / sizeof(AudioDeviceID);
- deviceids = new AudioDeviceID[numdevices];
- status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
- &size,
- deviceids);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to determine OS X audio device IDs: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- /* which audio device matches oo->device_name? */
- for (i = 0; i < numdevices; i++) {
- size = sizeof(name);
- status = AudioDeviceGetProperty(deviceids[i], 0, false,
- kAudioDevicePropertyDeviceName,
- &size, name);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to determine OS X device name "
- "(device %u): %s",
- (unsigned int) deviceids[i],
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
- if (strcmp(oo->device_name, name) == 0) {
- FormatDebug(osx_output_domain,
- "found matching device: ID=%u, name=%s",
- (unsigned)deviceids[i], name);
- break;
- }
- }
- if (i == numdevices) {
- FormatWarning(osx_output_domain,
- "Found no audio device with name '%s' "
- "(will use default audio device)",
- oo->device_name);
- goto done;
- }
-
- status = AudioUnitSetProperty(oo->au,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &(deviceids[i]),
- sizeof(AudioDeviceID));
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to set OS X audio output device: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- FormatDebug(osx_output_domain,
- "set OS X audio output device ID=%u, name=%s",
- (unsigned)deviceids[i], name);
-
-done:
- delete[] deviceids;
- return ret;
-}
-
-static OSStatus
-osx_render(void *vdata,
- gcc_unused AudioUnitRenderActionFlags *io_action_flags,
- gcc_unused const AudioTimeStamp *in_timestamp,
- gcc_unused UInt32 in_bus_number,
- gcc_unused UInt32 in_number_frames,
- AudioBufferList *buffer_list)
-{
- OSXOutput *od = (OSXOutput *) vdata;
- AudioBuffer *buffer = &buffer_list->mBuffers[0];
- size_t buffer_size = buffer->mDataByteSize;
-
- assert(od->buffer != NULL);
-
- od->mutex.lock();
-
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
-
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
-
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
-
- od->condition.signal();
- od->mutex.unlock();
-
- buffer->mDataByteSize = nbytes;
-
- unsigned i;
- for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
- buffer = &buffer_list->mBuffers[i];
- buffer->mDataByteSize = 0;
- }
-
- return 0;
-}
-
-static bool
-osx_output_enable(struct audio_output *ao, Error &error)
-{
- OSXOutput *oo = (OSXOutput *)ao;
-
- ComponentDescription desc;
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = oo->component_subtype;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-
- Component comp = FindNextComponent(NULL, &desc);
- if (comp == 0) {
- error.Set(osx_output_domain,
- "Error finding OS X component");
- return false;
- }
-
- OSStatus status = OpenAComponent(comp, &oo->au);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- if (!osx_output_set_device(oo, error)) {
- CloseComponent(oo->au);
- return false;
- }
-
- AURenderCallbackStruct callback;
- callback.inputProc = osx_render;
- callback.inputProcRefCon = oo;
-
- ComponentResult result =
- AudioUnitSetProperty(oo->au,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0,
- &callback, sizeof(callback));
- if (result != noErr) {
- CloseComponent(oo->au);
- error.Set(osx_output_domain, result,
- "unable to set callback for OS X audio unit");
- return false;
- }
-
- return true;
-}
-
-static void
-osx_output_disable(struct audio_output *ao)
-{
- OSXOutput *oo = (OSXOutput *)ao;
-
- CloseComponent(oo->au);
-}
-
-static void
-osx_output_cancel(struct audio_output *ao)
-{
- OSXOutput *od = (OSXOutput *)ao;
-
- const ScopeLock protect(od->mutex);
- fifo_buffer_clear(od->buffer);
-}
-
-static void
-osx_output_close(struct audio_output *ao)
-{
- OSXOutput *od = (OSXOutput *)ao;
-
- AudioOutputUnitStop(od->au);
- AudioUnitUninitialize(od->au);
-
- fifo_buffer_free(od->buffer);
-}
-
-static bool
-osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- OSXOutput *od = (OSXOutput *)ao;
-
- AudioStreamBasicDescription stream_description;
- stream_description.mSampleRate = audio_format.sample_rate;
- stream_description.mFormatID = kAudioFormatLinearPCM;
- stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
- switch (audio_format.format) {
- case SampleFormat::S8:
- stream_description.mBitsPerChannel = 8;
- break;
-
- case SampleFormat::S16:
- stream_description.mBitsPerChannel = 16;
- break;
-
- case SampleFormat::S32:
- stream_description.mBitsPerChannel = 32;
- break;
-
- default:
- audio_format.format = SampleFormat::S32;
- stream_description.mBitsPerChannel = 32;
- break;
- }
-
- if (IsBigEndian())
- stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-
- stream_description.mBytesPerPacket = audio_format.GetFrameSize();
- stream_description.mFramesPerPacket = 1;
- stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
- stream_description.mChannelsPerFrame = audio_format.channels;
-
- ComponentResult result =
- AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input, 0,
- &stream_description,
- sizeof(stream_description));
- if (result != noErr) {
- error.Set(osx_output_domain, result,
- "Unable to set format on OS X device");
- return false;
- }
-
- OSStatus status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to initialize OS X audio unit: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- /* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format.sample_rate *
- audio_format.GetFrameSize());
-
- status = AudioOutputUnitStart(od->au);
- if (status != 0) {
- AudioUnitUninitialize(od->au);
- error.Format(osx_output_domain, status,
- "unable to start audio output: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- return true;
-}
-
-static size_t
-osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
- gcc_unused Error &error)
-{
- OSXOutput *od = (OSXOutput *)ao;
-
- const ScopeLock protect(od->mutex);
-
- void *dest;
- size_t max_length;
-
- while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
- break;
-
- /* wait for some free space in the buffer */
- od->condition.wait(od->mutex);
- }
-
- if (size > max_length)
- size = max_length;
-
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
-
- return size;
-}
-
-const struct audio_output_plugin osx_output_plugin = {
- "osx",
- osx_output_test_default_device,
- osx_output_init,
- osx_output_finish,
- osx_output_enable,
- osx_output_disable,
- osx_output_open,
- osx_output_close,
- nullptr,
- nullptr,
- osx_output_play,
- nullptr,
- osx_output_cancel,
- nullptr,
- nullptr,
-};
diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/OSXOutputPlugin.hxx
deleted file mode 100644
index 2a4172880..000000000
--- a/src/output/OSXOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX
-#define MPD_OSX_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin osx_output_plugin;
-
-#endif
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx
deleted file mode 100644
index 268cf17cc..000000000
--- a/src/output/OpenALOutputPlugin.cxx
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OpenALOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-
-#ifndef __APPLE__
-#include <AL/al.h>
-#include <AL/alc.h>
-#else
-#include <OpenAL/al.h>
-#include <OpenAL/alc.h>
-#endif
-
-/* should be enough for buffer size = 2048 */
-#define NUM_BUFFERS 16
-
-struct OpenALOutput {
- struct audio_output base;
-
- const char *device_name;
- ALCdevice *device;
- ALCcontext *context;
- ALuint buffers[NUM_BUFFERS];
- unsigned filled;
- ALuint source;
- ALenum format;
- ALuint frequency;
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &openal_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain openal_output_domain("openal_output");
-
-static ALenum
-openal_audio_format(AudioFormat &audio_format)
-{
- /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
- AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
- samples, while MPD uses signed samples */
-
- switch (audio_format.format) {
- case SampleFormat::S16:
- if (audio_format.channels == 2)
- return AL_FORMAT_STEREO16;
- if (audio_format.channels == 1)
- return AL_FORMAT_MONO16;
-
- /* fall back to mono */
- audio_format.channels = 1;
- return openal_audio_format(audio_format);
-
- default:
- /* fall back to 16 bit */
- audio_format.format = SampleFormat::S16;
- return openal_audio_format(audio_format);
- }
-}
-
-gcc_pure
-static inline ALint
-openal_get_source_i(const OpenALOutput *od, ALenum param)
-{
- ALint value;
- alGetSourcei(od->source, param, &value);
- return value;
-}
-
-gcc_pure
-static inline bool
-openal_has_processed(const OpenALOutput *od)
-{
- return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
-}
-
-gcc_pure
-static inline ALint
-openal_is_playing(const OpenALOutput *od)
-{
- return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
-}
-
-static bool
-openal_setup_context(OpenALOutput *od, Error &error)
-{
- od->device = alcOpenDevice(od->device_name);
-
- if (od->device == nullptr) {
- error.Format(openal_output_domain,
- "Error opening OpenAL device \"%s\"",
- od->device_name);
- return false;
- }
-
- od->context = alcCreateContext(od->device, nullptr);
-
- if (od->context == nullptr) {
- error.Format(openal_output_domain,
- "Error creating context for \"%s\"",
- od->device_name);
- alcCloseDevice(od->device);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-openal_init(const config_param &param, Error &error)
-{
- const char *device_name = param.GetBlockValue("device");
- if (device_name == nullptr) {
- device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
- }
-
- OpenALOutput *od = new OpenALOutput();
- if (!od->Initialize(param, error)) {
- delete od;
- return nullptr;
- }
-
- od->device_name = device_name;
-
- return &od->base;
-}
-
-static void
-openal_finish(struct audio_output *ao)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
-
- od->Deinitialize();
- delete od;
-}
-
-static bool
-openal_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
-
- od->format = openal_audio_format(audio_format);
-
- if (!openal_setup_context(od, error)) {
- return false;
- }
-
- alcMakeContextCurrent(od->context);
- alGenBuffers(NUM_BUFFERS, od->buffers);
-
- if (alGetError() != AL_NO_ERROR) {
- error.Set(openal_output_domain, "Failed to generate buffers");
- return false;
- }
-
- alGenSources(1, &od->source);
-
- if (alGetError() != AL_NO_ERROR) {
- error.Set(openal_output_domain, "Failed to generate source");
- alDeleteBuffers(NUM_BUFFERS, od->buffers);
- return false;
- }
-
- od->filled = 0;
- od->frequency = audio_format.sample_rate;
-
- return true;
-}
-
-static void
-openal_close(struct audio_output *ao)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
-
- alcMakeContextCurrent(od->context);
- alDeleteSources(1, &od->source);
- alDeleteBuffers(NUM_BUFFERS, od->buffers);
- alcDestroyContext(od->context);
- alcCloseDevice(od->device);
-}
-
-static unsigned
-openal_delay(struct audio_output *ao)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
-
- return od->filled < NUM_BUFFERS || openal_has_processed(od)
- ? 0
- /* we don't know exactly how long we must wait for the
- next buffer to finish, so this is a random
- guess: */
- : 50;
-}
-
-static size_t
-openal_play(struct audio_output *ao, const void *chunk, size_t size,
- gcc_unused Error &error)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
- ALuint buffer;
-
- if (alcGetCurrentContext() != od->context) {
- alcMakeContextCurrent(od->context);
- }
-
- if (od->filled < NUM_BUFFERS) {
- /* fill all buffers */
- buffer = od->buffers[od->filled];
- od->filled++;
- } else {
- /* wait for processed buffer */
- while (!openal_has_processed(od))
- g_usleep(10);
-
- alSourceUnqueueBuffers(od->source, 1, &buffer);
- }
-
- alBufferData(buffer, od->format, chunk, size, od->frequency);
- alSourceQueueBuffers(od->source, 1, &buffer);
-
- if (!openal_is_playing(od))
- alSourcePlay(od->source);
-
- return size;
-}
-
-static void
-openal_cancel(struct audio_output *ao)
-{
- OpenALOutput *od = (OpenALOutput *)ao;
-
- od->filled = 0;
- alcMakeContextCurrent(od->context);
- alSourceStop(od->source);
-
- /* force-unqueue all buffers */
- alSourcei(od->source, AL_BUFFER, 0);
- od->filled = 0;
-}
-
-const struct audio_output_plugin openal_output_plugin = {
- "openal",
- nullptr,
- openal_init,
- openal_finish,
- nullptr,
- nullptr,
- openal_open,
- openal_close,
- openal_delay,
- nullptr,
- openal_play,
- nullptr,
- openal_cancel,
- nullptr,
- nullptr,
-};
diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/OpenALOutputPlugin.hxx
deleted file mode 100644
index e1ebf3d4f..000000000
--- a/src/output/OpenALOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX
-#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin openal_output_plugin;
-
-#endif
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx
deleted file mode 100644
index cdde6d562..000000000
--- a/src/output/OssOutputPlugin.cxx
+++ /dev/null
@@ -1,780 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OssOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <assert.h>
-
-#if defined(__OpenBSD__) || defined(__NetBSD__)
-# include <soundcard.h>
-#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-# include <sys/soundcard.h>
-#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-
-/* We got bug reports from FreeBSD users who said that the two 24 bit
- formats generate white noise on FreeBSD, but 32 bit works. This is
- a workaround until we know what exactly is expected by the kernel
- audio drivers. */
-#ifndef __linux__
-#undef AFMT_S24_PACKED
-#undef AFMT_S24_NE
-#endif
-
-#ifdef AFMT_S24_PACKED
-#include "pcm/PcmExport.hxx"
-#include "util/Manual.hxx"
-#endif
-
-struct OssOutput {
- struct audio_output base;
-
-#ifdef AFMT_S24_PACKED
- Manual<PcmExport> pcm_export;
-#endif
-
- int fd;
- const char *device;
-
- /**
- * The current input audio format. This is needed to reopen
- * the device after cancel().
- */
- AudioFormat audio_format;
-
- /**
- * The current OSS audio format. This is needed to reopen the
- * device after cancel().
- */
- int oss_format;
-
- OssOutput():fd(-1), device(nullptr) {}
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &oss_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain oss_output_domain("oss_output");
-
-enum oss_stat {
- OSS_STAT_NO_ERROR = 0,
- OSS_STAT_NOT_CHAR_DEV = -1,
- OSS_STAT_NO_PERMS = -2,
- OSS_STAT_DOESN_T_EXIST = -3,
- OSS_STAT_OTHER = -4,
-};
-
-static enum oss_stat
-oss_stat_device(const char *device, int *errno_r)
-{
- struct stat st;
-
- if (0 == stat(device, &st)) {
- if (!S_ISCHR(st.st_mode)) {
- return OSS_STAT_NOT_CHAR_DEV;
- }
- } else {
- *errno_r = errno;
-
- switch (errno) {
- case ENOENT:
- case ENOTDIR:
- return OSS_STAT_DOESN_T_EXIST;
- case EACCES:
- return OSS_STAT_NO_PERMS;
- default:
- return OSS_STAT_OTHER;
- }
- }
-
- return OSS_STAT_NO_ERROR;
-}
-
-static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
-
-static bool
-oss_output_test_default_device(void)
-{
- int fd, i;
-
- for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
- fd = open_cloexec(default_devices[i], O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
-
- FormatErrno(oss_output_domain,
- "Error opening OSS device \"%s\"",
- default_devices[i]);
- }
-
- return false;
-}
-
-static struct audio_output *
-oss_open_default(Error &error)
-{
- int err[ARRAY_SIZE(default_devices)];
- enum oss_stat ret[ARRAY_SIZE(default_devices)];
-
- const config_param empty;
- for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
- ret[i] = oss_stat_device(default_devices[i], &err[i]);
- if (ret[i] == OSS_STAT_NO_ERROR) {
- OssOutput *od = new OssOutput();
- if (!od->Initialize(empty, error)) {
- delete od;
- return NULL;
- }
-
- od->device = default_devices[i];
- return &od->base;
- }
- }
-
- for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
- const char *dev = default_devices[i];
- switch(ret[i]) {
- case OSS_STAT_NO_ERROR:
- /* never reached */
- break;
- case OSS_STAT_DOESN_T_EXIST:
- FormatWarning(oss_output_domain,
- "%s not found", dev);
- break;
- case OSS_STAT_NOT_CHAR_DEV:
- FormatWarning(oss_output_domain,
- "%s is not a character device", dev);
- break;
- case OSS_STAT_NO_PERMS:
- FormatWarning(oss_output_domain,
- "%s: permission denied", dev);
- break;
- case OSS_STAT_OTHER:
- FormatErrno(oss_output_domain, err[i],
- "Error accessing %s", dev);
- }
- }
-
- error.Set(oss_output_domain,
- "error trying to open default OSS device");
- return NULL;
-}
-
-static struct audio_output *
-oss_output_init(const config_param &param, Error &error)
-{
- const char *device = param.GetBlockValue("device");
- if (device != NULL) {
- OssOutput *od = new OssOutput();
- if (!od->Initialize(param, error)) {
- delete od;
- return NULL;
- }
-
- od->device = device;
- return &od->base;
- }
-
- return oss_open_default(error);
-}
-
-static void
-oss_output_finish(struct audio_output *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- ao_base_finish(&od->base);
- delete od;
-}
-
-#ifdef AFMT_S24_PACKED
-
-static bool
-oss_output_enable(struct audio_output *ao, gcc_unused Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->pcm_export.Construct();
- return true;
-}
-
-static void
-oss_output_disable(struct audio_output *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->pcm_export.Destruct();
-}
-
-#endif
-
-static void
-oss_close(OssOutput *od)
-{
- if (od->fd >= 0)
- close(od->fd);
- od->fd = -1;
-}
-
-/**
- * A tri-state type for oss_try_ioctl().
- */
-enum oss_setup_result {
- SUCCESS,
- ERROR,
- UNSUPPORTED,
-};
-
-/**
- * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
- * returned. If the parameter is not supported, UNSUPPORTED is
- * returned. Any other failure returns ERROR and allocates an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
- const char *msg, Error &error)
-{
- assert(fd >= 0);
- assert(value_r != NULL);
- assert(msg != NULL);
- assert(!error.IsDefined());
-
- int ret = ioctl(fd, request, value_r);
- if (ret >= 0)
- return SUCCESS;
-
- if (errno == EINVAL)
- return UNSUPPORTED;
-
- error.SetErrno(msg);
- return ERROR;
-}
-
-/**
- * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
- * returned. If the parameter is not supported, UNSUPPORTED is
- * returned. Any other failure returns ERROR and allocates an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl(int fd, unsigned long request, int value,
- const char *msg, Error &error_r)
-{
- return oss_try_ioctl_r(fd, request, &value, msg, error_r);
-}
-
-/**
- * Set up the channel number, and attempts to find alternatives if the
- * specified number is not supported.
- */
-static bool
-oss_setup_channels(int fd, AudioFormat &audio_format, Error &error)
-{
- const char *const msg = "Failed to set channel count";
- int channels = audio_format.channels;
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format.channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- for (unsigned i = 1; i < 2; ++i) {
- if (i == audio_format.channels)
- /* don't try that again */
- continue;
-
- channels = i;
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
- msg, error);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format.channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- error.Set(oss_output_domain, msg);
- return false;
-}
-
-/**
- * Set up the sample rate, and attempts to find alternatives if the
- * specified sample rate is not supported.
- */
-static bool
-oss_setup_sample_rate(int fd, AudioFormat &audio_format,
- Error &error)
-{
- const char *const msg = "Failed to set sample rate";
- int sample_rate = audio_format.sample_rate;
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
- msg, error);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_sample_rate(sample_rate))
- break;
-
- audio_format.sample_rate = sample_rate;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- static const int sample_rates[] = { 48000, 44100, 0 };
- for (unsigned i = 0; sample_rates[i] != 0; ++i) {
- sample_rate = sample_rates[i];
- if (sample_rate == (int)audio_format.sample_rate)
- continue;
-
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
- msg, error);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_sample_rate(sample_rate))
- break;
-
- audio_format.sample_rate = sample_rate;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- error.Set(oss_output_domain, msg);
- return false;
-}
-
-/**
- * Convert a MPD sample format to its OSS counterpart. Returns
- * AFMT_QUERY if there is no direct counterpart.
- */
-static int
-sample_format_to_oss(SampleFormat format)
-{
- switch (format) {
- case SampleFormat::UNDEFINED:
- case SampleFormat::FLOAT:
- case SampleFormat::DSD:
- return AFMT_QUERY;
-
- case SampleFormat::S8:
- return AFMT_S8;
-
- case SampleFormat::S16:
- return AFMT_S16_NE;
-
- case SampleFormat::S24_P32:
-#ifdef AFMT_S24_NE
- return AFMT_S24_NE;
-#else
- return AFMT_QUERY;
-#endif
-
- case SampleFormat::S32:
-#ifdef AFMT_S32_NE
- return AFMT_S32_NE;
-#else
- return AFMT_QUERY;
-#endif
- }
-
- return AFMT_QUERY;
-}
-
-/**
- * Convert an OSS sample format to its MPD counterpart. Returns
- * SampleFormat::UNDEFINED if there is no direct counterpart.
- */
-static SampleFormat
-sample_format_from_oss(int format)
-{
- switch (format) {
- case AFMT_S8:
- return SampleFormat::S8;
-
- case AFMT_S16_NE:
- return SampleFormat::S16;
-
-#ifdef AFMT_S24_PACKED
- case AFMT_S24_PACKED:
- return SampleFormat::S24_P32;
-#endif
-
-#ifdef AFMT_S24_NE
- case AFMT_S24_NE:
- return SampleFormat::S24_P32;
-#endif
-
-#ifdef AFMT_S32_NE
- case AFMT_S32_NE:
- return SampleFormat::S32;
-#endif
-
- default:
- return SampleFormat::UNDEFINED;
- }
-}
-
-/**
- * Probe one sample format.
- *
- * @return the selected sample format or SampleFormat::UNDEFINED on
- * error
- */
-static enum oss_setup_result
-oss_probe_sample_format(int fd, SampleFormat sample_format,
- SampleFormat *sample_format_r,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- PcmExport &pcm_export,
-#endif
- Error &error)
-{
- int oss_format = sample_format_to_oss(sample_format);
- if (oss_format == AFMT_QUERY)
- return UNSUPPORTED;
-
- enum oss_setup_result result =
- oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format,
- "Failed to set sample format", error);
-
-#ifdef AFMT_S24_PACKED
- if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
- /* if the driver doesn't support padded 24 bit, try
- packed 24 bit */
- oss_format = AFMT_S24_PACKED;
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format,
- "Failed to set sample format", error);
- }
-#endif
-
- if (result != SUCCESS)
- return result;
-
- sample_format = sample_format_from_oss(oss_format);
- if (sample_format == SampleFormat::UNDEFINED)
- return UNSUPPORTED;
-
- *sample_format_r = sample_format;
- *oss_format_r = oss_format;
-
-#ifdef AFMT_S24_PACKED
- pcm_export.Open(sample_format, 0, false, false,
- oss_format == AFMT_S24_PACKED,
- oss_format == AFMT_S24_PACKED &&
- !IsLittleEndian());
-#endif
-
- return SUCCESS;
-}
-
-/**
- * Set up the sample format, and attempts to find alternatives if the
- * specified format is not supported.
- */
-static bool
-oss_setup_sample_format(int fd, AudioFormat &audio_format,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- PcmExport &pcm_export,
-#endif
- Error &error)
-{
- SampleFormat mpd_format;
- enum oss_setup_result result =
- oss_probe_sample_format(fd, audio_format.format,
- &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
- pcm_export,
-#endif
- error);
- switch (result) {
- case SUCCESS:
- audio_format.format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
-
- if (result != UNSUPPORTED)
- return result == SUCCESS;
-
- /* the requested sample format is not available - probe for
- other formats supported by MPD */
-
- static const SampleFormat sample_formats[] = {
- SampleFormat::S24_P32,
- SampleFormat::S32,
- SampleFormat::S16,
- SampleFormat::S8,
- SampleFormat::UNDEFINED /* sentinel */
- };
-
- for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
- mpd_format = sample_formats[i];
- if (mpd_format == audio_format.format)
- /* don't try that again */
- continue;
-
- result = oss_probe_sample_format(fd, mpd_format,
- &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
- pcm_export,
-#endif
- error);
- switch (result) {
- case SUCCESS:
- audio_format.format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- error.Set(oss_output_domain, "Failed to set sample format");
- return false;
-}
-
-/**
- * Sets up the OSS device which was opened before.
- */
-static bool
-oss_setup(OssOutput *od, AudioFormat &audio_format,
- Error &error)
-{
- return oss_setup_channels(od->fd, audio_format, error) &&
- oss_setup_sample_rate(od->fd, audio_format, error) &&
- oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
-#ifdef AFMT_S24_PACKED
- od->pcm_export,
-#endif
- error);
-}
-
-/**
- * Reopen the device with the saved audio_format, without any probing.
- */
-static bool
-oss_reopen(OssOutput *od, Error &error)
-{
- assert(od->fd < 0);
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
- return false;
- }
-
- enum oss_setup_result result;
-
- const char *const msg1 = "Failed to set channel count";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
- od->audio_format.channels, msg1, error);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, msg1);
- return false;
- }
-
- const char *const msg2 = "Failed to set sample rate";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
- od->audio_format.sample_rate, msg2, error);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, msg2);
- return false;
- }
-
- const char *const msg3 = "Failed to set sample format";
- result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
- od->oss_format,
- msg3, error);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, msg3);
- return false;
- }
-
- return true;
-}
-
-static bool
-oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
- return false;
- }
-
- if (!oss_setup(od, audio_format, error)) {
- oss_close(od);
- return false;
- }
-
- od->audio_format = audio_format;
- return true;
-}
-
-static void
-oss_output_close(struct audio_output *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- oss_close(od);
-}
-
-static void
-oss_output_cancel(struct audio_output *ao)
-{
- OssOutput *od = (OssOutput *)ao;
-
- if (od->fd >= 0) {
- ioctl(od->fd, SNDCTL_DSP_RESET, 0);
- oss_close(od);
- }
-}
-
-static size_t
-oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
- ssize_t ret;
-
- assert(size > 0);
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_reopen(od, error))
- return 0;
-
-#ifdef AFMT_S24_PACKED
- chunk = od->pcm_export->Export(chunk, size, size);
-#endif
-
- assert(size > 0);
-
- while (true) {
- ret = write(od->fd, chunk, size);
- if (ret > 0) {
-#ifdef AFMT_S24_PACKED
- ret = od->pcm_export->CalcSourceSize(ret);
-#endif
- return ret;
- }
-
- if (ret < 0 && errno != EINTR) {
- error.FormatErrno("Write error on %s", od->device);
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin oss_output_plugin = {
- "oss",
- oss_output_test_default_device,
- oss_output_init,
- oss_output_finish,
-#ifdef AFMT_S24_PACKED
- oss_output_enable,
- oss_output_disable,
-#else
- nullptr,
- nullptr,
-#endif
- oss_output_open,
- oss_output_close,
- nullptr,
- nullptr,
- oss_output_play,
- nullptr,
- oss_output_cancel,
- nullptr,
-
- &oss_mixer_plugin,
-};
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx
deleted file mode 100644
index 6c5c9530b..000000000
--- a/src/output/OssOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX
-#define MPD_OSS_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin oss_output_plugin;
-
-#endif
diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx
new file mode 100644
index 000000000..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..2ec0670c1
--- /dev/null
+++ b/src/output/OutputThread.cxx
@@ -0,0 +1,704 @@
+/*
+ * 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 "pcm/Domain.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();
+
+ const AudioFormat retry_audio_format = out_audio_format;
+
+ retry_without_dsd:
+ 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);
+
+ mutex.unlock();
+ CloseFilter();
+ mutex.lock();
+
+ 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);
+
+ mutex.unlock();
+ ao_plugin_close(this);
+
+ if (error.IsDomain(pcm_domain) &&
+ out_audio_format.format == SampleFormat::DSD) {
+ /* if the audio output supports DSD, but not
+ the given sample rate, it asks MPD to
+ resample; resampling DSD however is not
+ implemented; our last resort is to give up
+ DSD and fall back to PCM */
+
+ // TODO: clean up this workaround
+
+ FormatError(output_domain, "Retrying without DSD");
+
+ out_audio_format = retry_audio_format;
+ out_audio_format.format = SampleFormat::FLOAT;
+
+ /* clear the Error to allow reusing it */
+ error.Clear();
+
+ /* sorry for the "goto" - this is a workaround
+ for the stable branch that should be as
+ unintrusive as possible */
+ goto retry_without_dsd;
+ }
+
+ CloseFilter();
+ mutex.lock();
+
+ 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;
+
+ mutex.unlock();
+ CloseFilter();
+ mutex.lock();
+
+ 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/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
deleted file mode 100644
index 34d615284..000000000
--- a/src/output/PipeOutputPlugin.cxx
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PipeOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <string>
-
-#include <stdio.h>
-
-struct PipeOutput {
- struct audio_output base;
-
- std::string cmd;
- FILE *fh;
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &pipe_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain pipe_output_domain("pipe_output");
-
-inline bool
-PipeOutput::Configure(const config_param &param, Error &error)
-{
- cmd = param.GetBlockValue("command", "");
- if (cmd.empty()) {
- error.Set(config_domain,
- "No \"command\" parameter specified");
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pipe_output_init(const config_param &param, Error &error)
-{
- PipeOutput *pd = new PipeOutput();
-
- if (!pd->Initialize(param, error)) {
- delete pd;
- return nullptr;
- }
-
- if (!pd->Configure(param, error)) {
- pd->Deinitialize();
- delete pd;
- return nullptr;
- }
-
- return &pd->base;
-}
-
-static void
-pipe_output_finish(struct audio_output *ao)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pd->Deinitialize();
- delete pd;
-}
-
-static bool
-pipe_output_open(struct audio_output *ao,
- gcc_unused AudioFormat &audio_format,
- Error &error)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pd->fh = popen(pd->cmd.c_str(), "w");
- if (pd->fh == nullptr) {
- error.FormatErrno("Error opening pipe \"%s\"",
- pd->cmd.c_str());
- return false;
- }
-
- return true;
-}
-
-static void
-pipe_output_close(struct audio_output *ao)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pclose(pd->fh);
-}
-
-static size_t
-pipe_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- PipeOutput *pd = (PipeOutput *)ao;
- size_t ret;
-
- ret = fwrite(chunk, 1, size, pd->fh);
- if (ret == 0)
- error.SetErrno("Write error on pipe");
-
- return ret;
-}
-
-const struct audio_output_plugin pipe_output_plugin = {
- "pipe",
- nullptr,
- pipe_output_init,
- pipe_output_finish,
- nullptr,
- nullptr,
- pipe_output_open,
- pipe_output_close,
- nullptr,
- nullptr,
- pipe_output_play,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
-};
diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/PipeOutputPlugin.hxx
deleted file mode 100644
index f0c29706b..000000000
--- a/src/output/PipeOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX
-#define MPD_PIPE_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin pipe_output_plugin;
-
-#endif
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx
deleted file mode 100644
index 1eece448a..000000000
--- a/src/output/PulseOutputPlugin.cxx
+++ /dev/null
@@ -1,887 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PulseOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "mixer/PulseMixerPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <pulse/thread-mainloop.h>
-#include <pulse/context.h>
-#include <pulse/stream.h>
-#include <pulse/introspect.h>
-#include <pulse/subscribe.h>
-#include <pulse/error.h>
-#include <pulse/version.h>
-
-#include <assert.h>
-#include <stddef.h>
-
-#define MPD_PULSE_NAME "Music Player Daemon"
-
-struct PulseOutput {
- struct audio_output base;
-
- const char *name;
- const char *server;
- const char *sink;
-
- PulseMixer *mixer;
-
- struct pa_threaded_mainloop *mainloop;
- struct pa_context *context;
- struct pa_stream *stream;
-
- size_t writable;
-};
-
-static constexpr Domain pulse_output_domain("pulse_output");
-
-static void
-SetError(Error &error, pa_context *context, const char *msg)
-{
- const int e = pa_context_errno(context);
- error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
-}
-
-void
-pulse_output_lock(PulseOutput *po)
-{
- pa_threaded_mainloop_lock(po->mainloop);
-}
-
-void
-pulse_output_unlock(PulseOutput *po)
-{
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm)
-{
- assert(po != nullptr);
- assert(po->mixer == nullptr);
- assert(pm != nullptr);
-
- po->mixer = pm;
-
- if (po->mainloop == nullptr)
- return;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != nullptr &&
- pa_context_get_state(po->context) == PA_CONTEXT_READY) {
- pulse_mixer_on_connect(pm, po->context);
-
- if (po->stream != nullptr &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY)
- pulse_mixer_on_change(pm, po->context, po->stream);
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_clear_mixer(PulseOutput *po, gcc_unused PulseMixer *pm)
-{
- assert(po != nullptr);
- assert(pm != nullptr);
- assert(po->mixer == pm);
-
- po->mixer = nullptr;
-}
-
-bool
-pulse_output_set_volume(PulseOutput *po, const pa_cvolume *volume,
- Error &error)
-{
- pa_operation *o;
-
- if (po->context == nullptr || po->stream == nullptr ||
- pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- error.Set(pulse_output_domain, "disconnected");
- return false;
- }
-
- o = pa_context_set_sink_input_volume(po->context,
- pa_stream_get_index(po->stream),
- volume, nullptr, nullptr);
- if (o == nullptr) {
- SetError(error, po->context,
- "failed to set PulseAudio volume");
- return false;
- }
-
- pa_operation_unref(o);
- return true;
-}
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- * unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- * false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
- struct pa_operation *operation)
-{
- pa_operation_state_t state;
-
- assert(mainloop != nullptr);
- assert(operation != nullptr);
-
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-/**
- * Callback function for stream operation. It just sends a signal to
- * the caller thread, to wake pulse_wait_for_operation() up.
- */
-static void
-pulse_output_stream_success_cb(gcc_unused pa_stream *s,
- gcc_unused int success, void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_context_state_cb(struct pa_context *context, void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY:
- if (po->mixer != nullptr)
- pulse_mixer_on_connect(po->mixer, context);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- if (po->mixer != nullptr)
- pulse_mixer_on_disconnect(po->mixer);
-
- /* the caller thread might be waiting for these
- states */
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- }
-}
-
-static void
-pulse_output_subscribe_cb(pa_context *context,
- pa_subscription_event_type_t t,
- uint32_t idx, void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
- pa_subscription_event_type_t facility =
- pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
- pa_subscription_event_type_t type =
- pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
-
- if (po->mixer != nullptr &&
- facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
- po->stream != nullptr &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY &&
- idx == pa_stream_get_index(po->stream) &&
- (type == PA_SUBSCRIPTION_EVENT_NEW ||
- type == PA_SUBSCRIPTION_EVENT_CHANGE))
- pulse_mixer_on_change(po->mixer, context, po->stream);
-}
-
-/**
- * Attempt to connect asynchronously to the PulseAudio server.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_connect(PulseOutput *po, Error &error)
-{
- assert(po != nullptr);
- assert(po->context != nullptr);
-
- if (pa_context_connect(po->context, po->server,
- (pa_context_flags_t)0, nullptr) < 0) {
- SetError(error, po->context,
- "pa_context_connect() has failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Frees and clears the stream.
- */
-static void
-pulse_output_delete_stream(PulseOutput *po)
-{
- assert(po != nullptr);
- assert(po->stream != nullptr);
-
- pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
-
- pa_stream_set_state_callback(po->stream, nullptr, nullptr);
- pa_stream_set_write_callback(po->stream, nullptr, nullptr);
-
- pa_stream_disconnect(po->stream);
- pa_stream_unref(po->stream);
- po->stream = nullptr;
-}
-
-/**
- * Frees and clears the context.
- *
- * Caller must lock the main loop.
- */
-static void
-pulse_output_delete_context(PulseOutput *po)
-{
- assert(po != nullptr);
- assert(po->context != nullptr);
-
- pa_context_set_state_callback(po->context, nullptr, nullptr);
- pa_context_set_subscribe_callback(po->context, nullptr, nullptr);
-
- pa_context_disconnect(po->context);
- pa_context_unref(po->context);
- po->context = nullptr;
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_context(PulseOutput *po, Error &error)
-{
- assert(po != nullptr);
- assert(po->mainloop != nullptr);
-
- po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
- MPD_PULSE_NAME);
- if (po->context == nullptr) {
- error.Set(pulse_output_domain, "pa_context_new() has failed");
- return false;
- }
-
- pa_context_set_state_callback(po->context,
- pulse_output_context_state_cb, po);
- pa_context_set_subscribe_callback(po->context,
- pulse_output_subscribe_cb, po);
-
- if (!pulse_output_connect(po, error)) {
- pulse_output_delete_context(po);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pulse_output_init(const config_param &param, Error &error)
-{
- PulseOutput *po;
-
- g_setenv("PULSE_PROP_media.role", "music", true);
-
- po = new PulseOutput();
- if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) {
- delete po;
- return nullptr;
- }
-
- po->name = param.GetBlockValue("name", "mpd_pulse");
- po->server = param.GetBlockValue("server");
- po->sink = param.GetBlockValue("sink");
-
- po->mixer = nullptr;
- po->mainloop = nullptr;
- po->context = nullptr;
- po->stream = nullptr;
-
- return &po->base;
-}
-
-static void
-pulse_output_finish(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- ao_base_finish(&po->base);
- delete po;
-}
-
-static bool
-pulse_output_enable(struct audio_output *ao, Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- assert(po->mainloop == nullptr);
- assert(po->context == nullptr);
-
- /* create the libpulse mainloop and start the thread */
-
- po->mainloop = pa_threaded_mainloop_new();
- if (po->mainloop == nullptr) {
- error.Set(pulse_output_domain,
- "pa_threaded_mainloop_new() has failed");
- return false;
- }
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_threaded_mainloop_start(po->mainloop) < 0) {
- pa_threaded_mainloop_unlock(po->mainloop);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = nullptr;
-
- error.Set(pulse_output_domain,
- "pa_threaded_mainloop_start() has failed");
- return false;
- }
-
- /* create the libpulse context and connect it */
-
- if (!pulse_output_setup_context(po, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- pa_threaded_mainloop_stop(po->mainloop);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = nullptr;
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static void
-pulse_output_disable(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- assert(po->mainloop != nullptr);
-
- pa_threaded_mainloop_stop(po->mainloop);
- if (po->context != nullptr)
- pulse_output_delete_context(po);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = nullptr;
-}
-
-/**
- * Check if the context is (already) connected, and waits if not. If
- * the context has been disconnected, retry to connect.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_connection(PulseOutput *po, Error &error)
-{
- assert(po->mainloop != nullptr);
-
- pa_context_state_t state;
-
- if (po->context == nullptr && !pulse_output_setup_context(po, error))
- return false;
-
- while (true) {
- state = pa_context_get_state(po->context);
- switch (state) {
- case PA_CONTEXT_READY:
- /* nothing to do */
- return true;
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- /* failure */
- SetError(error, po->context, "failed to connect");
- pulse_output_delete_context(po);
- return false;
-
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- /* wait some more */
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-static void
-pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
-
- assert(stream == po->stream || po->stream == nullptr);
- assert(po->mainloop != nullptr);
-
- /* wake up the main loop to break out of the loop in
- pulse_output_play() */
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
-
- assert(stream == po->stream || po->stream == nullptr);
- assert(po->mainloop != nullptr);
- assert(po->context != nullptr);
-
- switch (pa_stream_get_state(stream)) {
- case PA_STREAM_READY:
- if (po->mixer != nullptr)
- pulse_mixer_on_change(po->mixer, po->context, stream);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_STREAM_FAILED:
- case PA_STREAM_TERMINATED:
- if (po->mixer != nullptr)
- pulse_mixer_on_disconnect(po->mixer);
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
- break;
-
- case PA_STREAM_UNCONNECTED:
- case PA_STREAM_CREATING:
- break;
- }
-}
-
-static void
-pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes,
- void *userdata)
-{
- PulseOutput *po = (PulseOutput *)userdata;
-
- assert(po->mainloop != nullptr);
-
- po->writable = nbytes;
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-/**
- * Create, set up and connect a context.
- *
- * Caller must lock the main loop.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
- Error &error)
-{
- assert(po != nullptr);
- assert(po->context != nullptr);
-
- po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
- if (po->stream == nullptr) {
- SetError(error, po->context, "pa_stream_new() has failed");
- return false;
- }
-
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-
- pa_stream_set_state_callback(po->stream,
- pulse_output_stream_state_cb, po);
- pa_stream_set_write_callback(po->stream,
- pulse_output_stream_write_cb, po);
-
- return true;
-}
-
-static bool
-pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
- pa_sample_spec ss;
-
- assert(po->mainloop != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != nullptr) {
- switch (pa_context_get_state(po->context)) {
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- /* the connection was closed meanwhile; delete
- it, and pulse_output_wait_connection() will
- reopen it */
- pulse_output_delete_context(po);
- break;
-
- case PA_CONTEXT_READY:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- }
- }
-
- if (!pulse_output_wait_connection(po, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format.format = SampleFormat::S16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format.sample_rate;
- ss.channels = audio_format.channels;
-
- /* create a stream .. */
-
- if (!pulse_output_setup_stream(po, &ss, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* .. and connect it (asynchronously) */
-
- if (pa_stream_connect_playback(po->stream, po->sink,
- nullptr, pa_stream_flags_t(0),
- nullptr, nullptr) < 0) {
- pulse_output_delete_stream(po);
-
- SetError(error, po->context,
- "pa_stream_connect_playback() has failed");
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static void
-pulse_output_close(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
- pa_operation *o;
-
- assert(po->mainloop != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
- o = pa_stream_drain(po->stream,
- pulse_output_stream_success_cb, po);
- if (o == nullptr) {
- FormatWarning(pulse_output_domain,
- "pa_stream_drain() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- } else
- pulse_wait_for_operation(po->mainloop, o);
- }
-
- pulse_output_delete_stream(po);
-
- if (po->context != nullptr &&
- pa_context_get_state(po->context) != PA_CONTEXT_READY)
- pulse_output_delete_context(po);
-
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-/**
- * Check if the stream is (already) connected, and waits if not. The
- * mainloop must be locked before calling this function.
- *
- * @return true on success, false on error
- */
-static bool
-pulse_output_wait_stream(PulseOutput *po, Error &error)
-{
- while (true) {
- switch (pa_stream_get_state(po->stream)) {
- case PA_STREAM_READY:
- return true;
-
- case PA_STREAM_FAILED:
- case PA_STREAM_TERMINATED:
- case PA_STREAM_UNCONNECTED:
- SetError(error, po->context,
- "failed to connect the stream");
- return false;
-
- case PA_STREAM_CREATING:
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-/**
- * Sets cork mode on the stream.
- */
-static bool
-pulse_output_stream_pause(PulseOutput *po, bool pause,
- Error &error)
-{
- pa_operation *o;
-
- assert(po->mainloop != nullptr);
- assert(po->context != nullptr);
- assert(po->stream != nullptr);
-
- o = pa_stream_cork(po->stream, pause,
- pulse_output_stream_success_cb, po);
- if (o == nullptr) {
- SetError(error, po->context, "pa_stream_cork() has failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(po->mainloop, o)) {
- SetError(error, po->context, "pa_stream_cork() has failed");
- return false;
- }
-
- return true;
-}
-
-static unsigned
-pulse_output_delay(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
- unsigned result = 0;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->base.pause && pa_stream_is_corked(po->stream) &&
- pa_stream_get_state(po->stream) == PA_STREAM_READY)
- /* idle while paused */
- result = 1000;
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return result;
-}
-
-static size_t
-pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- assert(po->mainloop != nullptr);
- assert(po->stream != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already) connected */
-
- if (!pulse_output_wait_stream(po, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- assert(po->context != nullptr);
-
- /* unpause if previously paused */
-
- if (pa_stream_is_corked(po->stream) &&
- !pulse_output_stream_pause(po, false, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- /* wait until the server allows us to write */
-
- while (po->writable == 0) {
- if (pa_stream_is_suspended(po->stream)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- error.Set(pulse_output_domain, "suspended");
- return 0;
- }
-
- pa_threaded_mainloop_wait(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- pa_threaded_mainloop_unlock(po->mainloop);
- error.Set(pulse_output_domain, "disconnected");
- return 0;
- }
- }
-
- /* now write */
-
- if (size > po->writable)
- /* don't send more than possible */
- size = po->writable;
-
- po->writable -= size;
-
- int result = pa_stream_write(po->stream, chunk, size, nullptr,
- 0, PA_SEEK_RELATIVE);
- pa_threaded_mainloop_unlock(po->mainloop);
- if (result < 0) {
- SetError(error, po->context, "pa_stream_write() failed");
- return 0;
- }
-
- return size;
-}
-
-static void
-pulse_output_cancel(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
- pa_operation *o;
-
- assert(po->mainloop != nullptr);
- assert(po->stream != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- /* no need to flush when the stream isn't connected
- yet */
- pa_threaded_mainloop_unlock(po->mainloop);
- return;
- }
-
- assert(po->context != nullptr);
-
- o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
- if (o == nullptr) {
- FormatWarning(pulse_output_domain,
- "pa_stream_flush() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- pa_threaded_mainloop_unlock(po->mainloop);
- return;
- }
-
- pulse_wait_for_operation(po->mainloop, o);
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-static bool
-pulse_output_pause(struct audio_output *ao)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- assert(po->mainloop != nullptr);
- assert(po->stream != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already/still) connected */
-
- Error error;
- if (!pulse_output_wait_stream(po, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- LogError(error);
- return false;
- }
-
- assert(po->context != nullptr);
-
- /* cork the stream */
-
- if (!pa_stream_is_corked(po->stream) &&
- !pulse_output_stream_pause(po, true, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- LogError(error);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static bool
-pulse_output_test_default_device(void)
-{
- bool success;
-
- const config_param empty;
- PulseOutput *po = (PulseOutput *)
- pulse_output_init(empty, IgnoreError());
- if (po == nullptr)
- return false;
-
- success = pulse_output_wait_connection(po, IgnoreError());
- pulse_output_finish(&po->base);
-
- return success;
-}
-
-const struct audio_output_plugin pulse_output_plugin = {
- "pulse",
- pulse_output_test_default_device,
- pulse_output_init,
- pulse_output_finish,
- pulse_output_enable,
- pulse_output_disable,
- pulse_output_open,
- pulse_output_close,
- pulse_output_delay,
- nullptr,
- pulse_output_play,
- nullptr,
- pulse_output_cancel,
- pulse_output_pause,
-
- &pulse_mixer_plugin,
-};
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx
deleted file mode 100644
index 0ed8404bc..000000000
--- a/src/output/PulseOutputPlugin.hxx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX
-#define MPD_PULSE_OUTPUT_PLUGIN_HXX
-
-struct PulseOutput;
-struct PulseMixer;
-struct pa_cvolume;
-class Error;
-
-extern const struct audio_output_plugin pulse_output_plugin;
-
-void
-pulse_output_lock(PulseOutput *po);
-
-void
-pulse_output_unlock(PulseOutput *po);
-
-void
-pulse_output_set_mixer(PulseOutput *po, PulseMixer *pm);
-
-void
-pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm);
-
-bool
-pulse_output_set_volume(PulseOutput *po,
- const struct pa_cvolume *volume, Error &error);
-
-#endif
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
deleted file mode 100644
index 9a7eba01f..000000000
--- a/src/output/RecorderOutputPlugin.cxx
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RecorderOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/fd_util.h"
-#include "open.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-struct RecorderOutput {
- struct audio_output base;
-
- /**
- * The configured encoder plugin.
- */
- Encoder *encoder;
-
- /**
- * The destination file name.
- */
- const char *path;
-
- /**
- * The destination file descriptor.
- */
- int fd;
-
- /**
- * The buffer for encoder_read().
- */
- char buffer[32768];
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &recorder_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-
- bool WriteToFile(const void *data, size_t length, Error &error);
-
- /**
- * Writes pending data from the encoder to the output file.
- */
- bool EncoderToFile(Error &error);
-};
-
-static constexpr Domain recorder_output_domain("recorder_output");
-
-inline bool
-RecorderOutput::Configure(const config_param &param, Error &error)
-{
- /* read configuration */
-
- const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
- const auto encoder_plugin = encoder_plugin_get(encoder_name);
- if (encoder_plugin == nullptr) {
- error.Format(config_domain,
- "No such encoder: %s", encoder_name);
- return false;
- }
-
- path = param.GetBlockValue("path");
- if (path == nullptr) {
- error.Set(config_domain, "'path' not configured");
- return false;
- }
-
- /* initialize encoder */
-
- encoder = encoder_init(*encoder_plugin, param, error);
- if (encoder == nullptr)
- return false;
-
- return true;
-}
-
-static audio_output *
-recorder_output_init(const config_param &param, Error &error)
-{
- RecorderOutput *recorder = new RecorderOutput();
-
- if (!recorder->Initialize(param, error)) {
- delete recorder;
- return nullptr;
- }
-
- if (!recorder->Configure(param, error)) {
- recorder->Deinitialize();
- delete recorder;
- return nullptr;
- }
-
- return &recorder->base;
-}
-
-static void
-recorder_output_finish(struct audio_output *ao)
-{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- encoder_finish(recorder->encoder);
- recorder->Deinitialize();
- delete recorder;
-}
-
-inline bool
-RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
-{
- assert(length > 0);
-
- const uint8_t *data = (const uint8_t *)_data, *end = data + length;
-
- while (true) {
- ssize_t nbytes = write(fd, data, end - data);
- if (nbytes > 0) {
- data += nbytes;
- if (data == end)
- return true;
- } else if (nbytes == 0) {
- /* shouldn't happen for files */
- error.Set(recorder_output_domain,
- "write() returned 0");
- return false;
- } else if (errno != EINTR) {
- error.FormatErrno("Failed to write to '%s'", path);
- return false;
- }
- }
-}
-
-inline bool
-RecorderOutput::EncoderToFile(Error &error)
-{
- assert(fd >= 0);
-
- while (true) {
- /* read from the encoder */
-
- size_t size = encoder_read(encoder, buffer, sizeof(buffer));
- if (size == 0)
- return true;
-
- /* write everything into the file */
-
- if (!WriteToFile(buffer, size, error))
- return false;
- }
-}
-
-static bool
-recorder_output_open(struct audio_output *ao,
- AudioFormat &audio_format,
- Error &error)
-{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- /* create the output file */
-
- recorder->fd = open_cloexec(recorder->path,
- O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
- 0666);
- if (recorder->fd < 0) {
- error.FormatErrno("Failed to create '%s'", recorder->path);
- return false;
- }
-
- /* open the encoder */
-
- if (!encoder_open(recorder->encoder, audio_format, error)) {
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- if (!recorder->EncoderToFile(error)) {
- encoder_close(recorder->encoder);
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- return true;
-}
-
-static void
-recorder_output_close(struct audio_output *ao)
-{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- /* flush the encoder and write the rest to the file */
-
- if (encoder_end(recorder->encoder, IgnoreError()))
- recorder->EncoderToFile(IgnoreError());
-
- /* now really close everything */
-
- encoder_close(recorder->encoder);
-
- close(recorder->fd);
-}
-
-static size_t
-recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- return encoder_write(recorder->encoder, chunk, size, error) &&
- recorder->EncoderToFile(error)
- ? size : 0;
-}
-
-const struct audio_output_plugin recorder_output_plugin = {
- "recorder",
- nullptr,
- recorder_output_init,
- recorder_output_finish,
- nullptr,
- nullptr,
- recorder_output_open,
- recorder_output_close,
- nullptr,
- nullptr,
- recorder_output_play,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
-};
diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/RecorderOutputPlugin.hxx
deleted file mode 100644
index a27f51e23..000000000
--- a/src/output/RecorderOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX
-#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin recorder_output_plugin;
-
-#endif
diff --git a/src/output/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/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
deleted file mode 100644
index 20d69f3f9..000000000
--- a/src/output/RoarOutputPlugin.cxx
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
- * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RoarOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "thread/Mutex.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <string>
-
-/* libroar/services.h declares roar_service_stream::new - work around
- this C++ problem */
-#define new _new
-#include <roaraudio.h>
-#undef new
-
-class RoarOutput {
- struct audio_output base;
-
- std::string host, name;
-
- roar_vs_t * vss;
- int err;
- int role;
- struct roar_connection con;
- struct roar_audio_info info;
- mutable Mutex mutex;
- bool alive;
-
-public:
- RoarOutput()
- :err(ROAR_ERROR_NONE) {}
-
- operator audio_output *() {
- return &base;
- }
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &roar_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- void Configure(const config_param &param);
-
- bool Open(AudioFormat &audio_format, Error &error);
- void Close();
-
- void SendTag(const Tag &tag);
- size_t Play(const void *chunk, size_t size, Error &error);
- void Cancel();
-
- int GetVolume() const;
- bool SetVolume(unsigned volume);
-};
-
-static constexpr Domain roar_output_domain("roar_output");
-
-inline int
-RoarOutput::GetVolume() const
-{
- const ScopeLock protect(mutex);
-
- if (vss == nullptr || !alive)
- return -1;
-
- float l, r;
- int error;
- if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
- return -1;
-
- return (l + r) * 50;
-}
-
-int
-roar_output_get_volume(RoarOutput *roar)
-{
- return roar->GetVolume();
-}
-
-bool
-RoarOutput::SetVolume(unsigned volume)
-{
- assert(volume <= 100);
-
- const ScopeLock protect(mutex);
- if (vss == nullptr || !alive)
- return false;
-
- int error;
- float level = volume / 100.0;
-
- roar_vs_volume_mono(vss, level, &error);
- return true;
-}
-
-bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume)
-{
- return roar->SetVolume(volume);
-}
-
-inline void
-RoarOutput::Configure(const config_param &param)
-{
- host = param.GetBlockValue("server", "");
- name = param.GetBlockValue("name", "MPD");
-
- const char *_role = param.GetBlockValue("role", "music");
- role = _role != nullptr
- ? roar_str2role(_role)
- : ROAR_ROLE_MUSIC;
-}
-
-static struct audio_output *
-roar_init(const config_param &param, Error &error)
-{
- RoarOutput *self = new RoarOutput();
-
- if (!self->Initialize(param, error)) {
- delete self;
- return nullptr;
- }
-
- self->Configure(param);
- return *self;
-}
-
-static void
-roar_finish(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- self->Deinitialize();
- delete self;
-}
-
-static void
-roar_use_audio_format(struct roar_audio_info *info,
- AudioFormat &audio_format)
-{
- info->rate = audio_format.sample_rate;
- info->channels = audio_format.channels;
- info->codec = ROAR_CODEC_PCM_S;
-
- switch (audio_format.format) {
- case SampleFormat::UNDEFINED:
- case SampleFormat::FLOAT:
- case SampleFormat::DSD:
- info->bits = 16;
- audio_format.format = SampleFormat::S16;
- break;
-
- case SampleFormat::S8:
- info->bits = 8;
- break;
-
- case SampleFormat::S16:
- info->bits = 16;
- break;
-
- case SampleFormat::S24_P32:
- info->bits = 32;
- audio_format.format = SampleFormat::S32;
- break;
-
- case SampleFormat::S32:
- info->bits = 32;
- break;
- }
-}
-
-inline bool
-RoarOutput::Open(AudioFormat &audio_format, Error &error)
-{
- const ScopeLock protect(mutex);
-
- if (roar_simple_connect(&con,
- host.empty() ? nullptr : host.c_str(),
- name.c_str()) < 0) {
- error.Set(roar_output_domain,
- "Failed to connect to Roar server");
- return false;
- }
-
- vss = roar_vs_new_from_con(&con, &err);
-
- if (vss == nullptr || err != ROAR_ERROR_NONE) {
- error.Set(roar_output_domain, "Failed to connect to server");
- return false;
- }
-
- roar_use_audio_format(&info, audio_format);
-
- if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
- error.Set(roar_output_domain, "Failed to start stream");
- return false;
- }
-
- roar_vs_role(vss, role, &err);
- alive = true;
- return true;
-}
-
-static bool
-roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- return self->Open(audio_format, error);
-}
-
-inline void
-RoarOutput::Close()
-{
- const ScopeLock protect(mutex);
-
- alive = false;
-
- if (vss != nullptr)
- roar_vs_close(vss, ROAR_VS_TRUE, &err);
- vss = nullptr;
- roar_disconnect(&con);
-}
-
-static void
-roar_close(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
- self->Close();
-}
-
-inline void
-RoarOutput::Cancel()
-{
- const ScopeLock protect(mutex);
-
- if (vss == nullptr)
- return;
-
- roar_vs_t *_vss = vss;
- vss = nullptr;
- roar_vs_close(_vss, ROAR_VS_TRUE, &err);
- alive = false;
-
- _vss = roar_vs_new_from_con(&con, &err);
- if (_vss == nullptr)
- return;
-
- if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
- roar_vs_close(_vss, ROAR_VS_TRUE, &err);
- LogError(roar_output_domain, "Failed to start stream");
- return;
- }
-
- roar_vs_role(_vss, role, &err);
- vss = _vss;
- alive = true;
-}
-
-static void
-roar_cancel(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- self->Cancel();
-}
-
-inline size_t
-RoarOutput::Play(const void *chunk, size_t size, Error &error)
-{
- if (vss == nullptr) {
- error.Set(roar_output_domain, "Connection is invalid");
- return 0;
- }
-
- ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
- if (nbytes <= 0) {
- error.Set(roar_output_domain, "Failed to play data");
- return 0;
- }
-
- return nbytes;
-}
-
-static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- RoarOutput *self = (RoarOutput *)ao;
- return self->Play(chunk, size, error);
-}
-
-static const char*
-roar_tag_convert(TagType type, bool *is_uuid)
-{
- *is_uuid = false;
- switch (type)
- {
- case TAG_ARTIST:
- case TAG_ALBUM_ARTIST:
- return "AUTHOR";
- case TAG_ALBUM:
- return "ALBUM";
- case TAG_TITLE:
- return "TITLE";
- case TAG_TRACK:
- return "TRACK";
- case TAG_NAME:
- return "NAME";
- case TAG_GENRE:
- return "GENRE";
- case TAG_DATE:
- return "DATE";
- case TAG_PERFORMER:
- return "PERFORMER";
- case TAG_COMMENT:
- return "COMMENT";
- case TAG_DISC:
- return "DISCID";
- case TAG_COMPOSER:
-#ifdef ROAR_META_TYPE_COMPOSER
- return "COMPOSER";
-#else
- return "AUTHOR";
-#endif
- case TAG_MUSICBRAINZ_ARTISTID:
- case TAG_MUSICBRAINZ_ALBUMID:
- case TAG_MUSICBRAINZ_ALBUMARTISTID:
- case TAG_MUSICBRAINZ_TRACKID:
- *is_uuid = true;
- return "HASH";
-
- default:
- return nullptr;
- }
-}
-
-inline void
-RoarOutput::SendTag(const Tag &tag)
-{
- if (vss == nullptr)
- return;
-
- const ScopeLock protect(mutex);
-
- size_t cnt = 1;
- struct roar_keyval vals[32];
- char uuid_buf[32][64];
-
- char timebuf[16];
- snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
- tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60);
-
- vals[0].key = const_cast<char *>("LENGTH");
- vals[0].value = timebuf;
-
- for (unsigned i = 0; i < tag.num_items && cnt < 32; i++)
- {
- bool is_uuid = false;
- const char *key = roar_tag_convert(tag.items[i]->type,
- &is_uuid);
- if (key != nullptr) {
- vals[cnt].key = const_cast<char *>(key);
-
- if (is_uuid) {
- snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
- tag.items[i]->value);
- vals[cnt].value = uuid_buf[cnt];
- } else {
- vals[cnt].value = tag.items[i]->value;
- }
-
- cnt++;
- }
- }
-
- roar_vs_meta(vss, vals, cnt, &(err));
-}
-
-static void
-roar_send_tag(struct audio_output *ao, const Tag *meta)
-{
- RoarOutput *self = (RoarOutput *)ao;
- self->SendTag(*meta);
-}
-
-const struct audio_output_plugin roar_output_plugin = {
- "roar",
- nullptr,
- roar_init,
- roar_finish,
- nullptr,
- nullptr,
- roar_open,
- roar_close,
- nullptr,
- roar_send_tag,
- roar_play,
- nullptr,
- roar_cancel,
- nullptr,
- &roar_mixer_plugin,
-};
diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/RoarOutputPlugin.hxx
deleted file mode 100644
index 04949e421..000000000
--- a/src/output/RoarOutputPlugin.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
-#define MPD_ROAR_OUTPUT_PLUGIN_H
-
-class RoarOutput;
-
-extern const struct audio_output_plugin roar_output_plugin;
-
-int
-roar_output_get_volume(RoarOutput *roar);
-
-bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume);
-
-#endif
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx
deleted file mode 100644
index 19f2b61cd..000000000
--- a/src/output/ShoutOutputPlugin.cxx
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ShoutOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <shout/shout.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
-
-struct ShoutOutput final {
- struct audio_output base;
-
- shout_t *shout_conn;
- shout_metadata_t *shout_meta;
-
- Encoder *encoder;
-
- float quality;
- int bitrate;
-
- int timeout;
-
- uint8_t buffer[32768];
-
- ShoutOutput()
- :shout_conn(shout_new()),
- shout_meta(shout_metadata_new()),
- quality(-2.0),
- bitrate(-1),
- timeout(DEFAULT_CONN_TIMEOUT) {}
-
- ~ShoutOutput() {
- if (shout_meta != nullptr)
- shout_metadata_free(shout_meta);
- if (shout_conn != nullptr)
- shout_free(shout_conn);
- }
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &shout_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static int shout_init_count;
-
-static constexpr Domain shout_output_domain("shout_output");
-
-static const EncoderPlugin *
-shout_encoder_plugin_get(const char *name)
-{
- if (strcmp(name, "ogg") == 0)
- name = "vorbis";
- else if (strcmp(name, "mp3") == 0)
- name = "lame";
-
- return encoder_plugin_get(name);
-}
-
-gcc_pure
-static const char *
-require_block_string(const config_param &param, const char *name)
-{
- const char *value = param.GetBlockValue(name);
- if (value == nullptr)
- FormatFatalError("no \"%s\" defined for shout device defined "
- "at line %u\n", name, param.line);
-
- return value;
-}
-
-inline bool
-ShoutOutput::Configure(const config_param &param, Error &error)
-{
-
- const AudioFormat audio_format = base.config_audio_format;
- if (!audio_format.IsFullyDefined()) {
- error.Set(config_domain,
- "Need full audio format specification");
- return nullptr;
- }
-
- const char *host = require_block_string(param, "host");
- const char *mount = require_block_string(param, "mount");
- unsigned port = param.GetBlockValue("port", 0u);
- if (port == 0) {
- error.Set(config_domain, "shout port must be configured");
- return false;
- }
-
- const char *passwd = require_block_string(param, "password");
- const char *name = require_block_string(param, "name");
-
- bool is_public = param.GetBlockValue("public", false);
-
- const char *user = param.GetBlockValue("user", "source");
-
- const char *value = param.GetBlockValue("quality");
- if (value != nullptr) {
- char *test;
- quality = strtod(value, &test);
-
- if (*test != '\0' || quality < -1.0 || quality > 10.0) {
- error.Format(config_domain,
- "shout quality \"%s\" is not a number in the "
- "range -1 to 10",
- value);
- return false;
- }
-
- if (param.GetBlockValue("bitrate") != nullptr) {
- error.Set(config_domain,
- "quality and bitrate are "
- "both defined");
- return false;
- }
- } else {
- value = param.GetBlockValue("bitrate");
- if (value == nullptr) {
- error.Set(config_domain,
- "neither bitrate nor quality defined");
- return false;
- }
-
- char *test;
- bitrate = strtol(value, &test, 10);
-
- if (*test != '\0' || bitrate <= 0) {
- error.Set(config_domain,
- "bitrate must be a positive integer");
- return false;
- }
- }
-
- const char *encoding = param.GetBlockValue("encoding", "ogg");
- const auto encoder_plugin = shout_encoder_plugin_get(encoding);
- if (encoder_plugin == nullptr) {
- error.Format(config_domain,
- "couldn't find shout encoder plugin \"%s\"",
- encoding);
- return false;
- }
-
- encoder = encoder_init(*encoder_plugin, param, error);
- if (encoder == nullptr)
- return false;
-
- unsigned shout_format;
- if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
- shout_format = SHOUT_FORMAT_MP3;
- else
- shout_format = SHOUT_FORMAT_OGG;
-
- unsigned protocol;
- value = param.GetBlockValue("protocol");
- if (value != nullptr) {
- if (0 == strcmp(value, "shoutcast") &&
- 0 != strcmp(encoding, "mp3")) {
- error.Format(config_domain,
- "you cannot stream \"%s\" to shoutcast, use mp3",
- encoding);
- return false;
- } else if (0 == strcmp(value, "shoutcast"))
- protocol = SHOUT_PROTOCOL_ICY;
- else if (0 == strcmp(value, "icecast1"))
- protocol = SHOUT_PROTOCOL_XAUDIOCAST;
- else if (0 == strcmp(value, "icecast2"))
- protocol = SHOUT_PROTOCOL_HTTP;
- else {
- error.Format(config_domain,
- "shout protocol \"%s\" is not \"shoutcast\" or "
- "\"icecast1\"or \"icecast2\"",
- value);
- return false;
- }
- } else {
- protocol = SHOUT_PROTOCOL_HTTP;
- }
-
- if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
- shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
- shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
- shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS ||
- shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS ||
- shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS ||
- shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS ||
- shout_set_format(shout_conn, shout_format)
- != SHOUTERR_SUCCESS ||
- shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
- shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- /* optional paramters */
- timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
-
- value = param.GetBlockValue("genre");
- if (value != nullptr && shout_set_genre(shout_conn, value)) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- value = param.GetBlockValue("description");
- if (value != nullptr && shout_set_description(shout_conn, value)) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- value = param.GetBlockValue("url");
- if (value != nullptr && shout_set_url(shout_conn, value)) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- {
- char temp[11];
- memset(temp, 0, sizeof(temp));
-
- snprintf(temp, sizeof(temp), "%u", audio_format.channels);
- shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
-
- snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
-
- shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
-
- if (quality >= -1.0) {
- snprintf(temp, sizeof(temp), "%2.2f", quality);
- shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY,
- temp);
- } else {
- snprintf(temp, sizeof(temp), "%d", bitrate);
- shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE,
- temp);
- }
- }
-
- return true;
-}
-
-static struct audio_output *
-my_shout_init_driver(const config_param &param, Error &error)
-{
- ShoutOutput *sd = new ShoutOutput();
- if (!sd->Initialize(param, error)) {
- delete sd;
- return nullptr;
- }
-
- if (!sd->Configure(param, error)) {
- sd->Deinitialize();
- delete sd;
- return nullptr;
- }
-
- if (shout_init_count == 0)
- shout_init();
-
- shout_init_count++;
-
- return &sd->base;
-}
-
-static bool
-handle_shout_error(ShoutOutput *sd, int err, Error &error)
-{
- switch (err) {
- case SHOUTERR_SUCCESS:
- break;
-
- case SHOUTERR_UNCONNECTED:
- case SHOUTERR_SOCKET:
- error.Format(shout_output_domain, err,
- "Lost shout connection to %s:%i: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
-
- default:
- error.Format(shout_output_domain, err,
- "connection to %s:%i error: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
- }
-
- return true;
-}
-
-static bool
-write_page(ShoutOutput *sd, Error &error)
-{
- assert(sd->encoder != nullptr);
-
- while (true) {
- size_t nbytes = encoder_read(sd->encoder,
- sd->buffer, sizeof(sd->buffer));
- if (nbytes == 0)
- return true;
-
- int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
- if (!handle_shout_error(sd, err, error))
- return false;
- }
-
- return true;
-}
-
-static void close_shout_conn(ShoutOutput * sd)
-{
- if (sd->encoder != nullptr) {
- if (encoder_end(sd->encoder, IgnoreError()))
- write_page(sd, IgnoreError());
-
- encoder_close(sd->encoder);
- }
-
- if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
- shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
- FormatWarning(shout_output_domain,
- "problem closing connection to shout server: %s",
- shout_get_error(sd->shout_conn));
- }
-}
-
-static void
-my_shout_finish_driver(struct audio_output *ao)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- encoder_finish(sd->encoder);
-
- sd->Deinitialize();
- delete sd;
-
- shout_init_count--;
-
- if (shout_init_count == 0)
- shout_shutdown();
-}
-
-static void
-my_shout_drop_buffered_audio(struct audio_output *ao)
-{
- gcc_unused
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- /* needs to be implemented for shout */
-}
-
-static void
-my_shout_close_device(struct audio_output *ao)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- close_shout_conn(sd);
-}
-
-static bool
-shout_connect(ShoutOutput *sd, Error &error)
-{
- switch (shout_open(sd->shout_conn)) {
- case SHOUTERR_SUCCESS:
- case SHOUTERR_CONNECTED:
- return true;
-
- default:
- error.Format(shout_output_domain,
- "problem opening connection to shout server %s:%i: %s",
- shout_get_host(sd->shout_conn),
- shout_get_port(sd->shout_conn),
- shout_get_error(sd->shout_conn));
- return false;
- }
-}
-
-static bool
-my_shout_open_device(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- if (!shout_connect(sd, error))
- return false;
-
- if (!encoder_open(sd->encoder, audio_format, error)) {
- shout_close(sd->shout_conn);
- return false;
- }
-
- if (!write_page(sd, error)) {
- encoder_close(sd->encoder);
- shout_close(sd->shout_conn);
- return false;
- }
-
- return true;
-}
-
-static unsigned
-my_shout_delay(struct audio_output *ao)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- int delay = shout_delay(sd->shout_conn);
- if (delay < 0)
- delay = 0;
-
- return delay;
-}
-
-static size_t
-my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- return encoder_write(sd->encoder, chunk, size, error) &&
- write_page(sd, error)
- ? size
- : 0;
-}
-
-static bool
-my_shout_pause(struct audio_output *ao)
-{
- static char silence[1020];
-
- return my_shout_play(ao, silence, sizeof(silence), IgnoreError());
-}
-
-static void
-shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
-{
- char artist[size];
- char title[size];
-
- artist[0] = 0;
- title[0] = 0;
-
- for (unsigned i = 0; i < tag->num_items; i++) {
- switch (tag->items[i]->type) {
- case TAG_ARTIST:
- strncpy(artist, tag->items[i]->value, size);
- break;
- case TAG_TITLE:
- strncpy(title, tag->items[i]->value, size);
- break;
-
- default:
- break;
- }
- }
-
- snprintf(dest, size, "%s - %s", artist, title);
-}
-
-static void my_shout_set_tag(struct audio_output *ao,
- const Tag *tag)
-{
- ShoutOutput *sd = (ShoutOutput *)ao;
-
- if (sd->encoder->plugin.tag != nullptr) {
- /* encoder plugin supports stream tags */
-
- Error error;
- if (!encoder_pre_tag(sd->encoder, error) ||
- !write_page(sd, error) ||
- !encoder_tag(sd->encoder, tag, error)) {
- LogError(error);
- return;
- }
- } else {
- /* no stream tag support: fall back to icy-metadata */
- char song[1024];
- shout_tag_to_metadata(tag, song, sizeof(song));
-
- shout_metadata_add(sd->shout_meta, "song", song);
- if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
- sd->shout_meta)) {
- LogWarning(shout_output_domain,
- "error setting shout metadata");
- }
- }
-
- write_page(sd, IgnoreError());
-}
-
-const struct audio_output_plugin shout_output_plugin = {
- "shout",
- nullptr,
- my_shout_init_driver,
- my_shout_finish_driver,
- nullptr,
- nullptr,
- my_shout_open_device,
- my_shout_close_device,
- my_shout_delay,
- my_shout_set_tag,
- my_shout_play,
- nullptr,
- my_shout_drop_buffered_audio,
- my_shout_pause,
- nullptr,
-};
diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/ShoutOutputPlugin.hxx
deleted file mode 100644
index 496b77975..000000000
--- a/src/output/ShoutOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX
-#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin shout_output_plugin;
-
-#endif
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx
deleted file mode 100644
index 0836dc2e2..000000000
--- a/src/output/SolarisOutputPlugin.cxx
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SolarisOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-
-#include <sys/stropts.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#ifdef __sun
-#include <sys/audio.h>
-#else
-
-/* some fake declarations that allow build this plugin on systems
- other than Solaris, just to see if it compiles */
-
-#define AUDIO_GETINFO 0
-#define AUDIO_SETINFO 0
-#define AUDIO_ENCODING_LINEAR 0
-
-struct audio_info {
- struct {
- unsigned sample_rate, channels, precision, encoding;
- } play;
-};
-
-#endif
-
-struct SolarisOutput {
- struct audio_output base;
-
- /* configuration */
- const char *device;
-
- int fd;
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &solaris_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static bool
-solaris_output_test_default_device(void)
-{
- struct stat st;
-
- return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
- access("/dev/audio", W_OK) == 0;
-}
-
-static struct audio_output *
-solaris_output_init(const config_param &param, Error &error_r)
-{
- SolarisOutput *so = new SolarisOutput();
- if (!so->Initialize(param, error_r)) {
- delete so;
- return nullptr;
- }
-
- so->device = param.GetBlockValue("device", "/dev/audio");
-
- return &so->base;
-}
-
-static void
-solaris_output_finish(struct audio_output *ao)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
-
- so->Deinitialize();
- delete so;
-}
-
-static bool
-solaris_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
- struct audio_info info;
- int ret, flags;
-
- /* support only 16 bit mono/stereo for now; nothing else has
- been tested */
- audio_format.format = SampleFormat::S16;
-
- /* open the device in non-blocking mode */
-
- so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
- if (so->fd < 0) {
- error.FormatErrno("Failed to open %s",
- so->device);
- return false;
- }
-
- /* restore blocking mode */
-
- flags = fcntl(so->fd, F_GETFL);
- if (flags > 0 && (flags & O_NONBLOCK) != 0)
- fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
-
- /* configure the audio device */
-
- ret = ioctl(so->fd, AUDIO_GETINFO, &info);
- if (ret < 0) {
- error.SetErrno("AUDIO_GETINFO failed");
- close(so->fd);
- return false;
- }
-
- info.play.sample_rate = audio_format.sample_rate;
- info.play.channels = audio_format.channels;
- info.play.precision = 16;
- info.play.encoding = AUDIO_ENCODING_LINEAR;
-
- ret = ioctl(so->fd, AUDIO_SETINFO, &info);
- if (ret < 0) {
- error.SetErrno("AUDIO_SETINFO failed");
- close(so->fd);
- return false;
- }
-
- return true;
-}
-
-static void
-solaris_output_close(struct audio_output *ao)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
-
- close(so->fd);
-}
-
-static size_t
-solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
- ssize_t nbytes;
-
- nbytes = write(so->fd, chunk, size);
- if (nbytes <= 0) {
- error.SetErrno("Write failed");
- return 0;
- }
-
- return nbytes;
-}
-
-static void
-solaris_output_cancel(struct audio_output *ao)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
-
- ioctl(so->fd, I_FLUSH);
-}
-
-const struct audio_output_plugin solaris_output_plugin = {
- "solaris",
- solaris_output_test_default_device,
- solaris_output_init,
- solaris_output_finish,
- nullptr,
- nullptr,
- solaris_output_open,
- solaris_output_close,
- nullptr,
- nullptr,
- solaris_output_play,
- nullptr,
- solaris_output_cancel,
- nullptr,
- nullptr,
-};
diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/SolarisOutputPlugin.hxx
deleted file mode 100644
index d0fbd32c8..000000000
--- a/src/output/SolarisOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX
-#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
-
-extern const struct audio_output_plugin solaris_output_plugin;
-
-#endif
diff --git a/src/output/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/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
deleted file mode 100644
index d2508ee2a..000000000
--- a/src/output/WinmmOutputPlugin.cxx
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "WinmmOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "MixerList.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-
-#include <glib.h>
-
-#include <stdlib.h>
-#include <string.h>
-
-struct WinmmBuffer {
- PcmBuffer buffer;
-
- WAVEHDR hdr;
-};
-
-struct WinmmOutput {
- struct audio_output base;
-
- UINT device_id;
- HWAVEOUT handle;
-
- /**
- * This event is triggered by Windows when a buffer is
- * finished.
- */
- HANDLE event;
-
- WinmmBuffer buffers[8];
- unsigned next_buffer;
-};
-
-static constexpr Domain winmm_output_domain("winmm_output");
-
-HWAVEOUT
-winmm_output_get_handle(WinmmOutput *output)
-{
- return output->handle;
-}
-
-static bool
-winmm_output_test_default_device(void)
-{
- return waveOutGetNumDevs() > 0;
-}
-
-static bool
-get_device_id(const char *device_name, UINT *device_id, Error &error)
-{
- /* if device is not specified use wave mapper */
- if (device_name == nullptr) {
- *device_id = WAVE_MAPPER;
- return true;
- }
-
- UINT numdevs = waveOutGetNumDevs();
-
- /* check for device id */
- char *endptr;
- UINT id = strtoul(device_name, &endptr, 0);
- if (endptr > device_name && *endptr == 0) {
- if (id >= numdevs)
- goto fail;
- *device_id = id;
- return true;
- }
-
- /* check for device name */
- for (UINT i = 0; i < numdevs; i++) {
- WAVEOUTCAPS caps;
- MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
- if (result != MMSYSERR_NOERROR)
- continue;
- /* szPname is only 32 chars long, so it is often truncated.
- Use partial match to work around this. */
- if (strstr(device_name, caps.szPname) == device_name) {
- *device_id = i;
- return true;
- }
- }
-
-fail:
- error.Format(winmm_output_domain,
- "device \"%s\" is not found", device_name);
- return false;
-}
-
-static struct audio_output *
-winmm_output_init(const config_param &param, Error &error)
-{
- WinmmOutput *wo = new WinmmOutput();
- if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
- delete wo;
- return nullptr;
- }
-
- const char *device = param.GetBlockValue("device");
- if (!get_device_id(device, &wo->device_id, error)) {
- ao_base_finish(&wo->base);
- delete wo;
- return nullptr;
- }
-
- return &wo->base;
-}
-
-static void
-winmm_output_finish(struct audio_output *ao)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- ao_base_finish(&wo->base);
- delete wo;
-}
-
-static bool
-winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- wo->event = CreateEvent(nullptr, false, false, nullptr);
- if (wo->event == nullptr) {
- error.Set(winmm_output_domain, "CreateEvent() failed");
- return false;
- }
-
- switch (audio_format.format) {
- case SampleFormat::S8:
- case SampleFormat::S16:
- break;
-
- case SampleFormat::S24_P32:
- case SampleFormat::S32:
- case SampleFormat::FLOAT:
- case SampleFormat::DSD:
- case SampleFormat::UNDEFINED:
- /* we havn't tested formats other than S16 */
- audio_format.format = SampleFormat::S16;
- break;
- }
-
- if (audio_format.channels > 2)
- /* same here: more than stereo was not tested */
- audio_format.channels = 2;
-
- WAVEFORMATEX format;
- format.wFormatTag = WAVE_FORMAT_PCM;
- format.nChannels = audio_format.channels;
- format.nSamplesPerSec = audio_format.sample_rate;
- format.nBlockAlign = audio_format.GetFrameSize();
- format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
- format.wBitsPerSample = audio_format.GetSampleSize() * 8;
- format.cbSize = 0;
-
- MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
- (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
- if (result != MMSYSERR_NOERROR) {
- CloseHandle(wo->event);
- error.Set(winmm_output_domain, "waveOutOpen() failed");
- return false;
- }
-
- for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
- memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
- }
-
- wo->next_buffer = 0;
-
- return true;
-}
-
-static void
-winmm_output_close(struct audio_output *ao)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i)
- wo->buffers[i].buffer.Clear();
-
- waveOutClose(wo->handle);
-
- CloseHandle(wo->event);
-}
-
-/**
- * Copy data into a buffer, and prepare the wave header.
- */
-static bool
-winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
- const void *data, size_t size,
- Error &error)
-{
- void *dest = buffer->buffer.Get(size);
- assert(dest != nullptr);
-
- memcpy(dest, data, size);
-
- memset(&buffer->hdr, 0, sizeof(buffer->hdr));
- buffer->hdr.lpData = (LPSTR)dest;
- buffer->hdr.dwBufferLength = size;
-
- MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- if (result != MMSYSERR_NOERROR) {
- error.Set(winmm_output_domain, result,
- "waveOutPrepareHeader() failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Wait until the buffer is finished.
- */
-static bool
-winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
- Error &error)
-{
- if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
- /* already finished */
- return true;
-
- while (true) {
- MMRESULT result = waveOutUnprepareHeader(wo->handle,
- &buffer->hdr,
- sizeof(buffer->hdr));
- if (result == MMSYSERR_NOERROR)
- return true;
- else if (result != WAVERR_STILLPLAYING) {
- error.Set(winmm_output_domain, result,
- "waveOutUnprepareHeader() failed");
- return false;
- }
-
- /* wait some more */
- WaitForSingleObject(wo->event, INFINITE);
- }
-}
-
-static size_t
-winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- /* get the next buffer from the ring and prepare it */
- WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
- if (!winmm_drain_buffer(wo, buffer, error) ||
- !winmm_set_buffer(wo, buffer, chunk, size, error))
- return 0;
-
- /* enqueue the buffer */
- MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- if (result != MMSYSERR_NOERROR) {
- waveOutUnprepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- error.Set(winmm_output_domain, result,
- "waveOutWrite() failed");
- return 0;
- }
-
- /* mark our buffer as "used" */
- wo->next_buffer = (wo->next_buffer + 1) %
- ARRAY_SIZE(wo->buffers);
-
- return size;
-}
-
-static bool
-winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
-{
- for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
- return false;
-
- for (unsigned i = 0; i < wo->next_buffer; ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
- return false;
-
- return true;
-}
-
-static void
-winmm_stop(WinmmOutput *wo)
-{
- waveOutReset(wo->handle);
-
- for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
- WinmmBuffer *buffer = &wo->buffers[i];
- waveOutUnprepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- }
-}
-
-static void
-winmm_output_drain(struct audio_output *ao)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- if (!winmm_drain_all_buffers(wo, IgnoreError()))
- winmm_stop(wo);
-}
-
-static void
-winmm_output_cancel(struct audio_output *ao)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- winmm_stop(wo);
-}
-
-const struct audio_output_plugin winmm_output_plugin = {
- "winmm",
- winmm_output_test_default_device,
- winmm_output_init,
- winmm_output_finish,
- nullptr,
- nullptr,
- winmm_output_open,
- winmm_output_close,
- nullptr,
- nullptr,
- winmm_output_play,
- winmm_output_drain,
- winmm_output_cancel,
- nullptr,
- &winmm_mixer_plugin,
-};
diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/WinmmOutputPlugin.hxx
deleted file mode 100644
index a6b7733ec..000000000
--- a/src/output/WinmmOutputPlugin.hxx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX
-#define MPD_WINMM_OUTPUT_PLUGIN_HXX
-
-#include "check.h"
-
-#ifdef ENABLE_WINMM_OUTPUT
-
-#include "Compiler.h"
-
-#include <windows.h>
-#include <mmsystem.h>
-
-struct WinmmOutput;
-
-extern const struct audio_output_plugin winmm_output_plugin;
-
-gcc_pure
-HWAVEOUT
-winmm_output_get_handle(WinmmOutput *);
-
-#endif
-
-#endif
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
new file mode 100644
index 000000000..28c374a00
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -0,0 +1,895 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AlsaOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "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 <alsa/asoundlib.h>
+
+#include <string>
+
+#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;
+
+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 {
+ AudioOutput base;
+
+ Manual<PcmExport> pcm_export;
+
+ /**
+ * The configured name of the ALSA device; empty for the
+ * default device
+ */
+ std::string device;
+
+ /** use memory mapped I/O? */
+ bool use_mmap;
+
+ /**
+ * Enable DSD over PCM according to the DoP standard standard?
+ *
+ * @see http://dsd-guide.com/dop-open-standard
+ */
+ bool dop;
+
+ /** libasound's buffer_time setting (in microseconds) */
+ unsigned int buffer_time;
+
+ /** libasound's period_time setting (in microseconds) */
+ unsigned int period_time;
+
+ /** the mode flags passed to snd_pcm_open */
+ int mode;
+
+ /** the libasound PCM device handle */
+ snd_pcm_t *pcm;
+
+ /**
+ * a pointer to the libasound writei() function, which is
+ * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
+ * use_mmap configuration
+ */
+ alsa_writei_t *writei;
+
+ /**
+ * The size of one audio frame passed to method play().
+ */
+ size_t in_frame_size;
+
+ /**
+ * The size of one audio frame passed to libasound.
+ */
+ size_t out_frame_size;
+
+ /**
+ * The size of one period, in number of frames.
+ */
+ snd_pcm_uframes_t period_frames;
+
+ /**
+ * The number of frames written in the current period.
+ */
+ snd_pcm_uframes_t period_position;
+
+ /**
+ * Do we need to call snd_pcm_prepare() before the next write?
+ * It means that we put the device to SND_PCM_STATE_SETUP by
+ * calling snd_pcm_drop().
+ *
+ * Without this flag, we could easily recover after a failed
+ * optimistic write (returning -EBADFD), but the Raspberry Pi
+ * audio driver is infamous for generating ugly artefacts from
+ * this.
+ */
+ bool must_prepare;
+
+ /**
+ * This buffer gets allocated after opening the ALSA device.
+ * It contains silence samples, enough to fill one period (see
+ * #period_frames).
+ */
+ uint8_t *silence;
+
+ AlsaOutput()
+ :base(alsa_output_plugin),
+ mode(0), writei(snd_pcm_writei) {
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain alsa_output_domain("alsa_output");
+
+static const char *
+alsa_device(const AlsaOutput *ad)
+{
+ return ad->device.empty() ? default_device : ad->device.c_str();
+}
+
+inline bool
+AlsaOutput::Configure(const config_param &param, Error &error)
+{
+ if (!base.Configure(param, error))
+ return false;
+
+ device = param.GetBlockValue("device", "");
+
+ use_mmap = param.GetBlockValue("use_mmap", false);
+
+ 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);
+ period_time = param.GetBlockValue("period_time", 0u);
+
+#ifdef SND_PCM_NO_AUTO_RESAMPLE
+ if (!param.GetBlockValue("auto_resample", true))
+ mode |= SND_PCM_NO_AUTO_RESAMPLE;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_CHANNELS
+ if (!param.GetBlockValue("auto_channels", true))
+ mode |= SND_PCM_NO_AUTO_CHANNELS;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_FORMAT
+ if (!param.GetBlockValue("auto_format", true))
+ mode |= SND_PCM_NO_AUTO_FORMAT;
+#endif
+
+ return true;
+}
+
+static AudioOutput *
+alsa_init(const config_param &param, Error &error)
+{
+ AlsaOutput *ad = new AlsaOutput();
+
+ if (!ad->Configure(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ return &ad->base;
+}
+
+static void
+alsa_finish(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ delete ad;
+
+ /* free libasound's config cache */
+ snd_config_update_free_global();
+}
+
+static bool
+alsa_output_enable(AudioOutput *ao, gcc_unused Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Construct();
+ return true;
+}
+
+static void
+alsa_output_disable(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Destruct();
+}
+
+static bool
+alsa_test_default_device()
+{
+ snd_pcm_t *handle;
+
+ int ret = snd_pcm_open(&handle, default_device,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (ret) {
+ FormatError(alsa_output_domain,
+ "Error opening default ALSA device: %s",
+ snd_strerror(-ret));
+ return false;
+ } else
+ snd_pcm_close(handle);
+
+ return true;
+}
+
+/**
+ * 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;
+
+ case SampleFormat::S16:
+ return SND_PCM_FORMAT_S16;
+
+ case SampleFormat::S24_P32:
+ return SND_PCM_FORMAT_S24;
+
+ case SampleFormat::S32:
+ return SND_PCM_FORMAT_S32;
+
+ case SampleFormat::FLOAT:
+ return SND_PCM_FORMAT_FLOAT;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+/**
+ * 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) {
+ case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+ case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+ case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+ case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+ case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+
+ case SND_PCM_FORMAT_S24_3BE:
+ return SND_PCM_FORMAT_S24_3LE;
+
+ case SND_PCM_FORMAT_S24_3LE:
+ return SND_PCM_FORMAT_S24_3BE;
+
+ case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+/**
+ * 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)
+{
+ switch (fmt) {
+ case SND_PCM_FORMAT_S24_LE:
+ return SND_PCM_FORMAT_S24_3LE;
+
+ case SND_PCM_FORMAT_S24_BE:
+ return SND_PCM_FORMAT_S24_3BE;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+/**
+ * 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)
+{
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ fmt = alsa_to_packed_format(fmt);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = true;
+
+ return err;
+}
+
+/**
+ * Attempts to configure the specified sample format, and tries the
+ * reversed host byte order if was not supported.
+ */
+static int
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ SampleFormat sample_format,
+ bool *packed_r, bool *reverse_endian_r)
+{
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
+ packed_r);
+ if (err == 0)
+ *reverse_endian_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ alsa_format = byteswap_bitformat(alsa_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
+ if (err == 0)
+ *reverse_endian_r = true;
+
+ return err;
+}
+
+/**
+ * Configure a sample format, and probe other formats if that fails.
+ */
+static int
+alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ AudioFormat &audio_format,
+ bool *packed_r, bool *reverse_endian_r)
+{
+ /* try the input format first */
+
+ int err = alsa_output_try_format(pcm, hwparams,
+ audio_format.format,
+ packed_r, reverse_endian_r);
+
+ /* if unsupported by the hardware, try other formats */
+
+ static constexpr SampleFormat probe_formats[] = {
+ SampleFormat::S24_P32,
+ SampleFormat::S32,
+ SampleFormat::S16,
+ SampleFormat::S8,
+ SampleFormat::UNDEFINED,
+ };
+
+ for (unsigned i = 0;
+ err == -EINVAL && probe_formats[i] != SampleFormat::UNDEFINED;
+ ++i) {
+ const SampleFormat mpd_format = probe_formats[i];
+ if (mpd_format == audio_format.format)
+ continue;
+
+ err = alsa_output_try_format(pcm, hwparams, mpd_format,
+ packed_r, reverse_endian_r);
+ if (err == 0)
+ audio_format.format = mpd_format;
+ }
+
+ return err;
+}
+
+/**
+ * Set up the snd_pcm_t object which was opened by the caller. Set up
+ * the configured settings and the audio format.
+ */
+static bool
+alsa_setup(AlsaOutput *ad, AudioFormat &audio_format,
+ bool *packed_r, bool *reverse_endian_r, Error &error)
+{
+ unsigned int sample_rate = audio_format.sample_rate;
+ unsigned int channels = audio_format.channels;
+ int err;
+ const char *cmd = nullptr;
+ unsigned retry = MPD_ALSA_RETRY_NR;
+ unsigned int period_time, period_time_ro;
+ unsigned int buffer_time;
+
+ period_time_ro = period_time = ad->period_time;
+configure_hw:
+ /* configure HW params */
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+ cmd = "snd_pcm_hw_params_any";
+ err = snd_pcm_hw_params_any(ad->pcm, hwparams);
+ if (err < 0)
+ goto error;
+
+ if (ad->use_mmap) {
+ err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+ SND_PCM_ACCESS_MMAP_INTERLEAVED);
+ if (err < 0) {
+ FormatWarning(alsa_output_domain,
+ "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
+ alsa_device(ad), snd_strerror(-err));
+ LogWarning(alsa_output_domain,
+ "Falling back to direct write mode");
+ ad->use_mmap = false;
+ } else
+ ad->writei = snd_pcm_mmap_writei;
+ }
+
+ if (!ad->use_mmap) {
+ cmd = "snd_pcm_hw_params_set_access";
+ err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0)
+ goto error;
+ ad->writei = snd_pcm_writei;
+ }
+
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
+ packed_r, reverse_endian_r);
+ if (err < 0) {
+ error.Format(alsa_output_domain, err,
+ "ALSA device \"%s\" does not support format %s: %s",
+ alsa_device(ad),
+ sample_format_to_string(audio_format.format),
+ snd_strerror(-err));
+ return false;
+ }
+
+ snd_pcm_format_t format;
+ if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
+ FormatDebug(alsa_output_domain,
+ "format=%s (%s)", snd_pcm_format_name(format),
+ snd_pcm_format_description(format));
+
+ err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
+ &channels);
+ if (err < 0) {
+ error.Format(alsa_output_domain, err,
+ "ALSA device \"%s\" does not support %i channels: %s",
+ alsa_device(ad), (int)audio_format.channels,
+ snd_strerror(-err));
+ return false;
+ }
+ audio_format.channels = (int8_t)channels;
+
+ err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
+ &sample_rate, nullptr);
+ if (err < 0 || sample_rate == 0) {
+ error.Format(alsa_output_domain, err,
+ "ALSA device \"%s\" does not support %u Hz audio",
+ alsa_device(ad), audio_format.sample_rate);
+ return false;
+ }
+ audio_format.sample_rate = sample_rate;
+
+ snd_pcm_uframes_t buffer_size_min, buffer_size_max;
+ snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
+ snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
+ unsigned buffer_time_min, buffer_time_max;
+ snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
+ snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
+ FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
+ (unsigned)buffer_size_min, (unsigned)buffer_size_max,
+ buffer_time_min, buffer_time_max);
+
+ snd_pcm_uframes_t period_size_min, period_size_max;
+ snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
+ snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
+ unsigned period_time_min, period_time_max;
+ snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
+ snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
+ FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
+ (unsigned)period_size_min, (unsigned)period_size_max,
+ period_time_min, period_time_max);
+
+ if (ad->buffer_time > 0) {
+ buffer_time = ad->buffer_time;
+ cmd = "snd_pcm_hw_params_set_buffer_time_near";
+ err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
+ &buffer_time, nullptr);
+ if (err < 0)
+ goto error;
+ } else {
+ err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
+ nullptr);
+ if (err < 0)
+ buffer_time = 0;
+ }
+
+ if (period_time_ro == 0 && buffer_time >= 10000) {
+ period_time_ro = period_time = buffer_time / 4;
+
+ FormatDebug(alsa_output_domain,
+ "default period_time = buffer_time/4 = %u/4 = %u",
+ buffer_time, period_time);
+ }
+
+ if (period_time_ro > 0) {
+ period_time = period_time_ro;
+ cmd = "snd_pcm_hw_params_set_period_time_near";
+ err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
+ &period_time, nullptr);
+ if (err < 0)
+ goto error;
+ }
+
+ cmd = "snd_pcm_hw_params";
+ err = snd_pcm_hw_params(ad->pcm, hwparams);
+ if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
+ period_time_ro = period_time_ro >> 1;
+ goto configure_hw;
+ } else if (err < 0)
+ goto error;
+ if (retry != MPD_ALSA_RETRY_NR)
+ FormatDebug(alsa_output_domain,
+ "ALSA period_time set to %d", period_time);
+
+ snd_pcm_uframes_t alsa_buffer_size;
+ cmd = "snd_pcm_hw_params_get_buffer_size";
+ err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
+ if (err < 0)
+ goto error;
+
+ snd_pcm_uframes_t alsa_period_size;
+ cmd = "snd_pcm_hw_params_get_period_size";
+ err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
+ nullptr);
+ if (err < 0)
+ goto error;
+
+ /* configure SW params */
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_sw_params_alloca(&swparams);
+
+ cmd = "snd_pcm_sw_params_current";
+ err = snd_pcm_sw_params_current(ad->pcm, swparams);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params_set_start_threshold";
+ err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
+ alsa_buffer_size -
+ alsa_period_size);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params_set_avail_min";
+ err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
+ alsa_period_size);
+ if (err < 0)
+ goto error;
+
+ cmd = "snd_pcm_sw_params";
+ err = snd_pcm_sw_params(ad->pcm, swparams);
+ if (err < 0)
+ goto error;
+
+ FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
+ (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+
+ if (alsa_period_size == 0)
+ /* this works around a SIGFPE bug that occurred when
+ an ALSA driver indicated period_size==0; this
+ caused a division by zero in alsa_play(). By using
+ the fallback "1", we make sure that this won't
+ happen again. */
+ alsa_period_size = 1;
+
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
+ ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
+ alsa_period_size)];
+ snd_pcm_format_set_silence(format, ad->silence,
+ alsa_period_size * channels);
+
+ return true;
+
+error:
+ error.Format(alsa_output_domain, err,
+ "Error opening ALSA device \"%s\" (%s): %s",
+ alsa_device(ad), cmd, snd_strerror(-err));
+ return false;
+}
+
+static bool
+alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format,
+ bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+ Error &error)
+{
+ assert(ad->dop);
+ assert(audio_format.format == SampleFormat::DSD);
+
+ /* pass 24 bit to alsa_setup() */
+
+ AudioFormat dop_format = audio_format;
+ dop_format.format = SampleFormat::S24_P32;
+ dop_format.sample_rate /= 2;
+
+ const AudioFormat check = dop_format;
+
+ if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error))
+ return false;
+
+ /* 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 = dop_format.format == SampleFormat::S32;
+ if (dop_format.format == SampleFormat::S32)
+ dop_format.format = SampleFormat::S24_P32;
+
+ 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-PCM on ALSA device \"%s\"",
+ alsa_device(ad));
+ delete[] ad->silence;
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_setup_or_dop(AlsaOutput *ad, AudioFormat &audio_format,
+ Error &error)
+{
+ bool shift8 = false, packed, reverse_endian;
+
+ const bool dop = ad->dop &&
+ audio_format.format == SampleFormat::DSD;
+ const bool success = dop
+ ? alsa_setup_dop(ad, audio_format,
+ &shift8, &packed, &reverse_endian,
+ error)
+ : alsa_setup(ad, audio_format, &packed, &reverse_endian,
+ error);
+ if (!success)
+ return false;
+
+ ad->pcm_export->Open(audio_format.format,
+ audio_format.channels,
+ dop, shift8, packed, reverse_endian);
+ return true;
+}
+
+static bool
+alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
+ SND_PCM_STREAM_PLAYBACK, ad->mode);
+ if (err < 0) {
+ error.Format(alsa_output_domain, err,
+ "Failed to open ALSA device \"%s\": %s",
+ alsa_device(ad), snd_strerror(err));
+ return false;
+ }
+
+ FormatDebug(alsa_output_domain, "opened %s type=%s",
+ snd_pcm_name(ad->pcm),
+ snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+ if (!alsa_setup_or_dop(ad, audio_format, error)) {
+ snd_pcm_close(ad->pcm);
+ return false;
+ }
+
+ ad->in_frame_size = audio_format.GetFrameSize();
+ ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
+
+ ad->must_prepare = false;
+
+ return true;
+}
+
+/**
+ * Write silence to the ALSA device.
+ */
+static void
+alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
+{
+ ad->writei(ad->pcm, ad->silence, nframes);
+}
+
+static int
+alsa_recover(AlsaOutput *ad, int err)
+{
+ if (err == -EPIPE) {
+ FormatDebug(alsa_output_domain,
+ "Underrun on ALSA device \"%s\"", alsa_device(ad));
+ } else if (err == -ESTRPIPE) {
+ FormatDebug(alsa_output_domain,
+ "ALSA device \"%s\" was suspended",
+ alsa_device(ad));
+ }
+
+ switch (snd_pcm_state(ad->pcm)) {
+ case SND_PCM_STATE_PAUSED:
+ err = snd_pcm_pause(ad->pcm, /* disable */ 0);
+ break;
+ case SND_PCM_STATE_SUSPENDED:
+ err = snd_pcm_resume(ad->pcm);
+ if (err == -EAGAIN)
+ return 0;
+ /* fall-through to snd_pcm_prepare: */
+ case SND_PCM_STATE_SETUP:
+ case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
+ err = snd_pcm_prepare(ad->pcm);
+ break;
+ case SND_PCM_STATE_DISCONNECTED:
+ break;
+ /* this is no error, so just keep running */
+ case SND_PCM_STATE_RUNNING:
+ err = 0;
+ break;
+ default:
+ /* unknown state, do nothing */
+ break;
+ }
+
+ return err;
+}
+
+static void
+alsa_drain(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ alsa_write_silence(ad, nframes);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
+alsa_cancel(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->period_position = 0;
+ ad->must_prepare = true;
+
+ snd_pcm_drop(ad->pcm);
+}
+
+static void
+alsa_close(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ snd_pcm_close(ad->pcm);
+ delete[] ad->silence;
+}
+
+static size_t
+alsa_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ assert(size > 0);
+ assert(size % ad->in_frame_size == 0);
+
+ if (ad->must_prepare) {
+ ad->must_prepare = false;
+
+ int err = snd_pcm_prepare(ad->pcm);
+ if (err < 0) {
+ error.Set(alsa_output_domain, err, snd_strerror(-err));
+ return 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 size;
+
+ chunk = e.data;
+ size = e.size;
+
+ assert(size % ad->out_frame_size == 0);
+
+ size /= ad->out_frame_size;
+ assert(size > 0);
+
+ while (true) {
+ snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
+
+ size_t bytes_written = ret * ad->out_frame_size;
+ return ad->pcm_export->CalcSourceSize(bytes_written);
+ }
+
+ if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
+ alsa_recover(ad, ret) < 0) {
+ error.Set(alsa_output_domain, ret, snd_strerror(-ret));
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin alsa_output_plugin = {
+ "alsa",
+ alsa_test_default_device,
+ alsa_init,
+ alsa_finish,
+ alsa_output_enable,
+ alsa_output_disable,
+ alsa_open,
+ alsa_close,
+ nullptr,
+ nullptr,
+ alsa_play,
+ alsa_drain,
+ alsa_cancel,
+ nullptr,
+
+ &alsa_mixer_plugin,
+};
diff --git a/src/output/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx
new file mode 100644
index 000000000..f72116f91
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX
+#define MPD_ALSA_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
new file mode 100644
index 000000000..af8c88fa1
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AoOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <ao/ao.h>
+#include <glib.h>
+
+#include <string.h>
+
+/* An ao_sample_format, with all fields set to zero: */
+static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
+
+static unsigned ao_output_ref;
+
+struct AoOutput {
+ AudioOutput base;
+
+ size_t write_size;
+ int driver;
+ ao_option *options;
+ ao_device *device;
+
+ AoOutput()
+ :base(ao_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain ao_output_domain("ao_output");
+
+static void
+ao_output_error(Error &error_r)
+{
+ const char *error;
+
+ switch (errno) {
+ case AO_ENODRIVER:
+ error = "No such libao driver";
+ break;
+
+ case AO_ENOTLIVE:
+ error = "This driver is not a libao live device";
+ break;
+
+ case AO_EBADOPTION:
+ error = "Invalid libao option";
+ break;
+
+ case AO_EOPENDEVICE:
+ error = "Cannot open the libao device";
+ break;
+
+ case AO_EFAIL:
+ error = "Generic libao failure";
+ break;
+
+ default:
+ error_r.SetErrno();
+ return;
+ }
+
+ error_r.Set(ao_output_domain, errno, error);
+}
+
+inline bool
+AoOutput::Configure(const config_param &param, Error &error)
+{
+ const char *value;
+
+ options = nullptr;
+
+ write_size = param.GetBlockValue("write_size", 1024u);
+
+ if (ao_output_ref == 0) {
+ ao_initialize();
+ }
+ ao_output_ref++;
+
+ value = param.GetBlockValue("driver", "default");
+ if (0 == strcmp(value, "default"))
+ driver = ao_default_driver_id();
+ else
+ driver = ao_driver_id(value);
+
+ if (driver < 0) {
+ error.Format(ao_output_domain,
+ "\"%s\" is not a valid ao driver",
+ value);
+ return false;
+ }
+
+ ao_info *ai = ao_driver_info(driver);
+ if (ai == nullptr) {
+ error.Set(ao_output_domain, "problems getting driver info");
+ return false;
+ }
+
+ FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
+ ai->short_name, param.GetBlockValue("name", nullptr));
+
+ value = param.GetBlockValue("options", nullptr);
+ if (value != nullptr) {
+ gchar **_options = g_strsplit(value, ";", 0);
+
+ for (unsigned i = 0; _options[i] != nullptr; ++i) {
+ gchar **key_value = g_strsplit(_options[i], "=", 2);
+
+ if (key_value[0] == nullptr || key_value[1] == nullptr) {
+ error.Format(ao_output_domain,
+ "problems parsing options \"%s\"",
+ _options[i]);
+ return false;
+ }
+
+ ao_append_option(&options, key_value[0],
+ key_value[1]);
+
+ g_strfreev(key_value);
+ }
+
+ g_strfreev(_options);
+ }
+
+ return true;
+}
+
+static AudioOutput *
+ao_output_init(const config_param &param, Error &error)
+{
+ AoOutput *ad = new AoOutput();
+
+ if (!ad->Initialize(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ if (!ad->Configure(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ return &ad->base;
+}
+
+static void
+ao_output_finish(AudioOutput *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_free_options(ad->options);
+ delete ad;
+
+ ao_output_ref--;
+
+ if (ao_output_ref == 0)
+ ao_shutdown();
+}
+
+static void
+ao_output_close(AudioOutput *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_close(ad->device);
+}
+
+static bool
+ao_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
+ AoOutput *ad = (AoOutput *)ao;
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ format.bits = 8;
+ break;
+
+ case SampleFormat::S16:
+ format.bits = 16;
+ break;
+
+ default:
+ /* support for 24 bit samples in libao is currently
+ dubious, and until we have sorted that out,
+ convert everything to 16 bit */
+ audio_format.format = SampleFormat::S16;
+ format.bits = 16;
+ break;
+ }
+
+ format.rate = audio_format.sample_rate;
+ format.byte_format = AO_FMT_NATIVE;
+ format.channels = audio_format.channels;
+
+ ad->device = ao_open_live(ad->driver, &format, ad->options);
+
+ if (ad->device == nullptr) {
+ ao_output_error(error);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * For whatever reason, libao wants a non-const pointer. Let's hope
+ * it does not write to the buffer, and use the union deconst hack to
+ * work around this API misdesign.
+ */
+static int ao_play_deconst(ao_device *device, const void *output_samples,
+ uint_32 num_bytes)
+{
+ union {
+ const void *in;
+ char *out;
+ } u;
+
+ u.in = output_samples;
+ return ao_play(device, u.out, num_bytes);
+}
+
+static size_t
+ao_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ if (size > ad->write_size)
+ size = ad->write_size;
+
+ if (ao_play_deconst(ad->device, chunk, size) == 0) {
+ ao_output_error(error);
+ return 0;
+ }
+
+ return size;
+}
+
+const struct AudioOutputPlugin ao_output_plugin = {
+ "ao",
+ nullptr,
+ ao_output_init,
+ ao_output_finish,
+ nullptr,
+ nullptr,
+ ao_output_open,
+ ao_output_close,
+ nullptr,
+ nullptr,
+ ao_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx
new file mode 100644
index 000000000..07c2ba16b
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AO_OUTPUT_PLUGIN_HXX
+#define MPD_AO_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin ao_output_plugin;
+
+#endif
diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
new file mode 100644
index 000000000..9df5a74dd
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FifoOutputPlugin.hxx"
+#include "config/ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "open.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+
+struct FifoOutput {
+ AudioOutput base;
+
+ AllocatedPath path;
+ std::string path_utf8;
+
+ int input;
+ int output;
+ bool created;
+ Timer *timer;
+
+ FifoOutput()
+ :base(fifo_output_plugin),
+ path(AllocatedPath::Null()), input(-1), output(-1),
+ created(false) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Create(Error &error);
+ bool Check(Error &error);
+ void Delete();
+
+ bool Open(Error &error);
+ void Close();
+};
+
+static constexpr Domain fifo_output_domain("fifo_output");
+
+inline void
+FifoOutput::Delete()
+{
+ FormatDebug(fifo_output_domain,
+ "Removing FIFO \"%s\"", path_utf8.c_str());
+
+ if (!RemoveFile(path)) {
+ FormatErrno(fifo_output_domain,
+ "Could not remove FIFO \"%s\"",
+ path_utf8.c_str());
+ return;
+ }
+
+ created = false;
+}
+
+void
+FifoOutput::Close()
+{
+ if (input >= 0) {
+ close(input);
+ input = -1;
+ }
+
+ if (output >= 0) {
+ close(output);
+ output = -1;
+ }
+
+ struct stat st;
+ if (created && StatFile(path, st))
+ Delete();
+}
+
+inline bool
+FifoOutput::Create(Error &error)
+{
+ if (!MakeFifo(path, 0666)) {
+ error.FormatErrno("Couldn't create FIFO \"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ created = true;
+ return true;
+}
+
+inline bool
+FifoOutput::Check(Error &error)
+{
+ struct stat st;
+ if (!StatFile(path, st)) {
+ if (errno == ENOENT) {
+ /* Path doesn't exist */
+ return Create(error);
+ }
+
+ error.FormatErrno("Failed to stat FIFO \"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ error.Format(fifo_output_domain,
+ "\"%s\" already exists, but is not a FIFO",
+ path_utf8.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+FifoOutput::Open(Error &error)
+{
+ if (!Check(error))
+ return false;
+
+ input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
+ if (input < 0) {
+ error.FormatErrno("Could not open FIFO \"%s\" for reading",
+ path_utf8.c_str());
+ Close();
+ return false;
+ }
+
+ output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
+ if (output < 0) {
+ error.FormatErrno("Could not open FIFO \"%s\" for writing",
+ path_utf8.c_str());
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+fifo_open(FifoOutput *fd, Error &error)
+{
+ return fd->Open(error);
+}
+
+static AudioOutput *
+fifo_output_init(const config_param &param, Error &error)
+{
+ FifoOutput *fd = new FifoOutput();
+
+ fd->path = param.GetBlockPath("path", error);
+ if (fd->path.IsNull()) {
+ delete fd;
+
+ if (!error.IsDefined())
+ error.Set(config_domain,
+ "No \"path\" parameter specified");
+ return nullptr;
+ }
+
+ fd->path_utf8 = fd->path.ToUTF8();
+
+ if (!fd->Initialize(param, error)) {
+ delete fd;
+ return nullptr;
+ }
+
+ if (!fifo_open(fd, error)) {
+ delete fd;
+ return nullptr;
+ }
+
+ return &fd->base;
+}
+
+static void
+fifo_output_finish(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->Close();
+ delete fd;
+}
+
+static bool
+fifo_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ gcc_unused Error &error)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+fifo_output_close(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ delete fd->timer;
+}
+
+static void
+fifo_output_cancel(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+ char buf[FIFO_BUFFER_SIZE];
+ int bytes = 1;
+
+ fd->timer->Reset();
+
+ while (bytes > 0 && errno != EINTR)
+ bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
+
+ if (bytes < 0 && errno != EAGAIN) {
+ FormatErrno(fifo_output_domain,
+ "Flush of FIFO \"%s\" failed",
+ fd->path_utf8.c_str());
+ }
+}
+
+static unsigned
+fifo_output_delay(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ return fd->timer->IsStarted()
+ ? fd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+fifo_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+ ssize_t bytes;
+
+ if (!fd->timer->IsStarted())
+ fd->timer->Start();
+ fd->timer->Add(size);
+
+ while (true) {
+ bytes = write(fd->output, chunk, size);
+ if (bytes > 0)
+ return (size_t)bytes;
+
+ if (bytes < 0) {
+ switch (errno) {
+ case EAGAIN:
+ /* The pipe is full, so empty it */
+ fifo_output_cancel(&fd->base);
+ continue;
+ case EINTR:
+ continue;
+ }
+
+ error.FormatErrno("Failed to write to FIFO %s",
+ fd->path_utf8.c_str());
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin fifo_output_plugin = {
+ "fifo",
+ nullptr,
+ fifo_output_init,
+ fifo_output_finish,
+ nullptr,
+ nullptr,
+ fifo_output_open,
+ fifo_output_close,
+ fifo_output_delay,
+ nullptr,
+ fifo_output_play,
+ nullptr,
+ fifo_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx
new file mode 100644
index 000000000..f41ceded6
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX
+#define MPD_FIFO_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
new file mode 100644
index 000000000..e1dad7893
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "JackOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+
+struct JackOutput {
+ AudioOutput base;
+
+ /**
+ * libjack options passed to jack_client_open().
+ */
+ jack_options_t options;
+
+ const char *name;
+
+ const char *server_name;
+
+ /* configuration */
+
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
+ char *destination_ports[MAX_PORTS];
+ unsigned num_destination_ports;
+
+ size_t ringbuffer_size;
+
+ /* the current audio format */
+ AudioFormat audio_format;
+
+ /* jack library stuff */
+ jack_port_t *ports[MAX_PORTS];
+ jack_client_t *client;
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+ bool shutdown;
+
+ /**
+ * While this flag is set, the "process" callback generates
+ * silence.
+ */
+ bool pause;
+
+ JackOutput()
+ :base(jack_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain jack_output_domain("jack_output");
+
+/**
+ * Determine the number of frames guaranteed to be available on all
+ * channels.
+ */
+static jack_nframes_t
+mpd_jack_available(const JackOutput *jd)
+{
+ size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
+
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ if (current < min)
+ min = current;
+ }
+
+ assert(min % jack_sample_size == 0);
+
+ return min / jack_sample_size;
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ JackOutput *jd = (JackOutput *) arg;
+
+ if (nframes <= 0)
+ return 0;
+
+ if (jd->pause) {
+ /* empty the ring buffers */
+
+ const jack_nframes_t available = mpd_jack_available(jd);
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i)
+ jack_ringbuffer_read_advance(jd->ringbuffer[i],
+ available * jack_sample_size);
+
+ /* generate silence while MPD is paused */
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+ }
+
+ jack_nframes_t available = mpd_jack_available(jd);
+ if (available > nframes)
+ available = nframes;
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is
+ invoked anyway, but unable to get a
+ buffer */
+ continue;
+
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * jack_sample_size);
+
+ for (jack_nframes_t f = available; f < nframes; ++f)
+ /* ringbuffer underrun, fill with silence */
+ out[f] = 0.0;
+ }
+
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ jack_default_audio_sample_t *out =
+ (jack_default_audio_sample_t *)
+ jack_port_get_buffer(jd->ports[i], nframes);
+ if (out == nullptr)
+ /* workaround for libjack1 bug: if the server
+ connection fails, the process callback is
+ invoked anyway, but unable to get a
+ buffer */
+ continue;
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+ JackOutput *jd = (JackOutput *) arg;
+ jd->shutdown = true;
+}
+
+static void
+set_audioformat(JackOutput *jd, AudioFormat &audio_format)
+{
+ audio_format.sample_rate = jack_get_sample_rate(jd->client);
+
+ if (jd->num_source_ports == 1)
+ audio_format.channels = 1;
+ else if (audio_format.channels > jd->num_source_ports)
+ audio_format.channels = 2;
+
+ if (audio_format.format != SampleFormat::S16 &&
+ audio_format.format != SampleFormat::S24_P32)
+ audio_format.format = SampleFormat::S24_P32;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+ LogError(jack_output_domain, msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ LogDefault(jack_output_domain, msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(JackOutput *jd)
+{
+ assert(jd != nullptr);
+ assert(jd->client != nullptr);
+
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ jd->client = nullptr;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(JackOutput *jd, Error &error)
+{
+ jack_status_t status;
+
+ assert(jd != nullptr);
+
+ jd->shutdown = false;
+
+ jd->client = jack_client_open(jd->name, jd->options, &status,
+ jd->server_name);
+ if (jd->client == nullptr) {
+ error.Format(jack_output_domain, status,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (jd->ports[i] == nullptr) {
+ error.Format(jack_output_domain,
+ "Cannot register output port \"%s\"",
+ jd->source_ports[i]);
+ mpd_jack_disconnect(jd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+ return true;
+}
+
+static unsigned
+parse_port_list(const char *source, char **dest, Error &error)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != nullptr; ++n) {
+ if (n >= MAX_PORTS) {
+ error.Set(config_domain,
+ "too many port names");
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ error.Format(config_domain,
+ "at least one port name expected");
+ return 0;
+ }
+
+ return n;
+}
+
+static AudioOutput *
+mpd_jack_init(const config_param &param, Error &error)
+{
+ JackOutput *jd = new JackOutput();
+
+ if (!jd->Initialize(param, error)) {
+ delete jd;
+ return nullptr;
+ }
+
+ const char *value;
+
+ jd->options = JackNullOption;
+
+ jd->name = param.GetBlockValue("client_name", nullptr);
+ if (jd->name != nullptr)
+ jd->options = jack_options_t(jd->options | JackUseExactName);
+ else
+ /* if there's a no configured client name, we don't
+ care about the JackUseExactName option */
+ jd->name = "Music Player Daemon";
+
+ jd->server_name = param.GetBlockValue("server_name", nullptr);
+ if (jd->server_name != nullptr)
+ jd->options = jack_options_t(jd->options | JackServerName);
+
+ if (!param.GetBlockValue("autostart", false))
+ jd->options = jack_options_t(jd->options | JackNoStartServer);
+
+ /* configure the source ports */
+
+ value = param.GetBlockValue("source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(value,
+ jd->source_ports, error);
+ if (jd->num_source_ports == 0)
+ return nullptr;
+
+ /* configure the destination ports */
+
+ value = param.GetBlockValue("destination_ports", nullptr);
+ if (value == nullptr) {
+ /* compatibility with MPD < 0.16 */
+ value = param.GetBlockValue("ports", nullptr);
+ if (value != nullptr)
+ FormatWarning(jack_output_domain,
+ "deprecated option 'ports' in line %d",
+ param.line);
+ }
+
+ if (value != nullptr) {
+ jd->num_destination_ports =
+ parse_port_list(value,
+ jd->destination_ports, error);
+ if (jd->num_destination_ports == 0)
+ return nullptr;
+ } else {
+ jd->num_destination_ports = 0;
+ }
+
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ FormatWarning(jack_output_domain,
+ "number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param.line);
+
+ jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
+
+ jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+ jack_set_info_function(mpd_jack_info);
+#endif
+
+ return &jd->base;
+}
+
+static void
+mpd_jack_finish(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ delete jd;
+}
+
+static bool
+mpd_jack_enable(AudioOutput *ao, Error &error)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = nullptr;
+
+ return mpd_jack_connect(jd, error);
+}
+
+static void
+mpd_jack_disable(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ if (jd->client != nullptr)
+ mpd_jack_disconnect(jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] != nullptr) {
+ jack_ringbuffer_free(jd->ringbuffer[i]);
+ jd->ringbuffer[i] = nullptr;
+ }
+ }
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(JackOutput *jd)
+{
+ assert(jd != nullptr);
+
+ if (jd->client == nullptr)
+ return;
+
+ if (jd->shutdown)
+ /* the connection has failed; close it */
+ mpd_jack_disconnect(jd);
+ else
+ /* the connection is alive: just stop playback */
+ jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(JackOutput *jd, Error &error)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = nullptr;
+ unsigned num_destination_ports;
+
+ assert(jd->client != nullptr);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* allocate the ring buffers on the first open(); these
+ persist until MPD exits. It's too unsafe to delete them
+ because we can never know when mpd_jack_process() gets
+ called */
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] == nullptr)
+ jd->ringbuffer[i] =
+ jack_ringbuffer_create(jd->ringbuffer_size);
+
+ /* clear the ring buffer to be sure that data from
+ previous playbacks are gone */
+ jack_ringbuffer_reset(jd->ringbuffer[i]);
+ }
+
+ if ( jack_activate(jd->client) ) {
+ error.Set(jack_output_domain, "cannot activate client");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ if (jd->num_destination_ports == 0) {
+ /* no output ports were configured - ask libjack for
+ defaults */
+ jports = jack_get_ports(jd->client, nullptr, nullptr,
+ JackPortIsPhysical | JackPortIsInput);
+ if (jports == nullptr) {
+ error.Set(jack_output_domain, "no ports found");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ assert(*jports != nullptr);
+
+ for (num_destination_ports = 0;
+ num_destination_ports < MAX_PORTS &&
+ jports[num_destination_ports] != nullptr;
+ ++num_destination_ports) {
+ FormatDebug(jack_output_domain,
+ "destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } else {
+ /* use the configured output ports */
+
+ num_destination_ports = jd->num_destination_ports;
+ memcpy(destination_ports, jd->destination_ports,
+ num_destination_ports * sizeof(*destination_ports));
+
+ jports = nullptr;
+ }
+
+ assert(num_destination_ports > 0);
+
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ /* mix stereo signal on one speaker */
+
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
+ }
+
+ assert(num_destination_ports <= jd->num_source_ports);
+
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+ destination_ports[i]);
+ if (ret != 0) {
+ error.Format(jack_output_domain,
+ "Not a valid JACK port: %s",
+ destination_ports[i]);
+
+ if (jports != nullptr)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (duplicate_port != nullptr) {
+ /* mono input file: connect the one source channel to
+ the both destination channels */
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ duplicate_port);
+ if (ret != 0) {
+ error.Format(jack_output_domain,
+ "Not a valid JACK port: %s",
+ duplicate_port);
+
+ if (jports != nullptr)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (jports != nullptr)
+ free(jports);
+
+ return true;
+}
+
+static bool
+mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ assert(jd != nullptr);
+
+ jd->pause = false;
+
+ if (jd->client != nullptr && jd->shutdown)
+ mpd_jack_disconnect(jd);
+
+ if (jd->client == nullptr && !mpd_jack_connect(jd, error))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = audio_format;
+
+ if (!mpd_jack_start(jd, error))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(gcc_unused AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ mpd_jack_stop(jd);
+}
+
+static unsigned
+mpd_jack_delay(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ return jd->base.pause && jd->pause && !jd->shutdown
+ ? 1000
+ : 0;
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_16_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i],
+ (const char *)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_24_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i],
+ (const char *)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static void
+mpd_jack_write_samples(JackOutput *jd, const void *src,
+ unsigned num_samples)
+{
+ switch (jd->audio_format.format) {
+ case SampleFormat::S16:
+ mpd_jack_write_samples_16(jd, (const int16_t*)src,
+ num_samples);
+ break;
+
+ case SampleFormat::S24_P32:
+ mpd_jack_write_samples_24(jd, (const int32_t*)src,
+ num_samples);
+ break;
+
+ default:
+ assert(false);
+ gcc_unreachable();
+ }
+}
+
+static size_t
+mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ JackOutput *jd = (JackOutput *)ao;
+ const size_t frame_size = jd->audio_format.GetFrameSize();
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ error.Set(jack_output_domain,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= jack_sample_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= jack_sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ return true;
+}
+
+const struct AudioOutputPlugin jack_output_plugin = {
+ "jack",
+ mpd_jack_test_default_device,
+ mpd_jack_init,
+ mpd_jack_finish,
+ mpd_jack_enable,
+ mpd_jack_disable,
+ mpd_jack_open,
+ mpd_jack_close,
+ mpd_jack_delay,
+ nullptr,
+ mpd_jack_play,
+ nullptr,
+ nullptr,
+ mpd_jack_pause,
+ nullptr,
+};
diff --git a/src/output/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx
new file mode 100644
index 000000000..6f1f7ecb9
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX
+#define MPD_JACK_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin jack_output_plugin;
+
+#endif
diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
new file mode 100644
index 000000000..098f58926
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NullOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
+
+struct NullOutput {
+ AudioOutput base;
+
+ bool sync;
+
+ Timer *timer;
+
+ NullOutput()
+ :base(null_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+};
+
+static AudioOutput *
+null_init(const config_param &param, Error &error)
+{
+ NullOutput *nd = new NullOutput();
+
+ if (!nd->Initialize(param, error)) {
+ delete nd;
+ return nullptr;
+ }
+
+ nd->sync = param.GetBlockValue("sync", true);
+
+ return &nd->base;
+}
+
+static void
+null_finish(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ delete nd;
+}
+
+static bool
+null_open(AudioOutput *ao, AudioFormat &audio_format,
+ gcc_unused Error &error)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ nd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+null_close(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ delete nd->timer;
+}
+
+static unsigned
+null_delay(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ return nd->sync && nd->timer->IsStarted()
+ ? nd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size,
+ gcc_unused Error &error)
+{
+ NullOutput *nd = (NullOutput *)ao;
+ Timer *timer = nd->timer;
+
+ if (!nd->sync)
+ return size;
+
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
+
+ return size;
+}
+
+static void
+null_cancel(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (!nd->sync)
+ return;
+
+ nd->timer->Reset();
+}
+
+const struct AudioOutputPlugin null_output_plugin = {
+ "null",
+ nullptr,
+ null_init,
+ null_finish,
+ nullptr,
+ nullptr,
+ null_open,
+ null_close,
+ null_delay,
+ nullptr,
+ null_play,
+ nullptr,
+ null_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx
new file mode 100644
index 000000000..f25f5b9f3
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX
+#define MPD_NULL_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin null_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
new file mode 100644
index 000000000..13ac7b35e
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OSXOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+struct OSXOutput {
+ AudioOutput base;
+
+ /* configuration settings */
+ OSType component_subtype;
+ /* only applicable with kAudioUnitSubType_HALOutput */
+ const char *device_name;
+
+ AudioUnit au;
+ Mutex mutex;
+ Cond condition;
+
+ DynamicFifoBuffer<uint8_t> *buffer;
+
+ OSXOutput()
+ :base(osx_output_plugin) {}
+};
+
+static constexpr Domain osx_output_domain("osx_output");
+
+static bool
+osx_output_test_default_device(void)
+{
+ /* on a Mac, this is always the default plugin, if nothing
+ else is configured */
+ return true;
+}
+
+static void
+osx_output_configure(OSXOutput *oo, const config_param &param)
+{
+ const char *device = param.GetBlockValue("device");
+
+ if (device == NULL || 0 == strcmp(device, "default")) {
+ oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+ oo->device_name = NULL;
+ }
+ else if (0 == strcmp(device, "system")) {
+ oo->component_subtype = kAudioUnitSubType_SystemOutput;
+ oo->device_name = NULL;
+ }
+ else {
+ oo->component_subtype = kAudioUnitSubType_HALOutput;
+ /* XXX am I supposed to strdup() this? */
+ oo->device_name = device;
+ }
+}
+
+static AudioOutput *
+osx_output_init(const config_param &param, Error &error)
+{
+ OSXOutput *oo = new OSXOutput();
+ if (!oo->base.Configure(param, error)) {
+ delete oo;
+ return NULL;
+ }
+
+ osx_output_configure(oo, param);
+
+ return &oo->base;
+}
+
+static void
+osx_output_finish(AudioOutput *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ delete oo;
+}
+
+static bool
+osx_output_set_device(OSXOutput *oo, Error &error)
+{
+ bool ret = true;
+ OSStatus status;
+ UInt32 size, numdevices;
+ AudioDeviceID *deviceids = NULL;
+ char name[256];
+ unsigned int i;
+
+ if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+ goto done;
+
+ /* how many audio devices are there? */
+ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &size,
+ NULL);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to determine number of OS X audio devices: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* what are the available audio device IDs? */
+ numdevices = size / sizeof(AudioDeviceID);
+ deviceids = new AudioDeviceID[numdevices];
+ status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &size,
+ deviceids);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to determine OS X audio device IDs: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* which audio device matches oo->device_name? */
+ for (i = 0; i < numdevices; i++) {
+ size = sizeof(name);
+ status = AudioDeviceGetProperty(deviceids[i], 0, false,
+ kAudioDevicePropertyDeviceName,
+ &size, name);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to determine OS X device name "
+ "(device %u): %s",
+ (unsigned int) deviceids[i],
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ if (strcmp(oo->device_name, name) == 0) {
+ FormatDebug(osx_output_domain,
+ "found matching device: ID=%u, name=%s",
+ (unsigned)deviceids[i], name);
+ break;
+ }
+ }
+ if (i == numdevices) {
+ FormatWarning(osx_output_domain,
+ "Found no audio device with name '%s' "
+ "(will use default audio device)",
+ oo->device_name);
+ goto done;
+ }
+
+ status = AudioUnitSetProperty(oo->au,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &(deviceids[i]),
+ sizeof(AudioDeviceID));
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to set OS X audio output device: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ FormatDebug(osx_output_domain,
+ "set OS X audio output device ID=%u, name=%s",
+ (unsigned)deviceids[i], name);
+
+done:
+ delete[] deviceids;
+ return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+ gcc_unused AudioUnitRenderActionFlags *io_action_flags,
+ gcc_unused const AudioTimeStamp *in_timestamp,
+ gcc_unused UInt32 in_bus_number,
+ gcc_unused UInt32 in_number_frames,
+ AudioBufferList *buffer_list)
+{
+ OSXOutput *od = (OSXOutput *) vdata;
+ AudioBuffer *buffer = &buffer_list->mBuffers[0];
+ size_t buffer_size = buffer->mDataByteSize;
+
+ assert(od->buffer != NULL);
+
+ od->mutex.lock();
+
+ auto src = od->buffer->Read();
+ if (!src.IsEmpty()) {
+ if (src.size > buffer_size)
+ src.size = buffer_size;
+
+ memcpy(buffer->mData, src.data, src.size);
+ od->buffer->Consume(src.size);
+ }
+
+ od->condition.signal();
+ od->mutex.unlock();
+
+ buffer->mDataByteSize = src.size;
+
+ unsigned i;
+ for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
+ buffer = &buffer_list->mBuffers[i];
+ buffer->mDataByteSize = 0;
+ }
+
+ return 0;
+}
+
+static bool
+osx_output_enable(AudioOutput *ao, Error &error)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = oo->component_subtype;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == 0) {
+ error.Set(osx_output_domain,
+ "Error finding OS X component");
+ return false;
+ }
+
+ OSStatus status = OpenAComponent(comp, &oo->au);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to open OS X component: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ if (!osx_output_set_device(oo, error)) {
+ CloseComponent(oo->au);
+ return false;
+ }
+
+ AURenderCallbackStruct callback;
+ callback.inputProc = osx_render;
+ callback.inputProcRefCon = oo;
+
+ ComponentResult result =
+ AudioUnitSetProperty(oo->au,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0,
+ &callback, sizeof(callback));
+ if (result != noErr) {
+ CloseComponent(oo->au);
+ error.Set(osx_output_domain, result,
+ "unable to set callback for OS X audio unit");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+osx_output_disable(AudioOutput *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(AudioOutput *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+ od->buffer->Clear();
+}
+
+static void
+osx_output_close(AudioOutput *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ AudioOutputUnitStop(od->au);
+ AudioUnitUninitialize(od->au);
+
+ delete od->buffer;
+}
+
+static bool
+osx_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ AudioStreamBasicDescription stream_description;
+ stream_description.mSampleRate = audio_format.sample_rate;
+ stream_description.mFormatID = kAudioFormatLinearPCM;
+ stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SampleFormat::S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ case SampleFormat::S32:
+ stream_description.mBitsPerChannel = 32;
+ break;
+
+ default:
+ audio_format.format = SampleFormat::S32;
+ stream_description.mBitsPerChannel = 32;
+ break;
+ }
+
+ if (IsBigEndian())
+ stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+
+ stream_description.mBytesPerPacket = audio_format.GetFrameSize();
+ stream_description.mFramesPerPacket = 1;
+ stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
+ stream_description.mChannelsPerFrame = audio_format.channels;
+
+ ComponentResult result =
+ AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0,
+ &stream_description,
+ sizeof(stream_description));
+ if (result != noErr) {
+ error.Set(osx_output_domain, result,
+ "Unable to set format on OS X device");
+ return false;
+ }
+
+ OSStatus status = AudioUnitInitialize(od->au);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to initialize OS X audio unit: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ /* create a buffer of 1s */
+ od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
+ audio_format.GetFrameSize());
+
+ status = AudioOutputUnitStart(od->au);
+ if (status != 0) {
+ AudioUnitUninitialize(od->au);
+ error.Format(osx_output_domain, status,
+ "unable to start audio output: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+osx_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ gcc_unused Error &error)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+
+ DynamicFifoBuffer<uint8_t>::Range dest;
+ while (true) {
+ dest = od->buffer->Write();
+ if (!dest.IsEmpty())
+ break;
+
+ /* wait for some free space in the buffer */
+ od->condition.wait(od->mutex);
+ }
+
+ if (size > dest.size)
+ size = dest.size;
+
+ memcpy(dest.data, chunk, size);
+ od->buffer->Append(size);
+
+ return size;
+}
+
+const struct AudioOutputPlugin osx_output_plugin = {
+ "osx",
+ osx_output_test_default_device,
+ osx_output_init,
+ osx_output_finish,
+ osx_output_enable,
+ osx_output_disable,
+ osx_output_open,
+ osx_output_close,
+ nullptr,
+ nullptr,
+ osx_output_play,
+ nullptr,
+ osx_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
new file mode 100644
index 000000000..d7aed40b6
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX
+#define MPD_OSX_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin osx_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
new file mode 100644
index 000000000..2f095c0a4
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpenALOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <unistd.h>
+
+#ifndef __APPLE__
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct OpenALOutput {
+ AudioOutput base;
+
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ ALuint buffers[NUM_BUFFERS];
+ unsigned filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+
+ OpenALOutput()
+ :base(openal_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain openal_output_domain("openal_output");
+
+static ALenum
+openal_audio_format(AudioFormat &audio_format)
+{
+ /* note: cannot map SampleFormat::S8 to AL_FORMAT_STEREO8 or
+ AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
+ samples, while MPD uses signed samples */
+
+ switch (audio_format.format) {
+ case SampleFormat::S16:
+ if (audio_format.channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format.channels == 1)
+ return AL_FORMAT_MONO16;
+
+ /* fall back to mono */
+ audio_format.channels = 1;
+ return openal_audio_format(audio_format);
+
+ default:
+ /* fall back to 16 bit */
+ audio_format.format = SampleFormat::S16;
+ return openal_audio_format(audio_format);
+ }
+}
+
+gcc_pure
+static inline ALint
+openal_get_source_i(const OpenALOutput *od, ALenum param)
+{
+ ALint value;
+ alGetSourcei(od->source, param, &value);
+ return value;
+}
+
+gcc_pure
+static inline bool
+openal_has_processed(const OpenALOutput *od)
+{
+ return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+gcc_pure
+static inline ALint
+openal_is_playing(const OpenALOutput *od)
+{
+ return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
+}
+
+static bool
+openal_setup_context(OpenALOutput *od, Error &error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == nullptr) {
+ error.Format(openal_output_domain,
+ "Error opening OpenAL device \"%s\"",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, nullptr);
+
+ if (od->context == nullptr) {
+ error.Format(openal_output_domain,
+ "Error creating context for \"%s\"",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+openal_init(const config_param &param, Error &error)
+{
+ const char *device_name = param.GetBlockValue("device");
+ if (device_name == nullptr) {
+ device_name = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
+ }
+
+ OpenALOutput *od = new OpenALOutput();
+ if (!od->Initialize(param, error)) {
+ delete od;
+ return nullptr;
+ }
+
+ od->device_name = device_name;
+
+ return &od->base;
+}
+
+static void
+openal_finish(AudioOutput *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ delete od;
+}
+
+static bool
+openal_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ od->format = openal_audio_format(audio_format);
+
+ if (!openal_setup_context(od, error)) {
+ return false;
+ }
+
+ alcMakeContextCurrent(od->context);
+ alGenBuffers(NUM_BUFFERS, od->buffers);
+
+ if (alGetError() != AL_NO_ERROR) {
+ error.Set(openal_output_domain, "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ error.Set(openal_output_domain, "Failed to generate source");
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ return false;
+ }
+
+ od->filled = 0;
+ od->frequency = audio_format.sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(AudioOutput *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static unsigned
+openal_delay(AudioOutput *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ return od->filled < NUM_BUFFERS || openal_has_processed(od)
+ ? 0
+ /* we don't know exactly how long we must wait for the
+ next buffer to finish, so this is a random
+ guess: */
+ : 50;
+}
+
+static size_t
+openal_play(AudioOutput *ao, const void *chunk, size_t size,
+ gcc_unused Error &error)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+ ALuint buffer;
+
+ if (alcGetCurrentContext() != od->context) {
+ alcMakeContextCurrent(od->context);
+ }
+
+ if (od->filled < NUM_BUFFERS) {
+ /* fill all buffers */
+ buffer = od->buffers[od->filled];
+ od->filled++;
+ } else {
+ /* wait for processed buffer */
+ while (!openal_has_processed(od))
+ usleep(10);
+
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+
+ alBufferData(buffer, od->format, chunk, size, od->frequency);
+ alSourceQueueBuffers(od->source, 1, &buffer);
+
+ if (!openal_is_playing(od))
+ alSourcePlay(od->source);
+
+ return size;
+}
+
+static void
+openal_cancel(AudioOutput *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+
+ /* force-unqueue all buffers */
+ alSourcei(od->source, AL_BUFFER, 0);
+ od->filled = 0;
+}
+
+const struct AudioOutputPlugin openal_output_plugin = {
+ "openal",
+ nullptr,
+ openal_init,
+ openal_finish,
+ nullptr,
+ nullptr,
+ openal_open,
+ openal_close,
+ openal_delay,
+ nullptr,
+ openal_play,
+ nullptr,
+ openal_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx
new file mode 100644
index 000000000..a27e6b53c
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX
+#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin openal_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
new file mode 100644
index 000000000..39d87fc35
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -0,0 +1,779 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OssOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "system/fd_util.h"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+/* We got bug reports from FreeBSD users who said that the two 24 bit
+ formats generate white noise on FreeBSD, but 32 bit works. This is
+ a workaround until we know what exactly is expected by the kernel
+ audio drivers. */
+#ifndef __linux__
+#undef AFMT_S24_PACKED
+#undef AFMT_S24_NE
+#endif
+
+#ifdef AFMT_S24_PACKED
+#include "pcm/PcmExport.hxx"
+#include "util/Manual.hxx"
+#endif
+
+struct OssOutput {
+ AudioOutput base;
+
+#ifdef AFMT_S24_PACKED
+ Manual<PcmExport> pcm_export;
+#endif
+
+ int fd;
+ const char *device;
+
+ /**
+ * The current input audio format. This is needed to reopen
+ * the device after cancel().
+ */
+ AudioFormat audio_format;
+
+ /**
+ * The current OSS audio format. This is needed to reopen the
+ * device after cancel().
+ */
+ int oss_format;
+
+ OssOutput()
+ :base(oss_output_plugin),
+ fd(-1), device(nullptr) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain oss_output_domain("oss_output");
+
+enum oss_stat {
+ OSS_STAT_NO_ERROR = 0,
+ OSS_STAT_NOT_CHAR_DEV = -1,
+ OSS_STAT_NO_PERMS = -2,
+ OSS_STAT_DOESN_T_EXIST = -3,
+ OSS_STAT_OTHER = -4,
+};
+
+static enum oss_stat
+oss_stat_device(const char *device, int *errno_r)
+{
+ struct stat st;
+
+ if (0 == stat(device, &st)) {
+ if (!S_ISCHR(st.st_mode)) {
+ return OSS_STAT_NOT_CHAR_DEV;
+ }
+ } else {
+ *errno_r = errno;
+
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return OSS_STAT_DOESN_T_EXIST;
+ case EACCES:
+ return OSS_STAT_NO_PERMS;
+ default:
+ return OSS_STAT_OTHER;
+ }
+ }
+
+ return OSS_STAT_NO_ERROR;
+}
+
+static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
+
+static bool
+oss_output_test_default_device(void)
+{
+ int fd, i;
+
+ for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ }
+
+ FormatErrno(oss_output_domain,
+ "Error opening OSS device \"%s\"",
+ default_devices[i]);
+ }
+
+ return false;
+}
+
+static AudioOutput *
+oss_open_default(Error &error)
+{
+ int err[ARRAY_SIZE(default_devices)];
+ enum oss_stat ret[ARRAY_SIZE(default_devices)];
+
+ const config_param empty;
+ for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+ ret[i] = oss_stat_device(default_devices[i], &err[i]);
+ if (ret[i] == OSS_STAT_NO_ERROR) {
+ OssOutput *od = new OssOutput();
+ if (!od->Initialize(empty, error)) {
+ delete od;
+ return NULL;
+ }
+
+ od->device = default_devices[i];
+ return &od->base;
+ }
+ }
+
+ for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
+ const char *dev = default_devices[i];
+ switch(ret[i]) {
+ case OSS_STAT_NO_ERROR:
+ /* never reached */
+ break;
+ case OSS_STAT_DOESN_T_EXIST:
+ FormatWarning(oss_output_domain,
+ "%s not found", dev);
+ break;
+ case OSS_STAT_NOT_CHAR_DEV:
+ FormatWarning(oss_output_domain,
+ "%s is not a character device", dev);
+ break;
+ case OSS_STAT_NO_PERMS:
+ FormatWarning(oss_output_domain,
+ "%s: permission denied", dev);
+ break;
+ case OSS_STAT_OTHER:
+ FormatErrno(oss_output_domain, err[i],
+ "Error accessing %s", dev);
+ }
+ }
+
+ error.Set(oss_output_domain,
+ "error trying to open default OSS device");
+ return NULL;
+}
+
+static AudioOutput *
+oss_output_init(const config_param &param, Error &error)
+{
+ const char *device = param.GetBlockValue("device");
+ if (device != NULL) {
+ OssOutput *od = new OssOutput();
+ if (!od->Initialize(param, error)) {
+ delete od;
+ return NULL;
+ }
+
+ od->device = device;
+ return &od->base;
+ }
+
+ return oss_open_default(error);
+}
+
+static void
+oss_output_finish(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ delete od;
+}
+
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(AudioOutput *ao, gcc_unused Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->pcm_export.Construct();
+ return true;
+}
+
+static void
+oss_output_disable(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->pcm_export.Destruct();
+}
+
+#endif
+
+static void
+oss_close(OssOutput *od)
+{
+ if (od->fd >= 0)
+ close(od->fd);
+ od->fd = -1;
+}
+
+/**
+ * A tri-state type for oss_try_ioctl().
+ */
+enum oss_setup_result {
+ SUCCESS,
+ ERROR,
+ UNSUPPORTED,
+};
+
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
+ const char *msg, Error &error)
+{
+ assert(fd >= 0);
+ assert(value_r != NULL);
+ assert(msg != NULL);
+ assert(!error.IsDefined());
+
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
+
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ error.SetErrno(msg);
+ return ERROR;
+}
+
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+ const char *msg, Error &error_r)
+{
+ return oss_try_ioctl_r(fd, request, &value, msg, error_r);
+}
+
+/**
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
+ */
+static bool
+oss_setup_channels(int fd, AudioFormat &audio_format, Error &error)
+{
+ const char *const msg = "Failed to set channel count";
+ int channels = audio_format.channels;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format.channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ for (unsigned i = 1; i < 2; ++i) {
+ if (i == audio_format.channels)
+ /* don't try that again */
+ continue;
+
+ channels = i;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
+ msg, error);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format.channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, msg);
+ return false;
+}
+
+/**
+ * Set up the sample rate, and attempts to find alternatives if the
+ * specified sample rate is not supported.
+ */
+static bool
+oss_setup_sample_rate(int fd, AudioFormat &audio_format,
+ Error &error)
+{
+ const char *const msg = "Failed to set sample rate";
+ int sample_rate = audio_format.sample_rate;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format.sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ static const int sample_rates[] = { 48000, 44100, 0 };
+ for (unsigned i = 0; sample_rates[i] != 0; ++i) {
+ sample_rate = sample_rates[i];
+ if (sample_rate == (int)audio_format.sample_rate)
+ continue;
+
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format.sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, msg);
+ return false;
+}
+
+/**
+ * Convert a MPD sample format to its OSS counterpart. Returns
+ * AFMT_QUERY if there is no direct counterpart.
+ */
+static int
+sample_format_to_oss(SampleFormat format)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ return AFMT_QUERY;
+
+ case SampleFormat::S8:
+ return AFMT_S8;
+
+ case SampleFormat::S16:
+ return AFMT_S16_NE;
+
+ case SampleFormat::S24_P32:
+#ifdef AFMT_S24_NE
+ return AFMT_S24_NE;
+#else
+ return AFMT_QUERY;
+#endif
+
+ case SampleFormat::S32:
+#ifdef AFMT_S32_NE
+ return AFMT_S32_NE;
+#else
+ return AFMT_QUERY;
+#endif
+ }
+
+ return AFMT_QUERY;
+}
+
+/**
+ * Convert an OSS sample format to its MPD counterpart. Returns
+ * SampleFormat::UNDEFINED if there is no direct counterpart.
+ */
+static SampleFormat
+sample_format_from_oss(int format)
+{
+ switch (format) {
+ case AFMT_S8:
+ return SampleFormat::S8;
+
+ case AFMT_S16_NE:
+ return SampleFormat::S16;
+
+#ifdef AFMT_S24_PACKED
+ case AFMT_S24_PACKED:
+ return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S24_NE
+ case AFMT_S24_NE:
+ return SampleFormat::S24_P32;
+#endif
+
+#ifdef AFMT_S32_NE
+ case AFMT_S32_NE:
+ return SampleFormat::S32;
+#endif
+
+ default:
+ return SampleFormat::UNDEFINED;
+ }
+}
+
+/**
+ * Probe one sample format.
+ *
+ * @return the selected sample format or SampleFormat::UNDEFINED on
+ * error
+ */
+static enum oss_setup_result
+oss_probe_sample_format(int fd, SampleFormat sample_format,
+ SampleFormat *sample_format_r,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ PcmExport &pcm_export,
+#endif
+ Error &error)
+{
+ int oss_format = sample_format_to_oss(sample_format);
+ if (oss_format == AFMT_QUERY)
+ return UNSUPPORTED;
+
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error);
+
+#ifdef AFMT_S24_PACKED
+ if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
+ /* if the driver doesn't support padded 24 bit, try
+ packed 24 bit */
+ oss_format = AFMT_S24_PACKED;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error);
+ }
+#endif
+
+ if (result != SUCCESS)
+ return result;
+
+ sample_format = sample_format_from_oss(oss_format);
+ if (sample_format == SampleFormat::UNDEFINED)
+ return UNSUPPORTED;
+
+ *sample_format_r = sample_format;
+ *oss_format_r = oss_format;
+
+#ifdef AFMT_S24_PACKED
+ pcm_export.Open(sample_format, 0, false, false,
+ oss_format == AFMT_S24_PACKED,
+ oss_format == AFMT_S24_PACKED &&
+ !IsLittleEndian());
+#endif
+
+ return SUCCESS;
+}
+
+/**
+ * Set up the sample format, and attempts to find alternatives if the
+ * specified format is not supported.
+ */
+static bool
+oss_setup_sample_format(int fd, AudioFormat &audio_format,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ PcmExport &pcm_export,
+#endif
+ Error &error)
+{
+ SampleFormat mpd_format;
+ enum oss_setup_result result =
+ oss_probe_sample_format(fd, audio_format.format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ if (result != UNSUPPORTED)
+ return result == SUCCESS;
+
+ /* the requested sample format is not available - probe for
+ other formats supported by MPD */
+
+ static const SampleFormat sample_formats[] = {
+ SampleFormat::S24_P32,
+ SampleFormat::S32,
+ SampleFormat::S16,
+ SampleFormat::S8,
+ SampleFormat::UNDEFINED /* sentinel */
+ };
+
+ for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
+ mpd_format = sample_formats[i];
+ if (mpd_format == audio_format.format)
+ /* don't try that again */
+ continue;
+
+ result = oss_probe_sample_format(fd, mpd_format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, "Failed to set sample format");
+ return false;
+}
+
+/**
+ * Sets up the OSS device which was opened before.
+ */
+static bool
+oss_setup(OssOutput *od, AudioFormat &audio_format,
+ Error &error)
+{
+ return oss_setup_channels(od->fd, audio_format, error) &&
+ oss_setup_sample_rate(od->fd, audio_format, error) &&
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ od->pcm_export,
+#endif
+ error);
+}
+
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(OssOutput *od, Error &error)
+{
+ assert(od->fd < 0);
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ return false;
+ }
+
+ enum oss_setup_result result;
+
+ const char *const msg1 = "Failed to set channel count";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
+ od->audio_format.channels, msg1, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg1);
+ return false;
+ }
+
+ const char *const msg2 = "Failed to set sample rate";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
+ od->audio_format.sample_rate, msg2, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg2);
+ return false;
+ }
+
+ const char *const msg3 = "Failed to set sample format";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
+ od->oss_format,
+ msg3, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg3);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+oss_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ return false;
+ }
+
+ if (!oss_setup(od, audio_format, error)) {
+ oss_close(od);
+ return false;
+ }
+
+ od->audio_format = audio_format;
+ return true;
+}
+
+static void
+oss_output_close(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ oss_close(od);
+}
+
+static void
+oss_output_cancel(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ if (od->fd >= 0) {
+ ioctl(od->fd, SNDCTL_DSP_RESET, 0);
+ oss_close(od);
+ }
+}
+
+static size_t
+oss_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+ ssize_t ret;
+
+ assert(size > 0);
+
+ /* reopen the device since it was closed by dropBufferedAudio */
+ if (od->fd < 0 && !oss_reopen(od, error))
+ return 0;
+
+#ifdef AFMT_S24_PACKED
+ const auto e = od->pcm_export->Export({chunk, size});
+ chunk = e.data;
+ size = e.size;
+#endif
+
+ assert(size > 0);
+
+ while (true) {
+ ret = write(od->fd, chunk, size);
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = od->pcm_export->CalcSourceSize(ret);
+#endif
+ return ret;
+ }
+
+ if (ret < 0 && errno != EINTR) {
+ error.FormatErrno("Write error on %s", od->device);
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin oss_output_plugin = {
+ "oss",
+ oss_output_test_default_device,
+ oss_output_init,
+ oss_output_finish,
+#ifdef AFMT_S24_PACKED
+ oss_output_enable,
+ oss_output_disable,
+#else
+ nullptr,
+ nullptr,
+#endif
+ oss_output_open,
+ oss_output_close,
+ nullptr,
+ nullptr,
+ oss_output_play,
+ nullptr,
+ oss_output_cancel,
+ nullptr,
+
+ &oss_mixer_plugin,
+};
diff --git a/src/output/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx
new file mode 100644
index 000000000..f9970c8f0
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX
+#define MPD_OSS_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin oss_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
new file mode 100644
index 000000000..7a1f32258
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PipeOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string>
+
+#include <stdio.h>
+
+struct PipeOutput {
+ AudioOutput base;
+
+ std::string cmd;
+ FILE *fh;
+
+ PipeOutput()
+ :base(pipe_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain pipe_output_domain("pipe_output");
+
+inline bool
+PipeOutput::Configure(const config_param &param, Error &error)
+{
+ cmd = param.GetBlockValue("command", "");
+ if (cmd.empty()) {
+ error.Set(config_domain,
+ "No \"command\" parameter specified");
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+pipe_output_init(const config_param &param, Error &error)
+{
+ PipeOutput *pd = new PipeOutput();
+
+ if (!pd->Initialize(param, error)) {
+ delete pd;
+ return nullptr;
+ }
+
+ if (!pd->Configure(param, error)) {
+ delete pd;
+ return nullptr;
+ }
+
+ return &pd->base;
+}
+
+static void
+pipe_output_finish(AudioOutput *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ delete pd;
+}
+
+static bool
+pipe_output_open(AudioOutput *ao,
+ gcc_unused AudioFormat &audio_format,
+ Error &error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pd->fh = popen(pd->cmd.c_str(), "w");
+ if (pd->fh == nullptr) {
+ error.FormatErrno("Error opening pipe \"%s\"",
+ pd->cmd.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+static void
+pipe_output_close(AudioOutput *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pclose(pd->fh);
+}
+
+static size_t
+pipe_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+ size_t ret;
+
+ ret = fwrite(chunk, 1, size, pd->fh);
+ if (ret == 0)
+ error.SetErrno("Write error on pipe");
+
+ return ret;
+}
+
+const struct AudioOutputPlugin pipe_output_plugin = {
+ "pipe",
+ nullptr,
+ pipe_output_init,
+ pipe_output_finish,
+ nullptr,
+ nullptr,
+ pipe_output_open,
+ pipe_output_close,
+ nullptr,
+ nullptr,
+ pipe_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx
new file mode 100644
index 000000000..bdaf2ecfd
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX
+#define MPD_PIPE_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
new file mode 100644
index 000000000..120bad090
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -0,0 +1,882 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PulseOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "mixer/plugins/PulseMixerPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+#include <pulse/version.h>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+struct PulseOutput {
+ AudioOutput base;
+
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ PulseMixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+ PulseOutput()
+ :base(pulse_output_plugin) {}
+};
+
+static constexpr Domain pulse_output_domain("pulse_output");
+
+static void
+SetError(Error &error, pa_context *context, const char *msg)
+{
+ const int e = pa_context_errno(context);
+ error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
+}
+
+void
+pulse_output_lock(PulseOutput &po)
+{
+ pa_threaded_mainloop_lock(po.mainloop);
+}
+
+void
+pulse_output_unlock(PulseOutput &po)
+{
+ pa_threaded_mainloop_unlock(po.mainloop);
+}
+
+void
+pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm)
+{
+ assert(po.mixer == nullptr);
+
+ po.mixer = &pm;
+
+ if (po.mainloop == nullptr)
+ return;
+
+ pa_threaded_mainloop_lock(po.mainloop);
+
+ if (po.context != nullptr &&
+ pa_context_get_state(po.context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po.context);
+
+ if (po.stream != nullptr &&
+ pa_stream_get_state(po.stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po.context, po.stream);
+ }
+
+ pa_threaded_mainloop_unlock(po.mainloop);
+}
+
+void
+pulse_output_clear_mixer(PulseOutput &po, gcc_unused PulseMixer &pm)
+{
+ assert(po.mixer == &pm);
+
+ po.mixer = nullptr;
+}
+
+bool
+pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume,
+ Error &error)
+{
+ pa_operation *o;
+
+ if (po.context == nullptr || po.stream == nullptr ||
+ pa_stream_get_state(po.stream) != PA_STREAM_READY) {
+ error.Set(pulse_output_domain, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po.context,
+ pa_stream_get_index(po.stream),
+ volume, nullptr, nullptr);
+ if (o == nullptr) {
+ SetError(error, po.context,
+ "failed to set PulseAudio volume");
+ return false;
+ }
+
+ pa_operation_unref(o);
+ return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ * false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+ struct pa_operation *operation)
+{
+ assert(mainloop != nullptr);
+ assert(operation != nullptr);
+
+ pa_operation_state_t state;
+ while ((state = pa_operation_get_state(operation))
+ == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mainloop);
+
+ pa_operation_unref(operation);
+
+ return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation. It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(gcc_unused pa_stream *s,
+ gcc_unused int success, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_connect(*po->mixer, context);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_disconnect(*po->mixer);
+
+ /* the caller thread might be waiting for these
+ states */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+ pa_subscription_event_type_t t,
+ uint32_t idx, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+ pa_subscription_event_type_t facility =
+ pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
+ pa_subscription_event_type_t type =
+ pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
+
+ if (po->mixer != nullptr &&
+ facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+ po->stream != nullptr &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+ idx == pa_stream_get_index(po->stream) &&
+ (type == PA_SUBSCRIPTION_EVENT_NEW ||
+ type == PA_SUBSCRIPTION_EVENT_CHANGE))
+ pulse_mixer_on_change(*po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(PulseOutput *po, Error &error)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ if (pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, nullptr) < 0) {
+ SetError(error, po->context,
+ "pa_context_connect() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the stream.
+ */
+static void
+pulse_output_delete_stream(PulseOutput *po)
+{
+ assert(po != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
+
+ pa_stream_set_state_callback(po->stream, nullptr, nullptr);
+ pa_stream_set_write_callback(po->stream, nullptr, nullptr);
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = nullptr;
+}
+
+/**
+ * Frees and clears the context.
+ *
+ * Caller must lock the main loop.
+ */
+static void
+pulse_output_delete_context(PulseOutput *po)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ pa_context_set_state_callback(po->context, nullptr, nullptr);
+ pa_context_set_subscribe_callback(po->context, nullptr, nullptr);
+
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = nullptr;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(PulseOutput *po, Error &error)
+{
+ assert(po != nullptr);
+ assert(po->mainloop != nullptr);
+
+ po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+ MPD_PULSE_NAME);
+ if (po->context == nullptr) {
+ error.Set(pulse_output_domain, "pa_context_new() has failed");
+ return false;
+ }
+
+ pa_context_set_state_callback(po->context,
+ pulse_output_context_state_cb, po);
+ pa_context_set_subscribe_callback(po->context,
+ pulse_output_subscribe_cb, po);
+
+ if (!pulse_output_connect(po, error)) {
+ pulse_output_delete_context(po);
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+pulse_output_init(const config_param &param, Error &error)
+{
+ PulseOutput *po;
+
+ setenv("PULSE_PROP_media.role", "music", true);
+ setenv("PULSE_PROP_application.icon_name", "mpd", true);
+
+ po = new PulseOutput();
+ if (!po->base.Configure(param, error)) {
+ delete po;
+ return nullptr;
+ }
+
+ po->name = param.GetBlockValue("name", "mpd_pulse");
+ po->server = param.GetBlockValue("server");
+ po->sink = param.GetBlockValue("sink");
+
+ po->mixer = nullptr;
+ po->mainloop = nullptr;
+ po->context = nullptr;
+ po->stream = nullptr;
+
+ return &po->base;
+}
+
+static void
+pulse_output_finish(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ delete po;
+}
+
+static bool
+pulse_output_enable(AudioOutput *ao, Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop == nullptr);
+ assert(po->context == nullptr);
+
+ /* create the libpulse mainloop and start the thread */
+
+ po->mainloop = pa_threaded_mainloop_new();
+ if (po->mainloop == nullptr) {
+ error.Set(pulse_output_domain,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+
+ error.Set(pulse_output_domain,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ /* create the libpulse context and connect it */
+
+ if (!pulse_output_setup_context(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != nullptr)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(PulseOutput *po, Error &error)
+{
+ assert(po->mainloop != nullptr);
+
+ pa_context_state_t state;
+
+ if (po->context == nullptr && !pulse_output_setup_context(po, error))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ SetError(error, po->context, "failed to connect");
+ pulse_output_delete_context(po);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+static void
+pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+
+ /* wake up the main loop to break out of the loop in
+ pulse_output_play() */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_change(*po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_disconnect(*po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(po->mainloop != nullptr);
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
+ Error &error)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+ if (po->stream == nullptr) {
+ SetError(error, po->context, "pa_stream_new() has failed");
+ return false;
+ }
+
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ return true;
+}
+
+static bool
+pulse_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_sample_spec ss;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != nullptr) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format.format = SampleFormat::S16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format.sample_rate;
+ ss.channels = audio_format.channels;
+
+ /* create a stream .. */
+
+ if (!pulse_output_setup_stream(po, &ss, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* .. and connect it (asynchronously) */
+
+ if (pa_stream_connect_playback(po->stream, po->sink,
+ nullptr, pa_stream_flags_t(0),
+ nullptr, nullptr) < 0) {
+ pulse_output_delete_stream(po);
+
+ SetError(error, po->context,
+ "pa_stream_connect_playback() has failed");
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_close(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ FormatWarning(pulse_output_domain,
+ "pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pulse_output_delete_stream(po);
+
+ if (po->context != nullptr &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(PulseOutput *po, Error &error)
+{
+ while (true) {
+ switch (pa_stream_get_state(po->stream)) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ SetError(error, po->context,
+ "failed to connect the stream");
+ return false;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(PulseOutput *po, bool pause,
+ Error &error)
+{
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+ assert(po->stream != nullptr);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned
+pulse_output_delay(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ unsigned result = 0;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->base.pause && pa_stream_is_corked(po->stream) &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ /* idle while paused */
+ result = 1000;
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return result;
+}
+
+static size_t
+pulse_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != nullptr);
+
+ /* unpause if previously paused */
+
+ if (pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, false, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ if (pa_stream_is_suspended(po->stream)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "suspended");
+ return 0;
+ }
+
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "disconnected");
+ return 0;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ int result = pa_stream_write(po->stream, chunk, size, nullptr,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (result < 0) {
+ SetError(error, po->context, "pa_stream_write() failed");
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != nullptr);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ FormatWarning(pulse_output_domain,
+ "pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ Error error;
+ if (!pulse_output_wait_stream(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(error);
+ return false;
+ }
+
+ assert(po->context != nullptr);
+
+ /* cork the stream */
+
+ if (!pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, true, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ bool success;
+
+ const config_param empty;
+ PulseOutput *po = (PulseOutput *)
+ pulse_output_init(empty, IgnoreError());
+ if (po == nullptr)
+ return false;
+
+ success = pulse_output_wait_connection(po, IgnoreError());
+ pulse_output_finish(&po->base);
+
+ return success;
+}
+
+const struct AudioOutputPlugin pulse_output_plugin = {
+ "pulse",
+ pulse_output_test_default_device,
+ pulse_output_init,
+ pulse_output_finish,
+ pulse_output_enable,
+ pulse_output_disable,
+ pulse_output_open,
+ pulse_output_close,
+ pulse_output_delay,
+ nullptr,
+ pulse_output_play,
+ nullptr,
+ pulse_output_cancel,
+ pulse_output_pause,
+
+ &pulse_mixer_plugin,
+};
diff --git a/src/output/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx
new file mode 100644
index 000000000..9219780a5
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX
+#define MPD_PULSE_OUTPUT_PLUGIN_HXX
+
+struct PulseOutput;
+class PulseMixer;
+struct pa_cvolume;
+class Error;
+
+extern const struct AudioOutputPlugin pulse_output_plugin;
+
+void
+pulse_output_lock(PulseOutput &po);
+
+void
+pulse_output_unlock(PulseOutput &po);
+
+void
+pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm);
+
+void
+pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm);
+
+bool
+pulse_output_set_volume(PulseOutput &po,
+ const pa_cvolume *volume, Error &error);
+
+#endif
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
new file mode 100644
index 000000000..87e23f55a
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RecorderOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/fd_util.h"
+#include "open.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+struct RecorderOutput {
+ AudioOutput base;
+
+ /**
+ * The configured encoder plugin.
+ */
+ Encoder *encoder;
+
+ /**
+ * The destination file name.
+ */
+ const char *path;
+
+ /**
+ * The destination file descriptor.
+ */
+ int fd;
+
+ /**
+ * The buffer for encoder_read().
+ */
+ char buffer[32768];
+
+ RecorderOutput()
+ :base(recorder_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool WriteToFile(const void *data, size_t length, Error &error);
+
+ /**
+ * Writes pending data from the encoder to the output file.
+ */
+ bool EncoderToFile(Error &error);
+};
+
+static constexpr Domain recorder_output_domain("recorder_output");
+
+inline bool
+RecorderOutput::Configure(const config_param &param, Error &error)
+{
+ /* read configuration */
+
+ const char *encoder_name =
+ param.GetBlockValue("encoder", "vorbis");
+ const auto encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == nullptr) {
+ error.Format(config_domain,
+ "No such encoder: %s", encoder_name);
+ return false;
+ }
+
+ path = param.GetBlockValue("path");
+ if (path == nullptr) {
+ error.Set(config_domain, "'path' not configured");
+ return false;
+ }
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ if (encoder == nullptr)
+ return false;
+
+ return true;
+}
+
+static AudioOutput *
+recorder_output_init(const config_param &param, Error &error)
+{
+ RecorderOutput *recorder = new RecorderOutput();
+
+ if (!recorder->Initialize(param, error)) {
+ delete recorder;
+ return nullptr;
+ }
+
+ if (!recorder->Configure(param, error)) {
+ delete recorder;
+ return nullptr;
+ }
+
+ return &recorder->base;
+}
+
+static void
+recorder_output_finish(AudioOutput *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ encoder_finish(recorder->encoder);
+ delete recorder;
+}
+
+inline bool
+RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
+{
+ assert(length > 0);
+
+ const uint8_t *data = (const uint8_t *)_data, *end = data + length;
+
+ while (true) {
+ ssize_t nbytes = write(fd, data, end - data);
+ if (nbytes > 0) {
+ data += nbytes;
+ if (data == end)
+ return true;
+ } else if (nbytes == 0) {
+ /* shouldn't happen for files */
+ error.Set(recorder_output_domain,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ error.FormatErrno("Failed to write to '%s'", path);
+ return false;
+ }
+ }
+}
+
+inline bool
+RecorderOutput::EncoderToFile(Error &error)
+{
+ assert(fd >= 0);
+
+ while (true) {
+ /* read from the encoder */
+
+ size_t size = encoder_read(encoder, buffer, sizeof(buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ if (!WriteToFile(buffer, size, error))
+ return false;
+ }
+}
+
+static bool
+recorder_output_open(AudioOutput *ao,
+ AudioFormat &audio_format,
+ Error &error)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ /* create the output file */
+
+ recorder->fd = open_cloexec(recorder->path,
+ O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
+ 0666);
+ if (recorder->fd < 0) {
+ error.FormatErrno("Failed to create '%s'", recorder->path);
+ return false;
+ }
+
+ /* open the encoder */
+
+ if (!encoder_open(recorder->encoder, audio_format, error)) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ if (!recorder->EncoderToFile(error)) {
+ encoder_close(recorder->encoder);
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(AudioOutput *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_end(recorder->encoder, IgnoreError()))
+ recorder->EncoderToFile(IgnoreError());
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ return encoder_write(recorder->encoder, chunk, size, error) &&
+ recorder->EncoderToFile(error)
+ ? size : 0;
+}
+
+const struct AudioOutputPlugin recorder_output_plugin = {
+ "recorder",
+ nullptr,
+ recorder_output_init,
+ recorder_output_finish,
+ nullptr,
+ nullptr,
+ recorder_output_open,
+ recorder_output_close,
+ nullptr,
+ nullptr,
+ recorder_output_play,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx
new file mode 100644
index 000000000..ea1044e0f
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX
+#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
new file mode 100644
index 000000000..aa37c91b7
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RoarOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <string>
+
+/* libroar/services.h declares roar_service_stream::new - work around
+ this C++ problem */
+#define new _new
+#include <roaraudio.h>
+#undef new
+
+class RoarOutput {
+ AudioOutput base;
+
+ std::string host, name;
+
+ roar_vs_t * vss;
+ int err;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ mutable Mutex mutex;
+ bool alive;
+
+public:
+ RoarOutput()
+ :base(roar_output_plugin),
+ err(ROAR_ERROR_NONE) {}
+
+ operator AudioOutput *() {
+ return &base;
+ }
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ void Configure(const config_param &param);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
+
+ void SendTag(const Tag &tag);
+ size_t Play(const void *chunk, size_t size, Error &error);
+ void Cancel();
+
+ int GetVolume() const;
+ bool SetVolume(unsigned volume);
+};
+
+static constexpr Domain roar_output_domain("roar_output");
+
+inline int
+RoarOutput::GetVolume() const
+{
+ const ScopeLock protect(mutex);
+
+ if (vss == nullptr || !alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(RoarOutput &roar)
+{
+ return roar.GetVolume();
+}
+
+bool
+RoarOutput::SetVolume(unsigned volume)
+{
+ assert(volume <= 100);
+
+ const ScopeLock protect(mutex);
+ if (vss == nullptr || !alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(RoarOutput &roar, unsigned volume)
+{
+ return roar.SetVolume(volume);
+}
+
+inline void
+RoarOutput::Configure(const config_param &param)
+{
+ host = param.GetBlockValue("server", "");
+ name = param.GetBlockValue("name", "MPD");
+
+ const char *_role = param.GetBlockValue("role", "music");
+ role = _role != nullptr
+ ? roar_str2role(_role)
+ : ROAR_ROLE_MUSIC;
+}
+
+static AudioOutput *
+roar_init(const config_param &param, Error &error)
+{
+ RoarOutput *self = new RoarOutput();
+
+ if (!self->Initialize(param, error)) {
+ delete self;
+ return nullptr;
+ }
+
+ self->Configure(param);
+ return *self;
+}
+
+static void
+roar_finish(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ delete self;
+}
+
+static void
+roar_use_audio_format(struct roar_audio_info *info,
+ AudioFormat &audio_format)
+{
+ info->rate = audio_format.sample_rate;
+ info->channels = audio_format.channels;
+ info->codec = ROAR_CODEC_PCM_S;
+
+ switch (audio_format.format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ info->bits = 16;
+ audio_format.format = SampleFormat::S16;
+ break;
+
+ case SampleFormat::S8:
+ info->bits = 8;
+ break;
+
+ case SampleFormat::S16:
+ info->bits = 16;
+ break;
+
+ case SampleFormat::S24_P32:
+ info->bits = 32;
+ audio_format.format = SampleFormat::S32;
+ break;
+
+ case SampleFormat::S32:
+ info->bits = 32;
+ break;
+ }
+}
+
+inline bool
+RoarOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ const ScopeLock protect(mutex);
+
+ if (roar_simple_connect(&con,
+ host.empty() ? nullptr : host.c_str(),
+ name.c_str()) < 0) {
+ error.Set(roar_output_domain,
+ "Failed to connect to Roar server");
+ return false;
+ }
+
+ vss = roar_vs_new_from_con(&con, &err);
+
+ if (vss == nullptr || err != ROAR_ERROR_NONE) {
+ error.Set(roar_output_domain, "Failed to connect to server");
+ return false;
+ }
+
+ roar_use_audio_format(&info, audio_format);
+
+ if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+ error.Set(roar_output_domain, "Failed to start stream");
+ return false;
+ }
+
+ roar_vs_role(vss, role, &err);
+ alive = true;
+ return true;
+}
+
+static bool
+roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ return self->Open(audio_format, error);
+}
+
+inline void
+RoarOutput::Close()
+{
+ const ScopeLock protect(mutex);
+
+ alive = false;
+
+ if (vss != nullptr)
+ roar_vs_close(vss, ROAR_VS_TRUE, &err);
+ vss = nullptr;
+ roar_disconnect(&con);
+}
+
+static void
+roar_close(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ self->Close();
+}
+
+inline void
+RoarOutput::Cancel()
+{
+ const ScopeLock protect(mutex);
+
+ if (vss == nullptr)
+ return;
+
+ roar_vs_t *_vss = vss;
+ vss = nullptr;
+ roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+ alive = false;
+
+ _vss = roar_vs_new_from_con(&con, &err);
+ if (_vss == nullptr)
+ return;
+
+ if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+ roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+ LogError(roar_output_domain, "Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(_vss, role, &err);
+ vss = _vss;
+ alive = true;
+}
+
+static void
+roar_cancel(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ self->Cancel();
+}
+
+inline size_t
+RoarOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ if (vss == nullptr) {
+ error.Set(roar_output_domain, "Connection is invalid");
+ return 0;
+ }
+
+ ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
+ if (nbytes <= 0) {
+ error.Set(roar_output_domain, "Failed to play data");
+ return 0;
+ }
+
+ return nbytes;
+}
+
+static size_t
+roar_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ return self->Play(chunk, size, error);
+}
+
+static const char*
+roar_tag_convert(TagType type, bool *is_uuid)
+{
+ *is_uuid = false;
+ switch (type)
+ {
+ case TAG_ARTIST:
+ case TAG_ALBUM_ARTIST:
+ return "AUTHOR";
+ case TAG_ALBUM:
+ return "ALBUM";
+ case TAG_TITLE:
+ return "TITLE";
+ case TAG_TRACK:
+ return "TRACK";
+ case TAG_NAME:
+ return "NAME";
+ case TAG_GENRE:
+ return "GENRE";
+ case TAG_DATE:
+ return "DATE";
+ case TAG_PERFORMER:
+ return "PERFORMER";
+ case TAG_COMMENT:
+ return "COMMENT";
+ case TAG_DISC:
+ return "DISCID";
+ case TAG_COMPOSER:
+#ifdef ROAR_META_TYPE_COMPOSER
+ return "COMPOSER";
+#else
+ return "AUTHOR";
+#endif
+ case TAG_MUSICBRAINZ_ARTISTID:
+ case TAG_MUSICBRAINZ_ALBUMID:
+ case TAG_MUSICBRAINZ_ALBUMARTISTID:
+ case TAG_MUSICBRAINZ_TRACKID:
+ *is_uuid = true;
+ return "HASH";
+ case TAG_MUSICBRAINZ_RELEASETRACKID:
+ *is_uuid = true;
+ return "HASH";
+
+ default:
+ return nullptr;
+ }
+}
+
+inline void
+RoarOutput::SendTag(const Tag &tag)
+{
+ if (vss == nullptr)
+ return;
+
+ const ScopeLock protect(mutex);
+
+ size_t cnt = 0;
+ struct roar_keyval vals[32];
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ 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;
+ }
+
+ for (const auto &item : tag) {
+ if (cnt >= 32)
+ break;
+
+ bool is_uuid = false;
+ 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",
+ item.value);
+ vals[cnt].value = uuid_buf[cnt];
+ } else {
+ vals[cnt].value = const_cast<char *>(item.value);
+ }
+
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(vss, vals, cnt, &(err));
+}
+
+static void
+roar_send_tag(AudioOutput *ao, const Tag *meta)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ self->SendTag(*meta);
+}
+
+const struct AudioOutputPlugin roar_output_plugin = {
+ "roar",
+ nullptr,
+ roar_init,
+ roar_finish,
+ nullptr,
+ nullptr,
+ roar_open,
+ roar_close,
+ nullptr,
+ roar_send_tag,
+ roar_play,
+ nullptr,
+ roar_cancel,
+ nullptr,
+ &roar_mixer_plugin,
+};
diff --git a/src/output/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx
new file mode 100644
index 000000000..5f5a9246e
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+class RoarOutput;
+
+extern const struct AudioOutputPlugin roar_output_plugin;
+
+int
+roar_output_get_volume(RoarOutput &roar);
+
+bool
+roar_output_set_volume(RoarOutput &roar, unsigned volume);
+
+#endif
diff --git a/src/output/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
new file mode 100644
index 000000000..0341e1cf7
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ShoutOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+#include <shout/shout.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
+
+struct ShoutOutput final {
+ AudioOutput base;
+
+ shout_t *shout_conn;
+ shout_metadata_t *shout_meta;
+
+ Encoder *encoder;
+
+ float quality;
+ int bitrate;
+
+ int timeout;
+
+ uint8_t buffer[32768];
+
+ ShoutOutput()
+ :base(shout_output_plugin),
+ shout_conn(shout_new()),
+ shout_meta(shout_metadata_new()),
+ quality(-2.0),
+ bitrate(-1),
+ timeout(DEFAULT_CONN_TIMEOUT) {}
+
+ ~ShoutOutput() {
+ if (shout_meta != nullptr)
+ shout_metadata_free(shout_meta);
+ if (shout_conn != nullptr)
+ shout_free(shout_conn);
+ }
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static int shout_init_count;
+
+static constexpr Domain shout_output_domain("shout_output");
+
+static const EncoderPlugin *
+shout_encoder_plugin_get(const char *name)
+{
+ if (strcmp(name, "ogg") == 0)
+ name = "vorbis";
+ else if (strcmp(name, "mp3") == 0)
+ name = "lame";
+
+ return encoder_plugin_get(name);
+}
+
+gcc_pure
+static const char *
+require_block_string(const config_param &param, const char *name)
+{
+ const char *value = param.GetBlockValue(name);
+ if (value == nullptr)
+ FormatFatalError("no \"%s\" defined for shout device defined "
+ "at line %u\n", name, param.line);
+
+ return value;
+}
+
+inline bool
+ShoutOutput::Configure(const config_param &param, Error &error)
+{
+
+ const AudioFormat audio_format = base.config_audio_format;
+ if (!audio_format.IsFullyDefined()) {
+ error.Set(config_domain,
+ "Need full audio format specification");
+ return nullptr;
+ }
+
+ const char *host = require_block_string(param, "host");
+ const char *mount = require_block_string(param, "mount");
+ unsigned port = param.GetBlockValue("port", 0u);
+ if (port == 0) {
+ error.Set(config_domain, "shout port must be configured");
+ return false;
+ }
+
+ const char *passwd = require_block_string(param, "password");
+ const char *name = require_block_string(param, "name");
+
+ bool is_public = param.GetBlockValue("public", false);
+
+ const char *user = param.GetBlockValue("user", "source");
+
+ const char *value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ char *test;
+ quality = strtod(value, &test);
+
+ if (*test != '\0' || quality < -1.0 || quality > 10.0) {
+ error.Format(config_domain,
+ "shout quality \"%s\" is not a number in the "
+ "range -1 to 10",
+ value);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ error.Set(config_domain,
+ "quality and bitrate are "
+ "both defined");
+ return false;
+ }
+ } else {
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ error.Set(config_domain,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ char *test;
+ bitrate = strtol(value, &test, 10);
+
+ if (*test != '\0' || bitrate <= 0) {
+ error.Set(config_domain,
+ "bitrate must be a positive integer");
+ return false;
+ }
+ }
+
+ const char *encoding = param.GetBlockValue("encoding", "ogg");
+ const auto encoder_plugin = shout_encoder_plugin_get(encoding);
+ if (encoder_plugin == nullptr) {
+ error.Format(config_domain,
+ "couldn't find shout encoder plugin \"%s\"",
+ encoding);
+ return false;
+ }
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ if (encoder == nullptr)
+ return false;
+
+ unsigned shout_format;
+ if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
+ shout_format = SHOUT_FORMAT_MP3;
+ else
+ shout_format = SHOUT_FORMAT_OGG;
+
+ unsigned protocol;
+ value = param.GetBlockValue("protocol");
+ if (value != nullptr) {
+ if (0 == strcmp(value, "shoutcast") &&
+ 0 != strcmp(encoding, "mp3")) {
+ error.Format(config_domain,
+ "you cannot stream \"%s\" to shoutcast, use mp3",
+ encoding);
+ return false;
+ } else if (0 == strcmp(value, "shoutcast"))
+ protocol = SHOUT_PROTOCOL_ICY;
+ else if (0 == strcmp(value, "icecast1"))
+ protocol = SHOUT_PROTOCOL_XAUDIOCAST;
+ else if (0 == strcmp(value, "icecast2"))
+ protocol = SHOUT_PROTOCOL_HTTP;
+ else {
+ error.Format(config_domain,
+ "shout protocol \"%s\" is not \"shoutcast\" or "
+ "\"icecast1\"or \"icecast2\"",
+ value);
+ return false;
+ }
+ } else {
+ protocol = SHOUT_PROTOCOL_HTTP;
+ }
+
+ if (shout_set_host(shout_conn, host) != SHOUTERR_SUCCESS ||
+ shout_set_port(shout_conn, port) != SHOUTERR_SUCCESS ||
+ shout_set_password(shout_conn, passwd) != SHOUTERR_SUCCESS ||
+ shout_set_mount(shout_conn, mount) != SHOUTERR_SUCCESS ||
+ shout_set_name(shout_conn, name) != SHOUTERR_SUCCESS ||
+ shout_set_user(shout_conn, user) != SHOUTERR_SUCCESS ||
+ shout_set_public(shout_conn, is_public) != SHOUTERR_SUCCESS ||
+ shout_set_format(shout_conn, shout_format)
+ != SHOUTERR_SUCCESS ||
+ shout_set_protocol(shout_conn, protocol) != SHOUTERR_SUCCESS ||
+ shout_set_agent(shout_conn, "MPD") != SHOUTERR_SUCCESS) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ /* optional paramters */
+ timeout = param.GetBlockValue("timeout", DEFAULT_CONN_TIMEOUT);
+
+ value = param.GetBlockValue("genre");
+ if (value != nullptr && shout_set_genre(shout_conn, value)) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("description");
+ if (value != nullptr && shout_set_description(shout_conn, value)) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("url");
+ if (value != nullptr && shout_set_url(shout_conn, value)) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ {
+ char temp[11];
+ memset(temp, 0, sizeof(temp));
+
+ snprintf(temp, sizeof(temp), "%u", audio_format.channels);
+ shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
+
+ snprintf(temp, sizeof(temp), "%u", audio_format.sample_rate);
+
+ shout_set_audio_info(shout_conn, SHOUT_AI_SAMPLERATE, temp);
+
+ if (quality >= -1.0) {
+ snprintf(temp, sizeof(temp), "%2.2f", quality);
+ shout_set_audio_info(shout_conn, SHOUT_AI_QUALITY,
+ temp);
+ } else {
+ snprintf(temp, sizeof(temp), "%d", bitrate);
+ shout_set_audio_info(shout_conn, SHOUT_AI_BITRATE,
+ temp);
+ }
+ }
+
+ return true;
+}
+
+static AudioOutput *
+my_shout_init_driver(const config_param &param, Error &error)
+{
+ ShoutOutput *sd = new ShoutOutput();
+ if (!sd->Initialize(param, error)) {
+ delete sd;
+ return nullptr;
+ }
+
+ if (!sd->Configure(param, error)) {
+ delete sd;
+ return nullptr;
+ }
+
+ if (shout_init_count == 0)
+ shout_init();
+
+ shout_init_count++;
+
+ return &sd->base;
+}
+
+static bool
+handle_shout_error(ShoutOutput *sd, int err, Error &error)
+{
+ switch (err) {
+ case SHOUTERR_SUCCESS:
+ break;
+
+ case SHOUTERR_UNCONNECTED:
+ case SHOUTERR_SOCKET:
+ error.Format(shout_output_domain, err,
+ "Lost shout connection to %s:%i: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+
+ default:
+ error.Format(shout_output_domain, err,
+ "connection to %s:%i error: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+write_page(ShoutOutput *sd, Error &error)
+{
+ assert(sd->encoder != nullptr);
+
+ while (true) {
+ size_t nbytes = encoder_read(sd->encoder,
+ sd->buffer, sizeof(sd->buffer));
+ if (nbytes == 0)
+ return true;
+
+ int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
+ if (!handle_shout_error(sd, err, error))
+ return false;
+ }
+
+ return true;
+}
+
+static void close_shout_conn(ShoutOutput * sd)
+{
+ if (sd->encoder != nullptr) {
+ if (encoder_end(sd->encoder, IgnoreError()))
+ write_page(sd, IgnoreError());
+
+ encoder_close(sd->encoder);
+ }
+
+ if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
+ shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
+ FormatWarning(shout_output_domain,
+ "problem closing connection to shout server: %s",
+ shout_get_error(sd->shout_conn));
+ }
+}
+
+static void
+my_shout_finish_driver(AudioOutput *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ encoder_finish(sd->encoder);
+
+ delete sd;
+
+ shout_init_count--;
+
+ if (shout_init_count == 0)
+ shout_shutdown();
+}
+
+static void
+my_shout_drop_buffered_audio(AudioOutput *ao)
+{
+ gcc_unused
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ /* needs to be implemented for shout */
+}
+
+static void
+my_shout_close_device(AudioOutput *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ close_shout_conn(sd);
+}
+
+static bool
+shout_connect(ShoutOutput *sd, Error &error)
+{
+ switch (shout_open(sd->shout_conn)) {
+ case SHOUTERR_SUCCESS:
+ case SHOUTERR_CONNECTED:
+ return true;
+
+ default:
+ error.Format(shout_output_domain,
+ "problem opening connection to shout server %s:%i: %s",
+ shout_get_host(sd->shout_conn),
+ shout_get_port(sd->shout_conn),
+ shout_get_error(sd->shout_conn));
+ return false;
+ }
+}
+
+static bool
+my_shout_open_device(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ if (!shout_connect(sd, error))
+ return false;
+
+ if (!encoder_open(sd->encoder, audio_format, error)) {
+ shout_close(sd->shout_conn);
+ return false;
+ }
+
+ if (!write_page(sd, error)) {
+ encoder_close(sd->encoder);
+ shout_close(sd->shout_conn);
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned
+my_shout_delay(AudioOutput *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ int delay = shout_delay(sd->shout_conn);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
+static size_t
+my_shout_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ return encoder_write(sd->encoder, chunk, size, error) &&
+ write_page(sd, error)
+ ? size
+ : 0;
+}
+
+static bool
+my_shout_pause(AudioOutput *ao)
+{
+ static char silence[1020];
+
+ return my_shout_play(ao, silence, sizeof(silence), IgnoreError());
+}
+
+static void
+shout_tag_to_metadata(const Tag *tag, char *dest, size_t size)
+{
+ char artist[size];
+ char title[size];
+
+ artist[0] = 0;
+ title[0] = 0;
+
+ for (const auto &item : *tag) {
+ switch (item.type) {
+ case TAG_ARTIST:
+ strncpy(artist, item.value, size);
+ break;
+ case TAG_TITLE:
+ strncpy(title, item.value, size);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ snprintf(dest, size, "%s - %s", artist, title);
+}
+
+static void my_shout_set_tag(AudioOutput *ao,
+ const Tag *tag)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ if (sd->encoder->plugin.tag != nullptr) {
+ /* encoder plugin supports stream tags */
+
+ Error error;
+ if (!encoder_pre_tag(sd->encoder, error) ||
+ !write_page(sd, error) ||
+ !encoder_tag(sd->encoder, tag, error)) {
+ LogError(error);
+ return;
+ }
+ } else {
+ /* no stream tag support: fall back to icy-metadata */
+ char song[1024];
+ shout_tag_to_metadata(tag, song, sizeof(song));
+
+ shout_metadata_add(sd->shout_meta, "song", song);
+ if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
+ sd->shout_meta)) {
+ LogWarning(shout_output_domain,
+ "error setting shout metadata");
+ }
+ }
+
+ write_page(sd, IgnoreError());
+}
+
+const struct AudioOutputPlugin shout_output_plugin = {
+ "shout",
+ nullptr,
+ my_shout_init_driver,
+ my_shout_finish_driver,
+ nullptr,
+ nullptr,
+ my_shout_open_device,
+ my_shout_close_device,
+ my_shout_delay,
+ my_shout_set_tag,
+ my_shout_play,
+ nullptr,
+ my_shout_drop_buffered_audio,
+ my_shout_pause,
+ nullptr,
+};
diff --git a/src/output/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx
new file mode 100644
index 000000000..9f706fc3b
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX
+#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin shout_output_plugin;
+
+#endif
diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
new file mode 100644
index 000000000..30745f97c
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SolarisOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#include <sys/stropts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef __sun
+#include <sys/audio.h>
+#else
+
+/* some fake declarations that allow build this plugin on systems
+ other than Solaris, just to see if it compiles */
+
+#define AUDIO_GETINFO 0
+#define AUDIO_SETINFO 0
+#define AUDIO_ENCODING_LINEAR 0
+
+struct audio_info {
+ struct {
+ unsigned sample_rate, channels, precision, encoding;
+ } play;
+};
+
+#endif
+
+struct SolarisOutput {
+ AudioOutput base;
+
+ /* configuration */
+ const char *device;
+
+ int fd;
+
+ SolarisOutput()
+ :base(solaris_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static bool
+solaris_output_test_default_device(void)
+{
+ struct stat st;
+
+ return stat("/dev/audio", &st) == 0 && S_ISCHR(st.st_mode) &&
+ access("/dev/audio", W_OK) == 0;
+}
+
+static AudioOutput *
+solaris_output_init(const config_param &param, Error &error_r)
+{
+ SolarisOutput *so = new SolarisOutput();
+ if (!so->Initialize(param, error_r)) {
+ delete so;
+ return nullptr;
+ }
+
+ so->device = param.GetBlockValue("device", "/dev/audio");
+
+ return &so->base;
+}
+
+static void
+solaris_output_finish(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ delete so;
+}
+
+static bool
+solaris_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+ struct audio_info info;
+ int ret, flags;
+
+ /* support only 16 bit mono/stereo for now; nothing else has
+ been tested */
+ audio_format.format = SampleFormat::S16;
+
+ /* open the device in non-blocking mode */
+
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
+ if (so->fd < 0) {
+ error.FormatErrno("Failed to open %s",
+ so->device);
+ return false;
+ }
+
+ /* restore blocking mode */
+
+ flags = fcntl(so->fd, F_GETFL);
+ if (flags > 0 && (flags & O_NONBLOCK) != 0)
+ fcntl(so->fd, F_SETFL, flags & ~O_NONBLOCK);
+
+ /* configure the audio device */
+
+ ret = ioctl(so->fd, AUDIO_GETINFO, &info);
+ if (ret < 0) {
+ error.SetErrno("AUDIO_GETINFO failed");
+ close(so->fd);
+ return false;
+ }
+
+ info.play.sample_rate = audio_format.sample_rate;
+ info.play.channels = audio_format.channels;
+ info.play.precision = 16;
+ info.play.encoding = AUDIO_ENCODING_LINEAR;
+
+ ret = ioctl(so->fd, AUDIO_SETINFO, &info);
+ if (ret < 0) {
+ error.SetErrno("AUDIO_SETINFO failed");
+ close(so->fd);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+solaris_output_close(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ close(so->fd);
+}
+
+static size_t
+solaris_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+ ssize_t nbytes;
+
+ nbytes = write(so->fd, chunk, size);
+ if (nbytes <= 0) {
+ error.SetErrno("Write failed");
+ return 0;
+ }
+
+ return nbytes;
+}
+
+static void
+solaris_output_cancel(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ ioctl(so->fd, I_FLUSH);
+}
+
+const struct AudioOutputPlugin solaris_output_plugin = {
+ "solaris",
+ solaris_output_test_default_device,
+ solaris_output_init,
+ solaris_output_finish,
+ nullptr,
+ nullptr,
+ solaris_output_open,
+ solaris_output_close,
+ nullptr,
+ nullptr,
+ solaris_output_play,
+ nullptr,
+ solaris_output_cancel,
+ nullptr,
+ nullptr,
+};
diff --git a/src/output/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx
new file mode 100644
index 000000000..3f9ede7a6
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
new file mode 100644
index 000000000..e5c5a6f0c
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WinmmOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "mixer/MixerList.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct WinmmBuffer {
+ PcmBuffer buffer;
+
+ WAVEHDR hdr;
+};
+
+struct WinmmOutput {
+ AudioOutput base;
+
+ UINT device_id;
+ HWAVEOUT handle;
+
+ /**
+ * This event is triggered by Windows when a buffer is
+ * finished.
+ */
+ HANDLE event;
+
+ WinmmBuffer buffers[8];
+ unsigned next_buffer;
+
+ WinmmOutput()
+ :base(winmm_output_plugin) {}
+};
+
+static constexpr Domain winmm_output_domain("winmm_output");
+
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput &output)
+{
+ return output.handle;
+}
+
+static bool
+winmm_output_test_default_device(void)
+{
+ return waveOutGetNumDevs() > 0;
+}
+
+static bool
+get_device_id(const char *device_name, UINT *device_id, Error &error)
+{
+ /* if device is not specified use wave mapper */
+ if (device_name == nullptr) {
+ *device_id = WAVE_MAPPER;
+ return true;
+ }
+
+ UINT numdevs = waveOutGetNumDevs();
+
+ /* check for device id */
+ char *endptr;
+ UINT id = strtoul(device_name, &endptr, 0);
+ if (endptr > device_name && *endptr == 0) {
+ if (id >= numdevs)
+ goto fail;
+ *device_id = id;
+ return true;
+ }
+
+ /* check for device name */
+ for (UINT i = 0; i < numdevs; i++) {
+ WAVEOUTCAPS caps;
+ MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
+ if (result != MMSYSERR_NOERROR)
+ continue;
+ /* szPname is only 32 chars long, so it is often truncated.
+ Use partial match to work around this. */
+ if (strstr(device_name, caps.szPname) == device_name) {
+ *device_id = i;
+ return true;
+ }
+ }
+
+fail:
+ error.Format(winmm_output_domain,
+ "device \"%s\" is not found", device_name);
+ return false;
+}
+
+static AudioOutput *
+winmm_output_init(const config_param &param, Error &error)
+{
+ WinmmOutput *wo = new WinmmOutput();
+ 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)) {
+ delete wo;
+ return nullptr;
+ }
+
+ return &wo->base;
+}
+
+static void
+winmm_output_finish(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ delete wo;
+}
+
+static bool
+winmm_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ wo->event = CreateEvent(nullptr, false, false, nullptr);
+ if (wo->event == nullptr) {
+ error.Set(winmm_output_domain, "CreateEvent() failed");
+ return false;
+ }
+
+ switch (audio_format.format) {
+ case SampleFormat::S8:
+ case SampleFormat::S16:
+ break;
+
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ case SampleFormat::DSD:
+ case SampleFormat::UNDEFINED:
+ /* we havn't tested formats other than S16 */
+ audio_format.format = SampleFormat::S16;
+ break;
+ }
+
+ if (audio_format.channels > 2)
+ /* same here: more than stereo was not tested */
+ audio_format.channels = 2;
+
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = audio_format.channels;
+ format.nSamplesPerSec = audio_format.sample_rate;
+ format.nBlockAlign = audio_format.GetFrameSize();
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+ format.wBitsPerSample = audio_format.GetSampleSize() * 8;
+ format.cbSize = 0;
+
+ MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
+ (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
+ if (result != MMSYSERR_NOERROR) {
+ CloseHandle(wo->event);
+ error.Set(winmm_output_domain, "waveOutOpen() failed");
+ return false;
+ }
+
+ for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
+ memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
+ }
+
+ wo->next_buffer = 0;
+
+ return true;
+}
+
+static void
+winmm_output_close(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i)
+ wo->buffers[i].buffer.Clear();
+
+ waveOutClose(wo->handle);
+
+ CloseHandle(wo->event);
+}
+
+/**
+ * Copy data into a buffer, and prepare the wave header.
+ */
+static bool
+winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+ const void *data, size_t size,
+ Error &error)
+{
+ void *dest = buffer->buffer.Get(size);
+ assert(dest != nullptr);
+
+ memcpy(dest, data, size);
+
+ memset(&buffer->hdr, 0, sizeof(buffer->hdr));
+ buffer->hdr.lpData = (LPSTR)dest;
+ buffer->hdr.dwBufferLength = size;
+
+ MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ error.Set(winmm_output_domain, result,
+ "waveOutPrepareHeader() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+ Error &error)
+{
+ if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
+ /* already finished */
+ return true;
+
+ while (true) {
+ MMRESULT result = waveOutUnprepareHeader(wo->handle,
+ &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result == MMSYSERR_NOERROR)
+ return true;
+ else if (result != WAVERR_STILLPLAYING) {
+ error.Set(winmm_output_domain, result,
+ "waveOutUnprepareHeader() failed");
+ return false;
+ }
+
+ /* wait some more */
+ WaitForSingleObject(wo->event, INFINITE);
+ }
+}
+
+static size_t
+winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ /* get the next buffer from the ring and prepare it */
+ WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
+ if (!winmm_drain_buffer(wo, buffer, error) ||
+ !winmm_set_buffer(wo, buffer, chunk, size, error))
+ return 0;
+
+ /* enqueue the buffer */
+ MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ error.Set(winmm_output_domain, result,
+ "waveOutWrite() failed");
+ return 0;
+ }
+
+ /* mark our buffer as "used" */
+ wo->next_buffer = (wo->next_buffer + 1) %
+ ARRAY_SIZE(wo->buffers);
+
+ return size;
+}
+
+static bool
+winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
+{
+ for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+ return false;
+
+ for (unsigned i = 0; i < wo->next_buffer; ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+ return false;
+
+ return true;
+}
+
+static void
+winmm_stop(WinmmOutput *wo)
+{
+ waveOutReset(wo->handle);
+
+ for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
+ WinmmBuffer *buffer = &wo->buffers[i];
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ }
+}
+
+static void
+winmm_output_drain(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ if (!winmm_drain_all_buffers(wo, IgnoreError()))
+ winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ winmm_stop(wo);
+}
+
+const struct AudioOutputPlugin winmm_output_plugin = {
+ "winmm",
+ winmm_output_test_default_device,
+ winmm_output_init,
+ winmm_output_finish,
+ nullptr,
+ nullptr,
+ winmm_output_open,
+ winmm_output_close,
+ nullptr,
+ nullptr,
+ winmm_output_play,
+ winmm_output_drain,
+ winmm_output_cancel,
+ nullptr,
+ &winmm_mixer_plugin,
+};
diff --git a/src/output/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx
new file mode 100644
index 000000000..50fae4f2f
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX
+#define MPD_WINMM_OUTPUT_PLUGIN_HXX
+
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
+#include "Compiler.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+
+struct WinmmOutput;
+
+extern const struct AudioOutputPlugin winmm_output_plugin;
+
+gcc_pure
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput &output);
+
+#endif
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx
new file mode 100644
index 000000000..3797c3d26
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdClient.cxx
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdClient.hxx"
+#include "HttpdInternal.hxx"
+#include "util/ASCII.hxx"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/SocketError.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+HttpdClient::~HttpdClient()
+{
+ if (state == RESPONSE) {
+ if (current_page != nullptr)
+ current_page->Unref();
+
+ ClearQueue();
+ }
+
+ if (metadata)
+ metadata->Unref();
+
+ if (IsDefined())
+ BufferedSocket::Close();
+}
+
+void
+HttpdClient::Close()
+{
+ httpd.RemoveClient(*this);
+}
+
+void
+HttpdClient::LockClose()
+{
+ const ScopeLock protect(httpd.mutex);
+ Close();
+}
+
+void
+HttpdClient::BeginResponse()
+{
+ assert(state != RESPONSE);
+
+ state = RESPONSE;
+ current_page = nullptr;
+
+ if (!head_method)
+ httpd.SendHeader(*this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+ assert(state != RESPONSE);
+
+ if (state == REQUEST) {
+ if (memcmp(line, "HEAD /", 6) == 0) {
+ line += 6;
+ head_method = true;
+ } else if (memcmp(line, "GET /", 5) == 0) {
+ line += 5;
+ } else {
+ /* only GET is supported */
+ LogWarning(httpd_output_domain,
+ "malformed request line from client");
+ return false;
+ }
+
+ line = strchr(line, ' ');
+ if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
+ /* HTTP/0.9 without request headers */
+
+ if (head_method)
+ return false;
+
+ BeginResponse();
+ return true;
+ }
+
+ /* after the request line, request headers follow */
+ state = HEADERS;
+ return true;
+ } else {
+ if (*line == 0) {
+ /* empty line: request is finished */
+
+ BeginResponse();
+ return true;
+ }
+
+ if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
+ StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
+ /* Send icy metadata */
+ metadata_requested = metadata_supported;
+ return true;
+ }
+
+ if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
+ /* Send as dlna */
+ dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ metadata_supported = false;
+ metadata_requested = false;
+ return true;
+ }
+
+ /* expect more request headers */
+ return true;
+ }
+}
+
+/**
+ * Sends the status line and response headers to the client.
+ */
+bool
+HttpdClient::SendResponse()
+{
+ char buffer[1024], *allocated = nullptr;
+ const char *response;
+
+ assert(state == RESPONSE);
+
+ if (dlna_streaming_requested) {
+ snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 206 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Connection: close\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
+ "\r\n",
+ httpd.content_type);
+ response = buffer;
+
+ } else if (metadata_requested) {
+ response = allocated =
+ icy_server_metadata_header(httpd.name, httpd.genre,
+ httpd.website,
+ httpd.content_type,
+ metaint);
+ } else { /* revert to a normal HTTP request */
+ snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ httpd.content_type);
+ response = buffer;
+ }
+
+ ssize_t nbytes = SocketMonitor::Write(response, strlen(response));
+ delete[] allocated;
+ if (gcc_unlikely(nbytes < 0)) {
+ const SocketErrorMessage msg;
+ FormatWarning(httpd_output_domain,
+ "failed to write to client: %s",
+ (const char *)msg);
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
+ bool _metadata_supported)
+ :BufferedSocket(_fd, _loop),
+ httpd(_httpd),
+ state(REQUEST),
+ queue_size(0),
+ head_method(false),
+ dlna_streaming_requested(false),
+ metadata_supported(_metadata_supported),
+ metadata_requested(false), metadata_sent(true),
+ metaint(8192), /*TODO: just a std value */
+ metadata(nullptr),
+ metadata_current_position(0), metadata_fill(0)
+{
+}
+
+void
+HttpdClient::ClearQueue()
+{
+ assert(state == RESPONSE);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+#ifndef NDEBUG
+ assert(queue_size >= page->size);
+ queue_size -= page->size;
+#endif
+
+ page->Unref();
+ }
+
+ assert(queue_size == 0);
+}
+
+void
+HttpdClient::CancelQueue()
+{
+ if (state != RESPONSE)
+ return;
+
+ ClearQueue();
+
+ if (current_page == nullptr)
+ CancelWrite();
+}
+
+ssize_t
+HttpdClient::TryWritePage(const Page &page, size_t position)
+{
+ assert(position < page.size);
+
+ return Write(page.data + position, page.size - position);
+}
+
+ssize_t
+HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
+{
+ return n >= 0
+ ? Write(page.data + position, n)
+ : TryWritePage(page, position);
+}
+
+ssize_t
+HttpdClient::GetBytesTillMetaData() const
+{
+ if (metadata_requested &&
+ current_page->size - current_position > metaint - metadata_fill)
+ return metaint - metadata_fill;
+
+ return -1;
+}
+
+inline bool
+HttpdClient::TryWrite()
+{
+ const ScopeLock protect(httpd.mutex);
+
+ assert(state == RESPONSE);
+
+ if (current_page == nullptr) {
+ if (pages.empty()) {
+ /* another thread has removed the event source
+ while this thread was waiting for
+ httpd.mutex */
+ CancelWrite();
+ return true;
+ }
+
+ current_page = pages.front();
+ pages.pop();
+ current_position = 0;
+
+ assert(queue_size >= current_page->size);
+ queue_size -= current_page->size;
+ }
+
+ const ssize_t bytes_to_write = GetBytesTillMetaData();
+ if (bytes_to_write == 0) {
+ if (!metadata_sent) {
+ ssize_t nbytes = TryWritePage(*metadata,
+ metadata_current_position);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ FormatWarning(httpd_output_domain,
+ "failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ metadata_current_position += nbytes;
+
+ if (metadata->size - metadata_current_position == 0) {
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ metadata_sent = true;
+ }
+ } else {
+ char empty_data = 0;
+
+ ssize_t nbytes = Write(&empty_data, 1);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ FormatWarning(httpd_output_domain,
+ "failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ }
+ } else {
+ ssize_t nbytes =
+ TryWritePageN(*current_page, current_position,
+ bytes_to_write);
+ if (nbytes < 0) {
+ auto e = GetSocketError();
+ if (IsSocketErrorAgain(e))
+ return true;
+
+ if (!IsSocketErrorClosed(e)) {
+ SocketErrorMessage msg(e);
+ FormatWarning(httpd_output_domain,
+ "failed to write to client: %s",
+ (const char *)msg);
+ }
+
+ Close();
+ return false;
+ }
+
+ current_position += nbytes;
+ assert(current_position <= current_page->size);
+
+ if (metadata_requested)
+ metadata_fill += nbytes;
+
+ if (current_position >= current_page->size) {
+ current_page->Unref();
+ current_page = nullptr;
+
+ if (pages.empty())
+ /* all pages are sent: remove the
+ event source */
+ CancelWrite();
+ }
+ }
+
+ return true;
+}
+
+void
+HttpdClient::PushPage(Page *page)
+{
+ if (state != RESPONSE)
+ /* the client is still writing the HTTP request */
+ return;
+
+ if (queue_size > 256 * 1024) {
+ FormatDebug(httpd_output_domain,
+ "client is too slow, flushing its queue");
+ ClearQueue();
+ }
+
+ page->Ref();
+ pages.push(page);
+ queue_size += page->size;
+
+ ScheduleWrite();
+}
+
+void
+HttpdClient::PushMetaData(Page *page)
+{
+ assert(page != nullptr);
+
+ if (metadata) {
+ metadata->Unref();
+ metadata = nullptr;
+ }
+
+ page->Ref();
+ metadata = page;
+ metadata_sent = false;
+}
+
+bool
+HttpdClient::OnSocketReady(unsigned flags)
+{
+ if (!BufferedSocket::OnSocketReady(flags))
+ return false;
+
+ if (flags & WRITE)
+ if (!TryWrite())
+ return false;
+
+ return true;
+}
+
+BufferedSocket::InputResult
+HttpdClient::OnSocketInput(void *data, size_t length)
+{
+ if (state == RESPONSE) {
+ LogWarning(httpd_output_domain,
+ "unexpected input from client");
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ char *line = (char *)data;
+ char *newline = (char *)memchr(line, '\n', length);
+ if (newline == nullptr)
+ return InputResult::MORE;
+
+ ConsumeInput(newline + 1 - line);
+
+ if (newline > line && newline[-1] == '\r')
+ --newline;
+
+ /* terminate the string at the end of the line */
+ *newline = 0;
+
+ if (!HandleLine(line)) {
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ if (state == RESPONSE) {
+ if (!SendResponse())
+ return InputResult::CLOSED;
+
+ if (head_method) {
+ LockClose();
+ return InputResult::CLOSED;
+ }
+ }
+
+ return InputResult::AGAIN;
+}
+
+void
+HttpdClient::OnSocketError(Error &&error)
+{
+ LogError(error);
+}
+
+void
+HttpdClient::OnSocketClosed()
+{
+ LockClose();
+}
diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx
new file mode 100644
index 000000000..f94f05769
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdClient.hxx
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
+#define MPD_OUTPUT_HTTPD_CLIENT_HXX
+
+#include "event/BufferedSocket.hxx"
+#include "Compiler.h"
+
+#include <queue>
+#include <list>
+
+#include <stddef.h>
+
+class HttpdOutput;
+class Page;
+
+class HttpdClient final : BufferedSocket {
+ /**
+ * The httpd output object this client is connected to.
+ */
+ HttpdOutput &httpd;
+
+ /**
+ * The current state of the client.
+ */
+ enum {
+ /** reading the request line */
+ REQUEST,
+
+ /** reading the request headers */
+ HEADERS,
+
+ /** sending the HTTP response */
+ RESPONSE,
+ } state;
+
+ /**
+ * A queue of #Page objects to be sent to the client.
+ */
+ std::queue<Page *, std::list<Page *>> pages;
+
+ /**
+ * The sum of all page sizes in #pages.
+ */
+ size_t queue_size;
+
+ /**
+ * The #page which is currently being sent to the client.
+ */
+ Page *current_page;
+
+ /**
+ * The amount of bytes which were already sent from
+ * #current_page.
+ */
+ size_t current_position;
+
+ /**
+ * Is this a HEAD request?
+ */
+ bool head_method;
+
+ /**
+ * If DLNA streaming was an option.
+ */
+ bool dlna_streaming_requested;
+
+ /* ICY */
+
+ /**
+ * Do we support sending Icy-Metadata to the client? This is
+ * disabled if the httpd audio output uses encoder tags.
+ */
+ bool metadata_supported;
+
+ /**
+ * If we should sent icy metadata.
+ */
+ bool metadata_requested;
+
+ /**
+ * If the current metadata was already sent to the client.
+ */
+ bool metadata_sent;
+
+ /**
+ * The amount of streaming data between each metadata block
+ */
+ unsigned metaint;
+
+ /**
+ * The metadata as #Page which is currently being sent to the client.
+ */
+ Page *metadata;
+
+ /*
+ * The amount of bytes which were already sent from the metadata.
+ */
+ size_t metadata_current_position;
+
+ /**
+ * The amount of streaming data sent to the client
+ * since the last icy information was sent.
+ */
+ unsigned metadata_fill;
+
+public:
+ /**
+ * @param httpd the HTTP output device
+ * @param fd the socket file descriptor
+ */
+ HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop,
+ bool _metadata_supported);
+
+ /**
+ * Note: this does not remove the client from the
+ * #HttpdOutput object.
+ */
+ ~HttpdClient();
+
+ /**
+ * Frees the client and removes it from the server's client list.
+ */
+ void Close();
+
+ void LockClose();
+
+ /**
+ * Clears the page queue.
+ */
+ void CancelQueue();
+
+ /**
+ * Handle a line of the HTTP request.
+ */
+ bool HandleLine(const char *line);
+
+ /**
+ * Switch the client to the "RESPONSE" state.
+ */
+ void BeginResponse();
+
+ /**
+ * Sends the status line and response headers to the client.
+ */
+ bool SendResponse();
+
+ gcc_pure
+ ssize_t GetBytesTillMetaData() const;
+
+ ssize_t TryWritePage(const Page &page, size_t position);
+ ssize_t TryWritePageN(const Page &page, size_t position, ssize_t n);
+
+ bool TryWrite();
+
+ /**
+ * Appends a page to the client's queue.
+ */
+ void PushPage(Page *page);
+
+ /**
+ * Sends the passed metadata.
+ */
+ void PushMetaData(Page *page);
+
+private:
+ void ClearQueue();
+
+protected:
+ virtual bool OnSocketReady(unsigned flags) override;
+ virtual InputResult OnSocketInput(void *data, size_t length) override;
+ virtual void OnSocketError(Error &&error) override;
+ virtual void OnSocketClosed() override;
+};
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
new file mode 100644
index 000000000..20ff15e42
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Internal declarations for the "httpd" audio output plugin.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
+#define MPD_OUTPUT_HTTPD_INTERNAL_H
+
+#include "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++ */
+#include "HttpdClient.hxx"
+#endif
+
+#include <forward_list>
+#include <queue>
+#include <list>
+
+struct config_param;
+class Error;
+class EventLoop;
+class ServerSocket;
+class HttpdClient;
+class Page;
+struct Encoder;
+struct Tag;
+
+class HttpdOutput final : ServerSocket, DeferredMonitor {
+ AudioOutput base;
+
+ /**
+ * True if the audio output is open and accepts client
+ * connections.
+ */
+ bool open;
+
+ /**
+ * The configured encoder plugin.
+ */
+ Encoder *encoder;
+
+ /**
+ * Number of bytes which were fed into the encoder, without
+ * ever receiving new output. This is used to estimate
+ * whether MPD should manually flush the encoder, to avoid
+ * buffer underruns in the client.
+ */
+ size_t unflushed_input;
+
+public:
+ /**
+ * The MIME type produced by the #encoder.
+ */
+ const char *content_type;
+
+ /**
+ * This mutex protects the listener socket and the client
+ * list.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * This condition gets signalled when an item is removed from
+ * #pages.
+ */
+ Cond cond;
+
+private:
+ /**
+ * A #Timer object to synchronize this output with the
+ * wallclock.
+ */
+ Timer *timer;
+
+ /**
+ * The header page, which is sent to every client on connect.
+ */
+ Page *header;
+
+ /**
+ * The metadata, which is sent to every client.
+ */
+ Page *metadata;
+
+ /**
+ * The page queue, i.e. pages from the encoder to be
+ * broadcasted to all clients. This container is necessary to
+ * pass pages from the OutputThread to the IOThread. It is
+ * protected by #mutex, and removing signals #cond.
+ */
+ std::queue<Page *, std::list<Page *>> pages;
+
+ public:
+ /**
+ * The configured name.
+ */
+ char const *name;
+ /**
+ * The configured genre.
+ */
+ char const *genre;
+ /**
+ * The configured website address.
+ */
+ char const *website;
+
+private:
+ /**
+ * A linked list containing all clients which are currently
+ * connected.
+ */
+ std::forward_list<HttpdClient> clients;
+
+ /**
+ * A temporary buffer for the httpd_output_read_page()
+ * function.
+ */
+ char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ unsigned clients_max, clients_cnt;
+
+public:
+ HttpdOutput(EventLoop &_loop);
+ ~HttpdOutput();
+
+#if 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();
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool OpenEncoder(AudioFormat &audio_format, Error &error);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool Open(AudioFormat &audio_format, Error &error);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ void Close();
+
+ /**
+ * Check whether there is at least one client.
+ *
+ * Caller must lock the mutex.
+ */
+ gcc_pure
+ bool HasClients() const {
+ return !clients.empty();
+ }
+
+ /**
+ * Check whether there is at least one client.
+ */
+ gcc_pure
+ bool LockHasClients() const {
+ const ScopeLock protect(mutex);
+ return HasClients();
+ }
+
+ void AddClient(int fd);
+
+ /**
+ * Removes a client from the httpd_output.clients linked list.
+ */
+ void RemoveClient(HttpdClient &client);
+
+ /**
+ * Sends the encoder header to the client. This is called
+ * right after the response headers have been sent.
+ */
+ void SendHeader(HttpdClient &client) const;
+
+ gcc_pure
+ unsigned Delay() const;
+
+ /**
+ * Reads data from the encoder (as much as available) and
+ * returns it as a new #page object.
+ */
+ Page *ReadPage();
+
+ /**
+ * Broadcasts a page struct to all clients.
+ *
+ * Mutext must not be locked.
+ */
+ void BroadcastPage(Page *page);
+
+ /**
+ * Broadcasts data from the encoder to all clients.
+ */
+ void BroadcastFromEncoder();
+
+ bool EncodeAndPlay(const void *chunk, size_t size, Error &error);
+
+ void SendTag(const Tag *tag);
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void CancelAllClients();
+
+private:
+ virtual void RunDeferred() override;
+
+ virtual void OnAccept(int fd, const sockaddr &address,
+ size_t address_length, int uid) override;
+};
+
+extern const class Domain httpd_output_domain;
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
new file mode 100644
index 000000000..e3ba7727d
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdOutputPlugin.hxx"
+#include "HttpdInternal.hxx"
+#include "HttpdClient.hxx"
+#include "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 "IOThread.hxx"
+#include "event/Call.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_LIBWRAP
+#include <sys/socket.h> /* needed for AF_UNIX */
+#include <tcpd.h>
+#endif
+
+const Domain httpd_output_domain("httpd_output");
+
+inline
+HttpdOutput::HttpdOutput(EventLoop &_loop)
+ :ServerSocket(_loop), DeferredMonitor(_loop),
+ base(httpd_output_plugin),
+ encoder(nullptr), unflushed_input(0),
+ metadata(nullptr)
+{
+}
+
+HttpdOutput::~HttpdOutput()
+{
+ if (metadata != nullptr)
+ metadata->Unref();
+
+ if (encoder != nullptr)
+ encoder_finish(encoder);
+
+}
+
+inline bool
+HttpdOutput::Bind(Error &error)
+{
+ open = false;
+
+ bool result = false;
+ BlockingCall(GetEventLoop(), [this, &error, &result](){
+ result = ServerSocket::Open(error);
+ });
+ return result;
+}
+
+inline void
+HttpdOutput::Unbind()
+{
+ assert(!open);
+
+ BlockingCall(GetEventLoop(), [this](){
+ ServerSocket::Close();
+ });
+}
+
+inline bool
+HttpdOutput::Configure(const config_param &param, Error &error)
+{
+ /* read configuration */
+ name = param.GetBlockValue("name", "Set name in config");
+ genre = param.GetBlockValue("genre", "Set genre in config");
+ website = param.GetBlockValue("website", "Set website in config");
+
+ unsigned port = param.GetBlockValue("port", 8000u);
+
+ const char *encoder_name =
+ param.GetBlockValue("encoder", "vorbis");
+ const auto encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == nullptr) {
+ error.Format(httpd_output_domain,
+ "No such encoder: %s", encoder_name);
+ return false;
+ }
+
+ clients_max = param.GetBlockValue("max_clients", 0u);
+
+ /* set up bind_to_address */
+
+ const char *bind_to_address = param.GetBlockValue("bind_to_address");
+ bool success = bind_to_address != nullptr &&
+ strcmp(bind_to_address, "any") != 0
+ ? AddHost(bind_to_address, port, error)
+ : AddPort(port, error);
+ if (!success)
+ return false;
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ if (encoder == nullptr)
+ return false;
+
+ /* determine content type */
+ content_type = encoder_get_mime_type(encoder);
+ if (content_type == nullptr)
+ content_type = "application/octet-stream";
+
+ return true;
+}
+
+inline bool
+HttpdOutput::Init(const config_param &param, Error &error)
+{
+ return base.Configure(param, error);
+}
+
+static AudioOutput *
+httpd_output_init(const config_param &param, Error &error)
+{
+ HttpdOutput *httpd = new HttpdOutput(io_thread_get());
+
+ AudioOutput *result = httpd->InitAndConfigure(param, error);
+ if (result == nullptr)
+ delete httpd;
+
+ return result;
+}
+
+static void
+httpd_output_finish(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ delete httpd;
+}
+
+/**
+ * Creates a new #HttpdClient object and adds it into the
+ * HttpdOutput.clients linked list.
+ */
+inline void
+HttpdOutput::AddClient(int fd)
+{
+ clients.emplace_front(*this, fd, GetEventLoop(),
+ encoder->plugin.tag == nullptr);
+ ++clients_cnt;
+
+ /* pass metadata to client */
+ if (metadata != nullptr)
+ clients.front().PushMetaData(metadata);
+}
+
+void
+HttpdOutput::RunDeferred()
+{
+ /* this method runs in the IOThread; it broadcasts pages from
+ our own queue to all clients */
+
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+ for (auto &client : clients)
+ client.PushPage(page);
+
+ page->Unref();
+ }
+
+ /* wake up the client that may be waiting for the queue to be
+ flushed */
+ cond.broadcast();
+}
+
+void
+HttpdOutput::OnAccept(int fd, const sockaddr &address,
+ size_t address_length, gcc_unused int uid)
+{
+ /* the listener socket has become readable - a client has
+ connected */
+
+#ifdef HAVE_LIBWRAP
+ if (address.sa_family != AF_UNIX) {
+ const auto hostaddr = sockaddr_to_string(&address,
+ address_length);
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ FormatWarning(httpd_output_domain,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr.c_str());
+ close_socket(fd);
+ return;
+ }
+ }
+#else
+ (void)address;
+ (void)address_length;
+#endif /* HAVE_WRAP */
+
+ const ScopeLock protect(mutex);
+
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (open && (clients_max == 0 || clients_cnt < clients_max))
+ AddClient(fd);
+ else
+ close_socket(fd);
+ } else if (fd < 0 && errno != EINTR) {
+ LogErrno(httpd_output_domain, "accept() failed");
+ }
+}
+
+Page *
+HttpdOutput::ReadPage()
+{
+ if (unflushed_input >= 65536) {
+ /* we have fed a lot of input into the encoder, but it
+ didn't give anything back yet - flush now to avoid
+ buffer underruns */
+ encoder_flush(encoder, IgnoreError());
+ unflushed_input = 0;
+ }
+
+ size_t size = 0;
+ do {
+ size_t nbytes = encoder_read(encoder,
+ buffer + size,
+ sizeof(buffer) - size);
+ if (nbytes == 0)
+ break;
+
+ unflushed_input = 0;
+
+ size += nbytes;
+ } while (size < sizeof(buffer));
+
+ if (size == 0)
+ return nullptr;
+
+ return Page::Copy(buffer, size);
+}
+
+static bool
+httpd_output_enable(AudioOutput *ao, Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Bind(error);
+}
+
+static void
+httpd_output_disable(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ httpd->Unbind();
+}
+
+inline bool
+HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &error)
+{
+ if (!encoder_open(encoder, audio_format, error))
+ return false;
+
+ /* we have to remember the encoder header, i.e. the first
+ bytes of encoder output after opening it, because it has to
+ be sent to every new client */
+ header = ReadPage();
+
+ unflushed_input = 0;
+
+ return true;
+}
+
+inline bool
+HttpdOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ assert(!open);
+ assert(clients.empty());
+
+ /* open the encoder */
+
+ if (!OpenEncoder(audio_format, error))
+ return false;
+
+ /* initialize other attributes */
+
+ clients_cnt = 0;
+ timer = new Timer(audio_format);
+
+ open = true;
+
+ return true;
+}
+
+static bool
+httpd_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ const ScopeLock protect(httpd->mutex);
+ return httpd->Open(audio_format, error);
+}
+
+inline void
+HttpdOutput::Close()
+{
+ assert(open);
+
+ open = false;
+
+ delete timer;
+
+ BlockingCall(GetEventLoop(), [this](){
+ clients.clear();
+ });
+
+ if (header != nullptr)
+ header->Unref();
+
+ encoder_close(encoder);
+}
+
+static void
+httpd_output_close(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ const ScopeLock protect(httpd->mutex);
+ httpd->Close();
+}
+
+void
+HttpdOutput::RemoveClient(HttpdClient &client)
+{
+ assert(clients_cnt > 0);
+
+ for (auto prev = clients.before_begin(), i = std::next(prev);;
+ prev = i, i = std::next(prev)) {
+ assert(i != clients.end());
+ if (&*i == &client) {
+ clients.erase_after(prev);
+ clients_cnt--;
+ break;
+ }
+ }
+}
+
+void
+HttpdOutput::SendHeader(HttpdClient &client) const
+{
+ if (header != nullptr)
+ client.PushPage(header);
+}
+
+inline unsigned
+HttpdOutput::Delay() const
+{
+ if (!LockHasClients() && base.pause) {
+ /* if there's no client and this output is paused,
+ then httpd_output_pause() will not do anything, it
+ will not fill the buffer and it will not update the
+ timer; therefore, we reset the timer here */
+ timer->Reset();
+
+ /* some arbitrary delay that is long enough to avoid
+ consuming too much CPU, and short enough to notice
+ new clients quickly enough */
+ return 1000;
+ }
+
+ return timer->IsStarted()
+ ? timer->GetDelay()
+ : 0;
+}
+
+static unsigned
+httpd_output_delay(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Delay();
+}
+
+void
+HttpdOutput::BroadcastPage(Page *page)
+{
+ assert(page != nullptr);
+
+ mutex.lock();
+ pages.push(page);
+ page->Ref();
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
+}
+
+void
+HttpdOutput::BroadcastFromEncoder()
+{
+ /* synchronize with the IOThread */
+ mutex.lock();
+ while (!pages.empty())
+ cond.wait(mutex);
+
+ Page *page;
+ while ((page = ReadPage()) != nullptr)
+ pages.push(page);
+
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
+}
+
+inline bool
+HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
+{
+ if (!encoder_write(encoder, chunk, size, error))
+ return false;
+
+ unflushed_input += size;
+
+ BroadcastFromEncoder();
+ return true;
+}
+
+inline size_t
+HttpdOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ if (LockHasClients()) {
+ if (!EncodeAndPlay(chunk, size, error))
+ return 0;
+ }
+
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
+
+ return size;
+}
+
+static size_t
+httpd_output_play(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(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ if (httpd->LockHasClients()) {
+ static const char silence[1020] = { 0 };
+ return httpd_output_play(ao, silence, sizeof(silence),
+ IgnoreError()) > 0;
+ } else {
+ return true;
+ }
+}
+
+inline void
+HttpdOutput::SendTag(const Tag *tag)
+{
+ assert(tag != nullptr);
+
+ if (encoder->plugin.tag != nullptr) {
+ /* embed encoder tags */
+
+ /* flush the current stream, and end it */
+
+ encoder_pre_tag(encoder, IgnoreError());
+ BroadcastFromEncoder();
+
+ /* send the tag to the encoder - which starts a new
+ stream now */
+
+ encoder_tag(encoder, tag, IgnoreError());
+
+ /* the first page generated by the encoder will now be
+ used as the new "header" page, which is sent to all
+ new clients */
+
+ Page *page = ReadPage();
+ if (page != nullptr) {
+ if (header != nullptr)
+ header->Unref();
+ header = page;
+ BroadcastPage(page);
+ }
+ } else {
+ /* use Icy-Metadata */
+
+ if (metadata != nullptr)
+ metadata->Unref();
+
+ static constexpr TagType types[] = {
+ TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
+ TAG_NUM_OF_ITEM_TYPES
+ };
+
+ metadata = icy_server_metadata_page(*tag, &types[0]);
+ if (metadata != nullptr) {
+ const ScopeLock protect(mutex);
+ for (auto &client : clients)
+ client.PushMetaData(metadata);
+ }
+ }
+}
+
+static void
+httpd_output_tag(AudioOutput *ao, const Tag *tag)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ httpd->SendTag(tag);
+}
+
+inline void
+HttpdOutput::CancelAllClients()
+{
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+ page->Unref();
+ }
+
+ for (auto &client : clients)
+ client.CancelQueue();
+
+ cond.broadcast();
+}
+
+static void
+httpd_output_cancel(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ BlockingCall(io_thread_get(), [httpd](){
+ httpd->CancelAllClients();
+ });
+}
+
+const struct AudioOutputPlugin httpd_output_plugin = {
+ "httpd",
+ nullptr,
+ httpd_output_init,
+ httpd_output_finish,
+ httpd_output_enable,
+ httpd_output_disable,
+ httpd_output_open,
+ httpd_output_close,
+ httpd_output_delay,
+ httpd_output_tag,
+ httpd_output_play,
+ nullptr,
+ httpd_output_cancel,
+ httpd_output_pause,
+ nullptr,
+};
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
new file mode 100644
index 000000000..df99e2b43
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX
+#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
+
+extern const struct 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