Audio
PCM audio formats, device commands, bounded queues, mixing, VFS integration and platform output.
The audio crate supplies reusable PCM playback primitives and a fixed-capacity mixer. It is not a process-global audio subsystem: the caller owns the memory, stream handles, output reservations, and platform adapter. The xila feature audio exposes the API; native host output additionally requires drivers_native/audio.
Data flow and ownership
Section titled “Data flow and ownership”- A caller allocates
PlaybackBuffer<N>for a single playback device, or constructs and pinsAudioSystem<STREAMS, STREAM_BYTES, MIX_FRAMES, OUTPUTS, SINK_BYTES>for multiple streams. - Application writers enqueue interleaved PCM frames. Writes are nonblocking and may accept fewer bytes than requested.
- A renderer or
mix_next_block()consumes frames. Paused or empty streams contribute silence. - Output consumers owned by a platform adapter pull the mixed PCM. Native output uses
nanoaudio-cpal; browser output uses Web Audio integration. For deterministic tests without real audio hardware,drivers_headless::audio::HeadlessAudioOutputcan consume the same mix pipeline.
nanoaudio provides buffering, DSP, conversion, and volume support. The OS audio dependency does not include codecs or resampling. Host backends may adapt the fixed mix format to device capabilities.
PCM device protocol
Section titled “PCM device protocol”Input samples are interleaved little-endian PCM, with 1–8 channels. The input formats are U8 (silence 128), signed I16, packed I24, signed I32, and F32 (nominal full scale −1 to 1). Configuration is an eight-byte repr(C) value containing u32 sample_rate, u8 channels, u8 format, and two zero reserved bytes. Xila’s format codes are U8=0, I16=1, I24=2, I32=3, F32=4; these are not nanoaudio’s enum discriminants.
The device control prefix is b'A':
| Command | Number | Payload or result |
|---|---|---|
SET_CONFIGURATION |
1 | Configuration → () |
GET_CONFIGURATION |
2 | () → Configuration |
GET_WRITABLE |
3 | () → u32 free bytes |
SET_VOLUME |
4 | u8 percentage, 0–100 → () |
GET_VOLUME |
5 | () → u8 percentage |
START |
6 | () → () |
STOP |
7 | () → () |
Control payloads must have the specified size and alignment; malformed payloads return InvalidParameter. Configuration is fixed when storage is split and a backend is opened. Setting the same configuration succeeds, but changing it requires a new stream.
A new stream starts paused at 100% volume. START enables consumption; STOP pauses and preserves queued frames, although an in-progress callback may finish. write() only accepts complete frames that fit and returns the accepted byte count (possibly zero). Retry the remainder later rather than busy-spinning. flush() is a nonblocking drain poll: it reports RessourceBusy until the callback consumes queued PCM; it does not guarantee that the host output latency buffer is empty. Capture/read is unsupported.
Fixed-capacity mixer
Section titled “Fixed-capacity mixer”AudioSystem<STREAMS, STREAM_BYTES, MIX_FRAMES, OUTPUTS, SINK_BYTES> mixes 48 kHz stereo interleaved F32. The default type is AudioSystem<4, 2048, 128, 2, 3072>: four application streams, 128-frame mix blocks, and two output sinks. The system must be pinned before opening output handles or mixing.
let system = core::pin::pin!(audio::AudioSystem::<4, 2048, 128, 2, 3072>::new()?);let stream = system.open_stream()?;let mut output = system.as_ref().open_output()?;stream.start()?;system.as_ref().mix_next_block()?;output.with_consumer(|consumer| { /* platform adapter reads PCM */ });Each stream has its own queue, start/stop state and volume. Mixer gain and master gain use nanoaudio::Mixer. When an output sink lacks room for a full block, that sink drops the incoming block and increments dropped_blocks(); other sinks continue. Writes, controls and mixing use an atomic try-lock and return RessourceBusy under contention; output consumers do not acquire that lock.
The default system contains 14 KiB of PCM queue storage (8 KiB input and 6 KiB output). Core setup and steady-state operations are allocation-free, but this excludes platform adapter buffers and threads. A consumer transferred with into_consumer_with_reservation must be destroyed before its reservation is released; violating that unsafe lifetime contract is not permitted.
Deterministic headless output
Section titled “Deterministic headless output”Core #515 adds a HeadlessAudioOutput adapter to drivers_headless. It reserves an output on a caller-owned pinned AudioSystem; the test drives mixing explicitly with mix_next_block(), then drains whole 48 kHz stereo F32 frames using read(&mut buffer). Unlike a native sound device, it creates no threads, timers, additional queues or hardware dependencies. read returns zero when no frames are queued and rejects non-frame-aligned buffers without consuming audio. It exposes dropped_blocks() and releases its reservation on drop.
This makes mixer output deterministic in host tests while leaving audible native and browser adapters independent. See drivers/headless/src/audio.rs and drivers/headless/tests/audio.rs. Native ALSA development libraries are now needed only for opt-in host audio builds, not ordinary CI workspace audio tests.
Virtual filesystem integration
Section titled “Virtual filesystem integration”AudioStreamDevice mounts the local playback service at /devices/audio/playback. Each VFS open reserves an independent stream in its context. Writes and generic b'A' controls operate on that stream; closing the file releases the slot. Cloning an open context and reading from it are unsupported. Guests use normal file writes and descriptor controls, without audio-specific WASI imports.
Native playback and tests
Section titled “Native playback and tests”The optional nanoaudio-cpal driver creates one or two output sinks and a shared mixer scheduler on Linux, macOS, and Windows. Its callback plays silence on underrun. The owning Runner reports errors through check() and underruns through underruns(); dropping it stops the mixer before releasing output reservations.
cargo test -p xila-audiocargo run -p xila-drivers-native --features audio --example audioThe example emits a short 440 Hz square wave when a host audio device is available. Tests of core buffers and mixing do not prove audible output on a particular device. Web Audio and generic guest descriptor controls are available; ESP32 I2S and system sounds remain follow-up work.
Choosing a playback API
Section titled “Choosing a playback API”There are two distinct entrypoints, and neither constructs a global audio daemon:
- One device:
PlaybackBuffer<N>::new().split(configuration)returns anOutputDeviceproducer and aRendererconsumer borrowing the same caller-owned storage.Nmust be nonzero, bounded tou32::MAXbytes and divisible by the chosen PCM frame size. A new split resets state and starts paused. - Several streams and outputs:
AudioSystem::new()creates a fixed pool whoseSTREAM_BYTESandSINK_BYTEScapacities must be multiples of an interleaved stereo F32 frame (8 bytes). Each sink must hold a fullMIX_FRAMESblock. A value with invalid capacities returnsInvalidParameter.
The per-stream StreamHandle exposes start, stop, write, writable, flush, set_volume and volume. On an AudioSystem, set_master_volume and mix_next_block control mixing. These operations do not wait for the audio callback: contention returns RessourceBusy, and PCM writes may be short.
Lifetime rules and platform boundary
Section titled “Lifetime rules and platform boundary”Pin an AudioSystem before calling open_output or mix_next_block. A split output ring has a unique producer and consumer; the audio backend must not keep a consumer alive after releasing its matching OutputReservation. The unsafe into_consumer_with_reservation transfer is an explicit contract and needs careful teardown ordering.
The library implements PCM storage, format conversion and mixer plumbing, not decoding or audio-device discovery. Native cpal output, Web Audio output, and future ESP32/I2S drivers are platform adapters. Playback availability and host device format negotiation are therefore target-specific, even with identical Xila PCM streams.
Core implementation references
Section titled “Core implementation references”libraries/audio/src/playback.rs- borrowed single-device split.libraries/audio/src/system.rs- pinned mixer, output reservations and short writes.libraries/audio/src/command.rs- command values and directions.audio::AudioSystem- fixed-capacity mixer.