1

I've recently started learning audio programming, and I'm currently trying to write a Rust program using CPAL that will pass the input audio directly to the output. I've been referencing this example code, but the audio comes out distorted and crackly whenever I run the program. It seems as though the output callback function keeps falling behind the input callback. After some digging around, I thought it might be due to the fact that my input has only one channel while the output has two. Since CPAL uses interleaved samples for multichannel audio, I made the output buffer twice as large as the input buffer and added code to double each sample. However, this didn't seem to resolve the problem and I'm stuck right now. Any help would be appreciated! Here's my code (I'm using Rust 1.70.0):

main.rs:

use std::{thread, time};

use color_eyre::eyre::Result;
use cpal::{traits::{DeviceTrait, HostTrait, StreamTrait}, StreamConfig};
use ringbuf::HeapRb;

fn main() -> Result<()> {
    // Set up error handling
    color_eyre::install()?;

    // List all audio devices
    let host = cpal::default_host();
    let devices = host.devices().expect("Devices not found");
    for device in devices {
        println!("{}", device.name().expect("No name?"));
    }

    // List default input and output devices
    let input_device = host.default_input_device().unwrap();
    println!("Default input: {}", input_device.name().unwrap());
    let output_device = host.default_output_device().unwrap();
    println!("Default output: {}", output_device.name().unwrap());

    // Set up input and output configs
    let input_config = StreamConfig {
        channels: 1,
        sample_rate: cpal::SampleRate(44100),
        buffer_size: cpal::BufferSize::Fixed(64)
    };
    let output_config: cpal::StreamConfig = StreamConfig {
        channels: 2,
        sample_rate: cpal::SampleRate(44100),
        buffer_size: cpal::BufferSize::Fixed(128)
    };
    println!("{:#?}", input_config);
    println!("{:#?}", output_config);

    // Create a ringbuffer to store samples
    let buffer = HeapRb::<f32>::new(512);
    let (mut producer, mut consumer) = buffer.split();
    
    let input_callback = move | data: &[f32], _: &cpal::InputCallbackInfo | {
        // Process input data
        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 fell behind");
        }
    };

    let output_callback = move | data: &mut [f32], _: &cpal::OutputCallbackInfo | {
        let mut input_fell_behind = false;
        let mut channel_index = 0;
        let mut raw_sample = consumer.pop();
        for sample in data {
            if !(channel_index < output_config.channels) {
                channel_index = 0;
                raw_sample = consumer.pop();
            } else {
                channel_index += 1;
            }
            *sample = match raw_sample {
                Some(s) => s,
                None => {
                    input_fell_behind = true;
                    0.0
                }
            }
        }
        if input_fell_behind {
            eprintln!("Input fell behind");
        }
    };

    let err_callback = |err: cpal::StreamError| {
        eprintln!("{}", err);
    };

    let input_stream = input_device.build_input_stream(
        &input_config, 
        input_callback,
        err_callback,
        None
    )?;
    let output_stream = output_device.build_output_stream(
        &output_config,
        output_callback,
        err_callback,
        None
    )?;

    input_stream.play()?;
    output_stream.play()?;

    thread::sleep(time::Duration::from_secs(5));

    Ok(())
}

Cargo.toml:

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

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
color-eyre = "0.6.2"
cpal = "0.15.2"
ringbuf = "0.3.3"

0 Answers0