aec3 — Rust port of WebRTC AEC3
A small, pragmatic Rust port of WebRTC's AEC3 acoustic-echo-canceller. This
crate exposes the full audio-processing implementation (in
crate::audio_processing::aec3) and provides a small, ergonomic VOIP-style
wrapper in crate::voip::VoipAec3 for easy integration into real-time
pipelines.
What you'll find in this repository
src/audio_processing— low-level audio primitives (buffers, filters, FFT, and the AEC3 pipeline implementation).src/voip/mod.rs— a small wrapper that hides the plumbing and provides a convenient builder-based API for VOIP use-cases.examples/karaoke_loopback.rs— example that captures system loopback + mic and runs AEC in a processing thread.examples/karaoke_loopback_delayed.rs— delayed-loopback example that simulates playback latency to exercise AEC delay estimation.tests/voip.rs— unit tests that exercise the wrapper API.
Key features
- Implements the AEC3 algorithm compatible with WebRTC reference behavior.
- Supported full-band sample rates: 16 kHz, 32 kHz, 48 kHz.
- Builder-style wrapper for simple integration: optional HPF, initial delay
hint, custom
EchoCanceller3Config. - Small, dependency-light API intended for embedding in VoIP apps.
Quick start (development)
- Build and run the karaoke example (loopback + microphone). On Windows PowerShell:
cargo run --example karaoke_loopback
- Run the test-suite (unit + integration):
cargo test
Using the VOIP wrapper
The VoipAec3 wrapper is the recommended way to integrate AEC3 into a
real-time pipeline. It handles conversion between interleaved frame buffers
and the internal multi-band audio buffers, applies an optional high-pass
filter, and exposes a small set of methods mirroring the reference demo.
Example (synchronous caller):
use VoipAec3;
let mut pipeline = builder
.initial_delay_ms
.enable_high_pass
.build
.expect;
// Per 10 ms captured frame (interleaved f32 samples):
let capture_frame: = /* filled by your capture callback */;
let render_frame: = /* optional far-end data */;
let mut out = vec!;
let metrics = pipeline.process?;
println!;
Delayed-loopback example (simulating playback latency)
The examples/karaoke_loopback_delayed.rs example demonstrates how to
simulate a playback / speaker-path delay and exercise the AEC's delay
estimation and alignment logic. Important note: to simulate the real-world
behavior of a delayed speaker path you should delay the capture path (the
microphone frames) relative to the far-end reference, or provide an explicit
delay hint with set_audio_buffer_delay. Delaying the render (far-end)
reference itself will make the canceller attempt to remove audio that has
not yet occurred and prevents the filter from converging.
The example uses the wrapper's lower-level methods directly for clarity:
handle_render_frame(&mut, render_frame)— feed far-end frames as they are captured from system loopback. This keeps the reference aligned with what was actually played.process_capture_frame(&mut, capture_frame, level_change, out)— process microphone frames after they have been delayed to simulate playback latency.
High-level pattern used by the example:
- Forward loopback frames immediately into
handle_render_frame. - Buffer incoming capture frames with timestamps in the processing thread.
- Only process a capture frame once it is older than
target_delay(e.g. 20 ms). Before processing a delayed capture frame, drain any pending render frames into the AEC so the canceller sees the freshest far-end audio for that capture slot.
This preserves the proper timing relationship between far-end and near-end signals and allows the AEC to estimate and remove echo correctly.
API summary
-
VoipAec3::builder(sample_rate_hz, render_channels, capture_channels)
- .with_config(EchoCanceller3Config) — supply custom config
- .enable_high_pass(bool) — default true
- .initial_delay_ms(i32) — optional external delay hint (ms)
- .build() -> Result<VoipAec3, VoipAec3Error>
-
VoipAec3 methods
frame_samples()— samples per channel per 10 ms framesample_rate_hz()— configured sample ratehandle_render_frame(&mut self, render_frame: &[f32])— feed far-endprocess_capture_frame(&mut self, capture_frame: &[f32], level_change: bool, out: &mut [f32]) -> Result<Metrics, Error>process(&mut, capture_frame, Option<render_frame>, level_change, out)— convenienceset_audio_buffer_delay(&mut self, delay_ms: i32)— update delay hintmetrics(&self)— get current metrics
Notes and integration tips
- Frame shape: the wrapper expects interleaved f32 frames sized as
frame_samples * channels.frame_samples()returns the per-channel length for 10 ms frames (e.g. 480 for 48 kHz, 160 for 16 kHz). - Supported sample rates are intentionally gated from 16kHz to 48kHz. If you need other rates, resample before feeding frames to the wrapper.
- When you have both render and capture frames available at the same time,
prefer calling
process(capture, Some(render), ...)so the pipeline sees render first (consistent with the reference usage order).
Contributing
PRs welcome. Follow standard Rust contribution practices: ensure cargo test
passes and run cargo fmt before submitting.
Community projects
There are a few community-maintained projects that integrate with or wrap this crate. For example:
- aec3-py — a community Python wrapper for this crate: https://github.com/RustedBytes/aec3-py
If you maintain a project that uses or wraps aec3, please open a PR to add it
here so others can find it easily.
License
This repository is a port of code aligned with WebRTC reference algorithms. Adopt and/or license in accordance with your needs and the original project policy.