FreeBSD OSS mixer library implementation and a complete rewrite of mixer(8)
git clone git://git.margiolis.net/mixer.git
Log | Files | Refs | README

commit 47529790452349925011e29913d897d103cd64af
parent 03d70146d1eb749e65aaf90dbaf4f5cd32bc5268
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sun,  1 Aug 2021 18:33:19 +0300

updated manpage and improved mixer_close

Mmixer_lib/mixer.3 | 314++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mmixer_lib/mixer.c | 26+++++++++++++++++---------
Mmixer_lib/mixer.h | 4++--
3 files changed, 268 insertions(+), 76 deletions(-)

diff --git a/mixer_lib/mixer.3 b/mixer_lib/mixer.3 @@ -26,19 +26,26 @@ .Sh NAME .Nm mixer_open , .Nm mixer_close , -.Nm mixer_getdev , -.Nm mixer_getdevbyname , -.Nm mixer_setvol , -.Nm mixer_setmute , -.Nm mixer_modrecsrc , -.Nm mixer_getdunit , -.Nm mixer_setdunit , -.Nm mixer_getstatus , -.Nm mixer_getnmixers , +.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_ISRECSRC , +.Nm MIX_VOLNORM , +.Nm MIX_VOLDENORM .Nd interface to OSS mixers .Sh LIBRARY Mixer library (libmixer, -lmixer) @@ -49,23 +56,35 @@ Mixer library (libmixer, -lmixer) .Ft int .Fn mixer_close "struct mixer *m" .Ft struct mix_dev * -.Fn mixer_getdev "struct mixer *m" "int devno" +.Fn mixer_get_dev "struct mixer *m" "int devno" .Ft struct mix_dev * -.Fn mixer_getdevbyname "struct mixer *m" "name" +.Fn mixer_get_dev_byname "struct mixer *m" "name" .Ft int -.Fn mixer_setvol "struct mixer *m" "mix_volume_t vol" +.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_setmute "struct mixer *m" "int opt" +.Fn mixer_add_ctl_s "mix_ctl_t *ctl" .Ft int -.Fn mixer_modrecsrc "struct mixer *m" "int opt" +.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_getdunit "void" +.Fn mixer_set_vol "struct mixer *m" "mix_volume_t vol" .Ft int -.Fn mixer_setdunit "struct mixer *m" "int unit" +.Fn mixer_set_mute "struct mixer *m" "int opt" .Ft int -.Fn mixer_getstatus "int unit" +.Fn mixer_mod_recsrc "struct mixer *m" "int opt" .Ft int -.Fn mixer_getnmixers "void" +.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 @@ -74,6 +93,10 @@ Mixer library (libmixer, -lmixer) .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 @@ -84,57 +107,174 @@ a simple way. A mixer is described by the following structure: .Bd -literal struct mixer { - TAILQ_HEAD(, mix_dev) devs; - struct mix_dev *dev; - oss_mixerinfo mi; - oss_card_info ci; - char name[NAME_MAX]; - int fd; - int unit; - int ndev; - int devmask; + 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; - int recmask; + 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; - int f_default; -#define MIX_STATUS_NONE 0x00 -#define MIX_STATUS_PLAY 0x01 -#define MIX_STATUS_REC 0x02 - int status; + 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 structure is described as follows: +Each mixer device stored in a mixer is described as follows: .Bd -literal struct mix_dev { - char name[NAME_MAX]; - int devno; + 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)roundf((v) * 100.0f)) - float left; - float right; + 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 -.Ss Device names -The name a device is guaranteed to be one of the following: -.Bd -ragged -offset indent +.Pp +The fields are follows: +.Bl -tag -width "parent_mixer" +.It Fa parent_mixer +Pointer to the parent mixer. +.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 +Parent device for this control. +.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 @@ -168,16 +308,16 @@ always call it when the application is done using the mixer. .Ss Manipulating the mixer .Pp The -.Fn mixer_getdev +.Fn mixer_get_dev and -.Fn mixer_getdevbyname +.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_setvol +.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 @@ -185,7 +325,7 @@ device. The allowed volume values are between MIX_VOLMIN (0.0) and MIX_VOLMAX (1.0). .Pp The -.Fn mixer_setmute +.Fn mixer_set_mute function modifies the mute of a selected device. The .Ar opt parameter has to be one of the following options: @@ -199,7 +339,7 @@ Toggle the device's mute (e.g mute if unmuted and unmute if muted). .El .Pp The -.Fn mixer_modrecsrc +.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 @@ -216,9 +356,9 @@ Toggle device from the recording sources. .El .Pp The -.Fn mixer_getdunit +.Fn mixer_get_dunit and -.Fn mixer_setdunit +.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 @@ -226,8 +366,8 @@ the controls. .Pp The -.Fn mixer_getstatus -function returns the playback/recording status of the audio device the mixer +.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 @@ -241,7 +381,7 @@ Playback and recording. .El .Pp The -.Fn mixer_getnmixers +.Fn mixer_get_nmixers function returns the total number of mixer devices in the system. .Pp The @@ -261,6 +401,48 @@ macro checks if a device is a recording device. 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 @@ -269,19 +451,19 @@ function returns the newly created handle on success and NULL on failure. .Pp The .Fn mixer_close , -.Fn mixer_setvol , -.Fn mixer_setmute , -.Fn mixer_modrecsrc , -.Fn mixer_getdunut , -.Fn mixer_setdunit +.Fn mixer_set_vol , +.Fn mixer_set_mute , +.Fn mixer_mod_recsrc , +.Fn mixer_get_dunut , +.Fn mixer_set_dunit and -.Fn mixer_getnmixers +.Fn mixer_get_nmixers functions return 0 or positive values on success and -1 on failure. .Pp The -.Fn mixer_getdev +.Fn mixer_get_dev and -.Fn mixer_getdevbyname +.Fn mixer_get_dev_byname functions return the selected device on success and NULL on failure. .Pp All functions set the value of @@ -299,12 +481,12 @@ if ((m = mixer_open(mix_name)) == NULL) err(1, "mixer_open: %s", mix_name); dev_name = ...; -if ((m->dev = mixer_getdevbyname(m, dev_name)) < 0) +if ((m->dev = mixer_get_dev_byname(m, dev_name)) < 0) err(1, "unknown device: %s", dev_name); vol.left = ...; vol.right = ....; -if (mixer_setvol(m, vol) < 0) +if (mixer_set_vol(m, vol) < 0) warn("cannot change volume"); (void)mixer_close(m); @@ -320,7 +502,7 @@ TAILQ_FOREACH(dp, &m->devs, devs) { m->dev = dp; /* Select device. */ if (M_ISMUTE(m, dp->devno)) continue; - if (mixer_setmute(m, MIX_MUTE) < 0) + if (mixer_set_mute(m, MIX_MUTE) < 0) warn("cannot mute device: %s", dp->name); } @@ -348,6 +530,8 @@ TAILQ_FOREACH(dp, &m->devs, devs) { .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 diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c @@ -143,18 +143,14 @@ int mixer_close(struct mixer *m) { struct mix_dev *dp; - mix_ctl_t *cp; 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)) { - cp = TAILQ_FIRST(&dp->ctls); - TAILQ_REMOVE(&dp->ctls, cp, ctls); - free(cp); - } + while (!TAILQ_EMPTY(&dp->ctls)) + (void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls)); free(dp); } free(m); @@ -215,7 +211,8 @@ 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 *)) { - mix_ctl_t *ctl; + struct mix_dev *dp; + mix_ctl_t *ctl, *cp; /* XXX: should we accept NULL name? */ if (parent_dev == NULL) { @@ -230,8 +227,19 @@ mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name, (void)strlcpy(ctl->name, name, sizeof(ctl->name)); ctl->mod = mod; ctl->print = print; - TAILQ_INSERT_TAIL(&parent_dev->ctls, ctl, ctls); - parent_dev->nctl++; + dp = ctl->parent_dev; + /* Make sure the same ID or name already exists. */ + if (!TAILQ_EMPTY(&dp->ctls)) { + 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); } diff --git a/mixer_lib/mixer.h b/mixer_lib/mixer.h @@ -83,7 +83,7 @@ struct mixer { int fd; /* file descriptor */ int unit; /* audio card unit */ int ndev; /* number of devices */ - int devmask; /* valid devices */ + int devmask; /* supported devices */ #define MIX_MUTE 0x01 #define MIX_UNMUTE 0x02 #define MIX_TOGGLEMUTE 0x04 @@ -94,11 +94,11 @@ struct mixer { #define MIX_SETRECSRC 0x04 #define MIX_TOGGLERECSRC 0x08 int recsrc; /* recording sources */ - int f_default; /* default mixer flag */ #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 */ }; struct mixer *mixer_open(const char *);