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>