aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/output/AlsaOutputPlugin.cxx853
-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/FifoOutputPlugin.cxx315
-rw-r--r--src/output/FifoOutputPlugin.hxx25
-rw-r--r--src/output/HttpdClient.cxx450
-rw-r--r--src/output/HttpdClient.hxx186
-rw-r--r--src/output/HttpdInternal.hxx208
-rw-r--r--src/output/HttpdOutputPlugin.cxx565
-rw-r--r--src/output/HttpdOutputPlugin.hxx25
-rw-r--r--src/output/JackOutputPlugin.cxx769
-rw-r--r--src/output/JackOutputPlugin.hxx25
-rw-r--r--src/output/NullOutputPlugin.cxx143
-rw-r--r--src/output/NullOutputPlugin.hxx25
-rw-r--r--src/output/OSXOutputPlugin.cxx434
-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.cxx776
-rw-r--r--src/output/OssOutputPlugin.hxx25
-rw-r--r--src/output/PipeOutputPlugin.cxx149
-rw-r--r--src/output/PipeOutputPlugin.hxx25
-rw-r--r--src/output/PulseOutputPlugin.cxx889
-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/RoarOutputPlugin.cxx395
-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/WinmmOutputPlugin.cxx352
-rw-r--r--src/output/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/alsa_output_plugin.c819
-rw-r--r--src/output/alsa_output_plugin.h25
-rw-r--r--src/output/ao_output_plugin.c264
-rw-r--r--src/output/ao_output_plugin.h25
-rw-r--r--src/output/ffado_output_plugin.c359
-rw-r--r--src/output/ffado_output_plugin.h25
-rw-r--r--src/output/fifo_output_plugin.c315
-rw-r--r--src/output/fifo_output_plugin.h25
-rw-r--r--src/output/httpd_client.c764
-rw-r--r--src/output/httpd_client.h71
-rw-r--r--src/output/httpd_internal.h138
-rw-r--r--src/output/httpd_output_plugin.c623
-rw-r--r--src/output/httpd_output_plugin.h25
-rw-r--r--src/output/jack_output_plugin.c755
-rw-r--r--src/output/jack_output_plugin.h25
-rw-r--r--src/output/mvp_output_plugin.c344
-rw-r--r--src/output/mvp_output_plugin.h25
-rw-r--r--src/output/null_output_plugin.c129
-rw-r--r--src/output/null_output_plugin.h25
-rw-r--r--src/output/openal_output_plugin.c279
-rw-r--r--src/output/openal_output_plugin.h25
-rw-r--r--src/output/oss_output_plugin.c788
-rw-r--r--src/output/oss_output_plugin.h25
-rw-r--r--src/output/osx_output_plugin.c438
-rw-r--r--src/output/osx_output_plugin.h25
-rw-r--r--src/output/pipe_output_plugin.c121
-rw-r--r--src/output/pipe_output_plugin.h25
-rw-r--r--src/output/pulse_output_plugin.c955
-rw-r--r--src/output/pulse_output_plugin.h49
-rw-r--r--src/output/recorder_output_plugin.c251
-rw-r--r--src/output/recorder_output_plugin.h25
-rw-r--r--src/output/roar_output_plugin.c401
-rw-r--r--src/output/roar_output_plugin.h35
-rw-r--r--src/output/shout_output_plugin.c555
-rw-r--r--src/output/shout_output_plugin.h25
-rw-r--r--src/output/solaris_output_plugin.c203
-rw-r--r--src/output/solaris_output_plugin.h25
-rw-r--r--src/output/winmm_output_plugin.c356
-rw-r--r--src/output/winmm_output_plugin.h37
-rw-r--r--src/output_all.c590
-rw-r--r--src/output_all.h166
-rw-r--r--src/output_api.h29
-rw-r--r--src/output_command.c87
-rw-r--r--src/output_command.h46
-rw-r--r--src/output_control.c336
-rw-r--r--src/output_control.h94
-rw-r--r--src/output_finish.c60
-rw-r--r--src/output_init.c332
-rw-r--r--src/output_internal.h269
-rw-r--r--src/output_list.c106
-rw-r--r--src/output_list.h33
-rw-r--r--src/output_plugin.c109
-rw-r--r--src/output_plugin.h210
-rw-r--r--src/output_print.c45
-rw-r--r--src/output_print.h33
-rw-r--r--src/output_state.c91
-rw-r--r--src/output_state.h45
-rw-r--r--src/output_thread.c685
-rw-r--r--src/output_thread.h27
94 files changed, 8508 insertions, 12817 deletions
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
new file mode 100644
index 000000000..79b81282b
--- /dev/null
+++ b/src/output/AlsaOutputPlugin.cxx
@@ -0,0 +1,853 @@
+/*
+ * 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;
+
+ /**
+ * 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 NULL;
+ }
+
+ 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 = NULL;
+ 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, NULL);
+ 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, NULL);
+ if (err < 0)
+ goto error;
+ } else {
+ err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
+ NULL);
+ 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, NULL);
+ 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,
+ NULL);
+ 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);
+
+ return true;
+}
+
+/**
+ * Write silence to the ALSA device.
+ */
+static void
+alsa_write_silence(AlsaOutput *ad, snd_pcm_uframes_t nframes)
+{
+ ad->writei(ad->pcm, ad->silence, nframes);
+}
+
+static int
+alsa_recover(AlsaOutput *ad, int err)
+{
+ if (err == -EPIPE) {
+ FormatDebug(alsa_output_domain,
+ "Underrun on ALSA device \"%s\"", alsa_device(ad));
+ } else if (err == -ESTRPIPE) {
+ FormatDebug(alsa_output_domain,
+ "ALSA device \"%s\" was suspended",
+ alsa_device(ad));
+ }
+
+ switch (snd_pcm_state(ad->pcm)) {
+ case SND_PCM_STATE_PAUSED:
+ err = snd_pcm_pause(ad->pcm, /* disable */ 0);
+ break;
+ case SND_PCM_STATE_SUSPENDED:
+ err = snd_pcm_resume(ad->pcm);
+ if (err == -EAGAIN)
+ return 0;
+ /* fall-through to snd_pcm_prepare: */
+ case SND_PCM_STATE_SETUP:
+ case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
+ err = snd_pcm_prepare(ad->pcm);
+
+ if (err == 0) {
+ /* this works around a driver bug observed on
+ the Raspberry Pi: after snd_pcm_drop(), the
+ whole ring buffer must be invalidated, but
+ the snd_pcm_prepare() call above makes the
+ driver play random data that just happens
+ to be still in the buffer; by adding and
+ cancelling some silence, this bug does not
+ occur */
+ alsa_write_silence(ad, ad->period_frames);
+
+ /* cancel the silence data right away to avoid
+ increasing latency; even though this
+ function call invalidates the portion of
+ silence, the driver seems to avoid the
+ bug */
+ snd_pcm_reset(ad->pcm);
+ }
+
+ break;
+ case SND_PCM_STATE_DISCONNECTED:
+ break;
+ /* this is no error, so just keep running */
+ case SND_PCM_STATE_RUNNING:
+ err = 0;
+ break;
+ default:
+ /* unknown state, do nothing */
+ break;
+ }
+
+ return err;
+}
+
+static void
+alsa_drain(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ alsa_write_silence(ad, nframes);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
+alsa_cancel(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
+}
+
+static void
+alsa_close(struct audio_output *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ snd_pcm_close(ad->pcm);
+ 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 % ad->in_frame_size == 0);
+
+ chunk = ad->pcm_export->Export(chunk, size, size);
+
+ assert(size % ad->out_frame_size == 0);
+
+ size /= ad->out_frame_size;
+
+ while (true) {
+ snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
+
+ 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
new file mode 100644
index 000000000..dc7e639a8
--- /dev/null
+++ b/src/output/AlsaOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..e66969e20
--- /dev/null
+++ b/src/output/AoOutputPlugin.cxx
@@ -0,0 +1,286 @@
+/*
+ * 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
new file mode 100644
index 000000000..a44885e56
--- /dev/null
+++ b/src/output/AoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
new file mode 100644
index 000000000..babda5f9e
--- /dev/null
+++ b/src/output/FifoOutputPlugin.cxx
@@ -0,0 +1,315 @@
+/*
+ * 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/Path.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;
+
+ Path path;
+ std::string path_utf8;
+
+ int input;
+ int output;
+ bool created;
+ Timer *timer;
+
+ FifoOutput()
+ :path(Path::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
new file mode 100644
index 000000000..dca2886d8
--- /dev/null
+++ b/src/output/FifoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/HttpdClient.cxx b/src/output/HttpdClient.cxx
new file mode 100644
index 000000000..f7ae8d10c
--- /dev/null
+++ b/src/output/HttpdClient.cxx
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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/fifo_buffer.h"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/SocketError.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+HttpdClient::~HttpdClient()
+{
+ if (state == RESPONSE) {
+ if (current_page != nullptr)
+ current_page->Unref();
+
+ 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;
+
+ httpd->SendHeader(*this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+ assert(state != RESPONSE);
+
+ if (state == REQUEST) {
+ if (strncmp(line, "GET /", 5) != 0) {
+ /* only GET is supported */
+ LogWarning(httpd_output_domain,
+ "malformed request line from client");
+ return false;
+ }
+
+ line = strchr(line + 5, ' ');
+ if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) {
+ /* HTTP/0.9 without request headers */
+ 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 (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
+ /* Send icy metadata */
+ metadata_requested = metadata_supported;
+ return true;
+ }
+
+ if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
+ /* 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) {
+ gchar *metadata_header;
+
+ metadata_header =
+ icy_server_metadata_header(httpd->name, httpd->genre,
+ httpd->website,
+ httpd->content_type,
+ metaint);
+
+ g_strlcpy(buffer, metadata_header, sizeof(buffer));
+
+ g_free(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),
+ 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(const void *data, size_t length)
+{
+ if (state == RESPONSE) {
+ LogWarning(httpd_output_domain,
+ "unexpected input from client");
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ const char *line = (const char *)data;
+ const char *newline = (const 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; the const_cast
+ is a dirty hack */
+ *const_cast<char *>(newline) = 0;
+
+ if (!HandleLine(line)) {
+ assert(state == RESPONSE);
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ if (state == RESPONSE && !SendResponse())
+ 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
new file mode 100644
index 000000000..a596814ee
--- /dev/null
+++ b/src/output/HttpdClient.hxx
@@ -0,0 +1,186 @@
+/*
+ * 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 "gcc.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;
+
+ /**
+ * 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(const 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
new file mode 100644
index 000000000..445e2ff1a
--- /dev/null
+++ b/src/output/HttpdInternal.hxx
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Internal declarations for the "httpd" audio output plugin.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
+#define MPD_OUTPUT_HTTPD_INTERNAL_H
+
+#include "OutputInternal.hxx"
+#include "Timer.hxx"
+#include "thread/Mutex.hxx"
+#include "event/ServerSocket.hxx"
+
+#include <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
new file mode 100644
index 000000000..09f0f5e6b
--- /dev/null
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -0,0 +1,565 @@
+/*
+ * 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 == NULL) {
+ 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 != NULL &&
+ 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 NULL;
+
+ 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 != NULL)
+ 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 != NULL)
+ 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 != NULL);
+
+ 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 != NULL);
+
+ 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 != NULL) {
+ if (header != NULL)
+ header->Unref();
+ header = page;
+ BroadcastPage(page);
+ }
+ } else {
+ /* use Icy-Metadata */
+
+ if (metadata != NULL)
+ metadata->Unref();
+
+ static constexpr tag_type types[] = {
+ TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
+ TAG_NUM_OF_ITEM_TYPES
+ };
+
+ metadata = icy_server_metadata_page(*tag, &types[0]);
+ if (metadata != NULL) {
+ 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
new file mode 100644
index 000000000..c74d2bd4a
--- /dev/null
+++ b/src/output/HttpdOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
new file mode 100644
index 000000000..75c7daf81
--- /dev/null
+++ b/src/output/JackOutputPlugin.cxx
@@ -0,0 +1,769 @@
+/*
+ * 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)
+{
+ LogInfo(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
new file mode 100644
index 000000000..908105ad2
--- /dev/null
+++ b/src/output/JackOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
new file mode 100644
index 000000000..e2eec9dbc
--- /dev/null
+++ b/src/output/NullOutputPlugin.cxx
@@ -0,0 +1,143 @@
+/*
+ * 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
new file mode 100644
index 000000000..a58f1cb13
--- /dev/null
+++ b/src/output/NullOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..eee215b32
--- /dev/null
+++ b/src/output/OSXOutputPlugin.cxx
@@ -0,0 +1,434 @@
+/*
+ * 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 "Log.hxx"
+
+#include <glib.h>
+#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 G_BYTE_ORDER == G_BIG_ENDIAN
+ stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+#endif
+
+ 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.Set(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
new file mode 100644
index 000000000..2a4172880
--- /dev/null
+++ b/src/output/OSXOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..e753b206f
--- /dev/null
+++ b/src/output/OpenALOutputPlugin.cxx
@@ -0,0 +1,285 @@
+/*
+ * 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 HAVE_OSX
+#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
new file mode 100644
index 000000000..e1ebf3d4f
--- /dev/null
+++ b/src/output/OpenALOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..781e2bf43
--- /dev/null
+++ b/src/output/OssOutputPlugin.cxx
@@ -0,0 +1,776 @@
+/*
+ * 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 "Log.hxx"
+
+#include <glib.h>
+
+#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 = G_N_ELEMENTS(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[G_N_ELEMENTS(default_devices)];
+ enum oss_stat ret[G_N_ELEMENTS(default_devices)];
+
+ const config_param empty;
+ for (int i = G_N_ELEMENTS(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 = G_N_ELEMENTS(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 &&
+ G_BYTE_ORDER != G_LITTLE_ENDIAN);
+#endif
+
+ return SUCCESS;
+}
+
+/**
+ * Set up the sample format, and attempts to find alternatives if the
+ * specified format is not supported.
+ */
+static bool
+oss_setup_sample_format(int fd, AudioFormat &audio_format,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ PcmExport &pcm_export,
+#endif
+ Error &error)
+{
+ SampleFormat mpd_format;
+ enum oss_setup_result result =
+ oss_probe_sample_format(fd, audio_format.format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+
+ if (result != UNSUPPORTED)
+ return result == SUCCESS;
+
+ /* the requested sample format is not available - probe for
+ other formats supported by MPD */
+
+ static const SampleFormat sample_formats[] = {
+ SampleFormat::S24_P32,
+ SampleFormat::S32,
+ SampleFormat::S16,
+ SampleFormat::S8,
+ SampleFormat::UNDEFINED /* sentinel */
+ };
+
+ for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
+ mpd_format = sample_formats[i];
+ if (mpd_format == audio_format.format)
+ /* don't try that again */
+ continue;
+
+ result = oss_probe_sample_format(fd, mpd_format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ pcm_export,
+#endif
+ error);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, "Failed to set sample format");
+ return false;
+}
+
+/**
+ * Sets up the OSS device which was opened before.
+ */
+static bool
+oss_setup(OssOutput *od, AudioFormat &audio_format,
+ Error &error)
+{
+ return oss_setup_channels(od->fd, audio_format, error) &&
+ oss_setup_sample_rate(od->fd, audio_format, error) &&
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ od->pcm_export,
+#endif
+ error);
+}
+
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(OssOutput *od, Error &error)
+{
+ assert(od->fd < 0);
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ return false;
+ }
+
+ enum oss_setup_result result;
+
+ const char *const msg1 = "Failed to set channel count";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
+ od->audio_format.channels, msg1, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg1);
+ return false;
+ }
+
+ const char *const msg2 = "Failed to set sample rate";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
+ od->audio_format.sample_rate, msg2, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg2);
+ return false;
+ }
+
+ const char *const msg3 = "Failed to set sample format";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
+ od->oss_format,
+ msg3, error);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg3);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ return false;
+ }
+
+ if (!oss_setup(od, audio_format, error)) {
+ oss_close(od);
+ return false;
+ }
+
+ od->audio_format = audio_format;
+ return true;
+}
+
+static void
+oss_output_close(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ oss_close(od);
+}
+
+static void
+oss_output_cancel(struct audio_output *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ if (od->fd >= 0) {
+ ioctl(od->fd, SNDCTL_DSP_RESET, 0);
+ oss_close(od);
+ }
+}
+
+static size_t
+oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+ ssize_t ret;
+
+ /* reopen the device since it was closed by dropBufferedAudio */
+ if (od->fd < 0 && !oss_reopen(od, error))
+ return 0;
+
+#ifdef AFMT_S24_PACKED
+ chunk = od->pcm_export->Export(chunk, size, size);
+#endif
+
+ while (true) {
+ ret = write(od->fd, chunk, size);
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = od->pcm_export->CalcSourceSize(ret);
+#endif
+ return ret;
+ }
+
+ if (ret < 0 && errno != EINTR) {
+ error.FormatErrno("Write error on %s", od->device);
+ return 0;
+ }
+ }
+}
+
+const struct audio_output_plugin oss_output_plugin = {
+ "oss",
+ oss_output_test_default_device,
+ oss_output_init,
+ oss_output_finish,
+#ifdef AFMT_S24_PACKED
+ oss_output_enable,
+ oss_output_disable,
+#else
+ nullptr,
+ nullptr,
+#endif
+ oss_output_open,
+ oss_output_close,
+ nullptr,
+ nullptr,
+ oss_output_play,
+ nullptr,
+ oss_output_cancel,
+ nullptr,
+
+ &oss_mixer_plugin,
+};
diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx
new file mode 100644
index 000000000..6c5c9530b
--- /dev/null
+++ b/src/output/OssOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
new file mode 100644
index 000000000..2b830ef29
--- /dev/null
+++ b/src/output/PipeOutputPlugin.cxx
@@ -0,0 +1,149 @@
+/*
+ * 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 <glib.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+struct PipeOutput {
+ struct audio_output base;
+
+ char *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.DupBlockString("command");
+ if (cmd == nullptr) {
+ 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;
+
+ g_free(pd->cmd);
+ 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, "w");
+ if (pd->fh == nullptr) {
+ error.FormatErrno("Error opening pipe \"%s\"",
+ pd->cmd);
+ 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
new file mode 100644
index 000000000..f0c29706b
--- /dev/null
+++ b/src/output/PipeOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..ab797387d
--- /dev/null
+++ b/src/output/PulseOutputPlugin.cxx
@@ -0,0 +1,889 @@
+/*
+ * 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) {
+ g_free(po);
+
+ error.Set(pulse_output_domain,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+
+ error.Set(pulse_output_domain,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ /* create the libpulse context and connect it */
+
+ if (!pulse_output_setup_context(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != nullptr)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = nullptr;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(PulseOutput *po, Error &error)
+{
+ assert(po->mainloop != nullptr);
+
+ pa_context_state_t state;
+
+ if (po->context == nullptr && !pulse_output_setup_context(po, error))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ SetError(error, po->context, "failed to connect");
+ pulse_output_delete_context(po);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+static void
+pulse_output_stream_suspended_cb(gcc_unused pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+
+ /* wake up the main loop to break out of the loop in
+ pulse_output_play() */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(stream == po->stream || po->stream == nullptr);
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_change(po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != nullptr)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(gcc_unused pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ PulseOutput *po = (PulseOutput *)userdata;
+
+ assert(po->mainloop != nullptr);
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss,
+ Error &error)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+ if (po->stream == nullptr) {
+ SetError(error, po->context, "pa_stream_new() has failed");
+ return false;
+ }
+
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ return true;
+}
+
+static bool
+pulse_output_open(struct audio_output *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_sample_spec ss;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != nullptr) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format.format = SampleFormat::S16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format.sample_rate;
+ ss.channels = audio_format.channels;
+
+ /* create a stream .. */
+
+ if (!pulse_output_setup_stream(po, &ss, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* .. and connect it (asynchronously) */
+
+ if (pa_stream_connect_playback(po->stream, po->sink,
+ nullptr, pa_stream_flags_t(0),
+ nullptr, nullptr) < 0) {
+ pulse_output_delete_stream(po);
+
+ SetError(error, po->context,
+ "pa_stream_connect_playback() has failed");
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_close(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ FormatWarning(pulse_output_domain,
+ "pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pulse_output_delete_stream(po);
+
+ if (po->context != nullptr &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(PulseOutput *po, Error &error)
+{
+ while (true) {
+ switch (pa_stream_get_state(po->stream)) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ SetError(error, po->context,
+ "failed to connect the stream");
+ return false;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(PulseOutput *po, bool pause,
+ Error &error)
+{
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->context != nullptr);
+ assert(po->stream != nullptr);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned
+pulse_output_delay(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ unsigned result = 0;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->base.pause && pa_stream_is_corked(po->stream) &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ /* idle while paused */
+ result = 1000;
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return result;
+}
+
+static size_t
+pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != nullptr);
+
+ /* unpause if previously paused */
+
+ if (pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, false, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ if (pa_stream_is_suspended(po->stream)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "suspended");
+ return 0;
+ }
+
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "disconnected");
+ return 0;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ int result = pa_stream_write(po->stream, chunk, size, nullptr,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (result < 0) {
+ SetError(error, po->context, "pa_stream_write() failed");
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_operation *o;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != nullptr);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == nullptr) {
+ FormatWarning(pulse_output_domain,
+ "pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(struct audio_output *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ Error error;
+ if (!pulse_output_wait_stream(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(error);
+ return false;
+ }
+
+ assert(po->context != nullptr);
+
+ /* cork the stream */
+
+ if (!pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, true, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ bool success;
+
+ const config_param empty;
+ PulseOutput *po = (PulseOutput *)
+ pulse_output_init(empty, IgnoreError());
+ if (po == nullptr)
+ return false;
+
+ success = pulse_output_wait_connection(po, IgnoreError());
+ pulse_output_finish(&po->base);
+
+ return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+ "pulse",
+ pulse_output_test_default_device,
+ pulse_output_init,
+ pulse_output_finish,
+ pulse_output_enable,
+ pulse_output_disable,
+ pulse_output_open,
+ pulse_output_close,
+ pulse_output_delay,
+ nullptr,
+ pulse_output_play,
+ nullptr,
+ pulse_output_cancel,
+ pulse_output_pause,
+
+ &pulse_mixer_plugin,
+};
diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx
new file mode 100644
index 000000000..0ed8404bc
--- /dev/null
+++ b/src/output/PulseOutputPlugin.hxx
@@ -0,0 +1,46 @@
+/*
+ * 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
new file mode 100644
index 000000000..9a7eba01f
--- /dev/null
+++ b/src/output/RecorderOutputPlugin.cxx
@@ -0,0 +1,262 @@
+/*
+ * 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
new file mode 100644
index 000000000..a27f51e23
--- /dev/null
+++ b/src/output/RecorderOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
new file mode 100644
index 000000000..9d66bb63b
--- /dev/null
+++ b/src/output/RoarOutputPlugin.cxx
@@ -0,0 +1,395 @@
+/*
+ * 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 <glib.h>
+
+/* libroar/services.h declares roar_service_stream::new - work around
+ this C++ problem */
+#define new _new
+#include <roaraudio.h>
+#undef new
+
+struct RoarOutput {
+ struct audio_output base;
+
+ roar_vs_t * vss;
+ int err;
+ char *host;
+ char *name;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ Mutex mutex;
+ volatile bool alive;
+
+ RoarOutput()
+ :err(ROAR_ERROR_NONE),
+ host(nullptr), name(nullptr) {}
+
+ ~RoarOutput() {
+ g_free(host);
+ g_free(name);
+ }
+};
+
+static constexpr Domain roar_output_domain("roar_output");
+
+static int
+roar_output_get_volume_locked(RoarOutput *roar)
+{
+ if (roar->vss == nullptr || !roar->alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(RoarOutput *roar)
+{
+ const ScopeLock protect(roar->mutex);
+ return roar_output_get_volume_locked(roar);
+}
+
+static bool
+roar_output_set_volume_locked(RoarOutput *roar, unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (roar->vss == nullptr || !roar->alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(roar->vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(RoarOutput *roar, unsigned volume)
+{
+ const ScopeLock protect(roar->mutex);
+ return roar_output_set_volume_locked(roar, volume);
+}
+
+static void
+roar_configure(RoarOutput *self, const config_param &param)
+{
+ self->host = param.DupBlockString("server", nullptr);
+ self->name = param.DupBlockString("name", "MPD");
+
+ const char *role = param.GetBlockValue("role", "music");
+ self->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 (!ao_base_init(&self->base, &roar_output_plugin, param, error)) {
+ delete self;
+ return nullptr;
+ }
+
+ roar_configure(self, param);
+ return &self->base;
+}
+
+static void
+roar_finish(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ ao_base_finish(&self->base);
+ 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;
+ }
+}
+
+static bool
+roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ const ScopeLock protect(self->mutex);
+
+ if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
+ {
+ error.Set(roar_output_domain,
+ "Failed to connect to Roar server");
+ return false;
+ }
+
+ self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
+
+ if (self->vss == nullptr || self->err != ROAR_ERROR_NONE)
+ {
+ error.Set(roar_output_domain, "Failed to connect to server");
+ return false;
+ }
+
+ roar_use_audio_format(&self->info, audio_format);
+
+ if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0)
+ {
+ error.Set(roar_output_domain, "Failed to start stream");
+ return false;
+ }
+ roar_vs_role(self->vss, self->role, &(self->err));
+ self->alive = true;
+
+ return true;
+}
+
+static void
+roar_close(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ const ScopeLock protect(self->mutex);
+
+ self->alive = false;
+
+ if (self->vss != nullptr)
+ roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
+ self->vss = nullptr;
+ roar_disconnect(&(self->con));
+}
+
+static void
+roar_cancel_locked(RoarOutput *self)
+{
+ if (self->vss == nullptr)
+ return;
+
+ roar_vs_t *vss = self->vss;
+ self->vss = nullptr;
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ self->alive = false;
+
+ vss = roar_vs_new_from_con(&(self->con), &(self->err));
+ if (vss == nullptr)
+ return;
+
+ if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0) {
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ LogError(roar_output_domain, "Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(vss, self->role, &(self->err));
+ self->vss = vss;
+ self->alive = true;
+}
+
+static void
+roar_cancel(struct audio_output *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ const ScopeLock protect(self->mutex);
+ roar_cancel_locked(self);
+}
+
+static size_t
+roar_play(struct audio_output *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ ssize_t rc;
+
+ if (self->vss == nullptr)
+ {
+ error.Set(roar_output_domain, "Connection is invalid");
+ return 0;
+ }
+
+ rc = roar_vs_write(self->vss, chunk, size, &(self->err));
+ if ( rc <= 0 )
+ {
+ error.Set(roar_output_domain, "Failed to play data");
+ return 0;
+ }
+
+ return rc;
+}
+
+static const char*
+roar_tag_convert(enum tag_type 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;
+ }
+}
+
+static void
+roar_send_tag(struct audio_output *ao, const Tag *meta)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ if (self->vss == nullptr)
+ return;
+
+ const ScopeLock protect(self->mutex);
+
+ size_t cnt = 1;
+ struct roar_keyval vals[32];
+ memset(vals, 0, sizeof(vals));
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
+
+ vals[0].key = g_strdup("LENGTH");
+ vals[0].value = timebuf;
+
+ for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
+ {
+ bool is_uuid = false;
+ const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
+ if (key != nullptr)
+ {
+ if (is_uuid)
+ {
+ snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+ meta->items[i]->value);
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = uuid_buf[cnt];
+ }
+ else
+ {
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = meta->items[i]->value;
+ }
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(self->vss, vals, cnt, &(self->err));
+
+ for (unsigned i = 0; i < 32; i++)
+ g_free(vals[i].key);
+}
+
+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
new file mode 100644
index 000000000..faa4b4d5c
--- /dev/null
+++ b/src/output/RoarOutputPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+struct 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
new file mode 100644
index 000000000..19f2b61cd
--- /dev/null
+++ b/src/output/ShoutOutputPlugin.cxx
@@ -0,0 +1,544 @@
+/*
+ * 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
new file mode 100644
index 000000000..496b77975
--- /dev/null
+++ b/src/output/ShoutOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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
new file mode 100644
index 000000000..0836dc2e2
--- /dev/null
+++ b/src/output/SolarisOutputPlugin.cxx
@@ -0,0 +1,201 @@
+/*
+ * 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
new file mode 100644
index 000000000..d0fbd32c8
--- /dev/null
+++ b/src/output/SolarisOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * 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/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
new file mode 100644
index 000000000..d3f74dd44
--- /dev/null
+++ b/src/output/WinmmOutputPlugin.cxx
@@ -0,0 +1,352 @@
+/*
+ * 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 <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)) {
+ g_free(wo);
+ return nullptr;
+ }
+
+ const char *device = param.GetBlockValue("device");
+ if (!get_device_id(device, &wo->device_id, error)) {
+ ao_base_finish(&wo->base);
+ g_free(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 < G_N_ELEMENTS(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 < G_N_ELEMENTS(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) %
+ G_N_ELEMENTS(wo->buffers);
+
+ return size;
+}
+
+static bool
+winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
+{
+ for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(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 < G_N_ELEMENTS(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
new file mode 100644
index 000000000..e8688782e
--- /dev/null
+++ b/src/output/WinmmOutputPlugin.hxx
@@ -0,0 +1,42 @@
+/*
+ * 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 "gcc.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/alsa_output_plugin.c b/src/output/alsa_output_plugin.c
deleted file mode 100644
index d8b184273..000000000
--- a/src/output/alsa_output_plugin.c
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "alsa_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "pcm_export.h"
-
-#include <glib.h>
-#include <alsa/asoundlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "alsa"
-
-#define ALSA_PCM_NEW_HW_PARAMS_API
-#define ALSA_PCM_NEW_SW_PARAMS_API
-
-static const char default_device[] = "default";
-
-enum {
- 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 alsa_data {
- struct audio_output base;
-
- struct pcm_export_state export;
-
- /** the configured name of the ALSA device; NULL for the
- default device */
- char *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;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-alsa_output_quark(void)
-{
- return g_quark_from_static_string("alsa_output");
-}
-
-static const char *
-alsa_device(const struct alsa_data *ad)
-{
- return ad->device != NULL ? ad->device : default_device;
-}
-
-static struct alsa_data *
-alsa_data_new(void)
-{
- struct alsa_data *ret = g_new(struct alsa_data, 1);
-
- ret->mode = 0;
- ret->writei = snd_pcm_writei;
-
- return ret;
-}
-
-static void
-alsa_configure(struct alsa_data *ad, const struct config_param *param)
-{
- ad->device = config_dup_block_string(param, "device", NULL);
-
- ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
-
- ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false);
-
- ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
- MPD_ALSA_BUFFER_TIME_US);
- ad->period_time = config_get_block_unsigned(param, "period_time", 0);
-
-#ifdef SND_PCM_NO_AUTO_RESAMPLE
- if (!config_get_block_bool(param, "auto_resample", true))
- ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_CHANNELS
- if (!config_get_block_bool(param, "auto_channels", true))
- ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
-#endif
-
-#ifdef SND_PCM_NO_AUTO_FORMAT
- if (!config_get_block_bool(param, "auto_format", true))
- ad->mode |= SND_PCM_NO_AUTO_FORMAT;
-#endif
-}
-
-static struct audio_output *
-alsa_init(const struct config_param *param, GError **error_r)
-{
- struct alsa_data *ad = alsa_data_new();
-
- if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) {
- g_free(ad);
- return NULL;
- }
-
- alsa_configure(ad, param);
-
- return &ad->base;
-}
-
-static void
-alsa_finish(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- ao_base_finish(&ad->base);
-
- g_free(ad->device);
- g_free(ad);
-
- /* free libasound's config cache */
- snd_config_update_free_global();
-}
-
-static bool
-alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- pcm_export_init(&ad->export);
- return true;
-}
-
-static void
-alsa_output_disable(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- pcm_export_deinit(&ad->export);
-}
-
-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) {
- g_message("Error opening default ALSA device: %s\n",
- snd_strerror(-ret));
- return false;
- } else
- snd_pcm_close(handle);
-
- return true;
-}
-
-static snd_pcm_format_t
-get_bitformat(enum sample_format sample_format)
-{
- switch (sample_format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_DSD:
- return SND_PCM_FORMAT_UNKNOWN;
-
- case SAMPLE_FORMAT_S8:
- return SND_PCM_FORMAT_S8;
-
- case SAMPLE_FORMAT_S16:
- return SND_PCM_FORMAT_S16;
-
- case SAMPLE_FORMAT_S24_P32:
- return SND_PCM_FORMAT_S24;
-
- case SAMPLE_FORMAT_S32:
- return SND_PCM_FORMAT_S32;
-
- case SAMPLE_FORMAT_FLOAT:
- return SND_PCM_FORMAT_FLOAT;
- }
-
- assert(false);
- return SND_PCM_FORMAT_UNKNOWN;
-}
-
-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,
- enum sample_format 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,
- struct audio_format *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 enum sample_format probe_formats[] = {
- SAMPLE_FORMAT_S24_P32,
- SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S16,
- SAMPLE_FORMAT_S8,
- SAMPLE_FORMAT_UNDEFINED,
- };
-
- for (unsigned i = 0;
- err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED;
- ++i) {
- const enum sample_format 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(struct alsa_data *ad, struct audio_format *audio_format,
- bool *packed_r, bool *reverse_endian_r, GError **error)
-{
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- unsigned int sample_rate = audio_format->sample_rate;
- unsigned int channels = audio_format->channels;
- snd_pcm_uframes_t alsa_buffer_size;
- snd_pcm_uframes_t alsa_period_size;
- int err;
- const char *cmd = NULL;
- 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_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) {
- g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n",
- alsa_device(ad), snd_strerror(-err));
- g_warning("Falling back to direct write mode\n");
- 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) {
- g_set_error(error, alsa_output_quark(), 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)
- g_debug("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) {
- g_set_error(error, alsa_output_quark(), 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, NULL);
- if (err < 0 || sample_rate == 0) {
- g_set_error(error, alsa_output_quark(), 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);
- g_debug("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);
- g_debug("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, NULL);
- if (err < 0)
- goto error;
- } else {
- err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
- NULL);
- if (err < 0)
- buffer_time = 0;
- }
-
- if (period_time_ro == 0 && buffer_time >= 10000) {
- period_time_ro = period_time = buffer_time / 4;
-
- g_debug("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, NULL);
- 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)
- g_debug("ALSA period_time set to %d\n", period_time);
-
- 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;
-
- cmd = "snd_pcm_hw_params_get_period_size";
- err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
- NULL);
- if (err < 0)
- goto error;
-
- /* configure SW params */
- 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;
-
- g_debug("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;
-
- return true;
-
-error:
- g_set_error(error, alsa_output_quark(), err,
- "Error opening ALSA device \"%s\" (%s): %s",
- alsa_device(ad), cmd, snd_strerror(-err));
- return false;
-}
-
-static bool
-alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format,
- bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
- GError **error_r)
-{
- assert(ad->dsd_usb);
- assert(audio_format->format == SAMPLE_FORMAT_DSD);
-
- /* pass 24 bit to alsa_setup() */
-
- struct audio_format usb_format = *audio_format;
- usb_format.format = SAMPLE_FORMAT_S24_P32;
- usb_format.sample_rate /= 2;
-
- const struct audio_format check = usb_format;
-
- if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r))
- 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 == SAMPLE_FORMAT_S32;
- if (usb_format.format == SAMPLE_FORMAT_S32)
- usb_format.format = SAMPLE_FORMAT_S24_P32;
-
- if (!audio_format_equals(&usb_format, &check)) {
- /* no bit-perfect playback, which is required
- for DSD over USB */
- g_set_error(error_r, alsa_output_quark(), 0,
- "Failed to configure DSD-over-USB on ALSA device \"%s\"",
- alsa_device(ad));
- return false;
- }
-
- return true;
-}
-
-static bool
-alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format,
- GError **error_r)
-{
- bool shift8 = false, packed, reverse_endian;
-
- const bool dsd_usb = ad->dsd_usb &&
- audio_format->format == SAMPLE_FORMAT_DSD;
- const bool success = dsd_usb
- ? alsa_setup_dsd(ad, audio_format,
- &shift8, &packed, &reverse_endian,
- error_r)
- : alsa_setup(ad, audio_format, &packed, &reverse_endian,
- error_r);
- if (!success)
- return false;
-
- pcm_export_open(&ad->export,
- audio_format->format, audio_format->channels,
- dsd_usb, shift8, packed, reverse_endian);
- return true;
-}
-
-static bool
-alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
- int err;
- bool success;
-
- err = snd_pcm_open(&ad->pcm, alsa_device(ad),
- SND_PCM_STREAM_PLAYBACK, ad->mode);
- if (err < 0) {
- g_set_error(error, alsa_output_quark(), err,
- "Failed to open ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(err));
- return false;
- }
-
- g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
- snd_pcm_type_name(snd_pcm_type(ad->pcm)));
-
- success = alsa_setup_or_dsd(ad, audio_format, error);
- if (!success) {
- snd_pcm_close(ad->pcm);
- return false;
- }
-
- ad->in_frame_size = audio_format_frame_size(audio_format);
- ad->out_frame_size = pcm_export_frame_size(&ad->export, audio_format);
-
- return true;
-}
-
-static int
-alsa_recover(struct alsa_data *ad, int err)
-{
- if (err == -EPIPE) {
- g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
- } else if (err == -ESTRPIPE) {
- g_debug("ALSA device \"%s\" was suspended\n", 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)
-{
- struct alsa_data *ad = (struct alsa_data *)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;
- size_t nbytes = nframes * ad->out_frame_size;
- void *buffer = g_malloc(nbytes);
- snd_pcm_hw_params_t *params;
- snd_pcm_format_t format;
- unsigned channels;
-
- snd_pcm_hw_params_alloca(&params);
- snd_pcm_hw_params_current(ad->pcm, params);
- snd_pcm_hw_params_get_format(params, &format);
- snd_pcm_hw_params_get_channels(params, &channels);
-
- snd_pcm_format_set_silence(format, buffer, nframes * channels);
- ad->writei(ad->pcm, buffer, nframes);
- g_free(buffer);
- }
-
- snd_pcm_drain(ad->pcm);
-
- ad->period_position = 0;
-}
-
-static void
-alsa_cancel(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- ad->period_position = 0;
-
- snd_pcm_drop(ad->pcm);
-}
-
-static void
-alsa_close(struct audio_output *ao)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- snd_pcm_close(ad->pcm);
-}
-
-static size_t
-alsa_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct alsa_data *ad = (struct alsa_data *)ao;
-
- assert(size % ad->in_frame_size == 0);
-
- chunk = pcm_export(&ad->export, chunk, size, &size);
-
- assert(size % ad->out_frame_size == 0);
-
- size /= ad->out_frame_size;
-
- while (true) {
- snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0) {
- ad->period_position = (ad->period_position + ret)
- % ad->period_frames;
-
- size_t bytes_written = ret * ad->out_frame_size;
- return pcm_export_source_size(&ad->export,
- bytes_written);
- }
-
- if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
- alsa_recover(ad, ret) < 0) {
- g_set_error(error, alsa_output_quark(), errno,
- "%s", snd_strerror(-errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin alsa_output_plugin = {
- .name = "alsa",
- .test_default_device = alsa_test_default_device,
- .init = alsa_init,
- .finish = alsa_finish,
- .enable = alsa_output_enable,
- .disable = alsa_output_disable,
- .open = alsa_open,
- .play = alsa_play,
- .drain = alsa_drain,
- .cancel = alsa_cancel,
- .close = alsa_close,
-
- .mixer_plugin = &alsa_mixer_plugin,
-};
diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h
deleted file mode 100644
index daa1f3615..000000000
--- a/src/output/alsa_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_ALSA_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin alsa_output_plugin;
-
-#endif
diff --git a/src/output/ao_output_plugin.c b/src/output/ao_output_plugin.c
deleted file mode 100644
index d7e577fa4..000000000
--- a/src/output/ao_output_plugin.c
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "ao_output_plugin.h"
-#include "output_api.h"
-
-#include <ao/ao.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ao"
-
-/* An ao_sample_format, with all fields set to zero: */
-static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
-
-static unsigned ao_output_ref;
-
-struct ao_data {
- struct audio_output base;
-
- size_t write_size;
- int driver;
- ao_option *options;
- ao_device *device;
-} AoData;
-
-static inline GQuark
-ao_output_quark(void)
-{
- return g_quark_from_static_string("ao_output");
-}
-
-static void
-ao_output_error(GError **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 = g_strerror(errno);
- }
-
- g_set_error(error_r, ao_output_quark(), errno,
- "%s", error);
-}
-
-static struct audio_output *
-ao_output_init(const struct config_param *param,
- GError **error)
-{
- struct ao_data *ad = g_new(struct ao_data, 1);
-
- if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) {
- g_free(ad);
- return NULL;
- }
-
- ao_info *ai;
- const char *value;
-
- ad->options = NULL;
-
- ad->write_size = config_get_block_unsigned(param, "write_size", 1024);
-
- if (ao_output_ref == 0) {
- ao_initialize();
- }
- ao_output_ref++;
-
- value = config_get_block_string(param, "driver", "default");
- if (0 == strcmp(value, "default"))
- ad->driver = ao_default_driver_id();
- else
- ad->driver = ao_driver_id(value);
-
- if (ad->driver < 0) {
- g_set_error(error, ao_output_quark(), 0,
- "\"%s\" is not a valid ao driver",
- value);
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- if ((ai = ao_driver_info(ad->driver)) == NULL) {
- g_set_error(error, ao_output_quark(), 0,
- "problems getting driver info");
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
- config_get_block_string(param, "name", NULL));
-
- value = config_get_block_string(param, "options", NULL);
- if (value != NULL) {
- gchar **options = g_strsplit(value, ";", 0);
-
- for (unsigned i = 0; options[i] != NULL; ++i) {
- gchar **key_value = g_strsplit(options[i], "=", 2);
-
- if (key_value[0] == NULL || key_value[1] == NULL) {
- g_set_error(error, ao_output_quark(), 0,
- "problems parsing options \"%s\"",
- options[i]);
- ao_base_finish(&ad->base);
- g_free(ad);
- return NULL;
- }
-
- ao_append_option(&ad->options, key_value[0],
- key_value[1]);
-
- g_strfreev(key_value);
- }
-
- g_strfreev(options);
- }
-
- return &ad->base;
-}
-
-static void
-ao_output_finish(struct audio_output *ao)
-{
- struct ao_data *ad = (struct ao_data *)ao;
-
- ao_free_options(ad->options);
- ao_base_finish(&ad->base);
- g_free(ad);
-
- ao_output_ref--;
-
- if (ao_output_ref == 0)
- ao_shutdown();
-}
-
-static void
-ao_output_close(struct audio_output *ao)
-{
- struct ao_data *ad = (struct ao_data *)ao;
-
- ao_close(ad->device);
-}
-
-static bool
-ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
- struct ao_data *ad = (struct ao_data *)ao;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- format.bits = 8;
- break;
-
- case SAMPLE_FORMAT_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 = SAMPLE_FORMAT_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 == NULL) {
- 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;
- void *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,
- GError **error)
-{
- struct ao_data *ad = (struct ao_data *)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 = {
- .name = "ao",
- .init = ao_output_init,
- .finish = ao_output_finish,
- .open = ao_output_open,
- .close = ao_output_close,
- .play = ao_output_play,
-};
diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h
deleted file mode 100644
index 9a3a47c05..000000000
--- a/src/output/ao_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_AO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin ao_output_plugin;
-
-#endif
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
deleted file mode 100644
index ba239a4ad..000000000
--- a/src/output/ffado_output_plugin.c
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Warning: this plugin was not tested successfully. I just couldn't
- * keep libffado2 from crashing. Use at your own risk.
- *
- * For details, see my Debian bug reports:
- *
- * http://bugs.debian.org/601657
- * http://bugs.debian.org/601659
- * http://bugs.debian.org/601663
- *
- */
-
-#include "config.h"
-#include "ffado_output_plugin.h"
-#include "output_api.h"
-#include "timer.h"
-
-#include <glib.h>
-#include <assert.h>
-
-#include <libffado/ffado.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffado"
-
-enum {
- MAX_STREAMS = 8,
-};
-
-struct mpd_ffado_stream {
- /** libffado's stream number */
- int number;
-
- float *buffer;
-};
-
-struct mpd_ffado_device {
- struct audio_output base;
-
- char *device_name;
- int verbose;
- unsigned period_size, nb_buffers;
-
- ffado_device_t *dev;
-
- /**
- * The current sample position inside the stream buffers. New
- * samples get appended at this position on all streams at the
- * same time. When the buffers are full
- * (buffer_position==period_size),
- * ffado_streaming_transfer_playback_buffers() gets called to
- * hand them over to libffado.
- */
- unsigned buffer_position;
-
- /**
- * The number of streams which are really used by MPD.
- */
- int num_streams;
- struct mpd_ffado_stream streams[MAX_STREAMS];
-};
-
-static inline GQuark
-ffado_output_quark(void)
-{
- return g_quark_from_static_string("ffado_output");
-}
-
-static struct audio_output *
-ffado_init(const struct config_param *param,
- GError **error_r)
-{
- g_debug("using libffado version %s, API=%d",
- ffado_get_version(), ffado_get_api_version());
-
- struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
- if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) {
- g_free(fd);
- return NULL;
- }
-
- fd->device_name = config_dup_block_string(param, "device", NULL);
- fd->verbose = config_get_block_unsigned(param, "verbose", 0);
-
- fd->period_size = config_get_block_unsigned(param, "period_size",
- 1024);
- if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
- ao_base_finish(&fd->base);
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid period_size setting");
- return false;
- }
-
- fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
- if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
- ao_base_finish(&fd->base);
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid nb_buffers setting");
- return false;
- }
-
- return &fd->base;
-}
-
-static void
-ffado_finish(struct audio_output *ao)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- g_free(fd->device_name);
- ao_base_finish(&fd->base);
- g_free(fd);
-}
-
-static bool
-ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
- GError **error_r)
-{
- char *buffer = (char *)stream->buffer;
- if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
- buffer) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "failed to configure stream buffer");
- return false;
- }
-
- if (ffado_streaming_playback_stream_onoff(dev, stream->number,
- 1) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "failed to disable stream");
- return false;
- }
-
- return true;
-}
-
-static bool
-ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
- GError **error_r)
-{
- assert(fd != NULL);
- assert(fd->dev != NULL);
- assert(audio_format->channels <= MAX_STREAMS);
-
- if (ffado_streaming_set_audio_datatype(fd->dev,
- ffado_audio_datatype_float) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_set_audio_datatype() failed");
- return false;
- }
-
- int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
- if (num_streams < 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_get_nb_playback_streams() failed");
- return false;
- }
-
- g_debug("there are %d playback streams", num_streams);
-
- fd->num_streams = 0;
- for (int i = 0; i < num_streams; ++i) {
- char name[256];
- ffado_streaming_get_playback_stream_name(fd->dev, i, name,
- sizeof(name) - 1);
-
- ffado_streaming_stream_type type =
- ffado_streaming_get_playback_stream_type(fd->dev, i);
- if (type != ffado_stream_type_audio) {
- g_debug("stream %d name='%s': not an audio stream",
- i, name);
- continue;
- }
-
- if (fd->num_streams >= audio_format->channels) {
- g_debug("stream %d name='%s': ignoring",
- i, name);
- continue;
- }
-
- g_debug("stream %d name='%s'", i, name);
-
- struct mpd_ffado_stream *stream =
- &fd->streams[fd->num_streams++];
-
- stream->number = i;
-
- /* allocated buffer is zeroed = silence */
- stream->buffer = g_new0(float, fd->period_size);
-
- if (!ffado_configure_stream(fd->dev, stream, error_r))
- return false;
- }
-
- if (!audio_valid_channel_count(fd->num_streams)) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "invalid channel count from libffado: %u",
- audio_format->channels);
- return false;
- }
-
- g_debug("configured %d audio streams", fd->num_streams);
-
- if (ffado_streaming_prepare(fd->dev) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_prepare() failed");
- return false;
- }
-
- if (ffado_streaming_start(fd->dev) != 0) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_start() failed");
- return false;
- }
-
- audio_format->channels = fd->num_streams;
- return true;
-}
-
-static bool
-ffado_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- /* will be converted to floating point, choose best input
- format */
- audio_format->format = SAMPLE_FORMAT_S24_P32;
-
- ffado_device_info_t device_info;
- memset(&device_info, 0, sizeof(device_info));
- if (fd->device_name != NULL) {
- device_info.nb_device_spec_strings = 1;
- device_info.device_spec_strings = &fd->device_name;
- }
-
- ffado_options_t options;
- memset(&options, 0, sizeof(options));
- options.sample_rate = audio_format->sample_rate;
- options.period_size = fd->period_size;
- options.nb_buffers = fd->nb_buffers;
- options.verbose = fd->verbose;
-
- fd->dev = ffado_streaming_init(device_info, options);
- if (fd->dev == NULL) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_init() failed");
- return false;
- }
-
- if (!ffado_configure(fd, audio_format, error_r)) {
- ffado_streaming_finish(fd->dev);
-
- for (int i = 0; i < fd->num_streams; ++i) {
- struct mpd_ffado_stream *stream = &fd->streams[i];
- g_free(stream->buffer);
- }
-
- return false;
- }
-
- fd->buffer_position = 0;
-
- return true;
-}
-
-static void
-ffado_close(struct audio_output *ao)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- ffado_streaming_stop(fd->dev);
- ffado_streaming_finish(fd->dev);
-
- for (int i = 0; i < fd->num_streams; ++i) {
- struct mpd_ffado_stream *stream = &fd->streams[i];
- g_free(stream->buffer);
- }
-}
-
-static size_t
-ffado_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
-
- /* wait for prefious buffer to finish (if it was full) */
-
- if (fd->buffer_position >= fd->period_size) {
- switch (ffado_streaming_wait(fd->dev)) {
- case ffado_wait_ok:
- case ffado_wait_xrun:
- break;
-
- default:
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_wait() failed");
- return 0;
- }
-
- fd->buffer_position = 0;
- }
-
- /* copy samples to stream buffers, non-interleaved */
-
- const int32_t *p = chunk;
- unsigned num_frames = size / sizeof(*p) / fd->num_streams;
- if (num_frames > fd->period_size - fd->buffer_position)
- num_frames = fd->period_size - fd->buffer_position;
-
- for (unsigned i = num_frames; i > 0; --i) {
- for (int stream = 0; stream < fd->num_streams; ++stream)
- fd->streams[stream].buffer[fd->buffer_position] =
- *p++ / (float)(1 << 23);
- ++fd->buffer_position;
- }
-
- /* if buffer full, transfer to device */
-
- if (fd->buffer_position >= fd->period_size &&
- /* libffado documentation says this function returns -1 on
- error, but that is a lie - it returns a boolean value,
- and "false" means error */
- !ffado_streaming_transfer_playback_buffers(fd->dev)) {
- g_set_error(error_r, ffado_output_quark(), 0,
- "ffado_streaming_transfer_playback_buffers() failed");
- return 0;
- }
-
- return num_frames * sizeof(*p) * fd->num_streams;
-}
-
-const struct audio_output_plugin ffado_output_plugin = {
- .name = "ffado",
- .init = ffado_init,
- .finish = ffado_finish,
- .open = ffado_open,
- .close = ffado_close,
- .play = ffado_play,
-};
diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h
deleted file mode 100644
index 4dde01859..000000000
--- a/src/output/ffado_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_FFADO_OUTPUT_PLUGIN_H
-#define MPD_FFADO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin ffado_output_plugin;
-
-#endif
diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c
deleted file mode 100644
index 022be0b4a..000000000
--- a/src/output/fifo_output_plugin.c
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "fifo_output_plugin.h"
-#include "output_api.h"
-#include "utils.h"
-#include "timer.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "fifo"
-
-#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
-
-struct fifo_data {
- struct audio_output base;
-
- char *path;
- int input;
- int output;
- bool created;
- struct timer *timer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-fifo_output_quark(void)
-{
- return g_quark_from_static_string("fifo_output");
-}
-
-static struct fifo_data *fifo_data_new(void)
-{
- struct fifo_data *ret;
-
- ret = g_new(struct fifo_data, 1);
-
- ret->path = NULL;
- ret->input = -1;
- ret->output = -1;
- ret->created = false;
-
- return ret;
-}
-
-static void fifo_data_free(struct fifo_data *fd)
-{
- g_free(fd->path);
- g_free(fd);
-}
-
-static void fifo_delete(struct fifo_data *fd)
-{
- g_debug("Removing FIFO \"%s\"", fd->path);
-
- if (unlink(fd->path) < 0) {
- g_warning("Could not remove FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return;
- }
-
- fd->created = false;
-}
-
-static void
-fifo_close(struct fifo_data *fd)
-{
- struct stat st;
-
- if (fd->input >= 0) {
- close(fd->input);
- fd->input = -1;
- }
-
- if (fd->output >= 0) {
- close(fd->output);
- fd->output = -1;
- }
-
- if (fd->created && (stat(fd->path, &st) == 0))
- fifo_delete(fd);
-}
-
-static bool
-fifo_make(struct fifo_data *fd, GError **error)
-{
- if (mkfifo(fd->path, 0666) < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Couldn't create FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return false;
- }
-
- fd->created = true;
-
- return true;
-}
-
-static bool
-fifo_check(struct fifo_data *fd, GError **error)
-{
- struct stat st;
-
- if (stat(fd->path, &st) < 0) {
- if (errno == ENOENT) {
- /* Path doesn't exist */
- return fifo_make(fd, error);
- }
-
- g_set_error(error, fifo_output_quark(), errno,
- "Failed to stat FIFO \"%s\": %s",
- fd->path, g_strerror(errno));
- return false;
- }
-
- if (!S_ISFIFO(st.st_mode)) {
- g_set_error(error, fifo_output_quark(), 0,
- "\"%s\" already exists, but is not a FIFO",
- fd->path);
- return false;
- }
-
- return true;
-}
-
-static bool
-fifo_open(struct fifo_data *fd, GError **error)
-{
- if (!fifo_check(fd, error))
- return false;
-
- fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
- if (fd->input < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not open FIFO \"%s\" for reading: %s",
- fd->path, g_strerror(errno));
- fifo_close(fd);
- return false;
- }
-
- fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
- if (fd->output < 0) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not open FIFO \"%s\" for writing: %s",
- fd->path, g_strerror(errno));
- fifo_close(fd);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-fifo_output_init(const struct config_param *param,
- GError **error_r)
-{
- struct fifo_data *fd;
-
- GError *error = NULL;
- char *path = config_dup_block_path(param, "path", &error);
- if (!path) {
- if (error != NULL)
- g_propagate_error(error_r, error);
- else
- g_set_error(error_r, fifo_output_quark(), 0,
- "No \"path\" parameter specified");
- return NULL;
- }
-
- fd = fifo_data_new();
- fd->path = path;
-
- if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) {
- fifo_data_free(fd);
- return NULL;
- }
-
- if (!fifo_open(fd, error_r)) {
- ao_base_finish(&fd->base);
- fifo_data_free(fd);
- return NULL;
- }
-
- return &fd->base;
-}
-
-static void
-fifo_output_finish(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- fifo_close(fd);
- ao_base_finish(&fd->base);
- fifo_data_free(fd);
-}
-
-static bool
-fifo_output_open(struct audio_output *ao, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- fd->timer = timer_new(audio_format);
-
- return true;
-}
-
-static void
-fifo_output_close(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- timer_free(fd->timer);
-}
-
-static void
-fifo_output_cancel(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
- char buf[FIFO_BUFFER_SIZE];
- int bytes = 1;
-
- timer_reset(fd->timer);
-
- while (bytes > 0 && errno != EINTR)
- bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
-
- if (bytes < 0 && errno != EAGAIN) {
- g_warning("Flush of FIFO \"%s\" failed: %s",
- fd->path, g_strerror(errno));
- }
-}
-
-static unsigned
-fifo_output_delay(struct audio_output *ao)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
-
- return fd->timer->started
- ? timer_delay(fd->timer)
- : 0;
-}
-
-static size_t
-fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct fifo_data *fd = (struct fifo_data *)ao;
- ssize_t bytes;
-
- if (!fd->timer->started)
- timer_start(fd->timer);
- timer_add(fd->timer, 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;
- }
-
- g_set_error(error, fifo_output_quark(), errno,
- "Failed to write to FIFO %s: %s",
- fd->path, g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin fifo_output_plugin = {
- .name = "fifo",
- .init = fifo_output_init,
- .finish = fifo_output_finish,
- .open = fifo_output_open,
- .close = fifo_output_close,
- .delay = fifo_output_delay,
- .play = fifo_output_play,
- .cancel = fifo_output_cancel,
-};
diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h
deleted file mode 100644
index 85f7985e1..000000000
--- a/src/output/fifo_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_FIFO_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin fifo_output_plugin;
-
-#endif
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
deleted file mode 100644
index 72de90457..000000000
--- a/src/output/httpd_client.c
+++ /dev/null
@@ -1,764 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "httpd_client.h"
-#include "httpd_internal.h"
-#include "fifo_buffer.h"
-#include "page.h"
-#include "icy_server.h"
-#include "glib_socket.h"
-
-#include <stdbool.h>
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-struct httpd_client {
- /**
- * The httpd output object this client is connected to.
- */
- struct httpd_output *httpd;
-
- /**
- * The TCP socket.
- */
- GIOChannel *channel;
-
- /**
- * The GLib main loop source id for reading from the socket,
- * and to detect errors.
- */
- guint read_source_id;
-
- /**
- * The GLib main loop source id for writing to the socket. If
- * 0, then there is no event source currently (because there
- * are no queued pages).
- */
- guint write_source_id;
-
- /**
- * For buffered reading. This pointer is only valid while the
- * HTTP request is read.
- */
- struct fifo_buffer *input;
-
- /**
- * 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.
- */
- GQueue *pages;
-
- /**
- * The #page which is currently being sent to the client.
- */
- struct page *current_page;
-
- /**
- * The amount of bytes which were already sent from
- * #current_page.
- */
- size_t current_position;
-
- /**
- * 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
- */
- guint metaint;
-
- /**
- * The metadata as #page which is currently being sent to the client.
- */
- struct 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.
- */
- guint metadata_fill;
-};
-
-static void
-httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct page *page = data;
-
- page_unref(page);
-}
-
-void
-httpd_client_free(struct httpd_client *client)
-{
- assert(client != NULL);
-
- if (client->state == RESPONSE) {
- if (client->write_source_id != 0)
- g_source_remove(client->write_source_id);
-
- if (client->current_page != NULL)
- page_unref(client->current_page);
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_free(client->pages);
- } else
- fifo_buffer_free(client->input);
-
- if (client->metadata)
- page_unref (client->metadata);
-
- g_source_remove(client->read_source_id);
- g_io_channel_unref(client->channel);
- g_free(client);
-}
-
-/**
- * Frees the client and removes it from the server's client list.
- */
-static void
-httpd_client_close(struct httpd_client *client)
-{
- assert(client != NULL);
-
- httpd_output_remove_client(client->httpd, client);
- httpd_client_free(client);
-}
-
-/**
- * Switch the client to the "RESPONSE" state.
- */
-static void
-httpd_client_begin_response(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- client->state = RESPONSE;
- client->write_source_id = 0;
- client->pages = g_queue_new();
- client->current_page = NULL;
-
- httpd_output_send_header(client->httpd, client);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-static bool
-httpd_client_handle_line(struct httpd_client *client, const char *line)
-{
- assert(client->state != RESPONSE);
-
- if (client->state == REQUEST) {
- if (strncmp(line, "GET /", 5) != 0) {
- /* only GET is supported */
- g_warning("malformed request line from client");
- return false;
- }
-
- line = strchr(line + 5, ' ');
- if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) {
- /* HTTP/0.9 without request headers */
- httpd_client_begin_response(client);
- return true;
- }
-
- /* after the request line, request headers follow */
- client->state = HEADERS;
- return true;
- } else {
- if (*line == 0) {
- /* empty line: request is finished */
- httpd_client_begin_response(client);
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
- /* Send icy metadata */
- client->metadata_requested =
- client->metadata_supported;
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
- /* Send as dlna */
- client->dlna_streaming_requested = true;
- /* metadata is not supported by dlna streaming, so disable it */
- client->metadata_supported = false;
- client->metadata_requested = false;
- return true;
- }
-
- /* expect more request headers */
- return true;
- }
-}
-
-/**
- * Check if a complete line of input is present in the input buffer,
- * and duplicates it. It is removed from the input buffer. The
- * return value has to be freed with g_free().
- */
-static char *
-httpd_client_read_line(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- /* empty input buffer */
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- /* incomplete line */
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- /* remove trailing whitespace (e.g. '\r') */
- return g_strchomp(line);
-}
-
-/**
- * Sends the status line and response headers to the client.
- */
-static bool
-httpd_client_send_response(struct httpd_client *client)
-{
- char buffer[1024];
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->state == RESPONSE);
-
- if (client->dlna_streaming_requested) {
- g_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",
- client->httpd->content_type);
-
- } else if (client->metadata_requested) {
- gchar *metadata_header;
-
- metadata_header = icy_server_metadata_header(
- client->httpd->name,
- client->httpd->genre,
- client->httpd->website,
- client->httpd->content_type,
- client->metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- g_free(metadata_header);
-
- } else { /* revert to a normal HTTP request */
- g_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",
- client->httpd->content_type);
- }
-
- status = g_io_channel_write_chars(client->channel,
- buffer, strlen(buffer),
- &bytes_written, &error);
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- return false;
-}
-
-/**
- * Data has been received from the client and it is appended to the
- * input buffer.
- */
-static bool
-httpd_client_received(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- char *line;
- bool success;
-
- while ((line = httpd_client_read_line(client)) != NULL) {
- success = httpd_client_handle_line(client, line);
- g_free(line);
- if (!success) {
- assert(client->state != RESPONSE);
- return false;
- }
-
- if (client->state == RESPONSE) {
- if (!fifo_buffer_is_empty(client->input)) {
- g_warning("unexpected input from client");
- return false;
- }
-
- fifo_buffer_free(client->input);
-
- return httpd_client_send_response(client);
- }
- }
-
- return true;
-}
-
-static bool
-httpd_client_read(struct httpd_client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- if (client->state == RESPONSE) {
- /* the client has already sent the request, and he
- must not send more */
- char buffer[1];
-
- status = g_io_channel_read_chars(client->channel, buffer,
- sizeof(buffer), &bytes_read,
- NULL);
- if (status == G_IO_STATUS_NORMAL)
- g_warning("unexpected input from client");
-
- return false;
- }
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("buffer overflow");
- return false;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- fifo_buffer_append(client->input, bytes_read);
- return httpd_client_received(client);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return true;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client: %s",
- error->message);
- g_error_free(error);
- return false;
- }
-
- /* unreachable */
- return false;
-}
-
-static gboolean
-httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- bool ret;
-
- g_mutex_lock(httpd->mutex);
-
- if (condition == G_IO_IN && httpd_client_read(client)) {
- ret = true;
- } else {
- httpd_client_close(client);
- ret = false;
- }
-
- g_mutex_unlock(httpd->mutex);
-
- return ret;
-}
-
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
-{
- struct httpd_client *client = g_new(struct httpd_client, 1);
-
- client->httpd = httpd;
-
- client->channel = g_io_channel_new_socket(fd);
-
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->read_source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- httpd_client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
- client->state = REQUEST;
-
- client->dlna_streaming_requested = false;
- client->metadata_supported = metadata_supported;
- client->metadata_requested = false;
- client->metadata_sent = true;
- client->metaint = 8192; /*TODO: just a std value */
- client->metadata = NULL;
- client->metadata_current_position = 0;
- client->metadata_fill = 0;
-
- return client;
-}
-
-static void
-httpd_client_add_page_size(gpointer data, gpointer user_data)
-{
- struct page *page = data;
- size_t *size = user_data;
-
- *size += page->size;
-}
-
-size_t
-httpd_client_queue_size(const struct httpd_client *client)
-{
- size_t size = 0;
-
- if (client->state != RESPONSE)
- return 0;
-
- g_queue_foreach(client->pages, httpd_client_add_page_size, &size);
- return size;
-}
-
-void
-httpd_client_cancel(struct httpd_client *client)
-{
- if (client->state != RESPONSE)
- return;
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_clear(client->pages);
-
- if (client->write_source_id != 0 && client->current_page == NULL) {
- g_source_remove(client->write_source_id);
- client->write_source_id = 0;
- }
-}
-
-static GIOStatus
-write_page_to_channel(GIOChannel *channel,
- const struct page *page, size_t position,
- gsize *bytes_written_r, GError **error)
-{
- assert(channel != NULL);
- assert(page != NULL);
- assert(position < page->size);
-
- return g_io_channel_write_chars(channel,
- (const gchar*)page->data + position,
- page->size - position,
- bytes_written_r, error);
-}
-
-static GIOStatus
-write_n_bytes_to_channel(GIOChannel *channel, const struct page *page,
- size_t position, gint n,
- gsize *bytes_written_r, GError **error)
-{
- GIOStatus status;
-
- assert(channel != NULL);
- assert(page != NULL);
- assert(position < page->size);
-
- if (n == -1) {
- status = write_page_to_channel (channel, page, position,
- bytes_written_r, error);
- } else {
- status = g_io_channel_write_chars(channel,
- (const gchar*)page->data + position,
- n, bytes_written_r, error);
- }
-
- return status;
-}
-
-static gint
-bytes_left_till_metadata (struct httpd_client *client)
-{
- assert(client != NULL);
-
- if (client->metadata_requested &&
- client->current_page->size - client->current_position
- > client->metaint - client->metadata_fill)
- return client->metaint - client->metadata_fill;
-
- return -1;
-}
-
-static gboolean
-httpd_client_out_event(GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition, gpointer data)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
- gint bytes_to_write;
-
- g_mutex_lock(httpd->mutex);
-
- assert(condition == G_IO_OUT);
- assert(client->state == RESPONSE);
-
- if (client->write_source_id == 0) {
- /* another thread has removed the event source while
- this thread was waiting for httpd->mutex */
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- if (client->current_page == NULL) {
- client->current_page = g_queue_pop_head(client->pages);
- client->current_position = 0;
- }
-
- bytes_to_write = bytes_left_till_metadata(client);
-
- if (bytes_to_write == 0) {
- gint metadata_to_write;
-
- metadata_to_write = client->metadata_current_position;
-
- if (!client->metadata_sent) {
- status = write_page_to_channel(source,
- client->metadata,
- metadata_to_write,
- &bytes_written, &error);
-
- client->metadata_current_position += bytes_written;
-
- if (client->metadata->size
- - client->metadata_current_position == 0) {
- client->metadata_fill = 0;
- client->metadata_current_position = 0;
- client->metadata_sent = true;
- }
- } else {
- struct page *empty_meta;
- guchar empty_data = 0;
-
- empty_meta = page_new_copy(&empty_data, 1);
-
- status = write_page_to_channel(source,
- empty_meta,
- metadata_to_write,
- &bytes_written, &error);
-
- client->metadata_current_position += bytes_written;
-
- if (empty_meta->size
- - client->metadata_current_position == 0) {
- client->metadata_fill = 0;
- client->metadata_current_position = 0;
- }
- }
-
- bytes_written = 0;
- } else {
- status = write_n_bytes_to_channel(source, client->current_page,
- client->current_position, bytes_to_write,
- &bytes_written, &error);
- }
-
- switch (status) {
- case G_IO_STATUS_NORMAL:
- client->current_position += bytes_written;
- assert(client->current_position <= client->current_page->size);
-
- if (client->metadata_requested)
- client->metadata_fill += bytes_written;
-
- if (client->current_position >= client->current_page->size) {
- page_unref(client->current_page);
- client->current_page = NULL;
-
- if (g_queue_is_empty(client->pages)) {
- /* all pages are sent: remove the
- event source */
- client->write_source_id = 0;
-
- g_mutex_unlock(httpd->mutex);
- return false;
- }
- }
-
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_AGAIN:
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-}
-
-void
-httpd_client_send(struct httpd_client *client, struct page *page)
-{
- if (client->state != RESPONSE)
- /* the client is still writing the HTTP request */
- return;
-
- page_ref(page);
- g_queue_push_tail(client->pages, page);
-
- if (client->write_source_id == 0)
- client->write_source_id =
- g_io_add_watch(client->channel, G_IO_OUT,
- httpd_client_out_event, client);
-}
-
-void
-httpd_client_send_metadata(struct httpd_client *client, struct page *page)
-{
- if (client->metadata) {
- page_unref(client->metadata);
- client->metadata = NULL;
- }
-
- g_return_if_fail (page);
-
- page_ref(page);
- client->metadata = page;
- client->metadata_sent = false;
-}
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
deleted file mode 100644
index 739163f42..000000000
--- a/src/output/httpd_client.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_HTTPD_CLIENT_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct httpd_client;
-struct httpd_output;
-struct page;
-
-/**
- * Creates a new #httpd_client object
- *
- * @param httpd the HTTP output device
- * @param fd the socket file descriptor
- */
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported);
-
-/**
- * Frees memory and resources allocated by the #httpd_client object.
- * This does not remove it from the #httpd_output object.
- */
-void
-httpd_client_free(struct httpd_client *client);
-
-/**
- * Returns the total size of this client's page queue.
- */
-size_t
-httpd_client_queue_size(const struct httpd_client *client);
-
-/**
- * Clears the page queue.
- */
-void
-httpd_client_cancel(struct httpd_client *client);
-
-/**
- * Appends a page to the client's queue.
- */
-void
-httpd_client_send(struct httpd_client *client, struct page *page);
-
-/**
- * Sends the passed metadata.
- */
-void
-httpd_client_send_metadata(struct httpd_client *client, struct page *page);
-
-#endif
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
deleted file mode 100644
index 5dcb8ab9b..000000000
--- a/src/output/httpd_internal.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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.h"
-#include "timer.h"
-
-#include <glib.h>
-
-#include <stdbool.h>
-
-struct httpd_client;
-
-struct httpd_output {
- struct audio_output base;
-
- /**
- * True if the audio output is open and accepts client
- * connections.
- */
- bool open;
-
- /**
- * The configured encoder plugin.
- */
- struct 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.
- */
- GMutex *mutex;
-
- /**
- * A #timer object to synchronize this output with the
- * wallclock.
- */
- struct timer *timer;
-
- /**
- * The listener socket.
- */
- struct server_socket *server_socket;
-
- /**
- * The header page, which is sent to every client on connect.
- */
- struct page *header;
-
- /**
- * The metadata, which is sent to every client.
- */
- struct 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.
- */
- GList *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.
- */
- guint clients_max, clients_cnt;
-};
-
-/**
- * Removes a client from the httpd_output.clients linked list.
- */
-void
-httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client);
-
-/**
- * Sends the encoder header to the client. This is called right after
- * the response headers have been sent.
- */
-void
-httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client);
-
-#endif
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
deleted file mode 100644
index 1d730df7f..000000000
--- a/src/output/httpd_output_plugin.c
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "httpd_output_plugin.h"
-#include "httpd_internal.h"
-#include "httpd_client.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "resolver.h"
-#include "page.h"
-#include "icy_server.h"
-#include "fd_util.h"
-#include "server_socket.h"
-
-#include <assert.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#ifdef HAVE_LIBWRAP
-#include <sys/socket.h> /* needed for AF_UNIX */
-#include <tcpd.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-httpd_output_quark(void)
-{
- return g_quark_from_static_string("httpd_output");
-}
-
-/**
- * Check whether there is at least one client.
- *
- * Caller must lock the mutex.
- */
-G_GNUC_PURE
-static bool
-httpd_output_has_clients(const struct httpd_output *httpd)
-{
- return httpd->clients != NULL;
-}
-
-/**
- * Check whether there is at least one client.
- */
-G_GNUC_PURE
-static bool
-httpd_output_lock_has_clients(const struct httpd_output *httpd)
-{
- g_mutex_lock(httpd->mutex);
- bool result = httpd_output_has_clients(httpd);
- g_mutex_unlock(httpd->mutex);
- return result;
-}
-
-static void
-httpd_listen_in_event(int fd, const struct sockaddr *address,
- size_t address_length, int uid, void *ctx);
-
-static bool
-httpd_output_bind(struct httpd_output *httpd, GError **error_r)
-{
- httpd->open = false;
-
- g_mutex_lock(httpd->mutex);
- bool success = server_socket_open(httpd->server_socket, error_r);
- g_mutex_unlock(httpd->mutex);
-
- return success;
-}
-
-static void
-httpd_output_unbind(struct httpd_output *httpd)
-{
- assert(!httpd->open);
-
- g_mutex_lock(httpd->mutex);
- server_socket_close(httpd->server_socket);
- g_mutex_unlock(httpd->mutex);
-}
-
-static struct audio_output *
-httpd_output_init(const struct config_param *param,
- GError **error)
-{
- struct httpd_output *httpd = g_new(struct httpd_output, 1);
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) {
- g_free(httpd);
- return NULL;
- }
-
- /* read configuration */
- httpd->name =
- config_get_block_string(param, "name", "Set name in config");
- httpd->genre =
- config_get_block_string(param, "genre", "Set genre in config");
- httpd->website =
- config_get_block_string(param, "website", "Set website in config");
-
- guint port = config_get_block_unsigned(param, "port", 8000);
-
- const char *encoder_name =
- config_get_block_string(param, "encoder", "vorbis");
- const struct encoder_plugin *encoder_plugin =
- encoder_plugin_get(encoder_name);
- if (encoder_plugin == NULL) {
- g_set_error(error, httpd_output_quark(), 0,
- "No such encoder: %s", encoder_name);
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
-
- /* set up bind_to_address */
-
- httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
-
- const char *bind_to_address =
- config_get_block_string(param, "bind_to_address", NULL);
- bool success = bind_to_address != NULL &&
- strcmp(bind_to_address, "any") != 0
- ? server_socket_add_host(httpd->server_socket, bind_to_address,
- port, error)
- : server_socket_add_port(httpd->server_socket, port, error);
- if (!success) {
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- /* initialize metadata */
- httpd->metadata = NULL;
- httpd->unflushed_input = 0;
-
- /* initialize encoder */
-
- httpd->encoder = encoder_init(encoder_plugin, param, error);
- if (httpd->encoder == NULL) {
- ao_base_finish(&httpd->base);
- g_free(httpd);
- return NULL;
- }
-
- /* determine content type */
- httpd->content_type = encoder_get_mime_type(httpd->encoder);
- if (httpd->content_type == NULL) {
- httpd->content_type = "application/octet-stream";
- }
-
- httpd->mutex = g_mutex_new();
-
- return &httpd->base;
-}
-
-static void
-httpd_output_finish(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd->metadata)
- page_unref(httpd->metadata);
-
- encoder_finish(httpd->encoder);
- server_socket_free(httpd->server_socket);
- g_mutex_free(httpd->mutex);
- ao_base_finish(&httpd->base);
- g_free(httpd);
-}
-
-/**
- * Creates a new #httpd_client object and adds it into the
- * httpd_output.clients linked list.
- */
-static void
-httpd_client_add(struct httpd_output *httpd, int fd)
-{
- struct httpd_client *client =
- httpd_client_new(httpd, fd,
- httpd->encoder->plugin->tag == NULL);
-
- httpd->clients = g_list_prepend(httpd->clients, client);
- httpd->clients_cnt++;
-
- /* pass metadata to client */
- if (httpd->metadata)
- httpd_client_send_metadata(client, httpd->metadata);
-}
-
-static void
-httpd_listen_in_event(int fd, const struct sockaddr *address,
- size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
-{
- struct httpd_output *httpd = ctx;
-
- /* 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, NULL);
- 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 */
- g_warning("libwrap refused connection (libwrap=%s) from %s",
- progname, hostaddr);
- g_free(hostaddr);
- close_socket(fd);
- g_mutex_unlock(httpd->mutex);
- return;
- }
-
- g_free(hostaddr);
- }
-#else
- (void)address;
- (void)address_length;
-#endif /* HAVE_WRAP */
-
- g_mutex_lock(httpd->mutex);
-
- if (fd >= 0) {
- /* can we allow additional client */
- if (httpd->open &&
- (httpd->clients_max == 0 ||
- httpd->clients_cnt < httpd->clients_max))
- httpd_client_add(httpd, fd);
- else
- close_socket(fd);
- } else if (fd < 0 && errno != EINTR) {
- g_warning("accept() failed: %s", g_strerror(errno));
- }
-
- g_mutex_unlock(httpd->mutex);
-}
-
-/**
- * Reads data from the encoder (as much as available) and returns it
- * as a new #page object.
- */
-static struct page *
-httpd_output_read_page(struct httpd_output *httpd)
-{
- if (httpd->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(httpd->encoder, NULL);
- httpd->unflushed_input = 0;
- }
-
- size_t size = 0;
- do {
- size_t nbytes = encoder_read(httpd->encoder,
- httpd->buffer + size,
- sizeof(httpd->buffer) - size);
- if (nbytes == 0)
- break;
-
- httpd->unflushed_input = 0;
-
- size += nbytes;
- } while (size < sizeof(httpd->buffer));
-
- if (size == 0)
- return NULL;
-
- return page_new_copy(httpd->buffer, size);
-}
-
-static bool
-httpd_output_encoder_open(struct httpd_output *httpd,
- struct audio_format *audio_format,
- GError **error)
-{
- if (!encoder_open(httpd->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 */
- httpd->header = httpd_output_read_page(httpd);
-
- httpd->unflushed_input = 0;
-
- return true;
-}
-
-static bool
-httpd_output_enable(struct audio_output *ao, GError **error_r)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- return httpd_output_bind(httpd, error_r);
-}
-
-static void
-httpd_output_disable(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- httpd_output_unbind(httpd);
-}
-
-static bool
-httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
-
- /* open the encoder */
-
- if (!httpd_output_encoder_open(httpd, audio_format, error)) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- /* initialize other attributes */
-
- httpd->clients = NULL;
- httpd->clients_cnt = 0;
- httpd->timer = timer_new(audio_format);
-
- httpd->open = true;
-
- g_mutex_unlock(httpd->mutex);
- return true;
-}
-
-static void
-httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_free(client);
-}
-
-static void
-httpd_output_close(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
-
- httpd->open = false;
-
- timer_free(httpd->timer);
-
- g_list_foreach(httpd->clients, httpd_client_delete, NULL);
- g_list_free(httpd->clients);
-
- if (httpd->header != NULL)
- page_unref(httpd->header);
-
- encoder_close(httpd->encoder);
-
- g_mutex_unlock(httpd->mutex);
-}
-
-void
-httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client)
-{
- assert(httpd != NULL);
- assert(client != NULL);
-
- httpd->clients = g_list_remove(httpd->clients, client);
- httpd->clients_cnt--;
-}
-
-void
-httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client)
-{
- if (httpd->header != NULL)
- httpd_client_send(client, httpd->header);
-}
-
-static unsigned
-httpd_output_delay(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (!httpd_output_lock_has_clients(httpd) && 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 */
- timer_reset(httpd->timer);
-
- /* 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->started
- ? timer_delay(httpd->timer)
- : 0;
-}
-
-static void
-httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- if (httpd_client_queue_size(client) > 256 * 1024) {
- g_debug("client is too slow, flushing its queue");
- httpd_client_cancel(client);
- }
-}
-
-static void
-httpd_client_send_page(gpointer data, gpointer user_data)
-{
- struct httpd_client *client = data;
- struct page *page = user_data;
-
- httpd_client_send(client, page);
-}
-
-/**
- * Broadcasts a page struct to all clients.
- */
-static void
-httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page)
-{
- assert(page != NULL);
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_send_page, page);
- g_mutex_unlock(httpd->mutex);
-}
-
-/**
- * Broadcasts data from the encoder to all clients.
- */
-static void
-httpd_output_encoder_to_clients(struct httpd_output *httpd)
-{
- struct page *page;
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_check_queue, NULL);
- g_mutex_unlock(httpd->mutex);
-
- while ((page = httpd_output_read_page(httpd)) != NULL) {
- httpd_output_broadcast_page(httpd, page);
- page_unref(page);
- }
-}
-
-static bool
-httpd_output_encode_and_play(struct httpd_output *httpd,
- const void *chunk, size_t size, GError **error)
-{
- if (!encoder_write(httpd->encoder, chunk, size, error))
- return false;
-
- httpd->unflushed_input += size;
-
- httpd_output_encoder_to_clients(httpd);
-
- return true;
-}
-
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd_output_lock_has_clients(httpd)) {
- if (!httpd_output_encode_and_play(httpd, chunk, size, error_r))
- return 0;
- }
-
- if (!httpd->timer->started)
- timer_start(httpd->timer);
- timer_add(httpd->timer, size);
-
- return size;
-}
-
-static bool
-httpd_output_pause(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- if (httpd_output_lock_has_clients(httpd)) {
- static const char silence[1020];
- return httpd_output_play(ao, silence, sizeof(silence),
- NULL) > 0;
- } else {
- return true;
- }
-}
-
-static void
-httpd_send_metadata(gpointer data, gpointer user_data)
-{
- struct httpd_client *client = data;
- struct page *icy_metadata = user_data;
-
- httpd_client_send_metadata(client, icy_metadata);
-}
-
-static void
-httpd_output_tag(struct audio_output *ao, const struct tag *tag)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- assert(tag != NULL);
-
- if (httpd->encoder->plugin->tag != NULL) {
- /* embed encoder tags */
-
- /* flush the current stream, and end it */
-
- encoder_pre_tag(httpd->encoder, NULL);
- httpd_output_encoder_to_clients(httpd);
-
- /* send the tag to the encoder - which starts a new
- stream now */
-
- encoder_tag(httpd->encoder, tag, NULL);
-
- /* the first page generated by the encoder will now be
- used as the new "header" page, which is sent to all
- new clients */
-
- struct page *page = httpd_output_read_page(httpd);
- if (page != NULL) {
- if (httpd->header != NULL)
- page_unref(httpd->header);
- httpd->header = page;
- httpd_output_broadcast_page(httpd, page);
- }
- } else {
- /* use Icy-Metadata */
-
- if (httpd->metadata != NULL)
- page_unref (httpd->metadata);
-
- httpd->metadata =
- icy_server_metadata_page(tag, TAG_ALBUM,
- TAG_ARTIST, TAG_TITLE,
- TAG_NUM_OF_ITEM_TYPES);
- if (httpd->metadata != NULL) {
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients,
- httpd_send_metadata, httpd->metadata);
- g_mutex_unlock(httpd->mutex);
- }
- }
-}
-
-static void
-httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_cancel(client);
-}
-
-static void
-httpd_output_cancel(struct audio_output *ao)
-{
- struct httpd_output *httpd = (struct httpd_output *)ao;
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL);
- g_mutex_unlock(httpd->mutex);
-}
-
-const struct audio_output_plugin httpd_output_plugin = {
- .name = "httpd",
- .init = httpd_output_init,
- .finish = httpd_output_finish,
- .enable = httpd_output_enable,
- .disable = httpd_output_disable,
- .open = httpd_output_open,
- .close = httpd_output_close,
- .delay = httpd_output_delay,
- .send_tag = httpd_output_tag,
- .play = httpd_output_play,
- .pause = httpd_output_pause,
- .cancel = httpd_output_cancel,
-};
diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h
deleted file mode 100644
index d0eb1533f..000000000
--- a/src/output/httpd_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_HTTPD_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin httpd_output_plugin;
-
-#endif
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
deleted file mode 100644
index d5c8ca412..000000000
--- a/src/output/jack_output_plugin.c
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "jack_output_plugin.h"
-#include "output_api.h"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
-enum {
- MAX_PORTS = 16,
-};
-
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
-
-struct jack_data {
- 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 */
- struct audio_format 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;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const struct jack_data *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)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jack_default_audio_sample_t *out;
-
- 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) {
- out = 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) {
- out = jack_port_get_buffer(jd->ports[i], nframes);
- if (out == NULL)
- /* 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) {
- out = jack_port_get_buffer(jd->ports[i], nframes);
- if (out == NULL)
- /* 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)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(struct jack_data *jd, struct audio_format *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 != SAMPLE_FORMAT_S16 &&
- audio_format->format != SAMPLE_FORMAT_S24_P32)
- audio_format->format = SAMPLE_FORMAT_S24_P32;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- g_warning("%s", msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- g_message("%s", msg);
-}
-#endif
-
-/**
- * Disconnect the JACK client.
- */
-static void
-mpd_jack_disconnect(struct jack_data *jd)
-{
- assert(jd != NULL);
- assert(jd->client != NULL);
-
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
-}
-
-/**
- * Connect the JACK client and performs some basic setup
- * (e.g. register callbacks).
- */
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error_r)
-{
- jack_status_t status;
-
- assert(jd != NULL);
-
- jd->shutdown = false;
-
- jd->client = jack_client_open(jd->name, jd->options, &status,
- jd->server_name);
- if (jd->client == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "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] == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "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(int line, const char *source, char **dest, GError **error_r)
-{
- char **list = g_strsplit(source, ",", 0);
- unsigned n = 0;
-
- for (n = 0; list[n] != NULL; ++n) {
- if (n >= MAX_PORTS) {
- g_set_error(error_r, jack_output_quark(), 0,
- "too many port names in line %d",
- line);
- return 0;
- }
-
- dest[n] = list[n];
- }
-
- g_free(list);
-
- if (n == 0) {
- g_set_error(error_r, jack_output_quark(), 0,
- "at least one port name expected in line %d",
- line);
- return 0;
- }
-
- return n;
-}
-
-static struct audio_output *
-mpd_jack_init(const struct config_param *param, GError **error_r)
-{
- struct jack_data *jd = g_new(struct jack_data, 1);
-
- if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) {
- g_free(jd);
- return NULL;
- }
-
- const char *value;
-
- jd->options = JackNullOption;
-
- jd->name = config_get_block_string(param, "client_name", NULL);
- if (jd->name != NULL)
- 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 = config_get_block_string(param, "server_name", NULL);
- if (jd->server_name != NULL)
- jd->options |= JackServerName;
-
- if (!config_get_block_bool(param, "autostart", false))
- jd->options |= JackNoStartServer;
-
- /* configure the source ports */
-
- value = config_get_block_string(param, "source_ports", "left,right");
- jd->num_source_ports = parse_port_list(param->line, value,
- jd->source_ports, error_r);
- if (jd->num_source_ports == 0)
- return NULL;
-
- /* configure the destination ports */
-
- value = config_get_block_string(param, "destination_ports", NULL);
- if (value == NULL) {
- /* compatibility with MPD < 0.16 */
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL)
- g_warning("deprecated option 'ports' in line %d",
- param->line);
- }
-
- if (value != NULL) {
- jd->num_destination_ports =
- parse_port_list(param->line, value,
- jd->destination_ports, error_r);
- if (jd->num_destination_ports == 0)
- return NULL;
- } else {
- jd->num_destination_ports = 0;
- }
-
- if (jd->num_destination_ports > 0 &&
- jd->num_destination_ports != jd->num_source_ports)
- g_warning("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 =
- config_get_block_unsigned(param, "ringbuffer_size", 32768);
-
- 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)
-{
- struct jack_data *jd = (struct jack_data *)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]);
-
- ao_base_finish(&jd->base);
- g_free(jd);
-}
-
-static bool
-mpd_jack_enable(struct audio_output *ao, GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = NULL;
-
- return mpd_jack_connect(jd, error_r);
-}
-
-static void
-mpd_jack_disable(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- if (jd->client != NULL)
- mpd_jack_disconnect(jd);
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-/**
- * Stops the playback on the JACK connection.
- */
-static void
-mpd_jack_stop(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client == NULL)
- 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(struct jack_data *jd, GError **error_r)
-{
- const char *destination_ports[MAX_PORTS], **jports;
- const char *duplicate_port = NULL;
- unsigned num_destination_ports;
-
- assert(jd->client != NULL);
- 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] == NULL)
- 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) ) {
- g_set_error(error_r, jack_output_quark(), 0,
- "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, NULL, NULL,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == NULL) {
- g_set_error(error_r, jack_output_quark(), 0,
- "no ports found");
- mpd_jack_stop(jd);
- return false;
- }
-
- assert(*jports != NULL);
-
- for (num_destination_ports = 0;
- num_destination_ports < MAX_PORTS &&
- jports[num_destination_ports] != NULL;
- ++num_destination_ports) {
- g_debug("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 = NULL;
- }
-
- 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) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- destination_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (duplicate_port != NULL) {
- /* 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) {
- g_set_error(error_r, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- duplicate_port);
-
- if (jports != NULL)
- free(jports);
-
- mpd_jack_stop(jd);
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- assert(jd != NULL);
-
- jd->pause = false;
-
- if (jd->client != NULL && jd->shutdown)
- mpd_jack_disconnect(jd);
-
- if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
- return false;
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- if (!mpd_jack_start(jd, error_r))
- return false;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- mpd_jack_stop(jd);
-}
-
-static unsigned
-mpd_jack_delay(struct audio_output *ao)
-{
- struct jack_data *jd = (struct jack_data *)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(struct jack_data *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], (void*)&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(struct jack_data *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], (void*)&sample,
- sizeof(sample));
- }
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.format) {
- case SAMPLE_FORMAT_S16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error_r)
-{
- struct jack_data *jd = (struct jack_data *)ao;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- jd->pause = false;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error_r, jack_output_quark(), 0,
- "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)
-{
- struct jack_data *jd = (struct jack_data *)ao;
-
- if (jd->shutdown)
- return false;
-
- jd->pause = true;
-
- return true;
-}
-
-const struct audio_output_plugin jack_output_plugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .enable = mpd_jack_enable,
- .disable = mpd_jack_disable,
- .open = mpd_jack_open,
- .delay = mpd_jack_delay,
- .play = mpd_jack_play,
- .pause = mpd_jack_pause,
- .close = mpd_jack_close,
-};
diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h
deleted file mode 100644
index 2f94ae7dc..000000000
--- a/src/output/jack_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_JACK_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin jack_output_plugin;
-
-#endif
diff --git a/src/output/mvp_output_plugin.c b/src/output/mvp_output_plugin.c
deleted file mode 100644
index 37e0f7c93..000000000
--- a/src/output/mvp_output_plugin.c
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Media MVP audio output based on code from MVPMC project:
- * http://mvpmc.sourceforge.net/
- */
-
-#include "config.h"
-#include "mvp_output_plugin.h"
-#include "output_api.h"
-#include "fd_util.h"
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mvp"
-
-typedef struct {
- unsigned long dsp_status;
- unsigned long stream_decode_type;
- unsigned long sample_rate;
- unsigned long bit_rate;
- unsigned long raw[64 / sizeof(unsigned long)];
-} aud_status_t;
-
-#define MVP_SET_AUD_STOP _IOW('a',1,int)
-#define MVP_SET_AUD_PLAY _IOW('a',2,int)
-#define MVP_SET_AUD_PAUSE _IOW('a',3,int)
-#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int)
-#define MVP_SET_AUD_SRC _IOW('a',5,int)
-#define MVP_SET_AUD_MUTE _IOW('a',6,int)
-#define MVP_SET_AUD_BYPASS _IOW('a',8,int)
-#define MVP_SET_AUD_CHANNEL _IOW('a',9,int)
-#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t)
-#define MVP_SET_AUD_VOLUME _IOW('a',13,int)
-#define MVP_GET_AUD_VOLUME _IOR('a',14,int)
-#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int)
-#define MVP_SET_AUD_FORMAT _IOW('a',16,int)
-#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*)
-#define MVP_SET_AUD_STC _IOW('a',22,long long int *)
-#define MVP_SET_AUD_SYNC _IOW('a',23,int)
-#define MVP_SET_AUD_END_STREAM _IOW('a',25,int)
-#define MVP_SET_AUD_RESET _IOW('a',26,int)
-#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int)
-#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*)
-
-struct mvp_data {
- struct audio_output base;
-
- struct audio_format audio_format;
- int fd;
-};
-
-static const unsigned mvp_sample_rates[][3] = {
- {9, 8000, 32000},
- {10, 11025, 44100},
- {11, 12000, 48000},
- {1, 16000, 32000},
- {2, 22050, 44100},
- {3, 24000, 48000},
- {5, 32000, 32000},
- {0, 44100, 44100},
- {7, 48000, 48000},
- {13, 64000, 32000},
- {14, 88200, 44100},
- {15, 96000, 48000}
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-mvp_output_quark(void)
-{
- return g_quark_from_static_string("mvp_output");
-}
-
-/**
- * Translate a sample rate to a MVP sample rate.
- *
- * @param sample_rate the sample rate in Hz
- */
-static unsigned
-mvp_find_sample_rate(unsigned sample_rate)
-{
- for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i)
- if (mvp_sample_rates[i][1] == sample_rate)
- return mvp_sample_rates[i][0];
-
- return (unsigned)-1;
-}
-
-static bool
-mvp_output_test_default_device(void)
-{
- int fd;
-
- fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
-
- g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n",
- g_strerror(errno));
-
- return false;
-}
-
-static struct audio_output *
-mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error)
-{
- struct mvp_data *md = g_new(struct mvp_data, 1);
-
- if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) {
- g_free(md);
- return NULL;
- }
-
- md->fd = -1;
-
- return &md->base;
-}
-
-static void
-mvp_output_finish(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- ao_base_finish(&md->base);
- g_free(md);
-}
-
-static bool
-mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
- GError **error)
-{
- unsigned mix[5];
-
- switch (audio_format->channels) {
- case 1:
- mix[0] = 1;
- break;
-
- case 2:
- mix[0] = 0;
- break;
-
- default:
- g_debug("unsupported channel count %u - falling back to stereo",
- audio_format->channels);
- audio_format->channels = 2;
- mix[0] = 0;
- break;
- }
-
- /* 0,1=24bit(24) , 2,3=16bit */
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S16:
- mix[1] = 2;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- mix[1] = 0;
- break;
-
- default:
- g_debug("unsupported sample format %s - falling back to 16 bit",
- sample_format_to_string(audio_format->format));
- audio_format->format = SAMPLE_FORMAT_S16;
- mix[1] = 2;
- break;
- }
-
- mix[3] = 0; /* stream type? */
- mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN;
-
- /*
- * if there is an exact match for the frequency, use it.
- */
- mix[2] = mvp_find_sample_rate(audio_format->sample_rate);
- if (mix[2] == (unsigned)-1) {
- g_set_error(error, mvp_output_quark(), 0,
- "Can not find suitable output frequency for %u",
- audio_format->sample_rate);
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio format");
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio sync");
- return false;
- }
-
- if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Can not set audio play mode");
- return false;
- }
-
- return true;
-}
-
-static bool
-mvp_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- long long int stc = 0;
- int mix[5] = { 0, 2, 7, 1, 0 };
- bool success;
-
- md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0);
- if (md->fd < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error opening /dev/adec_pcm: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio source: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio streamtype: %s",
- g_strerror(errno));
- return false;
- }
- if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio format: %s",
- g_strerror(errno));
- return false;
- }
- ioctl(md->fd, MVP_SET_AUD_STC, &stc);
- if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
- g_set_error(error, mvp_output_quark(), errno,
- "Error setting audio streamtype: %s",
- g_strerror(errno));
- return false;
- }
-
- success = mvp_set_pcm_params(md, audio_format, error);
- if (!success)
- return false;
-
- md->audio_format = *audio_format;
- return true;
-}
-
-static void mvp_output_close(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- if (md->fd >= 0)
- close(md->fd);
- md->fd = -1;
-}
-
-static void mvp_output_cancel(struct audio_output *ao)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- if (md->fd >= 0) {
- ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
- close(md->fd);
- md->fd = -1;
- }
-}
-
-static size_t
-mvp_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct mvp_data *md = (struct mvp_data *)ao;
- ssize_t ret;
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (md->fd < 0) {
- bool success;
-
- success = mvp_output_open(ao, &md->audio_format, error);
- if (!success)
- return 0;
- }
-
- while (true) {
- ret = write(md->fd, chunk, size);
- if (ret > 0)
- return (size_t)ret;
-
- if (ret < 0) {
- if (errno == EINTR)
- continue;
-
- g_set_error(error, mvp_output_quark(), errno,
- "Failed to write: %s", g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin mvp_output_plugin = {
- .name = "mvp",
- .test_default_device = mvp_output_test_default_device,
- .init = mvp_output_init,
- .finish = mvp_output_finish,
- .open = mvp_output_open,
- .close = mvp_output_close,
- .play = mvp_output_play,
- .cancel = mvp_output_cancel,
-};
diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h
deleted file mode 100644
index e403de2b7..000000000
--- a/src/output/mvp_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_MVP_OUTPUT_PLUGIN_H
-#define MPD_MVP_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin mvp_output_plugin;
-
-#endif
diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c
deleted file mode 100644
index 9d7588fff..000000000
--- a/src/output/null_output_plugin.c
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "null_output_plugin.h"
-#include "output_api.h"
-#include "timer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-struct null_data {
- struct audio_output base;
-
- bool sync;
-
- struct timer *timer;
-};
-
-static struct audio_output *
-null_init(const struct config_param *param, GError **error_r)
-{
- struct null_data *nd = g_new(struct null_data, 1);
-
- if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) {
- g_free(nd);
- return NULL;
- }
-
- nd->sync = config_get_block_bool(param, "sync", true);
-
- return &nd->base;
-}
-
-static void
-null_finish(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- ao_base_finish(&nd->base);
- g_free(nd);
-}
-
-static bool
-null_open(struct audio_output *ao, struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (nd->sync)
- nd->timer = timer_new(audio_format);
-
- return true;
-}
-
-static void
-null_close(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (nd->sync)
- timer_free(nd->timer);
-}
-
-static unsigned
-null_delay(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- return nd->sync && nd->timer->started
- ? timer_delay(nd->timer)
- : 0;
-}
-
-static size_t
-null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct null_data *nd = (struct null_data *)ao;
- struct timer *timer = nd->timer;
-
- if (!nd->sync)
- return size;
-
- if (!timer->started)
- timer_start(timer);
- timer_add(timer, size);
-
- return size;
-}
-
-static void
-null_cancel(struct audio_output *ao)
-{
- struct null_data *nd = (struct null_data *)ao;
-
- if (!nd->sync)
- return;
-
- timer_reset(nd->timer);
-}
-
-const struct audio_output_plugin null_output_plugin = {
- .name = "null",
- .init = null_init,
- .finish = null_finish,
- .open = null_open,
- .close = null_close,
- .delay = null_delay,
- .play = null_play,
- .cancel = null_cancel,
-};
diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h
deleted file mode 100644
index 392bf0aa3..000000000
--- a/src/output/null_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_NULL_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin null_output_plugin;
-
-#endif
diff --git a/src/output/openal_output_plugin.c b/src/output/openal_output_plugin.c
deleted file mode 100644
index ebd35ef12..000000000
--- a/src/output/openal_output_plugin.c
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "openal_output_plugin.h"
-#include "output_api.h"
-
-#include <glib.h>
-
-#ifndef HAVE_OSX
-#include <AL/al.h>
-#include <AL/alc.h>
-#else
-#include <OpenAL/al.h>
-#include <OpenAL/alc.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "openal"
-
-/* should be enough for buffer size = 2048 */
-#define NUM_BUFFERS 16
-
-struct openal_data {
- struct audio_output base;
-
- const char *device_name;
- ALCdevice *device;
- ALCcontext *context;
- ALuint buffers[NUM_BUFFERS];
- unsigned filled;
- ALuint source;
- ALenum format;
- ALuint frequency;
-};
-
-static inline GQuark
-openal_output_quark(void)
-{
- return g_quark_from_static_string("openal_output");
-}
-
-static ALenum
-openal_audio_format(struct audio_format *audio_format)
-{
- /* note: cannot map SAMPLE_FORMAT_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 SAMPLE_FORMAT_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 = SAMPLE_FORMAT_S16;
- return openal_audio_format(audio_format);
- }
-}
-
-G_GNUC_PURE
-static inline ALint
-openal_get_source_i(const struct openal_data *od, ALenum param)
-{
- ALint value;
- alGetSourcei(od->source, param, &value);
- return value;
-}
-
-G_GNUC_PURE
-static inline bool
-openal_has_processed(const struct openal_data *od)
-{
- return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
-}
-
-G_GNUC_PURE
-static inline ALint
-openal_is_playing(const struct openal_data *od)
-{
- return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
-}
-
-static bool
-openal_setup_context(struct openal_data *od,
- GError **error)
-{
- od->device = alcOpenDevice(od->device_name);
-
- if (od->device == NULL) {
- g_set_error(error, openal_output_quark(), 0,
- "Error opening OpenAL device \"%s\"\n",
- od->device_name);
- return false;
- }
-
- od->context = alcCreateContext(od->device, NULL);
-
- if (od->context == NULL) {
- g_set_error(error, openal_output_quark(), 0,
- "Error creating context for \"%s\"\n",
- od->device_name);
- alcCloseDevice(od->device);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-openal_init(const struct config_param *param, GError **error_r)
-{
- const char *device_name = config_get_block_string(param, "device", NULL);
- struct openal_data *od;
-
- if (device_name == NULL) {
- device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
- }
-
- od = g_new(struct openal_data, 1);
- if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) {
- g_free(od);
- return NULL;
- }
-
- od->device_name = device_name;
-
- return &od->base;
-}
-
-static void
-openal_finish(struct audio_output *ao)
-{
- struct openal_data *od = (struct openal_data *)ao;
-
- ao_base_finish(&od->base);
- g_free(od);
-}
-
-static bool
-openal_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct openal_data *od = (struct openal_data *)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) {
- g_set_error(error, openal_output_quark(), 0,
- "Failed to generate buffers");
- return false;
- }
-
- alGenSources(1, &od->source);
-
- if (alGetError() != AL_NO_ERROR) {
- g_set_error(error, openal_output_quark(), 0,
- "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)
-{
- struct openal_data *od = (struct openal_data *)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)
-{
- struct openal_data *od = (struct openal_data *)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,
- G_GNUC_UNUSED GError **error)
-{
- struct openal_data *od = (struct openal_data *)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)
-{
- struct openal_data *od = (struct openal_data *)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 = {
- .name = "openal",
- .init = openal_init,
- .finish = openal_finish,
- .open = openal_open,
- .close = openal_close,
- .delay = openal_delay,
- .play = openal_play,
- .cancel = openal_cancel,
-};
diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h
deleted file mode 100644
index 25f6ccf46..000000000
--- a/src/output/openal_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OPENAL_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin openal_output_plugin;
-
-#endif
diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c
deleted file mode 100644
index e366a4537..000000000
--- a/src/output/oss_output_plugin.c
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "oss_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "fd_util.h"
-#include "glib_compat.h"
-
-#include <glib.h>
-
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "oss"
-
-#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_export.h"
-#endif
-
-struct oss_data {
- struct audio_output base;
-
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state export;
-#endif
-
- int fd;
- const char *device;
-
- /**
- * The current input audio format. This is needed to reopen
- * the device after cancel().
- */
- struct audio_format audio_format;
-
- /**
- * The current OSS audio format. This is needed to reopen the
- * device after cancel().
- */
- int oss_format;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-oss_output_quark(void)
-{
- return g_quark_from_static_string("oss_output");
-}
-
-static struct oss_data *
-oss_data_new(void)
-{
- struct oss_data *ret = g_new(struct oss_data, 1);
-
- ret->device = NULL;
- ret->fd = -1;
-
- return ret;
-}
-
-static void
-oss_data_free(struct oss_data *od)
-{
- g_free(od);
-}
-
-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 = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- fd = open_cloexec(default_devices[i], O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
- g_warning("Error opening OSS device \"%s\": %s\n",
- default_devices[i], g_strerror(errno));
- }
-
- return false;
-}
-
-static struct audio_output *
-oss_open_default(GError **error)
-{
- int i;
- int err[G_N_ELEMENTS(default_devices)];
- enum oss_stat ret[G_N_ELEMENTS(default_devices)];
-
- for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- ret[i] = oss_stat_device(default_devices[i], &err[i]);
- if (ret[i] == OSS_STAT_NO_ERROR) {
- struct oss_data *od = oss_data_new();
- if (!ao_base_init(&od->base, &oss_output_plugin, NULL,
- error)) {
- g_free(od);
- return NULL;
- }
-
- od->device = default_devices[i];
- return &od->base;
- }
- }
-
- for (i = G_N_ELEMENTS(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:
- g_warning("%s not found\n", dev);
- break;
- case OSS_STAT_NOT_CHAR_DEV:
- g_warning("%s is not a character device\n", dev);
- break;
- case OSS_STAT_NO_PERMS:
- g_warning("%s: permission denied\n", dev);
- break;
- case OSS_STAT_OTHER:
- g_warning("Error accessing %s: %s\n",
- dev, g_strerror(err[i]));
- }
- }
-
- g_set_error(error, oss_output_quark(), 0,
- "error trying to open default OSS device");
- return NULL;
-}
-
-static struct audio_output *
-oss_output_init(const struct config_param *param, GError **error)
-{
- const char *device = config_get_block_string(param, "device", NULL);
- if (device != NULL) {
- struct oss_data *od = oss_data_new();
- if (!ao_base_init(&od->base, &oss_output_plugin, param,
- error)) {
- g_free(od);
- return NULL;
- }
-
- od->device = device;
- return &od->base;
- }
-
- return oss_open_default(error);
-}
-
-static void
-oss_output_finish(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- ao_base_finish(&od->base);
- oss_data_free(od);
-}
-
-#ifdef AFMT_S24_PACKED
-
-static bool
-oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- pcm_export_init(&od->export);
- return true;
-}
-
-static void
-oss_output_disable(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- pcm_export_deinit(&od->export);
-}
-
-#endif
-
-static void
-oss_close(struct oss_data *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 a GError.
- */
-static enum oss_setup_result
-oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
- const char *msg, GError **error_r)
-{
- assert(fd >= 0);
- assert(value_r != NULL);
- assert(msg != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- int ret = ioctl(fd, request, value_r);
- if (ret >= 0)
- return SUCCESS;
-
- if (errno == EINVAL)
- return UNSUPPORTED;
-
- g_set_error(error_r, oss_output_quark(), errno,
- "%s: %s", msg, g_strerror(errno));
- 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 a GError.
- */
-static enum oss_setup_result
-oss_try_ioctl(int fd, unsigned long request, int value,
- const char *msg, GError **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, struct audio_format *audio_format, GError **error_r)
-{
- 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_r);
- 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_r);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format->channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", 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, struct audio_format *audio_format,
- GError **error_r)
-{
- 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_r);
- 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_r);
- 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;
- }
- }
-
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", 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(enum sample_format format)
-{
- switch (format) {
- case SAMPLE_FORMAT_UNDEFINED:
- case SAMPLE_FORMAT_FLOAT:
- case SAMPLE_FORMAT_DSD:
- return AFMT_QUERY;
-
- case SAMPLE_FORMAT_S8:
- return AFMT_S8;
-
- case SAMPLE_FORMAT_S16:
- return AFMT_S16_NE;
-
- case SAMPLE_FORMAT_S24_P32:
-#ifdef AFMT_S24_NE
- return AFMT_S24_NE;
-#else
- return AFMT_QUERY;
-#endif
-
- case SAMPLE_FORMAT_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
- * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart.
- */
-static enum sample_format
-sample_format_from_oss(int format)
-{
- switch (format) {
- case AFMT_S8:
- return SAMPLE_FORMAT_S8;
-
- case AFMT_S16_NE:
- return SAMPLE_FORMAT_S16;
-
-#ifdef AFMT_S24_PACKED
- case AFMT_S24_PACKED:
- return SAMPLE_FORMAT_S24_P32;
-#endif
-
-#ifdef AFMT_S24_NE
- case AFMT_S24_NE:
- return SAMPLE_FORMAT_S24_P32;
-#endif
-
-#ifdef AFMT_S32_NE
- case AFMT_S32_NE:
- return SAMPLE_FORMAT_S32;
-#endif
-
- default:
- return SAMPLE_FORMAT_UNDEFINED;
- }
-}
-
-/**
- * Probe one sample format.
- *
- * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on
- * error
- */
-static enum oss_setup_result
-oss_probe_sample_format(int fd, enum sample_format sample_format,
- enum sample_format *sample_format_r,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state *export,
-#endif
- GError **error_r)
-{
- 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_r);
-
-#ifdef AFMT_S24_PACKED
- if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_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_r);
- }
-#endif
-
- if (result != SUCCESS)
- return result;
-
- sample_format = sample_format_from_oss(oss_format);
- if (sample_format == SAMPLE_FORMAT_UNDEFINED)
- return UNSUPPORTED;
-
- *sample_format_r = sample_format;
- *oss_format_r = oss_format;
-
-#ifdef AFMT_S24_PACKED
- pcm_export_open(export, sample_format, 0, false, false,
- oss_format == AFMT_S24_PACKED,
- oss_format == AFMT_S24_PACKED &&
- G_BYTE_ORDER != G_LITTLE_ENDIAN);
-#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, struct audio_format *audio_format,
- int *oss_format_r,
-#ifdef AFMT_S24_PACKED
- struct pcm_export_state *export,
-#endif
- GError **error_r)
-{
- enum sample_format mpd_format;
- enum oss_setup_result result =
- oss_probe_sample_format(fd, audio_format->format,
- &mpd_format, oss_format_r,
-#ifdef AFMT_S24_PACKED
- export,
-#endif
- error_r);
- 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 enum sample_format sample_formats[] = {
- SAMPLE_FORMAT_S24_P32,
- SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S16,
- SAMPLE_FORMAT_S8,
- SAMPLE_FORMAT_UNDEFINED /* sentinel */
- };
-
- for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_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
- export,
-#endif
- error_r);
- switch (result) {
- case SUCCESS:
- audio_format->format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- g_set_error_literal(error_r, oss_output_quark(), EINVAL,
- "Failed to set sample format");
- return false;
-}
-
-/**
- * Sets up the OSS device which was opened before.
- */
-static bool
-oss_setup(struct oss_data *od, struct audio_format *audio_format,
- GError **error_r)
-{
- return oss_setup_channels(od->fd, audio_format, error_r) &&
- oss_setup_sample_rate(od->fd, audio_format, error_r) &&
- oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
-#ifdef AFMT_S24_PACKED
- &od->export,
-#endif
- error_r);
-}
-
-/**
- * Reopen the device with the saved audio_format, without any probing.
- */
-static bool
-oss_reopen(struct oss_data *od, GError **error_r)
-{
- assert(od->fd < 0);
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- g_set_error(error_r, oss_output_quark(), errno,
- "Error opening OSS device \"%s\": %s",
- od->device, g_strerror(errno));
- 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_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", 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_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", 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_r);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- g_set_error(error_r, oss_output_quark(), EINVAL,
- "%s", msg3);
- return false;
- }
-
- return true;
-}
-
-static bool
-oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- g_set_error(error, oss_output_quark(), errno,
- "Error opening OSS device \"%s\": %s",
- od->device, g_strerror(errno));
- 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)
-{
- struct oss_data *od = (struct oss_data *)ao;
-
- oss_close(od);
-}
-
-static void
-oss_output_cancel(struct audio_output *ao)
-{
- struct oss_data *od = (struct oss_data *)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,
- GError **error)
-{
- struct oss_data *od = (struct oss_data *)ao;
- ssize_t ret;
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_reopen(od, error))
- return 0;
-
-#ifdef AFMT_S24_PACKED
- chunk = pcm_export(&od->export, chunk, size, &size);
-#endif
-
- while (true) {
- ret = write(od->fd, chunk, size);
- if (ret > 0) {
-#ifdef AFMT_S24_PACKED
- ret = pcm_export_source_size(&od->export, ret);
-#endif
- return ret;
- }
-
- if (ret < 0 && errno != EINTR) {
- g_set_error(error, oss_output_quark(), errno,
- "Write error on %s: %s",
- od->device, g_strerror(errno));
- return 0;
- }
- }
-}
-
-const struct audio_output_plugin oss_output_plugin = {
- .name = "oss",
- .test_default_device = oss_output_test_default_device,
- .init = oss_output_init,
- .finish = oss_output_finish,
-#ifdef AFMT_S24_PACKED
- .enable = oss_output_enable,
- .disable = oss_output_disable,
-#endif
- .open = oss_output_open,
- .close = oss_output_close,
- .play = oss_output_play,
- .cancel = oss_output_cancel,
-
- .mixer_plugin = &oss_mixer_plugin,
-};
diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h
deleted file mode 100644
index 2aecc2b3a..000000000
--- a/src/output/oss_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OSS_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin oss_output_plugin;
-
-#endif
diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c
deleted file mode 100644
index cd51fca20..000000000
--- a/src/output/osx_output_plugin.c
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "osx_output_plugin.h"
-#include "output_api.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-#include <CoreAudio/AudioHardware.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "osx"
-
-struct osx_output {
- struct audio_output base;
-
- /* configuration settings */
- OSType component_subtype;
- /* only applicable with kAudioUnitSubType_HALOutput */
- const char *device_name;
-
- AudioUnit au;
- GMutex *mutex;
- GCond *condition;
-
- struct fifo_buffer *buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-osx_output_quark(void)
-{
- return g_quark_from_static_string("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(struct osx_output *oo, const struct config_param *param)
-{
- const char *device = config_get_block_string(param, "device", NULL);
-
- 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 struct config_param *param, GError **error_r)
-{
- struct osx_output *oo = g_new(struct osx_output, 1);
- if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) {
- g_free(oo);
- return NULL;
- }
-
- osx_output_configure(oo, param);
- oo->mutex = g_mutex_new();
- oo->condition = g_cond_new();
-
- return &oo->base;
-}
-
-static void
-osx_output_finish(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_free(od->mutex);
- g_cond_free(od->condition);
- g_free(od);
-}
-
-static bool
-osx_output_set_device(struct osx_output *oo, GError **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) {
- g_set_error(error, osx_output_quark(), 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 = g_malloc(size);
- status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
- &size,
- deviceids);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), 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) {
- g_set_error(error, osx_output_quark(), 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) {
- g_debug("found matching device: ID=%u, name=%s",
- (unsigned int) deviceids[i], name);
- break;
- }
- }
- if (i == numdevices) {
- g_warning("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) {
- g_set_error(error, osx_output_quark(), status,
- "Unable to set OS X audio output device: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
- g_debug("set OS X audio output device ID=%u, name=%s",
- (unsigned int) deviceids[i], name);
-
-done:
- if (deviceids != NULL)
- g_free(deviceids);
- return ret;
-}
-
-static OSStatus
-osx_render(void *vdata,
- G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
- G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
- G_GNUC_UNUSED UInt32 in_bus_number,
- G_GNUC_UNUSED UInt32 in_number_frames,
- AudioBufferList *buffer_list)
-{
- struct osx_output *od = (struct osx_output *) vdata;
- AudioBuffer *buffer = &buffer_list->mBuffers[0];
- size_t buffer_size = buffer->mDataByteSize;
-
- assert(od->buffer != NULL);
-
- g_mutex_lock(od->mutex);
-
- 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;
-
- g_cond_signal(od->condition);
- g_mutex_unlock(od->mutex);
-
- 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, GError **error_r)
-{
- struct osx_output *oo = (struct osx_output *)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) {
- g_set_error(error_r, osx_output_quark(), 0,
- "Error finding OS X component");
- return false;
- }
-
- OSStatus status = OpenAComponent(comp, &oo->au);
- if (status != noErr) {
- g_set_error(error_r, osx_output_quark(), status,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- if (!osx_output_set_device(oo, error_r)) {
- 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);
- g_set_error(error_r, osx_output_quark(), result,
- "unable to set callback for OS X audio unit");
- return false;
- }
-
- return true;
-}
-
-static void
-osx_output_disable(struct audio_output *ao)
-{
- struct osx_output *oo = (struct osx_output *)ao;
-
- CloseComponent(oo->au);
-}
-
-static void
-osx_output_cancel(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_lock(od->mutex);
- fifo_buffer_clear(od->buffer);
- g_mutex_unlock(od->mutex);
-}
-
-static void
-osx_output_close(struct audio_output *ao)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- AudioOutputUnitStop(od->au);
- AudioUnitUninitialize(od->au);
-
- fifo_buffer_free(od->buffer);
-}
-
-static bool
-osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- AudioStreamBasicDescription stream_description;
- stream_description.mSampleRate = audio_format->sample_rate;
- stream_description.mFormatID = kAudioFormatLinearPCM;
- stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- stream_description.mBitsPerChannel = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- stream_description.mBitsPerChannel = 16;
- break;
-
- case SAMPLE_FORMAT_S32:
- stream_description.mBitsPerChannel = 32;
- break;
-
- default:
- audio_format->format = SAMPLE_FORMAT_S32;
- stream_description.mBitsPerChannel = 32;
- break;
- }
-
-#if G_BYTE_ORDER == G_BIG_ENDIAN
- stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-#endif
-
- stream_description.mBytesPerPacket =
- audio_format_frame_size(audio_format);
- 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) {
- g_set_error(error, osx_output_quark(), result,
- "Unable to set format on OS X device");
- return false;
- }
-
- OSStatus status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), 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_frame_size(audio_format));
-
- status = AudioOutputUnitStart(od->au);
- if (status != 0) {
- AudioUnitUninitialize(od->au);
- g_set_error(error, osx_output_quark(), 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,
- G_GNUC_UNUSED GError **error)
-{
- struct osx_output *od = (struct osx_output *)ao;
-
- g_mutex_lock(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 */
- g_cond_wait(od->condition, od->mutex);
- }
-
- if (size > max_length)
- size = max_length;
-
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
-
- g_mutex_unlock(od->mutex);
-
- return size;
-}
-
-const struct audio_output_plugin osx_output_plugin = {
- .name = "osx",
- .test_default_device = osx_output_test_default_device,
- .init = osx_output_init,
- .finish = osx_output_finish,
- .enable = osx_output_enable,
- .disable = osx_output_disable,
- .open = osx_output_open,
- .close = osx_output_close,
- .play = osx_output_play,
- .cancel = osx_output_cancel,
-};
diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h
deleted file mode 100644
index 814702d4f..000000000
--- a/src/output/osx_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OSX_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin osx_output_plugin;
-
-#endif
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
deleted file mode 100644
index 90c5a5331..000000000
--- a/src/output/pipe_output_plugin.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "pipe_output_plugin.h"
-#include "output_api.h"
-
-#include <stdio.h>
-#include <errno.h>
-
-struct pipe_output {
- struct audio_output base;
-
- char *cmd;
- FILE *fh;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pipe_output_quark(void)
-{
- return g_quark_from_static_string("pipe_output");
-}
-
-static struct audio_output *
-pipe_output_init(const struct config_param *param,
- GError **error)
-{
- struct pipe_output *pd = g_new(struct pipe_output, 1);
-
- if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) {
- g_free(pd);
- return NULL;
- }
-
- pd->cmd = config_dup_block_string(param, "command", NULL);
- if (pd->cmd == NULL) {
- g_set_error(error, pipe_output_quark(), 0,
- "No \"command\" parameter specified");
- return NULL;
- }
-
- return &pd->base;
-}
-
-static void
-pipe_output_finish(struct audio_output *ao)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- g_free(pd->cmd);
- ao_base_finish(&pd->base);
- g_free(pd);
-}
-
-static bool
-pipe_output_open(struct audio_output *ao,
- G_GNUC_UNUSED struct audio_format *audio_format,
- G_GNUC_UNUSED GError **error)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- pd->fh = popen(pd->cmd, "w");
- if (pd->fh == NULL) {
- g_set_error(error, pipe_output_quark(), errno,
- "Error opening pipe \"%s\": %s",
- pd->cmd, g_strerror(errno));
- return false;
- }
-
- return true;
-}
-
-static void
-pipe_output_close(struct audio_output *ao)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
-
- pclose(pd->fh);
-}
-
-static size_t
-pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
-{
- struct pipe_output *pd = (struct pipe_output *)ao;
- size_t ret;
-
- ret = fwrite(chunk, 1, size, pd->fh);
- if (ret == 0)
- g_set_error(error, pipe_output_quark(), errno,
- "Write error on pipe: %s", g_strerror(errno));
-
- return ret;
-}
-
-const struct audio_output_plugin pipe_output_plugin = {
- .name = "pipe",
- .init = pipe_output_init,
- .finish = pipe_output_finish,
- .open = pipe_output_open,
- .close = pipe_output_close,
- .play = pipe_output_play,
-};
diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h
deleted file mode 100644
index 9f014f829..000000000
--- a/src/output/pipe_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_PIPE_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin pipe_output_plugin;
-
-#endif
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
deleted file mode 100644
index e267427df..000000000
--- a/src/output/pulse_output_plugin.c
+++ /dev/null
@@ -1,955 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "pulse_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "mixer/pulse_mixer_plugin.h"
-
-#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"
-
-#if !defined(PA_CHECK_VERSION)
-/**
- * This macro was implemented in libpulse 0.9.16.
- */
-#define PA_CHECK_VERSION(a,b,c) false
-#endif
-
-struct pulse_output {
- struct audio_output base;
-
- const char *name;
- const char *server;
- const char *sink;
-
- struct pulse_mixer *mixer;
-
- struct pa_threaded_mainloop *mainloop;
- struct pa_context *context;
- struct pa_stream *stream;
-
- size_t writable;
-
-#if !PA_CHECK_VERSION(0,9,11)
- /**
- * We need this variable because pa_stream_is_corked() wasn't
- * added before 0.9.11.
- */
- bool pause;
-#endif
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-void
-pulse_output_lock(struct pulse_output *po)
-{
- pa_threaded_mainloop_lock(po->mainloop);
-}
-
-void
-pulse_output_unlock(struct pulse_output *po)
-{
- pa_threaded_mainloop_unlock(po->mainloop);
-}
-
-void
-pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
-{
- assert(po != NULL);
- assert(po->mixer == NULL);
- assert(pm != NULL);
-
- po->mixer = pm;
-
- if (po->mainloop == NULL)
- return;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != NULL &&
- pa_context_get_state(po->context) == PA_CONTEXT_READY) {
- pulse_mixer_on_connect(pm, po->context);
-
- if (po->stream != NULL &&
- 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(struct pulse_output *po,
- G_GNUC_UNUSED struct pulse_mixer *pm)
-{
- assert(po != NULL);
- assert(pm != NULL);
- assert(po->mixer == pm);
-
- po->mixer = NULL;
-}
-
-bool
-pulse_output_set_volume(struct pulse_output *po,
- const struct pa_cvolume *volume, GError **error_r)
-{
- pa_operation *o;
-
- if (po->context == NULL || po->stream == NULL ||
- pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
- return false;
- }
-
- o = pa_context_set_sink_input_volume(po->context,
- pa_stream_get_index(po->stream),
- volume, NULL, NULL);
- if (o == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "failed to set PulseAudio volume: %s",
- pa_strerror(pa_context_errno(po->context)));
- 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 != NULL);
- assert(operation != NULL);
-
- 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(G_GNUC_UNUSED pa_stream *s,
- G_GNUC_UNUSED int success, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-static void
-pulse_output_context_state_cb(struct pa_context *context, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY:
- if (po->mixer != NULL)
- 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 != NULL)
- 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)
-{
- struct pulse_output *po = userdata;
- pa_subscription_event_type_t facility
- = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
- pa_subscription_event_type_t type
- = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
-
- if (po->mixer != NULL &&
- facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
- po->stream != NULL &&
- 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(struct pulse_output *po, GError **error_r)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- int error;
-
- error = pa_context_connect(po->context, po->server,
- (pa_context_flags_t)0, NULL);
- if (error < 0) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_context_connect() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
- return true;
-}
-
-/**
- * Frees and clears the stream.
- */
-static void
-pulse_output_delete_stream(struct pulse_output *po)
-{
- assert(po != NULL);
- assert(po->stream != NULL);
-
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream, NULL, NULL);
-#endif
-
- pa_stream_set_state_callback(po->stream, NULL, NULL);
- pa_stream_set_write_callback(po->stream, NULL, NULL);
-
- pa_stream_disconnect(po->stream);
- pa_stream_unref(po->stream);
- po->stream = NULL;
-}
-
-/**
- * Frees and clears the context.
- *
- * Caller must lock the main loop.
- */
-static void
-pulse_output_delete_context(struct pulse_output *po)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- pa_context_set_state_callback(po->context, NULL, NULL);
- pa_context_set_subscribe_callback(po->context, NULL, NULL);
-
- pa_context_disconnect(po->context);
- pa_context_unref(po->context);
- po->context = NULL;
-}
-
-/**
- * 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(struct pulse_output *po, GError **error_r)
-{
- assert(po != NULL);
- assert(po->mainloop != NULL);
-
- po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
- MPD_PULSE_NAME);
- if (po->context == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "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_r)) {
- pulse_output_delete_context(po);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pulse_output_init(const struct config_param *param, GError **error_r)
-{
- struct pulse_output *po;
-
- g_setenv("PULSE_PROP_media.role", "music", true);
-
- po = g_new(struct pulse_output, 1);
- if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) {
- g_free(po);
- return NULL;
- }
-
- po->name = config_get_block_string(param, "name", "mpd_pulse");
- po->server = config_get_block_string(param, "server", NULL);
- po->sink = config_get_block_string(param, "sink", NULL);
-
- po->mixer = NULL;
- po->mainloop = NULL;
- po->context = NULL;
- po->stream = NULL;
-
- return &po->base;
-}
-
-static void
-pulse_output_finish(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- ao_base_finish(&po->base);
- g_free(po);
-}
-
-static bool
-pulse_output_enable(struct audio_output *ao, GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- assert(po->mainloop == NULL);
- assert(po->context == NULL);
-
- /* create the libpulse mainloop and start the thread */
-
- po->mainloop = pa_threaded_mainloop_new();
- if (po->mainloop == NULL) {
- g_free(po);
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "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 = NULL;
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_threaded_mainloop_start() has failed");
- return false;
- }
-
- /* create the libpulse context and connect it */
-
- if (!pulse_output_setup_context(po, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- pa_threaded_mainloop_stop(po->mainloop);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = NULL;
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static void
-pulse_output_disable(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
-
- assert(po->mainloop != NULL);
-
- pa_threaded_mainloop_stop(po->mainloop);
- if (po->context != NULL)
- pulse_output_delete_context(po);
- pa_threaded_mainloop_free(po->mainloop);
- po->mainloop = NULL;
-}
-
-/**
- * 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(struct pulse_output *po, GError **error_r)
-{
- assert(po->mainloop != NULL);
-
- pa_context_state_t state;
-
- if (po->context == NULL && !pulse_output_setup_context(po, error_r))
- 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 */
- g_set_error(error_r, pulse_output_quark(), 0,
- "failed to connect: %s",
- pa_strerror(pa_context_errno(po->context)));
- 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;
- }
- }
-}
-
-#if PA_CHECK_VERSION(0,9,8)
-
-static void
-pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(stream == po->stream || po->stream == NULL);
- assert(po->mainloop != NULL);
-
- /* wake up the main loop to break out of the loop in
- pulse_output_play() */
- pa_threaded_mainloop_signal(po->mainloop, 0);
-}
-
-#endif
-
-static void
-pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(stream == po->stream || po->stream == NULL);
- assert(po->mainloop != NULL);
- assert(po->context != NULL);
-
- switch (pa_stream_get_state(stream)) {
- case PA_STREAM_READY:
- if (po->mixer != NULL)
- 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 != NULL)
- 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(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
- void *userdata)
-{
- struct pulse_output *po = userdata;
-
- assert(po->mainloop != NULL);
-
- 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(struct pulse_output *po, const pa_sample_spec *ss,
- GError **error_r)
-{
- assert(po != NULL);
- assert(po->context != NULL);
-
- po->stream = pa_stream_new(po->context, po->name, ss, NULL);
- if (po->stream == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_new() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-#endif
-
- 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, struct audio_format *audio_format,
- GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_sample_spec ss;
- int error;
-
- assert(po->mainloop != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->context != NULL) {
- 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_r)) {
- 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 = SAMPLE_FORMAT_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_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* .. and connect it (asynchronously) */
-
- error = pa_stream_connect_playback(po->stream, po->sink,
- NULL, 0, NULL, NULL);
- if (error < 0) {
- pulse_output_delete_stream(po);
-
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_connect_playback() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
-#if !PA_CHECK_VERSION(0,9,11)
- po->pause = false;
-#endif
-
- return true;
-}
-
-static void
-pulse_output_close(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_operation *o;
-
- assert(po->mainloop != NULL);
-
- 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 == NULL) {
- g_warning("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 != NULL &&
- 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(struct pulse_output *po, GError **error_r)
-{
- 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:
- g_set_error(error_r, pulse_output_quark(),
- pa_context_errno(po->context),
- "failed to connect the stream: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
-
- case PA_STREAM_CREATING:
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-/**
- * Determines whether the stream is paused. On libpulse older than
- * 0.9.11, it uses a custom pause flag.
- */
-static bool
-pulse_output_stream_is_paused(struct pulse_output *po)
-{
- assert(po->stream != NULL);
-
-#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
- return po->pause;
-#else
- return pa_stream_is_corked(po->stream);
-#endif
-}
-
-/**
- * Sets cork mode on the stream.
- */
-static bool
-pulse_output_stream_pause(struct pulse_output *po, bool pause,
- GError **error_r)
-{
- pa_operation *o;
-
- assert(po->mainloop != NULL);
- assert(po->context != NULL);
- assert(po->stream != NULL);
-
- o = pa_stream_cork(po->stream, pause,
- pulse_output_stream_success_cb, po);
- if (o == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_cork() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
- if (!pulse_wait_for_operation(po->mainloop, o)) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_cork() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
- return false;
- }
-
-#if !PA_CHECK_VERSION(0,9,11)
- po->pause = pause;
-#endif
- return true;
-}
-
-static unsigned
-pulse_output_delay(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- unsigned result = 0;
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- if (po->base.pause && pulse_output_stream_is_paused(po) &&
- 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,
- GError **error_r)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- int error;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already) connected */
-
- if (!pulse_output_wait_stream(po, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- assert(po->context != NULL);
-
- /* unpause if previously paused */
-
- if (pulse_output_stream_is_paused(po) &&
- !pulse_output_stream_pause(po, false, error_r)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- /* wait until the server allows us to write */
-
- while (po->writable == 0) {
-#if PA_CHECK_VERSION(0,9,8)
- if (pa_stream_is_suspended(po->stream)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_set_error(error_r, pulse_output_quark(), 0,
- "suspended");
- return 0;
- }
-#endif
-
- pa_threaded_mainloop_wait(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_set_error(error_r, pulse_output_quark(), 0,
- "disconnected");
- return 0;
- }
- }
-
- /* now write */
-
- if (size > po->writable)
- /* don't send more than possible */
- size = po->writable;
-
- po->writable -= size;
-
- error = pa_stream_write(po->stream, chunk, size, NULL,
- 0, PA_SEEK_RELATIVE);
- pa_threaded_mainloop_unlock(po->mainloop);
- if (error < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-static void
-pulse_output_cancel(struct audio_output *ao)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- pa_operation *o;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- 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 != NULL);
-
- o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
- if (o == NULL) {
- g_warning("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)
-{
- struct pulse_output *po = (struct pulse_output *)ao;
- GError *error = NULL;
-
- assert(po->mainloop != NULL);
- assert(po->stream != NULL);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already/still) connected */
-
- if (!pulse_output_wait_stream(po, &error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_warning("%s", error->message);
- g_error_free(error);
- return false;
- }
-
- assert(po->context != NULL);
-
- /* cork the stream */
-
- if (!pulse_output_stream_is_paused(po) &&
- !pulse_output_stream_pause(po, true, &error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- g_warning("%s", error->message);
- g_error_free(error);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- return true;
-}
-
-static bool
-pulse_output_test_default_device(void)
-{
- struct pulse_output *po;
- bool success;
-
- po = (struct pulse_output *)pulse_output_init(NULL, NULL);
- if (po == NULL)
- return false;
-
- success = pulse_output_wait_connection(po, NULL);
- pulse_output_finish(&po->base);
-
- return success;
-}
-
-const struct audio_output_plugin pulse_output_plugin = {
- .name = "pulse",
-
- .test_default_device = pulse_output_test_default_device,
- .init = pulse_output_init,
- .finish = pulse_output_finish,
- .enable = pulse_output_enable,
- .disable = pulse_output_disable,
- .open = pulse_output_open,
- .delay = pulse_output_delay,
- .play = pulse_output_play,
- .cancel = pulse_output_cancel,
- .pause = pulse_output_pause,
- .close = pulse_output_close,
-
- .mixer_plugin = &pulse_mixer_plugin,
-};
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
deleted file mode 100644
index 02a51f27b..000000000
--- a/src/output/pulse_output_plugin.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_PULSE_OUTPUT_PLUGIN_H
-
-#include <stdbool.h>
-
-#include <glib.h>
-
-struct pulse_output;
-struct pulse_mixer;
-struct pa_cvolume;
-
-extern const struct audio_output_plugin pulse_output_plugin;
-
-void
-pulse_output_lock(struct pulse_output *po);
-
-void
-pulse_output_unlock(struct pulse_output *po);
-
-void
-pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
-
-void
-pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm);
-
-bool
-pulse_output_set_volume(struct pulse_output *po,
- const struct pa_cvolume *volume, GError **error_r);
-
-#endif
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
deleted file mode 100644
index b84cb244c..000000000
--- a/src/output/recorder_output_plugin.c
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "recorder_output_plugin.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "recorder"
-
-struct recorder_output {
- struct audio_output base;
-
- /**
- * The configured encoder plugin.
- */
- struct encoder *encoder;
-
- /**
- * The destination file name.
- */
- const char *path;
-
- /**
- * The destination file descriptor.
- */
- int fd;
-
- /**
- * The buffer for encoder_read().
- */
- char buffer[32768];
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-recorder_output_quark(void)
-{
- return g_quark_from_static_string("recorder_output");
-}
-
-static struct audio_output *
-recorder_output_init(const struct config_param *param, GError **error_r)
-{
- struct recorder_output *recorder = g_new(struct recorder_output, 1);
- if (!ao_base_init(&recorder->base, &recorder_output_plugin, param,
- error_r)) {
- g_free(recorder);
- return NULL;
- }
-
- /* read configuration */
-
- const char *encoder_name =
- config_get_block_string(param, "encoder", "vorbis");
- const struct encoder_plugin *encoder_plugin =
- encoder_plugin_get(encoder_name);
- if (encoder_plugin == NULL) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "No such encoder: %s", encoder_name);
- goto failure;
- }
-
- recorder->path = config_get_block_string(param, "path", NULL);
- if (recorder->path == NULL) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "'path' not configured");
- goto failure;
- }
-
- /* initialize encoder */
-
- recorder->encoder = encoder_init(encoder_plugin, param, error_r);
- if (recorder->encoder == NULL)
- goto failure;
-
- return &recorder->base;
-
-failure:
- ao_base_finish(&recorder->base);
- g_free(recorder);
- return NULL;
-}
-
-static void
-recorder_output_finish(struct audio_output *ao)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- encoder_finish(recorder->encoder);
- ao_base_finish(&recorder->base);
- g_free(recorder);
-}
-
-static bool
-recorder_write_to_file(struct recorder_output *recorder,
- const void *_data, size_t length,
- GError **error_r)
-{
- assert(length > 0);
-
- const int fd = recorder->fd;
-
- 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 */
- g_set_error(error_r, recorder_output_quark(), 0,
- "write() returned 0");
- return false;
- } else if (errno != EINTR) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "Failed to write to '%s': %s",
- recorder->path, g_strerror(errno));
- return false;
- }
- }
-}
-
-/**
- * Writes pending data from the encoder to the output file.
- */
-static bool
-recorder_output_encoder_to_file(struct recorder_output *recorder,
- GError **error_r)
-{
- assert(recorder->fd >= 0);
-
- while (true) {
- /* read from the encoder */
-
- size_t size = encoder_read(recorder->encoder, recorder->buffer,
- sizeof(recorder->buffer));
- if (size == 0)
- return true;
-
- /* write everything into the file */
-
- if (!recorder_write_to_file(recorder, recorder->buffer, size,
- error_r))
- return false;
- }
-}
-
-static bool
-recorder_output_open(struct audio_output *ao,
- struct audio_format *audio_format,
- GError **error_r)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- /* create the output file */
-
- recorder->fd = open_cloexec(recorder->path,
- O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
- 0666);
- if (recorder->fd < 0) {
- g_set_error(error_r, recorder_output_quark(), 0,
- "Failed to create '%s': %s",
- recorder->path, g_strerror(errno));
- return false;
- }
-
- /* open the encoder */
-
- if (!encoder_open(recorder->encoder, audio_format, error_r)) {
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- if (!recorder_output_encoder_to_file(recorder, error_r)) {
- encoder_close(recorder->encoder);
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- return true;
-}
-
-static void
-recorder_output_close(struct audio_output *ao)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- /* flush the encoder and write the rest to the file */
-
- if (encoder_end(recorder->encoder, NULL))
- recorder_output_encoder_to_file(recorder, NULL);
-
- /* 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,
- GError **error_r)
-{
- struct recorder_output *recorder = (struct recorder_output *)ao;
-
- return encoder_write(recorder->encoder, chunk, size, error_r) &&
- recorder_output_encoder_to_file(recorder, error_r)
- ? size : 0;
-}
-
-const struct audio_output_plugin recorder_output_plugin = {
- .name = "recorder",
- .init = recorder_output_init,
- .finish = recorder_output_finish,
- .open = recorder_output_open,
- .close = recorder_output_close,
- .play = recorder_output_play,
-};
diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h
deleted file mode 100644
index a9bf755bd..000000000
--- a/src/output/recorder_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_RECORDER_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin recorder_output_plugin;
-
-#endif
diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c
deleted file mode 100644
index 1c2c48321..000000000
--- a/src/output/roar_output_plugin.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * Copyright (C) 2003-2010 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 "roar_output_plugin.h"
-#include "output_api.h"
-#include "mixer_list.h"
-#include "roar_output_plugin.h"
-
-#include <glib.h>
-#include <stdint.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-
-#include <roaraudio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "roaraudio"
-
-typedef struct roar
-{
- struct audio_output base;
-
- roar_vs_t * vss;
- int err;
- char *host;
- char *name;
- int role;
- struct roar_connection con;
- struct roar_audio_info info;
- GMutex *lock;
- volatile bool alive;
-} roar_t;
-
-static inline GQuark
-roar_output_quark(void)
-{
- return g_quark_from_static_string("roar_output");
-}
-
-static int
-roar_output_get_volume_locked(struct roar *roar)
-{
- if (roar->vss == NULL || !roar->alive)
- return -1;
-
- float l, r;
- int error;
- if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
- return -1;
-
- return (l + r) * 50;
-}
-
-int
-roar_output_get_volume(struct roar *roar)
-{
- g_mutex_lock(roar->lock);
- int volume = roar_output_get_volume_locked(roar);
- g_mutex_unlock(roar->lock);
- return volume;
-}
-
-static bool
-roar_output_set_volume_locked(struct roar *roar, unsigned volume)
-{
- assert(volume <= 100);
-
- if (roar->vss == NULL || !roar->alive)
- return false;
-
- int error;
- float level = volume / 100.0;
-
- roar_vs_volume_mono(roar->vss, level, &error);
- return true;
-}
-
-bool
-roar_output_set_volume(struct roar *roar, unsigned volume)
-{
- g_mutex_lock(roar->lock);
- bool success = roar_output_set_volume_locked(roar, volume);
- g_mutex_unlock(roar->lock);
- return success;
-}
-
-static void
-roar_configure(struct roar * self, const struct config_param *param)
-{
- self->host = config_dup_block_string(param, "server", NULL);
- self->name = config_dup_block_string(param, "name", "MPD");
-
- const char *role = config_get_block_string(param, "role", "music");
- self->role = role != NULL
- ? roar_str2role(role)
- : ROAR_ROLE_MUSIC;
-}
-
-static struct audio_output *
-roar_init(const struct config_param *param, GError **error_r)
-{
- struct roar *self = g_new0(struct roar, 1);
-
- if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) {
- g_free(self);
- return NULL;
- }
-
- self->lock = g_mutex_new();
- self->err = ROAR_ERROR_NONE;
- roar_configure(self, param);
- return &self->base;
-}
-
-static void
-roar_finish(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
-
- g_free(self->host);
- g_free(self->name);
- g_mutex_free(self->lock);
-
- ao_base_finish(&self->base);
- g_free(self);
-}
-
-static void
-roar_use_audio_format(struct roar_audio_info *info,
- struct audio_format *audio_format)
-{
- info->rate = audio_format->sample_rate;
- info->channels = audio_format->channels;
- info->codec = ROAR_CODEC_PCM_S;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_UNDEFINED:
- info->bits = 16;
- audio_format->format = SAMPLE_FORMAT_S16;
- break;
-
- case SAMPLE_FORMAT_S8:
- info->bits = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- info->bits = 16;
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- info->bits = 32;
- audio_format->format = SAMPLE_FORMAT_S32;
- break;
-
- case SAMPLE_FORMAT_S32:
- info->bits = 32;
- break;
- }
-}
-
-static bool
-roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
-{
- struct roar *self = (struct roar *)ao;
- g_mutex_lock(self->lock);
-
- if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
- {
- g_set_error(error, roar_output_quark(), 0,
- "Failed to connect to Roar server");
- g_mutex_unlock(self->lock);
- return false;
- }
-
- self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
-
- if (self->vss == NULL || self->err != ROAR_ERROR_NONE)
- {
- g_set_error(error, roar_output_quark(), 0,
- "Failed to connect to server");
- g_mutex_unlock(self->lock);
- return false;
- }
-
- roar_use_audio_format(&self->info, audio_format);
-
- if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
- &(self->err)) < 0)
- {
- g_set_error(error, roar_output_quark(), 0, "Failed to start stream");
- g_mutex_unlock(self->lock);
- return false;
- }
- roar_vs_role(self->vss, self->role, &(self->err));
- self->alive = true;
-
- g_mutex_unlock(self->lock);
- return true;
-}
-
-static void
-roar_close(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
- g_mutex_lock(self->lock);
- self->alive = false;
-
- if (self->vss != NULL)
- roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
- self->vss = NULL;
- roar_disconnect(&(self->con));
- g_mutex_unlock(self->lock);
-}
-
-static void
-roar_cancel_locked(struct roar *self)
-{
- if (self->vss == NULL)
- return;
-
- roar_vs_t *vss = self->vss;
- self->vss = NULL;
- roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
- self->alive = false;
-
- vss = roar_vs_new_from_con(&(self->con), &(self->err));
- if (vss == NULL)
- return;
-
- if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
- &(self->err)) < 0) {
- roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
- g_warning("Failed to start stream");
- return;
- }
-
- roar_vs_role(vss, self->role, &(self->err));
- self->vss = vss;
- self->alive = true;
-}
-
-static void
-roar_cancel(struct audio_output *ao)
-{
- struct roar *self = (struct roar *)ao;
-
- g_mutex_lock(self->lock);
- roar_cancel_locked(self);
- g_mutex_unlock(self->lock);
-}
-
-static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
-{
- struct roar *self = (struct roar *)ao;
- ssize_t rc;
-
- if (self->vss == NULL)
- {
- g_set_error(error, roar_output_quark(), 0, "Connection is invalid");
- return 0;
- }
-
- rc = roar_vs_write(self->vss, chunk, size, &(self->err));
- if ( rc <= 0 )
- {
- g_set_error(error, roar_output_quark(), 0, "Failed to play data");
- return 0;
- }
-
- return rc;
-}
-
-static const char*
-roar_tag_convert(enum tag_type 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 NULL;
- }
-}
-
-static void
-roar_send_tag(struct audio_output *ao, const struct tag *meta)
-{
- struct roar *self = (struct roar *)ao;
-
- if (self->vss == NULL)
- return;
-
- g_mutex_lock(self->lock);
- size_t cnt = 1;
- struct roar_keyval vals[32];
- memset(vals, 0, sizeof(vals));
- char uuid_buf[32][64];
-
- char timebuf[16];
- snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
- meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
-
- vals[0].key = g_strdup("LENGTH");
- vals[0].value = timebuf;
-
- for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
- {
- bool is_uuid = false;
- const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
- if (key != NULL)
- {
- if (is_uuid)
- {
- snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
- meta->items[i]->value);
- vals[cnt].key = g_strdup(key);
- vals[cnt].value = uuid_buf[cnt];
- }
- else
- {
- vals[cnt].key = g_strdup(key);
- vals[cnt].value = meta->items[i]->value;
- }
- cnt++;
- }
- }
-
- roar_vs_meta(self->vss, vals, cnt, &(self->err));
-
- for (unsigned i = 0; i < 32; i++)
- g_free(vals[i].key);
-
- g_mutex_unlock(self->lock);
-}
-
-const struct audio_output_plugin roar_output_plugin = {
- .name = "roar",
- .init = roar_init,
- .finish = roar_finish,
- .open = roar_open,
- .play = roar_play,
- .cancel = roar_cancel,
- .close = roar_close,
- .send_tag = roar_send_tag,
-
- .mixer_plugin = &roar_mixer_plugin
-};
diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h
deleted file mode 100644
index 78b628cc4..000000000
--- a/src/output/roar_output_plugin.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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
-
-#include <stdbool.h>
-
-struct roar;
-
-extern const struct audio_output_plugin roar_output_plugin;
-
-int
-roar_output_get_volume(struct roar *roar);
-
-bool
-roar_output_set_volume(struct roar *roar, unsigned volume);
-
-#endif
diff --git a/src/output/shout_output_plugin.c b/src/output/shout_output_plugin.c
deleted file mode 100644
index 56456a0ea..000000000
--- a/src/output/shout_output_plugin.c
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "shout_output_plugin.h"
-#include "output_api.h"
-#include "encoder_plugin.h"
-#include "encoder_list.h"
-#include "mpd_error.h"
-
-#include <shout/shout.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "shout"
-
-#define DEFAULT_CONN_TIMEOUT 2
-
-struct shout_data {
- struct audio_output base;
-
- shout_t *shout_conn;
- shout_metadata_t *shout_meta;
-
- struct encoder *encoder;
-
- float quality;
- int bitrate;
-
- int timeout;
-
- uint8_t buffer[32768];
-};
-
-static int shout_init_count;
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-shout_output_quark(void)
-{
- return g_quark_from_static_string("shout_output");
-}
-
-static const struct encoder_plugin *
-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);
-}
-
-static struct shout_data *new_shout_data(void)
-{
- struct shout_data *ret = g_new(struct shout_data, 1);
-
- ret->shout_conn = shout_new();
- ret->shout_meta = shout_metadata_new();
- ret->bitrate = -1;
- ret->quality = -2.0;
- ret->timeout = DEFAULT_CONN_TIMEOUT;
-
- return ret;
-}
-
-static void free_shout_data(struct shout_data *sd)
-{
- if (sd->shout_meta)
- shout_metadata_free(sd->shout_meta);
- if (sd->shout_conn)
- shout_free(sd->shout_conn);
-
- g_free(sd);
-}
-
-#define check_block_param(name) { \
- block_param = config_get_block_param(param, name); \
- if (!block_param) { \
- MPD_ERROR("no \"%s\" defined for shout device defined at line " \
- "%i\n", name, param->line); \
- } \
- }
-
-static struct audio_output *
-my_shout_init_driver(const struct config_param *param,
- GError **error)
-{
- struct shout_data *sd = new_shout_data();
- if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) {
- free_shout_data(sd);
- return NULL;
- }
-
- const struct audio_format *audio_format =
- &sd->base.config_audio_format;
- if (!audio_format_fully_defined(audio_format)) {
- g_set_error(error, shout_output_quark(), 0,
- "Need full audio format specification");
- ao_base_finish(&sd->base);
- free_shout_data(sd);
- return NULL;
- }
-
- if (shout_init_count == 0)
- shout_init();
-
- shout_init_count++;
-
- const struct block_param *block_param;
- check_block_param("host");
- char *host = block_param->value;
-
- check_block_param("mount");
- char *mount = block_param->value;
-
- unsigned port = config_get_block_unsigned(param, "port", 0);
- if (port == 0) {
- g_set_error(error, shout_output_quark(), 0,
- "shout port must be configured");
- goto failure;
- }
-
- check_block_param("password");
- const char *passwd = block_param->value;
-
- check_block_param("name");
- const char *name = block_param->value;
-
- bool public = config_get_block_bool(param, "public", false);
-
- const char *user = config_get_block_string(param, "user", "source");
-
- const char *value = config_get_block_string(param, "quality", NULL);
- if (value != NULL) {
- char *test;
- sd->quality = strtod(value, &test);
-
- if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
- g_set_error(error, shout_output_quark(), 0,
- "shout quality \"%s\" is not a number in the "
- "range -1 to 10, line %i",
- value, param->line);
- goto failure;
- }
-
- if (config_get_block_string(param, "bitrate", NULL) != NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "quality and bitrate are "
- "both defined");
- goto failure;
- }
- } else {
- value = config_get_block_string(param, "bitrate", NULL);
- if (value == NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "neither bitrate nor quality defined");
- goto failure;
- }
-
- char *test;
- sd->bitrate = strtol(value, &test, 10);
-
- if (*test != '\0' || sd->bitrate <= 0) {
- g_set_error(error, shout_output_quark(), 0,
- "bitrate must be a positive integer");
- goto failure;
- }
- }
-
- const char *encoding = config_get_block_string(param, "encoding",
- "ogg");
- const struct encoder_plugin *encoder_plugin =
- shout_encoder_plugin_get(encoding);
- if (encoder_plugin == NULL) {
- g_set_error(error, shout_output_quark(), 0,
- "couldn't find shout encoder plugin \"%s\"",
- encoding);
- goto failure;
- }
-
- sd->encoder = encoder_init(encoder_plugin, param, error);
- if (sd->encoder == NULL)
- goto failure;
-
- 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 = config_get_block_string(param, "protocol", NULL);
- if (value != NULL) {
- if (0 == strcmp(value, "shoutcast") &&
- 0 != strcmp(encoding, "mp3")) {
- g_set_error(error, shout_output_quark(), 0,
- "you cannot stream \"%s\" to shoutcast, use mp3",
- encoding);
- goto failure;
- } 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 {
- g_set_error(error, shout_output_quark(), 0,
- "shout protocol \"%s\" is not \"shoutcast\" or "
- "\"icecast1\"or \"icecast2\"",
- value);
- goto failure;
- }
- } else {
- protocol = SHOUT_PROTOCOL_HTTP;
- }
-
- if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS ||
- shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS ||
- shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS ||
- shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS ||
- shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
- shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
- shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
- shout_set_format(sd->shout_conn, shout_format)
- != SHOUTERR_SUCCESS ||
- shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
- shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- /* optional paramters */
- sd->timeout = config_get_block_unsigned(param, "timeout",
- DEFAULT_CONN_TIMEOUT);
-
- value = config_get_block_string(param, "genre", NULL);
- if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- value = config_get_block_string(param, "description", NULL);
- if (value != NULL && shout_set_description(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- value = config_get_block_string(param, "url", NULL);
- if (value != NULL && shout_set_url(sd->shout_conn, value)) {
- g_set_error(error, shout_output_quark(), 0,
- "%s", shout_get_error(sd->shout_conn));
- goto failure;
- }
-
- {
- char temp[11];
- memset(temp, 0, sizeof(temp));
-
- snprintf(temp, sizeof(temp), "%u", audio_format->channels);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
-
- snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate);
-
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
-
- if (sd->quality >= -1.0) {
- snprintf(temp, sizeof(temp), "%2.2f", sd->quality);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY,
- temp);
- } else {
- snprintf(temp, sizeof(temp), "%d", sd->bitrate);
- shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE,
- temp);
- }
- }
-
- return &sd->base;
-
-failure:
- ao_base_finish(&sd->base);
- free_shout_data(sd);
- return NULL;
-}
-
-static bool
-handle_shout_error(struct shout_data *sd, int err, GError **error)
-{
- switch (err) {
- case SHOUTERR_SUCCESS:
- break;
-
- case SHOUTERR_UNCONNECTED:
- case SHOUTERR_SOCKET:
- g_set_error(error, shout_output_quark(), 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:
- g_set_error(error, shout_output_quark(), 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(struct shout_data *sd, GError **error)
-{
- assert(sd->encoder != NULL);
-
- 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(struct shout_data * sd)
-{
- if (sd->encoder != NULL) {
- if (encoder_end(sd->encoder, NULL))
- write_page(sd, NULL);
-
- encoder_close(sd->encoder);
- }
-
- if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
- shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
- g_warning("problem closing connection to shout server: %s\n",
- shout_get_error(sd->shout_conn));
- }
-}
-
-static void
-my_shout_finish_driver(struct audio_output *ao)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- encoder_finish(sd->encoder);
-
- ao_base_finish(&sd->base);
- free_shout_data(sd);
-
- shout_init_count--;
-
- if (shout_init_count == 0)
- shout_shutdown();
-}
-
-static void
-my_shout_drop_buffered_audio(struct audio_output *ao)
-{
- G_GNUC_UNUSED
- struct shout_data *sd = (struct shout_data *)ao;
-
- /* needs to be implemented for shout */
-}
-
-static void
-my_shout_close_device(struct audio_output *ao)
-{
- struct shout_data *sd = (struct shout_data *)ao;
-
- close_shout_conn(sd);
-}
-
-static bool
-shout_connect(struct shout_data *sd, GError **error)
-{
- switch (shout_open(sd->shout_conn)) {
- case SHOUTERR_SUCCESS:
- case SHOUTERR_CONNECTED:
- return true;
-
- default:
- g_set_error(error, shout_output_quark(), 0,
- "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, struct audio_format *audio_format,
- GError **error)
-{
- struct shout_data *sd = (struct shout_data *)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)
-{
- struct shout_data *sd = (struct shout_data *)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,
- GError **error)
-{
- struct shout_data *sd = (struct shout_data *)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 const char silence[1020];
-
- return my_shout_play(ao, silence, sizeof(silence), NULL);
-}
-
-static void
-shout_tag_to_metadata(const struct 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 struct tag *tag)
-{
- struct shout_data *sd = (struct shout_data *)ao;
- GError *error = NULL;
-
- if (sd->encoder->plugin->tag != NULL) {
- /* encoder plugin supports stream tags */
-
- if (!encoder_pre_tag(sd->encoder, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return;
- }
-
- if (!write_page(sd, NULL))
- return;
-
- if (!encoder_tag(sd->encoder, tag, &error)) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
- } 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)) {
- g_warning("error setting shout metadata\n");
- }
- }
-
- write_page(sd, NULL);
-}
-
-const struct audio_output_plugin shout_output_plugin = {
- .name = "shout",
- .init = my_shout_init_driver,
- .finish = my_shout_finish_driver,
- .open = my_shout_open_device,
- .delay = my_shout_delay,
- .play = my_shout_play,
- .pause = my_shout_pause,
- .cancel = my_shout_drop_buffered_audio,
- .close = my_shout_close_device,
- .send_tag = my_shout_set_tag,
-};
diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h
deleted file mode 100644
index 9a7378803..000000000
--- a/src/output/shout_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_SHOUT_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin shout_output_plugin;
-
-#endif
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
deleted file mode 100644
index ce726009a..000000000
--- a/src/output/solaris_output_plugin.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "solaris_output_plugin.h"
-#include "output_api.h"
-#include "fd_util.h"
-
-#include <glib.h>
-
-#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
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "solaris_output"
-
-struct solaris_output {
- struct audio_output base;
-
- /* configuration */
- const char *device;
-
- int fd;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-solaris_output_quark(void)
-{
- return g_quark_from_static_string("solaris_output");
-}
-
-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 struct config_param *param, GError **error_r)
-{
- struct solaris_output *so = g_new(struct solaris_output, 1);
-
- if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) {
- g_free(so);
- return NULL;
- }
-
- so->device = config_get_block_string(param, "device", "/dev/audio");
-
- return &so->base;
-}
-
-static void
-solaris_output_finish(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- ao_base_finish(&so->base);
- g_free(so);
-}
-
-static bool
-solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
- struct audio_info info;
- int ret, flags;
-
- /* support only 16 bit mono/stereo for now; nothing else has
- been tested */
- audio_format->format = SAMPLE_FORMAT_S16;
-
- /* open the device in non-blocking mode */
-
- so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
- if (so->fd < 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "Failed to open %s: %s",
- so->device, g_strerror(errno));
- 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) {
- g_set_error(error, solaris_output_quark(), errno,
- "AUDIO_GETINFO failed: %s", g_strerror(errno));
- 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) {
- g_set_error(error, solaris_output_quark(), errno,
- "AUDIO_SETINFO failed: %s", g_strerror(errno));
- close(so->fd);
- return false;
- }
-
- return true;
-}
-
-static void
-solaris_output_close(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- close(so->fd);
-}
-
-static size_t
-solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
- ssize_t nbytes;
-
- nbytes = write(so->fd, chunk, size);
- if (nbytes <= 0) {
- g_set_error(error, solaris_output_quark(), errno,
- "Write failed: %s", g_strerror(errno));
- return 0;
- }
-
- return nbytes;
-}
-
-static void
-solaris_output_cancel(struct audio_output *ao)
-{
- struct solaris_output *so = (struct solaris_output *)ao;
-
- ioctl(so->fd, I_FLUSH);
-}
-
-const struct audio_output_plugin solaris_output_plugin = {
- .name = "solaris",
- .test_default_device = solaris_output_test_default_device,
- .init = solaris_output_init,
- .finish = solaris_output_finish,
- .open = solaris_output_open,
- .close = solaris_output_close,
- .play = solaris_output_play,
- .cancel = solaris_output_cancel,
-};
diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h
deleted file mode 100644
index 600aea8c2..000000000
--- a/src/output/solaris_output_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_SOLARIS_OUTPUT_PLUGIN_H
-
-extern const struct audio_output_plugin solaris_output_plugin;
-
-#endif
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
deleted file mode 100644
index 4d95834b9..000000000
--- a/src/output/winmm_output_plugin.c
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "winmm_output_plugin.h"
-#include "output_api.h"
-#include "pcm_buffer.h"
-#include "mixer_list.h"
-#include "winmm_output_plugin.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <windows.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "winmm_output"
-
-struct winmm_buffer {
- struct pcm_buffer buffer;
-
- WAVEHDR hdr;
-};
-
-struct winmm_output {
- struct audio_output base;
-
- UINT device_id;
- HWAVEOUT handle;
-
- /**
- * This event is triggered by Windows when a buffer is
- * finished.
- */
- HANDLE event;
-
- struct winmm_buffer buffers[8];
- unsigned next_buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-winmm_output_quark(void)
-{
- return g_quark_from_static_string("winmm_output");
-}
-
-HWAVEOUT
-winmm_output_get_handle(struct winmm_output* 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, GError **error_r)
-{
- /* if device is not specified use wave mapper */
- if (device_name == NULL) {
- *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:
- g_set_error(error_r, winmm_output_quark(), 0,
- "device \"%s\" is not found", device_name);
- return false;
-}
-
-static struct audio_output *
-winmm_output_init(const struct config_param *param, GError **error_r)
-{
- struct winmm_output *wo = g_new(struct winmm_output, 1);
- if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) {
- g_free(wo);
- return NULL;
- }
-
- const char *device = config_get_block_string(param, "device", NULL);
- if (!get_device_id(device, &wo->device_id, error_r)) {
- ao_base_finish(&wo->base);
- g_free(wo);
- return NULL;
- }
-
- return &wo->base;
-}
-
-static void
-winmm_output_finish(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- ao_base_finish(&wo->base);
- g_free(wo);
-}
-
-static bool
-winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error_r)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- wo->event = CreateEvent(NULL, false, false, NULL);
- if (wo->event == NULL) {
- g_set_error(error_r, winmm_output_quark(), 0,
- "CreateEvent() failed");
- return false;
- }
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- case SAMPLE_FORMAT_S16:
- break;
-
- case SAMPLE_FORMAT_S24_P32:
- case SAMPLE_FORMAT_S32:
- case SAMPLE_FORMAT_UNDEFINED:
- /* we havn't tested formats other than S16 */
- audio_format->format = SAMPLE_FORMAT_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_frame_size(audio_format);
- format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
- format.wBitsPerSample = audio_format_sample_size(audio_format) * 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);
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutOpen() failed");
- return false;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
- pcm_buffer_init(&wo->buffers[i].buffer);
- 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)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
- pcm_buffer_deinit(&wo->buffers[i].buffer);
-
- waveOutClose(wo->handle);
-
- CloseHandle(wo->event);
-}
-
-/**
- * Copy data into a buffer, and prepare the wave header.
- */
-static bool
-winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
- const void *data, size_t size,
- GError **error_r)
-{
- void *dest = pcm_buffer_get(&buffer->buffer, size);
- assert(dest != NULL);
-
- memcpy(dest, data, size);
-
- memset(&buffer->hdr, 0, sizeof(buffer->hdr));
- buffer->hdr.lpData = dest;
- buffer->hdr.dwBufferLength = size;
-
- MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- if (result != MMSYSERR_NOERROR) {
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutPrepareHeader() failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Wait until the buffer is finished.
- */
-static bool
-winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
- GError **error_r)
-{
- 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) {
- g_set_error(error_r, winmm_output_quark(), 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, GError **error_r)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- /* get the next buffer from the ring and prepare it */
- struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
- if (!winmm_drain_buffer(wo, buffer, error_r) ||
- !winmm_set_buffer(wo, buffer, chunk, size, error_r))
- 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));
- g_set_error(error_r, winmm_output_quark(), result,
- "waveOutWrite() failed");
- return 0;
- }
-
- /* mark our buffer as "used" */
- wo->next_buffer = (wo->next_buffer + 1) %
- G_N_ELEMENTS(wo->buffers);
-
- return size;
-}
-
-static bool
-winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
-{
- for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
- return false;
-
- for (unsigned i = 0; i < wo->next_buffer; ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
- return false;
-
- return true;
-}
-
-static void
-winmm_stop(struct winmm_output *wo)
-{
- waveOutReset(wo->handle);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
- struct winmm_buffer *buffer = &wo->buffers[i];
- waveOutUnprepareHeader(wo->handle, &buffer->hdr,
- sizeof(buffer->hdr));
- }
-}
-
-static void
-winmm_output_drain(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- if (!winmm_drain_all_buffers(wo, NULL))
- winmm_stop(wo);
-}
-
-static void
-winmm_output_cancel(struct audio_output *ao)
-{
- struct winmm_output *wo = (struct winmm_output *)ao;
-
- winmm_stop(wo);
-}
-
-const struct audio_output_plugin winmm_output_plugin = {
- .name = "winmm",
- .test_default_device = winmm_output_test_default_device,
- .init = winmm_output_init,
- .finish = winmm_output_finish,
- .open = winmm_output_open,
- .close = winmm_output_close,
- .play = winmm_output_play,
- .drain = winmm_output_drain,
- .cancel = winmm_output_cancel,
- .mixer_plugin = &winmm_mixer_plugin,
-};
diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h
deleted file mode 100644
index 0605530e1..000000000
--- a/src/output/winmm_output_plugin.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_WINMM_OUTPUT_PLUGIN_H
-
-#include "check.h"
-
-#ifdef ENABLE_WINMM_OUTPUT
-
-#include <windows.h>
-
-struct winmm_output;
-
-extern const struct audio_output_plugin winmm_output_plugin;
-
-HWAVEOUT winmm_output_get_handle(struct winmm_output*);
-
-#endif
-
-#endif
diff --git a/src/output_all.c b/src/output_all.c
deleted file mode 100644
index f56cd04ee..000000000
--- a/src/output_all.c
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_all.h"
-#include "output_internal.h"
-#include "output_control.h"
-#include "chunk.h"
-#include "conf.h"
-#include "pipe.h"
-#include "buffer.h"
-#include "player_control.h"
-#include "mpd_error.h"
-#include "notify.h"
-
-#ifndef NDEBUG
-#include "chunk.h"
-#endif
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-static struct audio_format input_audio_format;
-
-static struct audio_output **audio_outputs;
-static unsigned int num_audio_outputs;
-
-/**
- * The #music_buffer object where consumed chunks are returned.
- */
-static struct music_buffer *g_music_buffer;
-
-/**
- * The #music_pipe object which feeds all audio outputs. It is filled
- * by audio_output_all_play().
- */
-static struct music_pipe *g_mp;
-
-/**
- * The "elapsed_time" stamp of the most recently finished chunk.
- */
-static float audio_output_all_elapsed_time = -1.0;
-
-unsigned int audio_output_count(void)
-{
- return num_audio_outputs;
-}
-
-struct audio_output *
-audio_output_get(unsigned i)
-{
- assert(i < num_audio_outputs);
-
- assert(audio_outputs[i] != NULL);
-
- return audio_outputs[i];
-}
-
-struct audio_output *
-audio_output_find(const char *name)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_output_get(i);
-
- if (strcmp(ao->name, name) == 0)
- return ao;
- }
-
- /* name not found */
- return NULL;
-}
-
-static unsigned
-audio_output_config_count(void)
-{
- unsigned int nr = 0;
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
- nr++;
- if (!nr)
- nr = 1; /* we'll always have at least one device */
- return nr;
-}
-
-void
-audio_output_all_init(struct player_control *pc)
-{
- const struct config_param *param = NULL;
- unsigned int i;
- GError *error = NULL;
-
- notify_init(&audio_output_client_notify);
-
- num_audio_outputs = audio_output_config_count();
- audio_outputs = g_new(struct audio_output *, num_audio_outputs);
-
- for (i = 0; i < num_audio_outputs; i++)
- {
- unsigned int j;
-
- param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
-
- /* only allow param to be NULL if there just one audioOutput */
- assert(param || (num_audio_outputs == 1));
-
- struct audio_output *output = audio_output_new(param, pc, &error);
- if (output == NULL) {
- if (param != NULL)
- MPD_ERROR("line %i: %s",
- param->line, error->message);
- else
- MPD_ERROR("%s", error->message);
- }
-
- audio_outputs[i] = output;
-
- /* require output names to be unique: */
- for (j = 0; j < i; j++) {
- if (!strcmp(output->name, audio_outputs[j]->name)) {
- MPD_ERROR("output devices with identical "
- "names: %s\n", output->name);
- }
- }
- }
-}
-
-void
-audio_output_all_finish(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; i++) {
- audio_output_disable(audio_outputs[i]);
- audio_output_finish(audio_outputs[i]);
- }
-
- g_free(audio_outputs);
- audio_outputs = NULL;
- num_audio_outputs = 0;
-
- notify_deinit(&audio_output_client_notify);
-}
-
-void
-audio_output_all_enable_disable(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; i++) {
- struct audio_output *ao = audio_outputs[i];
- bool enabled;
-
- g_mutex_lock(ao->mutex);
- enabled = ao->really_enabled;
- g_mutex_unlock(ao->mutex);
-
- if (ao->enabled != enabled) {
- if (ao->enabled)
- audio_output_enable(ao);
- else
- audio_output_disable(ao);
- }
- }
-}
-
-/**
- * Determine if all (active) outputs have finished the current
- * command.
- */
-static bool
-audio_output_all_finished(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
- bool not_finished;
-
- g_mutex_lock(ao->mutex);
- not_finished = audio_output_is_open(ao) &&
- !audio_output_command_is_finished(ao);
- g_mutex_unlock(ao->mutex);
-
- if (not_finished)
- return false;
- }
-
- return true;
-}
-
-static void audio_output_wait_all(void)
-{
- while (!audio_output_all_finished())
- notify_wait(&audio_output_client_notify);
-}
-
-/**
- * Signals all audio outputs which are open.
- */
-static void
-audio_output_allow_play_all(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_allow_play(audio_outputs[i]);
-}
-
-static void
-audio_output_reset_reopen(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- if (!ao->open && ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- g_mutex_unlock(ao->mutex);
-}
-
-/**
- * Resets the "reopen" flag on all audio devices. MPD should
- * immediately retry to open the device instead of waiting for the
- * timeout when the user wants to start playback.
- */
-static void
-audio_output_all_reset_reopen(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- audio_output_reset_reopen(ao);
- }
-}
-
-/**
- * Opens all output devices which are enabled, but closed.
- *
- * @return true if there is at least open output device which is open
- */
-static bool
-audio_output_all_update(void)
-{
- unsigned int i;
- bool ret = false;
-
- if (!audio_format_defined(&input_audio_format))
- return false;
-
- for (i = 0; i < num_audio_outputs; ++i)
- ret = audio_output_update(audio_outputs[i],
- &input_audio_format, g_mp) || ret;
-
- return ret;
-}
-
-bool
-audio_output_all_play(struct music_chunk *chunk)
-{
- bool ret;
- unsigned int i;
-
- assert(g_music_buffer != NULL);
- assert(g_mp != NULL);
- assert(chunk != NULL);
- assert(music_chunk_check_format(chunk, &input_audio_format));
-
- ret = audio_output_all_update();
- if (!ret)
- return false;
-
- music_pipe_push(g_mp, chunk);
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_play(audio_outputs[i]);
-
- return true;
-}
-
-bool
-audio_output_all_open(const struct audio_format *audio_format,
- struct music_buffer *buffer)
-{
- bool ret = false, enabled = false;
- unsigned int i;
-
- assert(audio_format != NULL);
- assert(buffer != NULL);
- assert(g_music_buffer == NULL || g_music_buffer == buffer);
- assert((g_mp == NULL) == (g_music_buffer == NULL));
-
- g_music_buffer = buffer;
-
- /* the audio format must be the same as existing chunks in the
- pipe */
- assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format));
-
- if (g_mp == NULL)
- g_mp = music_pipe_new();
- else
- /* if the pipe hasn't been cleared, the the audio
- format must not have changed */
- assert(music_pipe_empty(g_mp) ||
- audio_format_equals(audio_format,
- &input_audio_format));
-
- input_audio_format = *audio_format;
-
- audio_output_all_reset_reopen();
- audio_output_all_enable_disable();
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_outputs[i]->enabled)
- enabled = true;
-
- if (audio_outputs[i]->open)
- ret = true;
- }
-
- if (!enabled)
- g_warning("All audio outputs are disabled");
-
- if (!ret)
- /* close all devices if there was an error */
- audio_output_all_close();
-
- return ret;
-}
-
-/**
- * Has the specified audio output already consumed this chunk?
- */
-static bool
-chunk_is_consumed_in(const struct audio_output *ao,
- const struct music_chunk *chunk)
-{
- if (!ao->open)
- return true;
-
- if (ao->chunk == NULL)
- return false;
-
- assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk));
-
- if (chunk != ao->chunk) {
- assert(chunk->next != NULL);
- return true;
- }
-
- return ao->chunk_finished && chunk->next == NULL;
-}
-
-/**
- * Has this chunk been consumed by all audio outputs?
- */
-static bool
-chunk_is_consumed(const struct music_chunk *chunk)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- const struct audio_output *ao = audio_outputs[i];
- bool consumed;
-
- g_mutex_lock(ao->mutex);
- consumed = chunk_is_consumed_in(ao, chunk);
- g_mutex_unlock(ao->mutex);
-
- if (!consumed)
- return false;
- }
-
- return true;
-}
-
-/**
- * There's only one chunk left in the pipe (#g_mp), and all audio
- * outputs have consumed it already. Clear the reference.
- */
-static void
-clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked)
-{
- assert(chunk->next == NULL);
- assert(music_pipe_contains(g_mp, chunk));
-
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- /* this mutex will be unlocked by the caller when it's
- ready */
- g_mutex_lock(ao->mutex);
- locked[i] = ao->open;
-
- if (!locked[i]) {
- g_mutex_unlock(ao->mutex);
- continue;
- }
-
- assert(ao->chunk == chunk);
- assert(ao->chunk_finished);
- ao->chunk = NULL;
- }
-}
-
-unsigned
-audio_output_all_check(void)
-{
- const struct music_chunk *chunk;
- bool is_tail;
- struct music_chunk *shifted;
- bool locked[num_audio_outputs];
-
- assert(g_music_buffer != NULL);
- assert(g_mp != NULL);
-
- while ((chunk = music_pipe_peek(g_mp)) != NULL) {
- assert(!music_pipe_empty(g_mp));
-
- if (!chunk_is_consumed(chunk))
- /* at least one output is not finished playing
- this chunk */
- return music_pipe_size(g_mp);
-
- if (chunk->length > 0 && chunk->times >= 0.0)
- /* only update elapsed_time if the chunk
- provides a defined value */
- audio_output_all_elapsed_time = chunk->times;
-
- is_tail = chunk->next == NULL;
- if (is_tail)
- /* this is the tail of the pipe - clear the
- chunk reference in all outputs */
- clear_tail_chunk(chunk, locked);
-
- /* remove the chunk from the pipe */
- shifted = music_pipe_shift(g_mp);
- assert(shifted == chunk);
-
- if (is_tail)
- /* unlock all audio outputs which were locked
- by clear_tail_chunk() */
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (locked[i])
- g_mutex_unlock(audio_outputs[i]->mutex);
-
- /* return the chunk to the buffer */
- music_buffer_return(g_music_buffer, shifted);
- }
-
- return 0;
-}
-
-bool
-audio_output_all_wait(struct player_control *pc, unsigned threshold)
-{
- player_lock(pc);
-
- if (audio_output_all_check() < threshold) {
- player_unlock(pc);
- return true;
- }
-
- player_wait(pc);
- player_unlock(pc);
-
- return audio_output_all_check() < threshold;
-}
-
-void
-audio_output_all_pause(void)
-{
- unsigned int i;
-
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_pause(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_drain(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_drain_async(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_cancel(void)
-{
- unsigned int i;
-
- /* send the cancel() command to all audio outputs */
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_cancel(audio_outputs[i]);
-
- audio_output_wait_all();
-
- /* clear the music pipe and return all chunks to the buffer */
-
- if (g_mp != NULL)
- music_pipe_clear(g_mp, g_music_buffer);
-
- /* the audio outputs are now waiting for a signal, to
- synchronize the cleared music pipe */
-
- audio_output_allow_play_all();
-
- /* invalidate elapsed_time */
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_close(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_close(audio_outputs[i]);
-
- if (g_mp != NULL) {
- assert(g_music_buffer != NULL);
-
- music_pipe_clear(g_mp, g_music_buffer);
- music_pipe_free(g_mp);
- g_mp = NULL;
- }
-
- g_music_buffer = NULL;
-
- audio_format_clear(&input_audio_format);
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_release(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_release(audio_outputs[i]);
-
- if (g_mp != NULL) {
- assert(g_music_buffer != NULL);
-
- music_pipe_clear(g_mp, g_music_buffer);
- music_pipe_free(g_mp);
- g_mp = NULL;
- }
-
- g_music_buffer = NULL;
-
- audio_format_clear(&input_audio_format);
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_song_border(void)
-{
- /* clear the elapsed_time pointer at the beginning of a new
- song */
- audio_output_all_elapsed_time = 0.0;
-}
-
-float
-audio_output_all_get_elapsed_time(void)
-{
- return audio_output_all_elapsed_time;
-}
diff --git a/src/output_all.h b/src/output_all.h
deleted file mode 100644
index 4eeb94f13..000000000
--- a/src/output_all.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 <stdbool.h>
-#include <stddef.h>
-
-struct audio_format;
-struct music_buffer;
-struct music_chunk;
-struct player_control;
-
-/**
- * Global initialization: load audio outputs from the configuration
- * file and initialize them.
- */
-void
-audio_output_all_init(struct player_control *pc);
-
-/**
- * Global finalization: free memory occupied by audio outputs. All
- */
-void
-audio_output_all_finish(void);
-
-/**
- * Returns the total number of audio output devices, including those
- * who are disabled right now.
- */
-unsigned int audio_output_count(void);
-
-/**
- * Returns the "i"th audio output device.
- */
-struct audio_output *
-audio_output_get(unsigned i);
-
-/**
- * Returns the audio output device with the specified name. Returns
- * NULL if the name does not exist.
- */
-struct audio_output *
-audio_output_find(const char *name);
-
-/**
- * Checks the "enabled" flag of all audio outputs, and if one has
- * changed, commit the change.
- */
-void
-audio_output_all_enable_disable(void);
-
-/**
- * Opens all audio outputs which are not disabled.
- *
- * @param audio_format the preferred audio format, or NULL to reuse
- * the previous format
- * @param buffer the #music_buffer where consumed #music_chunk objects
- * should be returned
- * @return true on success, false on failure
- */
-bool
-audio_output_all_open(const struct audio_format *audio_format,
- struct music_buffer *buffer);
-
-/**
- * Closes all audio outputs.
- */
-void
-audio_output_all_close(void);
-
-/**
- * Closes all audio outputs. Outputs with the "always_on" flag are
- * put into pause mode.
- */
-void
-audio_output_all_release(void);
-
-/**
- * Enqueue a #music_chunk object for playing, i.e. pushes it to a
- * #music_pipe.
- *
- * @param chunk the #music_chunk object to be played
- * @return true on success, false if no audio output was able to play
- * (all closed then)
- */
-bool
-audio_output_all_play(struct music_chunk *chunk);
-
-/**
- * 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 #music_pipe
- */
-unsigned
-audio_output_all_check(void);
-
-/**
- * Checks if the size of the #music_pipe is below the #threshold. If
- * not, it attempts to synchronize with all output threads, and waits
- * until another #music_chunk is finished.
- *
- * @param threshold the maximum number of chunks in the pipe
- * @return true if there are less than #threshold chunks in the pipe
- */
-bool
-audio_output_all_wait(struct player_control *pc, unsigned threshold);
-
-/**
- * Puts all audio outputs into pause mode. Most implementations will
- * simply close it then.
- */
-void
-audio_output_all_pause(void);
-
-/**
- * Drain all audio outputs.
- */
-void
-audio_output_all_drain(void);
-
-/**
- * Try to cancel data which may still be in the device's buffers.
- */
-void
-audio_output_all_cancel(void);
-
-/**
- * Indicate that a new song will begin now.
- */
-void
-audio_output_all_song_border(void);
-
-/**
- * Returns the "elapsed_time" stamp of the most recently finished
- * chunk. A negative value is returned when no chunk has been
- * finished yet.
- */
-float
-audio_output_all_get_elapsed_time(void);
-
-#endif
diff --git a/src/output_api.h b/src/output_api.h
deleted file mode 100644
index dfeef3518..000000000
--- a/src/output_api.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_API_H
-
-#include "output_plugin.h"
-#include "output_internal.h"
-#include "audio_format.h"
-#include "tag.h"
-#include "conf.h"
-
-#endif
diff --git a/src/output_command.c b/src/output_command.c
deleted file mode 100644
index 3988f350a..000000000
--- a/src/output_command.c
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_command.h"
-#include "output_all.h"
-#include "output_internal.h"
-#include "output_plugin.h"
-#include "mixer_control.h"
-#include "player_control.h"
-#include "idle.h"
-
-extern unsigned audio_output_state_version;
-
-bool
-audio_output_enable_index(unsigned idx)
-{
- struct audio_output *ao;
-
- if (idx >= audio_output_count())
- return false;
-
- ao = audio_output_get(idx);
- if (ao->enabled)
- return true;
-
- ao->enabled = true;
- idle_add(IDLE_OUTPUT);
-
- pc_update_audio(ao->player_control);
-
- ++audio_output_state_version;
-
- return true;
-}
-
-bool
-audio_output_disable_index(unsigned idx)
-{
- struct audio_output *ao;
- struct mixer *mixer;
-
- if (idx >= audio_output_count())
- return false;
-
- ao = audio_output_get(idx);
- if (!ao->enabled)
- return true;
-
- ao->enabled = false;
- idle_add(IDLE_OUTPUT);
-
- mixer = ao->mixer;
- if (mixer != NULL) {
- mixer_close(mixer);
- idle_add(IDLE_MIXER);
- }
-
- pc_update_audio(ao->player_control);
-
- ++audio_output_state_version;
-
- return true;
-}
diff --git a/src/output_command.h b/src/output_command.h
deleted file mode 100644
index eda30acc8..000000000
--- a/src/output_command.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 OUTPUT_COMMAND_H
-#define OUTPUT_COMMAND_H
-
-#include <stdbool.h>
-
-/**
- * Enables an audio output. Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_enable_index(unsigned idx);
-
-/**
- * Disables an audio output. Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_disable_index(unsigned idx);
-
-#endif
diff --git a/src/output_control.c b/src/output_control.c
deleted file mode 100644
index 7b95be49b..000000000
--- a/src/output_control.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_control.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "output_thread.h"
-#include "mixer_control.h"
-#include "mixer_plugin.h"
-#include "filter_plugin.h"
-#include "notify.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-enum {
- /** after a failure, wait this number of seconds before
- automatically reopening the device */
- REOPEN_AFTER = 10,
-};
-
-struct notify audio_output_client_notify;
-
-/**
- * Waits for command completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_wait(struct audio_output *ao)
-{
- while (ao->command != AO_COMMAND_NONE) {
- g_mutex_unlock(ao->mutex);
- notify_wait(&audio_output_client_notify);
- g_mutex_lock(ao->mutex);
- }
-}
-
-/**
- * Sends a command to the #audio_output object, but does not wait for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void ao_command_async(struct audio_output *ao,
- enum audio_output_command cmd)
-{
- assert(ao->command == AO_COMMAND_NONE);
- ao->command = cmd;
- g_cond_signal(ao->cond);
-}
-
-/**
- * Sends a command to the #audio_output object and waits for
- * completion.
- *
- * @param ao the #audio_output instance; must be locked
- */
-static void
-ao_command(struct audio_output *ao, enum audio_output_command cmd)
-{
- ao_command_async(ao, cmd);
- ao_command_wait(ao);
-}
-
-/**
- * Lock the #audio_output object and execute the command
- * synchronously.
- */
-static void
-ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
-{
- g_mutex_lock(ao->mutex);
- ao_command(ao, cmd);
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_enable(struct audio_output *ao)
-{
- if (ao->thread == NULL) {
- if (ao->plugin->enable == NULL) {
- /* don't bother to start the thread now if the
- device doesn't even have a enable() method;
- just assign the variable and we're done */
- ao->really_enabled = true;
- return;
- }
-
- audio_output_thread_start(ao);
- }
-
- ao_lock_command(ao, AO_COMMAND_ENABLE);
-}
-
-void
-audio_output_disable(struct audio_output *ao)
-{
- if (ao->thread == NULL) {
- if (ao->plugin->disable == NULL)
- ao->really_enabled = false;
- else
- /* if there's no thread yet, the device cannot
- be enabled */
- assert(!ao->really_enabled);
-
- return;
- }
-
- ao_lock_command(ao, AO_COMMAND_DISABLE);
-}
-
-/**
- * Object must be locked (and unlocked) by the caller.
- */
-static bool
-audio_output_open(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp)
-{
- bool open;
-
- assert(ao != NULL);
- assert(ao->allow_play);
- assert(audio_format_valid(audio_format));
- assert(mp != NULL);
-
- if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- if (ao->open &&
- audio_format_equals(audio_format, &ao->in_audio_format)) {
- assert(ao->pipe == mp ||
- (ao->always_on && ao->pause));
-
- if (ao->pause) {
- ao->chunk = NULL;
- ao->pipe = mp;
-
- /* unpause with the CANCEL command; this is a
- hack, but suits well for forcing the thread
- to leave the ao_pause() thread, and we need
- to flush the device buffer anyway */
-
- /* we're not using audio_output_cancel() here,
- because that function is asynchronous */
- ao_command(ao, AO_COMMAND_CANCEL);
- }
-
- return true;
- }
-
- ao->in_audio_format = *audio_format;
- ao->chunk = NULL;
-
- ao->pipe = mp;
-
- if (ao->thread == NULL)
- audio_output_thread_start(ao);
-
- ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
- open = ao->open;
-
- if (open && ao->mixer != NULL) {
- GError *error = NULL;
-
- if (!mixer_open(ao->mixer, &error)) {
- g_warning("Failed to open mixer for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
- }
-
- return open;
-}
-
-/**
- * Same as audio_output_close(), but expects the lock to be held by
- * the caller.
- */
-static void
-audio_output_close_locked(struct audio_output *ao)
-{
- assert(ao != NULL);
- assert(ao->allow_play);
-
- if (ao->mixer != NULL)
- mixer_auto_close(ao->mixer);
-
- assert(!ao->open || ao->fail_timer == NULL);
-
- if (ao->open)
- ao_command(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-}
-
-bool
-audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp)
-{
- assert(mp != NULL);
-
- g_mutex_lock(ao->mutex);
-
- if (ao->enabled && ao->really_enabled) {
- if (ao->fail_timer == NULL ||
- g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
- bool success = audio_output_open(ao, audio_format, mp);
- g_mutex_unlock(ao->mutex);
- return success;
- }
- } else if (audio_output_is_open(ao))
- audio_output_close_locked(ao);
-
- g_mutex_unlock(ao->mutex);
- return false;
-}
-
-void
-audio_output_play(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- assert(ao->allow_play);
-
- if (audio_output_is_open(ao))
- g_cond_signal(ao->cond);
-
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_pause(struct audio_output *ao)
-{
- if (ao->mixer != NULL && ao->plugin->pause == NULL)
- /* the device has no pause mode: close the mixer,
- unless its "global" flag is set (checked by
- mixer_auto_close()) */
- mixer_auto_close(ao->mixer);
-
- g_mutex_lock(ao->mutex);
- assert(ao->allow_play);
- if (audio_output_is_open(ao))
- ao_command_async(ao, AO_COMMAND_PAUSE);
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_drain_async(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
- assert(ao->allow_play);
- if (audio_output_is_open(ao))
- ao_command_async(ao, AO_COMMAND_DRAIN);
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_cancel(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- if (audio_output_is_open(ao)) {
- ao->allow_play = false;
- ao_command_async(ao, AO_COMMAND_CANCEL);
- }
-
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_allow_play(struct audio_output *ao)
-{
- g_mutex_lock(ao->mutex);
-
- ao->allow_play = true;
- if (audio_output_is_open(ao))
- g_cond_signal(ao->cond);
-
- g_mutex_unlock(ao->mutex);
-}
-
-void
-audio_output_release(struct audio_output *ao)
-{
- if (ao->always_on)
- audio_output_pause(ao);
- else
- audio_output_close(ao);
-}
-
-void audio_output_close(struct audio_output *ao)
-{
- assert(ao != NULL);
- assert(!ao->open || ao->fail_timer == NULL);
-
- g_mutex_lock(ao->mutex);
- audio_output_close_locked(ao);
- g_mutex_unlock(ao->mutex);
-}
-
-void audio_output_finish(struct audio_output *ao)
-{
- audio_output_close(ao);
-
- assert(ao->fail_timer == NULL);
-
- if (ao->thread != NULL) {
- assert(ao->allow_play);
- ao_lock_command(ao, AO_COMMAND_KILL);
- g_thread_join(ao->thread);
- ao->thread = NULL;
- }
-
- audio_output_free(ao);
-}
diff --git a/src/output_control.h b/src/output_control.h
deleted file mode 100644
index 874a53518..000000000
--- a/src/output_control.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_CONTROL_H
-
-#include <glib.h>
-
-#include <stddef.h>
-#include <stdbool.h>
-
-struct audio_output;
-struct audio_format;
-struct config_param;
-struct music_pipe;
-struct player_control;
-
-static inline GQuark
-audio_output_quark(void)
-{
- return g_quark_from_static_string("audio_output");
-}
-
-/**
- * Enables the device.
- */
-void
-audio_output_enable(struct audio_output *ao);
-
-/**
- * Disables the device.
- */
-void
-audio_output_disable(struct audio_output *ao);
-
-/**
- * Opens or closes the device, depending on the "enabled" flag.
- *
- * @return true if the device is open
- */
-bool
-audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format,
- const struct music_pipe *mp);
-
-void
-audio_output_play(struct audio_output *ao);
-
-void audio_output_pause(struct audio_output *ao);
-
-void
-audio_output_drain_async(struct audio_output *ao);
-
-/**
- * Clear the "allow_play" flag and send the "CANCEL" command
- * asynchronously. To finish the operation, the caller has to call
- * audio_output_allow_play().
- */
-void audio_output_cancel(struct audio_output *ao);
-
-/**
- * Set the "allow_play" and signal the thread.
- */
-void
-audio_output_allow_play(struct audio_output *ao);
-
-void audio_output_close(struct audio_output *ao);
-
-/**
- * Closes the audio output, but if the "always_on" flag is set, put it
- * into pause mode instead.
- */
-void
-audio_output_release(struct audio_output *ao);
-
-void audio_output_finish(struct audio_output *ao);
-
-#endif
diff --git a/src/output_finish.c b/src/output_finish.c
deleted file mode 100644
index e11b43675..000000000
--- a/src/output_finish.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_internal.h"
-#include "output_plugin.h"
-#include "mixer_control.h"
-#include "filter_plugin.h"
-
-#include <assert.h>
-
-void
-ao_base_finish(struct audio_output *ao)
-{
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->thread == NULL);
-
- if (ao->mixer != NULL)
- mixer_free(ao->mixer);
-
- g_cond_free(ao->cond);
- g_mutex_free(ao->mutex);
-
- if (ao->replay_gain_filter != NULL)
- filter_free(ao->replay_gain_filter);
-
- if (ao->other_replay_gain_filter != NULL)
- filter_free(ao->other_replay_gain_filter);
-
- filter_free(ao->filter);
-
- pcm_buffer_deinit(&ao->cross_fade_buffer);
-}
-
-void
-audio_output_free(struct audio_output *ao)
-{
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->thread == NULL);
-
- ao_plugin_finish(ao);
-}
diff --git a/src/output_init.c b/src/output_init.c
deleted file mode 100644
index c3b808e94..000000000
--- a/src/output_init.c
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_control.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "output_list.h"
-#include "audio_parser.h"
-#include "mixer_control.h"
-#include "mixer_type.h"
-#include "mixer_list.h"
-#include "mixer/software_mixer_plugin.h"
-#include "filter_plugin.h"
-#include "filter_registry.h"
-#include "filter_config.h"
-#include "filter/chain_filter_plugin.h"
-#include "filter/autoconvert_filter_plugin.h"
-#include "filter/replay_gain_filter_plugin.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-#define AUDIO_OUTPUT_TYPE "type"
-#define AUDIO_OUTPUT_NAME "name"
-#define AUDIO_OUTPUT_FORMAT "format"
-#define AUDIO_FILTERS "filters"
-
-static const struct audio_output_plugin *
-audio_output_detect(GError **error)
-{
- g_warning("Attempt to detect audio output device");
-
- audio_output_plugins_for_each(plugin) {
- if (plugin->test_default_device == NULL)
- continue;
-
- g_warning("Attempting to detect a %s audio device",
- plugin->name);
- if (ao_plugin_test_default_device(plugin))
- return plugin;
- }
-
- g_set_error(error, audio_output_quark(), 0,
- "Unable to detect an audio device");
- return NULL;
-}
-
-/**
- * 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.
- */
-static enum mixer_type
-audio_output_mixer_type(const struct config_param *param)
-{
- /* read the local "mixer_type" setting */
- const char *p = config_get_block_string(param, "mixer_type", NULL);
- if (p != NULL)
- return mixer_type_parse(p);
-
- /* try the local "mixer_enabled" setting next (deprecated) */
- if (!config_get_block_bool(param, "mixer_enabled", true))
- return MIXER_TYPE_NONE;
-
- /* fall back to the global "mixer_type" setting (also
- deprecated) */
- return mixer_type_parse(config_get_string("mixer_type", "hardware"));
-}
-
-static struct mixer *
-audio_output_load_mixer(struct audio_output *ao,
- const struct config_param *param,
- const struct mixer_plugin *plugin,
- struct filter *filter_chain,
- GError **error_r)
-{
- struct mixer *mixer;
-
- switch (audio_output_mixer_type(param)) {
- case MIXER_TYPE_NONE:
- case MIXER_TYPE_UNKNOWN:
- return NULL;
-
- case MIXER_TYPE_HARDWARE:
- if (plugin == NULL)
- return NULL;
-
- return mixer_new(plugin, ao, param, error_r);
-
- case MIXER_TYPE_SOFTWARE:
- mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL);
- assert(mixer != NULL);
-
- filter_chain_append(filter_chain,
- software_mixer_get_filter(mixer));
- return mixer;
- }
-
- assert(false);
- return NULL;
-}
-
-bool
-ao_base_init(struct audio_output *ao,
- const struct audio_output_plugin *plugin,
- const struct config_param *param, GError **error_r)
-{
- assert(ao != NULL);
- assert(plugin != NULL);
- assert(plugin->finish != NULL);
- assert(plugin->open != NULL);
- assert(plugin->close != NULL);
- assert(plugin->play != NULL);
-
- GError *error = NULL;
-
- if (param) {
- const char *p;
-
- ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
- NULL);
- if (ao->name == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Missing \"name\" configuration");
- return false;
- }
-
- p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
- NULL);
- if (p != NULL) {
- bool success =
- audio_format_parse(&ao->config_audio_format,
- p, true, error_r);
- if (!success)
- return false;
- } else
- audio_format_clear(&ao->config_audio_format);
- } else {
- ao->name = "default detected output";
-
- audio_format_clear(&ao->config_audio_format);
- }
-
- ao->plugin = plugin;
- ao->always_on = config_get_block_bool(param, "always_on", false);
- ao->enabled = config_get_block_bool(param, "enabled", true);
- ao->really_enabled = false;
- ao->open = false;
- ao->pause = false;
- ao->allow_play = true;
- ao->fail_timer = NULL;
-
- pcm_buffer_init(&ao->cross_fade_buffer);
-
- /* set up the filter chain */
-
- ao->filter = filter_chain_new();
- assert(ao->filter != NULL);
-
- /* create the normalization filter (if configured) */
-
- if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
- struct filter *normalize_filter =
- filter_new(&normalize_filter_plugin, NULL, NULL);
- assert(normalize_filter != NULL);
-
- filter_chain_append(ao->filter,
- autoconvert_filter_new(normalize_filter));
- }
-
- filter_chain_parse(ao->filter,
- config_get_block_string(param, AUDIO_FILTERS, ""),
- &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 (error != NULL) {
- g_warning("Failed to initialize filter chain for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
-
- ao->thread = NULL;
- ao->command = AO_COMMAND_NONE;
- ao->mutex = g_mutex_new();
- ao->cond = g_cond_new();
-
- ao->mixer = NULL;
- ao->replay_gain_filter = NULL;
- ao->other_replay_gain_filter = NULL;
-
- /* done */
-
- return true;
-}
-
-static bool
-audio_output_setup(struct audio_output *ao, const struct config_param *param,
- GError **error_r)
-{
-
- /* create the replay_gain filter */
-
- const char *replay_gain_handler =
- config_get_block_string(param, "replay_gain_handler",
- "software");
-
- if (strcmp(replay_gain_handler, "none") != 0) {
- ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->replay_gain_filter != NULL);
-
- ao->replay_gain_serial = 0;
-
- ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->other_replay_gain_filter != NULL);
-
- ao->other_replay_gain_serial = 0;
- } else {
- ao->replay_gain_filter = NULL;
- ao->other_replay_gain_filter = NULL;
- }
-
- /* set up the mixer */
-
- GError *error = NULL;
- ao->mixer = audio_output_load_mixer(ao, param,
- ao->plugin->mixer_plugin,
- ao->filter, &error);
- if (ao->mixer == NULL && error != NULL) {
- g_warning("Failed to initialize hardware mixer for '%s': %s",
- ao->name, error->message);
- g_error_free(error);
- }
-
- /* use the hardware mixer for replay gain? */
-
- if (strcmp(replay_gain_handler, "mixer") == 0) {
- if (ao->mixer != NULL)
- replay_gain_filter_set_mixer(ao->replay_gain_filter,
- ao->mixer, 100);
- else
- g_warning("No such mixer for output '%s'", ao->name);
- } else if (strcmp(replay_gain_handler, "software") != 0 &&
- ao->replay_gain_filter != NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "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, NULL, NULL);
- assert(ao->convert_filter != NULL);
-
- filter_chain_append(ao->filter, ao->convert_filter);
-
- return true;
-}
-
-struct audio_output *
-audio_output_new(const struct config_param *param,
- struct player_control *pc,
- GError **error_r)
-{
- const struct audio_output_plugin *plugin;
-
- if (param) {
- const char *p;
-
- p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (p == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Missing \"type\" configuration");
- return false;
- }
-
- plugin = audio_output_plugin_get(p);
- if (plugin == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "No such audio output plugin: %s", p);
- return false;
- }
- } else {
- g_warning("No \"%s\" defined in config file\n",
- CONF_AUDIO_OUTPUT);
-
- plugin = audio_output_detect(error_r);
- if (plugin == NULL)
- return false;
-
- g_message("Successfully detected a %s audio device",
- plugin->name);
- }
-
- struct audio_output *ao = ao_plugin_init(plugin, param, error_r);
- if (ao == NULL)
- return NULL;
-
- if (!audio_output_setup(ao, param, error_r)) {
- ao_plugin_finish(ao);
- return NULL;
- }
-
- ao->player_control = pc;
- return ao;
-}
diff --git a/src/output_internal.h b/src/output_internal.h
deleted file mode 100644
index 9d975d789..000000000
--- a/src/output_internal.h
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_INTERNAL_H
-
-#include "audio_format.h"
-#include "pcm_buffer.h"
-
-#include <glib.h>
-
-#include <time.h>
-
-struct config_param;
-
-enum audio_output_command {
- AO_COMMAND_NONE = 0,
- AO_COMMAND_ENABLE,
- AO_COMMAND_DISABLE,
- AO_COMMAND_OPEN,
-
- /**
- * This command is invoked when the input audio format
- * changes.
- */
- AO_COMMAND_REOPEN,
-
- AO_COMMAND_CLOSE,
- AO_COMMAND_PAUSE,
-
- /**
- * Drains the internal (hardware) buffers of the device. This
- * operation may take a while to complete.
- */
- AO_COMMAND_DRAIN,
-
- AO_COMMAND_CANCEL,
- AO_COMMAND_KILL
-};
-
-struct audio_output {
- /**
- * The device's configured display name.
- */
- const char *name;
-
- /**
- * The plugin which implements this output device.
- */
- const struct audio_output_plugin *plugin;
-
- /**
- * The #mixer object associated with this audio output device.
- * May be NULL if none is available, or if software volume is
- * configured.
- */
- struct mixer *mixer;
-
- /**
- * 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;
-
- /**
- * If not NULL, the device has failed, and this timer is used
- * to estimate how long it should stay disabled (unless
- * explicitly reopened with "play").
- */
- GTimer *fail_timer;
-
- /**
- * The configured audio format.
- */
- struct audio_format config_audio_format;
-
- /**
- * The audio_format in which audio data is received from the
- * player thread (which in turn receives it from the decoder).
- */
- struct audio_format 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().
- */
- struct audio_format out_audio_format;
-
- /**
- * The buffer used to allocate the cross-fading result.
- */
- struct pcm_buffer cross_fade_buffer;
-
- /**
- * The filter object of this audio output. This is an
- * instance of chain_filter_plugin.
- */
- struct filter *filter;
-
- /**
- * The replay_gain_filter_plugin instance of this audio
- * output.
- */
- struct 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.
- */
- struct 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.
- */
- struct filter *convert_filter;
-
- /**
- * The thread handle, or NULL if the output thread isn't
- * running.
- */
- GThread *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 struct music_pipe *pipe;
-
- /**
- * This mutex protects #open, #fail_timer, #chunk and
- * #chunk_finished.
- */
- GMutex *mutex;
-
- /**
- * This condition object wakes up the output thread after
- * #command has been set.
- */
- GCond *cond;
-
- /**
- * The player_control object which "owns" this output. This
- * object is needed to signal command completion.
- */
- struct player_control *player_control;
-
- /**
- * The #music_chunk which is currently being played. All
- * chunks before this one may be returned to the
- * #music_buffer, because they are not going to be used by
- * this output anymore.
- */
- const struct music_chunk *chunk;
-
- /**
- * Has the output finished playing #chunk?
- */
- bool chunk_finished;
-};
-
-/**
- * Notify object used by the thread's client, i.e. we will send a
- * notify signal to this object, expecting the caller to wait on it.
- */
-extern struct notify audio_output_client_notify;
-
-static inline bool
-audio_output_is_open(const struct audio_output *ao)
-{
- return ao->open;
-}
-
-static inline bool
-audio_output_command_is_finished(const struct audio_output *ao)
-{
- return ao->command == AO_COMMAND_NONE;
-}
-
-struct audio_output *
-audio_output_new(const struct config_param *param,
- struct player_control *pc,
- GError **error_r);
-
-bool
-ao_base_init(struct audio_output *ao,
- const struct audio_output_plugin *plugin,
- const struct config_param *param, GError **error_r);
-
-void
-ao_base_finish(struct audio_output *ao);
-
-void
-audio_output_free(struct audio_output *ao);
-
-#endif
diff --git a/src/output_list.c b/src/output_list.c
deleted file mode 100644
index 835c02bba..000000000
--- a/src/output_list.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_list.h"
-#include "output_api.h"
-#include "output/alsa_output_plugin.h"
-#include "output/ao_output_plugin.h"
-#include "output/ffado_output_plugin.h"
-#include "output/fifo_output_plugin.h"
-#include "output/httpd_output_plugin.h"
-#include "output/jack_output_plugin.h"
-#include "output/mvp_output_plugin.h"
-#include "output/null_output_plugin.h"
-#include "output/openal_output_plugin.h"
-#include "output/oss_output_plugin.h"
-#include "output/osx_output_plugin.h"
-#include "output/pipe_output_plugin.h"
-#include "output/pulse_output_plugin.h"
-#include "output/recorder_output_plugin.h"
-#include "output/roar_output_plugin.h"
-#include "output/shout_output_plugin.h"
-#include "output/solaris_output_plugin.h"
-#include "output/winmm_output_plugin.h"
-
-const struct audio_output_plugin *const audio_output_plugins[] = {
-#ifdef HAVE_SHOUT
- &shout_output_plugin,
-#endif
- &null_output_plugin,
-#ifdef HAVE_FIFO
- &fifo_output_plugin,
-#endif
-#ifdef ENABLE_PIPE_OUTPUT
- &pipe_output_plugin,
-#endif
-#ifdef HAVE_ALSA
- &alsa_output_plugin,
-#endif
-#ifdef HAVE_ROAR
- &roar_output_plugin,
-#endif
-#ifdef HAVE_AO
- &ao_output_plugin,
-#endif
-#ifdef HAVE_OSS
- &oss_output_plugin,
-#endif
-#ifdef HAVE_OPENAL
- &openal_output_plugin,
-#endif
-#ifdef HAVE_OSX
- &osx_output_plugin,
-#endif
-#ifdef ENABLE_SOLARIS_OUTPUT
- &solaris_output_plugin,
-#endif
-#ifdef HAVE_PULSE
- &pulse_output_plugin,
-#endif
-#ifdef HAVE_MVP
- &mvp_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
-#ifdef ENABLE_FFADO_OUTPUT
- &ffado_output_plugin,
-#endif
- NULL
-};
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name)
-{
- audio_output_plugins_for_each(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return NULL;
-}
diff --git a/src/output_list.h b/src/output_list.h
deleted file mode 100644
index 185ada716..000000000
--- a/src/output_list.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_LIST_H
-
-extern const struct audio_output_plugin *const audio_output_plugins[];
-
-const struct audio_output_plugin *
-audio_output_plugin_get(const char *name);
-
-#define audio_output_plugins_for_each(plugin) \
- for (const struct audio_output_plugin *plugin, \
- *const*output_plugin_iterator = &audio_output_plugins[0]; \
- (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator)
-
-#endif
diff --git a/src/output_plugin.c b/src/output_plugin.c
deleted file mode 100644
index 221570c1c..000000000
--- a/src/output_plugin.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_plugin.h"
-#include "output_internal.h"
-
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const struct config_param *param,
- GError **error)
-{
- assert(plugin != NULL);
- assert(plugin->init != NULL);
-
- return plugin->init(param, error);
-}
-
-void
-ao_plugin_finish(struct audio_output *ao)
-{
- ao->plugin->finish(ao);
-}
-
-bool
-ao_plugin_enable(struct audio_output *ao, GError **error_r)
-{
- return ao->plugin->enable != NULL
- ? ao->plugin->enable(ao, error_r)
- : true;
-}
-
-void
-ao_plugin_disable(struct audio_output *ao)
-{
- if (ao->plugin->disable != NULL)
- ao->plugin->disable(ao);
-}
-
-bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error)
-{
- return ao->plugin->open(ao, audio_format, error);
-}
-
-void
-ao_plugin_close(struct audio_output *ao)
-{
- ao->plugin->close(ao);
-}
-
-unsigned
-ao_plugin_delay(struct audio_output *ao)
-{
- return ao->plugin->delay != NULL
- ? ao->plugin->delay(ao)
- : 0;
-}
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag)
-{
- if (ao->plugin->send_tag != NULL)
- ao->plugin->send_tag(ao, tag);
-}
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error)
-{
- return ao->plugin->play(ao, chunk, size, error);
-}
-
-void
-ao_plugin_drain(struct audio_output *ao)
-{
- if (ao->plugin->drain != NULL)
- ao->plugin->drain(ao);
-}
-
-void
-ao_plugin_cancel(struct audio_output *ao)
-{
- if (ao->plugin->cancel != NULL)
- ao->plugin->cancel(ao);
-}
-
-bool
-ao_plugin_pause(struct audio_output *ao)
-{
- return ao->plugin->pause != NULL && ao->plugin->pause(ao);
-}
diff --git a/src/output_plugin.h b/src/output_plugin.h
deleted file mode 100644
index 209ca6221..000000000
--- a/src/output_plugin.h
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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_H
-#define MPD_OUTPUT_PLUGIN_H
-
-#include <glib.h>
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct config_param;
-struct audio_format;
-struct tag;
-
-/**
- * A plugin which controls an audio output device.
- */
-struct audio_output_plugin {
- /**
- * the plugin's name
- */
- const char *name;
-
- /**
- * Test if this plugin can provide a default output, in case
- * none has been configured. This method is optional.
- */
- bool (*test_default_device)(void);
-
- /**
- * Configure and initialize the device, but do not open it
- * yet.
- *
- * @param param the configuration section, or NULL if there is
- * no configuration
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- * @return NULL on error, or an opaque pointer to the plugin's
- * data
- */
- struct audio_output *(*init)(const struct config_param *param,
- GError **error);
-
- /**
- * Free resources allocated by this device.
- */
- void (*finish)(struct audio_output *data);
-
- /**
- * Enable the device. This may allocate resources, preparing
- * for the device to be opened. Enabling a device cannot
- * fail: if an error occurs during that, it should be reported
- * by the open() method.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return true on success, false on error
- */
- bool (*enable)(struct audio_output *data, GError **error_r);
-
- /**
- * Disables the device. It is closed before this method is
- * called.
- */
- void (*disable)(struct audio_output *data);
-
- /**
- * Really open the device.
- *
- * @param audio_format the audio format in which data is going
- * to be delivered; may be modified by the plugin
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- */
- bool (*open)(struct audio_output *data, struct audio_format *audio_format,
- GError **error);
-
- /**
- * Close the device.
- */
- void (*close)(struct audio_output *data);
-
- /**
- * Returns a positive number if the output thread shall delay
- * the next call to play() or pause(). This should be
- * implemented instead of doing a sleep inside the plugin,
- * because this allows MPD to listen to commands meanwhile.
- *
- * @return the number of milliseconds to wait
- */
- unsigned (*delay)(struct audio_output *data);
-
- /**
- * Display metadata for the next chunk. Optional method,
- * because not all devices can display metadata.
- */
- void (*send_tag)(struct audio_output *data, const struct tag *tag);
-
- /**
- * Play a chunk of audio data.
- *
- * @param error location to store the error occurring, or NULL
- * to ignore errors
- * @return the number of bytes played, or 0 on error
- */
- size_t (*play)(struct audio_output *data,
- const void *chunk, size_t size,
- GError **error);
-
- /**
- * Wait until the device has finished playing.
- */
- void (*drain)(struct audio_output *data);
-
- /**
- * Try to cancel data which may still be in the device's
- * buffers.
- */
- void (*cancel)(struct audio_output *data);
-
- /**
- * Pause the device. If supported, it may perform a special
- * action, which keeps the device open, but does not play
- * anything. Output plugins like "shout" might want to play
- * silence during pause, so their clients won't be
- * disconnected. Plugins which do not support pausing will
- * simply be closed, and have to be reopened when unpaused.
- *
- * @return false on error (output will be closed then), true
- * for continue to pause
- */
- bool (*pause)(struct audio_output *data);
-
- /**
- * The mixer plugin associated with this output plugin. This
- * may be NULL if no mixer plugin is implemented. When
- * created, this mixer plugin gets the same #config_param as
- * this audio output device.
- */
- const struct mixer_plugin *mixer_plugin;
-};
-
-static inline bool
-ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
-{
- return plugin->test_default_device != NULL
- ? plugin->test_default_device()
- : false;
-}
-
-G_GNUC_MALLOC
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const struct config_param *param,
- GError **error);
-
-void
-ao_plugin_finish(struct audio_output *ao);
-
-bool
-ao_plugin_enable(struct audio_output *ao, GError **error_r);
-
-void
-ao_plugin_disable(struct audio_output *ao);
-
-bool
-ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
- GError **error);
-
-void
-ao_plugin_close(struct audio_output *ao);
-
-G_GNUC_PURE
-unsigned
-ao_plugin_delay(struct audio_output *ao);
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag);
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
- GError **error);
-
-void
-ao_plugin_drain(struct audio_output *ao);
-
-void
-ao_plugin_cancel(struct audio_output *ao);
-
-bool
-ao_plugin_pause(struct audio_output *ao);
-
-#endif
diff --git a/src/output_print.c b/src/output_print.c
deleted file mode 100644
index 483648ca2..000000000
--- a/src/output_print.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_print.h"
-#include "output_internal.h"
-#include "output_all.h"
-#include "client.h"
-
-void
-printAudioDevices(struct client *client)
-{
- unsigned n = audio_output_count();
-
- for (unsigned i = 0; i < n; ++i) {
- const struct audio_output *ao = audio_output_get(i);
-
- client_printf(client,
- "outputid: %i\n"
- "outputname: %s\n"
- "outputenabled: %i\n",
- i, ao->name, ao->enabled);
- }
-}
diff --git a/src/output_print.h b/src/output_print.h
deleted file mode 100644
index e02f4e9f5..000000000
--- a/src/output_print.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 OUTPUT_PRINT_H
-#define OUTPUT_PRINT_H
-
-struct client;
-
-void
-printAudioDevices(struct client *client);
-
-#endif
diff --git a/src/output_state.c b/src/output_state.c
deleted file mode 100644
index 7bcafb36b..000000000
--- a/src/output_state.c
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_state.h"
-#include "output_internal.h"
-#include "output_all.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define AUDIO_DEVICE_STATE "audio_device_state:"
-
-unsigned audio_output_state_version;
-
-void
-audio_output_state_save(FILE *fp)
-{
- unsigned n = audio_output_count();
-
- assert(n > 0);
-
- for (unsigned i = 0; i < n; ++i) {
- const struct audio_output *ao = audio_output_get(i);
-
- fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
- ao->enabled, ao->name);
- }
-}
-
-bool
-audio_output_state_read(const char *line)
-{
- long value;
- char *endptr;
- const char *name;
- struct audio_output *ao;
-
- if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
- return false;
-
- line += sizeof(AUDIO_DEVICE_STATE) - 1;
-
- value = strtol(line, &endptr, 10);
- if (*endptr != ':' || (value != 0 && value != 1))
- return false;
-
- if (value != 0)
- /* state is "enabled": no-op */
- return true;
-
- name = endptr + 1;
- ao = audio_output_find(name);
- if (ao == NULL) {
- g_debug("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_state.h b/src/output_state.h
deleted file mode 100644
index 320a3520a..000000000
--- a/src/output_state.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 OUTPUT_STATE_H
-#define OUTPUT_STATE_H
-
-#include <stdbool.h>
-#include <stdio.h>
-
-bool
-audio_output_state_read(const char *line);
-
-void
-audio_output_state_save(FILE *fp);
-
-/**
- * Generates a version number for the current state of the audio
- * outputs. This is used by timer_save_state_file() to determine
- * whether the state has changed and the state file should be saved.
- */
-unsigned
-audio_output_state_get_version(void);
-
-#endif
diff --git a/src/output_thread.c b/src/output_thread.c
deleted file mode 100644
index 4eef2ccdd..000000000
--- a/src/output_thread.c
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "output_thread.h"
-#include "output_api.h"
-#include "output_internal.h"
-#include "chunk.h"
-#include "pipe.h"
-#include "player_control.h"
-#include "pcm_mix.h"
-#include "filter_plugin.h"
-#include "filter/convert_filter_plugin.h"
-#include "filter/replay_gain_filter_plugin.h"
-#include "mpd_error.h"
-#include "notify.h"
-#include "gcc.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
-static void ao_command_finished(struct audio_output *ao)
-{
- assert(ao->command != AO_COMMAND_NONE);
- ao->command = AO_COMMAND_NONE;
-
- g_mutex_unlock(ao->mutex);
- notify_signal(&audio_output_client_notify);
- g_mutex_lock(ao->mutex);
-}
-
-static bool
-ao_enable(struct audio_output *ao)
-{
- GError *error = NULL;
- bool success;
-
- if (ao->really_enabled)
- return true;
-
- g_mutex_unlock(ao->mutex);
- success = ao_plugin_enable(ao, &error);
- g_mutex_lock(ao->mutex);
- if (!success) {
- g_warning("Failed to enable \"%s\" [%s]: %s\n",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return false;
- }
-
- ao->really_enabled = true;
- return true;
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain);
-
-static void
-ao_disable(struct audio_output *ao)
-{
- if (ao->open)
- ao_close(ao, false);
-
- if (ao->really_enabled) {
- ao->really_enabled = false;
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_disable(ao);
- g_mutex_lock(ao->mutex);
- }
-}
-
-static const struct audio_format *
-ao_filter_open(struct audio_output *ao,
- struct audio_format *audio_format,
- GError **error_r)
-{
- assert(audio_format_valid(audio_format));
-
- /* the replay_gain filter cannot fail here */
- if (ao->replay_gain_filter != NULL)
- filter_open(ao->replay_gain_filter, audio_format, error_r);
- if (ao->other_replay_gain_filter != NULL)
- filter_open(ao->other_replay_gain_filter, audio_format,
- error_r);
-
- const struct audio_format *af
- = filter_open(ao->filter, audio_format, error_r);
- if (af == NULL) {
- if (ao->replay_gain_filter != NULL)
- filter_close(ao->replay_gain_filter);
- if (ao->other_replay_gain_filter != NULL)
- filter_close(ao->other_replay_gain_filter);
- }
-
- return af;
-}
-
-static void
-ao_filter_close(struct audio_output *ao)
-{
- if (ao->replay_gain_filter != NULL)
- filter_close(ao->replay_gain_filter);
- if (ao->other_replay_gain_filter != NULL)
- filter_close(ao->other_replay_gain_filter);
-
- filter_close(ao->filter);
-}
-
-static void
-ao_open(struct audio_output *ao)
-{
- bool success;
- GError *error = NULL;
- const struct audio_format *filter_audio_format;
- struct audio_format_string af_string;
-
- assert(!ao->open);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
- assert(audio_format_valid(&ao->in_audio_format));
-
- if (ao->fail_timer != NULL) {
- /* this can only happen when this
- output thread fails while
- audio_output_open() is run in the
- player thread */
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- /* enable the device (just in case the last enable has failed) */
-
- if (!ao_enable(ao))
- /* still no luck */
- return;
-
- /* open the filter */
-
- filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
- if (filter_audio_format == NULL) {
- g_warning("Failed to open filter for \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- return;
- }
-
- assert(audio_format_valid(filter_audio_format));
-
- ao->out_audio_format = *filter_audio_format;
- audio_format_mask_apply(&ao->out_audio_format,
- &ao->config_audio_format);
-
- g_mutex_unlock(ao->mutex);
- success = ao_plugin_open(ao, &ao->out_audio_format, &error);
- g_mutex_lock(ao->mutex);
-
- assert(!ao->open);
-
- if (!success) {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao_filter_close(ao);
- ao->fail_timer = g_timer_new();
- return;
- }
-
- convert_filter_set(ao->convert_filter, &ao->out_audio_format);
-
- ao->open = true;
-
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%s",
- ao->plugin->name, ao->name,
- audio_format_to_string(&ao->out_audio_format, &af_string));
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %s",
- audio_format_to_string(&ao->in_audio_format,
- &af_string));
-}
-
-static void
-ao_close(struct audio_output *ao, bool drain)
-{
- assert(ao->open);
-
- ao->pipe = NULL;
-
- ao->chunk = NULL;
- ao->open = false;
-
- g_mutex_unlock(ao->mutex);
-
- if (drain)
- ao_plugin_drain(ao);
- else
- ao_plugin_cancel(ao);
-
- ao_plugin_close(ao);
- ao_filter_close(ao);
-
- g_mutex_lock(ao->mutex);
-
- g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
-}
-
-static void
-ao_reopen_filter(struct audio_output *ao)
-{
- const struct audio_format *filter_audio_format;
- GError *error = NULL;
-
- ao_filter_close(ao);
- filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
- if (filter_audio_format == NULL) {
- g_warning("Failed to open filter for \"%s\" [%s]: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- /* this is a little code duplication fro ao_close(),
- but we cannot call this function because we must
- not call filter_close(ao->filter) again */
-
- ao->pipe = NULL;
-
- ao->chunk = NULL;
- ao->open = false;
- ao->fail_timer = g_timer_new();
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_close(ao);
- g_mutex_lock(ao->mutex);
-
- return;
- }
-
- convert_filter_set(ao->convert_filter, &ao->out_audio_format);
-}
-
-static void
-ao_reopen(struct audio_output *ao)
-{
- if (!audio_format_fully_defined(&ao->config_audio_format)) {
- if (ao->open) {
- const struct music_pipe *mp = ao->pipe;
- ao_close(ao, true);
- ao->pipe = mp;
- }
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- audio_format_mask_apply(&ao->out_audio_format,
- &ao->config_audio_format);
- }
-
- if (ao->open)
- /* the audio format has changed, and all filters have
- to be reconfigured */
- ao_reopen_filter(ao);
- else
- ao_open(ao);
-}
-
-/**
- * Wait until the output's delay reaches zero.
- *
- * @return true if playback should be continued, false if a command
- * was issued
- */
-static bool
-ao_wait(struct audio_output *ao)
-{
- while (true) {
- unsigned delay = ao_plugin_delay(ao);
- if (delay == 0)
- return true;
-
- GTimeVal tv;
- g_get_current_time(&tv);
- g_time_val_add(&tv, delay * 1000);
- (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
-
- if (ao->command != AO_COMMAND_NONE)
- return false;
- }
-}
-
-static const char *
-ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
- struct filter *replay_gain_filter,
- unsigned *replay_gain_serial_p,
- size_t *length_r)
-{
- assert(chunk != NULL);
- assert(!music_chunk_is_empty(chunk));
- assert(music_chunk_check_format(chunk, &ao->in_audio_format));
-
- const char *data = chunk->data;
- size_t length = chunk->length;
-
- (void)ao;
-
- assert(length % audio_format_frame_size(&ao->in_audio_format) == 0);
-
- if (length > 0 && replay_gain_filter != NULL) {
- 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
- : NULL);
- *replay_gain_serial_p = chunk->replay_gain_serial;
- }
-
- GError *error = NULL;
- data = filter_filter(replay_gain_filter, data, length,
- &length, &error);
- if (data == NULL) {
- g_warning("\"%s\" [%s] failed to filter: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return NULL;
- }
- }
-
- *length_r = length;
- return data;
-}
-
-static const char *
-ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
- size_t *length_r)
-{
- GError *error = NULL;
-
- size_t length;
- const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
- &ao->replay_gain_serial, &length);
- if (data == NULL)
- return NULL;
-
- if (length == 0) {
- /* empty chunk, nothing to do */
- *length_r = 0;
- return data;
- }
-
- /* cross-fade */
-
- if (chunk->other != NULL) {
- size_t other_length;
- const char *other_data =
- ao_chunk_data(ao, chunk->other,
- ao->other_replay_gain_filter,
- &ao->other_replay_gain_serial,
- &other_length);
- if (other_data == NULL)
- return NULL;
-
- if (other_length == 0) {
- *length_r = 0;
- return data;
- }
-
- /* if the "other" chunk is longer, then that trailer
- is used as-is, without mixing; it is part of the
- "next" song being faded in, and if there's a rest,
- it means cross-fading ends here */
-
- if (length > other_length)
- length = other_length;
-
- char *dest = pcm_buffer_get(&ao->cross_fade_buffer,
- other_length);
- memcpy(dest, other_data, other_length);
- if (!pcm_mix(dest, data, length, ao->in_audio_format.format,
- 1.0 - chunk->mix_ratio)) {
- g_warning("Cannot cross-fade format %s",
- sample_format_to_string(ao->in_audio_format.format));
- return NULL;
- }
-
- data = dest;
- length = other_length;
- }
-
- /* apply filter chain */
-
- data = filter_filter(ao->filter, data, length, &length, &error);
- if (data == NULL) {
- g_warning("\"%s\" [%s] failed to filter: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
- return NULL;
- }
-
- *length_r = length;
- return data;
-}
-
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
-{
- GError *error = NULL;
-
- assert(ao != NULL);
- assert(ao->filter != NULL);
-
- if (chunk->tag != NULL) {
- g_mutex_unlock(ao->mutex);
- ao_plugin_send_tag(ao, chunk->tag);
- g_mutex_lock(ao->mutex);
- }
-
- size_t size;
-#if GCC_CHECK_VERSION(4,7)
- /* workaround -Wmaybe-uninitialized false positive */
- size = 0;
-#endif
- const char *data = ao_filter_chunk(ao, chunk, &size);
- if (data == NULL) {
- ao_close(ao, false);
-
- /* don't automatically reopen this device for 10
- seconds */
- ao->fail_timer = g_timer_new();
- return false;
- }
-
- while (size > 0 && ao->command == AO_COMMAND_NONE) {
- size_t nbytes;
-
- if (!ao_wait(ao))
- break;
-
- g_mutex_unlock(ao->mutex);
- nbytes = ao_plugin_play(ao, data, size, &error);
- g_mutex_lock(ao->mutex);
- if (nbytes == 0) {
- /* play()==0 means failure */
- g_warning("\"%s\" [%s] failed to play: %s",
- ao->name, ao->plugin->name, error->message);
- g_error_free(error);
-
- ao_close(ao, false);
-
- /* don't automatically reopen this device for
- 10 seconds */
- assert(ao->fail_timer == NULL);
- ao->fail_timer = g_timer_new();
-
- return false;
- }
-
- assert(nbytes <= size);
- assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0);
-
- data += nbytes;
- size -= nbytes;
- }
-
- return true;
-}
-
-static const struct music_chunk *
-ao_next_chunk(struct audio_output *ao)
-{
- return ao->chunk != NULL
- /* continue the previous play() call */
- ? ao->chunk->next
- /* get the first chunk from the pipe */
- : music_pipe_peek(ao->pipe);
-}
-
-/**
- * Plays all remaining chunks, until the tail of the pipe has been
- * reached (and no more chunks are queued), or until a command is
- * received.
- *
- * @return true if at least one chunk has been available, false if the
- * tail of the pipe was already reached
- */
-static bool
-ao_play(struct audio_output *ao)
-{
- bool success;
- const struct music_chunk *chunk;
-
- assert(ao->pipe != NULL);
-
- chunk = ao_next_chunk(ao);
- if (chunk == NULL)
- /* no chunk available */
- return false;
-
- ao->chunk_finished = false;
-
- while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
- assert(!ao->chunk_finished);
-
- ao->chunk = chunk;
-
- success = ao_play_chunk(ao, chunk);
- if (!success) {
- assert(ao->chunk == NULL);
- break;
- }
-
- assert(ao->chunk == chunk);
- chunk = chunk->next;
- }
-
- ao->chunk_finished = true;
-
- g_mutex_unlock(ao->mutex);
- player_lock_signal(ao->player_control);
- g_mutex_lock(ao->mutex);
-
- return true;
-}
-
-static void ao_pause(struct audio_output *ao)
-{
- bool ret;
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao);
- g_mutex_lock(ao->mutex);
-
- ao->pause = true;
- ao_command_finished(ao);
-
- do {
- if (!ao_wait(ao))
- break;
-
- g_mutex_unlock(ao->mutex);
- ret = ao_plugin_pause(ao);
- g_mutex_lock(ao->mutex);
-
- if (!ret) {
- ao_close(ao, false);
- break;
- }
- } while (ao->command == AO_COMMAND_NONE);
-
- ao->pause = false;
-}
-
-static gpointer audio_output_task(gpointer arg)
-{
- struct audio_output *ao = arg;
-
- g_mutex_lock(ao->mutex);
-
- while (1) {
- switch (ao->command) {
- case AO_COMMAND_NONE:
- break;
-
- case AO_COMMAND_ENABLE:
- ao_enable(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_DISABLE:
- ao_disable(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_OPEN:
- ao_open(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_REOPEN:
- ao_reopen(ao);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_CLOSE:
- assert(ao->open);
- assert(ao->pipe != NULL);
-
- ao_close(ao, false);
- ao_command_finished(ao);
- break;
-
- case AO_COMMAND_PAUSE:
- if (!ao->open) {
- /* the output has failed after
- audio_output_all_pause() has
- submitted the PAUSE command; bail
- out */
- ao_command_finished(ao);
- break;
- }
-
- ao_pause(ao);
- /* don't "break" here: this might cause
- ao_play() to be called when command==CLOSE
- ends the paused state - "continue" checks
- the new command first */
- continue;
-
- case AO_COMMAND_DRAIN:
- if (ao->open) {
- assert(ao->chunk == NULL);
- assert(music_pipe_peek(ao->pipe) == NULL);
-
- g_mutex_unlock(ao->mutex);
- ao_plugin_drain(ao);
- g_mutex_lock(ao->mutex);
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_CANCEL:
- ao->chunk = NULL;
-
- if (ao->open) {
- g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao);
- g_mutex_lock(ao->mutex);
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_KILL:
- ao->chunk = NULL;
- ao_command_finished(ao);
- g_mutex_unlock(ao->mutex);
- return NULL;
- }
-
- if (ao->open && ao->allow_play && ao_play(ao))
- /* don't wait for an event if there are more
- chunks in the pipe */
- continue;
-
- if (ao->command == AO_COMMAND_NONE)
- g_cond_wait(ao->cond, ao->mutex);
- }
-}
-
-void audio_output_thread_start(struct audio_output *ao)
-{
- GError *e = NULL;
-
- assert(ao->command == AO_COMMAND_NONE);
-
- if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
- MPD_ERROR("Failed to spawn output task: %s\n", e->message);
-}
diff --git a/src/output_thread.h b/src/output_thread.h
deleted file mode 100644
index 5ad9a7527..000000000
--- a/src/output_thread.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_THREAD_H
-#define MPD_OUTPUT_THREAD_H
-
-struct audio_output;
-
-void audio_output_thread_start(struct audio_output *ao);
-
-#endif