From 5e22fe488ed7209c6e470e542826da4674e93338 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 17 Sep 2011 08:54:50 +0200 Subject: output: rename plugin source files --- Makefile.am | 18 +- src/output/alsa_output_plugin.c | 683 +++++++++++++++++++++++++++++++++++++ src/output/alsa_plugin.c | 683 ------------------------------------- src/output/ao_output_plugin.c | 252 ++++++++++++++ src/output/ao_plugin.c | 252 -------------- src/output/mvp_output_plugin.c | 334 +++++++++++++++++++ src/output/mvp_plugin.c | 334 ------------------- src/output/null_output_plugin.c | 119 +++++++ src/output/null_plugin.c | 119 ------- src/output/openal_output_plugin.c | 277 +++++++++++++++ src/output/openal_plugin.c | 277 --------------- src/output/oss_output_plugin.c | 684 ++++++++++++++++++++++++++++++++++++++ src/output/oss_plugin.c | 684 -------------------------------------- src/output/osx_output_plugin.c | 429 ++++++++++++++++++++++++ src/output/osx_plugin.c | 429 ------------------------ src/output/roar_output_plugin.c | 324 ++++++++++++++++++ src/output/roar_plugin.c | 324 ------------------ src/output/shout_output_plugin.c | 564 +++++++++++++++++++++++++++++++ src/output/shout_plugin.c | 564 ------------------------------- 19 files changed, 3675 insertions(+), 3675 deletions(-) create mode 100644 src/output/alsa_output_plugin.c delete mode 100644 src/output/alsa_plugin.c create mode 100644 src/output/ao_output_plugin.c delete mode 100644 src/output/ao_plugin.c create mode 100644 src/output/mvp_output_plugin.c delete mode 100644 src/output/mvp_plugin.c create mode 100644 src/output/null_output_plugin.c delete mode 100644 src/output/null_plugin.c create mode 100644 src/output/openal_output_plugin.c delete mode 100644 src/output/openal_plugin.c create mode 100644 src/output/oss_output_plugin.c delete mode 100644 src/output/oss_plugin.c create mode 100644 src/output/osx_output_plugin.c delete mode 100644 src/output/osx_plugin.c create mode 100644 src/output/roar_output_plugin.c delete mode 100644 src/output/roar_plugin.c create mode 100644 src/output/shout_output_plugin.c delete mode 100644 src/output/shout_plugin.c diff --git a/Makefile.am b/Makefile.am index 41b905be9..c07262d9a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -723,7 +723,7 @@ OUTPUT_API_SRC = \ src/output_init.c OUTPUT_SRC = \ - src/output/null_plugin.c + src/output/null_output_plugin.c MIXER_API_SRC = \ src/mixer_control.c \ @@ -735,12 +735,12 @@ MIXER_SRC = \ src/mixer/software_mixer_plugin.c if HAVE_ALSA -OUTPUT_SRC += src/output/alsa_plugin.c +OUTPUT_SRC += src/output/alsa_output_plugin.c MIXER_SRC += src/mixer/alsa_mixer_plugin.c endif if HAVE_ROAR -OUTPUT_SRC += src/output/roar_plugin.c +OUTPUT_SRC += src/output/roar_output_plugin.c MIXER_SRC += src/mixer/roar_mixer_plugin.c endif @@ -749,7 +749,7 @@ OUTPUT_SRC += src/output/ffado_output_plugin.c endif if HAVE_AO -OUTPUT_SRC += src/output/ao_plugin.c +OUTPUT_SRC += src/output/ao_output_plugin.c endif if HAVE_FIFO @@ -765,20 +765,20 @@ OUTPUT_SRC += src/output/jack_output_plugin.c endif if HAVE_MVP -OUTPUT_SRC += src/output/mvp_plugin.c +OUTPUT_SRC += src/output/mvp_output_plugin.c endif if HAVE_OSS -OUTPUT_SRC += src/output/oss_plugin.c +OUTPUT_SRC += src/output/oss_output_plugin.c MIXER_SRC += src/mixer/oss_mixer_plugin.c endif if HAVE_OPENAL -OUTPUT_SRC += src/output/openal_plugin.c +OUTPUT_SRC += src/output/openal_output_plugin.c endif if HAVE_OSX -OUTPUT_SRC += src/output/osx_plugin.c +OUTPUT_SRC += src/output/osx_output_plugin.c endif if ENABLE_RAOP_OUTPUT @@ -796,7 +796,7 @@ MIXER_SRC += src/mixer/pulse_mixer_plugin.c endif if HAVE_SHOUT -OUTPUT_SRC += src/output/shout_plugin.c +OUTPUT_SRC += src/output/shout_output_plugin.c endif if ENABLE_RECORDER_OUTPUT diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c new file mode 100644 index 000000000..0bbe231fd --- /dev/null +++ b/src/output/alsa_output_plugin.c @@ -0,0 +1,683 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "mixer_list.h" + +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "alsa" + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +static const char default_device[] = "default"; + +enum { + MPD_ALSA_BUFFER_TIME_US = 500000, +}; + +#define MPD_ALSA_RETRY_NR 5 + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +struct alsa_data { + /** the configured name of the ALSA device; NULL for the + default device */ + char *device; + + /** use memory mapped I/O? */ + bool use_mmap; + + /** libasound's buffer_time setting (in microseconds) */ + unsigned int buffer_time; + + /** libasound's period_time setting (in microseconds) */ + unsigned int period_time; + + /** the mode flags passed to snd_pcm_open */ + int mode; + + /** the libasound PCM device handle */ + snd_pcm_t *pcm; + + /** + * a pointer to the libasound writei() function, which is + * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the + * use_mmap configuration + */ + alsa_writei_t *writei; + + /** the size of one audio frame */ + size_t frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +alsa_output_quark(void) +{ + return g_quark_from_static_string("alsa_output"); +} + +static const char * +alsa_device(const struct alsa_data *ad) +{ + return ad->device != NULL ? ad->device : default_device; +} + +static struct alsa_data * +alsa_data_new(void) +{ + struct alsa_data *ret = g_new(struct alsa_data, 1); + + ret->mode = 0; + ret->writei = snd_pcm_writei; + + return ret; +} + +static void +alsa_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->buffer_time = config_get_block_unsigned(param, "buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = config_get_block_unsigned(param, "period_time", 0); + +#ifdef SND_PCM_NO_AUTO_RESAMPLE + if (!config_get_block_bool(param, "auto_resample", true)) + ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; +#endif + +#ifdef SND_PCM_NO_AUTO_CHANNELS + if (!config_get_block_bool(param, "auto_channels", true)) + ad->mode |= SND_PCM_NO_AUTO_CHANNELS; +#endif + +#ifdef SND_PCM_NO_AUTO_FORMAT + if (!config_get_block_bool(param, "auto_format", true)) + ad->mode |= SND_PCM_NO_AUTO_FORMAT; +#endif +} + +static void * +alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct alsa_data *ad = alsa_data_new(); + + alsa_configure(ad, param); + + return ad; +} + +static void +alsa_finish(void *data) +{ + struct alsa_data *ad = data; + + alsa_data_free(ad); + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +static bool +alsa_test_default_device(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, default_device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (ret) { + g_message("Error opening default ALSA device: %s\n", + snd_strerror(-ret)); + return false; + } else + snd_pcm_close(handle); + + return true; +} + +static snd_pcm_format_t +get_bitformat(enum sample_format sample_format) +{ + switch (sample_format) { + case SAMPLE_FORMAT_S8: + return SND_PCM_FORMAT_S8; + + case SAMPLE_FORMAT_S16: + return SND_PCM_FORMAT_S16; + + case SAMPLE_FORMAT_S24_P32: + return SND_PCM_FORMAT_S24; + + case SAMPLE_FORMAT_S24: + return G_BYTE_ORDER == G_BIG_ENDIAN + ? SND_PCM_FORMAT_S24_3BE + : SND_PCM_FORMAT_S24_3LE; + + case SAMPLE_FORMAT_S32: + return SND_PCM_FORMAT_S32; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + + case SND_PCM_FORMAT_S24_3BE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_3LE: + return SND_PCM_FORMAT_S24_3BE; + + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + +/** + * Attempts to configure the specified sample format. + */ +static int +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) + audio_format->format = sample_format; + + return err; +} + +/** + * Attempts to configure the specified sample format with reversed + * host byte order. + */ +static int +alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = + byteswap_bitformat(get_bitformat(sample_format)); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) { + audio_format->format = sample_format; + audio_format->reverse_endian = true; + } + + return err; +} + +/** + * Attempts to configure the specified sample format, and tries the + * reversed host byte order if was not supported. + */ +static int +alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + int err = alsa_output_try_format(pcm, hwparams, audio_format, + sample_format); + if (err == -EINVAL) + err = alsa_output_try_reverse(pcm, hwparams, audio_format, + sample_format); + + return err; +} + +/** + * Configure a sample format, and probe other formats if that fails. + */ +static int +alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format) +{ + /* try the input format first */ + + int err = alsa_output_try_format_both(pcm, hwparams, audio_format, + audio_format->format); + if (err != -EINVAL) + return err; + + /* if unsupported by the hardware, try other formats */ + + static const enum sample_format probe_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED, + }; + + for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + if (probe_formats[i] == audio_format->format) + continue; + + err = alsa_output_try_format_both(pcm, hwparams, audio_format, + probe_formats[i]); + if (err != -EINVAL) + return err; + } + + return -EINVAL; +} + +/** + * Set up the snd_pcm_t object which was opened by the caller. Set up + * the configured settings and the audio format. + */ +static bool +alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, + GError **error) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + unsigned int sample_rate = audio_format->sample_rate; + unsigned int channels = audio_format->channels; + snd_pcm_uframes_t alsa_buffer_size; + snd_pcm_uframes_t alsa_period_size; + int err; + const char *cmd = NULL; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_alloca(&hwparams); + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcm, hwparams); + if (err < 0) + goto error; + + if (ad->use_mmap) { + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", + alsa_device(ad), snd_strerror(-err)); + g_warning("Falling back to direct write mode\n"); + ad->use_mmap = false; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->use_mmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format->format), + snd_strerror(-err)); + return false; + } + + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, + &channels); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support %i channels: %s", + alsa_device(ad), (int)audio_format->channels, + snd_strerror(-err)); + return false; + } + audio_format->channels = (int8_t)channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, + &sample_rate, NULL); + if (err < 0 || sample_rate == 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support %u Hz audio", + alsa_device(ad), audio_format->sample_rate); + return false; + } + audio_format->sample_rate = sample_rate; + + snd_pcm_uframes_t buffer_size_min, buffer_size_max; + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + unsigned buffer_time_min, buffer_time_max; + snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); + snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); + g_debug("buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); + + snd_pcm_uframes_t period_size_min, period_size_max; + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); + unsigned period_time_min, period_time_max; + snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); + snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); + g_debug("period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); + + if (ad->buffer_time > 0) { + buffer_time = ad->buffer_time; + cmd = "snd_pcm_hw_params_set_buffer_time_near"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, + &buffer_time, NULL); + if (err < 0) + goto error; + } else { + err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, + NULL); + if (err < 0) + buffer_time = 0; + } + + if (period_time_ro == 0 && buffer_time >= 10000) { + period_time_ro = period_time = buffer_time / 4; + + g_debug("default period_time = buffer_time/4 = %u/4 = %u", + buffer_time, period_time); + } + + if (period_time_ro > 0) { + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, + &period_time, NULL); + if (err < 0) + goto error; + } + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcm, hwparams); + if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + g_debug("ALSA period_time set to %d\n", period_time); + + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + NULL); + if (err < 0) + goto error; + + /* configure SW params */ + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcm, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcm, swparams); + if (err < 0) + goto error; + + g_debug("buffer_size=%u period_size=%u", + (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + + if (alsa_period_size == 0) + /* this works around a SIGFPE bug that occurred when + an ALSA driver indicated period_size==0; this + caused a division by zero in alsa_play(). By using + the fallback "1", we make sure that this won't + happen again. */ + alsa_period_size = 1; + + ad->period_frames = alsa_period_size; + ad->period_position = 0; + + return true; + +error: + g_set_error(error, alsa_output_quark(), err, + "Error opening ALSA device \"%s\" (%s): %s", + alsa_device(ad), cmd, snd_strerror(-err)); + return false; +} + +static bool +alsa_open(void *data, struct audio_format *audio_format, GError **error) +{ + struct alsa_data *ad = data; + int err; + bool success; + + err = snd_pcm_open(&ad->pcm, alsa_device(ad), + SND_PCM_STREAM_PLAYBACK, ad->mode); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "Failed to open ALSA device \"%s\": %s", + alsa_device(ad), snd_strerror(err)); + return false; + } + + success = alsa_setup(ad, audio_format, error); + if (!success) { + snd_pcm_close(ad->pcm); + return false; + } + + ad->frame_size = audio_format_frame_size(audio_format); + + return true; +} + +static int +alsa_recover(struct alsa_data *ad, int err) +{ + if (err == -EPIPE) { + g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); + } else if (err == -ESTRPIPE) { + g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); + } + + switch (snd_pcm_state(ad->pcm)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcm, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = snd_pcm_resume(ad->pcm); + if (err == -EAGAIN) + return 0; + /* fall-through to snd_pcm_prepare: */ + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_XRUN: + ad->period_position = 0; + err = snd_pcm_prepare(ad->pcm); + break; + case SND_PCM_STATE_DISCONNECTED: + break; + /* this is no error, so just keep running */ + case SND_PCM_STATE_RUNNING: + err = 0; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void +alsa_drain(void *data) +{ + struct alsa_data *ad = data; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + size_t nbytes = nframes * ad->frame_size; + void *buffer = g_malloc(nbytes); + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + unsigned channels; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_current(ad->pcm, params); + snd_pcm_hw_params_get_format(params, &format); + snd_pcm_hw_params_get_channels(params, &channels); + + snd_pcm_format_set_silence(format, buffer, nframes * channels); + ad->writei(ad->pcm, buffer, nframes); + g_free(buffer); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void +alsa_cancel(void *data) +{ + struct alsa_data *ad = data; + + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); +} + +static void +alsa_close(void *data) +{ + struct alsa_data *ad = data; + + snd_pcm_close(ad->pcm); +} + +static size_t +alsa_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct alsa_data *ad = data; + + size /= ad->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; + } + + if (ret < 0 && ret != -EAGAIN && ret != -EINTR && + alsa_recover(ad, ret) < 0) { + g_set_error(error, alsa_output_quark(), errno, + "%s", snd_strerror(-errno)); + return 0; + } + } +} + +const struct audio_output_plugin alsaPlugin = { + .name = "alsa", + .test_default_device = alsa_test_default_device, + .init = alsa_init, + .finish = alsa_finish, + .open = alsa_open, + .play = alsa_play, + .drain = alsa_drain, + .cancel = alsa_cancel, + .close = alsa_close, + + .mixer_plugin = &alsa_mixer_plugin, +}; diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c deleted file mode 100644 index 0bbe231fd..000000000 --- a/src/output/alsa_plugin.c +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "mixer_list.h" - -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "alsa" - -#define ALSA_PCM_NEW_HW_PARAMS_API -#define ALSA_PCM_NEW_SW_PARAMS_API - -static const char default_device[] = "default"; - -enum { - MPD_ALSA_BUFFER_TIME_US = 500000, -}; - -#define MPD_ALSA_RETRY_NR 5 - -typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, - snd_pcm_uframes_t size); - -struct alsa_data { - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; - - /** use memory mapped I/O? */ - bool use_mmap; - - /** libasound's buffer_time setting (in microseconds) */ - unsigned int buffer_time; - - /** libasound's period_time setting (in microseconds) */ - unsigned int period_time; - - /** the mode flags passed to snd_pcm_open */ - int mode; - - /** the libasound PCM device handle */ - snd_pcm_t *pcm; - - /** - * a pointer to the libasound writei() function, which is - * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the - * use_mmap configuration - */ - alsa_writei_t *writei; - - /** the size of one audio frame */ - size_t frame_size; - - /** - * The size of one period, in number of frames. - */ - snd_pcm_uframes_t period_frames; - - /** - * The number of frames written in the current period. - */ - snd_pcm_uframes_t period_position; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -alsa_output_quark(void) -{ - return g_quark_from_static_string("alsa_output"); -} - -static const char * -alsa_device(const struct alsa_data *ad) -{ - return ad->device != NULL ? ad->device : default_device; -} - -static struct alsa_data * -alsa_data_new(void) -{ - struct alsa_data *ret = g_new(struct alsa_data, 1); - - ret->mode = 0; - ret->writei = snd_pcm_writei; - - return ret; -} - -static void -alsa_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->buffer_time = config_get_block_unsigned(param, "buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = config_get_block_unsigned(param, "period_time", 0); - -#ifdef SND_PCM_NO_AUTO_RESAMPLE - if (!config_get_block_bool(param, "auto_resample", true)) - ad->mode |= SND_PCM_NO_AUTO_RESAMPLE; -#endif - -#ifdef SND_PCM_NO_AUTO_CHANNELS - if (!config_get_block_bool(param, "auto_channels", true)) - ad->mode |= SND_PCM_NO_AUTO_CHANNELS; -#endif - -#ifdef SND_PCM_NO_AUTO_FORMAT - if (!config_get_block_bool(param, "auto_format", true)) - ad->mode |= SND_PCM_NO_AUTO_FORMAT; -#endif -} - -static void * -alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) -{ - struct alsa_data *ad = alsa_data_new(); - - alsa_configure(ad, param); - - return ad; -} - -static void -alsa_finish(void *data) -{ - struct alsa_data *ad = data; - - alsa_data_free(ad); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_test_default_device(void) -{ - snd_pcm_t *handle; - - int ret = snd_pcm_open(&handle, default_device, - SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (ret) { - g_message("Error opening default ALSA device: %s\n", - snd_strerror(-ret)); - return false; - } else - snd_pcm_close(handle); - - return true; -} - -static snd_pcm_format_t -get_bitformat(enum sample_format sample_format) -{ - switch (sample_format) { - case SAMPLE_FORMAT_S8: - return SND_PCM_FORMAT_S8; - - case SAMPLE_FORMAT_S16: - return SND_PCM_FORMAT_S16; - - case SAMPLE_FORMAT_S24_P32: - return SND_PCM_FORMAT_S24; - - case SAMPLE_FORMAT_S24: - return G_BYTE_ORDER == G_BIG_ENDIAN - ? SND_PCM_FORMAT_S24_3BE - : SND_PCM_FORMAT_S24_3LE; - - case SAMPLE_FORMAT_S32: - return SND_PCM_FORMAT_S32; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -static snd_pcm_format_t -byteswap_bitformat(snd_pcm_format_t fmt) -{ - switch(fmt) { - case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; - case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; - case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; - case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; - case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; - - case SND_PCM_FORMAT_S24_3BE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_3LE: - return SND_PCM_FORMAT_S24_3BE; - - case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; - default: return SND_PCM_FORMAT_UNKNOWN; - } -} - -/** - * Attempts to configure the specified sample format. - */ -static int -alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) -{ - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); - if (err == 0) - audio_format->format = sample_format; - - return err; -} - -/** - * Attempts to configure the specified sample format with reversed - * host byte order. - */ -static int -alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) -{ - snd_pcm_format_t alsa_format = - byteswap_bitformat(get_bitformat(sample_format)); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); - if (err == 0) { - audio_format->format = sample_format; - audio_format->reverse_endian = true; - } - - return err; -} - -/** - * Attempts to configure the specified sample format, and tries the - * reversed host byte order if was not supported. - */ -static int -alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) -{ - int err = alsa_output_try_format(pcm, hwparams, audio_format, - sample_format); - if (err == -EINVAL) - err = alsa_output_try_reverse(pcm, hwparams, audio_format, - sample_format); - - return err; -} - -/** - * Configure a sample format, and probe other formats if that fails. - */ -static int -alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format) -{ - /* try the input format first */ - - int err = alsa_output_try_format_both(pcm, hwparams, audio_format, - audio_format->format); - if (err != -EINVAL) - return err; - - /* if unsupported by the hardware, try other formats */ - - static const enum sample_format probe_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S24, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED, - }; - - for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { - if (probe_formats[i] == audio_format->format) - continue; - - err = alsa_output_try_format_both(pcm, hwparams, audio_format, - probe_formats[i]); - if (err != -EINVAL) - return err; - } - - return -EINVAL; -} - -/** - * Set up the snd_pcm_t object which was opened by the caller. Set up - * the configured settings and the audio format. - */ -static bool -alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - GError **error) -{ - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; - unsigned int sample_rate = audio_format->sample_rate; - unsigned int channels = audio_format->channels; - snd_pcm_uframes_t alsa_buffer_size; - snd_pcm_uframes_t alsa_period_size; - int err; - const char *cmd = NULL; - int retry = MPD_ALSA_RETRY_NR; - unsigned int period_time, period_time_ro; - unsigned int buffer_time; - - period_time_ro = period_time = ad->period_time; -configure_hw: - /* configure HW params */ - snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; - err = snd_pcm_hw_params_any(ad->pcm, hwparams); - if (err < 0) - goto error; - - if (ad->use_mmap) { - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_MMAP_INTERLEAVED); - if (err < 0) { - g_warning("Cannot set mmap'ed mode on ALSA device \"%s\": %s\n", - alsa_device(ad), snd_strerror(-err)); - g_warning("Falling back to direct write mode\n"); - ad->use_mmap = false; - } else - ad->writei = snd_pcm_mmap_writei; - } - - if (!ad->use_mmap) { - cmd = "snd_pcm_hw_params_set_access"; - err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED); - if (err < 0) - goto error; - ad->writei = snd_pcm_writei; - } - - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), - sample_format_to_string(audio_format->format), - snd_strerror(-err)); - return false; - } - - err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, - &channels); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %i channels: %s", - alsa_device(ad), (int)audio_format->channels, - snd_strerror(-err)); - return false; - } - audio_format->channels = (int8_t)channels; - - err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, - &sample_rate, NULL); - if (err < 0 || sample_rate == 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u Hz audio", - alsa_device(ad), audio_format->sample_rate); - return false; - } - audio_format->sample_rate = sample_rate; - - snd_pcm_uframes_t buffer_size_min, buffer_size_max; - snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); - snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); - unsigned buffer_time_min, buffer_time_max; - snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); - snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); - g_debug("buffer: size=%u..%u time=%u..%u", - (unsigned)buffer_size_min, (unsigned)buffer_size_max, - buffer_time_min, buffer_time_max); - - snd_pcm_uframes_t period_size_min, period_size_max; - snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); - snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); - unsigned period_time_min, period_time_max; - snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); - snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); - g_debug("period: size=%u..%u time=%u..%u", - (unsigned)period_size_min, (unsigned)period_size_max, - period_time_min, period_time_max); - - if (ad->buffer_time > 0) { - buffer_time = ad->buffer_time; - cmd = "snd_pcm_hw_params_set_buffer_time_near"; - err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams, - &buffer_time, NULL); - if (err < 0) - goto error; - } else { - err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, - NULL); - if (err < 0) - buffer_time = 0; - } - - if (period_time_ro == 0 && buffer_time >= 10000) { - period_time_ro = period_time = buffer_time / 4; - - g_debug("default period_time = buffer_time/4 = %u/4 = %u", - buffer_time, period_time); - } - - if (period_time_ro > 0) { - period_time = period_time_ro; - cmd = "snd_pcm_hw_params_set_period_time_near"; - err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, - &period_time, NULL); - if (err < 0) - goto error; - } - - cmd = "snd_pcm_hw_params"; - err = snd_pcm_hw_params(ad->pcm, hwparams); - if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { - period_time_ro = period_time_ro >> 1; - goto configure_hw; - } else if (err < 0) - goto error; - if (retry != MPD_ALSA_RETRY_NR) - g_debug("ALSA period_time set to %d\n", period_time); - - cmd = "snd_pcm_hw_params_get_buffer_size"; - err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_hw_params_get_period_size"; - err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, - NULL); - if (err < 0) - goto error; - - /* configure SW params */ - snd_pcm_sw_params_alloca(&swparams); - - cmd = "snd_pcm_sw_params_current"; - err = snd_pcm_sw_params_current(ad->pcm, swparams); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_start_threshold"; - err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams, - alsa_buffer_size - - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params_set_avail_min"; - err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams, - alsa_period_size); - if (err < 0) - goto error; - - cmd = "snd_pcm_sw_params"; - err = snd_pcm_sw_params(ad->pcm, swparams); - if (err < 0) - goto error; - - g_debug("buffer_size=%u period_size=%u", - (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); - - if (alsa_period_size == 0) - /* this works around a SIGFPE bug that occurred when - an ALSA driver indicated period_size==0; this - caused a division by zero in alsa_play(). By using - the fallback "1", we make sure that this won't - happen again. */ - alsa_period_size = 1; - - ad->period_frames = alsa_period_size; - ad->period_position = 0; - - return true; - -error: - g_set_error(error, alsa_output_quark(), err, - "Error opening ALSA device \"%s\" (%s): %s", - alsa_device(ad), cmd, snd_strerror(-err)); - return false; -} - -static bool -alsa_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct alsa_data *ad = data; - int err; - bool success; - - err = snd_pcm_open(&ad->pcm, alsa_device(ad), - SND_PCM_STREAM_PLAYBACK, ad->mode); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "Failed to open ALSA device \"%s\": %s", - alsa_device(ad), snd_strerror(err)); - return false; - } - - success = alsa_setup(ad, audio_format, error); - if (!success) { - snd_pcm_close(ad->pcm); - return false; - } - - ad->frame_size = audio_format_frame_size(audio_format); - - return true; -} - -static int -alsa_recover(struct alsa_data *ad, int err) -{ - if (err == -EPIPE) { - g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad)); - } else if (err == -ESTRPIPE) { - g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad)); - } - - switch (snd_pcm_state(ad->pcm)) { - case SND_PCM_STATE_PAUSED: - err = snd_pcm_pause(ad->pcm, /* disable */ 0); - break; - case SND_PCM_STATE_SUSPENDED: - err = snd_pcm_resume(ad->pcm); - if (err == -EAGAIN) - return 0; - /* fall-through to snd_pcm_prepare: */ - case SND_PCM_STATE_SETUP: - case SND_PCM_STATE_XRUN: - ad->period_position = 0; - err = snd_pcm_prepare(ad->pcm); - break; - case SND_PCM_STATE_DISCONNECTED: - break; - /* this is no error, so just keep running */ - case SND_PCM_STATE_RUNNING: - err = 0; - break; - default: - /* unknown state, do nothing */ - break; - } - - return err; -} - -static void -alsa_drain(void *data) -{ - struct alsa_data *ad = data; - - if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) - return; - - if (ad->period_position > 0) { - /* generate some silence to finish the partial - period */ - snd_pcm_uframes_t nframes = - ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->frame_size; - void *buffer = g_malloc(nbytes); - snd_pcm_hw_params_t *params; - snd_pcm_format_t format; - unsigned channels; - - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_hw_params_current(ad->pcm, params); - snd_pcm_hw_params_get_format(params, &format); - snd_pcm_hw_params_get_channels(params, &channels); - - snd_pcm_format_set_silence(format, buffer, nframes * channels); - ad->writei(ad->pcm, buffer, nframes); - g_free(buffer); - } - - snd_pcm_drain(ad->pcm); - - ad->period_position = 0; -} - -static void -alsa_cancel(void *data) -{ - struct alsa_data *ad = data; - - ad->period_position = 0; - - snd_pcm_drop(ad->pcm); -} - -static void -alsa_close(void *data) -{ - struct alsa_data *ad = data; - - snd_pcm_close(ad->pcm); -} - -static size_t -alsa_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct alsa_data *ad = data; - - size /= ad->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; - } - - if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { - g_set_error(error, alsa_output_quark(), errno, - "%s", snd_strerror(-errno)); - return 0; - } - } -} - -const struct audio_output_plugin alsaPlugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, -}; diff --git a/src/output/ao_output_plugin.c b/src/output/ao_output_plugin.c new file mode 100644 index 000000000..33366d3b8 --- /dev/null +++ b/src/output/ao_output_plugin.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" + +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ao" + +/* An ao_sample_format, with all fields set to zero: */ +static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; + +static unsigned ao_output_ref; + +struct ao_data { + size_t write_size; + int driver; + ao_option *options; + ao_device *device; +} AoData; + +static inline GQuark +ao_output_quark(void) +{ + return g_quark_from_static_string("ao_output"); +} + +static void +ao_output_error(GError **error_r) +{ + const char *error; + + switch (errno) { + case AO_ENODRIVER: + error = "No such libao driver"; + break; + + case AO_ENOTLIVE: + error = "This driver is not a libao live device"; + break; + + case AO_EBADOPTION: + error = "Invalid libao option"; + break; + + case AO_EOPENDEVICE: + error = "Cannot open the libao device"; + break; + + case AO_EFAIL: + error = "Generic libao failure"; + break; + + default: + error = 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, + GError **error) +{ + struct ao_data *ad = g_new(struct ao_data, 1); + ao_info *ai; + const char *value; + + ad->options = NULL; + + ad->write_size = config_get_block_unsigned(param, "write_size", 1024); + + if (ao_output_ref == 0) { + ao_initialize(); + } + ao_output_ref++; + + value = config_get_block_string(param, "driver", "default"); + if (0 == strcmp(value, "default")) + ad->driver = ao_default_driver_id(); + else + ad->driver = ao_driver_id(value); + + if (ad->driver < 0) { + g_set_error(error, ao_output_quark(), 0, + "\"%s\" is not a valid ao driver", + value); + g_free(ad); + return NULL; + } + + if ((ai = ao_driver_info(ad->driver)) == NULL) { + g_set_error(error, ao_output_quark(), 0, + "problems getting driver info"); + g_free(ad); + return NULL; + } + + g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name, + config_get_block_string(param, "name", NULL)); + + value = config_get_block_string(param, "options", NULL); + if (value != NULL) { + gchar **options = g_strsplit(value, ";", 0); + + for (unsigned i = 0; options[i] != NULL; ++i) { + gchar **key_value = g_strsplit(options[i], "=", 2); + + if (key_value[0] == NULL || key_value[1] == NULL) { + g_set_error(error, ao_output_quark(), 0, + "problems parsing options \"%s\"", + options[i]); + g_free(ad); + return NULL; + } + + ao_append_option(&ad->options, key_value[0], + key_value[1]); + + g_strfreev(key_value); + } + + g_strfreev(options); + } + + return ad; +} + +static void +ao_output_finish(void *data) +{ + struct ao_data *ad = (struct ao_data *)data; + + ao_free_options(ad->options); + g_free(ad); + + ao_output_ref--; + + if (ao_output_ref == 0) + ao_shutdown(); +} + +static void +ao_output_close(void *data) +{ + struct ao_data *ad = (struct ao_data *)data; + + ao_close(ad->device); +} + +static bool +ao_output_open(void *data, struct audio_format *audio_format, + GError **error) +{ + ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; + struct ao_data *ad = (struct ao_data *)data; + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + format.bits = 8; + break; + + case SAMPLE_FORMAT_S16: + format.bits = 16; + break; + + default: + /* support for 24 bit samples in libao is currently + dubious, and until we have sorted that out, + convert everything to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + format.bits = 16; + break; + } + + format.rate = audio_format->sample_rate; + format.byte_format = AO_FMT_NATIVE; + format.channels = audio_format->channels; + + ad->device = ao_open_live(ad->driver, &format, ad->options); + + if (ad->device == NULL) { + ao_output_error(error); + return false; + } + + return true; +} + +/** + * For whatever reason, libao wants a non-const pointer. Let's hope + * it does not write to the buffer, and use the union deconst hack to + * work around this API misdesign. + */ +static int ao_play_deconst(ao_device *device, const void *output_samples, + uint_32 num_bytes) +{ + union { + const void *in; + void *out; + } u; + + u.in = output_samples; + return ao_play(device, u.out, num_bytes); +} + +static size_t +ao_output_play(void *data, const void *chunk, size_t size, + GError **error) +{ + struct ao_data *ad = (struct ao_data *)data; + + if (size > ad->write_size) + size = ad->write_size; + + if (ao_play_deconst(ad->device, chunk, size) == 0) { + ao_output_error(error); + return 0; + } + + return size; +} + +const struct audio_output_plugin ao_output_plugin = { + .name = "ao", + .init = ao_output_init, + .finish = ao_output_finish, + .open = ao_output_open, + .close = ao_output_close, + .play = ao_output_play, +}; diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c deleted file mode 100644 index 33366d3b8..000000000 --- a/src/output/ao_plugin.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" - -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "ao" - -/* An ao_sample_format, with all fields set to zero: */ -static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; - -static unsigned ao_output_ref; - -struct ao_data { - size_t write_size; - int driver; - ao_option *options; - ao_device *device; -} AoData; - -static inline GQuark -ao_output_quark(void) -{ - return g_quark_from_static_string("ao_output"); -} - -static void -ao_output_error(GError **error_r) -{ - const char *error; - - switch (errno) { - case AO_ENODRIVER: - error = "No such libao driver"; - break; - - case AO_ENOTLIVE: - error = "This driver is not a libao live device"; - break; - - case AO_EBADOPTION: - error = "Invalid libao option"; - break; - - case AO_EOPENDEVICE: - error = "Cannot open the libao device"; - break; - - case AO_EFAIL: - error = "Generic libao failure"; - break; - - default: - error = 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, - GError **error) -{ - struct ao_data *ad = g_new(struct ao_data, 1); - ao_info *ai; - const char *value; - - ad->options = NULL; - - ad->write_size = config_get_block_unsigned(param, "write_size", 1024); - - if (ao_output_ref == 0) { - ao_initialize(); - } - ao_output_ref++; - - value = config_get_block_string(param, "driver", "default"); - if (0 == strcmp(value, "default")) - ad->driver = ao_default_driver_id(); - else - ad->driver = ao_driver_id(value); - - if (ad->driver < 0) { - g_set_error(error, ao_output_quark(), 0, - "\"%s\" is not a valid ao driver", - value); - g_free(ad); - return NULL; - } - - if ((ai = ao_driver_info(ad->driver)) == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems getting driver info"); - g_free(ad); - return NULL; - } - - g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name, - config_get_block_string(param, "name", NULL)); - - value = config_get_block_string(param, "options", NULL); - if (value != NULL) { - gchar **options = g_strsplit(value, ";", 0); - - for (unsigned i = 0; options[i] != NULL; ++i) { - gchar **key_value = g_strsplit(options[i], "=", 2); - - if (key_value[0] == NULL || key_value[1] == NULL) { - g_set_error(error, ao_output_quark(), 0, - "problems parsing options \"%s\"", - options[i]); - g_free(ad); - return NULL; - } - - ao_append_option(&ad->options, key_value[0], - key_value[1]); - - g_strfreev(key_value); - } - - g_strfreev(options); - } - - return ad; -} - -static void -ao_output_finish(void *data) -{ - struct ao_data *ad = (struct ao_data *)data; - - ao_free_options(ad->options); - g_free(ad); - - ao_output_ref--; - - if (ao_output_ref == 0) - ao_shutdown(); -} - -static void -ao_output_close(void *data) -{ - struct ao_data *ad = (struct ao_data *)data; - - ao_close(ad->device); -} - -static bool -ao_output_open(void *data, struct audio_format *audio_format, - GError **error) -{ - ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - struct ao_data *ad = (struct ao_data *)data; - - switch (audio_format->format) { - case SAMPLE_FORMAT_S8: - format.bits = 8; - break; - - case SAMPLE_FORMAT_S16: - format.bits = 16; - break; - - default: - /* support for 24 bit samples in libao is currently - dubious, and until we have sorted that out, - convert everything to 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; - format.bits = 16; - break; - } - - format.rate = audio_format->sample_rate; - format.byte_format = AO_FMT_NATIVE; - format.channels = audio_format->channels; - - ad->device = ao_open_live(ad->driver, &format, ad->options); - - if (ad->device == NULL) { - ao_output_error(error); - return false; - } - - return true; -} - -/** - * For whatever reason, libao wants a non-const pointer. Let's hope - * it does not write to the buffer, and use the union deconst hack to - * work around this API misdesign. - */ -static int ao_play_deconst(ao_device *device, const void *output_samples, - uint_32 num_bytes) -{ - union { - const void *in; - void *out; - } u; - - u.in = output_samples; - return ao_play(device, u.out, num_bytes); -} - -static size_t -ao_output_play(void *data, const void *chunk, size_t size, - GError **error) -{ - struct ao_data *ad = (struct ao_data *)data; - - if (size > ad->write_size) - size = ad->write_size; - - if (ao_play_deconst(ad->device, chunk, size) == 0) { - ao_output_error(error); - return 0; - } - - return size; -} - -const struct audio_output_plugin ao_output_plugin = { - .name = "ao", - .init = ao_output_init, - .finish = ao_output_finish, - .open = ao_output_open, - .close = ao_output_close, - .play = ao_output_play, -}; diff --git a/src/output/mvp_output_plugin.c b/src/output/mvp_output_plugin.c new file mode 100644 index 000000000..be4c8dbc0 --- /dev/null +++ b/src/output/mvp_output_plugin.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Media MVP audio output based on code from MVPMC project: + * http://mvpmc.sourceforge.net/ + */ + +#include "config.h" +#include "output_api.h" +#include "fd_util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mvp" + +typedef struct { + unsigned long dsp_status; + unsigned long stream_decode_type; + unsigned long sample_rate; + unsigned long bit_rate; + unsigned long raw[64 / sizeof(unsigned long)]; +} aud_status_t; + +#define MVP_SET_AUD_STOP _IOW('a',1,int) +#define MVP_SET_AUD_PLAY _IOW('a',2,int) +#define MVP_SET_AUD_PAUSE _IOW('a',3,int) +#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) +#define MVP_SET_AUD_SRC _IOW('a',5,int) +#define MVP_SET_AUD_MUTE _IOW('a',6,int) +#define MVP_SET_AUD_BYPASS _IOW('a',8,int) +#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) +#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) +#define MVP_SET_AUD_VOLUME _IOW('a',13,int) +#define MVP_GET_AUD_VOLUME _IOR('a',14,int) +#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) +#define MVP_SET_AUD_FORMAT _IOW('a',16,int) +#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) +#define MVP_SET_AUD_STC _IOW('a',22,long long int *) +#define MVP_SET_AUD_SYNC _IOW('a',23,int) +#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) +#define MVP_SET_AUD_RESET _IOW('a',26,int) +#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) +#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) + +struct mvp_data { + struct audio_format audio_format; + int fd; +}; + +static const unsigned mvp_sample_rates[][3] = { + {9, 8000, 32000}, + {10, 11025, 44100}, + {11, 12000, 48000}, + {1, 16000, 32000}, + {2, 22050, 44100}, + {3, 24000, 48000}, + {5, 32000, 32000}, + {0, 44100, 44100}, + {7, 48000, 48000}, + {13, 64000, 32000}, + {14, 88200, 44100}, + {15, 96000, 48000} +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +mvp_output_quark(void) +{ + return g_quark_from_static_string("mvp_output"); +} + +/** + * Translate a sample rate to a MVP sample rate. + * + * @param sample_rate the sample rate in Hz + */ +static unsigned +mvp_find_sample_rate(unsigned sample_rate) +{ + for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i) + if (mvp_sample_rates[i][1] == sample_rate) + return mvp_sample_rates[i][0]; + + return (unsigned)-1; +} + +static bool +mvp_output_test_default_device(void) +{ + int fd; + + fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); + + if (fd >= 0) { + close(fd); + return true; + } + + g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n", + 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) +{ + struct mvp_data *md = g_new(struct mvp_data, 1); + md->fd = -1; + + return md; +} + +static void +mvp_output_finish(void *data) +{ + struct mvp_data *md = data; + g_free(md); +} + +static bool +mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, + GError **error) +{ + unsigned mix[5]; + + switch (audio_format->channels) { + case 1: + mix[0] = 1; + break; + + case 2: + mix[0] = 0; + break; + + default: + g_debug("unsupported channel count %u - falling back to stereo", + audio_format->channels); + audio_format->channels = 2; + mix[0] = 0; + break; + } + + /* 0,1=24bit(24) , 2,3=16bit */ + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: + mix[1] = 2; + break; + + case SAMPLE_FORMAT_S24_P32: + mix[1] = 0; + break; + + default: + g_debug("unsupported sample format %s - falling back to 16 bit", + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S16; + mix[1] = 2; + break; + } + + mix[3] = 0; /* stream type? */ + mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN; + + /* + * if there is an exact match for the frequency, use it. + */ + mix[2] = mvp_find_sample_rate(audio_format->sample_rate); + if (mix[2] == (unsigned)-1) { + g_set_error(error, mvp_output_quark(), 0, + "Can not find suitable output frequency for %u", + audio_format->sample_rate); + return false; + } + + if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { + g_set_error(error, mvp_output_quark(), errno, + "Can not set audio format"); + return false; + } + + if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { + g_set_error(error, mvp_output_quark(), errno, + "Can not set audio sync"); + return false; + } + + if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { + g_set_error(error, mvp_output_quark(), errno, + "Can not set audio play mode"); + return false; + } + + return true; +} + +static bool +mvp_output_open(void *data, struct audio_format *audio_format, GError **error) +{ + struct mvp_data *md = data; + long long int stc = 0; + int mix[5] = { 0, 2, 7, 1, 0 }; + bool success; + + md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); + if (md->fd < 0) { + g_set_error(error, mvp_output_quark(), errno, + "Error opening /dev/adec_pcm: %s", + 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)); + 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)); + 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)); + 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)); + return false; + } + + success = mvp_set_pcm_params(md, audio_format, error); + if (!success) + return false; + + md->audio_format = *audio_format; + return true; +} + +static void mvp_output_close(void *data) +{ + struct mvp_data *md = data; + if (md->fd >= 0) + close(md->fd); + md->fd = -1; +} + +static void mvp_output_cancel(void *data) +{ + struct mvp_data *md = data; + if (md->fd >= 0) { + ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); + close(md->fd); + md->fd = -1; + } +} + +static size_t +mvp_output_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct mvp_data *md = data; + 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); + if (!success) + return 0; + } + + while (true) { + ret = write(md->fd, chunk, size); + if (ret > 0) + return (size_t)ret; + + if (ret < 0) { + if (errno == EINTR) + continue; + + g_set_error(error, mvp_output_quark(), errno, + "Failed to write: %s", strerror(errno)); + return 0; + } + } +} + +const struct audio_output_plugin mvp_output_plugin = { + .name = "mvp", + .test_default_device = mvp_output_test_default_device, + .init = mvp_output_init, + .finish = mvp_output_finish, + .open = mvp_output_open, + .close = mvp_output_close, + .play = mvp_output_play, + .cancel = mvp_output_cancel, +}; diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c deleted file mode 100644 index be4c8dbc0..000000000 --- a/src/output/mvp_plugin.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Media MVP audio output based on code from MVPMC project: - * http://mvpmc.sourceforge.net/ - */ - -#include "config.h" -#include "output_api.h" -#include "fd_util.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "mvp" - -typedef struct { - unsigned long dsp_status; - unsigned long stream_decode_type; - unsigned long sample_rate; - unsigned long bit_rate; - unsigned long raw[64 / sizeof(unsigned long)]; -} aud_status_t; - -#define MVP_SET_AUD_STOP _IOW('a',1,int) -#define MVP_SET_AUD_PLAY _IOW('a',2,int) -#define MVP_SET_AUD_PAUSE _IOW('a',3,int) -#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) -#define MVP_SET_AUD_SRC _IOW('a',5,int) -#define MVP_SET_AUD_MUTE _IOW('a',6,int) -#define MVP_SET_AUD_BYPASS _IOW('a',8,int) -#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) -#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) -#define MVP_SET_AUD_VOLUME _IOW('a',13,int) -#define MVP_GET_AUD_VOLUME _IOR('a',14,int) -#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) -#define MVP_SET_AUD_FORMAT _IOW('a',16,int) -#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) -#define MVP_SET_AUD_STC _IOW('a',22,long long int *) -#define MVP_SET_AUD_SYNC _IOW('a',23,int) -#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) -#define MVP_SET_AUD_RESET _IOW('a',26,int) -#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) -#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) - -struct mvp_data { - struct audio_format audio_format; - int fd; -}; - -static const unsigned mvp_sample_rates[][3] = { - {9, 8000, 32000}, - {10, 11025, 44100}, - {11, 12000, 48000}, - {1, 16000, 32000}, - {2, 22050, 44100}, - {3, 24000, 48000}, - {5, 32000, 32000}, - {0, 44100, 44100}, - {7, 48000, 48000}, - {13, 64000, 32000}, - {14, 88200, 44100}, - {15, 96000, 48000} -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -mvp_output_quark(void) -{ - return g_quark_from_static_string("mvp_output"); -} - -/** - * Translate a sample rate to a MVP sample rate. - * - * @param sample_rate the sample rate in Hz - */ -static unsigned -mvp_find_sample_rate(unsigned sample_rate) -{ - for (unsigned i = 0; i < G_N_ELEMENTS(mvp_sample_rates); ++i) - if (mvp_sample_rates[i][1] == sample_rate) - return mvp_sample_rates[i][0]; - - return (unsigned)-1; -} - -static bool -mvp_output_test_default_device(void) -{ - int fd; - - fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - - g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n", - 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) -{ - struct mvp_data *md = g_new(struct mvp_data, 1); - md->fd = -1; - - return md; -} - -static void -mvp_output_finish(void *data) -{ - struct mvp_data *md = data; - g_free(md); -} - -static bool -mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, - GError **error) -{ - unsigned mix[5]; - - switch (audio_format->channels) { - case 1: - mix[0] = 1; - break; - - case 2: - mix[0] = 0; - break; - - default: - g_debug("unsupported channel count %u - falling back to stereo", - audio_format->channels); - audio_format->channels = 2; - mix[0] = 0; - break; - } - - /* 0,1=24bit(24) , 2,3=16bit */ - switch (audio_format->format) { - case SAMPLE_FORMAT_S16: - mix[1] = 2; - break; - - case SAMPLE_FORMAT_S24_P32: - mix[1] = 0; - break; - - default: - g_debug("unsupported sample format %s - falling back to 16 bit", - sample_format_to_string(audio_format->format)); - audio_format->format = SAMPLE_FORMAT_S16; - mix[1] = 2; - break; - } - - mix[3] = 0; /* stream type? */ - mix[4] = G_BYTE_ORDER == G_LITTLE_ENDIAN; - - /* - * if there is an exact match for the frequency, use it. - */ - mix[2] = mvp_find_sample_rate(audio_format->sample_rate); - if (mix[2] == (unsigned)-1) { - g_set_error(error, mvp_output_quark(), 0, - "Can not find suitable output frequency for %u", - audio_format->sample_rate); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio format"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio sync"); - return false; - } - - if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Can not set audio play mode"); - return false; - } - - return true; -} - -static bool -mvp_output_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct mvp_data *md = data; - long long int stc = 0; - int mix[5] = { 0, 2, 7, 1, 0 }; - bool success; - - md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); - if (md->fd < 0) { - g_set_error(error, mvp_output_quark(), errno, - "Error opening /dev/adec_pcm: %s", - 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)); - 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)); - 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)); - 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)); - return false; - } - - success = mvp_set_pcm_params(md, audio_format, error); - if (!success) - return false; - - md->audio_format = *audio_format; - return true; -} - -static void mvp_output_close(void *data) -{ - struct mvp_data *md = data; - if (md->fd >= 0) - close(md->fd); - md->fd = -1; -} - -static void mvp_output_cancel(void *data) -{ - struct mvp_data *md = data; - if (md->fd >= 0) { - ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); - close(md->fd); - md->fd = -1; - } -} - -static size_t -mvp_output_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct mvp_data *md = data; - 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); - if (!success) - return 0; - } - - while (true) { - ret = write(md->fd, chunk, size); - if (ret > 0) - return (size_t)ret; - - if (ret < 0) { - if (errno == EINTR) - continue; - - g_set_error(error, mvp_output_quark(), errno, - "Failed to write: %s", strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin mvp_output_plugin = { - .name = "mvp", - .test_default_device = mvp_output_test_default_device, - .init = mvp_output_init, - .finish = mvp_output_finish, - .open = mvp_output_open, - .close = mvp_output_close, - .play = mvp_output_play, - .cancel = mvp_output_cancel, -}; diff --git a/src/output/null_output_plugin.c b/src/output/null_output_plugin.c new file mode 100644 index 000000000..f572959a0 --- /dev/null +++ b/src/output/null_output_plugin.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include + +#include + +struct null_data { + bool sync; + + 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) +{ + struct null_data *nd = g_new(struct null_data, 1); + + nd->sync = config_get_block_bool(param, "sync", true); + nd->timer = NULL; + + return nd; +} + +static void +null_finish(void *data) +{ + struct null_data *nd = data; + + assert(nd->timer == NULL); + + g_free(nd); +} + +static bool +null_open(void *data, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + struct null_data *nd = data; + + if (nd->sync) + nd->timer = timer_new(audio_format); + + return true; +} + +static void +null_close(void *data) +{ + struct null_data *nd = data; + + if (nd->timer != NULL) { + timer_free(nd->timer); + nd->timer = NULL; + } +} + +static size_t +null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct null_data *nd = data; + 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) +{ + struct null_data *nd = data; + + if (!nd->sync) + return; + + timer_reset(nd->timer); +} + +const struct audio_output_plugin null_output_plugin = { + .name = "null", + .init = null_init, + .finish = null_finish, + .open = null_open, + .close = null_close, + .play = null_play, + .cancel = null_cancel, +}; diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c deleted file mode 100644 index f572959a0..000000000 --- a/src/output/null_plugin.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "timer.h" - -#include - -#include - -struct null_data { - bool sync; - - 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) -{ - struct null_data *nd = g_new(struct null_data, 1); - - nd->sync = config_get_block_bool(param, "sync", true); - nd->timer = NULL; - - return nd; -} - -static void -null_finish(void *data) -{ - struct null_data *nd = data; - - assert(nd->timer == NULL); - - g_free(nd); -} - -static bool -null_open(void *data, struct audio_format *audio_format, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = data; - - if (nd->sync) - nd->timer = timer_new(audio_format); - - return true; -} - -static void -null_close(void *data) -{ - struct null_data *nd = data; - - if (nd->timer != NULL) { - timer_free(nd->timer); - nd->timer = NULL; - } -} - -static size_t -null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct null_data *nd = data; - 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) -{ - struct null_data *nd = data; - - if (!nd->sync) - return; - - timer_reset(nd->timer); -} - -const struct audio_output_plugin null_output_plugin = { - .name = "null", - .init = null_init, - .finish = null_finish, - .open = null_open, - .close = null_close, - .play = null_play, - .cancel = null_cancel, -}; diff --git a/src/output/openal_output_plugin.c b/src/output/openal_output_plugin.c new file mode 100644 index 000000000..23641c6c5 --- /dev/null +++ b/src/output/openal_output_plugin.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include + +#ifndef HAVE_OSX +#include +#include +#else +#include +#include +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "openal" + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct openal_data { + const char *device_name; + ALCdevice *device; + ALCcontext *context; + struct timer *timer; + ALuint buffers[NUM_BUFFERS]; + int filled; + ALuint source; + ALenum format; + ALuint frequency; +}; + +static inline GQuark +openal_output_quark(void) +{ + return g_quark_from_static_string("openal_output"); +} + +static ALenum +openal_audio_format(struct audio_format *audio_format) +{ + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + + case SAMPLE_FORMAT_S8: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO8; + if (audio_format->channels == 1) + return AL_FORMAT_MONO8; + break; + + default: + /* fall back to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + } + + return 0; +} + +static bool +openal_setup_context(struct openal_data *od, + GError **error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error opening OpenAL device \"%s\"\n", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, NULL); + + if (od->context == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error creating context for \"%s\"\n", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static void +openal_unqueue_buffers(struct openal_data *od) +{ + ALint num; + ALuint buffer; + + alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); + + while (num--) { + alSourceUnqueueBuffers(od->source, 1, &buffer); + } +} + +static void * +openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + const char *device_name = config_get_block_string(param, "device", NULL); + struct openal_data *od; + + if (device_name == NULL) { + device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + od = g_new(struct openal_data, 1); + od->device_name = device_name; + + return od; +} + +static void +openal_finish(void *data) +{ + struct openal_data *od = data; + + g_free(od); +} + +static bool +openal_open(void *data, struct audio_format *audio_format, + GError **error) +{ + struct openal_data *od = data; + + od->format = openal_audio_format(audio_format); + + if (!od->format) { + struct audio_format_string s; + g_set_error(error, openal_output_quark(), 0, + "Unsupported audio format: %s", + audio_format_to_string(audio_format, &s)); + return false; + } + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->timer = timer_new(audio_format); + od->frequency = audio_format->sample_rate; + + return true; +} + +static void +openal_close(void *data) +{ + struct openal_data *od = data; + + timer_free(od->timer); + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static size_t +openal_play(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct openal_data *od = data; + ALuint buffer; + ALint num, state; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (num < 1) { + if (!od->timer->started) { + timer_start(od->timer); + } else { + timer_sync(od->timer); + } + + timer_add(od->timer, size); + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + } + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + alGetSourcei(od->source, AL_SOURCE_STATE, &state); + + if (state != AL_PLAYING) { + alSourcePlay(od->source); + } + + return size; +} + +static void +openal_cancel(void *data) +{ + struct openal_data *od = data; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + openal_unqueue_buffers(od); +} + +const struct audio_output_plugin openal_output_plugin = { + .name = "openal", + .init = openal_init, + .finish = openal_finish, + .open = openal_open, + .close = openal_close, + .play = openal_play, + .cancel = openal_cancel, +}; diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c deleted file mode 100644 index 23641c6c5..000000000 --- a/src/output/openal_plugin.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "timer.h" - -#include - -#ifndef HAVE_OSX -#include -#include -#else -#include -#include -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "openal" - -/* should be enough for buffer size = 2048 */ -#define NUM_BUFFERS 16 - -struct openal_data { - const char *device_name; - ALCdevice *device; - ALCcontext *context; - struct timer *timer; - ALuint buffers[NUM_BUFFERS]; - int filled; - ALuint source; - ALenum format; - ALuint frequency; -}; - -static inline GQuark -openal_output_quark(void) -{ - return g_quark_from_static_string("openal_output"); -} - -static ALenum -openal_audio_format(struct audio_format *audio_format) -{ - switch (audio_format->format) { - case SAMPLE_FORMAT_S16: - if (audio_format->channels == 2) - return AL_FORMAT_STEREO16; - if (audio_format->channels == 1) - return AL_FORMAT_MONO16; - break; - - case SAMPLE_FORMAT_S8: - if (audio_format->channels == 2) - return AL_FORMAT_STEREO8; - if (audio_format->channels == 1) - return AL_FORMAT_MONO8; - break; - - default: - /* fall back to 16 bit */ - audio_format->format = SAMPLE_FORMAT_S16; - if (audio_format->channels == 2) - return AL_FORMAT_STEREO16; - if (audio_format->channels == 1) - return AL_FORMAT_MONO16; - break; - } - - return 0; -} - -static bool -openal_setup_context(struct openal_data *od, - GError **error) -{ - od->device = alcOpenDevice(od->device_name); - - if (od->device == NULL) { - g_set_error(error, openal_output_quark(), 0, - "Error opening OpenAL device \"%s\"\n", - od->device_name); - return false; - } - - od->context = alcCreateContext(od->device, NULL); - - if (od->context == NULL) { - g_set_error(error, openal_output_quark(), 0, - "Error creating context for \"%s\"\n", - od->device_name); - alcCloseDevice(od->device); - return false; - } - - return true; -} - -static void -openal_unqueue_buffers(struct openal_data *od) -{ - ALint num; - ALuint buffer; - - alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); - - while (num--) { - alSourceUnqueueBuffers(od->source, 1, &buffer); - } -} - -static void * -openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) -{ - const char *device_name = config_get_block_string(param, "device", NULL); - struct openal_data *od; - - if (device_name == NULL) { - device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); - } - - od = g_new(struct openal_data, 1); - od->device_name = device_name; - - return od; -} - -static void -openal_finish(void *data) -{ - struct openal_data *od = data; - - g_free(od); -} - -static bool -openal_open(void *data, struct audio_format *audio_format, - GError **error) -{ - struct openal_data *od = data; - - od->format = openal_audio_format(audio_format); - - if (!od->format) { - struct audio_format_string s; - g_set_error(error, openal_output_quark(), 0, - "Unsupported audio format: %s", - audio_format_to_string(audio_format, &s)); - return false; - } - - if (!openal_setup_context(od, error)) { - return false; - } - - alcMakeContextCurrent(od->context); - alGenBuffers(NUM_BUFFERS, od->buffers); - - if (alGetError() != AL_NO_ERROR) { - g_set_error(error, openal_output_quark(), 0, - "Failed to generate buffers"); - return false; - } - - alGenSources(1, &od->source); - - if (alGetError() != AL_NO_ERROR) { - g_set_error(error, openal_output_quark(), 0, - "Failed to generate source"); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - return false; - } - - od->filled = 0; - od->timer = timer_new(audio_format); - od->frequency = audio_format->sample_rate; - - return true; -} - -static void -openal_close(void *data) -{ - struct openal_data *od = data; - - timer_free(od->timer); - alcMakeContextCurrent(od->context); - alDeleteSources(1, &od->source); - alDeleteBuffers(NUM_BUFFERS, od->buffers); - alcDestroyContext(od->context); - alcCloseDevice(od->device); -} - -static size_t -openal_play(void *data, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct openal_data *od = data; - ALuint buffer; - ALint num, state; - - if (alcGetCurrentContext() != od->context) { - alcMakeContextCurrent(od->context); - } - - alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); - - if (od->filled < NUM_BUFFERS) { - /* fill all buffers */ - buffer = od->buffers[od->filled]; - od->filled++; - } else { - /* wait for processed buffer */ - while (num < 1) { - if (!od->timer->started) { - timer_start(od->timer); - } else { - timer_sync(od->timer); - } - - timer_add(od->timer, size); - - alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); - } - - alSourceUnqueueBuffers(od->source, 1, &buffer); - } - - alBufferData(buffer, od->format, chunk, size, od->frequency); - alSourceQueueBuffers(od->source, 1, &buffer); - alGetSourcei(od->source, AL_SOURCE_STATE, &state); - - if (state != AL_PLAYING) { - alSourcePlay(od->source); - } - - return size; -} - -static void -openal_cancel(void *data) -{ - struct openal_data *od = data; - - od->filled = 0; - alcMakeContextCurrent(od->context); - alSourceStop(od->source); - openal_unqueue_buffers(od); -} - -const struct audio_output_plugin openal_output_plugin = { - .name = "openal", - .init = openal_init, - .finish = openal_finish, - .open = openal_open, - .close = openal_close, - .play = openal_play, - .cancel = openal_cancel, -}; diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c new file mode 100644 index 000000000..d7df594d3 --- /dev/null +++ b/src/output/oss_output_plugin.c @@ -0,0 +1,684 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "mixer_list.h" +#include "fd_util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "oss" + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +/* We got bug reports from FreeBSD users who said that the two 24 bit + formats generate white noise on FreeBSD, but 32 bit works. This is + a workaround until we know what exactly is expected by the kernel + audio drivers. */ +#ifndef __linux__ +#undef AFMT_S24_PACKED +#undef AFMT_S24_NE +#endif + +struct oss_data { + int fd; + const char *device; + + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + struct audio_format audio_format; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_output_quark(void) +{ + return g_quark_from_static_string("oss_output"); +} + +static struct oss_data * +oss_data_new(void) +{ + struct oss_data *ret = g_new(struct oss_data, 1); + + ret->device = NULL; + ret->fd = -1; + + return ret; +} + +static void +oss_data_free(struct oss_data *od) +{ + g_free(od); +} + +enum oss_stat { + OSS_STAT_NO_ERROR = 0, + OSS_STAT_NOT_CHAR_DEV = -1, + OSS_STAT_NO_PERMS = -2, + OSS_STAT_DOESN_T_EXIST = -3, + OSS_STAT_OTHER = -4, +}; + +static enum oss_stat +oss_stat_device(const char *device, int *errno_r) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *errno_r = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return OSS_STAT_NO_ERROR; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static bool +oss_output_test_default_device(void) +{ + int fd, i; + + for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { + close(fd); + return true; + } + g_warning("Error opening OSS device \"%s\": %s\n", + default_devices[i], strerror(errno)); + } + + return false; +} + +static void * +oss_open_default(GError **error) +{ + int i; + int err[G_N_ELEMENTS(default_devices)]; + enum oss_stat ret[G_N_ELEMENTS(default_devices)]; + + for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + ret[i] = oss_stat_device(default_devices[i], &err[i]); + if (ret[i] == OSS_STAT_NO_ERROR) { + struct oss_data *od = oss_data_new(); + od->device = default_devices[i]; + return od; + } + } + + for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_NO_ERROR: + /* never reached */ + break; + case OSS_STAT_DOESN_T_EXIST: + g_warning("%s not found\n", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + g_warning("%s is not a character device\n", dev); + break; + case OSS_STAT_NO_PERMS: + g_warning("%s: permission denied\n", dev); + break; + case OSS_STAT_OTHER: + g_warning("Error accessing %s: %s\n", + dev, strerror(err[i])); + } + } + + g_set_error(error, oss_output_quark(), 0, + "error trying to open default OSS device"); + return NULL; +} + +static void * +oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + 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(); + od->device = device; + return od; + } + + return oss_open_default(error); +} + +static void +oss_output_finish(void *data) +{ + struct oss_data *od = data; + + oss_data_free(od); +} + +static void +oss_close(struct oss_data *od) +{ + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, GError **error_r) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(error_r == NULL || *error_r == NULL); + + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; + + if (errno == EINVAL) + return UNSUPPORTED; + + g_set_error(error_r, oss_output_quark(), errno, + "%s: %s", msg, g_strerror(errno)); + return ERROR; +} + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, GError **error_r) +{ + return oss_try_ioctl_r(fd, request, &value, msg, error_r); +} + +/** + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. + */ +static bool +oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) +{ + const char *const msg = "Failed to set channel count"; + int channels = audio_format->channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format->channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format->sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format->sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return AFMT_QUERY; + + case SAMPLE_FORMAT_S8: + return AFMT_S8; + + case SAMPLE_FORMAT_S16: + return AFMT_S16_NE; + + case SAMPLE_FORMAT_S24: +#ifdef AFMT_S24_PACKED + return AFMT_S24_PACKED; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S32: +#ifdef AFMT_S32_NE + return AFMT_S32_NE; +#else + return AFMT_QUERY; +#endif + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + */ +static enum sample_format +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SAMPLE_FORMAT_S8; + + case AFMT_S16_NE: + return SAMPLE_FORMAT_S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SAMPLE_FORMAT_S24; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SAMPLE_FORMAT_S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SAMPLE_FORMAT_S32; +#endif + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample format"; + int oss_format = sample_format_to_oss(audio_format->format); + enum oss_setup_result result = oss_format != AFMT_QUERY + ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r) + : UNSUPPORTED; + enum sample_format mpd_format; + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + +#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: + return false; + + case UNSUPPORTED: + break; + } + + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format->format) + /* don't try that again */ + continue; + + oss_format = sample_format_to_oss(mpd_format); + if (oss_format == AFMT_QUERY) + /* not supported by this OSS version */ + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r); + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + +#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: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Sets up the OSS device which was opened before. + */ +static bool +oss_setup(struct oss_data *od, struct audio_format *audio_format, + GError **error_r) +{ + return oss_setup_channels(od->fd, audio_format, error_r) && + oss_setup_sample_rate(od->fd, audio_format, error_r) && + oss_setup_sample_format(od->fd, audio_format, error_r); +} + +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(struct oss_data *od, GError **error_r) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error_r, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, strerror(errno)); + return false; + } + + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY); + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + sample_format_to_oss(od->audio_format.format), + msg3, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg3); + return false; + } + + return true; +} + +static bool +oss_output_open(void *data, struct audio_format *audio_format, GError **error) +{ + struct oss_data *od = data; + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, strerror(errno)); + return false; + } + + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } + + od->audio_format = *audio_format; + return true; +} + +static void +oss_output_close(void *data) +{ + struct oss_data *od = data; + + oss_close(od); +} + +static void +oss_output_cancel(void *data) +{ + struct oss_data *od = data; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static size_t +oss_output_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct oss_data *od = data; + ssize_t ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && !oss_reopen(od, error)) + return 0; + + while (true) { + ret = write(od->fd, chunk, size); + if (ret > 0) + return (size_t)ret; + + if (ret < 0 && errno != EINTR) { + g_set_error(error, oss_output_quark(), errno, + "Write error on %s: %s", + od->device, strerror(errno)); + return 0; + } + } +} + +const struct audio_output_plugin oss_output_plugin = { + .name = "oss", + .test_default_device = oss_output_test_default_device, + .init = oss_output_init, + .finish = oss_output_finish, + .open = oss_output_open, + .close = oss_output_close, + .play = oss_output_play, + .cancel = oss_output_cancel, + + .mixer_plugin = &oss_mixer_plugin, +}; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c deleted file mode 100644 index d7df594d3..000000000 --- a/src/output/oss_plugin.c +++ /dev/null @@ -1,684 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "oss" - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -/* We got bug reports from FreeBSD users who said that the two 24 bit - formats generate white noise on FreeBSD, but 32 bit works. This is - a workaround until we know what exactly is expected by the kernel - audio drivers. */ -#ifndef __linux__ -#undef AFMT_S24_PACKED -#undef AFMT_S24_NE -#endif - -struct oss_data { - int fd; - const char *device; - - /** - * The current input audio format. This is needed to reopen - * the device after cancel(). - */ - struct audio_format audio_format; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_output_quark(void) -{ - return g_quark_from_static_string("oss_output"); -} - -static struct oss_data * -oss_data_new(void) -{ - struct oss_data *ret = g_new(struct oss_data, 1); - - ret->device = NULL; - ret->fd = -1; - - return ret; -} - -static void -oss_data_free(struct oss_data *od) -{ - g_free(od); -} - -enum oss_stat { - OSS_STAT_NO_ERROR = 0, - OSS_STAT_NOT_CHAR_DEV = -1, - OSS_STAT_NO_PERMS = -2, - OSS_STAT_DOESN_T_EXIST = -3, - OSS_STAT_OTHER = -4, -}; - -static enum oss_stat -oss_stat_device(const char *device, int *errno_r) -{ - struct stat st; - - if (0 == stat(device, &st)) { - if (!S_ISCHR(st.st_mode)) { - return OSS_STAT_NOT_CHAR_DEV; - } - } else { - *errno_r = errno; - - switch (errno) { - case ENOENT: - case ENOTDIR: - return OSS_STAT_DOESN_T_EXIST; - case EACCES: - return OSS_STAT_NO_PERMS; - default: - return OSS_STAT_OTHER; - } - } - - return OSS_STAT_NO_ERROR; -} - -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; - -static bool -oss_output_test_default_device(void) -{ - int fd, i; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - fd = open_cloexec(default_devices[i], O_WRONLY, 0); - - if (fd >= 0) { - close(fd); - return true; - } - g_warning("Error opening OSS device \"%s\": %s\n", - default_devices[i], strerror(errno)); - } - - return false; -} - -static void * -oss_open_default(GError **error) -{ - int i; - int err[G_N_ELEMENTS(default_devices)]; - enum oss_stat ret[G_N_ELEMENTS(default_devices)]; - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - ret[i] = oss_stat_device(default_devices[i], &err[i]); - if (ret[i] == OSS_STAT_NO_ERROR) { - struct oss_data *od = oss_data_new(); - od->device = default_devices[i]; - return od; - } - } - - for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - const char *dev = default_devices[i]; - switch(ret[i]) { - case OSS_STAT_NO_ERROR: - /* never reached */ - break; - case OSS_STAT_DOESN_T_EXIST: - g_warning("%s not found\n", dev); - break; - case OSS_STAT_NOT_CHAR_DEV: - g_warning("%s is not a character device\n", dev); - break; - case OSS_STAT_NO_PERMS: - g_warning("%s: permission denied\n", dev); - break; - case OSS_STAT_OTHER: - g_warning("Error accessing %s: %s\n", - dev, strerror(err[i])); - } - } - - g_set_error(error, oss_output_quark(), 0, - "error trying to open default OSS device"); - return NULL; -} - -static void * -oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - 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(); - od->device = device; - return od; - } - - return oss_open_default(error); -} - -static void -oss_output_finish(void *data) -{ - struct oss_data *od = data; - - oss_data_free(od); -} - -static void -oss_close(struct oss_data *od) -{ - if (od->fd >= 0) - close(od->fd); - od->fd = -1; -} - -/** - * A tri-state type for oss_try_ioctl(). - */ -enum oss_setup_result { - SUCCESS, - ERROR, - UNSUPPORTED, -}; - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. - */ -static enum oss_setup_result -oss_try_ioctl_r(int fd, unsigned long request, int *value_r, - const char *msg, GError **error_r) -{ - assert(fd >= 0); - assert(value_r != NULL); - assert(msg != NULL); - assert(error_r == NULL || *error_r == NULL); - - int ret = ioctl(fd, request, value_r); - if (ret >= 0) - return SUCCESS; - - if (errno == EINVAL) - return UNSUPPORTED; - - g_set_error(error_r, oss_output_quark(), errno, - "%s: %s", msg, g_strerror(errno)); - return ERROR; -} - -/** - * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is - * returned. If the parameter is not supported, UNSUPPORTED is - * returned. Any other failure returns ERROR and allocates a GError. - */ -static enum oss_setup_result -oss_try_ioctl(int fd, unsigned long request, int value, - const char *msg, GError **error_r) -{ - return oss_try_ioctl_r(fd, request, &value, msg, error_r); -} - -/** - * Set up the channel number, and attempts to find alternatives if the - * specified number is not supported. - */ -static bool -oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) -{ - const char *const msg = "Failed to set channel count"; - int channels = audio_format->channels; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format->channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - for (unsigned i = 1; i < 2; ++i) { - if (i == audio_format->channels) - /* don't try that again */ - continue; - - channels = i; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_channel_count(channels)) - break; - - audio_format->channels = channels; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); - return false; -} - -/** - * Set up the sample rate, and attempts to find alternatives if the - * specified sample rate is not supported. - */ -static bool -oss_setup_sample_rate(int fd, struct audio_format *audio_format, - GError **error_r) -{ - const char *const msg = "Failed to set sample rate"; - int sample_rate = audio_format->sample_rate; - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format->sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - static const int sample_rates[] = { 48000, 44100, 0 }; - for (unsigned i = 0; sample_rates[i] != 0; ++i) { - sample_rate = sample_rates[i]; - if (sample_rate == (int)audio_format->sample_rate) - continue; - - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, - msg, error_r); - switch (result) { - case SUCCESS: - if (!audio_valid_sample_rate(sample_rate)) - break; - - audio_format->sample_rate = sample_rate; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); - return false; -} - -/** - * Convert a MPD sample format to its OSS counterpart. Returns - * AFMT_QUERY if there is no direct counterpart. - */ -static int -sample_format_to_oss(enum sample_format format) -{ - switch (format) { - case SAMPLE_FORMAT_UNDEFINED: - return AFMT_QUERY; - - case SAMPLE_FORMAT_S8: - return AFMT_S8; - - case SAMPLE_FORMAT_S16: - return AFMT_S16_NE; - - case SAMPLE_FORMAT_S24: -#ifdef AFMT_S24_PACKED - return AFMT_S24_PACKED; -#else - return AFMT_QUERY; -#endif - - case SAMPLE_FORMAT_S24_P32: -#ifdef AFMT_S24_NE - return AFMT_S24_NE; -#else - return AFMT_QUERY; -#endif - - case SAMPLE_FORMAT_S32: -#ifdef AFMT_S32_NE - return AFMT_S32_NE; -#else - return AFMT_QUERY; -#endif - } - - return AFMT_QUERY; -} - -/** - * Convert an OSS sample format to its MPD counterpart. Returns - * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. - */ -static enum sample_format -sample_format_from_oss(int format) -{ - switch (format) { - case AFMT_S8: - return SAMPLE_FORMAT_S8; - - case AFMT_S16_NE: - return SAMPLE_FORMAT_S16; - -#ifdef AFMT_S24_PACKED - case AFMT_S24_PACKED: - return SAMPLE_FORMAT_S24; -#endif - -#ifdef AFMT_S24_NE - case AFMT_S24_NE: - return SAMPLE_FORMAT_S24_P32; -#endif - -#ifdef AFMT_S32_NE - case AFMT_S32_NE: - return SAMPLE_FORMAT_S32; -#endif - - default: - return SAMPLE_FORMAT_UNDEFINED; - } -} - -/** - * Set up the sample format, and attempts to find alternatives if the - * specified format is not supported. - */ -static bool -oss_setup_sample_format(int fd, struct audio_format *audio_format, - GError **error_r) -{ - const char *const msg = "Failed to set sample format"; - int oss_format = sample_format_to_oss(audio_format->format); - enum oss_setup_result result = oss_format != AFMT_QUERY - ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, msg, error_r) - : UNSUPPORTED; - enum sample_format mpd_format; - switch (result) { - case SUCCESS: - mpd_format = sample_format_from_oss(oss_format); - if (mpd_format == SAMPLE_FORMAT_UNDEFINED) - break; - - audio_format->format = mpd_format; - -#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: - return false; - - case UNSUPPORTED: - break; - } - - /* the requested sample format is not available - probe for - other formats supported by MPD */ - - static const enum sample_format sample_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S24, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED /* sentinel */ - }; - - for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { - mpd_format = sample_formats[i]; - if (mpd_format == audio_format->format) - /* don't try that again */ - continue; - - oss_format = sample_format_to_oss(mpd_format); - if (oss_format == AFMT_QUERY) - /* not supported by this OSS version */ - continue; - - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, msg, error_r); - switch (result) { - case SUCCESS: - mpd_format = sample_format_from_oss(oss_format); - if (mpd_format == SAMPLE_FORMAT_UNDEFINED) - break; - - audio_format->format = mpd_format; - -#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: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); - return false; -} - -/** - * Sets up the OSS device which was opened before. - */ -static bool -oss_setup(struct oss_data *od, struct audio_format *audio_format, - GError **error_r) -{ - return oss_setup_channels(od->fd, audio_format, error_r) && - oss_setup_sample_rate(od->fd, audio_format, error_r) && - oss_setup_sample_format(od->fd, audio_format, error_r); -} - -/** - * Reopen the device with the saved audio_format, without any probing. - */ -static bool -oss_reopen(struct oss_data *od, GError **error_r) -{ - assert(od->fd < 0); - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error_r, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, strerror(errno)); - return false; - } - - enum oss_setup_result result; - - const char *const msg1 = "Failed to set channel count"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, - od->audio_format.channels, msg1, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg1); - return false; - } - - const char *const msg2 = "Failed to set sample rate"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, - od->audio_format.sample_rate, msg2, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg2); - return false; - } - - const char *const msg3 = "Failed to set sample format"; - assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY); - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - sample_format_to_oss(od->audio_format.format), - msg3, error_r); - if (result != SUCCESS) { - oss_close(od); - if (result == UNSUPPORTED) - g_set_error(error_r, oss_output_quark(), EINVAL, - "%s", msg3); - return false; - } - - return true; -} - -static bool -oss_output_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct oss_data *od = data; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, strerror(errno)); - return false; - } - - if (!oss_setup(od, audio_format, error)) { - oss_close(od); - return false; - } - - od->audio_format = *audio_format; - return true; -} - -static void -oss_output_close(void *data) -{ - struct oss_data *od = data; - - oss_close(od); -} - -static void -oss_output_cancel(void *data) -{ - struct oss_data *od = data; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); - } -} - -static size_t -oss_output_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct oss_data *od = data; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) - return 0; - - while (true) { - ret = write(od->fd, chunk, size); - if (ret > 0) - return (size_t)ret; - - if (ret < 0 && errno != EINTR) { - g_set_error(error, oss_output_quark(), errno, - "Write error on %s: %s", - od->device, strerror(errno)); - return 0; - } - } -} - -const struct audio_output_plugin oss_output_plugin = { - .name = "oss", - .test_default_device = oss_output_test_default_device, - .init = oss_output_init, - .finish = oss_output_finish, - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, -}; diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c new file mode 100644 index 000000000..8091660ab --- /dev/null +++ b/src/output/osx_output_plugin.c @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "osx" + +struct osx_output { + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + + AudioUnit au; + GMutex *mutex; + GCond *condition; + char *buffer; + size_t buffer_size; + size_t pos; + size_t len; +}; + +/** + * 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 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); + + osx_output_configure(oo, param); + oo->mutex = g_mutex_new(); + oo->condition = g_cond_new(); + + oo->pos = 0; + oo->len = 0; + oo->buffer = NULL; + oo->buffer_size = 0; + + return oo; +} + +static void osx_output_finish(void *data) +{ + struct osx_output *od = data; + + g_free(od->buffer); + 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); + od->len = 0; + 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); +} + +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; + size_t bytes_to_copy; + size_t trailer_length; + size_t dest_pos = 0; + + g_mutex_lock(od->mutex); + + bytes_to_copy = MIN(od->len, buffer_size); + buffer_size = bytes_to_copy; + od->len -= bytes_to_copy; + + trailer_length = od->buffer_size - od->pos; + if (bytes_to_copy > trailer_length) { + memcpy((unsigned char*)buffer->mData + dest_pos, + od->buffer + od->pos, trailer_length); + od->pos = 0; + dest_pos += trailer_length; + bytes_to_copy -= trailer_length; + } + + memcpy((unsigned char*)buffer->mData + dest_pos, + od->buffer + od->pos, bytes_to_copy); + od->pos += bytes_to_copy; + + if (od->pos >= od->buffer_size) + od->pos = 0; + + g_cond_signal(od->condition); + g_mutex_unlock(od->mutex); + + buffer->mDataByteSize = buffer_size; + + if (!buffer_size) { + g_usleep(1000); + } + + return 0; +} + +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 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 = od->component_subtype; + 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(), status, + "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(), status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + if (!osx_output_set_device(od, error)) + 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(), result, + "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(), result, + "Unable to set format on OS X device"); + return false; + } + + /* create a buffer of 1s */ + od->buffer_size = (audio_format->sample_rate) * + audio_format_frame_size(audio_format); + od->buffer = g_realloc(od->buffer, od->buffer_size); + + od->pos = 0; + od->len = 0; + + status = AudioOutputUnitStart(od->au); + if (status != 0) { + 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(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct osx_output *od = data; + size_t start, nbytes; + + g_mutex_lock(od->mutex); + + while (od->len >= od->buffer_size) + /* wait for some free space in the buffer */ + g_cond_wait(od->condition, od->mutex); + + start = od->pos + od->len; + if (start >= od->buffer_size) + start -= od->buffer_size; + + nbytes = start < od->pos + ? od->pos - start + : od->buffer_size - start; + + assert(nbytes > 0); + + if (nbytes > size) + nbytes = size; + + memcpy(od->buffer + start, chunk, nbytes); + od->len += nbytes; + + g_mutex_unlock(od->mutex); + + return nbytes; +} + +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/osx_plugin.c b/src/output/osx_plugin.c deleted file mode 100644 index 8091660ab..000000000 --- a/src/output/osx_plugin.c +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "osx" - -struct osx_output { - /* configuration settings */ - OSType component_subtype; - /* only applicable with kAudioUnitSubType_HALOutput */ - const char *device_name; - - AudioUnit au; - GMutex *mutex; - GCond *condition; - char *buffer; - size_t buffer_size; - size_t pos; - size_t len; -}; - -/** - * 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 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); - - osx_output_configure(oo, param); - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); - - oo->pos = 0; - oo->len = 0; - oo->buffer = NULL; - oo->buffer_size = 0; - - return oo; -} - -static void osx_output_finish(void *data) -{ - struct osx_output *od = data; - - g_free(od->buffer); - 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); - od->len = 0; - 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); -} - -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; - size_t bytes_to_copy; - size_t trailer_length; - size_t dest_pos = 0; - - g_mutex_lock(od->mutex); - - bytes_to_copy = MIN(od->len, buffer_size); - buffer_size = bytes_to_copy; - od->len -= bytes_to_copy; - - trailer_length = od->buffer_size - od->pos; - if (bytes_to_copy > trailer_length) { - memcpy((unsigned char*)buffer->mData + dest_pos, - od->buffer + od->pos, trailer_length); - od->pos = 0; - dest_pos += trailer_length; - bytes_to_copy -= trailer_length; - } - - memcpy((unsigned char*)buffer->mData + dest_pos, - od->buffer + od->pos, bytes_to_copy); - od->pos += bytes_to_copy; - - if (od->pos >= od->buffer_size) - od->pos = 0; - - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); - - buffer->mDataByteSize = buffer_size; - - if (!buffer_size) { - g_usleep(1000); - } - - return 0; -} - -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 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 = od->component_subtype; - 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(), status, - "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(), status, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - if (!osx_output_set_device(od, error)) - 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(), result, - "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(), result, - "Unable to set format on OS X device"); - return false; - } - - /* create a buffer of 1s */ - od->buffer_size = (audio_format->sample_rate) * - audio_format_frame_size(audio_format); - od->buffer = g_realloc(od->buffer, od->buffer_size); - - od->pos = 0; - od->len = 0; - - status = AudioOutputUnitStart(od->au); - if (status != 0) { - 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(void *data, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct osx_output *od = data; - size_t start, nbytes; - - g_mutex_lock(od->mutex); - - while (od->len >= od->buffer_size) - /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); - - start = od->pos + od->len; - if (start >= od->buffer_size) - start -= od->buffer_size; - - nbytes = start < od->pos - ? od->pos - start - : od->buffer_size - start; - - assert(nbytes > 0); - - if (nbytes > size) - nbytes = size; - - memcpy(od->buffer + start, chunk, nbytes); - od->len += nbytes; - - g_mutex_unlock(od->mutex); - - return nbytes; -} - -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/roar_output_plugin.c b/src/output/roar_output_plugin.c new file mode 100644 index 000000000..f9d44a3d8 --- /dev/null +++ b/src/output/roar_output_plugin.c @@ -0,0 +1,324 @@ +/* + * 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 "output_api.h" +#include "mixer_list.h" +#include "roar_output_plugin.h" + +#include +#include +#include +#include +#include +#include + + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +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"); + char *role = config_dup_block_string(param, "role", "music"); + if (role != NULL) + { + self->role = roar_str2role(role); + g_free(role); + } + else + self->role = ROAR_ROLE_MUSIC; +} + +static void * +roar_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + GMutex *lock = g_mutex_new(); + + roar_t * self = roar_mm_calloc(1, sizeof(*self)); + if (self == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Failed to allocate memory"); + return NULL; + } + + self->lock = lock; + self->err = ROAR_ERROR_NONE; + roar_configure(self, param); + return self; +} + +static void +roar_close(void *data) +{ + roar_t * self = data; + 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_finish(void *data) +{ + roar_t * self = data; + + g_free(self->host); + g_free(self->name); + g_mutex_free(self->lock); + + roar_mm_free(data); +} + +static bool +roar_open(void *data, struct audio_format *audio_format, GError **error) +{ + roar_t * self = data; + 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; + } + + self->info.rate = audio_format->sample_rate; + self->info.channels = audio_format->channels; + self->info.codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) + { + case SAMPLE_FORMAT_S8: + self->info.bits = 8; + break; + case SAMPLE_FORMAT_S16: + self->info.bits = 16; + break; + case SAMPLE_FORMAT_S24: + self->info.bits = 24; + break; + case SAMPLE_FORMAT_S24_P32: + self->info.bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + case SAMPLE_FORMAT_S32: + self->info.bits = 32; + break; + default: + self->info.bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + } + audio_format->reverse_endian = 0; + + 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_cancel(void *data) +{ + roar_t * self = data; + + g_mutex_lock(self->lock); + if (self->vss != NULL) + { + 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) + { + roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, &(self->err)); + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; + } + } + g_mutex_unlock(self->lock); +} + +static size_t +roar_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct roar * self = data; + 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(void *data, const struct tag *meta) +{ + struct roar * self = data; + + 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_plugin.c b/src/output/roar_plugin.c deleted file mode 100644 index f9d44a3d8..000000000 --- a/src/output/roar_plugin.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2003-2010 The Music Player Daemon Project - * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft - * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "mixer_list.h" -#include "roar_output_plugin.h" - -#include -#include -#include -#include -#include -#include - - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "roaraudio" - -static inline GQuark -roar_output_quark(void) -{ - return g_quark_from_static_string("roar_output"); -} - -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"); - char *role = config_dup_block_string(param, "role", "music"); - if (role != NULL) - { - self->role = roar_str2role(role); - g_free(role); - } - else - self->role = ROAR_ROLE_MUSIC; -} - -static void * -roar_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) -{ - GMutex *lock = g_mutex_new(); - - roar_t * self = roar_mm_calloc(1, sizeof(*self)); - if (self == NULL) - { - g_set_error(error, roar_output_quark(), 0, "Failed to allocate memory"); - return NULL; - } - - self->lock = lock; - self->err = ROAR_ERROR_NONE; - roar_configure(self, param); - return self; -} - -static void -roar_close(void *data) -{ - roar_t * self = data; - 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_finish(void *data) -{ - roar_t * self = data; - - g_free(self->host); - g_free(self->name); - g_mutex_free(self->lock); - - roar_mm_free(data); -} - -static bool -roar_open(void *data, struct audio_format *audio_format, GError **error) -{ - roar_t * self = data; - 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; - } - - self->info.rate = audio_format->sample_rate; - self->info.channels = audio_format->channels; - self->info.codec = ROAR_CODEC_PCM_S; - - switch (audio_format->format) - { - case SAMPLE_FORMAT_S8: - self->info.bits = 8; - break; - case SAMPLE_FORMAT_S16: - self->info.bits = 16; - break; - case SAMPLE_FORMAT_S24: - self->info.bits = 24; - break; - case SAMPLE_FORMAT_S24_P32: - self->info.bits = 32; - audio_format->format = SAMPLE_FORMAT_S32; - break; - case SAMPLE_FORMAT_S32: - self->info.bits = 32; - break; - default: - self->info.bits = 16; - audio_format->format = SAMPLE_FORMAT_S16; - } - audio_format->reverse_endian = 0; - - 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_cancel(void *data) -{ - roar_t * self = data; - - g_mutex_lock(self->lock); - if (self->vss != NULL) - { - 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) - { - roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, &(self->err)); - roar_vs_role(vss, self->role, &(self->err)); - self->vss = vss; - self->alive = true; - } - } - g_mutex_unlock(self->lock); -} - -static size_t -roar_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct roar * self = data; - 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(void *data, const struct tag *meta) -{ - struct roar * self = data; - - 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/shout_output_plugin.c b/src/output/shout_output_plugin.c new file mode 100644 index 000000000..80adf1638 --- /dev/null +++ b/src/output/shout_output_plugin.c @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "mpd_error.h" + +#include +#include + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "shout" + +#define DEFAULT_CONN_TIMEOUT 2 + +struct shout_buffer { + unsigned char data[32768]; + size_t len; +}; + +struct shout_data { + shout_t *shout_conn; + shout_metadata_t *shout_meta; + + struct encoder *encoder; + + float quality; + int bitrate; + + int timeout; + + struct shout_buffer buf; +}; + +static int shout_init_count; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +shout_output_quark(void) +{ + return g_quark_from_static_string("shout_output"); +} + +static const struct encoder_plugin * +shout_encoder_plugin_get(const char *name) +{ + if (strcmp(name, "ogg") == 0) + name = "vorbis"; + else if (strcmp(name, "mp3") == 0) + name = "lame"; + + return encoder_plugin_get(name); +} + +static struct shout_data *new_shout_data(void) +{ + struct shout_data *ret = g_new(struct shout_data, 1); + + ret->shout_conn = shout_new(); + ret->shout_meta = shout_metadata_new(); + ret->bitrate = -1; + ret->quality = -2.0; + ret->timeout = DEFAULT_CONN_TIMEOUT; + + return ret; +} + +static void free_shout_data(struct shout_data *sd) +{ + if (sd->shout_meta) + shout_metadata_free(sd->shout_meta); + if (sd->shout_conn) + shout_free(sd->shout_conn); + + g_free(sd); +} + +#define check_block_param(name) { \ + block_param = config_get_block_param(param, name); \ + if (!block_param) { \ + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ + } \ + } + +static void * +my_shout_init_driver(const struct audio_format *audio_format, + const struct config_param *param, + GError **error) +{ + struct shout_data *sd; + char *test; + unsigned port; + char *host; + char *mount; + char *passwd; + const char *encoding; + const struct encoder_plugin *encoder_plugin; + unsigned shout_format; + unsigned protocol; + const char *user; + char *name; + const char *value; + const struct block_param *block_param; + int public; + + if (audio_format == NULL || + !audio_format_fully_defined(audio_format)) { + g_set_error(error, shout_output_quark(), 0, + "Need full audio format specification"); + return NULL; + } + + sd = new_shout_data(); + + if (shout_init_count == 0) + shout_init(); + + shout_init_count++; + + check_block_param("host"); + host = block_param->value; + + check_block_param("mount"); + mount = block_param->value; + + port = config_get_block_unsigned(param, "port", 0); + if (port == 0) { + g_set_error(error, shout_output_quark(), 0, + "shout port must be configured"); + goto failure; + } + + check_block_param("password"); + passwd = block_param->value; + + check_block_param("name"); + name = block_param->value; + + public = config_get_block_bool(param, "public", false); + + user = config_get_block_string(param, "user", "source"); + + value = config_get_block_string(param, "quality", NULL); + if (value != NULL) { + sd->quality = strtod(value, &test); + + if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { + g_set_error(error, shout_output_quark(), 0, + "shout quality \"%s\" is not a number in the " + "range -1 to 10, line %i", + value, param->line); + goto failure; + } + + if (config_get_block_string(param, "bitrate", NULL) != NULL) { + g_set_error(error, shout_output_quark(), 0, + "quality and bitrate are " + "both defined"); + goto failure; + } + } else { + value = config_get_block_string(param, "bitrate", NULL); + if (value == NULL) { + g_set_error(error, shout_output_quark(), 0, + "neither bitrate nor quality defined"); + goto failure; + } + + sd->bitrate = strtol(value, &test, 10); + + if (*test != '\0' || sd->bitrate <= 0) { + g_set_error(error, shout_output_quark(), 0, + "bitrate must be a positive integer"); + goto failure; + } + } + + encoding = config_get_block_string(param, "encoding", "ogg"); + encoder_plugin = shout_encoder_plugin_get(encoding); + if (encoder_plugin == NULL) { + g_set_error(error, shout_output_quark(), 0, + "couldn't find shout encoder plugin \"%s\"", + encoding); + goto failure; + } + + sd->encoder = encoder_init(encoder_plugin, param, error); + if (sd->encoder == NULL) + goto failure; + + if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) + shout_format = SHOUT_FORMAT_MP3; + else + shout_format = SHOUT_FORMAT_OGG; + + value = config_get_block_string(param, "protocol", NULL); + if (value != NULL) { + if (0 == strcmp(value, "shoutcast") && + 0 != strcmp(encoding, "mp3")) { + g_set_error(error, shout_output_quark(), 0, + "you cannot stream \"%s\" to shoutcast, use mp3", + encoding); + goto failure; + } else if (0 == strcmp(value, "shoutcast")) + protocol = SHOUT_PROTOCOL_ICY; + else if (0 == strcmp(value, "icecast1")) + protocol = SHOUT_PROTOCOL_XAUDIOCAST; + else if (0 == strcmp(value, "icecast2")) + protocol = SHOUT_PROTOCOL_HTTP; + else { + g_set_error(error, shout_output_quark(), 0, + "shout protocol \"%s\" is not \"shoutcast\" or " + "\"icecast1\"or \"icecast2\"", + value); + goto failure; + } + } else { + protocol = SHOUT_PROTOCOL_HTTP; + } + + if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS || + shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS || + shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS || + shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS || + shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS || + shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS || + shout_set_format(sd->shout_conn, shout_format) + != SHOUTERR_SUCCESS || + shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS || + shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + + /* optional paramters */ + sd->timeout = config_get_block_unsigned(param, "timeout", + DEFAULT_CONN_TIMEOUT); + + value = config_get_block_string(param, "genre", NULL); + if (value != NULL && shout_set_genre(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + + value = config_get_block_string(param, "description", NULL); + if (value != NULL && shout_set_description(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + + value = config_get_block_string(param, "url", NULL); + if (value != NULL && shout_set_url(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%u", audio_format->channels); + shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate); + + shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp); + + if (sd->quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", sd->quality); + shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", sd->bitrate); + shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE, + temp); + } + } + + return sd; + +failure: + free_shout_data(sd); + return NULL; +} + +static bool +handle_shout_error(struct shout_data *sd, int err, GError **error) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + g_set_error(error, shout_output_quark(), err, + "Lost shout connection to %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + + default: + g_set_error(error, shout_output_quark(), err, + "connection to %s:%i error: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } + + return true; +} + +static bool +write_page(struct shout_data *sd, GError **error) +{ + int err; + + assert(sd->encoder != NULL); + + sd->buf.len = encoder_read(sd->encoder, + sd->buf.data, sizeof(sd->buf.data)); + if (sd->buf.len == 0) + return true; + + err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len); + if (!handle_shout_error(sd, err, error)) + return false; + + return true; +} + +static void close_shout_conn(struct shout_data * sd) +{ + sd->buf.len = 0; + + if (sd->encoder != NULL) { + if (encoder_flush(sd->encoder, NULL)) + write_page(sd, NULL); + + encoder_close(sd->encoder); + } + + if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && + shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { + g_warning("problem closing connection to shout server: %s\n", + shout_get_error(sd->shout_conn)); + } +} + +static void my_shout_finish_driver(void *data) +{ + struct shout_data *sd = (struct shout_data *)data; + + encoder_finish(sd->encoder); + + free_shout_data(sd); + + shout_init_count--; + + if (shout_init_count == 0) + shout_shutdown(); +} + +static void my_shout_drop_buffered_audio(void *data) +{ + G_GNUC_UNUSED + struct shout_data *sd = (struct shout_data *)data; + + /* needs to be implemented for shout */ +} + +static void my_shout_close_device(void *data) +{ + struct shout_data *sd = (struct shout_data *)data; + + close_shout_conn(sd); +} + +static bool +shout_connect(struct shout_data *sd, GError **error) +{ + int state; + + state = shout_open(sd->shout_conn); + switch (state) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + return true; + + default: + g_set_error(error, shout_output_quark(), 0, + "problem opening connection to shout server %s:%i: %s", + shout_get_host(sd->shout_conn), + shout_get_port(sd->shout_conn), + shout_get_error(sd->shout_conn)); + return false; + } +} + +static bool +my_shout_open_device(void *data, struct audio_format *audio_format, + GError **error) +{ + struct shout_data *sd = (struct shout_data *)data; + bool ret; + + ret = shout_connect(sd, error); + if (!ret) + return false; + + sd->buf.len = 0; + + ret = encoder_open(sd->encoder, audio_format, error) && + write_page(sd, error); + if (!ret) { + shout_close(sd->shout_conn); + return false; + } + + return true; +} + +static unsigned +my_shout_delay(void *data) +{ + struct shout_data *sd = (struct shout_data *)data; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + +static size_t +my_shout_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct shout_data *sd = (struct shout_data *)data; + + return encoder_write(sd->encoder, chunk, size, error) && + write_page(sd, error) + ? size + : 0; +} + +static bool +my_shout_pause(void *data) +{ + static const char silence[1020]; + + return my_shout_play(data, silence, sizeof(silence), NULL); +} + +static void +shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) +{ + char artist[size]; + char title[size]; + + artist[0] = 0; + title[0] = 0; + + for (unsigned i = 0; i < tag->num_items; i++) { + switch (tag->items[i]->type) { + case TAG_ARTIST: + strncpy(artist, tag->items[i]->value, size); + break; + case TAG_TITLE: + strncpy(title, tag->items[i]->value, size); + break; + + default: + break; + } + } + + snprintf(dest, size, "%s - %s", artist, title); +} + +static void my_shout_set_tag(void *data, + const struct tag *tag) +{ + struct shout_data *sd = (struct shout_data *)data; + bool ret; + GError *error = NULL; + + if (sd->encoder->plugin->tag != NULL) { + /* encoder plugin supports stream tags */ + + ret = encoder_pre_tag(sd->encoder, &error); + if (!ret) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + ret = write_page(sd, NULL); + if (!ret) + return; + + ret = encoder_tag(sd->encoder, tag, &error); + if (!ret) { + g_warning("%s", error->message); + g_error_free(error); + } + } else { + /* no stream tag support: fall back to icy-metadata */ + char song[1024]; + + shout_tag_to_metadata(tag, song, sizeof(song)); + + shout_metadata_add(sd->shout_meta, "song", song); + if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, + sd->shout_meta)) { + g_warning("error setting shout metadata\n"); + } + } + + write_page(sd, NULL); +} + +const struct audio_output_plugin shoutPlugin = { + .name = "shout", + .init = my_shout_init_driver, + .finish = my_shout_finish_driver, + .open = my_shout_open_device, + .delay = my_shout_delay, + .play = my_shout_play, + .pause = my_shout_pause, + .cancel = my_shout_drop_buffered_audio, + .close = my_shout_close_device, + .send_tag = my_shout_set_tag, +}; diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c deleted file mode 100644 index 80adf1638..000000000 --- a/src/output/shout_plugin.c +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "mpd_error.h" - -#include -#include - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "shout" - -#define DEFAULT_CONN_TIMEOUT 2 - -struct shout_buffer { - unsigned char data[32768]; - size_t len; -}; - -struct shout_data { - shout_t *shout_conn; - shout_metadata_t *shout_meta; - - struct encoder *encoder; - - float quality; - int bitrate; - - int timeout; - - struct shout_buffer buf; -}; - -static int shout_init_count; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -shout_output_quark(void) -{ - return g_quark_from_static_string("shout_output"); -} - -static const struct encoder_plugin * -shout_encoder_plugin_get(const char *name) -{ - if (strcmp(name, "ogg") == 0) - name = "vorbis"; - else if (strcmp(name, "mp3") == 0) - name = "lame"; - - return encoder_plugin_get(name); -} - -static struct shout_data *new_shout_data(void) -{ - struct shout_data *ret = g_new(struct shout_data, 1); - - ret->shout_conn = shout_new(); - ret->shout_meta = shout_metadata_new(); - ret->bitrate = -1; - ret->quality = -2.0; - ret->timeout = DEFAULT_CONN_TIMEOUT; - - return ret; -} - -static void free_shout_data(struct shout_data *sd) -{ - if (sd->shout_meta) - shout_metadata_free(sd->shout_meta); - if (sd->shout_conn) - shout_free(sd->shout_conn); - - g_free(sd); -} - -#define check_block_param(name) { \ - block_param = config_get_block_param(param, name); \ - if (!block_param) { \ - MPD_ERROR("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ - } \ - } - -static void * -my_shout_init_driver(const struct audio_format *audio_format, - const struct config_param *param, - GError **error) -{ - struct shout_data *sd; - char *test; - unsigned port; - char *host; - char *mount; - char *passwd; - const char *encoding; - const struct encoder_plugin *encoder_plugin; - unsigned shout_format; - unsigned protocol; - const char *user; - char *name; - const char *value; - const struct block_param *block_param; - int public; - - if (audio_format == NULL || - !audio_format_fully_defined(audio_format)) { - g_set_error(error, shout_output_quark(), 0, - "Need full audio format specification"); - return NULL; - } - - sd = new_shout_data(); - - if (shout_init_count == 0) - shout_init(); - - shout_init_count++; - - check_block_param("host"); - host = block_param->value; - - check_block_param("mount"); - mount = block_param->value; - - port = config_get_block_unsigned(param, "port", 0); - if (port == 0) { - g_set_error(error, shout_output_quark(), 0, - "shout port must be configured"); - goto failure; - } - - check_block_param("password"); - passwd = block_param->value; - - check_block_param("name"); - name = block_param->value; - - public = config_get_block_bool(param, "public", false); - - user = config_get_block_string(param, "user", "source"); - - value = config_get_block_string(param, "quality", NULL); - if (value != NULL) { - sd->quality = strtod(value, &test); - - if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { - g_set_error(error, shout_output_quark(), 0, - "shout quality \"%s\" is not a number in the " - "range -1 to 10, line %i", - value, param->line); - goto failure; - } - - if (config_get_block_string(param, "bitrate", NULL) != NULL) { - g_set_error(error, shout_output_quark(), 0, - "quality and bitrate are " - "both defined"); - goto failure; - } - } else { - value = config_get_block_string(param, "bitrate", NULL); - if (value == NULL) { - g_set_error(error, shout_output_quark(), 0, - "neither bitrate nor quality defined"); - goto failure; - } - - sd->bitrate = strtol(value, &test, 10); - - if (*test != '\0' || sd->bitrate <= 0) { - g_set_error(error, shout_output_quark(), 0, - "bitrate must be a positive integer"); - goto failure; - } - } - - encoding = config_get_block_string(param, "encoding", "ogg"); - encoder_plugin = shout_encoder_plugin_get(encoding); - if (encoder_plugin == NULL) { - g_set_error(error, shout_output_quark(), 0, - "couldn't find shout encoder plugin \"%s\"", - encoding); - goto failure; - } - - sd->encoder = encoder_init(encoder_plugin, param, error); - if (sd->encoder == NULL) - goto failure; - - if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0) - shout_format = SHOUT_FORMAT_MP3; - else - shout_format = SHOUT_FORMAT_OGG; - - value = config_get_block_string(param, "protocol", NULL); - if (value != NULL) { - if (0 == strcmp(value, "shoutcast") && - 0 != strcmp(encoding, "mp3")) { - g_set_error(error, shout_output_quark(), 0, - "you cannot stream \"%s\" to shoutcast, use mp3", - encoding); - goto failure; - } else if (0 == strcmp(value, "shoutcast")) - protocol = SHOUT_PROTOCOL_ICY; - else if (0 == strcmp(value, "icecast1")) - protocol = SHOUT_PROTOCOL_XAUDIOCAST; - else if (0 == strcmp(value, "icecast2")) - protocol = SHOUT_PROTOCOL_HTTP; - else { - g_set_error(error, shout_output_quark(), 0, - "shout protocol \"%s\" is not \"shoutcast\" or " - "\"icecast1\"or \"icecast2\"", - value); - goto failure; - } - } else { - protocol = SHOUT_PROTOCOL_HTTP; - } - - if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS || - shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS || - shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS || - shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS || - shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS || - shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS || - shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS || - shout_set_format(sd->shout_conn, shout_format) - != SHOUTERR_SUCCESS || - shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS || - shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - /* optional paramters */ - sd->timeout = config_get_block_unsigned(param, "timeout", - DEFAULT_CONN_TIMEOUT); - - value = config_get_block_string(param, "genre", NULL); - if (value != NULL && shout_set_genre(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - value = config_get_block_string(param, "description", NULL); - if (value != NULL && shout_set_description(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - value = config_get_block_string(param, "url", NULL); - if (value != NULL && shout_set_url(sd->shout_conn, value)) { - g_set_error(error, shout_output_quark(), 0, - "%s", shout_get_error(sd->shout_conn)); - goto failure; - } - - { - char temp[11]; - memset(temp, 0, sizeof(temp)); - - snprintf(temp, sizeof(temp), "%u", audio_format->channels); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp); - - snprintf(temp, sizeof(temp), "%u", audio_format->sample_rate); - - shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp); - - if (sd->quality >= -1.0) { - snprintf(temp, sizeof(temp), "%2.2f", sd->quality); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY, - temp); - } else { - snprintf(temp, sizeof(temp), "%d", sd->bitrate); - shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE, - temp); - } - } - - return sd; - -failure: - free_shout_data(sd); - return NULL; -} - -static bool -handle_shout_error(struct shout_data *sd, int err, GError **error) -{ - switch (err) { - case SHOUTERR_SUCCESS: - break; - - case SHOUTERR_UNCONNECTED: - case SHOUTERR_SOCKET: - g_set_error(error, shout_output_quark(), err, - "Lost shout connection to %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - - default: - g_set_error(error, shout_output_quark(), err, - "connection to %s:%i error: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } - - return true; -} - -static bool -write_page(struct shout_data *sd, GError **error) -{ - int err; - - assert(sd->encoder != NULL); - - sd->buf.len = encoder_read(sd->encoder, - sd->buf.data, sizeof(sd->buf.data)); - if (sd->buf.len == 0) - return true; - - err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len); - if (!handle_shout_error(sd, err, error)) - return false; - - return true; -} - -static void close_shout_conn(struct shout_data * sd) -{ - sd->buf.len = 0; - - if (sd->encoder != NULL) { - if (encoder_flush(sd->encoder, NULL)) - write_page(sd, NULL); - - encoder_close(sd->encoder); - } - - if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED && - shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) { - g_warning("problem closing connection to shout server: %s\n", - shout_get_error(sd->shout_conn)); - } -} - -static void my_shout_finish_driver(void *data) -{ - struct shout_data *sd = (struct shout_data *)data; - - encoder_finish(sd->encoder); - - free_shout_data(sd); - - shout_init_count--; - - if (shout_init_count == 0) - shout_shutdown(); -} - -static void my_shout_drop_buffered_audio(void *data) -{ - G_GNUC_UNUSED - struct shout_data *sd = (struct shout_data *)data; - - /* needs to be implemented for shout */ -} - -static void my_shout_close_device(void *data) -{ - struct shout_data *sd = (struct shout_data *)data; - - close_shout_conn(sd); -} - -static bool -shout_connect(struct shout_data *sd, GError **error) -{ - int state; - - state = shout_open(sd->shout_conn); - switch (state) { - case SHOUTERR_SUCCESS: - case SHOUTERR_CONNECTED: - return true; - - default: - g_set_error(error, shout_output_quark(), 0, - "problem opening connection to shout server %s:%i: %s", - shout_get_host(sd->shout_conn), - shout_get_port(sd->shout_conn), - shout_get_error(sd->shout_conn)); - return false; - } -} - -static bool -my_shout_open_device(void *data, struct audio_format *audio_format, - GError **error) -{ - struct shout_data *sd = (struct shout_data *)data; - bool ret; - - ret = shout_connect(sd, error); - if (!ret) - return false; - - sd->buf.len = 0; - - ret = encoder_open(sd->encoder, audio_format, error) && - write_page(sd, error); - if (!ret) { - shout_close(sd->shout_conn); - return false; - } - - return true; -} - -static unsigned -my_shout_delay(void *data) -{ - struct shout_data *sd = (struct shout_data *)data; - - int delay = shout_delay(sd->shout_conn); - if (delay < 0) - delay = 0; - - return delay; -} - -static size_t -my_shout_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct shout_data *sd = (struct shout_data *)data; - - return encoder_write(sd->encoder, chunk, size, error) && - write_page(sd, error) - ? size - : 0; -} - -static bool -my_shout_pause(void *data) -{ - static const char silence[1020]; - - return my_shout_play(data, silence, sizeof(silence), NULL); -} - -static void -shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) -{ - char artist[size]; - char title[size]; - - artist[0] = 0; - title[0] = 0; - - for (unsigned i = 0; i < tag->num_items; i++) { - switch (tag->items[i]->type) { - case TAG_ARTIST: - strncpy(artist, tag->items[i]->value, size); - break; - case TAG_TITLE: - strncpy(title, tag->items[i]->value, size); - break; - - default: - break; - } - } - - snprintf(dest, size, "%s - %s", artist, title); -} - -static void my_shout_set_tag(void *data, - const struct tag *tag) -{ - struct shout_data *sd = (struct shout_data *)data; - bool ret; - GError *error = NULL; - - if (sd->encoder->plugin->tag != NULL) { - /* encoder plugin supports stream tags */ - - ret = encoder_pre_tag(sd->encoder, &error); - if (!ret) { - g_warning("%s", error->message); - g_error_free(error); - return; - } - - ret = write_page(sd, NULL); - if (!ret) - return; - - ret = encoder_tag(sd->encoder, tag, &error); - if (!ret) { - g_warning("%s", error->message); - g_error_free(error); - } - } else { - /* no stream tag support: fall back to icy-metadata */ - char song[1024]; - - shout_tag_to_metadata(tag, song, sizeof(song)); - - shout_metadata_add(sd->shout_meta, "song", song); - if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, - sd->shout_meta)) { - g_warning("error setting shout metadata\n"); - } - } - - write_page(sd, NULL); -} - -const struct audio_output_plugin shoutPlugin = { - .name = "shout", - .init = my_shout_init_driver, - .finish = my_shout_finish_driver, - .open = my_shout_open_device, - .delay = my_shout_delay, - .play = my_shout_play, - .pause = my_shout_pause, - .cancel = my_shout_drop_buffered_audio, - .close = my_shout_close_device, - .send_tag = my_shout_set_tag, -}; -- cgit v1.2.3