diff options
Diffstat (limited to '')
-rw-r--r-- | src/output/alsa_output_plugin.c (renamed from src/output/alsa_plugin.c) | 313 |
1 files changed, 222 insertions, 91 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_output_plugin.c index ae06847c2..a6dc92fa0 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,10 @@ */ #include "config.h" +#include "alsa_output_plugin.h" #include "output_api.h" #include "mixer_list.h" +#include "pcm_export.h" #include <glib.h> #include <alsa/asoundlib.h> @@ -42,6 +44,10 @@ typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); struct alsa_data { + struct audio_output base; + + struct pcm_export_state export; + /** the configured name of the ALSA device; NULL for the default device */ char *device; @@ -49,6 +55,14 @@ struct alsa_data { /** use memory mapped I/O? */ bool use_mmap; + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + /** libasound's buffer_time setting (in microseconds) */ unsigned int buffer_time; @@ -68,8 +82,15 @@ struct alsa_data { */ alsa_writei_t *writei; - /** the size of one audio frame */ - size_t frame_size; + /** + * The size of one audio frame passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; /** * The size of one period, in number of frames. @@ -109,19 +130,14 @@ alsa_data_new(void) } static void -alsa_data_free(struct alsa_data *ad) -{ - g_free(ad->device); - g_free(ad); -} - -static void alsa_configure(struct alsa_data *ad, const struct config_param *param) { ad->device = config_dup_block_string(param, "device", NULL); ad->use_mmap = config_get_block_bool(param, "use_mmap", false); + ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); + ad->buffer_time = config_get_block_unsigned(param, "buffer_time", MPD_ALSA_BUFFER_TIME_US); ad->period_time = config_get_block_unsigned(param, "period_time", 0); @@ -142,30 +158,53 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param) #endif } -static void * -alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +alsa_init(const struct config_param *param, GError **error_r) { struct alsa_data *ad = alsa_data_new(); + if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { + g_free(ad); + return NULL; + } + alsa_configure(ad, param); - return ad; + return &ad->base; } static void -alsa_finish(void *data) +alsa_finish(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; - alsa_data_free(ad); + ao_base_finish(&ad->base); + + g_free(ad->device); + g_free(ad); /* free libasound's config cache */ snd_config_update_free_global(); } static bool +alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + struct alsa_data *ad = (struct alsa_data *)ao; + + pcm_export_init(&ad->export); + return true; +} + +static void +alsa_output_disable(struct audio_output *ao) +{ + struct alsa_data *ad = (struct alsa_data *)ao; + + pcm_export_deinit(&ad->export); +} + +static bool alsa_test_default_device(void) { snd_pcm_t *handle; @@ -187,6 +226,7 @@ get_bitformat(enum sample_format sample_format) { switch (sample_format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: return SND_PCM_FORMAT_UNKNOWN; case SAMPLE_FORMAT_S8: @@ -198,13 +238,11 @@ get_bitformat(enum sample_format sample_format) case SAMPLE_FORMAT_S24_P32: return SND_PCM_FORMAT_S24; - case SAMPLE_FORMAT_S24: - return G_BYTE_ORDER == G_BIG_ENDIAN - ? SND_PCM_FORMAT_S24_3BE - : SND_PCM_FORMAT_S24_3LE; - case SAMPLE_FORMAT_S32: return SND_PCM_FORMAT_S32; + + case SAMPLE_FORMAT_FLOAT: + return SND_PCM_FORMAT_FLOAT; } assert(false); @@ -232,44 +270,39 @@ byteswap_bitformat(snd_pcm_format_t fmt) } } -/** - * Attempts to configure the specified sample format. - */ -static int -alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) { - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; - int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); - if (err == 0) - audio_format->format = sample_format; + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; - return err; + default: + return SND_PCM_FORMAT_UNKNOWN; + } } -/** - * Attempts to configure the specified sample format with reversed - * host byte order. - */ static int -alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) { - snd_pcm_format_t alsa_format = - byteswap_bitformat(get_bitformat(sample_format)); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == SND_PCM_FORMAT_UNKNOWN) return -EINVAL; - int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); - if (err == 0) { - audio_format->format = sample_format; - audio_format->reverse_endian = true; - } + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = true; return err; } @@ -279,15 +312,29 @@ alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, * reversed host byte order if was not supported. */ static int -alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format, - enum sample_format sample_format) +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + enum sample_format sample_format, + bool *packed_r, bool *reverse_endian_r) { - int err = alsa_output_try_format(pcm, hwparams, audio_format, - sample_format); - if (err == -EINVAL) - err = alsa_output_try_reverse(pcm, hwparams, audio_format, - sample_format); + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; return err; } @@ -297,37 +344,38 @@ alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static int alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format) + struct audio_format *audio_format, + bool *packed_r, bool *reverse_endian_r) { /* try the input format first */ - int err = alsa_output_try_format_both(pcm, hwparams, audio_format, - audio_format->format); - if (err != -EINVAL) - return err; + int err = alsa_output_try_format(pcm, hwparams, audio_format->format, + packed_r, reverse_endian_r); /* if unsupported by the hardware, try other formats */ static const enum sample_format probe_formats[] = { SAMPLE_FORMAT_S24_P32, SAMPLE_FORMAT_S32, - SAMPLE_FORMAT_S24, SAMPLE_FORMAT_S16, SAMPLE_FORMAT_S8, SAMPLE_FORMAT_UNDEFINED, }; - for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { - if (probe_formats[i] == audio_format->format) + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; + ++i) { + const enum sample_format mpd_format = probe_formats[i]; + if (mpd_format == audio_format->format) continue; - err = alsa_output_try_format_both(pcm, hwparams, audio_format, - probe_formats[i]); - if (err != -EINVAL) - return err; + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format->format = mpd_format; } - return -EINVAL; + return err; } /** @@ -336,7 +384,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static bool alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - GError **error) + bool *packed_r, bool *reverse_endian_r, GError **error) { snd_pcm_hw_params_t *hwparams; snd_pcm_sw_params_t *swparams; @@ -380,7 +428,8 @@ configure_hw: ad->writei = snd_pcm_writei; } - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, + packed_r, reverse_endian_r); if (err < 0) { g_set_error(error, alsa_output_quark(), err, "ALSA device \"%s\" does not support format %s: %s", @@ -390,6 +439,11 @@ configure_hw: return false; } + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + g_debug("format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, &channels); if (err < 0) { @@ -532,9 +586,72 @@ error: } static bool -alsa_open(void *data, struct audio_format *audio_format, GError **error) +alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + GError **error_r) { - struct alsa_data *ad = data; + assert(ad->dsd_usb); + assert(audio_format->format == SAMPLE_FORMAT_DSD); + + /* pass 24 bit to alsa_setup() */ + + struct audio_format usb_format = *audio_format; + usb_format.format = SAMPLE_FORMAT_S24_P32; + usb_format.sample_rate /= 2; + + const struct audio_format check = usb_format; + + if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; + if (usb_format.format == SAMPLE_FORMAT_S32) + usb_format.format = SAMPLE_FORMAT_S24_P32; + + if (!audio_format_equals(&usb_format, &check)) { + /* no bit-perfect playback, which is required + for DSD over USB */ + g_set_error(error_r, alsa_output_quark(), 0, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, + GError **error_r) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format->format == SAMPLE_FORMAT_DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error_r) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error_r); + if (!success) + return false; + + pcm_export_open(&ad->export, + audio_format->format, audio_format->channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct alsa_data *ad = (struct alsa_data *)ao; int err; bool success; @@ -547,13 +664,17 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error) return false; } - success = alsa_setup(ad, audio_format, error); + g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + success = alsa_setup_or_dsd(ad, audio_format, error); if (!success) { snd_pcm_close(ad->pcm); return false; } - ad->frame_size = audio_format_frame_size(audio_format); + ad->in_frame_size = audio_format_frame_size(audio_format); + ad->out_frame_size = ad->export.pack24 ? 3 : ad->in_frame_size; return true; } @@ -596,9 +717,9 @@ alsa_recover(struct alsa_data *ad, int err) } static void -alsa_drain(void *data) +alsa_drain(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) return; @@ -608,7 +729,7 @@ alsa_drain(void *data) period */ snd_pcm_uframes_t nframes = ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->frame_size; + size_t nbytes = nframes * ad->out_frame_size; void *buffer = g_malloc(nbytes); snd_pcm_hw_params_t *params; snd_pcm_format_t format; @@ -630,9 +751,9 @@ alsa_drain(void *data) } static void -alsa_cancel(void *data) +alsa_cancel(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; ad->period_position = 0; @@ -640,26 +761,34 @@ alsa_cancel(void *data) } static void -alsa_close(void *data) +alsa_close(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; snd_pcm_close(ad->pcm); } static size_t -alsa_play(void *data, const void *chunk, size_t size, GError **error) +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; + + assert(size % ad->in_frame_size == 0); + + chunk = pcm_export(&ad->export, chunk, size, &size); + + assert(size % ad->out_frame_size == 0); - size /= ad->frame_size; + size /= ad->out_frame_size; while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); if (ret > 0) { ad->period_position = (ad->period_position + ret) % ad->period_frames; - return ret * ad->frame_size; + return pcm_export_source_size(&ad->export, + ret * ad->in_frame_size); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && @@ -671,11 +800,13 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) } } -const struct audio_output_plugin alsaPlugin = { +const struct audio_output_plugin alsa_output_plugin = { .name = "alsa", .test_default_device = alsa_test_default_device, .init = alsa_init, .finish = alsa_finish, + .enable = alsa_output_enable, + .disable = alsa_output_disable, .open = alsa_open, .play = alsa_play, .drain = alsa_drain, |