[pulseaudio-discuss] [PATCH v2 6/6] ucm: Add support for "JackHWMute"

Tanu Kaskinen tanu.kaskinen at linux.intel.com
Mon May 4 11:03:45 PDT 2015


JackHWMute is used to list devices that get forcibly muted by
a particular jack. We mark ports unavailable based on that
information.
---
 src/modules/alsa/alsa-mixer.c | 52 +++++++++++++++++++++++++++++
 src/modules/alsa/alsa-mixer.h |  2 ++
 src/modules/alsa/alsa-ucm.c   | 76 +++++++++++++++++++++++++++++++++++++++++++
 src/modules/alsa/alsa-ucm.h   |  4 +++
 4 files changed, 134 insertions(+)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 6cad540..8a8c18c 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -119,6 +119,7 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
     jack->state_unplugged = PA_AVAILABLE_NO;
     jack->state_plugged = PA_AVAILABLE_YES;
     jack->ucm_devices = pa_dynarray_new(NULL);
+    jack->ucm_hw_mute_devices = pa_dynarray_new(NULL);
 
     return jack;
 }
@@ -126,6 +127,9 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
 void pa_alsa_jack_free(pa_alsa_jack *jack) {
     pa_assert(jack);
 
+    if (jack->ucm_hw_mute_devices)
+        pa_dynarray_free(jack->ucm_hw_mute_devices);
+
     if (jack->ucm_devices)
         pa_dynarray_free(jack->ucm_devices);
 
@@ -145,6 +149,9 @@ void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control) {
 
     jack->has_control = has_control;
 
+    PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx)
+        pa_alsa_ucm_device_update_available(device);
+
     PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx)
         pa_alsa_ucm_device_update_available(device);
 }
@@ -160,6 +167,44 @@ void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in) {
 
     jack->plugged_in = plugged_in;
 
+    /* XXX: If this is a headphone jack that mutes speakers when plugged in,
+     * and the headphones get unplugged, then the headphone device must be set
+     * to unavailable and the speaker device must be set to unknown. So far so
+     * good. But there's an ugly detail: we must first set the availability of
+     * the speakers and then the headphones. We shouldn't need to care about
+     * the order, but we have to, because module-switch-on-port-available gets
+     * separate events for the two devices, and the intermediate state between
+     * the two events is such that the second event doesn't trigger the desired
+     * port switch, if the event order is "wrong".
+     *
+     * These are the transitions when the event order is "right":
+     *
+     *     speakers:   1) unavailable -> 2) unknown   -> 3) unknown
+     *     headphones: 1) available   -> 2) available -> 3) unavailable
+     *
+     * In the 2 -> 3 transition, headphones become unavailable, and
+     * module-switch-on-port-available sees that speakers can be used, so the
+     * port gets changed as it should.
+     *
+     * These are the transitions when the event order is "wrong":
+     *
+     *     speakers:   1) unavailable -> 2) unavailable -> 3) unknown
+     *     headphones: 1) available   -> 2) unavailable -> 3) unavailable
+     *
+     * In the 1 -> 2 transition, headphones become unavailable, and there are
+     * no available ports to use, so no port change happens. In the 2 -> 3
+     * transition, speaker availability becomes unknown, but that's not
+     * a strong enough signal for module-switch-on-port-available, so it still
+     * doesn't do the port switch.
+     *
+     * We should somehow merge the two events so that
+     * module-switch-on-port-available would handle both transitions in one go.
+     * If module-switch-on-port-available used a defer event to delay
+     * the port availability processing, that would probably do the trick. */
+
+    PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx)
+        pa_alsa_ucm_device_update_available(device);
+
     PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx)
         pa_alsa_ucm_device_update_available(device);
 }
@@ -171,6 +216,13 @@ void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device)
     pa_dynarray_append(jack->ucm_devices, device);
 }
 
+void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device) {
+    pa_assert(jack);
+    pa_assert(device);
+
+    pa_dynarray_append(jack->ucm_hw_mute_devices, device);
+}
+
 static const char *lookup_description(const char *key, const struct description_map dm[], unsigned n) {
     unsigned i;
 
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 96a7a5e..621a71b 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -170,6 +170,7 @@ struct pa_alsa_jack {
     pa_alsa_required_t required_absent;
 
     pa_dynarray *ucm_devices; /* pa_alsa_ucm_device */
+    pa_dynarray *ucm_hw_mute_devices; /* pa_alsa_ucm_device */
 };
 
 pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name);
@@ -177,6 +178,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack);
 void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
 void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
 void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);
+void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);
 
 /* A path wraps a series of elements into a single entity which can be
  * used to control it as if it had a single volume slider, a single
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index d2c7140..c231bb4 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -77,6 +77,9 @@ struct ucm_info {
 };
 
 static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
+static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
+
+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;
@@ -107,6 +110,7 @@ static struct ucm_items item[] = {
     {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
     {"TQ", PA_ALSA_PROP_UCM_QOS},
     {"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL},
+    {"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE},
     {NULL, NULL},
 };
 
@@ -414,6 +418,7 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
         pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i]));
         pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i + 1]));
         d->ucm_ports = pa_dynarray_new(NULL);
+        d->hw_mute_jacks = pa_dynarray_new(NULL);
         d->available = PA_AVAILABLE_UNKNOWN;
 
         PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
@@ -1381,6 +1386,7 @@ static int ucm_create_profile(
 
     PA_LLIST_FOREACH(dev, verb->devices) {
         pa_alsa_jack *jack;
+        const char *jack_hw_mute;
 
         name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
 
@@ -1391,6 +1397,37 @@ static int ucm_create_profile(
 
         jack = ucm_get_jack(ucm, dev);
         device_set_jack(dev, jack);
+
+        /* JackHWMute contains a list of device names. Each listed device must
+         * be associated with the jack object that we just created. */
+        jack_hw_mute = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_HW_MUTE);
+        if (jack_hw_mute) {
+            char *hw_mute_device_name;
+            const char *state = NULL;
+
+            while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) {
+                pa_alsa_ucm_verb *verb2;
+                bool device_found = false;
+
+                /* Search the referenced device from all verbs. If there are
+                 * multiple verbs that have a device with this name, we add the
+                 * hw mute association to each of those devices. */
+                PA_LLIST_FOREACH(verb2, ucm->verbs) {
+                    pa_alsa_ucm_device *hw_mute_device;
+
+                    hw_mute_device = verb_find_device(verb2, hw_mute_device_name);
+                    if (hw_mute_device) {
+                        device_found = true;
+                        device_add_hw_mute_jack(hw_mute_device, jack);
+                    }
+                }
+
+                if (!device_found)
+                    pa_log("[%s] JackHWMute references an unknown device: %s", name, hw_mute_device_name);
+
+                pa_xfree(hw_mute_device_name);
+            }
+        }
     }
 
     /* Now find modifiers that have their own PlaybackPCM and create
@@ -1596,6 +1633,9 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
     PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
         PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
 
+        if (di->hw_mute_jacks)
+            pa_dynarray_free(di->hw_mute_jacks);
+
         if (di->ucm_ports)
             pa_dynarray_free(di->ucm_ports);
 
@@ -1621,6 +1661,23 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
     pa_xfree(verb);
 }
 
+static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name) {
+    pa_alsa_ucm_device *device;
+
+    pa_assert(verb);
+    pa_assert(device_name);
+
+    PA_LLIST_FOREACH(device, verb->devices) {
+        const char *name;
+
+        name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
+        if (pa_streq(name, device_name))
+            return device;
+    }
+
+    return NULL;
+}
+
 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
     pa_alsa_ucm_verb *vi, *vn;
     pa_alsa_jack *ji, *jn;
@@ -1737,6 +1794,16 @@ static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
     pa_alsa_ucm_device_update_available(device);
 }
 
+static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
+    pa_assert(device);
+    pa_assert(jack);
+
+    pa_dynarray_append(device->hw_mute_jacks, jack);
+    pa_alsa_jack_add_ucm_hw_mute_device(jack, device);
+
+    pa_alsa_ucm_device_update_available(device);
+}
+
 static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
     struct ucm_port *port;
     unsigned idx;
@@ -1754,12 +1821,21 @@ static void device_set_available(pa_alsa_ucm_device *device, pa_available_t avai
 
 void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
     pa_available_t available = PA_AVAILABLE_UNKNOWN;
+    pa_alsa_jack *jack;
+    unsigned idx;
 
     pa_assert(device);
 
     if (device->jack && device->jack->has_control)
         available = device->jack->plugged_in ? PA_AVAILABLE_YES : PA_AVAILABLE_NO;
 
+    PA_DYNARRAY_FOREACH(jack, device->hw_mute_jacks, idx) {
+        if (jack->plugged_in) {
+            available = PA_AVAILABLE_NO;
+            break;
+        }
+    }
+
     device_set_available(device, available);
 }
 
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 5ccc466..0930303 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -87,6 +87,9 @@ typedef void snd_use_case_mgr_t;
 /* Corresponds to the "JackControl" UCM value. */
 #define PA_ALSA_PROP_UCM_JACK_CONTROL               "alsa.ucm.jack_control"
 
+/* Corresponds to the "JackHWMute" UCM value. */
+#define PA_ALSA_PROP_UCM_JACK_HW_MUTE               "alsa.ucm.jack_hw_mute"
+
 typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb;
 typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
 typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
@@ -148,6 +151,7 @@ struct pa_alsa_ucm_device {
     pa_dynarray *ucm_ports; /* struct ucm_port */
 
     pa_alsa_jack *jack;
+    pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */
     pa_available_t available;
 };
 
-- 
1.9.3



More information about the pulseaudio-discuss mailing list