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.
This commit is contained in:
Pauli Virtanen 2025-03-12 18:40:14 +02:00 committed by Wim Taymans
parent 6c9ada270b
commit 4338189f76

View file

@ -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) {