Most network audio players shipping today are embedded Linux systems. The marketing copy talks about bit-perfect paths and femtosecond clocks. The engineering reality is a kernel version, a device tree, an ALSA driver, a build system and an update mechanism. Get those five right and the product is maintainable for a decade. Get them wrong and the field returns start at the first firmware push.

What follows is a walk through the parts that consume the most schedule on a Linux audio program, drawn from work on Allwinner and Broadcom class hardware.

The quirk table is where DSD support actually lives

USB Audio Class 2 has no standard descriptor that says "this alternate setting carries raw DSD." A DAC that supports native DSD typically exposes an extra alternate setting on its streaming interface, declares it as some PCM format, and expects the host to know better. The Linux USB audio driver handles that with a hardcoded table.

The function is snd_usb_interface_dsd_format_quirks() in sound/usb/quirks.c, called from format.c while the driver parses each audio format. It switches on chip->usb_id and inspects fp->altsetting, then returns an ALSA format bit. The upstream code reads like this (Linux, GPL-2.0):

c case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */ case USB_ID(0x21ed, 0xd75a): /* Accuphase DAC-60 option card */ case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */ case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */ if (fp->altsetting == 2) return SNDRV_PCM_FMTBIT_DSD_U32_BE; break;

That is the entire mechanism. Two adjacent flags, fp->dsd_dop and fp->dsd_bitrev, cover devices that want DoP framing or reversed bit order inside each byte. The DSD_U32_BE and DSD_U16_BE formats were added to ALSA PCM by Jussi Laako in commit d42472ecffd7, which also corrected the XMOS reference design mapping from little-endian to big-endian.

On a recent program we were integrating a USB DAC from a high-end audio manufacturer into a Linux player. The DAC advertised native DSD on alternate setting 4. The stock kernel had no entry for it, so ALSA never exposed a DSD format. Every attempt to open the device at 2822400 Hz returned Invalid argument, and the playback stack silently fell back to converting DSD to PCM. The fix was four lines in the quirk table mapping that vendor and product ID at altsetting 4 to SNDRV_PCM_FMTBIT_DSD_U32_BE.

Verification is straightforward once the entry exists. cat /proc/asound/card1/stream0 should show DSD raw: DOP=0, bitrev=0 against the correct interface and altset. aplay -D hw:1,0 --dump-hw-params should list the DSD format. speaker-test -D hw:1,0 -r 2822400 -c 2 -t sine -l 1 should run instead of failing. DSD64, DSD128 and DSD256 present as 2822400, 5644800 and 11289600 Hz respectively.

For devices whose behavior fits an existing flag, the driver also accepts runtime configuration. The snd-usb-audio module takes a quirk_flags parameter in the form VID:PID:FLAGS, with roughly 33 defined flags covering clock selection, interface setup and rate handling. That avoids a kernel patch when one of the standard flags is sufficient. Native DSD format mapping is not one of them.

DoP is the fallback, and it costs bandwidth

The DoP open Standard version 1.1, published in March 2012, exists because Apple's audio path only carried PCM. DSD64 is packed into 24-bit PCM frames at 176.4 kHz. The upper eight bits alternate between 0x05 and 0xFA as a marker so the receiver can detect the stream, and the lower 16 bits carry DSD data. DSD128 moves to 352.8 kHz. If a DAC misreads the marker as audio, the result is a tone near 88 kHz at about -34 dB, which is audible to nothing and harmful to nothing.

DoP works. It also doubles the required USB bandwidth for a given DSD rate and requires a bit-perfect path from end to end, which means no volume control and no resampling anywhere in the chain. Native DSD through a dedicated alternate setting carries the same audio in a third of the frame budget. On products targeting DSD256 and above, that difference decides whether the link closes at all.

Rebuild only the driver during bring-up

Cross compiling a full arm64 kernel is the documented path. For an Allwinner H618 target such as the Orange Pi Zero 3, that means make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- orangepi_zero3_defconfig, then Image dtbs modules, then placing sun50i-h618-orangepi-zero3.dtb and the new Image.

During bring-up that loop is too slow. When the change is confined to one driver, building only that driver on the target is faster. Copy /boot/config-$(uname -r) into the tree, run make olddefconfig, then make modules_prepare followed by make M=sound/usb/. That produces snd-usb-audio.ko in five to ten minutes on the board itself. A full native kernel build on the same hardware runs two to four hours. Back up the existing module, drop the new one into /lib/modules/$(uname -r)/kernel/sound/usb/, run depmod -a, then rmmod snd_usb_audio && modprobe snd_usb_audio.

The module must be built against the exact kernel version and configuration the board is running. Reserve native builds for iteration. Production images should come out of a reproducible cross build inside the image recipe.

Pin the kernel before anything else

The kernel version is the longest-lived decision in the product. Vendor BSPs for Allwinner and similar SoCs commonly track 6.1, which kernel.org lists as longterm with a projected end of life in December 2027. Newer longterm branches, 6.12 and 6.18, run to December 2028. A product entering production in 2026 with an eight-year service expectation needs a plan for what happens after that date, and the plan is either a BSP forward port or a vendor contract.

Pin the branch, carry your patches as a versioned series applied on top, and keep every out-of-tree change in that series. A DSD quirk maintained as a patch file survives a kernel bump. The same change made by editing a checked-out tree does not.

Yocto, Buildroot and the update path

Buildroot produces a small, auditable image quickly, and for a single-variant product it is often the right answer. Its cost appears later. Releases are quarterly snapshots, CVE fixes are cherry-picked into a pinned branch by hand, and variants tend to become copied defconfigs.

Yocto costs more at the start and holds up better across a product line. Major releases land every six months, in April and October. An LTS is cut every two years and supported for four: 5.0 Scarthgap runs to April 2028, and 6.0 Wrynose, released May 2026, runs to April 2030. Layers let a common base carry per-SKU overlays instead of duplicated configuration. Partner SDKs distributed as BitBake layers integrate with no glue.

Whichever build system, the update mechanism should be A/B from the first prototype. RAUC models each updatable partition as a slot, hashes every image with SHA-256 inside a bundle, requires bundle signing, and delegates boot selection to U-Boot, GRUB or barebox with a confirmation step that falls back on a failed boot. SWUpdate offers the same double-copy strategy, where each copy holds the kernel and root filesystem, plus a single-copy variant with a rescue image of a few megabytes. Both track update state in bootloader environment variables so an interruption resolves to a known state.

Retrofitting A/B onto a product with a single rootfs partition after the enclosure is tooled is not a software task. It is a hardware respin.

What this adds up to

An audio product on Linux is a kernel decision, a driver decision, an ALSA configuration, a build system and a signed update path. The DSD quirk described above is four lines of code and roughly two days of instrumentation to find. The image and update decisions around it determine whether the next four lines are cheap or expensive.

Grinalds Solutions works on embedded Linux audio platforms from Cincinnati, Ohio, including platform and integration work on the PS Audio network audio streamer.

References