Skip to content

Windows: CPAL Input Callback Fails with Thread Communication (ringbuf, mpsc) #970

Open
@bwanedead

Description

@bwanedead

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:

Image

attachments/assets/31135658-bea2-42a5-977a-c66e856b9382" />

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions