From ae9ddeb63443cc2c46e0f0b915466cca0f800372 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 25 Aug 2006 12:00:14 +0200 Subject: [PATCH] Add handling of dB to amixer Added the dB value handling to amixer sset command. Also, simplify the parser code. Updated man page for dB suffix and some examples. --- amixer/amixer.1 | 29 +++++- amixer/amixer.c | 257 ++++++++++++++++++++++++++++-------------------- 2 files changed, 175 insertions(+), 111 deletions(-) diff --git a/amixer/amixer.1 b/amixer/amixer.1 index e8fc63b..a8d95e1 100644 --- a/amixer/amixer.1 +++ b/amixer/amixer.1 @@ -33,14 +33,23 @@ Shows a complete list of simple mixer controls with their contents. .TP \fIset\fP or \fIsset\fP <\fISCONTROL\fP> <\fIPARAMETER\fP> ... Sets the simple mixer control contents. The parameter can be the volume -either as a percentage from 0% to 100% or an exact hardware value. -The parameters \fIcap, nocap, mute, unmute, toggle\fP are used to -change capture (recording) and muting for the group specified. -The parameters \fIfront, rear, center, woofer\fP are used to specify -channels to be changed. When plus(+) or minus(\-) letter is appended after +either as a percentage from 0% to 100% with \fI%\fP suffix, +a dB gain with \fIdB\fP suffix (like -12.5dB), or an exact hardware value. +The dB gain can be used only for the mixer elements with available +dB information. +When plus(+) or minus(\-) letter is appended after volume value, the volume is incremented or decremented from the current value, respectively. +The parameters \fIcap, nocap, mute, unmute, toggle\fP are used to +change capture (recording) and muting for the group specified. + +The optional modifiers can be put as extra parameters to specify +the stream direction or channels to apply. +The modifiers \fIplayback\fP and \fIcapture\fP specify the stream, +and the modifiers \fIfront, rear, center, woofer\fP are used to specify +channels to be changed. + A simple mixer control must be specified. Only one device can be controlled at a time. @@ -107,6 +116,16 @@ will set the second soundcard's left line input volume to 80% and right line input to 40%, unmute it, and select it as a source for capture (recording).\fR +.TP +\fBamixer \-c 1 \-\- sset Master playback -20dB\fR +will set the master volume of the second card to -20dB. If the master +has multiple channels, all channels are set to the same value. + +.TP +\fBamixer \-c 1 set PCM 2dB+\fR +will increase the PCM volume of the second card with 2dB. When both +playback and capture volumes exist, this is applied to both volumes. + .TP \fBamixer \-c 2 cset iface=MIXER,name='Line Playback Volume",index=1 40%\fR will set the third soundcard's second line playback volume(s) to 40% diff --git a/amixer/amixer.c b/amixer/amixer.c index 921a5e0..691c1c5 100644 --- a/amixer/amixer.c +++ b/amixer/amixer.c @@ -161,17 +161,8 @@ static const char *control_access(snd_ctl_elem_info_t *info) return result; } -static int check_range(int val, int min, int max) -{ - if (no_check) - return val; - if (val < min) - return min; - if (val > max) - return max; - return val; -} - +#define check_range(val, min, max) \ + (no_check ? (val) : ((val < min) ? (min) : (val > max) ? (max) : (val))) #if 0 static int convert_range(int val, int omin, int omax, int nmin, int nmax) { @@ -210,17 +201,8 @@ static int convert_prange(int val, int min, int max) /* Function to convert from percentage to volume. val = percentage */ -static int convert_prange1(int val, int min, int max) -{ - int range = max - min; - int tmp; - - if (range == 0) - return 0; - - tmp = rint((double)range * ((double)val*.01)) + min; - return tmp; -} +#define convert_prange1(val, min, max) \ + ceil((val) * ((max) - (min)) * 0.01 + (min)) static const char *get_percent(int val, int min, int max) { @@ -247,90 +229,167 @@ static const char *get_percent1(int val, int min, int max, int min_dB, int max_d static long get_integer(char **ptr, long min, long max) { - int tmp, tmp1, tmp2; + long val = min; + char *p = *ptr, *s; - if (**ptr == ':') - (*ptr)++; - if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) - return min; - tmp = strtol(*ptr, ptr, 10); - tmp1 = tmp; - tmp2 = 0; - if (**ptr == '.') { - (*ptr)++; - tmp2 = strtol(*ptr, ptr, 10); + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); } - if (**ptr == '%') { - tmp1 = convert_prange1(tmp, min, max); - (*ptr)++; + if (*p == '%') { + val = (long)convert_prange1(strtod(s, NULL), min, max); + p++; } - tmp1 = check_range(tmp1, min, max); - if (**ptr == ',') - (*ptr)++; - return tmp1; + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; } static long get_integer64(char **ptr, long long min, long long max) { - long long tmp, tmp1, tmp2; + long long val = min; + char *p = *ptr, *s; - if (**ptr == ':') - (*ptr)++; - if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) - return min; - tmp = strtol(*ptr, ptr, 10); - tmp1 = tmp; - tmp2 = 0; - if (**ptr == '.') { - (*ptr)++; - tmp2 = strtol(*ptr, ptr, 10); + if (*p == ':') + p++; + if (*p == '\0' || (!isdigit(*p) && *p != '-')) + goto out; + + s = p; + val = strtol(s, &p, 10); + if (*p == '.') { + p++; + strtol(p, &p, 10); } - if (**ptr == '%') { - tmp1 = convert_prange1(tmp, min, max); - (*ptr)++; + if (*p == '%') { + val = (long long)convert_prange1(strtod(s, NULL), min, max); + p++; } - tmp1 = check_range(tmp1, min, max); - if (**ptr == ',') - (*ptr)++; - return tmp1; + val = check_range(val, min, max); + if (*p == ',') + p++; + out: + *ptr = p; + return val; } -static int get_volume_simple(char **ptr, int min, int max, int orig) -{ - int tmp, tmp1, tmp2; +struct volume_ops { + int (*get_range)(snd_mixer_elem_t *elem, long *min, long *max); + 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); +}; + +enum { VOL_RAW, VOL_DB }; - if (**ptr == ':') - (*ptr)++; - if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) - return min; - tmp = atoi(*ptr); - if (**ptr == '-') - (*ptr)++; - while (isdigit(**ptr)) - (*ptr)++; - tmp1 = tmp; - tmp2 = 0; - if (**ptr == '.') { - (*ptr)++; - tmp2 = atoi(*ptr); - while (isdigit(**ptr)) - (*ptr)++; +struct volume_ops_set { + int (*has_volume)(snd_mixer_elem_t *elem); + struct volume_ops v[2]; +}; + +static int set_playback_dB(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, long value) +{ + return snd_mixer_selem_set_playback_dB(elem, c, value, 0); +} + +static int set_capture_dB(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t c, long value) +{ + return snd_mixer_selem_set_capture_dB(elem, c, value, 0); +} + +static 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 }, + { snd_mixer_selem_get_playback_dB_range, + snd_mixer_selem_get_playback_dB, + set_playback_dB }}, + }, + { + .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 }, + { snd_mixer_selem_get_capture_dB_range, + snd_mixer_selem_get_capture_dB, + set_capture_dB }}, + }, +}; + +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, vol_type = VOL_RAW; + + if (! vol_ops[dir].has_volume(elem)) + invalid = 1; + + if (*p == ':') + p++; + 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 == '.') { + p++; + strtol(p, &p, 10); } - if (**ptr == '%') { - tmp1 = convert_prange1(tmp, min, max); - (*ptr)++; + if (*p == '%') { + if (! invalid) + val = (long)convert_prange1(strtod(s, NULL), pmin, pmax); + 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; + } + p += 2; } - if (**ptr == '+') { - tmp1 = orig + tmp1; - (*ptr)++; - } else if (**ptr == '-') { - tmp1 = orig - tmp1; - (*ptr)++; + if (*p == '+' || *p == '-') { + if (! invalid) { + if (vol_ops[dir].v[vol_type].get(elem, chn, &orig) < 0) + invalid = 1; + if (*p == '+') + val = orig + val; + else + val = orig - val; + } + p++; } - tmp1 = check_range(tmp1, min, max); - if (**ptr == ',') - (*ptr)++; - return tmp1; + if (! invalid) { + val = check_range(val, pmin, pmax); + vol_ops[dir].v[vol_type].set(elem, chn, val); + } + skip: + if (*p == ',') + p++; + *ptr = p; + return 0; } static int get_bool_simple(char **ptr, char *str, int invert, int orig) @@ -1283,7 +1342,6 @@ static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) snd_mixer_selem_channel_id_t chn; unsigned int channels = ~0U; unsigned int dir = 3, okflag = 3; - long pmin, pmax, cmin, cmax; static snd_mixer_t *handle = NULL; snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid; @@ -1337,8 +1395,6 @@ static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) } if (roflag) goto __skip_write; - snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax); - snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax); for (idx = 1; idx < argc; idx++) { char *ptr = argv[idx], *optr; int multi, firstchn = 1; @@ -1353,7 +1409,6 @@ static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { char *sptr = NULL; int ival; - long lval; if (!(channels & (1 << chn))) continue; @@ -1390,12 +1445,7 @@ static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) } simple_skip_word(&ptr, "toggle"); } else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') { - if (snd_mixer_selem_has_playback_volume(elem)) { - snd_mixer_selem_get_playback_volume(elem, chn, &lval); - snd_mixer_selem_set_playback_volume(elem, chn, get_volume_simple(&ptr, pmin, pmax, lval)); - } else { - get_volume_simple(&ptr, 0, 100, 0); - } + set_volume_simple(elem, chn, &ptr, 0); } else if (simple_skip_word(&ptr, "cap") || simple_skip_word(&ptr, "rec") || simple_skip_word(&ptr, "nocap") || simple_skip_word(&ptr, "norec")) { /* nothing */ @@ -1426,12 +1476,7 @@ static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) } simple_skip_word(&ptr, "toggle"); } else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') { - if (snd_mixer_selem_has_capture_volume(elem)) { - snd_mixer_selem_get_capture_volume(elem, chn, &lval); - snd_mixer_selem_set_capture_volume(elem, chn, get_volume_simple(&ptr, cmin, cmax, lval)); - } else { - get_volume_simple(&ptr, 0, 100, 0); - } + set_volume_simple(elem, chn, &ptr, 1); } else if (simple_skip_word(&ptr, "mute") || simple_skip_word(&ptr, "off") || simple_skip_word(&ptr, "unmute") || simple_skip_word(&ptr, "on")) { /* nothing */