summaryrefslogtreecommitdiff
path: root/src/modules/alsa/alsa-mixer.c
diff options
context:
space:
mode:
authorColin Guthrie <colin@mageia.org>2011-07-02 18:14:48 +0100
committerColin Guthrie <colin@mageia.org>2011-07-20 22:23:11 +0100
commit5c832a5b8a2bc2ce7447eab10acaeed30cd3ed78 (patch)
tree13ee25699fb203ab0060d44a209d27deace2c961 /src/modules/alsa/alsa-mixer.c
parent85834107a0eb81a823287b3a9ae834314961c290 (diff)
alsa-mixer: Detect and then drop pointless paths in the path set.
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).
Diffstat (limited to 'src/modules/alsa/alsa-mixer.c')
-rw-r--r--src/modules/alsa/alsa-mixer.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index abd3bf24f..f6a2a2037 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2873,6 +2873,196 @@ void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
2873 pa_alsa_path_dump(p); 2873 pa_alsa_path_dump(p);
2874} 2874}
2875 2875
2876
2877static pa_bool_t options_have_option(pa_alsa_option *options, const char *alsa_name) {
2878 pa_alsa_option *o;
2879
2880 pa_assert(options);
2881 pa_assert(alsa_name);
2882
2883 PA_LLIST_FOREACH(o, options) {
2884 if (pa_streq(o->alsa_name, alsa_name))
2885 return TRUE;
2886 }
2887 return FALSE;
2888}
2889
2890static pa_bool_t enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_options) {
2891 pa_alsa_option *oa, *ob;
2892
2893 pa_assert(a_options);
2894 pa_assert(b_options);
2895
2896 /* If there is an option A offers that B does not, then A is not a subset of B. */
2897 PA_LLIST_FOREACH(oa, a_options) {
2898 pa_bool_t found = FALSE;
2899 PA_LLIST_FOREACH(ob, b_options) {
2900 if (pa_streq(oa->alsa_name, ob->alsa_name)) {
2901 found = TRUE;
2902 break;
2903 }
2904 }
2905 if (!found)
2906 return FALSE;
2907 }
2908 return TRUE;
2909}
2910
2911/**
2912 * Compares two elements to see if a is a subset of b
2913 */
2914static pa_bool_t element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
2915 pa_assert(a);
2916 pa_assert(b);
2917 pa_assert(m);
2918
2919 /* General rules:
2920 * Every state is a subset of itself (with caveats for volume_limits and options)
2921 * IGNORE is a subset of every other state */
2922
2923 /* Check the volume_use */
2924 if (a->volume_use != PA_ALSA_VOLUME_IGNORE) {
2925
2926 /* "Constant" is subset of "Constant" only when their constant values are equal */
2927 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume)
2928 return FALSE;
2929
2930 /* Different volume uses when b is not "Merge" means we are definitely not a subset */
2931 if (a->volume_use != b->volume_use && b->volume_use != PA_ALSA_VOLUME_MERGE)
2932 return FALSE;
2933
2934 /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant.
2935 * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge"
2936 * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit" */
2937 if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) {
2938 long a_limit;
2939
2940 if (a->volume_use == PA_ALSA_VOLUME_CONSTANT)
2941 a_limit = a->constant_volume;
2942 else if (a->volume_use == PA_ALSA_VOLUME_ZERO) {
2943 long dB = 0;
2944
2945 if (a->db_fix) {
2946 int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1);
2947 a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding);
2948 } else {
2949 snd_mixer_selem_id_t *sid;
2950 snd_mixer_elem_t *me;
2951
2952 SELEM_INIT(sid, a->alsa_name);
2953 if (!(me = snd_mixer_find_selem(m, sid))) {
2954 pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
2955 return FALSE;
2956 }
2957
2958 if (a->direction == PA_ALSA_DIRECTION_OUTPUT) {
2959 if (snd_mixer_selem_ask_playback_dB_vol(me, dB, +1, &a_limit) < 0)
2960 return FALSE;
2961 } else {
2962 if (snd_mixer_selem_ask_capture_dB_vol(me, dB, -1, &a_limit) < 0)
2963 return FALSE;
2964 }
2965 }
2966 } else if (a->volume_use == PA_ALSA_VOLUME_OFF)
2967 a_limit = a->min_volume;
2968 else if (a->volume_use == PA_ALSA_VOLUME_MERGE)
2969 a_limit = a->volume_limit;
2970 else
2971 /* This should never be reached */
2972 pa_assert(FALSE);
2973
2974 if (a_limit > b->volume_limit)
2975 return FALSE;
2976 }
2977 }
2978
2979 if (a->switch_use != PA_ALSA_SWITCH_IGNORE) {
2980 /* "On" is a subset of "Mute".
2981 * "Off" is a subset of "Mute".
2982 * "On" is a subset of "Select", if there is an "Option:On" in B.
2983 * "Off" is a subset of "Select", if there is an "Option:Off" in B.
2984 * "Select" is a subset of "Select", if they have the same options (is this always true?). */
2985
2986 if (a->switch_use != b->switch_use) {
2987
2988 if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE
2989 || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON)
2990 return FALSE;
2991
2992 if (b->switch_use == PA_ALSA_SWITCH_SELECT) {
2993 if (a->switch_use == PA_ALSA_SWITCH_ON) {
2994 if (!options_have_option(b->options, "on"))
2995 return FALSE;
2996 } else if (a->switch_use == PA_ALSA_SWITCH_OFF) {
2997 if (!options_have_option(b->options, "off"))
2998 return FALSE;
2999 }
3000 }
3001 } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) {
3002 if (!enumeration_is_subset(a->options, b->options))
3003 return FALSE;
3004 }
3005 }
3006
3007 if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) {
3008 if (!enumeration_is_subset(a->options, b->options))
3009 return FALSE;
3010 }
3011
3012 return TRUE;
3013}
3014
3015static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
3016 pa_alsa_path *p, *np;
3017
3018 pa_assert(ps);
3019 pa_assert(m);
3020
3021 /* If we only have one path, then don't bother */
3022 if (!ps->paths || !ps->paths->next)
3023 return;
3024
3025 for (p = ps->paths; p; p = np) {
3026 pa_alsa_path *p2;
3027 np = p->next;
3028
3029 PA_LLIST_FOREACH(p2, ps->paths) {
3030 pa_alsa_element *ea, *eb;
3031 pa_bool_t is_subset = TRUE;
3032
3033 if (p == p2)
3034 continue;
3035
3036 /* Compare the elements of each set... */
3037 pa_assert_se(ea = p->elements);
3038 pa_assert_se(eb = p2->elements);
3039
3040 while (is_subset) {
3041 if (pa_streq(ea->alsa_name, eb->alsa_name)) {
3042 if (element_is_subset(ea, eb, m)) {
3043 ea = ea->next;
3044 eb = eb->next;
3045 if ((ea && !eb) || (!ea && eb))
3046 is_subset = FALSE;
3047 else if (!ea && !eb)
3048 break;
3049 } else
3050 is_subset = FALSE;
3051
3052 } else
3053 is_subset = FALSE;
3054 }
3055
3056 if (is_subset) {
3057 pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name);
3058 PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
3059 pa_alsa_path_free(p);
3060 break;
3061 }
3062 }
3063 }
3064}
3065
2876static void path_set_make_paths_unique(pa_alsa_path_set *ps) { 3066static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
2877 pa_alsa_path *p, *q; 3067 pa_alsa_path *p, *q;
2878 3068
@@ -2928,8 +3118,15 @@ void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t igno
2928 } 3118 }
2929 } 3119 }
2930 3120
3121 pa_log_debug("Found mixer paths (before tidying):");
3122 pa_alsa_path_set_dump(ps);
3123
3124 path_set_condense(ps, m);
2931 path_set_make_paths_unique(ps); 3125 path_set_make_paths_unique(ps);
2932 ps->probed = TRUE; 3126 ps->probed = TRUE;
3127
3128 pa_log_debug("Available mixer paths (after tidying):");
3129 pa_alsa_path_set_dump(ps);
2933} 3130}
2934 3131
2935static void mapping_free(pa_alsa_mapping *m) { 3132static void mapping_free(pa_alsa_mapping *m) {