Changeset View
Changeset View
Standalone View
Standalone View
mixer_lib/mixer.3
- This file was added.
.\"- | |||||
.\" 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. | |||||
.\" | |||||
.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 , | |||||
.Xr errno 2 | |||||
.Sh AUTHORS | |||||
.An Christos Margiolis Aq Mt christos@margiolis.net |