summaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
authorTanu Kaskinen <tanu.kaskinen@digia.com>2011-03-11 13:37:17 +0200
committerTanu Kaskinen <tanu.kaskinen@digia.com>2011-03-11 13:37:17 +0200
commite01a28cd53925e5a351ac18f8fbbb8a696139a03 (patch)
tree62786f8120de85f843b52428c4c93ca3d3d49257 /src/modules
parent4842077a1e62af4f6be22fc5479a0a3eef7e6045 (diff)
alsa-mixer: Use decibel fixes when getting and setting decibel volumes.
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/alsa/alsa-mixer.c260
-rw-r--r--src/modules/alsa/alsa-mixer.h8
2 files changed, 212 insertions, 56 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index c0bbaa0a..340e0635 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -491,6 +491,15 @@ static void option_free(pa_alsa_option *o) {
pa_xfree(o);
}
+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);
+}
+
static void element_free(pa_alsa_element *e) {
pa_alsa_option *o;
pa_assert(e);
@@ -500,6 +509,9 @@ static void element_free(pa_alsa_element *e) {
option_free(o);
}
+ if (e->db_fix)
+ decibel_fix_free(e->db_fix);
+
pa_xfree(e->alsa_name);
pa_xfree(e);
}
@@ -593,14 +605,60 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
long value = 0;
if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
- if (snd_mixer_selem_has_playback_channel(me, c))
- r = snd_mixer_selem_get_playback_dB(me, c, &value);
- else
+ if (snd_mixer_selem_has_playback_channel(me, c)) {
+ if (e->db_fix) {
+ if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) {
+ /* If the channel volume is outside the limits set
+ * by the dB fix, we clamp the hw volume to be
+ * within the limits. */
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+ pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_playback_volume(me, c, value);
+ pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ }
+
+ /* Volume step -> dB value conversion. */
+ value = e->db_fix->db_values[value - e->db_fix->min_step];
+ }
+ } else
+ r = snd_mixer_selem_get_playback_dB(me, c, &value);
+ } else
r = -1;
} else {
- if (snd_mixer_selem_has_capture_channel(me, c))
- r = snd_mixer_selem_get_capture_dB(me, c, &value);
- else
+ if (snd_mixer_selem_has_capture_channel(me, c)) {
+ if (e->db_fix) {
+ if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) {
+ /* If the channel volume is outside the limits set
+ * by the dB fix, we clamp the hw volume to be
+ * within the limits. */
+ if (value < e->db_fix->min_step) {
+ value = e->db_fix->min_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+ pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ } else if (value > e->db_fix->max_step) {
+ value = e->db_fix->max_step;
+ snd_mixer_selem_set_capture_volume(me, c, value);
+ pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. "
+ "Volume reset to %0.2f dB.", e->alsa_name, c,
+ e->db_fix->db_values[value - e->db_fix->min_step] / 100.0);
+ }
+
+ /* Volume step -> dB value conversion. */
+ value = e->db_fix->db_values[value - e->db_fix->min_step];
+ }
+ } else
+ r = snd_mixer_selem_get_capture_dB(me, c, &value);
+ } else
r = -1;
}
@@ -760,6 +818,37 @@ int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
return 0;
}
+/* Finds the closest item in db_fix->db_values and returns the corresponding
+ * step. *db_value is replaced with the value from the db_values table.
+ * Rounding is done based on the rounding parameter: -1 means rounding down and
+ * +1 means rounding up. */
+static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) {
+ unsigned i = 0;
+ unsigned max_i = 0;
+
+ pa_assert(db_fix);
+ pa_assert(db_value);
+ pa_assert(rounding != 0);
+
+ max_i = db_fix->max_step - db_fix->min_step;
+
+ if (rounding > 0) {
+ for (i = 0; i < max_i; i++) {
+ if (db_fix->db_values[i] >= *db_value)
+ break;
+ }
+ } else {
+ for (i = 0; i < max_i; i++) {
+ if (db_fix->db_values[i + 1] > *db_value)
+ break;
+ }
+ }
+
+ *db_value = db_fix->db_values[i];
+
+ return i + db_fix->min_step;
+}
+
static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t write_to_hw) {
snd_mixer_selem_id_t *sid;
@@ -807,29 +896,49 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
int rounding = value > 0 ? -1 : +1;
if (e->direction == PA_ALSA_DIRECTION_OUTPUT) {
- /* If we call set_play_volume() without checking first
- * if the channel is available, ALSA behaves ver
+ /* If we call set_playback_volume() without checking first
+ * if the channel is available, ALSA behaves very
* strangely and doesn't fail the call */
if (snd_mixer_selem_has_playback_channel(me, c)) {
- if (write_to_hw) {
- if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
- r = snd_mixer_selem_get_playback_dB(me, c, &value);
+ if (e->db_fix) {
+ if (write_to_hw)
+ r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
+ else {
+ decibel_fix_get_step(e->db_fix, &value, rounding);
+ r = 0;
+ }
+
} else {
- long alsa_val;
- if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
- r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
+ if (write_to_hw) {
+ if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0)
+ r = snd_mixer_selem_get_playback_dB(me, c, &value);
+ } else {
+ long alsa_val;
+ if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
+ }
}
} else
r = -1;
} else {
if (snd_mixer_selem_has_capture_channel(me, c)) {
- if (write_to_hw) {
- if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
- r = snd_mixer_selem_get_capture_dB(me, c, &value);
+ if (e->db_fix) {
+ if (write_to_hw)
+ r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding));
+ else {
+ decibel_fix_get_step(e->db_fix, &value, rounding);
+ r = 0;
+ }
+
} else {
- long alsa_val;
- if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
- r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
+ if (write_to_hw) {
+ if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0)
+ r = snd_mixer_selem_get_capture_dB(me, c, &value);
+ } else {
+ long alsa_val;
+ if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0)
+ r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
+ }
}
} else
r = -1;
@@ -1013,9 +1122,19 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) {
}
if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
- r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
+ if (e->db_fix) {
+ long value = 0;
+
+ r = snd_mixer_selem_set_playback_volume_all(me, decibel_fix_get_step(e->db_fix, &value, +1));
+ } else
+ r = snd_mixer_selem_set_playback_dB_all(me, 0, +1);
else
- r = snd_mixer_selem_set_capture_dB_all(me, 0, +1);
+ if (e->db_fix) {
+ long value = 0;
+
+ r = snd_mixer_selem_set_capture_volume_all(me, decibel_fix_get_step(e->db_fix, &value, +1));
+ } else
+ r = snd_mixer_selem_set_capture_dB_all(me, 0, +1);
if (r < 0)
pa_log_warn("Failed to set volume to 0dB of %s: %s", e->alsa_name, pa_alsa_strerror(errno));
@@ -1230,26 +1349,6 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
e->direction_try_other = FALSE;
if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
- e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
- else
- e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
-
- if (e->has_dB) {
-#ifdef HAVE_VALGRIND_MEMCHECK_H
- VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
- VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
-#endif
-
- e->min_dB = ((double) min_dB) / 100.0;
- e->max_dB = ((double) max_dB) / 100.0;
-
- if (min_dB >= max_dB) {
- pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB);
- e->has_dB = FALSE;
- }
- }
-
- if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume);
else
r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume);
@@ -1259,7 +1358,6 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
return -1;
}
-
if (e->min_volume >= e->max_volume) {
pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", e->min_volume, e->max_volume);
e->volume_use = PA_ALSA_VOLUME_IGNORE;
@@ -1268,6 +1366,45 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
pa_bool_t is_mono;
pa_channel_position_t p;
+ if (e->db_fix &&
+ ((e->min_volume > e->db_fix->min_step) ||
+ (e->max_volume < e->db_fix->max_step))) {
+ pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the "
+ "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name,
+ e->db_fix->min_step, e->db_fix->max_step,
+ e->min_volume, e->max_volume);
+
+ decibel_fix_free(e->db_fix);
+ e->db_fix = NULL;
+ }
+
+ if (e->db_fix) {
+ e->has_dB = TRUE;
+ e->min_volume = e->db_fix->min_step;
+ e->max_volume = e->db_fix->max_step;
+ min_dB = e->db_fix->db_values[0];
+ max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step];
+ } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
+ e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0;
+ else
+ e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0;
+
+ if (e->has_dB) {
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ VALGRIND_MAKE_MEM_DEFINED(&min_dB, sizeof(min_dB));
+ VALGRIND_MAKE_MEM_DEFINED(&max_dB, sizeof(max_dB));
+#endif
+
+ e->min_dB = ((double) min_dB) / 100.0;
+ e->max_dB = ((double) max_dB) / 100.0;
+
+ if (min_dB >= max_dB) {
+ pa_assert(!e->db_fix);
+ pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB);
+ e->has_dB = FALSE;
+ }
+ }
+
if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
is_mono = snd_mixer_selem_is_playback_mono(me) > 0;
else
@@ -2401,8 +2538,12 @@ void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mix
pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction) {
pa_alsa_path_set *ps;
char **pn = NULL, **en = NULL, **ie;
+ pa_alsa_decibel_fix *db_fix;
+ void *state;
pa_assert(m);
+ pa_assert(m->profile_set);
+ pa_assert(m->profile_set->decibel_fixes);
pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT);
if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction)
@@ -2444,7 +2585,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
pa_xfree(fn);
}
- return ps;
+ goto finish;
}
if (direction == PA_ALSA_DIRECTION_OUTPUT)
@@ -2485,6 +2626,28 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d
ps->last_path = p;
}
+finish:
+ /* Assign decibel fixes to elements. */
+ PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) {
+ pa_alsa_path *p;
+
+ PA_LLIST_FOREACH(p, ps->paths) {
+ pa_alsa_element *e;
+
+ PA_LLIST_FOREACH(e, p->elements) {
+ if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) {
+ /* The profile set that contains the dB fix may be freed
+ * before the element, so we have to copy the dB fix
+ * object. */
+ e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1);
+ e->db_fix->profile_set = NULL;
+ e->db_fix->name = pa_xstrdup(db_fix->name);
+ e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long));
+ }
+ }
+ }
+ }
+
return ps;
}
@@ -2640,15 +2803,6 @@ 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);
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 75da1389..8ec5dca1 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -119,9 +119,9 @@ struct pa_alsa_option {
pa_alsa_required_t required_absent;
};
-/* And element wraps one specific ALSA element. A series of elements *
-make up a path (see below). If the element is an enumeration or switch
-* element it may includes a list of options. */
+/* An element wraps one specific ALSA element. A series of elements
+ * make up a path (see below). If the element is an enumeration or switch
+ * element it may include a list of options. */
struct pa_alsa_element {
pa_alsa_path *path;
PA_LLIST_FIELDS(pa_alsa_element);
@@ -150,6 +150,8 @@ struct pa_alsa_element {
pa_channel_position_mask_t merged_mask;
PA_LLIST_HEAD(pa_alsa_option, options);
+
+ pa_alsa_decibel_fix *db_fix;
};
/* A path wraps a series of elements into a single entity which can be