Skip to content

Conversation

roderickvd
Copy link
Member

@roderickvd roderickvd commented Sep 11, 2025

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:

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.

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a StreamConfigBuilder with support for platform-specific audio configuration options, improving ergonomics of stream creation while maintaining full cross-platform compatibility. The builder enables fine-grained control over backend-specific settings without requiring conditional compilation.

Key Changes

  • Added StreamConfigBuilder with fluent API for configuring audio streams
  • Extended platform support to include JACK on all compatible platforms
  • Introduced platform-specific configuration wrappers (ALSA, JACK, WASAPI)

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/lib.rs Core builder implementation and device extensions for improved stream creation API
src/traits.rs Added Clone requirement to DeviceTrait for builder pattern support
src/platform/mod.rs Extended JACK support to macOS and Windows platforms
src/host/wasapi/mod.rs Added WASAPI-specific configuration for exclusive mode support
src/host/wasapi/device.rs Implemented WASAPI configuration methods for stream building
src/host/jack/mod.rs Added JACK-specific configuration for client management
src/host/jack/device.rs Implemented JACK configuration methods with custom client options
src/host/alsa/mod.rs Added ALSA-specific configuration for periods and access types
Cargo.toml Updated feature flags and dependencies for new backend support

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +1113 to +1118
pub fn with_buffer_size(mut self, _buffer_size: BufferSize) -> Self {
// Create a new config with the updated buffer size
let supported_config = SupportedStreamConfig::new(
self.config.channels(),
self.config.sample_rate(),
self.config.buffer_size(),
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _buffer_size parameter is prefixed with an underscore but is used in the method body, indicating it should not be marked as unused.

Suggested change
pub fn with_buffer_size(mut self, _buffer_size: BufferSize) -> Self {
// Create a new config with the updated buffer size
let supported_config = SupportedStreamConfig::new(
self.config.channels(),
self.config.sample_rate(),
self.config.buffer_size(),
pub fn with_buffer_size(mut self, buffer_size: BufferSize) -> Self {
// Create a new config with the updated buffer size
let supported_config = SupportedStreamConfig::new(
self.config.channels(),
self.config.sample_rate(),
buffer_size,

Copilot uses AI. Check for mistakes.

Comment on lines +1314 to +1315
pub fn buffer_size(&self) -> SupportedBufferSize {
self.buffer_size
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method signature change from returning &SupportedBufferSize to SupportedBufferSize could be a breaking change for existing users who expect a reference.

Suggested change
pub fn buffer_size(&self) -> SupportedBufferSize {
self.buffer_size
pub fn buffer_size(&self) -> &SupportedBufferSize {
&self.buffer_size

Copilot uses AI. Check for mistakes.


impl LocalProcessHandler {
#[allow(too_many_arguments)]
#[allow(clippy::too_many_arguments)]
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider refactoring this function to use a configuration struct instead of many individual parameters to improve readability and maintainability.

Copilot uses AI. Check for mistakes.

{
let stream_inner =
self.build_stream_inner(conf, sample_format, alsa::Direction::Capture)?;
self.build_stream_inner(conf, sample_format, alsa::Direction::Capture, None, None)?;
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using None, None for optional parameters makes the code unclear. Consider using named parameters or a configuration struct to improve readability.

Copilot uses AI. Check for mistakes.

- Add ergonomic StreamConfigBuilder API for stream configuration
- Add platform-specific configuration types:
  - AlsaStreamConfig: configure periods and access types (RW/MMap modes)
  - JackStreamConfig: configure client names and port auto-connection
  - WasapiStreamConfig: configure exclusive/shared mode
- Add device-tied stream building methods on SupportedStreamConfig
- Add convenience methods: Device::default_input/output_config()
- Add platform builder methods: .on_alsa(), .on_jack(), .on_wasapi()
- Add jack feature flag for JACK audio backend support
- Add audio_thread_priority feature flag for ALSA/WASAPI real-time scheduling
- Update documentation with Quick Start guide and cross-platform examples
- Maintain backward compatibility with existing Device::build_*_stream APIs

This introduces a major ergonomic improvement for cross-platform audio
configuration while preserving the existing low-level APIs.
@roderickvd roderickvd force-pushed the feat/cross-platform-builder branch from 5ec0841 to cbd8e2a Compare September 11, 2025 20:49
roderickvd referenced this pull request Sep 12, 2025
This mostly reverts #990 for device compatibility, by letting ALSA calculate
the period size from the device default period count. Forcing the period count
to 2 caused underruns on some systems.
@roderickvd roderickvd changed the title feat: add StreamConfigBuilder with cross-platform configuration support feat!: add StreamConfigBuilder with cross-platform configuration support Sep 17, 2025
@roderickvd
Copy link
Member Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant