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:
parent
884f38590c
commit
903873ce15
@ -69,6 +69,7 @@ SUBDIR= ${SUBDIR_BOOTSTRAP} \
|
|||||||
liblzma \
|
liblzma \
|
||||||
libmemstat \
|
libmemstat \
|
||||||
libmd \
|
libmd \
|
||||||
|
libmixer \
|
||||||
libmt \
|
libmt \
|
||||||
lib80211 \
|
lib80211 \
|
||||||
libnetbsd \
|
libnetbsd \
|
||||||
|
8
lib/libmixer/Makefile
Normal file
8
lib/libmixer/Makefile
Normal 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
540
lib/libmixer/mixer.3
Normal 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
493
lib/libmixer/mixer.c
Normal 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
123
lib/libmixer/mixer.h
Normal 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_ */
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
.include <src.opts.mk>
|
.include <src.opts.mk>
|
||||||
|
|
||||||
PROG= mixer
|
PROG= mixer
|
||||||
MAN= mixer.8
|
SRCS= ${PROG}.c
|
||||||
|
MAN= ${PROG}.8
|
||||||
HAS_TESTS=
|
LDFLAGS+= -lmixer
|
||||||
SUBDIR.${MK_TESTS}+= tests
|
|
||||||
|
|
||||||
.include <bsd.prog.mk>
|
.include <bsd.prog.mk>
|
||||||
|
@ -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
|
.\" Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
.\" modification, are permitted provided that the following conditions
|
.\" of this software and associated documentation files (the "Software"), to deal
|
||||||
.\" are met:
|
.\" in the Software without restriction, including without limitation the rights
|
||||||
.\" 1. Redistributions of source code must retain the above copyright
|
.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
.\" notice, this list of conditions and the following disclaimer.
|
.\" copies of the Software, and to permit persons to whom the Software is
|
||||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
.\" furnished to do so, subject to the following conditions:
|
||||||
.\" 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.
|
|
||||||
.\"
|
.\"
|
||||||
.\" THIS SOFTWARE IS PROVIDED BY MIKE PRITCHARD AND CONTRIBUTORS ``AS IS'' AND
|
.\" The above copyright notice and this permission notice shall be included in
|
||||||
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
.\" all copies or substantial portions of the Software.
|
||||||
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
.\"
|
||||||
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
.\" THE SOFTWARE.
|
||||||
.\" SUCH DAMAGE.
|
|
||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd June 2, 2014
|
|
||||||
.Dt MIXER 8
|
.Dd June 30, 2021
|
||||||
|
.Dt mixer 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm mixer
|
.Nm mixer
|
||||||
.Nd set/display soundcard mixer values
|
.Nd manipulate soundcard mixer controls
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl f Ar device
|
.Op Fl f Ar device
|
||||||
.Op Fl s | S
|
.Op Fl d Ar unit
|
||||||
.Oo
|
.Op Fl os
|
||||||
.Ar dev
|
.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value
|
||||||
.Sm off
|
|
||||||
.Oo
|
|
||||||
.Op Cm + | -
|
|
||||||
.Ar lvol
|
|
||||||
.Op : Oo Cm + | - Oc Ar rvol
|
|
||||||
.Oc
|
|
||||||
.Oc
|
|
||||||
.Sm on
|
|
||||||
.Ar ...
|
.Ar ...
|
||||||
.Nm
|
.Nm
|
||||||
.Op Fl f Ar device
|
.Op Fl d Ar unit
|
||||||
.Op Fl s | S
|
.Op Fl os
|
||||||
.Cm recsrc
|
.Fl a
|
||||||
.Ar ...
|
|
||||||
.Nm
|
|
||||||
.Op Fl f Ar device
|
|
||||||
.Op Fl s | S
|
|
||||||
.Sm off
|
|
||||||
.Bro
|
|
||||||
.Cm ^ | + | - | =
|
|
||||||
.Brc
|
|
||||||
.Cm rec
|
|
||||||
.Sm on
|
|
||||||
.Ar rdev ...
|
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The
|
The
|
||||||
.Nm
|
.Nm
|
||||||
utility is used to set and display soundcard mixer device levels.
|
utility is used to set and display soundcard mixer device controls.
|
||||||
It may
|
.Pp
|
||||||
also be used to start and stop recording from the soundcard.
|
The options are as follows:
|
||||||
The list
|
.Bl -tag -width "-f device"
|
||||||
of mixer devices that may be modified are:
|
.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
|
.Bd -ragged -offset indent
|
||||||
vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
|
vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
|
||||||
pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
|
pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
|
||||||
@ -81,101 +76,170 @@ Not all mixer devices are available.
|
|||||||
.Pp
|
.Pp
|
||||||
Without any arguments,
|
Without any arguments,
|
||||||
.Nm
|
.Nm
|
||||||
displays the current settings for all supported devices, followed by information
|
displays all information for each one of the mixer's supported devices to
|
||||||
about the current recording input devices.
|
.Ar stdout .
|
||||||
If the
|
If the
|
||||||
.Ar dev
|
.Ar dev
|
||||||
argument is specified,
|
argument is specified,
|
||||||
.Nm
|
.Nm
|
||||||
displays only the value for that
|
displays only the values for
|
||||||
.Ar dev .
|
.Ar dev .
|
||||||
|
More than one device may be specified.
|
||||||
.Pp
|
.Pp
|
||||||
To modify the mixer value
|
Commands use the following format:
|
||||||
.Ar dev ,
|
.Pp
|
||||||
the optional left and right channel settings of
|
.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent
|
||||||
.Ar lvol Ns Op : Ns Ar rvol
|
.It Sy "Name Action"
|
||||||
may be specified.
|
.It "dev Display all controls"
|
||||||
The
|
.It "dev.control Display only the specified control"
|
||||||
.Ar lvol
|
.It "dev.control=value Set control value"
|
||||||
and
|
.El
|
||||||
.Ar rvol
|
.Pp
|
||||||
arguments may be from 0 - 100.
|
The available controls are as follows (replace
|
||||||
Omitting
|
|
||||||
.Ar dev
|
.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
|
.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 +
|
.Cm +
|
||||||
or
|
or
|
||||||
.Cm - ,
|
.Cm - ,
|
||||||
the value following will be used as a relative adjustment, modifying the
|
the value following will be used as a relative adjustment, modifying the
|
||||||
current settings by the amount specified.
|
current settings by the amount specified.
|
||||||
.Pp
|
.Pp
|
||||||
If the
|
The
|
||||||
.Fl s
|
.Ar dev.mute
|
||||||
flag is used, the current mixer values will be displayed in a format suitable
|
control (un)mutes a device. The following values are available:
|
||||||
for use as the command-line arguments to a future invocation of
|
.Bl -tag -width = -offset indent
|
||||||
.Nm
|
.It Cm 0
|
||||||
(as above).
|
unmutes
|
||||||
|
.Ar dev .
|
||||||
|
.It Cm 1
|
||||||
|
mutes
|
||||||
|
.Ar dev .
|
||||||
|
.It Cm ^
|
||||||
|
toggles the mute of
|
||||||
|
.Ar dev .
|
||||||
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The
|
The
|
||||||
.Fl S
|
.Ar dev.recsrc
|
||||||
flag provides the above output without mixing field separators.
|
control modifies the recording sources of a mixer.
|
||||||
.Pp
|
.Nm
|
||||||
To change the recording device you use one of:
|
marks devices which can be used as a recording source with
|
||||||
.Bl -tag -width =rec -offset indent
|
.Ar rec .
|
||||||
.It Cm ^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
|
toggles
|
||||||
.Ar rdev
|
.Ar dev
|
||||||
of possible recording devices
|
of possible recording devices
|
||||||
.It Cm +rec
|
.It Cm +
|
||||||
adds
|
adds
|
||||||
.Ar rdev
|
.Ar dev
|
||||||
to possible recording devices
|
to possible recording devices
|
||||||
.It Cm -rec
|
.It Cm -
|
||||||
removes
|
removes
|
||||||
.Ar rdev
|
.Ar dev
|
||||||
from possible recording devices
|
from possible recording devices
|
||||||
.It Cm =rec
|
.It Cm =
|
||||||
sets the recording device to
|
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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The above commands work on an internal mask.
|
Increase the
|
||||||
After all the options
|
.Ar mic
|
||||||
have been parsed, it will set then read the mask from the sound card.
|
device's left volume by 0.10 and decrease the right
|
||||||
This will let you see EXACTLY what the soundcard is using for the
|
volume by 0.05:
|
||||||
recording device(s).
|
.Bl -tag -width Ds -offset indent
|
||||||
|
.It $ mixer mic.volume=+0.10:-0.05
|
||||||
|
.El
|
||||||
.Pp
|
.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
|
.Pp
|
||||||
The option
|
Set
|
||||||
.Fl f
|
.Ar mic
|
||||||
.Ar device
|
and toggle
|
||||||
will open
|
.Ar line
|
||||||
.Ar device
|
recording sources:
|
||||||
as the mixer device.
|
.Bl -tag -width Ds -offset indent
|
||||||
.Sh FILES
|
.It $ mixer mic.recsrc=+ line.recsrc=^
|
||||||
.Bl -tag -width /dev/mixer -compact
|
.El
|
||||||
.It Pa /dev/mixer
|
.Pp
|
||||||
the default mixer device
|
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
|
.El
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr cdcontrol 1 ,
|
.Xr mixer 3 ,
|
||||||
.Xr sound 4
|
.Xr sound 4 ,
|
||||||
|
.Xr sysctl 8
|
||||||
.Sh HISTORY
|
.Sh HISTORY
|
||||||
The
|
The
|
||||||
.Nm
|
.Nm
|
||||||
utility first appeared in
|
utility first appeared in FreeBSD 2.0.5 and was rewritten completely in
|
||||||
.Fx 2.0.5 .
|
FreeBSD 12.2. \" FIXME: replace 12.2 with proper version.
|
||||||
.Sh AUTHORS
|
.Sh AUTHORS
|
||||||
.An -nosplit
|
.An Christos Margiolis Aq Mt christos@margiolis.net
|
||||||
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 .
|
|
||||||
|
@ -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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* devmask kludge - cmetz@thor.tjhsst.edu
|
* 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 <err.h>
|
||||||
#include <fcntl.h>
|
#include <errno.h>
|
||||||
#include <libgen.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <unistd.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 void usage(void) __dead2;
|
||||||
static int res_name(const char *name, int mask);
|
static void initctls(struct mixer *);
|
||||||
static void print_recsrc(int recsrc, int recmask, int sflag);
|
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
|
static const mix_ctl_t ctl_dunit = {
|
||||||
usage(int devmask, int recmask)
|
.parent_dev = NULL,
|
||||||
{
|
.id = -1,
|
||||||
int i, n;
|
.name = "default_unit",
|
||||||
|
.mod = mod_dunit,
|
||||||
printf("usage: mixer [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n"
|
.print = NULL
|
||||||
" 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char mixer[PATH_MAX] = "/dev/mixer";
|
struct mixer *m;
|
||||||
char lstr[8], rstr[8];
|
mix_ctl_t *cp;
|
||||||
char *name, *eptr;
|
char *name = NULL, buf[NAME_MAX];
|
||||||
int devmask = 0, recmask = 0, recsrc = 0, orecsrc;
|
char *p, *bufp, *devstr, *ctlstr, *valstr = NULL;
|
||||||
int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0;
|
int dunit, i, n, pall = 1;
|
||||||
int l, r, lrel, rrel;
|
int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
|
||||||
int ch, foo, bar, baz, dev, m, n, t;
|
char ch;
|
||||||
|
|
||||||
if ((name = strdup(basename(argv[0]))) == NULL)
|
while ((ch = getopt(argc, argv, "ad:f:os")) != -1) {
|
||||||
err(1, "strdup()");
|
switch (ch) {
|
||||||
if (strncmp(name, "mixer", 5) == 0 && name[5] != '\0') {
|
case 'a':
|
||||||
n = strtol(name + 5, &eptr, 10) - 1;
|
aflag = 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] != '-')
|
|
||||||
break;
|
break;
|
||||||
if (strlen(argv[n]) != 2) {
|
case 'd':
|
||||||
if (strcmp(argv[n] + 1, "rec") != 0)
|
dunit = strtol(optarg, NULL, 10);
|
||||||
dusage = 1;
|
if (errno == EINVAL || errno == ERANGE)
|
||||||
|
err(1, "strtol");
|
||||||
|
dflag = 1;
|
||||||
break;
|
break;
|
||||||
}
|
case 'f':
|
||||||
ch = *(argv[n] + 1);
|
name = optarg;
|
||||||
if (ch == 'f' && n < argc - 1) {
|
break;
|
||||||
name = argv[n + 1];
|
case 'o':
|
||||||
n += 2;
|
oflag = 1;
|
||||||
} else if (ch == 's') {
|
break;
|
||||||
|
case 's':
|
||||||
sflag = 1;
|
sflag = 1;
|
||||||
n++;
|
|
||||||
} else if (ch == 'S') {
|
|
||||||
Sflag = 1;
|
|
||||||
n++;
|
|
||||||
} else {
|
|
||||||
dusage = 1;
|
|
||||||
break;
|
break;
|
||||||
|
case '?':
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sflag && Sflag)
|
argc -= optind;
|
||||||
dusage = 1;
|
argv += optind;
|
||||||
|
|
||||||
argc -= n - 1;
|
/* Print all mixers and exit. */
|
||||||
argv += n - 1;
|
if (aflag) {
|
||||||
|
if ((n = mixer_get_nmixers()) < 0)
|
||||||
if ((baz = open(name, O_RDWR)) < 0)
|
err(1, "mixer_get_nmixers");
|
||||||
err(1, "%s", name);
|
for (i = 0; i < n; i++) {
|
||||||
if (ioctl(baz, SOUND_MIXER_READ_DEVMASK, &devmask) == -1)
|
(void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
|
||||||
err(1, "SOUND_MIXER_READ_DEVMASK");
|
if ((m = mixer_open(buf)) == NULL)
|
||||||
if (ioctl(baz, SOUND_MIXER_READ_RECMASK, &recmask) == -1)
|
err(1, "mixer_open: %s", buf);
|
||||||
err(1, "SOUND_MIXER_READ_RECMASK");
|
initctls(m);
|
||||||
if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
|
if (sflag)
|
||||||
err(1, "SOUND_MIXER_READ_RECSRC");
|
printrecsrc(m, oflag);
|
||||||
orecsrc = recsrc;
|
else {
|
||||||
|
printall(m, oflag);
|
||||||
if (argc == 1 && dusage == 0) {
|
if (oflag)
|
||||||
for (foo = 0, n = 0; foo < SOUND_MIXER_NRDEVICES; foo++) {
|
printf("\n");
|
||||||
if (!((1 << foo) & devmask))
|
|
||||||
continue;
|
|
||||||
if (ioctl(baz, MIXER_READ(foo),&bar) == -1) {
|
|
||||||
warn("MIXER_READ");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (Sflag || sflag) {
|
(void)mixer_close(m);
|
||||||
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);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
argc--;
|
if ((m = mixer_open(name)) == NULL)
|
||||||
argv++;
|
err(1, "mixer_open: %s", name);
|
||||||
|
|
||||||
n = 0;
|
initctls(m);
|
||||||
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)
|
if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
|
||||||
dev = 0;
|
goto parse;
|
||||||
else if ((dev = res_name(*argv, devmask)) == -1) {
|
if (sflag) {
|
||||||
warnx("unknown device: %s", *argv);
|
printrecsrc(m, oflag);
|
||||||
dusage = 1;
|
(void)mixer_close(m);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (dusage) {
|
parse:
|
||||||
close(baz);
|
while (argc > 0) {
|
||||||
usage(devmask, recmask);
|
if ((p = bufp = strdup(*argv)) == NULL)
|
||||||
/* NOTREACHED */
|
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 (pall)
|
||||||
if (ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1)
|
printall(m, oflag);
|
||||||
err(1, "SOUND_MIXER_WRITE_RECSRC");
|
(void)mixer_close(m);
|
||||||
if (ioctl(baz, SOUND_MIXER_READ_RECSRC, &recsrc) == -1)
|
|
||||||
err(1, "SOUND_MIXER_READ_RECSRC");
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drecsrc)
|
static void __dead2
|
||||||
print_recsrc(recsrc, recmask, Sflag || sflag);
|
usage(void)
|
||||||
|
{
|
||||||
close(baz);
|
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);
|
return (0);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# $FreeBSD$
|
|
||||||
|
|
||||||
ATF_TESTS_SH+= mixer_test
|
|
||||||
|
|
||||||
.include <bsd.test.mk>
|
|
@ -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
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user