[PATCH] Don't settle on a card profile if it doesn't have any available port

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When creating a new card and selecting a new profile we need to check
whether there's a port available for that profile before settling on it,
or we might end up with a useless profile when we might have others that
would be a better choice.

In order to achieve this, we need to make sure that port availability
based on the state of the actual alsa jacks for a path is properly
initialized before creating the card, so that we can make the right
decision once its created, by checking the card's port availability.

This patch avoids scenarios like starting with the analog-stereo profile
selected when there are neither built-in speakers nor an external
headset connected, even when the device is connected to an external
screen with audio capabilities through HDMI (so it could use the
available hdmi-output port from the the hdmi-stereo profile).

https://bugs.freedesktop.org/show_bug.cgi?id=87002
---
 src/modules/alsa/alsa-mixer.c                 | 21 +++++++++
 src/modules/alsa/alsa-util.c                  | 60 +++++++++++++++++++++++++
 src/modules/alsa/alsa-util.h                  |  2 +
 src/modules/alsa/module-alsa-card.c           | 65 ++++++++-------------------
 src/modules/module-switch-on-port-available.c | 54 ++++++++++++++++++++++
 5 files changed, 156 insertions(+), 46 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 2fe2ae4..dc8a481 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -4618,6 +4618,20 @@ void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *ps) {
     }
 }
 
+static pa_dynarray *jacks_array_from_path(pa_alsa_path *path)
+{
+    pa_dynarray *jacks;
+    pa_alsa_jack *jack;
+
+    pa_assert(path);
+
+    jacks = pa_dynarray_new(NULL);
+    PA_LLIST_FOREACH(jack, path->jacks)
+        pa_dynarray_append(jacks, jack);
+
+    return jacks;
+}
+
 static pa_device_port* device_port_alsa_init(pa_hashmap *ports, /* card ports */
     const char* name,
     const char* description,
@@ -4636,12 +4650,19 @@ static pa_device_port* device_port_alsa_init(pa_hashmap *ports, /* card ports */
     if (!p) {
         pa_alsa_port_data *data;
         pa_device_port_new_data port_data;
+        pa_available_t pa;
+        pa_dynarray *jacks_array;
 
         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, description);
         pa_device_port_new_data_set_direction(&port_data, path->direction == PA_ALSA_DIRECTION_OUTPUT ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
 
+        jacks_array = jacks_array_from_path(path);
+        pa = pa_alsa_availability_from_jacks(jacks_array, NULL, NULL);
+        pa_dynarray_free(jacks_array);
+        pa_device_port_new_data_set_available(&port_data, pa);
+
         p = pa_device_port_new(core, &port_data, sizeof(pa_alsa_port_data));
         pa_device_port_new_data_done(&port_data);
         pa_assert(p);
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index bb79e71..10434a6 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1702,3 +1702,63 @@ int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) {
 
     return 0;
 }
+
+/*
+ * port: can be null, in which case all jacks in the list will be checked.
+ * card: null for non-UCM scenarios, where we can use the jack->path->port pointer.
+ *       Set this to the right card only for UCM jacks.
+ */
+pa_available_t pa_alsa_availability_from_jacks(pa_dynarray *jacks, pa_device_port *port, pa_card *card) {
+    pa_alsa_jack *jack;
+    pa_available_t pa = PA_AVAILABLE_UNKNOWN;
+    pa_device_port *p;
+    int idx;
+
+    pa_assert(jacks);
+
+    PA_DYNARRAY_FOREACH(jack, jacks, idx) {
+        pa_available_t cpa;
+
+        /* If a port has NOT been provided, it means we will trust the list
+         * of jacks passed to be the ones we are interested in checking, so
+         * get the actual port from the jack only when a port has been passed. */
+        if (port) {
+            if (card)
+                p = pa_hashmap_get(card->ports, jack->name);
+            else {
+                if (jack->path)
+                    p = jack->path->port;
+                else
+                    continue;
+            }
+
+            if (p != port)
+                continue;
+        }
+
+        cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged;
+
+        if (cpa == PA_AVAILABLE_NO) {
+            /* If a plugged-in jack causes the availability to go to NO, it
+             * should override all other availability information (like a
+             * blacklist) so set and bail */
+            if (jack->plugged_in) {
+                pa = cpa;
+                break;
+            }
+
+            /* If the current availablility is unknown go the more precise no,
+             * but otherwise don't change state */
+            if (pa == PA_AVAILABLE_UNKNOWN)
+                pa = cpa;
+        } else if (cpa == PA_AVAILABLE_YES) {
+            /* Output is available through at least one jack, so go to that
+             * level of availability. We still need to continue iterating through
+             * the jacks in case a jack is plugged in that forces the state to no
+             */
+            pa = cpa;
+        }
+    }
+
+    return pa;
+}
diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h
index 8345a0b..cbdf6fc 100644
--- a/src/modules/alsa/alsa-util.h
+++ b/src/modules/alsa/alsa-util.h
@@ -151,4 +151,6 @@ struct pa_hdmi_eld {
 
 int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld);
 
+pa_available_t pa_alsa_availability_from_jacks(pa_dynarray *jacks, pa_device_port *port, pa_card *card);
+
 #endif
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 382e40d..a93eb69 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -304,52 +304,19 @@ static void init_profile(struct userdata *u) {
             am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am);
 }
 
-static void report_port_state(pa_device_port *p, struct userdata *u) {
-    void *state;
+static pa_dynarray *jacks_array_from_userdata(struct userdata *u)
+{
+    pa_dynarray *jacks;
     pa_alsa_jack *jack;
-    pa_available_t pa = PA_AVAILABLE_UNKNOWN;
-    pa_device_port *port;
-
-    PA_HASHMAP_FOREACH(jack, u->jacks, state) {
-        pa_available_t cpa;
-
-        if (u->use_ucm)
-            port = pa_hashmap_get(u->card->ports, jack->name);
-        else {
-            if (jack->path)
-                port = jack->path->port;
-            else
-                continue;
-        }
+    void *state;
 
-        if (p != port)
-            continue;
+    pa_assert(u);
 
-        cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged;
-
-        if (cpa == PA_AVAILABLE_NO) {
-          /* If a plugged-in jack causes the availability to go to NO, it
-           * should override all other availability information (like a
-           * blacklist) so set and bail */
-          if (jack->plugged_in) {
-            pa = cpa;
-            break;
-          }
-
-          /* If the current availablility is unknown go the more precise no,
-           * but otherwise don't change state */
-          if (pa == PA_AVAILABLE_UNKNOWN)
-            pa = cpa;
-        } else if (cpa == PA_AVAILABLE_YES) {
-          /* Output is available through at least one jack, so go to that
-           * level of availability. We still need to continue iterating through
-           * the jacks in case a jack is plugged in that forces the state to no
-           */
-          pa = cpa;
-        }
-    }
+    jacks = pa_dynarray_new(NULL);
+    PA_HASHMAP_FOREACH(jack, u->jacks, state)
+        pa_dynarray_append(jacks, jack);
 
-    pa_device_port_set_available(p, pa);
+    return jacks;
 }
 
 static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
@@ -382,13 +349,18 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
             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);
+                port = jack->path->port;
+            }
+            if (port) {
+                pa_card *card = u->use_ucm ? u->card : NULL;
+                pa_dynarray *jacks_array = jacks_array_from_userdata(u);
+                pa_available_t pa = pa_alsa_availability_from_jacks(jacks_array, port, card);
+                pa_dynarray_free(jacks_array);
+                pa_device_port_set_available(port, pa);
             }
-            report_port_state(port, u);
         }
     return 0;
 }
@@ -732,6 +704,8 @@ int pa__init(pa_module *m) {
         if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)))
             pa_reserve_wrapper_set_application_device_name(reserve, description);
 
+    init_jacks(u);
+
     add_profiles(u, data.profiles, data.ports);
 
     if (pa_hashmap_isempty(data.profiles)) {
@@ -760,7 +734,6 @@ int pa__init(pa_module *m) {
     u->card->userdata = u;
     u->card->set_profile = card_set_profile;
 
-    init_jacks(u);
     init_profile(u);
     init_eld_ctls(u);
 
diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
index eb8f2d7..58a4fd8 100644
--- a/src/modules/module-switch-on-port-available.c
+++ b/src/modules/module-switch-on-port-available.c
@@ -240,6 +240,58 @@ static pa_device_port *new_sink_source(pa_hashmap *ports, const char *name) {
     return p;
 }
 
+static bool profile_contains_available_ports(char *profile_name, pa_hashmap *ports) {
+    pa_device_port *port;
+    void *state;
+
+    pa_assert(profile_name);
+
+    PA_HASHMAP_FOREACH(port, ports, state) {
+        if (pa_hashmap_get(port->profiles, profile_name)
+            && port->available != PA_AVAILABLE_NO)
+            return true;
+    }
+
+    return false;
+}
+
+static pa_card_profile *find_best_profile_with_available_ports(pa_card_new_data *data) {
+    pa_card_profile *profile = NULL;
+    pa_card_profile *best_profile = NULL;
+    void *state;
+
+    pa_assert(data);
+
+    PA_HASHMAP_FOREACH(profile, data->profiles, state) {
+        if (profile->available == PA_AVAILABLE_NO)
+            continue;
+
+        if (!profile_contains_available_ports(profile->name, data->ports))
+            continue;
+
+        if (!best_profile || profile->priority > best_profile->priority)
+            best_profile = profile;
+    }
+
+    return best_profile;
+}
+
+static pa_hook_result_t card_new_hook_callback(pa_core *c, pa_card_new_data *data, void *u) {
+    pa_card_profile *alt_profile;
+
+    if (data->active_profile && profile_contains_available_ports(data->active_profile, data->ports))
+        return PA_HOOK_OK;
+
+    /* Try to avoid situations where we could settle on a profile when
+       there are not available ports that could be actually used. */
+    if (alt_profile = find_best_profile_with_available_ports(data)) {
+        pa_card_new_data_set_profile(data, alt_profile->name);
+        data->save_profile = false;
+    }
+
+    return PA_HOOK_OK;
+}
+
 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, void *u) {
 
     pa_device_port *p = new_sink_source(new_data->ports, new_data->active_port);
@@ -266,6 +318,8 @@ int pa__init(pa_module*m) {
     pa_assert(m);
 
     /* Make sure we are after module-device-restore, so we can overwrite that suggestion if necessary */
+    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_CARD_NEW],
+                           PA_HOOK_NORMAL, (pa_hook_cb_t) card_new_hook_callback, NULL);
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_NEW],
                            PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, NULL);
     pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_NEW],
-- 
2.1.0



[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux