This guide covers breaking changes requiring code updates. See CHANGELOG.md for the complete list of changes and improvements.
- Add wildcard arms to exhaustive
matchexpressions on cpal error enums - Optionally handle the new
DeviceBusyvariant for retryable device errors (ALSA)
What changed: Public error enums in cpal are now marked #[non_exhaustive]. This lets cpal
add new variants in future minor releases without a SemVer-breaking change.
// Before (v0.17)
match device.default_output_config() {
Ok(config) => config,
Err(DefaultStreamConfigError::DeviceNotAvailable) => panic!("device gone"),
Err(DefaultStreamConfigError::StreamTypeNotSupported) => panic!("unsupported"),
Err(DefaultStreamConfigError::BackendSpecific { err }) => panic!("{err}"),
}
// After (v0.18)
loop {
match device.default_output_config() {
Ok(config) => break config,
Err(DefaultStreamConfigError::DeviceBusy) => {
std::thread::sleep(std::time::Duration::from_millis(100));
}
Err(DefaultStreamConfigError::DeviceNotAvailable) => panic!("device gone"),
Err(DefaultStreamConfigError::StreamTypeNotSupported) => panic!("unsupported"),
Err(DefaultStreamConfigError::BackendSpecific { err }) => panic!("{err}"),
Err(_) => panic!("unknown error"),
}
}What changed: On ALSA, EBUSY/EAGAIN errors from device open calls now produce DeviceBusy
instead of DeviceNotAvailable.
Unlike DeviceNotAvailable (device is gone), DeviceBusy signals a transient condition. Retrying after a short delay may succeed, as shown in the example above.
This guide covers breaking changes requiring code updates. See CHANGELOG.md for the complete list of changes and improvements.
- Replace
SampleRate(n)with plainnvalues - Update
windowscrate to >= 0.59, <= 0.62 (Windows only) - Update
alsacrate to 0.11 (Linux only) - Remove
wee_allocfeature from Wasm builds (if used) - Wrap CoreAudio streams in
Arcif you were cloning them (macOS only) - Handle
BuildStreamError::StreamConfigNotSupportedforBufferSize::Fixed(JACK, strict validation) - Update device name matching if using ALSA (Linux only)
Recommended migrations:
- Replace deprecated
device.name()calls withdevice.description()ordevice.id()
What changed: SampleRate changed from a struct to a u32 type alias.
// Before (v0.16)
use cpal::SampleRate;
let config = StreamConfig {
channels: 2,
sample_rate: SampleRate(44100),
buffer_size: BufferSize::Default,
};
// After (v0.17)
let config = StreamConfig {
channels: 2,
sample_rate: 44100,
buffer_size: BufferSize::Default,
};Impact: Remove SampleRate() constructor calls. The type is now just u32, so use integer literals or variables directly.
What changed: Device::name() is deprecated in favor of id() and description().
// Old (still works but shows deprecation warning)
let name = device.name()?;
// New: For user-facing display
let desc = device.description()?;
println!("Device: {}", desc); // or desc.name() for just the name
// New: For stable identification and persistence
let id = device.id()?;
let id_string = id.to_string(); // Save this
// Later...
let device = host.device_by_id(&id_string.parse()?)?;Impact: Deprecation warnings only. The old API still works in v0.17. Update when convenient to prepare for future versions.
Why: Separates stable device identification (id()) from human-readable names (description()).
What changed: On macOS, Stream no longer implements Clone. Use Arc instead.
// Before (v0.16) - macOS only
let stream = device.build_output_stream(&config, data_fn, err_fn, None)?;
let stream_clone = stream.clone();
// After (v0.17) - all platforms
let stream = Arc::new(device.build_output_stream(&config, data_fn, err_fn, None)?);
let stream_clone = Arc::clone(&stream);Why: Removed as part of making Stream implement Send on macOS.
What changed: BufferSize::Default now defers to the audio host/device defaults instead of applying cpal's opinionated defaults.
Impact: Buffer sizes may differ from v0.16, affecting latency characteristics:
- Latency will vary based on host/device defaults (which may be lower, higher, or similar)
- May underrun or have different latency depending on what the host chooses
- Better integration with system audio configuration: cpal now respects configured settings instead of imposing its own buffers. For example, on ALSA, PipeWire quantum settings (via the pipewire-alsa device) are now honored instead of being overridden.
Migration: If you experience underruns or need specific latency, use BufferSize::Fixed(size) instead of relying on defaults.
Platform-specific notes:
- ALSA: Previously used cpal's hardcoded 25ms periods / 100ms buffer, now uses device defaults
- All platforms: Default buffer sizes now match what the host audio system expects
What changed: Several backends now have different validation behavior for BufferSize::Fixed:
- ALSA: Now uses
set_buffer_size_near()for improved hardware compatibility with devices requiring byte-alignment, power-of-two sizes, or other alignment constraints (was: exact size viaset_buffer_size(), which would reject unsupported sizes) - JACK: Must exactly match server buffer size (was: silently ignored)
- Emscripten/WebAudio: Validates min/max range
- ASIO: Stricter lower bound validation
// Handle validation errors
let mut config = StreamConfig {
channels: 2,
sample_rate: 44100,
buffer_size: BufferSize::Fixed(512),
};
match device.build_output_stream(&config, data_fn, err_fn, None) {
Ok(stream) => { /* success */ },
Err(BuildStreamError::StreamConfigNotSupported) => {
config.buffer_size = BufferSize::Default; // Fallback
device.build_output_stream(&config, data_fn, err_fn, None)?
},
Err(e) => return Err(e),
}JACK users: Use BufferSize::Default to automatically match the server's configured size.
Update these dependencies if you use them directly:
[dependencies]
cpal = "0.17"
# Platform-specific (if used directly):
alsa = "0.11" # Linux only
windows = { version = ">=0.59, <=0.62" } # Windows only
audio_thread_priority = "0.34" # All platformsWhat changed: Device enumeration now returns all devices from aplay -L. v0.16 had a regression that only returned card names, missing all device variants.
- v0.16: Only card names ("Loopback", "HDA Intel PCH")
- v0.17: All aplay -L devices (default, hw:CARD=X,DEV=Y, plughw:, front:, surround51:, etc.)
Impact: Many more devices will be enumerated. Device names/IDs will be much more detailed. Update any code that matches specific ALSA device names.
What changed: The optional wee_alloc feature was removed for security reasons.
# Before (v0.16)
cpal = { version = "0.16", features = ["wasm-bindgen", "wee_alloc"] }
# After (v0.17)
cpal = { version = "0.17", features = ["wasm-bindgen"] }v0.17 also includes significant improvements that don't require code changes:
- Stable device IDs: New
device.id()returns persistent device identifiers that survive reboots/reconnections. Usehost.device_by_id()to reliably select saved devices. - Streams are Send+Sync everywhere: All platforms now support moving/sharing streams across threads
- 24-bit sample formats: Added
I24/U24support on ALSA, CoreAudio, WASAPI, ASIO - Custom host support: Implement your own
Host/Device/Streamfor proprietary platforms - Predictable buffer sizes: CoreAudio and AAudio now ensure consistent callback buffer sizes
- Expanded sample rate support: ALSA supports 12, 24, 352.8, 384, 705.6, and 768 kHz
- WASAPI advanced interop: Exposed
IMMDevicefor Windows COM interop scenarios - Platform improvements: macOS loopback recording (14.6+), improved ALSA audio callback performance, improved timestamp accuracy, iOS AVAudioSession integration, JACK on all platforms
See CHANGELOG.md for complete details and examples/ for updated usage patterns.
- Full details: CHANGELOG.md
- Examples: examples/
- Issues: https://github.com/RustAudio/cpal/issues