Rolling your own minimal embedded Linux for the Raspberry Pi -- part four: audio

Pi logo This series of articles is about building up a custom, minimal Linux installation for the Raspberry Pi (or similar), for use in an appliance. As I've complained repeatedly, the Raspberry Pi units -- particularly the small ones -- are ideal for low-volume embedded use, but all the official Linux releases for the Pi are oriented towards desktop-type operation. To assemble a Linux installation suitable for embedded operation means, essentially, starting from the very beginning, with a kernel and a shell.

The previous article in the series, on managing services and remote access was fairly complicated but this one, I hope, will be a lighter read. It's still not trivially simple -- I envisage a system where everything has to be configured manually -- in an appliance-oriented Linux we can't rely on udev, DBus, systemd, and all the other heavyweight infrastructure that a desktop or server Linux would have.

Constraints

Although many desktop Linux systems now use Pulse or JACK for complex audio processing and routing, for an embedded system it's likely to be better to rely on native ALSA support. ALSA is already quite sophisticated, except perhaps where integration with graphical tools is concerned. I question whether the complexity of Pulse is really necessary on most desktop Linux systems -- but I'm sure it's rarely necessary in an appliance.

I'm assuming that, in an embedded system, there will only be one audio device. It isn't all that difficult to handle multiple devices with ALSA, but the absence of management frameworks like DBus creates more work for the system builder, if you want the user to be able to switch devices at runtime.

The built-in audio hardware on the Pi 3B range is.... adequate. It's OK for speech and general warning beeps, but it's not exactly hi-fi. On the other hand, more-or-less any general-purpose USB audio device will work with the Pi, given the appropriate drivers. The Pi 3's share the internal USB bus with the networking components, and early models (or early kernels) were prone to drop-outs in USB audio. This doesn't seem to be a problem for the latest models (or the latest kernels), at least at CD audio sampling rates or lower. In any case, it doesn't make a huge difference to software configuration what kind of audio device you use, but supporting multiple devices in a minimal appliance will be a bit of a challenge. At the time of writing, I'm not sure whether the Pi 4 will change this situation.

Initial checks

As a first step, if you plan to use the Pi's built-in audio capabilities, ensure that audio is not disabled in firmware. In the boot partition, in config.txt, ensure there is a line like this:

dtparam=audio=on

Some Pi models (or some kernels) automatically route the built-in audio to the HDMI when an HDMI monitor is connected. There are software tools to change this, and there are HDMI in-line dongles that will extract the audio to a headphone jack if you're using an HDMI monitor without audio support. In any case, it's important to be aware that, if audio seems to be working but you're not hearing anything, the problem might not be in software -- you might just have your speaker/headphone connected to the wrong port.

Software required

Most of the required software is in kernel modules. If you have kernel modules in the conventional tree structure, and an up-to-date modules.dep file (e.g., from running depmod) then loading just the device-level module will suffice -- the module-loading infrastructure will take take of the dependencies. If you're handling modules explicitly, you'll need to work out all the dependencies, either by trial-and-error, or by examining the modules.dep from a desktop Raspbian system.

In the Raspberry Pi 3 series, the built-in sound generator is supported by the snd-bcm2835 module. Its dependencies are snd-pcm, snd-timer, and snd. Most USB sound devices are supported by snd-usb-audio, whose dependencies are snd-hwdep, snd-usbmidi-lib, snd-rawmidi, snd-seq-device, snd-pcm, snd-timer, and snd. I don't really understand why the USB audio module has dependencies on MIDI modules -- I presume that USB MIDI devices and USB PCM devices are supported by the same module.

Although it is possible, in principle, to produce audio by writing to the ALSA devices in /dev/snd (of which, more below), in practice I think every application or library that uses ALSA does so through the libasound.so library. This library is in the libasound2 Raspbian package.

Unless you're very lucky, or very knowledgeable, it's probably worth installing the basic ALSA utilities like alsamixer and aplay. These are all in the alsa-utils package. In the experimental stage, aplay -L is particularly useful, as it displays a list of output devices. This will usually be a long list, even if only the built-in audio is enabled, as there will be multiple virtual devices with different capabilities.

There will be, for example, different devices for the 3.5mm audio jack and the HDMI audio channel. An additional important distinction is between the hw and plughw devices -- in most cases the plughw device is more appropriate, because it will do the appropriate sample rate and format conversions when playing audio with properties that not directly supported by the hardware.

/dev/snd devices

If the driver modules are installed and initialized properly, you should see entries created in the /dev/snd directory. You don't need udev for this to happen -- the pseudo-devices are created directly by the kernel. With a single PCM audio device (like the built-in audio hardware), you should see something like this:

$ ls -l /dev/snd
crw-rw---- 1 root root 116,  0 Feb 29 09:59 controlC0
crw-rw---- 1 root root 116, 16 Feb 29 09:59 pcmC0D0p
crw-rw---- 1 root root 116, 17 Feb 29 09:59 pcmC0D1p
crw-rw---- 1 root root 116,  1 Nov  8 20:17 seq
crw-rw---- 1 root root 116, 33 Feb 29 09:59 timer

In a desktop system, however, the group ownership of these devices is conventionally changed by udev to audio. This is a sensible change, even in an embedded system -- you probably don't want the kind of processes that produce audio running as root. In the absence of udev, you'll need to make the ownership change in some script -- probably the same script that loads the kernel modules.

Then, all being well, any process that is a member of the group audio will be able to play audio.

Next: running X applications on an appliance; or go to the series index.