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

Arun Raghavan arun at accosted.net
Wed Mar 18 00:00:45 PDT 2015


On Thu, 2015-02-12 at 15:11 +0200, Tanu Kaskinen wrote:
> "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.

Wouldn't the "right" way for this to happen be to have the corresponding
mute control of the device be set to mute by the driver? Are there
circumstances where this might not be possible, or are we basically
providing a shortcut to describe hardware behaviour via configuration
rather than via the driver (i.e. exposing and updating kcontrols)?

> 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;
>  };

Might make sense to prefix those two with "ucm_" to make the meaning
clearer wherever they're used.

>  
> +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 {

I'd prefer ucm_port or pa_alsa_ucm_port -- the namespacing means that
it's easier to look up stuff with ctags/cscope/whatever shiny thing
comes in the future (maybe some day we'll have a way of distinguishing
the several struct userdatas that we have too).

> +    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;

Shouldn't the default availability be unknown, with actual availability
set if we have a corresponding jack?

>  
>          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);

Should just pass port_free() as the free function in pa_dynarray_new().

> +
> +        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;

I don't know if this actually ever does happen this way, but I can
imagine that you might have separate headphone and mic jack kcontrols
for a combined physical headset port. If that might be the case, the XXX
comment can be removed.

> +
> +    /* 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. */

Mario was working on implementing David's comments from
https://bugs.freedesktop.org/show_bug.cgi?id=87002#c18 -- which might
make this comment obsolete (but the condition correct). 

> +    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) {

-- Arun



More information about the pulseaudio-discuss mailing list