use aec3::voip::VoipAec3;
use anyhow::Result;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::{Receiver, Sender, bounded};
use std::collections::VecDeque;
use std::thread;
use std::time::{Duration, Instant};
fn processing_thread(
rx_in: Receiver<Vec<f32>>,
rx_render: Receiver<Vec<f32>>,
tx_out: Sender<Vec<f32>>,
sample_rate: usize,
channels: usize,
) {
let mut pipeline = VoipAec3::builder(sample_rate, channels, channels)
.initial_delay_ms(116)
.build()
.expect("failed to create AEC pipeline");
let mut last_metrics = Instant::now();
let metrics_interval = Duration::from_secs(5);
while let Ok(frame) = rx_in.recv() {
let render_frame = rx_render.try_recv().ok();
let mut output = vec![0f32; frame.len()];
match pipeline.process(&frame, render_frame.as_deref(), false, &mut output) {
Ok(metrics) => {
if last_metrics.elapsed() >= metrics_interval {
println!("AEC metrics: {:?}", metrics);
last_metrics = Instant::now();
}
let _ = tx_out.try_send(output);
}
Err(err) => {
eprintln!("AEC processing error: {err}");
}
}
}
}
fn main() -> Result<()> {
let host = cpal::default_host();
let input_device = host
.default_input_device()
.expect("No input device available");
let output_device = host
.default_output_device()
.expect("No output device available");
let loopback_supported = output_device.default_output_config()?;
let loopback_stream_config: cpal::StreamConfig = loopback_supported.clone().into();
let sample_rate_hz = loopback_stream_config.sample_rate.0 as usize;
let channels = loopback_stream_config.channels as usize;
let frames_per_buffer = (sample_rate_hz / 100) as usize;
let (tx_in, rx_in) = bounded::<Vec<f32>>(16);
let (tx_out, rx_out) = bounded::<Vec<f32>>(16);
let (tx_render, rx_render) = bounded::<Vec<f32>>(16);
thread::spawn(move || processing_thread(rx_in, rx_render, tx_out, sample_rate_hz, channels));
let in_config = cpal::StreamConfig {
channels: channels as u16,
sample_rate: cpal::SampleRate(sample_rate_hz as u32),
buffer_size: cpal::BufferSize::Fixed(frames_per_buffer as u32),
};
let tx_in_clone = tx_in.clone();
let input_stream = input_device.build_input_stream(
&in_config,
move |data: &[f32], _| {
let vec = data.to_vec();
let _ = tx_in_clone.try_send(vec);
},
move |err| eprintln!("input stream error: {:?}", err),
None,
)?;
let tx_render_clone = tx_render.clone();
let loopback_input_stream = output_device.build_input_stream(
&loopback_stream_config,
move |data: &[f32], _| {
let vec = data.to_vec();
let _ = tx_render_clone.try_send(vec);
},
move |err| eprintln!("loopback input stream error: {:?}", err),
None,
)?;
let out_config = cpal::StreamConfig {
channels: channels as u16,
sample_rate: cpal::SampleRate(sample_rate_hz as u32),
buffer_size: cpal::BufferSize::Fixed(frames_per_buffer as u32),
};
let mut delay_queue: VecDeque<Vec<f32>> = VecDeque::new();
let delay_buffers = std::cmp::max(1usize, (sample_rate_hz * 3) / frames_per_buffer);
let output_stream = output_device.build_output_stream(
&out_config,
move |out: &mut [f32], _| {
while let Ok(frame) = rx_out.try_recv() {
delay_queue.push_back(frame);
}
if delay_queue.len() > delay_buffers {
if let Some(frame_to_play) = delay_queue.pop_front() {
let len = out.len().min(frame_to_play.len());
out[..len].copy_from_slice(&frame_to_play[..len]);
if len < out.len() {
for s in out[len..].iter_mut() {
*s = 0.0;
}
}
} else {
for s in out.iter_mut() {
*s = 0.0;
}
}
} else {
for s in out.iter_mut() {
*s = 0.0;
}
}
},
move |err| eprintln!("output stream error: {:?}", err),
None,
)?;
input_stream.play()?;
loopback_input_stream.play()?;
output_stream.play()?;
println!("Running karaoke loopback — press Ctrl+C to exit");
loop {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}