cpal icon indicating copy to clipboard operation
cpal copied to clipboard

feat!: add StreamConfigBuilder with cross-platform configuration support

Open roderickvd opened this issue 3 months ago • 2 comments

This PR introduces an StreamConfigBuilder with support for platform-specific audio configuration options. This addresses recurring requests for fine-grained control over backend-specific settings while maintaining cross-platform compatibility. At the same time, it improves ergonomics of stream creation.

Motivation

There is a recurring interest in exposing platform-specific audio configuration options:

  • #990 - ALSA buffer size configuration issues requiring set_buffer_size_near
  • #995 - Android input_preset configuration for AEC support
  • #987 - WASAPI raw audio stream configuration needs
  • #917 - ALSA period/buffer size configuration problems

Users have consistently needed platform-specific control over:

  • ALSA: Period counts, access types (RW vs MMap; interleaved vs. non-interleaved)
  • JACK: Client names, automatic port connections, server startup
  • WASAPI: Exclusive vs shared mode

Not yet added:

  • Android: Input presets for AEC and noise cancellation

Currently, these options are either impossible to configure or would require hard-coded defaults or introduction of environment variables.

Solution

All existing APIs remain unchanged. The new builder and platform configuration are purely additive:

use cpal::{StreamConfigBuilder, SampleRate, SampleFormat, BufferSize};

// - builds on all platforms
// - offers more ergonomic stream creation
// - allows platform-specific options
let stream = device.default_output_config()?
    .with_buffer_size(BufferSize::Fixed(256))
    .on_alsa(|alsa| alsa.periods(2))                  // No-op on non-Linux
    .on_wasapi(|wasapi| wasapi.exclusive_mode(true))  // No-op on non-Windows  
    .on_jack(|jack| jack.client_name("app"))          // No-op without JACK
    .build_output_stream(/* callbacks */)?;

As opportunistic refactoring, the following feature flags were re-added:

  • jack - Enable JACK audio backend support
  • audio_thread_priority - Enable real-time thread priority for ALSA/WASAPI

And documentation was updated for readability and correctness.

roderickvd avatar Sep 11 '25 19:09 roderickvd

Note to self: consider making configurable whether CoreAudio follows default audio device changes or not (#1012).

roderickvd avatar Sep 23 '25 19:09 roderickvd

Instead of this we could use extension traits. These would add backend-specific methods without polluting the core trait:

// Core trait - stays platform-agnostic
pub trait DeviceTrait {
    fn name(&self) -> Result<String, DeviceNameError>;
    // ... other core methods
}

// Backend-specific extension trait
#[cfg(target_os = "windows")]
pub trait WasapiDeviceExt {
    /// Access the underlying Windows IMMDevice COM interface.
    fn immdevice(&self) -> &IMMDevice;
}

// Implement the extension only for the WASAPI Device
#[cfg(target_os = "windows")]
impl WasapiDeviceExt for crate::platform::wasapi::Device {
    fn immdevice(&self) -> &IMMDevice {
        &self.device
    }
}

Then how to use:

use cpal::{Device, DeviceTrait}; // Core API

// Works on all platforms
fn portable_code(device: &Device) {
    let name = device.name().unwrap();
    println!("Device: {}", name);
}

// Platform-specific code
#[cfg(target_os = "windows")]
fn windows_specific(device: &Device) {
    use cpal::platform::WasapiDeviceExt;
    let imm_device = device.immdevice();
    // ... do Windows-specific stuff
}

#[cfg(not(target_os = "windows"))]
fn windows_specific(_device: &Device) {
    // ... no-op
}

Or similarly one could create fn platform_specific_setup() for various targets.

roderickvd avatar Dec 09 '25 21:12 roderickvd