[PATCH 1/2] alsa-mixer: Implement support for setting element specific upper limits for volume.

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

 



From: Tanu Kaskinen <ext-tanu.kaskinen@xxxxxxxxx>

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                      |   68 +++++++++++++++++++-
 src/modules/alsa/alsa-mixer.h                      |    1 +
 .../alsa/mixer/paths/analog-output.conf.common     |    1 +
 3 files changed, 69 insertions(+), 1 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 9a7e045..41997a7 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -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;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 8ec5dca..c24a896 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -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];
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf.common b/src/modules/alsa/mixer/paths/analog-output.conf.common
index ffd1b41..c7c4435 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf.common
+++ b/src/modules/alsa/mixer/paths/analog-output.conf.common
@@ -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
-- 
1.7.4.1




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

  Powered by Linux