- 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
29 KiB
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
- Architecture Overview
- Package Installation
- Kernel Command-Line Tuning
- PAM Limits for Realtime Audio
- CPU Frequency Governor
- ALSA Buffer and Period Tuning
- JACK2 Realtime Configuration
- Multi-Channel Routing
- IRQ Priority Tuning (rtirq)
- USB Audio Quirks
- Latency Testing with jack_delay
- Full Session Startup Script
- 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
# 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:
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
# 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:
# 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
# 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
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
# 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:
[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
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
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
# 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
# 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:
[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_FIFOpriority 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:
#!/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
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")
# Start plumbing daemon after JACK is running
jack-plumbing &
8.4 Patch persistence with jack_snapshot
# 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:
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:
# /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
sudo systemctl enable rtirq
sudo systemctl start rtirq
sudo systemctl status rtirq
9.3 Verify IRQ priorities
# 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
# 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
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
# 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
#!/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:
#!/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"
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:
- USB cable quality / length (< 2m)
- Power delivery to interface (use powered hub)
- Other USB devices on same bus
- CPU governor not set to performance
- IRQ priorities not applied
12. Full Session Startup Script
Create /usr/local/bin/audio-stack-start:
#!/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"
sudo chmod +x /usr/local/bin/audio-stack-start
Stop script
Create /usr/local/bin/audio-stack-stop:
#!/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"
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
audiogroup - Loopback cable available (output → input)
Kernel
cat /proc/cmdlineshows:isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3 threadirqscat /sys/devices/system/cpu/isolatedshows1-3cat /sys/kernel/realtimereturns1
CPU
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governorall showperformancecpufreq-info | grep "current CPU frequency"shows max speed (~1.5 GHz)
Limits
ulimit -rreturns99ulimit -lreturnsunlimited
USB Audio
aplay -llists the USB interfacearecord -llists the USB interfacecat /proc/asound/cardsshows the USB card
IRQ
ps -eo pid,comm,rtprio | grep "irq/.*xhci"shows priority ≥ 95rtirq statusshows correct priorities
JACK
jackd -P70 -t2000 -d alsa -d hw:USB -r48000 -p128 -n3 &starts without errorsjack_lspshowssystem:capture_*andsystem:playback_*portsjack_connect system:capture_1 system:playback_1succeeds
Latency
- Loopback cable connected: output 1 → input 1
jack_delay -O system:playback_1 -I system:capture_1returns result- Round-trip latency at 128 frames ≤ 12ms
- No xruns during 60-second latency measurement
Stress test
# 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 | /boot/firmware/cmdline.txt |
Kernel boot parameters |
| 99-audio.conf | /etc/security/limits.d/99-audio.conf |
PAM realtime limits |
| cpu-performance.service | /etc/systemd/system/ |
CPU governor systemd unit |
| jackd.service | /etc/systemd/system/ |
JACK2 systemd service |
| jackdrc | /etc/jackdrc |
JACK server defaults |
| alsa-usb.conf | /etc/modprobe.d/ |
ALSA USB buffer tuning |
| rtirq | /etc/default/rtirq |
IRQ priority config |
| jack-route-default | /usr/local/bin/ |
Auto-routing script |
| jack-latency-test | /usr/local/bin/ |
Latency measurement script |
| audio-stack-start | /usr/local/bin/ |
Full stack startup |
| audio-stack-stop | /usr/local/bin/ |
Full stack shutdown |
References
- JACK Audio Connection Kit — Linux RT Configuration
- ALSA USB Audio — Kernel Documentation
- Linux RT Kernel — Red Hat Documentation
- Raspberry Pi Audio — PiSound Documentation
- Project Research Report:
docs/research/research-report.md - Base OS Decision:
docs/research/base-os-decision.md