aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/alsa_plugin.c
diff options
context:
space:
mode:
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,