[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 12 commits: alsa-ucm: use ucm2 name for the direct card index open

Arun Raghavan gitlab at gitlab.freedesktop.org
Fri Dec 6 10:13:52 UTC 2019



Arun Raghavan pushed to branch master at PulseAudio / pulseaudio


Commits:
c8f06525 by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: use ucm2 name for the direct card index open

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
ab5be56a by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: add mixer IDs to ucm_items

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
7f4b8e1a by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-mixer: handle the index for ALSA mixer element identifiers

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
1c240b7a by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-mixer: improve alsa_id_decode() function

Accept those identifiers:

        Speaker,1
        'Speaker',1
        "Speaker",1

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
3dfccada by Arun Raghavan at 2019-12-06T10:05:44Z
alsa-ucm: Support Playback/CaptureVolume

This allows us to support the PlaybackVolume and CaptureVolume commands
in UCM, specifying a mixer control to use for hardware volume control.
This only works with ports corresponding to single devices at the
moment, and doesn't support stacking controls for combination ports.

The configuration is intended to provide a control (like Headphone
Playback Volume), but we try to resolve to a simple mixer control
(Headphone) to reuse existing volume paths.

On the UCM side, this also requires that when disabling the device for
the port, the volume should be reset to some default.

When enabling/disabling combination devices, things are a bit iffy since
we have no way to reset the volume before switching to a combination
device. It would be nice to have a combination-transition-sequence
command in UCM to handle this and other similar cases.

PlaybackSwitch and CaptureSwitch are yet to be implemented.

- - - - -
9acacd9b by Jaska Uimonen at 2019-12-06T10:05:44Z
alsa-ucm: Fix volume control based on review

- sync mixer logic added
- mixer path creation, empty set in mapping creation, paths added in path creation
- path creation moved inside new port creation as it might be called twice otherwise
- some comments added

- - - - -
dc9dc70f by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: use the correct mixer identifiers as first

The mixer identifiers should be used for snd_mixer_selem API.
Use them as first, then try to fallback to the raw control
identifiers.

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
6d830bf0 by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: add support for master volume

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
156bd774 by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: split correctly JackHWMute device names

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
e04f14eb by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: fix parsing for JackControl

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
f5c02dfc by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: add comments to ucm_get_mixer_id()

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -
e6779ad2 by Jaroslav Kysela at 2019-12-06T10:05:44Z
alsa-ucm: validate access to PA_DEVICE_PORT_DATA()

Signed-off-by: Jaroslav Kysela <perex at perex.cz>

- - - - -


9 changed files:

- src/modules/alsa/alsa-mixer.c
- 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/core-util.c
- src/pulsecore/core-util.h


Changes:

=====================================
src/modules/alsa/alsa-mixer.c
=====================================
@@ -107,6 +107,46 @@ struct description_map {
     const char *description;
 };
 
+static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) {
+    if (id->index > 0) {
+        snprintf(dst, dst_len, "'%s',%d", id->name, id->index);
+    } else {
+        snprintf(dst, dst_len, "'%s'", id->name);
+    }
+    return dst;
+}
+
+static int alsa_id_decode(const char *src, char *name, int *index) {
+    char *idx, c;
+    int i;
+
+    *index = 0;
+    c = src[0];
+    /* Strip quotes in entries such as 'Speaker',1 or "Speaker",1 */
+    if (c == '\'' || c == '"') {
+        strcpy(name, src + 1);
+        for (i = 0; name[i] != '\0' && name[i] != c; i++);
+        idx = NULL;
+        if (name[i]) {
+                name[i] = '\0';
+                idx = strchr(name + i + 1, ',');
+        }
+    } else {
+        strcpy(name, src);
+        idx = strchr(name, ',');
+    }
+    if (idx == NULL)
+        return 0;
+    *idx = '\0';
+    idx++;
+    if (*idx < '0' || *idx > '9') {
+        pa_log("Element %s: index value is invalid", src);
+        return 1;
+    }
+    *index = atoi(idx);
+    return 0;
+}
+
 pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
     pa_alsa_jack *jack;
 
@@ -641,6 +681,7 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
     pa_xfree(db_fix->name);
     pa_xfree(db_fix->db_values);
 
+    pa_xfree(db_fix->key);
     pa_xfree(db_fix);
 }
 
@@ -656,7 +697,7 @@ static void element_free(pa_alsa_element *e) {
     if (e->db_fix)
         decibel_fix_free(e->db_fix);
 
-    pa_xfree(e->alsa_name);
+    pa_xfree(e->alsa_id.name);
     pa_xfree(e);
 }
 
@@ -717,11 +758,11 @@ static pa_volume_t from_alsa_volume(long v, long min, long max) {
     return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min));
 }
 
-#define SELEM_INIT(sid, name)                           \
-    do {                                                \
-        snd_mixer_selem_id_alloca(&(sid));              \
-        snd_mixer_selem_id_set_name((sid), (name));     \
-        snd_mixer_selem_id_set_index((sid), 0);         \
+#define SELEM_INIT(sid, aid)                                     \
+    do {                                                     \
+        snd_mixer_selem_id_alloca(&(sid));                   \
+        snd_mixer_selem_id_set_name((sid), (aid)->name);     \
+        snd_mixer_selem_id_set_index((sid), (aid)->index);   \
     } while(false)
 
 static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
@@ -729,6 +770,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
     snd_mixer_elem_t *me;
     snd_mixer_selem_channel_id_t c;
     pa_channel_position_mask_t mask = 0;
+    char buf[64];
     unsigned k;
 
     pa_assert(m);
@@ -736,9 +778,10 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
     pa_assert(cm);
     pa_assert(v);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -763,14 +806,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
                             if (value < e->db_fix->min_step) {
                                 value = e->db_fix->min_step;
                                 snd_mixer_selem_set_playback_volume(me, c, value);
+                                alsa_id_str(buf, sizeof(buf), &e->alsa_id);
                                 pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
-                                             "Volume reset to %0.2f dB.", e->alsa_name, c,
+                                             "Volume reset to %0.2f dB.", buf, c,
                                              e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
                             } else if (value > e->db_fix->max_step) {
                                 value = e->db_fix->max_step;
                                 snd_mixer_selem_set_playback_volume(me, c, value);
+                                alsa_id_str(buf, sizeof(buf), &e->alsa_id);
                                 pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
-                                             "Volume reset to %0.2f dB.", e->alsa_name, c,
+                                             "Volume reset to %0.2f dB.", buf, c,
                                              e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
                             }
 
@@ -791,14 +836,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
                             if (value < e->db_fix->min_step) {
                                 value = e->db_fix->min_step;
                                 snd_mixer_selem_set_capture_volume(me, c, value);
+                                alsa_id_str(buf, sizeof(buf), &e->alsa_id);
                                 pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
-                                             "Volume reset to %0.2f dB.", e->alsa_name, c,
+                                             "Volume reset to %0.2f dB.", buf, c,
                                              e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
                             } else if (value > e->db_fix->max_step) {
                                 value = e->db_fix->max_step;
                                 snd_mixer_selem_set_capture_volume(me, c, value);
+                                alsa_id_str(buf, sizeof(buf), &e->alsa_id);
                                 pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
-                                             "Volume reset to %0.2f dB.", e->alsa_name, c,
+                                             "Volume reset to %0.2f dB.", buf, c,
                                              e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
                             }
 
@@ -896,14 +943,16 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) {
     snd_mixer_selem_id_t *sid;
     snd_mixer_elem_t *me;
     snd_mixer_selem_channel_id_t c;
+    char buf[64];
 
     pa_assert(m);
     pa_assert(e);
     pa_assert(b);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -1057,6 +1106,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
     snd_mixer_elem_t *me;
     snd_mixer_selem_channel_id_t c;
     pa_channel_position_mask_t mask = 0;
+    char buf[64];
     unsigned k;
 
     pa_assert(m);
@@ -1065,9 +1115,10 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
     pa_assert(v);
     pa_assert(pa_cvolume_compatible_with_channel_map(v, cm));
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -1250,14 +1301,16 @@ int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_ma
 static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
     snd_mixer_elem_t *me;
     snd_mixer_selem_id_t *sid;
+    char buf[64];
     int r;
 
     pa_assert(m);
     pa_assert(e);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -1266,8 +1319,10 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) {
     else
         r = snd_mixer_selem_set_capture_switch_all(me, b);
 
-    if (r < 0)
-        pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+    if (r < 0) {
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+    }
 
     return r;
 }
@@ -1302,13 +1357,15 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
     int r = 0;
     long volume = -1;
     bool volume_set = false;
+    char buf[64];
 
     pa_assert(m);
     pa_assert(e);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -1351,8 +1408,10 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) {
             r = snd_mixer_selem_set_capture_dB_all(me, 0, -1);
     }
 
-    if (r < 0)
-        pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+    if (r < 0) {
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno));
+    }
 
     return r;
 }
@@ -1530,6 +1589,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
     int r;
     bool is_mono;
     pa_channel_position_t p;
+    char buf[64];
 
     if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
         if (!snd_mixer_selem_has_playback_volume(me)) {
@@ -1555,29 +1615,33 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
         r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
 
     if (r < 0) {
-        pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r));
         return false;
     }
 
     if (e->min_volume >= e->max_volume) {
-        pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.",
-                    e->min_volume, e->max_volume);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.",
+                    buf, e->min_volume, e->max_volume);
         return false;
     }
     if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) {
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
         pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.",
-                    e->constant_volume, e->alsa_name, e->min_volume, e->max_volume);
+                    e->constant_volume, buf, e->min_volume, e->max_volume);
         return false;
     }
 
 
     if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) {
-          pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
-                      "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
-                      e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
+                    "real hardware range (%li-%li). Disabling the decibel fix.", buf,
+                    e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume);
 
-          decibel_fix_free(e->db_fix);
-          e->db_fix = NULL;
+        decibel_fix_free(e->db_fix);
+        e->db_fix = NULL;
     }
 
     if (e->db_fix) {
@@ -1598,19 +1662,22 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
         long max_dB_checked = 0;
 
         if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) {
-            pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume);
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+            pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume);
             return false;
         }
 
         if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) {
-            pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume);
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+            pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume);
             return false;
         }
 
         if (min_dB != min_dB_checked || max_dB != max_dB_checked) {
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
             pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) "
                         "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, "
-                        "%0.2f dB at level %li.", e->alsa_name, min_dB / 100.0, max_dB / 100.0,
+                        "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0,
                         min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume);
             return false;
         }
@@ -1629,11 +1696,12 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
     }
 
     if (e->volume_limit >= 0) {
-        if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
+        if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) {
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
             pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
                         "%li-%li. The volume limit is ignored.",
-                        e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
-        else {
+                        buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
+        } else {
             e->max_volume = e->volume_limit;
 
             if (e->has_dB) {
@@ -1641,7 +1709,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
                     e->db_fix->max_step = e->max_volume;
                     e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
                 } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) {
-                    pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+                    alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+                    pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r));
                     e->has_dB = false;
                 } else
                     e->max_dB = ((double) max_dB) / 100.0;
@@ -1681,7 +1750,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
     }
 
     if (e->n_channels <= 0) {
-        pa_log_warn("Volume element %s with no channels?", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Volume element %s with no channels?", buf);
         return false;
     } else if (e->n_channels > 2) {
         /* FIXME: In some places code like this is used:
@@ -1695,7 +1765,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) {
          * Since the array size is fixed at 2, we obviously
          * don't support elements with more than two
          * channels... */
-        pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels);
         return false;
     }
 
@@ -1733,7 +1804,7 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
     pa_assert(e);
     pa_assert(e->path);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
 
     if (!(me = snd_mixer_find_selem(m, sid))) {
 
@@ -1852,8 +1923,10 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m)
     return 0;
 }
 
-static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool prefixed) {
+pa_alsa_element * pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed) {
     pa_alsa_element *e;
+    char *name;
+    int index;
 
     pa_assert(p);
     pa_assert(section);
@@ -1869,16 +1942,22 @@ static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool p
     if (strchr(section, ':'))
         return NULL;
 
-    if (p->last_element && pa_streq(p->last_element->alsa_name, section))
+    name = alloca(strlen(section) + 1);
+    if (alsa_id_decode(section, name, &index))
+        return NULL;
+
+    if (p->last_element && pa_streq(p->last_element->alsa_id.name, name) &&
+        p->last_element->alsa_id.index == index)
         return p->last_element;
 
     PA_LLIST_FOREACH(e, p->elements)
-        if (pa_streq(e->alsa_name, section))
+        if (pa_streq(e->alsa_id.name, name) && e->alsa_id.index == index)
             goto finish;
 
     e = pa_xnew0(pa_alsa_element, 1);
     e->path = p;
-    e->alsa_name = pa_xstrdup(section);
+    e->alsa_id.name = pa_xstrdup(name);
+    e->alsa_id.index = index;
     e->direction = p->direction;
     e->volume_limit = -1;
 
@@ -1912,10 +1991,12 @@ finish:
 }
 
 static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
-    char *en;
+    char *en, *name;
     const char *on;
     pa_alsa_option *o;
     pa_alsa_element *e;
+    size_t len;
+    int index;
 
     if (!pa_startswith(section, "Option "))
         return NULL;
@@ -1926,18 +2007,25 @@ static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) {
     if (!(on = strchr(section, ':')))
         return NULL;
 
-    en = pa_xstrndup(section, on - section);
+    len = on - section;
+    en = alloca(len + 1);
+    strncpy(en, section, len);
+    en[len] = '\0';
+
+    name = alloca(strlen(en) + 1);
+    if (alsa_id_decode(en, name, &index))
+        return NULL;
+
     on++;
 
     if (p->last_option &&
-        pa_streq(p->last_option->element->alsa_name, en) &&
+        pa_streq(p->last_option->element->alsa_id.name, name) &&
+        p->last_option->element->alsa_id.index == index &&
         pa_streq(p->last_option->alsa_name, on)) {
-        pa_xfree(en);
         return p->last_option;
     }
 
-    pa_assert_se(e = element_get(p, en, false));
-    pa_xfree(en);
+    pa_assert_se(e = pa_alsa_element_get(p, en, false));
 
     PA_LLIST_FOREACH(o, e->options)
         if (pa_streq(o->alsa_name, on))
@@ -1966,7 +2054,7 @@ static int element_parse_switch(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Switch makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -1997,7 +2085,7 @@ static int element_parse_volume(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Volume makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2033,7 +2121,7 @@ static int element_parse_enumeration(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Enumeration makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2125,7 +2213,7 @@ static int element_parse_required(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    e = element_get(p, state->section, true);
+    e = pa_alsa_element_get(p, state->section, true);
     o = option_get(p, state->section);
     j = jack_get(p, state->section);
     if (!e && !o && !j) {
@@ -2191,7 +2279,7 @@ static int element_parse_direction(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2217,7 +2305,7 @@ static int element_parse_direction_try_other(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2240,7 +2328,7 @@ static int element_parse_volume_limit(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2298,7 +2386,7 @@ static int element_parse_override_map(pa_config_parser_state *state) {
 
     p = state->userdata;
 
-    if (!(e = element_get(p, state->section, true))) {
+    if (!(e = pa_alsa_element_get(p, state->section, true))) {
         pa_log("[%s:%u] Override map makes no sense in '%s'", state->filename, state->lineno, state->section);
         return -1;
     }
@@ -2393,14 +2481,16 @@ static int jack_parse_append_pcm_to_name(pa_config_parser_state *state) {
 static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) {
     snd_mixer_selem_id_t *sid;
     snd_mixer_elem_t *me;
+    char buf[64];
     int r;
 
     pa_assert(e);
     pa_assert(m);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return -1;
     }
 
@@ -2411,14 +2501,18 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx)
         else
             r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx);
 
-        if (r < 0)
-            pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+        if (r < 0) {
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+            pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno));
+        }
 
     } else {
         pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT);
 
-        if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0)
-            pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
+        if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) {
+            alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+            pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno));
+        }
     }
 
     return r;
@@ -2462,6 +2556,7 @@ static int option_verify(pa_alsa_option *o) {
         { "output-speaker",            N_("Speaker") },
         { "output-headphones",         N_("Headphones") }
     };
+    char buf[64];
 
     pa_assert(o);
 
@@ -2472,14 +2567,16 @@ static int option_verify(pa_alsa_option *o) {
 
     if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT &&
         o->element->switch_use != PA_ALSA_SWITCH_SELECT) {
-        pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name);
+        alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+        pa_log("Element %s of option %s not set for select.", buf, o->name);
         return -1;
     }
 
     if (o->element->switch_use == PA_ALSA_SWITCH_SELECT &&
         !pa_streq(o->alsa_name, "on") &&
         !pa_streq(o->alsa_name, "off")) {
-        pa_log("Switch %s options need be named off or on ", o->element->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &o->element->alsa_id);
+        pa_log("Switch %s options need be named off or on ", buf);
         return -1;
     }
 
@@ -2495,6 +2592,7 @@ static int option_verify(pa_alsa_option *o) {
 
 static int element_verify(pa_alsa_element *e) {
     pa_alsa_option *o;
+    char buf[64];
 
     pa_assert(e);
 
@@ -2503,12 +2601,14 @@ static int element_verify(pa_alsa_element *e) {
         (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) ||
         (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) ||
         (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) {
-        pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log("Element %s cannot be required and absent at the same time.", buf);
         return -1;
     }
 
     if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) {
-        pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log("Element %s cannot set select for both switch and enumeration.", buf);
         return -1;
     }
 
@@ -2660,9 +2760,15 @@ fail:
 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) {
     pa_alsa_path *p;
     pa_alsa_element *e;
+    char *name;
+    int index;
 
     pa_assert(element);
 
+    name = alloca(strlen(element) + 1);
+    if (alsa_id_decode(element, name, &index))
+        return NULL;
+
     p = pa_xnew0(pa_alsa_path, 1);
     p->name = pa_xstrdup(element);
     p->direction = direction;
@@ -2670,7 +2776,8 @@ pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t d
 
     e = pa_xnew0(pa_alsa_element, 1);
     e->path = p;
-    e->alsa_name = pa_xstrdup(element);
+    e->alsa_id.name = pa_xstrdup(name);
+    e->alsa_id.index = index;
     e->direction = direction;
     e->volume_limit = -1;
 
@@ -2820,6 +2927,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
     double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX];
     pa_channel_position_t t;
     pa_channel_position_mask_t path_volume_channels = 0;
+    char buf[64];
 
     pa_assert(p);
     pa_assert(m);
@@ -2843,12 +2951,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
     }
 
     PA_LLIST_FOREACH(e, p->elements) {
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
         if (element_probe(e, m) < 0) {
             p->supported = false;
-            pa_log_debug("Probe of element '%s' failed.", e->alsa_name);
+            pa_log_debug("Probe of element %s failed.", buf);
             return -1;
         }
-        pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use);
+        pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use);
 
         if (ignore_dB)
             e->has_dB = false;
@@ -2884,13 +2993,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m
                          * which cannot do dB volumes, so we we need
                          * to 'neutralize' this slider */
                         e->volume_use = PA_ALSA_VOLUME_ZERO;
-                        pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name);
+                        pa_log_info("Zeroing volume of %s on path '%s'", buf, p->name);
                     }
                 }
             } else if (p->has_volume) {
                 /* We can't use this volume, so let's ignore it */
                 e->volume_use = PA_ALSA_VOLUME_IGNORE;
-                pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name);
+                pa_log_info("Ignoring volume of %s on path '%s' (missing dB info)", buf, p->name);
             }
             p->has_volume = true;
         }
@@ -2954,11 +3063,14 @@ void pa_alsa_option_dump(pa_alsa_option *o) {
 }
 
 void pa_alsa_element_dump(pa_alsa_element *e) {
+    char buf[64];
+
     pa_alsa_option *o;
     pa_assert(e);
 
+    alsa_id_str(buf, sizeof(buf), &e->alsa_id);
     pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
-                 e->alsa_name,
+                 buf,
                  e->direction,
                  e->switch_use,
                  e->volume_use,
@@ -3008,14 +3120,16 @@ void pa_alsa_path_dump(pa_alsa_path *p) {
 static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) {
     snd_mixer_selem_id_t *sid;
     snd_mixer_elem_t *me;
+    char buf[64];
 
     pa_assert(e);
     pa_assert(m);
     pa_assert(cb);
 
-    SELEM_INIT(sid, e->alsa_name);
+    SELEM_INIT(sid, &e->alsa_id);
     if (!(me = snd_mixer_find_selem(m, sid))) {
-        pa_log_warn("Element %s seems to have disappeared.", e->alsa_name);
+        alsa_id_str(buf, sizeof(buf), &e->alsa_id);
+        pa_log_warn("Element %s seems to have disappeared.", buf);
         return;
     }
 
@@ -3081,6 +3195,8 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
     char **pn = NULL, **en = NULL, **ie;
     pa_alsa_decibel_fix *db_fix;
     void *state, *state2;
+    char name[64];
+    int index;
 
     pa_assert(m);
     pa_assert(m->profile_set);
@@ -3160,9 +3276,18 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
             if (je == ie)
                 continue;
 
+            if (strlen(*je) + 1 >= sizeof(name)) {
+                pa_log("Element identifier %s is too long!", *je);
+                continue;
+            }
+
+            if (alsa_id_decode(*je, name, &index))
+                continue;
+
             e = pa_xnew0(pa_alsa_element, 1);
             e->path = p;
-            e->alsa_name = pa_xstrdup(*je);
+            e->alsa_id.name = pa_xstrdup(name);
+            e->alsa_id.index = index;
             e->direction = direction;
             e->required_absent = PA_ALSA_REQUIRED_ANY;
             e->volume_limit = -1;
@@ -3183,7 +3308,8 @@ finish:
             pa_alsa_element *e;
 
             PA_LLIST_FOREACH(e, p->elements) {
-                if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
+                if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_id.name) &&
+                    db_fix->index == e->alsa_id.index) {
                     /* The profile set that contains the dB fix may be freed
                      * before the element, so we have to copy the dB fix
                      * object. */
@@ -3256,6 +3382,8 @@ static bool enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_o
  *  Compares two elements to see if a is a subset of b
  */
 static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
+    char buf[64];
+
     pa_assert(a);
     pa_assert(b);
     pa_assert(m);
@@ -3293,9 +3421,10 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
                     snd_mixer_selem_id_t *sid;
                     snd_mixer_elem_t *me;
 
-                    SELEM_INIT(sid, a->alsa_name);
+                    SELEM_INIT(sid, &a->alsa_id);
                     if (!(me = snd_mixer_find_selem(m, sid))) {
-                        pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
+                        alsa_id_str(buf, sizeof(buf), &a->alsa_id);
+                        pa_log_warn("Element %s seems to have disappeared.", buf);
                         return false;
                     }
 
@@ -3325,8 +3454,9 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_
                 return false;
             for (s = 0; s <= SND_MIXER_SCHN_LAST; s++)
                 if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) {
+                    alsa_id_str(buf, sizeof(buf), &a->alsa_id);
                     pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d",
-                        a->alsa_name, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
+                                 buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s);
                     return false;
                }
         }
@@ -3423,7 +3553,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
                     break;
 
                 PA_LLIST_FOREACH(eb, p2->elements) {
-                    if (pa_streq(ea->alsa_name, eb->alsa_name)) {
+                    if (pa_streq(ea->alsa_id.name, eb->alsa_id.name) &&
+                        ea->alsa_id.index == eb->alsa_id.index) {
                         found_matching_element = true;
                         is_subset = element_is_subset(ea, eb, m);
                         break;
@@ -3600,22 +3731,30 @@ static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
     return p;
 }
 
-static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
+static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *alsa_id) {
     pa_alsa_decibel_fix *db_fix;
+    char *name;
+    int index;
 
-    if (!pa_startswith(name, "DecibelFix "))
+    if (!pa_startswith(alsa_id, "DecibelFix "))
         return NULL;
 
-    name += 11;
+    alsa_id += 11;
 
-    if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
+    if ((db_fix = pa_hashmap_get(ps->decibel_fixes, alsa_id)))
         return db_fix;
 
+    name = alloca(strlen(alsa_id) + 1);
+    if (alsa_id_decode(alsa_id, name, &index))
+        return NULL;
+
     db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
     db_fix->profile_set = ps;
     db_fix->name = pa_xstrdup(name);
+    db_fix->index = index;
+    db_fix->key = pa_xstrdup(alsa_id);
 
-    pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
+    pa_hashmap_put(ps->decibel_fixes, db_fix->key, db_fix);
 
     return db_fix;
 }


=====================================
src/modules/alsa/alsa-mixer.h
=====================================
@@ -34,6 +34,7 @@
 typedef struct pa_alsa_fdlist pa_alsa_fdlist;
 typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
 typedef struct pa_alsa_setting pa_alsa_setting;
+typedef struct pa_alsa_mixer_id pa_alsa_mixer_id;
 typedef struct pa_alsa_option pa_alsa_option;
 typedef struct pa_alsa_element pa_alsa_element;
 typedef struct pa_alsa_jack pa_alsa_jack;
@@ -97,6 +98,12 @@ struct pa_alsa_setting {
     unsigned priority;
 };
 
+/* ALSA mixer element identifier */
+struct pa_alsa_mixer_id {
+    char *name;
+    int index;
+};
+
 /* An option belongs to an element and refers to one enumeration item
  * of the element is an enumeration item, or a switch status if the
  * element is a switch item. */
@@ -123,7 +130,7 @@ struct pa_alsa_element {
     pa_alsa_path *path;
     PA_LLIST_FIELDS(pa_alsa_element);
 
-    char *alsa_name;
+    struct pa_alsa_mixer_id alsa_id;
     pa_alsa_direction_t direction;
 
     pa_alsa_switch_use_t switch_use;
@@ -237,6 +244,7 @@ void pa_alsa_element_dump(pa_alsa_element *e);
 
 pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction);
 pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
+pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed);
 int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB);
 void pa_alsa_path_dump(pa_alsa_path *p);
 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
@@ -315,9 +323,12 @@ struct pa_alsa_profile {
 };
 
 struct pa_alsa_decibel_fix {
+    char *key;
+
     pa_alsa_profile_set *profile_set;
 
     char *name; /* Alsa volume element name. */
+    int index;  /* Alsa volume element index. */
     long min_step;
     long max_step;
 


=====================================
src/modules/alsa/alsa-sink.c
=====================================
@@ -1266,7 +1266,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) {
 
     /* port may be NULL, because if we use a synthesized mixer path, then the
      * sink has no ports. */
-    if (port) {
+    if (port && !u->ucm_context) {
         pa_alsa_port_data *data;
 
         data = PA_DEVICE_PORT_DATA(port);
@@ -1598,7 +1598,7 @@ static void sink_set_mute_cb(pa_sink *s) {
 static void mixer_volume_init(struct userdata *u) {
     pa_assert(u);
 
-    if (!u->mixer_path->has_volume) {
+    if (!u->mixer_path || !u->mixer_path->has_volume) {
         pa_sink_set_write_volume_callback(u->sink, NULL);
         pa_sink_set_get_volume_callback(u->sink, NULL);
         pa_sink_set_set_volume_callback(u->sink, NULL);
@@ -1633,7 +1633,7 @@ static void mixer_volume_init(struct userdata *u) {
         pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
     }
 
-    if (!u->mixer_path->has_mute) {
+    if (!u->mixer_path || !u->mixer_path->has_mute) {
         pa_sink_set_get_mute_callback(u->sink, NULL);
         pa_sink_set_set_mute_callback(u->sink, NULL);
         pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1646,11 +1646,22 @@ static void mixer_volume_init(struct userdata *u) {
 
 static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) {
     struct userdata *u = s->userdata;
+    pa_alsa_ucm_port_data *data;
 
     pa_assert(u);
     pa_assert(p);
+    pa_assert(u->mixer_handle);
     pa_assert(u->ucm_context);
 
+    data = PA_DEVICE_PORT_DATA(p);
+    pa_assert_se(u->mixer_path = data->path);
+    mixer_volume_init(u);
+
+    if (s->flags & PA_SINK_DEFERRED_VOLUME)
+        pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_SYNC_MIXER, p, 0, NULL);
+    else
+        sync_mixer(u, p);
+
     return pa_alsa_ucm_set_port(u->ucm_context, p, true);
 }
 
@@ -1661,6 +1672,7 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
     pa_assert(u);
     pa_assert(p);
     pa_assert(u->mixer_handle);
+    pa_assert(!u->ucm_context);
 
     data = PA_DEVICE_PORT_DATA(p);
     pa_assert_se(u->mixer_path = data->path);
@@ -2071,6 +2083,7 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de
 }
 
 static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) {
+
     if (!mapping && !element)
         return;
 
@@ -2116,16 +2129,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
         return 0;
 
     if (u->sink->active_port) {
-        pa_alsa_port_data *data;
+        if (!u->ucm_context) {
+            pa_alsa_port_data *data;
+
+            /* We have a list of supported paths, so let's activate the
+             * one that has been chosen as active */
 
-        /* We have a list of supported paths, so let's activate the
-         * one that has been chosen as active */
+            data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+            u->mixer_path = data->path;
 
-        data = PA_DEVICE_PORT_DATA(u->sink->active_port);
-        u->mixer_path = data->path;
+            pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+        } else {
+            pa_alsa_ucm_port_data *data;
+
+            /* First activate the port on the UCM side */
+            if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
+                return -1;
 
-        pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+            data = PA_DEVICE_PORT_DATA(u->sink->active_port);
 
+            /* Now activate volume controls, if any */
+            if (data->path) {
+                u->mixer_path = data->path;
+                pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->sink->muted);
+            }
+        }
     } else {
 
         if (!u->mixer_path && u->mixer_path_set)
@@ -2135,7 +2163,6 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
             /* Hmm, we have only a single path, then let's activate it */
 
             pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted);
-
         } else
             return 0;
     }
@@ -2466,8 +2493,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     /* ALSA might tweak the sample spec, so recalculate the frame size */
     frame_size = pa_frame_size(&ss);
 
-    if (!u->ucm_context)
-        find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+    find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
 
     pa_sink_new_data_init(&data);
     data.driver = driver;
@@ -2524,7 +2550,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     }
 
     if (u->ucm_context)
-        pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card);
+        pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB);
     else if (u->mixer_path_set)
         pa_alsa_add_ports(&data, u->mixer_path_set, card);
 
@@ -2598,10 +2624,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     if (update_sw_params(u, false) < 0)
         goto fail;
 
-    if (u->ucm_context) {
-        if (u->sink->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
-            goto fail;
-    } else if (setup_mixer(u, ignore_dB) < 0)
+    if (setup_mixer(u, ignore_dB) < 0)
         goto fail;
 
     pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2666,7 +2689,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
      * pa_sink_suspend() between pa_sink_new() and pa_sink_put() would
      * otherwise work, but currently pa_sink_suspend() will crash if
      * pa_sink_put() hasn't been called. */
-    if (u->sink->active_port) {
+    if (u->sink->active_port && !u->ucm_context) {
         pa_alsa_port_data *port_data;
 
         port_data = PA_DEVICE_PORT_DATA(u->sink->active_port);
@@ -2725,7 +2748,8 @@ static void userdata_free(struct userdata *u) {
     if (u->mixer_fdl)
         pa_alsa_fdlist_free(u->mixer_fdl);
 
-    if (u->mixer_path && !u->mixer_path_set)
+    /* Only free the mixer_path if the sink owns it */
+    if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
         pa_alsa_path_free(u->mixer_path);
 
     if (u->mixer_handle)


=====================================
src/modules/alsa/alsa-source.c
=====================================
@@ -1137,7 +1137,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) {
 
     /* port may be NULL, because if we use a synthesized mixer path, then the
      * source has no ports. */
-    if (port) {
+    if (port && !u->ucm_context) {
         pa_alsa_port_data *data;
 
         data = PA_DEVICE_PORT_DATA(port);
@@ -1469,7 +1469,7 @@ static void source_set_mute_cb(pa_source *s) {
 static void mixer_volume_init(struct userdata *u) {
     pa_assert(u);
 
-    if (!u->mixer_path->has_volume) {
+    if (!u->mixer_path || !u->mixer_path->has_volume) {
         pa_source_set_write_volume_callback(u->source, NULL);
         pa_source_set_get_volume_callback(u->source, NULL);
         pa_source_set_set_volume_callback(u->source, NULL);
@@ -1504,7 +1504,7 @@ static void mixer_volume_init(struct userdata *u) {
         pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
     }
 
-    if (!u->mixer_path->has_mute) {
+    if (!u->mixer_path || !u->mixer_path->has_mute) {
         pa_source_set_get_mute_callback(u->source, NULL);
         pa_source_set_set_mute_callback(u->source, NULL);
         pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1517,11 +1517,22 @@ static void mixer_volume_init(struct userdata *u) {
 
 static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) {
     struct userdata *u = s->userdata;
+    pa_alsa_ucm_port_data *data;
 
     pa_assert(u);
     pa_assert(p);
+    pa_assert(u->mixer_handle);
     pa_assert(u->ucm_context);
 
+    data = PA_DEVICE_PORT_DATA(p);
+    pa_assert_se(u->mixer_path = data->path);
+    mixer_volume_init(u);
+
+    if (s->flags & PA_SOURCE_DEFERRED_VOLUME)
+        pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_SYNC_MIXER, p, 0, NULL);
+    else
+        sync_mixer(u, p);
+
     return pa_alsa_ucm_set_port(u->ucm_context, p, false);
 }
 
@@ -1532,6 +1543,7 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) {
     pa_assert(u);
     pa_assert(p);
     pa_assert(u->mixer_handle);
+    pa_assert(!u->ucm_context);
 
     data = PA_DEVICE_PORT_DATA(p);
     pa_assert_se(u->mixer_path = data->path);
@@ -1822,16 +1834,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
         return 0;
 
     if (u->source->active_port) {
-        pa_alsa_port_data *data;
+        if (!u->ucm_context) {
+            pa_alsa_port_data *data;
 
-        /* We have a list of supported paths, so let's activate the
-         * one that has been chosen as active */
+            /* We have a list of supported paths, so let's activate the
+             * one that has been chosen as active */
 
-        data = PA_DEVICE_PORT_DATA(u->source->active_port);
-        u->mixer_path = data->path;
+            data = PA_DEVICE_PORT_DATA(u->source->active_port);
+            u->mixer_path = data->path;
 
-        pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+            pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+        } else {
+            pa_alsa_ucm_port_data *data;
+
+            /* First activate the port on the UCM side */
+            if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
+                return -1;
 
+            data = PA_DEVICE_PORT_DATA(u->source->active_port);
+
+            /* Now activate volume controls, if any */
+            if (data->path) {
+                u->mixer_path = data->path;
+                pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->source->muted);
+            }
+        }
     } else {
 
         if (!u->mixer_path && u->mixer_path_set)
@@ -2152,8 +2179,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
     /* ALSA might tweak the sample spec, so recalculate the frame size */
     frame_size = pa_frame_size(&ss);
 
-    if (!u->ucm_context)
-        find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+    find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
 
     pa_source_new_data_init(&data);
     data.driver = driver;
@@ -2210,7 +2236,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
     }
 
     if (u->ucm_context)
-        pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card);
+        pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card, u->pcm_handle, ignore_dB);
     else if (u->mixer_path_set)
         pa_alsa_add_ports(&data, u->mixer_path_set, card);
 
@@ -2276,10 +2302,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
     if (update_sw_params(u) < 0)
         goto fail;
 
-    if (u->ucm_context) {
-        if (u->source->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
-            goto fail;
-    } else if (setup_mixer(u, ignore_dB) < 0)
+    if (setup_mixer(u, ignore_dB) < 0)
         goto fail;
 
     pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2368,7 +2391,8 @@ static void userdata_free(struct userdata *u) {
     if (u->mixer_fdl)
         pa_alsa_fdlist_free(u->mixer_fdl);
 
-    if (u->mixer_path && !u->mixer_path_set)
+    /* Only free the mixer_path if the sink owns it */
+    if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
         pa_alsa_path_free(u->mixer_path);
 
     if (u->mixer_handle)


=====================================
src/modules/alsa/alsa-ucm.c
=====================================
@@ -81,30 +81,28 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
 
 static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
 
-struct ucm_port {
-    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 */
-};
 
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
-                          pa_alsa_ucm_device **devices, unsigned n_devices);
-static void ucm_port_free(pa_device_port *port);
-static void ucm_port_update_available(struct ucm_port *port);
+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);
+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_items item[] = {
     {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
     {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE},
     {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME},
     {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH},
+    {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM},
+    {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM},
+    {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE},
     {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY},
     {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE},
     {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS},
     {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME},
     {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH},
+    {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM},
+    {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM},
+    {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE},
     {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY},
     {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE},
     {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
@@ -202,6 +200,114 @@ static void ucm_add_devices_to_idxset(
     }
 }
 
+/* Split a string into words. Like pa_split_spaces() but handle '' and "". */
+static char *ucm_split_devnames(const char *c, const char **state) {
+    const char *current = *state ? *state : c;
+    char h;
+    size_t l;
+
+    if (!*current || *c == 0)
+        return NULL;
+
+    current += strspn(current, "\n\r \t");
+    h = *current;
+    if (h == '\'' || h =='"') {
+        c = ++current;
+        for (l = 0; *c && *c != h; l++) c++;
+        if (*c != h)
+            return NULL;
+        *state = c + 1;
+    } else {
+        l = strcspn(current, "\n\r \t");
+        *state = current+l;
+    }
+
+    return pa_xstrndup(current, l);
+}
+
+
+static void ucm_volume_free(pa_alsa_ucm_volume *vol) {
+    pa_assert(vol);
+    pa_xfree(vol->mixer_elem);
+    pa_xfree(vol->master_elem);
+    pa_xfree(vol->master_type);
+    pa_xfree(vol);
+}
+
+/* Get the volume identifier */
+static char *ucm_get_mixer_id(
+        pa_alsa_ucm_device *device,
+        const char *mprop,
+        const char *cprop,
+        const char *cid)
+{
+#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
+    snd_ctl_elem_id_t *ctl;
+    int err;
+#endif
+    const char *value;
+    char *value2;
+    int index;
+
+    /* mixer element as first, if it's found, return it without modifications */
+    value = pa_proplist_gets(device->proplist, mprop);
+    if (value)
+        return pa_xstrdup(value);
+    /* fallback, get the control element identifier */
+    /* and try to do some heuristic to determine the mixer element name */
+    value = pa_proplist_gets(device->proplist, cprop);
+    if (value == NULL)
+        return NULL;
+#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */
+    /* The new parser may return also element index. */
+    snd_ctl_elem_id_alloca(&ctl);
+    err = snd_use_case_parse_ctl_elem_id(ctl, cid, value);
+    if (err < 0)
+        return NULL;
+    value = snd_ctl_elem_id_get_name(ctl);
+    index = snd_ctl_elem_id_get_index(ctl);
+#else
+#warning "Upgrade to alsa-lib 1.2.1!"
+    index = 0;
+#endif
+    if (!(value2 = pa_str_strip_suffix(value, " Playback Volume")))
+        if (!(value2 = pa_str_strip_suffix(value, " Capture Volume")))
+            if (!(value2 = pa_str_strip_suffix(value, " Volume")))
+                value2 = pa_xstrdup(value);
+    if (index > 0) {
+        char *mix = pa_sprintf_malloc("'%s',%d", value2, index);
+        pa_xfree(value2);
+        return mix;
+    }
+    return value2;
+}
+
+/* Get the volume identifier */
+static pa_alsa_ucm_volume *ucm_get_mixer_volume(
+        pa_alsa_ucm_device *device,
+        const char *mprop,
+        const char *cprop,
+        const char *cid,
+        const char *masterid,
+        const char *mastertype)
+{
+    pa_alsa_ucm_volume *vol;
+    char *mixer_elem;
+
+    mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid);
+    if (mixer_elem == NULL)
+        return NULL;
+    vol = pa_xnew0(pa_alsa_ucm_volume, 1);
+    if (vol == NULL) {
+        pa_xfree(mixer_elem);
+        return NULL;
+    }
+    vol->mixer_elem = mixer_elem;
+    vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid));
+    vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype));
+    return vol;
+}
+
 /* Create a property list for this ucm device */
 static int ucm_get_device_property(
         pa_alsa_ucm_device *device,
@@ -216,6 +322,7 @@ static int ucm_get_device_property(
     int err;
     uint32_t ui;
     int n_confdev, n_suppdev;
+    pa_alsa_ucm_volume *vol;
 
     for (i = 0; item[i].id; i++) {
         id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name);
@@ -297,6 +404,15 @@ static int ucm_get_device_property(
             else
                 pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
         }
+
+        vol = ucm_get_mixer_volume(device,
+                                   PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM,
+                                   PA_ALSA_PROP_UCM_PLAYBACK_VOLUME,
+                                   "PlaybackVolume",
+                                   PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM,
+                                   PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE);
+        if (vol)
+            pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
     }
 
     if (device->capture_channels) { /* source device */
@@ -318,6 +434,15 @@ static int ucm_get_device_property(
             else
                 pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
         }
+
+        vol = ucm_get_mixer_volume(device,
+                                   PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM,
+                                   PA_ALSA_PROP_UCM_CAPTURE_VOLUME,
+                                   "CaptureVolume",
+                                   PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM,
+                                   PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE);
+        if (vol)
+          pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol);
     }
 
     if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
@@ -421,6 +546,11 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
         d->hw_mute_jacks = pa_dynarray_new(NULL);
         d->available = PA_AVAILABLE_UNKNOWN;
 
+        d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+                                                  (pa_free_cb_t) ucm_volume_free);
+        d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+                                                 (pa_free_cb_t) ucm_volume_free);
+
         PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
     }
 
@@ -576,17 +706,25 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
     const char **verb_list;
     int num_verbs, i, err = 0;
 
-    /* is UCM available for this card ? */
-    err = snd_card_get_name(card_index, &card_name);
-    if (err < 0) {
-        pa_log("Card can't get card_name from card_index %d", card_index);
-        goto name_fail;
-    }
-
+    /* support multiple card instances, address card directly by index */
+    card_name = pa_sprintf_malloc("hw:%i", card_index);
+    if (card_name == NULL)
+        return -ENOMEM;
     err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
     if (err < 0) {
-        pa_log_info("UCM not available for card %s", card_name);
-        goto ucm_mgr_fail;
+        /* fallback longname: is UCM available for this card ? */
+        pa_xfree(card_name);
+        err = snd_card_get_name(card_index, &card_name);
+        if (err < 0) {
+            pa_log("Card can't get card_name from card_index %d", card_index);
+            goto name_fail;
+        }
+
+        err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
+        if (err < 0) {
+            pa_log_info("UCM not available for card %s", card_name);
+            goto ucm_mgr_fail;
+        }
     }
 
     pa_log_info("UCM available for card %s", card_name);
@@ -626,7 +764,7 @@ ucm_verb_fail:
     }
 
 ucm_mgr_fail:
-    free(card_name);
+    pa_xfree(card_name);
 
 name_fail:
     return err;
@@ -693,6 +831,46 @@ static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
     return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
 }
 
+static void probe_volumes(pa_hashmap *hash, snd_pcm_t *pcm_handle, bool ignore_dB) {
+    pa_device_port *port;
+    pa_alsa_path *path;
+    pa_alsa_ucm_port_data *data;
+    snd_mixer_t *mixer_handle;
+    const char *profile;
+    void *state, *state2;
+
+    if (!(mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL))) {
+        pa_log_error("Failed to find a working mixer device.");
+        goto fail;
+    }
+
+    PA_HASHMAP_FOREACH(port, hash, state) {
+        data = PA_DEVICE_PORT_DATA(port);
+
+        PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) {
+            if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) {
+                pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name);
+                pa_hashmap_remove(data->paths, profile);
+            } else if (!path->has_volume) {
+                pa_log_warn("Path %s is not a volume control", data->path->name);
+                pa_hashmap_remove(data->paths, profile);
+            } else
+                pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name);
+        }
+    }
+
+    snd_mixer_close(mixer_handle);
+
+    return;
+
+fail:
+    /* We could not probe the paths we created. Free them and revert to software volumes. */
+    PA_HASHMAP_FOREACH(port, hash, state) {
+        data = PA_DEVICE_PORT_DATA(port);
+        pa_hashmap_remove_all(data->paths);
+    }
+}
+
 static void ucm_add_port_combination(
         pa_hashmap *hash,
         pa_alsa_ucm_mapping_context *context,
@@ -710,7 +888,11 @@ static void ucm_add_port_combination(
     char *name, *desc;
     const char *dev_name;
     const char *direction;
+    const char *profile;
     pa_alsa_ucm_device *sorted[num], *dev;
+    pa_alsa_ucm_port_data *data;
+    pa_alsa_ucm_volume *vol;
+    void *state;
 
     for (i = 0; i < num; i++)
         sorted[i] = pdevices[i];
@@ -758,8 +940,6 @@ static void ucm_add_port_combination(
 
     port = pa_hashmap_get(ports, name);
     if (!port) {
-        struct ucm_port *ucm_port;
-
         pa_device_port_new_data port_data;
 
         pa_device_port_new_data_init(&port_data);
@@ -767,15 +947,45 @@ static void ucm_add_port_combination(
         pa_device_port_new_data_set_description(&port_data, desc);
         pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
 
-        port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port));
-        port->impl_free = ucm_port_free;
+        port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
         pa_device_port_new_data_done(&port_data);
 
-        ucm_port = PA_DEVICE_PORT_DATA(port);
-        ucm_port_init(ucm_port, context->ucm, port, pdevices, num);
+        data = PA_DEVICE_PORT_DATA(port);
+        ucm_port_data_init(data, context->ucm, port, pdevices, num);
+        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. */
+            data = PA_DEVICE_PORT_DATA(port);
+
+            PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) {
+                pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem,
+                                                             is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT);
+
+                if (!path)
+                    pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem);
+                else {
+                    if (vol->master_elem) {
+                        pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false);
+                        e->switch_use = PA_ALSA_SWITCH_MUTE;
+                        e->volume_use = PA_ALSA_VOLUME_MERGE;
+                    }
+
+                    pa_hashmap_put(data->paths, pa_xstrdup(profile), path);
+
+                    /* Add path also to already created empty path set */
+                    dev = sorted[0];
+                    if (is_sink)
+                        pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
+                    else
+                        pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path);
+                }
+            }
+        }
     }
 
     port->priority = priority;
@@ -957,7 +1167,9 @@ void pa_alsa_ucm_add_ports(
         pa_proplist *proplist,
         pa_alsa_ucm_mapping_context *context,
         bool is_sink,
-        pa_card *card) {
+        pa_card *card,
+        snd_pcm_t *pcm_handle,
+        bool ignore_dB) {
 
     uint32_t idx;
     char *merged_roles;
@@ -972,6 +1184,9 @@ void pa_alsa_ucm_add_ports(
     /* add ports first */
     pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
 
+    /* now set up volume paths if any */
+    probe_volumes(*p, pcm_handle, ignore_dB);
+
     /* 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) {
@@ -996,10 +1211,13 @@ 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, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
     int ret = 0;
     const char *profile;
     pa_alsa_ucm_verb *verb;
+    pa_device_port *port;
+    pa_alsa_ucm_port_data *data;
+    void *state;
 
     if (new_profile == old_profile)
         return ret;
@@ -1028,6 +1246,12 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, co
         }
     }
 
+    /* select volume controls on ports */
+    PA_HASHMAP_FOREACH(port, card->ports, state) {
+        data = PA_DEVICE_PORT_DATA(port);
+        data->path = pa_hashmap_get(data->paths, new_profile);
+    }
+
     return ret;
 }
 
@@ -1079,16 +1303,27 @@ int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *p
 
 static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) {
 
+    pa_alsa_path_set *ps;
+
+    /* create empty path set for the future path additions */
+    ps = pa_xnew0(pa_alsa_path_set, 1);
+    ps->direction = m->direction;
+    ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+
     switch (m->direction) {
         case PA_ALSA_DIRECTION_ANY:
             pa_idxset_put(p->output_mappings, m, NULL);
             pa_idxset_put(p->input_mappings, m, NULL);
+            m->output_path_set = ps;
+            m->input_path_set = ps;
             break;
         case PA_ALSA_DIRECTION_OUTPUT:
             pa_idxset_put(p->output_mappings, m, NULL);
+            m->output_path_set = ps;
             break;
         case PA_ALSA_DIRECTION_INPUT:
             pa_idxset_put(p->input_mappings, m, NULL);
+            m->input_path_set = ps;
             break;
     }
 }
@@ -1303,6 +1538,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
 
     jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL);
     if (jack_control) {
+#if SND_LIB_VERSION >= 0x10201
+        snd_ctl_elem_id_t *ctl;
+        int err, index;
+        snd_ctl_elem_id_alloca(&ctl);
+        err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control);
+        if (err < 0)
+            return NULL;
+        jack_control = snd_ctl_elem_id_get_name(ctl);
+        index = snd_ctl_elem_id_get_index(ctl);
+        if (index > 0) {
+            pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index);
+            return NULL;
+        }
+#else
+#warning "Upgrade to alsa-lib 1.2.1!"
+#endif
         if (!pa_endswith(jack_control, " Jack")) {
             pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control);
             return NULL;
@@ -1402,7 +1653,7 @@ static int ucm_create_profile(
             char *hw_mute_device_name;
             const char *state = NULL;
 
-            while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) {
+            while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) {
                 pa_alsa_ucm_verb *verb2;
                 bool device_found = false;
 
@@ -1636,11 +1887,18 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
         if (di->ucm_ports)
             pa_dynarray_free(di->ucm_ports);
 
+        if (di->playback_volumes)
+            pa_hashmap_free(di->playback_volumes);
+        if (di->capture_volumes)
+            pa_hashmap_free(di->capture_volumes);
+
         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_xfree(di);
     }
 
@@ -1771,7 +2029,7 @@ 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, struct ucm_port *port) {
+static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) {
     pa_assert(device);
     pa_assert(port);
 
@@ -1799,7 +2057,7 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
 }
 
 static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
-    struct ucm_port *port;
+    pa_alsa_ucm_port_data *port;
     unsigned idx;
 
     pa_assert(device);
@@ -1833,8 +2091,8 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
     device_set_available(device, available);
 }
 
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
-                          pa_alsa_ucm_device **devices, unsigned n_devices) {
+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_assert(ucm);
@@ -1850,11 +2108,14 @@ static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_dev
         device_add_ucm_port(devices[i], 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);
 }
 
-static void ucm_port_free(pa_device_port *port) {
-    struct ucm_port *ucm_port;
+static void ucm_port_data_free(pa_device_port *port) {
+    pa_alsa_ucm_port_data *ucm_port;
 
     pa_assert(port);
 
@@ -1862,9 +2123,12 @@ static void ucm_port_free(pa_device_port *port) {
 
     if (ucm_port->devices)
         pa_dynarray_free(ucm_port->devices);
+
+    if (ucm_port->paths)
+        pa_hashmap_free(ucm_port->paths);
 }
 
-static void ucm_port_update_available(struct ucm_port *port) {
+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;
@@ -1896,7 +2160,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, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
     return -1;
 }
 
@@ -1909,7 +2173,9 @@ void pa_alsa_ucm_add_ports(
         pa_proplist *proplist,
         pa_alsa_ucm_mapping_context *context,
         bool is_sink,
-        pa_card *card) {
+        pa_card *card,
+        snd_pcm_t *pcm_handle,
+        bool ignore_dB) {
 }
 
 void pa_alsa_ucm_add_ports_combination(


=====================================
src/modules/alsa/alsa-ucm.h
=====================================
@@ -51,6 +51,21 @@ typedef void snd_use_case_mgr_t;
 /** For devices: Playback switch e.g PlaybackSwitch */
 #define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH            "alsa.ucm.playback.switch"
 
+/** For devices: Playback mixer identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM        "alsa.ucm.playback.mixer.element"
+
+/** For devices: Playback mixer master identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM       "alsa.ucm.playback.master.element"
+
+/** For devices: Playback mixer master type */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE       "alsa.ucm.playback.master.type"
+
+/** For devices: Playback mixer master identifier */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID         "alsa.ucm.playback.master.id"
+
+/** For devices: Playback mixer master type */
+#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE       "alsa.ucm.playback.master.type"
+
 /** For devices: Playback priority */
 #define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY          "alsa.ucm.playback.priority"
 
@@ -69,6 +84,21 @@ typedef void snd_use_case_mgr_t;
 /** For devices: Capture switch e.g CaptureSwitch */
 #define PA_ALSA_PROP_UCM_CAPTURE_SWITCH             "alsa.ucm.capture.switch"
 
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM         "alsa.ucm.capture.mixer.element"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM        "alsa.ucm.capture.master.element"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE        "alsa.ucm.capture.master.type"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID          "alsa.ucm.capture.master.id"
+
+/** For devices: Capture mixer identifier */
+#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE        "alsa.ucm.capture.master.type"
+
 /** For devices: Capture priority */
 #define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY           "alsa.ucm.capture.priority"
 
@@ -95,10 +125,12 @@ 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_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, const char *new_profile, const char *old_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_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb);
 
@@ -107,7 +139,9 @@ void pa_alsa_ucm_add_ports(
         pa_proplist *proplist,
         pa_alsa_ucm_mapping_context *context,
         bool is_sink,
-        pa_card *card);
+        pa_card *card,
+        snd_pcm_t *pcm_handle,
+        bool ignore_dB);
 void pa_alsa_ucm_add_ports_combination(
         pa_hashmap *hash,
         pa_alsa_ucm_mapping_context *context,
@@ -139,6 +173,11 @@ struct pa_alsa_ucm_device {
     unsigned playback_channels;
     unsigned capture_channels;
 
+    /* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to
+     * make this a hashmap of verb -> per-verb-device-properties-struct. */
+    pa_hashmap *playback_volumes;
+    pa_hashmap *capture_volumes;
+
     pa_alsa_mapping *playback_mapping;
     pa_alsa_mapping *capture_mapping;
 
@@ -206,4 +245,24 @@ struct pa_alsa_ucm_mapping_context {
     pa_idxset *ucm_modifiers;
 };
 
+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 */
+
+    /* profile name -> pa_alsa_path for volume control */
+    pa_hashmap *paths;
+    /* Current path, set when activating profile */
+    pa_alsa_path *path;
+};
+
+struct pa_alsa_ucm_volume {
+    char *mixer_elem;	/* mixer element identifier */
+    char *master_elem;	/* master mixer element identifier */
+    char *master_type;
+};
+
 #endif


=====================================
src/modules/alsa/module-alsa-card.c
=====================================
@@ -241,7 +241,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, nd->profile ? nd->profile->name : NULL,
+        if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL,
                     od->profile ? od->profile->name : NULL) < 0) {
             ret = -1;
             goto finish;
@@ -294,7 +294,7 @@ static void init_profile(struct userdata *u) {
 
     if (d->profile && u->use_ucm) {
         /* Set initial verb */
-        if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) {
+        if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) {
             pa_log("Failed to set ucm profile %s", d->profile->name);
             return;
         }
@@ -538,6 +538,9 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) {
     if (mask == SND_CTL_EVENT_MASK_REMOVE)
         return 0;
 
+    if (u->use_ucm)
+        return 0;
+
     p = find_port_with_eld_device(u->card->ports, device);
     if (p == NULL) {
         pa_log_error("Invalid device changed in ALSA: %d", device);
@@ -900,7 +903,8 @@ int pa__init(pa_module *m) {
      * results in an infinite loop of "fill buffer, handle underrun". To work
      * around this issue, the suspend_when_unavailable flag is used to stop
      * playback when the HDMI cable is unplugged. */
-    if (pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) {
+    if (!u->use_ucm &&
+        pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) {
         pa_device_port *port;
         void *state;
 


=====================================
src/pulsecore/core-util.c
=====================================
@@ -2893,6 +2893,32 @@ bool pa_str_in_list_spaces(const char *haystack, const char *needle) {
     return false;
 }
 
+char* pa_str_strip_suffix(const char *str, const char *suffix) {
+    size_t str_l, suf_l, prefix;
+    char *ret;
+
+    pa_assert(str);
+    pa_assert(suffix);
+
+    str_l = strlen(str);
+    suf_l = strlen(suffix);
+
+    if (str_l < suf_l)
+        return NULL;
+
+    prefix = str_l - suf_l;
+
+    if (!pa_streq(&str[prefix], suffix))
+        return NULL;
+
+    ret = pa_xmalloc(prefix + 1);
+
+    strncpy(ret, str, prefix);
+    ret[prefix] = '\0';
+
+    return ret;
+}
+
 char *pa_get_user_name_malloc(void) {
     ssize_t k;
     char *u;


=====================================
src/pulsecore/core-util.h
=====================================
@@ -232,6 +232,8 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
 bool pa_str_in_list_spaces(const char *needle, const char *haystack);
 bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle);
 
+char* pa_str_strip_suffix(const char *str, const char *suffix);
+
 char *pa_get_host_name_malloc(void);
 char *pa_get_user_name_malloc(void);
 



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/compare/1ee1f749e154d2f64b4661f833eebaa18ae1a081...e6779ad229d5858f90f5f10c3796c9778f05c3fa

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/compare/1ee1f749e154d2f64b4661f833eebaa18ae1a081...e6779ad229d5858f90f5f10c3796c9778f05c3fa
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/20191206/256559a9/attachment-0001.html>


More information about the pulseaudio-commits mailing list