A PipeWire SPA acoustic echo cancellation (AEC) plugin backed by the DTLN-aec neural network model.
The plugin is fixed to 16 kHz mono with a 128-sample quantum. It runs inference via the TensorFlow Lite C API with FFTW for the FFT operations. Pre-trained DTLN-aec models in 128-, 256-, and 512-unit variants are downloaded at build time; the active model pair is selected via the PipeWire configuration.
After playing with spa-aec-speex I wanted to find something more modern, and stumbled upon the DTLN-aec. Unlike Speex or WebRTC, the pretrained models are limited to 16 kHz audio. This should not be an issue for most voice applications, but it adds resampling overhead to the PipeWire pipeline. Beware of imposing any additional sampling or quantum constraints to your pipeline; it may introduce a significant overhead (either due to polling / interrupt handling or expensive oversampling).
The results were subjectively better than both Speex and WebRTC in my testing, but at a CPU cost. PipeWire eats up over 20% of a core (Ryzen 5 5600X) on my system when I use the largest model. You may experiment with smaller models to save on CPU cycles.
After fighting with TensorFlow Lite and its (arguably broken) build scripts, I opted for using Conan that has a patched version that builds properly.
An AI aided the creation of this project. While I did my best reviewing and testing it, there is always a chance I've missed something.
The code was inspired by the two existing AEC plugins in the PipeWire (WebRTC and the "null" one).
Be aware of security implications of running third-party software, including untrusted models.
| Tool | Notes |
|---|---|
| CMake ≥ 3.16 | Build system |
| Conan 2.x | C++ package manager (pip install conan, or use your distribution package manager) |
| PkgConfig | For PipeWire headers |
| C/C++ compiler | gcc or clang |
Conan manages:
tensorflow-lite/2.15.0— inference enginefftw/3.3.10— single-precision FFT
PipeWire development headers (libpipewire-0.3-dev or equivalent) must be present at build time and are found via PkgConfig.
| Package | Notes |
|---|---|
| PipeWire | With the echo-cancel module enabled |
| DTLN-aec models | one of the dtln_aec_{128,256,512}_{1,2}.tflite pairs |
conan profile detectconan install . --build=missingThis downloads and builds the Conan packages into build/ and generates CMake preset files under build/Release/generators/.
cmake --preset conan-release -DCMAKE_INSTALL_PREFIX=/usr
cmake --build --preset conan-releaseThe default install prefix (if omitted) is /usr/local.
The preset is provided by CMakeUserPresets.json, which includes the Conan-generated preset file.
sudo cmake --install build/ReleaseOn Fedora/RHEL the build detects that /usr/lib64 is a real directory and automatically uses lib64 instead of Conan's default of lib.
Installed paths:
- Plugin:
${libdir}/spa-0.2/aec/libspa-aec-dtln.so - Models:
${datadir}/spa-dtln/dtln_aec_{128,256,512}_{1,2}.tflite
By default, CMake downloads all three model sizes from the DTLN-aec GitHub repository during configuration:
| Size | Files | Notes |
|---|---|---|
| 128-unit | dtln_aec_128_{1,2}.tflite |
Lowest CPU usage |
| 256-unit | dtln_aec_256_{1,2}.tflite |
Middle ground |
| 512-unit | dtln_aec_512_{1,2}.tflite |
Best quality (default in config) |
All downloaded files are verified against embedded SHA-256 hashes. The build stops with a fatal error if a hash does not match, protecting against a compromised upstream repository.
To pin downloads to a specific upstream commit (recommended for production):
cmake --preset conan-release -DDTLN_GIT_REF=<commit-sha>To update the pin: set DTLN_GIT_REF to the new commit SHA and update the six DTLN_MODEL_HASH_* variables in CMakeLists.txt to match the new files.
To skip the download entirely (e.g. on an air-gapped system), place the .tflite files in the build/Release directory and pass -DDTLN_DOWNLOAD_MODELS=OFF with your cmake configuration options.
Copy or symlink 60-aec-dtln.conf to your PipeWire configuration directory:
mkdir -p ~/.config/pipewire/pipewire.conf.d
cp 60-aec-dtln.conf ~/.config/pipewire/pipewire.conf.d/Update the aec.args block in case you used a different install prefix or want to experiment with a different set of models. Both dtln.model.1 and dtln.model.2 are required; the plugin will refuse to initialize and log an error if either is absent.
MIT — see the SPDX headers in each source file.