diff options
Diffstat (limited to 'src/output')
40 files changed, 2305 insertions, 860 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_output_plugin.c index ae06847c2..d8b184273 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 = pcm_export_frame_size(&ad->export, audio_format); 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,36 @@ 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); - size /= ad->frame_size; + chunk = pcm_export(&ad->export, chunk, size, &size); + + assert(size % ad->out_frame_size == 0); + + size /= ad->out_frame_size; while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); if (ret > 0) { ad->period_position = (ad->period_position + ret) % ad->period_frames; - return ret * ad->frame_size; + + size_t bytes_written = ret * ad->out_frame_size; + return pcm_export_source_size(&ad->export, + bytes_written); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && @@ -671,11 +802,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/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 |