[PATCH 1/7] alsa-mixer: Add DecibelFix section to the profile set config file format.

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

 



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

This commit only implements the parser, the decibel fix data is not yet used
for anything.
---
 src/modules/alsa/alsa-mixer.c                    |  223 +++++++++++++++++++++-
 src/modules/alsa/alsa-mixer.h                    |   19 ++
 src/modules/alsa/mixer/profile-sets/default.conf |   49 ++++-
 3 files changed, 277 insertions(+), 14 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index fa07674..c0bbaa0 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2640,6 +2640,15 @@ static void profile_free(pa_alsa_profile *p) {
     pa_xfree(p);
 }
 
+static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) {
+    pa_assert(db_fix);
+
+    pa_xfree(db_fix->name);
+    pa_xfree(db_fix->db_values);
+
+    pa_xfree(db_fix);
+}
+
 void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
     pa_assert(ps);
 
@@ -2661,6 +2670,15 @@ void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) {
         pa_hashmap_free(ps->mappings, NULL, NULL);
     }
 
+    if (ps->decibel_fixes) {
+        pa_alsa_decibel_fix *db_fix;
+
+        while ((db_fix = pa_hashmap_steal_first(ps->decibel_fixes)))
+            decibel_fix_free(db_fix);
+
+        pa_hashmap_free(ps->decibel_fixes, NULL, NULL);
+    }
+
     pa_xfree(ps);
 }
 
@@ -2705,6 +2723,26 @@ static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) {
     return p;
 }
 
+static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) {
+    pa_alsa_decibel_fix *db_fix;
+
+    if (!pa_startswith(name, "DecibelFix "))
+        return NULL;
+
+    name += 11;
+
+    if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name)))
+        return db_fix;
+
+    db_fix = pa_xnew0(pa_alsa_decibel_fix, 1);
+    db_fix->profile_set = ps;
+    db_fix->name = pa_xstrdup(name);
+
+    pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix);
+
+    return db_fix;
+}
+
 static int mapping_parse_device_strings(
         const char *filename,
         unsigned line,
@@ -2975,6 +3013,130 @@ static int profile_parse_skip_probe(
     return 0;
 }
 
+static int decibel_fix_parse_db_values(
+        const char *filename,
+        unsigned line,
+        const char *section,
+        const char *lvalue,
+        const char *rvalue,
+        void *data,
+        void *userdata) {
+
+    pa_alsa_profile_set *ps = userdata;
+    pa_alsa_decibel_fix *db_fix;
+    char **items;
+    char *item;
+    long *db_values;
+    unsigned n = 8; /* Current size of the db_values table. */
+    unsigned min_step = 0;
+    unsigned max_step = 0;
+    unsigned i = 0; /* Index to the items table. */
+    unsigned prev_step = 0;
+    double prev_db = 0;
+
+    pa_assert(filename);
+    pa_assert(section);
+    pa_assert(lvalue);
+    pa_assert(rvalue);
+    pa_assert(ps);
+
+    if (!(db_fix = decibel_fix_get(ps, section))) {
+        pa_log("[%s:%u] %s invalid in section %s", filename, line, lvalue, section);
+        return -1;
+    }
+
+    if (!(items = pa_split_spaces_strv(rvalue))) {
+        pa_log("[%s:%u] Value missing", pa_strnull(filename), line);
+        return -1;
+    }
+
+    db_values = pa_xnew(long, n);
+
+    while ((item = items[i++])) {
+        char *s = item; /* Step value string. */
+        char *d = item; /* dB value string. */
+        uint32_t step;
+        double db;
+
+        /* Move d forward until it points to a colon or to the end of the item. */
+        for (; *d && *d != ':'; ++d);
+
+        if (d == s) {
+            /* item started with colon. */
+            pa_log("[%s:%u] No step value found in %s", filename, line, item);
+            goto fail;
+        }
+
+        if (!*d || !*(d + 1)) {
+            /* No colon found, or it was the last character in item. */
+            pa_log("[%s:%u] No dB value found in %s", filename, line, item);
+            goto fail;
+        }
+
+        /* pa_atou() needs a null-terminating string. Let's replace the colon
+         * with a zero byte. */
+        *d++ = '\0';
+
+        if (pa_atou(s, &step) < 0) {
+            pa_log("[%s:%u] Invalid step value: %s", filename, line, s);
+            goto fail;
+        }
+
+        if (pa_atod(d, &db) < 0) {
+            pa_log("[%s:%u] Invalid dB value: %s", filename, line, d);
+            goto fail;
+        }
+
+        if (step <= prev_step && i != 1) {
+            pa_log("[%s:%u] Step value %u not greater than the previous value %u", filename, line, step, prev_step);
+            goto fail;
+        }
+
+        if (db < prev_db && i != 1) {
+            pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", filename, line, db, prev_db);
+            goto fail;
+        }
+
+        if (i == 1) {
+            min_step = step;
+            db_values[0] = (long) (db * 100.0);
+            prev_step = step;
+            prev_db = db;
+        } else {
+            /* Interpolate linearly. */
+            double db_increment = (db - prev_db) / (step - prev_step);
+
+            for (; prev_step < step; ++prev_step, prev_db += db_increment) {
+
+                /* Reallocate the db_values table if it's about to overflow. */
+                if (prev_step + 1 - min_step == n) {
+                    n *= 2;
+                    db_values = pa_xrenew(long, db_values, n);
+                }
+
+                db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0);
+            }
+        }
+
+        max_step = step;
+    }
+
+    db_fix->min_step = min_step;
+    db_fix->max_step = max_step;
+    pa_xfree(db_fix->db_values);
+    db_fix->db_values = db_values;
+
+    pa_xstrfreev(items);
+
+    return 0;
+
+fail:
+    pa_xstrfreev(items);
+    pa_xfree(db_values);
+
+    return -1;
+}
+
 static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
 
     static const struct description_map well_known_descriptions[] = {
@@ -3012,7 +3174,7 @@ static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) {
 
     if ((m->input_path_names && m->input_element) ||
         (m->output_path_names && m->output_element)) {
-        pa_log("Mapping %s must have either mixer path or mixer elment, not both.", m->name);
+        pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name);
         return -1;
     }
 
@@ -3257,10 +3419,52 @@ void pa_alsa_profile_dump(pa_alsa_profile *p) {
             pa_log_debug("Output %s", m->name);
 }
 
+static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) {
+    pa_assert(db_fix);
+
+    /* Check that the dB mapping has been configured. Since "db-values" is
+     * currently the only option in the DecibelFix section, and decibel fix
+     * objects don't get created if a DecibelFix section is empty, this is
+     * actually a redundant check. Having this may prevent future bugs,
+     * however. */
+    if (!db_fix->db_values) {
+        pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name);
+        return -1;
+    }
+
+    return 0;
+}
+
+void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) {
+    char *db_values = NULL;
+
+    pa_assert(db_fix);
+
+    if (db_fix->db_values) {
+        pa_strbuf *buf;
+        long i;
+        long max_i = db_fix->max_step - db_fix->min_step;
+
+        buf = pa_strbuf_new();
+        pa_strbuf_printf(buf, "[%li]:%0.2f", db_fix->min_step, db_fix->db_values[0] / 100.0);
+
+        for (i = 1; i <= max_i; ++i)
+            pa_strbuf_printf(buf, " [%li]:%0.2f", i + db_fix->min_step, db_fix->db_values[i] / 100.0);
+
+        db_values = pa_strbuf_tostring_free(buf);
+    }
+
+    pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s",
+                 db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values));
+
+    pa_xfree(db_values);
+}
+
 pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) {
     pa_alsa_profile_set *ps;
     pa_alsa_profile *p;
     pa_alsa_mapping *m;
+    pa_alsa_decibel_fix *db_fix;
     char *fn;
     int r;
     void *state;
@@ -3286,12 +3490,16 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
         { "input-mappings",         profile_parse_mappings,       NULL, NULL },
         { "output-mappings",        profile_parse_mappings,       NULL, NULL },
         { "skip-probe",             profile_parse_skip_probe,     NULL, NULL },
+
+        /* [DecibelFix ...] */
+        { "db-values",              decibel_fix_parse_db_values,  NULL, NULL },
         { NULL, NULL, NULL, NULL }
     };
 
     ps = pa_xnew0(pa_alsa_profile_set, 1);
     ps->mappings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     ps->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
     items[0].data = &ps->auto_profiles;
 
@@ -3321,6 +3529,10 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
         if (profile_verify(p) < 0)
             goto fail;
 
+    PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
+        if (decibel_fix_verify(db_fix) < 0)
+            goto fail;
+
     return ps;
 
 fail:
@@ -3501,23 +3713,28 @@ void pa_alsa_profile_set_probe(
 void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) {
     pa_alsa_profile *p;
     pa_alsa_mapping *m;
+    pa_alsa_decibel_fix *db_fix;
     void *state;
 
     pa_assert(ps);
 
-    pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u",
+    pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u",
                  (void*)
                  ps,
                  pa_yes_no(ps->auto_profiles),
                  pa_yes_no(ps->probed),
                  pa_hashmap_size(ps->mappings),
-                 pa_hashmap_size(ps->profiles));
+                 pa_hashmap_size(ps->profiles),
+                 pa_hashmap_size(ps->decibel_fixes));
 
     PA_HASHMAP_FOREACH(m, ps->mappings, state)
         pa_alsa_mapping_dump(m);
 
     PA_HASHMAP_FOREACH(p, ps->profiles, state)
         pa_alsa_profile_dump(p);
+
+    PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state)
+        pa_alsa_decibel_fix_dump(db_fix);
 }
 
 void pa_alsa_add_ports(pa_hashmap **p, pa_alsa_path_set *ps) {
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 0fcfc0a..75da138 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -46,6 +46,7 @@ typedef struct pa_alsa_path pa_alsa_path;
 typedef struct pa_alsa_path_set pa_alsa_path_set;
 typedef struct pa_alsa_mapping pa_alsa_mapping;
 typedef struct pa_alsa_profile pa_alsa_profile;
+typedef struct pa_alsa_decibel_fix pa_alsa_decibel_fix;
 typedef struct pa_alsa_profile_set pa_alsa_profile_set;
 typedef struct pa_alsa_port_data pa_alsa_port_data;
 
@@ -266,9 +267,26 @@ struct pa_alsa_profile {
     pa_idxset *output_mappings;
 };
 
+struct pa_alsa_decibel_fix {
+    pa_alsa_profile_set *profile_set;
+
+    char *name; /* Alsa volume element name. */
+    long min_step;
+    long max_step;
+
+    /* An array that maps alsa volume element steps to decibels. The steps can
+     * be used as indices to this array, after substracting min_step from the
+     * real value.
+     *
+     * The values are actually stored as integers representing millibels,
+     * because that's the format the alsa API uses. */
+    long *db_values;
+};
+
 struct pa_alsa_profile_set {
     pa_hashmap *mappings;
     pa_hashmap *profiles;
+    pa_hashmap *decibel_fixes;
 
     pa_bool_t auto_profiles;
     pa_bool_t probed:1;
@@ -276,6 +294,7 @@ struct pa_alsa_profile_set {
 
 void pa_alsa_mapping_dump(pa_alsa_mapping *m);
 void pa_alsa_profile_dump(pa_alsa_profile *p);
+void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix);
 
 pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
 void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
diff --git a/src/modules/alsa/mixer/profile-sets/default.conf b/src/modules/alsa/mixer/profile-sets/default.conf
index cf207bd..4759896 100644
--- a/src/modules/alsa/mixer/profile-sets/default.conf
+++ b/src/modules/alsa/mixer/profile-sets/default.conf
@@ -16,17 +16,27 @@
 
 ; Default profile definitions for the ALSA backend of PulseAudio. This
 ; is used as fallback for all cards that have no special mapping
-; assigned. (and should be good enough for the vast majority of
-; cards).  Use the udev property PULSE_PROFILE_SET to assign a
-; different profile set than this one to a device.  So what is this
-; about? Simply, what we do here is map ALSA devices to how they are
-; exposed in PA. We say which ALSA device string to use to open a
-; device, which channel mapping to use then, and which mixer path to
-; use. This is encoded in a 'mapping'. Multiple of these mappings can
-; be bound together in a 'profile' which is then directly exposed in
-; the UI as a card profile. Each mapping assigned to a profile will
-; result in one sink/source to be created if the profile is selected
-; for the card.
+; assigned (and should be good enough for the vast majority of
+; cards). If you want to assign a different profile set than this one
+; to a device, either set the udev property PULSE_PROFILE_SET for the
+; card, or use the "profile_set" module argument when loading
+; module-alsa-card.
+;
+; So what is this about? Simply, what we do here is map ALSA devices
+; to how they are exposed in PA. We say which ALSA device string to
+; use to open a device, which channel mapping to use then, and which
+; mixer path to use. This is encoded in a 'mapping'. Multiple of these
+; mappings can be bound together in a 'profile' which is then directly
+; exposed in the UI as a card profile. Each mapping assigned to a
+; profile will result in one sink/source to be created if the profile
+; is selected for the card.
+;
+; Additionally, the path set configuration files can describe the
+; decibel values assigned to the steps of the volume elements. This
+; can be used to work around situations when the alsa driver doesn't
+; provide any decibel information, or when the information is
+; incorrect.
+
 
 ; [General]
 ; auto-profiles = no | yes                  # Instead of defining all profiles manually, autogenerate
@@ -55,6 +65,23 @@
 ; skip-probe = no | yes                     # Skip probing for availability? If this is yes then this profile
 ;                                           # will be assumed as working without probing. Makes initialization
 ;                                           # a bit faster but only works if the card is really known well.
+;
+; [DecibelFix element]                      # The element in the section title is the name of the volume element.
+; db-values = ...                           # The option value consists of pairs of step numbers and decibel values.
+;                                           # The pairs are separated with whitespace, and steps are separated from
+;                                           # the corresponding decibel values with a colon. The values must be in an
+;                                           # increasing order. Here's an example of a valid string:
+;                                           #
+;                                           #     "0:-40.50  1:-38.70  3:-33.00  11:0"
+;                                           #
+;                                           # The lowest step imposes a lower limit for hardware volume and the
+;                                           # highest step correspondingly imposes a higher limit. That means that
+;                                           # that the mixer will never be set outside those values - the rest of the
+;                                           # volume scale is done using software volume.
+;                                           #
+;                                           # As seen in the example, you don't need to specify a dB value for each
+;                                           # step. The dB value for skipped steps will be linearly interpolated
+;                                           # using the nearest steps that are given.
 
 [General]
 auto-profiles = yes
-- 
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