alsa: add DSD format support

Add DSD support to the ALSA plugin.

You will need to manually enable the supported DSD formats for the
platform you are running on because we don't support any conversions or
probing for DSD:

PIPEWIRE_ALSA='{ alsa.format = "DSD_U32_BE" }' src/tools/pw-alsa-dsd somefile.dsf

Fixes #4160
This commit is contained in:
Wim Taymans 2024-08-02 13:01:35 +02:00
parent 3e5a85b3bc
commit dede88fc22

View file

@ -81,7 +81,8 @@ typedef struct {
int64_t now;
uintptr_t seq;
struct spa_audio_info_raw format;
struct spa_audio_info requested;
struct spa_audio_info format;
} snd_pcm_pipewire_t;
static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io);
@ -344,6 +345,25 @@ static void on_stream_param_changed(void *data, uint32_t id, const struct spa_po
if (param == NULL || id != SPA_PARAM_Format)
return;
if (spa_format_audio_parse(param, &pw->format) < 0) {
pw->error = -EINVAL;
} else {
switch (pw->format.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
break;
case SPA_MEDIA_SUBTYPE_dsd:
if (pw->format.info.dsd.interleave != pw->requested.info.dsd.interleave ||
pw->format.info.dsd.bitorder != pw->requested.info.dsd.bitorder) {
pw->error = -EINVAL;
}
break;
}
}
if (pw->error < 0) {
pw_thread_loop_signal(pw->main_loop, false);
return;
}
io->period_size = pw->min_avail;
buffers = SPA_CLAMP(io->buffer_size / io->period_size, MIN_BUFFERS, MAX_BUFFERS);
@ -525,7 +545,7 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io)
pw_properties_setf(pw->props, PW_KEY_NODE_LATENCY, "%lu/%u", pw->min_avail, io->rate);
pw_properties_setf(pw->props, PW_KEY_NODE_RATE, "1/%u", io->rate);
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->format);
params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &pw->format);
if (pw->stream != NULL) {
pw_stream_set_active(pw->stream, false);
@ -622,29 +642,29 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable)
#define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE
#endif
static int set_default_channels(struct spa_audio_info_raw *info)
static int set_default_channels(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
{
switch (info->channels) {
switch (channels) {
case 8:
info->position[6] = SPA_AUDIO_CHANNEL_SL;
info->position[7] = SPA_AUDIO_CHANNEL_SR;
position[6] = SPA_AUDIO_CHANNEL_SL;
position[7] = SPA_AUDIO_CHANNEL_SR;
SPA_FALLTHROUGH
case 6:
info->position[5] = SPA_AUDIO_CHANNEL_LFE;
position[5] = SPA_AUDIO_CHANNEL_LFE;
SPA_FALLTHROUGH
case 5:
info->position[4] = SPA_AUDIO_CHANNEL_FC;
position[4] = SPA_AUDIO_CHANNEL_FC;
SPA_FALLTHROUGH
case 4:
info->position[2] = SPA_AUDIO_CHANNEL_RL;
info->position[3] = SPA_AUDIO_CHANNEL_RR;
position[2] = SPA_AUDIO_CHANNEL_RL;
position[3] = SPA_AUDIO_CHANNEL_RR;
SPA_FALLTHROUGH
case 2:
info->position[0] = SPA_AUDIO_CHANNEL_FL;
info->position[1] = SPA_AUDIO_CHANNEL_FR;
position[0] = SPA_AUDIO_CHANNEL_FL;
position[1] = SPA_AUDIO_CHANNEL_FR;
return 1;
case 1:
info->position[0] = SPA_AUDIO_CHANNEL_MONO;
position[0] = SPA_AUDIO_CHANNEL_MONO;
return 1;
default:
return 0;
@ -656,6 +676,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io,
{
snd_pcm_pipewire_t *pw = io->private_data;
bool planar;
const char *fmt_str = NULL;
snd_pcm_hw_params_dump(params, pw->output);
fflush(pw->log_file);
@ -676,48 +697,98 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io,
return -EINVAL;
}
pw->requested.media_type = SPA_MEDIA_TYPE_audio;
switch(io->format) {
case SND_PCM_FORMAT_U8:
pw->format.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8;
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8;
break;
case SND_PCM_FORMAT_S16_LE:
pw->format.format = _FORMAT_LE(planar, S16);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_LE(planar, S16);
break;
case SND_PCM_FORMAT_S16_BE:
pw->format.format = _FORMAT_BE(planar, S16);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_BE(planar, S16);
break;
case SND_PCM_FORMAT_S24_LE:
pw->format.format = _FORMAT_LE(planar, S24_32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_LE(planar, S24_32);
break;
case SND_PCM_FORMAT_S24_BE:
pw->format.format = _FORMAT_BE(planar, S24_32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_BE(planar, S24_32);
break;
case SND_PCM_FORMAT_S32_LE:
pw->format.format = _FORMAT_LE(planar, S32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_LE(planar, S32);
break;
case SND_PCM_FORMAT_S32_BE:
pw->format.format = _FORMAT_BE(planar, S32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_BE(planar, S32);
break;
case SND_PCM_FORMAT_S24_3LE:
pw->format.format = _FORMAT_LE(planar, S24);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_LE(planar, S24);
break;
case SND_PCM_FORMAT_S24_3BE:
pw->format.format = _FORMAT_BE(planar, S24);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_BE(planar, S24);
break;
case SND_PCM_FORMAT_FLOAT_LE:
pw->format.format = _FORMAT_LE(planar, F32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_LE(planar, F32);
break;
case SND_PCM_FORMAT_FLOAT_BE:
pw->format.format = _FORMAT_BE(planar, F32);
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw;
pw->requested.info.raw.format = _FORMAT_BE(planar, F32);
break;
case SND_PCM_FORMAT_DSD_U32_BE:
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd;
pw->requested.info.dsd.interleave = 4;
break;
case SND_PCM_FORMAT_DSD_U32_LE:
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd;
pw->requested.info.dsd.interleave = -4;
break;
case SND_PCM_FORMAT_DSD_U16_BE:
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd;
pw->requested.info.dsd.interleave = 2;
break;
case SND_PCM_FORMAT_DSD_U16_LE:
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd;
pw->requested.info.dsd.interleave = -2;
break;
case SND_PCM_FORMAT_DSD_U8:
pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd;
pw->requested.info.dsd.interleave = 1;
break;
default:
SNDERR("PipeWire: invalid format: %d\n", io->format);
return -EINVAL;
}
pw->format.channels = io->channels;
pw->format.rate = io->rate;
set_default_channels(&pw->format);
switch (pw->requested.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
pw->requested.info.raw.channels = io->channels;
pw->requested.info.raw.rate = io->rate;
set_default_channels(io->channels, pw->requested.info.raw.position);
fmt_str = spa_debug_type_find_name(spa_type_audio_format, pw->requested.info.raw.format);
pw->format = pw->requested;
break;
case SPA_MEDIA_SUBTYPE_dsd:
pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb;
pw->requested.info.dsd.channels = io->channels;
pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave);
set_default_channels(io->channels, pw->requested.info.dsd.position);
pw->format = pw->requested;
/* we need to let the server decide these values */
pw->format.info.dsd.bitorder = 0;
pw->format.info.dsd.interleave = 0;
fmt_str = "DSD";
break;
default:
return -EIO;
}
pw->sample_bits = snd_pcm_format_physical_width(io->format);
if (planar) {
@ -728,8 +799,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io,
pw->stride = (io->channels * pw->sample_bits) / 8;
}
pw->hw_params_changed = true;
pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw,
spa_debug_type_find_name(spa_type_audio_format, pw->format.format),
pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw, fmt_str,
io->channels, io->rate, pw->stride, pw->blocks);
return 0;
@ -831,14 +901,26 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io,
{
snd_pcm_pipewire_t *pw = io->private_data;
unsigned int i;
uint32_t *position;
pw->format.channels = map->channels;
switch (pw->requested.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
pw->requested.info.raw.channels = map->channels;
position = pw->requested.info.raw.position;
break;
case SPA_MEDIA_SUBTYPE_dsd:
pw->requested.info.dsd.channels = map->channels;
position = pw->requested.info.dsd.position;
break;
default:
return -EINVAL;
}
for (i = 0; i < map->channels; i++) {
pw->format.position[i] = chmap_to_channel(map->pos[i]);
position[i] = chmap_to_channel(map->pos[i]);
pw_log_debug("map %d: %s / %s", i,
snd_pcm_chmap_name(map->pos[i]),
spa_debug_type_find_short_name(spa_type_audio_channel,
pw->format.position[i]));
position[i]));
}
return 1;
}
@ -847,13 +929,26 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io)
{
snd_pcm_pipewire_t *pw = io->private_data;
snd_pcm_chmap_t *map;
uint32_t i;
uint32_t i, channels, *position;
switch (pw->requested.media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
channels = pw->requested.info.raw.channels;
position = pw->requested.info.raw.position;
break;
case SPA_MEDIA_SUBTYPE_dsd:
channels = pw->requested.info.dsd.channels;
position = pw->requested.info.dsd.position;
break;
default:
return NULL;
}
map = calloc(1, sizeof(snd_pcm_chmap_t) +
pw->format.channels * sizeof(unsigned int));
map->channels = pw->format.channels;
for (i = 0; i < pw->format.channels; i++)
map->pos[i] = channel_to_chmap(pw->format.position[i]);
channels * sizeof(unsigned int));
map->channels = channels;
for (i = 0; i < channels; i++)
map->pos[i] = channel_to_chmap(position[i]);
return map;
}
@ -989,7 +1084,16 @@ struct param_info infos[] = {
SND_PCM_FORMAT_S24_3BE,
SND_PCM_FORMAT_S16_BE,
#endif
SND_PCM_FORMAT_U8 }, 7, collect_format },
SND_PCM_FORMAT_U8,
/* we don't add DSD formats here, use alsa.formats to
* force this. Because we can't convert to/from DSD, enabling this
* might fail when the system has no native DSD
* SND_PCM_FORMAT_DSD_U32_BE,
* SND_PCM_FORMAT_DSD_U32_LE,
* SND_PCM_FORMAT_DSD_U16_BE,
* SND_PCM_FORMAT_DSD_U16_LE,
* SND_PCM_FORMAT_DSD_U8 */
}, 7, collect_format },
{ "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX,
{ 1, MAX_RATE }, 2, collect_int },
{ "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX,