alsa-mixer: Use decibel fixes when getting and setting decibel volumes.

This commit is contained in:
Tanu Kaskinen 2011-03-11 13:37:17 +02:00
parent 4842077a1e
commit e01a28cd53
2 changed files with 212 additions and 56 deletions

View file

@ -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))
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
} else
r = -1;
} else {
if (snd_mixer_selem_has_capture_channel(me, c))
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
} 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,10 +896,19 @@ 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 (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 {
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);
@ -819,10 +917,20 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
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 (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 {
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);
@ -831,6 +939,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
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,8 +1122,18 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) {
}
if (e->direction == PA_ALSA_DIRECTION_OUTPUT)
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
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)
@ -1230,6 +1349,42 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
e->direction_try_other = 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);
if (r < 0) {
pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
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;
} else {
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;
@ -1244,30 +1399,12 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) {
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)
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);
if (r < 0) {
pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r));
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;
} else {
pa_bool_t is_mono;
pa_channel_position_t p;
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);

View file

@ -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