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);
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);
}
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,
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[] = {
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;
}
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;
{ "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;
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:
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) {
; 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
; 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] # Decibel fixes can be used to work around missing or incorrect dB
+; # information from alsa. A decibel fix is a table that maps volume steps
+; # to decibel values for one volume element. The "element" part in the
+; # section title is the name of the volume element.
+; #
+; # NOTE: This feature is meant just as a help for figuring out the correct
+; # decibel values. Pulseaudio is not the correct place to maintain the
+; # decibel mappings!
+; #
+; # If you need this feature, then you should make sure that when you have
+; # the correct values figured out, the alsa driver developers get informed
+; # too, so that they can fix the driver.
+;
+; 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 can be seen in the example, you don't need to specify a dB value for
+; # each step. The dB values for skipped steps will be linearly interpolated
+; # using the nearest steps that are given.
[General]
auto-profiles = yes