Skip to content
Rymflux Documentation
Esc
navigateopen⌘Jpreview
On this page

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: cpal handles cross-platform audio output (PulseAudio/PipeWire on Linux, CoreAudio on macOS, WASAPI on Windows). symphonia handles 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/ffmpeg subprocess: 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
  • symphonia handles format detection and decoding. Supported formats: MP3, FLAC, OGG, WAV, AAC.
  • cpal manages 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 cpal requires (alsa/pulse on Linux, CoreAudio on macOS).

Alternatives considered

  • rodio: Simplifies the API but wraps cpal such 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).
  • cpal is the de facto standard for Rust audio output, maintained and cross-platform tested.
  • symphonia supports 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)

Was this page helpful?