VoiceBlender v0.15.0 has a second noise suppression filter, denoise_gtcrn.
It sits next to the RNNoise-based denoise filter from
the previous post, and you pick
one or the other per leg.
It is built on gtcrn-go, our pure-Go port of GTCRN by Xiaobin Rong and contributors (ICASSP 2024, “GTCRN: A Speech Enhancement Model Requiring Ultralow Computational Resources”). Like rnnoise-go it needs no cgo, and it is MIT-licensed like upstream.
Why a second denoiser
RNNoise works well, but its current model is not small. The weights are 3.4 MB, and every 10 ms frame reads most of them. It is also built for 48 kHz audio, and we had to teach it to run at telephony rates.
GTCRN comes from the other direction. It was designed for 16 kHz speech and for very small devices: 48,000 parameters and 206 KB of weights, about 17 times smaller. Most calls through VoiceBlender are 8 or 16 kHz, so a model made for that range was worth porting.
The port runs upstream’s causal streaming model and matches the PyTorch original to about 130 dB. It runs natively at 8, 12 and 16 kHz with no resampling. On 8 kHz telephony audio, running natively scores within 0.02 PESQ of resampling to 16 kHz and back.
Where GTCRN is better
CPU. Same machine as before (AMD Ryzen 9 7900, AVX2), cost per 10 ms of audio:
| µs per 10 ms, one stream | calls per core, CPU fully loaded | |
|---|---|---|
| RNNoise, 8 kHz | ~56 | ~144 |
| RNNoise, 16 kHz | ~63 | ~130 |
| GTCRN, 8 kHz | ~24 | ~330 |
| GTCRN, 16 kHz | ~26 | ~310 |
That is about 2.4 times as many denoised calls on the same hardware.
Noise removal. On ten speech clips mixed with call-centre and street noise at 0, 5 and 10 dB SNR:
| PESQ | STOI | SI-SDR | |
|---|---|---|---|
| 8 kHz, RNNoise | 2.63 | 0.930 | 9.6 dB |
| 8 kHz, GTCRN | 2.66 | 0.922 | 11.3 dB |
| 16 kHz, RNNoise | 1.63 | 0.928 | 9.1 dB |
| 16 kHz, GTCRN | 1.79 | 0.923 | 11.1 dB |
GTCRN leaves about 2 dB less noise and distortion in the output (SI-SDR), and scores higher on perceived quality (PESQ), most clearly at 16 kHz. RNNoise is slightly ahead on intelligibility (STOI). This is a small test set, so read it as a direction, not a final score.
Where RNNoise is still better
48 kHz audio. GTCRN has no trained weights above 8 kHz of audio bandwidth. In a 48 kHz room, VoiceBlender converts the leg to 16 kHz, filters it, and converts it back, which removes everything above 8 kHz. That costs nothing for G.711, G.722 or AMR callers, who have nothing up there anyway. For fullband Opus, WebRTC or LiveKit legs it removes the top of the voice. RNNoise runs at 48 kHz and keeps the full band.
AMD in very heavy noise. We ran answering-machine detection on the same noisy human greetings as in the first post (46 per row, correct answer is always “human”):
| noise | SNR | no filter | RNNoise | GTCRN |
|---|---|---|---|---|
| busy city | 15 dB | 6.5% | 100% | 100% |
| busy city | 5 dB | 0% | 100% | 100% |
| busy city | 0 dB | 0% | 100% | 100% |
| busy city | −5 dB | 0% | 91.3% | 84.8% |
| busy city | −10 dB | 0% | 63.0% | 45.7% |
| office | 15 dB | 0% | 100% | 100% |
| office | 5 dB | 0% | 100% | 93.5% |
| office | 0 dB | 0% | 89.1% | 73.9% |
| office | −5 dB | 0% | 65.2% | 47.8% |
| office | −10 dB | 0% | 39.1% | 34.8% |
At ordinary noise levels both denoisers fix AMD completely. Once the noise is about as loud as the voice, RNNoise holds on for longer, especially with office noise. So better scores on audio quality did not mean better AMD results here.
(These files use different random noise segments from the sweep in the first post, so the RNNoise column differs a little from that table.)
Which one to use
- Narrowband or wideband telephony, many calls, CPU matters: use
denoise_gtcrn. It is cheaper and cleans the audio more. - 48 kHz rooms with fullband sources (WebRTC, Opus, LiveKit): use
denoise. - AMD on calls from very noisy places: use
denoise.
Latency is about the same: a 16 ms model delay for GTCRN and 20 ms for RNNoise.
curl -X POST http://localhost:8080/v1/legs \
-H 'Content-Type: application/json' \
-d '{
"type": "sip",
"to": "sip:+15551234567@carrier.example.com",
"filters": [{"type": "denoise_gtcrn"}]
}'
AUDIO_FILTERS=denoise_gtcrn sets it for every new leg, and
PUT /v1/legs/{id}/filters switches between the two during a call. A chain
with both is rejected.
gtcrn-go also works on its own, as a library or from the command line:
go install github.com/VoiceBlender/gtcrn-go/cmd/gtcrn@latest
gtcrn noisy.wav clean.wav
Thanks to Xiaobin Rong and the GTCRN contributors for the network and the trained weights.