mixer

FreeBSD OSS mixer library implementation and a complete rewrite of mixer(8)
git clone git://git.margiolis.net/mixer.git
Log | Files | Refs | README | LICENSE

commit 7d6319d403d85cebe77bbc7c02cb2983c7ce9429
parent a247c24486bdb938591e98224dd92567dff93ef9
Author: Christos Margiolis <christos@margiolis.net>
Date:   Tue, 28 Sep 2021 18:43:28 +0300

pushed to upstream

Diffstat:
ALICENSE | 20++++++++++++++++++++
MREADME | 3+--
Mdiff/mixer_kern.diff | 338+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmixer_lib/Makefile | 1-
Mmixer_lib/mixer.3 | 7++++---
Mmixer_lib/mixer.c | 52++++++++++++++++++++++++++++------------------------
Mmixer_prog/Makefile | 5+++--
Amixer_prog/mixer.8 | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amixer_prog/mixer.c | 484+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmixer_prog/mixer_prog.8 | 243-------------------------------------------------------------------------------
Dmixer_prog/mixer_prog.c | 472-------------------------------------------------------------------------------
11 files changed, 1123 insertions(+), 747 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,20 @@ +MIT License + +(c) 2021-Present Christos Margiolis <christos@FreeBSD.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README @@ -2,8 +2,7 @@ mixer ===== An OSS mixer library implementation, a complete rewrite of mixer(8) -and sound(4) update for FreeBSD. Some of its development is part of -Google Summer of Code 2021. +and sound(4) update for FreeBSD. Usage ----- diff --git a/diff/mixer_kern.diff b/diff/mixer_kern.diff @@ -1,3 +1,341 @@ +diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c +index ecf89df4c9a..234a0cb2435 100644 +--- a/sys/dev/sound/pcm/channel.c ++++ b/sys/dev/sound/pcm/channel.c +@@ -1218,6 +1218,8 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction) + c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; + c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; + ++ memset(c->muted, 0, sizeof(c->muted)); ++ + chn_vpc_reset(c, SND_VOL_C_PCM, 1); + + ret = ENODEV; +@@ -1389,6 +1391,75 @@ chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt) + return (c->volume[vc][vt]); + } + ++int ++chn_setmute_multi(struct pcm_channel *c, int vc, int mute) ++{ ++ int i, ret; ++ ++ ret = 0; ++ ++ for (i = 0; i < SND_CHN_T_MAX; i++) { ++ if ((1 << i) & SND_CHN_LEFT_MASK) ++ ret |= chn_setmute_matrix(c, vc, i, mute); ++ else if ((1 << i) & SND_CHN_RIGHT_MASK) ++ ret |= chn_setmute_matrix(c, vc, i, mute) << 8; ++ else ++ ret |= chn_setmute_matrix(c, vc, i, mute) << 16; ++ } ++ return (ret); ++} ++ ++int ++chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute) ++{ ++ int i; ++ ++ KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && ++ (vc == SND_VOL_C_MASTER || (vc & 1)) && ++ (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ++ ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d", ++ __func__, c, vc, vt, mute)); ++ ++ CHN_LOCKASSERT(c); ++ ++ mute = (mute != 0); ++ ++ c->muted[vc][vt] = mute; ++ ++ /* ++ * Do relative calculation here and store it into class + 1 ++ * to ease the job of feeder_volume. ++ */ ++ if (vc == SND_VOL_C_MASTER) { ++ for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; ++ vc += SND_VOL_C_STEP) ++ c->muted[SND_VOL_C_VAL(vc)][vt] = mute; ++ } else if (vc & 1) { ++ if (vt == SND_CHN_T_VOL_0DB) { ++ for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END; ++ i += SND_CHN_T_STEP) { ++ c->muted[SND_VOL_C_VAL(vc)][i] = mute; ++ } ++ } else { ++ c->muted[SND_VOL_C_VAL(vc)][vt] = mute; ++ } ++ } ++ return (mute); ++} ++ ++int ++chn_getmute_matrix(struct pcm_channel *c, int vc, int vt) ++{ ++ KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && ++ (vt == SND_CHN_T_VOL_0DB || ++ (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), ++ ("%s(): invalid mute matrix c=%p vc=%d vt=%d", ++ __func__, c, vc, vt)); ++ CHN_LOCKASSERT(c); ++ ++ return (c->muted[vc][vt]); ++} ++ + struct pcmchan_matrix * + chn_getmatrix(struct pcm_channel *c) + { +diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h +index 969b8dba27d..45b517f22ce 100644 +--- a/sys/dev/sound/pcm/channel.h ++++ b/sys/dev/sound/pcm/channel.h +@@ -166,7 +166,8 @@ struct pcm_channel { + struct pcmchan_matrix matrix; + struct pcmchan_matrix matrix_scratch; + +- int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; ++ int16_t volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; ++ int8_t muted[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; + + void *data1, *data2; + }; +@@ -271,6 +272,9 @@ int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right, + int center); + int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val); + int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt); ++int chn_setmute_multi(struct pcm_channel *c, int vc, int mute); ++int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute); ++int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt); + void chn_vpc_reset(struct pcm_channel *c, int vc, int force); + int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed); + int chn_setspeed(struct pcm_channel *c, uint32_t speed); +@@ -307,6 +311,8 @@ int chn_syncdestroy(struct pcm_channel *c); + #define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z]) + #endif + ++#define CHN_GETMUTE(x, y, z) ((x)->muted[y][z]) ++ + #ifdef OSSV4_EXPERIMENT + int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); + #endif +diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c +index 9dfc47d60fb..0c5c8af341d 100644 +--- a/sys/dev/sound/pcm/dsp.c ++++ b/sys/dev/sound/pcm/dsp.c +@@ -964,6 +964,7 @@ dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd, + struct snddev_info *d; + struct pcm_channel *rdch, *wrch; + int j, devtype, ret; ++ int left, right, center, mute; + + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC)) +@@ -1002,67 +1003,95 @@ dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd, + } + + /* Final validation */ +- if (volch != NULL) { +- CHN_LOCK(volch); +- if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { +- CHN_UNLOCK(volch); +- return (-1); +- } +- if (volch->direction == PCMDIR_PLAY) +- wrch = volch; +- else +- rdch = volch; +- } +- +- ret = EINVAL; ++ if (volch == NULL) ++ return (EINVAL); + +- if (volch != NULL && +- ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) || +- (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) { +- if ((cmd & ~0xff) == MIXER_WRITE(0)) { +- int left, right, center; ++ CHN_LOCK(volch); ++ if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { ++ CHN_UNLOCK(volch); ++ return (EINVAL); ++ } + ++ switch (cmd & ~0xff) { ++ case MIXER_WRITE(0): ++ switch (j) { ++ case SOUND_MIXER_MUTE: ++ if (volch->direction == PCMDIR_REC) { ++ chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0); ++ } else { ++ chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0); ++ } ++ break; ++ case SOUND_MIXER_PCM: ++ if (volch->direction != PCMDIR_PLAY) ++ break; + left = *(int *)arg & 0x7f; + right = ((*(int *)arg) >> 8) & 0x7f; + center = (left + right) >> 1; +- chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right, +- center); +- } else if ((cmd & ~0xff) == MIXER_READ(0)) { +- *(int *)arg = CHN_GETVOLUME(volch, +- SND_VOL_C_PCM, SND_CHN_T_FL); +- *(int *)arg |= CHN_GETVOLUME(volch, +- SND_VOL_C_PCM, SND_CHN_T_FR) << 8; ++ chn_setvolume_multi(volch, SND_VOL_C_PCM, ++ left, right, center); ++ break; ++ case SOUND_MIXER_RECLEV: ++ if (volch->direction != PCMDIR_REC) ++ break; ++ left = *(int *)arg & 0x7f; ++ right = ((*(int *)arg) >> 8) & 0x7f; ++ center = (left + right) >> 1; ++ chn_setvolume_multi(volch, SND_VOL_C_PCM, ++ left, right, center); ++ break; ++ default: ++ /* ignore all other mixer writes */ ++ break; + } +- ret = 0; +- } else if (rdch != NULL || wrch != NULL) { ++ break; ++ ++ case MIXER_READ(0): + switch (j) { ++ case SOUND_MIXER_MUTE: ++ mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) || ++ CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR); ++ if (volch->direction == PCMDIR_REC) { ++ *(int *)arg = mute << SOUND_MIXER_RECLEV; ++ } else { ++ *(int *)arg = mute << SOUND_MIXER_PCM; ++ } ++ break; ++ case SOUND_MIXER_PCM: ++ if (volch->direction != PCMDIR_PLAY) ++ break; ++ *(int *)arg = CHN_GETVOLUME(volch, ++ SND_VOL_C_PCM, SND_CHN_T_FL); ++ *(int *)arg |= CHN_GETVOLUME(volch, ++ SND_VOL_C_PCM, SND_CHN_T_FR) << 8; ++ break; ++ case SOUND_MIXER_RECLEV: ++ if (volch->direction != PCMDIR_REC) ++ break; ++ *(int *)arg = CHN_GETVOLUME(volch, ++ SND_VOL_C_PCM, SND_CHN_T_FL); ++ *(int *)arg |= CHN_GETVOLUME(volch, ++ SND_VOL_C_PCM, SND_CHN_T_FR) << 8; ++ break; + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: +- if ((cmd & ~0xff) == MIXER_READ(0)) { +- *(int *)arg = 0; +- if (rdch != NULL) +- *(int *)arg |= SOUND_MASK_RECLEV; +- if (wrch != NULL) +- *(int *)arg |= SOUND_MASK_PCM; +- } +- ret = 0; +- break; +- case SOUND_MIXER_RECMASK: +- case SOUND_MIXER_RECSRC: +- if ((cmd & ~0xff) == MIXER_READ(0)) +- *(int *)arg = 0; +- ret = 0; ++ if (volch->direction == PCMDIR_REC) ++ *(int *)arg = SOUND_MASK_RECLEV; ++ else ++ *(int *)arg = SOUND_MASK_PCM; + break; + default: ++ *(int *)arg = 0; + break; + } +- } +- +- if (volch != NULL) +- CHN_UNLOCK(volch); ++ break; + +- return (ret); ++ default: ++ break; ++ } ++ CHN_UNLOCK(volch); ++ return (0); + } + + static int +diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c +index 322d7f6b2c8..2312bd89c9d 100644 +--- a/sys/dev/sound/pcm/feeder_volume.c ++++ b/sys/dev/sound/pcm/feeder_volume.c +@@ -237,10 +237,13 @@ static int + feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) + { ++ int temp_vol[SND_CHN_T_VOL_MAX]; + struct feed_volume_info *info; + uint32_t j, align; +- int i, *vol, *matrix; ++ int i, *matrix; + uint8_t *dst; ++ const int16_t *vol; ++ const int8_t *muted; + + /* + * Fetch filter data operation. +@@ -251,6 +254,7 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + return (FEEDER_FEED(f->source, c, b, count, source)); + + vol = c->volume[SND_VOL_C_VAL(info->volume_class)]; ++ muted = c->muted[SND_VOL_C_VAL(info->volume_class)]; + matrix = info->matrix; + + /* +@@ -258,17 +262,22 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + */ + j = 0; + i = info->channels; +- do { +- if (vol[matrix[--i]] != SND_VOL_FLAT) { ++ while (i--) { ++ if (vol[matrix[i]] != SND_VOL_FLAT || ++ muted[matrix[i]] != 0) { + j = 1; + break; + } +- } while (i != 0); ++ } + + /* Nope, just bypass entirely. */ + if (j == 0) + return (FEEDER_FEED(f->source, c, b, count, source)); + ++ /* Check if any controls are muted. */ ++ for (j = 0; j != SND_CHN_T_VOL_MAX; j++) ++ temp_vol[j] = muted[j] ? 0 : vol[j]; ++ + dst = b; + align = info->bps * info->channels; + +@@ -281,7 +290,7 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + if (j == 0) + break; + +- info->apply(vol, matrix, info->channels, dst, j); ++ info->apply(temp_vol, matrix, info->channels, dst, j); + + j *= align; + dst += j; diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index 92c5f3d613e..8275d1a2684 100644 --- a/sys/dev/sound/pcm/mixer.c diff --git a/mixer_lib/Makefile b/mixer_lib/Makefile @@ -4,6 +4,5 @@ LIB= mixer SRCS= ${LIB}.c INCS= ${LIB}.h MAN= ${LIB}.3 -CFLAGS+= -pedantic -Wall -Wunused .include <bsd.lib.mk> diff --git a/mixer_lib/mixer.3 b/mixer_lib/mixer.3 @@ -22,7 +22,7 @@ .\" $FreeBSD$ .\" -.Dd June 30, 2021 +.Dd September 22, 2021 .Dt mixer 3 .Os .Sh NAME @@ -533,7 +533,8 @@ TAILQ_FOREACH(dp, &m->devs, devs) { .Xr mixer 8 , .Xr sound 4 , .Xr sysctl 3 , -.Xr queue 3 , +.Xr queue 3 +and .Xr errno 2 .Sh AUTHORS -.An Christos Margiolis Aq Mt christos@margiolis.net +.An Christos Margiolis Aq Mt christos@FreeBSD.org diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c @@ -28,6 +28,7 @@ #include <errno.h> #include <fcntl.h> +#include <libgen.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -35,7 +36,7 @@ #include "mixer.h" -#define BASEPATH "/dev/mixer" +#define BASEPATH "/dev/mixer" static int _mixer_readvol(struct mixer *, struct mix_dev *); @@ -57,7 +58,7 @@ _mixer_readvol(struct mixer *m, struct mix_dev *dev) /* * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer. - * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the + * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the * mixer for pcm0, and so on. * * @param name path to mixer device. NULL or "/dev/mixer" for the @@ -69,27 +70,25 @@ mixer_open(const char *name) struct mixer *m = NULL; struct mix_dev *dp; const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + char *p = NULL; int i; if ((m = calloc(1, sizeof(struct mixer))) == NULL) goto fail; if (name != NULL) { - /* `name` does not start with "/dev/mixer". */ - if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) { - errno = EINVAL; + /* XXX: should we remove `const` altogether? */ + if ((p = strdup(basename((char *)name))) == NULL) goto fail; - } - /* `name` is "/dev/mixer" so, we'll use the default unit. */ - if (strncmp(name, BASEPATH, strlen(name)) == 0) + if (strncmp(p, "mixer", 5) == 0 && p[5] == '\0') goto dunit; - m->unit = strtol(name + strlen(BASEPATH), NULL, 10); + (void)sscanf(name, "%*[^0123456789]%d", &m->unit); (void)strlcpy(m->name, name, sizeof(m->name)); } else { dunit: if ((m->unit = mixer_get_dunit()) < 0) goto fail; - (void)snprintf(m->name, sizeof(m->name) - 1, "/dev/mixer%d", m->unit); + (void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit); } if ((m->fd = open(m->name, O_RDWR)) < 0) @@ -101,9 +100,13 @@ dunit: /* The unit number _must_ be set before the ioctl. */ m->mi.dev = m->unit; m->ci.card = m->unit; - if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0 || - ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0 || - ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 || + if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) { + memset(&m->mi, 0, sizeof(m->mi)); + strlcpy(m->mi.name, m->name, sizeof(m->mi.name)); + } + if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0) + memset(&m->ci, 0, sizeof(m->ci)); + if(ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 || ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 || ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 || ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) @@ -125,14 +128,15 @@ dunit: TAILQ_INSERT_TAIL(&m->devs, dp, devs); m->ndev++; } - - /* The default device is always "vol". */ m->dev = TAILQ_FIRST(&m->devs); return (m); fail: - if (m != NULL) - (void)mixer_close(m); + if (p != NULL) + free(p); + /* XXX: do we need this? */ + /*if (m != NULL)*/ + /*(void)mixer_close(m);*/ return (NULL); } @@ -238,7 +242,7 @@ mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name, } TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls); dp->nctl++; - + return (0); } @@ -251,7 +255,7 @@ mixer_add_ctl_s(mix_ctl_t *ctl) if (ctl == NULL) return (-1); - return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name, + return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name, ctl->mod, ctl->print)); } @@ -324,7 +328,7 @@ mixer_set_vol(struct mixer *m, mix_volume_t vol) { int v; - if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX || + if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX || vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) { errno = ERANGE; return (-1); @@ -432,7 +436,7 @@ mixer_get_dunit(void) * Change the default audio card. This is normally _not_ a mixer feature, but * it's useful to have, so the caller can avoid having to manually use * the sysctl API. - * + * * @param unit the audio card number (e.g pcm0, pcm1, ...). */ int @@ -460,10 +464,10 @@ mixer_get_mode(int unit) size_t size; unsigned int mode; - (void)snprintf(buf, sizeof(buf) - 1, "dev.pcm.%d.mode", unit); + (void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit); size = sizeof(unsigned int); if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0) - return (-1); + return (0); return (mode); } @@ -477,7 +481,7 @@ mixer_get_nmixers(void) struct mixer *m; oss_sysinfo si; - /* + /* * Open a dummy mixer because we need the `fd` field for the * `ioctl` to work. */ diff --git a/mixer_prog/Makefile b/mixer_prog/Makefile @@ -1,10 +1,11 @@ # $FreeBSD$ -PROG= mixer_prog +#.include <src.opts.mk> + +PROG= mixer BINDIR= /usr/sbin SRCS= ${PROG}.c MAN= ${PROG}.8 -CFLAGS+= -pedantic -Wall -Wunused LDFLAGS+= -lmixer .include <bsd.prog.mk> diff --git a/mixer_prog/mixer.8 b/mixer_prog/mixer.8 @@ -0,0 +1,245 @@ +.\"- +.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining a copy +.\" of this software and associated documentation files (the "Software"), to deal +.\" in the Software without restriction, including without limitation the rights +.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.\" copies of the Software, and to permit persons to whom the Software is +.\" furnished to do so, subject to the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included in +.\" all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.\" THE SOFTWARE. +.\" +.\" $FreeBSD$ +.\" + +.Dd September 22, 2021 +.Dt mixer 8 +.Os +.Sh NAME +.Nm mixer +.Nd manipulate soundcard mixer controls +.Sh SYNOPSIS +.Nm +.Op Fl f Ar device +.Op Fl d Ar unit +.Op Fl os +.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value +.Ar ... +.Nm +.Op Fl d Ar unit +.Op Fl os +.Fl a +.Sh DESCRIPTION +The +.Nm +utility is used to set and display soundcard mixer device controls. +.Pp +The options are as follows: +.Bl -tag -width "-f device" +.It Fl a +Print the values for all mixer devices available in the system (see FILES). +.It Fl d Ar unit +Change the default audio card to +.Ar unit . +The unit has to be an integer value. To see what unit values are available, look +at the number each mixer device has by running +.Nm . +.It Fl f Ar device +Open +.Ar device +as the mixer device (see FILES). +.It Fl o +Print mixer values in a format suitable for use inside scripts. The +mixer's header (name, audio card name, ...) will not be printed. +.It Fl s +Print only the recording source(s) of the mixer device. +.El +.Pp +The list of mixer devices that may be modified are: +.Bd -ragged -offset indent +vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix, +pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3, +phin, phout, video, radio, and monitor. +.Ed +.Pp +Not all mixer devices are available. +.Pp +Without any arguments, +.Nm +displays all information for each one of the mixer's supported devices to +.Ar stdout . +If the +.Ar dev +argument is specified, +.Nm +displays only the values for +.Ar dev . +More than one device may be specified. +.Pp +Commands use the following format: +.Pp +.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent +.It Sy "Name Action" +.It "dev Display all controls" +.It "dev.control Display only the specified control" +.It "dev.control=value Set control value" +.El +.Pp +The available controls are as follows (replace +.Ar dev +with one of the available devices): +.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent +.It Sy "Name Value" +.It "dev.volume [[+|-]lvol[:[+|-]rvol]]" +.It "dev.mute {0|1|^}" +.It "dev.recsrc {+|-|^|=}" +.El +.Pp +The +.Ar dev.volume +control modifies a device's volume. The optional +.Ar lvol +and/or +.Ar rvol +values have to be specified. The values have to be normalized 32-bit floats, +from 0.0 to 1.0 inclusivly. If no "." character is present, the value is treated +like a percentage, for backwards compatibility. +If the the left or right volume values are prefixed with +.Cm + +or +.Cm - , +the value following will be used as a relative adjustment, modifying the +current settings by the amount specified. +.Pp +The +.Ar dev.mute +control (un)mutes a device. The following values are available: +.Bl -tag -width = -offset indent +.It Cm 0 +unmutes +.Ar dev . +.It Cm 1 +mutes +.Ar dev . +.It Cm ^ +toggles the mute of +.Ar dev . +.El +.Pp +The +.Ar dev.recsrc +control modifies the recording sources of a mixer. +.Nm +marks devices which can be used as a recording source with +.Ar rec . +Recording sources are marked with +.Ar src . +To modify the recording source you can use one of the following modifiers +on a +.Ar rec +device: +.Bl -tag -width = -offset indent +.It Cm ^ +toggles +.Ar dev +of possible recording devices +.It Cm + +adds +.Ar dev +to possible recording devices +.It Cm - +removes +.Ar dev +from possible recording devices +.It Cm = +sets the recording device to +.Ar dev +.El +.Sh FILES +.Bl -tag -width /dev/mixerN -compact +.It Pa /dev/mixerN +The mixer device, where +.Ar N +is the number of that device, for example +.Ar /dev/mixer0 . +PCM cards and mixers have a 1:1 relationship, which means that +.Ar mixer0 +is the mixer for +.Ar pcm0 +and so on. By default, +.Nm +prints both the audio card's number and the mixer associated with it +in the form of +.Ar pcmN:mixer . +The +.Ar /dev/mixer +file, although it doesn't exist in the filesystem, points to the default +mixer device and is the file +.Nm +opens when the +.Fl f Ar device +option has not been specified. +.El +.Sh EXAMPLES +.Pp +Change the volume for the +.Ar vol +device of the +.Ar /dev/mixer0 +mixer device to 0.65: +.Bl -tag -width Ds -offset indent +.It $ mixer -f /dev/mixer0 vol.volume=0.65 +.El +.Pp +Increase the +.Ar mic +device's left volume by 0.10 and decrease the right +volume by 0.05: +.Bl -tag -width Ds -offset indent +.It $ mixer mic.volume=+0.10:-0.05 +.El +.Pp +Toggle the mute for +.Ar vol : +.Bl -tag -width Ds -offset indent +.It $ mixer vol.mute=^ +.El +.Pp +Set +.Ar mic +and toggle +.Ar line +recording sources: +.Bl -tag -width Ds -offset indent +.It $ mixer mic.recsrc=+ line.recsrc=^ +.El +.Pp +Dump +.Ar /dev/mixer0 +information to a file and retrieve back later +.Bl -tag -width Ds -offset indent +.It $ mixer -f /dev/mixer0 -o > info +.It ... +.It $ mixer -f /dev/mixer0 `cat info` +.El +.Sh SEE ALSO +.Xr mixer 3 , +.Xr sound 4 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +utility first appeared in FreeBSD 2.0.5 and was rewritten completely in +FreeBSD 14.0. +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@FreeBSD.org diff --git a/mixer_prog/mixer.c b/mixer_prog/mixer.c @@ -0,0 +1,484 @@ +/*- + * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * $FreeBSD$ + */ + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <mixer.h> + +static void usage(void) __dead2; +static void initctls(struct mixer *); +static void printall(struct mixer *, int); +static void printminfo(struct mixer *, int); +static void printdev(struct mixer *, int); +static void printrecsrc(struct mixer *, int); /* XXX: change name */ +/* Control handlers */ +static int mod_dunit(struct mix_dev *, void *); +static int mod_volume(struct mix_dev *, void *); +static int mod_mute(struct mix_dev *, void *); +static int mod_recsrc(struct mix_dev *, void *); +static int print_volume(struct mix_dev *, void *); +static int print_mute(struct mix_dev *, void *); +static int print_recsrc(struct mix_dev *, void *); + +static const mix_ctl_t ctl_dunit = { + .parent_dev = NULL, + .id = -1, + .name = "default_unit", + .mod = mod_dunit, + .print = NULL +}; + +int +main(int argc, char *argv[]) +{ + struct mixer *m; + mix_ctl_t *cp; + char *name = NULL, buf[NAME_MAX]; + char *p, *bufp, *devstr, *ctlstr, *valstr = NULL; + int dunit, i, n, pall = 1; + int aflag = 0, dflag = 0, oflag = 0, sflag = 0; + int ch; + + while ((ch = getopt(argc, argv, "ad:f:os")) != -1) { + switch (ch) { + case 'a': + aflag = 1; + break; + case 'd': + dunit = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) + err(1, "strtol"); + dflag = 1; + break; + case 'f': + name = optarg; + break; + case 'o': + oflag = 1; + break; + case 's': + sflag = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* Print all mixers and exit. */ + if (aflag) { + if ((n = mixer_get_nmixers()) < 0) + err(1, "mixer_get_nmixers"); + for (i = 0; i < n; i++) { + (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i); + if ((m = mixer_open(buf)) == NULL) + err(1, "mixer_open: %s", buf); + initctls(m); + if (sflag) + printrecsrc(m, oflag); + else { + printall(m, oflag); + if (oflag) + printf("\n"); + } + (void)mixer_close(m); + } + return (0); + } + + if ((m = mixer_open(name)) == NULL) + err(1, "mixer_open: %s", name); + + initctls(m); + + if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0) + goto parse; + if (sflag) { + printrecsrc(m, oflag); + (void)mixer_close(m); + return (0); + } + +parse: + while (argc > 0) { + if ((p = bufp = strdup(*argv)) == NULL) + err(1, "strdup(%s)", *argv); + /* Split the string into device, control and value. */ + devstr = strsep(&p, "."); + if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { + warnx("%s: no such device", devstr); + goto next; + } + /* Input: `dev`. */ + if (p == NULL) { + printdev(m, 1); + pall = 0; + goto next; + } + ctlstr = strsep(&p, "="); + if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { + warnx("%s.%s: no such control", devstr, ctlstr); + goto next; + } + + /* Input: `dev.control`. */ + if (p == NULL) { + (void)cp->print(cp->parent_dev, cp->name); + pall = 0; + goto next; + } + valstr = p; + /* Input: `dev.control=val`. */ + cp->mod(cp->parent_dev, valstr); +next: + free(p); + argc--; + argv++; + } + + if (pall) + printall(m, oflag); + (void)mixer_close(m); + + return (0); +} + +static void __dead2 +usage(void) +{ + printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n" + " %1$s [-d unit] [-os] -a\n", + getprogname()); + exit(1); +} + +static void +initctls(struct mixer *m) +{ + struct mix_dev *dp; + int rc = 0; + +#define C_VOL 0 +#define C_MUT 1 +#define C_SRC 2 + TAILQ_FOREACH(dp, &m->devs, devs) { + rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); + rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); + rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); + } + if (rc) { + (void)mixer_close(m); + err(1, "cannot make controls"); + } +} + +static void +printall(struct mixer *m, int oflag) +{ + struct mix_dev *dp; + + printminfo(m, oflag); + TAILQ_FOREACH(dp, &m->devs, devs) { + m->dev = dp; + printdev(m, oflag); + } +} + +static void +printminfo(struct mixer *m, int oflag) +{ + int playrec = MIX_MODE_PLAY | MIX_MODE_REC; + + if (oflag) + return; + printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info); + printf(" ("); + if (m->mode & MIX_MODE_PLAY) + printf("play"); + if ((m->mode & playrec) == playrec) + printf("/"); + if (m->mode & MIX_MODE_REC) + printf("rec"); + printf(")"); + if (m->f_default) + printf(" (default)"); + printf("\n"); +} + +static void +printdev(struct mixer *m, int oflag) +{ + struct mix_dev *d = m->dev; + mix_ctl_t *cp; + + if (!oflag) { + char buffer[32]; + (void)snprintf(buffer, sizeof(buffer), + "%s.%s", d->name, "volume"); + + printf(" %-16s= %.2f:%.2f\t", + buffer, d->vol.left, d->vol.right); + if (!MIX_ISREC(m, d->devno)) + printf(" pbk"); + if (MIX_ISREC(m, d->devno)) + printf(" rec"); + if (MIX_ISRECSRC(m, d->devno)) + printf(" src"); + if (MIX_ISMUTE(m, d->devno)) + printf(" mute"); + printf("\n"); + } else { + TAILQ_FOREACH(cp, &d->ctls, ctls) { + (void)cp->print(cp->parent_dev, cp->name); + } + } +} + +static void +printrecsrc(struct mixer *m, int oflag) +{ + struct mix_dev *dp; + int n = 0; + + if (!m->recmask) + return; + if (!oflag) + printf("%s: ", m->mi.name); + TAILQ_FOREACH(dp, &m->devs, devs) { + if (MIX_ISRECSRC(m, dp->devno)) { + if (n++ && !oflag) + printf(", "); + printf("%s", dp->name); + if (oflag) + printf(".%s=+%s", + mixer_get_ctl(dp, C_SRC)->name, + n ? " " : ""); + } + } + printf("\n"); +} + +static int +mod_dunit(struct mix_dev *d, void *p) +{ + int dunit = *((int *)p); + int n; + + if ((n = mixer_get_dunit()) < 0) { + warn("cannot get default unit"); + return (-1); + } + if (mixer_set_dunit(d->parent_mixer, dunit) < 0) { + warn("cannot set default unit to: %d", dunit); + return (-1); + } + printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit); + + return (0); +} + +static int +mod_volume(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + mix_volume_t v; + const char *val; + char lstr[8], rstr[8]; + float lprev, rprev, lrel, rrel; + int n; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_VOL); + val = p; + n = sscanf(val, "%7[^:]:%7s", lstr, rstr); + if (n == EOF) { + warnx("invalid volume value: %s", val); + return (-1); + } + lrel = rrel = 0; + if (n > 0) { + if (*lstr == '+' || *lstr == '-') + lrel = rrel = 1; + v.left = strtof(lstr, NULL); + + /* be backwards compatible */ + if (strstr(lstr, ".") == NULL) + v.left /= 100.0f; + } + if (n > 1) { + if (*rstr == '+' || *rstr == '-') + rrel = 1; + v.right = strtof(rstr, NULL); + + /* be backwards compatible */ + if (strstr(rstr, ".") == NULL) + v.right /= 100.0f; + } + switch (n) { + case 1: + v.right = v.left; /* FALLTHROUGH */ + case 2: + if (lrel) + v.left += m->dev->vol.left; + if (rrel) + v.right += m->dev->vol.right; + + if (v.left < MIX_VOLMIN) + v.left = MIX_VOLMIN; + else if (v.left > MIX_VOLMAX) + v.left = MIX_VOLMAX; + if (v.right < MIX_VOLMIN) + v.right = MIX_VOLMIN; + else if (v.right > MIX_VOLMAX) + v.right = MIX_VOLMAX; + + lprev = m->dev->vol.left; + rprev = m->dev->vol.right; + if (mixer_set_vol(m, v) < 0) + warn("%s.%s=%.2f:%.2f", + m->dev->name, cp->name, v.left, v.right); + else + printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", + m->dev->name, cp->name, lprev, rprev, v.left, v.right); + } + + return (0); +} + +static int +mod_mute(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_MUT); + val = p; + switch (*val) { + case '0': + opt = MIX_UNMUTE; + break; + case '1': + opt = MIX_MUTE; + break; + case '^': + opt = MIX_TOGGLEMUTE; + break; + default: + warnx("%c: no such modifier", *val); + return (-1); + } + n = MIX_ISMUTE(m, m->dev->devno); + if (mixer_set_mute(m, opt) < 0) + warn("%s.%s=%c", m->dev->name, cp->name, *val); + else + printf("%s.%s: %d -> %d\n", + m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno)); + + return (0); +} + +static int +mod_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m; + mix_ctl_t *cp; + const char *val; + int n, opt = -1; + + m = d->parent_mixer; + cp = mixer_get_ctl(m->dev, C_SRC); + val = p; + switch (*val) { + case '+': + opt = MIX_ADDRECSRC; + break; + case '-': + opt = MIX_REMOVERECSRC; + break; + case '=': + opt = MIX_SETRECSRC; + break; + case '^': + opt = MIX_TOGGLERECSRC; + break; + default: + warnx("%c: no such modifier", *val); + return (-1); + } + n = MIX_ISRECSRC(m, m->dev->devno); + if (mixer_mod_recsrc(m, opt) < 0) + warn("%s.%s=%c", m->dev->name, cp->name, *val); + else + printf("%s.%s: %d -> %d\n", + m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno)); + + return (0); +} + +static int +print_volume(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%.2f:%.2f\n", + m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right); + + return (0); +} + +static int +print_mute(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno)); + + return (0); +} + +static int +print_recsrc(struct mix_dev *d, void *p) +{ + struct mixer *m = d->parent_mixer; + const char *ctl_name = p; + + if (!MIX_ISRECSRC(m, m->dev->devno)) + return (-1); + printf("%s.%s=+\n", m->dev->name, ctl_name); + + return (0); +} diff --git a/mixer_prog/mixer_prog.8 b/mixer_prog/mixer_prog.8 @@ -1,243 +0,0 @@ -.\"- -.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> -.\" -.\" Permission is hereby granted, free of charge, to any person obtaining a copy -.\" of this software and associated documentation files (the "Software"), to deal -.\" in the Software without restriction, including without limitation the rights -.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -.\" copies of the Software, and to permit persons to whom the Software is -.\" furnished to do so, subject to the following conditions: -.\" -.\" The above copyright notice and this permission notice shall be included in -.\" all copies or substantial portions of the Software. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -.\" THE SOFTWARE. -.\" -.\" $FreeBSD$ -.\" - -.Dd June 30, 2021 -.Dt mixer 8 -.Os -.Sh NAME -.Nm mixer -.Nd manipulate soundcard mixer controls -.Sh SYNOPSIS -.Nm -.Op Fl f Ar device -.Op Fl d Ar unit -.Op Fl os -.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value -.Ar ... -.Nm -.Op Fl d Ar unit -.Op Fl os -.Fl a -.Sh DESCRIPTION -The -.Nm -utility is used to set and display soundcard mixer device controls. -.Pp -The options are as follows: -.Bl -tag -width "-f device" -.It Fl a -Print the values for all mixer devices available in the system (see FILES). -.It Fl d Ar unit -Change the default audio card to -.Ar unit . -The unit has to be an integer value. To see what unit values are available, look -at the number each mixer device has by running -.Nm . -.It Fl f Ar device -Open -.Ar device -as the mixer device (see FILES). -.It Fl o -Print mixer values in a format suitable for use inside scripts. The -mixer's header (name, audio card name, ...) will not be printed. -.It Fl s -Print only the recording source(s) of the mixer device. -.El -.Pp -The list of mixer devices that may be modified are: -.Bd -ragged -offset indent -vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix, -pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3, -phin, phout, video, radio, and monitor. -.Ed -.Pp -Not all mixer devices are available. -.Pp -Without any arguments, -.Nm -displays all information for each one of the mixer's supported devices to -.Ar stdout . -If the -.Ar dev -argument is specified, -.Nm -displays only the values for -.Ar dev . -More than one device may be specified. -.Pp -Commands use the following format: -.Pp -.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent -.It Sy "Name Action" -.It "dev Display all controls" -.It "dev.control Display only the specified control" -.It "dev.control=value Set control value" -.El -.Pp -The available controls are as follows (replace -.Ar dev -with one of the available devices): -.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent -.It Sy "Name Value" -.It "dev.volume [[+|-]lvol[:[+|-]rvol]]" -.It "dev.mute {0|1|^}" -.It "dev.recsrc {+|-|^|=}" -.El -.Pp -The -.Ar dev.volume -control modifies a device's volume. The optional -.Ar lvol -and/or -.Ar rvol -values have to be specified. The values have to be normalized 32-bit floats -(0.0 to 1.0). If the the left or right volume values are prefixed with -.Cm + -or -.Cm - , -the value following will be used as a relative adjustment, modifying the -current settings by the amount specified. -.Pp -The -.Ar dev.mute -control (un)mutes a device. The following values are available: -.Bl -tag -width = -offset indent -.It Cm 0 -unmutes -.Ar dev . -.It Cm 1 -mutes -.Ar dev . -.It Cm ^ -toggles the mute of -.Ar dev . -.El -.Pp -The -.Ar dev.recsrc -control modifies the recording sources of a mixer. -.Nm -marks devices which can be used as a recording source with -.Ar rec . -Recording sources are marked with -.Ar src . -To modify the recording source you can use one of the following modifiers -on a -.Ar rec -device: -.Bl -tag -width = -offset indent -.It Cm ^ -toggles -.Ar dev -of possible recording devices -.It Cm + -adds -.Ar dev -to possible recording devices -.It Cm - -removes -.Ar dev -from possible recording devices -.It Cm = -sets the recording device to -.Ar dev -.El -.Sh FILES -.Bl -tag -width /dev/mixerN -compact -.It Pa /dev/mixerN -The mixer device, where -.Ar N -is the number of that device, for example -.Ar /dev/mixer0 . -PCM cards and mixers have a 1:1 relationship, which means that -.Ar mixer0 -is the mixer for -.Ar pcm0 -and so on. By default, -.Nm -prints both the audio card's number and the mixer associated with it -in the form of -.Ar pcmN:mixer . -The -.Ar /dev/mixer -file, although it doesn't exist in the filesystem, points to the default -mixer device and is the file -.Nm -opens when the -.Fl f Ar device -option has not been specified. -.El -.Sh EXAMPLES -.Pp -Change the volume for the -.Ar vol -device of the -.Ar /dev/mixer0 -mixer device to 0.65: -.Bl -tag -width Ds -offset indent -.It $ mixer -f /dev/mixer0 vol.volume=0.65 -.El -.Pp -Increase the -.Ar mic -device's left volume by 0.10 and decrease the right -volume by 0.05: -.Bl -tag -width Ds -offset indent -.It $ mixer mic.volume=+0.10:-0.05 -.El -.Pp -Toggle the mute for -.Ar vol : -.Bl -tag -width Ds -offset indent -.It $ mixer vol.mute=^ -.El -.Pp -Set -.Ar mic -and toggle -.Ar line -recording sources: -.Bl -tag -width Ds -offset indent -.It $ mixer mic.recsrc=+ line.recsrc=^ -.El -.Pp -Dump -.Ar /dev/mixer0 -information to a file and retrieve back later -.Bl -tag -width Ds -offset indent -.It $ mixer -f /dev/mixer0 -o > info -.It ... -.It $ mixer -f /dev/mixer0 `cat info` -.El -.Sh SEE ALSO -.Xr mixer 3 , -.Xr sound 4 , -.Xr sysctl 8 -.Sh HISTORY -The -.Nm -utility first appeared in FreeBSD 2.0.5 and was rewritten completely in -FreeBSD 12.2. \" FIXME: replace 12.2 with proper version. -.Sh AUTHORS -.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/mixer_prog/mixer_prog.c b/mixer_prog/mixer_prog.c @@ -1,472 +0,0 @@ -/*- - * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * $FreeBSD$ - */ - -#include <err.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <mixer.h> - -static void usage(void) __dead2; -static void initctls(struct mixer *); -static void printall(struct mixer *, int); -static void printminfo(struct mixer *, int); -static void printdev(struct mixer *, int); -static void printrecsrc(struct mixer *, int); /* XXX: change name */ -/* Control handlers */ -static int mod_dunit(struct mix_dev *, void *); -static int mod_volume(struct mix_dev *, void *); -static int mod_mute(struct mix_dev *, void *); -static int mod_recsrc(struct mix_dev *, void *); -static int print_volume(struct mix_dev *, void *); -static int print_mute(struct mix_dev *, void *); -static int print_recsrc(struct mix_dev *, void *); - -static const mix_ctl_t ctl_dunit = { - .parent_dev = NULL, - .id = -1, - .name = "default_unit", - .mod = mod_dunit, - .print = NULL -}; - -int -main(int argc, char *argv[]) -{ - struct mixer *m; - mix_ctl_t *cp; - char *name = NULL, buf[NAME_MAX]; - char *p, *bufp, *devstr, *ctlstr, *valstr = NULL; - int dunit, i, n, pall = 1; - int aflag = 0, dflag = 0, oflag = 0, sflag = 0; - char ch; - - while ((ch = getopt(argc, argv, "ad:f:os")) != -1) { - switch (ch) { - case 'a': - aflag = 1; - break; - case 'd': - dunit = strtol(optarg, NULL, 10); - if (errno == EINVAL || errno == ERANGE) - err(1, "strtol"); - dflag = 1; - break; - case 'f': - name = optarg; - break; - case 'o': - oflag = 1; - break; - case 's': - sflag = 1; - break; - case '?': - default: - usage(); - } - } - argc -= optind; - argv += optind; - - /* Print all mixers and exit. */ - if (aflag) { - if ((n = mixer_get_nmixers()) < 0) - err(1, "mixer_get_nmixers"); - for (i = 0; i < n; i++) { - (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i); - if ((m = mixer_open(buf)) == NULL) - err(1, "mixer_open: %s", buf); - initctls(m); - if (sflag) - printrecsrc(m, oflag); - else { - printall(m, oflag); - if (oflag) - printf("\n"); - } - (void)mixer_close(m); - } - return (0); - } - - if ((m = mixer_open(name)) == NULL) - err(1, "mixer_open: %s", name); - - initctls(m); - - if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0) - goto parse; - if (sflag) { - printrecsrc(m, oflag); - (void)mixer_close(m); - return (0); - } - -parse: - while (argc > 0) { - if ((p = bufp = strdup(*argv)) == NULL) - err(1, "strdup(%s)", *argv); - /* Split the string into device, control and value. */ - devstr = strsep(&p, "."); - if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) { - warnx("%s: no such device", devstr); - goto next; - } - /* Input: `dev`. */ - if (p == NULL) { - printdev(m, 1); - pall = 0; - goto next; - } - ctlstr = strsep(&p, "="); - if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) { - warnx("%s.%s: no such control", devstr, ctlstr); - goto next; - } - - /* Input: `dev.control`. */ - if (p == NULL) { - (void)cp->print(cp->parent_dev, cp->name); - pall = 0; - goto next; - } - valstr = p; - /* Input: `dev.control=val`. */ - cp->mod(cp->parent_dev, valstr); -next: - free(p); - argc--; - argv++; - } - - if (pall) - printall(m, oflag); - (void)mixer_close(m); - - return (0); -} - -static void __dead2 -usage(void) -{ - printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n" - " %1$s [-d unit] [-os] -a\n", - getprogname()); - exit(1); -} - -static void -initctls(struct mixer *m) -{ - struct mix_dev *dp; - int rc = 0; - -#define C_VOL 0 -#define C_MUT 1 -#define C_SRC 2 - TAILQ_FOREACH(dp, &m->devs, devs) { - rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); - rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); - rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); - } - if (rc) { - (void)mixer_close(m); - err(1, "cannot make controls"); - } -} - -static void -printall(struct mixer *m, int oflag) -{ - struct mix_dev *dp; - - printminfo(m, oflag); - TAILQ_FOREACH(dp, &m->devs, devs) { - m->dev = dp; - printdev(m, oflag); - } -} - -static void -printminfo(struct mixer *m, int oflag) -{ - int playrec = MIX_MODE_PLAY | MIX_MODE_REC; - - if (oflag) - return; - printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info); - printf(" ("); - if (m->mode & MIX_MODE_PLAY) - printf("play"); - if ((m->mode & playrec) == playrec) - printf("/"); - if (m->mode & MIX_MODE_REC) - printf("rec"); - printf(")"); - if (m->f_default) - printf(" (default)"); - printf("\n"); -} - -static void -printdev(struct mixer *m, int oflag) -{ - struct mix_dev *d = m->dev; - mix_ctl_t *cp; - - if (!oflag) { - printf(" %-11s= %.2f:%.2f\t", - d->name, d->vol.left, d->vol.right); - if (!MIX_ISREC(m, d->devno)) - printf(" pbk"); - if (MIX_ISREC(m, d->devno)) - printf(" rec"); - if (MIX_ISRECSRC(m, d->devno)) - printf(" src"); - if (MIX_ISMUTE(m, d->devno)) - printf(" mute"); - printf("\n"); - } else { - TAILQ_FOREACH(cp, &d->ctls, ctls) { - (void)cp->print(cp->parent_dev, cp->name); - } - } -} - -static void -printrecsrc(struct mixer *m, int oflag) -{ - struct mix_dev *dp; - int n = 0; - - if (!m->recmask) - return; - if (!oflag) - printf("%s: ", m->mi.name); - TAILQ_FOREACH(dp, &m->devs, devs) { - if (MIX_ISRECSRC(m, dp->devno)) { - if (n++ && !oflag) - printf(", "); - printf("%s", dp->name); - if (oflag) - printf(".%s=+%s", - mixer_get_ctl(dp, C_SRC)->name, - n ? " " : ""); - } - } - printf("\n"); -} - -static int -mod_dunit(struct mix_dev *d, void *p) -{ - int dunit = *((int *)p); - int n; - - if ((n = mixer_get_dunit()) < 0) { - warn("cannot get default unit"); - return (-1); - } - if (mixer_set_dunit(d->parent_mixer, dunit) < 0) { - warn("cannot set default unit to: %d", dunit); - return (-1); - } - printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit); - - return (0); -} - -static int -mod_volume(struct mix_dev *d, void *p) -{ - struct mixer *m; - mix_ctl_t *cp; - mix_volume_t v; - const char *val; - char lstr[8], rstr[8]; - float lprev, rprev, lrel, rrel; - int n; - - m = d->parent_mixer; - cp = mixer_get_ctl(m->dev, C_VOL); - val = p; - n = sscanf(val, "%7[^:]:%7s", lstr, rstr); - if (n == EOF) { - warnx("invalid volume value: %s", val); - return (-1); - } - lrel = rrel = 0; - if (n > 0) { - if (*lstr == '+' || *lstr == '-') - lrel = rrel = 1; - v.left = strtof(lstr, NULL); - } - if (n > 1) { - if (*rstr == '+' || *rstr == '-') - rrel = 1; - v.right = strtof(rstr, NULL); - } - switch (n) { - case 1: - v.right = v.left; /* FALLTHROUGH */ - case 2: - if (lrel) - v.left += m->dev->vol.left; - if (rrel) - v.right += m->dev->vol.right; - - if (v.left < MIX_VOLMIN) - v.left = MIX_VOLMIN; - else if (v.left > MIX_VOLMAX) - v.left = MIX_VOLMAX; - if (v.right < MIX_VOLMIN) - v.right = MIX_VOLMIN; - else if (v.right > MIX_VOLMAX) - v.right = MIX_VOLMAX; - - lprev = m->dev->vol.left; - rprev = m->dev->vol.right; - if (mixer_set_vol(m, v) < 0) - warn("%s.%s=%.2f:%.2f", - m->dev->name, cp->name, v.left, v.right); - else - printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n", - m->dev->name, cp->name, lprev, rprev, v.left, v.right); - } - - return (0); -} - -static int -mod_mute(struct mix_dev *d, void *p) -{ - struct mixer *m; - mix_ctl_t *cp; - const char *val; - int n, opt = -1; - - m = d->parent_mixer; - cp = mixer_get_ctl(m->dev, C_MUT); - val = p; - switch (*val) { - case '0': - opt = MIX_UNMUTE; - break; - case '1': - opt = MIX_MUTE; - break; - case '^': - opt = MIX_TOGGLEMUTE; - break; - default: - warnx("%c: no such modifier", *val); - return (-1); - } - n = MIX_ISMUTE(m, m->dev->devno); - if (mixer_set_mute(m, opt) < 0) - warn("%s.%s=%c", m->dev->name, cp->name, *val); - else - printf("%s.%s: %d -> %d\n", - m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno)); - - return (0); -} - -static int -mod_recsrc(struct mix_dev *d, void *p) -{ - struct mixer *m; - mix_ctl_t *cp; - const char *val; - int n, opt = -1; - - m = d->parent_mixer; - cp = mixer_get_ctl(m->dev, C_SRC); - val = p; - switch (*val) { - case '+': - opt = MIX_ADDRECSRC; - break; - case '-': - opt = MIX_REMOVERECSRC; - break; - case '=': - opt = MIX_SETRECSRC; - break; - case '^': - opt = MIX_TOGGLERECSRC; - break; - default: - warnx("%c: no such modifier", *val); - return (-1); - } - n = MIX_ISRECSRC(m, m->dev->devno); - if (mixer_mod_recsrc(m, opt) < 0) - warn("%s.%s=%c", m->dev->name, cp->name, *val); - else - printf("%s.%s: %d -> %d\n", - m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno)); - - return (0); -} - -static int -print_volume(struct mix_dev *d, void *p) -{ - struct mixer *m = d->parent_mixer; - const char *ctl_name = p; - - printf("%s.%s=%.2f:%.2f\n", - m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right); - - return (0); -} - -static int -print_mute(struct mix_dev *d, void *p) -{ - struct mixer *m = d->parent_mixer; - const char *ctl_name = p; - - printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno)); - - return (0); -} - -static int -print_recsrc(struct mix_dev *d, void *p) -{ - struct mixer *m = d->parent_mixer; - const char *ctl_name = p; - - if (!MIX_ISRECSRC(m, m->dev->devno)) - return (-1); - printf("%s.%s=+\n", m->dev->name, ctl_name); - - return (0); -}