RNNoise 是 Xiph.Org(libopus / Vorbis 的娘家)2017 年发布的实时降噪开源实现 · 作者 Jean-Marc Valin。跟主流大模型 + 端到端路线不同 · RNNoise 是混合 DSP + 深度学习 :22 个 Bark 感知带的 gain 用 3 层 GRU 预测 · pitch filter 单独用传统 comb 滤波做谐波间去噪 · 一整个网络只有 215 单元 · 约 88K 参数 · 编译后 100 KB 量级。本文在同一套 5 case benchmark 上跑一遍 · 定位是最小可用降噪 baseline · 而不是 SOTA。
一、RNNoise 是什么
- 代码 & 权重 https://github.com/xiph/rnnoise · BSD 3-Clause · 纯 C runtime · 可编译成单个动态库
.so/.dll/.dylib· 权重 hard-code 到源码里(约 88 K 参数)· 无需外部下载 - 采样率 48 kHz 单声道 int16 · 帧长固定 480 samples · 10 ms
- 训练目标 22 个 Bark 感知带的 ideal gain(按能量比推出的幅度增益)+ 一个辅助 VAD 输出
- 训练 loss 对 gain 做感知压缩
γ=1/2后计算 MSE:L(g, ĝ) = (g^0.5 - ĝ^0.5)²· 避免大幅 gain 主导 loss - 训练数据 McGill TSP + NTT 多语种 speech(6 h)+ 环境噪声(4 h)· 随机 EQ / SNR 混合展开到 140 h · 小数据也能训动
- 延迟 20 ms 分析窗 + 50% overlap · 不需要 look-ahead · 算法延迟 10 ms
- CPU 开销 论文口径单线程 x86 CPU ~0.14% 单核占用
它是 speech-enhancement 模型 · 主要适合稳态 / 低结构背景噪声 :22 个 Bark band gain 只能整体压 / 保 · 拿不到分离流。信号处理层面 · 主干抑制由 22 个 Bark band gain 完成 · 另有基于 pitch 的传统 comb 滤波处理谐波间残留 ------ 这是它能压到 88 K 参数的关键、也是它对复杂结构化噪声(键盘瞬态 / babble / 音乐)表达力有限的根源。Bark 与 ERB 都是感知频率尺度但分带定义不同------后文 PercepNet 从 22 Bark → 30 ERB 不只是数量升级 · 是换了一套听觉尺度。
演进 :作者 Valin 后来出了 PercepNet (2020 · https://arxiv.org/abs/2008.04259) 加 pitch-aware 后置滤波 · 30 ERB 带 · 复杂度更高;再往后 LACE / NoLACE 迭代到 2023-2024。RNNoise 本身在开源侧仍是最小可行降噪的经典 baseline · PipeWire / WebRTC / Discord noise gate 类实时管线里最常见的开源实现之一。
RNNoise 的整体处理管线(DSP 主体 + RNN 只出 gain):

42 维手工特征 → 88 K RNN → 22 band gain + 1 VAD · 跟现代 SE 的输入 / 输出流最大的差别一张图看清:

核心机制:
- 42 个手工输入特征 :
22 BFCC + 6 Δ + 6 ΔΔ + 6 pitch-correlation DCT + 1 pitch period + 1 spectral variability = 42------ 全是 30 年信号处理沉淀下来的物理特征 · 不学 raw spectrum · 不学 mel。这是它体积能压到 88K 参数的关键 - 网络结构 · 3 GRU + 分支输出 · Dense tanh(24) → GRU ReLU(24) → Dense sig(1) 作为 VAD 输出 · 同时输入通过 GRU ReLU(48) 做噪声谱估计 → GRU ReLU(96) 做谱减法 → Dense sig(22) 输出 22 个 Bark band gain · 总共 4 hidden layer · 215 units · 88K params
- 两条互补分支 · RNN 分支 估计 22 个 Bark band gain 处理频谱包络;Pitch 分支 (传统 pitch analysis + comb filter · 无神经网络参与)单独处理谐波间 inter-harmonic 的残留噪声 ------ 只对语音 pitch 附近的能量做谐波间衰减 · 最后在频域合成。不是端到端网络输出 waveform · 是 RNN 只估 22 band gain · 加上传统 pitch DSP 一起在频域耦合输出
- VAD 辅助头 · 只用 24 additional weights · 作为训练期辅助任务 (training-time auxiliary loss)------ 让共享的中间 GRU 表示同时学到 speech vs noise 二分类 · 可能起正则化 / 表示学习作用;推理输出
speech_prob可顺手拿来做粗门控(需要按业务阈值校准) - 辅助 loss :VAD 用交叉熵 · gain 用感知加权 MSE
(g^0.5 - ĝ^0.5)²· noise-only 段和高频段 gain 标为 "undefined" 不参与 loss ------ 避免网络过度压制
为什么不直接用 STFT + GRU + 复数 mask:这是 2020 年之后的现代 SE 主流路线(DFN / DCCRN / FRCRN 都走这条)· 而 RNNoise 是 2017 年 · 处在深度学习刚接入 SE 的早期阶段。两条路线的对照:
| 维度 | RNNoise(手工特征 + Bark gain) | 现代 SE(STFT / ERB 特征 + 网络学 mask / filter) |
|---|---|---|
| 输入表达力 | 42 个物理特征 · 强先验 · 但表达上界低 · 音乐 / 键盘瞬态 / babble 直接翻车 | 完整复数 STFT 或 ERB 特征 · 网络自己学更高粒度表示 |
| 输出粒度 | 22 个 Bark band 实数 gain · 每帧只 22 个自由度 | STFT 频率维几百点 · 复数 mask(cIRM)或多帧复数 FIR(DF)· 表达粒度高一到两个数量级 |
| 相位处理 | RNN 只学 22 个幅度 gain · 相位不由网络重建;系统另有传统 pitch comb filter 会改动谐波间频谱细节 · 但不显式建模相位 | 网络显式学复数 mask 或多帧 FIR · 同时估计幅度和相位 · 高 SNR 段相位重建带来额外收益 |
| 训练数据 | 140 h 展开 · 小数据也能训动 | DFN 系列常用 DNS Challenge 大规模 clean speech + noise 混合;FRCRN 见对应篇 |
| 本文 5 case OVRL | 1.85-3.28 | DFN3 2.05-3.41 · FRCRN 2.76-3.47 |
RNNoise 至今没被淘汰的原因 只有一个 ------ 88 K 参数 · 100 KB 权重 · <1% 单核 CPU · 10 ms 延迟 。这些指标在本文比较的现代 SE baseline 里做不到 :DFN3 已经算轻了也要 2.3 M 参数 / 10 MB 权重 / 40 ms 延迟(参数量约 26× RNNoise · 权重体积约 100× );FRCRN 6.9 M 参数 / 55 MB 权重 / 30 ms(参数量约 78× RNNoise · 权重体积约 550× )。到嵌入式(几十 KB 权重 · 几百 KFLOPs · <20 ms 延迟)这一档 · 本文比较的 4 个模型里只有 RNNoise 能跑 (更小的方案可能存在于闭源商用 SDK 或传统 DSP · 本文不覆盖)。所以 RNNoise 是极致约束下的最优解 · 不是通用最优 ------ 大多数 RTC / 服务器场景(几 MB 权重能接受 · 40 ms 延迟能接受)· 可以优先考虑 STFT / ERB 特征 + 网络学 mask / filter 这条路线。
二、Python 与 CLI 两种用法
安装(命令示意 · pip 包装了 C 实现的 ctypes wrapper):
pip install pyrnnoise
方式 1 · CLI (rnnoise_demo 是原始 C repo 提供的命令行 · 读 raw pcm 输出 raw pcm · 需要用户自己 wrap 上 WAV header):
# 从原始 C repo 编译 · 输入输出都是 48 kHz mono 16-bit LE PCM
./rnnoise_demo in.raw out.raw
pip 装完的 pyrnnoise 提供的 CLI 是 Python 封装 · 但底层依赖 audiolab 有版本冲突(本文实测 numpy 2.x 下 av.option API 不兼容)· 落地不建议走 CLI · 直接调 Python。
方式 2 · Python API (本文实测走这条 · 通过原始 ctypes 绕过 pyrnnoise 上层的 audiolab 依赖 · 用的是 pyrnnoise==<版本> 内部 wrapper · 不是公开稳定 API · 跨版本可能失效):
import importlib.util
import numpy as np
import soundfile as sf
import librosa
# 直接 load ctypes wrapper · 跳过依赖有问题的 __init__.py
spec = importlib.util.spec_from_file_location(
"rn", "/path/to/site-packages/pyrnnoise/rnnoise.py"
)
rn = importlib.util.module_from_spec(spec); spec.loader.exec_module(rn)
FRAME = rn.FRAME_SIZE # 480 · 10 ms @ 48 kHz
SR = rn.SAMPLE_RATE # 48000
audio, sr = sf.read("audio.wav", dtype="float32", always_2d=True)
audio = audio.mean(-1) # 混单声道
if sr != SR:
audio = librosa.resample(audio, orig_sr=sr, target_sr=SR)
audio_i16 = (audio * 32767).clip(-32768, 32767).astype(np.int16)
state = rn.create()
out = []
for i in range(len(audio_i16) // FRAME):
frame = audio_i16[i*FRAME:(i+1)*FRAME]
proc, speech_prob = rn.process_mono_frame(state, frame)
out.append(proc)
rn.destroy(state)
denoised = np.concatenate(out).astype(np.float32) / 32767.0
sf.write("out.wav", denoised, SR)
几点工程侧要点:
- 10 ms 单帧处理 · 天然流式 · 每个 frame 是 480 samples · 逐 frame 前向 · 中间维护 GRU hidden state · 天然是 online causal · 算法延迟 10 ms(20 ms 分析窗 - 10 ms hop = 10 ms 一进一出的延迟)· 助听器 / 双向 walkie-talkie 这类 <20 ms 硬约束场景合适
- 输出附带 VAD 概率 · 每帧
speech_prob∈ 0, 1 · 可做粗门控(比如触发下游 ASR)· 但需要按业务阈值校准 · 本文 case02 白噪场景实测均值 0.674 · 存在误判倾向 · 别当精确 VAD 用 - 无 GPU · 无 Python torch 依赖 · 底层
.so是纯 C · 一个 20 s 音频本文实测远快于实时(Python 端到端 · 含 wrapper 开销 · 未严格 profile)· 部署侧只需要 100 KB 权重 + libc - 对训练分布外的噪声几乎没有泛化 · 22 Bark band gain 表达能力有限 · 遇到复杂结构化噪声(比如键盘瞬态 / babble)会显著降级
三、5 条 case · 降噪前后
沿用同一套 benchmark(benchmark/*.wav)· 同一套 DNSMOS 打分口径。注意 RNNoise 输出是 48 kHz · DNSMOS 计算前 downsample 到 16 kHz。
RNNoise 是单路输出 SE 模型------只出一路 denoised speech · 没有分离出的 no_speech / noise stem。所以下面每条 case 只放「降噪前 / 降噪后」两路对比 · 频谱图也是两栏。
| # | case(场景) | DNSMOS OVRL 前 → 后 | BAK 前 → 后 | 判决 |
|---|---|---|---|---|
| 01 | 加州旅馆(音乐 BGM) | 1.12 → 1.85 (+0.73) | 1.14 → 3.25 (+2.11) | ✅ 大幅提升 · 音乐部分有明显压制痕迹 |
| 02 | 白噪(稳态宽带) | 2.10 → 3.28 (+1.18) | 2.01 → 4.09 (+2.08) | ✅ 大幅提升 · 稳态噪声主场 |
| 03 | 键盘打字(瞬态噪声) | 2.17 → 3.18 (+1.01) | 1.90 → 4.06 (+2.16) | ⚠️ BAK 高 · 但键盘瞬态有可听残留 |
| 04 | 会议室(室内环境混合) | 2.12 → 2.97 (+0.85) | 2.14 → 3.97 (+1.83) | ✅ 明显提升 |
| 05 | 餐厅 babble(多说话人混叠) | 2.17 → 2.75 (+0.58) | 2.19 → 3.88 (+1.69) | ⚠️ BAK 提升是把 babble 压掉 · 目标说话人不保证 |
一句话结论 :5 条 case 里 5 条 BAK 都有 +1.7 到 +2.2 分的显著提升 · 后值全部落在 3.2-4.1 区间 ------ 对一个 88 K 参数 · 100 KB 编译体积的模型来说是不错的表现。case01 音乐场景压制过头 · 副歌段人声被削得很薄;case03 键盘瞬态处理不干净 · 频谱能看到打字瞬间的残留 ------ 22 Bark band gain 对亚帧级瞬态响应慢。case05 餐厅 babble 场景不真正分离说话人 · 而是按能量把非主导说话人一起压掉。
3.1 case01 · 加州旅馆 · BGM + vocals 混合
降噪前:
降噪后:

听感:intro 段前奏 + 引入的 vocals 一起被大量削减 · 语音剪切严重、能量断续 ------ 22 Bark band 把整段音乐当作稳态噪声压 · 副歌人声再度进来时也没有恢复。RNNoise 训练分布里没有音乐 · 遇到 non-speech 结构化谐波会激进压制。
3.2 case02 · 白噪 · 稳态宽带噪声
降噪前:
降噪后:

看点:宽带底噪整体下移 · speech 谐波保留清晰 · 稳态噪声是 22 Bark band gain 的舒适区。
3.3 case03 · 键盘打字 · 瞬态噪声
降噪前:
降噪后:

看点 :键盘敲击的宽带瞬态被削弱但仍有可闻残留------10 ms 帧 + Bark band gain 对亚帧级瞬态响应慢 · 频谱图上打字瞬间的短竖线没完全压干净。
直观看 Bark gain 的时变模式 (玩具数据示意 · 不来自 RNNoise 内部 trace · 颜色和数字都只是为了展示可能模式 )------ 网络对 speech 段通常保留、对 pause 段大幅压低、遇到瞬态噪声时高频压得比低频狠 · VAD 副产物在瞬态段可能被误判为中等 probability。要拿到真实 gain 数值需要 hook xiph/rnnoise C repo 的 compute_rnn() 中间层 · 本文不覆盖:

3.4 case04 · 会议室 · 室内环境混合
降噪前:
降噪后:

看点:低频环境噪底 + 空调 hum 明显下降 · speech 保留清晰 · 环境类近稳态噪声在 RNNoise 上也有效。
3.5 case05 · 餐厅 babble · 多说话人混叠
降噪前:
降噪后:

看点 :主导说话人保留 · 但 babble 压得不干净(Bark band gain 对多个说话人混叠的谐波结构分辨力有限)· 目标说话人不占优时会翻车。
四、四模型对比 · RNNoise 适合什么场景
下面是全系列 4 篇公用的一张 DNSMOS 对比图 · 5 case × 4 模型(htdemucs / DFN3 / RNNoise / FRCRN)· 每篇都放这张 · 别的模型的具体行为详见对应篇。

RNNoise 在这套 benchmark 上的落点:
- case02 白噪 · OVRL 3.28 · case03 键盘 · 3.18 · case04 会议室 · 2.97 ------ 3 条稳态 / 瞬态 / 环境 case 都在 3.0 附近或以上 · 是 4 个模型里最「够用」的最小方案
- case05 餐厅 babble · OVRL 2.75 ------ 能压 babble 但 BAK 3.88 比 DFN3/FRCRN 略低 · 按能量选主
- case01 音乐 BGM · OVRL 1.85 ------ 4 个模型里最低 · 22 Bark band 表达力不足以处理音乐 · 副歌段压制过头
- 整体分数 普遍比 DFN3 / FRCRN 略低 ------ 但参数量差 25×(88K vs 2.3M / 6.9M)· 权重体积差 100×(100 KB vs 10 MB / 55 MB)
RNNoise 适合:
- 端侧极致轻量场景 ------ <100 KB weights · <1% CPU · <20 ms latency · 视频会议 / 通话 / WebRTC / Discord noise gate / 麦克风驱动层至今常见默认
- 助听器 / 双向 walkie-talkie 这类 <20 ms 硬约束场景 ------ 10 ms 算法延迟 · 是 4 个模型里唯一能进这个区间的
- 稳态背景噪声主场 ------ case02 白噪 / case04 会议室这类环境噪声上分数够用
- 需要免费 VAD 的场景 ------ 每帧
speech_prob拿来做粗粒度门控
RNNoise 不适合:
- 音乐场景剥人声 ------ case01 分数明显低 · 副歌段压制过头
- 瞬态噪声(键盘 / 敲击 · 见 case03 频谱残留)· 10 ms 帧 + Bark band gain 对亚帧级瞬态响应慢
- 听感极致自然 ------ 22 Bark band 硬拉低会出「频谱空洞感」
- 多说话人 babble 要指定目标 ------ 按能量选主 · 需要 TSE 类
五、写在最后
RNNoise 是小模型也能做实用降噪的经典证明 ------ 用 30 年 DSP 沉淀(Bark scale / BFCC / pitch analysis / comb filter)打底 · 只用 GRU 学最难手工调的那部分(每帧 22 个 band 该给多少 gain)· 换来 100 KB 的编译体积和 10 ms 延迟。这是端侧 real-time 场景(视频会议 · 通话 · WebRTC · Discord noise gate · 麦克风驱动层)至今仍常见的默认选择。
工程侧几个提醒:RNNoise 输出的每帧 speech_prob 是免费的 VAD (case02 白噪场景本文实测均值 0.674 · 说明网络在纯噪声段也倾向于误判为 speech ------ VAD 输出适合做「有语音时激活下游 ASR」这类粗粒度门控 · 别当作精确 VAD 用)。观察都基于 5 条样本 · 落地前用自己业务的一批 case 验证。