[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 21 commits: idxset: Add set contains() function

PulseAudio Marge Bot (@pulseaudio-merge-bot) gitlab at gitlab.freedesktop.org
Tue Jun 28 17:51:50 UTC 2022



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
fb63e589 by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
idxset: Add set contains() function

This is functionally equivalent to get_by_data(s, p, NULL) == p, but
with a more obvious name and form because some existing code is instead
manually iterating through idxsets to check for existence of an item.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
ec668ac4 by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
idxset: Add set comparison operations

Add isdisjoint(), issubset(), issuperset() and equals() functions that
element-wise compare two idxsets.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
97d9c285 by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
idxset: Add reverse iteration functions

Add complementary functions to the existing idxset iterate(),
steal_first(), first(), next() functions that work in the reverse
direction: reverse_iterate(), steal_last(), last() and previous().

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
d8c89de2 by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
alsa-ucm: Always create device conflicting/supported device idxsets

This is intended to make the current and upcoming code a bit clearer, as
we won't need to constantly check for the existence of these idxsets
before using or operating on them.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
9b06e8fe by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
alsa-ucm: Make modifiers track conflicting/supported devices as idxsets

Modifiers currently keep their conflicting and supported devices's
names, and these names are resolved to devices every time we need to use
them. Instead, resolve these device names while creating the modifier
struct and keep track of the resulting device structs in idxsets, same
as how device structs keep track of their support relations.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
c83b3451 by Alper Nebi Yasak at 2022-06-28T15:08:45+03:00
alsa-ucm: Add enable, disable, status helpers for devices

Right now manipulating device status is done inline once while setting a
port. However, we will need to reuse this code to disable conflicting
devices of a device we want to enable. Split it into enable and disable
helper functions.

There is another issue with the device enable logic, where trying to
disabling an already disabled device sometimes fails. To avoid that,
implement a status helper and check if the device we want to enable is
already enabled/disabled before trying to do so.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
880ff393 by Jaroslav Kysela at 2022-06-28T15:08:45+03:00
alsa-ucm: Set profiles by their struct instance, not their name

While switching profiles, it's possible that we will want to do more
work besides switching UCM verbs. The alsa-card module already has our
profiles as structs, but passes in only the names instead of the entire
struct. Make things work with the struct instead, so we can add other
things (like a UCM context) to it and use those here.

Co-authored-by: Tanu Kaskinen <tanuk at iki.fi>
[Alper: Split into its own commit and integrated Tanu's snippet.]

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
9fc7064b by Alper Nebi Yasak at 2022-06-28T15:10:26+03:00
alsa-ucm: Let profiles know their associated UCM verb

Currently each UCM verb generates one profile named the same as the
verb, meaning it's trivial to know which verb the profile belongs to.
This will be slightly harder to do when we generate multiple profiles
per UCM verb (e.g. to make use of conflicting devices).

It would still be possible to parse the profile name to get the UCM
verb, but instead let's keep track of the struct instance representing
the profile's associated verb. This also lets us remove a block of code
searching for the verb by its name.

Co-authored-by: Jaroslav Kysela <perex at perex.cz>
[Alper: Reused Jaroslav's UCM profile context changes for UCM verb
instead of combined devices.]

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
45278904 by Alper Nebi Yasak at 2022-06-28T15:11:17+03:00
alsa-ucm: Stop conflating profile name with UCM verb name

So far each profile had the exact name as their associated UCM verb,
which caused the one to be used where the other should have been.
Explicitly get and use the verb name where that was intended, and make
sure things about profiles aren't named after verbs.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
aa5ced38 by Alper Nebi Yasak at 2022-06-28T15:11:20+03:00
alsa-ucm: Make mapping creation independent from indvidual profiles

The ucm_create_mapping() function is not idempotent. It looks like it
was meant to be called once per device for the devices of a UCM verb
and takes a profile argument simply because a verb has generated a
single profile so far.

Make sure creating mappings per device and adding those mappings to the
profiles happens as separate steps to make it easier to split UCM verbs
and profiles as concepts.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
638574c0 by Alper Nebi Yasak at 2022-06-28T15:11:55+03:00
alsa-ucm: Split profile creation into verb and profile parts

To support having multiple profiles per UCM verb, split the profile
creation into two parts based on whether they should run once for each
verb or for each profile (maybe multiple times per verb).

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
0f260228 by Alper Nebi Yasak at 2022-06-28T15:11:56+03:00
alsa-ucm: Rewrite conformant device group generation with idxsets

The existing code meant to generate device groups for combination ports
is tightly coupled to port creation. Similar functionality would be
useful to generate nonconflicting device groups for multiple profiles as
well, so this tries to rewrite it into a more reusable state.

Several things (e.g devices, mapping contexts) use idxsets to store a
device selection. This also switches this conformance check and device
group generation to using idxsets to make it easier to work with those,
with the eventual aim to unify device group representations.

Also try to adjust users of these functions to use idxsets these will
need/return, without causing too much interference.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
52451177 by Alper Nebi Yasak at 2022-06-28T15:11:56+03:00
alsa-ucm: Fix device conformance check

Right now this check is rejecting devices whose UCM config specifies
neither a conflicting device nor a supported device list, and accepting
devices which specify both. However, a device without neither list is
actually unrestricted, and a device with both lists is a configuration
error. Fix the check to accept the former.

Furthermore, this is missing another case where an already selected
device might have a supported devices list that doesn't have the
candidate device. Make this function also check against that, and also
make it accept devices already in the set.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
e8e2f432 by Alper Nebi Yasak at 2022-06-28T15:11:56+03:00
alsa-ucm: Split out helpers for device set name, description, priority

Combination port logic calculates some useful properties for device
groups that we could reuse while generating multiple profiles to support
conflicting devices. Split them into their own functions.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
a50330a4 by Alper Nebi Yasak at 2022-06-28T15:11:56+03:00
alsa-ucm: Make one input/output mapping per UCM device

PulseAudio combines UCM devices that have the same PlaybackPCM or
CapturePCM value into a single mapping with multiple ports. It also
creates ports in the same mapping for each valid combination of those
UCM devices.

Since mappings are the things we put in profiles, we can put in a
profile either all devices of a joint mapping or none of them. This
causes some complications with device conflicts. For example, a
different UCM device might be marked as conflicting with some (but not
all) of the devices in a joint mapping. In this case we can do one of
three things:

- Include all devices in one profile, and hope the conflicting device
  isn't chosen as the mapping's active port. We shouldn't do this as it
  puts conflicting devices in the same profile.

- Make one profile with the joint group, and one with the other device.
  This is somewhat acceptable as we have no conflicts, but we sacrifice
  some compatible combinations of devices.

- Do not group the devices into the same mapping, and make one profile
  for each compatible combination of devices. This appears to be the
  best option, one where we can always have the maximum number of
  working devices.

This patch chooses the third option and makes one input and/or output
mapping per UCM device, by using UCM device names instead of PCM device
strings in the mapping names.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
0789a8fb by Alper Nebi Yasak at 2022-06-28T15:11:56+03:00
alsa-ucm: Remove combination port generation logic

A previous commit makes mapping names depend on the UCM device name.
Since UCM device names are unique, this means a mapping will at most
have one port and thus no combination ports can be generated.

This removes the dead code in the pa_alsa_ucm_add_ports_combination()
function, unrolls the remaining code in its helper functions that it
used, and renames it to pa_alsa_ucm_add_port() to signal that it no
longer generates combinations.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
bf170821 by Alper Nebi Yasak at 2022-06-28T15:12:37+03:00
alsa-ucm: Make ports store only one device

After previous patches, we should be generating no combination ports, so
we don't need to store multiple devices per port. Simplify the code
based on this.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
084d70a1 by Alper Nebi Yasak at 2022-06-28T15:15:07+03:00
alsa-ucm: Make mapping UCM contexts have only one device

After previous patches, we should be generating no combination ports, so
we don't need to store multiple devices per mapping. Simplify the code
based on this.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
4821a056 by Alper Nebi Yasak at 2022-06-28T15:15:08+03:00
alsa-ucm: Make mapping UCM contexts have only one modifier

After previous patches, we should be generating no combination ports, so
we don't need to store multiple modifiers per mapping. Simplify the code
based on this.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
e49da7bc by Alper Nebi Yasak at 2022-06-28T15:15:08+03:00
alsa-ucm: Disable old devices when switching profiles of same verb

While switching profiles, it was enough to switch UCM verbs since that
disables all enabled UCM devices and every profile had a distinct verb.
However, switching to the current verb does not disable any devices.

To support multiple profiles for a verb we need to explicitly disable
the old profile's devices, since they might be conflicting with the new
profile's devices and will prevent them from being enabled. Compare both
profiles' mappings, and disable the devices not in the new mappings.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -
5dd41119 by Alper Nebi Yasak at 2022-06-28T15:15:08+03:00
alsa-ucm: Create multiple profiles per verb for conflicting devices

Right now we try to add all UCM devices of a verb to a single profile.
But if some devices using different PCMs are configured as conflicting
with one another, we will only be able to utilize one of them, chosen
seemingly based on the order in the UCM config file.

This is not a problem with conflicting devices sharing a PCM, as they
are assigned to the same mapping and the ports mechanism only enables
one of them to be active at a time.

To utilize all devices in a UCM verb even when there are conflicting
devices using different PCMs, calculate subsets of devices which
can be simultaneously used and create a profile for each such set.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak at gmail.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/596>

- - - - -


8 changed files:

- src/modules/alsa/alsa-mixer.h
- src/modules/alsa/alsa-sink.c
- src/modules/alsa/alsa-source.c
- src/modules/alsa/alsa-ucm.c
- src/modules/alsa/alsa-ucm.h
- src/modules/alsa/module-alsa-card.c
- src/pulsecore/idxset.c
- src/pulsecore/idxset.h


Changes:

=====================================
src/modules/alsa/alsa-mixer.h
=====================================
@@ -318,7 +318,7 @@ struct pa_alsa_mapping {
     pa_sink *sink;
     pa_source *source;
 
-    /* ucm device context*/
+    /* ucm device context */
     pa_alsa_ucm_mapping_context ucm_context;
 };
 
@@ -342,6 +342,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_alsa_decibel_fix {


=====================================
src/modules/alsa/alsa-sink.c
=====================================
@@ -1721,7 +1721,7 @@ static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) {
     else
         sync_mixer(u, p);
 
-    return pa_alsa_ucm_set_port(u->ucm_context, p, true);
+    return pa_alsa_ucm_set_port(u->ucm_context, p);
 }
 
 static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
@@ -2239,7 +2239,7 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
      * will be NULL, but the UCM device enable sequence will still need to be
      * executed. */
     if (u->sink->active_port && u->ucm_context) {
-        if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
+        if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port) < 0)
             return -1;
     }
 


=====================================
src/modules/alsa/alsa-source.c
=====================================
@@ -1595,7 +1595,7 @@ static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) {
     else
         sync_mixer(u, p);
 
-    return pa_alsa_ucm_set_port(u->ucm_context, p, false);
+    return pa_alsa_ucm_set_port(u->ucm_context, p);
 }
 
 static int source_set_port_cb(pa_source *s, pa_device_port *p) {
@@ -1943,7 +1943,7 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
      * will be NULL, but the UCM device enable sequence will still need to be
      * executed. */
     if (u->source->active_port && u->ucm_context) {
-        if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
+        if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port) < 0)
             return -1;
     }
 


=====================================
src/modules/alsa/alsa-ucm.c
=====================================
@@ -89,9 +89,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},
@@ -187,17 +186,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,
@@ -523,10 +511,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);
     }
@@ -535,10 +523,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);
     }
@@ -547,10 +535,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;
@@ -567,16 +561,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;
 };
@@ -613,6 +619,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;
@@ -659,23 +718,15 @@ 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;
-
-    PA_LLIST_FOREACH(d, list) {
-        const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME);
+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);
 
-        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) {
@@ -704,11 +755,12 @@ static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
     return sub;
 }
 
-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)
@@ -718,11 +770,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);
     }
 }
 
@@ -730,29 +782,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) {
@@ -891,11 +931,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;
@@ -914,41 +954,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;
-        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);
     }
 }
 
@@ -958,24 +983,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;
@@ -1004,91 +1019,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];
-
-    /* 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 = context->ucm_device;
+    if (!dev)
+        return;
 
-    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;
@@ -1105,37 +1170,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);
-
-                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_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);
+                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);
-                }
+                /* 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);
             }
         }
     }
@@ -1157,113 +1217,112 @@ static void ucm_add_port_combination(
         pa_hashmap_put(hash, port->name, port);
         pa_device_port_ref(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;
+static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) {
+    pa_alsa_ucm_device *d;
+    uint32_t idx;
+
+    pa_assert(devices);
+    pa_assert(dev);
+
+    /* Can add anything to empty group */
+    if (pa_idxset_isempty(devices))
+        return true;
 
-    if (!port_name || !dev_name)
+    /* Device already selected */
+    if (pa_idxset_contains(devices, dev))
+        return true;
+
+    /* No conflicting device must already be selected */
+    if (!pa_idxset_isdisjoint(devices, dev->conflicting_devices))
         return false;
 
-    port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
+    /* No already selected device must be unsupported */
+    if (!pa_idxset_isempty(dev->supported_devices))
+        if (!pa_idxset_issubset(devices, dev->supported_devices))
+           return false;
 
-    while ((r = pa_split_in_place(port_name, "+", &len, &state))) {
-        if (strlen(dev_name) == len && !strncmp(r, dev_name, len)) {
-            ret = 1;
-            break;
-        }
-    }
+    /* 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 ret;
+    return true;
 }
 
-static int ucm_check_conformance(
-        pa_alsa_ucm_mapping_context *context,
-        pa_alsa_ucm_device **pdevices,
-        int dev_num,
-        pa_alsa_ucm_device *dev) {
-
+/* 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_alsa_ucm_device *d;
-    int i;
+    pa_alsa_ucm_device *dev;
 
-    pa_assert(dev);
+    pa_assert(devices);
+    pa_assert(state);
 
-    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;
-    }
+    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);
 
-    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;
+    } 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);
     }
 
-    pa_log_debug("Device added to combination, number %d", dev_num + 1);
-    return 1;
-}
-
-static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) {
-    pa_alsa_ucm_device *dev;
+    /* 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 (*idx == PA_IDXSET_INVALID)
-        dev = pa_idxset_first(idxset, idx);
-    else
-        dev = pa_idxset_next(idxset, idx);
+    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 dev;
+    return pa_idxset_copy(*state, NULL);
 }
 
-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) {
-
+/* 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;
-    uint32_t idx = map_index;
-
-    if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL)
-        return;
+    pa_idxset *subset;
 
-    /* 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);
+    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);
+        }
     }
 
-    /* try other device with current elements number */
-    ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core);
+    return subset;
 }
 
 static char* merge_roles(const char *cur, const char *add) {
@@ -1295,28 +1354,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,
@@ -1326,7 +1363,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;
@@ -1337,7 +1373,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);
@@ -1352,19 +1388,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);
@@ -1374,86 +1412,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 ((snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) {
-        pa_log("Failed to set verb %s", profile);
-        ret = -1;
+    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);
     }
 
-    /* find active verb */
-    ucm->active_verb = NULL;
-    PA_LLIST_FOREACH(verb, ucm->verbs) {
-        const char *verb_name;
-        verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME);
-        if (pa_streq(verb_name, profile)) {
-            ucm->active_verb = verb;
-            break;
+    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);
 
-    update_mixer_paths(card->ports, profile);
     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));
-
-    /* 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);
+    data = PA_DEVICE_PORT_DATA(port);
+    dev = context->ucm_device;
+    pa_assert(dev == data->device);
 
-        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) {
@@ -1488,7 +1509,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;
@@ -1517,7 +1538,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;
@@ -1558,17 +1579,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);
 
@@ -1583,7 +1598,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,
@@ -1593,7 +1607,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;
@@ -1604,8 +1618,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;
 
@@ -1613,7 +1626,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);
@@ -1635,7 +1647,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,
@@ -1644,16 +1655,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;
 
@@ -1662,10 +1671,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);
 
@@ -1675,7 +1681,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,
@@ -1690,9 +1695,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;
 }
@@ -1764,27 +1769,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);
@@ -1792,10 +1798,36 @@ static int ucm_create_profile(
     p->supported = true;
     pa_hashmap_put(ps->profiles, p->name, p);
 
+    PA_IDXSET_FOREACH(map, mappings, idx)
+        ucm_add_mapping(p, map);
+
+    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 */
-    priority = verb->priority;
+    verb_priority = verb->priority;
 
-    if (priority == 0) {
+    if (verb_priority == 0) {
         char *verb_cmp, *c;
         c = verb_cmp = pa_xstrdup(verb_name);
         while (*c) {
@@ -1804,15 +1836,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;
@@ -1822,7 +1852,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)
@@ -1873,12 +1903,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;
 }
@@ -1887,7 +1979,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;
@@ -1903,13 +1994,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) {
@@ -1973,38 +2063,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;
-
-        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;
-        }
+    dev = context->ucm_device;
+    if (!dev->jack || !dev->jack->mixer_device_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);
+    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) {
+        pa_log_info("Probing profile %s", p->name);
+
         /* change verb */
-        pa_log_info("Set ucm verb to %s", p->name);
+        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", p->name)) < 0) {
-            pa_log("Failed to set verb %s", p->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;
         }
@@ -2084,7 +2175,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);
@@ -2113,10 +2204,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);
 
@@ -2126,10 +2215,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);
     }
@@ -2177,29 +2264,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;
     }
 }
 
@@ -2252,13 +2332,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);
@@ -2291,7 +2364,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) {
@@ -2315,27 +2388,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,
                                       (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) {
@@ -2345,34 +2413,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 */
@@ -2386,7 +2432,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;
 }
 
@@ -2404,7 +2450,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,
@@ -2413,7 +2459,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;
 }
 


=====================================
src/modules/alsa/alsa-ucm.h
=====================================
@@ -140,12 +140,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);
 
@@ -157,14 +158,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);
@@ -221,11 +222,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;
 
@@ -264,17 +262,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;


=====================================
src/modules/alsa/module-alsa-card.c
=====================================
@@ -162,7 +162,7 @@ static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) {
 
             PA_IDXSET_FOREACH(m, ap->output_mappings, idx) {
                 if (u->use_ucm)
-                    pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, true, ports, cp, u->core);
+                    pa_alsa_ucm_add_port(NULL, &m->ucm_context, true, ports, cp, u->core);
                 else
                     pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL, u->core);
                 if (m->channel_map.channels > cp->max_sink_channels)
@@ -175,7 +175,7 @@ static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) {
 
             PA_IDXSET_FOREACH(m, ap->input_mappings, idx) {
                 if (u->use_ucm)
-                    pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, false, ports, cp, u->core);
+                    pa_alsa_ucm_add_port(NULL, &m->ucm_context, false, ports, cp, u->core);
                 else
                     pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL, u->core);
                 if (m->channel_map.channels > cp->max_source_channels)
@@ -249,8 +249,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
 
     /* if UCM is available for this card then update the verb */
     if (u->use_ucm) {
-        if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL,
-                    od->profile ? od->profile->name : NULL) < 0) {
+        if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile, od->profile) < 0) {
             ret = -1;
             goto finish;
         }
@@ -302,7 +301,7 @@ static void init_profile(struct userdata *u) {
 
     if (d->profile && u->use_ucm) {
         /* Set initial verb */
-        if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) {
+        if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile, NULL) < 0) {
             pa_log("Failed to set ucm profile %s", d->profile->name);
             return;
         }


=====================================
src/pulsecore/idxset.c
=====================================
@@ -258,6 +258,20 @@ void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) {
     return e->data;
 }
 
+bool pa_idxset_contains(pa_idxset *s, const void *p) {
+    unsigned hash;
+    struct idxset_entry *e;
+
+    pa_assert(s);
+
+    hash = s->hash_func(p) % NBUCKETS;
+
+    if (!(e = data_scan(s, hash, p)))
+        return false;
+
+    return e->data == p;
+}
+
 void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) {
     struct idxset_entry *e;
     unsigned hash;
@@ -367,6 +381,39 @@ at_end:
     return NULL;
 }
 
+void *pa_idxset_reverse_iterate(pa_idxset *s, void **state, uint32_t *idx) {
+    struct idxset_entry *e;
+
+    pa_assert(s);
+    pa_assert(state);
+
+    if (*state == (void*) -1)
+        goto at_end;
+
+    if ((!*state && !s->iterate_list_tail))
+        goto at_end;
+
+    e = *state ? *state : s->iterate_list_tail;
+
+    if (e->iterate_previous)
+        *state = e->iterate_previous;
+    else
+        *state = (void*) -1;
+
+    if (idx)
+        *idx = e->idx;
+
+    return e->data;
+
+at_end:
+    *state = (void *) -1;
+
+    if (idx)
+        *idx = PA_IDXSET_INVALID;
+
+    return NULL;
+}
+
 void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx) {
     void *data;
 
@@ -385,6 +432,24 @@ void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx) {
     return data;
 }
 
+void* pa_idxset_steal_last(pa_idxset *s, uint32_t *idx) {
+    void *data;
+
+    pa_assert(s);
+
+    if (!s->iterate_list_tail)
+        return NULL;
+
+    data = s->iterate_list_tail->data;
+
+    if (idx)
+        *idx = s->iterate_list_tail->idx;
+
+    remove_entry(s, s->iterate_list_tail);
+
+    return data;
+}
+
 void* pa_idxset_first(pa_idxset *s, uint32_t *idx) {
     pa_assert(s);
 
@@ -400,6 +465,21 @@ void* pa_idxset_first(pa_idxset *s, uint32_t *idx) {
     return s->iterate_list_head->data;
 }
 
+void* pa_idxset_last(pa_idxset *s, uint32_t *idx) {
+    pa_assert(s);
+
+    if (!s->iterate_list_tail) {
+        if (idx)
+            *idx = PA_IDXSET_INVALID;
+        return NULL;
+    }
+
+    if (idx)
+        *idx = s->iterate_list_tail->idx;
+
+    return s->iterate_list_tail->data;
+}
+
 void *pa_idxset_next(pa_idxset *s, uint32_t *idx) {
     struct idxset_entry *e;
     unsigned hash;
@@ -444,6 +524,50 @@ void *pa_idxset_next(pa_idxset *s, uint32_t *idx) {
     }
 }
 
+void *pa_idxset_previous(pa_idxset *s, uint32_t *idx) {
+    struct idxset_entry *e;
+    unsigned hash;
+
+    pa_assert(s);
+    pa_assert(idx);
+
+    if (*idx == PA_IDXSET_INVALID)
+        return NULL;
+
+    hash = *idx % NBUCKETS;
+
+    if ((e = index_scan(s, hash, *idx))) {
+
+        e = e->iterate_previous;
+
+        if (e) {
+            *idx = e->idx;
+            return e->data;
+        } else {
+            *idx = PA_IDXSET_INVALID;
+            return NULL;
+        }
+
+    } else {
+
+        /* If the entry passed doesn't exist anymore we try to find
+         * the preceding one. */
+
+        for ((*idx)--; *idx < s->current_index; (*idx)--) {
+
+            hash = *idx % NBUCKETS;
+
+            if ((e = index_scan(s, hash, *idx))) {
+                *idx = e->idx;
+                return e->data;
+            }
+        }
+
+        *idx = PA_IDXSET_INVALID;
+        return NULL;
+    }
+}
+
 unsigned pa_idxset_size(pa_idxset*s) {
     pa_assert(s);
 
@@ -456,6 +580,40 @@ bool pa_idxset_isempty(pa_idxset *s) {
     return s->n_entries == 0;
 }
 
+bool pa_idxset_isdisjoint(pa_idxset *s, pa_idxset *t) {
+    struct idxset_entry *i;
+
+    pa_assert(s);
+    pa_assert(t);
+
+    for (i = s->iterate_list_head; i; i = i->iterate_next)
+        if (pa_idxset_contains(t, i->data))
+            return false;
+
+    return true;
+}
+
+bool pa_idxset_issubset(pa_idxset *s, pa_idxset *t) {
+    struct idxset_entry *i;
+
+    pa_assert(s);
+    pa_assert(t);
+
+    for (i = s->iterate_list_head; i; i = i->iterate_next)
+        if (!pa_idxset_contains(t, i->data))
+            return false;
+
+    return true;
+}
+
+bool pa_idxset_issuperset(pa_idxset *s, pa_idxset *t) {
+    return pa_idxset_issubset(t, s);
+}
+
+bool pa_idxset_equals(pa_idxset *s, pa_idxset *t) {
+    return pa_idxset_issubset(s, t) && pa_idxset_issuperset(s, t);
+}
+
 pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func) {
     pa_idxset *copy;
     struct idxset_entry *i;


=====================================
src/pulsecore/idxset.h
=====================================
@@ -66,6 +66,9 @@ void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx);
 /* Get the entry by its data. The index is returned in *idx */
 void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx);
 
+/* Return true if item is in idxset */
+bool pa_idxset_contains(pa_idxset *s, const void *p);
+
 /* Similar to pa_idxset_get_by_index(), but removes the entry from the idxset. */
 void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx);
 
@@ -85,18 +88,25 @@ void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx);
 
 /* Iterate through the idxset. At first iteration state should be NULL */
 void *pa_idxset_iterate(pa_idxset *s, void **state, uint32_t *idx);
+void *pa_idxset_reverse_iterate(pa_idxset *s, void **state, uint32_t *idx);
 
-/* Return the oldest entry in the idxset and remove it. If idx is not NULL fill in its index in *idx */
+/* Return the oldest or newest entry in the idxset and remove it.
+ * If idx is not NULL fill in its index in *idx */
 void* pa_idxset_steal_first(pa_idxset *s, uint32_t *idx);
+void* pa_idxset_steal_last(pa_idxset *s, uint32_t *idx);
 
-/* Return the oldest entry in the idxset. Fill in its index in *idx. */
+/* Return the oldest or newest entry in the idxset.
+ * Fill in its index in *idx. */
 void* pa_idxset_first(pa_idxset *s, uint32_t *idx);
+void* pa_idxset_last(pa_idxset *s, uint32_t *idx);
 
-/* Return the entry following the entry indexed by *idx.  After the
- * call *index contains the index of the returned
- * object. pa_idxset_first() and pa_idxset_next() may be used to
- * iterate through the set.*/
+/* Return the entry following or preceding the entry indexed by *idx.
+ * After the call *index contains the index of the returned object.
+ * pa_idxset_first() and pa_idxset_next() may be used to iterate through
+ * the set. pa_idxset_last() and pa_idxset_previous() may be used to
+ * iterate through the set in reverse. */
 void *pa_idxset_next(pa_idxset *s, uint32_t *idx);
+void *pa_idxset_previous(pa_idxset *s, uint32_t *idx);
 
 /* Return the current number of entries in the idxset */
 unsigned pa_idxset_size(pa_idxset*s);
@@ -104,6 +114,18 @@ unsigned pa_idxset_size(pa_idxset*s);
 /* Return true of the idxset is empty */
 bool pa_idxset_isempty(pa_idxset *s);
 
+/* Return true if s and t have no entries in common */
+bool pa_idxset_isdisjoint(pa_idxset *s, pa_idxset *t);
+
+/* Return true if all entries in s are also in t */
+bool pa_idxset_issubset(pa_idxset *s, pa_idxset *t);
+
+/* Return true if all entries in t are also in s */
+bool pa_idxset_issuperset(pa_idxset *s, pa_idxset *t);
+
+/* Return true if s and t have all entries in common */
+bool pa_idxset_equals(pa_idxset *s, pa_idxset *t);
+
 /* Duplicate the idxset. This will not copy the actual indexes. If copy_func is
  * set, each entry is copied using the provided function, otherwise a shallow
  * copy will be made. */



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/def8eb074eb4a80836c39fa320c33fe89bce38d9...5dd411190deb1957a7ed86f284e4f7a0c58bb877

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/def8eb074eb4a80836c39fa320c33fe89bce38d9...5dd411190deb1957a7ed86f284e4f7a0c58bb877
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20220628/f6d3d0ac/attachment-0001.htm>


More information about the pulseaudio-commits mailing list