aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/output/alsa_plugin.c277
-rw-r--r--src/output/ao_plugin.c28
-rw-r--r--src/output/ffado_output_plugin.c347
-rw-r--r--src/output/fifo_output_plugin.c (renamed from src/output/fifo_plugin.c)16
-rw-r--r--src/output/httpd_client.c20
-rw-r--r--src/output/httpd_client.h2
-rw-r--r--src/output/httpd_internal.h50
-rw-r--r--src/output/httpd_output_plugin.c237
-rw-r--r--src/output/jack_output_plugin.c722
-rw-r--r--src/output/jack_plugin.c450
-rw-r--r--src/output/mvp_plugin.c23
-rw-r--r--src/output/null_plugin.c7
-rw-r--r--src/output/openal_plugin.c277
-rw-r--r--src/output/oss_plugin.c686
-rw-r--r--src/output/osx_plugin.c24
-rw-r--r--src/output/pipe_output_plugin.c3
-rw-r--r--src/output/pulse_output_plugin.c825
-rw-r--r--src/output/pulse_output_plugin.h72
-rw-r--r--src/output/pulse_plugin.c179
-rw-r--r--src/output/recorder_output_plugin.c218
-rw-r--r--src/output/shout_plugin.c42
-rw-r--r--src/output/solaris_output_plugin.c10
-rw-r--r--src/output/winmm_output_plugin.c337
-rw-r--r--src/output/winmm_output_plugin.h29
-rw-r--r--src/output_all.c160
-rw-r--r--src/output_all.h36
-rw-r--r--src/output_api.h2
-rw-r--r--src/output_command.c18
-rw-r--r--src/output_command.h2
-rw-r--r--src/output_control.c186
-rw-r--r--src/output_control.h28
-rw-r--r--src/output_init.c211
-rw-r--r--src/output_internal.h95
-rw-r--r--src/output_list.c28
-rw-r--r--src/output_list.h2
-rw-r--r--src/output_plugin.h66
-rw-r--r--src/output_print.c3
-rw-r--r--src/output_print.h2
-rw-r--r--src/output_state.c58
-rw-r--r--src/output_state.h17
-rw-r--r--src/output_thread.c572
-rw-r--r--src/output_thread.h2
42 files changed, 4958 insertions, 1411 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 818c83ca2..9177fabe4 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
#include <glib.h>
@@ -69,6 +70,16 @@ struct alsa_data {
/** the size of one audio frame */
size_t 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;
};
/**
@@ -172,15 +183,148 @@ alsa_test_default_device(void)
}
static snd_pcm_format_t
-get_bitformat(const struct audio_format *af)
+get_bitformat(enum sample_format sample_format)
+{
+ switch (sample_format) {
+ 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_S24:
+ return G_BYTE_ORDER == G_BIG_ENDIAN
+ ? SND_PCM_FORMAT_S24_3BE
+ : SND_PCM_FORMAT_S24_3LE;
+
+ case SAMPLE_FORMAT_S32:
+ return SND_PCM_FORMAT_S32;
+
+ default:
+ 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;
+ }
+}
+
+/**
+ * Attempts to configure the specified sample format.
+ */
+static int
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ struct audio_format *audio_format,
+ enum sample_format sample_format)
+{
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
+ if (err == 0)
+ audio_format->format = sample_format;
+
+ return err;
+}
+
+/**
+ * Attempts to configure the specified sample format with reversed
+ * host byte order.
+ */
+static int
+alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ struct audio_format *audio_format,
+ enum sample_format sample_format)
+{
+ snd_pcm_format_t alsa_format =
+ byteswap_bitformat(get_bitformat(sample_format));
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
+ if (err == 0) {
+ audio_format->format = sample_format;
+ audio_format->reverse_endian = 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_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ struct audio_format *audio_format,
+ enum sample_format sample_format)
+{
+ int err = alsa_output_try_format(pcm, hwparams, audio_format,
+ sample_format);
+ if (err == -EINVAL)
+ err = alsa_output_try_reverse(pcm, hwparams, audio_format,
+ sample_format);
+
+ 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)
{
- switch (af->bits) {
- case 8: return SND_PCM_FORMAT_S8;
- case 16: return SND_PCM_FORMAT_S16;
- case 24: return SND_PCM_FORMAT_S24;
- case 32: return SND_PCM_FORMAT_S32;
+ /* try the input format first */
+
+ int err = alsa_output_try_format_both(pcm, hwparams, audio_format,
+ audio_format->format);
+ if (err != -EINVAL)
+ return err;
+
+ /* if unsupported by the hardware, try other formats */
+
+ static const enum sample_format probe_formats[] = {
+ SAMPLE_FORMAT_S24_P32,
+ SAMPLE_FORMAT_S32,
+ SAMPLE_FORMAT_S24,
+ SAMPLE_FORMAT_S16,
+ SAMPLE_FORMAT_S8,
+ SAMPLE_FORMAT_UNDEFINED,
+ };
+
+ for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
+ if (probe_formats[i] == audio_format->format)
+ continue;
+
+ err = alsa_output_try_format_both(pcm, hwparams, audio_format,
+ probe_formats[i]);
+ if (err != -EINVAL)
+ return err;
}
- return SND_PCM_FORMAT_UNKNOWN;
+
+ return -EINVAL;
}
/**
@@ -189,7 +333,6 @@ get_bitformat(const struct audio_format *af)
*/
static bool
alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
- snd_pcm_format_t bitformat,
GError **error)
{
snd_pcm_hw_params_t *hwparams;
@@ -208,7 +351,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
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)
@@ -235,31 +377,12 @@ configure_hw:
ad->writei = snd_pcm_writei;
}
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat);
- if (err == -EINVAL && (audio_format->bits == 24 ||
- audio_format->bits == 16)) {
- /* fall back to 32 bit, let pcm_convert.c do the conversion */
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
- SND_PCM_FORMAT_S32);
- if (err == 0)
- audio_format->bits = 32;
- }
-
- if (err == -EINVAL && audio_format->bits != 16) {
- /* fall back to 16 bit, let pcm_convert.c do the conversion */
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
- SND_PCM_FORMAT_S16);
- if (err == 0) {
- g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n",
- alsa_device(ad), audio_format->bits);
- audio_format->bits = 16;
- }
- }
-
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format);
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support %u bit audio: %s",
- alsa_device(ad), audio_format->bits,
+ "ALSA device \"%s\" does not support format %s: %s",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format),
snd_strerror(-err));
return false;
}
@@ -285,6 +408,26 @@ configure_hw:
}
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";
@@ -365,6 +508,9 @@ configure_hw:
g_debug("buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
return true;
error:
@@ -378,19 +524,9 @@ static bool
alsa_open(void *data, struct audio_format *audio_format, GError **error)
{
struct alsa_data *ad = data;
- snd_pcm_format_t bitformat;
int err;
bool success;
- bitformat = get_bitformat(audio_format);
- if (bitformat == SND_PCM_FORMAT_UNKNOWN) {
- /* sample format is not supported by this plugin -
- fall back to 16 bit samples */
-
- audio_format->bits = 16;
- bitformat = SND_PCM_FORMAT_S16;
- }
-
err = snd_pcm_open(&ad->pcm, alsa_device(ad),
SND_PCM_STREAM_PLAYBACK, ad->mode);
if (err < 0) {
@@ -400,7 +536,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error)
return false;
}
- success = alsa_setup(ad, audio_format, bitformat, error);
+ success = alsa_setup(ad, audio_format, error);
if (!success) {
snd_pcm_close(ad->pcm);
return false;
@@ -431,6 +567,7 @@ alsa_recover(struct alsa_data *ad, int err)
/* 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:
@@ -448,11 +585,47 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
+alsa_drain(void *data)
+{
+ struct alsa_data *ad = data;
+
+ 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->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(void *data)
{
struct alsa_data *ad = data;
- alsa_recover(ad, snd_pcm_drop(ad->pcm));
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
}
static void
@@ -460,9 +633,6 @@ alsa_close(void *data)
{
struct alsa_data *ad = data;
- if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING)
- snd_pcm_drain(ad->pcm);
-
snd_pcm_close(ad->pcm);
}
@@ -475,8 +645,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0)
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
return ret * ad->frame_size;
+ }
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
alsa_recover(ad, ret) < 0) {
@@ -494,7 +667,9 @@ const struct audio_output_plugin alsaPlugin = {
.finish = alsa_finish,
.open = alsa_open,
.play = alsa_play,
+ .drain = alsa_drain,
.cancel = alsa_cancel,
.close = alsa_close,
- .mixer_plugin = &alsa_mixer,
+
+ .mixer_plugin = &alsa_mixer_plugin,
};
diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c
index 12d2b7552..d5c95018c 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <ao/ao.h>
#include <glib.h>
@@ -169,13 +170,24 @@ ao_output_open(void *data, struct audio_format *audio_format,
ao_sample_format format;
struct ao_data *ad = (struct ao_data *)data;
- /* support for 24 bit samples in libao is currently dubious,
- and until we have sorted that out, resample everything to
- 16 bit */
- if (audio_format->bits > 16)
- audio_format->bits = 16;
+ 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.bits = audio_format->bits;
format.rate = audio_format->sample_rate;
format.byte_format = AO_FMT_NATIVE;
format.channels = audio_format->channels;
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
new file mode 100644
index 000000000..723698ed0
--- /dev/null
+++ b/src/output/ffado_output_plugin.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 "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 {
+ 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 void *
+ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ 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);
+ 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) {
+ 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) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid nb_buffers setting");
+ return false;
+ }
+
+ return fd;
+}
+
+static void
+ffado_finish(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ g_free(fd->device_name);
+ 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(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* 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(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ 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(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* 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/fifo_plugin.c b/src/output/fifo_output_plugin.c
index 76bbe8cfa..f4217ec4d 100644
--- a/src/output/fifo_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,15 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../utils.h"
-#include "../timer.h"
+#include "config.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 <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
@@ -152,7 +154,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (!fifo_check(fd, error))
return false;
- fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
+ 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",
@@ -161,7 +163,7 @@ fifo_open(struct fifo_data *fd, GError **error)
return false;
}
- fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
+ 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",
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 52a398e3b..6bd095838 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,11 +17,13 @@
* 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_compat.h"
#include <stdbool.h>
#include <assert.h>
@@ -280,11 +282,12 @@ httpd_client_send_response(struct httpd_client *client)
} else {
gchar *metadata_header;
- metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */
- "Add config information here!", /* TODO */
- "Add config information here!", /* TODO */
- client->httpd->content_type,
- client->metaint);
+ 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));
@@ -482,11 +485,6 @@ httpd_client_queue_size(const struct httpd_client *client)
return size;
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
void
httpd_client_cancel(struct httpd_client *client)
{
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
index 4a2912f80..7ebd0bbc0 100644
--- a/src/output/httpd_client.h
+++ b/src/output/httpd_client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 2257e27a2..fee72c07f 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,30 +29,34 @@
#include <glib.h>
-#include <sys/socket.h>
+#include <stdbool.h>
struct httpd_client;
struct httpd_output {
/**
- * The configured encoder plugin.
+ * True if the audio output is open and accepts client
+ * connections.
*/
- struct encoder *encoder;
+ bool open;
/**
- * The MIME type produced by the #encoder.
+ * The configured encoder plugin.
*/
- const char *content_type;
+ struct encoder *encoder;
/**
- * The configured address of the listener socket.
+ * 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.
*/
- struct sockaddr_storage address;
+ size_t unflushed_input;
/**
- * The size of #address.
+ * The MIME type produced by the #encoder.
*/
- socklen_t address_size;
+ const char *content_type;
/**
* This mutex protects the listener socket and the client
@@ -69,12 +73,7 @@ struct httpd_output {
/**
* The listener socket.
*/
- int fd;
-
- /**
- * A GLib main loop source id for the listener socket.
- */
- guint source_id;
+ struct server_socket *server_socket;
/**
* The header page, which is sent to every client on connect.
@@ -87,6 +86,19 @@ struct httpd_output {
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.
*/
@@ -97,6 +109,12 @@ struct httpd_output {
* function.
*/
char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ guint clients_max, clients_cnt;
};
/**
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 026e8d9d8..72994018a 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
@@ -25,15 +26,19 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
+#include "server_socket.h"
#include <assert.h>
#include <sys/types.h>
-#include <netinet/in.h>
-#include <netdb.h>
#include <unistd.h>
#include <errno.h>
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
@@ -46,18 +51,49 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+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 void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
- const char *encoder_name;
+ const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
- struct sockaddr_in *sin;
/* 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");
port = config_get_block_unsigned(param, "port", 8000);
@@ -69,21 +105,21 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- if (strcmp(encoder_name, "vorbis") == 0)
- httpd->content_type = "audio/ogg";
- else if (strcmp(encoder_name, "lame") == 0)
- httpd->content_type = "audio/mpeg";
- else
- httpd->content_type = "application/octet-stream";
+ httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
+
+ /* set up bind_to_address */
- /* initialize listen address */
+ httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
- sin = (struct sockaddr_in *)&httpd->address;
- memset(sin, 0, sizeof(sin));
- sin->sin_port = htons(port);
- sin->sin_family = AF_INET;
- sin->sin_addr.s_addr = INADDR_ANY;
- httpd->address_size = sizeof(*sin);
+ 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)
+ return NULL;
/* initialize metadata */
httpd->metadata = NULL;
@@ -94,6 +130,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (httpd->encoder == NULL)
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;
@@ -108,6 +150,7 @@ httpd_output_finish(void *data)
page_unref(httpd->metadata);
encoder_finish(httpd->encoder);
+ server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
g_free(httpd);
}
@@ -124,36 +167,64 @@ httpd_client_add(struct httpd_output *httpd, int 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 gboolean
-httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
+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 = data;
- int fd;
- struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
-
- g_mutex_lock(httpd->mutex);
+ struct httpd_output *httpd = ctx;
/* the listener socket has become readable - a client has
connected */
- fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0)
- httpd_client_add(httpd, fd);
- else if (fd < 0 && errno != EINTR)
+#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(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(fd);
+ } else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
g_mutex_unlock(httpd->mutex);
-
- return true;
}
/**
@@ -165,12 +236,22 @@ httpd_output_read_page(struct httpd_output *httpd)
{
size_t size = 0, nbytes;
+ 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;
+ }
+
do {
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));
@@ -195,41 +276,41 @@ httpd_output_encoder_open(struct httpd_output *httpd,
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(void *data, GError **error_r)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd_output_bind(httpd, error_r);
+}
+
+static void
+httpd_output_disable(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ httpd_output_unbind(httpd);
+}
+
+static bool
httpd_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = data;
bool success;
- GIOChannel *channel;
g_mutex_lock(httpd->mutex);
- /* create and set up listener socket */
-
- httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
- (struct sockaddr *)&httpd->address,
- httpd->address_size,
- 16, error);
- if (httpd->fd < 0) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- channel = g_io_channel_unix_new(httpd->fd);
- httpd->source_id = g_io_add_watch(channel, G_IO_IN,
- httpd_listen_in_event, httpd);
- g_io_channel_unref(channel);
-
/* open the encoder */
success = httpd_output_encoder_open(httpd, audio_format, error);
if (!success) {
- g_source_remove(httpd->source_id);
- close(httpd->fd);
g_mutex_unlock(httpd->mutex);
return false;
}
@@ -237,8 +318,11 @@ httpd_output_open(void *data, struct audio_format *audio_format,
/* 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;
}
@@ -257,6 +341,8 @@ static void httpd_output_close(void *data)
g_mutex_lock(httpd->mutex);
+ httpd->open = false;
+
timer_free(httpd->timer);
g_list_foreach(httpd->clients, httpd_client_delete, NULL);
@@ -267,9 +353,6 @@ static void httpd_output_close(void *data)
encoder_close(httpd->encoder);
- g_source_remove(httpd->source_id);
- close(httpd->fd);
-
g_mutex_unlock(httpd->mutex);
}
@@ -281,6 +364,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -291,6 +375,16 @@ httpd_output_send_header(struct httpd_output *httpd,
httpd_client_send(client, httpd->header);
}
+static unsigned
+httpd_output_delay(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd->timer->started
+ ? timer_delay(httpd->timer)
+ : 0;
+}
+
static void
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -352,6 +446,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
if (!success)
return false;
+ httpd->unflushed_input += size;
+
httpd_output_encoder_to_clients(httpd);
return true;
@@ -378,13 +474,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
if (!httpd->timer->started)
timer_start(httpd->timer);
- else
- timer_sync(httpd->timer);
timer_add(httpd->timer, size);
return size;
}
+static bool
+httpd_output_pause(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ g_mutex_lock(httpd->mutex);
+ bool has_clients = httpd->clients != NULL;
+ g_mutex_unlock(httpd->mutex);
+
+ if (has_clients) {
+ static const char silence[1020];
+ return httpd_output_play(data, silence, sizeof(silence), NULL);
+ } else {
+ g_usleep(100000);
+ return true;
+ }
+}
+
static void
httpd_send_metadata(gpointer data, gpointer user_data)
{
@@ -433,9 +545,8 @@ httpd_output_tag(void *data, const struct tag *tag)
page_unref (httpd->metadata);
httpd->metadata =
- icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
- TAG_ITEM_ARTIST,
- TAG_ITEM_TITLE,
+ 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);
@@ -468,9 +579,13 @@ 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/jack_output_plugin.c b/src/output/jack_output_plugin.c
new file mode 100644
index 000000000..110ee5f26
--- /dev/null
+++ b/src/output/jack_output_plugin.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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_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 sample_size = sizeof(jack_default_audio_sample_t);
+
+struct jack_data {
+ /**
+ * 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 % sample_size == 0);
+
+ return min / 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 * 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);
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * 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);
+
+ 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 void *
+mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct jack_data *jd;
+ const char *value;
+
+ jd = g_new(struct jack_data, 1);
+ 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;
+}
+
+static void
+mpd_jack_finish(void *data)
+{
+ struct jack_data *jd = data;
+
+ 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]);
+
+ g_free(jd);
+}
+
+static bool
+mpd_jack_enable(void *data, GError **error_r)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ 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(void *data)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ 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(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct jack_data *jd = data;
+
+ assert(jd != NULL);
+
+ jd->pause = false;
+
+ 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 void *data)
+{
+ struct jack_data *jd = data;
+
+ mpd_jack_stop(jd);
+}
+
+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(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct jack_data *jd = data;
+ 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 >= frame_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(void *data)
+{
+ struct jack_data *jd = data;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ /* due to a MPD API limitation, we have to sleep a little bit
+ here, to avoid hogging the CPU */
+ g_usleep(50000);
+
+ 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,
+ .play = mpd_jack_play,
+ .pause = mpd_jack_pause,
+ .close = mpd_jack_close,
+};
diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c
deleted file mode 100644
index 5dc1eca24..000000000
--- a/src/output/jack_plugin.c
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "../output_api.h"
-#include "config.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"
-
-static const size_t sample_size = sizeof(jack_default_audio_sample_t);
-
-static const char *const port_names[2] = {
- "left", "right",
-};
-
-struct jack_data {
- const char *name;
-
- /* configuration */
- char *output_ports[2];
- int ringbuffer_size;
-
- /* the current audio format */
- struct audio_format audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[2];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[2];
-
- bool shutdown;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-static void
-mpd_jack_client_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client != NULL) {
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-static void
-mpd_jack_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i)
- g_free(jd->output_ports[i]);
-
- g_free(jd);
-}
-
-static void
-mpd_jack_finish(void *data)
-{
- struct jack_data *jd = data;
- mpd_jack_free(jd);
-}
-
-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;
- size_t available;
-
- if (nframes <= 0)
- return 0;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- assert(available % sample_size == 0);
- available /= sample_size;
- if (available > nframes)
- available = nframes;
-
- out = jack_port_get_buffer(jd->ports[i], nframes);
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * sample_size);
-
- while (available < nframes)
- /* ringbuffer underrun, fill with silence */
- out[available++] = 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);
- audio_format->channels = 2;
-
- if (audio_format->bits != 16 && audio_format->bits != 24)
- audio_format->bits = 24;
-}
-
-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
-
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error)
-{
- struct jack_data *jd;
- const char *value;
-
- jd = g_new(struct jack_data, 1);
- jd->name = config_get_block_string(param, "name", "mpd_jack");
-
- g_debug("mpd_jack_init (pid=%d)", getpid());
-
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL) {
- char **ports = g_strsplit(value, ",", 0);
-
- if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "two port names expected in line %d",
- param->line);
- return NULL;
- }
-
- jd->output_ports[0] = ports[0];
- jd->output_ports[1] = ports[1];
-
- g_free(ports);
- } else {
- jd->output_ports[0] = NULL;
- jd->output_ports[1] = NULL;
- }
-
- 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;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error)
-{
- const char *output_ports[2], **jports;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- jd->shutdown = false;
-
- if ((jd->client = jack_client_new(jd->name)) == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Failed to connect to JACK server");
- 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 < G_N_ELEMENTS(jd->ports); ++i) {
- jd->ports[i] = jack_port_register(jd->client, port_names[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Cannot register output port \"%s\"",
- port_names[i]);
- return false;
- }
- }
-
- if ( jack_activate(jd->client) ) {
- g_set_error(error, jack_output_quark(), 0,
- "cannot activate client");
- return false;
- }
-
- if (jd->output_ports[1] == NULL) {
- /* 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, jack_output_quark(), 0,
- "no ports found");
- return false;
- }
-
- output_ports[0] = jports[0];
- output_ports[1] = jports[1] != NULL ? jports[1] : jports[0];
-
- g_debug("output_ports: %s %s", jports[0], jports[1]);
- } else {
- /* use the configured output ports */
-
- output_ports[0] = jd->output_ports[0];
- output_ports[1] = jd->output_ports[1];
-
- jports = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- output_ports[i]);
- if (ret != 0) {
- g_set_error(error, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- output_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct jack_data *jd = data;
-
- assert(jd != NULL);
-
- if (!mpd_jack_connect(jd, error)) {
- mpd_jack_client_free(jd);
- return false;
- }
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
-{
- struct jack_data *jd = data;
-
- mpd_jack_client_free(jd);
-}
-
-static void
-mpd_jack_cancel (G_GNUC_UNUSED void *data)
-{
-}
-
-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;
-
- while (num_samples-- > 0) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (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;
-
- while (num_samples-- > 0) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.bits) {
- case 16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case 24:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
-{
- struct jack_data *jd = data;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error, jack_output_quark(), 0,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
-
- if (space >= frame_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-const struct audio_output_plugin jackPlugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .open = mpd_jack_open,
- .play = mpd_jack_play,
- .cancel = mpd_jack_cancel,
- .close = mpd_jack_close,
-};
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c
index 96f9435a8..20587f5c5 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,7 +22,9 @@
* http://mvpmc.sourceforge.net/
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -115,7 +117,7 @@ mvp_output_test_default_device(void)
{
int fd;
- fd = open("/dev/adec_pcm", O_WRONLY);
+ fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
if (fd >= 0) {
close(fd);
@@ -170,19 +172,19 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
}
/* 0,1=24bit(24) , 2,3=16bit */
- switch (audio_format->bits) {
- case 16:
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S16:
mix[1] = 2;
break;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
mix[1] = 0;
break;
default:
- g_debug("unsupported sample format %u - falling back to stereo",
- audio_format->bits);
- audio_format->bits = 16;
+ 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;
}
@@ -230,7 +232,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
- if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
+ 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",
strerror(errno));
diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c
index e9731b019..89abbd91f 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../timer.h"
+#include "config.h"
+#include "output_api.h"
+#include "timer.h"
#include <glib.h>
diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c
new file mode 100644
index 000000000..767b3eb17
--- /dev/null
+++ b/src/output/openal_plugin.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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_api.h"
+#include "timer.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 {
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ Timer *timer;
+ ALuint buffers[NUM_BUFFERS];
+ int 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)
+{
+ 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;
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO8;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO8;
+ break;
+
+ default:
+ /* fall back to 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+ }
+
+ return 0;
+}
+
+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 void
+openal_unqueue_buffers(struct openal_data *od)
+{
+ ALint num;
+ ALuint buffer;
+
+ alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num);
+
+ while (num--) {
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+}
+
+static void *
+openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ 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);
+ od->device_name = device_name;
+
+ return od;
+}
+
+static void
+openal_finish(void *data)
+{
+ struct openal_data *od = data;
+
+ g_free(od);
+}
+
+static bool
+openal_open(void *data, struct audio_format *audio_format,
+ GError **error)
+{
+ struct openal_data *od = data;
+
+ od->format = openal_audio_format(audio_format);
+
+ if (!od->format) {
+ struct audio_format_string s;
+ g_set_error(error, openal_output_quark(), 0,
+ "Unsupported audio format: %s",
+ audio_format_to_string(audio_format, &s));
+ return false;
+ }
+
+ 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->timer = timer_new(audio_format);
+ od->frequency = audio_format->sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(void *data)
+{
+ struct openal_data *od = data;
+
+ timer_free(od->timer);
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static size_t
+openal_play(void *data, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct openal_data *od = data;
+ ALuint buffer;
+ ALint num, state;
+
+ if (alcGetCurrentContext() != od->context) {
+ alcMakeContextCurrent(od->context);
+ }
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+
+ if (od->filled < NUM_BUFFERS) {
+ /* fill all buffers */
+ buffer = od->buffers[od->filled];
+ od->filled++;
+ } else {
+ /* wait for processed buffer */
+ while (num < 1) {
+ if (!od->timer->started) {
+ timer_start(od->timer);
+ } else {
+ timer_sync(od->timer);
+ }
+
+ timer_add(od->timer, size);
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+ }
+
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+
+ alBufferData(buffer, od->format, chunk, size, od->frequency);
+ alSourceQueueBuffers(od->source, 1, &buffer);
+ alGetSourcei(od->source, AL_SOURCE_STATE, &state);
+
+ if (state != AL_PLAYING) {
+ alSourcePlay(od->source);
+ }
+
+ return size;
+}
+
+static void
+openal_cancel(void *data)
+{
+ struct openal_data *od = data;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+ openal_unqueue_buffers(od);
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+ .name = "openal",
+ .init = openal_init,
+ .finish = openal_finish,
+ .open = openal_open,
+ .close = openal_close,
+ .play = openal_play,
+ .cancel = openal_cancel,
+};
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index a66bc0598..bb4164990 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
+#include "fd_util.h"
#include <glib.h>
@@ -28,6 +30,7 @@
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "oss"
@@ -38,33 +41,15 @@
# include <sys/soundcard.h>
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-#if G_BYTE_ORDER == G_BIG_ENDIAN
-# define AFMT_S16_MPD AFMT_S16_BE
-#else
-# define AFMT_S16_MPD AFMT_S16_LE
-#endif
-
struct oss_data {
int fd;
const char *device;
- struct audio_format audio_format;
- int bitFormat;
- int *supported[3];
- unsigned num_supported[3];
- int *unsupported[3];
- unsigned num_unsupported[3];
-};
-
-enum oss_support {
- OSS_SUPPORTED = 1,
- OSS_UNSUPPORTED = 0,
- OSS_UNKNOWN = -1,
-};
-enum oss_param {
- OSS_RATE = 0,
- OSS_CHANNELS = 1,
- OSS_BITS = 2,
+ /**
+ * The current input audio format. This is needed to reopen
+ * the device after cancel().
+ */
+ struct audio_format audio_format;
};
/**
@@ -76,188 +61,6 @@ oss_output_quark(void)
return g_quark_from_static_string("oss_output");
}
-static enum oss_param
-oss_param_from_ioctl(unsigned param)
-{
- enum oss_param idx = OSS_RATE;
-
- switch (param) {
- case SNDCTL_DSP_SPEED:
- idx = OSS_RATE;
- break;
- case SNDCTL_DSP_CHANNELS:
- idx = OSS_CHANNELS;
- break;
- case SNDCTL_DSP_SAMPLESIZE:
- idx = OSS_BITS;
- break;
- }
-
- return idx;
-}
-
-static bool
-oss_find_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_supported[idx]; i++)
- if (od->supported[idx][i] == val)
- return true;
-
- return false;
-}
-
-static bool
-oss_can_convert(int idx, int val)
-{
- switch (idx) {
- case OSS_BITS:
- if (val != 16)
- return false;
- break;
- case OSS_CHANNELS:
- if (val != 2)
- return false;
- break;
- }
-
- return true;
-}
-
-static int
-oss_get_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
- int ret = -1;
- int least = val;
- int diff;
-
- for (unsigned i = 0; i < od->num_supported[idx]; i++) {
- diff = od->supported[idx][i] - val;
- if (diff < 0)
- diff = -diff;
- if (diff < least) {
- if (!oss_can_convert(idx, od->supported[idx][i]))
- continue;
-
- least = diff;
- ret = od->supported[idx][i];
- }
- }
-
- return ret;
-}
-
-static bool
-oss_find_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_unsupported[idx]; i++) {
- if (od->unsupported[idx][i] == val)
- return true;
- }
-
- return false;
-}
-
-static void
-oss_add_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- od->num_supported[idx]++;
- od->supported[idx] = g_realloc(od->supported[idx],
- od->num_supported[idx] * sizeof(int));
- od->supported[idx][od->num_supported[idx] - 1] = val;
-}
-
-static void
-oss_add_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- od->num_unsupported[idx]++;
- od->unsupported[idx] = g_realloc(od->unsupported[idx],
- od->num_unsupported[idx] *
- sizeof(int));
- od->unsupported[idx][od->num_unsupported[idx] - 1] = val;
-}
-
-static void
-oss_remove_supported_param(struct oss_data *od, unsigned param, int val)
-{
- unsigned j = 0;
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_supported[idx] - 1; i++) {
- if (od->supported[idx][i] == val)
- j = 1;
- od->supported[idx][i] = od->supported[idx][i + j];
- }
-
- od->num_supported[idx]--;
- od->supported[idx] = g_realloc(od->supported[idx],
- od->num_supported[idx] * sizeof(int));
-}
-
-static void
-oss_remove_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- unsigned j = 0;
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_unsupported[idx] - 1; i++) {
- if (od->unsupported[idx][i] == val)
- j = 1;
- od->unsupported[idx][i] = od->unsupported[idx][i + j];
- }
-
- od->num_unsupported[idx]--;
- od->unsupported[idx] = g_realloc(od->unsupported[idx],
- od->num_unsupported[idx] *
- sizeof(int));
-}
-
-static enum oss_support
-oss_param_is_supported(struct oss_data *od, unsigned param, int val)
-{
- if (oss_find_supported_param(od, param, val))
- return OSS_SUPPORTED;
- if (oss_find_unsupported_param(od, param, val))
- return OSS_UNSUPPORTED;
- return OSS_UNKNOWN;
-}
-
-static void
-oss_set_supported(struct oss_data *od, unsigned param, int val)
-{
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- if (supported == OSS_SUPPORTED)
- return;
-
- if (supported == OSS_UNSUPPORTED)
- oss_remove_unsupported_param(od, param, val);
-
- oss_add_supported_param(od, param, val);
-}
-
-static void
-oss_set_unsupported(struct oss_data *od, unsigned param, int val)
-{
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- if (supported == OSS_UNSUPPORTED)
- return;
-
- if (supported == OSS_SUPPORTED)
- oss_remove_supported_param(od, param, val);
-
- oss_add_unsupported_param(od, param, val);
-}
-
static struct oss_data *
oss_data_new(void)
{
@@ -266,38 +69,12 @@ oss_data_new(void)
ret->device = NULL;
ret->fd = -1;
- ret->supported[OSS_RATE] = NULL;
- ret->supported[OSS_CHANNELS] = NULL;
- ret->supported[OSS_BITS] = NULL;
- ret->unsupported[OSS_RATE] = NULL;
- ret->unsupported[OSS_CHANNELS] = NULL;
- ret->unsupported[OSS_BITS] = NULL;
-
- ret->num_supported[OSS_RATE] = 0;
- ret->num_supported[OSS_CHANNELS] = 0;
- ret->num_supported[OSS_BITS] = 0;
- ret->num_unsupported[OSS_RATE] = 0;
- ret->num_unsupported[OSS_CHANNELS] = 0;
- ret->num_unsupported[OSS_BITS] = 0;
-
- oss_set_supported(ret, SNDCTL_DSP_SPEED, 48000);
- oss_set_supported(ret, SNDCTL_DSP_SPEED, 44100);
- oss_set_supported(ret, SNDCTL_DSP_CHANNELS, 2);
- oss_set_supported(ret, SNDCTL_DSP_SAMPLESIZE, 16);
-
return ret;
}
static void
oss_data_free(struct oss_data *od)
{
- g_free(od->supported[OSS_RATE]);
- g_free(od->supported[OSS_CHANNELS]);
- g_free(od->supported[OSS_BITS]);
- g_free(od->unsupported[OSS_RATE]);
- g_free(od->unsupported[OSS_CHANNELS]);
- g_free(od->unsupported[OSS_BITS]);
-
g_free(od);
}
@@ -343,7 +120,9 @@ oss_output_test_default_device(void)
int fd, i;
for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
close(fd);
return true;
}
@@ -419,113 +198,386 @@ oss_output_finish(void *data)
oss_data_free(od);
}
-static int
-oss_set_param(struct oss_data *od, unsigned param, int *value)
+static void
+oss_close(struct oss_data *od)
{
- int val = *value;
- int copy;
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- do {
- if (supported == OSS_UNSUPPORTED) {
- val = oss_get_supported_param(od, param, val);
- if (copy < 0)
- return -1;
- }
- copy = val;
- if (ioctl(od->fd, param, &copy)) {
- oss_set_unsupported(od, param, val);
- supported = OSS_UNSUPPORTED;
- } else {
- if (supported == OSS_UNKNOWN) {
- oss_set_supported(od, param, val);
- supported = OSS_SUPPORTED;
- }
- val = copy;
- }
- } while (supported == OSS_UNSUPPORTED);
+ 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);
- *value = val;
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
- return 0;
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ g_set_error(error_r, oss_output_quark(), errno,
+ "%s: %s", msg, g_strerror(errno));
+ return ERROR;
}
-static void
-oss_close(struct oss_data *od)
+/**
+ * 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)
{
- if (od->fd >= 0)
- while (close(od->fd) && errno == EINTR) ;
- od->fd = -1;
+ return oss_try_ioctl_r(fd, request, &value, msg, error_r);
}
/**
- * Sets up the OSS device which was opened before.
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
*/
static bool
-oss_setup(struct oss_data *od, GError **error)
+oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
{
- int tmp;
-
- tmp = od->audio_format.channels;
- if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u channels: %s",
- od->device, od->audio_format.channels,
- strerror(errno));
+ 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;
}
- od->audio_format.channels = tmp;
- tmp = od->audio_format.sample_rate;
- if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u Hz audio: %s",
- od->device, od->audio_format.sample_rate,
- strerror(errno));
- return false;
+ 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;
+ }
}
- od->audio_format.sample_rate = tmp;
- switch (od->audio_format.bits) {
- case 8:
- tmp = AFMT_S8;
- break;
- case 16:
- tmp = AFMT_S16_MPD;
+ 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:
+ return AFMT_QUERY;
+
+ case SAMPLE_FORMAT_S8:
+ return AFMT_S8;
+
+ case SAMPLE_FORMAT_S16:
+ return AFMT_S16_NE;
+
+ case SAMPLE_FORMAT_S24:
+#ifdef AFMT_S24_PACKED
+ return AFMT_S24_PACKED;
+#else
+ return AFMT_QUERY;
+#endif
+
+ 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;
+#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:
- /* not supported by OSS - fall back to 16 bit */
- od->audio_format.bits = 16;
- tmp = AFMT_S16_MPD;
- break;
+ return SAMPLE_FORMAT_UNDEFINED;
}
+}
- if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u bit audio: %s",
- od->device, tmp, strerror(errno));
+/**
+ * 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,
+ GError **error_r)
+{
+ const char *const msg = "Failed to set sample format";
+ int oss_format = sample_format_to_oss(audio_format->format);
+ enum oss_setup_result result = oss_format != AFMT_QUERY
+ ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format, msg, error_r)
+ : UNSUPPORTED;
+ enum sample_format mpd_format;
+ switch (result) {
+ case SUCCESS:
+ mpd_format = sample_format_from_oss(oss_format);
+ if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
+ break;
+
+ audio_format->format = mpd_format;
+ return true;
+
+ case ERROR:
return false;
+
+ case UNSUPPORTED:
+ break;
}
- return true;
+ /* 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_S24,
+ 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;
+
+ oss_format = sample_format_to_oss(mpd_format);
+ if (oss_format == AFMT_QUERY)
+ /* not supported by this OSS version */
+ continue;
+
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format, msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ mpd_format = sample_format_from_oss(oss_format);
+ if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
+ break;
+
+ audio_format->format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
}
+/**
+ * Sets up the OSS device which was opened before.
+ */
static bool
-oss_open(struct oss_data *od, GError **error)
+oss_setup(struct oss_data *od, struct audio_format *audio_format,
+ GError **error_r)
{
- bool success;
+ 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, error_r);
+}
- if ((od->fd = open(od->device, O_WRONLY)) < 0) {
- g_set_error(error, oss_output_quark(), errno,
+/**
+ * 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, strerror(errno));
return false;
}
- success = oss_setup(od, error);
- if (!success) {
+ 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";
+ assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY);
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
+ sample_format_to_oss(od->audio_format.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;
}
@@ -535,18 +587,23 @@ oss_open(struct oss_data *od, GError **error)
static bool
oss_output_open(void *data, struct audio_format *audio_format, GError **error)
{
- bool ret;
struct oss_data *od = data;
- od->audio_format = *audio_format;
-
- ret = oss_open(od, error);
- if (!ret)
+ 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, strerror(errno));
return false;
+ }
- *audio_format = od->audio_format;
+ if (!oss_setup(od, audio_format, error)) {
+ oss_close(od);
+ return false;
+ }
- return ret;
+ od->audio_format = *audio_format;
+ return true;
}
static void
@@ -575,7 +632,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error)
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_open(od, error))
+ if (od->fd < 0 && !oss_reopen(od, error))
return 0;
while (true) {
@@ -601,5 +658,6 @@ const struct audio_output_plugin oss_output_plugin = {
.close = oss_output_close,
.play = oss_output_play,
.cancel = oss_output_cancel,
- .mixer_plugin = &oss_mixer,
+
+ .mixer_plugin = &oss_mixer_plugin,
};
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
index 04173bf79..17d138d35 100644
--- a/src/output/osx_plugin.c
+++ b/src/output/osx_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <glib.h>
#include <AudioUnit/AudioUnit.h>
@@ -165,9 +166,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
OSStatus status;
ComponentResult result;
- if (audio_format->bits > 16)
- audio_format->bits = 16;
-
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
@@ -225,7 +223,21 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
- stream_description.mBitsPerChannel = audio_format->bits;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S16;
+ stream_description.mBitsPerChannel = 16;
+ break;
+ }
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
index 610ad9e8d..1d1aec7b1 100644
--- a/src/output/pipe_output_plugin.c
+++ b/src/output/pipe_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include <stdio.h>
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
new file mode 100644
index 000000000..d29fbd705
--- /dev/null
+++ b/src/output/pulse_output_plugin.c
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 <assert.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+ return g_quark_from_static_string("pulse_output");
+}
+
+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, 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)
+{
+ 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;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(struct pulse_output *po, GError **error_r)
+{
+ 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)) {
+ pa_context_unref(po->context);
+ po->context = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the context.
+ */
+static void
+pulse_output_delete_context(struct pulse_output *po)
+{
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = NULL;
+}
+
+static void *
+pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_output *po;
+
+ g_setenv("PULSE_PROP_media.role", "music", true);
+
+ po = g_new(struct pulse_output, 1);
+ 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;
+}
+
+static void
+pulse_output_finish(void *data)
+{
+ struct pulse_output *po = data;
+
+ g_free(po);
+}
+
+static bool
+pulse_output_enable(void *data, GError **error_r)
+{
+ struct pulse_output *po = data;
+
+ 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;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ /* create the libpulse context and connect it */
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ 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(void *data)
+{
+ struct pulse_output *po = data;
+
+ 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.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
+{
+ pa_context_state_t state;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ 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 */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ 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);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ 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_state_cb(pa_stream *stream, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ 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;
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static bool
+pulse_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct pulse_output *po = data;
+ pa_sample_spec ss;
+ int error;
+
+ 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))
+ 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;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* create a stream .. */
+
+ 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)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ 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);
+
+ /* .. and connect it (asynchronously) */
+
+ error = pa_stream_connect_playback(po->stream, po->sink,
+ NULL, 0, NULL, NULL);
+ if (error < 0) {
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ 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(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ 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);
+ }
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ 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 for a signal
+ * if not. The mainloop must be locked before calling this function.
+ *
+ * @return the current stream state
+ */
+static pa_stream_state_t
+pulse_output_check_stream(struct pulse_output *po)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ break;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ state = pa_stream_get_state(po->stream);
+ break;
+ }
+
+ return state;
+}
+
+/**
+ * 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)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ 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(), 0,
+ "disconnected");
+ return false;
+
+ case PA_STREAM_CREATING:
+ break;
+ }
+
+ do {
+ state = pulse_output_check_stream(po);
+ } while (state == PA_STREAM_CREATING);
+
+ if (state != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect the stream: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * 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->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 size_t
+pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct pulse_output *po = data;
+ int error;
+
+ 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))
+ return 0;
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ 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 false;
+ }
+ }
+
+ /* 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(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ 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(void *data)
+{
+ struct pulse_output *po = data;
+ GError *error = 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)) {
+ /* already paused; due to a MPD API limitation, we
+ have to sleep a little bit here, to avoid hogging
+ the CPU */
+
+ g_usleep(50000);
+ } else if (!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 = pulse_output_init(NULL, NULL, NULL);
+ if (po == NULL)
+ return false;
+
+ success = pulse_output_wait_connection(po, NULL);
+ pulse_output_finish(po);
+
+ 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,
+ .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
new file mode 100644
index 000000000..06e3aec43
--- /dev/null
+++ b/src/output/pulse_output_plugin.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 <stddef.h>
+
+#include <glib.h>
+
+#include <pulse/version.h>
+
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct pa_operation;
+struct pa_cvolume;
+
+struct pulse_output {
+ 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
+};
+
+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/pulse_plugin.c b/src/output/pulse_plugin.c
deleted file mode 100644
index ffc7abc8b..000000000
--- a/src/output/pulse_plugin.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 "../output_api.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-#include <pulse/simple.h>
-#include <pulse/error.h>
-
-#define MPD_PULSE_NAME "mpd"
-
-struct pulse_data {
- const char *name;
- const char *server;
- const char *sink;
-
- pa_simple *s;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-static struct pulse_data *pulse_new_data(void)
-{
- struct pulse_data *ret;
-
- ret = g_new(struct pulse_data, 1);
-
- ret->server = NULL;
- ret->sink = NULL;
-
- return ret;
-}
-
-static void pulse_free_data(struct pulse_data *pd)
-{
- g_free(pd);
-}
-
-static void *
-pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, G_GNUC_UNUSED GError **error)
-{
- struct pulse_data *pd;
-
- pd = pulse_new_data();
- pd->name = config_get_block_string(param, "name", "mpd_pulse");
- pd->server = config_get_block_string(param, "server", NULL);
- pd->sink = config_get_block_string(param, "sink", NULL);
-
- return pd;
-}
-
-static void pulse_finish(void *data)
-{
- struct pulse_data *pd = data;
-
- pulse_free_data(pd);
-}
-
-static bool pulse_test_default_device(void)
-{
- pa_simple *s;
- pa_sample_spec ss;
- int error;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = 44100;
- ss.channels = 2;
-
- s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL,
- MPD_PULSE_NAME, &ss, NULL, NULL, &error);
- if (!s) {
- g_message("Cannot connect to default PulseAudio server: %s\n",
- pa_strerror(error));
- return false;
- }
-
- pa_simple_free(s);
-
- return true;
-}
-
-static bool
-pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
-{
- struct pulse_data *pd = data;
- pa_sample_spec ss;
- int error;
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format->bits = 16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format->sample_rate;
- ss.channels = audio_format->channels;
-
- pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK,
- pd->sink, pd->name,
- &ss, NULL, NULL,
- &error);
- if (!pd->s) {
- g_set_error(error_r, pulse_output_quark(), error,
- "Cannot connect to PulseAudio server: %s",
- pa_strerror(error));
- return false;
- }
-
- return true;
-}
-
-static void pulse_cancel(void *data)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_flush(pd->s, &error) < 0)
- g_warning("Flush failed in PulseAudio output \"%s\": %s\n",
- pd->name, pa_strerror(error));
-}
-
-static void pulse_close(void *data)
-{
- struct pulse_data *pd = data;
-
- pa_simple_drain(pd->s, NULL);
- pa_simple_free(pd->s);
-}
-
-static size_t
-pulse_play(void *data, const void *chunk, size_t size, GError **error_r)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_write(pd->s, chunk, size, &error) < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin pulse_plugin = {
- .name = "pulse",
- .test_default_device = pulse_test_default_device,
- .init = pulse_init,
- .finish = pulse_finish,
- .open = pulse_open,
- .play = pulse_play,
- .cancel = pulse_cancel,
- .close = pulse_close,
- .mixer_plugin = &pulse_mixer,
-};
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
new file mode 100644
index 000000000..c01d927c4
--- /dev/null
+++ b/src/output/recorder_output_plugin.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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_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 {
+ /**
+ * 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 void *
+recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ const char *encoder_name;
+ const struct encoder_plugin *encoder_plugin;
+
+ /* read configuration */
+
+ encoder_name = config_get_block_string(param, "encoder", "vorbis");
+ 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);
+ return NULL;
+ }
+
+ 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");
+ return NULL;
+ }
+
+ /* initialize encoder */
+
+ recorder->encoder = encoder_init(encoder_plugin, param, error_r);
+ if (recorder->encoder == NULL)
+ return NULL;
+
+ return recorder;
+}
+
+static void
+recorder_output_finish(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ encoder_finish(recorder->encoder);
+ g_free(recorder);
+}
+
+/**
+ * Writes pending data from the encoder to the output file.
+ */
+static bool
+recorder_output_encoder_to_file(struct recorder_output *recorder,
+ GError **error_r)
+{
+ size_t size = 0, position, nbytes;
+
+ assert(recorder->fd >= 0);
+
+ /* read from the encoder */
+
+ size = encoder_read(recorder->encoder, recorder->buffer,
+ sizeof(recorder->buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ position = 0;
+ while (true) {
+ nbytes = write(recorder->fd, recorder->buffer + position,
+ size - position);
+ if (nbytes > 0) {
+ position += (size_t)nbytes;
+ if (position >= size)
+ 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;
+ }
+ }
+}
+
+static bool
+recorder_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+ bool success;
+
+ /* 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 */
+
+ success = encoder_open(recorder->encoder, audio_format, error_r);
+ if (!success) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_flush(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(void *data, const void *chunk, size_t size,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+
+ 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/shout_plugin.c b/src/output/shout_plugin.c
index 4412d26ff..4b73bb334 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
+#include "mpd_error.h"
#include <shout/shout.h>
#include <glib.h>
@@ -100,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
#define check_block_param(name) { \
block_param = config_get_block_param(param, name); \
if (!block_param) { \
- g_error("no \"%s\" defined for shout device defined at line " \
- "%i\n", name, param->line); \
+ MPD_ERROR("no \"%s\" defined for shout device defined at line " \
+ "%i\n", name, param->line); \
} \
}
@@ -126,6 +128,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
struct block_param *block_param;
int public;
+ if (audio_format == NULL ||
+ !audio_format_fully_defined(audio_format)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "Need full audio format specification");
+ return NULL;
+ }
+
sd = new_shout_data();
if (shout_init_count == 0)
@@ -191,8 +200,6 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- check_block_param("format");
-
encoding = config_get_block_string(param, "encoding", "ogg");
encoder_plugin = shout_encoder_plugin_get(encoding);
if (encoder_plugin == NULL) {
@@ -335,7 +342,6 @@ write_page(struct shout_data *sd, GError **error)
if (sd->buf.len == 0)
return true;
- shout_sync(sd->shout_conn);
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (!handle_shout_error(sd, err, error))
return false;
@@ -434,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
return true;
}
+static unsigned
+my_shout_delay(void *data)
+{
+ struct shout_data *sd = (struct shout_data *)data;
+
+ int delay = shout_delay(sd->shout_conn);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
static size_t
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
{
@@ -448,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
static bool
my_shout_pause(void *data)
{
- struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
- if (shout_delay(sd->shout_conn) > 500) {
- /* cap the latency for unpause */
- g_usleep(500000);
- return true;
- }
-
return my_shout_play(data, silence, sizeof(silence), NULL);
}
@@ -471,10 +482,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
for (unsigned i = 0; i < tag->num_items; i++) {
switch (tag->items[i]->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strncpy(artist, tag->items[i]->value, size);
break;
- case TAG_ITEM_TITLE:
+ case TAG_TITLE:
strncpy(title, tag->items[i]->value, size);
break;
@@ -533,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = {
.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,
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 5febf0afc..deb3298a5 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -87,11 +89,11 @@ solaris_output_open(void *data, struct audio_format *audio_format,
/* support only 16 bit mono/stereo for now; nothing else has
been tested */
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
/* open the device in non-blocking mode */
- so->fd = open(so->device, O_WRONLY|O_NONBLOCK);
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
if (so->fd < 0) {
g_set_error(error, solaris_output_quark(), errno,
"Failed to open %s: %s",
@@ -117,7 +119,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
info.play.sample_rate = audio_format->sample_rate;
info.play.channels = audio_format->channels;
- info.play.precision = audio_format->bits;
+ info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
ret = ioctl(so->fd, AUDIO_SETINFO, &info);
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
new file mode 100644
index 000000000..b9687874d
--- /dev/null
+++ b/src/output/winmm_output_plugin.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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_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 {
+ 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 UINT
+get_device_id(const char *device_name)
+{
+ /* if device is not specified use wave mapper */
+ if (device_name == NULL)
+ return WAVE_MAPPER;
+
+ /* check for device id */
+ char *endptr;
+ UINT id = strtoul(device_name, &endptr, 0);
+ if (endptr > device_name && *endptr == 0)
+ return id;
+
+ /* check for device name */
+ for (UINT i = 0; i < waveOutGetNumDevs(); 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)
+ return i;
+ }
+
+ /* fallback to wave mapper */
+ return WAVE_MAPPER;
+}
+
+static void *
+winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct winmm_output *wo = g_new(struct winmm_output, 1);
+ const char *device = config_get_block_string(param, "device", NULL);
+ wo->device_id = get_device_id(device);
+ return wo;
+}
+
+static void
+winmm_output_finish(void *data)
+{
+ struct winmm_output *wo = data;
+
+ g_free(wo);
+}
+
+static bool
+winmm_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct winmm_output *wo = data;
+
+ 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:
+ 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(void *data)
+{
+ struct winmm_output *wo = data;
+
+ 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);
+ if (dest == NULL) {
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "Out of memory");
+ return false;
+ }
+
+ 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(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct winmm_output *wo = data;
+
+ /* 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(void *data)
+{
+ struct winmm_output *wo = data;
+
+ if (!winmm_drain_all_buffers(wo, NULL))
+ winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(void *data)
+{
+ struct winmm_output *wo = data;
+
+ 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
new file mode 100644
index 000000000..39507275a
--- /dev/null
+++ b/src/output/winmm_output_plugin.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 <windows.h>
+
+struct winmm_output;
+
+HWAVEOUT winmm_output_get_handle(struct winmm_output*);
+
+#endif
diff --git a/src/output_all.c b/src/output_all.c
index 86e78e13e..19c0f0166 100644
--- a/src/output_all.c
+++ b/src/output_all.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
* 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"
@@ -25,6 +26,7 @@
#include "pipe.h"
#include "buffer.h"
#include "player_control.h"
+#include "mpd_error.h"
#ifndef NDEBUG
#include "chunk.h"
@@ -52,6 +54,11 @@ static struct music_buffer *g_music_buffer;
*/
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;
@@ -116,17 +123,17 @@ audio_output_all_init(void)
if (!audio_output_init(output, param, &error)) {
if (param != NULL)
- g_error("line %i: %s",
- param->line, error->message);
+ MPD_ERROR("line %i: %s",
+ param->line, error->message);
else
- g_error("%s", error->message);
+ MPD_ERROR("%s", error->message);
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j].name)) {
- g_error("output devices with identical "
- "names: %s\n", output->name);
+ MPD_ERROR("output devices with identical "
+ "names: %s\n", output->name);
}
}
}
@@ -138,6 +145,7 @@ 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]);
}
@@ -148,6 +156,25 @@ audio_output_all_finish(void)
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
@@ -156,10 +183,18 @@ audio_output_all_finish(void)
static bool
audio_output_all_finished(void)
{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]) &&
- !audio_output_command_is_finished(&audio_outputs[i]))
+ 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;
}
@@ -170,6 +205,29 @@ static void audio_output_wait_all(void)
notify_wait(&audio_output_client_notify);
}
+/**
+ * Signals the audio output if it is open. This function locks the
+ * mutex.
+ */
+static void
+audio_output_lock_signal(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+ g_mutex_unlock(ao->mutex);
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_signal_all(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_lock_signal(&audio_outputs[i]);
+}
+
static void
audio_output_reset_reopen(struct audio_output *ao)
{
@@ -237,8 +295,7 @@ audio_output_all_play(struct music_chunk *chunk)
music_pipe_push(g_mp, chunk);
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_play(&audio_outputs[i]);
+ audio_output_play(&audio_outputs[i]);
return true;
}
@@ -273,6 +330,7 @@ audio_output_all_open(const struct audio_format *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) {
@@ -385,6 +443,11 @@ audio_output_all_check(void)
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
@@ -412,10 +475,15 @@ audio_output_all_check(void)
bool
audio_output_all_wait(unsigned threshold)
{
- if (audio_output_all_check() < threshold)
+ player_lock();
+
+ if (audio_output_all_check() < threshold) {
+ player_unlock();
return true;
+ }
- notify_wait(&pc.notify);
+ player_wait();
+ player_unlock();
return audio_output_all_check() < threshold;
}
@@ -428,8 +496,16 @@ audio_output_all_pause(void)
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_pause(&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();
}
@@ -441,10 +517,8 @@ audio_output_all_cancel(void)
/* send the cancel() command to all audio outputs */
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_cancel(&audio_outputs[i]);
- }
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_cancel(&audio_outputs[i]);
audio_output_wait_all();
@@ -452,6 +526,15 @@ audio_output_all_cancel(void)
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_signal_all();
+
+ /* invalidate elapsed_time */
+
+ audio_output_all_elapsed_time = -1.0;
}
void
@@ -473,4 +556,43 @@ audio_output_all_close(void)
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
index 2a09514b2..a579bf5f1 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -66,6 +66,13 @@ 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
@@ -85,6 +92,13 @@ 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.
*
@@ -123,9 +137,29 @@ 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
index c31893bc6..8e002dd48 100644
--- a/src/output_api.h
+++ b/src/output_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_command.c b/src/output_command.c
index 5da176dde..825884e8e 100644
--- a/src/output_command.c
+++ b/src/output_command.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,13 +24,17 @@
*
*/
+#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)
{
@@ -40,10 +44,16 @@ audio_output_enable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (ao->enabled)
+ return true;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
@@ -57,6 +67,8 @@ audio_output_disable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (!ao->enabled)
+ return true;
ao->enabled = false;
idle_add(IDLE_OUTPUT);
@@ -67,5 +79,9 @@ audio_output_disable_index(unsigned idx)
idle_add(IDLE_MIXER);
}
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
diff --git a/src/output_command.h b/src/output_command.h
index d92ff5ec8..fab015c3f 100644
--- a/src/output_command.h
+++ b/src/output_command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_control.c b/src/output_control.c
index ecd226821..161404f78 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,12 +17,15 @@
* 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>
@@ -38,8 +41,9 @@ struct notify audio_output_client_notify;
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
notify_wait(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
}
}
@@ -47,29 +51,55 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
+ g_cond_signal(ao->cond);
ao_command_wait(ao);
}
-/**
- * Like ao_command(), but assumes the object is locked by the caller.
- */
-static void
-ao_command_locked(struct audio_output *ao, enum audio_output_command cmd)
+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);
+}
+
+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);
+ }
- g_mutex_unlock(ao->mutex);
- ao_command_wait(ao);
g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_ENABLE);
+ g_mutex_unlock(ao->mutex);
}
-static void ao_command_async(struct audio_output *ao,
- enum audio_output_command cmd)
+void
+audio_output_disable(struct audio_output *ao)
{
- assert(ao->command == AO_COMMAND_NONE);
- ao->command = cmd;
- notify_signal(&ao->notify);
+ 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;
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_DISABLE);
+ g_mutex_unlock(ao->mutex);
}
static void
@@ -94,9 +124,13 @@ audio_output_open(struct audio_output *ao,
if (ao->open &&
audio_format_equals(audio_format, &ao->in_audio_format)) {
- assert(ao->pipe == mp);
+ 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
@@ -104,7 +138,11 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
- ao_command_locked(ao, AO_COMMAND_CANCEL);
+ ao_command(ao, AO_COMMAND_CANCEL);
+
+ /* the audio output is now waiting for a
+ signal; wake it up immediately */
+ g_cond_signal(ao->cond);
}
return true;
@@ -113,33 +151,49 @@ audio_output_open(struct audio_output *ao,
ao->in_audio_format = *audio_format;
ao->chunk = NULL;
- if (!ao->config_audio_format) {
- if (ao->open)
- audio_output_close_locked(ao);
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- }
-
ao->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_command_locked(ao, AO_COMMAND_OPEN);
- open = ao->open;
- }
- if (open && ao->mixer != NULL)
- mixer_open(ao->mixer);
+ 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);
+
+ 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,
@@ -149,28 +203,29 @@ audio_output_update(struct audio_output *ao,
g_mutex_lock(ao->mutex);
- if (ao->enabled) {
+ if (ao->enabled && ao->really_enabled) {
if (ao->fail_timer == NULL ||
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
- bool ret = audio_output_open(ao, audio_format, mp);
+ bool success = audio_output_open(ao, audio_format, mp);
g_mutex_unlock(ao->mutex);
- return ret;
+ 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)
{
- if (!ao->open)
- return;
+ g_mutex_lock(ao->mutex);
- notify_signal(&ao->notify);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+
+ g_mutex_unlock(ao->mutex);
}
void audio_output_pause(struct audio_output *ao)
@@ -181,29 +236,36 @@ void audio_output_pause(struct audio_output *ao)
mixer_auto_close()) */
mixer_auto_close(ao->mixer);
- ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_unlock(ao->mutex);
}
-void audio_output_cancel(struct audio_output *ao)
+void
+audio_output_drain_async(struct audio_output *ao)
{
- ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_DRAIN);
+ g_mutex_unlock(ao->mutex);
}
-static void
-audio_output_close_locked(struct audio_output *ao)
+void audio_output_cancel(struct audio_output *ao)
{
- assert(ao != NULL);
- assert(!ao->open || ao->fail_timer == NULL);
-
- if (ao->mixer != NULL)
- mixer_auto_close(ao->mixer);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_unlock(ao->mutex);
+}
- if (ao->open)
- ao_command_locked(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
+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)
@@ -223,7 +285,9 @@ void audio_output_finish(struct audio_output *ao)
assert(ao->fail_timer == NULL);
if (ao->thread != NULL) {
+ g_mutex_lock(ao->mutex);
ao_command(ao, AO_COMMAND_KILL);
+ g_mutex_unlock(ao->mutex);
g_thread_join(ao->thread);
}
@@ -232,6 +296,16 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data);
- notify_deinit(&ao->notify);
+ 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);
}
diff --git a/src/output_control.h b/src/output_control.h
index ce3abe3f6..7f4f4a53c 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -38,7 +38,19 @@ audio_output_quark(void)
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error);
+ GError **error_r);
+
+/**
+ * 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.
@@ -55,8 +67,20 @@ audio_output_play(struct audio_output *ao);
void audio_output_pause(struct audio_output *ao);
+void
+audio_output_drain_async(struct audio_output *ao);
+
void audio_output_cancel(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_init.c b/src/output_init.c
index 927424324..f4700dfb2 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,21 +17,34 @@
* 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)
@@ -56,46 +69,109 @@ audio_output_detect(GError **error)
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(void *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
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error)
+ GError **error_r)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
+ GError *error = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ 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(type);
+ plugin = audio_output_plugin_get(p);
if (plugin == NULL) {
- g_set_error(error, audio_output_quark(), 0,
- "No such audio output plugin: %s",
- type);
+ g_set_error(error_r, audio_output_quark(), 0,
+ "No such audio output plugin: %s", p);
return false;
}
ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
NULL);
if (ao->name == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"name\" configuration");
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ 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 {
g_warning("No \"%s\" defined in config file\n",
CONF_AUDIO_OUTPUT);
- plugin = audio_output_detect(error);
+ plugin = audio_output_detect(error_r);
if (plugin == NULL)
return false;
@@ -103,44 +179,115 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+
+ 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->fail_timer = NULL;
- pcm_convert_init(&ao->convert_state);
+ pcm_buffer_init(&ao->cross_fade_buffer);
- ao->config_audio_format = format != NULL;
- if (ao->config_audio_format) {
- bool ret;
+ /* set up the filter chain */
- ret = audio_format_parse(&ao->out_audio_format, format,
- error);
- if (!ret)
- return false;
+ ao->filter = filter_chain_new();
+ assert(ao->filter != NULL);
+
+ /* 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;
+ }
+
+ /* 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;
- notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
ao->mutex = g_mutex_new();
+ ao->cond = g_cond_new();
ao->data = ao_plugin_init(plugin,
- ao->config_audio_format
- ? &ao->out_audio_format : NULL,
- param, error);
+ &ao->config_audio_format,
+ param, error_r);
if (ao->data == NULL)
return false;
- if (plugin->mixer_plugin != NULL &&
- config_get_block_bool(param, "mixer_enabled", true))
- ao->mixer = mixer_new(plugin->mixer_plugin, param);
- else
- ao->mixer = NULL;
+ ao->mixer = audio_output_load_mixer(ao->data, param,
+ 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);
+
+ /* done */
return true;
}
diff --git a/src/output_internal.h b/src/output_internal.h
index b863c9ecc..18d431352 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,16 +21,33 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
-#include "notify.h"
+#include "pcm_buffer.h"
+
+#include <glib.h>
#include <time.h>
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
};
@@ -60,10 +77,10 @@ struct audio_output {
struct mixer *mixer;
/**
- * This flag is true, when the audio_format of this device is
- * configured in mpd.conf.
+ * Shall this output always play something (i.e. silence),
+ * even when playback is stopped?
*/
- bool config_audio_format;
+ bool always_on;
/**
* Has the user enabled this device?
@@ -71,6 +88,12 @@ struct audio_output {
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.
@@ -94,6 +117,11 @@ struct audio_output {
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).
*/
@@ -107,18 +135,55 @@ struct audio_output {
*/
struct audio_format out_audio_format;
- struct pcm_convert_state convert_state;
+ /**
+ * The buffer used to allocate the cross-fading result.
+ */
+ struct pcm_buffer cross_fade_buffer;
/**
- * The thread handle, or NULL if the output thread isn't
- * running.
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
*/
- GThread *thread;
+ struct filter *filter;
/**
- * Notify object for the thread.
+ * The replay_gain_filter_plugin instance of this audio
+ * output.
*/
- struct notify notify;
+ 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.
@@ -137,6 +202,12 @@ struct audio_output {
GMutex *mutex;
/**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ GCond *cond;
+
+ /**
* 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
diff --git a/src/output_list.c b/src/output_list.c
index 81de16649..8238f581b 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_list.h"
#include "output_api.h"
-#include "config.h"
extern const struct audio_output_plugin shoutPlugin;
extern const struct audio_output_plugin null_output_plugin;
@@ -28,12 +28,16 @@ extern const struct audio_output_plugin pipe_output_plugin;
extern const struct audio_output_plugin alsaPlugin;
extern const struct audio_output_plugin ao_output_plugin;
extern const struct audio_output_plugin oss_output_plugin;
+extern const struct audio_output_plugin openal_output_plugin;
extern const struct audio_output_plugin osxPlugin;
extern const struct audio_output_plugin solaris_output_plugin;
-extern const struct audio_output_plugin pulse_plugin;
+extern const struct audio_output_plugin pulse_output_plugin;
extern const struct audio_output_plugin mvp_output_plugin;
-extern const struct audio_output_plugin jackPlugin;
+extern const struct audio_output_plugin jack_output_plugin;
extern const struct audio_output_plugin httpd_output_plugin;
+extern const struct audio_output_plugin recorder_output_plugin;
+extern const struct audio_output_plugin winmm_output_plugin;
+extern const struct audio_output_plugin ffado_output_plugin;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -55,6 +59,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_OSS
&oss_output_plugin,
#endif
+#ifdef HAVE_OPENAL
+ &openal_output_plugin,
+#endif
#ifdef HAVE_OSX
&osxPlugin,
#endif
@@ -62,17 +69,26 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&solaris_output_plugin,
#endif
#ifdef HAVE_PULSE
- &pulse_plugin,
+ &pulse_output_plugin,
#endif
#ifdef HAVE_MVP
&mvp_output_plugin,
#endif
#ifdef HAVE_JACK
- &jackPlugin,
+ &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
};
diff --git a/src/output_list.h b/src/output_list.h
index b6f82163c..d72bc224b 100644
--- a/src/output_list.h
+++ b/src/output_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 13dba0d0b..36e17ed1b 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -67,6 +67,24 @@ struct audio_output_plugin {
void (*finish)(void *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 occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*enable)(void *data, GError **error_r);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(void *data);
+
+ /**
* Really open the device.
*
* @param audio_format the audio format in which data is going
@@ -83,6 +101,16 @@ struct audio_output_plugin {
void (*close)(void *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)(void *data);
+
+ /**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
*/
@@ -99,6 +127,11 @@ struct audio_output_plugin {
GError **error);
/**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(void *data);
+
+ /**
* Try to cancel data which may still be in the device's
* buffers.
*/
@@ -150,6 +183,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data)
}
static inline bool
+ao_plugin_enable(const struct audio_output_plugin *plugin, void *data,
+ GError **error_r)
+{
+ return plugin->enable != NULL
+ ? plugin->enable(data, error_r)
+ : true;
+}
+
+static inline void
+ao_plugin_disable(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->disable != NULL)
+ plugin->disable(data);
+}
+
+static inline bool
ao_plugin_open(const struct audio_output_plugin *plugin,
void *data, struct audio_format *audio_format,
GError **error)
@@ -163,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
plugin->close(data);
}
+static inline unsigned
+ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
+{
+ return plugin->delay != NULL
+ ? plugin->delay(data)
+ : 0;
+}
+
static inline void
ao_plugin_send_tag(const struct audio_output_plugin *plugin,
void *data, const struct tag *tag)
@@ -180,6 +237,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin,
}
static inline void
+ao_plugin_drain(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->drain != NULL)
+ plugin->drain(data);
+}
+
+static inline void
ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data)
{
if (plugin->cancel != NULL)
diff --git a/src/output_print.c b/src/output_print.c
index 11e53c32c..7a747ad2f 100644
--- a/src/output_print.c
+++ b/src/output_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_print.h"
#include "output_internal.h"
#include "output_all.h"
diff --git a/src/output_print.h b/src/output_print.h
index aec6f0f87..5ad7e34c7 100644
--- a/src/output_print.h
+++ b/src/output_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..e1187b951 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_state.h"
#include "output_internal.h"
#include "output_all.h"
@@ -34,8 +35,10 @@
#define AUDIO_DEVICE_STATE "audio_device_state:"
+unsigned audio_output_state_version;
+
void
-saveAudioDevicesState(FILE *fp)
+audio_output_state_save(FILE *fp)
{
unsigned n = audio_output_count();
@@ -49,35 +52,40 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+audio_output_state_read(const char *line)
{
- char buffer[1024];
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- g_strchomp(buffer);
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
+ }
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
+ ao->enabled = false;
+ return true;
+}
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
- }
+unsigned
+audio_output_state_get_version(void)
+{
+ return audio_output_state_version;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..962ccd97a 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,12 +25,21 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+audio_output_state_read(const char *line);
void
-saveAudioDevicesState(FILE *fp);
+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
index 035cf99c1..380956fac 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,12 +17,18 @@
* 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 <glib.h>
@@ -37,71 +43,423 @@ 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->plugin, ao->data, &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->plugin, ao->data);
+ 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)
+{
+ /* 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);
+
+ 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;
+ }
+
+ 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->plugin, ao->data,
+ &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)
+ao_close(struct audio_output *ao, bool drain)
{
assert(ao->open);
ao->pipe = NULL;
- g_mutex_lock(ao->mutex);
ao->chunk = NULL;
ao->open = false;
+
g_mutex_unlock(ao->mutex);
+ if (drain)
+ ao_plugin_drain(ao->plugin, ao->data);
+ else
+ ao_plugin_cancel(ao->plugin, ao->data);
+
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ ao_filter_close(ao);
+
+ g_mutex_lock(ao->mutex);
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
}
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
+static void
+ao_reopen_filter(struct audio_output *ao)
{
- const char *data = chunk->data;
- size_t size = chunk->length;
+ 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->plugin, ao->data);
+ 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->plugin, ao->data);
+ if (delay == 0)
+ return true;
+
+ GTimeVal tv;
+ g_get_current_time(&tv);
+ g_time_val_add(&tv, delay * 1000);
+ 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));
- assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
- if (chunk->tag != NULL)
+ 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);
+ pcm_mix(dest, data, length, &ao->in_audio_format,
+ 1.0 - chunk->mix_ratio);
+
+ 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->plugin, ao->data, chunk->tag);
+ g_mutex_lock(ao->mutex);
+ }
- if (size == 0)
- return true;
+ size_t size;
+ const char *data = ao_filter_chunk(ao, chunk, &size);
+ if (data == NULL) {
+ ao_close(ao, false);
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format)) {
- data = pcm_convert(&ao->convert_state,
- &ao->in_audio_format, data, size,
- &ao->out_audio_format, &size);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return true;
+ /* 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->plugin, ao->data, 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_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
/* don't automatically reopen this device for
10 seconds */
@@ -124,32 +482,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
return true;
}
-static void ao_play(struct audio_output *ao)
+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);
- g_mutex_lock(ao->mutex);
- chunk = ao->chunk;
- if (chunk != NULL)
- /* continue the previous play() call */
- chunk = chunk->next;
- else
- chunk = music_pipe_peek(ao->pipe);
+ 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;
- g_mutex_unlock(ao->mutex);
success = ao_play_chunk(ao, chunk);
-
- g_mutex_lock(ao->mutex);
-
if (!success) {
assert(ao->chunk == NULL);
break;
@@ -160,23 +531,35 @@ static void ao_play(struct audio_output *ao)
}
ao->chunk_finished = true;
+
g_mutex_unlock(ao->mutex);
+ player_lock_signal();
+ g_mutex_lock(ao->mutex);
- notify_signal(&pc.notify);
+ return true;
}
static void ao_pause(struct audio_output *ao)
{
bool ret;
+ g_mutex_unlock(ao->mutex);
ao_plugin_cancel(ao->plugin, ao->data);
+ 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->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
if (!ret) {
- ao_close(ao);
+ ao_close(ao, false);
break;
}
} while (ao->command == AO_COMMAND_NONE);
@@ -187,64 +570,31 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
+
+ g_mutex_lock(ao->mutex);
while (1) {
switch (ao->command) {
case AO_COMMAND_NONE:
break;
- case AO_COMMAND_OPEN:
- assert(!ao->open);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
-
- 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;
- }
-
- error = NULL;
- ret = ao_plugin_open(ao->plugin, ao->data,
- &ao->out_audio_format,
- &error);
-
- assert(!ao->open);
- if (ret) {
- pcm_convert_init(&ao->convert_state);
+ case AO_COMMAND_ENABLE:
+ ao_enable(ao);
+ ao_command_finished(ao);
+ break;
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
+ case AO_COMMAND_DISABLE:
+ ao_disable(ao);
+ ao_command_finished(ao);
+ break;
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%u:%u:%u",
- ao->plugin->name,
- ao->name,
- ao->out_audio_format.sample_rate,
- ao->out_audio_format.bits,
- ao->out_audio_format.channels);
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %u:%u:%u",
- ao->in_audio_format.sample_rate,
- ao->in_audio_format.bits,
- ao->in_audio_format.channels);
- } else {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name,
- error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- }
+ case AO_COMMAND_OPEN:
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -252,11 +602,7 @@ static gpointer audio_output_task(gpointer arg)
assert(ao->open);
assert(ao->pipe != NULL);
- ao->pipe = NULL;
- ao->chunk = NULL;
-
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
ao_command_finished(ao);
break;
@@ -277,38 +623,46 @@ static gpointer audio_output_task(gpointer arg)
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->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+
+ ao_command_finished(ao);
+ continue;
+
case AO_COMMAND_CANCEL:
ao->chunk = NULL;
if (ao->open)
ao_plugin_cancel(ao->plugin, ao->data);
-
- /* we must clear the notification now, because
- the notify_wait() call below must wait
- until audio_output_all_cancel() has cleared
- the pipe; if another notification happens
- to be still pending, we get a race
- condition with a crash or an assertion
- failure */
- notify_clear(&ao->notify);
-
ao_command_finished(ao);
/* the player thread will now clear our music
pipe - wait for a notify, to give it some
time */
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
continue;
case AO_COMMAND_KILL:
ao->chunk = NULL;
ao_command_finished(ao);
+ g_mutex_unlock(ao->mutex);
return NULL;
}
- if (ao->open)
- ao_play(ao);
+ if (ao->open && ao_play(ao))
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
}
}
@@ -319,5 +673,5 @@ void audio_output_thread_start(struct audio_output *ao)
assert(ao->command == AO_COMMAND_NONE);
if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
- g_error("Failed to spawn output task: %s\n", e->message);
+ MPD_ERROR("Failed to spawn output task: %s\n", e->message);
}
diff --git a/src/output_thread.h b/src/output_thread.h
index a79c3b250..1ee0856f2 100644
--- a/src/output_thread.h
+++ b/src/output_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify