diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 39475baf5..a0152e5a8 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -469,7 +469,7 @@ static void add_profiles(pa_card *impl) pa_dynarray_append(&impl->out.devices, dev); } if (impl->use_ucm) { - pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, + pa_alsa_ucm_add_port(NULL, &m->ucm_context, true, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, true, impl, dev->pcm_handle, impl->profile_set->ignore_dB); @@ -491,7 +491,7 @@ static void add_profiles(pa_card *impl) } if (impl->use_ucm) { - pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, + pa_alsa_ucm_add_port(NULL, &m->ucm_context, false, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, false, impl, dev->pcm_handle, impl->profile_set->ignore_dB); @@ -1231,8 +1231,7 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) * will be NULL, but the UCM device enable sequence will still need to be * executed. */ if (dev->active_port && dev->ucm_context) { - if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port, - dev->direction == PA_ALSA_DIRECTION_OUTPUT)) < 0) + if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port)) < 0) return res; } @@ -1408,8 +1407,7 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index, uint32_t fla /* if UCM is available for this card then update the verb */ if (impl->use_ucm) { if ((res = pa_alsa_ucm_set_profile(&impl->ucm, impl, - np->profile.flags & ACP_PROFILE_OFF ? NULL : np->profile.name, - op ? op->profile.name : NULL)) < 0) { + np->profile.flags & ACP_PROFILE_OFF ? NULL : np, op)) < 0) { return res; } } @@ -1418,7 +1416,7 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index, uint32_t fla PA_IDXSET_FOREACH(am, np->output_mappings, idx) { if (impl->use_ucm) /* Update ports priorities */ - pa_alsa_ucm_add_ports_combination(am->output.ports, &am->ucm_context, + pa_alsa_ucm_add_port(am->output.ports, &am->ucm_context, true, impl->ports, np, NULL); device_enable(impl, am, &am->output); } @@ -1428,7 +1426,7 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index, uint32_t fla PA_IDXSET_FOREACH(am, np->input_mappings, idx) { if (impl->use_ucm) /* Update ports priorities */ - pa_alsa_ucm_add_ports_combination(am->input.ports, &am->ucm_context, + pa_alsa_ucm_add_port(am->input.ports, &am->ucm_context, false, impl->ports, np, NULL); device_enable(impl, am, &am->input); } @@ -1810,8 +1808,7 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t fl mixer_volume_init(impl, d); sync_mixer(d, p); - res = pa_alsa_ucm_set_port(d->ucm_context, p, - dev->direction == ACP_DIRECTION_PLAYBACK); + res = pa_alsa_ucm_set_port(d->ucm_context, p); } else { pa_alsa_port_data *data; diff --git a/spa/plugins/alsa/acp/alsa-mixer.h b/spa/plugins/alsa/acp/alsa-mixer.h index 643f03dba..1fbfd5a31 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.h +++ b/spa/plugins/alsa/acp/alsa-mixer.h @@ -381,6 +381,9 @@ struct pa_alsa_profile { pa_idxset *input_mappings; pa_idxset *output_mappings; + /* ucm device context */ + pa_alsa_ucm_profile_context ucm_context; + struct { pa_dynarray devices; } out; diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index eea173b32..fdfbe143a 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -72,9 +72,8 @@ static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char * static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices); + pa_alsa_ucm_device *device); static void ucm_port_data_free(pa_device_port *port); -static void ucm_port_update_available(pa_alsa_ucm_port_data *port); static struct ucm_type types[] = { {"None", PA_DEVICE_PORT_TYPE_UNKNOWN}, @@ -170,17 +169,6 @@ static char *ucm_verb_value( return (char *)value; } -static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) { - pa_alsa_ucm_device *d; - uint32_t idx; - - PA_IDXSET_FOREACH(d, idxset, idx) - if (d == dev) - return 1; - - return 0; -} - static void ucm_add_devices_to_idxset( pa_idxset *idxset, pa_alsa_ucm_device *me, @@ -506,10 +494,10 @@ static int ucm_get_device_property( n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); + device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_confdev <= 0) pa_log_debug("No %s for device %s", "_conflictingdevs", device_name); else { - device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev); snd_use_case_free_list(devices, n_confdev); } @@ -518,10 +506,10 @@ static int ucm_get_device_property( n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); + device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_suppdev <= 0) pa_log_debug("No %s for device %s", "_supporteddevs", device_name); else { - device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev); snd_use_case_free_list(devices, n_suppdev); } @@ -530,10 +518,16 @@ static int ucm_get_device_property( }; /* Create a property list for this ucm modifier */ -static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) { +static int ucm_get_modifier_property( + pa_alsa_ucm_modifier *modifier, + snd_use_case_mgr_t *uc_mgr, + pa_alsa_ucm_verb *verb, + const char *modifier_name) { const char *value; char *id; int i; + const char **devices; + int n_confdev, n_suppdev; for (i = 0; item[i].id; i++) { int err; @@ -550,16 +544,28 @@ static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_cas } id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name); - modifier->n_confdev = snd_use_case_get_list(uc_mgr, id, &modifier->conflicting_devices); + n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); - if (modifier->n_confdev < 0) + + modifier->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (n_confdev <= 0) pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name); + else { + ucm_add_devices_to_idxset(modifier->conflicting_devices, NULL, verb->devices, devices, n_confdev); + snd_use_case_free_list(devices, n_confdev); + } id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name); - modifier->n_suppdev = snd_use_case_get_list(uc_mgr, id, &modifier->supported_devices); + n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); - if (modifier->n_suppdev < 0) + + modifier->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (n_suppdev <= 0) pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name); + else { + ucm_add_devices_to_idxset(modifier->supported_devices, NULL, verb->devices, devices, n_suppdev); + snd_use_case_free_list(devices, n_suppdev); + } return 0; }; @@ -596,6 +602,59 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { return 0; }; +static long ucm_device_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + char *devstatus; + long status = 0; + + devstatus = pa_sprintf_malloc("_devstatus/%s", dev_name); + if (snd_use_case_geti(ucm->ucm_mgr, devstatus, &status) < 0) { + pa_log_debug("Failed to get status for UCM device %s", dev_name); + status = -1; + } + pa_xfree(devstatus); + + return status; +} + +static int ucm_device_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + /* If any of dev's conflicting devices is enabled, trying to disable + * dev gives an error despite the fact that it's already disabled. + * Check that dev is enabled to avoid this error. */ + if (ucm_device_status(ucm, dev) == 0) { + pa_log_debug("UCM device %s is already disabled", dev_name); + return 0; + } + + pa_log_debug("Disabling UCM device %s", dev_name); + if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) < 0) { + pa_log("Failed to disable UCM device %s", dev_name); + return -1; + } + + return 0; +} + +static int ucm_device_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + /* We don't need to enable devices that are already enabled */ + if (ucm_device_status(ucm, dev) > 0) { + pa_log_debug("UCM device %s is already enabled", dev_name); + return 0; + } + + pa_log_debug("Enabling UCM device %s", dev_name); + if (snd_use_case_set(ucm->ucm_mgr, "_enadev", dev_name) < 0) { + pa_log("Failed to enable UCM device %s", dev_name); + return -1; + } + + return 0; +} + static int ucm_get_modifiers(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { const char **mod_list; int num_mod, i; @@ -642,27 +701,19 @@ static void add_role_to_device(pa_alsa_ucm_device *dev, const char *dev_name, co role_name)); } -static void add_media_role(const char *name, pa_alsa_ucm_device *list, const char *role_name, const char *role, bool is_sink) { - pa_alsa_ucm_device *d; +static void add_media_role(pa_alsa_ucm_device *dev, const char *role_name, const char *role, bool is_sink) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + const char *sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); + const char *source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); - PA_LLIST_FOREACH(d, list) { - const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); - - if (pa_streq(dev_name, name)) { - 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 (is_sink && sink) - add_role_to_device(d, dev_name, role_name, role); - else if (!is_sink && source) - add_role_to_device(d, dev_name, role_name, role); - break; - } - } + if (is_sink && sink) + add_role_to_device(dev, dev_name, role_name, role); + else if (!is_sink && source) + add_role_to_device(dev, dev_name, role_name, role); } static char *modifier_name_to_role(const char *mod_name, bool *is_sink) { - char *sub = NULL, *tmp; + char *sub = NULL, *tmp, *pos; *is_sink = false; @@ -672,26 +723,32 @@ static char *modifier_name_to_role(const char *mod_name, bool *is_sink) { } else if (pa_startswith(mod_name, "Capture")) sub = pa_xstrdup(mod_name + 7); - if (!sub || !*sub) { + pos = sub; + while (pos && *pos == ' ') pos++; + + if (!pos || !*pos) { pa_xfree(sub); pa_log_warn("Can't match media roles for modifier %s", mod_name); return NULL; } - tmp = sub; + tmp = pos; do { *tmp = tolower(*tmp); } while (*(++tmp)); - return sub; + tmp = pa_xstrdup(pos); + pa_xfree(sub); + return tmp; } -static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) { - int i; +static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, const char *mod_name) { + pa_alsa_ucm_device *dev; bool is_sink = false; char *sub = NULL; const char *role_name; + uint32_t idx; sub = modifier_name_to_role(mod_name, &is_sink); if (!sub) @@ -701,11 +758,11 @@ static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_devi modifier->media_role = sub; role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; - for (i = 0; i < modifier->n_suppdev; i++) { + PA_IDXSET_FOREACH(dev, modifier->supported_devices, idx) { /* if modifier has no specific pcm, we add role intent to its supported devices */ if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) && !pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE)) - add_media_role(modifier->supported_devices[i], list, role_name, sub, is_sink); + add_media_role(dev, role_name, sub, is_sink); } } @@ -713,29 +770,17 @@ static void append_lost_relationship(pa_alsa_ucm_device *dev) { uint32_t idx; pa_alsa_ucm_device *d; - if (dev->conflicting_devices) { - PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) { - if (!d->conflicting_devices) - d->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) + if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0) + pa_log_warn("Add lost conflicting device %s to %s", + pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), + pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0) - pa_log_warn("Add lost conflicting device %s to %s", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), - pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - } - } - - if (dev->supported_devices) { - PA_IDXSET_FOREACH(d, dev->supported_devices, idx) { - if (!d->supported_devices) - d->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - - if (pa_idxset_put(d->supported_devices, dev, NULL) == 0) - pa_log_warn("Add lost supported device %s to %s", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), - pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - } - } + PA_IDXSET_FOREACH(d, dev->supported_devices, idx) + if (pa_idxset_put(d->supported_devices, dev, NULL) == 0) + pa_log_warn("Add lost supported device %s to %s", + pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), + pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); } int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { @@ -876,11 +921,11 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); /* Modifier properties */ - ucm_get_modifier_property(mod, uc_mgr, mod_name); + ucm_get_modifier_property(mod, uc_mgr, verb, mod_name); /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */ pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name); - ucm_set_media_roles(mod, verb->devices, mod_name); + ucm_set_media_roles(mod, mod_name); } *p_verb = verb; @@ -899,43 +944,26 @@ static void set_eld_devices(pa_hashmap *hash) pa_device_port *port; pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; - const char *eld_mixer_device_name; void *state; - int idx, eld_device; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); - eld_mixer_device_name = NULL; - eld_device = -1; - PA_DYNARRAY_FOREACH(dev, data->devices, idx) { - if (dev->eld_device >= 0 && dev->eld_mixer_device_name) { - if (eld_device >= 0 && eld_device != dev->eld_device) { - pa_log_error("The ELD device is already set!"); - } else if (eld_mixer_device_name && pa_streq(dev->eld_mixer_device_name, eld_mixer_device_name)) { - pa_log_error("The ELD mixer device is already set (%s, %s)!", dev->eld_mixer_device_name, dev->eld_mixer_device_name); - } else { - eld_mixer_device_name = dev->eld_mixer_device_name; - eld_device = dev->eld_device; - } - } - } - data->eld_device = eld_device; - if (data->eld_mixer_device_name) - pa_xfree(data->eld_mixer_device_name); - data->eld_mixer_device_name = pa_xstrdup(eld_mixer_device_name); + dev = data->device; + data->eld_device = dev->eld_device; + data->eld_mixer_device_name = pa_xstrdup(dev->eld_mixer_device_name); } } -static void update_mixer_paths(pa_hashmap *ports, const char *profile) { +static void update_mixer_paths(pa_hashmap *ports, const char *profile_name) { pa_device_port *port; pa_alsa_ucm_port_data *data; void *state; /* select volume controls on ports */ PA_HASHMAP_FOREACH(port, ports, state) { - pa_log_info("Updating mixer path for %s: %s", profile, port->name); + pa_log_info("Updating mixer path for %s: %s", profile_name, port->name); data = PA_DEVICE_PORT_DATA(port); - data->path = pa_hashmap_get(data->paths, profile); + data->path = pa_hashmap_get(data->paths, profile_name); } } @@ -945,24 +973,14 @@ static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; snd_mixer_t *mixer_handle; - const char *profile, *mdev, *mdev2; + const char *profile, *mdev; void *state, *state2; - int idx; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); - mdev = NULL; - PA_DYNARRAY_FOREACH(dev, data->devices, idx) { - mdev2 = get_mixer_device(dev, is_sink); - if (mdev && mdev2 && !pa_streq(mdev, mdev2)) { - pa_log_error("Two mixer device names found ('%s', '%s'), using s/w volume", mdev, mdev2); - goto fail; - } - if (mdev2) - mdev = mdev2; - } - + dev = data->device; + mdev = get_mixer_device(dev, is_sink); if (mdev == NULL || !(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) { pa_log_error("Failed to find a working mixer device (%s).", mdev); goto fail; @@ -991,91 +1009,141 @@ fail: } } -static void ucm_add_port_combination( +static char *devset_name(pa_idxset *devices, const char *sep) { + int i = 0; + int num = pa_idxset_size(devices); + pa_alsa_ucm_device *sorted[num], *dev; + char *dev_names = NULL; + char *tmp = NULL; + uint32_t idx; + + PA_IDXSET_FOREACH(dev, devices, idx) { + sorted[i] = dev; + i++; + } + + /* Sort by alphabetical order so as to have a deterministic naming scheme */ + qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); + + for (i = 0; i < num; i++) { + dev = sorted[i]; + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + if (!dev_names) { + dev_names = pa_xstrdup(dev_name); + } else { + tmp = pa_sprintf_malloc("%s%s%s", dev_names, sep, dev_name); + pa_xfree(dev_names); + dev_names = tmp; + } + } + + return dev_names; +} + +PA_UNUSED static char *devset_description(pa_idxset *devices, const char *sep) { + int i = 0; + int num = pa_idxset_size(devices); + pa_alsa_ucm_device *sorted[num], *dev; + char *dev_descs = NULL; + char *tmp = NULL; + uint32_t idx; + + PA_IDXSET_FOREACH(dev, devices, idx) { + sorted[i] = dev; + i++; + } + + /* Sort by alphabetical order to match devset_name() */ + qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); + + for (i = 0; i < num; i++) { + dev = sorted[i]; + const char *dev_desc = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); + + if (!dev_descs) { + dev_descs = pa_xstrdup(dev_desc); + } else { + tmp = pa_sprintf_malloc("%s%s%s", dev_descs, sep, dev_desc); + pa_xfree(dev_descs); + dev_descs = tmp; + } + } + + return dev_descs; +} + +/* If invert is true, uses the formula 1/p = 1/p1 + 1/p2 + ... 1/pn. + * This way, the result will always be less than the individual components, + * yet higher components will lead to higher result. */ +static unsigned devset_playback_priority(pa_idxset *devices, bool invert) { + pa_alsa_ucm_device *dev; + uint32_t idx; + double priority = 0; + + PA_IDXSET_FOREACH(dev, devices, idx) { + if (dev->playback_priority > 0 && invert) + priority += 1.0 / dev->playback_priority; + else + priority += dev->playback_priority; + } + + if (priority > 0 && invert) + return 1.0 / priority; + + return (unsigned) priority; +} + +static unsigned devset_capture_priority(pa_idxset *devices, bool invert) { + pa_alsa_ucm_device *dev; + uint32_t idx; + double priority = 0; + + PA_IDXSET_FOREACH(dev, devices, idx) { + if (dev->capture_priority > 0 && invert) + priority += 1.0 / dev->capture_priority; + else + priority += dev->capture_priority; + } + + if (priority > 0 && invert) + return 1.0 / priority; + + return (unsigned) priority; +} + +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, - pa_alsa_ucm_device **pdevices, - int num, pa_hashmap *ports, pa_card_profile *cp, pa_core *core) { pa_device_port *port; - int i; unsigned priority; - double prio2; char *name, *desc; const char *dev_name; const char *direction; const char *profile; - pa_alsa_ucm_device *sorted[num], *dev; + pa_alsa_ucm_device *dev; pa_alsa_ucm_port_data *data; pa_alsa_ucm_volume *vol; - pa_alsa_jack *jack, *jack2; - pa_device_port_type_t type, type2; + pa_alsa_jack *jack; + pa_device_port_type_t type; void *state; - for (i = 0; i < num; i++) - sorted[i] = pdevices[i]; + dev = context->ucm_device; + if (!dev) + return; - /* Sort by alphabetical order so as to have a deterministic naming scheme - * for combination ports */ - qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); - - dev = sorted[0]; dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name); - desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION)) - : pa_sprintf_malloc("Combination port for %s", dev_name); - + desc = pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION)); priority = is_sink ? dev->playback_priority : dev->capture_priority; - prio2 = (priority == 0 ? 0 : 1.0/priority); jack = ucm_get_jack(context->ucm, dev); type = dev->type; - for (i = 1; i < num; i++) { - char *tmp; - - dev = sorted[i]; - dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - - tmp = pa_sprintf_malloc("%s+%s", name, dev_name); - pa_xfree(name); - name = tmp; - - tmp = pa_sprintf_malloc("%s,%s", desc, dev_name); - pa_xfree(desc); - desc = tmp; - - priority = is_sink ? dev->playback_priority : dev->capture_priority; - if (priority != 0 && prio2 > 0) - prio2 += 1.0/priority; - - jack2 = ucm_get_jack(context->ucm, dev); - if (jack2) { - if (jack && jack != jack2) - pa_log_warn("Multiple jacks per combined device '%s': '%s' '%s'", name, jack->name, jack2->name); - jack = jack2; - } - - type2 = dev->type; - if (type2 != PA_DEVICE_PORT_TYPE_UNKNOWN) { - if (type != PA_DEVICE_PORT_TYPE_UNKNOWN && type != type2) - pa_log_warn("Multiple device types per combined device '%s': %d %d", name, type, type2); - type = type2; - } - } - - /* Make combination ports always have lower priority, and use the formula - 1/p = 1/p1 + 1/p2 + ... 1/pn. - This way, the result will always be less than the individual components, - yet higher components will lead to higher result. */ - - if (num > 1) - priority = prio2 > 0 ? 1.0/prio2 : 0; - port = pa_hashmap_get(ports, name); if (!port) { pa_device_port_new_data port_data; @@ -1092,37 +1160,32 @@ static void ucm_add_port_combination( pa_device_port_new_data_done(&port_data); data = PA_DEVICE_PORT_DATA(port); - ucm_port_data_init(data, context->ucm, port, pdevices, num); + ucm_port_data_init(data, context->ucm, port, dev); port->impl_free = ucm_port_data_free; pa_hashmap_put(ports, port->name, port); pa_log_debug("Add port %s: %s", port->name, port->description); - if (num == 1) { - /* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination - * ports. */ - PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { - pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, - is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); + PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { + pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); - if (!path) - pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); - else { - if (vol->master_elem) { - pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); - e->switch_use = PA_ALSA_SWITCH_MUTE; - e->volume_use = PA_ALSA_VOLUME_MERGE; - } - - pa_hashmap_put(data->paths, pa_xstrdup(profile), path); - - /* Add path also to already created empty path set */ - dev = sorted[0]; - if (is_sink) - pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); - else - pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + if (!path) + pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); + else { + if (vol->master_elem) { + pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; } + + pa_hashmap_put(data->paths, pa_xstrdup(profile), path); + + /* Add path also to already created empty path set */ + if (is_sink) + pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + else + pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); } } } @@ -1143,113 +1206,112 @@ static void ucm_add_port_combination( if (hash) { pa_hashmap_put(hash, port->name, port); } + + /* ELD devices */ + set_eld_devices(ports); } -static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) { - int ret = 0; - const char *r; - const char *state = NULL; - size_t len; - - if (!port_name || !dev_name) - return false; - - port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT); - - while ((r = pa_split_in_place(port_name, "+", &len, &state))) { - if (strlen(dev_name) == len && !strncmp(r, dev_name, len)) { - ret = 1; - break; - } - } - - return ret; -} - -static int ucm_check_conformance( - pa_alsa_ucm_mapping_context *context, - pa_alsa_ucm_device **pdevices, - int dev_num, - pa_alsa_ucm_device *dev) { - - uint32_t idx; +static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) { pa_alsa_ucm_device *d; - int i; + uint32_t idx; + pa_assert(devices); pa_assert(dev); - pa_log_debug("Check device %s conformance with %d other devices", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num); - if (dev_num == 0) { - pa_log_debug("First device in combination, number 1"); - return 1; - } + /* Can add anything to empty group */ + if (pa_idxset_isempty(devices)) + return true; - if (dev->conflicting_devices) { /* the device defines conflicting devices */ - PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) { - for (i = 0; i < dev_num; i++) { - if (pdevices[i] == d) { - pa_log_debug("Conflicting device found"); - return 0; - } - } - } - } else if (dev->supported_devices) { /* the device defines supported devices */ - for (i = 0; i < dev_num; i++) { - if (!ucm_device_exists(dev->supported_devices, pdevices[i])) { - pa_log_debug("Supported device not found"); - return 0; - } - } - } else { /* not support any other devices */ - pa_log_debug("Not support any other devices"); - return 0; - } + /* Device already selected */ + if (pa_idxset_contains(devices, dev)) + return true; - pa_log_debug("Device added to combination, number %d", dev_num + 1); - return 1; + /* No conflicting device must already be selected */ + if (!pa_idxset_isdisjoint(devices, dev->conflicting_devices)) + return false; + + /* No already selected device must be unsupported */ + if (!pa_idxset_isempty(dev->supported_devices)) + if (!pa_idxset_issubset(devices, dev->supported_devices)) + return false; + + /* Must not be unsupported by any selected device */ + PA_IDXSET_FOREACH(d, devices, idx) + if (!pa_idxset_isempty(d->supported_devices)) + if (!pa_idxset_contains(d->supported_devices, dev)) + return false; + + return true; } -static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) { +/* Iterates nonempty subsets of UCM devices that can be simultaneously + * used, including subsets of previously returned subsets. At start, + * *state should be NULL. It's not safe to modify the devices argument + * until iteration ends. The returned idxsets must be freed by the + * caller. */ +static pa_idxset *iterate_device_subsets(pa_idxset *devices, void **state) { + uint32_t idx = PA_IDXSET_INVALID; pa_alsa_ucm_device *dev; - if (*idx == PA_IDXSET_INVALID) - dev = pa_idxset_first(idxset, idx); - else - dev = pa_idxset_next(idxset, idx); + pa_assert(devices); + pa_assert(state); - return dev; -} + if (*state == NULL) { + /* First iteration, start adding from first device */ + *state = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + dev = pa_idxset_first(devices, &idx); -static void ucm_add_ports_combination( - pa_hashmap *hash, - pa_alsa_ucm_mapping_context *context, - bool is_sink, - pa_alsa_ucm_device **pdevices, - int dev_num, - uint32_t map_index, - pa_hashmap *ports, - pa_card_profile *cp, - pa_core *core) { - - pa_alsa_ucm_device *dev; - uint32_t idx = map_index; - - if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL) - return; - - /* check if device at map_index can combine with existing devices combination */ - if (ucm_check_conformance(context, pdevices, dev_num, dev)) { - /* add device at map_index to devices combination */ - pdevices[dev_num] = dev; - /* add current devices combination as a new port */ - ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core); - /* try more elements combination */ - ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core); + } else { + /* Backtrack the most recent device we added and skip it */ + dev = pa_idxset_steal_last(*state, NULL); + pa_idxset_get_by_data(devices, dev, &idx); + if (dev) + dev = pa_idxset_next(devices, &idx); } - /* try other device with current elements number */ - ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core); + /* Try adding devices we haven't decided on yet */ + for (; dev; dev = pa_idxset_next(devices, &idx)) { + if (devset_supports_device(*state, dev)) + pa_idxset_put(*state, dev, NULL); + } + + if (pa_idxset_isempty(*state)) { + /* No more choices to backtrack on, therefore no more subsets to + * return after this. Don't return the empty set, instead clean + * up and end iteration. */ + pa_idxset_free(*state, NULL); + *state = NULL; + return NULL; + } + + return pa_idxset_copy(*state, NULL); +} + +/* This a wrapper around iterate_device_subsets() that only returns the + * biggest possible groups and not any of their subsets. */ +static pa_idxset *iterate_maximal_device_subsets(pa_idxset *devices, void **state) { + uint32_t idx; + pa_alsa_ucm_device *dev; + pa_idxset *subset; + + pa_assert(devices); + pa_assert(state); + + subset = iterate_device_subsets(devices, state); + if (!subset) + return subset; + + /* Skip this group if it's incomplete, by checking if we can add any + * other device. If we can, this iteration is a subset of another + * group that we already returned or eventually return. */ + PA_IDXSET_FOREACH(dev, devices, idx) { + if (!pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) { + pa_idxset_free(subset, NULL); + return iterate_maximal_device_subsets(devices, state); + } + } + + return subset; } static char* merge_roles(const char *cur, const char *add) { @@ -1281,28 +1343,6 @@ static char* merge_roles(const char *cur, const char *add) { return ret; } -void pa_alsa_ucm_add_ports_combination( - pa_hashmap *p, - pa_alsa_ucm_mapping_context *context, - bool is_sink, - pa_hashmap *ports, - pa_card_profile *cp, - pa_core *core) { - - pa_alsa_ucm_device **pdevices; - - pa_assert(context->ucm_devices); - - if (pa_idxset_size(context->ucm_devices) > 0) { - pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices)); - ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core); - pa_xfree(pdevices); - } - - /* ELD devices */ - set_eld_devices(ports); -} - void pa_alsa_ucm_add_ports( pa_hashmap **p, pa_proplist *proplist, @@ -1312,7 +1352,6 @@ void pa_alsa_ucm_add_ports( snd_pcm_t *pcm_handle, bool ignore_dB) { - uint32_t idx; char *merged_roles; const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; pa_alsa_ucm_device *dev; @@ -1323,7 +1362,7 @@ void pa_alsa_ucm_add_ports( pa_assert(*p); /* add ports first */ - pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core); + pa_alsa_ucm_add_port(*p, context, is_sink, card->ports, NULL, card->core); /* now set up volume paths if any */ probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB); @@ -1338,19 +1377,21 @@ void pa_alsa_ucm_add_ports( /* then set property PA_PROP_DEVICE_INTENDED_ROLES */ merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + + dev = context->ucm_device; + if (dev) { const char *roles = pa_proplist_gets(dev->proplist, role_name); tmp = merge_roles(merged_roles, roles); pa_xfree(merged_roles); merged_roles = tmp; } - if (context->ucm_modifiers) - PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) { - tmp = merge_roles(merged_roles, mod->media_role); - pa_xfree(merged_roles); - merged_roles = tmp; - } + mod = context->ucm_modifier; + if (mod) { + tmp = merge_roles(merged_roles, mod->media_role); + pa_xfree(merged_roles); + merged_roles = tmp; + } if (merged_roles) pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles); @@ -1360,85 +1401,69 @@ void pa_alsa_ucm_add_ports( } /* Change UCM verb and device to match selected card profile */ -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { int ret = 0; - const char *profile; + const char *verb_name, *profile_name; pa_alsa_ucm_verb *verb; + pa_alsa_mapping *map; + uint32_t idx; if (new_profile == old_profile) - return ret; - else if (new_profile == NULL || old_profile == NULL) - profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE; - else if (!pa_streq(new_profile, old_profile)) - profile = new_profile; - else - return ret; + return 0; - /* change verb */ - pa_log_info("Set UCM verb to %s", profile); - if ((ret = snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) { - pa_log("Failed to set verb %s: %s", profile, snd_strerror(ret)); - } - - /* find active verb */ - ucm->active_verb = NULL; - PA_LLIST_FOREACH(verb, ucm->verbs) { - const char *verb_name; + if (new_profile == NULL) { + verb = NULL; + profile_name = SND_USE_CASE_VERB_INACTIVE; + verb_name = SND_USE_CASE_VERB_INACTIVE; + } else { + verb = new_profile->ucm_context.verb; + profile_name = new_profile->name; verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); - if (pa_streq(verb_name, profile)) { - ucm->active_verb = verb; - break; - } } - update_mixer_paths(card->ports, profile); + pa_log_info("Set profile to %s", profile_name); + if (ucm->active_verb != verb) { + /* change verb */ + pa_log_info("Set UCM verb to %s", verb_name); + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { + pa_log("Failed to set verb %s", verb_name); + ret = -1; + } + + } else if (ucm->active_verb) { + /* Disable devices not in new profile */ + PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map)) + if (ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) + ret = -1; + + PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map)) + if (ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) + ret = -1; + } + ucm->active_verb = verb; + + update_mixer_paths(card->ports, profile_name); + return ret; } -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) { - int i; - int ret = 0; +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { pa_alsa_ucm_config *ucm; - const char **enable_devs; - int enable_num = 0; - uint32_t idx; pa_alsa_ucm_device *dev; + pa_alsa_ucm_port_data *data; pa_assert(context && context->ucm); ucm = context->ucm; pa_assert(ucm->ucm_mgr); - enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices)); + data = PA_DEVICE_PORT_DATA(port); + dev = context->ucm_device; + pa_assert(dev == data->device); - /* first disable then enable */ - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - - if (ucm_port_contains(port->name, dev_name, is_sink)) - enable_devs[enable_num++] = dev_name; - else { - pa_log_debug("Disable ucm device %s", dev_name); - if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) { - pa_log("Failed to disable ucm device %s", dev_name); - ret = -1; - break; - } - } - } - - for (i = 0; i < enable_num; i++) { - pa_log_debug("Enable ucm device %s", enable_devs[i]); - if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devs[i]) < 0) { - pa_log("Failed to enable ucm device %s", enable_devs[i]); - ret = -1; - break; - } - } - - pa_xfree(enable_devs); - - return ret; + return ucm_device_enable(ucm, dev); } static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { @@ -1474,7 +1499,7 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device * const char *new_desc, *mdev; bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; - pa_idxset_put(m->ucm_context.ucm_devices, device, NULL); + m->ucm_context.ucm_device = device; new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; @@ -1503,7 +1528,7 @@ static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifi const char *new_desc, *mod_name, *channel_str; uint32_t channels = 0; - pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL); + m->ucm_context.ucm_modifier = modifier; new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; @@ -1544,17 +1569,11 @@ static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifi pa_channel_map_init(&m->channel_map); } -static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *device_str, bool is_sink) { +static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *ucm_name, bool is_sink) { pa_alsa_mapping *m; char *mapping_name; - size_t ucm_alibpref_len = 0; - /* find private alsa-lib's configuration device prefix */ - - if (ucm->alib_prefix && pa_startswith(device_str, ucm->alib_prefix)) - ucm_alibpref_len = strlen(ucm->alib_prefix); - - mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str + ucm_alibpref_len, is_sink ? "sink" : "source"); + mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, ucm_name, is_sink ? "sink" : "source"); m = pa_alsa_mapping_get(ps, mapping_name); @@ -1569,7 +1588,6 @@ static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_pr static int ucm_create_mapping_direction( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, @@ -1579,7 +1597,7 @@ static int ucm_create_mapping_direction( pa_alsa_mapping *m; unsigned priority, rate, channels; - m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink); + m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_name, is_sink); if (!m) return -1; @@ -1590,8 +1608,7 @@ static int ucm_create_mapping_direction( rate = is_sink ? device->playback_rate : device->capture_rate; channels = is_sink ? device->playback_channels : device->capture_channels; - if (!m->ucm_context.ucm_devices) { /* new mapping */ - m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (!m->ucm_context.ucm_device) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; @@ -1599,7 +1616,6 @@ static int ucm_create_mapping_direction( m->device_strings[0] = pa_xstrdup(device_str); m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; - ucm_add_mapping(p, m); if (rate) m->sample_spec.rate = rate; pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); @@ -1621,7 +1637,6 @@ static int ucm_create_mapping_direction( static int ucm_create_mapping_for_modifier( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_modifier *modifier, const char *verb_name, const char *mod_name, @@ -1630,16 +1645,14 @@ static int ucm_create_mapping_for_modifier( pa_alsa_mapping *m; - m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink); + m = ucm_alsa_mapping_get(ucm, ps, verb_name, mod_name, is_sink); if (!m) return -1; pa_log_info("UCM mapping: %s modifier %s", m->name, mod_name); - if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */ - m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (!m->ucm_context.ucm_device && !m->ucm_context.ucm_modifier) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; @@ -1648,10 +1661,7 @@ static int ucm_create_mapping_for_modifier( m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; /* Modifier sinks should not be routed to by default */ m->priority = 0; - - ucm_add_mapping(p, m); - } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */ - m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + } alsa_mapping_add_ucm_modifier(m, modifier); @@ -1661,7 +1671,6 @@ static int ucm_create_mapping_for_modifier( static int ucm_create_mapping( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, @@ -1676,9 +1685,9 @@ static int ucm_create_mapping( } if (sink) - ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true); + ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, sink, true); if (ret == 0 && source) - ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false); + ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, source, false); return ret; } @@ -1751,27 +1760,28 @@ static int ucm_create_profile( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_verb *verb, - const char *verb_name, - const char *verb_desc) { + pa_idxset *mappings, + const char *profile_name, + const char *profile_desc, + unsigned int profile_priority) { pa_alsa_profile *p; - pa_alsa_ucm_device *dev; - pa_alsa_ucm_modifier *mod; - int i = 0; - const char *name, *sink, *source; - unsigned int priority; + pa_alsa_mapping *map; + uint32_t idx; pa_assert(ps); - if (pa_hashmap_get(ps->profiles, verb_name)) { - pa_log("Verb %s already exists", verb_name); + if (pa_hashmap_get(ps->profiles, profile_name)) { + pa_log("Profile %s already exists", profile_name); return -1; } p = pa_xnew0(pa_alsa_profile, 1); p->profile_set = ps; - p->name = pa_xstrdup(verb_name); - p->description = pa_xstrdup(verb_desc); + p->name = pa_xstrdup(profile_name); + p->description = pa_xstrdup(profile_desc); + p->priority = profile_priority; + p->ucm_context.verb = verb; p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); @@ -1779,10 +1789,36 @@ static int ucm_create_profile( p->supported = true; pa_hashmap_put(ps->profiles, p->name, p); - /* TODO: get profile priority from policy management */ - priority = verb->priority; + PA_IDXSET_FOREACH(map, mappings, idx) + ucm_add_mapping(p, map); - if (priority == 0) { + pa_alsa_profile_dump(p); + + return 0; +} + +static int ucm_create_verb_profiles( + pa_alsa_ucm_config *ucm, + pa_alsa_profile_set *ps, + pa_alsa_ucm_verb *verb, + const char *verb_name, + const char *verb_desc) { + + pa_idxset *verb_devices, *p_devices, *p_mappings; + pa_alsa_ucm_device *dev; + pa_alsa_ucm_modifier *mod; + int i = 0; + int n_profiles = 0; + const char *name, *sink, *source; + char *p_name, *p_desc, *tmp; + unsigned int verb_priority, p_priority; + uint32_t idx; + void *state = NULL; + + /* TODO: get profile priority from policy management */ + verb_priority = verb->priority; + + if (verb_priority == 0) { char *verb_cmp, *c; c = verb_cmp = pa_xstrdup(verb_name); while (*c) { @@ -1791,15 +1827,13 @@ static int ucm_create_profile( } for (i = 0; verb_info[i].id; i++) { if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { - priority = verb_info[i].priority; + verb_priority = verb_info[i].priority; break; } } pa_xfree(verb_cmp); } - p->priority = priority; - PA_LLIST_FOREACH(dev, verb->devices) { pa_alsa_jack *jack; const char *jack_hw_mute; @@ -1809,7 +1843,7 @@ static int ucm_create_profile( sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); - ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source); + ucm_create_mapping(ucm, ps, dev, verb_name, name, sink, source); jack = ucm_get_jack(ucm, dev); if (jack) @@ -1860,12 +1894,74 @@ static int ucm_create_profile( source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE); if (sink) - ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, true); + ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, sink, true); else if (source) - ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, false); + ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, source, false); } - pa_alsa_profile_dump(p); + verb_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + PA_LLIST_FOREACH(dev, verb->devices) + pa_idxset_put(verb_devices, dev, NULL); + + while ((p_devices = iterate_maximal_device_subsets(verb_devices, &state))) { + p_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + /* Add the mappings that include our selected devices */ + PA_IDXSET_FOREACH(dev, p_devices, idx) { + if (dev->playback_mapping) + pa_idxset_put(p_mappings, dev->playback_mapping, NULL); + if (dev->capture_mapping) + pa_idxset_put(p_mappings, dev->capture_mapping, NULL); + } + + /* Add mappings only for the modifiers that can work with our + * device selection */ + PA_LLIST_FOREACH(mod, verb->modifiers) + if (pa_idxset_isempty(mod->supported_devices) || pa_idxset_issubset(mod->supported_devices, p_devices)) + if (pa_idxset_isdisjoint(mod->conflicting_devices, p_devices)) { + if (mod->playback_mapping) + pa_idxset_put(p_mappings, mod->playback_mapping, NULL); + if (mod->capture_mapping) + pa_idxset_put(p_mappings, mod->capture_mapping, NULL); + } + + /* If we'll have multiple profiles for this verb, their names + * must be unique. Use a list of chosen devices to disambiguate + * them. If the profile contains all devices of a verb, we'll + * generate only onle profile whose name should be the verb + * name. GUIs usually show the profile description instead of + * the name, add the device names to those as well. */ + tmp = devset_name(p_devices, ", "); + if (pa_idxset_equals(p_devices, verb_devices)) { + p_name = pa_xstrdup(verb_name); + p_desc = pa_xstrdup(verb_desc); + } else { + p_name = pa_sprintf_malloc("%s (%s)", verb_name, tmp); + p_desc = pa_sprintf_malloc("%s (%s)", verb_desc, tmp); + } + + /* Make sure profiles with higher-priority devices are + * prioritized. */ + p_priority = verb_priority + devset_playback_priority(p_devices, false) + devset_capture_priority(p_devices, false); + + if (ucm_create_profile(ucm, ps, verb, p_mappings, p_name, p_desc, p_priority) == 0) { + pa_log_debug("Created profile %s for UCM verb %s", p_name, verb_name); + n_profiles++; + } + + pa_xfree(tmp); + pa_xfree(p_name); + pa_xfree(p_desc); + pa_idxset_free(p_mappings, NULL); + pa_idxset_free(p_devices, NULL); + } + + pa_idxset_free(verb_devices, NULL); + + if (n_profiles == 0) { + pa_log("UCM verb %s created no profiles", verb_name); + return -1; + } return 0; } @@ -1874,7 +1970,6 @@ static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) { pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; - uint32_t idx; char *mdev, *alib_prefix; snd_pcm_info_t *info; int pcm_card, pcm_device; @@ -1890,13 +1985,12 @@ static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) alib_prefix = context->ucm->alib_prefix; - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card); - if (mdev == NULL) - continue; - dev->eld_mixer_device_name = mdev; - dev->eld_device = pcm_device; - } + dev = context->ucm_device; + mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card); + if (mdev == NULL) + return; + dev->eld_mixer_device_name = mdev; + dev->eld_device = pcm_device; } static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { @@ -1962,38 +2056,39 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { snd_mixer_t *mixer_handle; pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; - uint32_t idx; + bool has_control; - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - bool has_control; + dev = context->ucm_device; + if (!dev->jack || !dev->jack->mixer_device_name) + return; - if (!dev->jack || !dev->jack->mixer_device_name) - continue; - - mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); - if (!mixer_handle) { - pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); - continue; - } - - has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; - pa_alsa_jack_set_has_control(dev->jack, has_control); - pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); + mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); + if (!mixer_handle) { + pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); + return; } + + has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); } static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { void *state; pa_alsa_profile *p; pa_alsa_mapping *m; + const char *verb_name; uint32_t idx; PA_HASHMAP_FOREACH(p, ps->profiles, state) { - /* change verb */ - pa_log_info("Set ucm verb to %s", p->name); + pa_log_info("Probing profile %s", p->name); - if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) { - pa_log("Failed to set verb %s", p->name); + /* change verb */ + verb_name = pa_proplist_gets(p->ucm_context.verb->proplist, PA_ALSA_PROP_UCM_NAME); + pa_log_info("Set ucm verb to %s", verb_name); + + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { + pa_log("Failed to set verb %s", verb_name); p->supported = false; continue; } @@ -2075,7 +2170,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha continue; } - ucm_create_profile(ucm, ps, verb, verb_name, verb_desc); + ucm_create_verb_profiles(ucm, ps, verb, verb_name, verb_desc); } ucm_probe_profile_set(ucm, ps); @@ -2104,10 +2199,8 @@ static void free_verb(pa_alsa_ucm_verb *verb) { pa_proplist_free(di->proplist); - if (di->conflicting_devices) - pa_idxset_free(di->conflicting_devices, NULL); - if (di->supported_devices) - pa_idxset_free(di->supported_devices, NULL); + pa_idxset_free(di->conflicting_devices, NULL); + pa_idxset_free(di->supported_devices, NULL); pa_xfree(di->eld_mixer_device_name); @@ -2117,10 +2210,8 @@ static void free_verb(pa_alsa_ucm_verb *verb) { PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) { PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi); pa_proplist_free(mi->proplist); - if (mi->n_suppdev > 0) - snd_use_case_free_list(mi->supported_devices, mi->n_suppdev); - if (mi->n_confdev > 0) - snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev); + pa_idxset_free(mi->conflicting_devices, NULL); + pa_idxset_free(mi->supported_devices, NULL); pa_xfree(mi->media_role); pa_xfree(mi); } @@ -2168,29 +2259,22 @@ void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) { void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { pa_alsa_ucm_device *dev; pa_alsa_ucm_modifier *mod; - uint32_t idx; - if (context->ucm_devices) { + dev = context->ucm_device; + if (dev) { /* clear ucm device pointer to mapping */ - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - if (context->direction == PA_DIRECTION_OUTPUT) - dev->playback_mapping = NULL; - else - dev->capture_mapping = NULL; - } - - pa_idxset_free(context->ucm_devices, NULL); + if (context->direction == PA_DIRECTION_OUTPUT) + dev->playback_mapping = NULL; + else + dev->capture_mapping = NULL; } - if (context->ucm_modifiers) { - PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) { - if (context->direction == PA_DIRECTION_OUTPUT) - mod->playback_mapping = NULL; - else - mod->capture_mapping = NULL; - } - - pa_idxset_free(context->ucm_modifiers, NULL); + mod = context->ucm_modifier; + if (mod) { + if (context->direction == PA_DIRECTION_OUTPUT) + mod->playback_mapping = NULL; + else + mod->capture_mapping = NULL; } } @@ -2243,13 +2327,6 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_ } } -static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) { - pa_assert(device); - pa_assert(port); - - pa_dynarray_append(device->ucm_ports, port); -} - static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) { pa_assert(device); pa_assert(jack); @@ -2282,7 +2359,7 @@ static void device_set_available(pa_alsa_ucm_device *device, pa_available_t avai device->available = available; PA_DYNARRAY_FOREACH(port, device->ucm_ports, idx) - ucm_port_update_available(port); + pa_device_port_set_available(port->core_port, port->device->available); } void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) { @@ -2306,26 +2383,22 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) { } static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices) { - unsigned i; - + pa_alsa_ucm_device *device) { pa_assert(ucm); pa_assert(core_port); - pa_assert(devices); + pa_assert(device); port->ucm = ucm; port->core_port = core_port; - port->devices = pa_dynarray_new(NULL); port->eld_device = -1; - for (i = 0; i < n_devices; i++) { - pa_dynarray_append(port->devices, devices[i]); - device_add_ucm_port(devices[i], port); - } + port->device = device; + pa_dynarray_append(device->ucm_ports, port); - port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, NULL); + port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, + (pa_free_cb_t) pa_alsa_path_free); - ucm_port_update_available(port); + pa_device_port_set_available(port->core_port, port->device->available); } static void ucm_port_data_free(pa_device_port *port) { @@ -2335,34 +2408,12 @@ static void ucm_port_data_free(pa_device_port *port) { ucm_port = PA_DEVICE_PORT_DATA(port); - if (ucm_port->devices) - pa_dynarray_free(ucm_port->devices); - if (ucm_port->paths) pa_hashmap_free(ucm_port->paths); pa_xfree(ucm_port->eld_mixer_device_name); } -static void ucm_port_update_available(pa_alsa_ucm_port_data *port) { - pa_alsa_ucm_device *device; - unsigned idx; - pa_available_t available = PA_AVAILABLE_YES; - - pa_assert(port); - - PA_DYNARRAY_FOREACH(device, port->devices, idx) { - if (device->available == PA_AVAILABLE_UNKNOWN) - available = PA_AVAILABLE_UNKNOWN; - else if (device->available == PA_AVAILABLE_NO) { - available = PA_AVAILABLE_NO; - break; - } - } - - pa_device_port_set_available(port->core_port, available); -} - #else /* HAVE_ALSA_UCM */ /* Dummy functions for systems without UCM support */ @@ -2376,7 +2427,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha return NULL; } -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { return -1; } @@ -2394,7 +2445,7 @@ void pa_alsa_ucm_add_ports( bool ignore_dB) { } -void pa_alsa_ucm_add_ports_combination( +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, @@ -2403,7 +2454,7 @@ void pa_alsa_ucm_add_ports_combination( pa_core *core) { } -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) { +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { return -1; } diff --git a/spa/plugins/alsa/acp/alsa-ucm.h b/spa/plugins/alsa/acp/alsa-ucm.h index 696209eba..33cc8befb 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.h +++ b/spa/plugins/alsa/acp/alsa-ucm.h @@ -142,12 +142,13 @@ typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier; typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; 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; 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); -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile); +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile); 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); @@ -159,14 +160,14 @@ void pa_alsa_ucm_add_ports( pa_card *card, snd_pcm_t *pcm_handle, bool ignore_dB); -void pa_alsa_ucm_add_ports_combination( +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_hashmap *ports, pa_card_profile *cp, pa_core *core); -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink); +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port); void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm); void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context); @@ -223,11 +224,8 @@ struct pa_alsa_ucm_modifier { pa_proplist *proplist; - int n_confdev; - int n_suppdev; - - const char **conflicting_devices; - const char **supported_devices; + pa_idxset *conflicting_devices; + pa_idxset *supported_devices; pa_direction_t action_direction; @@ -270,17 +268,19 @@ struct pa_alsa_ucm_mapping_context { pa_alsa_ucm_config *ucm; pa_direction_t direction; - pa_idxset *ucm_devices; - pa_idxset *ucm_modifiers; + pa_alsa_ucm_device *ucm_device; + pa_alsa_ucm_modifier *ucm_modifier; +}; + +struct pa_alsa_ucm_profile_context { + pa_alsa_ucm_verb *verb; }; struct pa_alsa_ucm_port_data { pa_alsa_ucm_config *ucm; pa_device_port *core_port; - /* A single port will be associated with multiple devices if it represents - * a combination of devices. */ - pa_dynarray *devices; /* pa_alsa_ucm_device */ + pa_alsa_ucm_device *device; /* profile name -> pa_alsa_path for volume control */ pa_hashmap *paths; diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index e9f78d3ca..6624b8fb8 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -1626,20 +1626,29 @@ static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, { int err; const char *name = snd_hctl_elem_get_name(helem); - // NOTE: The remove event defined as '~0U`. + /* NOTE: The remove event is defined as '~0U`. */ if (mask == SND_CTL_EVENT_MASK_REMOVE) { - // NOTE: unless remove pointer to melem from link-list at private_data of helem, hits - // assersion in alsa-lib since the list is not empty. + /* NOTE: Unless we remove the pointer to melem from the linked-list at + * private_data of helem, an assertion will be hit in alsa-lib since + * the list is not empty. */ snd_mixer_elem_detach(melem, helem); } else if (mask & SND_CTL_EVENT_MASK_ADD) { snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); if (iface == SND_CTL_ELEM_IFACE_CARD || iface == SND_CTL_ELEM_IFACE_PCM) { + snd_mixer_t *mixer = snd_mixer_class_get_mixer(class); + snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); + const char *name = snd_hctl_elem_get_name(helem); + const int index = snd_hctl_elem_get_index(helem); + const int device = snd_hctl_elem_get_device(helem); snd_mixer_elem_t *new_melem; - - /* Put the hctl pointer as our private data - it will be useful for callbacks */ - if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, helem, NULL)) < 0) { - pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); - return 0; + + new_melem = pa_alsa_mixer_find(mixer, iface, name, index, device); + if (!new_melem) { + /* Put the hctl pointer as our private data - it will be useful for callbacks */ + if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, helem, NULL)) < 0) { + pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); + return 0; + } } if ((err = snd_mixer_elem_attach(new_melem, helem)) < 0) { diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index 46ef1fd4e..36ab2fee8 100644 --- a/spa/plugins/alsa/acp/compat.h +++ b/spa/plugins/alsa/acp/compat.h @@ -52,6 +52,7 @@ typedef void (*pa_free_cb_t)(void *p); #define PA_UNLIKELY(x) (x) #define PA_PRINTF_FUNC(fmt, arg1) #endif +#define PA_UNUSED SPA_UNUSED #define PA_MIN(a,b) \ ({ \ diff --git a/spa/plugins/alsa/acp/idxset.h b/spa/plugins/alsa/acp/idxset.h index 6e88a8470..0a12ec04a 100644 --- a/spa/plugins/alsa/acp/idxset.h +++ b/spa/plugins/alsa/acp/idxset.h @@ -115,6 +115,11 @@ static inline int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) return res; } +static inline bool pa_idxset_contains(pa_idxset *s, const void *p) +{ + return pa_idxset_find(s, p) != NULL; +} + static inline pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func) { pa_idxset_item *item; @@ -134,6 +139,35 @@ static inline bool pa_idxset_isempty(const pa_idxset *s) return false; return true; } + +static inline bool pa_idxset_isdisjoint(pa_idxset *s, pa_idxset *t) +{ + pa_idxset_item *item; + pa_array_for_each(item, &s->array) + if (item->ptr != NULL && pa_idxset_contains(t, item->ptr)) + return false; + return true; +} + +static inline bool pa_idxset_issubset(pa_idxset *s, pa_idxset *t) +{ + pa_idxset_item *item; + pa_array_for_each(item, &s->array) + if (item->ptr != NULL && !pa_idxset_contains(t, item->ptr)) + return false; + return true; +} + +static inline bool pa_idxset_issuperset(pa_idxset *s, pa_idxset *t) +{ + return pa_idxset_issubset(t, s); +} + +static inline bool pa_idxset_equals(pa_idxset *s, pa_idxset *t) +{ + return pa_idxset_issubset(s, t) && pa_idxset_issuperset(s, t); +} + static inline unsigned pa_idxset_size(pa_idxset*s) { unsigned count = 0; @@ -144,13 +178,13 @@ static inline unsigned pa_idxset_size(pa_idxset*s) return count; } -static inline void *pa_idxset_search(pa_idxset *s, uint32_t *idx) +static inline pa_idxset_item *pa_idxset_search(pa_idxset *s, uint32_t *idx) { pa_idxset_item *item; for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item); pa_array_check(&s->array, item); item++, (*idx)++) { if (item->ptr != NULL) - return item->ptr; + return item; } *idx = PA_IDXSET_INVALID; return NULL; @@ -158,19 +192,52 @@ static inline void *pa_idxset_search(pa_idxset *s, uint32_t *idx) static inline void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { + pa_idxset_item *item; (*idx)++;; - return pa_idxset_search(s, idx); + item = pa_idxset_search(s, idx); + return item ? item->ptr : NULL; } static inline void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { uint32_t i = 0; - void *ptr = pa_idxset_search(s, &i); + pa_idxset_item *item = pa_idxset_search(s, &i); if (idx) *idx = i; + return item ? item->ptr : NULL; +} + +static inline void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx) +{ + uint32_t i = 0; + void *ptr; + pa_idxset_item *item = pa_idxset_search(s, &i); + if (idx) + *idx = i; + ptr = item ? item->ptr : NULL; + item->ptr = NULL; return ptr; } +static inline void* pa_idxset_steal_last(pa_idxset *s, uint32_t *idx) +{ + pa_idxset_item *item; + uint32_t i; + for (i = pa_array_get_len(&s->array, pa_idxset_item); i > 0; i--) { + item = pa_array_get_unchecked(&s->array, i-1, pa_idxset_item); + if (item->ptr != NULL) { + void *ptr = item->ptr; + item->ptr = NULL; + if (idx) + *idx = i-1; + return ptr; + } + } + if (idx) + *idx = PA_IDXSET_INVALID; + return NULL; +} + static inline void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { pa_idxset_item *item = pa_idxset_find(s, p);