Rolling your own minimal embedded Linux for the Raspberry Pi -- part four: audio
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.