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 builder = VoipAec3::builder(sample_rate, channels, channels).initial_delay_ms(0);
let builder = {
#[cfg(feature = "diagnostics")]
{
builder.enable_diagnostics(true)
}
#[cfg(not(feature = "diagnostics"))]
{
builder
}
};
let mut pipeline = builder.build().expect("failed to create AEC pipeline");
let mut last_metrics = Instant::now();
let metrics_interval = Duration::from_secs(5);
let target_delay = Duration::from_millis(20);
let mut capture_queue: VecDeque<(Instant, Vec<f32>)> = VecDeque::new();
while let Ok(frame) = rx_in.recv() {
capture_queue.push_back((Instant::now(), frame));
loop {
let ready = match capture_queue.front() {
Some((ts, _)) => Instant::now().saturating_duration_since(*ts) >= target_delay,
None => false,
};
if !ready {
break;
}
let (_, capture_frame) = capture_queue
.pop_front()
.expect("queue signaled ready but was empty");
while let Ok(render_frame) = rx_render.try_recv() {
if let Err(err) = pipeline.handle_render_frame(&render_frame) {
eprintln!("handle_render_frame error: {err}");
}
}
let mut output = vec![0f32; capture_frame.len()];
match pipeline.process_capture_frame(&capture_frame, 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>>(32);
let (tx_out, rx_out) = bounded::<Vec<f32>>(32);
let (tx_render, rx_render) = bounded::<Vec<f32>>(32);
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 delayed karaoke loopback — press Ctrl+C to exit (capture delay ≈ 20ms)");
#[cfg(feature = "diagnostics")]
{
println!("Diagnostics enabled (feature=diagnostics): writing WAL + WAV files to ./aec3_diagnostics");
println!("Inspect WAL: cargo run --features diagnostics --bin parse-diagnostics");
}
loop {
thread::sleep(Duration::from_millis(100));
}
}