Skip to content

cpal 0.18.0

Choose a tag to compare

@roderickvd roderickvd released this 06 Jun 21:07
· 5 commits to master since this release
45fe976

Hey everyone! cpal 0.18.0 is out, bringing two long-requested native Linux backends, a unified error API, and accurate timestamps across every platform.

What's New

Native PipeWire and PulseAudio

Two new first-class backends join the Linux and BSD lineup:

  • PipeWire
  • PulseAudio

Enable them with the pipewire and pulseaudio Cargo features. When multiple backends are compiled in, cpal selects the best available one at runtime: PipeWire > PulseAudio > ALSA.

Unified Error API

All per-operation error enums (DevicesError, BuildStreamError, StreamError, etc.) are replaced by a single cpal::Error with a kind() getter:

match device.default_output_config() {
    Err(e) => match e.kind() {
        cpal::ErrorKind::DeviceNotAvailable => { /* ... */ }
        cpal::ErrorKind::DeviceBusy => { /* retry */ }
        _ => { /* ... */ }
    }
}

Two new error kinds make previously indistinguishable cases actionable: DeviceBusy (EBUSY/EAGAIN is retryable) and PermissionDenied for OS-level access denials. See the upgrading guide for the mapping table.

Accurate Timestamps and A/V Sync

Timestamps previously reflected when the callback fired rather than when audio would actually reach hardware. This release corrects that across every backend:

  • AAudio: pipeline buffer depth accounted for; fallback to zero on error fixed
  • ALSA: LinkSynchronized hardware cross-timestamps for reduced jitter on supported devices
  • ASIO: driver-reported hardware latency included; re-queried on kAsioLatenciesChanged
  • CoreAudio: device latency and safety offset included
  • JACK: precise hardware cycle deadline; input capture timestamps were previously using callback execution time
  • WASAPI: hardware pipeline latency included
  • WebAudio: base and output latency included

A new StreamTrait::now() method lets you query the stream's clock from outside the callback for A/V sync: read the audio clock at any point and correlate it with your video timeline.

48 kHz is the New Default

default_input_config() and default_output_config() now prefer 48 kHz, then 44.1 kHz, on all backends. Defaulting to 44.1 kHz meant cpal's chosen rate often didn't match the hardware's preferred rate. Pin it explicitly if you need 44.1 kHz.

New API

  • StreamTrait::buffer_size() queries the stream's current buffer size
  • SupportedStreamConfigRange::try_with_standard_sample_rate() / with_standard_sample_rate() snaps to 48 kHz or 44.1 kHz from a supported range

Platform Improvements

  • ALSA: device_by_id() now accepts PCM shorthand names like hw:0,0 and plughw:foo; streams attempt to recover after system suspend; capture streams no longer hang on overruns; backward-stepping timestamps during startup and xrun recovery are fixed.

  • ASIO: collect() on the device iterator no longer stops after the first device; device enumeration and stream creation now work correctly when called from spawned threads; distortion from drivers that fire the buffer callback multiple times per cycle is fixed.

  • CoreAudio: the physical stream format is now set directly on the hardware device rather than relying on the HAL mixer; user-specified timeouts are now respected when building a stream.

  • JACK: buffer size changes no longer fire an error callback but resizes internal buffers, avoiding unnecessary stream rebuilds; server shutdown now surfaces as ErrorKind::DeviceNotAvailable.

  • WASAPI: device names now prefer FriendlyName over DeviceDesc, so you see the readable name from system settings; default streams automatically reroute when the system default device changes.

...and a lot more. The changelog has the full picture.

Breaking Changes

  • Streams now require an explicit play() call: ALSA, CoreAudio, and JACK previously auto-started streams on creation. If you never called play(), your callback will never fire after upgrading.
  • Error types unified: match on e.kind() instead of per-operation error enums.
  • StreamConfig passed by value: StreamConfig now implements Copy; drop the & at build_*_stream call sites.
  • StreamInstant API overhauled: aligns with std::time::Instant. Change add/sub to checked_add/checked_sub (or +/-); duration_since returns Duration (saturating), secs/nanos are now u64.
  • Default sample rate is now 48 kHz: pin explicitly if you need 44.1 kHz.
  • Default sample format heuristics now fully ranked: floats before integers, higher bit-depth before lower; pin F32 explicitly if you were relying on it as the default.
  • Emscripten host removed: migrate to wasm32-unknown-unknown with the wasm-bindgen feature.

Full details and migration examples in the upgrading guide.

Looking Ahead to v0.19

The design goals are tracked in #1220. Highlights:

  • Extension traits: clean access to platform-specific functionality like RAW mode on WASAPI, control panel on ASIO, identifying stream properties on PipeWire, etc.
  • Exclusive mode on CoreAudio and WASAPI
  • Duplex stream API
  • Input streams on WASM: microphone access from the browser
  • Stream lifecycle normalization: play/pause to start/pause/stop with a draining stop
  • Native DSD on WASAPI
  • BufferSize refactor with range support

The feature set may change.

Thanks to Our Contributors

16 people contributed to this release, 13 of them for the first time: Access (@Decodetalkers), atlv (@atlv24), Chandler Newman (@csnewman), Colin Marc (@colinmarc), Edwin Löffler (@edwloef), Jerry.Wang (@wangjia184), Mat Silverstein (@silverstein), Mike Hilgendorf (@m-hilgendorf), osoftware (@osoftware), Raphael Poss (@knz), Seto Elkahfi (@setoelkahfi), Sintel (@Sin-tel), thewh1teagle (@thewh1teagle), Umer Haider (@umerhd), and Worik Stanton (@worikgh). Welcome aboard, and thank you all!

Support the Project

If you find value in cpal, sponsorships are a heartfelt token of appreciation and help cover the costs of building it: music service subscriptions, hardware for cross-platform compatibility, and tooling. Every contribution helps: sponsor me at GitHub.

Links

Huge thanks to everyone who contributed to this release!