]> code.delx.au - pulseaudio/commitdiff
alsa-mixer: Implement support for setting element specific upper limits for volume.
authorTanu Kaskinen <tanu.kaskinen@digia.com>
Fri, 11 Mar 2011 11:40:51 +0000 (13:40 +0200)
committerTanu Kaskinen <tanu.kaskinen@digia.com>
Fri, 11 Mar 2011 11:40:51 +0000 (13:40 +0200)
This feature is mainly useful in embedded systems that have built-in speakers.
In such situations the full audio path is known beforehand, so it's possible to
know what is the maximum sensible volume, and any higher volume can be
disabled.

The volume limit is set in path configuration files in the [Element] section,
using option "volume-limit". The value is the desired maximum volume step of
the volume element.

src/modules/alsa/alsa-mixer.c
src/modules/alsa/alsa-mixer.h
src/modules/alsa/mixer/paths/analog-output.conf.common

index 9a7e045a448bef251369a6f4dd045cdd8063de7b..41997a7918c8929003e8fbb9ea6ae50c97607f8f 100644 (file)
@@ -895,6 +895,9 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
             long value = to_alsa_dB(f);
             int rounding = value > 0 ? -1 : +1;
 
+            if (e->volume_limit >= 0 && value > (e->max_dB * 100))
+                value = e->max_dB * 100;
+
             if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
                 /* If we call set_playback_volume() without checking first
                  * if the channel is available, ALSA behaves very
@@ -1284,6 +1287,7 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
 
     pa_assert(m);
     pa_assert(e);
+    pa_assert(e->path);
 
     SELEM_INIT(sid, e->alsa_name);
 
@@ -1407,6 +1411,36 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
                     }
                 }
 
+                if (e->volume_limit >= 0) {
+                    if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume)
+                        pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range "
+                                    "%li-%li. The volume limit is ignored.",
+                                    e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume);
+
+                    else {
+                        e->max_volume = e->volume_limit;
+
+                        if (e->has_dB) {
+                            if (e->db_fix) {
+                                e->db_fix->max_step = e->max_volume;
+                                e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0;
+
+                            } else {
+                                if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+                                    r = snd_mixer_selem_ask_playback_vol_dB(me, e->max_volume, &max_dB);
+                                else
+                                    r = snd_mixer_selem_ask_capture_vol_dB(me, e->max_volume, &max_dB);
+
+                                if (r < 0) {
+                                    pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r));
+                                    e->has_dB = FALSE;
+                                } else
+                                    e->max_dB = ((double) max_dB) / 100.0;
+                            }
+                        }
+                    }
+                }
+
                 if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
                     is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
                 else
@@ -1530,6 +1564,7 @@ static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, pa_boo
     e->path = p;
     e->alsa_name = pa_xstrdup(section);
     e->direction = p->direction;
+    e->volume_limit = -1;
 
     PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
 
@@ -1864,6 +1899,33 @@ static int element_parse_direction_try_other(
     return 0;
 }
 
+static int element_parse_volume_limit(
+        const char *filename,
+        unsigned line,
+        const char *section,
+        const char *lvalue,
+        const char *rvalue,
+        void *data,
+        void *userdata) {
+
+    pa_alsa_path *p = userdata;
+    pa_alsa_element *e;
+    uint32_t volume_limit;
+
+    if (!(e = element_get(p, section, TRUE))) {
+        pa_log("[%s:%u] volume-limit makes no sense in '%s'", filename, line, section);
+        return -1;
+    }
+
+    if (pa_atou(rvalue, &volume_limit) < 0 || volume_limit > LONG_MAX) {
+        pa_log("[%s:%u] Invalid value for volume-limit", filename, line);
+        return -1;
+    }
+
+    e->volume_limit = volume_limit;
+    return 0;
+}
+
 static pa_channel_position_mask_t parse_mask(const char *m) {
     pa_channel_position_mask_t v;
 
@@ -2141,6 +2203,7 @@ pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction)
         { "required-absent",     element_parse_required,            NULL, NULL },
         { "direction",           element_parse_direction,           NULL, NULL },
         { "direction-try-other", element_parse_direction_try_other, NULL, NULL },
+        { "volume-limit",        element_parse_volume_limit,        NULL, NULL },
         { NULL, NULL, NULL, NULL }
     };
 
@@ -2191,6 +2254,7 @@ pa_alsa_path* pa_alsa_path_synthesize(const char*element, pa_alsa_direction_t di
     e->path = p;
     e->alsa_name = pa_xstrdup(element);
     e->direction = direction;
+    e->volume_limit = -1;
 
     e->switch_use = PA_ALSA_SWITCH_MUTE;
     e->volume_use = PA_ALSA_VOLUME_MERGE;
@@ -2454,11 +2518,12 @@ void pa_alsa_element_dump(pa_alsa_element *e) {
     pa_alsa_option *o;
     pa_assert(e);
 
-    pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
+    pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s",
                  e->alsa_name,
                  e->direction,
                  e->switch_use,
                  e->volume_use,
+                 e->volume_limit,
                  e->enumeration_use,
                  e->required,
                  e->required_any,
@@ -2619,6 +2684,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
             e->alsa_name = pa_xstrdup(*je);
             e->direction = direction;
             e->required_absent = PA_ALSA_REQUIRED_ANY;
+            e->volume_limit = -1;
 
             PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e);
             p->last_element = e;
index 8ec5dca1ef7ce3c573880eefbb753e99040e83fc..c24a896579fbcb80a2bd0050523674253b3447b0 100644 (file)
@@ -142,6 +142,7 @@ struct pa_alsa_element {
 
     pa_bool_t has_dB:1;
     long min_volume, max_volume;
+    long volume_limit; /* -1 for no configured limit */
     double min_dB, max_dB;
 
     pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST][2];
index ffd1b4137b42fb5b34c124985ed781787b217b7b..c7c44350797c398d25b6a0af14ff23f13b4a9b02 100644 (file)
@@ -82,6 +82,7 @@
 ; volume = ignore | merge | off | zero   # What to do with this volume: ignore it, merge it into the device
 ;                                        # volume slider, always set it to the lowest value possible, or always
 ;                                        # set it to 0 dB (for whatever that means)
+; volume-limit = <volume step>           # Limit the maximum volume by disabling the volume steps above <volume step>.
 ; enumeration = ignore | select          # What to do with this enumeration, ignore it or make it selectable
 ;                                        # via device ports. If set to 'select' you need to define an Option section
 ;                                        # for each of the items you want to expose