mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
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:
parent
7c7a54dd87
commit
dcccfcab7f
12 changed files with 422 additions and 22 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue