[PATCH 4/8] alsa-mixer: Detect and then drop pointless paths in the path set.

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

 



In order to try and avoid 'spamming' the user with port choices,
attempt to detect and remove any pointless paths in a path set. That is
any paths which are subsets of other paths.

This should solve a problem case with some USB Headsets which result in
two paths both involving the 'Speaker' element. When no 'Master' element
exists (which is quite common on head/handsets), then the first path
(analog-output) will contain the 'Speaker' in a way that completely fits
with in the use of the 'Speaker' element in the other path
(analog-output-speaker).
---
 src/modules/alsa/alsa-mixer.c  |  167 ++++++++++++++++++++++++++++++++++++++++
 src/modules/alsa/alsa-sink.c   |    3 -
 src/modules/alsa/alsa-source.c |    3 -
 3 files changed, 167 insertions(+), 6 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index abd3bf2..842fba2 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2873,6 +2873,166 @@ void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
         pa_alsa_path_dump(p);
 }
 
+/**
+ *  Compares two elements to see if a is a subset of b
+ */
+static pa_bool_t element_is_subset(pa_alsa_element *a, pa_alsa_element *b) {
+    pa_assert(a);
+    pa_assert(b);
+
+    /* General rules:
+     * Every state is a subset of itself (with caveats for volume_limits and options)
+     * IGNORE is a subset of every other state */
+
+    /* Check the volume_use */
+    if (a->volume_use != PA_ALSA_VOLUME_IGNORE) {
+
+        /* "Constant" is subset of "Constant" only when their constant values are equal */
+        if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume)
+            return FALSE;
+
+        /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant.
+         * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge"
+         * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit".
+         * "Zero" is a subset of "Off" */
+        if (a->volume_use != b->volume_use || b->volume_use == PA_ALSA_VOLUME_MERGE) {
+            if (a->volume_use == PA_ALSA_VOLUME_OFF && b->volume_use == PA_ALSA_VOLUME_ZERO)
+                return FALSE;
+
+            if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) {
+                long a_limit = -1;
+                if (a->volume_use == PA_ALSA_VOLUME_CONSTANT)
+                    a_limit = a->constant_volume;
+                else if (a->volume_use == PA_ALSA_VOLUME_ZERO || a->volume_use == PA_ALSA_VOLUME_OFF)
+                    a_limit = 0;
+                else if (a->volume_use == PA_ALSA_VOLUME_MERGE)
+                    a_limit = a->volume_limit;
+
+                if (a_limit < 0 || a_limit > b->volume_limit)
+                    return FALSE;
+            }
+        }
+    }
+
+    if (a->switch_use != PA_ALSA_SWITCH_IGNORE) {
+        /* "On" is a subset of "Mute".
+         * "Off" is a subset of "Mute".
+         * "On" is a subset of "Select", if there is an "Option:On" in B.
+         * "Off" is a subset of "Select", if there is an "Option:Off" in B.
+         * "Select" is a subset of "Select", if they have the same options (is this always true?). */
+
+        if (a->switch_use != b->switch_use) {
+
+            if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE
+                || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON)
+                return FALSE;
+
+            if (b->switch_use == PA_ALSA_SWITCH_SELECT) {
+                if (a->switch_use == PA_ALSA_SWITCH_ON) {
+                    pa_alsa_option *o;
+                    pa_bool_t found = FALSE;
+                    PA_LLIST_FOREACH(o, b->options) {
+                        if (pa_streq(o->alsa_name, "on"))
+                            found = TRUE;
+                    }
+                    if (!found)
+                        return FALSE;
+                } else if (a->switch_use == PA_ALSA_SWITCH_OFF) {
+                    pa_alsa_option *o;
+                    pa_bool_t found = FALSE;
+                    PA_LLIST_FOREACH(o, b->options) {
+                        if (pa_streq(o->alsa_name, "off"))
+                            found = TRUE;
+                    }
+                    if (!found)
+                        return FALSE;
+                }
+            }
+        } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) {
+            /* Treat this like an enumeration */
+            /* If there is an option A offers that B does not, then A is not a subset of B. */
+            pa_alsa_option *oa, *ob;
+            PA_LLIST_FOREACH(oa, a->options) {
+                pa_bool_t found = FALSE;
+                PA_LLIST_FOREACH(ob, b->options) {
+                    if (pa_streq(oa->alsa_name, ob->alsa_name)) {
+                        found = TRUE;
+                        break;
+                    }
+                }
+                if (!found)
+                    return FALSE;
+            }
+        }
+    }
+
+    if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) {
+        /* If there is an option A offers that B does not, then A is not a subset of B. */
+        pa_alsa_option *oa, *ob;
+        PA_LLIST_FOREACH(oa, a->options) {
+            pa_bool_t found = FALSE;
+            PA_LLIST_FOREACH(ob, b->options) {
+                if (pa_streq(oa->alsa_name, ob->alsa_name)) {
+                    found = TRUE;
+                    break;
+                }
+            }
+            if (!found)
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void path_set_condense(pa_alsa_path_set *ps) {
+    pa_alsa_path *p, *np;
+
+    /* If we only have one path, then don't bother */
+    if (!ps->paths || !ps->paths->next)
+        return;
+
+    for (p = ps->paths; p; p = np) {
+        pa_alsa_path *p2;
+        np = p->next;
+
+        PA_LLIST_FOREACH(p2, ps->paths) {
+            pa_alsa_element *ea, *eb;
+            pa_bool_t is_subset = TRUE;
+
+            if (p == p2)
+                continue;
+
+            /* Compare the elements of each set... */
+            pa_assert_se(ea = p->elements);
+            pa_assert_se(eb = p2->elements);
+
+            while (is_subset) {
+                if (pa_streq(ea->alsa_name, eb->alsa_name)) {
+                    if (element_is_subset(ea, eb)) {
+                        ea = ea->next;
+                        eb = eb->next;
+                        if ((ea && !eb) || (!ea && eb))
+                            is_subset = FALSE;
+                        else if (!ea && !eb)
+                            break;
+                    } else
+                        is_subset = FALSE;
+
+                } else
+                    is_subset = FALSE;
+            }
+
+            if (is_subset) {
+                pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name);
+                PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
+                pa_alsa_path_free(p);
+                break;
+            }
+        }
+    }
+}
+
 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
     pa_alsa_path *p, *q;
 
@@ -2928,8 +3088,15 @@ void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t igno
         }
     }
 
+    pa_log_debug("Found mixer paths (before tidying):");
+    pa_alsa_path_set_dump(ps);
+
+    path_set_condense(ps);
     path_set_make_paths_unique(ps);
     ps->probed = TRUE;
+
+    pa_log_debug("Available mixer paths (after tidying):");
+    pa_alsa_path_set_dump(ps);
 }
 
 static void mapping_free(pa_alsa_mapping *m) {
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index bdcf702..d6f251d 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1750,9 +1750,6 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
             goto fail;
 
         pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
-
-        pa_log_debug("Probed mixer paths:");
-        pa_alsa_path_set_dump(u->mixer_path_set);
     }
 
     return;
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 5feeec1..c2f73ed 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1525,9 +1525,6 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
             goto fail;
 
         pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
-
-        pa_log_debug("Probed mixer paths:");
-        pa_alsa_path_set_dump(u->mixer_path_set);
     }
 
     return;
-- 
1.7.6



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

  Powered by Linux