rustaudioasio

No audio input via ASIO with feedback example using cpal


I'm trying to build an audio application with cpal on Windows. With the Wasapi driver I got everything to work fine, but with ASIO only the outputs work, I can't get any input signals. I modified the feedback example a little bit to use the ASIO driver and i32 sample format.

main.rs:

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::HeapRb;

fn main() -> anyhow::Result<()> {
    let host_ids: Vec<cpal::HostId> = cpal::platform::available_hosts();
    let host = cpal::host_from_id(*host_ids.get(0).unwrap()).unwrap(); // .get(0) is ASIO on my system
    println!("host: {:?}", host.id());

    // Find devices.
    let input_device = host.default_input_device().expect("failed to find input device");
    let output_device = host.default_output_device().expect("failed to find output device");

    println!("Using input device: \"{}\"", input_device.name()?);
    println!("Using output device: \"{}\"", output_device.name()?);

    // We'll try and use the same configuration between streams to keep it simple.
    let config: cpal::StreamConfig = input_device.default_input_config()?.into();

    // Create a delay in case the input and output devices aren't synced.
    let latency_frames = (150.0 / 1_000.0) * config.sample_rate.0 as f32;
    let latency_samples = latency_frames as usize * config.channels as usize;

    // The buffer to share samples
    let ring = HeapRb::<i32>::new(latency_samples * 2);
    let (mut producer, mut consumer) = ring.split();

    // Fill the samples with 0.0 equal to the length of the delay.
    for _ in 0..latency_samples {
        // The ring buffer has twice as much space as necessary to add latency here,
        // so this should never fail
        producer.push(0).unwrap();
    }

    let input_data_fn = move |data: &[i32], _: &cpal::InputCallbackInfo| {
        let mut output_fell_behind = false;
        for &sample in data {
            if producer.push(sample).is_err() {
                output_fell_behind = true;
            }
        }
        if output_fell_behind {
            eprintln!("output stream fell behind: try increasing latency");
        }
    };

    let output_data_fn = move |data: &mut [i32], _: &cpal::OutputCallbackInfo| {
        let mut input_fell_behind = false;
        for sample in data {
            *sample = match consumer.pop() {
                Some(s) => s,
                None => {
                    input_fell_behind = true;
                    0
                }
            };
        }
        if input_fell_behind {
            eprintln!("input stream fell behind: try increasing latency");
        }
    };

    // Build streams.
    println!(
        "Attempting to build both streams with i32 samples and `{:?}`.",
        config
    );
    let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
    let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
    println!("Successfully built streams.");

    // Play the streams.
    println!("Starting the input and output streams with 150 milliseconds of latency.");
    input_stream.play()?;
    output_stream.play()?;

    loop {
        println!(".");
        std::thread::sleep(std::time::Duration::from_secs(3));
    }
}

fn err_fn(err: cpal::StreamError) {
    eprintln!("an error occurred on stream: {}", err);
}

cargo.toml:

[package]
name = "audio2"
version = "0.1.0"
edition = "2021"

[dependencies]
colored = "2.1.0"
cpal = { version = "0.15.3", features = ["asio"] }
num = "0.4.1"
rand = "0.8.5"
ringbuf = "0.3.3"

I tried this (via cargo run) on two computers (both Windows 11) with different ASIO audio interfaces and had the same issue. No error messages.

When I create a sample myself like this

for &sample in data {
    if producer.push(1_000_000).is_err() {
        output_fell_behind = true;
    }
}

it gets to the output, so it doesn't seem to be a problem with the ring buffer, but it seems all samples I'm getting are zero.

Is there anything I'm missing, or maybe another feature I need to turn on?


Solution

  • When using ASIO, my audio interface shows up as one device, which has various input and output configurations, unlike with Wasapi, where it shows up as multiple devices for multiple inputs and outputs. You can get the same device twice with host.default_input_device() and host.default_output_device() without errors, but only one of them will work. I assume this is not only true for ASIO, but for any situation where input and output device are the same.


    After a day of frustration I finally figured it out myself, and it's only 3 characters to replace.

    This part:

    let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
    let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
    

    needs to be:

    let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
    let output_stream = input_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
    

    So when using ASIO, input device and output device need to be the same. I suppose this has something to do with ASIO's exclusive mode, but don't know for sure. If anyone knows more about the topic, please explain why this is happening.