FreeBSD sound mixer improvements

tags: bsd · projects · programming

This project was part of Google Summer of Code 2021, but development is still active. The development report can be found on the FreeBSD Wiki. The reason behind this project is that the FreeBSD’s OSS mixer capabilities were really basic and outdated — even un/muting didn’t exist and one had to write custom scripts for such a basic task. Setting default audio devices had to be done by tweaking sysctls and programs needing to use the mixer required DIY implementations as there was no mixer library available. The project was merged to upstream on FreeBSD 14.0.

Table of contents

Kernel patches

Un/muting (commit)

I decided that un/muting is better to be implemented in sound(4) in order to avoid having to write daemons or use files. The way this works is by implementing the SOUND_MIXER_READ_MUTE and SOUND_MIXER_WRITE_MUTE ioctls, which did exist in older OSS implementations, but were considered obselete. One thing to note is that the functionality isn’t the same as their old one. Older OSS versions had those 2 ioctls take/return an integer with a value of 0 or 1, which indicated whether the whole mixer is muted or not. My implementation takes/returns a bitmask that tells which devices are muted. This allows us to mute and unmute only the devices we want, instead of the whole mixer. If you’re familiar with the OSS API, this bitmask works the same way as DEVMASK, RECMASK and RECSRC.

Playback/recording mode information (commit)

Here I implemented a sysctl (dev.pcm.<N>.mode) which gives information about a device’s playback/recording mode. The rationale for this control is to include /dev/sndstat’s mixer information in the output of the new mixer(8). The sysctl can return the following values (NOTE: these values are OR’ed together if more than one mode is supported):

Value Meaning
0x01 Mixer
0x02 Playback device
0x04 Recording device


mixer(3) implementation (commit)

mixer(3) provides a simple interface for working with the OSS mixer. The man page explains how the library works, including some examples, so there’s no need to repeat myself. You can see the library in action in the source code for mixer(8).

The basic structure of a program looks like this (link with -lmixer):

#include <err.h>
#include <mixer.h>

main(int argc, char *argv[])
	struct mixer *m;
	const char *name = "/dev/mixer0";

	if ((m = mixer_open(name)) == NULL)
		err(1, "mixer_open(%s)", name);

	/* do stuff */

	return (0);

mixer(8) rewrite (commit)

This implementation is a complete rewrite of the old mixer(8) utility. It now uses mixer(3) as a backend and implements all the new features the library provides. It’s got more command line options and works with a control-oriented interface inspired by OpenBSD’s mixerctl(8). Again, everything is detailed in the man page.

Old mixer(8) output:

$ mixer.old

Mixer vol      is currently set to  85:85
Mixer pcm      is currently set to 100:100
Mixer speaker  is currently set to  74:74
Mixer line     is currently set to   1:1
Mixer mic      is currently set to  67:67
Mixer mix      is currently set to  74:74
Mixer rec      is currently set to  37:37
Mixer igain    is currently set to   0:0
Mixer ogain    is currently set to 100:100
Mixer monitor  is currently set to  67:67
Recording source: mic

New mixer(8) output:

$ mixer

pcm0:mixer: <Realtek ALC662 rev3 (Analog 2.0+HP/2.0)> on hdaa0 kld snd_hda (play/rec) (default)
    vol       = 0.85:0.85     pbk
    pcm       = 1.00:1.00     pbk
    speaker   = 0.74:0.74     rec
    line      = 0.01:0.01     rec
    mic       = 0.67:0.67     rec src
    mix       = 0.74:0.74     rec
    rec       = 0.37:0.37     pbk
    igain     = 0.00:0.00     pbk
    ogain     = 1.00:1.00     pbk
    monitor   = 0.67:0.67     rec

Code and manuals