aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to 'src/output')
-rw-r--r--src/output/alsa_plugin.c257
-rw-r--r--src/output/ao_plugin.c28
-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.h33
-rw-r--r--src/output/httpd_output_plugin.c170
-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.c16
-rw-r--r--src/output/solaris_output_plugin.c10
-rw-r--r--src/output/win32_output_plugin.c296
22 files changed, 3248 insertions, 1086 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 818c83ca2..8c36e32bd 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 (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;
+ 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;
}
- 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)
+{
+ /* 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 -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;
}
@@ -365,6 +488,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 +504,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 +516,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 +547,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 +565,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 +613,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 +625,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 +647,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/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..55843e73f 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,12 +29,24 @@
#include <glib.h>
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
#include <sys/socket.h>
+#endif
+#include <stdbool.h>
struct httpd_client;
struct httpd_output {
/**
+ * True if the audio output is open and accepts client
+ * connections.
+ */
+ bool open;
+
+ /**
* The configured encoder plugin.
*/
struct encoder *encoder;
@@ -87,6 +99,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 +122,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 9fdf46456..140ea7d82 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,25 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
#include <assert.h>
#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
#include <netinet/in.h>
#include <netdb.h>
+#endif
#include <unistd.h>
#include <errno.h>
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
@@ -46,6 +57,52 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+static gboolean
+httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data);
+
+static bool
+httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+{
+ GIOChannel *channel;
+
+ httpd->open = false;
+
+ /* 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_r);
+ if (httpd->fd < 0)
+ return false;
+
+ g_mutex_lock(httpd->mutex);
+
+ 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);
+
+ g_mutex_unlock(httpd->mutex);
+
+ return true;
+}
+
+static void
+httpd_output_unbind(struct httpd_output *httpd)
+{
+ assert(!httpd->open);
+
+ g_mutex_lock(httpd->mutex);
+
+ g_source_remove(httpd->source_id);
+ close(httpd->fd);
+
+ g_mutex_unlock(httpd->mutex);
+}
+
static void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
@@ -58,6 +115,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
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,12 +132,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- if (strcmp(encoder_name, "vorbis") == 0)
- httpd->content_type = "application/x-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);
/* initialize listen address */
@@ -94,6 +152,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;
@@ -124,6 +188,7 @@ 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)
@@ -138,18 +203,50 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
struct httpd_output *httpd = data;
int fd;
struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
+ size_t sa_length = sizeof(sa);
g_mutex_lock(httpd->mutex);
/* 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)
+ fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
+ &sa_length);
+#ifdef HAVE_LIBWRAP
+ struct sockaddr *sa_p = (struct sockaddr *)&sa;
+ if (sa_p->sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(sa_p, sa_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 true;
+ }
+
+ g_free(hostaddr);
+ }
+#endif /* HAVE_WRAP */
+ 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);
@@ -199,31 +296,30 @@ httpd_output_encoder_open(struct httpd_output *httpd,
}
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);
@@ -237,8 +333,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 +356,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 +368,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 +379,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -433,9 +532,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,6 +566,8 @@ 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,
.send_tag = httpd_output_tag,
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..cb50954cc 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,37 +198,6 @@ oss_output_finish(void *data)
oss_data_free(od);
}
-static int
-oss_set_param(struct oss_data *od, unsigned param, int *value)
-{
- 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);
-
- *value = val;
-
- return 0;
-}
-
static void
oss_close(struct oss_data *od)
{
@@ -459,73 +207,377 @@ oss_close(struct oss_data *od)
}
/**
- * Sets up the OSS device which was opened before.
+ * A tri-state type for oss_try_ioctl().
*/
-static bool
-oss_setup(struct oss_data *od, GError **error)
+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)
{
- int tmp;
+ assert(fd >= 0);
+ assert(value_r != NULL);
+ assert(msg != NULL);
+ assert(error_r == NULL || *error_r == NULL);
- 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));
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
+
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ g_set_error(error_r, oss_output_quark(), errno,
+ "%s: %s", msg, g_strerror(errno));
+ return ERROR;
+}
+
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates a GError.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+ const char *msg, GError **error_r)
+{
+ return oss_try_ioctl_r(fd, request, &value, msg, error_r);
+}
+
+/**
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
+ */
+static bool
+oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
+{
+ const char *const msg = "Failed to set channel count";
+ int channels = audio_format->channels;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format->channels = channels;
+ return true;
+
+ case ERROR:
return false;
+
+ case UNSUPPORTED:
+ break;
}
- 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..a8b409be2 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,6 +17,7 @@
* 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"
@@ -126,6 +127,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 +199,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) {
@@ -471,10 +477,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;
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/win32_output_plugin.c b/src/output/win32_output_plugin.c
new file mode 100644
index 000000000..970c62d79
--- /dev/null
+++ b/src/output/win32_output_plugin.c
@@ -0,0 +1,296 @@
+/*
+ * 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 <windows.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "win32_output"
+
+struct win32_buffer {
+ struct pcm_buffer buffer;
+
+ WAVEHDR hdr;
+};
+
+struct win32_output {
+ HWAVEOUT handle;
+
+ /**
+ * This event is triggered by Windows when a buffer is
+ * finished.
+ */
+ HANDLE event;
+
+ struct win32_buffer buffers[8];
+ unsigned next_buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+win32_output_quark(void)
+{
+ return g_quark_from_static_string("win32_output");
+}
+
+static bool
+win32_output_test_default_device(void)
+{
+ /* we assume that Wave is always available */
+ return true;
+}
+
+static void *
+win32_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 win32_output *wo = g_new(struct win32_output, 1);
+
+ return wo;
+}
+
+static void
+win32_output_finish(void *data)
+{
+ struct win32_output *wo = data;
+
+ g_free(wo);
+}
+
+static bool
+win32_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct win32_output *wo = data;
+
+ wo->event = CreateEvent(NULL, false, false, NULL);
+ if (wo->event == NULL) {
+ g_set_error(error_r, win32_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, WAVE_MAPPER, &format,
+ (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
+ if (result != MMSYSERR_NOERROR) {
+ CloseHandle(wo->event);
+ g_set_error(error_r, win32_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
+win32_output_close(void *data)
+{
+ struct win32_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
+win32_set_buffer(struct win32_output *wo, struct win32_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, win32_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, win32_output_quark(), result,
+ "waveOutPrepareHeader() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+win32_drain_buffer(struct win32_output *wo, struct win32_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, win32_output_quark(), result,
+ "waveOutUnprepareHeader() failed");
+ return false;
+ }
+
+ /* wait some more */
+ WaitForSingleObject(wo->event, INFINITE);
+ }
+}
+
+static size_t
+win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct win32_output *wo = data;
+
+ /* get the next buffer from the ring and prepare it */
+ struct win32_buffer *buffer = &wo->buffers[wo->next_buffer];
+ if (!win32_drain_buffer(wo, buffer, error_r) ||
+ !win32_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, win32_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
+win32_drain_all_buffers(struct win32_output *wo, GError **error_r)
+{
+ for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
+ if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ for (unsigned i = 0; i < wo->next_buffer; ++i)
+ if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ return true;
+}
+
+static void
+win32_stop(struct win32_output *wo)
+{
+ waveOutReset(wo->handle);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
+ struct win32_buffer *buffer = &wo->buffers[i];
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ }
+}
+
+static void
+win32_output_drain(void *data)
+{
+ struct win32_output *wo = data;
+
+ if (!win32_drain_all_buffers(wo, NULL))
+ win32_stop(wo);
+}
+
+static void
+win32_output_cancel(void *data)
+{
+ struct win32_output *wo = data;
+
+ win32_stop(wo);
+}
+
+const struct audio_output_plugin win32_output_plugin = {
+ .name = "win32",
+ .test_default_device = win32_output_test_default_device,
+ .init = win32_output_init,
+ .finish = win32_output_finish,
+ .open = win32_output_open,
+ .close = win32_output_close,
+ .play = win32_output_play,
+ .drain = win32_output_drain,
+ .cancel = win32_output_cancel,
+};