diff --git a/Makefile b/Makefile new file mode 100644 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +SUBDIR= mixer_lib mixer_prog + +.include diff --git a/diff/mixer_kern.diff b/diff/mixer_kern.diff new file mode 100644 --- /dev/null +++ b/diff/mixer_kern.diff @@ -0,0 +1,381 @@ +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 ++++ b/sys/dev/sound/pcm/mixer.c +@@ -51,16 +51,16 @@ struct snd_mixer { + KOBJ_FIELDS; + void *devinfo; + int busy; +- int hwvol_muted; + int hwvol_mixer; + int hwvol_step; + int type; + device_t dev; +- u_int32_t hwvol_mute_level; + u_int32_t devs; ++ u_int32_t mutedevs; + u_int32_t recdevs; + u_int32_t recsrc; + u_int16_t level[32]; ++ u_int16_t level_muted[32]; + u_int8_t parent[32]; + u_int32_t child[32]; + u_int8_t realdev[32]; +@@ -244,7 +244,7 @@ mixer_set_eq(struct snd_mixer *m, struct snddev_info *d, + } + + static int +-mixer_set(struct snd_mixer *m, u_int dev, u_int lev) ++mixer_set(struct snd_mixer *m, u_int dev, u_int32_t muted, u_int lev) + { + struct snddev_info *d; + u_int l, r, tl, tr; +@@ -254,7 +254,7 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + + if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || + (0 == (m->devs & (1 << dev)))) +- return -1; ++ return (-1); + + l = min((lev & 0x00ff), 100); + r = min(((lev & 0xff00) >> 8), 100); +@@ -262,7 +262,7 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + + d = device_get_softc(m->dev); + if (d == NULL) +- return -1; ++ return (-1); + + /* It is safe to drop this mutex due to Giant. */ + if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) +@@ -270,6 +270,11 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + else + dropmtx = 0; + ++ /* Allow the volume to be "changed" while muted. */ ++ if (muted & (1 << dev)) { ++ m->level_muted[dev] = l | (r << 8); ++ return (0); ++ } + MIXER_SET_UNLOCK(m, dropmtx); + + /* TODO: recursive handling */ +@@ -287,7 +292,7 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + else if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, tl, tr) < 0) { + MIXER_SET_LOCK(m, dropmtx); +- return -1; ++ return (-1); + } + } else if (child != 0) { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { +@@ -305,8 +310,8 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + realdev = m->realdev[dev]; + if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, l, r) < 0) { +- MIXER_SET_LOCK(m, dropmtx); +- return -1; ++ MIXER_SET_LOCK(m, dropmtx); ++ return (-1); + } + } else { + if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) +@@ -317,7 +322,7 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + else if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, l, r) < 0) { + MIXER_SET_LOCK(m, dropmtx); +- return -1; ++ return (-1); + } + } + +@@ -326,16 +331,43 @@ mixer_set(struct snd_mixer *m, u_int dev, u_int lev) + m->level[dev] = l | (r << 8); + m->modify_counter++; + +- return 0; ++ return (0); + } + + static int + mixer_get(struct snd_mixer *mixer, int dev) + { +- if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) +- return mixer->level[dev]; +- else +- return -1; ++ if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) { ++ if (mixer->mutedevs & (1 << dev)) ++ return (mixer->level_muted[dev]); ++ else ++ return (mixer->level[dev]); ++ } else { ++ return (-1); ++ } ++} ++ ++void ++mix_setmutedevs(struct snd_mixer *mixer, u_int32_t mutedevs) ++{ ++ int i; ++ u_int32_t delta; ++ ++ /* Filter out invalid values. */ ++ mutedevs &= mixer->devs; ++ delta = (mixer->mutedevs ^ mutedevs) & mixer->devs; ++ mixer->mutedevs = mutedevs; ++ ++ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { ++ if (!(delta & (1 << i))) ++ continue; ++ if (mutedevs & (1 << i)) { ++ mixer->level_muted[i] = mixer->level[i]; ++ mixer_set(mixer, i, 0, 0); ++ } else { ++ mixer_set(mixer, i, 0, mixer->level_muted[i]); ++ } ++ } + } + + static int +@@ -598,6 +630,12 @@ mix_getdevs(struct snd_mixer *m) + return m->devs; + } + ++u_int32_t ++mix_getmutedevs(struct snd_mixer *m) ++{ ++ return m->mutedevs; ++} ++ + u_int32_t + mix_getrecdevs(struct snd_mixer *m) + { +@@ -721,7 +759,7 @@ mixer_init(device_t dev, kobj_class_t cls, void *devinfo) + } + } + +- mixer_set(m, i, v | (v << 8)); ++ mixer_set(m, i, 0, v | (v << 8)); + } + + mixer_setrecsrc(m, 0); /* Set default input. */ +@@ -799,7 +837,7 @@ mixer_uninit(device_t dev) + snd_mtxlock(m->lock); + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) +- mixer_set(m, i, 0); ++ mixer_set(m, i, 0, 0); + + mixer_setrecsrc(m, SOUND_MASK_MIC); + +@@ -836,8 +874,12 @@ mixer_reinit(device_t dev) + return i; + } + +- for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) +- mixer_set(m, i, m->level[i]); ++ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { ++ if (m->mutedevs & (1 << i)) ++ mixer_set(m, i, 0, 0); ++ else ++ mixer_set(m, i, 0, m->level[i]); ++ } + + mixer_setrecsrc(m, m->recsrc); + snd_mtxunlock(m->lock); +@@ -863,10 +905,8 @@ sysctl_hw_snd_hwvol_mixer(SYSCTL_HANDLER_ARGS) + if (dev == -1) { + snd_mtxunlock(m->lock); + return EINVAL; +- } +- else if (dev != m->hwvol_mixer) { ++ } else { + m->hwvol_mixer = dev; +- m->hwvol_muted = 0; + } + } + snd_mtxunlock(m->lock); +@@ -897,14 +937,7 @@ mixer_hwvol_init(device_t dev) + void + mixer_hwvol_mute_locked(struct snd_mixer *m) + { +- if (m->hwvol_muted) { +- m->hwvol_muted = 0; +- mixer_set(m, m->hwvol_mixer, m->hwvol_mute_level); +- } else { +- m->hwvol_muted++; +- m->hwvol_mute_level = mixer_get(m, m->hwvol_mixer); +- mixer_set(m, m->hwvol_mixer, 0); +- } ++ mix_setmutedevs(m, m->mutedevs ^ (1 << m->hwvol_mixer)); + } + + void +@@ -925,11 +958,8 @@ mixer_hwvol_step_locked(struct snd_mixer *m, int left_step, int right_step) + { + int level, left, right; + +- if (m->hwvol_muted) { +- m->hwvol_muted = 0; +- level = m->hwvol_mute_level; +- } else +- level = mixer_get(m, m->hwvol_mixer); ++ level = mixer_get(m, m->hwvol_mixer); ++ + if (level != -1) { + left = level & 0xff; + right = (level >> 8) & 0xff; +@@ -943,7 +973,8 @@ mixer_hwvol_step_locked(struct snd_mixer *m, int left_step, int right_step) + right = 0; + else if (right > 100) + right = 100; +- mixer_set(m, m->hwvol_mixer, left | right << 8); ++ ++ mixer_set(m, m->hwvol_mixer, m->mutedevs, left | right << 8); + } + } + +@@ -976,7 +1007,7 @@ mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right) + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); +- ret = mixer_set(m, dev, left | (right << 8)); ++ ret = mixer_set(m, dev, m->mutedevs, left | (right << 8)); + snd_mtxunlock(m->lock); + + return ((ret != 0) ? ENXIO : 0); +@@ -1305,10 +1336,18 @@ mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + goto done; + } + if ((cmd & ~0xff) == MIXER_WRITE(0)) { +- if (j == SOUND_MIXER_RECSRC) ++ switch (j) { ++ case SOUND_MIXER_RECSRC: + ret = mixer_setrecsrc(m, *arg_i); +- else +- ret = mixer_set(m, j, *arg_i); ++ break; ++ case SOUND_MIXER_MUTE: ++ mix_setmutedevs(m, *arg_i); ++ ret = 0; ++ break; ++ default: ++ ret = mixer_set(m, j, m->mutedevs, *arg_i); ++ break; ++ } + snd_mtxunlock(m->lock); + return ((ret == 0) ? 0 : ENXIO); + } +@@ -1319,6 +1358,9 @@ mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + case SOUND_MIXER_STEREODEVS: + v = mix_getdevs(m); + break; ++ case SOUND_MIXER_MUTE: ++ v = mix_getmutedevs(m); ++ break; + case SOUND_MIXER_RECMASK: + v = mix_getrecdevs(m); + break; +@@ -1327,6 +1369,7 @@ mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + break; + default: + v = mixer_get(m, j); ++ break; + } + *arg_i = v; + snd_mtxunlock(m->lock); +@@ -1555,5 +1598,5 @@ mix_set_locked(struct snd_mixer *m, u_int dev, int left, int right) + + level = (left & 0xFF) | ((right & 0xFF) << 8); + +- return (mixer_set(m, dev, level)); ++ return (mixer_set(m, dev, m->mutedevs, level)); + } +diff --git a/sys/dev/sound/pcm/mixer.h b/sys/dev/sound/pcm/mixer.h +index 8e11d553a3e..7857609b289 100644 +--- a/sys/dev/sound/pcm/mixer.h ++++ b/sys/dev/sound/pcm/mixer.h +@@ -60,8 +60,10 @@ device_t mix_get_dev(struct snd_mixer *m); + + void mix_setdevs(struct snd_mixer *m, u_int32_t v); + void mix_setrecdevs(struct snd_mixer *m, u_int32_t v); ++void mix_setmutedevs(struct snd_mixer *m, u_int32_t v); + u_int32_t mix_getdevs(struct snd_mixer *m); + u_int32_t mix_getrecdevs(struct snd_mixer *m); ++u_int32_t mix_getmutedevs(struct snd_mixer *m); + void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs); + void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev); + u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev); +diff --git a/sys/dev/sound/pcm/sound.c b/sys/dev/sound/pcm/sound.c +index b4be28aeff8..6b1b8bcbc93 100644 +--- a/sys/dev/sound/pcm/sound.c ++++ b/sys/dev/sound/pcm/sound.c +@@ -1012,12 +1012,30 @@ SYSCTL_PROC(_hw_snd, OID_AUTO, clone_gc, CTLTYPE_INT | CTLFLAG_RWTUN, + "global clone garbage collector"); + #endif + ++static u_int8_t ++pcm_mode_init(struct snddev_info *d) ++{ ++ u_int8_t mode = 0; ++ ++ if (d->playcount > 0) ++ mode |= PCM_MODE_PLAY; ++ if (d->reccount > 0) ++ mode |= PCM_MODE_REC; ++ if (d->mixer_dev != NULL) ++ mode |= PCM_MODE_MIXER; ++ ++ return (mode); ++} ++ + static void + pcm_sysinit(device_t dev) + { + struct snddev_info *d = device_get_softc(dev); ++ u_int8_t mode; ++ ++ mode = pcm_mode_init(d); + +- /* XXX: an user should be able to set this with a control tool, the ++ /* XXX: a user should be able to set this with a control tool, the + sysadmin then needs min+max sysctls for this */ + SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), +@@ -1027,6 +1045,11 @@ pcm_sysinit(device_t dev) + "bitperfect", CTLTYPE_INT | CTLFLAG_RWTUN, d, sizeof(d), + sysctl_dev_pcm_bitperfect, "I", + "bit-perfect playback/recording (0=disable, 1=enable)"); ++ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), ++ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), ++ OID_AUTO, "mode", CTLFLAG_RD, NULL, mode, ++ "mode (1=mixer, 2=play, 4=rec. The values are OR'ed if more than one" ++ "mode is supported)"); + #ifdef SND_DEBUG + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, +@@ -1130,7 +1153,7 @@ pcm_register(device_t dev, void *devinfo, int numplay, int numrec) + sysctl_ctx_init(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", +- CTLFLAG_RD, 0, "record channels node"); ++ CTLFLAG_RD, 0, "recording channels node"); + + if (numplay > 0 || numrec > 0) + d->flags |= SD_F_AUTOVCHAN; +diff --git a/sys/dev/sound/pcm/sound.h b/sys/dev/sound/pcm/sound.h +index d4b3a23e8eb..e9979cf426c 100644 +--- a/sys/dev/sound/pcm/sound.h ++++ b/sys/dev/sound/pcm/sound.h +@@ -411,6 +411,10 @@ struct snddev_info { + void sound_oss_sysinfo(oss_sysinfo *); + int sound_oss_card_info(oss_card_info *); + ++#define PCM_MODE_MIXER 0x01 ++#define PCM_MODE_PLAY 0x02 ++#define PCM_MODE_REC 0x04 ++ + #define PCM_LOCKOWNED(d) mtx_owned((d)->lock) + #define PCM_LOCK(d) mtx_lock((d)->lock) + #define PCM_UNLOCK(d) mtx_unlock((d)->lock) diff --git a/diff/mixer_rc.diff b/diff/mixer_rc.diff new file mode 100644 --- /dev/null +++ b/diff/mixer_rc.diff @@ -0,0 +1,8 @@ +61c61 +< /usr/sbin/mixer_prog -f ${dev} -o > /var/db/${1}-state 2>/dev/null +--- +> /usr/sbin/mixer -f ${dev} -s > /var/db/${1}-state 2>/dev/null +75c75 +< /usr/sbin/mixer_prog -f ${dev} `cat ${file}` > /dev/null +--- +> /usr/sbin/mixer -f ${dev} `cat ${file}` > /dev/null diff --git a/mixer_lib/Makefile b/mixer_lib/Makefile new file mode 100644 --- /dev/null +++ b/mixer_lib/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +LIB= mixer +SRCS= ${LIB}.c +INCS= ${LIB}.h +MAN= ${LIB}.3 +CFLAGS+= -pedantic -Wall -Wunused +LDFLAGS+= -lm + +.include diff --git a/mixer_lib/mixer.h b/mixer_lib/mixer.h new file mode 100644 --- /dev/null +++ b/mixer_lib/mixer.h @@ -0,0 +1,124 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * 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. + */ + +#ifndef _MIXER_H_ +#define _MIXER_H_ + +__BEGIN_DECLS + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include + +#define MIX_ISSET(n,f) (((1 << (n)) & (f)) ? 1 : 0) +#define MIX_ISDEV(m,n) MIX_ISSET(n, (m)->devmask) +#define MIX_ISMUTE(m,n) MIX_ISSET(n, (m)->mutemask) +#define MIX_ISREC(m,n) MIX_ISSET(n, (m)->recmask) +#define MIX_ISRECSRC(m,n) MIX_ISSET(n, (m)->recsrc) + +/* Forward declarations */ +struct mixer; +struct mix_dev; + +typedef struct mix_ctl mix_ctl_t; +typedef struct mix_volume mix_volume_t; + +/* User-defined controls */ +struct mix_ctl { + struct mix_dev *parent_dev; /* parent device */ + int id; /* control id */ + char name[NAME_MAX]; /* control name */ + int (*mod)(struct mix_dev *, void *); /* modify control values */ + int (*print)(struct mix_dev *, void *); /* print control */ + TAILQ_ENTRY(mix_ctl) ctls; +}; + +struct mix_dev { + struct mixer *parent_mixer; /* parent mixer */ + char name[NAME_MAX]; /* device name (e.g "vol") */ + int devno; /* device number */ + struct mix_volume { +#define MIX_VOLMIN 0.0f +#define MIX_VOLMAX 1.0f +#define MIX_VOLNORM(v) ((v) / 100.0f) +#define MIX_VOLDENORM(v) ((int)roundf((v) * 100.0f)) + float left; /* left volume */ + float right; /* right volume */ + } vol; + int nctl; /* number of controls */ + TAILQ_HEAD(, mix_ctl) ctls; /* control list */ + TAILQ_ENTRY(mix_dev) devs; +}; + +struct mixer { + TAILQ_HEAD(, mix_dev) devs; /* device list */ + struct mix_dev *dev; /* selected device */ + oss_mixerinfo mi; /* mixer info */ + oss_card_info ci; /* audio card info */ + char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */ + int fd; /* file descriptor */ + int unit; /* audio card unit */ + int ndev; /* number of devices */ + int devmask; /* supported devices */ +#define MIX_MUTE 0x01 +#define MIX_UNMUTE 0x02 +#define MIX_TOGGLEMUTE 0x04 + int mutemask; /* muted devices */ + int recmask; /* recording devices */ +#define MIX_ADDRECSRC 0x01 +#define MIX_REMOVERECSRC 0x02 +#define MIX_SETRECSRC 0x04 +#define MIX_TOGGLERECSRC 0x08 + int recsrc; /* recording sources */ +#define MIX_MODE_MIXER 0x01 +#define MIX_MODE_PLAY 0x02 +#define MIX_MODE_REC 0x04 + int mode; /* dev.pcm.X.mode sysctl */ + int f_default; /* default mixer flag */ +}; + +struct mixer *mixer_open(const char *); +int mixer_close(struct mixer *); +struct mix_dev *mixer_get_dev(struct mixer *, int); +struct mix_dev *mixer_get_dev_byname(struct mixer *, const char *); +int mixer_add_ctl(struct mix_dev *, int, const char *, + int (*)(struct mix_dev *, void *), int (*)(struct mix_dev *, void *)); +int mixer_add_ctl_s(mix_ctl_t *); +int mixer_remove_ctl(mix_ctl_t *); +mix_ctl_t *mixer_get_ctl(struct mix_dev *, int); +mix_ctl_t *mixer_get_ctl_byname(struct mix_dev *, const char *); +int mixer_set_vol(struct mixer *, mix_volume_t); +int mixer_set_mute(struct mixer *, int); +int mixer_mod_recsrc(struct mixer *, int); +int mixer_get_dunit(void); +int mixer_set_dunit(struct mixer *, int); +int mixer_get_mode(int); +int mixer_get_nmixers(void); + +__END_DECLS + +#endif /* _MIXER_H_ */ diff --git a/mixer_lib/mixer.3 b/mixer_lib/mixer.3 new file mode 100644 --- /dev/null +++ b/mixer_lib/mixer.3 @@ -0,0 +1,537 @@ +.\"- +.\" Copyright (c) 2021 Christos Margiolis +.\" +.\" 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. +.\" + +.Dd June 30, 2021 +.Dt mixer 3 +.Os +.Sh NAME +.Nm mixer_open , +.Nm mixer_close , +.Nm mixer_get_dev , +.Nm mixer_get_dev_byname , +.Nm mixer_add_ctl , +.Nm mixer_add_ctl_s , +.Nm mixer_remove_ctl , +.Nm mixer_get_ctl , +.Nm mixer_get_ctl_byname , +.Nm mixer_set_vol , +.Nm mixer_set_mute , +.Nm mixer_mod_recsrc , +.Nm mixer_get_dunit , +.Nm mixer_set_dunit , +.Nm mixer_get_mode, +.Nm mixer_get_nmixers , +.Nm MIX_ISDEV , +.Nm MIX_ISMUTE , +.Nm MIX_ISREC , +.Nm MIX_ISRECSRC , +.Nm MIX_VOLNORM , +.Nm MIX_VOLDENORM +.Nd interface to OSS mixers +.Sh LIBRARY +Mixer library (libmixer, -lmixer) +.Sh SYNOPSIS +.In mixer.h +.Ft struct mixer * +.Fn mixer_open "const char *name" +.Ft int +.Fn mixer_close "struct mixer *m" +.Ft struct mix_dev * +.Fn mixer_get_dev "struct mixer *m" "int devno" +.Ft struct mix_dev * +.Fn mixer_get_dev_byname "struct mixer *m" "name" +.Ft int +.Fn mixer_add_ctl "struct mix_dev *parent" "int id" "const char *name" \ + "int (*mod)(struct mix_dev *d, void *p)" \ + "int (*print)(struct mix_dev *d, void *p) +.Ft int +.Fn mixer_add_ctl_s "mix_ctl_t *ctl" +.Ft int +.Fn mixer_remove_ctl "mix_ctl_t *ctl" +.Ft mix_ctl_t * +.Fn mixer_get_ctl "struct mix_dev *d" "int id" +.Ft mix_ctl_t * +.Fn mixer_get_ctl_byname "struct mix_dev *d" "const char *name" +.Ft int +.Fn mixer_set_vol "struct mixer *m" "mix_volume_t vol" +.Ft int +.Fn mixer_set_mute "struct mixer *m" "int opt" +.Ft int +.Fn mixer_mod_recsrc "struct mixer *m" "int opt" +.Ft int +.Fn mixer_get_dunit "void" +.Ft int +.Fn mixer_set_dunit "struct mixer *m" "int unit" +.Ft int +.Fn mixer_get_mode "int unit" +.Ft int +.Fn mixer_get_nmixers "void" +.Ft int +.Fn MIX_ISDEV "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISMUTE "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISREC "struct mixer *m" "int devno" +.Ft int +.Fn MIX_ISRECSRC "struct mixer *m" "int devno" +.Ft float +.Fn MIX_VOLNORM "int v" +.Ft int +.Fn MIX_VOLDENORM "float v" +.Sh DESCRIPTION +The +.Nm mixer +library allows userspace programs to access and manipulate OSS sound mixers in +a simple way. +.Ss Mixer +.Pp +A mixer is described by the following structure: +.Bd -literal +struct mixer { + TAILQ_HEAD(, mix_dev) devs; /* device list */ + struct mix_dev *dev; /* selected device */ + oss_mixerinfo mi; /* mixer info */ + oss_card_info ci; /* audio card info */ + char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */ + int fd; /* file descriptor */ + int unit; /* audio card unit */ + int ndev; /* number of devices */ + int devmask; /* supported devices */ +#define MIX_MUTE 0x01 +#define MIX_UNMUTE 0x02 +#define MIX_TOGGLEMUTE 0x04 + int mutemask; /* muted devices */ + int recmask; /* recording devices */ +#define MIX_ADDRECSRC 0x01 +#define MIX_REMOVERECSRC 0x02 +#define MIX_SETRECSRC 0x04 +#define MIX_TOGGLERECSRC 0x08 + int recsrc; /* recording sources */ +#define MIX_MODE_MIXER 0x01 +#define MIX_MODE_PLAY 0x02 +#define MIX_MODE_REC 0x04 + int mode; /* dev.pcm.X.mode sysctl */ + int f_default; /* default mixer flag */ +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "f_default" +.It Fa devs +A tail queue structure containing all supported mixer devices. +.It Fa dev +A pointer to the currently selected device. The device is one of the elements in +.Ar devs . +.It Fa mi +OSS information about the mixer. Look at the definition of the +.Ft oss_mixerinfo +structure in +.In sys/soundcard.h +to see its fields. +.It Fa ci +OSS audio card information. This structure is also defined in +.In sys/soundcard.h . +.It Fa name +Path to the mixer (e.g /dev/mixer0). +.It Fa fd +File descriptor returned when the mixer is opened in +.Fn mixer_open . +.It Fa unit +Audio card unit. Since each mixer device maps to a pcmX device, +.Ar unit +is always equal to the number of that pcmX device. For example, if the audio +device's number is 0 (i.e pcm0), then +.Ar unit +is 0 as well. This number is useful when checking if the mixer's audio +card is the default one. +.It Fa ndev +Number of devices in +.Ar devs . +.It Fa devmask +Bit mask containing all supported devices for the mixer. For example +if device 10 is supported, then the 10th bit in the mask will be set. By default, +.Fn mixer_open +stores only the supported devices in devs, so it's very unlikely this mask will +be needed. +.It Fa mutemask +Bit mask containing all muted devices. The logic is the same as with +.Ar devmask . +.It Fa recmask +Bit mask containing all recording devices. Again, same logic as with the +other masks. +.It Fa recsrc +Bit mask containing all recording sources. Yes, same logic again. +.It Fa mode +Bit mask containing the supported modes for this audio device. It holds the value +of the +.Ar dev.pcm.X.mode +sysctl. +.It Fa f_default +Flag which tells whether the mixer's audio card is the default one. +.El +.Ss Mixer device +.Pp +Each mixer device stored in a mixer is described as follows: +.Bd -literal +struct mix_dev { + struct mixer *parent_mixer; /* parent mixer */ + char name[NAME_MAX]; /* device name (e.g "vol") */ + int devno; /* device number */ + struct mix_volume { +#define MIX_VOLMIN 0.0f +#define MIX_VOLMAX 1.0f +#define MIX_VOLNORM(v) ((v) / 100.0f) +#define MIX_VOLDENORM(v) ((int)roundf((v) * 100.0f)) + float left; /* left volume */ + float right; /* right volume */ + } vol; + int nctl; /* number of controls */ + TAILQ_HEAD(, mix_ctl) ctls; /* control list */ + TAILQ_ENTRY(mix_dev) devs; +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "parent_mixer" +.It Fa parent_mixer +Pointer to the mixer the device is attached to. +.It Fa name +Device name given by the OSS API. Devices can have one of the following names: +.Bd -ragged +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 +.It Fa devno +Device's index in the SOUND_MIXER_NRDEVICES macro defined in +.In sys/soundcard.h . +This number is used to check against the masks defined in the +.Ar mixer +structure. +.It Fa left, right +Left and right-ear volumes. Although the OSS API stores volumes in integers from +0-100, we normalize them to 32-bit floating point numbers. However, the volumes +can be denormalized using the +.Ar MIX_VOLDENORM +macro if needed. +.It Fa nctl +Number of user-defined mixer controls associated with the device. +.It Fa ctls +A tail queue containing user-defined mixer controls. +.El +.Ss User-defined mixer controls +.Pp +Each mixer device can have user-defined controls. The control structure +is defined as follows: +.Bd -literal +struct mix_ctl { + struct mix_dev *parent_dev; /* parent device */ + int id; /* control id */ + char name[NAME_MAX]; /* control name */ + int (*mod)(struct mix_dev *, void *); /* modify control values */ + int (*print)(struct mix_dev *, void *); /* print control */ + TAILQ_ENTRY(mix_ctl) ctls; +}; +.Ed +.Pp +The fields are follows: +.Bl -tag -width "parent_dev" +.It Fa parent_dev +Pointer to the device the control is attached to. +.It Fa id +Control ID assigned by the caller. Even though the library will +report it, care has to be taken to not give a control the same ID in case +the caller has to choose controls using their ID. +.It Fa name +Control name. As with +.Ar id , +the caller has to make sure the same name is not used more than once. +.It Fa mod +Function pointer to a control modification function. As in +.Xr mixer 8 , +each mixer control's values can be modified. For example, if we have a +volume control, the +.Ar mod +function will be responsible for handling volume changes. +.It Fa print +Function pointer to a control print function. +.El +.Ss Opening and closing the mixer +.Pp +The application must first call the +.Fn mixer_open +function to obtain a handle to the device, which is used as an argument +in most other functions and macros. The parameter +.Ar name +specifies the path to the mixer. OSS mixers are stored under +.Ar /dev/mixerN +where +.Ar N +is the number of the mixer device. Each device maps to an actual +.Ar pcm +audio card, so +.Ar /dev/mixer0 +is the mixer for +.Ar pcm0 , +and so on. If +.Ar name +is +.Ar NULL +or +.Ar /dev/mixer , +.Fn mixer_open +opens the default mixer (hw.snd.defaul_unit). +.Pp +The +.Fn mixer_close +function frees resources and closes the mixer device. It's a good practice to +always call it when the application is done using the mixer. +.Ss Manipulating the mixer +.Pp +The +.Fn mixer_get_dev +and +.Fn mixer_get_dev_byname +functions select a mixer device, either by its number or by its name +respectively. The mixer structure keeps a list of all the devices, but only +one can be manipulated at a time. Each time a new device is to be manipulated, +one of the two functions has to be called. +.Pp +The +.Fn mixer_set_vol +function changes the volume of the selected mixer device. The +.Ar vol +parameter is a structure that stores the left and right volumes of a given +device. The allowed volume values are between MIX_VOLMIN (0.0) and +MIX_VOLMAX (1.0). +.Pp +The +.Fn mixer_set_mute +function modifies the mute of a selected device. The +.Ar opt +parameter has to be one of the following options: +.Bl -tag -width MIX_TOGGLEMUTE -offset indent +.It Dv MIX_MUTE +Mute the device. +.It Dv MIX_UNMUTE +Unmute the device. +.It Dv MIX_TOGGLEMUTE +Toggle the device's mute (e.g mute if unmuted and unmute if muted). +.El +.Pp +The +.Fn mixer_mod_recsrc +function modifies a recording device. The selected device has to be +a recording device, otherwise the function will fail. The +.Ar opt +parameter has to be one of the following options: +.Bl -tag -width MIX_REMOVERECSRC -offset indent +.It Dv MIX_ADDRECSRC +Add device to the recording sources. +.It Dv MIX_REMOVERECSRC +Remove device from the recording sources. +.It Dv MIX_SETRECSRC +Set device as the only recording source. +.It Dv MIX_TOGGLERECSRC +Toggle device from the recording sources. +.El +.Pp +The +.Fn mixer_get_dunit +and +.Fn mixer_set_dunit +functions get and set the default audio card in the system. Although this is +not really a mixer feature, it's useful to have instead of having to use +the +.Xr sysctl 3 +controls. +.Pp +The +.Fn mixer_get_mode +function returns the playback/recording mode of the audio device the mixer +belongs to. The available values are the following: +.Bl -tag -width "MIX_STATUS_PLAY | MIX_STATUS_REC" -offset indent +.It Dv MIX_STATUS_NONE +Neither playback nor recording. +.It Dv MIX_STATUS_PLAY +Playback. +.It Dv MIX_STATUS_REC +Recording. +.It Dv MIX_STATUS_PLAY | MIX_STATUS_REC +Playback and recording. +.El +.Pp +The +.Fn mixer_get_nmixers +function returns the total number of mixer devices in the system. +.Pp +The +.Fn MIX_ISDEV +macro checks if a device is actually a valid device for a given mixer. It's very +unlikely that this macro will ever be needed since the library stores only +valid devices by default. +.Pp +The +.Fn MIX_ISMUTE +macro checks if a device is muted. +.Pp +The +.Fn MIX_ISREC +macro checks if a device is a recording device. +.Pp +The +.Fn MIX_ISRECSRC +macro checks if a device is a recording source. +.Pp +The +.Fn MIX_VOLNORM +macro normalizes a value to 32-bit floating point number. It's used +to normalize the volumes read from the OSS API. +.Pp +The +.Fn MIX_VOLDENORM +macro denormalizes the left and right volumes stores in the +.Ft mix_dev +structure. +.Ss Defining and using mixer controls +.Pp +The +.Fn mix_add_ctl +function creates a control and attaches it to the device specified in the +.Ar parent +argument. +.Pp +The +.Fn mix_add_ctl_s +function does the same thing as with +.Fn mix_add_ctl +but the caller passes a +.Ft mix_ctl_t * +structure instead of each field as a seperate argument. +.Pp +The +.Fn mixer_remove_ctl +functions removes a control from the device its attached to. +.Pp +The +.Fn mixer_get_ctl +function searches for a control in the device specified in the +.Ar d +argument and returns a pointer to it. The search is done using the control's ID. +.Pp +The +.Fn mixer_get_ctl_byname +function is the same as with +.Fn mixer_get_ctl +but the search is done using the control's name. +.Sh RETURN VALUES +.Pp +The +.Fn mixer_open +function returns the newly created handle on success and NULL on failure. +.Pp +The +.Fn mixer_close , +.Fn mixer_set_vol , +.Fn mixer_set_mute , +.Fn mixer_mod_recsrc , +.Fn mixer_get_dunut , +.Fn mixer_set_dunit +and +.Fn mixer_get_nmixers +functions return 0 or positive values on success and -1 on failure. +.Pp +The +.Fn mixer_get_dev +and +.Fn mixer_get_dev_byname +functions return the selected device on success and NULL on failure. +.Pp +All functions set the value of +.Ar errno +on failure. +.Sh EXAMPLES +.Ss Change the volume of a device +.Bd -literal +struct mixer *m; +mix_volume_t vol; +char *mix_name, *dev_name; + +mix_name = ...; +if ((m = mixer_open(mix_name)) == NULL) + err(1, "mixer_open: %s", mix_name); + +dev_name = ...; +if ((m->dev = mixer_get_dev_byname(m, dev_name)) < 0) + err(1, "unknown device: %s", dev_name); + +vol.left = ...; +vol.right = ....; +if (mixer_set_vol(m, vol) < 0) + warn("cannot change volume"); + +(void)mixer_close(m); +.Ed +.Ss Mute all unmuted devices +.Bd -literal +struct mixer *m; +struct mix_dev *dp; + +if ((m = mixer_open(NULL)) == NULL) /* Open the default mixer. */ + err(1, "mixer_open"); +TAILQ_FOREACH(dp, &m->devs, devs) { + m->dev = dp; /* Select device. */ + if (M_ISMUTE(m, dp->devno)) + continue; + if (mixer_set_mute(m, MIX_MUTE) < 0) + warn("cannot mute device: %s", dp->name); +} + +(void)mixer_close(m); +.Ed +.Ss Print all recording sources' names and volumes +.Bd -literal +struct mixer *m; +struct mix_dev *dp; + +char *mix_name, *dev_name; + +mix_name = ...; +if ((m = mixer_open(mix_name)) == NULL) + err(1, "mixer_open: %s", mix_name); + +TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISRECSRC(m, dp->devno)) + printf("%s\\t%.2f:%.2f\\n", + dp->name, dp->vol.left, dp->vol.right); +} + +(void)mixer_close(m); +.Ed +.Sh SEE ALSO +.Xr mixer 8 , +.Xr sound 4 , +.Xr sysctl 3 , +.Xr queue 3 , +.Xr errno 2 +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c new file mode 100644 --- /dev/null +++ b/mixer_lib/mixer.c @@ -0,0 +1,491 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mixer.h" + +#define BASEPATH "/dev/mixer" + +static int _mixer_readvol(struct mixer *, struct mix_dev *); + +/* + * Fetch volume from the device. + */ +static int +_mixer_readvol(struct mixer *m, struct mix_dev *dev) +{ + int v; + + if (ioctl(m->fd, MIXER_READ(dev->devno), &v) < 0) + return (-1); + dev->vol.left = MIX_VOLNORM(v & 0x00ff); + dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff); + + return (0); +} + +/* + * 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 + * mixer for pcm0, and so on. + * + * @param name path to mixer device. NULL or "/dev/mixer" for the + * the default mixer (i.e `hw.snd.default_unit`). + */ +struct mixer * +mixer_open(const char *name) +{ + struct mixer *m = NULL; + struct mix_dev *dp; + const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + 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; + goto fail; + } + /* `name` is "/dev/mixer" so, we'll use the default unit. */ + if (strncmp(name, BASEPATH, strlen(name)) == 0) + goto dunit; + m->unit = strtol(name + strlen(BASEPATH), NULL, 10); + (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); + } + + if ((m->fd = open(m->name, O_RDWR)) < 0) + goto fail; + + m->devmask = m->recmask = m->recsrc = 0; + m->f_default = m->unit == mixer_get_dunit(); + m->mode = mixer_get_mode(m->unit); + /* 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 || + 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) + goto fail; + + TAILQ_INIT(&m->devs); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!MIX_ISDEV(m, i)) + continue; + if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL) + goto fail; + dp->parent_mixer = m; + dp->devno = i; + dp->nctl = 0; + if (_mixer_readvol(m, dp) < 0) + goto fail; + (void)strlcpy(dp->name, names[i], sizeof(dp->name)); + TAILQ_INIT(&dp->ctls); + 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); + + return (NULL); +} + +/* + * Free resources and close the mixer. + */ +int +mixer_close(struct mixer *m) +{ + struct mix_dev *dp; + int r; + + r = close(m->fd); + while (!TAILQ_EMPTY(&m->devs)) { + dp = TAILQ_FIRST(&m->devs); + TAILQ_REMOVE(&m->devs, dp, devs); + while (!TAILQ_EMPTY(&dp->ctls)) + (void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls)); + free(dp); + } + free(m); + + return (r); +} + +/* + * Select a mixer device. The mixer structure keeps a list of all the devices + * the mixer has, but only one can be manipulated at a time -- this is what + * the `dev` in the mixer structure field is for. Each time a device is to be + * manipulated, `dev` has to point to it first. + * + * The caller must manually assign the return value to `m->dev`. + */ +struct mix_dev * +mixer_get_dev(struct mixer *m, int dev) +{ + struct mix_dev *dp; + + if (dev < 0 || dev >= m->ndev) { + errno = ERANGE; + return (NULL); + } + TAILQ_FOREACH(dp, &m->devs, devs) { + if (dp->devno == dev) + return (dp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Select a device by name. + * + * @param name device name (e.g vol, pcm, ...) + */ +struct mix_dev * +mixer_get_dev_byname(struct mixer *m, const char *name) +{ + struct mix_dev *dp; + + TAILQ_FOREACH(dp, &m->devs, devs) { + if (!strncmp(dp->name, name, sizeof(dp->name))) + return (dp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Add a mixer control to a device. + */ +int +mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name, + int (*mod)(struct mix_dev *, void *), + int (*print)(struct mix_dev *, void *)) +{ + struct mix_dev *dp; + mix_ctl_t *ctl, *cp; + + /* XXX: should we accept NULL name? */ + if (parent_dev == NULL) { + errno = EINVAL; + return (-1); + } + if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL) + return (-1); + ctl->parent_dev = parent_dev; + ctl->id = id; + if (name != NULL) + (void)strlcpy(ctl->name, name, sizeof(ctl->name)); + ctl->mod = mod; + ctl->print = print; + dp = ctl->parent_dev; + /* Make sure the same ID or name doesn't exist already. */ + TAILQ_FOREACH(cp, &dp->ctls, ctls) { + if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) { + errno = EINVAL; + return (-1); + } + } + TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls); + dp->nctl++; + + return (0); +} + +/* + * Same as `mixer_add_ctl`. + */ +int +mixer_add_ctl_s(mix_ctl_t *ctl) +{ + if (ctl == NULL) + return (-1); + + return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name, + ctl->mod, ctl->print)); +} + +/* + * Remove a mixer control from a device. + */ +int +mixer_remove_ctl(mix_ctl_t *ctl) +{ + struct mix_dev *p = ctl->parent_dev; + + if (ctl == NULL) { + errno = EINVAL; + return (-1); + } + if (!TAILQ_EMPTY(&p->ctls)) { + TAILQ_REMOVE(&p->ctls, ctl, ctls); + free(ctl); + } + + return (0); +} + +/* + * Get a mixer control by id. + */ +mix_ctl_t * +mixer_get_ctl(struct mix_dev *d, int id) +{ + mix_ctl_t *cp; + + TAILQ_FOREACH(cp, &d->ctls, ctls) { + if (cp->id == id) + return (cp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Get a mixer control by name. + */ +mix_ctl_t * +mixer_get_ctl_byname(struct mix_dev *d, const char *name) +{ + mix_ctl_t *cp; + + TAILQ_FOREACH(cp, &d->ctls, ctls) { + if (!strncmp(cp->name, name, sizeof(cp->name))) + return (cp); + } + errno = EINVAL; + + return (NULL); +} + +/* + * Change the mixer's left and right volume. The allowed volume values are + * between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires + * an integer value between 0 and 100 stored as `lvol | rvol << 8` -- for + * that reason, we de-normalize the 32-bit float volume value, before + * we pass it to the `ioctl`. + * + * Volume clumping should be done by the caller. + */ +int +mixer_set_vol(struct mixer *m, mix_volume_t vol) +{ + int v; + + if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX || + vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) { + errno = ERANGE; + return (-1); + } + v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8; + if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0) + return (-1); + if (_mixer_readvol(m, m->dev) < 0) + return (-1); + + return (0); +} + +/* + * Manipulate a device's mute. + * + * @param opt MIX_MUTE mute device + * MIX_UNMUTE unmute device + * MIX_TOGGLEMUTE toggle device's mute + */ +int +mixer_set_mute(struct mixer *m, int opt) +{ + switch (opt) { + case MIX_MUTE: + m->mutemask |= (1 << m->dev->devno); + break; + case MIX_UNMUTE: + m->mutemask &= ~(1 << m->dev->devno); + break; + case MIX_TOGGLEMUTE: + m->mutemask ^= (1 << m->dev->devno); + break; + default: + errno = EINVAL; + return (-1); + } + if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0) + return (-1); + if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0) + return (-1); + + return 0; +} + +/* + * Modify a recording device. The selected device has to be a recording device, + * otherwise the function will fail. + * + * @param opt MIX_ADDRECSRC add device to recording sources + * MIX_REMOVERECSRC remove device from recording sources + * MIX_SETRECSRC set device as the only recording source + * MIX_TOGGLERECSRC toggle device from recording sources + */ +int +mixer_mod_recsrc(struct mixer *m, int opt) +{ + if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) { + errno = ENODEV; + return (-1); + } + switch (opt) { + case MIX_ADDRECSRC: + m->recsrc |= (1 << m->dev->devno); + break; + case MIX_REMOVERECSRC: + m->recsrc &= ~(1 << m->dev->devno); + break; + case MIX_SETRECSRC: + m->recsrc = (1 << m->dev->devno); + break; + case MIX_TOGGLERECSRC: + m->recsrc ^= (1 << m->dev->devno); + break; + default: + errno = EINVAL; + return (-1); + } + if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0) + return (-1); + if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) + return (-1); + + return (0); +} + +/* + * Get default audio card's number. This is used to open the default mixer + * and set the mixer structure's `f_default` flag. + */ +int +mixer_get_dunit(void) +{ + size_t size; + int unit; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0) + return (-1); + + return (unit); +} + +/* + * 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 +mixer_set_dunit(struct mixer *m, int unit) +{ + size_t size; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0) + return (-1); + /* XXX: how will other mixers get updated? */ + m->f_default = m->unit == unit; + + return (0); +} + +/* + * Get sound device mode (none, play, rec, play+rec). Userland programs can + * use the MIX_STATUS_* flags to determine the mode of the device. + */ +int +mixer_get_mode(int unit) +{ + char buf[64]; + size_t size; + unsigned int mode; + + (void)snprintf(buf, sizeof(buf) - 1, "dev.pcm.%d.mode", unit); + size = sizeof(unsigned int); + if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0) + return (-1); + + return (mode); +} + +/* + * Get the total number of mixers in the system. + */ +int +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. + */ + if ((m = mixer_open(NULL)) == NULL) + return (-1); + if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) { + (void)mixer_close(m); + return (-1); + } + (void)mixer_close(m); + + return (si.nummixers); +} diff --git a/mixer_prog/Makefile b/mixer_prog/Makefile new file mode 100644 --- /dev/null +++ b/mixer_prog/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= mixer_prog +BINDIR= /usr/sbin +SRCS= ${PROG}.c +MAN= ${PROG}.8 +CFLAGS+= -pedantic -Wall -Wunused +LDFLAGS+= -lmixer -lm + +.include diff --git a/mixer_prog/mixer_prog.8 b/mixer_prog/mixer_prog.8 new file mode 100644 --- /dev/null +++ b/mixer_prog/mixer_prog.8 @@ -0,0 +1,241 @@ +.\"- +.\" Copyright (c) 2021 Christos Margiolis +.\" +.\" 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. +.\" + +.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 new file mode 100644 --- /dev/null +++ b/mixer_prog/mixer_prog.c @@ -0,0 +1,465 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include + +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; + +#define C_VOL 0 +#define C_MUT 1 +#define C_SRC 2 + TAILQ_FOREACH(dp, &m->devs, devs) { + (void)mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume); + (void)mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute); + (void)mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc); + } +} + +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); +}