alsactl - restore dB level

When alsactl saved state has dB level information and an attribute
of a control element is changed after save (e.g. volume range is
changed), try to restore the values to keep the same dB level.

This change requires the new alsa-lib functions for TLV dB
conversion, so we check it in configure (until AM_PATH_ALSA(1.0.16)
works).
This commit is contained in:
Takashi Iwai 2007-10-24 13:53:14 +02:00
parent 030686a847
commit c88c15a478
2 changed files with 247 additions and 36 deletions

View file

@ -188,6 +188,46 @@ static unsigned int *str_to_tlv(const char *s)
return tlv; return tlv;
} }
/*
* add the TLV string and dB ranges to comment fields
*/
static int add_tlv_comments(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
snd_ctl_elem_info_t *info, snd_config_t *comment)
{
unsigned int tlv[MAX_USER_TLV_SIZE];
unsigned int *db;
long dbmin, dbmax;
int err;
if (snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)) < 0)
return 0; /* ignore error */
if (snd_ctl_elem_info_is_tlv_writable(info)) {
char *s = tlv_to_str(tlv);
if (s) {
err = snd_config_string_add(comment, "tlv", s);
if (err < 0) {
error("snd_config_string_add: %s", snd_strerror(err));
return err;
}
free(s);
}
}
err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db);
if (err <= 0)
return 0;
snd_tlv_get_dB_range(db, snd_ctl_elem_info_get_min(info),
snd_ctl_elem_info_get_max(info),
&dbmin, &dbmax);
if (err < 0)
return err;
snd_config_integer_add(comment, "dbmin", dbmin);
snd_config_integer_add(comment, "dbmax", dbmax);
return 0;
}
static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top) static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top)
{ {
snd_ctl_elem_value_t *ctl; snd_ctl_elem_value_t *ctl;
@ -285,8 +325,12 @@ static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *t
error("snd_config_string_add: %s", snd_strerror(err)); error("snd_config_string_add: %s", snd_strerror(err));
return err; return err;
} }
if (snd_ctl_elem_info_is_tlv_readable(info) && if (snd_ctl_elem_info_is_tlv_readable(info)) {
snd_ctl_elem_info_is_tlv_writable(info)) { err = add_tlv_comments(handle, id, info, comment);
if (err < 0)
return err;
}
if (snd_ctl_elem_info_is_tlv_readable(info)) {
unsigned int tlv[MAX_USER_TLV_SIZE]; unsigned int tlv[MAX_USER_TLV_SIZE];
err = snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)); err = snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv));
if (err >= 0) { if (err >= 0) {
@ -765,6 +809,56 @@ static int is_user_control(snd_config_t *conf)
return 0; return 0;
} }
/*
* get the item type from the given comment config
*/
static int get_comment_type(snd_config_t *n)
{
const char *type;
if (snd_config_get_string(n, &type) < 0)
return -EINVAL;
if (strcmp(type, "BOOLEAN") == 0)
return SND_CTL_ELEM_TYPE_BOOLEAN;
else if (strcmp(type, "INTEGER") == 0)
return SND_CTL_ELEM_TYPE_INTEGER;
else if (strcmp(type, "ENUMERATED") == 0)
return SND_CTL_ELEM_TYPE_ENUMERATED;
else if (strcmp(type, "INTEGER64") == 0)
return SND_CTL_ELEM_TYPE_INTEGER;
else if (strcmp(type, "IEC958") == 0)
return SND_CTL_ELEM_TYPE_IEC958;
else
return -EINVAL;
}
/*
* get the value range from the given comment config
*/
static int get_comment_range(snd_config_t *n, int ctype,
long *imin, long *imax, long *istep)
{
const char *s;
int err;
if (snd_config_get_string(n, &s) < 0)
return -EINVAL;
switch (ctype) {
case SND_CTL_ELEM_TYPE_INTEGER:
err = sscanf(s, "%li - %li (step %li)", imin, imax, istep);
if (err != 3) {
istep = 0;
err = sscanf(s, "%li - %li", imin, imax);
if (err != 2)
return -EINVAL;
}
break;
default:
return -EINVAL;
}
return 0;
}
static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf) static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf)
{ {
snd_ctl_elem_id_t *id; snd_ctl_elem_id_t *id;
@ -781,39 +875,20 @@ static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_co
tlv = NULL; tlv = NULL;
snd_config_for_each(i, next, conf) { snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i); snd_config_t *n = snd_config_iterator_entry(i);
const char *id, *type; const char *id;
if (snd_config_get_id(n, &id) < 0) if (snd_config_get_id(n, &id) < 0)
continue; continue;
if (strcmp(id, "type") == 0) { if (strcmp(id, "type") == 0) {
if ((err = snd_config_get_string(n, &type)) < 0) err = get_comment_type(n);
return -EINVAL; if (err < 0)
if (strcmp(type, "BOOLEAN") == 0) return err;
ctype = SND_CTL_ELEM_TYPE_BOOLEAN; ctype = err;
else if (strcmp(type, "INTEGER") == 0)
ctype = SND_CTL_ELEM_TYPE_INTEGER;
else if (strcmp(type, "IEC958") == 0)
ctype = SND_CTL_ELEM_TYPE_IEC958;
else
return -EINVAL;
continue; continue;
} }
if (strcmp(id, "range") == 0) { if (strcmp(id, "range") == 0) {
const char *s; err = get_comment_range(n, ctype, &imin, &imax, &istep);
if ((err = snd_config_get_string(n, &s)) < 0) if (err < 0)
return -EINVAL; return err;
switch (ctype) {
case SND_CTL_ELEM_TYPE_INTEGER:
err = sscanf(s, "%li - %li (step %li)", &imin, &imax, &istep);
if (err != 3) {
istep = 0;
err = sscanf(s, "%li - %li", &imin, &imax);
if (err != 2)
return -EINVAL;
}
break;
default:
return -EINVAL;
}
continue; continue;
} }
if (strcmp(id, "count") == 0) { if (strcmp(id, "count") == 0) {
@ -867,6 +942,137 @@ static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_co
return snd_ctl_elem_info(handle, info); return snd_ctl_elem_info(handle, info);
} }
/*
* look for a config node with the given item name
*/
static snd_config_t *search_comment_item(snd_config_t *conf, const char *name)
{
snd_config_iterator_t i, next;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, name) == 0)
return n;
}
return NULL;
}
/*
* check whether the config item has the same of compatible type
*/
static int check_comment_type(snd_config_t *conf, int type)
{
snd_config_t *n = search_comment_item(conf, "type");
int ctype;
if (!n)
return 0; /* not defined */
ctype = get_comment_type(n);
if (ctype == type)
return 0;
if ((ctype == SND_CTL_ELEM_TYPE_BOOLEAN ||
ctype == SND_CTL_ELEM_TYPE_INTEGER ||
ctype == SND_CTL_ELEM_TYPE_INTEGER64 ||
ctype == SND_CTL_ELEM_TYPE_ENUMERATED) &&
(type == SND_CTL_ELEM_TYPE_BOOLEAN ||
type == SND_CTL_ELEM_TYPE_INTEGER ||
type == SND_CTL_ELEM_TYPE_INTEGER64 ||
type == SND_CTL_ELEM_TYPE_ENUMERATED))
return 0; /* OK, compatible */
return -EINVAL;
}
/*
* convert from an old value to a new value with the same dB level
*/
static int convert_to_new_db(snd_config_t *value, long omin, long omax,
long nmin, long nmax,
long odbmin, long odbmax,
long ndbmin, long ndbmax)
{
long val;
if (config_integer(value, &val) < 0)
return -EINVAL;
if (val < omin || val > omax)
return -EINVAL;
val = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin;
if (val < ndbmin)
val = ndbmin;
else if (val > ndbmax)
val = ndbmax;
val = ((val - ndbmin) * (nmax - nmin)) / (ndbmax - ndbmin) + nmin;
return snd_config_set_integer(value, val);
}
/*
* compare the current value range with the old range in comments.
* also, if dB information is available, try to compare them.
* if any change occurs, try to keep the same dB level.
*/
static int check_comment_range(snd_ctl_t *handle, snd_config_t *conf,
snd_ctl_elem_info_t *info, snd_config_t *value)
{
snd_config_t *n;
long omin, omax, ostep;
long nmin, nmax;
long odbmin, odbmax;
long ndbmin, ndbmax;
snd_ctl_elem_id_t *id;
n = search_comment_item(conf, "range");
if (!n)
return 0;
if (get_comment_range(n, SND_CTL_ELEM_TYPE_INTEGER,
&omin, &omax, &ostep) < 0)
return 0;
nmin = snd_ctl_elem_info_get_min(info);
nmax = snd_ctl_elem_info_get_max(info);
if (omin != nmin && omax != nmax) {
/* Hey, the range mismatches */
if (!force_restore)
return -EINVAL;
}
if (omin >= omax || nmin >= nmax)
return 0; /* invalid values */
n = search_comment_item(conf, "dbmin");
if (!n)
return 0;
if (config_integer(n, &odbmin) < 0)
return 0;
n = search_comment_item(conf, "dbmax");
if (!n)
return 0;
if (config_integer(n, &odbmax) < 0)
return 0;
if (odbmin >= odbmax)
return 0; /* invalid values */
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_info_get_id(info, id);
if (snd_ctl_get_dB_range(handle, id, &ndbmin, &ndbmax) < 0)
return 0;
if (ndbmin >= ndbmax)
return 0; /* invalid values */
if (omin == nmin && omax == nmax &&
odbmin == ndbmin && odbmax == ndbmax)
return 0; /* OK, identical one */
/* Let's guess the current value from dB range */
if (snd_config_get_type(value) == SND_CONFIG_TYPE_COMPOUND) {
snd_config_iterator_t i, next;
snd_config_for_each(i, next, value) {
snd_config_t *n = snd_config_iterator_entry(i);
convert_to_new_db(n, omin, omax, nmin, nmax,
odbmin, odbmax, ndbmin, ndbmax);
}
} else
convert_to_new_db(value, omin, omax, nmin, nmax,
odbmin, odbmax, ndbmin, ndbmax);
return 0;
}
static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info, static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
snd_ctl_elem_iface_t type, snd_ctl_elem_iface_t type,
snd_config_t *value, snd_config_t *value,
@ -1104,14 +1310,17 @@ static int set_control(snd_ctl_t *handle, snd_config_t *control)
return -ENOENT; return -ENOENT;
} }
#if 0
if (comment) { if (comment) {
check_comment_type(comment, type); if (check_comment_type(comment, type) < 0)
if (type == SND_CTL_ELEM_TYPE_INTEGER || error("incompatible field type for control #%d", numid);
type == SND_CTL_ELEM_TYPE_INTEGER64) if (type == SND_CTL_ELEM_TYPE_INTEGER) {
check_comment_range(comment, info); if (check_comment_range(handle, comment, info, value) < 0) {
error("value range mismatch for control #%d",
numid);
return -EINVAL;
}
}
} }
#endif
if (!snd_ctl_elem_info_is_writable(info)) if (!snd_ctl_elem_info_is_writable(info))
return 0; return 0;

View file

@ -27,7 +27,9 @@ AC_PROG_CC
dnl AC_PROG_CXX dnl AC_PROG_CXX
AC_PROG_INSTALL AC_PROG_INSTALL
AC_PROG_LN_S AC_PROG_LN_S
AM_PATH_ALSA(1.0.12) AM_PATH_ALSA(1.0.15)
AC_CHECK_FUNC(snd_tlv_get_dB_range,,
AC_ERROR([No TLV support code in alsa-lib]))
AC_ARG_ENABLE(alsamixer, AC_ARG_ENABLE(alsamixer,
[ --disable-alsamixer Disable alsamixer compilation], [ --disable-alsamixer Disable alsamixer compilation],