MIXER(3)

πŸ“¦ version 15.1, in freebsd 14.5, freebsd 15.1

πŸŽ€ NAME πŸŽ€

This is MIXER(3) from the Library Functions Manual, and it's literally the whole interface to OSS mixers. The squad is: mixer_open, mixer_close, mixer_get_dev, mixer_get_dev_byname, mixer_add_ctl, mixer_add_ctl_s, mixer_remove_ctl, mixer_get_ctl, mixer_get_ctl_byname, mixer_set_vol, mixer_set_mute, mixer_mod_recsrc, mixer_get_dunit, mixer_set_dunit, mixer_get_mode, mixer_get_nmixers, mixer_get_path, MIX_ISDEV, MIX_ISMUTE, MIX_ISREC, MIX_ISRECSRC, MIX_VOLNORM, and MIX_VOLDENORM. That's the interface to OSS mixers, periodt. πŸ’…

πŸ“š LIBRARY πŸ“š

Mixer library (libmixer, -lmixer) β€” that's the whole vibe, bestie. 🫢

✨ SYNOPSIS ✨

#include <mixer.h>

struct mixer *
mixer_open(const char *name);

int
mixer_close(struct mixer *m);

struct mix_dev *
mixer_get_dev(struct mixer *m, int devno);

struct mix_dev *
mixer_get_dev_byname(struct mixer *m, name);

int
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));

int
mixer_add_ctl_s(mix_ctl_t *ctl);

int
mixer_remove_ctl(mix_ctl_t *ctl);

mix_ctl_t *
mixer_get_ctl(struct mix_dev *d, int id);

mix_ctl_t *
mixer_get_ctl_byname(struct mix_dev *d, const char *name);

int
mixer_set_vol(struct mixer *m, mix_volume_t vol);

int
mixer_set_mute(struct mixer *m, int opt);

int
mixer_mod_recsrc(struct mixer *m, int opt);

int
mixer_get_dunit(void);

int
mixer_set_dunit(struct mixer *m, int unit);

int
mixer_get_mode(int unit);

int
mixer_get_nmixers(void);

int
mixer_get_path(char * buf, size_t size, int unit);

int
MIX_ISDEV(struct mixer *m, int devno);

int
MIX_ISMUTE(struct mixer *m, int devno);

int
MIX_ISREC(struct mixer *m, int devno);

int
MIX_ISRECSRC(struct mixer *m, int devno);

float
MIX_VOLNORM(int v);

int
MIX_VOLDENORM(float v);

πŸ¦‹ DESCRIPTION πŸ¦‹

The mixer library lets userspace programs access and manipulate OSS sound mixers in a simple way. Iconic behavior, honestly. πŸ‘‘

πŸ’– Mixer πŸ’–

A mixer is described by the following structure β€” main character energy:

struct mixer {
        TAILQ_HEAD(mix_devhead, 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 */
};

The fields are giving:

  • devs β€” A tail queue structure containing all supported mixer devices.
  • dev β€” A pointer to the currently selected device. The device is one of the elements in devs.
  • mi β€” OSS information about the mixer. Look at the definition of the oss_mixerinfo structure in <sys/soundcard.h> to see its fields.
  • ci β€” OSS audio card information. This structure is also defined in <sys/soundcard.h>.
  • name β€” Path to the mixer (e.g /dev/mixer0).
  • fd β€” File descriptor returned when the mixer is opened in mixer_open().
  • unit β€” Audio card unit. Since each mixer device maps to a pcmX device, 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 unit is 0 as well. This number is useful when checking if the mixer's audio card is the default one.
  • ndev β€” Number of devices in devs.
  • 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, mixer_open() stores only the supported devices in devs, so it is very unlikely this mask will be needed.
  • mutemask β€” Bit mask containing all muted devices. The logic is the same as with devmask.
  • recmask β€” Bit mask containing all recording devices. Again, same logic as with the other masks.
  • recsrc β€” Bit mask containing all recording sources. Yes, same logic again.
  • mode β€” Bit mask containing the supported modes for this audio device. It holds the value of the dev.pcm.X.mode sysctl.
  • f_default β€” Flag which tells whether the mixer's audio card is the default one.

🎧 Mixer device 🎧

Each mixer device stored in a mixer is described as follows β€” lowkey adorable:

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_ctlhead, mix_ctl) ctls;  /* control list */
        TAILQ_ENTRY(mix_dev) devs;
};

The fields are follows:

  • parent_mixer β€” Pointer to the mixer the device is attached to.
  • name β€” Device name given by the OSS API. Devices can have one of the following names:

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.

  • devno β€” Device's index in the SOUND_MIXER_NRDEVICES macro defined in <sys/soundcard.h>. This number is used to check against the masks defined in the mixer structure.
  • 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 MIX_VOLDENORM macro if needed.
  • nctl β€” Number of user-defined mixer controls associated with the device.
  • ctls β€” A tail queue containing user-defined mixer controls.

🧚 User-defined mixer controls 🧚

Each mixer device can have user-defined controls. The control structure is defined as follows β€” custom-fit, bestie:

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

The fields are follows:

  • parent_dev β€” Pointer to the device the control is attached to.
  • 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.
  • name β€” Control name. As with id, the caller has to make sure the same name is not used more than once.
  • mod β€” Function pointer to a control modification function. As in mixer(8), each mixer control's values can be modified. For example, if we have a volume control, the mod function will be responsible for handling volume changes.
  • print β€” Function pointer to a control print function.

🌸 Opening and closing the mixer 🌸

The application must first call the mixer_open() function to obtain a handle to the device, which is used as an argument in most other functions and macros. The parameter name specifies the path to the mixer. OSS mixers are stored under /dev/mixerN where N is the number of the mixer device. Each device maps to an actual pcm audio card, so /dev/mixer0 is the mixer for pcm0, and so on. If name is NULL or /dev/mixer, mixer_open() opens the default mixer (hw.snd.default_unit).

The mixer_close() function frees resources and closes the mixer device. It is a good practice to always call it when the application is done using the mixer. no cap, clean up after yourself. 🧼

πŸ”₯ Manipulating the mixer πŸ”₯

The mixer_get_dev() and 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.

The mixer_set_vol() function changes the volume of the selected mixer device. The 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). 🎚️

The mixer_set_mute() function modifies the mute of a selected device. The opt parameter has to be one of the following options:

  • MIX_MUTE β€” Mute the device.
  • MIX_UNMUTE β€” Unmute the device.
  • MIX_TOGGLEMUTE β€” Toggle the device's mute (e.g mute if unmuted and unmute if muted).

The mixer_mod_recsrc() function modifies a recording device. The selected device has to be a recording device, otherwise the function will fail. The opt parameter has to be one of the following options:

  • MIX_ADDRECSRC β€” Add device to the recording sources.
  • MIX_REMOVERECSRC β€” Remove device from the recording sources.
  • MIX_SETRECSRC β€” Set device as the only recording source.
  • MIX_TOGGLERECSRC β€” Toggle device from the recording sources.

okay so, mixer_get_dunit() and mixer_set_dunit() are your get/set besties for the default audio card in the system 🎧. honestly it's not really a mixer feature, but it's useful to have instead of having to use the sysctl(3) controls. convenience slay.

mixer_get_mode() returns the operating mode of the audio device the mixer belongs to. you can OR these values together in case more than one mode is supported:

  • MIX_MODE_MIXER β€” the audio device has a mixer.
  • MIX_MODE_PLAY β€” the audio device supports playback.
  • MIX_MODE_REC β€” the audio device supports recording.

mixer_get_nmixers() returns the maximum mixer unit number. and yes bestie, that might sound like incorrect behavior at first β€” you'd expect β€œnmixers” to refer to the total number of active mixers β€” but it's actually more intuitive for applications that want to loop through all mixer devices (peep the β€œEXAMPLES” section πŸ‘€).

mixer_get_path() writes the path of the mixer device specified in the unit argument to the buffer specified in buf. unit can be either -1, in which case mixer_get_path() will fetch the path of the default mixer, or between 0 and the maximum mixer unit.

the MIX_ISDEV() macro checks if a device is actually a valid device for a given mixer. it is very unlikely that this macro will ever be needed, since the library stores only valid devices by default. lowkey iconic of it πŸ’….

the MIX_ISMUTE() macro checks if a device is muted.

the MIX_ISREC() macro checks if a device is a recording device.

the MIX_ISRECSRC() macro checks if a device is a recording source.

the MIX_VOLNORM() macro normalizes a value to a 32-bit floating point number. it is used to normalize the volumes read from the OSS API.

the MIX_VOLDENORM() macro denormalizes the left and right volumes stored in the mix_dev structure.

πŸŽ›οΈ Defining and using mixer controls πŸŽ›οΈ

mix_add_ctl() creates a control and attaches it to the device specified in the parent argument.

mix_add_ctl_s() does the same thing as mix_add_ctl(), but the caller passes a mix_ctl_t * structure instead of each field as a separate argument.

mixer_remove_ctl() removes a control from the device it's attached to.

mixer_get_ctl() searches for a control in the device specified in the d argument and returns a pointer to it. the search is done using the control's ID.

mixer_get_ctl_byname() is the same as mixer_get_ctl(), but the search is done using the control's name.

πŸ’– RETURN VALUES πŸ’–

mixer_open() returns the newly created handle on success and NULL on failure.

the mixer_close(), mixer_set_vol(), mixer_set_mute(), mixer_mod_recsrc(), mixer_get_dunut(), mixer_set_dunit(), mixer_get_nmixers(), and mixer_get_path() functions return 0 or positive values on success and -1 on failure.

the mixer_get_dev() and mixer_get_dev_byname() functions return the selected device on success and NULL on failure.

all functions set the value of errno on failure.

🌸 EXAMPLES 🌸

Change the volume of a device

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);

Mute all unmuted devices

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);
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);

Loop through all mixer devices in the system

struct mixer *m;
char buf[NAME_MAX];
int n;

if ((n = mixer_get_nmixers()) < 0)
        errx(1, "no mixers present in the system");
for (i = 0; i < n; i++) {
        (void)mixer_get_path(buf, sizeof(buf), i);
        if ((m = mixer_open(buf)) == NULL)
                continue;
        ...
        (void)mixer_close(m);
}

πŸ” SEE ALSO πŸ”

go check out queue(3), sysctl(3), sound(4), mixer(8) and errno(2) πŸ’œ.

πŸ‘‘ AUTHORS πŸ‘‘

the icon behind this page is Christos Margiolis <christos@FreeBSD.org> 🫢.


and that's the tea β˜•βœ¨ β€” this manual page is the Debian edition, dated August 4, 2024, and it's MIXER(3). byeee bestie πŸŒˆπŸ’–

πŸ“¦ where this page comes from