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
This commit is contained in:
Hans Petter Selasky 2021-09-22 15:42:51 +02:00
parent 884f38590c
commit 903873ce15
10 changed files with 1800 additions and 557 deletions

View file

@ -69,6 +69,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
liblzma \
libmemstat \
libmd \
libmixer \
libmt \
lib80211 \
libnetbsd \

8
lib/libmixer/Makefile Normal file
View file

@ -0,0 +1,8 @@
# $FreeBSD$
LIB= mixer
SRCS= ${LIB}.c
INCS= ${LIB}.h
MAN= ${LIB}.3
.include <bsd.lib.mk>

540
lib/libmixer/mixer.3 Normal file
View file

@ -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

493
lib/libmixer/mixer.c Normal file
View file

@ -0,0 +1,493 @@
/*-
* Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* $FreeBSD$
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mixer.h"
#define BASEPATH "/dev/mixer"
static int _mixer_readvol(struct mixer *, struct mix_dev *);
/*
* Fetch volume from the device.
*/
static int
_mixer_readvol(struct mixer *m, struct mix_dev *dev)
{
int v;
if (ioctl(m->fd, MIXER_READ(dev->devno), &v) < 0)
return (-1);
dev->vol.left = MIX_VOLNORM(v & 0x00ff);
dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff);
return (0);
}
/*
* Open a mixer device in `/dev/mixerN`, where N is the number of the mixer.
* Each device maps to an actual pcm audio card, so `/dev/mixer0` is the
* mixer for pcm0, and so on.
*
* @param name path to mixer device. NULL or "/dev/mixer" for the
* the default mixer (i.e `hw.snd.default_unit`).
*/
struct mixer *
mixer_open(const char *name)
{
struct mixer *m = NULL;
struct mix_dev *dp;
const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
int i;
if ((m = calloc(1, sizeof(struct mixer))) == NULL)
goto fail;
if (name != NULL) {
/* `name` does not start with "/dev/mixer". */
if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
errno = EINVAL;
goto fail;
}
/* `name` is "/dev/mixer" so, we'll use the default unit. */
if (strncmp(name, BASEPATH, strlen(name)) == 0)
goto dunit;
m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
(void)strlcpy(m->name, name, sizeof(m->name));
} else {
dunit:
if ((m->unit = mixer_get_dunit()) < 0)
goto fail;
(void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit);
}
if ((m->fd = open(m->name, O_RDWR)) < 0)
goto fail;
m->devmask = m->recmask = m->recsrc = 0;
m->f_default = m->unit == mixer_get_dunit();
m->mode = mixer_get_mode(m->unit);
/* The unit number _must_ be set before the ioctl. */
m->mi.dev = m->unit;
m->ci.card = m->unit;
if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0 ||
ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
goto fail;
TAILQ_INIT(&m->devs);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (!MIX_ISDEV(m, i))
continue;
if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL)
goto fail;
dp->parent_mixer = m;
dp->devno = i;
dp->nctl = 0;
if (_mixer_readvol(m, dp) < 0)
goto fail;
(void)strlcpy(dp->name, names[i], sizeof(dp->name));
TAILQ_INIT(&dp->ctls);
TAILQ_INSERT_TAIL(&m->devs, dp, devs);
m->ndev++;
}
/* The default device is always "vol". */
m->dev = TAILQ_FIRST(&m->devs);
return (m);
fail:
if (m != NULL)
(void)mixer_close(m);
return (NULL);
}
/*
* Free resources and close the mixer.
*/
int
mixer_close(struct mixer *m)
{
struct mix_dev *dp;
int r;
r = close(m->fd);
while (!TAILQ_EMPTY(&m->devs)) {
dp = TAILQ_FIRST(&m->devs);
TAILQ_REMOVE(&m->devs, dp, devs);
while (!TAILQ_EMPTY(&dp->ctls))
(void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls));
free(dp);
}
free(m);
return (r);
}
/*
* Select a mixer device. The mixer structure keeps a list of all the devices
* the mixer has, but only one can be manipulated at a time -- this is what
* the `dev` in the mixer structure field is for. Each time a device is to be
* manipulated, `dev` has to point to it first.
*
* The caller must manually assign the return value to `m->dev`.
*/
struct mix_dev *
mixer_get_dev(struct mixer *m, int dev)
{
struct mix_dev *dp;
if (dev < 0 || dev >= m->ndev) {
errno = ERANGE;
return (NULL);
}
TAILQ_FOREACH(dp, &m->devs, devs) {
if (dp->devno == dev)
return (dp);
}
errno = EINVAL;
return (NULL);
}
/*
* Select a device by name.
*
* @param name device name (e.g vol, pcm, ...)
*/
struct mix_dev *
mixer_get_dev_byname(struct mixer *m, const char *name)
{
struct mix_dev *dp;
TAILQ_FOREACH(dp, &m->devs, devs) {
if (!strncmp(dp->name, name, sizeof(dp->name)))
return (dp);
}
errno = EINVAL;
return (NULL);
}
/*
* Add a mixer control to a device.
*/
int
mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name,
int (*mod)(struct mix_dev *, void *),
int (*print)(struct mix_dev *, void *))
{
struct mix_dev *dp;
mix_ctl_t *ctl, *cp;
/* XXX: should we accept NULL name? */
if (parent_dev == NULL) {
errno = EINVAL;
return (-1);
}
if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL)
return (-1);
ctl->parent_dev = parent_dev;
ctl->id = id;
if (name != NULL)
(void)strlcpy(ctl->name, name, sizeof(ctl->name));
ctl->mod = mod;
ctl->print = print;
dp = ctl->parent_dev;
/* Make sure the same ID or name doesn't exist already. */
TAILQ_FOREACH(cp, &dp->ctls, ctls) {
if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) {
errno = EINVAL;
return (-1);
}
}
TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls);
dp->nctl++;
return (0);
}
/*
* Same as `mixer_add_ctl`.
*/
int
mixer_add_ctl_s(mix_ctl_t *ctl)
{
if (ctl == NULL)
return (-1);
return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
ctl->mod, ctl->print));
}
/*
* Remove a mixer control from a device.
*/
int
mixer_remove_ctl(mix_ctl_t *ctl)
{
struct mix_dev *p;
if (ctl == NULL) {
errno = EINVAL;
return (-1);
}
p = ctl->parent_dev;
if (!TAILQ_EMPTY(&p->ctls)) {
TAILQ_REMOVE(&p->ctls, ctl, ctls);
free(ctl);
}
return (0);
}
/*
* Get a mixer control by id.
*/
mix_ctl_t *
mixer_get_ctl(struct mix_dev *d, int id)
{
mix_ctl_t *cp;
TAILQ_FOREACH(cp, &d->ctls, ctls) {
if (cp->id == id)
return (cp);
}
errno = EINVAL;
return (NULL);
}
/*
* Get a mixer control by name.
*/
mix_ctl_t *
mixer_get_ctl_byname(struct mix_dev *d, const char *name)
{
mix_ctl_t *cp;
TAILQ_FOREACH(cp, &d->ctls, ctls) {
if (!strncmp(cp->name, name, sizeof(cp->name)))
return (cp);
}
errno = EINVAL;
return (NULL);
}
/*
* Change the mixer's left and right volume. The allowed volume values are
* between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires
* an integer value between 0 and 100 stored as `lvol | rvol << 8` -- for
* that reason, we de-normalize the 32-bit float volume value, before
* we pass it to the `ioctl`.
*
* Volume clumping should be done by the caller.
*/
int
mixer_set_vol(struct mixer *m, mix_volume_t vol)
{
int v;
if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) {
errno = ERANGE;
return (-1);
}
v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8;
if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0)
return (-1);
if (_mixer_readvol(m, m->dev) < 0)
return (-1);
return (0);
}
/*
* Manipulate a device's mute.
*
* @param opt MIX_MUTE mute device
* MIX_UNMUTE unmute device
* MIX_TOGGLEMUTE toggle device's mute
*/
int
mixer_set_mute(struct mixer *m, int opt)
{
switch (opt) {
case MIX_MUTE:
m->mutemask |= (1 << m->dev->devno);
break;
case MIX_UNMUTE:
m->mutemask &= ~(1 << m->dev->devno);
break;
case MIX_TOGGLEMUTE:
m->mutemask ^= (1 << m->dev->devno);
break;
default:
errno = EINVAL;
return (-1);
}
if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0)
return (-1);
if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0)
return (-1);
return 0;
}
/*
* Modify a recording device. The selected device has to be a recording device,
* otherwise the function will fail.
*
* @param opt MIX_ADDRECSRC add device to recording sources
* MIX_REMOVERECSRC remove device from recording sources
* MIX_SETRECSRC set device as the only recording source
* MIX_TOGGLERECSRC toggle device from recording sources
*/
int
mixer_mod_recsrc(struct mixer *m, int opt)
{
if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) {
errno = ENODEV;
return (-1);
}
switch (opt) {
case MIX_ADDRECSRC:
m->recsrc |= (1 << m->dev->devno);
break;
case MIX_REMOVERECSRC:
m->recsrc &= ~(1 << m->dev->devno);
break;
case MIX_SETRECSRC:
m->recsrc = (1 << m->dev->devno);
break;
case MIX_TOGGLERECSRC:
m->recsrc ^= (1 << m->dev->devno);
break;
default:
errno = EINVAL;
return (-1);
}
if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0)
return (-1);
if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
return (-1);
return (0);
}
/*
* Get default audio card's number. This is used to open the default mixer
* and set the mixer structure's `f_default` flag.
*/
int
mixer_get_dunit(void)
{
size_t size;
int unit;
size = sizeof(int);
if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0)
return (-1);
return (unit);
}
/*
* Change the default audio card. This is normally _not_ a mixer feature, but
* it's useful to have, so the caller can avoid having to manually use
* the sysctl API.
*
* @param unit the audio card number (e.g pcm0, pcm1, ...).
*/
int
mixer_set_dunit(struct mixer *m, int unit)
{
size_t size;
size = sizeof(int);
if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0)
return (-1);
/* XXX: how will other mixers get updated? */
m->f_default = m->unit == unit;
return (0);
}
/*
* Get sound device mode (none, play, rec, play+rec). Userland programs can
* use the MIX_STATUS_* flags to determine the mode of the device.
*/
int
mixer_get_mode(int unit)
{
char buf[64];
size_t size;
unsigned int mode;
(void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit);
size = sizeof(unsigned int);
if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0)
return (-1);
return (mode);
}
/*
* Get the total number of mixers in the system.
*/
int
mixer_get_nmixers(void)
{
struct mixer *m;
oss_sysinfo si;
/*
* Open a dummy mixer because we need the `fd` field for the
* `ioctl` to work.
*/
if ((m = mixer_open(NULL)) == NULL)
return (-1);
if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) {
(void)mixer_close(m);
return (-1);
}
(void)mixer_close(m);
return (si.nummixers);
}

123
lib/libmixer/mixer.h Normal file
View file

@ -0,0 +1,123 @@
/*-
* Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* $FreeBSD$
*/
#ifndef _MIXER_H_
#define _MIXER_H_
#include <sys/cdefs.h>
#include <sys/queue.h>
#include <sys/soundcard.h>
#include <limits.h>
#define MIX_ISSET(n,f) (((1U << (n)) & (f)) ? 1 : 0)
#define MIX_ISDEV(m,n) MIX_ISSET(n, (m)->devmask)
#define MIX_ISMUTE(m,n) MIX_ISSET(n, (m)->mutemask)
#define MIX_ISREC(m,n) MIX_ISSET(n, (m)->recmask)
#define MIX_ISRECSRC(m,n) MIX_ISSET(n, (m)->recsrc)
/* Forward declarations */
struct mixer;
struct mix_dev;
typedef struct mix_ctl mix_ctl_t;
typedef struct mix_volume mix_volume_t;
/* User-defined controls */
struct mix_ctl {
struct mix_dev *parent_dev; /* parent device */
int id; /* control id */
char name[NAME_MAX]; /* control name */
int (*mod)(struct mix_dev *, void *); /* modify control values */
int (*print)(struct mix_dev *, void *); /* print control */
TAILQ_ENTRY(mix_ctl) ctls;
};
struct mix_dev {
struct mixer *parent_mixer; /* parent mixer */
char name[NAME_MAX]; /* device name (e.g "vol") */
int devno; /* device number */
struct mix_volume {
#define MIX_VOLMIN 0.0f
#define MIX_VOLMAX 1.0f
#define MIX_VOLNORM(v) ((v) / 100.0f)
#define MIX_VOLDENORM(v) ((int)((v) * 100.0f + 0.5f))
float left; /* left volume */
float right; /* right volume */
} vol;
int nctl; /* number of controls */
TAILQ_HEAD(, mix_ctl) ctls; /* control list */
TAILQ_ENTRY(mix_dev) devs;
};
struct mixer {
TAILQ_HEAD(, mix_dev) devs; /* device list */
struct mix_dev *dev; /* selected device */
oss_mixerinfo mi; /* mixer info */
oss_card_info ci; /* audio card info */
char name[NAME_MAX]; /* mixer name (e.g /dev/mixer0) */
int fd; /* file descriptor */
int unit; /* audio card unit */
int ndev; /* number of devices */
int devmask; /* supported devices */
#define MIX_MUTE 0x01
#define MIX_UNMUTE 0x02
#define MIX_TOGGLEMUTE 0x04
int mutemask; /* muted devices */
int recmask; /* recording devices */
#define MIX_ADDRECSRC 0x01
#define MIX_REMOVERECSRC 0x02
#define MIX_SETRECSRC 0x04
#define MIX_TOGGLERECSRC 0x08
int recsrc; /* recording sources */
#define MIX_MODE_MIXER 0x01
#define MIX_MODE_PLAY 0x02
#define MIX_MODE_REC 0x04
int mode; /* dev.pcm.X.mode sysctl */
int f_default; /* default mixer flag */
};
__BEGIN_DECLS
struct mixer *mixer_open(const char *);
int mixer_close(struct mixer *);
struct mix_dev *mixer_get_dev(struct mixer *, int);
struct mix_dev *mixer_get_dev_byname(struct mixer *, const char *);
int mixer_add_ctl(struct mix_dev *, int, const char *,
int (*)(struct mix_dev *, void *), int (*)(struct mix_dev *, void *));
int mixer_add_ctl_s(mix_ctl_t *);
int mixer_remove_ctl(mix_ctl_t *);
mix_ctl_t *mixer_get_ctl(struct mix_dev *, int);
mix_ctl_t *mixer_get_ctl_byname(struct mix_dev *, const char *);
int mixer_set_vol(struct mixer *, mix_volume_t);
int mixer_set_mute(struct mixer *, int);
int mixer_mod_recsrc(struct mixer *, int);
int mixer_get_dunit(void);
int mixer_set_dunit(struct mixer *, int);
int mixer_get_mode(int);
int mixer_get_nmixers(void);
__END_DECLS
#endif /* _MIXER_H_ */

View file

@ -2,10 +2,9 @@
.include <src.opts.mk>
PROG= mixer
MAN= mixer.8
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests
PROG= mixer
SRCS= ${PROG}.c
MAN= ${PROG}.8
LDFLAGS+= -lmixer
.include <bsd.prog.mk>

View file

@ -1,76 +1,71 @@
.\" Copyright (c) 1997
.\" Mike Pritchard <mpp@FreeBSD.org>. All rights reserved.
.\"-
.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. Neither the name of the author nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\" Permission is hereby granted, free of charge, to any person obtaining a copy
.\" of this software and associated documentation files (the "Software"), to deal
.\" in the Software without restriction, including without limitation the rights
.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
.\" copies of the Software, and to permit persons to whom the Software is
.\" furnished to do so, subject to the following conditions:
.\"
.\" THIS SOFTWARE IS PROVIDED BY MIKE PRITCHARD AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\" The above copyright notice and this permission notice shall be included in
.\" all copies or substantial portions of the Software.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
.\" THE SOFTWARE.
.\"
.\" $FreeBSD$
.\"
.Dd June 2, 2014
.Dt MIXER 8
.Dd June 30, 2021
.Dt mixer 8
.Os
.Sh NAME
.Nm mixer
.Nd set/display soundcard mixer values
.Nd manipulate soundcard mixer controls
.Sh SYNOPSIS
.Nm
.Op Fl f Ar device
.Op Fl s | S
.Oo
.Ar dev
.Sm off
.Oo
.Op Cm + | -
.Ar lvol
.Op : Oo Cm + | - Oc Ar rvol
.Oc
.Oc
.Sm on
.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
.Cm recsrc
.Ar ...
.Nm
.Op Fl f Ar device
.Op Fl s | S
.Sm off
.Bro
.Cm ^ | + | - | =
.Brc
.Cm rec
.Sm on
.Ar rdev ...
.Op Fl d Ar unit
.Op Fl os
.Fl a
.Sh DESCRIPTION
The
.Nm
utility is used to set and display soundcard mixer device levels.
It may
also be used to start and stop recording from the soundcard.
The list
of mixer devices that may be modified are:
utility is used to set and display soundcard mixer device controls.
.Pp
The options are as follows:
.Bl -tag -width "-f device"
.It Fl a
Print the values for all mixer devices available in the system (see FILES).
.It Fl d Ar unit
Change the default audio card to
.Ar unit .
The unit has to be an integer value. To see what unit values are available, look
at the number each mixer device has by running
.Nm .
.It Fl f Ar device
Open
.Ar device
as the mixer device (see FILES).
.It Fl o
Print mixer values in a format suitable for use inside scripts. The
mixer's header (name, audio card name, ...) will not be printed.
.It Fl s
Print only the recording source(s) of the mixer device.
.El
.Pp
The list of mixer devices that may be modified are:
.Bd -ragged -offset indent
vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
@ -81,101 +76,170 @@ Not all mixer devices are available.
.Pp
Without any arguments,
.Nm
displays the current settings for all supported devices, followed by information
about the current recording input devices.
displays all information for each one of the mixer's supported devices to
.Ar stdout .
If the
.Ar dev
argument is specified,
.Nm
displays only the value for that
displays only the values for
.Ar dev .
More than one device may be specified.
.Pp
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 lvol
and
.Ar rvol
arguments may be from 0 - 100.
Omitting
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
and including only the channel settings will change the main volume level.
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
If the left or right channel settings are prefixed with
The
.Ar dev.volume
control modifies a device's volume. The optional
.Ar lvol
and/or
.Ar rvol
values have to be specified. The values have to be normalized 32-bit floats,
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).
The
.Ar dev.mute
control (un)mutes a device. The following values are available:
.Bl -tag -width = -offset indent
.It Cm 0
unmutes
.Ar dev .
.It Cm 1
mutes
.Ar dev .
.It Cm ^
toggles the mute of
.Ar dev .
.El
.Pp
The
.Fl S
flag provides the above output without mixing field separators.
.Pp
To change the recording device you use one of:
.Bl -tag -width =rec -offset indent
.It Cm ^rec
.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
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 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).
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 recsrc will display the current recording devices.
Toggle the mute for
.Ar vol :
.Bl -tag -width Ds -offset indent
.It $ mixer vol.mute=^
.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
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

View file

@ -1,341 +1,484 @@
/*
* This is an example of a mixer program for Linux
/*-
* Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
*
* updated 1/1/93 to add stereo, level query, broken
* devmask kludge - cmetz@thor.tjhsst.edu
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* (C) Craig Metz and Hannu Savolainen 1993.
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* You may do anything you wish with this program.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* ditto for my modifications (John-Mark Gurney, 1997)
* $FreeBSD$
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <err.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/soundcard.h>
static const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
#include <mixer.h>
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);
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 void __dead2
usage(int devmask, int recmask)
{
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++;
}
}
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++;
}
}
printf("\n");
exit(1);
}
static int
res_name(const char *name, int mask)
{
int foo;
for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
if ((1 << foo) & mask && strcmp(names[foo], name) == 0)
break;
return (foo == SOUND_MIXER_NRDEVICES ? -1 : foo);
}
static void
print_recsrc(int recsrc, int recmask, int sflag)
{
int i, n;
if (recmask == 0)
return;
if (!sflag)
printf("Recording source: ");
for (i = 0, n = 0; i < SOUND_MIXER_NRDEVICES; i++)
if ((1 << i) & recsrc) {
if (sflag)
printf("%srec ", n ? " +" : "=");
else if (n)
printf(", ");
printf("%s", names[i]);
n++;
}
if (!sflag)
printf("\n");
}
static const mix_ctl_t ctl_dunit = {
.parent_dev = NULL,
.id = -1,
.name = "default_unit",
.mod = mod_dunit,
.print = NULL
};
int
main(int argc, char *argv[])
{
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;
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;
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;
n = 1;
for (;;) {
if (n >= argc || *argv[n] != '-')
while ((ch = getopt(argc, argv, "ad:f:os")) != -1) {
switch (ch) {
case 'a':
aflag = 1;
break;
if (strlen(argv[n]) != 2) {
if (strcmp(argv[n] + 1, "rec") != 0)
dusage = 1;
case 'd':
dunit = strtol(optarg, NULL, 10);
if (errno == EINVAL || errno == ERANGE)
err(1, "strtol");
dflag = 1;
break;
}
ch = *(argv[n] + 1);
if (ch == 'f' && n < argc - 1) {
name = argv[n + 1];
n += 2;
} else if (ch == 's') {
case 'f':
name = optarg;
break;
case 'o':
oflag = 1;
break;
case 's':
sflag = 1;
n++;
} else if (ch == 'S') {
Sflag = 1;
n++;
} else {
dusage = 1;
break;
case '?':
default:
usage();
}
}
if (sflag && Sflag)
dusage = 1;
argc -= optind;
argv += optind;
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;
/* 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");
}
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);
(void)mixer_close(m);
}
if (n && recmask)
printf(" ");
print_recsrc(recsrc, recmask, Sflag || sflag);
return (0);
}
argc--;
argv++;
if ((m = mixer_open(name)) == NULL)
err(1, "mixer_open: %s", name);
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;
}
initctls(m);
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;
}
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);
}
}
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;
}
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;
}
if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
goto parse;
if (sflag) {
printrecsrc(m, oflag);
(void)mixer_close(m);
return (0);
}
if (dusage) {
close(baz);
usage(devmask, recmask);
/* NOTREACHED */
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 (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");
}
if (drecsrc)
print_recsrc(recsrc, recmask, Sflag || sflag);
close(baz);
if (pall)
printall(m, oflag);
(void)mixer_close(m);
return (0);
}
static void __dead2
usage(void)
{
printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
" %1$s [-d unit] [-os] -a\n",
getprogname());
exit(1);
}
static void
initctls(struct mixer *m)
{
struct mix_dev *dp;
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");
}
}
static void
printall(struct mixer *m, int oflag)
{
struct mix_dev *dp;
printminfo(m, oflag);
TAILQ_FOREACH(dp, &m->devs, devs) {
m->dev = dp;
printdev(m, oflag);
}
}
static void
printminfo(struct mixer *m, int oflag)
{
int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
if (oflag)
return;
printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info);
printf(" (");
if (m->mode & MIX_MODE_PLAY)
printf("play");
if ((m->mode & playrec) == playrec)
printf("/");
if (m->mode & MIX_MODE_REC)
printf("rec");
printf(")");
if (m->f_default)
printf(" (default)");
printf("\n");
}
static void
printdev(struct mixer *m, int oflag)
{
struct mix_dev *d = m->dev;
mix_ctl_t *cp;
if (!oflag) {
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);
}
}
}
static void
printrecsrc(struct mixer *m, int oflag)
{
struct mix_dev *dp;
int n = 0;
if (!m->recmask)
return;
if (!oflag)
printf("%s: ", m->mi.name);
TAILQ_FOREACH(dp, &m->devs, devs) {
if (MIX_ISRECSRC(m, dp->devno)) {
if (n++ && !oflag)
printf(", ");
printf("%s", dp->name);
if (oflag)
printf(".%s=+%s",
mixer_get_ctl(dp, C_SRC)->name,
n ? " " : "");
}
}
printf("\n");
}
static int
mod_dunit(struct mix_dev *d, void *p)
{
int dunit = *((int *)p);
int n;
if ((n = mixer_get_dunit()) < 0) {
warn("cannot get default unit");
return (-1);
}
if (mixer_set_dunit(d->parent_mixer, dunit) < 0) {
warn("cannot set default unit to: %d", dunit);
return (-1);
}
printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit);
return (0);
}
static int
mod_volume(struct mix_dev *d, void *p)
{
struct mixer *m;
mix_ctl_t *cp;
mix_volume_t v;
const char *val;
char lstr[8], rstr[8];
float lprev, rprev, lrel, rrel;
int n;
m = d->parent_mixer;
cp = mixer_get_ctl(m->dev, C_VOL);
val = p;
n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
if (n == EOF) {
warnx("invalid volume value: %s", val);
return (-1);
}
lrel = rrel = 0;
if (n > 0) {
if (*lstr == '+' || *lstr == '-')
lrel = rrel = 1;
v.left = strtof(lstr, NULL);
/* 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);
}
return (0);
}
static int
mod_mute(struct mix_dev *d, void *p)
{
struct mixer *m;
mix_ctl_t *cp;
const char *val;
int n, opt = -1;
m = d->parent_mixer;
cp = mixer_get_ctl(m->dev, C_MUT);
val = p;
switch (*val) {
case '0':
opt = MIX_UNMUTE;
break;
case '1':
opt = MIX_MUTE;
break;
case '^':
opt = MIX_TOGGLEMUTE;
break;
default:
warnx("%c: no such modifier", *val);
return (-1);
}
n = MIX_ISMUTE(m, m->dev->devno);
if (mixer_set_mute(m, opt) < 0)
warn("%s.%s=%c", m->dev->name, cp->name, *val);
else
printf("%s.%s: %d -> %d\n",
m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
return (0);
}
static int
mod_recsrc(struct mix_dev *d, void *p)
{
struct mixer *m;
mix_ctl_t *cp;
const char *val;
int n, opt = -1;
m = d->parent_mixer;
cp = mixer_get_ctl(m->dev, C_SRC);
val = p;
switch (*val) {
case '+':
opt = MIX_ADDRECSRC;
break;
case '-':
opt = MIX_REMOVERECSRC;
break;
case '=':
opt = MIX_SETRECSRC;
break;
case '^':
opt = MIX_TOGGLERECSRC;
break;
default:
warnx("%c: no such modifier", *val);
return (-1);
}
n = MIX_ISRECSRC(m, m->dev->devno);
if (mixer_mod_recsrc(m, opt) < 0)
warn("%s.%s=%c", m->dev->name, cp->name, *val);
else
printf("%s.%s: %d -> %d\n",
m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
return (0);
}
static int
print_volume(struct mix_dev *d, void *p)
{
struct mixer *m = d->parent_mixer;
const char *ctl_name = p;
printf("%s.%s=%.2f:%.2f\n",
m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
return (0);
}
static int
print_mute(struct mix_dev *d, void *p)
{
struct mixer *m = d->parent_mixer;
const char *ctl_name = p;
printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
return (0);
}
static int
print_recsrc(struct mix_dev *d, void *p)
{
struct mixer *m = d->parent_mixer;
const char *ctl_name = p;
if (!MIX_ISRECSRC(m, m->dev->devno))
return (-1);
printf("%s.%s=+\n", m->dev->name, ctl_name);
return (0);
}

View file

@ -1,5 +0,0 @@
# $FreeBSD$
ATF_TESTS_SH+= mixer_test
.include <bsd.test.mk>

View file

@ -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
}