Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/gueritta/primemixxx/llms.txt

Use this file to discover all available pages before exploring further.

Running real-time audio on a consumer-grade ARM SoC is an exercise in controlled resource contention. The Rockchip RK3288’s four Cortex-A17 cores are shared between MIXXX’s audio engine, the Mali-T76x GPU rendering the skin, the ILI2117 touchscreen driver, Qt’s thread pool, GLib’s mainloop, ALSA DMA, and a dozen other subsystems. A PREEMPT_RT kernel alone is not enough to prevent any of these from briefly pre-empting an audio callback — explicit CPU shielding is required to guarantee stable latency.

Why CPU Shielding Is Necessary

The Prime Go runs kernel 6.1.111-inmusic-2024-09-19-rt41 with PREEMPT_RT, which makes the kernel fully preemptible so that high-priority threads are never blocked inside a kernel critical section. However, PREEMPT_RT only controls when threads can be interrupted — it does not prevent unrelated threads from competing for the same CPU cores. Without shielding:
  • Mali GPU rendering threads fire on all four cores simultaneously
  • Qt’s evdev touchscreen thread wakes on every finger movement
  • GLib’s gmain/gdbus threads poll at unpredictable intervals
  • The Qt thread pool spawns work items on whichever core is available
Any of these can steal cycles from the audio engine at the worst possible moment — mid-callback, when the period buffer must be refilled within 23 ms or an xrun occurs.

Shielding Strategy

The launcher implements a two-phase approach:
1

Launch on cores 2-3

MIXXX is started with taskset -c 2,3, so the initial process and all threads it spawns during startup land on the audio-dedicated cores by default.
2

Post-launch thread classification loop

A 12-iteration loop (running for ~12 seconds after launch) inspects every thread in MIXXX’s task group by name. Audio-critical threads are boosted to SCHED_FIFO 98 and pinned to cores 2–3. All other threads are demoted to SCHED_OTHER and banished to cores 0–1.
3

Main thread assignment

After the loop completes, the main MIXXX process is set to SCHED_OTHER 0 (normal scheduling) and pinned to cores 0–1. This ensures that any new threads spawned after the classification loop (such as Qt thread pool workers) inherit non-audio affinity by default, rather than landing on the audio-dedicated cores.

Thread Classification

Audio-Critical Threads (SCHED_FIFO 98, cores 2–3)

These are the only two threads that must never miss their deadline:
Thread NameRole
EngineWorkerSchAudio engine worker scheduler — drives the mix callback
EngineSideChainSidechain bus for headphone cue and effects

Non-Audio Threads (SCHED_OTHER, cores 0–1)

Everything else is banished. The full banish list used by the launcher:
PatternSubsystem
mali-*Mali GPU render threads
QEvdevTouchTouchscreen input handler
CachingReaderMIXXX audio file read-ahead cache
StatsManagerInternal profiling/metrics
QDBusD-Bus bridge for system integration
VinylControlTimecode vinyl input processing
LibraryScannerBackground music library scan
BrowseThreadFile browser backend
AnalyzerThreadBPM/key analysis worker
Controller$MIDI controller mapping engine
VSyncQt vsync notifier
gmain / gdbusGLib main loop and D-Bus threads
Thread (pooled)Qt generic thread pool workers
QQuickPixmapReaQt Quick image loader
QQmlThreadQML JavaScript engine thread
libusb_eventlibusb event-handling thread (XMOS controller)

The Launcher Post-Launch Loop

This is the exact working code from mixxx_launcher.sh:
BANISH_PATTERNS="mali-|CachingReader|QEvdevTouch|StatsManager|QDBus|VinylControl|LibraryScanner|BrowseThread|AnalyzerThread|Controller$|VSync|gmain|gdbus|Thread \(pooled\)|QQuickPixmapRea|QQmlThread|libusb_event"

for i in $(seq 1 12); do
    sleep 1
    for tid in $(ls /proc/$MIXPID/task/ 2>/dev/null); do
        name=$(cat /proc/$MIXPID/task/$tid/comm 2>/dev/null)
        case "$name" in
            EngineWorkerSch|EngineSideChain)
                chrt -f -p 98 $tid 2>/dev/null
                taskset -p 0x0C $tid 2>/dev/null
                ;;
        esac
        if echo "$name" | grep -qE "$BANISH_PATTERNS"; then
            chrt -o -p 0 $tid 2>/dev/null
            taskset -p 0x03 $tid 2>/dev/null
        fi
    done
done
# Pin main process to non-audio cores 0-1 at SCHED_OTHER so newly-spawned
# threads inherit non-audio affinity by default.
chrt -o -p 0 $MIXPID 2>/dev/null
taskset -p 0x03 $MIXPID 2>/dev/null
The 0x0C taskset mask (binary 1100) pins threads to cores 2–3. The 0x03 mask (binary 0011) pins threads to cores 0–1.
Do not set SCHED_FIFO 99 (or any RT priority) on the main MIXXX process before the thread classification loop has run. MIXXX spawns 44+ child threads, and all of them inherit the scheduler policy of their parent. Setting the main process to SCHED_FIFO 99 causes Mali GPU threads, Qt thread pool workers, GLib loops, and every other non-audio thread to run at real-time priority — directly competing with the audio engine and making latency worse, not better.The correct sequence is: launch at SCHED_OTHER, classify threads, then move the main process to SCHED_OTHER on cores 0–1 last.

IRQ Affinity

The TKGL bootstrap module pins all non-audio hardware IRQs to CPU 0 at boot before MIXXX launches:
  • GPU IRQ (Mali-T76x) → CPU 0
  • USB IRQ (XMOS audio controller, control surface) → CPU 0
  • MMC IRQ (SD card and internal eMMC) → CPU 0
The ALSA DMA IRQ (IRQ 45) is left untouched and stays on its default core alongside the audio engine.

ALSA Buffer Configuration

The audio buffer size directly determines worst-case latency and is the most fragile tuning parameter on this hardware:
ParameterValueNotes
Devicehw:JP11,0Direct hardware device, no plughw conversion
FormatS32_LE32-bit signed little-endian
Channels4Main output (L/R) + booth/headphone
Sample rate44100 HzFixed — resampling adds CPU load
latency setting5Maps to period_size=1024
Period size1024 frames23.2 ms per period
Buffer size2048 frames46.4 ms total ring buffer
Setting latency=4 (period_size=512, 11.6 ms) causes regular xruns when the Mali GPU is active. The GPU’s DMA bursts cause brief memory bus stalls that the shorter period cannot absorb. Use latency=5 (period_size=1024) as the minimum safe value on this hardware.

I/O and VM Tuning

The launcher applies a set of kernel tuning parameters before starting MIXXX to reduce latency variance from I/O and memory management: Block device (MMC/SD):
  • Scheduler: none (replaces BFQ) — BFQ’s per-process I/O accounting wastes CPU on flash storage
  • Read-ahead: 128 KB → 512 KB — reduces sequential read stalls for large audio files
  • nr_requests: 128 → 64 — limits I/O queue depth to reduce dispatch latency
Virtual memory:
  • dirty_background_bytes: set to 20480 bytes (20 KB) — forces early writeback to prevent large dirty-page flushes stalling the audio thread
  • swappiness: 60 → 1 — no swap device present; prevents pointless page reclaim
  • vfs_cache_pressure: 100 → 200 — aggressively reclaim dentry/inode cache under memory pressure
  • stat_interval: 1 s → 10 s — reduces periodic VM stat collection overhead by 10×
Filesystem:
  • All ext4 mounts remounted with noatime,nodiratime,commit=60 — eliminates access-time metadata writes; batches journal commits to 60 s intervals
systemd service limits:
  • LimitMEMLOCK=infinity — allows audio threads to mlock() buffers into physical RAM
  • LimitRTPRIO=99 — allows SCHED_FIFO priority up to 99
  • sched_rt_runtime_us=-1 — disables the kernel’s RT throttle (default reserves 5% of CPU time for non-RT tasks)

Kernel Limitations

The following limitations exist in the stock 6.1.111-inmusic-2024-09-19-rt41 kernel and cannot be fixed without rebuilding the kernel from source. They cause a small but measurable ~0.1–0.2% CPU steal on audio cores:
  • CONFIG_NO_HZ_FULL not set — the 1 ms timer tick fires on all four cores including the isolated audio cores 2–3. Full tickless operation would eliminate this.
  • CONFIG_HZ=1000 — 1 kHz scheduler tick rate; each tick is ~0.1 ms of overhead per core per second.
  • No rcu_nocbs — RCU (Read-Copy-Update) grace-period callbacks are not offloaded and can execute on the audio cores.
  • isolcpus=1-3 in kernel cmdline — CPU isolation is set, but without the nohz_full companion parameter it is incomplete; the timer tick still fires.
These are documented dead ends. Do not attempt to work around them without a kernel rebuild.

Build docs developers (and LLMs) love