From ba3a36b3d1ed57ae6a022050e7a7b2199a693e36 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 12 Mar 2025 18:40:14 +0200 Subject: [PATCH] spa: acp: allow also too few channels in SplitPCM configs GoXLR Mini has different numbers of channels actually available (21, 23, or 25) depending on its firmware/etc, but its UCM profile specifies always 23. The count can then be bigger or smaller than what is actually available. Fail a bit more gracefully in the case of too few channels: create all the split devices specified by the profile. The channels that aren't actually available in HW just won't get routed anywhere. ALSA upstream IIUC is saying that the channel counts should be fixed, so spew warnings that say the UCM profiles are wrong if they look wrong. --- spa/plugins/alsa/acp/alsa-ucm.c | 45 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index f64c573a4..41e0a1874 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -353,6 +353,15 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd const char *device_name; int i; uint32_t hw_channels; + const char *pcm_name; + const char *rule_name; + + if (spa_streq(prefix, "Playback")) + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); + else + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); + if (!pcm_name) + pcm_name = ""; device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); if (!device_name) @@ -373,15 +382,20 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd break; if (idx >= hw_channels) - goto fail; + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d", + pcm_name, device_name, prefix, i, idx, prefix, hw_channels); value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); - if (!value) + if (!value) { + rule_name = "ChannelPos"; goto fail; + } map = snd_pcm_chmap_parse_string(value); - if (!map) + if (!map) { + rule_name = "ChannelPos value"; goto fail; + } if (map->channels == 1) { pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)", @@ -391,6 +405,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd free(map); } else { free(map); + rule_name = "channel map parsing"; goto fail; } } @@ -405,7 +420,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd return split; fail: - pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name); + pa_log_warn("Invalid SplitPCM ALSA UCM %s for device %s (%s)", rule_name, pcm_name, device_name); pa_xfree(split); return NULL; } @@ -2422,21 +2437,25 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, if (pcm) { if (m->split) { - if (try_map.channels < m->split->hw_channels) { - pa_alsa_close(&pcm); + const char *mode_name = mode == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"; - pa_logl((max_channels ? PA_LOG_WARN : PA_LOG_DEBUG), - "Too few channels in %s for ALSA UCM SplitPCM: avail %d < required %d", - m->device_strings[0], try_map.channels, m->split->hw_channels); + if (try_map.channels < m->split->hw_channels) { + pa_logl((max_channels ? PA_LOG_NOTICE : PA_LOG_DEBUG), + "Error in ALSA UCM profile for %s (%s): %sChannels=%d > avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); /* Retry with max channel count, in case ALSA rounded down */ - if (!max_channels) + if (!max_channels) { + pa_alsa_close(&pcm); return mapping_open_pcm(ucm, m, mode, true); + } - return NULL; + /* Just accept whatever we got... Some of the routings won't get connected + * anywhere */ + m->split->hw_channels = try_map.channels; } else if (try_map.channels > m->split->hw_channels) { - pa_log_debug("Update split PCM channel count for %s: %d -> %d", - m->device_strings[0], m->split->hw_channels, try_map.channels); + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); m->split->hw_channels = try_map.channels; } } else if (!exact_channels) {