Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 22 Sep 2021 18:03:42 GMT
From:      Hans Petter Selasky <hselasky@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 903873ce1560 - main - Implement and use new mixer(3) library for FreeBSD.
Message-ID:  <202109221803.18MI3gdA013391@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by hselasky:

URL: https://cgit.FreeBSD.org/src/commit/?id=903873ce15600fc02a0ea42cbf888cff232b411d

commit 903873ce15600fc02a0ea42cbf888cff232b411d
Author:     Hans Petter Selasky <hselasky@FreeBSD.org>
AuthorDate: 2021-09-22 13:42:51 +0000
Commit:     Hans Petter Selasky <hselasky@FreeBSD.org>
CommitDate: 2021-09-22 17:43:56 +0000

    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
---
 lib/Makefile                       |   1 +
 lib/libmixer/Makefile              |   8 +
 lib/libmixer/mixer.3               | 540 +++++++++++++++++++++++++++
 lib/libmixer/mixer.c               | 493 +++++++++++++++++++++++++
 lib/libmixer/mixer.h               | 123 +++++++
 usr.sbin/mixer/Makefile            |   9 +-
 usr.sbin/mixer/mixer.8             | 306 +++++++++-------
 usr.sbin/mixer/mixer.c             | 723 ++++++++++++++++++++++---------------
 usr.sbin/mixer/tests/Makefile      |   5 -
 usr.sbin/mixer/tests/mixer_test.sh | 123 -------
 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
*** 1550 LINES SKIPPED ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202109221803.18MI3gdA013391>