fix: use with_buffer_size() after with_supported_config() to prevent underruns
rodio 0.22's with_supported_config() resets buffer_size to Default via ..Default::default(), so our BufferSize settings in StreamConfig were never reaching CPAL. MixerDeviceSink has zero internal buffering, making CPAL's buffer the ONLY buffer between the mixer and audio hardware. Chain .with_buffer_size(Fixed(sample_rate/10)) AFTER with_supported_config() to actually pass ~100ms buffer to CPAL, matching old vendored cpal behavior.
This commit is contained in:
3 files changed
+55
-22
No files matched your search
@@ -246,17 +246,12 @@ impl AudioBackend for PipeWireBackend {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create StreamConfig with desired sample rate
|
// Create StreamConfig with desired sample rate
|
||||||
|
// Note: buffer_size here is unused — with_supported_config() resets it.
|
||||||
|
// The actual buffer size is set via with_buffer_size() below.
|
||||||
let stream_config = StreamConfig {
|
let stream_config = StreamConfig {
|
||||||
channels: config.channels,
|
channels: config.channels,
|
||||||
sample_rate: config.sample_rate,
|
sample_rate: config.sample_rate,
|
||||||
buffer_size: if config.exclusive_mode {
|
buffer_size: BufferSize::Default,
|
||||||
BufferSize::Fixed(512) // Lower latency for exclusive mode
|
|
||||||
} else {
|
|
||||||
// PipeWire manages its own buffer scheduling via quantum;
|
|
||||||
// forcing a fixed buffer size here conflicts and causes underruns
|
|
||||||
// at high sample rates (192kHz)
|
|
||||||
BufferSize::Default
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if device supports this configuration
|
// Check if device supports this configuration
|
||||||
@@ -296,10 +291,24 @@ impl AudioBackend for PipeWireBackend {
|
|||||||
SampleFormat::F32,
|
SampleFormat::F32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Compute buffer size — must be applied AFTER with_supported_config()
|
||||||
|
// because that method resets buffer_size to Default via ..Default::default().
|
||||||
|
// MixerDeviceSink has zero internal buffering, so CPAL's buffer is the
|
||||||
|
// ONLY buffer between the mixer and audio hardware.
|
||||||
|
let cpal_buffer_size = if config.exclusive_mode {
|
||||||
|
BufferSize::Fixed(512) // Low latency for exclusive mode
|
||||||
|
} else {
|
||||||
|
// ~100ms buffer, matching old vendored cpal period size.
|
||||||
|
// Prevents underruns at high sample rates (192kHz = 19200 frames).
|
||||||
|
BufferSize::Fixed(config.sample_rate / 10)
|
||||||
|
};
|
||||||
|
log::info!("[PipeWire Backend] Buffer size: {:?}", cpal_buffer_size);
|
||||||
|
|
||||||
// Create MixerDeviceSink with custom config
|
// Create MixerDeviceSink with custom config
|
||||||
let mixer_sink = DeviceSinkBuilder::from_device(device)
|
let mixer_sink = DeviceSinkBuilder::from_device(device)
|
||||||
.map_err(|e| format!("Failed to create device sink builder: {}", e))?
|
.map_err(|e| format!("Failed to create device sink builder: {}", e))?
|
||||||
.with_supported_config(&supported_config)
|
.with_supported_config(&supported_config)
|
||||||
|
.with_buffer_size(cpal_buffer_size)
|
||||||
.open_stream()
|
.open_stream()
|
||||||
.map_err(|e| format!("Failed to create output stream at {}Hz: {}", config.sample_rate, e))?;
|
.map_err(|e| format!("Failed to create output stream at {}Hz: {}", config.sample_rate, e))?;
|
||||||
|
|
||||||
|
|||||||
@@ -352,14 +352,12 @@ fn create_output_stream_with_config(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create StreamConfig with desired sample rate
|
// Create StreamConfig with desired sample rate
|
||||||
|
// Note: buffer_size here is unused — with_supported_config() resets it.
|
||||||
|
// The actual buffer size is set via with_buffer_size() below.
|
||||||
let config = StreamConfig {
|
let config = StreamConfig {
|
||||||
channels,
|
channels,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
buffer_size: if exclusive_mode {
|
buffer_size: BufferSize::Default,
|
||||||
BufferSize::Fixed(512) // Lower latency for exclusive mode
|
|
||||||
} else {
|
|
||||||
BufferSize::Default
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if device supports this configuration
|
// Check if device supports this configuration
|
||||||
@@ -399,10 +397,25 @@ fn create_output_stream_with_config(
|
|||||||
SampleFormat::F32,
|
SampleFormat::F32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Compute buffer size — must be applied AFTER with_supported_config()
|
||||||
|
// because that method resets buffer_size to Default via ..Default::default().
|
||||||
|
// MixerDeviceSink has zero internal buffering, so CPAL's buffer is the
|
||||||
|
// ONLY buffer between the mixer and audio hardware.
|
||||||
|
let cpal_buffer_size = if exclusive_mode {
|
||||||
|
BufferSize::Fixed(512) // Low latency for exclusive mode
|
||||||
|
} else {
|
||||||
|
// ~100ms buffer, matching old vendored cpal period size.
|
||||||
|
// Prevents underruns at high sample rates (192kHz = 19200 frames).
|
||||||
|
BufferSize::Fixed(sample_rate / 10)
|
||||||
|
};
|
||||||
|
log::info!("Buffer size: {:?}", cpal_buffer_size);
|
||||||
|
|
||||||
// Create MixerDeviceSink with custom config
|
// Create MixerDeviceSink with custom config
|
||||||
match DeviceSinkBuilder::from_device(device) {
|
match DeviceSinkBuilder::from_device(device) {
|
||||||
Ok(builder) => {
|
Ok(builder) => {
|
||||||
match builder.with_supported_config(&supported_config).open_stream() {
|
match builder.with_supported_config(&supported_config)
|
||||||
|
.with_buffer_size(cpal_buffer_size)
|
||||||
|
.open_stream() {
|
||||||
Ok(mixer_sink) => {
|
Ok(mixer_sink) => {
|
||||||
log::info!("MixerDeviceSink created successfully at {}Hz", sample_rate);
|
log::info!("MixerDeviceSink created successfully at {}Hz", sample_rate);
|
||||||
Ok(mixer_sink)
|
Ok(mixer_sink)
|
||||||
|
|||||||
@@ -379,16 +379,12 @@ fn create_output_stream_with_config(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create StreamConfig with desired sample rate
|
// Create StreamConfig with desired sample rate
|
||||||
|
// Note: buffer_size here is unused — with_supported_config() resets it.
|
||||||
|
// The actual buffer size is set via with_buffer_size() below.
|
||||||
let config = StreamConfig {
|
let config = StreamConfig {
|
||||||
channels,
|
channels,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
buffer_size: if exclusive_mode {
|
buffer_size: BufferSize::Default,
|
||||||
BufferSize::Fixed(512) // Lower latency for exclusive mode
|
|
||||||
} else {
|
|
||||||
// PipeWire manages its own buffer scheduling;
|
|
||||||
// forcing a fixed size causes underruns at high sample rates
|
|
||||||
BufferSize::Default
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if device supports this configuration
|
// Check if device supports this configuration
|
||||||
@@ -428,10 +424,25 @@ fn create_output_stream_with_config(
|
|||||||
SampleFormat::F32,
|
SampleFormat::F32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Compute buffer size — must be applied AFTER with_supported_config()
|
||||||
|
// because that method resets buffer_size to Default via ..Default::default().
|
||||||
|
// MixerDeviceSink has zero internal buffering, so CPAL's buffer is the
|
||||||
|
// ONLY buffer between the mixer and audio hardware.
|
||||||
|
let cpal_buffer_size = if exclusive_mode {
|
||||||
|
BufferSize::Fixed(512) // Low latency for exclusive mode
|
||||||
|
} else {
|
||||||
|
// ~100ms buffer, matching old vendored cpal period size.
|
||||||
|
// Prevents underruns at high sample rates (192kHz = 19200 frames).
|
||||||
|
BufferSize::Fixed(sample_rate / 10)
|
||||||
|
};
|
||||||
|
log::info!("Buffer size: {:?}", cpal_buffer_size);
|
||||||
|
|
||||||
// Create MixerDeviceSink with custom config
|
// Create MixerDeviceSink with custom config
|
||||||
match DeviceSinkBuilder::from_device(device) {
|
match DeviceSinkBuilder::from_device(device) {
|
||||||
Ok(builder) => {
|
Ok(builder) => {
|
||||||
match builder.with_supported_config(&supported_config).open_stream() {
|
match builder.with_supported_config(&supported_config)
|
||||||
|
.with_buffer_size(cpal_buffer_size)
|
||||||
|
.open_stream() {
|
||||||
Ok(mixer_sink) => {
|
Ok(mixer_sink) => {
|
||||||
log::info!("MixerDeviceSink created successfully at {}Hz", sample_rate);
|
log::info!("MixerDeviceSink created successfully at {}Hz", sample_rate);
|
||||||
Ok(mixer_sink)
|
Ok(mixer_sink)
|
||||||
|
|||||||
Reference in new issue
Block a user