Files
shawn 96a6b96b7e P1-R3: Initial project infrastructure
- 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
2026-05-19 19:06:12 -04:00

980 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 164096 frames
# - Periods: 232
# - Sample rate: 8000192000 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`