diff --git a/lib/Makefile b/lib/Makefile --- a/lib/Makefile +++ b/lib/Makefile @@ -69,6 +69,7 @@ liblzma \ libmemstat \ libmd \ + libmixer \ libmt \ lib80211 \ libnetbsd \ diff --git a/lib/libmixer/Makefile b/lib/libmixer/Makefile new file mode 100644 --- /dev/null +++ b/lib/libmixer/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +LIB= mixer +SRCS= ${LIB}.c +INCS= ${LIB}.h +MAN= ${LIB}.3 + +.include diff --git a/lib/libmixer/mixer.h b/lib/libmixer/mixer.h new file mode 100644 --- /dev/null +++ b/lib/libmixer/mixer.h @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * $FreeBSD$ + */ + +#ifndef _MIXER_H_ +#define _MIXER_H_ + +#include +#include +#include + +#include + +#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/lib/libmixer/mixer.3 b/lib/libmixer/mixer.3 new file mode 100644 --- /dev/null +++ b/lib/libmixer/mixer.3 @@ -0,0 +1,540 @@ +.\"- +.\" Copyright (c) 2021 Christos Margiolis +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining a copy +.\" of this software and associated documentation files (the "Software"), to deal +.\" in the Software without restriction, including without limitation the rights +.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +.\" copies of the Software, and to permit persons to whom the Software is +.\" furnished to do so, subject to the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included in +.\" all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +.\" THE SOFTWARE. +.\" +.\" $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 --- /dev/null +++ b/lib/libmixer/mixer.c @@ -0,0 +1,493 @@ +/*- + * Copyright (c) 2021 Christos Margiolis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * $FreeBSD$ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mixer.h" + +#define BASEPATH "/dev/mixer" + +static int _mixer_readvol(struct mixer *, struct mix_dev *); + +/* + * Fetch volume from the device. + */ +static int +_mixer_readvol(struct mixer *m, struct mix_dev *dev) +{ + int v; + + if (ioctl(m->fd, MIXER_READ(dev->devno), &v) < 0) + return (-1); + dev->vol.left = MIX_VOLNORM(v & 0x00ff); + dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff); + + return (0); +} + +/* + * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer. + * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the + * mixer for pcm0, and so on. + * + * @param name path to mixer device. NULL or "/dev/mixer" for the + * the default mixer (i.e `hw.snd.default_unit`). + */ +struct mixer * +mixer_open(const char *name) +{ + struct mixer *m = NULL; + struct mix_dev *dp; + const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + int i; + + if ((m = calloc(1, sizeof(struct mixer))) == NULL) + goto fail; + + if (name != NULL) { + /* `name` does not start with "/dev/mixer". */ + if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) { + errno = EINVAL; + goto fail; + } + /* `name` is "/dev/mixer" so, we'll use the default unit. */ + if (strncmp(name, BASEPATH, strlen(name)) == 0) + goto dunit; + m->unit = strtol(name + strlen(BASEPATH), NULL, 10); + (void)strlcpy(m->name, name, sizeof(m->name)); + } else { +dunit: + if ((m->unit = mixer_get_dunit()) < 0) + goto fail; + (void)snprintf(m->name, sizeof(m->name), "/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/usr.sbin/mixer/Makefile b/usr.sbin/mixer/Makefile --- a/usr.sbin/mixer/Makefile +++ b/usr.sbin/mixer/Makefile @@ -2,10 +2,9 @@ .include -PROG= mixer -MAN= mixer.8 - -HAS_TESTS= -SUBDIR.${MK_TESTS}+= tests +PROG= mixer +SRCS= ${PROG}.c +MAN= ${PROG}.8 +LDFLAGS+= -lmixer .include diff --git a/usr.sbin/mixer/mixer.8 b/usr.sbin/mixer/mixer.8 --- a/usr.sbin/mixer/mixer.8 +++ b/usr.sbin/mixer/mixer.8 @@ -1,76 +1,71 @@ -.\" Copyright (c) 1997 -.\" Mike Pritchard . All rights reserved. +.\"- +.\" Copyright (c) 2021 Christos Margiolis .\" -.\" 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 @@ .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 --- 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 * - * 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 -__FBSDID("$FreeBSD$"); - #include -#include -#include -#include +#include #include -#include #include +#include #include -#include - -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 + +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 --- a/usr.sbin/mixer/tests/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# $FreeBSD$ - -ATF_TESTS_SH+= mixer_test - -.include diff --git a/usr.sbin/mixer/tests/mixer_test.sh b/usr.sbin/mixer/tests/mixer_test.sh deleted file mode 100755 --- 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 -}