Description
CPAL Callback Stops Firing with Inter-Thread Communication on Windows
Description
The CPAL input callback stops firing when it includes operations for inter-thread communication, such as ringbuf::Producer::push, ringbuf::Producer::push_slice, or std::sync::mpsc::Sender::try_send. A minimal callback that only logs (e.g., println!("Callback fired")) works correctly, firing repeatedly, but adding any inter-thread communication logic causes the callback to not execute at all. This issue was observed with both ringbuf (versions 0.3.3 and 0.4.8) and std::sync::mpsc, suggesting a broader issue with CPAL’s callback handling or driver compatibility on Windows.
Steps to Reproduce
Run the minimal reproducible example below with either the ringbuf or mpsc code uncommented.
Observe that the callback does not print when inter-thread communication is included.
Comment out the communication logic (e.g., replace with just println!) and confirm the callback fires repeatedly.
Test on a Windows system with CPAL 0.15.x and Rust 1.85.x.
Minimal Reproducible Example
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::HeapRb;
use std::sync::mpsc;
fn main() {
env_logger::init();
let host = cpal::default_host();
let device = host.default_input_device().expect("no input device");
let config = device.default_input_config().expect("no config").into();
// Test with ringbuf
let (producer, consumer) = HeapRb::<f32>::new(1024).split();
let stream = device.build_input_stream(
&config,
move |data: &[f32], _: &_| {
println!("Callback fired, len: {}", data.len()); // Works without push
producer.push_slice(data); // Stops callback
},
|err| eprintln!("Error: {:?}", err),
None,
).expect("failed to build stream");
stream.play().expect("failed to play stream");
std::thread::spawn(move || {
while let Some(sample) = consumer.pop() {
println!("Consumed: {}", sample);
}
});
// Test with mpsc (comment out ringbuf code above to try)
/*
let (sender, receiver) = mpsc::channel();
let stream = device.build_input_stream(
&config,
move |data: &[f32], _: &_| {
println!("Callback fired, len: {}", data.len()); // Works without send
sender.try_send(data.to_vec()).unwrap(); // Stops callback
},
|err| eprintln!("Error: {:?}", err),
None,
).expect("failed to build stream");
stream.play().expect("failed to play stream");
std::thread::spawn(move || {
while let Ok(data) = receiver.recv() {
println!("Received: {} samples", data.len());
}
});
*/
std::thread::sleep(std::time::Duration::from_secs(5));
}
Expected Behavior
The input callback should fire repeatedly, sending audio data to a consumer thread via ringbuf or mpsc, allowing real-time audio processing.
Actual Behavior
The callback does not fire at all when inter-thread communication operations are included in the callback closure. Without these operations (e.g., just printing), the callback fires as expected.
Environment
Operating System: Windows 11, Version 23H2 (OS Build 22631.5189)
Rust Version: 1.85.1
CPAL Version: 0.15.x
ringbuf Version: 0.3.3 (also tested with 0.4.8)
Audio Hardware/Driver: Realtek(R) Audio (Microphone Array), driver version 6.0.9679.1, driver date 5/7/2024
Dependencies:
[dependencies]
cpal = "0.15"
ringbuf = "0.3.3"
env_logger = "0.11"
Additional Context
The issue persists across different inter-thread communication mechanisms (ringbuf::Producer and std::sync::mpsc::Sender), suggesting it’s not specific to a particular library.
Simply capturing the producer or sender (e.g., let _ = &producer;) without using it allows the callback to fire, but any operation like push, push_slice, or try_send stops it.
The issue was reproduced in a minimal test project, ruling out project-specific complexity.
Audio drivers are currently on version 6.0.9679.1 (dated 5/7/2024). I have not yet updated to the latest version as of April 2025, but this has been recommended as a potential fix.
This behavior may be related to CPAL’s callback handling on Windows or compatibility with specific audio drivers.
Debugging Steps Taken
Confirmed the callback fires with minimal logic (e.g., logging).
Tested with both ringbuf (0.3.3 and 0.4.8) and mpsc, with identical results.
Created a minimal test project to isolate the issue, which reproduced the same behavior.
Observed no error messages from CPAL’s error callback, indicating the stream appears to start but the callback silently fails.
Request
Please investigate why the CPAL input callback stops firing when inter-thread communication is included. Any insights into potential workarounds, driver compatibility issues, or fixes would be greatly appreciated. I’m happy to provide additional logs or test patches if needed.
Screenshots
Attached are screenshots of my audio device properties and Windows version for reference:
Audio Device Properties:
attachments/assets/31135658-bea2-42a5-977a-c66e856b9382" />