spa: acp: get and emit UCM information for SplitPCM devices

When api.alsa.split-enable=true for ACP device, instruct UCM to not
use alsa-lib plugins for SplitPCM devices.

Grab the information from UCM for the intended channel remapping, and
add the splitting information to the nodes emitted.

Session manager can then look at that, and load nodes to do the channel
splitting.
This commit is contained in:
Pauli Virtanen 2024-12-07 13:27:09 +02:00
parent 7c7a54dd87
commit dcccfcab7f
12 changed files with 422 additions and 22 deletions

View file

@ -713,6 +713,11 @@ Normally, the maximum amount of channels will be used but with this setting
this can be reduced, which can make it possible to use other samplerates on
some devices.
@PAR@ device-prop api.alsa.split-enable # boolean
\parblock
\copydoc SPA_KEY_API_ALSA_SPLIT_ENABLE
\endparblock
## Node properties
@PAR@ node-prop audio.channels # integer
@ -796,6 +801,36 @@ UNDOCUMENTED
Enable only specific IEC958 codecs. This can be used to disable some codecs the hardware supports.
Available values: PCM, AC3, DTS, MPEG, MPEG2-AAC, EAC3, TRUEHD, DTSHD
@PAR@ device-prop api.alsa.split.parent # boolean
\parblock
\copydoc SPA_KEY_API_ALSA_SPLIT_PARENT
\endparblock
@PAR@ node-prop api.alsa.split.position # JSON
\parblock
\copybrief SPA_KEY_API_ALSA_SPLIT_POSITION
Informative property.
\endparblock
@PAR@ node-prop api.alsa.split.leader # boolean
\parblock
\copybrief SPA_KEY_API_ALSA_SPLIT_LEADER
Informative property.
\endparblock
@PAR@ node-prop api.alsa.split.hw-channels # int
\parblock
\copybrief SPA_KEY_API_ALSA_SPLIT_HW_CHANNELS
Informative property.
\endparblock
@PAR@ node-prop api.alsa.split.hw-position # JSON
\parblock
\copybrief SPA_KEY_API_ALSA_SPLIT_HW_POSITION
Informative property.
\endparblock
# BLUETOOTH PROPERTIES @IDX@ props
## Monitor properties

View file

@ -45,6 +45,14 @@ extern "C" {
#define SPA_KEY_API_ALSA_DISABLE_LONGNAME \
"api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */
#define SPA_KEY_API_ALSA_BIND_CTLS "api.alsa.bind-ctls" /**< alsa controls to bind as params */
#define SPA_KEY_API_ALSA_SPLIT_ENABLE "api.alsa.split-enable" /**< For UCM devices with split PCMs, don't split to
* multiple PCMs using alsa-lib plugins, but instead
* add api.alsa.split properties to emitted nodes
* with PCM splitting information.
*/
#define SPA_KEY_API_ALSA_SPLIT_PARENT "api.alsa.split.parent" /**< PCM is UCM SplitPCM parent PCM,
* to be opened with SplitPCM set.
*/
/** info from alsa card_info */
#define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */
@ -67,6 +75,21 @@ extern "C" {
#define SPA_KEY_API_ALSA_PCM_SUBCLASS "api.alsa.pcm.subclass" /**< subclass from pcm_info as string */
#define SPA_KEY_API_ALSA_PCM_SYNC_ID "api.alsa.pcm.sync-id" /**< sync id */
#define SPA_KEY_API_ALSA_SPLIT_POSITION "api.alsa.split.position" /**< (SPA JSON list) If present, this is a
* virtual device corresponding to a subset of
* channels in an underlying PCM, listed in this
* property. The \ref SPA_KEY_API_ALSA_PATH
* contains the underlying split PCM. */
#define SPA_KEY_API_ALSA_SPLIT_LEADER "api.alsa.split.leader" /**< (bool) This virtual device is split leader:
* one of the split devices for the same split
* PCM is selected as the leader. */
#define SPA_KEY_API_ALSA_SPLIT_HW_CHANNELS \
"api.alsa.split.hw-channels" /**< (int) Number of channels in the underlying
* split PCM. */
#define SPA_KEY_API_ALSA_SPLIT_HW_POSITION \
"api.alsa.split.hw-position" /**< (SPA JSON list) Channel map of the
* underlying split PCM. */
/** keys for v4l2 api */
#define SPA_KEY_API_V4L2 "api.v4l2" /**< key for the v4l2 api */
#define SPA_KEY_API_V4L2_PATH "api.v4l2.path" /**< v4l2 device path as can be

View file

@ -230,6 +230,28 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t
dev->device.direction = ACP_DIRECTION_CAPTURE;
pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist);
}
if (m->split) {
char pos[2048];
struct spa_strbuf b;
int i;
pa_proplist_sets(dev->proplist, "api.alsa.split.leader", m->split->leader ? "true" : "false");
pa_proplist_setf(dev->proplist, "api.alsa.split.hw-channels", "%d", m->split->hw_channels);
spa_strbuf_init(&b, pos, sizeof(pos));
spa_strbuf_append(&b, "[");
for (i = 0; i < m->split->channels; ++i)
spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), m->split->idx[i]);
spa_strbuf_append(&b, "]");
pa_proplist_sets(dev->proplist, "api.alsa.split.position", pos);
spa_strbuf_init(&b, pos, sizeof(pos));
spa_strbuf_append(&b, "[");
for (i = 0; i < m->split->hw_channels; ++i)
spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), i);
spa_strbuf_append(&b, "]");
pa_proplist_sets(dev->proplist, "api.alsa.split.hw-position", pos);
}
pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_NAME, m->name);
pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, m->description);
pa_proplist_setf(dev->proplist, "card.profile.device", "%u", index);
@ -1758,8 +1780,17 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
impl->rate = atoi(s);
if ((s = acp_dict_lookup(props, "api.acp.pro-channels")) != NULL)
impl->pro_channels = atoi(s);
if ((s = acp_dict_lookup(props, "api.alsa.split-enable")) != NULL)
impl->ucm.split_enable = spa_atob(s);
}
#if SND_LIB_VERSION < 0x10207
if (impl->ucm.split_enable)
pa_log_info("alsa-lib too old for PipeWire-side UCM SplitPCM");
impl->ucm.split_enable = false; /* API addition in 1.2.7 */
#endif
impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE;
impl->ucm.default_sample_spec.rate = impl->rate;
impl->ucm.default_sample_spec.channels = 2;

View file

@ -3787,6 +3787,8 @@ void pa_alsa_mapping_free(pa_alsa_mapping *m) {
pa_proplist_free(m->input_proplist);
pa_proplist_free(m->output_proplist);
pa_xfree(m->split);
pa_assert(!m->input_pcm);
pa_assert(!m->output_pcm);

View file

@ -327,6 +327,8 @@ struct pa_alsa_mapping {
pa_sample_spec sample_spec;
pa_channel_map channel_map;
pa_alsa_ucm_split *split;
char **device_strings;
char **input_path_names;

View file

@ -326,6 +326,90 @@ static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink)
return dev_name;
}
static PA_PRINTF_FUNC(2,3) const char *ucm_get_string(snd_use_case_mgr_t *uc_mgr, const char *fmt, ...)
{
char *id;
const char *value;
va_list args;
int err;
va_start(args, fmt);
id = pa_vsprintf_malloc(fmt, args);
va_end(args);
err = snd_use_case_get(uc_mgr, id, &value);
if (err >= 0)
pa_log_debug("Got %s: %s", id, value);
pa_xfree(id);
if (err < 0) {
errno = -err;
return NULL;
}
return value;
}
static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, const char *prefix) {
pa_alsa_ucm_split *split;
const char *value;
const char *device_name;
int i;
uint32_t hw_channels;
device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
if (!device_name)
return NULL;
value = ucm_get_string(uc_mgr, "%sChannels/%s", prefix, device_name);
if (pa_atou(value, &hw_channels) < 0)
return NULL;
split = pa_xnew0(pa_alsa_ucm_split, 1);
for (i = 0; i < PA_CHANNELS_MAX; i++) {
uint32_t idx;
snd_pcm_chmap_t *map;
value = ucm_get_string(uc_mgr, "%sChannel%d/%s", prefix, i, device_name);
if (pa_atou(value, &idx) < 0)
break;
if (idx >= hw_channels)
goto fail;
value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name);
if (!value)
goto fail;
map = snd_pcm_chmap_parse_string(value);
if (!map)
goto fail;
if (map->channels == 1) {
pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)",
prefix, (int)idx, device_name, i, value, map->pos[0]);
split->idx[i] = idx;
split->pos[i] = map->pos[0];
free(map);
} else {
free(map);
goto fail;
}
}
if (i == 0) {
pa_xfree(split);
return NULL;
}
split->channels = i;
split->hw_channels = hw_channels;
return split;
fail:
pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name);
pa_xfree(split);
return NULL;
}
/* Create a property list for this ucm device */
static int ucm_get_device_property(
pa_alsa_ucm_device *device,
@ -470,6 +554,9 @@ static int ucm_get_device_property(
pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
}
device->playback_split = ucm_get_split_channels(device, uc_mgr, "Playback");
device->capture_split = ucm_get_split_channels(device, uc_mgr, "Capture");
if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
/* get priority from static table */
for (i = 0; dev_info[i].id; i++) {
@ -868,21 +955,30 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
char *card_name;
const char **verb_list, *value;
int num_verbs, i, err = 0;
const char *split_prefix = ucm->split_enable ? "<<<SplitPCM=1>>>" : "";
/* support multiple card instances, address card directly by index */
card_name = pa_sprintf_malloc("hw:%i", card_index);
card_name = pa_sprintf_malloc("%shw:%i", split_prefix, card_index);
if (card_name == NULL)
return -PA_ALSA_ERR_UNSPECIFIED;
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
char *ucm_card_name;
/* fallback longname: is UCM available for this card ? */
pa_xfree(card_name);
err = snd_card_get_name(card_index, &card_name);
err = snd_card_get_name(card_index, &ucm_card_name);
if (err < 0) {
pa_log("Card can't get card_name from card_index %d", card_index);
err = -PA_ALSA_ERR_UNSPECIFIED;
goto name_fail;
}
card_name = pa_sprintf_malloc("%s%s", split_prefix, ucm_card_name);
free(ucm_card_name);
if (card_name == NULL) {
err = -PA_ALSA_ERR_UNSPECIFIED;
goto name_fail;
}
err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
if (err < 0) {
@ -955,6 +1051,54 @@ name_fail:
return err;
}
static void ucm_verb_set_split_leaders(pa_alsa_ucm_verb *verb) {
pa_alsa_ucm_device *d, *d2;
/* Set first virtual device in each split HW PCM as the split leader */
PA_LLIST_FOREACH(d, verb->devices) {
if (d->playback_split)
d->playback_split->leader = true;
if (d->capture_split)
d->capture_split->leader = true;
}
PA_LLIST_FOREACH(d, verb->devices) {
const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (d->playback_split) {
if (!sink)
d->playback_split->leader = false;
if (d->playback_split->leader) {
PA_LLIST_FOREACH(d2, verb->devices) {
const char *sink2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SINK);
if (d == d2 || !d2->playback_split || !sink || !sink2 || !pa_streq(sink, sink2))
continue;
d2->playback_split->leader = false;
}
}
}
if (d->capture_split) {
if (!source)
d->capture_split->leader = false;
if (d->capture_split->leader) {
PA_LLIST_FOREACH(d2, verb->devices) {
const char *source2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (d == d2 || !d2->capture_split || !source || !source2 || !pa_streq(source, source2))
continue;
d2->capture_split->leader = false;
}
}
}
}
}
int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) {
pa_alsa_ucm_device *d;
pa_alsa_ucm_modifier *mod;
@ -994,6 +1138,9 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons
/* Devices properties */
ucm_get_device_property(d, uc_mgr, verb, dev_name);
}
ucm_verb_set_split_leaders(verb);
/* make conflicting or supported device mutual */
PA_LLIST_FOREACH(d, verb->devices)
append_lost_relationship(d);
@ -1372,15 +1519,19 @@ static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev)
if (!pa_idxset_contains(d->supported_devices, dev))
return false;
/* PlaybackPCM must not be the same as any selected device */
/* PlaybackPCM must not be the same as any selected device, except when both split */
sink2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK);
if (sink && sink2 && pa_streq(sink, sink2))
return false;
if (sink && sink2 && pa_streq(sink, sink2)) {
if (!(dev->playback_split && d->playback_split))
return false;
}
/* CapturePCM must not be the same as any selected device */
/* CapturePCM must not be the same as any selected device, except when both split */
source2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE);
if (source && source2 && pa_streq(source, source2))
return false;
if (source && source2 && pa_streq(source, source2)) {
if (!(dev->capture_split && d->capture_split))
return false;
}
}
return true;
@ -1753,6 +1904,69 @@ static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_pr
return m;
}
static const struct {
enum snd_pcm_chmap_position pos;
pa_channel_position_t channel;
} chmap_info[] = {
[SND_CHMAP_MONO] = { SND_CHMAP_MONO, PA_CHANNEL_POSITION_MONO },
[SND_CHMAP_FL] = { SND_CHMAP_FL, PA_CHANNEL_POSITION_FRONT_LEFT },
[SND_CHMAP_FR] = { SND_CHMAP_FR, PA_CHANNEL_POSITION_FRONT_RIGHT },
[SND_CHMAP_RL] = { SND_CHMAP_RL, PA_CHANNEL_POSITION_REAR_LEFT },
[SND_CHMAP_RR] = { SND_CHMAP_RR, PA_CHANNEL_POSITION_REAR_RIGHT },
[SND_CHMAP_FC] = { SND_CHMAP_FC, PA_CHANNEL_POSITION_FRONT_CENTER },
[SND_CHMAP_LFE] = { SND_CHMAP_LFE, PA_CHANNEL_POSITION_LFE },
[SND_CHMAP_SL] = { SND_CHMAP_SL, PA_CHANNEL_POSITION_SIDE_LEFT },
[SND_CHMAP_SR] = { SND_CHMAP_SR, PA_CHANNEL_POSITION_SIDE_RIGHT },
[SND_CHMAP_RC] = { SND_CHMAP_RC, PA_CHANNEL_POSITION_REAR_CENTER },
[SND_CHMAP_FLC] = { SND_CHMAP_FLC, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER },
[SND_CHMAP_FRC] = { SND_CHMAP_FRC, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER },
/* XXX: missing channel positions, mapped to aux... */
/* [SND_CHMAP_RLC] = { SND_CHMAP_RLC, PA_CHANNEL_POSITION_REAR_LEFT_OF_CENTER }, */
/* [SND_CHMAP_RRC] = { SND_CHMAP_RRC, PA_CHANNEL_POSITION_REAR_RIGHT_OF_CENTER }, */
/* [SND_CHMAP_FLW] = { SND_CHMAP_FLW, PA_CHANNEL_POSITION_FRONT_LEFT_WIDE }, */
/* [SND_CHMAP_FRW] = { SND_CHMAP_FRW, PA_CHANNEL_POSITION_FRONT_RIGHT_WIDE }, */
/* [SND_CHMAP_FLH] = { SND_CHMAP_FLH, PA_CHANNEL_POSITION_FRONT_LEFT_HIGH }, */
/* [SND_CHMAP_FCH] = { SND_CHMAP_FCH, PA_CHANNEL_POSITION_FRONT_CENTER_HIGH }, */
/* [SND_CHMAP_FRH] = { SND_CHMAP_FRH, PA_CHANNEL_POSITION_FRONT_RIGHT_HIGH }, */
[SND_CHMAP_TC] = { SND_CHMAP_TC, PA_CHANNEL_POSITION_TOP_CENTER },
[SND_CHMAP_TFL] = { SND_CHMAP_TFL, PA_CHANNEL_POSITION_TOP_FRONT_LEFT },
[SND_CHMAP_TFR] = { SND_CHMAP_TFR, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT },
[SND_CHMAP_TFC] = { SND_CHMAP_TFC, PA_CHANNEL_POSITION_TOP_FRONT_CENTER },
[SND_CHMAP_TRL] = { SND_CHMAP_TRL, PA_CHANNEL_POSITION_TOP_REAR_LEFT },
[SND_CHMAP_TRR] = { SND_CHMAP_TRR, PA_CHANNEL_POSITION_TOP_REAR_RIGHT },
[SND_CHMAP_TRC] = { SND_CHMAP_TRC, PA_CHANNEL_POSITION_TOP_REAR_CENTER },
/* [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, PA_CHANNEL_POSITION_TOP_FRONT_LEFT_OF_CENTER }, */
/* [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT_OF_CENTER }, */
/* [SND_CHMAP_TSL] = { SND_CHMAP_TSL, PA_CHANNEL_POSITION_TOP_SIDE_LEFT }, */
/* [SND_CHMAP_TSR] = { SND_CHMAP_TSR, PA_CHANNEL_POSITION_TOP_SIDE_RIGHT }, */
/* [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, PA_CHANNEL_POSITION_LEFT_LFE }, */
/* [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, PA_CHANNEL_POSITION_RIGHT_LFE }, */
/* [SND_CHMAP_BC] = { SND_CHMAP_BC, PA_CHANNEL_POSITION_BOTTOM_CENTER }, */
/* [SND_CHMAP_BLC] = { SND_CHMAP_BLC, PA_CHANNEL_POSITION_BOTTOM_LEFT_OF_CENTER }, */
/* [SND_CHMAP_BRC] = { SND_CHMAP_BRC, PA_CHANNEL_POSITION_BOTTOM_RIGHT_OF_CENTER }, */
};
static void ucm_split_to_channel_map(pa_channel_map *m, const pa_alsa_ucm_split *s)
{
const int n = sizeof(chmap_info) / sizeof(chmap_info[0]);
int i;
int aux = 0;
for (i = 0; i < s->channels; ++i) {
int p = s->pos[i];
if (p >= 0 && p < n && (int)chmap_info[p].pos == p)
m->map[i] = chmap_info[p].channel;
else
m->map[i] = PA_CHANNEL_POSITION_AUX0 + aux++;
if (aux >= 32)
break;
}
m->channels = i;
}
static int ucm_create_mapping_direction(
pa_alsa_ucm_config *ucm,
pa_alsa_profile_set *ps,
@ -1797,6 +2011,14 @@ static int ucm_create_mapping_direction(
if (channels < m->channel_map.channels)
pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA);
if (is_sink && device->playback_split) {
m->split = pa_xmemdup(device->playback_split, sizeof(*m->split));
ucm_split_to_channel_map(&m->channel_map, m->split);
} else if (!is_sink && device->capture_split) {
m->split = pa_xmemdup(device->capture_split, sizeof(*m->split));
ucm_split_to_channel_map(&m->channel_map, m->split);
}
alsa_mapping_add_ucm_device(m, device);
return 0;
@ -2168,11 +2390,22 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
snd_pcm_uframes_t try_period_size, try_buffer_size;
bool exact_channels = m->channel_map.channels > 0;
if (exact_channels) {
try_map = m->channel_map;
try_ss.channels = try_map.channels;
} else
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
if (!m->split) {
if (exact_channels) {
try_map = m->channel_map;
try_ss.channels = try_map.channels;
} else
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA);
} else {
if (!m->split->leader) {
errno = EINVAL;
return NULL;
}
exact_channels = true;
try_ss.channels = m->split->hw_channels;
pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX);
}
try_period_size =
pa_usec_to_bytes(ucm->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) /
@ -2191,6 +2424,32 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m,
return pcm;
}
static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction)
{
pa_proplist *props = pa_proplist_new();
uint32_t idx;
pa_alsa_mapping *m;
if (direction == PA_DIRECTION_OUTPUT)
pa_alsa_init_proplist_pcm(NULL, props, leader->output_pcm);
else
pa_alsa_init_proplist_pcm(NULL, props, leader->input_pcm);
PA_IDXSET_FOREACH(m, mappings, idx) {
if (!m->split)
continue;
if (!pa_streq(m->device_strings[0], leader->device_strings[0]))
continue;
if (direction == PA_DIRECTION_OUTPUT)
pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props);
else
pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props);
}
pa_proplist_free(props);
}
static void profile_finalize_probing(pa_alsa_profile *p) {
pa_alsa_mapping *m;
uint32_t idx;
@ -2202,7 +2461,11 @@ static void profile_finalize_probing(pa_alsa_profile *p) {
if (!m->output_pcm)
continue;
pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm);
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm);
else
pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT);
pa_alsa_close(&m->output_pcm);
}
@ -2213,7 +2476,11 @@ static void profile_finalize_probing(pa_alsa_profile *p) {
if (!m->input_pcm)
continue;
pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm);
if (!m->split)
pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm);
else
pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT);
pa_alsa_close(&m->input_pcm);
}
}
@ -2266,6 +2533,9 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
continue;
}
if (m->split && !m->split->leader)
continue;
m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK);
if (!m->output_pcm) {
p->supported = false;
@ -2281,6 +2551,9 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *
continue;
}
if (m->split && !m->split->leader)
continue;
m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE);
if (!m->input_pcm) {
p->supported = false;
@ -2370,6 +2643,9 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
pa_xfree(di->eld_mixer_device_name);
pa_xfree(di->playback_split);
pa_xfree(di->capture_split);
pa_xfree(di);
}

View file

@ -145,6 +145,7 @@ typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context;
typedef struct pa_alsa_ucm_profile_context pa_alsa_ucm_profile_context;
typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data;
typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume;
typedef struct pa_alsa_ucm_split pa_alsa_ucm_split;
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index);
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
@ -177,6 +178,15 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
/* UCM - Use Case Manager is available on some audio cards */
struct pa_alsa_ucm_split {
/* UCM SplitPCM channel remapping */
bool leader;
int hw_channels;
int channels;
int idx[PA_CHANNELS_MAX];
enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX];
};
struct pa_alsa_ucm_device {
PA_LLIST_FIELDS(pa_alsa_ucm_device);
@ -215,6 +225,9 @@ struct pa_alsa_ucm_device {
char *eld_mixer_device_name;
int eld_device;
pa_alsa_ucm_split *playback_split;
pa_alsa_ucm_split *capture_split;
};
void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device);
@ -254,6 +267,7 @@ struct pa_alsa_ucm_config {
pa_channel_map default_channel_map;
unsigned default_fragment_size_msec;
unsigned default_n_fragments;
bool split_enable;
snd_use_case_mgr_t *ucm_mgr;
pa_alsa_ucm_verb *active_verb;

View file

@ -414,6 +414,14 @@ static PA_PRINTF_FUNC(1,2) inline char *pa_sprintf_malloc(const char *fmt, ...)
return res;
}
static PA_PRINTF_FUNC(1,0) inline char *pa_vsprintf_malloc(const char *fmt, va_list args)
{
char *res;
if (vasprintf(&res, fmt, args) < 0)
res = NULL;
return res;
}
#define pa_fopen_cloexec(f,m) fopen(f,m"e")
static inline char *pa_path_get_filename(const char *p)

View file

@ -27,6 +27,7 @@ static void reset_props(struct props *props)
{
strncpy(props->device, default_device, 64);
props->use_chmap = DEFAULT_USE_CHMAP;
spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Sink");
}
static int impl_node_enum_params(void *object, int seq,

View file

@ -28,6 +28,7 @@ static void reset_props(struct props *props)
{
strncpy(props->device, default_device, 64);
props->use_chmap = DEFAULT_USE_CHMAP;
spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Source");
}
static int impl_node_enum_params(void *object, int seq,

View file

@ -33,10 +33,10 @@ static struct card *find_card(uint32_t index)
return NULL;
}
static struct card *ensure_card(uint32_t index, bool ucm)
static struct card *ensure_card(uint32_t index, bool ucm, bool ucm_split)
{
struct card *c;
char card_name[64];
char card_name[128];
const char *alibpref = NULL;
int err;
@ -51,7 +51,9 @@ static struct card *ensure_card(uint32_t index, bool ucm)
c->index = index;
if (ucm) {
snprintf(card_name, sizeof(card_name), "hw:%i", index);
const char *split_prefix = ucm_split ? "<<<SplitPCM=1>>>" : "";
snprintf(card_name, sizeof(card_name), "%shw:%i", split_prefix, index);
err = snd_use_case_mgr_open(&c->ucm, card_name);
if (err < 0) {
char *name;
@ -59,7 +61,7 @@ static struct card *ensure_card(uint32_t index, bool ucm)
if (err < 0)
goto error;
snprintf(card_name, sizeof(card_name), "%s", name);
snprintf(card_name, sizeof(card_name), "%s%s", split_prefix, name);
free(name);
err = snd_use_case_mgr_open(&c->ucm, card_name);
@ -211,6 +213,10 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
} else if (spa_strstartswith(k, "api.alsa.bind-ctl.")) {
write_bind_ctl_param(state, k, s);
fmt_change++;
} else if (spa_streq(k, SPA_KEY_MEDIA_CLASS)) {
spa_scnprintf(state->props.media_class, sizeof(state->props.media_class), "%s", s);
} else if (spa_streq(k, "api.alsa.split.parent")) {
state->is_split_parent = true;
} else
return 0;
@ -1020,7 +1026,7 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info)
state->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM;
}
state->card = ensure_card(state->card_index, state->open_ucm);
state->card = ensure_card(state->card_index, state->open_ucm, state->is_split_parent);
state->log_file = fopencookie(state, "w", io_funcs);
if (state->log_file == NULL) {
@ -3821,8 +3827,7 @@ void spa_alsa_emit_node_info(struct state *state, bool full)
char latency[64] = "", period[64] = "", nperiods[64] = "", headroom[64] = "";
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS,
state->stream == SND_PCM_STREAM_PLAYBACK ? "Audio/Sink" : "Audio/Source");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, state->props.media_class);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true");
if (state->have_format)

View file

@ -50,6 +50,7 @@ struct props {
char device[64];
char device_name[128];
char card_name[128];
char media_class[128];
bool use_chmap;
};
@ -153,6 +154,7 @@ struct state {
unsigned int disable_mmap:1;
unsigned int disable_batch:1;
unsigned int disable_tsched:1;
unsigned int is_split_parent:1;
char clock_name[64];
uint32_t quantum_limit;