ADR-004: Audio backend — cpal + symphonia
ADR-004: Audio backend — cpal + symphonia
Status
Superseded by ADR-004 (phonic)
Context
The playback engine (FR-CORE-1) must play audio from URLs and file paths. It must be source-agnostic and cross-platform (Linux, macOS, Windows). The Rust audio ecosystem offers several approaches:
- cpal + symphonia:
cpalhandles cross-platform audio output (PulseAudio/PipeWire on Linux, CoreAudio on macOS, WASAPI on Windows).symphoniahandles audio decoding (MP3, FLAC, OGG, WAV, AAC). This is the standard Rust audio stack. - rodio: Higher-level library built on
cpal+symphonia. Simpler API but less control over buffering, seeking, and gapless playback. - GStreamer (via gstreamer-rs): Full multimedia framework. Powerful but massive dependency tree, GObject introspection, platform-specific installation requirements.
- librespot + custom: Only relevant for Spotify streaming. Not source-agnostic.
- System
ffplay/ffmpegsubprocess: Would violate C1 (external executables) for the core itself.
Decision
Use cpal for audio output and symphonia for decoding. The playback engine architecture:
Audio Source (URL / File Path)
↓
symphonia (decode)
↓
Audio buffer (ring buffer)
↓
cpal (output stream)
↓
Physical audio device
symphoniahandles format detection and decoding. Supported formats: MP3, FLAC, OGG, WAV, AAC.cpalmanages device enumeration, stream configuration, and output.- The engine runs its own audio thread. Tauri commands (
play,pause,seek,set_volume) communicate with the audio thread via channels. - No GStreamer, no system dependencies beyond what
cpalrequires (alsa/pulse on Linux, CoreAudio on macOS).
Alternatives considered
- rodio: Simplifies the API but wraps
cpalsuch that fine-grained control over buffer fills, seeking, and gapless transitions is lost. The queue requires crossfade and seamless track transitions eventually (W8, v2+); rodio’s decoder-per-stream model makes this harder to implement. - GStreamer: Cross-platform, battle-tested, supports every format. But introduces a massive dependency (2,000+ files, system library requirement, GObject type system). For an app that plays local files and stream URLs, GStreamer is a cannon for a fly.
- Subprocess ffmpeg: Would make the core depend on an external executable not managed through the plugin system. Violates the platform philosophy that the core owns playback.
Consequences
Positive:
- Pure Rust dependency tree (no C libraries beyond what’s needed for audio I/O).
cpalis the de facto standard for Rust audio output, maintained and cross-platform tested.symphoniasupports all formats the reference plugin (local-files) would index.- The ring buffer architecture supports future gapless playback (W8) — pre-decode the next track while the current one plays.
Negative:
cpal’s API is low-level (raw audio buffers, sample rate negotiation). The engine needs a thin abstraction layer.symphonia’s format support is narrower than ffmpeg (no WMA, no DSD, no AC-3). Acceptable for v1 music-focused scope.- Platform-specific audio device handling must be tested on all three targets.
References
- Constraint C5 (cross-platform), R7 (platform fragmentation) — (constraints-assumptions.md, risk-register.md)
- FR-CORE-1 (functional.md)