aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Petter Selasky <hselasky@FreeBSD.org>2021-09-22 13:42:51 +0000
committerHans Petter Selasky <hselasky@FreeBSD.org>2021-09-22 17:43:56 +0000
commit903873ce15600fc02a0ea42cbf888cff232b411d (patch)
treeff78e316bb0cc47612311aca4f32331e84d35c5a
parent884f38590c3cc0b1a2c00904c1f1f6c791376308 (diff)
downloadsrc-903873ce15600fc02a0ea42cbf888cff232b411d.tar.gz
src-903873ce15600fc02a0ea42cbf888cff232b411d.zip
Implement and use new mixer(3) library for FreeBSD.
Wiki article: https://wiki.freebsd.org/SummerOfCode2021Projects/SoundMixerImprovements This project was part of Google Summer of Code 2021. Submitted by: christos@ Differential Revision: https://reviews.freebsd.org/D31636 Sponsored by: NVIDIA Networking
-rw-r--r--lib/Makefile1
-rw-r--r--lib/libmixer/Makefile8
-rw-r--r--lib/libmixer/mixer.3540
-rw-r--r--lib/libmixer/mixer.c493
-rw-r--r--lib/libmixer/mixer.h123
-rw-r--r--usr.sbin/mixer/Makefile9
-rw-r--r--usr.sbin/mixer/mixer.8306
-rw-r--r--usr.sbin/mixer/mixer.c723
-rw-r--r--usr.sbin/mixer/tests/Makefile5
-rwxr-xr-xusr.sbin/mixer/tests/mixer_test.sh123
10 files changed, 1787 insertions, 544 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 1e375bb456e6..038763bfcba8 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -69,6 +69,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
liblzma \
libmemstat \
libmd \
+ libmixer \
libmt \
lib80211 \
libnetbsd \
diff --git a/lib/libmixer/Makefile b/lib/libmixer/Makefile
new file mode 100644
index 000000000000..12081ee3835b
--- /dev/null
+++ b/lib/libmixer/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+LIB= mixer
+SRCS= ${LIB}.c
+INCS= ${LIB}.h
+MAN= ${LIB}.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libmixer/mixer.3 b/lib/libmixer/mixer.3
new file mode 100644
index 000000000000..f97a88bb79a0
--- /dev/null
+++ b/lib/libmixer/mixer.3
@@ -0,0 +1,540 @@
+.\"-
+.\" 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 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)((v) * 100.0f + 0.5f))
+ 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
+and
+.Xr errno 2
+.Sh AUTHORS
+.An Christos Margiolis Aq Mt christos@margiolis.net
diff --git a/lib/libmixer/mixer.c b/lib/libmixer/mixer.c
new file mode 100644
index 000000000000..b10d5c6607cf
--- /dev/null
+++ b/lib/libmixer/mixer.c
@@ -0,0 +1,493 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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), "/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;
+
+ if (ctl == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ p = ctl->parent_dev;
+ 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), "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/lib/libmixer/mixer.h b/lib/libmixer/mixer.h
new file mode 100644
index 000000000000..6e77fdec421f
--- /dev/null
+++ b/lib/libmixer/mixer.h
@@ -0,0 +1,123 @@
+/*-
+ * 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$
+ */
+
+#ifndef _MIXER_H_
+#define _MIXER_H_
+
+#include <sys/cdefs.h>
+#include <sys/queue.h>
+#include <sys/soundcard.h>
+
+#include <limits.h>
+
+#define MIX_ISSET(n,f) (((1U << (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)((v) * 100.0f + 0.5f))
+ 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 */
+};
+
+__BEGIN_DECLS
+
+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/usr.sbin/mixer/Makefile b/usr.sbin/mixer/Makefile
index dce135bc7891..6bbc6f07c7e1 100644
--- a/usr.sbin/mixer/Makefile
+++ b/usr.sbin/mixer/Makefile
@@ -2,10 +2,9 @@
.include <src.opts.mk>
-PROG= mixer
-MAN= mixer.8
-
-HAS_TESTS=
-SUBDIR.${MK_TESTS}+= tests
+PROG= mixer
+SRCS= ${PROG}.c
+MAN= ${PROG}.8
+LDFLAGS+= -lmixer
.include <bsd.prog.mk>
diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8
index 6e569cc8cfd9..39b30b53d529 100644
--- a/usr.sbin/mixer/mixer.8
+++ b/usr.sbin/mixer/mixer.8
@@ -1,76 +1,71 @@
-.\" Copyright (c) 1997
-.\" Mike Pritchard <mpp@FreeBSD.org>. All rights reserved.
+.\"-
+.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
.\"
-.\" Redistribution and use in source and binary forms, with or without
-.\" modification, are permitted provided that the following conditions
-.\" are met:
-.\" 1. Redistributions of source code must retain the above copyright
-.\" notice, this list of conditions and the following disclaimer.
-.\" 2. Redistributions in binary form must reproduce the above copyright
-.\" notice, this list of conditions and the following disclaimer in the
-.\" documentation and/or other materials provided with the distribution.
-.\" 3. Neither the name of the author nor the names of its contributors
-.\" may be used to endorse or promote products derived from this software
-.\" without specific prior written permission.
+.\" 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:
.\"
-.\" THIS SOFTWARE IS PROVIDED BY MIKE PRITCHARD AND CONTRIBUTORS ``AS IS'' AND
-.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-.\" SUCH DAMAGE.
+.\" 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 2, 2014
-.Dt MIXER 8
+
+.Dd June 30, 2021
+.Dt mixer 8
.Os
.Sh NAME
.Nm mixer
-.Nd set/display soundcard mixer values
+.Nd manipulate soundcard mixer controls
.Sh SYNOPSIS
.Nm
.Op Fl f Ar device
-.Op Fl s | S
-.Oo
-.Ar dev
-.Sm off
-.Oo
-.Op Cm + | -
-.Ar lvol
-.Op : Oo Cm + | - Oc Ar rvol
-.Oc
-.Oc
-.Sm on
-.Ar ...
-.Nm
-.Op Fl f Ar device
-.Op Fl s | S
-.Cm recsrc
+.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 f Ar device
-.Op Fl s | S
-.Sm off
-.Bro
-.Cm ^ | + | - | =
-.Brc
-.Cm rec
-.Sm on
-.Ar rdev ...
+.Op Fl d Ar unit
+.Op Fl os
+.Fl a
.Sh DESCRIPTION
The
.Nm
-utility is used to set and display soundcard mixer device levels.
-It may
-also be used to start and stop recording from the soundcard.
-The list
-of mixer devices that may be modified are:
+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,
@@ -81,101 +76,170 @@ Not all mixer devices are available.
.Pp
Without any arguments,
.Nm
-displays the current settings for all supported devices, followed by information
-about the current recording input devices.
+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 value for that
+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
-To modify the mixer value
-.Ar dev ,
-the optional left and right channel settings of
-.Ar lvol Ns Op : Ns Ar rvol
-may be specified.
The
+.Ar dev.volume
+control modifies a device's volume. The optional
.Ar lvol
-and
+and/or
.Ar rvol
-arguments may be from 0 - 100.
-Omitting
-.Ar dev
-and including only the channel settings will change the main volume level.
-.Pp
-If the left or right channel settings are prefixed with
+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
-If the
-.Fl s
-flag is used, the current mixer values will be displayed in a format suitable
-for use as the command-line arguments to a future invocation of
-.Nm
-(as above).
-.Pp
The
-.Fl S
-flag provides the above output without mixing field separators.
+.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
-To change the recording device you use one of:
-.Bl -tag -width =rec -offset indent
-.It Cm ^rec
+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 rdev
+.Ar dev
of possible recording devices
-.It Cm +rec
+.It Cm +
adds
-.Ar rdev
+.Ar dev
to possible recording devices
-.It Cm -rec
+.It Cm -
removes
-.Ar rdev
+.Ar dev
from possible recording devices
-.It Cm =rec
+.It Cm =
sets the recording device to
-.Ar rdev
+.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
-The above commands work on an internal mask.
-After all the options
-have been parsed, it will set then read the mask from the sound card.
-This will let you see EXACTLY what the soundcard is using for the
-recording device(s).
+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
-The option recsrc will display the current recording devices.
+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
-The option
-.Fl f
-.Ar device
-will open
-.Ar device
-as the mixer device.
-.Sh FILES
-.Bl -tag -width /dev/mixer -compact
-.It Pa /dev/mixer
-the default mixer device
+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 cdcontrol 1 ,
-.Xr sound 4
+.Xr mixer 3 ,
+.Xr sound 4 ,
+.Xr sysctl 8
.Sh HISTORY
The
.Nm
-utility first appeared in
-.Fx 2.0.5 .
+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 -nosplit
-Original source by
-.An Craig Metz Aq Mt cmetz@thor.tjhsst.edu
-and
-.An Hannu Savolainen .
-Mostly rewritten by
-.An John-Mark Gurney Aq Mt jmg@FreeBSD.org .
-This
-manual page was written by
-.An Mike Pritchard Aq Mt mpp@FreeBSD.org .
+.An Christos Margiolis Aq Mt christos@margiolis.net
diff --git a/usr.sbin/mixer/mixer.c b/usr.sbin/mixer/mixer.c
index 23193f22324b..3bbb8eebf93f 100644
--- a/usr.sbin/mixer/mixer.c
+++ b/usr.sbin/mixer/mixer.c
@@ -1,341 +1,484 @@
-/*
- * This is an example of a mixer program for Linux
+/*-
+ * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
*
- * updated 1/1/93 to add stereo, level query, broken
- * devmask kludge - cmetz@thor.tjhsst.edu
+ * 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:
*
- * (C) Craig Metz and Hannu Savolainen 1993.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
*
- * You may do anything you wish with this program.
+ * 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.
*
- * ditto for my modifications (John-Mark Gurney, 1997)
+ * $FreeBSD$
*/
-#include <sys/cdefs.h>
-__FBSDID("$FreeBSD$");
-
#include <err.h>
-#include <fcntl.h>
-#include <libgen.h>
-#include <limits.h>
+#include <errno.h>
#include <stdio.h>
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
-#include <sys/soundcard.h>
-
-static const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
-static void usage(int devmask, int recmask) __dead2;
-static int res_name(const char *name, int mask);
-static void print_recsrc(int recsrc, int recmask, int sflag);
+#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
+};
-static void __dead2
-usage(int devmask, int recmask)
+int
+main(int argc, char *argv[])
{
- int i, n;
-
- printf("usage: mixer [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n"
- " mixer [-f device] [-s | -S] recsrc ...\n"
- " mixer [-f device] [-s | -S] {^|+|-|=}rec rdev ...\n");
- if (devmask != 0) {
- printf(" devices: ");
- for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
- if ((1 << i) & devmask) {
- if (n)
- printf(", ");
- printf("%s", names[i]);
- n++;
- }
+ 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();
+ }
}
- if (recmask != 0) {
- printf("\n rec devices: ");
- for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
- if ((1 << i) & recmask) {
- if (n)
- printf(", ");
- printf("%s", names[i]);
- n++;
+ 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);
}
- printf("\n");
+
+ 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 int
-res_name(const char *name, int mask)
+static void
+initctls(struct mixer *m)
{
- int foo;
+ 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");
+ }
+}
- for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
- if ((1 << foo) & mask && strcmp(names[foo], name) == 0)
- break;
+static void
+printall(struct mixer *m, int oflag)
+{
+ struct mix_dev *dp;
- return (foo == SOUND_MIXER_NRDEVICES ? -1 : foo);
+ printminfo(m, oflag);
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ m->dev = dp;
+ printdev(m, oflag);
+ }
}
static void
-print_recsrc(int recsrc, int recmask, int sflag)
+printminfo(struct mixer *m, int oflag)
{
- int i, n;
+ int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
- if (recmask == 0)
+ 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");
+}
- if (!sflag)
- printf("Recording source: ");
+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);
+ }
+ }
+}
- for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
- if ((1 << i) & recsrc) {
- if (sflag)
- printf("%srec ", n ? " +" : "=");
- else if (n)
+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", names[i]);
- n++;
+ printf("%s", dp->name);
+ if (oflag)
+ printf(".%s=+%s",
+ mixer_get_ctl(dp, C_SRC)->name,
+ n ? " " : "");
}
- if (!sflag)
- printf("\n");
+ }
+ printf("\n");
}
-int
-main(int argc, char *argv[])
+static int
+mod_dunit(struct mix_dev *d, void *p)
{
- char mixer[PATH_MAX] = "/dev/mixer";
- char lstr[8], rstr[8];
- char *name, *eptr;
- int devmask = 0, recmask = 0, recsrc = 0, orecsrc;
- int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0;
- int l, r, lrel, rrel;
- int ch, foo, bar, baz, dev, m, n, t;
-
- if ((name = strdup(basename(argv[0]))) == NULL)
- err(1, "strdup()");
- if (strncmp(name, "mixer", 5) == 0 && name[5] != '\0') {
- n = strtol(name + 5, &eptr, 10) - 1;
- if (n > 0 && *eptr == '\0')
- snprintf(mixer, PATH_MAX - 1, "/dev/mixer%d", n);
- }
- free(name);
- name = mixer;
+ int dunit = *((int *)p);
+ int n;
- n = 1;
- for (;;) {
- if (n >= argc || *argv[n] != '-')
- break;
- if (strlen(argv[n]) != 2) {
- if (strcmp(argv[n] + 1, "rec") != 0)
- dusage = 1;
- break;
- }
- ch = *(argv[n] + 1);
- if (ch == 'f' && n < argc - 1) {
- name = argv[n + 1];
- n += 2;
- } else if (ch == 's') {
- sflag = 1;
- n++;
- } else if (ch == 'S') {
- Sflag = 1;
- n++;
- } else {
- dusage = 1;
- break;
- }
+ if ((n = mixer_get_dunit()) < 0) {
+ warn("cannot get default unit");
+ return (-1);
}
- if (sflag && Sflag)
- dusage = 1;
-
- argc -= n - 1;
- argv += n - 1;
-
- if ((baz = open(name, O_RDWR)) < 0)
- err(1, "%s", name);
- if (ioctl(baz, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
- err(1, "SOUND_MIXER_READ_DEVMASK");
- if (ioctl(baz, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
- err(1, "SOUND_MIXER_READ_RECMASK");
- if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
- err(1, "SOUND_MIXER_READ_RECSRC");
- orecsrc = recsrc;
-
- if (argc == 1 && dusage == 0) {
- for (foo = 0, n = 0; foo < SOUND_MIXER_NRDEVICES; foo++) {
- if (!((1 << foo) & devmask))
- continue;
- if (ioctl(baz, MIXER_READ(foo),&bar) == -1) {
- warn("MIXER_READ");
- continue;
- }
- if (Sflag || sflag) {
- printf("%s%s%c%d:%d", n ? " " : "",
- names[foo], Sflag ? ':' : ' ',
- bar & 0x7f, (bar >> 8) & 0x7f);
- n++;
- } else
- printf("Mixer %-8s is currently set to "
- "%3d:%d\n", names[foo], bar & 0x7f,
- (bar >> 8) & 0x7f);
- }
- if (n && recmask)
- printf(" ");
- print_recsrc(recsrc, recmask, Sflag || sflag);
- return (0);
+ 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);
- argc--;
- argv++;
-
- n = 0;
- while (argc > 0 && dusage == 0) {
- if (strcmp("recsrc", *argv) == 0) {
- drecsrc = 1;
- argc--;
- argv++;
- continue;
- } else if (strcmp("rec", *argv + 1) == 0) {
- if (**argv != '+' && **argv != '-' &&
- **argv != '=' && **argv != '^') {
- warnx("unknown modifier: %c", **argv);
- dusage = 1;
- break;
- }
- if (argc <= 1) {
- warnx("no recording device specified");
- dusage = 1;
- break;
- }
- if ((dev = res_name(argv[1], recmask)) == -1) {
- warnx("unknown recording device: %s", argv[1]);
- dusage = 1;
- break;
- }
- switch (**argv) {
- case '+':
- recsrc |= (1 << dev);
- break;
- case '-':
- recsrc &= ~(1 << dev);
- break;
- case '=':
- recsrc = (1 << dev);
- break;
- case '^':
- recsrc ^= (1 << dev);
- break;
- }
- drecsrc = 1;
- argc -= 2;
- argv += 2;
- continue;
- }
-
- if ((t = sscanf(*argv, "%d:%d", &l, &r)) > 0)
- dev = 0;
- else if ((dev = res_name(*argv, devmask)) == -1) {
- warnx("unknown device: %s", *argv);
- dusage = 1;
- break;
- }
+ return (0);
+}
- lrel = rrel = 0;
- if (argc > 1) {
- m = sscanf(argv[1], "%7[^:]:%7s", lstr, rstr);
- if (m == EOF) {
- warnx("invalid value: %s", argv[1]);
- dusage = 1;
- break;
- }
- if (m > 0) {
- if (*lstr == '+' || *lstr == '-')
- lrel = rrel = 1;
- l = strtol(lstr, NULL, 10);
- }
- if (m > 1) {
- if (*rstr == '+' || *rstr == '-')
- rrel = 1;
- r = strtol(rstr, NULL, 10);
- }
- }
+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);
+ }
- switch (argc > 1 ? m : t) {
- case 0:
- if (ioctl(baz, MIXER_READ(dev), &bar) == -1) {
- warn("MIXER_READ");
- argc--;
- argv++;
- continue;
- }
- if (Sflag || sflag) {
- printf("%s%s%c%d:%d", n ? " " : "",
- names[dev], Sflag ? ':' : ' ',
- bar & 0x7f, (bar >> 8) & 0x7f);
- n++;
- } else
- printf("Mixer %-8s is currently set to "
- "%3d:%d\n", names[dev], bar & 0x7f,
- (bar >> 8) & 0x7f);
-
- argc--;
- argv++;
- break;
- case 1:
- r = l;
- /* FALLTHROUGH */
- case 2:
- if (ioctl(baz, MIXER_READ(dev), &bar) == -1) {
- warn("MIXER_READ");
- argc--;
- argv++;
- continue;
- }
+ return (0);
+}
- if (lrel)
- l = (bar & 0x7f) + l;
- if (rrel)
- r = ((bar >> 8) & 0x7f) + r;
-
- if (l < 0)
- l = 0;
- else if (l > 100)
- l = 100;
- if (r < 0)
- r = 0;
- else if (r > 100)
- r = 100;
-
- if (!Sflag)
- printf("Setting the mixer %s from %d:%d to "
- "%d:%d.\n", names[dev], bar & 0x7f,
- (bar >> 8) & 0x7f, l, r);
-
- l |= r << 8;
- if (ioctl(baz, MIXER_WRITE(dev), &l) == -1)
- warn("WRITE_MIXER");
-
- argc -= 2;
- argv += 2;
- break;
- }
+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));
- if (dusage) {
- close(baz);
- usage(devmask, recmask);
- /* NOTREACHED */
- }
+ return (0);
+}
- if (orecsrc != recsrc) {
- if (ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1)
- err(1, "SOUND_MIXER_WRITE_RECSRC");
- if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
- err(1, "SOUND_MIXER_READ_RECSRC");
+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);
+}
- if (drecsrc)
- print_recsrc(recsrc, recmask, Sflag || sflag);
+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;
- close(baz);
+ if (!MIX_ISRECSRC(m, m->dev->devno))
+ return (-1);
+ printf("%s.%s=+\n", m->dev->name, ctl_name);
return (0);
}
diff --git a/usr.sbin/mixer/tests/Makefile b/usr.sbin/mixer/tests/Makefile
deleted file mode 100644
index 032cab369f7e..000000000000
--- a/usr.sbin/mixer/tests/Makefile
+++ /dev/null
@@ -1,5 +0,0 @@
-# $FreeBSD$
-
-ATF_TESTS_SH+= mixer_test
-
-.include <bsd.test.mk>
diff --git a/usr.sbin/mixer/tests/mixer_test.sh b/usr.sbin/mixer/tests/mixer_test.sh
deleted file mode 100755
index 1a1266553b3f..000000000000
--- a/usr.sbin/mixer/tests/mixer_test.sh
+++ /dev/null
@@ -1,123 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
-#
-# Copyright (c) 2019 Mateusz Piotrowski <0mp@FreeBSD.org>
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-
-# $FreeBSD$
-
-mixer_unavailable()
-{
- ! { mixer && mixer vol; } >/dev/null 2>&1
-}
-
-save_mixer_vol()
-{
- atf_check -o match:'^[0-9]{1,3}:[0-9]{1,3}$' -o save:saved_vol \
- -x "mixer vol | awk '{print \$7}'"
-}
-
-set_mixer_vol()
-{
- atf_check \
- -o match:'^Setting the mixer vol from [0-9]{1,3}:[0-9]{1,3} to 0:0\.$' \
- mixer vol 0
-}
-
-restore_mixer_vol()
-{
- if [ -r "saved_vol" ]; then
- mixer vol "$(cat saved_vol)"
- fi
-}
-
-atf_test_case s_flag cleanup
-s_flag_head()
-{
- atf_set "descr" "Verify that the output of the -s flag could be " \
- "reused as command-line arguments to the mixer command"
-}
-s_flag_body()
-{
- if mixer_unavailable; then
- atf_skip "This test requires mixer support"
- fi
- save_mixer_vol
- set_mixer_vol
- atf_check -o inline:"vol 0:0" -o save:values mixer -s vol
- atf_check -o inline:"Setting the mixer vol from 0:0 to 0:0.\n" \
- mixer $(cat values)
-}
-s_flag_cleanup()
-{
- restore_mixer_vol
-}
-
-atf_test_case S_flag cleanup
-S_flag_head()
-{
- atf_set "descr" "Verify that the output of the -S flag is " \
- "matching the documented behavior"
-}
-S_flag_body()
-{
- if mixer_unavailable; then
- atf_skip "This test requires mixer support"
- fi
- save_mixer_vol
- set_mixer_vol
- atf_check -o inline:"vol:0:0" mixer -S vol
-}
-S_flag_cleanup()
-{
- restore_mixer_vol
-}
-
-atf_test_case set_empty_value cleanup
-set_empty_value_head()
-{
- atf_set "descr" "Verify that mixer returns when the provided " \
- "value to set is an empty string instead of a number"
- atf_set "timeout" "1"
-}
-set_empty_value_body()
-{
- if mixer_unavailable; then
- atf_skip "This test requires mixer support"
- fi
- save_mixer_vol
- atf_check -s exit:1 -e inline:"mixer: invalid value: \n" \
- -o match:"^usage:" mixer vol ""
-}
-set_empty_value_cleanup()
-{
- restore_mixer_vol
-}
-
-
-atf_init_test_cases()
-{
- atf_add_test_case s_flag
- atf_add_test_case S_flag
- atf_add_test_case set_empty_value
-}