From 26a9ce7b2927f2fc79af46c3152fbc41ee602197 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 29 Jan 2013 14:32:32 +0100 Subject: output/{alsa,oss}: convert to C++ --- Makefile.am | 8 +- src/OutputList.cxx | 4 +- src/mixer/OssMixerPlugin.cxx | 216 +++++++++++ src/mixer/oss_mixer_plugin.c | 216 ----------- src/output/AlsaOutputPlugin.cxx | 825 ++++++++++++++++++++++++++++++++++++++++ src/output/AlsaOutputPlugin.hxx | 25 ++ src/output/OssOutputPlugin.cxx | 794 ++++++++++++++++++++++++++++++++++++++ src/output/OssOutputPlugin.hxx | 25 ++ src/output/alsa_output_plugin.c | 819 --------------------------------------- src/output/alsa_output_plugin.h | 25 -- src/output/oss_output_plugin.c | 788 -------------------------------------- src/output/oss_output_plugin.h | 25 -- src/pcm_export.h | 8 + 13 files changed, 1900 insertions(+), 1878 deletions(-) create mode 100644 src/mixer/OssMixerPlugin.cxx delete mode 100644 src/mixer/oss_mixer_plugin.c create mode 100644 src/output/AlsaOutputPlugin.cxx create mode 100644 src/output/AlsaOutputPlugin.hxx create mode 100644 src/output/OssOutputPlugin.cxx create mode 100644 src/output/OssOutputPlugin.hxx delete mode 100644 src/output/alsa_output_plugin.c delete mode 100644 src/output/alsa_output_plugin.h delete mode 100644 src/output/oss_output_plugin.c delete mode 100644 src/output/oss_output_plugin.h diff --git a/Makefile.am b/Makefile.am index 4a1dfea7a..959865679 100644 --- a/Makefile.am +++ b/Makefile.am @@ -809,7 +809,8 @@ libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ if HAVE_ALSA liboutput_plugins_a_SOURCES += \ - src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h + src/output/AlsaOutputPlugin.cxx \ + src/output/AlsaOutputPlugin.hxx libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx endif @@ -851,8 +852,9 @@ endif if HAVE_OSS liboutput_plugins_a_SOURCES += \ - src/output/oss_output_plugin.c src/output/oss_output_plugin.h -libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c + src/output/OssOutputPlugin.cxx \ + src/output/OssOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx endif if HAVE_OPENAL diff --git a/src/OutputList.cxx b/src/OutputList.cxx index a9a0b3d33..87e441757 100644 --- a/src/OutputList.cxx +++ b/src/OutputList.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "OutputList.hxx" #include "output_api.h" -#include "output/alsa_output_plugin.h" +#include "output/AlsaOutputPlugin.hxx" #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" @@ -29,7 +29,7 @@ #include "output/mvp_output_plugin.h" #include "output/null_output_plugin.h" #include "output/openal_output_plugin.h" -#include "output/oss_output_plugin.h" +#include "output/OssOutputPlugin.hxx" #include "output/osx_output_plugin.h" #include "output/pipe_output_plugin.h" #include "output/pulse_output_plugin.h" diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx new file mode 100644 index 000000000..490a65414 --- /dev/null +++ b/src/mixer/OssMixerPlugin.cxx @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2003-2013 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 "mixer_api.h" +#include "output_api.h" +#include "fd_util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +struct oss_mixer { + /** the base mixer class */ + struct mixer base; + + const char *device; + const char *control; + + int device_fd; + int volume_control; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_mixer_quark(void) +{ + return g_quark_from_static_string("oss_mixer"); +} + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +static struct mixer * +oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + GError **error_r) +{ + struct oss_mixer *om = g_new(struct oss_mixer, 1); + + mixer_init(&om->base, &oss_mixer_plugin); + + om->device = config_get_block_string(param, "mixer_device", + VOLUME_MIXER_OSS_DEFAULT); + om->control = config_get_block_string(param, "mixer_control", NULL); + + if (om->control != NULL) { + om->volume_control = oss_find_mixer(om->control); + if (om->volume_control < 0) { + g_free(om); + g_set_error(error_r, oss_mixer_quark(), 0, + "no such mixer control: %s", om->control); + return NULL; + } + } else + om->volume_control = SOUND_MIXER_PCM; + + return &om->base; +} + +static void +oss_mixer_finish(struct mixer *data) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + g_free(om); +} + +static void +oss_mixer_close(struct mixer *data) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + assert(om->device_fd >= 0); + + close(om->device_fd); +} + +static bool +oss_mixer_open(struct mixer *data, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *) data; + + om->device_fd = open_cloexec(om->device, O_RDONLY, 0); + if (om->device_fd < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to open %s: %s", + om->device, g_strerror(errno)); + return false; + } + + if (om->control) { + int devmask = 0; + + if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "READ_DEVMASK failed: %s", + g_strerror(errno)); + oss_mixer_close(data); + return false; + } + + if (((1 << om->volume_control) & devmask) == 0) { + g_set_error(error_r, oss_mixer_quark(), 0, + "mixer control \"%s\" not usable", + om->control); + oss_mixer_close(data); + return false; + } + } + return true; +} + +static int +oss_mixer_get_volume(struct mixer *mixer, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *)mixer; + int left, right, level; + int ret; + + assert(om->device_fd >= 0); + + ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); + if (ret < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to read OSS volume: %s", + g_strerror(errno)); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + g_warning("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +static bool +oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct oss_mixer *om = (struct oss_mixer *)mixer; + int level; + int ret; + + assert(om->device_fd >= 0); + assert(volume <= 100); + + level = (volume << 8) + volume; + + ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); + if (ret < 0) { + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to set OSS volume: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +const struct mixer_plugin oss_mixer_plugin = { + oss_mixer_init, + oss_mixer_finish, + oss_mixer_open, + oss_mixer_close, + oss_mixer_get_volume, + oss_mixer_set_volume, + true, +}; diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c deleted file mode 100644 index 608f1f9b8..000000000 --- a/src/mixer/oss_mixer_plugin.c +++ /dev/null @@ -1,216 +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 "mixer_api.h" -#include "output_api.h" -#include "fd_util.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#if defined(__OpenBSD__) || defined(__NetBSD__) -# include -#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -# include -#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ - -#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" - -struct oss_mixer { - /** the base mixer class */ - struct mixer base; - - const char *device; - const char *control; - - int device_fd; - int volume_control; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -oss_mixer_quark(void) -{ - return g_quark_from_static_string("oss_mixer"); -} - -static int -oss_find_mixer(const char *name) -{ - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (g_ascii_strncasecmp(name, labels[i], name_length) == 0 && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - return -1; -} - -static struct mixer * -oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, - GError **error_r) -{ - struct oss_mixer *om = g_new(struct oss_mixer, 1); - - mixer_init(&om->base, &oss_mixer_plugin); - - om->device = config_get_block_string(param, "mixer_device", - VOLUME_MIXER_OSS_DEFAULT); - om->control = config_get_block_string(param, "mixer_control", NULL); - - if (om->control != NULL) { - om->volume_control = oss_find_mixer(om->control); - if (om->volume_control < 0) { - g_free(om); - g_set_error(error_r, oss_mixer_quark(), 0, - "no such mixer control: %s", om->control); - return NULL; - } - } else - om->volume_control = SOUND_MIXER_PCM; - - return &om->base; -} - -static void -oss_mixer_finish(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - g_free(om); -} - -static void -oss_mixer_close(struct mixer *data) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - assert(om->device_fd >= 0); - - close(om->device_fd); -} - -static bool -oss_mixer_open(struct mixer *data, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *) data; - - om->device_fd = open_cloexec(om->device, O_RDONLY, 0); - if (om->device_fd < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to open %s: %s", - om->device, g_strerror(errno)); - return false; - } - - if (om->control) { - int devmask = 0; - - if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "READ_DEVMASK failed: %s", - g_strerror(errno)); - oss_mixer_close(data); - return false; - } - - if (((1 << om->volume_control) & devmask) == 0) { - g_set_error(error_r, oss_mixer_quark(), 0, - "mixer control \"%s\" not usable", - om->control); - oss_mixer_close(data); - return false; - } - } - return true; -} - -static int -oss_mixer_get_volume(struct mixer *mixer, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int left, right, level; - int ret; - - assert(om->device_fd >= 0); - - ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); - if (ret < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to read OSS volume: %s", - g_strerror(errno)); - return false; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - g_warning("volume for left and right is not the same, \"%i\" and " - "\"%i\"\n", left, right); - } - - return left; -} - -static bool -oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) -{ - struct oss_mixer *om = (struct oss_mixer *)mixer; - int level; - int ret; - - assert(om->device_fd >= 0); - assert(volume <= 100); - - level = (volume << 8) + volume; - - ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); - if (ret < 0) { - g_set_error(error_r, oss_mixer_quark(), errno, - "failed to set OSS volume: %s", - g_strerror(errno)); - return false; - } - - return true; -} - -const struct mixer_plugin oss_mixer_plugin = { - .init = oss_mixer_init, - .finish = oss_mixer_finish, - .open = oss_mixer_open, - .close = oss_mixer_close, - .get_volume = oss_mixer_get_volume, - .set_volume = oss_mixer_set_volume, - .global = true, -}; diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx new file mode 100644 index 000000000..4d9f259ad --- /dev/null +++ b/src/output/AlsaOutputPlugin.cxx @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2003-2013 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 "AlsaOutputPlugin.hxx" +#include "output_api.h" +#include "mixer_list.h" +#include "pcm_export.h" + +#include +#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 AlsaOutput { + struct audio_output base; + + struct pcm_export_state pcm_export; + + /** + * The configured name of the ALSA device; empty for the + * default device + */ + std::string device; + + /** use memory mapped I/O? */ + bool use_mmap; + + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + + /** libasound's buffer_time setting (in microseconds) */ + unsigned int buffer_time; + + /** 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 passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; + + AlsaOutput():mode(0), writei(snd_pcm_writei) { + } + + bool Init(const config_param *param, GError **error_r) { + return ao_base_init(&base, &alsa_output_plugin, + param, error_r); + } + + void Deinit() { + ao_base_finish(&base); + } +}; + +/** + * 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 AlsaOutput *ad) +{ + return ad->device.empty() ? default_device : ad->device.c_str(); +} + +static void +alsa_configure(AlsaOutput *ad, const struct config_param *param) +{ + ad->device = config_dup_block_string(param, "device", NULL); + + ad->use_mmap = config_get_block_bool(param, "use_mmap", false); + + ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); + + ad->buffer_time = config_get_block_unsigned(param, "buffer_time", + MPD_ALSA_BUFFER_TIME_US); + ad->period_time = config_get_block_unsigned(param, "period_time", 0); + +#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 struct audio_output * +alsa_init(const struct config_param *param, GError **error_r) +{ + AlsaOutput *ad = new AlsaOutput(); + + if (!ad->Init(param, error_r)) { + delete ad; + return NULL; + } + + alsa_configure(ad, param); + + return &ad->base; +} + +static void +alsa_finish(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->Deinit(); + delete ad; + + /* free libasound's config cache */ + snd_config_update_free_global(); +} + +static bool +alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + pcm_export_init(&ad->pcm_export); + return true; +} + +static void +alsa_output_disable(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + pcm_export_deinit(&ad->pcm_export); +} + +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_UNDEFINED: + case SAMPLE_FORMAT_DSD: + return SND_PCM_FORMAT_UNKNOWN; + + 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_S32: + return SND_PCM_FORMAT_S32; + + case SAMPLE_FORMAT_FLOAT: + return SND_PCM_FORMAT_FLOAT; + } + + assert(false); + 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; + } +} + +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) +{ + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static int +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) +{ + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = 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(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + enum sample_format sample_format, + bool *packed_r, bool *reverse_endian_r) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; + + return err; +} + +/** + * 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, + bool *packed_r, bool *reverse_endian_r) +{ + /* try the input format first */ + + int err = alsa_output_try_format(pcm, hwparams, + sample_format(audio_format->format), + packed_r, reverse_endian_r); + + /* if unsupported by the hardware, try other formats */ + + static const enum sample_format probe_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED, + }; + + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; + ++i) { + const enum sample_format mpd_format = probe_formats[i]; + if (mpd_format == audio_format->format) + continue; + + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format->format = mpd_format; + } + + return err; +} + +/** + * 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(AlsaOutput *ad, struct audio_format *audio_format, + bool *packed_r, bool *reverse_endian_r, GError **error) +{ + unsigned int sample_rate = audio_format->sample_rate; + unsigned int channels = audio_format->channels; + 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_t *hwparams; + 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, + packed_r, reverse_endian_r); + if (err < 0) { + g_set_error(error, alsa_output_quark(), err, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(sample_format(audio_format->format)), + snd_strerror(-err)); + return false; + } + + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + g_debug("format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, + &channels); + if (err < 0) { + 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); + + snd_pcm_uframes_t alsa_buffer_size; + 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; + + snd_pcm_uframes_t alsa_period_size; + 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_t *swparams; + 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_setup_dsd(AlsaOutput *ad, struct audio_format *audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + GError **error_r) +{ + assert(ad->dsd_usb); + assert(audio_format->format == SAMPLE_FORMAT_DSD); + + /* pass 24 bit to alsa_setup() */ + + struct audio_format usb_format = *audio_format; + usb_format.format = SAMPLE_FORMAT_S24_P32; + usb_format.sample_rate /= 2; + + const struct audio_format check = usb_format; + + if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; + if (usb_format.format == SAMPLE_FORMAT_S32) + usb_format.format = SAMPLE_FORMAT_S24_P32; + + if (!audio_format_equals(&usb_format, &check)) { + /* no bit-perfect playback, which is required + for DSD over USB */ + g_set_error(error_r, alsa_output_quark(), 0, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(AlsaOutput *ad, struct audio_format *audio_format, + GError **error_r) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format->format == SAMPLE_FORMAT_DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error_r) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error_r); + if (!success) + return false; + + pcm_export_open(&ad->pcm_export, + sample_format(audio_format->format), + audio_format->channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + int 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; + } + + g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + if (!alsa_setup_or_dsd(ad, audio_format, error)) { + snd_pcm_close(ad->pcm); + return false; + } + + ad->in_frame_size = audio_format_frame_size(audio_format); + ad->out_frame_size = pcm_export_frame_size(&ad->pcm_export, + audio_format); + + return true; +} + +static int +alsa_recover(AlsaOutput *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(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + 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->out_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(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); +} + +static void +alsa_close(struct audio_output *ao) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + snd_pcm_close(ad->pcm); +} + +static size_t +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + AlsaOutput *ad = (AlsaOutput *)ao; + + assert(size % ad->in_frame_size == 0); + + chunk = pcm_export(&ad->pcm_export, chunk, size, &size); + + assert(size % ad->out_frame_size == 0); + + size /= ad->out_frame_size; + + while (true) { + snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; + + size_t bytes_written = ret * ad->out_frame_size; + return pcm_export_source_size(&ad->pcm_export, + bytes_written); + } + + 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 alsa_output_plugin = { + "alsa", + alsa_test_default_device, + alsa_init, + alsa_finish, + alsa_output_enable, + alsa_output_disable, + alsa_open, + alsa_close, + nullptr, + nullptr, + alsa_play, + alsa_drain, + alsa_cancel, + nullptr, + + &alsa_mixer_plugin, +}; diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/AlsaOutputPlugin.hxx new file mode 100644 index 000000000..dc7e639a8 --- /dev/null +++ b/src/output/AlsaOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX +#define MPD_ALSA_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin alsa_output_plugin; + +#endif diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx new file mode 100644 index 000000000..5810825f1 --- /dev/null +++ b/src/output/OssOutputPlugin.cxx @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2003-2013 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 "OssOutputPlugin.hxx" +#include "output_api.h" +#include "mixer_list.h" +#include "fd_util.h" +#include "glib_compat.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 + +#ifdef AFMT_S24_PACKED +#include "pcm_export.h" +#endif + +struct oss_data { + struct audio_output base; + +#ifdef AFMT_S24_PACKED + struct pcm_export_state pcm_export; +#endif + + 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 current OSS audio format. This is needed to reopen the + * device after cancel(). + */ + int oss_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], g_strerror(errno)); + } + + return false; +} + +static struct audio_output * +oss_open_default(GError **error) +{ + int err[G_N_ELEMENTS(default_devices)]; + enum oss_stat ret[G_N_ELEMENTS(default_devices)]; + + for (int 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(); + if (!ao_base_init(&od->base, &oss_output_plugin, NULL, + error)) { + g_free(od); + return NULL; + } + + od->device = default_devices[i]; + return &od->base; + } + } + + for (int 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, g_strerror(err[i])); + } + } + + g_set_error(error, oss_output_quark(), 0, + "error trying to open default OSS device"); + return NULL; +} + +static struct audio_output * +oss_output_init(const struct config_param *param, GError **error) +{ + const char *device = config_get_block_string(param, "device", NULL); + if (device != NULL) { + struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, param, + error)) { + g_free(od); + return NULL; + } + + od->device = device; + return &od->base; + } + + return oss_open_default(error); +} + +static void +oss_output_finish(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + ao_base_finish(&od->base); + oss_data_free(od); +} + +#ifdef AFMT_S24_PACKED + +static bool +oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_init(&od->pcm_export); + return true; +} + +static void +oss_output_disable(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_deinit(&od->pcm_export); +} + +#endif + +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: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: + return AFMT_QUERY; + + case SAMPLE_FORMAT_S8: + return AFMT_S8; + + case SAMPLE_FORMAT_S16: + return AFMT_S16_NE; + + 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_P32; +#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; + } +} + +/** + * Probe one sample format. + * + * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on + * error + */ +static enum oss_setup_result +oss_probe_sample_format(int fd, enum sample_format sample_format, + enum sample_format *sample_format_r, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *pcm_export, +#endif + GError **error_r) +{ + int oss_format = sample_format_to_oss(sample_format); + if (oss_format == AFMT_QUERY) + return UNSUPPORTED; + + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + +#ifdef AFMT_S24_PACKED + if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { + /* if the driver doesn't support padded 24 bit, try + packed 24 bit */ + oss_format = AFMT_S24_PACKED; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + } +#endif + + if (result != SUCCESS) + return result; + + sample_format = sample_format_from_oss(oss_format); + if (sample_format == SAMPLE_FORMAT_UNDEFINED) + return UNSUPPORTED; + + *sample_format_r = sample_format; + *oss_format_r = oss_format; + +#ifdef AFMT_S24_PACKED + pcm_export_open(pcm_export, sample_format, 0, false, false, + oss_format == AFMT_S24_PACKED, + oss_format == AFMT_S24_PACKED && + G_BYTE_ORDER != G_LITTLE_ENDIAN); +#endif + + return SUCCESS; +} + +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *pcm_export, +#endif + GError **error_r) +{ + enum sample_format mpd_format; + enum oss_setup_result result = + oss_probe_sample_format(fd, sample_format(audio_format->format), + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error_r); + switch (result) { + case SUCCESS: + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + + if (result != UNSUPPORTED) + return result == SUCCESS; + + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_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; + + result = oss_probe_sample_format(fd, mpd_format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + pcm_export, +#endif + error_r); + switch (result) { + case SUCCESS: + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error_literal(error_r, oss_output_quark(), EINVAL, + "Failed to set sample format"); + 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, &od->oss_format, +#ifdef AFMT_S24_PACKED + &od->pcm_export, +#endif + 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, g_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"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + od->oss_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(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + struct oss_data *od = (struct oss_data *)ao; + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, g_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(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + oss_close(od); +} + +static void +oss_output_cancel(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static size_t +oss_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + struct oss_data *od = (struct oss_data *)ao; + ssize_t ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && !oss_reopen(od, error)) + return 0; + +#ifdef AFMT_S24_PACKED + chunk = pcm_export(&od->pcm_export, chunk, size, &size); +#endif + + while (true) { + ret = write(od->fd, chunk, size); + if (ret > 0) { +#ifdef AFMT_S24_PACKED + ret = pcm_export_source_size(&od->pcm_export, ret); +#endif + return ret; + } + + if (ret < 0 && errno != EINTR) { + g_set_error(error, oss_output_quark(), errno, + "Write error on %s: %s", + od->device, g_strerror(errno)); + return 0; + } + } +} + +const struct audio_output_plugin oss_output_plugin = { + "oss", + oss_output_test_default_device, + oss_output_init, + oss_output_finish, +#ifdef AFMT_S24_PACKED + oss_output_enable, + oss_output_disable, +#else + nullptr, + nullptr, +#endif + oss_output_open, + oss_output_close, + nullptr, + nullptr, + oss_output_play, + nullptr, + oss_output_cancel, + nullptr, + + &oss_mixer_plugin, +}; diff --git a/src/output/OssOutputPlugin.hxx b/src/output/OssOutputPlugin.hxx new file mode 100644 index 000000000..6c5c9530b --- /dev/null +++ b/src/output/OssOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX +#define MPD_OSS_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin oss_output_plugin; + +#endif diff --git a/src/output/alsa_output_plugin.c b/src/output/alsa_output_plugin.c deleted file mode 100644 index d8b184273..000000000 --- a/src/output/alsa_output_plugin.c +++ /dev/null @@ -1,819 +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 "alsa_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "pcm_export.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 { - struct audio_output base; - - struct pcm_export_state export; - - /** the configured name of the ALSA device; NULL for the - default device */ - char *device; - - /** use memory mapped I/O? */ - bool use_mmap; - - /** - * Enable DSD over USB according to the dCS suggested - * standard? - * - * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf - */ - bool dsd_usb; - - /** libasound's buffer_time setting (in microseconds) */ - unsigned int buffer_time; - - /** 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 passed to method play(). - */ - size_t in_frame_size; - - /** - * The size of one audio frame passed to libasound. - */ - size_t out_frame_size; - - /** - * The size of one period, in number of frames. - */ - 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_configure(struct alsa_data *ad, const struct config_param *param) -{ - ad->device = config_dup_block_string(param, "device", NULL); - - ad->use_mmap = config_get_block_bool(param, "use_mmap", false); - - ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); - - ad->buffer_time = config_get_block_unsigned(param, "buffer_time", - MPD_ALSA_BUFFER_TIME_US); - ad->period_time = config_get_block_unsigned(param, "period_time", 0); - -#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 struct audio_output * -alsa_init(const struct config_param *param, GError **error_r) -{ - struct alsa_data *ad = alsa_data_new(); - - if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { - g_free(ad); - return NULL; - } - - alsa_configure(ad, param); - - return &ad->base; -} - -static void -alsa_finish(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ao_base_finish(&ad->base); - - g_free(ad->device); - g_free(ad); - - /* free libasound's config cache */ - snd_config_update_free_global(); -} - -static bool -alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_init(&ad->export); - return true; -} - -static void -alsa_output_disable(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - pcm_export_deinit(&ad->export); -} - -static bool -alsa_test_default_device(void) -{ - snd_pcm_t *handle; - - 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_UNDEFINED: - case SAMPLE_FORMAT_DSD: - return SND_PCM_FORMAT_UNKNOWN; - - 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_S32: - return SND_PCM_FORMAT_S32; - - case SAMPLE_FORMAT_FLOAT: - return SND_PCM_FORMAT_FLOAT; - } - - assert(false); - 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; - } -} - -static snd_pcm_format_t -alsa_to_packed_format(snd_pcm_format_t fmt) -{ - switch (fmt) { - case SND_PCM_FORMAT_S24_LE: - return SND_PCM_FORMAT_S24_3LE; - - case SND_PCM_FORMAT_S24_BE: - return SND_PCM_FORMAT_S24_3BE; - - default: - return SND_PCM_FORMAT_UNKNOWN; - } -} - -static int -alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - snd_pcm_format_t fmt, bool *packed_r) -{ - int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = false; - - if (err != -EINVAL) - return err; - - fmt = alsa_to_packed_format(fmt); - if (fmt == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); - if (err == 0) - *packed_r = 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(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - enum sample_format sample_format, - bool *packed_r, bool *reverse_endian_r) -{ - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, - packed_r); - if (err == 0) - *reverse_endian_r = false; - - if (err != -EINVAL) - return err; - - alsa_format = byteswap_bitformat(alsa_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; - - err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); - if (err == 0) - *reverse_endian_r = true; - - return err; -} - -/** - * 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, - bool *packed_r, bool *reverse_endian_r) -{ - /* try the input format first */ - - int err = alsa_output_try_format(pcm, hwparams, audio_format->format, - packed_r, reverse_endian_r); - - /* if unsupported by the hardware, try other formats */ - - static const enum sample_format probe_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S16, - SAMPLE_FORMAT_S8, - SAMPLE_FORMAT_UNDEFINED, - }; - - for (unsigned i = 0; - err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; - ++i) { - const enum sample_format mpd_format = probe_formats[i]; - if (mpd_format == audio_format->format) - continue; - - err = alsa_output_try_format(pcm, hwparams, mpd_format, - packed_r, reverse_endian_r); - if (err == 0) - audio_format->format = mpd_format; - } - - return err; -} - -/** - * 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, - bool *packed_r, bool *reverse_endian_r, 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, - packed_r, reverse_endian_r); - if (err < 0) { - g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support format %s: %s", - alsa_device(ad), - sample_format_to_string(audio_format->format), - snd_strerror(-err)); - return false; - } - - snd_pcm_format_t format; - if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) - g_debug("format=%s (%s)", snd_pcm_format_name(format), - snd_pcm_format_description(format)); - - err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, - &channels); - if (err < 0) { - 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_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, - bool *shift8_r, bool *packed_r, bool *reverse_endian_r, - GError **error_r) -{ - assert(ad->dsd_usb); - assert(audio_format->format == SAMPLE_FORMAT_DSD); - - /* pass 24 bit to alsa_setup() */ - - struct audio_format usb_format = *audio_format; - usb_format.format = SAMPLE_FORMAT_S24_P32; - usb_format.sample_rate /= 2; - - const struct audio_format check = usb_format; - - if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) - return false; - - /* if the device allows only 32 bit, shift all DSD-over-USB - samples left by 8 bit and leave the lower 8 bit cleared; - the DSD-over-USB documentation does not specify whether - this is legal, but there is anecdotical evidence that this - is possible (and the only option for some devices) */ - *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; - if (usb_format.format == SAMPLE_FORMAT_S32) - usb_format.format = SAMPLE_FORMAT_S24_P32; - - if (!audio_format_equals(&usb_format, &check)) { - /* no bit-perfect playback, which is required - for DSD over USB */ - g_set_error(error_r, alsa_output_quark(), 0, - "Failed to configure DSD-over-USB on ALSA device \"%s\"", - alsa_device(ad)); - return false; - } - - return true; -} - -static bool -alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, - GError **error_r) -{ - bool shift8 = false, packed, reverse_endian; - - const bool dsd_usb = ad->dsd_usb && - audio_format->format == SAMPLE_FORMAT_DSD; - const bool success = dsd_usb - ? alsa_setup_dsd(ad, audio_format, - &shift8, &packed, &reverse_endian, - error_r) - : alsa_setup(ad, audio_format, &packed, &reverse_endian, - error_r); - if (!success) - return false; - - pcm_export_open(&ad->export, - audio_format->format, audio_format->channels, - dsd_usb, shift8, packed, reverse_endian); - return true; -} - -static bool -alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - int err; - bool success; - - 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; - } - - g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), - snd_pcm_type_name(snd_pcm_type(ad->pcm))); - - success = alsa_setup_or_dsd(ad, audio_format, error); - if (!success) { - snd_pcm_close(ad->pcm); - return false; - } - - ad->in_frame_size = audio_format_frame_size(audio_format); - ad->out_frame_size = pcm_export_frame_size(&ad->export, 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(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - 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->out_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(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - ad->period_position = 0; - - snd_pcm_drop(ad->pcm); -} - -static void -alsa_close(struct audio_output *ao) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - snd_pcm_close(ad->pcm); -} - -static size_t -alsa_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct alsa_data *ad = (struct alsa_data *)ao; - - assert(size % ad->in_frame_size == 0); - - chunk = pcm_export(&ad->export, chunk, size, &size); - - assert(size % ad->out_frame_size == 0); - - size /= ad->out_frame_size; - - while (true) { - snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) { - ad->period_position = (ad->period_position + ret) - % ad->period_frames; - - size_t bytes_written = ret * ad->out_frame_size; - return pcm_export_source_size(&ad->export, - bytes_written); - } - - if (ret < 0 && ret != -EAGAIN && ret != -EINTR && - alsa_recover(ad, ret) < 0) { - g_set_error(error, alsa_output_quark(), errno, - "%s", snd_strerror(-errno)); - return 0; - } - } -} - -const struct audio_output_plugin alsa_output_plugin = { - .name = "alsa", - .test_default_device = alsa_test_default_device, - .init = alsa_init, - .finish = alsa_finish, - .enable = alsa_output_enable, - .disable = alsa_output_disable, - .open = alsa_open, - .play = alsa_play, - .drain = alsa_drain, - .cancel = alsa_cancel, - .close = alsa_close, - - .mixer_plugin = &alsa_mixer_plugin, -}; diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h deleted file mode 100644 index daa1f3615..000000000 --- a/src/output/alsa_output_plugin.h +++ /dev/null @@ -1,25 +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. - */ - -#ifndef MPD_ALSA_OUTPUT_PLUGIN_H -#define MPD_ALSA_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin alsa_output_plugin; - -#endif diff --git a/src/output/oss_output_plugin.c b/src/output/oss_output_plugin.c deleted file mode 100644 index e366a4537..000000000 --- a/src/output/oss_output_plugin.c +++ /dev/null @@ -1,788 +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 "oss_output_plugin.h" -#include "output_api.h" -#include "mixer_list.h" -#include "fd_util.h" -#include "glib_compat.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 - -#ifdef AFMT_S24_PACKED -#include "pcm_export.h" -#endif - -struct oss_data { - struct audio_output base; - -#ifdef AFMT_S24_PACKED - struct pcm_export_state export; -#endif - - int fd; - const char *device; - - /** - * The current input audio format. This is needed to reopen - * the device after cancel(). - */ - struct audio_format audio_format; - - /** - * The current OSS audio format. This is needed to reopen the - * device after cancel(). - */ - int oss_format; -}; - -/** - * 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], g_strerror(errno)); - } - - return false; -} - -static struct audio_output * -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(); - if (!ao_base_init(&od->base, &oss_output_plugin, NULL, - error)) { - g_free(od); - return NULL; - } - - od->device = default_devices[i]; - return &od->base; - } - } - - 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, g_strerror(err[i])); - } - } - - g_set_error(error, oss_output_quark(), 0, - "error trying to open default OSS device"); - return NULL; -} - -static struct audio_output * -oss_output_init(const struct config_param *param, GError **error) -{ - const char *device = config_get_block_string(param, "device", NULL); - if (device != NULL) { - struct oss_data *od = oss_data_new(); - if (!ao_base_init(&od->base, &oss_output_plugin, param, - error)) { - g_free(od); - return NULL; - } - - od->device = device; - return &od->base; - } - - return oss_open_default(error); -} - -static void -oss_output_finish(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - ao_base_finish(&od->base); - oss_data_free(od); -} - -#ifdef AFMT_S24_PACKED - -static bool -oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_init(&od->export); - return true; -} - -static void -oss_output_disable(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - pcm_export_deinit(&od->export); -} - -#endif - -static void -oss_close(struct oss_data *od) -{ - 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: - case SAMPLE_FORMAT_FLOAT: - case SAMPLE_FORMAT_DSD: - return AFMT_QUERY; - - case SAMPLE_FORMAT_S8: - return AFMT_S8; - - case SAMPLE_FORMAT_S16: - return AFMT_S16_NE; - - 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_P32; -#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; - } -} - -/** - * Probe one sample format. - * - * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on - * error - */ -static enum oss_setup_result -oss_probe_sample_format(int fd, enum sample_format sample_format, - enum sample_format *sample_format_r, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - int oss_format = sample_format_to_oss(sample_format); - if (oss_format == AFMT_QUERY) - return UNSUPPORTED; - - enum oss_setup_result result = - oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - -#ifdef AFMT_S24_PACKED - if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { - /* if the driver doesn't support padded 24 bit, try - packed 24 bit */ - oss_format = AFMT_S24_PACKED; - result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, - &oss_format, - "Failed to set sample format", error_r); - } -#endif - - if (result != SUCCESS) - return result; - - sample_format = sample_format_from_oss(oss_format); - if (sample_format == SAMPLE_FORMAT_UNDEFINED) - return UNSUPPORTED; - - *sample_format_r = sample_format; - *oss_format_r = oss_format; - -#ifdef AFMT_S24_PACKED - pcm_export_open(export, sample_format, 0, false, false, - oss_format == AFMT_S24_PACKED, - oss_format == AFMT_S24_PACKED && - G_BYTE_ORDER != G_LITTLE_ENDIAN); -#endif - - return SUCCESS; -} - -/** - * Set up the sample format, and attempts to find alternatives if the - * specified format is not supported. - */ -static bool -oss_setup_sample_format(int fd, struct audio_format *audio_format, - int *oss_format_r, -#ifdef AFMT_S24_PACKED - struct pcm_export_state *export, -#endif - GError **error_r) -{ - enum sample_format mpd_format; - enum oss_setup_result result = - oss_probe_sample_format(fd, audio_format->format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - - if (result != UNSUPPORTED) - return result == SUCCESS; - - /* the requested sample format is not available - probe for - other formats supported by MPD */ - - static const enum sample_format sample_formats[] = { - SAMPLE_FORMAT_S24_P32, - SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_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; - - result = oss_probe_sample_format(fd, mpd_format, - &mpd_format, oss_format_r, -#ifdef AFMT_S24_PACKED - export, -#endif - error_r); - switch (result) { - case SUCCESS: - audio_format->format = mpd_format; - return true; - - case ERROR: - return false; - - case UNSUPPORTED: - break; - } - } - - g_set_error_literal(error_r, oss_output_quark(), EINVAL, - "Failed to set sample format"); - 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, &od->oss_format, -#ifdef AFMT_S24_PACKED - &od->export, -#endif - 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, g_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"; - result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, - od->oss_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(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - - od->fd = open_cloexec(od->device, O_WRONLY, 0); - if (od->fd < 0) { - g_set_error(error, oss_output_quark(), errno, - "Error opening OSS device \"%s\": %s", - od->device, g_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(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - oss_close(od); -} - -static void -oss_output_cancel(struct audio_output *ao) -{ - struct oss_data *od = (struct oss_data *)ao; - - if (od->fd >= 0) { - ioctl(od->fd, SNDCTL_DSP_RESET, 0); - oss_close(od); - } -} - -static size_t -oss_output_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - struct oss_data *od = (struct oss_data *)ao; - ssize_t ret; - - /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_reopen(od, error)) - return 0; - -#ifdef AFMT_S24_PACKED - chunk = pcm_export(&od->export, chunk, size, &size); -#endif - - while (true) { - ret = write(od->fd, chunk, size); - if (ret > 0) { -#ifdef AFMT_S24_PACKED - ret = pcm_export_source_size(&od->export, ret); -#endif - return ret; - } - - if (ret < 0 && errno != EINTR) { - g_set_error(error, oss_output_quark(), errno, - "Write error on %s: %s", - od->device, g_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, -#ifdef AFMT_S24_PACKED - .enable = oss_output_enable, - .disable = oss_output_disable, -#endif - .open = oss_output_open, - .close = oss_output_close, - .play = oss_output_play, - .cancel = oss_output_cancel, - - .mixer_plugin = &oss_mixer_plugin, -}; diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h deleted file mode 100644 index 2aecc2b3a..000000000 --- a/src/output/oss_output_plugin.h +++ /dev/null @@ -1,25 +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. - */ - -#ifndef MPD_OSS_OUTPUT_PLUGIN_H -#define MPD_OSS_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin oss_output_plugin; - -#endif diff --git a/src/pcm_export.h b/src/pcm_export.h index a7e7c3f68..005db48e0 100644 --- a/src/pcm_export.h +++ b/src/pcm_export.h @@ -87,6 +87,10 @@ struct pcm_export_state { uint8_t reverse_endian; }; +#ifdef __cplusplus +extern "C" { +#endif + /** * Initialize a #pcm_export_state object. */ @@ -144,4 +148,8 @@ G_GNUC_PURE size_t pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size); +#ifdef __cplusplus +} +#endif + #endif -- cgit v1.2.3