use crate::audio::SAMPLE_RATE;
use crate::delay::{ReferenceRing, estimate_delay_samples};
use crate::fdaf::{FdafConfig, FdafNlms};
use crate::residual::{ResidualWeights, embedded_residual_weights};
use anyhow::{Result, ensure};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct AecConfig {
pub n_fft: usize,
pub frame_samples: usize,
pub step_size: f32,
pub max_delay_ms: u32,
pub residual: bool,
pub delay_reestimate_frames: usize,
}
impl Default for AecConfig {
fn default() -> Self {
Self {
n_fft: 1024,
frame_samples: 160,
step_size: 0.05,
max_delay_ms: 300,
residual: true,
delay_reestimate_frames: 50,
}
}
}
pub struct AecSession {
cfg: AecConfig,
delay_samples: usize,
max_delay_samples: usize,
reference_ring: ReferenceRing,
fdaf: FdafNlms,
frame_count: usize,
pending_far: Vec<f32>,
}
impl AecSession {
pub fn new(cfg: AecConfig) -> Result<Self> {
let max_delay_samples = (cfg.max_delay_ms as usize * SAMPLE_RATE) / 1000;
let residual = if cfg.residual {
Some(embedded_residual_weights()?)
} else {
None
};
let fdaf_cfg = FdafConfig {
n_fft: cfg.n_fft,
frame_samples: cfg.frame_samples,
step_size: cfg.step_size,
adapt: true,
use_residual: cfg.residual,
};
let fdaf = FdafNlms::new(fdaf_cfg, residual)?;
Ok(Self {
cfg,
delay_samples: 0,
max_delay_samples,
reference_ring: ReferenceRing::new(max_delay_samples),
fdaf,
frame_count: 0,
pending_far: Vec::new(),
})
}
pub fn with_residual_weights(cfg: AecConfig, weights: ResidualWeights) -> Result<Self> {
let max_delay_samples = (cfg.max_delay_ms as usize * SAMPLE_RATE) / 1000;
let fdaf_cfg = FdafConfig {
n_fft: cfg.n_fft,
frame_samples: cfg.frame_samples,
step_size: cfg.step_size,
adapt: true,
use_residual: cfg.residual,
};
let residual = if cfg.residual { Some(weights) } else { None };
let fdaf = FdafNlms::new(fdaf_cfg, residual)?;
Ok(Self {
cfg,
delay_samples: 0,
max_delay_samples,
reference_ring: ReferenceRing::new(max_delay_samples),
fdaf,
frame_count: 0,
pending_far: Vec::new(),
})
}
pub fn reset(&mut self) {
self.delay_samples = 0;
self.frame_count = 0;
self.reference_ring.clear();
self.pending_far.clear();
self.fdaf.reset();
}
pub fn delay_samples(&self) -> usize {
self.delay_samples
}
pub fn push_reference(&mut self, far_end: &[f32]) {
self.reference_ring.push(far_end);
self.pending_far.extend_from_slice(far_end);
}
pub fn process(&mut self, mic: &[f32], far_end: &[f32]) -> Result<Vec<f32>> {
ensure!(mic.len() == far_end.len());
self.push_reference(far_end);
self.process_mic(mic)?
.ok_or_else(|| anyhow::anyhow!("empty mic buffer"))
}
pub fn process_mic(&mut self, mic: &[f32]) -> Result<Option<Vec<f32>>> {
if mic.is_empty() {
return Ok(None);
}
let hop = self.cfg.frame_samples;
let mut out = vec![0.0f32; mic.len()];
let mut pos = 0;
while pos < mic.len() {
let end = (pos + hop).min(mic.len());
let chunk = end - pos;
let mut aligned = vec![0.0f32; hop];
self.reference_ring
.read_delayed(self.delay_samples, hop, &mut aligned);
if chunk < hop {
aligned.fill(0.0);
self.reference_ring
.read_delayed(self.delay_samples, hop, &mut aligned);
}
let mut frame_out = vec![0.0f32; hop];
if chunk == hop {
self.fdaf
.process_frame(&mic[pos..end], &aligned, &mut frame_out)?;
out[pos..end].copy_from_slice(&frame_out);
} else {
let mut mp = vec![0.0f32; hop];
mp[..chunk].copy_from_slice(&mic[pos..end]);
self.fdaf.process_frame(&mp, &aligned, &mut frame_out)?;
out[pos..end].copy_from_slice(&frame_out[..chunk]);
}
pos = end;
self.frame_count += 1;
if self.frame_count % self.cfg.delay_reestimate_frames == 1 {
self.maybe_reestimate_delay(mic);
}
}
Ok(Some(out))
}
fn maybe_reestimate_delay(&mut self, mic: &[f32]) {
let n = self
.pending_far
.len()
.min(mic.len())
.min(self.cfg.n_fft * 4);
if n < 256 {
return;
}
let est = estimate_delay_samples(
&self.pending_far[..n],
&mic[..n],
self.cfg.n_fft,
self.max_delay_samples,
);
self.delay_samples = est;
}
pub fn process_aligned_buffers(&mut self, mic: &[f32], far: &[f32]) -> Result<Vec<f32>> {
ensure!(mic.len() == far.len());
let n = mic.len().min(far.len()).min(self.cfg.n_fft * 8);
if n >= 256 {
self.delay_samples = estimate_delay_samples(
&far[..n],
&mic[..n],
self.cfg.n_fft,
self.max_delay_samples,
);
}
let mut aligned_far = vec![0.0f32; far.len()];
if self.delay_samples < far.len() {
aligned_far[self.delay_samples..]
.copy_from_slice(&far[..far.len() - self.delay_samples]);
}
let mut out = vec![0.0f32; mic.len()];
self.fdaf.process_buffer(mic, &aligned_far, &mut out)?;
Ok(out)
}
pub fn process_wav_files(
mic_path: &Path,
ref_path: &Path,
cfg: &AecConfig,
) -> Result<(Vec<f32>, usize)> {
let mic = crate::audio::load_wav_16k(mic_path)?;
let far = crate::audio::load_wav_16k(ref_path)?;
let len = mic.len().min(far.len());
let mut session = AecSession::new(cfg.clone())?;
let out = session.process_aligned_buffers(&mic[..len], &far[..len])?;
Ok((out, session.delay_samples()))
}
}