96a6b96b7e
- Project directory structure: docs/, src/, build/, recipes/, scripts/, wiki/ - README.md with project overview and status - .editorconfig for consistent code style - .gitignore for Python, C/C++, kernel, and build artifacts - docs/research/: system research report + base OS decision - docs/audio-stack-config.md: audio stack configuration notes - config/: JACK, ALSA, CPU performance configs - Preserved research from P1-R1 and P1-R2 tasks
980 lines
29 KiB
Markdown
980 lines
29 KiB
Markdown
# Audio Stack Configuration — Low-Latency USB Audio on RPi4B
|
||
|
||
**Date:** 2026-05-19
|
||
**Parent Task:** P1-R1 Research + P1-R2 Base OS Decision
|
||
**Target Platform:** Raspberry Pi 4B, RPi OS Lite 64-bit (Bookworm), PREEMPT_RT 6.12.y kernel
|
||
**Scope:** JACK2 + ALSA realtime audio stack for multi-channel USB audio interfaces
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Architecture Overview](#1-architecture-overview)
|
||
2. [Package Installation](#2-package-installation)
|
||
3. [Kernel Command-Line Tuning](#3-kernel-command-line-tuning)
|
||
4. [PAM Limits for Realtime Audio](#4-pam-limits-for-realtime-audio)
|
||
5. [CPU Frequency Governor](#5-cpu-frequency-governor)
|
||
6. [ALSA Buffer and Period Tuning](#6-alsa-buffer-and-period-tuning)
|
||
7. [JACK2 Realtime Configuration](#7-jack2-realtime-configuration)
|
||
8. [Multi-Channel Routing](#8-multi-channel-routing)
|
||
9. [IRQ Priority Tuning (rtirq)](#9-irq-priority-tuning-rtirq)
|
||
10. [USB Audio Quirks](#10-usb-audio-quirks)
|
||
11. [Latency Testing with jack_delay](#11-latency-testing-with-jack_delay)
|
||
12. [Full Session Startup Script](#12-full-session-startup-script)
|
||
13. [Verification Checklist](#13-verification-checklist)
|
||
|
||
---
|
||
|
||
## 1. Architecture Overview
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ User Space │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
|
||
│ │ Ardour / │ │ Carla │ │ Custom DSP (Python) │ │
|
||
│ │ DAW │ │ Rack │ │ │ │
|
||
│ └────┬──────┘ └────┬──────┘ └──────────┬───────────┘ │
|
||
│ │ │ │ │
|
||
│ └──────────────┼────────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────▼────────┐ │
|
||
│ │ JACK2 │ Realtime SCHED_FIFO │
|
||
│ │ jackd (RT) │ Priority 70-80 │
|
||
│ └───────┬────────┘ │
|
||
│ │ ALSA backend (hw:USB) │
|
||
├──────────────────────┼───────────────────────────────────┤
|
||
│ ┌───────▼────────┐ │
|
||
│ Kernel │ snd-usb-audio │ Threaded IRQ (xhci_hcd) │
|
||
│ Space │ (xhci_hcd) │ IRQ prio 95 (rtirq) │
|
||
│ └───────┬────────┘ │
|
||
│ │ │
|
||
│ ┌────────────▼────────────┐ │
|
||
│ │ VIA VL805 xHCI │ │
|
||
│ │ USB 3.0 Host Ctrl │ │
|
||
│ └────────────┬───────────┘ │
|
||
│ │ │
|
||
└──────────────────────┼────────────────────────────────────┘
|
||
│
|
||
┌────────▼────────┐
|
||
│ USB Audio I/F │
|
||
│ (class UAC2) │
|
||
└─────────────────┘
|
||
```
|
||
|
||
**Core design decisions:**
|
||
|
||
| Parameter | Value | Rationale |
|
||
|-----------|-------|-----------|
|
||
| Sample rate | 48000 Hz | Standard for pro audio; lower CPU than 96k |
|
||
| Buffer size | 128 samples | ~2.7ms per period; sweet spot for latency vs xruns |
|
||
| Periods/buffer | 3 | JACK default; 2 for aggressive, 3 for stability |
|
||
| JACK priority | 70 (server), 80 (clients) | Below IRQ handlers (95), above normal tasks |
|
||
| Audio CPU cores | 1-3 | Core 0 left for system/IRQ handling |
|
||
| JACK backend | ALSA (hw:) | Direct hardware access, no dmix/dsnoop overhead |
|
||
|
||
---
|
||
|
||
## 2. Package Installation
|
||
|
||
```bash
|
||
# Core audio stack
|
||
sudo apt update
|
||
sudo apt install -y \
|
||
jackd2 \
|
||
jackd2-firewire \
|
||
libjack-jackd2-0 \
|
||
libjack-jackd2-dev \
|
||
alsa-utils \
|
||
alsa-tools \
|
||
alsa-tools-gui \
|
||
rtirq-init \
|
||
rt-tests \
|
||
cpufrequtils \
|
||
usbutils \
|
||
a2jmidid
|
||
|
||
# Optional but recommended for development
|
||
sudo apt install -y \
|
||
carla \
|
||
python3-jack-client \
|
||
python3-pip
|
||
|
||
# jack_delay (compile from source for latency measurement)
|
||
sudo apt install -y build-essential libjack-jackd2-dev libasound2-dev
|
||
cd /tmp
|
||
git clone https://github.com/jackaudio/jack_delay.git
|
||
cd jack_delay
|
||
make
|
||
sudo make install # installs to /usr/local/bin/jack_delay
|
||
```
|
||
|
||
**Verify installations:**
|
||
|
||
```bash
|
||
jackd --version # jackd version 1.9.22+ expected
|
||
aplay -l # List playback devices
|
||
arecord -l # List capture devices
|
||
cat /proc/asound/cards # Kernel view of sound cards
|
||
cyclictest --version # rt-tests installed
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Kernel Command-Line Tuning
|
||
|
||
Edit `/boot/firmware/cmdline.txt` (RPi OS Bookworm) or `/boot/cmdline.txt` (older).
|
||
|
||
### Full recommended cmdline.txt
|
||
|
||
```
|
||
console=serial0,115200 console=tty1 root=PARTUUID=xxxxxxxx-02 rootfstype=ext4 fsck.repair=yes rootwait isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3 threadirqs irqaffinity=0 quiet
|
||
```
|
||
|
||
### Parameter reference
|
||
|
||
| Parameter | Value | Effect |
|
||
|-----------|-------|--------|
|
||
| `isolcpus=1-3` | CPUs 1-3 | Removes audio cores from scheduler load-balancing |
|
||
| `nohz_full=1-3` | CPUs 1-3 | Disables periodic timer tick on audio cores (adaptive-ticks) |
|
||
| `rcu_nocbs=1-3` | CPUs 1-3 | Offloads RCU callbacks from audio cores to CPU 0 |
|
||
| `threadirqs` | — | Forces all IRQ handlers to run as kernel threads (required for rtirq priority tuning) |
|
||
| `irqaffinity=0` | CPU 0 | Pins hardware IRQs to core 0 by default |
|
||
| `quiet` | — | Reduces console spam (optional) |
|
||
|
||
### Verification after reboot
|
||
|
||
```bash
|
||
# Check kernel cmdline
|
||
cat /proc/cmdline
|
||
|
||
# Verify CPU isolation
|
||
cat /sys/devices/system/cpu/isolated # should show 1-3
|
||
cat /sys/devices/system/cpu/nohz_full # should show 1-3
|
||
cat /sys/kernel/rcu_expedited # 1 if available
|
||
|
||
# Check threaded IRQs
|
||
ps -eo pid,comm,psr | grep 'irq/' # IRQ threads should run on CPU 0
|
||
```
|
||
|
||
---
|
||
|
||
## 4. PAM Limits for Realtime Audio
|
||
|
||
Create `/etc/security/limits.d/99-audio.conf`:
|
||
|
||
```ini
|
||
# Realtime audio group limits
|
||
# Apply to members of the 'audio' group
|
||
@audio - rtprio 99
|
||
@audio - memlock unlimited
|
||
@audio - nice -20
|
||
@audio - priority 99
|
||
```
|
||
|
||
### Apply and verify
|
||
|
||
```bash
|
||
# Add your user to the audio group
|
||
sudo usermod -a -G audio $USER
|
||
|
||
# Verify current session (requires logout/login)
|
||
ulimit -r # should show 99 (max realtime priority)
|
||
ulimit -l # should show unlimited (max locked memory)
|
||
|
||
# Check PAM limit configuration
|
||
sudo chmod 644 /etc/security/limits.d/99-audio.conf
|
||
```
|
||
|
||
### Real-time group membership check
|
||
|
||
```bash
|
||
groups | grep audio # must include 'audio'
|
||
sudo -u nobody jackd -d alsa -d hw:USB 2>&1 | head -1
|
||
# Should NOT fail with "cannot use real-time scheduling"
|
||
```
|
||
|
||
---
|
||
|
||
## 5. CPU Frequency Governor
|
||
|
||
Set `performance` governor to prevent clock scaling from adding latency jitter.
|
||
|
||
### One-time set
|
||
|
||
```bash
|
||
# Set all cores to performance
|
||
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
|
||
|
||
# Verify
|
||
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
|
||
# Each core should output: performance
|
||
```
|
||
|
||
### Persistent via systemd service
|
||
|
||
Create `/etc/systemd/system/cpu-performance.service`:
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=Set CPU governor to performance
|
||
After=multi-user.target
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
ExecStart=/bin/sh -c 'echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor'
|
||
RemainAfterExit=yes
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
```bash
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable cpu-performance.service
|
||
sudo systemctl start cpu-performance.service
|
||
sudo systemctl status cpu-performance.service
|
||
```
|
||
|
||
### Verify current frequency
|
||
|
||
```bash
|
||
cpufreq-info | grep -E 'governor|current CPU frequency'
|
||
# Should show "performance" and max frequency (typically 1.5 GHz for RPi4B)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. ALSA Buffer and Period Tuning
|
||
|
||
### 6.1 Identify USB audio device
|
||
|
||
```bash
|
||
# List ALSA devices
|
||
aplay -l
|
||
arecord -l
|
||
cat /proc/asound/cards
|
||
|
||
# Typical output for USB interface:
|
||
# card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
|
||
# The hw: designation is hw:1,0 or hw:USB,0
|
||
```
|
||
|
||
### 6.2 ALSA device configuration
|
||
|
||
Create `/etc/modprobe.d/alsa-usb.conf`:
|
||
|
||
```
|
||
# USB audio buffer tuning
|
||
# Larger preallocated buffer reduces xruns at cost of ~20MB RAM per device
|
||
options snd-usb-audio index=0 vid=0x0000 pid=0x0000 nrpacks=1
|
||
|
||
# nrpacks=1: disable implicit feedback sync buffering
|
||
# - Reduces USB transfer latency at cost of slightly higher CPU
|
||
# - Critical for round-trip latency < 10ms
|
||
# - Default is 8 (aggressive buffering for reliability)
|
||
#
|
||
# index=0: force USB device to be card 0
|
||
# vid/pid: filter specific device (optional; 0x0000 = match any)
|
||
```
|
||
|
||
### 6.3 ALSA buffer confirmation
|
||
|
||
```bash
|
||
# Show current ALSA buffer/period limits
|
||
cat /proc/asound/card1/pcm0p/sub0/hw_params # playback
|
||
cat /proc/asound/card1/pcm0c/sub0/hw_params # capture
|
||
# (card number depends on your interface)
|
||
|
||
# Query via alsacap
|
||
alsacap -d hw:USB
|
||
|
||
# JACK will request these values; ALSA exposes caps:
|
||
# The USB driver typically supports:
|
||
# - Period size: 16–4096 frames
|
||
# - Periods: 2–32
|
||
# - Sample rate: 8000–192000 Hz
|
||
```
|
||
|
||
### 6.4 JACK ALSA backend parameters
|
||
|
||
When launching JACK, the key ALSA parameters are:
|
||
|
||
| JACK flag | Meaning | Recommended |
|
||
|-----------|---------|-------------|
|
||
| `-d alsa` | Use ALSA backend | Required for USB audio |
|
||
| `-d hw:USB` | Device name | Match your interface |
|
||
| `-r 48000` | Sample rate | 48000 (or 44100/96000) |
|
||
| `-p 128` | Frames per period | 128 (~2.67ms at 48k) |
|
||
| `-n 3` | Periods per buffer | 3 (total buffer = 384 frames / 8ms) |
|
||
| `-S` | 16-bit samples | Use only if 32-bit causes issues |
|
||
| `-M` | MIDI sequencer | Enable if using MIDI |
|
||
| `-X seq` | MIDI driver | ALSA sequencer |
|
||
|
||
**Latency math (48 kHz):**
|
||
- Period latency: `128 / 48000 = 2.67 ms`
|
||
- Total buffer: `3 × 2.67 = 8.0 ms`
|
||
- Target round-trip: `8.0 + USB overhead + DSP ≈ 10-12 ms`
|
||
|
||
**Aggressive profile** (for direct monitoring rigs):
|
||
```
|
||
-p 64 -n 2 # 1.33ms period, 2.67ms buffer — risk of xruns
|
||
```
|
||
|
||
**Safe profile** (for recording with plugins):
|
||
```
|
||
-p 256 -n 3 # 5.33ms period, 16ms buffer — stable for DSP
|
||
```
|
||
|
||
---
|
||
|
||
## 7. JACK2 Realtime Configuration
|
||
|
||
### 7.1 System-wide JACK config
|
||
|
||
Create `/etc/jackdrc` (system default):
|
||
|
||
```
|
||
# /etc/jackdrc — JACK2 system-wide defaults
|
||
# Paired with PAM limits and RT kernel tuning
|
||
|
||
# ALSA backend, USB audio interface (adjust hw:USB to your device)
|
||
-d alsa -d hw:USB -r 48000 -p 128 -n 3 -M -X seq
|
||
```
|
||
|
||
### 7.2 JACK DBus service (auto-start with systemd)
|
||
|
||
Create `/etc/systemd/system/jackd.service`:
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=JACK2 Audio Server
|
||
After=sound.target network.target
|
||
Wants=sound.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=pi
|
||
Group=audio
|
||
Environment=JACK_NO_AUDIO_RESERVATION=1
|
||
Environment=JACK_PROMISCUOUS_SERVER=1
|
||
|
||
# Real-time scheduling
|
||
LimitRTPRIO=99
|
||
LimitMEMLOCK=infinity
|
||
LimitNICE=-20
|
||
|
||
# CPU affinity — pin JACK server to audio cores 1-3
|
||
CPUAffinity=1-3
|
||
|
||
# I/O scheduling — realtime class
|
||
IOSchedulingClass=realtime
|
||
IOSchedulingPriority=0
|
||
|
||
# JACK daemon command
|
||
ExecStart=/usr/bin/jackd \
|
||
-P 70 \
|
||
-t 2000 \
|
||
-d alsa \
|
||
-d hw:USB \
|
||
-r 48000 \
|
||
-p 128 \
|
||
-n 3 \
|
||
-M \
|
||
-X seq \
|
||
-S
|
||
|
||
# Restart on crash (but not on clean shutdown)
|
||
Restart=on-failure
|
||
RestartSec=2
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
**JACK flags explained:**
|
||
|
||
| Flag | Value | Purpose |
|
||
|------|-------|---------|
|
||
| `-P 70` | Priority 70 | SCHED_FIFO prio for JACK server thread |
|
||
| `-t 2000` | Timeout 2s | Client timeout (ms) — clients killed if unresponsive |
|
||
| `-d alsa` | Backend | ALSA direct hardware driver |
|
||
| `-S` | 16-bit | Force 16-bit samples (lower USB bandwidth, OK for mixer) |
|
||
|
||
### 7.3 Per-user JACK config
|
||
|
||
Create `~/.jackdrc` (overrides system-wide):
|
||
|
||
```
|
||
# ~/.jackdrc — user-specific JACK config
|
||
# This file is read by qjackctl and other JACK frontends
|
||
# Same format as /etc/jackdrc
|
||
/usr/bin/jackd -P 70 -t 2000 -d alsa -d hw:USB -r 48000 -p 128 -n 3 -M -X seq
|
||
```
|
||
|
||
### 7.4 Real-time scheduling notes
|
||
|
||
- JACK server threads run at `SCHED_FIFO` priority 70 (set by `-P 70`)
|
||
- JACK client callbacks inherit priority 80 (server + 10)
|
||
- xHCI USB IRQ handler runs at priority 95 (set by rtirq, see §9)
|
||
- Kernel IRQ handlers > JACK server > JACK clients > normal processes
|
||
|
||
**Priority hierarchy:**
|
||
```
|
||
95: xHCI USB IRQ (hardware interrupt thread)
|
||
90: System timer (hrtimer)
|
||
80: JACK client process callbacks
|
||
75: JACK watchdog
|
||
70: JACK server (ALSA backend, engine)
|
||
50: Other audio IRQs (I2S, GPIO audio)
|
||
0: All other processes (CFS scheduler)
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Multi-Channel Routing
|
||
|
||
### 8.1 JACK port naming convention
|
||
|
||
For a typical 8-in/8-out USB interface:
|
||
|
||
```
|
||
system:capture_1 → Input channel 1 (left)
|
||
system:capture_2 → Input channel 2 (right)
|
||
system:capture_3 → Input channel 3
|
||
...
|
||
system:capture_8 → Input channel 8
|
||
|
||
system:playback_1 → Output channel 1 (left main)
|
||
system:playback_2 → Output channel 2 (right main)
|
||
...
|
||
system:playback_8 → Output channel 8
|
||
```
|
||
|
||
### 8.2 Automatic routing script
|
||
|
||
Create `/usr/local/bin/jack-route-default`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# Auto-connect JACK system captures to playback (through-patch)
|
||
# Connect each input to its corresponding output for direct monitoring
|
||
# Run after JACK is started
|
||
|
||
jack_wait -w
|
||
|
||
# Get available ports
|
||
captures=$(jack_lsp -c system | grep capture)
|
||
playbacks=$(jack_lsp -c system | grep playback)
|
||
|
||
# Route capture → playback in pairs
|
||
# Capture 1 → Playback 1, Capture 2 → Playback 2, etc.
|
||
for i in $(seq 1 8); do
|
||
jack_connect "system:capture_${i}" "system:playback_${i}" 2>/dev/null
|
||
done
|
||
|
||
# Optional: route MIDI through a2jmidid
|
||
a2j_control start 2>/dev/null
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/bin/jack-route-default
|
||
```
|
||
|
||
### 8.3 Complex routing with jack-plumbing
|
||
|
||
Create `~/.jack-plumbing`:
|
||
|
||
```
|
||
# jack-plumbing rules for persistent connections
|
||
# Format: (connect "source" "destination")
|
||
# Applied on JACK start and maintained across disconnects
|
||
|
||
(connect "system:capture_1" "system:playback_1")
|
||
(connect "system:capture_2" "system:playback_2")
|
||
(connect "system:capture_3" "system:playback_3")
|
||
(connect "system:capture_4" "system:playback_4")
|
||
```
|
||
|
||
```bash
|
||
# Start plumbing daemon after JACK is running
|
||
jack-plumbing &
|
||
```
|
||
|
||
### 8.4 Patch persistence with jack_snapshot
|
||
|
||
```bash
|
||
# Save current connections
|
||
jack_snapshot -s > ~/jack-patch-session.xml
|
||
|
||
# Restore connections
|
||
jack_snapshot -r ~/jack-patch-session.xml
|
||
```
|
||
|
||
### 8.5 Carla patchbay (graphical)
|
||
|
||
For complex setups, Carla's patchbay can save/restore entire connection graphs:
|
||
|
||
```bash
|
||
carla &
|
||
# File → Save Project → mixer-session.carxp
|
||
# File → Load Project → mixer-session.carxp
|
||
```
|
||
|
||
---
|
||
|
||
## 9. IRQ Priority Tuning (rtirq)
|
||
|
||
### 9.1 RTIRQ configuration
|
||
|
||
Edit `/etc/default/rtirq`:
|
||
|
||
```bash
|
||
# /etc/default/rtirq — realtime IRQ thread priority tuning
|
||
# RTIRQ_NAME_LIST: IRQ threads to boost (space-separated regex)
|
||
RTIRQ_NAME_LIST="xhci_hcd snd usb"
|
||
|
||
# RTIRQ_PRIO_HIGH: highest priority for listed threads
|
||
RTIRQ_PRIO_HIGH=95
|
||
|
||
# RTIRQ_PRIO_DECR: decrement per match (so xhci=95, snd=94, usb=93)
|
||
RTIRQ_PRIO_DECR=1
|
||
|
||
# RTIRQ_PRIO_LOW: lowest priority for unlisted threads
|
||
RTIRQ_PRIO_LOW=50
|
||
|
||
# RTIRQ_RESET_IRQ: reset IRQ thread priorities before applying
|
||
RTIRQ_RESET_IRQ=0
|
||
```
|
||
|
||
### 9.2 Enable and start rtirq
|
||
|
||
```bash
|
||
sudo systemctl enable rtirq
|
||
sudo systemctl start rtirq
|
||
sudo systemctl status rtirq
|
||
```
|
||
|
||
### 9.3 Verify IRQ priorities
|
||
|
||
```bash
|
||
# Show all IRQ threads with priorities
|
||
ps -eo pid,comm,rtprio,psr | grep 'irq/' | sort -k3 -rn
|
||
|
||
# Expected output:
|
||
# PID COMMAND RTPRIO PSR
|
||
# 123 irq/51-xhci_hcd 95 0
|
||
# 124 irq/52-snd_usb 94 0
|
||
# 125 irq/53-usb3 93 0
|
||
|
||
# Also check with:
|
||
rtirq status
|
||
```
|
||
|
||
---
|
||
|
||
## 10. USB Audio Quirks
|
||
|
||
### 10.1 Common quirks
|
||
|
||
| Interface | Issue | Quirk Parameter |
|
||
|-----------|-------|-----------------|
|
||
| Focusrite Scarlett 18i20 (3rd Gen) | Channel routing requires ALSA mixer | `device_setup=1` |
|
||
| Behringer UMC1820 | Clock source selection | `implicit_fb=1` |
|
||
| Behringer UMC404HD | Sometimes needs sync mode set | `implicit_fb=1` |
|
||
| RME Babyface Pro FS | Class-compliant mode | No quirks needed |
|
||
| Zoom UAC-8 | USB 3.0 bandwith negotiation | `nrpacks=1` |
|
||
| M-Audio M-Track 8X4 | Sample rate switching | `autoclock=0` |
|
||
|
||
### 10.2 Quirks via modprobe
|
||
|
||
Create `/etc/modprobe.d/usb-audio-quirks.conf`:
|
||
|
||
```
|
||
# Apply USB audio device quirks
|
||
# General: disable implicit feedback for lower latency
|
||
options snd-usb-audio nrpacks=1
|
||
|
||
# Focusrite Scarlett (vid=1235, pid=8210 for 18i20 3rd Gen)
|
||
# options snd-usb-audio vid=0x1235 pid=0x8210 device_setup=1
|
||
|
||
# Behringer UMC interfaces
|
||
# options snd-usb-audio vid=0x1397 pid=0x0508 implicit_fb=1
|
||
|
||
# Disable ALSA sequencer for pure audio use (saves CPU)
|
||
# Not needed for most USB audio interfaces
|
||
```
|
||
|
||
### 10.3 Dynamic quirk application
|
||
|
||
```bash
|
||
# Reload snd-usb-audio with new parameters
|
||
sudo modprobe -r snd-usb-audio
|
||
sudo modprobe snd-usb-audio nrpacks=1
|
||
|
||
# Or via sysfs (if supported by kernel)
|
||
echo 1 > /sys/module/snd_usb_audio/parameters/nrpacks
|
||
```
|
||
|
||
### 10.4 Identify USB audio VID/PID
|
||
|
||
```bash
|
||
lsusb | grep -i audio
|
||
# Example: Bus 001 Device 005: ID 1397:0508 BEHRINGER International GmbH UMC1820
|
||
|
||
# The vid and pid are the hex values:
|
||
# vid=0x1397 pid=0x0508
|
||
```
|
||
|
||
### 10.5 ALSA USB mixer controls
|
||
|
||
```bash
|
||
# List all mixer controls for the USB interface
|
||
amixer -c USB controls
|
||
|
||
# Common useful controls:
|
||
# - "Clock Source" — Internal vs SPDIF sync
|
||
# - "Sample Rate" — 44100/48000/96000
|
||
# - "Buffer Size" — if exposed by driver
|
||
|
||
# Get current value
|
||
amixer -c USB get "Clock Source"
|
||
|
||
# Set clock source to internal
|
||
amixer -c USB set "Clock Source" Internal
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Latency Testing with jack_delay
|
||
|
||
### 11.1 Hardware loopback setup
|
||
|
||
```
|
||
┌──────────────────────┐ 3.5mm TRS cable ┌──────────────────────┐
|
||
│ USB Audio Interface │ output ────► input │ USB Audio Interface │
|
||
│ Output 1 (L) ├──────────────────────────►│ Input 1 (L) │
|
||
│ │ │ │
|
||
│ │ (physical loopback) │ │
|
||
└──────────────────────┘ └──────────────────────┘
|
||
```
|
||
|
||
Connect an output directly to an input using a short TRS/TS patch cable.
|
||
For balanced interfaces use TRS; for unbalanced use TS.
|
||
**Keep the cable as short as possible** (< 30cm) so cable delay doesn't skew measurement.
|
||
|
||
### 11.2 Run jack_delay
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# jack_delay latency measurement script
|
||
# Requires JACK running with appropriate buffer settings
|
||
|
||
# 1. Start JACK (if not running)
|
||
if ! jack_control status | grep -q "started"; then
|
||
echo "Starting JACK..."
|
||
jackd -P70 -t2000 -d alsa -d hw:USB -r48000 -p128 -n3 &
|
||
sleep 2
|
||
fi
|
||
|
||
# 2. Create physical loopback: connect output 1 → input 1 with patch cable
|
||
|
||
# 3. Run jack_delay measurement
|
||
# jack_delay uses an MLS (Maximum Length Sequence) to measure
|
||
# the round-trip delay including:
|
||
# - JACK output buffer
|
||
# - USB output transfer
|
||
# - DAC conversion
|
||
# - Cable propagation
|
||
# - ADC conversion
|
||
# - USB input transfer
|
||
# - JACK input buffer
|
||
|
||
jack_delay -O system:playback_1 -I system:capture_1 -c 128
|
||
|
||
# Sample output:
|
||
# 459.853 frames 9.580 ms
|
||
#
|
||
# The reported delay is the total round-trip in frames and milliseconds.
|
||
# Subtract cable + converter latency (~0.5-1.0ms for typical interfaces)
|
||
# to get the pure software+USB stack latency.
|
||
```
|
||
|
||
### 11.3 Latency test script
|
||
|
||
Create `/usr/local/bin/jack-latency-test`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# jack-latency-test — measure USB audio round-trip latency at various buffer sizes
|
||
# Usage: jack-latency-test [device] [sample_rate]
|
||
|
||
DEVICE="${1:-hw:USB}"
|
||
RATE="${2:-48000}"
|
||
OUTPUT="system:playback_1"
|
||
INPUT="system:capture_1"
|
||
RESULT_FILE="/tmp/jack-latency-results.txt"
|
||
|
||
echo "=== JACK Latency Test ==="
|
||
echo "Device: $DEVICE"
|
||
echo "Sample rate: $RATE"
|
||
echo "Output port: $OUTPUT"
|
||
echo "Input port: $INPUT"
|
||
echo "Ensure physical loopback cable is connected: Output 1 → Input 1"
|
||
echo "=========================================="
|
||
echo ""
|
||
|
||
# Test various buffer sizes
|
||
for PERIOD in 32 64 128 256 512 1024; do
|
||
echo -n "Period $PERIOD frames: "
|
||
|
||
# Kill any running JACK
|
||
killall -9 jackd 2>/dev/null
|
||
sleep 1
|
||
|
||
# Start JACK with this period
|
||
jackd -P70 -t2000 -d alsa -d "$DEVICE" -r "$RATE" -p "$PERIOD" -n 3 -S \
|
||
> /tmp/jackd.log 2>&1 &
|
||
|
||
# Wait for JACK to be ready
|
||
timeout=10
|
||
while [ $timeout -gt 0 ]; do
|
||
if jack_control status 2>/dev/null | grep -q "started"; then
|
||
break
|
||
fi
|
||
sleep 1
|
||
timeout=$((timeout - 1))
|
||
done
|
||
|
||
if [ $timeout -eq 0 ]; then
|
||
echo "JACK failed to start"
|
||
continue
|
||
fi
|
||
|
||
sleep 1
|
||
|
||
# Run jack_delay measurement
|
||
RESULT=$(jack_delay -O "$OUTPUT" -I "$INPUT" -c 128 2>/dev/null | tail -1)
|
||
if [ -n "$RESULT" ]; then
|
||
echo "$RESULT"
|
||
echo "$PERIOD frames: $RESULT" >> "$RESULT_FILE"
|
||
else
|
||
echo "measurement failed (check loopback cable)"
|
||
fi
|
||
|
||
killall -9 jackd jack_delay 2>/dev/null
|
||
sleep 1
|
||
done
|
||
|
||
echo ""
|
||
echo "Results saved to: $RESULT_FILE"
|
||
cat "$RESULT_FILE"
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/bin/jack-latency-test
|
||
```
|
||
|
||
### 11.4 Expected latency targets
|
||
|
||
| Buffer Size (frames) | Period Latency (48kHz) | Target Round-Trip | Use Case |
|
||
|----------------------|----------------------|--------------------|----------|
|
||
| 32 | 0.67 ms | 3-5 ms | Direct monitoring (very aggressive — may xrun) |
|
||
| 64 | 1.33 ms | 5-8 ms | Live performance, real-time effects |
|
||
| **128** | **2.67 ms** | **8-12 ms** | **Pro audio mixer (target)** |
|
||
| 256 | 5.33 ms | 15-20 ms | Recording, DAW playback |
|
||
| 512 | 10.67 ms | 25-35 ms | Overdubs, non-critical monitoring |
|
||
|
||
If round-trip exceeds 15ms at 128 frames, investigate:
|
||
1. USB cable quality / length (< 2m)
|
||
2. Power delivery to interface (use powered hub)
|
||
3. Other USB devices on same bus
|
||
4. CPU governor not set to performance
|
||
5. IRQ priorities not applied
|
||
|
||
---
|
||
|
||
## 12. Full Session Startup Script
|
||
|
||
Create `/usr/local/bin/audio-stack-start`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# audio-stack-start — initialize the full low-latency audio environment
|
||
# Run as root (sudo) for system-level tuning, then launches JACK as user
|
||
|
||
set -e
|
||
|
||
echo "=== Audio Stack Initialization ==="
|
||
|
||
# 1. Set CPU governor to performance
|
||
echo "1/6 Setting CPU governor → performance..."
|
||
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
|
||
echo performance > "$cpu" 2>/dev/null || true
|
||
done
|
||
|
||
# 2. Enable threaded IRQs and apply IRQ priorities
|
||
echo "2/6 Configuring IRQ priorities..."
|
||
if systemctl is-active --quiet rtirq; then
|
||
systemctl restart rtirq
|
||
else
|
||
systemctl start rtirq
|
||
fi
|
||
|
||
# 3. Verify PAM limits
|
||
echo "3/6 Checking PAM limits..."
|
||
CURRENT_RTPRIO=$(ulimit -r)
|
||
CURRENT_MEMLOCK=$(ulimit -l)
|
||
echo " rtprio: $CURRENT_RTPRIO (need 99)"
|
||
echo " memlock: $CURRENT_MEMLOCK (need unlimited)"
|
||
|
||
# 4. Load USB audio module with low-latency params
|
||
echo "4/6 Configuring USB audio driver..."
|
||
if lsmod | grep -q snd_usb_audio; then
|
||
echo " snd-usb-audio already loaded"
|
||
else
|
||
modprobe snd-usb-audio nrpacks=1
|
||
echo " snd-usb-audio loaded with nrpacks=1"
|
||
fi
|
||
|
||
# 5. Verify USB audio device present
|
||
echo "5/6 Checking USB audio device..."
|
||
if aplay -l | grep -qi usb; then
|
||
echo " USB audio interface detected"
|
||
else
|
||
echo " WARNING: No USB audio interface found"
|
||
fi
|
||
|
||
# 6. Start JACK
|
||
echo "6/6 Starting JACK2..."
|
||
if pgrep -x jackd > /dev/null; then
|
||
echo " JACK already running"
|
||
else
|
||
# Drop to pi user for JACK if running as root
|
||
if [ "$EUID" -eq 0 ] && id pi >/dev/null 2>&1; then
|
||
sudo -u pi jackd -P70 -t2000 -d alsa -d hw:USB -r48000 -p128 -n3 -S &
|
||
else
|
||
jackd -P70 -t2000 -d alsa -d hw:USB -r48000 -p128 -n3 -S &
|
||
fi
|
||
sleep 2
|
||
echo " JACK started"
|
||
fi
|
||
|
||
echo ""
|
||
echo "=== Audio Stack Ready ==="
|
||
echo "Check: jack_lsp -c system"
|
||
echo "Check: jack_delay -O system:playback_1 -I system:capture_1"
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/bin/audio-stack-start
|
||
```
|
||
|
||
### Stop script
|
||
|
||
Create `/usr/local/bin/audio-stack-stop`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# Gracefully stop the audio stack
|
||
killall -15 jackd 2>/dev/null || true
|
||
sleep 1
|
||
killall -9 jackd 2>/dev/null || true
|
||
echo "Audio stack stopped"
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/bin/audio-stack-stop
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Verification Checklist
|
||
|
||
Run through this checklist after deploying the configuration on the target Raspberry Pi.
|
||
|
||
### Prerequisites
|
||
|
||
- [ ] RPi OS Lite 64-bit (Bookworm) installed
|
||
- [ ] PREEMPT_RT kernel running (`uname -v | grep PREEMPT_RT`)
|
||
- [ ] USB audio interface connected and powered
|
||
- [ ] User is member of `audio` group
|
||
- [ ] Loopback cable available (output → input)
|
||
|
||
### Kernel
|
||
|
||
- [ ] `cat /proc/cmdline` shows: `isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3 threadirqs`
|
||
- [ ] `cat /sys/devices/system/cpu/isolated` shows `1-3`
|
||
- [ ] `cat /sys/kernel/realtime` returns `1`
|
||
|
||
### CPU
|
||
|
||
- [ ] `cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor` all show `performance`
|
||
- [ ] `cpufreq-info | grep "current CPU frequency"` shows max speed (~1.5 GHz)
|
||
|
||
### Limits
|
||
|
||
- [ ] `ulimit -r` returns `99`
|
||
- [ ] `ulimit -l` returns `unlimited`
|
||
|
||
### USB Audio
|
||
|
||
- [ ] `aplay -l` lists the USB interface
|
||
- [ ] `arecord -l` lists the USB interface
|
||
- [ ] `cat /proc/asound/cards` shows the USB card
|
||
|
||
### IRQ
|
||
|
||
- [ ] `ps -eo pid,comm,rtprio | grep "irq/.*xhci"` shows priority ≥ 95
|
||
- [ ] `rtirq status` shows correct priorities
|
||
|
||
### JACK
|
||
|
||
- [ ] `jackd -P70 -t2000 -d alsa -d hw:USB -r48000 -p128 -n3 &` starts without errors
|
||
- [ ] `jack_lsp` shows `system:capture_*` and `system:playback_*` ports
|
||
- [ ] `jack_connect system:capture_1 system:playback_1` succeeds
|
||
|
||
### Latency
|
||
|
||
- [ ] Loopback cable connected: output 1 → input 1
|
||
- [ ] `jack_delay -O system:playback_1 -I system:capture_1` returns result
|
||
- [ ] Round-trip latency at 128 frames ≤ 12ms
|
||
- [ ] No xruns during 60-second latency measurement
|
||
|
||
### Stress test
|
||
|
||
```bash
|
||
# Run latency test while loading CPU
|
||
stress-ng --cpu 4 --timeout 60s &
|
||
jack_delay -O system:playback_1 -I system:capture_1 -s 60
|
||
# Round-trip should stay within ±2ms of idle measurement
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration Files Reference
|
||
|
||
| File | Location | Purpose |
|
||
|------|----------|---------|
|
||
| [cmdline.txt](./config/cmdline.txt) | `/boot/firmware/cmdline.txt` | Kernel boot parameters |
|
||
| [99-audio.conf](./config/99-audio.conf) | `/etc/security/limits.d/99-audio.conf` | PAM realtime limits |
|
||
| [cpu-performance.service](./config/cpu-performance.service) | `/etc/systemd/system/` | CPU governor systemd unit |
|
||
| [jackd.service](./config/jackd.service) | `/etc/systemd/system/` | JACK2 systemd service |
|
||
| [jackdrc](./config/jackdrc) | `/etc/jackdrc` | JACK server defaults |
|
||
| [alsa-usb.conf](./config/alsa-usb.conf) | `/etc/modprobe.d/` | ALSA USB buffer tuning |
|
||
| [rtirq](./config/rtirq) | `/etc/default/rtirq` | IRQ priority config |
|
||
| [jack-route-default](./scripts/jack-route-default) | `/usr/local/bin/` | Auto-routing script |
|
||
| [jack-latency-test](./scripts/jack-latency-test) | `/usr/local/bin/` | Latency measurement script |
|
||
| [audio-stack-start](./scripts/audio-stack-start) | `/usr/local/bin/` | Full stack startup |
|
||
| [audio-stack-stop](./scripts/audio-stack-stop) | `/usr/local/bin/` | Full stack shutdown |
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- [JACK Audio Connection Kit — Linux RT Configuration](https://jackaudio.org/faq/linux_rt_config.html)
|
||
- [ALSA USB Audio — Kernel Documentation](https://www.kernel.org/doc/html/latest/sound/alsa-configuration.html)
|
||
- [Linux RT Kernel — Red Hat Documentation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/)
|
||
- [Raspberry Pi Audio — PiSound Documentation](https://blokas.io/pisound/docs/)
|
||
- Project Research Report: `docs/research/research-report.md`
|
||
- Base OS Decision: `docs/research/base-os-decision.md`
|