At Fri, 9 Mar 2012 18:06:17 +0800, Adam Lee wrote: > > On Fri, Mar 09, 2012 at 10:30:27AM +0100, Takashi Iwai wrote: > > At Fri, 9 Mar 2012 17:07:17 +0800, > > Adam Lee wrote: > > > > > > On Fri, Mar 09, 2012 at 07:40:27AM +0100, Takashi Iwai wrote: > > > > At Fri, 9 Mar 2012 12:22:47 +0800, > > > > Adam Lee wrote: > > > > > > > > > > Add Vincent in cc, because conky read amixer's result. > > > > > > > > > > On Thu, Mar 08, 2012 at 05:45:14PM +0100, Takashi Iwai wrote: > > > > > > > * Adam Lee <adam8...@gmail.com> [2012-03-08 20:36 +0800]: > > > > > > > > > > > > > > Package: alsa-utils > > > > > > > Version: 1.0.25-1 > > > > > > > Severity: important > > > > > > > > > > > > > > > > > > > > > db is not linear, but amixer believe it is. > > > > > > > > > > > > > > "amixer get Master" says "Limits: Playback 0 - 74", then > > > > > > > everytime I run > > > > > > > "amixer -q sset Master 10%-", there is 8db dec. > > > > > > > > > > > > > > For example, at first Master is 100% and 0db, both alsamixer and > > > > > > > amixer > > > > > > > think it is, and after I run "amixer -q sset Master 10%-", both > > > > > > > alsamixer and amixer says Master is -8.00db, but alsamixer says > > > > > > > it is > > > > > > > 72%, amixer says it is 89%. > > > > > > > > > > > > > > alsamixer is right, amixer calc and set wrongly. > > > > > > > > > > > > No, both are correct. You are dreaming too much on the world > > > > > > unified > > > > > > percentage representation :) > > > > > > > > > > > > The percentage in amixer has nothing to do with dB level. > > > > > > It's just the percentage of the raw value range of that mixer > > > > > > element. Thus showing 89% is correct. It's 10% down from 100% > > > > > > (1% is because of the resolution of the raw values). > > > > > > > > > > > > Now, alsamixer shows the percentage in a different way. It's > > > > > > explained well in the source code (alsamixer/volume_mapping.c), but > > > > > > not mentioned in the man page, unfortunately. > > > > > > > > > > > > * The mapping is designed so that the position in the interval is > > > > > > proportional > > > > > > * to the volume as a human ear would perceive it (i.e., the > > > > > > position is the > > > > > > * cubic root of the linear sample multiplication factor). For > > > > > > controls with > > > > > > * a small range (24 dB or less), the mapping is linear in the dB > > > > > > values so > > > > > > * that each step has the same size visually. Only for controls > > > > > > without dB > > > > > > * information, a linear mapping of the hardware volume register > > > > > > values is used > > > > > > * (this is the same algorithm as used in the old alsamixer). > > > > > > > > > > > > The percentage representation in alsamixer corresponds to this > > > > > > mapping, thus it's neither dB nor linear percent. > > > > > > > > > > > > > > > > Hi, Takashi > > > > > > > > > > Thank you for replying. But I still insist this is a bug. Three > > > > > questions: > > > > > > > > > > 1, several months ago, it's OK, both amixer and alsamixer use the > > > > > human > > > > > mapping(0-10% and 90%-100% are the same change by a human ear), why > > > > > not > > > > > now? > > > > > > > > amixer hasn't been changed until yet. It handles either in raw values > > > > or in dB. No human-ear mapping at all. It's never changed since > > > > years, and won't be changed. If the volume mapping would be > > > > implemented to amixer in future, it must be only optional. > > > > > > > > Only the recent alsamixer introduced the volume mapping to visualize > > > > the volumes reasonably. > > > > > > > > > > OK, thank you. Maybe an optional will make everyone happy. > > > > > > > > 2, conky(Vincent, I mean ${mixer}), some other software, lot of user's > > > > > scripts use amixer to set or get volume, expecting the human mapping, > > > > > why change the behavior? > > > > > > > > You must be smoking something bad. The behavior of amixer hasn't been > > > > changed. > > > > > > > > > > I figured out a reason probably, when the limits range is wide, like > > > 0-65536, amixer's mapping and alsamixer's human-ear mapping are close. > > > > > > If I remember right, my hardware's limits was 0-65536, but it becomes > > > 0-74 after I run "alsactl init", but unfortunately, I don't know how to > > > modify it back. > > > > > > > > 3, alsamixer and amixer use the same dB value, why there is difference > > > > > in percentage? If alsa-utils developer think the human mapping sucks, > > > > > why you guys still use it in alsamixer? There is no "both correct", > > > > > the > > > > > difference confuses user... > > > > > > > > That's true. alsamixer should have stopped showing the stupid > > > > percentage. > > > > > > > > The biggest understand is that people (including you) think there is > > > > an absolutely perfect percentage definition for the sound level. It's > > > > an illusion. > > > > > > > > > > I don't expect an absolutely perfect percentage definition. I want a > > > human-ear mapping, which alsamixer does well, 100% is about as ten times > > > loud as 10%. > > > > How did you measure _quantitatively_ it's exactly ten times louder? > > And you think 50% is ten times loud as 5% volume, 10% is ten times > > loud as 1%? Things aren't so easy, unfortunately. > > > > > But amixer doesn't work like that, amixer's 100% is about > > > as *one hundred times* as amixer's 10%. > > > > Yes, this is what's amixer expected to behave. It's a value just > > representing the percentage of a "raw value" of the mixer element. > > It never says it's corresponding to any practical volume. This is the > > misunderstanding first of all. > > > > For example, I guess in your 64k case, the raw value is even not in dB > > unit but it's a linear volume. amixer doesn't care. 10% means only > > the 6553 of 65536. That's all. So simple. > > (In addition, amixer can handle dB, e.g. amixer set Master +10dB or > > such. But it's not suitable for percentage unit because dB level > > can't be represented in the absolute percent volume -- what is 10% in > > dB?) > > > > > Maybe it is beautiful, and alsamixer's human-ear mapping is stupid in > > > the sound science universe. But as a common user, I don't think so. And > > > I don't know how you guys put up with it :( > > > > No, you misunderstand what I wrote. alsamixer's mapping is really an > > improvement. That's why it was implemented recently. But if it shows > > a different number, user may wonder, just like you did. If you didn't > > see a number, you didn't notice the difference :) > > > > But amixer wasn't changed. amixer is a tool to manipulate raw > > values. Of course, it'd be also nice to implement the same percentage > > expression, but as already mentioned, it should be activated only via > > an option. The default behavior of amixer must not be changed for > > compatibility reason. > > > > OK. > > Looking forward to the option, if your time and other capacity allow :)
The implementation isn't too hard. I wrote a quick patch now as below. It became bigger than I thought since it contains many clean-ups that are needed to adapt the mapped volume. With the patch, amixer shows and reads the percent value with -M option just like in alsamixer. Takashi --- diff --git a/amixer/Makefile.am b/amixer/Makefile.am index fcd0e81..ba67a57 100644 --- a/amixer/Makefile.am +++ b/amixer/Makefile.am @@ -4,7 +4,7 @@ LDADD = -lm # CFLAGS += -g -Wall bin_PROGRAMS = amixer -amixer_SOURCES = amixer.c +amixer_SOURCES = amixer.c ../alsamixer/volume_mapping.c noinst_HEADERS = amixer.h man_MANS = amixer.1 EXTRA_DIST = amixer.1 diff --git a/amixer/amixer.c b/amixer/amixer.c index 9d2855d..a0a7387 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -29,7 +29,9 @@ #include <assert.h> #include <alsa/asoundlib.h> #include <sys/poll.h> +#include <stdint.h> #include "amixer.h" +#include "../alsamixer/volume_mapping.h" #define LEVEL_BASIC (1<<0) #define LEVEL_INACTIVE (1<<1) @@ -68,6 +70,8 @@ static int help(void) printf(" -i,--inactive show also inactive controls\n"); printf(" -a,--abstract L select abstraction level (none or basic)\n"); printf(" -s,--stdin Read and execute commands from stdin sequentially\n"); + printf(" -R,--raw-volume Use the raw value (default)\n"); + printf(" -M,--mapped-volume Use the mapped volume\n"); printf("\nAvailable commands:\n"); printf(" scontrols show all mixer simple controls\n"); printf(" scontents show contents of all mixer simple controls (default command)\n"); @@ -187,9 +191,9 @@ static int convert_db_range(int val, int omin, int omax, int nmin, int nmax) /* Fuction to convert from volume to percentage. val = volume */ -static int convert_prange(int val, int min, int max) +static int convert_prange(long val, long min, long max) { - int range = max - min; + long range = max - min; int tmp; if (range == 0) @@ -204,29 +208,6 @@ static int convert_prange(int val, int min, int max) #define convert_prange1(val, min, max) \ ceil((val) * ((max) - (min)) * 0.01 + (min)) -static const char *get_percent(int val, int min, int max) -{ - static char str[32]; - int p; - - p = convert_prange(val, min, max); - sprintf(str, "%i [%i%%]", val, p); - return str; -} - -#if 0 -static const char *get_percent1(int val, int min, int max, int min_dB, int max_dB) -{ - static char str[32]; - int p, db; - - p = convert_prange(val, min, max); - db = convert_db_range(val, min, max, min_dB, max_dB); - sprintf(str, "%i [%i%%] [%i.%02idB]", val, p, db / 100, abs(db % 100)); - return str; -} -#endif - static long get_integer(char **ptr, long min, long max) { long val = min; @@ -288,26 +269,83 @@ struct volume_ops { int (*get)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, long *value); int (*set)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, - long value); + long value, int dir); }; -enum { VOL_RAW, VOL_DB }; +enum { VOL_RAW, VOL_DB, VOL_MAP }; struct volume_ops_set { int (*has_volume)(snd_mixer_elem_t *elem); - struct volume_ops v[2]; + struct volume_ops v[3]; }; static int set_playback_dB(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t c, long value) + snd_mixer_selem_channel_id_t c, long value, int dir) { - return snd_mixer_selem_set_playback_dB(elem, c, value, 0); + return snd_mixer_selem_set_playback_dB(elem, c, value, dir); } static int set_capture_dB(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t c, long value) + snd_mixer_selem_channel_id_t c, long value, int dir) +{ + return snd_mixer_selem_set_capture_dB(elem, c, value, dir); +} + +static int set_playback_raw_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long value, int dir) +{ + return snd_mixer_selem_set_playback_volume(elem, c, value); +} + +static int set_capture_raw_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long value, int dir) +{ + return snd_mixer_selem_set_capture_volume(elem, c, value); +} + +/* FIXME: normalize to int32 space to be compatible with other types */ +#define MAP_VOL_RES (INT32_MAX / 100) + +static int get_mapped_volume_range(snd_mixer_elem_t *elem, + long *pmin, long *pmax) +{ + *pmin = 0; + *pmax = MAP_VOL_RES; + return 0; +} + +static int get_playback_mapped_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long *value) +{ + *value = (rint)(get_normalized_playback_volume(elem, c) * MAP_VOL_RES); + return 0; +} + +static int set_playback_mapped_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long value, int dir) { - return snd_mixer_selem_set_capture_dB(elem, c, value, 0); + return set_normalized_playback_volume(elem, c, + (double)value / MAP_VOL_RES, dir); +} + +static int get_capture_mapped_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long *value) +{ + *value = (rint)(get_normalized_capture_volume(elem, c) * MAP_VOL_RES); + return 0; +} + +static int set_capture_mapped_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, + long value, int dir) +{ + return set_normalized_capture_volume(elem, c, + (double)value / MAP_VOL_RES, dir); } static const struct volume_ops_set vol_ops[2] = { @@ -315,29 +353,42 @@ static const struct volume_ops_set vol_ops[2] = { .has_volume = snd_mixer_selem_has_playback_volume, .v = {{ snd_mixer_selem_get_playback_volume_range, snd_mixer_selem_get_playback_volume, - snd_mixer_selem_set_playback_volume }, + set_playback_raw_volume }, { snd_mixer_selem_get_playback_dB_range, snd_mixer_selem_get_playback_dB, - set_playback_dB }}, + set_playback_dB }, + { get_mapped_volume_range, + get_playback_mapped_volume, + set_playback_mapped_volume }, + }, }, { .has_volume = snd_mixer_selem_has_capture_volume, .v = {{ snd_mixer_selem_get_capture_volume_range, snd_mixer_selem_get_capture_volume, - snd_mixer_selem_set_capture_volume }, + set_capture_raw_volume }, { snd_mixer_selem_get_capture_dB_range, snd_mixer_selem_get_capture_dB, - set_capture_dB }}, + set_capture_dB }, + { get_mapped_volume_range, + get_capture_mapped_volume, + set_capture_mapped_volume }, + }, }, }; +static int std_vol_type = VOL_RAW; + static int set_volume_simple(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t chn, char **ptr, int dir) { long val, orig, pmin, pmax; char *p = *ptr, *s; - int invalid = 0, err = 0, vol_type = VOL_RAW; + int invalid = 0, percent = 0, err = 0; + int vol_type = std_vol_type; + double scale = 1.0; + int correct = 0; if (! vol_ops[dir].has_volume(elem)) invalid = 1; @@ -347,10 +398,6 @@ static int set_volume_simple(snd_mixer_elem_t *elem, if (*p == '\0' || (!isdigit(*p) && *p != '-')) goto skip; - if (! invalid && - vol_ops[dir].v[VOL_RAW].get_range(elem, &pmin, &pmax) < 0) - invalid = 1; - s = p; val = strtol(s, &p, 10); if (*p == '.') { @@ -358,32 +405,37 @@ static int set_volume_simple(snd_mixer_elem_t *elem, strtol(p, &p, 10); } if (*p == '%') { - if (! invalid) - val = (long)convert_prange1(strtod(s, NULL), pmin, pmax); + percent = 1; p++; } else if (p[0] == 'd' && p[1] == 'B') { - if (! invalid) { - val = (long)(strtod(s, NULL) * 100.0); - vol_type = VOL_DB; - if (vol_ops[dir].v[vol_type].get_range(elem, &pmin, &pmax) < 0) - invalid = 1; - } + vol_type = VOL_DB; p += 2; - } + scale = 100; + } else + vol_type = VOL_RAW; + + val = (long)(strtod(s, NULL) * scale); + if (vol_ops[dir].v[vol_type].get_range(elem, &pmin, &pmax) < 0) + invalid = 1; + if (percent) + val = (long)convert_prange1(val, pmin, pmax); if (*p == '+' || *p == '-') { if (! invalid) { if (vol_ops[dir].v[vol_type].get(elem, chn, &orig) < 0) invalid = 1; - if (*p == '+') + if (*p == '+') { val = orig + val; - else + correct = -1; + } else { val = orig - val; + correct = 1; + } } p++; } if (! invalid) { val = check_range(val, pmin, pmax); - err = vol_ops[dir].v[vol_type].set(elem, chn, val); + err = vol_ops[dir].v[vol_type].set(elem, chn, val, correct); } skip: if (*p == ',') @@ -711,15 +763,33 @@ static int controls(int level) return 0; } +static void show_selem_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t chn, int dir, + long min, long max) +{ + long raw, val; + vol_ops[dir].v[VOL_RAW].get(elem, chn, &raw); + if (std_vol_type == VOL_RAW) + val = convert_prange(raw, min, max); + else { + vol_ops[dir].v[std_vol_type].get(elem, chn, &val); + val = convert_prange(val, 0, MAP_VOL_RES); + } + printf(" %li [%li%%]", raw, val); + if (!vol_ops[dir].v[VOL_DB].get(elem, chn, &val)) { + printf(" ["); + print_dB(val); + printf("]"); + } +} + static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char *space, int level) { snd_mixer_selem_channel_id_t chn; long pmin = 0, pmax = 0; long cmin = 0, cmax = 0; - long pvol, cvol; int psw, csw; int pmono, cmono, mono_ok = 0; - long db; snd_mixer_elem_t *elem; elem = snd_mixer_find_selem(handle, id); @@ -868,13 +938,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char mono_ok = 1; } if (snd_mixer_selem_has_common_volume(elem)) { - snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &pvol); - printf(" %s", get_percent(pvol, pmin, pmax)); - if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, SND_MIXER_SCHN_MONO, 0, pmin, pmax); } if (snd_mixer_selem_has_common_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw); @@ -891,13 +955,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char if (snd_mixer_selem_has_playback_volume(elem)) { printf(" Playback"); title = 1; - snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &pvol); - printf(" %s", get_percent(pvol, pmin, pmax)); - if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, SND_MIXER_SCHN_MONO, 0, pmin, pmax); } } if (!snd_mixer_selem_has_common_switch(elem)) { @@ -919,13 +977,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char if (snd_mixer_selem_has_capture_volume(elem)) { printf(" Capture"); title = 1; - snd_mixer_selem_get_capture_volume(elem, SND_MIXER_SCHN_MONO, &cvol); - printf(" %s", get_percent(cvol, cmin, cmax)); - if (!snd_mixer_selem_get_capture_dB(elem, SND_MIXER_SCHN_MONO, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, SND_MIXER_SCHN_MONO, 1, cmin, cmax); } } if (!snd_mixer_selem_has_common_switch(elem)) { @@ -946,13 +998,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char continue; printf("%s%s:", space, snd_mixer_selem_channel_name(chn)); if (!pmono && !cmono && snd_mixer_selem_has_common_volume(elem)) { - snd_mixer_selem_get_playback_volume(elem, chn, &pvol); - printf(" %s", get_percent(pvol, pmin, pmax)); - if (!snd_mixer_selem_get_playback_dB(elem, chn, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, chn, 0, pmin, pmax); } if (!pmono && !cmono && snd_mixer_selem_has_common_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &psw); @@ -964,13 +1010,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char if (snd_mixer_selem_has_playback_volume(elem)) { printf(" Playback"); title = 1; - snd_mixer_selem_get_playback_volume(elem, chn, &pvol); - printf(" %s", get_percent(pvol, pmin, pmax)); - if (!snd_mixer_selem_get_playback_dB(elem, chn, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, chn, 0, pmin, pmax); } } if (!snd_mixer_selem_has_common_switch(elem)) { @@ -988,13 +1028,7 @@ static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char if (snd_mixer_selem_has_capture_volume(elem)) { printf(" Capture"); title = 1; - snd_mixer_selem_get_capture_volume(elem, chn, &cvol); - printf(" %s", get_percent(cvol, cmin, cmax)); - if (!snd_mixer_selem_get_capture_dB(elem, chn, &db)) { - printf(" ["); - print_dB(db); - printf("]"); - } + show_selem_volume(elem, chn, 1, cmin, cmax); } } if (!snd_mixer_selem_has_common_switch(elem)) { @@ -1927,6 +1961,8 @@ int main(int argc, char *argv[]) {"version", 0, NULL, 'v'}, {"abstract", 1, NULL, 'a'}, {"stdin", 0, NULL, 's'}, + {"raw-volume", 0, NULL, 'R'}, + {"mapped-volume", 0, NULL, 'M'}, {NULL, 0, NULL, 0}, }; @@ -1934,7 +1970,7 @@ int main(int argc, char *argv[]) while (1) { int c; - if ((c = getopt_long(argc, argv, "hc:D:qidnva:s", long_option, NULL)) < 0) + if ((c = getopt_long(argc, argv, "hc:D:qidnva:sRM", long_option, NULL)) < 0) break; switch (c) { case 'h': @@ -1987,6 +2023,12 @@ int main(int argc, char *argv[]) case 's': read_stdin = 1; break; + case 'R': + std_vol_type = VOL_RAW; + break; + case 'M': + std_vol_type = VOL_MAP; + break; default: fprintf(stderr, "Invalid switch or option needs an argument.\n"); morehelp++; -- To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org with a subject of "unsubscribe". Trouble? Contact listmas...@lists.debian.org