diff --git a/alsa-utils-hg-fixes.diff b/alsa-utils-hg-fixes.diff new file mode 100644 index 0000000..85c82b5 --- /dev/null +++ b/alsa-utils-hg-fixes.diff @@ -0,0 +1,651 @@ +diff -r 3b8e4ee4363e alsactl/state.c +--- a/alsactl/state.c Mon Oct 15 10:36:47 2007 +0200 ++++ b/alsactl/state.c Fri Oct 26 02:01:44 2007 +0200 +@@ -188,6 +188,46 @@ static unsigned int *str_to_tlv(const ch + 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) + { + snd_ctl_elem_value_t *ctl; +@@ -285,8 +325,12 @@ static int get_control(snd_ctl_t *handle + error("snd_config_string_add: %s", snd_strerror(err)); + return err; + } +- if (snd_ctl_elem_info_is_tlv_readable(info) && +- snd_ctl_elem_info_is_tlv_writable(info)) { ++ if (snd_ctl_elem_info_is_tlv_readable(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]; + err = snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)); + if (err >= 0) { +@@ -651,6 +695,7 @@ static int config_bool(snd_config_t *n) + const char *str; + long val; + long long lval; ++ + switch (snd_config_get_type(n)) { + case SND_CONFIG_TYPE_INTEGER: + snd_config_get_integer(n, &val); +@@ -665,6 +710,11 @@ static int config_bool(snd_config_t *n) + case SND_CONFIG_TYPE_STRING: + snd_config_get_string(n, &str); + break; ++ case SND_CONFIG_TYPE_COMPOUND: ++ if (!force_restore) ++ return -1; ++ n = snd_config_iterator_entry(snd_config_iterator_first(n)); ++ return config_bool(n); + default: + return -1; + } +@@ -682,6 +732,7 @@ static int config_enumerated(snd_config_ + long val; + long long lval; + unsigned int idx, items; ++ + switch (snd_config_get_type(n)) { + case SND_CONFIG_TYPE_INTEGER: + snd_config_get_integer(n, &val); +@@ -692,6 +743,11 @@ static int config_enumerated(snd_config_ + case SND_CONFIG_TYPE_STRING: + snd_config_get_string(n, &str); + break; ++ case SND_CONFIG_TYPE_COMPOUND: ++ if (!force_restore) ++ return -1; ++ n = snd_config_iterator_entry(snd_config_iterator_first(n)); ++ return config_enumerated(n, handle, info); + default: + return -1; + } +@@ -708,6 +764,30 @@ static int config_enumerated(snd_config_ + return idx; + } + return -1; ++} ++ ++static int config_integer(snd_config_t *n, long *val) ++{ ++ int err = snd_config_get_integer(n, val); ++ if (err < 0 && force_restore) { ++ if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) ++ return err; ++ n = snd_config_iterator_entry(snd_config_iterator_first(n)); ++ return config_integer(n, val); ++ } ++ return err; ++} ++ ++static int config_integer64(snd_config_t *n, long long *val) ++{ ++ int err = snd_config_get_integer64(n, val); ++ if (err < 0 && force_restore) { ++ if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) ++ return err; ++ n = snd_config_iterator_entry(snd_config_iterator_first(n)); ++ return config_integer64(n, val); ++ } ++ return err; + } + + static int is_user_control(snd_config_t *conf) +@@ -729,6 +809,56 @@ static int is_user_control(snd_config_t + 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) + { + snd_ctl_elem_id_t *id; +@@ -745,39 +875,20 @@ static int add_user_control(snd_ctl_t *h + tlv = NULL; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); +- const char *id, *type; ++ const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "type") == 0) { +- if ((err = snd_config_get_string(n, &type)) < 0) +- return -EINVAL; +- if (strcmp(type, "BOOLEAN") == 0) +- ctype = SND_CTL_ELEM_TYPE_BOOLEAN; +- 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; ++ err = get_comment_type(n); ++ if (err < 0) ++ return err; ++ ctype = err; + continue; + } + if (strcmp(id, "range") == 0) { +- const char *s; +- if ((err = 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; +- } ++ err = get_comment_range(n, ctype, &imin, &imax, &istep); ++ if (err < 0) ++ return err; + continue; + } + if (strcmp(id, "count") == 0) { +@@ -831,6 +942,213 @@ static int add_user_control(snd_ctl_t *h + 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, ++ snd_ctl_elem_iface_t type, ++ snd_config_t *value, ++ snd_ctl_elem_value_t *ctl, int idx) ++{ ++ long val; ++ long long lval; ++ int err; ++ ++ switch (type) { ++ case SND_CTL_ELEM_TYPE_BOOLEAN: ++ val = config_bool(value); ++ if (val >= 0) { ++ snd_ctl_elem_value_set_boolean(ctl, idx, val); ++ return 1; ++ } ++ break; ++ case SND_CTL_ELEM_TYPE_INTEGER: ++ err = config_integer(value, &val); ++ if (err == 0) { ++ snd_ctl_elem_value_set_integer(ctl, idx, val); ++ return 1; ++ } ++ break; ++ case SND_CTL_ELEM_TYPE_INTEGER64: ++ err = config_integer64(value, &lval); ++ if (err == 0) { ++ snd_ctl_elem_value_set_integer64(ctl, idx, lval); ++ return 1; ++ } ++ break; ++ case SND_CTL_ELEM_TYPE_ENUMERATED: ++ val = config_enumerated(value, handle, info); ++ if (val >= 0) { ++ snd_ctl_elem_value_set_enumerated(ctl, idx, val); ++ return 1; ++ } ++ break; ++ case SND_CTL_ELEM_TYPE_BYTES: ++ case SND_CTL_ELEM_TYPE_IEC958: ++ break; ++ default: ++ error("Unknow control type: %d", type); ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int restore_config_value2(snd_ctl_t *handle, snd_ctl_elem_info_t *info, ++ snd_ctl_elem_iface_t type, ++ snd_config_t *value, ++ snd_ctl_elem_value_t *ctl, int idx, ++ unsigned int numid) ++{ ++ int err = restore_config_value(handle, info, type, value, ctl, idx); ++ long val; ++ ++ if (err != 0) ++ return err; ++ switch (type) { ++ case SND_CTL_ELEM_TYPE_BYTES: ++ case SND_CTL_ELEM_TYPE_IEC958: ++ err = snd_config_get_integer(value, &val); ++ if (err < 0 || val < 0 || val > 255) { ++ error("bad control.%d.value.%d content", numid, idx); ++ return force_restore ? 0 : -EINVAL; ++ } ++ snd_ctl_elem_value_set_byte(ctl, idx, val); ++ return 1; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ + static int set_control(snd_ctl_t *handle, snd_config_t *control) + { + snd_ctl_elem_value_t *ctl; +@@ -852,8 +1170,6 @@ static int set_control(snd_ctl_t *handle + long index = -1; + snd_config_t *value = NULL; + snd_config_t *comment = NULL; +- long val; +- long long lval; + unsigned int idx; + int err; + char *set; +@@ -994,56 +1310,28 @@ static int set_control(snd_ctl_t *handle + return -ENOENT; + } + +-#if 0 + if (comment) { +- check_comment_type(comment, type); +- if (type == SND_CTL_ELEM_TYPE_INTEGER || +- type == SND_CTL_ELEM_TYPE_INTEGER64) +- check_comment_range(comment, info); ++ if (check_comment_type(comment, type) < 0) ++ error("incompatible field type for control #%d", numid); ++ if (type == SND_CTL_ELEM_TYPE_INTEGER) { ++ 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)) + return 0; + snd_ctl_elem_value_set_numid(ctl, numid1); + + if (count == 1) { +- switch (type) { +- case SND_CTL_ELEM_TYPE_BOOLEAN: +- val = config_bool(value); +- if (val >= 0) { +- snd_ctl_elem_value_set_boolean(ctl, 0, val); +- goto _ok; +- } +- break; +- case SND_CTL_ELEM_TYPE_INTEGER: +- err = snd_config_get_integer(value, &val); +- if (err == 0) { +- snd_ctl_elem_value_set_integer(ctl, 0, val); +- goto _ok; +- } +- break; +- case SND_CTL_ELEM_TYPE_INTEGER64: +- err = snd_config_get_integer64(value, &lval); +- if (err == 0) { +- snd_ctl_elem_value_set_integer64(ctl, 0, lval); +- goto _ok; +- } +- break; +- case SND_CTL_ELEM_TYPE_ENUMERATED: +- val = config_enumerated(value, handle, info); +- if (val >= 0) { +- snd_ctl_elem_value_set_enumerated(ctl, 0, val); +- goto _ok; +- } +- break; +- case SND_CTL_ELEM_TYPE_BYTES: +- case SND_CTL_ELEM_TYPE_IEC958: +- break; +- default: +- error("Unknow control type: %d", type); +- return -EINVAL; +- } ++ err = restore_config_value(handle, info, type, value, ctl, 0); ++ if (err < 0) ++ return err; ++ if (err > 0) ++ goto _ok; + } + switch (type) { + case SND_CTL_ELEM_TYPE_BYTES: +@@ -1080,8 +1368,17 @@ static int set_control(snd_ctl_t *handle + break; + } + if (snd_config_get_type(value) != SND_CONFIG_TYPE_COMPOUND) { +- error("bad control.%d.value type", numid); +- return -EINVAL; ++ if (!force_restore) { ++ error("bad control.%d.value type", numid); ++ return -EINVAL; ++ } ++ for (idx = 0; idx < count; ++idx) { ++ err = restore_config_value2(handle, info, type, value, ++ ctl, idx, numid); ++ if (err < 0) ++ return err; ++ } ++ goto _ok; + } + + set = (char*) alloca(count); +@@ -1095,59 +1392,22 @@ static int set_control(snd_ctl_t *handle + if (idx < 0 || idx >= count || + set[idx]) { + error("bad control.%d.value index", numid); +- return -EINVAL; ++ if (!force_restore) ++ return -EINVAL; ++ continue; + } +- switch (type) { +- case SND_CTL_ELEM_TYPE_BOOLEAN: +- val = config_bool(n); +- if (val < 0) { +- error("bad control.%d.value.%d content", numid, idx); +- return -EINVAL; +- } +- snd_ctl_elem_value_set_boolean(ctl, idx, val); +- break; +- case SND_CTL_ELEM_TYPE_INTEGER: +- err = snd_config_get_integer(n, &val); +- if (err < 0) { +- error("bad control.%d.value.%d content", numid, idx); +- return -EINVAL; +- } +- snd_ctl_elem_value_set_integer(ctl, idx, val); +- break; +- case SND_CTL_ELEM_TYPE_INTEGER64: +- err = snd_config_get_integer64(n, &lval); +- if (err < 0) { +- error("bad control.%d.value.%d content", numid, idx); +- return -EINVAL; +- } +- snd_ctl_elem_value_set_integer64(ctl, idx, lval); +- break; +- case SND_CTL_ELEM_TYPE_ENUMERATED: +- val = config_enumerated(n, handle, info); +- if (val < 0) { +- error("bad control.%d.value.%d content", numid, idx); +- return -EINVAL; +- } +- snd_ctl_elem_value_set_enumerated(ctl, idx, val); +- break; +- case SND_CTL_ELEM_TYPE_BYTES: +- case SND_CTL_ELEM_TYPE_IEC958: +- err = snd_config_get_integer(n, &val); +- if (err < 0 || val < 0 || val > 255) { +- error("bad control.%d.value.%d content", numid, idx); +- return -EINVAL; +- } +- snd_ctl_elem_value_set_byte(ctl, idx, val); +- break; +- default: +- break; +- } +- set[idx] = 1; ++ err = restore_config_value2(handle, info, type, value, ++ ctl, idx, numid); ++ if (err < 0) ++ return err; ++ if (err > 0) ++ set[idx] = 1; + } + for (idx = 0; idx < count; ++idx) { + if (!set[idx]) { + error("control.%d.value.%d is not specified", numid, idx); +- return -EINVAL; ++ if (!force_restore) ++ return -EINVAL; + } + } + +diff -r 3b8e4ee4363e configure.in +--- a/configure.in Mon Oct 15 10:36:47 2007 +0200 ++++ b/configure.in Fri Oct 26 02:01:44 2007 +0200 +@@ -27,7 +27,9 @@ dnl AC_PROG_CXX + dnl AC_PROG_CXX + AC_PROG_INSTALL + 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, + [ --disable-alsamixer Disable alsamixer compilation], +diff -r 3b8e4ee4363e seq/aseqnet/aseqnet.c +--- a/seq/aseqnet/aseqnet.c Mon Oct 15 10:36:47 2007 +0200 ++++ b/seq/aseqnet/aseqnet.c Fri Oct 26 02:01:44 2007 +0200 +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + #include "aconfig.h" + #include "gettext.h" + diff --git a/alsa-utils.changes b/alsa-utils.changes index 6eab5fb..d76dd7e 100644 --- a/alsa-utils.changes +++ b/alsa-utils.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Mon Oct 29 11:27:18 CET 2007 - tiwai@suse.de + +- make alsactl more robust + * restore the volume values with channel changes + * restore from dB level if available + ------------------------------------------------------------------- Tue Oct 16 15:02:20 CEST 2007 - tiwai@suse.de diff --git a/alsa-utils.spec b/alsa-utils.spec index 8e36af0..f854748 100644 --- a/alsa-utils.spec +++ b/alsa-utils.spec @@ -20,9 +20,9 @@ Requires: dialog pciutils AutoReqProv: on Summary: Advanced Linux Sound Architecture Utilities Version: 1.0.15 -Release: 1 +Release: 3 Source: ftp://ftp.alsa-project.org/pub/util/alsa-utils-%{package_version}.tar.bz2 -# Patch: alsa-utils-hg-fixes.diff +Patch: alsa-utils-hg-fixes.diff Url: http://www.alsa-project.org/ BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -39,7 +39,7 @@ Authors: %prep %setup -q -n %{name}-%{package_version} -# %patch -p1 +%patch -p1 mv alsamixer/README alsamixer/README-alsamixer %{?suse_update_config:%{suse_update_config -f .}} @@ -68,6 +68,10 @@ make DESTDIR=$RPM_BUILD_ROOT install %{_datadir}/sounds/alsa %{_datadir}/alsa %changelog +* Mon Oct 29 2007 - tiwai@suse.de +- make alsactl more robust + * restore the volume values with channel changes + * restore from dB level if available * Tue Oct 16 2007 - tiwai@suse.de - updated to version 1.0.15-final * including all previous patches