aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to 'src/output')
-rw-r--r--src/output/alsa_output_plugin.c (renamed from src/output/alsa_plugin.c)313
-rw-r--r--src/output/alsa_output_plugin.h25
-rw-r--r--src/output/ao_output_plugin.c (renamed from src/output/ao_plugin.c)40
-rw-r--r--src/output/ao_output_plugin.h25
-rw-r--r--src/output/ffado_output_plugin.c38
-rw-r--r--src/output/ffado_output_plugin.h25
-rw-r--r--src/output/fifo_output_plugin.c92
-rw-r--r--src/output/fifo_output_plugin.h25
-rw-r--r--src/output/httpd_client.c42
-rw-r--r--src/output/httpd_client.h2
-rw-r--r--src/output/httpd_internal.h9
-rw-r--r--src/output/httpd_output_plugin.c73
-rw-r--r--src/output/httpd_output_plugin.h25
-rw-r--r--src/output/jack_output_plugin.c52
-rw-r--r--src/output/jack_output_plugin.h25
-rw-r--r--src/output/mvp_output_plugin.c (renamed from src/output/mvp_plugin.c)58
-rw-r--r--src/output/mvp_output_plugin.h25
-rw-r--r--src/output/null_output_plugin.c (renamed from src/output/null_plugin.c)64
-rw-r--r--src/output/null_output_plugin.h25
-rw-r--r--src/output/openal_output_plugin.c (renamed from src/output/openal_plugin.c)128
-rw-r--r--src/output/openal_output_plugin.h25
-rw-r--r--src/output/oss_output_plugin.c (renamed from src/output/oss_plugin.c)246
-rw-r--r--src/output/oss_output_plugin.h25
-rw-r--r--src/output/osx_output_plugin.c434
-rw-r--r--src/output/osx_output_plugin.h25
-rw-r--r--src/output/osx_plugin.c289
-rw-r--r--src/output/pipe_output_plugin.c35
-rw-r--r--src/output/pipe_output_plugin.h25
-rw-r--r--src/output/pulse_output_plugin.c149
-rw-r--r--src/output/pulse_output_plugin.h39
-rw-r--r--src/output/raop_output_plugin.c1056
-rw-r--r--src/output/raop_output_plugin.h37
-rw-r--r--src/output/recorder_output_plugin.c37
-rw-r--r--src/output/recorder_output_plugin.h25
-rw-r--r--src/output/roar_output_plugin.c401
-rw-r--r--src/output/roar_output_plugin.h35
-rw-r--r--src/output/shout_output_plugin.c (renamed from src/output/shout_plugin.c)77
-rw-r--r--src/output/shout_output_plugin.h25
-rw-r--r--src/output/solaris_output_plugin.c40
-rw-r--r--src/output/solaris_output_plugin.h25
-rw-r--r--src/output/winmm_output_plugin.c85
-rw-r--r--src/output/winmm_output_plugin.h10
42 files changed, 3396 insertions, 860 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_output_plugin.c
index ae06847c2..a6dc92fa0 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
*/
#include "config.h"
+#include "alsa_output_plugin.h"
#include "output_api.h"
#include "mixer_list.h"
+#include "pcm_export.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,6 +44,10 @@ typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
snd_pcm_uframes_t size);
struct alsa_data {
+ struct audio_output base;
+
+ struct pcm_export_state export;
+
/** the configured name of the ALSA device; NULL for the
default device */
char *device;
@@ -49,6 +55,14 @@ struct alsa_data {
/** use memory mapped I/O? */
bool use_mmap;
+ /**
+ * Enable DSD over USB according to the dCS suggested
+ * standard?
+ *
+ * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
+ */
+ bool dsd_usb;
+
/** libasound's buffer_time setting (in microseconds) */
unsigned int buffer_time;
@@ -68,8 +82,15 @@ struct alsa_data {
*/
alsa_writei_t *writei;
- /** the size of one audio frame */
- size_t frame_size;
+ /**
+ * The size of one audio frame passed to method play().
+ */
+ size_t in_frame_size;
+
+ /**
+ * The size of one audio frame passed to libasound.
+ */
+ size_t out_frame_size;
/**
* The size of one period, in number of frames.
@@ -109,19 +130,14 @@ alsa_data_new(void)
}
static void
-alsa_data_free(struct alsa_data *ad)
-{
- g_free(ad->device);
- g_free(ad);
-}
-
-static void
alsa_configure(struct alsa_data *ad, const struct config_param *param)
{
ad->device = config_dup_block_string(param, "device", NULL);
ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
+ ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false);
+
ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
MPD_ALSA_BUFFER_TIME_US);
ad->period_time = config_get_block_unsigned(param, "period_time", 0);
@@ -142,30 +158,53 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param)
#endif
}
-static void *
-alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+alsa_init(const struct config_param *param, GError **error_r)
{
struct alsa_data *ad = alsa_data_new();
+ if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) {
+ g_free(ad);
+ return NULL;
+ }
+
alsa_configure(ad, param);
- return ad;
+ return &ad->base;
}
static void
-alsa_finish(void *data)
+alsa_finish(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
- alsa_data_free(ad);
+ ao_base_finish(&ad->base);
+
+ g_free(ad->device);
+ g_free(ad);
/* free libasound's config cache */
snd_config_update_free_global();
}
static bool
+alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ pcm_export_init(&ad->export);
+ return true;
+}
+
+static void
+alsa_output_disable(struct audio_output *ao)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ pcm_export_deinit(&ad->export);
+}
+
+static bool
alsa_test_default_device(void)
{
snd_pcm_t *handle;
@@ -187,6 +226,7 @@ get_bitformat(enum sample_format sample_format)
{
switch (sample_format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
return SND_PCM_FORMAT_UNKNOWN;
case SAMPLE_FORMAT_S8:
@@ -198,13 +238,11 @@ get_bitformat(enum sample_format sample_format)
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;
+
+ case SAMPLE_FORMAT_FLOAT:
+ return SND_PCM_FORMAT_FLOAT;
}
assert(false);
@@ -232,44 +270,39 @@ byteswap_bitformat(snd_pcm_format_t fmt)
}
}
-/**
- * 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)
+static snd_pcm_format_t
+alsa_to_packed_format(snd_pcm_format_t fmt)
{
- snd_pcm_format_t alsa_format = get_bitformat(sample_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
+ switch (fmt) {
+ case SND_PCM_FORMAT_S24_LE:
+ return SND_PCM_FORMAT_S24_3LE;
- int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
- if (err == 0)
- audio_format->format = sample_format;
+ case SND_PCM_FORMAT_S24_BE:
+ return SND_PCM_FORMAT_S24_3BE;
- return err;
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
}
-/**
- * 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)
+alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ snd_pcm_format_t fmt, bool *packed_r)
{
- snd_pcm_format_t alsa_format =
- byteswap_bitformat(get_bitformat(sample_format));
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ fmt = alsa_to_packed_format(fmt);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
return -EINVAL;
- 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;
- }
+ err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = true;
return err;
}
@@ -279,15 +312,29 @@ alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
* 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)
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ enum sample_format sample_format,
+ bool *packed_r, bool *reverse_endian_r)
{
- 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);
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
+ packed_r);
+ if (err == 0)
+ *reverse_endian_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ alsa_format = byteswap_bitformat(alsa_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
+ if (err == 0)
+ *reverse_endian_r = true;
return err;
}
@@ -297,37 +344,38 @@ alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
*/
static int
alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format)
+ struct audio_format *audio_format,
+ bool *packed_r, bool *reverse_endian_r)
{
/* try the input format first */
- int err = alsa_output_try_format_both(pcm, hwparams, audio_format,
- audio_format->format);
- if (err != -EINVAL)
- return err;
+ int err = alsa_output_try_format(pcm, hwparams, audio_format->format,
+ packed_r, reverse_endian_r);
/* if unsupported by the hardware, try other formats */
static const enum sample_format probe_formats[] = {
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_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)
+ for (unsigned i = 0;
+ err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED;
+ ++i) {
+ const enum sample_format mpd_format = probe_formats[i];
+ if (mpd_format == audio_format->format)
continue;
- err = alsa_output_try_format_both(pcm, hwparams, audio_format,
- probe_formats[i]);
- if (err != -EINVAL)
- return err;
+ err = alsa_output_try_format(pcm, hwparams, mpd_format,
+ packed_r, reverse_endian_r);
+ if (err == 0)
+ audio_format->format = mpd_format;
}
- return -EINVAL;
+ return err;
}
/**
@@ -336,7 +384,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
*/
static bool
alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
- GError **error)
+ bool *packed_r, bool *reverse_endian_r, GError **error)
{
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
@@ -380,7 +428,8 @@ configure_hw:
ad->writei = snd_pcm_writei;
}
- err = alsa_output_setup_format(ad->pcm, hwparams, audio_format);
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
+ packed_r, reverse_endian_r);
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
"ALSA device \"%s\" does not support format %s: %s",
@@ -390,6 +439,11 @@ configure_hw:
return false;
}
+ snd_pcm_format_t format;
+ if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
+ g_debug("format=%s (%s)", snd_pcm_format_name(format),
+ snd_pcm_format_description(format));
+
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
&channels);
if (err < 0) {
@@ -532,9 +586,72 @@ error:
}
static bool
-alsa_open(void *data, struct audio_format *audio_format, GError **error)
+alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format,
+ bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+ GError **error_r)
{
- struct alsa_data *ad = data;
+ assert(ad->dsd_usb);
+ assert(audio_format->format == SAMPLE_FORMAT_DSD);
+
+ /* pass 24 bit to alsa_setup() */
+
+ struct audio_format usb_format = *audio_format;
+ usb_format.format = SAMPLE_FORMAT_S24_P32;
+ usb_format.sample_rate /= 2;
+
+ const struct audio_format check = usb_format;
+
+ if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r))
+ return false;
+
+ /* if the device allows only 32 bit, shift all DSD-over-USB
+ samples left by 8 bit and leave the lower 8 bit cleared;
+ the DSD-over-USB documentation does not specify whether
+ this is legal, but there is anecdotical evidence that this
+ is possible (and the only option for some devices) */
+ *shift8_r = usb_format.format == SAMPLE_FORMAT_S32;
+ if (usb_format.format == SAMPLE_FORMAT_S32)
+ usb_format.format = SAMPLE_FORMAT_S24_P32;
+
+ if (!audio_format_equals(&usb_format, &check)) {
+ /* no bit-perfect playback, which is required
+ for DSD over USB */
+ g_set_error(error_r, alsa_output_quark(), 0,
+ "Failed to configure DSD-over-USB on ALSA device \"%s\"",
+ alsa_device(ad));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format,
+ GError **error_r)
+{
+ bool shift8 = false, packed, reverse_endian;
+
+ const bool dsd_usb = ad->dsd_usb &&
+ audio_format->format == SAMPLE_FORMAT_DSD;
+ const bool success = dsd_usb
+ ? alsa_setup_dsd(ad, audio_format,
+ &shift8, &packed, &reverse_endian,
+ error_r)
+ : alsa_setup(ad, audio_format, &packed, &reverse_endian,
+ error_r);
+ if (!success)
+ return false;
+
+ pcm_export_open(&ad->export,
+ audio_format->format, audio_format->channels,
+ dsd_usb, shift8, packed, reverse_endian);
+ return true;
+}
+
+static bool
+alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
int err;
bool success;
@@ -547,13 +664,17 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error)
return false;
}
- success = alsa_setup(ad, audio_format, error);
+ g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
+ snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+ success = alsa_setup_or_dsd(ad, audio_format, error);
if (!success) {
snd_pcm_close(ad->pcm);
return false;
}
- ad->frame_size = audio_format_frame_size(audio_format);
+ ad->in_frame_size = audio_format_frame_size(audio_format);
+ ad->out_frame_size = ad->export.pack24 ? 3 : ad->in_frame_size;
return true;
}
@@ -596,9 +717,9 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
-alsa_drain(void *data)
+alsa_drain(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
return;
@@ -608,7 +729,7 @@ alsa_drain(void *data)
period */
snd_pcm_uframes_t nframes =
ad->period_frames - ad->period_position;
- size_t nbytes = nframes * ad->frame_size;
+ size_t nbytes = nframes * ad->out_frame_size;
void *buffer = g_malloc(nbytes);
snd_pcm_hw_params_t *params;
snd_pcm_format_t format;
@@ -630,9 +751,9 @@ alsa_drain(void *data)
}
static void
-alsa_cancel(void *data)
+alsa_cancel(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
ad->period_position = 0;
@@ -640,26 +761,34 @@ alsa_cancel(void *data)
}
static void
-alsa_close(void *data)
+alsa_close(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
snd_pcm_close(ad->pcm);
}
static size_t
-alsa_play(void *data, const void *chunk, size_t size, GError **error)
+alsa_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ assert(size % ad->in_frame_size == 0);
+
+ chunk = pcm_export(&ad->export, chunk, size, &size);
+
+ assert(size % ad->out_frame_size == 0);
- size /= ad->frame_size;
+ size /= ad->out_frame_size;
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
if (ret > 0) {
ad->period_position = (ad->period_position + ret)
% ad->period_frames;
- return ret * ad->frame_size;
+ return pcm_export_source_size(&ad->export,
+ ret * ad->in_frame_size);
}
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
@@ -671,11 +800,13 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
}
}
-const struct audio_output_plugin alsaPlugin = {
+const struct audio_output_plugin alsa_output_plugin = {
.name = "alsa",
.test_default_device = alsa_test_default_device,
.init = alsa_init,
.finish = alsa_finish,
+ .enable = alsa_output_enable,
+ .disable = alsa_output_disable,
.open = alsa_open,
.play = alsa_play,
.drain = alsa_drain,
diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h
new file mode 100644
index 000000000..daa1f3615
--- /dev/null
+++ b/src/output/alsa_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALSA_OUTPUT_PLUGIN_H
+#define MPD_ALSA_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/ao_plugin.c b/src/output/ao_output_plugin.c
index 42ece5a3a..d7e577fa4 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "ao_output_plugin.h"
#include "output_api.h"
#include <ao/ao.h>
@@ -32,6 +33,8 @@ static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
static unsigned ao_output_ref;
struct ao_data {
+ struct audio_output base;
+
size_t write_size;
int driver;
ao_option *options;
@@ -71,19 +74,24 @@ ao_output_error(GError **error_r)
break;
default:
- error = strerror(errno);
+ error = g_strerror(errno);
}
g_set_error(error_r, ao_output_quark(), errno,
"%s", error);
}
-static void *
-ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+ao_output_init(const struct config_param *param,
GError **error)
{
struct ao_data *ad = g_new(struct ao_data, 1);
+
+ if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) {
+ g_free(ad);
+ return NULL;
+ }
+
ao_info *ai;
const char *value;
@@ -106,6 +114,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"\"%s\" is not a valid ao driver",
value);
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -113,6 +122,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if ((ai = ao_driver_info(ad->driver)) == NULL) {
g_set_error(error, ao_output_quark(), 0,
"problems getting driver info");
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -131,6 +141,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"problems parsing options \"%s\"",
options[i]);
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -144,15 +155,16 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_strfreev(options);
}
- return ad;
+ return &ad->base;
}
static void
-ao_output_finish(void *data)
+ao_output_finish(struct audio_output *ao)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
ao_free_options(ad->options);
+ ao_base_finish(&ad->base);
g_free(ad);
ao_output_ref--;
@@ -162,19 +174,19 @@ ao_output_finish(void *data)
}
static void
-ao_output_close(void *data)
+ao_output_close(struct audio_output *ao)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
ao_close(ad->device);
}
static bool
-ao_output_open(void *data, struct audio_format *audio_format,
+ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
switch (audio_format->format) {
case SAMPLE_FORMAT_S8:
@@ -226,10 +238,10 @@ static int ao_play_deconst(ao_device *device, const void *output_samples,
}
static size_t
-ao_output_play(void *data, const void *chunk, size_t size,
+ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
if (size > ad->write_size)
size = ad->write_size;
diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h
new file mode 100644
index 000000000..9a3a47c05
--- /dev/null
+++ b/src/output/ao_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AO_OUTPUT_PLUGIN_H
+#define MPD_AO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin ao_output_plugin;
+
+#endif
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
index 723698ed0..ba239a4ad 100644
--- a/src/output/ffado_output_plugin.c
+++ b/src/output/ffado_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,6 +30,7 @@
*/
#include "config.h"
+#include "ffado_output_plugin.h"
#include "output_api.h"
#include "timer.h"
@@ -53,6 +54,8 @@ struct mpd_ffado_stream {
};
struct mpd_ffado_device {
+ struct audio_output base;
+
char *device_name;
int verbose;
unsigned period_size, nb_buffers;
@@ -82,21 +85,26 @@ ffado_output_quark(void)
return g_quark_from_static_string("ffado_output");
}
-static void *
-ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+ffado_init(const struct config_param *param,
GError **error_r)
{
g_debug("using libffado version %s, API=%d",
ffado_get_version(), ffado_get_api_version());
struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
+ if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) {
+ g_free(fd);
+ return NULL;
+ }
+
fd->device_name = config_dup_block_string(param, "device", NULL);
fd->verbose = config_get_block_unsigned(param, "verbose", 0);
fd->period_size = config_get_block_unsigned(param, "period_size",
1024);
if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
+ ao_base_finish(&fd->base);
g_set_error(error_r, ffado_output_quark(), 0,
"invalid period_size setting");
return false;
@@ -104,20 +112,22 @@ ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
+ ao_base_finish(&fd->base);
g_set_error(error_r, ffado_output_quark(), 0,
"invalid nb_buffers setting");
return false;
}
- return fd;
+ return &fd->base;
}
static void
-ffado_finish(void *data)
+ffado_finish(struct audio_output *ao)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
g_free(fd->device_name);
+ ao_base_finish(&fd->base);
g_free(fd);
}
@@ -227,9 +237,10 @@ ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
}
static bool
-ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
+ffado_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error_r)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
/* will be converted to floating point, choose best input
format */
@@ -273,9 +284,9 @@ ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
}
static void
-ffado_close(void *data)
+ffado_close(struct audio_output *ao)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
ffado_streaming_stop(fd->dev);
ffado_streaming_finish(fd->dev);
@@ -287,9 +298,10 @@ ffado_close(void *data)
}
static size_t
-ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
+ffado_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
/* wait for prefious buffer to finish (if it was full) */
diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h
new file mode 100644
index 000000000..4dde01859
--- /dev/null
+++ b/src/output/ffado_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFADO_OUTPUT_PLUGIN_H
+#define MPD_FFADO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin ffado_output_plugin;
+
+#endif
diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c
index f4217ec4d..022be0b4a 100644
--- a/src/output/fifo_output_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "fifo_output_plugin.h"
#include "output_api.h"
#include "utils.h"
#include "timer.h"
@@ -38,11 +39,13 @@
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
struct fifo_data {
+ struct audio_output base;
+
char *path;
int input;
int output;
bool created;
- Timer *timer;
+ struct timer *timer;
};
/**
@@ -80,7 +83,7 @@ static void fifo_delete(struct fifo_data *fd)
if (unlink(fd->path) < 0) {
g_warning("Could not remove FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return;
}
@@ -112,7 +115,7 @@ fifo_make(struct fifo_data *fd, GError **error)
if (mkfifo(fd->path, 0666) < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Couldn't create FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return false;
}
@@ -134,7 +137,7 @@ fifo_check(struct fifo_data *fd, GError **error)
g_set_error(error, fifo_output_quark(), errno,
"Failed to stat FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return false;
}
@@ -158,7 +161,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (fd->input < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for reading: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
fifo_close(fd);
return false;
}
@@ -167,7 +170,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (fd->output < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for writing: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
fifo_close(fd);
return false;
}
@@ -175,54 +178,55 @@ fifo_open(struct fifo_data *fd, GError **error)
return true;
}
-static void *
-fifo_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- GError **error)
+static struct audio_output *
+fifo_output_init(const struct config_param *param,
+ GError **error_r)
{
struct fifo_data *fd;
- char *value, *path;
-
- value = config_dup_block_string(param, "path", NULL);
- if (value == NULL) {
- g_set_error(error, fifo_output_quark(), errno,
- "No \"path\" parameter specified");
- return NULL;
- }
- path = parsePath(value);
- g_free(value);
+ GError *error = NULL;
+ char *path = config_dup_block_path(param, "path", &error);
if (!path) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not parse \"path\" parameter");
+ if (error != NULL)
+ g_propagate_error(error_r, error);
+ else
+ g_set_error(error_r, fifo_output_quark(), 0,
+ "No \"path\" parameter specified");
return NULL;
}
fd = fifo_data_new();
fd->path = path;
- if (!fifo_open(fd, error)) {
+ if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) {
+ fifo_data_free(fd);
+ return NULL;
+ }
+
+ if (!fifo_open(fd, error_r)) {
+ ao_base_finish(&fd->base);
fifo_data_free(fd);
return NULL;
}
- return fd;
+ return &fd->base;
}
static void
-fifo_output_finish(void *data)
+fifo_output_finish(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
fifo_close(fd);
+ ao_base_finish(&fd->base);
fifo_data_free(fd);
}
static bool
-fifo_output_open(void *data, struct audio_format *audio_format,
+fifo_output_open(struct audio_output *ao, struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
fd->timer = timer_new(audio_format);
@@ -230,17 +234,17 @@ fifo_output_open(void *data, struct audio_format *audio_format,
}
static void
-fifo_output_close(void *data)
+fifo_output_close(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
timer_free(fd->timer);
}
static void
-fifo_output_cancel(void *data)
+fifo_output_cancel(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
char buf[FIFO_BUFFER_SIZE];
int bytes = 1;
@@ -251,22 +255,29 @@ fifo_output_cancel(void *data)
if (bytes < 0 && errno != EAGAIN) {
g_warning("Flush of FIFO \"%s\" failed: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
}
}
+static unsigned
+fifo_output_delay(struct audio_output *ao)
+{
+ struct fifo_data *fd = (struct fifo_data *)ao;
+
+ return fd->timer->started
+ ? timer_delay(fd->timer)
+ : 0;
+}
+
static size_t
-fifo_output_play(void *data, const void *chunk, size_t size,
+fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
ssize_t bytes;
if (!fd->timer->started)
timer_start(fd->timer);
- else
- timer_sync(fd->timer);
-
timer_add(fd->timer, size);
while (true) {
@@ -278,7 +289,7 @@ fifo_output_play(void *data, const void *chunk, size_t size,
switch (errno) {
case EAGAIN:
/* The pipe is full, so empty it */
- fifo_output_cancel(fd);
+ fifo_output_cancel(&fd->base);
continue;
case EINTR:
continue;
@@ -298,6 +309,7 @@ const struct audio_output_plugin fifo_output_plugin = {
.finish = fifo_output_finish,
.open = fifo_output_open,
.close = fifo_output_close,
+ .delay = fifo_output_delay,
.play = fifo_output_play,
.cancel = fifo_output_cancel,
};
diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h
new file mode 100644
index 000000000..85f7985e1
--- /dev/null
+++ b/src/output/fifo_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FIFO_OUTPUT_PLUGIN_H
+#define MPD_FIFO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 995c1f659..8efedc2b3 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -93,6 +93,11 @@ struct httpd_client {
*/
size_t current_position;
+ /**
+ * If DLNA streaming was an option.
+ */
+ bool dlna_streaming_requested;
+
/* ICY */
/**
@@ -234,6 +239,15 @@ httpd_client_handle_line(struct httpd_client *client, const char *line)
return true;
}
+ if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
+ /* Send as dlna */
+ client->dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ client->metadata_supported = false;
+ client->metadata_requested = false;
+ return true;
+ }
+
/* expect more request headers */
return true;
}
@@ -285,16 +299,21 @@ httpd_client_send_response(struct httpd_client *client)
assert(client != NULL);
assert(client->state == RESPONSE);
- if (!client->metadata_requested) {
+ if (client->dlna_streaming_requested) {
g_snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 200 OK\r\n"
+ "HTTP/1.1 206 OK\r\n"
"Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache, no-store\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
client->httpd->content_type);
- } else {
+
+ } else if (client->metadata_requested) {
gchar *metadata_header;
metadata_header = icy_server_metadata_header(
@@ -307,6 +326,16 @@ httpd_client_send_response(struct httpd_client *client)
g_strlcpy(buffer, metadata_header, sizeof(buffer));
g_free(metadata_header);
+
+ } else { /* revert to a normal HTTP request */
+ g_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ client->httpd->content_type);
}
status = g_io_channel_write_chars(client->channel,
@@ -476,6 +505,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
client->input = fifo_buffer_new(4096);
client->state = REQUEST;
+ client->dlna_streaming_requested = false;
client->metadata_supported = metadata_supported;
client->metadata_requested = false;
client->metadata_sent = true;
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
index 7ebd0bbc0..739163f42 100644
--- a/src/output/httpd_client.h
+++ b/src/output/httpd_client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 277e70f11..5dcb8ab9b 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
+#include "output_internal.h"
#include "timer.h"
#include <glib.h>
@@ -34,6 +35,8 @@
struct httpd_client;
struct httpd_output {
+ struct audio_output base;
+
/**
* True if the audio output is open and accepts client
* connections.
@@ -65,10 +68,10 @@ struct httpd_output {
GMutex *mutex;
/**
- * A #Timer object to synchronize this output with the
+ * A #timer object to synchronize this output with the
* wallclock.
*/
- Timer *timer;
+ struct timer *timer;
/**
* The listener socket.
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 2c140a300..e7344320c 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,12 +18,13 @@
*/
#include "config.h"
+#include "httpd_output_plugin.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
-#include "socket_util.h"
+#include "resolver.h"
#include "page.h"
#include "icy_server.h"
#include "fd_util.h"
@@ -78,12 +79,16 @@ httpd_output_unbind(struct httpd_output *httpd)
g_mutex_unlock(httpd->mutex);
}
-static void *
-httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+httpd_output_init(const struct config_param *param,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
+ if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) {
+ g_free(httpd);
+ return NULL;
+ }
+
const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
@@ -103,6 +108,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error, httpd_output_quark(), 0,
"No such encoder: %s", encoder_name);
+ ao_base_finish(&httpd->base);
g_free(httpd);
return NULL;
}
@@ -120,8 +126,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
? server_socket_add_host(httpd->server_socket, bind_to_address,
port, error)
: server_socket_add_port(httpd->server_socket, port, error);
- if (!success)
+ if (!success) {
+ ao_base_finish(&httpd->base);
+ g_free(httpd);
return NULL;
+ }
/* initialize metadata */
httpd->metadata = NULL;
@@ -130,8 +139,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
/* initialize encoder */
httpd->encoder = encoder_init(encoder_plugin, param, error);
- if (httpd->encoder == NULL)
+ if (httpd->encoder == NULL) {
+ ao_base_finish(&httpd->base);
+ g_free(httpd);
return NULL;
+ }
/* determine content type */
httpd->content_type = encoder_get_mime_type(httpd->encoder);
@@ -141,13 +153,13 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
httpd->mutex = g_mutex_new();
- return httpd;
+ return &httpd->base;
}
static void
-httpd_output_finish(void *data)
+httpd_output_finish(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
if (httpd->metadata)
page_unref(httpd->metadata);
@@ -155,6 +167,7 @@ httpd_output_finish(void *data)
encoder_finish(httpd->encoder);
server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
+ ao_base_finish(&httpd->base);
g_free(httpd);
}
@@ -286,26 +299,26 @@ httpd_output_encoder_open(struct httpd_output *httpd,
}
static bool
-httpd_output_enable(void *data, GError **error_r)
+httpd_output_enable(struct audio_output *ao, GError **error_r)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
return httpd_output_bind(httpd, error_r);
}
static void
-httpd_output_disable(void *data)
+httpd_output_disable(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
httpd_output_unbind(httpd);
}
static bool
-httpd_output_open(void *data, struct audio_format *audio_format,
+httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
bool success;
g_mutex_lock(httpd->mutex);
@@ -338,9 +351,10 @@ httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data)
httpd_client_free(client);
}
-static void httpd_output_close(void *data)
+static void
+httpd_output_close(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
@@ -379,9 +393,9 @@ httpd_output_send_header(struct httpd_output *httpd,
}
static unsigned
-httpd_output_delay(void *data)
+httpd_output_delay(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
return httpd->timer->started
? timer_delay(httpd->timer)
@@ -457,9 +471,10 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
}
static size_t
-httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
+httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
bool has_clients;
g_mutex_lock(httpd->mutex);
@@ -483,9 +498,9 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
}
static bool
-httpd_output_pause(void *data)
+httpd_output_pause(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
bool has_clients = httpd->clients != NULL;
@@ -493,7 +508,7 @@ httpd_output_pause(void *data)
if (has_clients) {
static const char silence[1020];
- return httpd_output_play(data, silence, sizeof(silence),
+ return httpd_output_play(ao, silence, sizeof(silence),
NULL) > 0;
} else {
g_usleep(100000);
@@ -511,9 +526,9 @@ httpd_send_metadata(gpointer data, gpointer user_data)
}
static void
-httpd_output_tag(void *data, const struct tag *tag)
+httpd_output_tag(struct audio_output *ao, const struct tag *tag)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
assert(tag != NULL);
@@ -570,9 +585,9 @@ httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
}
static void
-httpd_output_cancel(void *data)
+httpd_output_cancel(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL);
diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h
new file mode 100644
index 000000000..d0eb1533f
--- /dev/null
+++ b/src/output/httpd_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H
+#define MPD_HTTPD_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin httpd_output_plugin;
+
+#endif
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
index c67fcd38a..a24cb8557 100644
--- a/src/output/jack_output_plugin.c
+++ b/src/output/jack_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "jack_output_plugin.h"
#include "output_api.h"
#include <assert.h>
@@ -43,6 +44,8 @@ enum {
static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
struct jack_data {
+ struct audio_output base;
+
/**
* libjack options passed to jack_client_open().
*/
@@ -304,14 +307,18 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r)
return n;
}
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error_r)
+static struct audio_output *
+mpd_jack_init(const struct config_param *param, GError **error_r)
{
- struct jack_data *jd;
+ struct jack_data *jd = g_new(struct jack_data, 1);
+
+ if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) {
+ g_free(jd);
+ return NULL;
+ }
+
const char *value;
- jd = g_new(struct jack_data, 1);
jd->options = JackNullOption;
jd->name = config_get_block_string(param, "client_name", NULL);
@@ -374,13 +381,13 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
jack_set_info_function(mpd_jack_info);
#endif
- return jd;
+ return &jd->base;
}
static void
-mpd_jack_finish(void *data)
+mpd_jack_finish(struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
for (unsigned i = 0; i < jd->num_source_ports; ++i)
g_free(jd->source_ports[i]);
@@ -388,13 +395,14 @@ mpd_jack_finish(void *data)
for (unsigned i = 0; i < jd->num_destination_ports; ++i)
g_free(jd->destination_ports[i]);
+ ao_base_finish(&jd->base);
g_free(jd);
}
static bool
-mpd_jack_enable(void *data, GError **error_r)
+mpd_jack_enable(struct audio_output *ao, GError **error_r)
{
- struct jack_data *jd = (struct jack_data *)data;
+ struct jack_data *jd = (struct jack_data *)ao;
for (unsigned i = 0; i < jd->num_source_ports; ++i)
jd->ringbuffer[i] = NULL;
@@ -403,9 +411,9 @@ mpd_jack_enable(void *data, GError **error_r)
}
static void
-mpd_jack_disable(void *data)
+mpd_jack_disable(struct audio_output *ao)
{
- struct jack_data *jd = (struct jack_data *)data;
+ struct jack_data *jd = (struct jack_data *)ao;
if (jd->client != NULL)
mpd_jack_disconnect(jd);
@@ -568,9 +576,10 @@ mpd_jack_start(struct jack_data *jd, GError **error_r)
}
static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
+mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error_r)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
assert(jd != NULL);
@@ -592,9 +601,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
}
static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
+mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
mpd_jack_stop(jd);
}
@@ -664,9 +673,10 @@ mpd_jack_write_samples(struct jack_data *jd, const void *src,
}
static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
+mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
const size_t frame_size = audio_format_frame_size(&jd->audio_format);
size_t space = 0, space1;
@@ -708,9 +718,9 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
}
static bool
-mpd_jack_pause(void *data)
+mpd_jack_pause(struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
if (jd->shutdown)
return false;
diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h
new file mode 100644
index 000000000..2f94ae7dc
--- /dev/null
+++ b/src/output/jack_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_JACK_OUTPUT_PLUGIN_H
+#define MPD_JACK_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin jack_output_plugin;
+
+#endif
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_output_plugin.c
index 6cc8fa34e..37e0f7c93 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
*/
#include "config.h"
+#include "mvp_output_plugin.h"
#include "output_api.h"
#include "fd_util.h"
@@ -69,6 +70,8 @@ typedef struct {
#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*)
struct mvp_data {
+ struct audio_output base;
+
struct audio_format audio_format;
int fd;
};
@@ -125,26 +128,31 @@ mvp_output_test_default_device(void)
}
g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n",
- strerror(errno));
+ g_strerror(errno));
return false;
}
-static void *
-mvp_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error)
{
struct mvp_data *md = g_new(struct mvp_data, 1);
+
+ if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) {
+ g_free(md);
+ return NULL;
+ }
+
md->fd = -1;
- return md;
+ return &md->base;
}
static void
-mvp_output_finish(void *data)
+mvp_output_finish(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
+ ao_base_finish(&md->base);
g_free(md);
}
@@ -225,9 +233,10 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
}
static bool
-mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
+mvp_output_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
long long int stc = 0;
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
@@ -236,32 +245,32 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
if (md->fd < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error opening /dev/adec_pcm: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio source: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio streamtype: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio format: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
ioctl(md->fd, MVP_SET_AUD_STC, &stc);
if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio streamtype: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
@@ -273,17 +282,17 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
return true;
}
-static void mvp_output_close(void *data)
+static void mvp_output_close(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
if (md->fd >= 0)
close(md->fd);
md->fd = -1;
}
-static void mvp_output_cancel(void *data)
+static void mvp_output_cancel(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
if (md->fd >= 0) {
ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
close(md->fd);
@@ -292,16 +301,17 @@ static void mvp_output_cancel(void *data)
}
static size_t
-mvp_output_play(void *data, const void *chunk, size_t size, GError **error)
+mvp_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (md->fd < 0) {
bool success;
- success = mvp_output_open(md, &md->audio_format, error);
+ success = mvp_output_open(ao, &md->audio_format, error);
if (!success)
return 0;
}
@@ -316,7 +326,7 @@ mvp_output_play(void *data, const void *chunk, size_t size, GError **error)
continue;
g_set_error(error, mvp_output_quark(), errno,
- "Failed to write: %s", strerror(errno));
+ "Failed to write: %s", g_strerror(errno));
return 0;
}
}
diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h
new file mode 100644
index 000000000..e403de2b7
--- /dev/null
+++ b/src/output/mvp_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MVP_OUTPUT_PLUGIN_H
+#define MPD_MVP_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin mvp_output_plugin;
+
+#endif
diff --git a/src/output/null_plugin.c b/src/output/null_output_plugin.c
index 89abbd91f..9d7588fff 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "null_output_plugin.h"
#include "output_api.h"
#include "timer.h"
@@ -26,39 +27,42 @@
#include <assert.h>
struct null_data {
+ struct audio_output base;
+
bool sync;
- Timer *timer;
+ struct timer *timer;
};
-static void *
-null_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+null_init(const struct config_param *param, GError **error_r)
{
struct null_data *nd = g_new(struct null_data, 1);
+ if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) {
+ g_free(nd);
+ return NULL;
+ }
+
nd->sync = config_get_block_bool(param, "sync", true);
- nd->timer = NULL;
- return nd;
+ return &nd->base;
}
static void
-null_finish(void *data)
+null_finish(struct audio_output *ao)
{
- struct null_data *nd = data;
-
- assert(nd->timer == NULL);
+ struct null_data *nd = (struct null_data *)ao;
+ ao_base_finish(&nd->base);
g_free(nd);
}
static bool
-null_open(void *data, struct audio_format *audio_format,
+null_open(struct audio_output *ao, struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
if (nd->sync)
nd->timer = timer_new(audio_format);
@@ -67,40 +71,45 @@ null_open(void *data, struct audio_format *audio_format,
}
static void
-null_close(void *data)
+null_close(struct audio_output *ao)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
- if (nd->timer != NULL) {
+ if (nd->sync)
timer_free(nd->timer);
- nd->timer = NULL;
- }
+}
+
+static unsigned
+null_delay(struct audio_output *ao)
+{
+ struct null_data *nd = (struct null_data *)ao;
+
+ return nd->sync && nd->timer->started
+ ? timer_delay(nd->timer)
+ : 0;
}
static size_t
-null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size,
+null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size,
G_GNUC_UNUSED GError **error)
{
- struct null_data *nd = data;
- Timer *timer = nd->timer;
+ struct null_data *nd = (struct null_data *)ao;
+ struct timer *timer = nd->timer;
if (!nd->sync)
return size;
if (!timer->started)
timer_start(timer);
- else
- timer_sync(timer);
-
timer_add(timer, size);
return size;
}
static void
-null_cancel(void *data)
+null_cancel(struct audio_output *ao)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
if (!nd->sync)
return;
@@ -114,6 +123,7 @@ const struct audio_output_plugin null_output_plugin = {
.finish = null_finish,
.open = null_open,
.close = null_close,
+ .delay = null_delay,
.play = null_play,
.cancel = null_cancel,
};
diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h
new file mode 100644
index 000000000..392bf0aa3
--- /dev/null
+++ b/src/output/null_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NULL_OUTPUT_PLUGIN_H
+#define MPD_NULL_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin null_output_plugin;
+
+#endif
diff --git a/src/output/openal_plugin.c b/src/output/openal_output_plugin.c
index e5db8ac34..ebd35ef12 100644
--- a/src/output/openal_plugin.c
+++ b/src/output/openal_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,8 @@
*/
#include "config.h"
+#include "openal_output_plugin.h"
#include "output_api.h"
-#include "timer.h"
#include <glib.h>
@@ -38,12 +38,13 @@
#define NUM_BUFFERS 16
struct openal_data {
+ struct audio_output base;
+
const char *device_name;
ALCdevice *device;
ALCcontext *context;
- Timer *timer;
ALuint buffers[NUM_BUFFERS];
- int filled;
+ unsigned filled;
ALuint source;
ALenum format;
ALuint frequency;
@@ -80,6 +81,29 @@ openal_audio_format(struct audio_format *audio_format)
}
}
+G_GNUC_PURE
+static inline ALint
+openal_get_source_i(const struct openal_data *od, ALenum param)
+{
+ ALint value;
+ alGetSourcei(od->source, param, &value);
+ return value;
+}
+
+G_GNUC_PURE
+static inline bool
+openal_has_processed(const struct openal_data *od)
+{
+ return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+G_GNUC_PURE
+static inline ALint
+openal_is_playing(const struct openal_data *od)
+{
+ return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
+}
+
static bool
openal_setup_context(struct openal_data *od,
GError **error)
@@ -106,23 +130,8 @@ openal_setup_context(struct openal_data *od,
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)
+static struct audio_output *
+openal_init(const struct config_param *param, GError **error_r)
{
const char *device_name = config_get_block_string(param, "device", NULL);
struct openal_data *od;
@@ -132,35 +141,33 @@ openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
}
od = g_new(struct openal_data, 1);
+ if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device_name = device_name;
- return od;
+ return &od->base;
}
static void
-openal_finish(void *data)
+openal_finish(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
+ ao_base_finish(&od->base);
g_free(od);
}
static bool
-openal_open(void *data, struct audio_format *audio_format,
+openal_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
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;
}
@@ -184,18 +191,16 @@ openal_open(void *data, struct audio_format *audio_format,
}
od->filled = 0;
- od->timer = timer_new(audio_format);
od->frequency = audio_format->sample_rate;
return true;
}
static void
-openal_close(void *data)
+openal_close(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
- timer_free(od->timer);
alcMakeContextCurrent(od->context);
alDeleteSources(1, &od->source);
alDeleteBuffers(NUM_BUFFERS, od->buffers);
@@ -203,61 +208,63 @@ openal_close(void *data)
alcCloseDevice(od->device);
}
+static unsigned
+openal_delay(struct audio_output *ao)
+{
+ struct openal_data *od = (struct openal_data *)ao;
+
+ return od->filled < NUM_BUFFERS || openal_has_processed(od)
+ ? 0
+ /* we don't know exactly how long we must wait for the
+ next buffer to finish, so this is a random
+ guess: */
+ : 50;
+}
+
static size_t
-openal_play(void *data, const void *chunk, size_t size,
+openal_play(struct audio_output *ao, const void *chunk, size_t size,
G_GNUC_UNUSED GError **error)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
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);
- }
+ while (!openal_has_processed(od))
+ g_usleep(10);
alSourceUnqueueBuffers(od->source, 1, &buffer);
}
alBufferData(buffer, od->format, chunk, size, od->frequency);
alSourceQueueBuffers(od->source, 1, &buffer);
- alGetSourcei(od->source, AL_SOURCE_STATE, &state);
- if (state != AL_PLAYING) {
+ if (!openal_is_playing(od))
alSourcePlay(od->source);
- }
return size;
}
static void
-openal_cancel(void *data)
+openal_cancel(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
od->filled = 0;
alcMakeContextCurrent(od->context);
alSourceStop(od->source);
- openal_unqueue_buffers(od);
+
+ /* force-unqueue all buffers */
+ alSourcei(od->source, AL_BUFFER, 0);
+ od->filled = 0;
}
const struct audio_output_plugin openal_output_plugin = {
@@ -266,6 +273,7 @@ const struct audio_output_plugin openal_output_plugin = {
.finish = openal_finish,
.open = openal_open,
.close = openal_close,
+ .delay = openal_delay,
.play = openal_play,
.cancel = openal_cancel,
};
diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h
new file mode 100644
index 000000000..25f6ccf46
--- /dev/null
+++ b/src/output/openal_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPENAL_OUTPUT_PLUGIN_H
+#define MPD_OPENAL_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin openal_output_plugin;
+
+#endif
diff --git a/src/output/oss_plugin.c b/src/output/oss_output_plugin.c
index 9261b423c..e366a4537 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,11 @@
*/
#include "config.h"
+#include "oss_output_plugin.h"
#include "output_api.h"
#include "mixer_list.h"
#include "fd_util.h"
+#include "glib_compat.h"
#include <glib.h>
@@ -50,7 +52,17 @@
#undef AFMT_S24_NE
#endif
+#ifdef AFMT_S24_PACKED
+#include "pcm_export.h"
+#endif
+
struct oss_data {
+ struct audio_output base;
+
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state export;
+#endif
+
int fd;
const char *device;
@@ -59,6 +71,12 @@ struct oss_data {
* the device after cancel().
*/
struct audio_format audio_format;
+
+ /**
+ * The current OSS audio format. This is needed to reopen the
+ * device after cancel().
+ */
+ int oss_format;
};
/**
@@ -136,13 +154,13 @@ oss_output_test_default_device(void)
return true;
}
g_warning("Error opening OSS device \"%s\": %s\n",
- default_devices[i], strerror(errno));
+ default_devices[i], g_strerror(errno));
}
return false;
}
-static void *
+static struct audio_output *
oss_open_default(GError **error)
{
int i;
@@ -153,8 +171,14 @@ oss_open_default(GError **error)
ret[i] = oss_stat_device(default_devices[i], &err[i]);
if (ret[i] == OSS_STAT_NO_ERROR) {
struct oss_data *od = oss_data_new();
+ if (!ao_base_init(&od->base, &oss_output_plugin, NULL,
+ error)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device = default_devices[i];
- return od;
+ return &od->base;
}
}
@@ -175,7 +199,7 @@ oss_open_default(GError **error)
break;
case OSS_STAT_OTHER:
g_warning("Error accessing %s: %s\n",
- dev, strerror(err[i]));
+ dev, g_strerror(err[i]));
}
}
@@ -184,29 +208,55 @@ oss_open_default(GError **error)
return NULL;
}
-static void *
-oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- GError **error)
+static struct audio_output *
+oss_output_init(const struct config_param *param, GError **error)
{
const char *device = config_get_block_string(param, "device", NULL);
if (device != NULL) {
struct oss_data *od = oss_data_new();
+ if (!ao_base_init(&od->base, &oss_output_plugin, param,
+ error)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device = device;
- return od;
+ return &od->base;
}
return oss_open_default(error);
}
static void
-oss_output_finish(void *data)
+oss_output_finish(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
+ ao_base_finish(&od->base);
oss_data_free(od);
}
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ struct oss_data *od = (struct oss_data *)ao;
+
+ pcm_export_init(&od->export);
+ return true;
+}
+
+static void
+oss_output_disable(struct audio_output *ao)
+{
+ struct oss_data *od = (struct oss_data *)ao;
+
+ pcm_export_deinit(&od->export);
+}
+
+#endif
+
static void
oss_close(struct oss_data *od)
{
@@ -381,6 +431,8 @@ sample_format_to_oss(enum sample_format format)
{
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_FLOAT:
+ case SAMPLE_FORMAT_DSD:
return AFMT_QUERY;
case SAMPLE_FORMAT_S8:
@@ -389,13 +441,6 @@ sample_format_to_oss(enum sample_format format)
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;
@@ -430,7 +475,7 @@ sample_format_from_oss(int format)
#ifdef AFMT_S24_PACKED
case AFMT_S24_PACKED:
- return SAMPLE_FORMAT_S24;
+ return SAMPLE_FORMAT_S24_P32;
#endif
#ifdef AFMT_S24_NE
@@ -449,33 +494,83 @@ sample_format_from_oss(int format)
}
/**
+ * Probe one sample format.
+ *
+ * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on
+ * error
+ */
+static enum oss_setup_result
+oss_probe_sample_format(int fd, enum sample_format sample_format,
+ enum sample_format *sample_format_r,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state *export,
+#endif
+ GError **error_r)
+{
+ int oss_format = sample_format_to_oss(sample_format);
+ if (oss_format == AFMT_QUERY)
+ return UNSUPPORTED;
+
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+
+#ifdef AFMT_S24_PACKED
+ if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) {
+ /* if the driver doesn't support padded 24 bit, try
+ packed 24 bit */
+ oss_format = AFMT_S24_PACKED;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+ }
+#endif
+
+ if (result != SUCCESS)
+ return result;
+
+ sample_format = sample_format_from_oss(oss_format);
+ if (sample_format == SAMPLE_FORMAT_UNDEFINED)
+ return UNSUPPORTED;
+
+ *sample_format_r = sample_format;
+ *oss_format_r = oss_format;
+
+#ifdef AFMT_S24_PACKED
+ pcm_export_open(export, sample_format, 0, false, false,
+ oss_format == AFMT_S24_PACKED,
+ oss_format == AFMT_S24_PACKED &&
+ G_BYTE_ORDER != G_LITTLE_ENDIAN);
+#endif
+
+ return SUCCESS;
+}
+
+/**
* Set up the sample format, and attempts to find alternatives if the
* specified format is not supported.
*/
static bool
oss_setup_sample_format(int fd, struct audio_format *audio_format,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state *export,
+#endif
GError **error_r)
{
- 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;
+ enum oss_setup_result result =
+ oss_probe_sample_format(fd, audio_format->format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ export,
+#endif
+ error_r);
switch (result) {
case SUCCESS:
- mpd_format = sample_format_from_oss(oss_format);
- if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
- break;
-
audio_format->format = mpd_format;
-
-#ifdef AFMT_S24_PACKED
- if (oss_format == AFMT_S24_PACKED)
- audio_format->reverse_endian =
- G_BYTE_ORDER != G_LITTLE_ENDIAN;
-#endif
return true;
case ERROR:
@@ -485,13 +580,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
break;
}
+ if (result != UNSUPPORTED)
+ return result == SUCCESS;
+
/* the requested sample format is not available - probe for
other formats supported by MPD */
static const enum sample_format sample_formats[] = {
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S24,
SAMPLE_FORMAT_S16,
SAMPLE_FORMAT_S8,
SAMPLE_FORMAT_UNDEFINED /* sentinel */
@@ -503,26 +600,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_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);
+ result = oss_probe_sample_format(fd, mpd_format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ export,
+#endif
+ error_r);
switch (result) {
case SUCCESS:
- mpd_format = sample_format_from_oss(oss_format);
- if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
- break;
-
audio_format->format = mpd_format;
-
-#ifdef AFMT_S24_PACKED
- if (oss_format == AFMT_S24_PACKED)
- audio_format->reverse_endian =
- G_BYTE_ORDER != G_LITTLE_ENDIAN;
-#endif
return true;
case ERROR:
@@ -533,7 +619,8 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
}
}
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ g_set_error_literal(error_r, oss_output_quark(), EINVAL,
+ "Failed to set sample format");
return false;
}
@@ -546,7 +633,11 @@ oss_setup(struct oss_data *od, struct audio_format *audio_format,
{
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);
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ &od->export,
+#endif
+ error_r);
}
/**
@@ -561,7 +652,7 @@ oss_reopen(struct oss_data *od, GError **error_r)
if (od->fd < 0) {
g_set_error(error_r, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return false;
}
@@ -590,9 +681,8 @@ oss_reopen(struct oss_data *od, GError **error_r)
}
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),
+ od->oss_format,
msg3, error_r);
if (result != SUCCESS) {
oss_close(od);
@@ -606,15 +696,16 @@ oss_reopen(struct oss_data *od, GError **error_r)
}
static bool
-oss_output_open(void *data, struct audio_format *audio_format, GError **error)
+oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
od->fd = open_cloexec(od->device, O_WRONLY, 0);
if (od->fd < 0) {
g_set_error(error, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return false;
}
@@ -628,17 +719,17 @@ oss_output_open(void *data, struct audio_format *audio_format, GError **error)
}
static void
-oss_output_close(void *data)
+oss_output_close(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
oss_close(od);
}
static void
-oss_output_cancel(void *data)
+oss_output_cancel(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
if (od->fd >= 0) {
ioctl(od->fd, SNDCTL_DSP_RESET, 0);
@@ -647,24 +738,33 @@ oss_output_cancel(void *data)
}
static size_t
-oss_output_play(void *data, const void *chunk, size_t size, GError **error)
+oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (od->fd < 0 && !oss_reopen(od, error))
return 0;
+#ifdef AFMT_S24_PACKED
+ chunk = pcm_export(&od->export, chunk, size, &size);
+#endif
+
while (true) {
ret = write(od->fd, chunk, size);
- if (ret > 0)
- return (size_t)ret;
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = pcm_export_source_size(&od->export, ret);
+#endif
+ return ret;
+ }
if (ret < 0 && errno != EINTR) {
g_set_error(error, oss_output_quark(), errno,
"Write error on %s: %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return 0;
}
}
@@ -675,6 +775,10 @@ const struct audio_output_plugin oss_output_plugin = {
.test_default_device = oss_output_test_default_device,
.init = oss_output_init,
.finish = oss_output_finish,
+#ifdef AFMT_S24_PACKED
+ .enable = oss_output_enable,
+ .disable = oss_output_disable,
+#endif
.open = oss_output_open,
.close = oss_output_close,
.play = oss_output_play,
diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h
new file mode 100644
index 000000000..2aecc2b3a
--- /dev/null
+++ b/src/output/oss_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSS_OUTPUT_PLUGIN_H
+#define MPD_OSS_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin oss_output_plugin;
+
+#endif
diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c
new file mode 100644
index 000000000..fbba81749
--- /dev/null
+++ b/src/output/osx_output_plugin.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "osx_output_plugin.h"
+#include "output_api.h"
+#include "fifo_buffer.h"
+
+#include <glib.h>
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "osx"
+
+struct osx_output {
+ struct audio_output base;
+
+ /* configuration settings */
+ OSType component_subtype;
+ /* only applicable with kAudioUnitSubType_HALOutput */
+ const char *device_name;
+
+ AudioUnit au;
+ GMutex *mutex;
+ GCond *condition;
+
+ struct fifo_buffer *buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+osx_output_quark(void)
+{
+ return g_quark_from_static_string("osx_output");
+}
+
+static bool
+osx_output_test_default_device(void)
+{
+ /* on a Mac, this is always the default plugin, if nothing
+ else is configured */
+ return true;
+}
+
+static void
+osx_output_configure(struct osx_output *oo, const struct config_param *param)
+{
+ const char *device = config_get_block_string(param, "device", NULL);
+
+ if (device == NULL || 0 == strcmp(device, "default")) {
+ oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+ oo->device_name = NULL;
+ }
+ else if (0 == strcmp(device, "system")) {
+ oo->component_subtype = kAudioUnitSubType_SystemOutput;
+ oo->device_name = NULL;
+ }
+ else {
+ oo->component_subtype = kAudioUnitSubType_HALOutput;
+ /* XXX am I supposed to g_strdup() this? */
+ oo->device_name = device;
+ }
+}
+
+static struct audio_output *
+osx_output_init(const struct config_param *param, GError **error_r)
+{
+ struct osx_output *oo = g_new(struct osx_output, 1);
+ if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) {
+ g_free(oo);
+ return NULL;
+ }
+
+ osx_output_configure(oo, param);
+ oo->mutex = g_mutex_new();
+ oo->condition = g_cond_new();
+
+ return &oo->base;
+}
+
+static void
+osx_output_finish(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_free(od->mutex);
+ g_cond_free(od->condition);
+ g_free(od);
+}
+
+static bool
+osx_output_set_device(struct osx_output *oo, GError **error)
+{
+ bool ret = true;
+ OSStatus status;
+ UInt32 size, numdevices;
+ AudioDeviceID *deviceids = NULL;
+ char name[256];
+ unsigned int i;
+
+ if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+ goto done;
+
+ /* how many audio devices are there? */
+ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &size,
+ NULL);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine number of OS X audio devices: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* what are the available audio device IDs? */
+ numdevices = size / sizeof(AudioDeviceID);
+ deviceids = g_malloc(size);
+ status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &size,
+ deviceids);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X audio device IDs: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* which audio device matches oo->device_name? */
+ for (i = 0; i < numdevices; i++) {
+ size = sizeof(name);
+ status = AudioDeviceGetProperty(deviceids[i], 0, false,
+ kAudioDevicePropertyDeviceName,
+ &size, name);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X device name "
+ "(device %u): %s",
+ (unsigned int) deviceids[i],
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ if (strcmp(oo->device_name, name) == 0) {
+ g_debug("found matching device: ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+ break;
+ }
+ }
+ if (i == numdevices) {
+ g_warning("Found no audio device with name '%s' "
+ "(will use default audio device)",
+ oo->device_name);
+ goto done;
+ }
+
+ status = AudioUnitSetProperty(oo->au,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &(deviceids[i]),
+ sizeof(AudioDeviceID));
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to set OS X audio output device: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ g_debug("set OS X audio output device ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+
+done:
+ if (deviceids != NULL)
+ g_free(deviceids);
+ return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+ G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
+ G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
+ G_GNUC_UNUSED UInt32 in_bus_number,
+ G_GNUC_UNUSED UInt32 in_number_frames,
+ AudioBufferList *buffer_list)
+{
+ struct osx_output *od = (struct osx_output *) vdata;
+ AudioBuffer *buffer = &buffer_list->mBuffers[0];
+ size_t buffer_size = buffer->mDataByteSize;
+
+ assert(od->buffer != NULL);
+
+ g_mutex_lock(od->mutex);
+
+ size_t nbytes;
+ const void *src = fifo_buffer_read(od->buffer, &nbytes);
+
+ if (src != NULL) {
+ if (nbytes > buffer_size)
+ nbytes = buffer_size;
+
+ memcpy(buffer->mData, src, nbytes);
+ fifo_buffer_consume(od->buffer, nbytes);
+ } else
+ nbytes = 0;
+
+ g_cond_signal(od->condition);
+ g_mutex_unlock(od->mutex);
+
+ if (nbytes < buffer_size)
+ memset((unsigned char*)buffer->mData + nbytes, 0,
+ buffer_size - nbytes);
+
+ return 0;
+}
+
+static bool
+osx_output_enable(struct audio_output *ao, GError **error_r)
+{
+ struct osx_output *oo = (struct osx_output *)ao;
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = oo->component_subtype;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == 0) {
+ g_set_error(error_r, osx_output_quark(), 0,
+ "Error finding OS X component");
+ return false;
+ }
+
+ OSStatus status = OpenAComponent(comp, &oo->au);
+ if (status != noErr) {
+ g_set_error(error_r, osx_output_quark(), status,
+ "Unable to open OS X component: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ if (!osx_output_set_device(oo, error_r)) {
+ CloseComponent(oo->au);
+ return false;
+ }
+
+ AURenderCallbackStruct callback;
+ callback.inputProc = osx_render;
+ callback.inputProcRefCon = oo;
+
+ ComponentResult result =
+ AudioUnitSetProperty(oo->au,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0,
+ &callback, sizeof(callback));
+ if (result != noErr) {
+ CloseComponent(oo->au);
+ g_set_error(error_r, osx_output_quark(), result,
+ "unable to set callback for OS X audio unit");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+osx_output_disable(struct audio_output *ao)
+{
+ struct osx_output *oo = (struct osx_output *)ao;
+
+ CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_lock(od->mutex);
+ fifo_buffer_clear(od->buffer);
+ g_mutex_unlock(od->mutex);
+}
+
+static void
+osx_output_close(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ AudioOutputUnitStop(od->au);
+ AudioUnitUninitialize(od->au);
+
+ fifo_buffer_free(od->buffer);
+}
+
+static bool
+osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ AudioStreamBasicDescription stream_description;
+ stream_description.mSampleRate = audio_format->sample_rate;
+ stream_description.mFormatID = kAudioFormatLinearPCM;
+ stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ stream_description.mBitsPerChannel = 32;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S32;
+ stream_description.mBitsPerChannel = 32;
+ break;
+ }
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+ stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+#endif
+
+ stream_description.mBytesPerPacket =
+ audio_format_frame_size(audio_format);
+ stream_description.mFramesPerPacket = 1;
+ stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
+ stream_description.mChannelsPerFrame = audio_format->channels;
+
+ ComponentResult result =
+ AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0,
+ &stream_description,
+ sizeof(stream_description));
+ if (result != noErr) {
+ g_set_error(error, osx_output_quark(), result,
+ "Unable to set format on OS X device");
+ return false;
+ }
+
+ OSStatus status = AudioUnitInitialize(od->au);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to initialize OS X audio unit: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ /* create a buffer of 1s */
+ od->buffer = fifo_buffer_new(audio_format->sample_rate *
+ audio_format_frame_size(audio_format));
+
+ status = AudioOutputUnitStart(od->au);
+ if (status != 0) {
+ AudioUnitUninitialize(od->au);
+ g_set_error(error, osx_output_quark(), status,
+ "unable to start audio output: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_lock(od->mutex);
+
+ void *dest;
+ size_t max_length;
+
+ while (true) {
+ dest = fifo_buffer_write(od->buffer, &max_length);
+ if (dest != NULL)
+ break;
+
+ /* wait for some free space in the buffer */
+ g_cond_wait(od->condition, od->mutex);
+ }
+
+ if (size > max_length)
+ size = max_length;
+
+ memcpy(dest, chunk, size);
+ fifo_buffer_append(od->buffer, size);
+
+ g_mutex_unlock(od->mutex);
+
+ return size;
+}
+
+const struct audio_output_plugin osx_output_plugin = {
+ .name = "osx",
+ .test_default_device = osx_output_test_default_device,
+ .init = osx_output_init,
+ .finish = osx_output_finish,
+ .enable = osx_output_enable,
+ .disable = osx_output_disable,
+ .open = osx_output_open,
+ .close = osx_output_close,
+ .play = osx_output_play,
+ .cancel = osx_output_cancel,
+};
diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h
new file mode 100644
index 000000000..814702d4f
--- /dev/null
+++ b/src/output/osx_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSX_OUTPUT_PLUGIN_H
+#define MPD_OSX_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin osx_output_plugin;
+
+#endif
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
deleted file mode 100644
index 501dcec10..000000000
--- a/src/output/osx_plugin.c
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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 "fifo_buffer.h"
-
-#include <glib.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "osx"
-
-struct osx_output {
- AudioUnit au;
- GMutex *mutex;
- GCond *condition;
-
- struct fifo_buffer *buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-osx_output_quark(void)
-{
- return g_quark_from_static_string("osx_output");
-}
-
-static bool
-osx_output_test_default_device(void)
-{
- /* on a Mac, this is always the default plugin, if nothing
- else is configured */
- return true;
-}
-
-static void *
-osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
-{
- struct osx_output *oo = g_new(struct osx_output, 1);
-
- oo->mutex = g_mutex_new();
- oo->condition = g_cond_new();
-
- return oo;
-}
-
-static void osx_output_finish(void *data)
-{
- struct osx_output *od = data;
-
- g_mutex_free(od->mutex);
- g_cond_free(od->condition);
- g_free(od);
-}
-
-static void osx_output_cancel(void *data)
-{
- struct osx_output *od = data;
-
- g_mutex_lock(od->mutex);
- fifo_buffer_clear(od->buffer);
- g_mutex_unlock(od->mutex);
-}
-
-static void osx_output_close(void *data)
-{
- struct osx_output *od = data;
-
- AudioOutputUnitStop(od->au);
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
-
- fifo_buffer_free(od->buffer);
-}
-
-static OSStatus
-osx_render(void *vdata,
- G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
- G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
- G_GNUC_UNUSED UInt32 in_bus_number,
- G_GNUC_UNUSED UInt32 in_number_frames,
- AudioBufferList *buffer_list)
-{
- struct osx_output *od = (struct osx_output *) vdata;
- AudioBuffer *buffer = &buffer_list->mBuffers[0];
- size_t buffer_size = buffer->mDataByteSize;
-
- assert(od->buffer != NULL);
-
- g_mutex_lock(od->mutex);
-
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
-
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
-
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
-
- g_cond_signal(od->condition);
- g_mutex_unlock(od->mutex);
-
- if (nbytes < buffer_size)
- memset((unsigned char*)buffer->mData + nbytes, 0,
- buffer_size - nbytes);
-
- return 0;
-}
-
-static bool
-osx_output_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct osx_output *od = data;
- ComponentDescription desc;
- Component comp;
- AURenderCallbackStruct callback;
- AudioStreamBasicDescription stream_description;
- OSStatus status;
- ComponentResult result;
-
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-
- comp = FindNextComponent(NULL, &desc);
- if (comp == 0) {
- g_set_error(error, osx_output_quark(), 0,
- "Error finding OS X component");
- return false;
- }
-
- status = OpenAComponent(comp, &od->au);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), 0,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "Unable to initialize OS X audio unit: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- callback.inputProc = osx_render;
- callback.inputProcRefCon = od;
-
- result = AudioUnitSetProperty(od->au,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0,
- &callback, sizeof(callback));
- if (result != noErr) {
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "unable to set callback for OS X audio unit");
- return false;
- }
-
- stream_description.mSampleRate = audio_format->sample_rate;
- stream_description.mFormatID = kAudioFormatLinearPCM;
- stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- stream_description.mBitsPerChannel = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- stream_description.mBitsPerChannel = 16;
- break;
-
- default:
- audio_format->format = SAMPLE_FORMAT_S16;
- stream_description.mBitsPerChannel = 16;
- break;
- }
-
-#if G_BYTE_ORDER == G_BIG_ENDIAN
- stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-#endif
-
- stream_description.mBytesPerPacket =
- audio_format_frame_size(audio_format);
- stream_description.mFramesPerPacket = 1;
- stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
- stream_description.mChannelsPerFrame = audio_format->channels;
-
- result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input, 0,
- &stream_description,
- sizeof(stream_description));
- if (result != noErr) {
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "Unable to set format on OS X device");
- return false;
- }
-
- /* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format->sample_rate *
- audio_format_frame_size(audio_format));
-
- status = AudioOutputUnitStart(od->au);
- if (status != 0) {
- fifo_buffer_free(od->buffer);
- g_set_error(error, osx_output_quark(), 0,
- "unable to start audio output: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- return true;
-}
-
-static size_t
-osx_output_play(void *data, const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct osx_output *od = data;
-
- g_mutex_lock(od->mutex);
-
- void *dest;
- size_t max_length;
-
- while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
- break;
-
- /* wait for some free space in the buffer */
- g_cond_wait(od->condition, od->mutex);
- }
-
- if (size > max_length)
- size = max_length;
-
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
-
- g_mutex_unlock(od->mutex);
-
- return size;
-}
-
-const struct audio_output_plugin osxPlugin = {
- .name = "osx",
- .test_default_device = osx_output_test_default_device,
- .init = osx_output_init,
- .finish = osx_output_finish,
- .open = osx_output_open,
- .close = osx_output_close,
- .play = osx_output_play,
- .cancel = osx_output_cancel,
-};
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
index 1d1aec7b1..90c5a5331 100644
--- a/src/output/pipe_output_plugin.c
+++ b/src/output/pipe_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,12 +18,15 @@
*/
#include "config.h"
+#include "pipe_output_plugin.h"
#include "output_api.h"
#include <stdio.h>
#include <errno.h>
struct pipe_output {
+ struct audio_output base;
+
char *cmd;
FILE *fh;
};
@@ -37,13 +40,17 @@ pipe_output_quark(void)
return g_quark_from_static_string("pipe_output");
}
-static void *
-pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+pipe_output_init(const struct config_param *param,
GError **error)
{
struct pipe_output *pd = g_new(struct pipe_output, 1);
+ if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) {
+ g_free(pd);
+ return NULL;
+ }
+
pd->cmd = config_dup_block_string(param, "command", NULL);
if (pd->cmd == NULL) {
g_set_error(error, pipe_output_quark(), 0,
@@ -51,23 +58,25 @@ pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- return pd;
+ return &pd->base;
}
static void
-pipe_output_finish(void *data)
+pipe_output_finish(struct audio_output *ao)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
g_free(pd->cmd);
+ ao_base_finish(&pd->base);
g_free(pd);
}
static bool
-pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format,
+pipe_output_open(struct audio_output *ao,
+ G_GNUC_UNUSED struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
pd->fh = popen(pd->cmd, "w");
if (pd->fh == NULL) {
@@ -81,17 +90,17 @@ pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format,
}
static void
-pipe_output_close(void *data)
+pipe_output_close(struct audio_output *ao)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
pclose(pd->fh);
}
static size_t
-pipe_output_play(void *data, const void *chunk, size_t size, GError **error)
+pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
size_t ret;
ret = fwrite(chunk, 1, size, pd->fh);
diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h
new file mode 100644
index 000000000..9f014f829
--- /dev/null
+++ b/src/output/pipe_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PIPE_OUTPUT_PLUGIN_H
+#define MPD_PIPE_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
index 5fe2f572e..0dc9be0e4 100644
--- a/src/output/pulse_output_plugin.c
+++ b/src/output/pulse_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,11 +31,44 @@
#include <pulse/introspect.h>
#include <pulse/subscribe.h>
#include <pulse/error.h>
+#include <pulse/version.h>
#include <assert.h>
+#include <stddef.h>
#define MPD_PULSE_NAME "Music Player Daemon"
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct pulse_output {
+ struct audio_output base;
+
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ struct pulse_mixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+#if !PA_CHECK_VERSION(0,9,11)
+ /**
+ * We need this variable because pa_stream_is_corked() wasn't
+ * added before 0.9.11.
+ */
+ bool pause;
+#endif
+};
+
/**
* The quark used for GError.domain.
*/
@@ -46,6 +79,18 @@ pulse_output_quark(void)
}
void
+pulse_output_lock(struct pulse_output *po)
+{
+ pa_threaded_mainloop_lock(po->mainloop);
+}
+
+void
+pulse_output_unlock(struct pulse_output *po)
+{
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
{
assert(po != NULL);
@@ -299,16 +344,19 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r)
return true;
}
-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)
+static struct audio_output *
+pulse_output_init(const struct config_param *param, GError **error_r)
{
struct pulse_output *po;
g_setenv("PULSE_PROP_media.role", "music", true);
po = g_new(struct pulse_output, 1);
+ if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) {
+ g_free(po);
+ return NULL;
+ }
+
po->name = config_get_block_string(param, "name", "mpd_pulse");
po->server = config_get_block_string(param, "server", NULL);
po->sink = config_get_block_string(param, "sink", NULL);
@@ -318,21 +366,22 @@ pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
po->context = NULL;
po->stream = NULL;
- return po;
+ return &po->base;
}
static void
-pulse_output_finish(void *data)
+pulse_output_finish(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
+ ao_base_finish(&po->base);
g_free(po);
}
static bool
-pulse_output_enable(void *data, GError **error_r)
+pulse_output_enable(struct audio_output *ao, GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
assert(po->mainloop == NULL);
assert(po->context == NULL);
@@ -376,9 +425,9 @@ pulse_output_enable(void *data, GError **error_r)
}
static void
-pulse_output_disable(void *data)
+pulse_output_disable(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
assert(po->mainloop != NULL);
@@ -494,11 +543,46 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
pa_threaded_mainloop_signal(po->mainloop, 0);
}
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss,
+ GError **error_r)
+{
+ assert(po != NULL);
+ assert(po->context != NULL);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, NULL);
+ if (po->stream == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_new() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if PA_CHECK_VERSION(0,9,8)
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+#endif
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ return true;
+}
+
static bool
-pulse_output_open(void *data, struct audio_format *audio_format,
+pulse_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_sample_spec ss;
int error;
@@ -540,25 +624,11 @@ pulse_output_open(void *data, struct audio_format *audio_format,
/* 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)));
+ if (!pulse_output_setup_stream(po, &ss, error_r)) {
pa_threaded_mainloop_unlock(po->mainloop);
return false;
}
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-#endif
-
- pa_stream_set_state_callback(po->stream,
- pulse_output_stream_state_cb, po);
- pa_stream_set_write_callback(po->stream,
- pulse_output_stream_write_cb, po);
-
/* .. and connect it (asynchronously) */
error = pa_stream_connect_playback(po->stream, po->sink,
@@ -583,9 +653,9 @@ pulse_output_open(void *data, struct audio_format *audio_format,
}
static void
-pulse_output_close(void *data)
+pulse_output_close(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_operation *o;
assert(po->mainloop != NULL);
@@ -732,9 +802,10 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause,
}
static size_t
-pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
int error;
assert(po->mainloop != NULL);
@@ -802,9 +873,9 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
}
static void
-pulse_output_cancel(void *data)
+pulse_output_cancel(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_operation *o;
assert(po->mainloop != NULL);
@@ -834,9 +905,9 @@ pulse_output_cancel(void *data)
}
static bool
-pulse_output_pause(void *data)
+pulse_output_pause(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
GError *error = NULL;
assert(po->mainloop != NULL);
@@ -881,12 +952,12 @@ pulse_output_test_default_device(void)
struct pulse_output *po;
bool success;
- po = pulse_output_init(NULL, NULL, NULL);
+ po = (struct pulse_output *)pulse_output_init(NULL, NULL);
if (po == NULL)
return false;
success = pulse_output_wait_connection(po, NULL);
- pulse_output_finish(po);
+ pulse_output_finish(&po->base);
return success;
}
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
index 06e3aec43..02a51f27b 100644
--- a/src/output/pulse_output_plugin.h
+++ b/src/output/pulse_output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,43 +21,20 @@
#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 pulse_output;
+struct pulse_mixer;
struct pa_cvolume;
-struct pulse_output {
- const char *name;
- const char *server;
- const char *sink;
-
- struct pulse_mixer *mixer;
+extern const struct audio_output_plugin pulse_output_plugin;
- struct pa_threaded_mainloop *mainloop;
- struct pa_context *context;
- struct pa_stream *stream;
-
- size_t writable;
+void
+pulse_output_lock(struct pulse_output *po);
-#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_unlock(struct pulse_output *po);
void
pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c
new file mode 100644
index 000000000..6177b9b7d
--- /dev/null
+++ b/src/output/raop_output_plugin.c
@@ -0,0 +1,1056 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "raop_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "fd_util.h"
+#include "ntp_server.h"
+#include "rtsp_client.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <openssl/aes.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/engine.h>
+
+#ifndef WIN32
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "raop"
+
+struct play_state {
+ bool playing;
+ unsigned short seq_num;
+ unsigned int rtptime;
+ unsigned int sync_src;
+ unsigned int start_rtptime;
+ struct timeval start_time;
+ struct timeval last_send;
+};
+
+/*********************************************************************/
+
+enum pause_state {
+ NO_PAUSE = 0,
+ OP_PAUSE,
+ NODATA_PAUSE,
+};
+
+#define MINIMUM_SAMPLE_SIZE 32
+
+#define RAOP_FD_READ (1<<0)
+#define RAOP_FD_WRITE (1<<1)
+
+/*********************************************************************/
+
+struct encrypt_data {
+ AES_KEY ctx;
+ unsigned char iv[16]; // initialization vector for aes-cbc
+ unsigned char nv[16]; // next vector for aes-cbc
+ unsigned char key[16]; // key for aes-cbc
+};
+
+/*********************************************************************/
+
+struct raop_data {
+ struct audio_output base;
+
+ struct rtspcl_data *rtspcl;
+ const char *addr; // target host address
+ short rtsp_port;
+ struct sockaddr_in ctrl_addr;
+ struct sockaddr_in data_addr;
+
+ bool is_master;
+ struct raop_data *next;
+
+ unsigned volume;
+
+ GMutex *control_mutex;
+
+ bool started;
+ bool paused;
+};
+
+/*********************************************************************/
+
+struct control_data {
+ unsigned short port;
+ int fd;
+};
+
+/*********************************************************************/
+
+#define NUMSAMPLES 352
+#define RAOP_BUFFER_SIZE NUMSAMPLES * 4
+#define RAOP_HEADER_SIZE 12
+#define ALAC_MAX_HEADER_SIZE 8
+#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE
+
+// session
+struct raop_session_data {
+ struct raop_data *raop_list;
+ struct ntp_server ntp;
+ struct control_data ctrl;
+ struct encrypt_data encrypt;
+ struct play_state play_state;
+
+ int data_fd;
+
+ unsigned char buffer[RAOP_BUFFER_SIZE];
+ size_t bufferSize;
+
+ unsigned char data[RAOP_MAX_PACKET_SIZE];
+ int wblk_wsize;
+ int wblk_remsize;
+
+ GMutex *data_mutex;
+ GMutex *list_mutex;
+};
+
+/*********************************************************************/
+
+static struct raop_session_data *raop_session = NULL;
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+raop_output_quark(void)
+{
+ return g_quark_from_static_string("raop_output");
+}
+
+static void
+raop_session_free(struct raop_session_data *session)
+{
+ assert(session != NULL);
+ assert(session->raop_list == NULL);
+
+ ntp_server_close(&session->ntp);
+
+ if (session->data_mutex != NULL)
+ g_mutex_free(session->data_mutex);
+
+ if (session->list_mutex != NULL)
+ g_mutex_free(session->list_mutex);
+
+ if (raop_session->data_fd >= 0)
+ close_socket(raop_session->data_fd);
+
+ if (raop_session->ctrl.fd >= 0)
+ close_socket(raop_session->ctrl.fd);
+
+ g_free(session);
+}
+
+static struct raop_session_data *
+raop_session_new(GError **error_r)
+{
+ struct raop_session_data *session = g_new(struct raop_session_data, 1);
+ session->raop_list = NULL;
+
+ session->data_mutex = g_mutex_new();
+ session->list_mutex = g_mutex_new();
+
+ ntp_server_init(&session->ntp);
+ session->ctrl.port = 6001;
+ session->ctrl.fd = -1;
+ session->play_state.playing = false;
+ session->play_state.seq_num = (short) g_random_int();
+ session->play_state.rtptime = g_random_int();
+ session->play_state.sync_src = g_random_int();
+ session->play_state.last_send.tv_sec = 0;
+ session->play_state.last_send.tv_usec = 0;
+ session->data_fd = -1;
+
+ if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) ||
+ !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) {
+ raop_session_free(session);
+ g_set_error(error_r, raop_output_quark(), 0,
+ "RAND_bytes error code=%ld", ERR_get_error());
+ return NULL;
+ }
+ memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv));
+ for (unsigned i = 0; i < 16; i++) {
+ printf("0x%x ", session->encrypt.key[i]);
+ }
+ printf("\n");
+ AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx);
+
+ memset(session->buffer, 0, RAOP_BUFFER_SIZE);
+ session->bufferSize = 0;
+
+ return session;
+}
+
+static struct raop_data *
+new_raop_data(const struct config_param *param, GError **error_r)
+{
+ struct raop_data *ret = g_new(struct raop_data, 1);
+ if (!ao_base_init(&ret->base, &raop_output_plugin, param, error_r)) {
+ g_free(ret);
+ return NULL;
+ }
+
+ ret->control_mutex = g_mutex_new();
+
+ ret->next = NULL;
+ ret->is_master = 0;
+ ret->started = 0;
+ ret->paused = 0;
+
+ if (raop_session == NULL &&
+ (raop_session = raop_session_new(error_r)) == NULL) {
+ g_mutex_free(ret->control_mutex);
+ ao_base_finish(&ret->base);
+ g_free(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * remove one character from a string
+ * return the number of deleted characters
+ */
+static int
+remove_char_from_string(char *str, char c)
+{
+ char *src, *dst;
+
+ /* skip all characters that don't need to be copied */
+ src = strchr(str, c);
+ if (!src)
+ return 0;
+
+ for (dst = src; *src; src++)
+ if (*src != c)
+ *(dst++) = *src;
+
+ *dst = '\0';
+
+ return src - dst;
+}
+
+/* bind an opened socket to specified hostname and port.
+ * if hostname=NULL, use INADDR_ANY.
+ * if *port=0, use dynamically assigned port
+ */
+static int bind_host(int sd, char *hostname, unsigned long ulAddr,
+ unsigned short *port, GError **error_r)
+{
+ struct sockaddr_in my_addr;
+ socklen_t nlen = sizeof(struct sockaddr);
+ struct hostent *h;
+
+ memset(&my_addr, 0, sizeof(my_addr));
+ /* use specified hostname */
+ if (hostname) {
+ /* get server IP address (no check if input is IP address or DNS name) */
+ h = gethostbyname(hostname);
+ if (h == NULL) {
+ if (strstr(hostname, "255.255.255.255") == hostname) {
+ my_addr.sin_addr.s_addr=-1;
+ } else {
+ if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) {
+ g_set_error(error_r, raop_output_quark(), 0,
+ "failed to resolve host '%s'",
+ hostname);
+ return -1;
+ }
+ }
+ my_addr.sin_family = AF_INET;
+ } else {
+ my_addr.sin_family = h->h_addrtype;
+ memcpy((char *) &my_addr.sin_addr.s_addr,
+ h->h_addr_list[0], h->h_length);
+ }
+ } else {
+ // if hostname=NULL, use INADDR_ANY
+ if (ulAddr)
+ my_addr.sin_addr.s_addr = ulAddr;
+ else
+ my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ my_addr.sin_family = AF_INET;
+ }
+
+ /* bind a specified port */
+ my_addr.sin_port = htons(*port);
+
+ if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to bind socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+
+ if (*port == 0) {
+ getsockname(sd, (struct sockaddr *) &my_addr, &nlen);
+ *port = ntohs(my_addr.sin_port);
+ }
+
+ return 0;
+}
+
+/*
+ * open udp port
+ */
+static int
+open_udp_socket(char *hostname, unsigned short *port,
+ GError **error_r)
+{
+ int sd;
+ const int size = 30000;
+
+ /* socket creation */
+ sd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sd < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to create UDP socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+ if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to set UDP buffer size: %s",
+ g_strerror(errno));
+ return -1;
+ }
+ if (bind_host(sd, hostname, 0, port, error_r)) {
+ close_socket(sd);
+ return -1;
+ }
+
+ return sd;
+}
+
+static bool
+get_sockaddr_by_host(const char *host, short destport,
+ struct sockaddr_in *addr,
+ GError **error_r)
+{
+ struct hostent *h;
+
+ h = gethostbyname(host);
+ if (h) {
+ addr->sin_family = h->h_addrtype;
+ memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length);
+ } else {
+ addr->sin_family = AF_INET;
+ if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) {
+ g_set_error(error_r, rtsp_client_quark(), 0,
+ "failed to resolve host '%s'", host);
+ return false;
+ }
+ }
+ addr->sin_port = htons(destport);
+ return true;
+}
+
+/*
+ * Calculate the current NTP time, store it in the buffer.
+ */
+static void
+fill_int(unsigned char *buffer, uint32_t value)
+{
+ uint32_t be = GINT32_TO_BE(value);
+ memcpy(buffer, &be, sizeof(be));
+}
+
+/*
+ * Store time in the NTP format in the buffer
+ */
+static void
+fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout)
+{
+ unsigned long secs_to_baseline = 964697997;
+ double fraction;
+ unsigned long long_fraction;
+ unsigned long secs;
+
+ fraction = ((double) tout->tv_usec) / 1000000.0;
+ long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0);
+ secs = secs_to_baseline + tout->tv_sec;
+ fill_int(buffer, secs);
+ fill_int(buffer + 4, long_fraction);
+}
+
+static void
+get_time_for_rtp(struct play_state *state, struct timeval *tout)
+{
+ unsigned long rtp_diff = state->rtptime - state->start_rtptime;
+ unsigned long add_secs = rtp_diff / 44100;
+ unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000;
+ tout->tv_sec = state->start_time.tv_sec + add_secs;
+ tout->tv_usec = state->start_time.tv_usec + add_usecs;
+ if (tout->tv_usec >= 1000000) {
+ tout->tv_sec++;
+ tout->tv_usec = tout->tv_usec % 1000000;
+ }
+}
+
+/*
+ * Send a control command
+ */
+static bool
+send_control_command(struct control_data *ctrl, struct raop_data *rd,
+ struct play_state *state,
+ GError **error_r)
+{
+ unsigned char buf[20];
+ int diff;
+ int num_bytes;
+ struct timeval ctrl_time;
+
+ diff = 88200;
+ if (rd->started) {
+ buf[0] = 0x80;
+ diff += NUMSAMPLES;
+ } else {
+ buf[0] = 0x90;
+ state->playing = true;
+ state->start_rtptime = state->rtptime;
+ }
+ buf[1] = 0xd4;
+ buf[2] = 0x00;
+ buf[3] = 0x07;
+ fill_int(buf + 4, state->rtptime - diff);
+ get_time_for_rtp(state, &ctrl_time);
+ fill_time_buffer_with_time(buf + 8, &ctrl_time);
+ fill_int(buf + 16, state->rtptime);
+
+ num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0,
+ (struct sockaddr *)&rd->ctrl_addr,
+ sizeof(rd->ctrl_addr));
+ if (num_bytes < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "Unable to send control command: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res)
+{
+ RSA *rsa;
+ gsize usize;
+ unsigned char *modulus;
+ unsigned char *exponent;
+ int size;
+
+ char n[] =
+ "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
+ "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
+ "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
+ "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
+ "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
+ "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
+ char e[] = "AQAB";
+
+ rsa = RSA_new();
+
+ modulus = g_base64_decode(n, &usize);
+ rsa->n = BN_bin2bn(modulus, usize, NULL);
+ exponent = g_base64_decode(e, &usize);
+ rsa->e = BN_bin2bn(exponent, usize, NULL);
+ g_free(modulus);
+ g_free(exponent);
+ size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING);
+
+ RSA_free(rsa);
+ return size;
+}
+
+static int
+raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size)
+{
+ // any bytes that fall beyond the last 16 byte page should be sent
+ // in the clear
+ int alt_size = size - (size % 16);
+
+ memcpy(encryp->nv, encryp->iv, 16);
+
+ AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1);
+
+ return size;
+}
+
+/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb
+ d=data, blen=length of bits field
+*/
+static inline void
+bits_write(unsigned char **p, unsigned char d, int blen, int *bpos)
+{
+ int lb, rb, bd;
+ lb =7 - *bpos;
+ rb = lb - blen + 1;
+ if (rb >= 0) {
+ bd = d << rb;
+ if (*bpos)
+ **p |= bd;
+ else
+ **p = bd;
+ *bpos += blen;
+ } else {
+ bd = d >> -rb;
+ **p |= bd;
+ *p += 1;
+ **p = d << (8 + rb);
+ *bpos = -rb;
+ }
+}
+
+static bool
+wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize)
+{
+ unsigned char one[4];
+ int count = 0;
+ int bpos = 0;
+ unsigned char *bp = buffer;
+ int i, nodata = 0;
+ bits_write(&bp, 1, 3, &bpos); // channel=1, stereo
+ bits_write(&bp, 0, 4, &bpos); // unknown
+ bits_write(&bp, 0, 8, &bpos); // unknown
+ bits_write(&bp, 0, 4, &bpos); // unknown
+ if (bsize != 4096 && false)
+ bits_write(&bp, 1, 1, &bpos); // hassize
+ else
+ bits_write(&bp, 0, 1, &bpos); // hassize
+ bits_write(&bp, 0, 2, &bpos); // unused
+ bits_write(&bp, 1, 1, &bpos); // is-not-compressed
+ if (bsize != 4096 && false) {
+ // size of data, integer, big endian
+ bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos);
+ bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos);
+ bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos);
+ bits_write(&bp, bsize&0xff, 8, &bpos);
+ }
+ while (1) {
+ if (inSize <= count * 4) nodata = 1;
+ if (nodata) break;
+ one[0] = inData[count * 4];
+ one[1] = inData[count * 4 + 1];
+ one[2] = inData[count * 4 + 2];
+ one[3] = inData[count * 4 + 3];
+
+#if BYTE_ORDER == BIG_ENDIAN
+ bits_write(&bp, one[0], 8, &bpos);
+ bits_write(&bp, one[1], 8, &bpos);
+ bits_write(&bp, one[2], 8, &bpos);
+ bits_write(&bp, one[3], 8, &bpos);
+#else
+ bits_write(&bp, one[1], 8, &bpos);
+ bits_write(&bp, one[0], 8, &bpos);
+ bits_write(&bp, one[3], 8, &bpos);
+ bits_write(&bp, one[2], 8, &bpos);
+#endif
+
+ if (++count == bsize) break;
+ }
+ if (!count) return false; // when no data at all, it should stop playing
+ /* when readable size is less than bsize, fill 0 at the bottom */
+ for(i = 0; i < (bsize - count) * 4; i++) {
+ bits_write(&bp, 0, 8, &bpos);
+ }
+ *size = (int)(bp - buffer);
+ if (bpos) *size += 1;
+ return true;
+}
+
+static bool
+raopcl_connect(struct raop_data *rd, GError **error_r)
+{
+ unsigned char buf[4 + 8 + 16];
+ char sid[16];
+ char sci[24];
+ char act_r[17];
+ char *sac=NULL, *key = NULL, *iv = NULL;
+ char sdp[1024];
+ int rval = false;
+ unsigned char rsakey[512];
+ struct timeval current_time;
+ unsigned int sessionNum;
+ int i;
+
+
+ gettimeofday(&current_time,NULL);
+ sessionNum = current_time.tv_sec + 2082844804;
+
+ RAND_bytes(buf, sizeof(buf));
+ sprintf(act_r, "%u", (unsigned int) g_random_int());
+ sprintf(sid, "%u", sessionNum);
+ sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8)));
+ sac = g_base64_encode(buf + 12, 16);
+ rd->rtspcl = rtspcl_open();
+ rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)");
+ rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci);
+ rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci);
+ rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r);
+ if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r))
+ goto erexit;
+
+ i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey);
+ key = g_base64_encode(rsakey, i);
+ remove_char_from_string(key, '=');
+ iv = g_base64_encode(raop_session->encrypt.iv, 16);
+ remove_char_from_string(iv, '=');
+ sprintf(sdp,
+ "v=0\r\n"
+ "o=iTunes %s 0 IN IP4 %s\r\n"
+ "s=iTunes\r\n"
+ "c=IN IP4 %s\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 96\r\n"
+ "a=rtpmap:96 AppleLossless\r\n"
+ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+ "a=rsaaeskey:%s\r\n"
+ "a=aesiv:%s\r\n",
+ sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv);
+ remove_char_from_string(sac, '=');
+ // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac);
+ if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r))
+ goto erexit;
+ // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit;
+ if (!rtspcl_setup(rd->rtspcl, NULL,
+ raop_session->ctrl.port, raop_session->ntp.port,
+ error_r))
+ goto erexit;
+
+ if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port,
+ &rd->ctrl_addr, error_r))
+ goto erexit;
+
+ if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port,
+ &rd->data_addr, error_r))
+ goto erexit;
+
+ if (!rtspcl_record(rd->rtspcl,
+ raop_session->play_state.seq_num,
+ raop_session->play_state.rtptime,
+ error_r))
+ goto erexit;
+
+ rval = true;
+
+ erexit:
+ g_free(sac);
+ g_free(key);
+ g_free(iv);
+ return rval;
+}
+
+static int
+difference (struct timeval *t1, struct timeval *t2)
+{
+ int ret = 150000000;
+ if (t1->tv_sec - t2->tv_sec < 150) {
+ ret = (t1->tv_sec - t2->tv_sec) * 1000000;
+ ret += t1->tv_usec - t2->tv_usec;
+ }
+ return ret;
+}
+
+/*
+ * With airtunes version 2, we don't get responses back when we send audio
+ * data. The only requests we get from the airtunes device are timing
+ * requests.
+ */
+static bool
+send_audio_data(int fd, GError **error_r)
+{
+ int i = 0;
+ struct timeval current_time, rtp_time;
+ struct raop_data *rd = raop_session->raop_list;
+
+ get_time_for_rtp(&raop_session->play_state, &rtp_time);
+ gettimeofday(&current_time, NULL);
+ int diff = difference(&rtp_time, &current_time);
+ if (diff > 0)
+ g_usleep(diff);
+
+ gettimeofday(&raop_session->play_state.last_send, NULL);
+ while (rd) {
+ if (rd->started) {
+ raop_session->data[1] = 0x60;
+ } else {
+ rd->started = true;
+ raop_session->data[1] = 0xe0;
+ }
+ i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize),
+ raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr,
+ sizeof(rd->data_addr));
+ if (i < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "write error: %s",
+ g_strerror(errno));
+ return false;
+ }
+ if (i == 0) {
+ g_set_error_literal(error_r, raop_output_quark(), 0,
+ "disconnected on the other end");
+ return false;
+ }
+ rd = rd->next;
+ }
+ raop_session->wblk_wsize += i;
+ raop_session->wblk_remsize -= i;
+
+ return true;
+}
+
+static struct audio_output *
+raop_output_init(const struct config_param *param, GError **error_r)
+{
+ const char *host = config_get_block_string(param, "host", NULL);
+ if (host == NULL) {
+ g_set_error_literal(error_r, raop_output_quark(), 0,
+ "missing option 'host'");
+ return NULL;
+ }
+
+ struct raop_data *rd;
+
+ rd = new_raop_data(param, error_r);
+ if (rd == NULL)
+ return NULL;
+
+ rd->addr = host;
+ rd->rtsp_port = config_get_block_unsigned(param, "port", 5000);
+ rd->volume = config_get_block_unsigned(param, "volume", 75);
+ return &rd->base;
+}
+
+static bool
+raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r)
+{
+ char vol_str[128];
+ sprintf(vol_str, "volume: %d.000000\r\n", volume);
+ return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r);
+}
+
+
+static void
+raop_output_finish(struct audio_output *ao)
+{
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ if (rd->rtspcl)
+ rtspcl_close(rd->rtspcl);
+
+ g_mutex_free(rd->control_mutex);
+ ao_base_finish(&rd->base);
+ g_free(rd);
+
+ if (raop_session->raop_list == NULL) {
+ raop_session_free(raop_session);
+ raop_session = NULL;
+ }
+}
+
+#define RAOP_VOLUME_MIN -30
+#define RAOP_VOLUME_MAX 0
+
+int
+raop_get_volume(struct raop_data *rd)
+{
+ return rd->volume;
+}
+
+bool
+raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r)
+{
+ int raop_volume;
+ bool rval;
+
+ //set parameter volume
+ if (volume == 0) {
+ raop_volume = -144;
+ } else {
+ raop_volume = RAOP_VOLUME_MIN +
+ (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100;
+ }
+ g_mutex_lock(rd->control_mutex);
+ rval = raop_set_volume_local(rd, raop_volume, error_r);
+ if (rval) rd->volume = volume;
+ g_mutex_unlock(rd->control_mutex);
+
+ return rval;
+}
+
+static void
+raop_output_cancel(struct audio_output *ao)
+{
+ //flush
+ struct key_data kd;
+ struct raop_data *rd = (struct raop_data *)ao;
+ int flush_diff = 1;
+
+ rd->started = 0;
+ if (rd->is_master) {
+ raop_session->play_state.playing = false;
+ }
+ if (rd->paused) {
+ return;
+ }
+
+ g_mutex_lock(rd->control_mutex);
+ static char rtp_key[] = "RTP-Info";
+ kd.key = rtp_key;
+ char buf[128];
+ sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff);
+ kd.data = buf;
+ kd.next = NULL;
+ exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1,
+ &kd, NULL, NULL);
+ g_mutex_unlock(rd->control_mutex);
+}
+
+static bool
+raop_output_pause(struct audio_output *ao)
+{
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ rd->paused = true;
+ return true;
+}
+
+/**
+ * Remove the output from the session's list. Caller must not lock
+ * the list_mutex.
+ */
+static void
+raop_output_remove(struct raop_data *rd)
+{
+ struct raop_data *iter = raop_session->raop_list;
+ struct raop_data *prev = NULL;
+
+ g_mutex_lock(raop_session->list_mutex);
+ while (iter) {
+ if (iter == rd) {
+ if (prev != NULL) {
+ prev->next = rd->next;
+ } else {
+ raop_session->raop_list = rd->next;
+ }
+ if (rd->is_master && raop_session->raop_list != NULL) {
+ raop_session->raop_list->is_master = true;
+ }
+ rd->next = NULL;
+ rd->is_master = false;
+ break;
+ }
+ prev = iter;
+ iter = iter->next;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+
+ if (raop_session->raop_list == NULL) {
+ ntp_server_close(&raop_session->ntp);
+ close(raop_session->ctrl.fd);
+ raop_session->ctrl.fd = -1;
+ }
+}
+
+static void
+raop_output_close(struct audio_output *ao)
+{
+ //teardown
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ raop_output_remove(rd);
+
+ g_mutex_lock(rd->control_mutex);
+ exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0,
+ NULL, NULL, NULL);
+ g_mutex_unlock(rd->control_mutex);
+
+ rd->started = 0;
+}
+
+
+static bool
+raop_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r)
+{
+ //setup, etc.
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ g_mutex_lock(raop_session->list_mutex);
+ if (raop_session->raop_list == NULL) {
+ // first raop, need to initialize session data
+ unsigned short myport = 0;
+ raop_session->raop_list = rd;
+ rd->is_master = true;
+
+ raop_session->data_fd = open_udp_socket(NULL, &myport,
+ error_r);
+ if (raop_session->data_fd < 0)
+ return false;
+
+ if (!ntp_server_open(&raop_session->ntp, error_r))
+ return false;
+
+ raop_session->ctrl.fd =
+ open_udp_socket(NULL, &raop_session->ctrl.port,
+ error_r);
+ if (raop_session->ctrl.fd < 0) {
+ ntp_server_close(&raop_session->ntp);
+ raop_session->ctrl.fd = -1;
+ g_mutex_unlock(raop_session->list_mutex);
+ return false;
+ }
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+
+ audio_format->format = SAMPLE_FORMAT_S16;
+ if (!raopcl_connect(rd, error_r)) {
+ raop_output_remove(rd);
+ return false;
+ }
+
+ if (!raop_set_volume(rd, rd->volume, error_r)) {
+ raop_output_remove(rd);
+ return false;
+ }
+
+ g_mutex_lock(raop_session->list_mutex);
+ if (!rd->is_master) {
+ rd->next = raop_session->raop_list;
+ raop_session->raop_list = rd;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+ return true;
+}
+
+static size_t
+raop_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ //raopcl_send_sample
+ struct raop_data *rd = (struct raop_data *)ao;
+ size_t rval = 0, orig_size = size;
+
+ rd->paused = false;
+ if (!rd->is_master) {
+ // only process data for the master raop
+ return size;
+ }
+
+ g_mutex_lock(raop_session->data_mutex);
+
+ if (raop_session->play_state.rtptime <= NUMSAMPLES) {
+ // looped over, need new reference point to calculate correct times
+ raop_session->play_state.playing = false;
+ }
+
+ while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) {
+ // ntp header
+ unsigned char header[] = {
+ 0x80, 0x60, 0x00, 0x00,
+ // rtptime
+ 0x00, 0x00, 0x00, 0x00,
+ // device
+ 0x7e, 0xad, 0xd2, 0xd3,
+ };
+
+
+ int count = 0;
+ int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize;
+
+ if (!raop_session->play_state.playing ||
+ raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) {
+ struct raop_data *iter;
+ g_mutex_lock(raop_session->list_mutex);
+ if (!raop_session->play_state.playing) {
+ gettimeofday(&raop_session->play_state.start_time,NULL);
+ }
+ iter = raop_session->raop_list;
+ while (iter) {
+ if (!send_control_command(&raop_session->ctrl, iter,
+ &raop_session->play_state,
+ error_r))
+ goto erexit;
+
+ iter = iter->next;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+ }
+
+ fill_int(header + 8, raop_session->play_state.sync_src);
+
+ memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes);
+ raop_session->bufferSize += copyBytes;
+ chunk = ((const char *)chunk) + copyBytes;
+ size -= copyBytes;
+
+ if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) {
+ g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE);
+ }
+
+ memcpy(raop_session->data, header, RAOP_HEADER_SIZE);
+ raop_session->data[2] = raop_session->play_state.seq_num >> 8;
+ raop_session->data[3] = raop_session->play_state.seq_num & 0xff;
+ raop_session->play_state.seq_num ++;
+
+ fill_int(raop_session->data + 4, raop_session->play_state.rtptime);
+ raop_session->play_state.rtptime += NUMSAMPLES;
+
+ raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count);
+ raop_session->wblk_remsize = count + RAOP_HEADER_SIZE;
+ raop_session->wblk_wsize = 0;
+
+ if (!send_audio_data(raop_session->data_fd, error_r))
+ goto erexit;
+
+ raop_session->bufferSize = 0;
+ }
+ if (size > 0) {
+ memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size);
+ raop_session->bufferSize += size;
+ }
+ rval = orig_size;
+ erexit:
+ g_mutex_unlock(raop_session->data_mutex);
+ return rval;
+}
+
+const struct audio_output_plugin raop_output_plugin = {
+ .name = "raop",
+ .init = raop_output_init,
+ .finish = raop_output_finish,
+ .open = raop_output_open,
+ .play = raop_output_play,
+ .cancel = raop_output_cancel,
+ .pause = raop_output_pause,
+ .close = raop_output_close,
+ .mixer_plugin = &raop_mixer_plugin,
+};
diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h
new file mode 100644
index 000000000..6aca97836
--- /dev/null
+++ b/src/output/raop_output_plugin.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RAOP_OUTPUT_PLUGIN_H
+#define MPD_RAOP_OUTPUT_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+struct raop_data;
+
+extern const struct audio_output_plugin raop_output_plugin;
+
+bool
+raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r);
+
+int
+raop_get_volume(struct raop_data *rd);
+
+#endif
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
index 2f088a107..ea299468b 100644
--- a/src/output/recorder_output_plugin.c
+++ b/src/output/recorder_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "recorder_output_plugin.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -34,6 +35,8 @@
#define G_LOG_DOMAIN "recorder"
struct recorder_output {
+ struct audio_output base;
+
/**
* The configured encoder plugin.
*/
@@ -64,11 +67,16 @@ 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)
+static struct audio_output *
+recorder_output_init(const struct config_param *param, GError **error_r)
{
struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ if (!ao_base_init(&recorder->base, &recorder_output_plugin, param,
+ error_r)) {
+ g_free(recorder);
+ return NULL;
+ }
+
const char *encoder_name;
const struct encoder_plugin *encoder_plugin;
@@ -95,19 +103,21 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (recorder->encoder == NULL)
goto failure;
- return recorder;
+ return &recorder->base;
failure:
+ ao_base_finish(&recorder->base);
g_free(recorder);
return NULL;
}
static void
-recorder_output_finish(void *data)
+recorder_output_finish(struct audio_output *ao)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
encoder_finish(recorder->encoder);
+ ao_base_finish(&recorder->base);
g_free(recorder);
}
@@ -154,10 +164,11 @@ recorder_output_encoder_to_file(struct recorder_output *recorder,
}
static bool
-recorder_output_open(void *data, struct audio_format *audio_format,
+recorder_output_open(struct audio_output *ao,
+ struct audio_format *audio_format,
GError **error_r)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
bool success;
/* create the output file */
@@ -185,9 +196,9 @@ recorder_output_open(void *data, struct audio_format *audio_format,
}
static void
-recorder_output_close(void *data)
+recorder_output_close(struct audio_output *ao)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
/* flush the encoder and write the rest to the file */
@@ -202,10 +213,10 @@ recorder_output_close(void *data)
}
static size_t
-recorder_output_play(void *data, const void *chunk, size_t size,
+recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error_r)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
return encoder_write(recorder->encoder, chunk, size, error_r) &&
recorder_output_encoder_to_file(recorder, error_r)
diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h
new file mode 100644
index 000000000..a9bf755bd
--- /dev/null
+++ b/src/output/recorder_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RECORDER_OUTPUT_PLUGIN_H
+#define MPD_RECORDER_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c
new file mode 100644
index 000000000..1c2c48321
--- /dev/null
+++ b/src/output/roar_output_plugin.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "roar_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "roar_output_plugin.h"
+
+#include <glib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <roaraudio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "roaraudio"
+
+typedef struct roar
+{
+ struct audio_output base;
+
+ roar_vs_t * vss;
+ int err;
+ char *host;
+ char *name;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ GMutex *lock;
+ volatile bool alive;
+} roar_t;
+
+static inline GQuark
+roar_output_quark(void)
+{
+ return g_quark_from_static_string("roar_output");
+}
+
+static int
+roar_output_get_volume_locked(struct roar *roar)
+{
+ if (roar->vss == NULL || !roar->alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(struct roar *roar)
+{
+ g_mutex_lock(roar->lock);
+ int volume = roar_output_get_volume_locked(roar);
+ g_mutex_unlock(roar->lock);
+ return volume;
+}
+
+static bool
+roar_output_set_volume_locked(struct roar *roar, unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (roar->vss == NULL || !roar->alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(roar->vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(struct roar *roar, unsigned volume)
+{
+ g_mutex_lock(roar->lock);
+ bool success = roar_output_set_volume_locked(roar, volume);
+ g_mutex_unlock(roar->lock);
+ return success;
+}
+
+static void
+roar_configure(struct roar * self, const struct config_param *param)
+{
+ self->host = config_dup_block_string(param, "server", NULL);
+ self->name = config_dup_block_string(param, "name", "MPD");
+
+ const char *role = config_get_block_string(param, "role", "music");
+ self->role = role != NULL
+ ? roar_str2role(role)
+ : ROAR_ROLE_MUSIC;
+}
+
+static struct audio_output *
+roar_init(const struct config_param *param, GError **error_r)
+{
+ struct roar *self = g_new0(struct roar, 1);
+
+ if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) {
+ g_free(self);
+ return NULL;
+ }
+
+ self->lock = g_mutex_new();
+ self->err = ROAR_ERROR_NONE;
+ roar_configure(self, param);
+ return &self->base;
+}
+
+static void
+roar_finish(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+
+ g_free(self->host);
+ g_free(self->name);
+ g_mutex_free(self->lock);
+
+ ao_base_finish(&self->base);
+ g_free(self);
+}
+
+static void
+roar_use_audio_format(struct roar_audio_info *info,
+ struct audio_format *audio_format)
+{
+ info->rate = audio_format->sample_rate;
+ info->channels = audio_format->channels;
+ info->codec = ROAR_CODEC_PCM_S;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ info->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ info->bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ info->bits = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ info->bits = 32;
+ audio_format->format = SAMPLE_FORMAT_S32;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ info->bits = 32;
+ break;
+ }
+}
+
+static bool
+roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct roar *self = (struct roar *)ao;
+ g_mutex_lock(self->lock);
+
+ if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to Roar server");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+
+ self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
+
+ if (self->vss == NULL || self->err != ROAR_ERROR_NONE)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to server");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+
+ roar_use_audio_format(&self->info, audio_format);
+
+ if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to start stream");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+ roar_vs_role(self->vss, self->role, &(self->err));
+ self->alive = true;
+
+ g_mutex_unlock(self->lock);
+ return true;
+}
+
+static void
+roar_close(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+ g_mutex_lock(self->lock);
+ self->alive = false;
+
+ if (self->vss != NULL)
+ roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
+ self->vss = NULL;
+ roar_disconnect(&(self->con));
+ g_mutex_unlock(self->lock);
+}
+
+static void
+roar_cancel_locked(struct roar *self)
+{
+ if (self->vss == NULL)
+ return;
+
+ roar_vs_t *vss = self->vss;
+ self->vss = NULL;
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ self->alive = false;
+
+ vss = roar_vs_new_from_con(&(self->con), &(self->err));
+ if (vss == NULL)
+ return;
+
+ if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0) {
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ g_warning("Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(vss, self->role, &(self->err));
+ self->vss = vss;
+ self->alive = true;
+}
+
+static void
+roar_cancel(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+
+ g_mutex_lock(self->lock);
+ roar_cancel_locked(self);
+ g_mutex_unlock(self->lock);
+}
+
+static size_t
+roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
+{
+ struct roar *self = (struct roar *)ao;
+ ssize_t rc;
+
+ if (self->vss == NULL)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Connection is invalid");
+ return 0;
+ }
+
+ rc = roar_vs_write(self->vss, chunk, size, &(self->err));
+ if ( rc <= 0 )
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to play data");
+ return 0;
+ }
+
+ return rc;
+}
+
+static const char*
+roar_tag_convert(enum tag_type type, bool *is_uuid)
+{
+ *is_uuid = false;
+ switch (type)
+ {
+ case TAG_ARTIST:
+ case TAG_ALBUM_ARTIST:
+ return "AUTHOR";
+ case TAG_ALBUM:
+ return "ALBUM";
+ case TAG_TITLE:
+ return "TITLE";
+ case TAG_TRACK:
+ return "TRACK";
+ case TAG_NAME:
+ return "NAME";
+ case TAG_GENRE:
+ return "GENRE";
+ case TAG_DATE:
+ return "DATE";
+ case TAG_PERFORMER:
+ return "PERFORMER";
+ case TAG_COMMENT:
+ return "COMMENT";
+ case TAG_DISC:
+ return "DISCID";
+ case TAG_COMPOSER:
+#ifdef ROAR_META_TYPE_COMPOSER
+ return "COMPOSER";
+#else
+ return "AUTHOR";
+#endif
+ case TAG_MUSICBRAINZ_ARTISTID:
+ case TAG_MUSICBRAINZ_ALBUMID:
+ case TAG_MUSICBRAINZ_ALBUMARTISTID:
+ case TAG_MUSICBRAINZ_TRACKID:
+ *is_uuid = true;
+ return "HASH";
+
+ default:
+ return NULL;
+ }
+}
+
+static void
+roar_send_tag(struct audio_output *ao, const struct tag *meta)
+{
+ struct roar *self = (struct roar *)ao;
+
+ if (self->vss == NULL)
+ return;
+
+ g_mutex_lock(self->lock);
+ size_t cnt = 1;
+ struct roar_keyval vals[32];
+ memset(vals, 0, sizeof(vals));
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
+
+ vals[0].key = g_strdup("LENGTH");
+ vals[0].value = timebuf;
+
+ for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
+ {
+ bool is_uuid = false;
+ const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
+ if (key != NULL)
+ {
+ if (is_uuid)
+ {
+ snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+ meta->items[i]->value);
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = uuid_buf[cnt];
+ }
+ else
+ {
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = meta->items[i]->value;
+ }
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(self->vss, vals, cnt, &(self->err));
+
+ for (unsigned i = 0; i < 32; i++)
+ g_free(vals[i].key);
+
+ g_mutex_unlock(self->lock);
+}
+
+const struct audio_output_plugin roar_output_plugin = {
+ .name = "roar",
+ .init = roar_init,
+ .finish = roar_finish,
+ .open = roar_open,
+ .play = roar_play,
+ .cancel = roar_cancel,
+ .close = roar_close,
+ .send_tag = roar_send_tag,
+
+ .mixer_plugin = &roar_mixer_plugin
+};
diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h
new file mode 100644
index 000000000..78b628cc4
--- /dev/null
+++ b/src/output/roar_output_plugin.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+#include <stdbool.h>
+
+struct roar;
+
+extern const struct audio_output_plugin roar_output_plugin;
+
+int
+roar_output_get_volume(struct roar *roar);
+
+bool
+roar_output_set_volume(struct roar *roar, unsigned volume);
+
+#endif
diff --git a/src/output/shout_plugin.c b/src/output/shout_output_plugin.c
index 27ef3b993..7867ae63c 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "shout_output_plugin.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -41,6 +42,8 @@ struct shout_buffer {
};
struct shout_data {
+ struct audio_output base;
+
shout_t *shout_conn;
shout_metadata_t *shout_meta;
@@ -107,9 +110,8 @@ static void free_shout_data(struct shout_data *sd)
} \
}
-static void *
-my_shout_init_driver(const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+my_shout_init_driver(const struct config_param *param,
GError **error)
{
struct shout_data *sd;
@@ -125,18 +127,26 @@ my_shout_init_driver(const struct audio_format *audio_format,
const char *user;
char *name;
const char *value;
- struct block_param *block_param;
+ const struct block_param *block_param;
int public;
- if (audio_format == NULL ||
- !audio_format_fully_defined(audio_format)) {
+ sd = new_shout_data();
+
+ if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) {
+ free_shout_data(sd);
+ return NULL;
+ }
+
+ const struct audio_format *audio_format =
+ &sd->base.config_audio_format;
+ if (!audio_format_fully_defined(audio_format)) {
g_set_error(error, shout_output_quark(), 0,
"Need full audio format specification");
+ ao_base_finish(&sd->base);
+ free_shout_data(sd);
return NULL;
}
- sd = new_shout_data();
-
if (shout_init_count == 0)
shout_init();
@@ -277,6 +287,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
goto failure;
}
+ value = config_get_block_string(param, "url", NULL);
+ if (value != NULL && shout_set_url(sd->shout_conn, value)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "%s", shout_get_error(sd->shout_conn));
+ goto failure;
+ }
+
{
char temp[11];
memset(temp, 0, sizeof(temp));
@@ -299,9 +316,10 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- return sd;
+ return &sd->base;
failure:
+ ao_base_finish(&sd->base);
free_shout_data(sd);
return NULL;
}
@@ -371,12 +389,14 @@ static void close_shout_conn(struct shout_data * sd)
}
}
-static void my_shout_finish_driver(void *data)
+static void
+my_shout_finish_driver(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
encoder_finish(sd->encoder);
+ ao_base_finish(&sd->base);
free_shout_data(sd);
shout_init_count--;
@@ -385,17 +405,19 @@ static void my_shout_finish_driver(void *data)
shout_shutdown();
}
-static void my_shout_drop_buffered_audio(void *data)
+static void
+my_shout_drop_buffered_audio(struct audio_output *ao)
{
G_GNUC_UNUSED
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
/* needs to be implemented for shout */
}
-static void my_shout_close_device(void *data)
+static void
+my_shout_close_device(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
close_shout_conn(sd);
}
@@ -422,10 +444,10 @@ shout_connect(struct shout_data *sd, GError **error)
}
static bool
-my_shout_open_device(void *data, struct audio_format *audio_format,
+my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
bool ret;
ret = shout_connect(sd, error);
@@ -445,9 +467,9 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
}
static unsigned
-my_shout_delay(void *data)
+my_shout_delay(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
int delay = shout_delay(sd->shout_conn);
if (delay < 0)
@@ -457,9 +479,10 @@ my_shout_delay(void *data)
}
static size_t
-my_shout_play(void *data, const void *chunk, size_t size, GError **error)
+my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
return encoder_write(sd->encoder, chunk, size, error) &&
write_page(sd, error)
@@ -468,11 +491,11 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
}
static bool
-my_shout_pause(void *data)
+my_shout_pause(struct audio_output *ao)
{
static const char silence[1020];
- return my_shout_play(data, silence, sizeof(silence), NULL);
+ return my_shout_play(ao, silence, sizeof(silence), NULL);
}
static void
@@ -501,10 +524,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
snprintf(dest, size, "%s - %s", artist, title);
}
-static void my_shout_set_tag(void *data,
+static void my_shout_set_tag(struct audio_output *ao,
const struct tag *tag)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
bool ret;
GError *error = NULL;
@@ -543,7 +566,7 @@ static void my_shout_set_tag(void *data,
write_page(sd, NULL);
}
-const struct audio_output_plugin shoutPlugin = {
+const struct audio_output_plugin shout_output_plugin = {
.name = "shout",
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h
new file mode 100644
index 000000000..9a7378803
--- /dev/null
+++ b/src/output/shout_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SHOUT_OUTPUT_PLUGIN_H
+#define MPD_SHOUT_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin shout_output_plugin;
+
+#endif
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 6f6f507b3..ce726009a 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "solaris_output_plugin.h"
#include "output_api.h"
#include "fd_util.h"
@@ -53,6 +54,8 @@ struct audio_info {
#define G_LOG_DOMAIN "solaris_output"
struct solaris_output {
+ struct audio_output base;
+
/* configuration */
const char *device;
@@ -77,31 +80,35 @@ solaris_output_test_default_device(void)
access("/dev/audio", W_OK) == 0;
}
-static void *
-solaris_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+solaris_output_init(const struct config_param *param, GError **error_r)
{
struct solaris_output *so = g_new(struct solaris_output, 1);
+ if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) {
+ g_free(so);
+ return NULL;
+ }
+
so->device = config_get_block_string(param, "device", "/dev/audio");
- return so;
+ return &so->base;
}
static void
-solaris_output_finish(void *data)
+solaris_output_finish(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
+ ao_base_finish(&so->base);
g_free(so);
}
static bool
-solaris_output_open(void *data, struct audio_format *audio_format,
+solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
struct audio_info info;
int ret, flags;
@@ -152,17 +159,18 @@ solaris_output_open(void *data, struct audio_format *audio_format,
}
static void
-solaris_output_close(void *data)
+solaris_output_close(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
close(so->fd);
}
static size_t
-solaris_output_play(void *data, const void *chunk, size_t size, GError **error)
+solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
ssize_t nbytes;
nbytes = write(so->fd, chunk, size);
@@ -176,9 +184,9 @@ solaris_output_play(void *data, const void *chunk, size_t size, GError **error)
}
static void
-solaris_output_cancel(void *data)
+solaris_output_cancel(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
ioctl(so->fd, I_FLUSH);
}
diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h
new file mode 100644
index 000000000..600aea8c2
--- /dev/null
+++ b/src/output/solaris_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_H
+#define MPD_SOLARIS_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
index 4312c635e..4d95834b9 100644
--- a/src/output/winmm_output_plugin.c
+++ b/src/output/winmm_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "winmm_output_plugin.h"
#include "output_api.h"
#include "pcm_buffer.h"
#include "mixer_list.h"
@@ -37,6 +38,8 @@ struct winmm_buffer {
};
struct winmm_output {
+ struct audio_output base;
+
UINT device_id;
HWAVEOUT handle;
@@ -71,59 +74,80 @@ winmm_output_test_default_device(void)
return waveOutGetNumDevs() > 0;
}
-static UINT
-get_device_id(const char *device_name)
+static bool
+get_device_id(const char *device_name, UINT *device_id, GError **error_r)
{
/* if device is not specified use wave mapper */
- if (device_name == NULL)
- return WAVE_MAPPER;
+ if (device_name == NULL) {
+ *device_id = WAVE_MAPPER;
+ return true;
+ }
+
+ UINT numdevs = waveOutGetNumDevs();
/* check for device id */
char *endptr;
UINT id = strtoul(device_name, &endptr, 0);
- if (endptr > device_name && *endptr == 0)
- return id;
+ if (endptr > device_name && *endptr == 0) {
+ if (id >= numdevs)
+ goto fail;
+ *device_id = id;
+ return true;
+ }
/* check for device name */
- for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
+ for (UINT i = 0; i < numdevs; i++) {
WAVEOUTCAPS caps;
MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
if (result != MMSYSERR_NOERROR)
continue;
/* szPname is only 32 chars long, so it is often truncated.
Use partial match to work around this. */
- if (strstr(device_name, caps.szPname) == device_name)
- return i;
+ if (strstr(device_name, caps.szPname) == device_name) {
+ *device_id = i;
+ return true;
+ }
}
- /* fallback to wave mapper */
- return WAVE_MAPPER;
+fail:
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "device \"%s\" is not found", device_name);
+ return false;
}
-static void *
-winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+winmm_output_init(const struct config_param *param, GError **error_r)
{
struct winmm_output *wo = g_new(struct winmm_output, 1);
+ if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) {
+ g_free(wo);
+ return NULL;
+ }
+
const char *device = config_get_block_string(param, "device", NULL);
- wo->device_id = get_device_id(device);
- return wo;
+ if (!get_device_id(device, &wo->device_id, error_r)) {
+ ao_base_finish(&wo->base);
+ g_free(wo);
+ return NULL;
+ }
+
+ return &wo->base;
}
static void
-winmm_output_finish(void *data)
+winmm_output_finish(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
+ ao_base_finish(&wo->base);
g_free(wo);
}
static bool
-winmm_output_open(void *data, struct audio_format *audio_format,
+winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error_r)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
wo->event = CreateEvent(NULL, false, false, NULL);
if (wo->event == NULL) {
@@ -137,7 +161,6 @@ winmm_output_open(void *data, struct audio_format *audio_format,
case SAMPLE_FORMAT_S16:
break;
- case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
case SAMPLE_FORMAT_UNDEFINED:
@@ -179,9 +202,9 @@ winmm_output_open(void *data, struct audio_format *audio_format,
}
static void
-winmm_output_close(void *data)
+winmm_output_close(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
pcm_buffer_deinit(&wo->buffers[i].buffer);
@@ -248,9 +271,9 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
}
static size_t
-winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
/* get the next buffer from the ring and prepare it */
struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
@@ -303,18 +326,18 @@ winmm_stop(struct winmm_output *wo)
}
static void
-winmm_output_drain(void *data)
+winmm_output_drain(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
if (!winmm_drain_all_buffers(wo, NULL))
winmm_stop(wo);
}
static void
-winmm_output_cancel(void *data)
+winmm_output_cancel(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
winmm_stop(wo);
}
diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h
index 39507275a..0605530e1 100644
--- a/src/output/winmm_output_plugin.h
+++ b/src/output/winmm_output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,18 @@
#ifndef MPD_WINMM_OUTPUT_PLUGIN_H
#define MPD_WINMM_OUTPUT_PLUGIN_H
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
#include <windows.h>
struct winmm_output;
+extern const struct audio_output_plugin winmm_output_plugin;
+
HWAVEOUT winmm_output_get_handle(struct winmm_output*);
#endif
+
+#endif