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

29 KiB
Raw Permalink Blame History

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
  2. Package Installation
  3. Kernel Command-Line Tuning
  4. PAM Limits for Realtime Audio
  5. CPU Frequency Governor
  6. ALSA Buffer and Period Tuning
  7. JACK2 Realtime Configuration
  8. Multi-Channel Routing
  9. IRQ Priority Tuning (rtirq)
  10. USB Audio Quirks
  11. Latency Testing with jack_delay
  12. Full Session Startup Script
  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

# 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).

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: 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:

[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:

#!/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:

  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:

#!/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 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

# 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