音频降噪实战:ffmpeg 滤镜、RNNoise 与人声分离方案对比
凌晨一点,你终于补完了那期口播,回放时才发现:笔记本风扇的呼呼声比你的声音还清楚,窗外空调外机嗡嗡作响,楼下夜宵摊的嘈杂隐约飘了进来。你戴着耳机听了三遍,每听一遍心凉一截——重录是不可能的,室友已经睡了,明天一早就要交片。
别慌,这篇文章就解决这个场景:不重录,用后期手段把音频救回来。我们把三条能真正落地的路线——ffmpeg 传统滤镜、RNNoise 神经网络降噪、Demucs/Spleeter 人声分离——放到一起对比,讲清楚各自的原理、适用边界和可以直接运行的命令,你按自己的噪声类型对号入座即可。
📑 文章目录
- 一、先搞清楚你的噪声是哪一种
- 二、ffmpeg 自带滤镜:afftdn、anlmdn 与 highpass
- 三、RNNoise:几百 KB 的神经网络降噪器
- 四、人声分离:Demucs 与 Spleeter
- 五、批量处理与工作流整合
- 常见问题 FAQ
- 总结与参考文献
一、🎧 先搞清楚你的噪声是哪一种
降噪这件事,七分靠判断,三分靠命令。动手之前先花一分钟听一遍素材、看一眼波形,把噪声归类,比直接套滤镜重要得多——同一个参数,在风扇底噪上是救星,在键盘敲击声上就是灾难。
| 噪声类型 | 典型来源 | 频谱特征 | 推荐对策 |
|---|---|---|---|
| 稳态噪声 | 风扇、空调、电流底噪 | 能量集中在固定频段,随时间变化小 | ffmpeg afftdn / anlmdn、RNNoise |
| 瞬态噪声 | 键盘、鼠标点击、爆音 | 持续时间极短,随机出现 | 人声分离兜底,或剪辑软件逐个修复 |
| 宽带背景声 | 街道、咖啡馆、风声 | 频谱与人声大面积重叠 | RNNoise、Demucs 人声分离 |
| 房间混响 | 空旷房间反射 | 人声拖尾,能量分散 | 后期较难根除,优先改善录音环境(近讲、吸音) |
判断方法很朴素:把波形放大,找一段"没有人说话"的空白区间——一条平稳的毛毯状曲线是稳态噪声;一根根突然窜起的细刺是瞬态噪声;波形看着正常但听着脏,大概率是宽带背景声。肉眼拿不准时,可以用ffmpeg -i noisy.wav -lavfi showspectrumpic=s=800x400 spectrum.png导出一张频谱图,噪声能量聚集的频段在图上一目了然。
💡思考:为什么没有一个大一统的滤镜能去掉所有噪声?
🤔解答:降噪的本质是从带噪信号里估计出干净信号,可利用的只有噪声与人声在时频域上的统计差异。稳态噪声频谱固定,容易被建模和减除;而键盘声这类瞬态噪声与语音辅音在时频结构上高度相似,一刀切必然伤到人声。所以工程上的正确姿势是"先分类、再选工具",而不是迷信某个万能参数。
二、🧰 ffmpeg 自带滤镜:afftdn、anlmdn 与 highpass
ffmpeg 的音频滤镜库是命令行玩家的入门利器:不用装额外依赖,处理速度快,还方便嵌进自动化脚本。降噪常用的就三个:
- afftdn:基于 FFT 的频域降噪,把信号变换到频域后估计噪声底频谱,再做谱减;
- anlmdn:自适应非局部均值降噪,把音频切成小块,在时间轴上寻找相似片段来估计噪声,对细密的稳态底噪效果好;
- highpass:高通滤波,严格说不算降噪,但几乎是降噪的前置动作——人声基频基本在 85 Hz 以上,先把 80 Hz 以下的低频隆隆声滤掉,能给后续滤镜一个更干净的输入。
# 组合拳:高通滤掉低频隆隆声,再用 afftdn 压稳态噪声ffmpeg-inoisy.wav-af"highpass=f=80,afftdn=nr=18:nf=-40:tn=1"denoised.wav# 用开头的纯噪声段采样噪声底(前 2 秒无人说话),官方文档同款写法ffmpeg-inoisy.wav-af"asendcmd=0.0 afftdn sn start,asendcmd=2.0 afftdn sn stop,afftdn=nr=20:nf=-40"denoised.wav# anlmdn:细密底噪场景ffmpeg-inoisy.wav-af"highpass=f=80,anlmdn=s=0.00002"denoised.wav参数说明整理成表格:
| 滤镜 | 关键参数 | 含义 | 调参建议 |
|---|---|---|---|
| afftdn | nr | 降噪量,单位 dB,取值 0.01~97 | 从 12 起步,每次加 4~6 对比试听,超过 24 容易出"水声" |
| afftdn | nf | 噪声底阈值,单位 dB,取值 -80~-20 | 默认 -50,底噪较大时可上提到 -40 左右 |
| afftdn | tn | 是否跟踪噪声变化 | 风扇变速等噪声强度波动场景置 1 |
| anlmdn | s | 降噪强度,默认 0.00001 | 数值越大压得越狠,代价是语音细节损失 |
| anlmdn | p | patch 块大小,默认 7 | 一般不动,动之前先读官方文档 |
| highpass | f | 截止频率,单位 Hz | 口播人声 80~100,注意别把男声基频切掉 |
💡思考:nr 是不是越大越干净?
🤔解答:不是。nr 是"减去多少估计出来的噪声",减多了会把人声高频一起减掉,听感像隔着一层水说话,业内叫"水声伪影"。实用做法是导出 nr=12 / 18 / 24 三版,用耳机在同一位置反复 A/B 对比,选听不出水声的档位里数值偏高的那个。文档写得再细,也代替不了自己的耳朵——降噪参数这件事,试听才是验收标准。
另外 ffmpeg 还有 arnndn 滤镜,可以直接加载 RNNoise 导出的模型文件,把神经网络降噪整合进命令行,代价是 ffmpeg 编译时要开启 librnnoise 支持。
三、🧠 RNNoise:几百 KB 的神经网络降噪器
RNNoise 是 Xiph.Org 开源的神经网络降噪库,思路与传统谱减完全不同:它用门控循环单元(GRU)在 48kHz 音频的约 22 个 Bark 频带上直接预测"每个频带该保留多少增益",模型小到几百 KB,CPU 上实时处理毫无压力。对街道、咖啡馆这类与人声纠缠的宽带背景声,它通常比 afftdn 好一档。
它的发行形态是一个 C 库,源码里附带一个 rnnoise_demo 示例程序,只吃 48kHz、单声道、16 位有符号整数的裸 PCM,所以经典用法是用 sox 做格式转换,再用管道串起来:
# 在 rnnoise 源码目录编译示例程序(仓库地址见文末参考文献)./autogen.sh&&./configure&&make# 管道三连:sox 转 raw → rnnoise_demo 降噪 → sox 封回 wavsox noisy.wav-r48000-b16-c1-esigned-traw -\|./examples/rnnoise_demo - -\|sox-traw-r48000-b16-c1-esigned - clean.wav两个注意点:一是链路里两处 sox 的采样率、位深、声道数必须严格一致,任何一处不一致,raw 流的字节边界就错位了,听感上是变速或满屏杂音;二是素材若不是 48kHz,sox 会自动重采样,有一点音质代价但可以接受。关于采样率与音频上下文的基础概念,MDN 的 Web Audio 文档里有清晰的解释。
如果你的 ffmpeg 编译时开启了 librnnoise 支持,也可以用 arnndn 滤镜直接加载模型,省掉 sox 管道:ffmpeg -i noisy.wav -af arnndn=model=rnnoise.rnnn out.wav。代价是模型文件需要另行编译生成,所以通用性上,sox 管道方案反而更省心。
💡思考:RNNoise 效果好,能直接取代 ffmpeg 滤镜吗?
🤔解答:分场景。RNNoise 是为"人声语音 + 背景噪声"训练的,一旦目标声音不是人声(比如乐器、ASMR 环境声),它可能反过来把想要的声音当噪声压掉。它也没有 afftdn 那种按时间区间采样噪声底的灵活度。实战中两者经常串联:RNNoise 先粗降噪,afftdn 用小参数做二次清理。
四、🎤 人声分离:Demucs 与 Spleeter
当噪声和人声在频谱上纠缠到分不开时,换思路:不做减法,改做分离——让模型把人声和伴奏/背景拆成两条音轨,取人声轨,背景噪声随伴奏一起被剥离。
Demucs是 Meta 开源的音源分离模型,htdemucs 权重的分离质量很能打;Spleeter是 Deezer 开源的老牌方案,基于预训练频谱掩码模型,CPU 上也跑得快。两者都是 pip 安装即用:
# Demucs:两轨分离,只要人声和伴奏pipinstalldemucs demucs --two-stems=vocals-nhtdemucs-ostems/ song.mp3# 结果在 stems/htdemucs/song/vocals.wav 与 no_vocals.wav# Spleeter:预训练两轨模型pipinstallspleeter spleeter separate-pspleeter:2stems-ostems/ song.mp3# 结果在 stems/song/vocals.wav 与 accompaniment.wav补充两点:Demucs 默认做四轨分离(人声、鼓、贝斯、其他),换-n htdemucs_6s可以扩展到六轨(增加钢琴、吉他);但它没有专门的"混响轨",前文表格里的房间混响,模型帮不上太多忙,仍要靠录音端控制。Spleeter 则提供 2stems / 4stems / 5stems 三档预训练模型,按需选择即可。
三条路线到这里都亮相了,做一张选型总览表:
| 维度 | ffmpeg 滤镜 | RNNoise | Demucs / Spleeter |
|---|---|---|---|
| 原理 | 频域谱减 / 非局部均值 | GRU 预测频带增益 | 深度模型做音源分离 |
| 适用场景 | 稳态底噪、低频隆隆声 | 语音 + 宽带背景噪声 | 噪声与伴奏、BGM 混杂 |
| 计算成本 | 低,可实时、可批量 | 低,可实时 | 高,GPU 明显提速 |
| 上手成本 | 一条命令,参数直观 | 需编译,注意格式链路 | pip 安装即用,模型自动下载 |
| 音质风险 | 过调出"水声" | 非人声素材被误伤 | 人声可能带轻微金属感 |
💡思考:分离出来的人声,为什么建议再过一遍轻量降噪?
🤔解答:分离模型理解的"人声轨"包含呼吸声、齿音,以及渗进来的少量背景残余。用 afftdn 以 nr=10~14 的小参数过一遍,可以把残余压得更干净,小参数几乎不伤音质。顺序别反:先分离、后轻降噪、再做响度归一。
五、⚙️ 批量处理与工作流整合
单个文件跑通之后,真实需求往往是"一期节目 40 条素材"。把命令固化成脚本,才能从救火走向流水线,这也是命令行方案相对图形软件的核心优势。
先来一个 bash 版批量降噪,思路很简单:循环、加滤镜、输出到子目录,绝不覆盖原始素材——原始文件永远是你的后悔药:
#!/usr/bin/env bash# batch_denoise.sh:对当前目录所有 wav 应用高通 + afftdnmkdir-pdenoisedforfin*.wav;doffmpeg-y-i"$f"-af"highpass=f=80,afftdn=nr=16:nf=-42:tn=1""denoised/$f"echo"done:$f"done再进一步,用 Python 串起"人声分离 + 二次降噪"两道工序:
importsubprocessfrompathlibimportPath SRC=Path("raw_audio")# 原始素材目录OUT=Path("stems")# 输出目录OUT.mkdir(exist_ok=True)forfinsorted(SRC.glob("*.mp3")):# 先做两轨分离subprocess.run(["demucs","--two-stems=vocals","-n","htdemucs","-o",str(OUT),str(f)],check=True,)vocal=OUT/"htdemucs"/f.stem/"vocals.wav"# 再做轻量二次降噪 + 响度归一,统一到 48kHzsubprocess.run(["ffmpeg","-y","-i",str(vocal),"-af","highpass=f=80,afftdn=nr=12,loudnorm=I=-16:TP=-1.5","-ar","48000",str(OUT/f"{f.stem}_clean.wav")],check=True,)print(f"processed:{f.name}")两点提醒:批量处理前先用 2~3 个文件试跑,确认参数没有"水声"再全量执行,返工成本会低很多;另外,如果素材来自你自己下载或采集的音视频,请注意——仅供个人学习使用,请遵守原平台版权规则。
还有一步容易漏:交付前做响度归一。多素材拼接时,一条轻一条响的听感非常影响成片质量,可以统一到平台常见的 -16 LUFS:
ffmpeg-iclean.wav-af"loudnorm=I=-16:TP=-1.5:LRA=11"final.wav还有个容易被忽视的环节:处理完的音频会很快堆积成denoised_0912、final_v2这样一串目录,事后找起来相当痛苦。我在自己的流程里会把处理过的音频和原始视频放进素材库统一管理、按标签筛选回溯,比如下面这种按标签归档的界面,找上个月处理过的人声素材只需要一次筛选:
常见问题 FAQ
Q1:afftdn 处理后人声有"水下感",怎么救?
直觉是降 nr,但更有效的是检查 nf 与 tn:噪声底波动大时 tn=1 会让估计更贴近实际;如果仍不满意,换 anlmdn 或直接上 RNNoise——谱减类滤镜的固有伪影,只能靠换算法解决。
Q2:rnnoise_demo 输出的音频变速或全是杂音?
检查链路两端 sox 的参数是否完全一致:采样率必须是 48000、单声道、16 位、有符号整数。任何一项对不上,raw 流的字节边界就错位了。
Q3:Demucs 分离的人声里还残留伴奏,怎么办?
试试加--shifts 2(多次移位平均,速度减半但残余更少),或换更新的模型权重;残留集中在低频时,补一个highpass=f=100常有明显改善。原曲混响很重的情况下,接受一定残余更现实。
Q4:降噪应该放在剪辑流程的哪一步?
推荐顺序:降噪 → EQ/音色调整 → 压缩 → 响度归一。降噪必须放在压缩前面——压缩器会抬高噪声底,之后再降噪就得下狠参数,音质损失更大。响度归一用前面提到的 loudnorm 即可,口播类内容 I 设 -16,音乐向内容可放宽到 -14,TP 建议控制在 -1.5 以内防止削波。
Q5:手机户外录音还有救吗?
风噪用高通能压一部分;手机 AGC 自动增益造成的音量忽大忽小,降噪工具管不了,得靠压缩器反向平滑。硬件层面的缺陷,后期只能缓解、不能根治——预算允许时,一支领夹麦带来的提升超过一切后期手段。
Q6:为什么同一套参数,换个素材效果就天差地别?
因为噪声底不一样。afftdn 的降噪强度建立在"噪声估计"之上:素材 A 的底噪是空调的稳态低频,素材 B 是咖啡馆里此起彼伏的交谈声,两者在时频域的统计特征完全不同,同一组 nr/nf 参数自然水土不服。正确的习惯是每个新场景都重新采样噪声底、重新试听验收,而不是把某篇教程里的参数当成万能钥匙——参数是死的,噪声底是活的,你的耳朵才是最终的裁判。
总结
回顾三条路线:稳态噪声交给 ffmpeg 的 afftdn/anlmdn 加 highpass,宽带背景声交给 RNNoise,人声与伴奏纠缠的场景交给 Demucs/Spleeter,最后用批量脚本把流程固化。选型的核心不是哪个更强,而是搞清楚你的噪声属于哪一类。
说点题外话。我之所以持续折腾这些命令行工具,是因为见过太多认真做内容的人,被一段"听不清"的音频劝退——设备不贵、环境不好,都不该成为表达的门槛。音频这东西,观众未必说得出好坏,但身体很诚实:听三秒难受,就划走了。把声音弄干净,是对听众时间的尊重,也是内容可信度里成本很低的一环。
顺带一提,我自己在做一款叫「影栈」的素材库工具,支持把视频素材一键提取为 MP3 音频,提取出来的音频正好可以丢进本文的降噪流水线。后续我会在 CSDN 持续更新这款工具的实战记录,感兴趣的可以关注我的博客主页。
参考文献
- FFmpeg Filters Documentation(afftdn / anlmdn / highpass / arnndn 官方滤镜文档):https://ffmpeg.org/ffmpeg-filters.html
- FFmpeg 官方文档索引:https://ffmpeg.org/documentation.html
- RNNoise 源码仓库(Xiph.Org):https://github.com/xiph/rnnoise
- Demucs 源码仓库(Meta):https://github.com/adefossez/demucs
- Spleeter 源码仓库(Deezer):https://github.com/deezer/spleeter
- MDN Web Audio API(采样率与音频上下文概念):https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API