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

Tanu Kaskinen tanu.kaskinen at linux.intel.com
Thu Feb 12 05:11:53 PST 2015


"JackHWMute" is a UCM device value that, if set, indicates that the
jack of the device mutes some other device at the hardware or driver
level. A common example would be a headphone jack that mutes built-in
speakers. PulseAudio should show such auto-muted devices as
unavailable.

Previously there was only a simple relationship: each UCM device was
related to one jack and vice versa. Now each device is still related
to exactly one jack, but each jack can be related to two devices: one
that the jack enables, and one that the jack disables (mutes).

The patch adds pa_alsa_jack_set_plugged_in() to encapsulate the logic
of marking the appropriate ports available/unavailable.
pa_alsa_jack_set_plugged_in() propagates the status to UCM devices,
and the devices propagate the status further to ports. Previously the
logic of mapping jack status to port availability was done by
module-alsa-card.c, which I think is not the right place, so I didn't
want to add the UCM "mute jack" logic there.
---
 src/modules/alsa/alsa-mixer.c       |  15 ++
 src/modules/alsa/alsa-mixer.h       |  14 ++
 src/modules/alsa/alsa-ucm.c         | 286 +++++++++++++++++++++++++++++++++++-
 src/modules/alsa/alsa-ucm.h         |  37 +++++
 src/modules/alsa/module-alsa-card.c |  18 ++-
 src/pulsecore/device-port.c         |  14 +-
 6 files changed, 362 insertions(+), 22 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 2fe2ae4..423832f 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2768,6 +2768,21 @@ int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, bool ignore_dB) {
     return 0;
 }
 
+void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in) {
+    pa_assert(jack);
+
+    if (plugged_in == jack->plugged_in)
+        return;
+
+    jack->plugged_in = plugged_in;
+
+    if (jack->device)
+        pa_alsa_ucm_device_set_available(jack->device, plugged_in);
+
+    if (jack->hw_mute)
+        pa_alsa_ucm_device_set_available(jack->hw_mute, !plugged_in);
+}
+
 void pa_alsa_setting_dump(pa_alsa_setting *s) {
     pa_assert(s);
 
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index ec39fab..dad1ea2 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -168,8 +168,22 @@ struct pa_alsa_jack {
     pa_alsa_required_t required;
     pa_alsa_required_t required_any;
     pa_alsa_required_t required_absent;
+
+    /* UCM specific: this is the "owner" device of this jack. When the jack is
+     * plugged in, the device referenced here gets marked as available, and
+     * when the jack is unplugged, the device gets marked as unavailable. */
+    pa_alsa_ucm_device *device;
+
+    /* UCM specific: if this is non-NULL, then the referenced device gets muted
+     * by the hardware (or driver) when this jack is plugged in. The muting can't
+     * be overridden by PulseAudio, so the device gets marked as
+     * unavailable (so if a device is referenced by this field, the effect is
+     * inverse to being referenced by the "device" field). */
+    pa_alsa_ucm_device *hw_mute;
 };
 
+void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
+
 /* 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
  * mute switch and a single list of selectable options. */
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index ef6adcd..061b8f7 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -66,6 +66,17 @@
 
 #ifdef HAVE_ALSA_UCM
 
+static void device_set_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
+
+struct port {
+    pa_alsa_ucm_config *ucm;
+    pa_device_port *core_port;
+
+    /* Usually a port is associated with only one device, but ports can also be
+     * a combination of several devices. */
+    pa_dynarray *devices; /* pa_alsa_ucm_device */
+};
+
 struct ucm_items {
     const char *id;
     const char *property;
@@ -91,6 +102,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},
 };
 
@@ -394,9 +406,12 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
     for (i = 0; i < num_dev; i += 2) {
         pa_alsa_ucm_device *d = pa_xnew0(pa_alsa_ucm_device, 1);
 
+        d->verb = verb;
         d->proplist = pa_proplist_new();
         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->ports = pa_dynarray_new(NULL);
+        d->available = true;
 
         PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
     }
@@ -553,6 +568,8 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
     const char **verb_list;
     int num_verbs, i, err = 0;
 
+    ucm->ports = pa_dynarray_new(NULL);
+
     /* is UCM available for this card ? */
     err = snd_card_get_name(card_index, &card_name);
     if (err < 0) {
@@ -670,6 +687,67 @@ 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 device_add_port(pa_alsa_ucm_device *device, struct port *port) {
+    pa_assert(device);
+    pa_assert(port);
+
+    pa_dynarray_append(device->ports, port);
+}
+
+static void device_remove_port(pa_alsa_ucm_device *device, struct port *port) {
+    pa_assert(device);
+    pa_assert(port);
+
+    pa_dynarray_remove_by_data(device->ports, port);
+}
+
+static struct port *port_new(pa_alsa_ucm_config *ucm, pa_device_port *core_port, pa_alsa_ucm_device **devices,
+                             unsigned n_devices) {
+    struct port *port;
+    unsigned i;
+    pa_available_t available = PA_AVAILABLE_YES;
+
+    pa_assert(ucm);
+    pa_assert(core_port);
+    pa_assert(devices);
+
+    port = pa_xnew0(struct port, 1);
+    port->ucm = ucm;
+    port->core_port = core_port;
+    port->devices = pa_dynarray_new(NULL);
+
+    for (i = 0; i < n_devices; i++) {
+        device_add_port(devices[i], port);
+        pa_dynarray_append(port->devices, devices[i]);
+
+        if (!devices[i]->available)
+            available = PA_AVAILABLE_NO;
+    }
+
+    pa_device_port_set_available(core_port, available);
+
+    pa_dynarray_append(ucm->ports, port);
+
+    return port;
+}
+
+static void port_free(struct port *port) {
+    pa_assert(port);
+
+    pa_dynarray_remove_by_data(port->ucm->ports, port);
+
+    if (port->devices) {
+        pa_alsa_ucm_device *device;
+
+        while ((device = pa_dynarray_steal_last(port->devices)))
+            device_remove_port(device, port);
+
+        pa_dynarray_free(port->devices);
+    }
+
+    pa_xfree(port);
+}
+
 static void ucm_add_port_combination(
         pa_hashmap *hash,
         pa_alsa_ucm_mapping_context *context,
@@ -688,6 +766,9 @@ static void ucm_add_port_combination(
     const char *dev_name;
     const char *direction;
     pa_alsa_ucm_device *sorted[num], *dev;
+    pa_alsa_ucm_config *ucm;
+
+    ucm = context->ucm;
 
     for (i = 0; i < num; i++)
         sorted[i] = pdevices[i];
@@ -735,16 +816,21 @@ static void ucm_add_port_combination(
 
     port = pa_hashmap_get(ports, name);
     if (!port) {
+        struct port *ucm_port;
+
         pa_device_port_new_data port_data;
 
         pa_device_port_new_data_init(&port_data);
         pa_device_port_new_data_set_name(&port_data, name);
         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);
+        pa_device_port_new_data_set_available(&port_data, PA_AVAILABLE_YES);
 
-        port = pa_device_port_new(core, &port_data, 0);
+        port = pa_device_port_new(core, &port_data, sizeof(struct port *));
         pa_device_port_new_data_done(&port_data);
-        pa_assert(port);
+
+        ucm_port = port_new(ucm, port, pdevices, num);
+        *((struct port **) PA_DEVICE_PORT_DATA(port)) = ucm_port;
 
         pa_hashmap_put(ports, port->name, port);
         pa_log_debug("Add port %s: %s", port->name, port->description);
@@ -1263,16 +1349,67 @@ static int ucm_create_mapping(
     return ret;
 }
 
-static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device, const char *pre_tag) {
+static void device_set_output_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
+    pa_assert(device);
+    pa_assert(!jack || !device->mute_jack);
+
+    if (jack == device->output_jack)
+        return;
+
+    device->output_jack = jack;
+
+    if (jack)
+        pa_alsa_ucm_device_set_available(device, jack->plugged_in);
+    else
+        pa_alsa_ucm_device_set_available(device, true);
+}
+
+static void device_set_input_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
+    pa_assert(device);
+    pa_assert(!jack || !device->mute_jack);
+
+    if (jack == device->input_jack)
+        return;
+
+    device->input_jack = jack;
+
+    if (jack)
+        pa_alsa_ucm_device_set_available(device, jack->plugged_in);
+    else
+        pa_alsa_ucm_device_set_available(device, true);
+}
+
+static void device_set_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
+    pa_assert(device);
+    pa_assert(!jack || !device->output_jack);
+    pa_assert(!jack || !device->input_jack);
+
+    if (jack == device->mute_jack)
+        return;
+
+    device->mute_jack = jack;
+
+    if (jack)
+        pa_alsa_ucm_device_set_available(device, !jack->plugged_in);
+    else
+        pa_alsa_ucm_device_set_available(device, true);
+}
+
+static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device, const char *pre_tag, bool implicit) {
+    const char *jack_control;
     pa_alsa_jack *j;
     const char *device_name;
     char *name;
-    const char *jack_control;
+    const char *jack_hw_mute;
 
     pa_assert(ucm);
     pa_assert(device);
     pa_assert(pre_tag);
 
+    jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL);
+    if (!jack_control && !implicit)
+        return NULL;
+
     device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
     name = pa_sprintf_malloc("%s%s", pre_tag, device_name);
 
@@ -1281,6 +1418,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d
             goto out;
 
     j = pa_xnew0(pa_alsa_jack, 1);
+    j->device = device;
     j->state_unplugged = PA_AVAILABLE_NO;
     j->state_plugged = PA_AVAILABLE_YES;
     j->name = pa_xstrdup(name);
@@ -1288,9 +1426,38 @@ 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)
         j->alsa_name = pa_xstrdup(jack_control);
-    else
+    else if (implicit)
         j->alsa_name = pa_sprintf_malloc("%s Jack", device_name);
 
+    jack_hw_mute = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_HW_MUTE);
+    if (jack_hw_mute) {
+        pa_alsa_ucm_device *device2;
+        bool device_found = false;
+
+        PA_LLIST_FOREACH(device2, device->verb->devices) {
+            const char *device2_name;
+
+            device2_name = pa_proplist_gets(device2->proplist, PA_ALSA_PROP_UCM_NAME);
+
+            if (pa_streq(device2_name, jack_hw_mute)) {
+                device_found = true;
+
+                if (!device2->output_jack && !device2->input_jack) {
+                    j->hw_mute = device2;
+                    device_set_mute_jack(device2, j);
+                } else {
+                    pa_log("[%s] Ignoring \"JackHWMute\", because device availability is already governed by jack \"%s\".",
+                           device2_name, device2->output_jack ? device2->output_jack->name : device2->input_jack->name);
+                }
+
+                break;
+            }
+        }
+
+        if (!device_found)
+            pa_log("[%s] JackHWMute references unknown device: %s", device_name, jack_hw_mute);
+    }
+
     PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
 
 out:
@@ -1349,7 +1516,12 @@ static int ucm_create_profile(
     if (verb_info[i].id == NULL)
         p->priority = 1000;
 
+    /* Get jacks for devices (and also associate the device with mappings). No
+     * implicit jacks are allowed (that is, if "JackControl" is not set, a jack
+     * will not be created). */
     PA_LLIST_FOREACH(dev, verb->devices) {
+        pa_alsa_jack *jack;
+
         name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
 
         sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
@@ -1358,9 +1530,57 @@ static int ucm_create_profile(
         ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
 
         if (sink)
-            dev->output_jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_OUTPUT);
+            jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_OUTPUT, false);
+
+        if (source)
+            jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_INPUT, false);
+
+        if (jack) {
+            if (dev->mute_jack) {
+                pa_log("[%s] Overriding \"JackHWMute\", because \"JackControl\" is set and takes precedence.", name);
+                device_set_mute_jack(dev, NULL);
+            }
+        }
+
+        if (sink)
+            device_set_output_jack(dev, jack);
+
         if (source)
-            dev->input_jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_INPUT);
+            device_set_input_jack(dev, jack);
+    }
+
+    /* Now go over the device list again and get implicit jacks for devices
+     * that don't have "JackControl" set nor are referenced by any other
+     * device's "JackHWMute". "Implicit" means that the kcontrol name is not
+     * explicitly configured; instead, we generate the kcontrol name from the
+     * device name.
+     *
+     * We need a separate loop instead of creating the implicit jacks in the
+     * same loop as the other jacks, because the full set of mute jack
+     * relationships isn't known before we have iterated over all devices,
+     * and the relationships need to be known before creating the implicit
+     * jacks, since otherwise the implicit jacks could override the mute jacks,
+     * which we don't want. */
+    PA_LLIST_FOREACH(dev, verb->devices) {
+        pa_alsa_jack *jack;
+
+        if (dev->output_jack || dev->input_jack || dev->mute_jack)
+            continue;
+
+        name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
+
+        sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK);
+        source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
+
+        if (sink) {
+            jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_OUTPUT, true);
+            device_set_output_jack(dev, jack);
+        }
+
+        if (source) {
+            jack = ucm_get_jack(ucm, dev, PA_UCM_PRE_TAG_INPUT, true);
+            device_set_input_jack(dev, jack);
+        }
     }
 
     /* Now find modifiers that have their own PlaybackPCM and create
@@ -1451,7 +1671,10 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
     PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
         pa_alsa_jack *jack;
         jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
-        pa_assert (jack);
+
+        if (!jack)
+            continue;
+
         jack->has_control = pa_alsa_mixer_find(mixer_handle, jack->alsa_name, 0) != NULL;
         pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
     }
@@ -1565,6 +1788,12 @@ 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->ports) {
+            pa_assert(pa_dynarray_size(di->ports) == 0);
+            pa_dynarray_free(di->ports);
+        }
+
         pa_proplist_free(di->proplist);
         if (di->conflicting_devices)
             pa_idxset_free(di->conflicting_devices, NULL);
@@ -1591,6 +1820,15 @@ void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
     pa_alsa_ucm_verb *vi, *vn;
     pa_alsa_jack *ji, *jn;
 
+    if (ucm->ports) {
+        struct port *port;
+
+        while ((port = pa_dynarray_last(ucm->ports)))
+            port_free(port);
+
+        pa_dynarray_free(ucm->ports);
+    }
+
     PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
         PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
         free_verb(vi);
@@ -1685,6 +1923,35 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
     }
 }
 
+void pa_alsa_ucm_device_set_available(pa_alsa_ucm_device *device, bool available) {
+    struct port *port;
+    unsigned idx;
+
+    pa_assert(device);
+
+    if (available == device->available)
+        return;
+
+    device->available = available;
+
+    PA_DYNARRAY_FOREACH(port, device->ports, idx) {
+        pa_alsa_ucm_device *device2;
+        unsigned idx2;
+        pa_available_t port_available = PA_AVAILABLE_YES;
+
+        /* If there are any devices associated with the port that are
+         * unavailable, then the port is considered unavailable. */
+        PA_DYNARRAY_FOREACH(device2, port->devices, idx2) {
+            if (!device2->available) {
+                port_available = PA_AVAILABLE_NO;
+                break;
+            }
+        }
+
+        pa_device_port_set_available(port->core_port, port_available);
+    }
+}
+
 #else /* HAVE_ALSA_UCM */
 
 /* Dummy functions for systems without UCM support */
@@ -1739,4 +2006,7 @@ void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, p
 void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
 }
 
+void pa_alsa_ucm_device_set_available(pa_alsa_ucm_device *device, bool available) {
+}
+
 #endif
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index a8c8090..1d76889 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -30,6 +30,8 @@ typedef void snd_use_case_mgr_t;
 
 #include "alsa-mixer.h"
 
+#include <pulsecore/dynarray.h>
+
 /** For devices: List of verbs, devices or modifiers available */
 #define PA_ALSA_PROP_UCM_NAME                       "alsa.ucm.name"
 
@@ -87,6 +89,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;
@@ -122,7 +127,15 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
 
 /* UCM - Use Case Manager is available on some audio cards */
 
+struct pa_alsa_ucm_port {
+    pa_device_port *core_port;
+    pa_dynarray *devices; /* pa_alsa_ucm_device */
+    bool available;
+};
+
 struct pa_alsa_ucm_device {
+    pa_alsa_ucm_verb *verb;
+
     PA_LLIST_FIELDS(pa_alsa_ucm_device);
 
     pa_proplist *proplist;
@@ -142,10 +155,33 @@ struct pa_alsa_ucm_device {
     pa_idxset *conflicting_devices;
     pa_idxset *supported_devices;
 
+    pa_dynarray *ports; /* pa_alsa_ucm_port */
+
+    /* These jacks are owned by this device. When these jacks are plugged in,
+     * the device is marked as available, and when these jacks are unplugged,
+     * the device is marked as unavailable. Not all devices have jacks
+     * associated with them; those devices are always marked as available.
+     *
+     * XXX: It's probably pointless to have separate jacks for input and
+     * output. Both jack objects will anyway reference the same alsa
+     * kcontrol, so they will always have the same state. */
     pa_alsa_jack *input_jack;
     pa_alsa_jack *output_jack;
+
+    /* The "mute jack" is owned by some other device. The mute jack makes this
+     * device unavailable when it's plugged in. A device can't have both a
+     * "normal" jack (meaning the output_jack or input_jack field) and a mute
+     * jack, because there would be ambiguity about which jack is controlling
+     * the device availability. If the configuration tries to set both types of
+     * jacks for one device, the normal jack has precedence and mute_jack gets
+     * set to NULL. */
+    pa_alsa_jack *mute_jack;
+
+    bool available;
 };
 
+void pa_alsa_ucm_device_set_available(pa_alsa_ucm_device *device, bool available);
+
 struct pa_alsa_ucm_modifier {
     PA_LLIST_FIELDS(pa_alsa_ucm_modifier);
 
@@ -185,6 +221,7 @@ struct pa_alsa_ucm_config {
 
     PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
     PA_LLIST_HEAD(pa_alsa_jack, jacks);
+    pa_dynarray *ports; /* struct port */
 };
 
 struct pa_alsa_ucm_mapping_context {
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index a7fec04..7ac750f 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -384,16 +384,18 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
 
     PA_HASHMAP_FOREACH(jack, u->jacks, state)
         if (jack->melem == melem) {
-            jack->plugged_in = plugged_in;
+            pa_alsa_jack_set_plugged_in(jack, plugged_in);
+
             if (u->use_ucm) {
-                pa_assert(u->card->ports);
-                port = pa_hashmap_get(u->card->ports, jack->name);
-                pa_assert(port);
-            }
-            else {
-                pa_assert(jack->path);
-                pa_assert_se(port = jack->path->port);
+                /* When using UCM, pa_alsa_jack_set_plugged_in() maps the jack
+                 * state to port availability. */
+                continue;
             }
+
+            /* When not using UCM, we have to do the jack state -> port
+             * availability mapping ourselves. */
+            pa_assert(jack->path);
+            pa_assert_se(port = jack->path->port);
             report_port_state(port, u);
         }
     return 0;
diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index cfe2a80..906ab1f 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -66,8 +66,6 @@ void pa_device_port_new_data_done(pa_device_port_new_data *data) {
 }
 
 void pa_device_port_set_available(pa_device_port *p, pa_available_t status) {
-    pa_core *core;
-
     pa_assert(p);
 
     if (p->available == status)
@@ -80,10 +78,14 @@ void pa_device_port_set_available(pa_device_port *p, pa_available_t status) {
        status == PA_AVAILABLE_NO ? "no" : "unknown");
 
     /* Post subscriptions to the card which owns us */
-    pa_assert_se(core = p->core);
-    pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
-
-    pa_hook_fire(&core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p);
+    /* XXX: We need to check p->card, because this function may be called
+     * before the card object has been created. The card object should probably
+     * be created before port objects, and then p->card could be non-NULL for
+     * the whole lifecycle of pa_device_port. */
+    if (p->card) {
+        pa_subscription_post(p->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, p->card->index);
+        pa_hook_fire(&p->core->hooks[PA_CORE_HOOK_PORT_AVAILABLE_CHANGED], p);
+    }
 }
 
 static void device_port_free(pa_object *o) {
-- 
1.9.3



More information about the pulseaudio-discuss mailing list