语音合成技术发展到现在,音色和韵律已经相当接近真人,但不少合成结果里总能听到一层薄薄的沙沙声,安静环境下尤其明显。这类底噪的来源往往不是单一环节的问题,而是合成链路中多个微小误差叠加的结果。要彻底解决它,除了在模型侧优化,工程上更实用的做法是在合成输出之后加一层后处理,用滤波和频谱修复手段把残留噪声压下去。本文围绕这个思路展开,先定位底噪来源,再给出几套可落地的处理方案。

底噪从哪里来:先定位再处理
动手消除底噪之前,必须先弄清它的来源,否则很容易用错方法。合成语音的底噪大致可以分成三类。第一类是声码器引入的量化误差,比如基于GAN或扩散模型的神经声码器,在生成高频段时容易出现能量分布不均的情况,表现为持续的高频嘶声。第二类是采样率与重采样问题,训练数据是24kHz而推理输出16kHz时,重采样滤波器的过渡带会带来混叠噪声。第三类是训练数据本身的污染,录音环境里的空调声、电流声被模型学进去,合成结果里就带上了固定的噪声成分。
定位方法并不复杂。先取一段合成语音中没有人声的静音部分,做短时傅里叶变换观察频谱。如果噪声集中在8kHz以上的高频区,基本可以判定是声码器问题;如果在50Hz或100Hz处出现尖峰,多半是录音设备引入的工频干扰被模型学到了;如果是全频段均匀分布的能量,则可能是量化位数不足或随机噪声注入导致。搞清楚频谱特征,后面选择滤波方案时才有依据,盲目套用通用降噪算法反而会损伤语音本身。
后处理滤波:传统方法的正确打开方式
滤波是成本最低的去除手段,适合处理频谱特征固定的噪声。最直接的是设计带阻滤波器,针对50Hz工频干扰及其谐波,可以用一个陷波滤波器逐点消除。用Python的scipy库实现非常简单:
from scipy.signal import iirnotch, filtfilt
import numpy as np
import soundfile as sf
# 读取合成语音
audio, sr = sf.read('output.wav')
# 对50Hz及其谐波设置陷波,Q值为30保证窄带
filtered = audio.copy()
for freq in [50, 100, 150]:
b, a = iirnotch(freq, 30, sr)
filtered = filtfilt(b, a, filtered, axis=0)
sf.write('notch_filtered.wav', filtered, sr)注意这里使用filtfilt做零相位滤波,避免普通IIR滤波器引入相位失真导致语音听感发闷。陷波滤波器的优点是精准、几乎不损伤语音,缺点是只能处理频率固定的窄带噪声。对于宽带底噪,就需要谱减法这类基于统计的方案了。谱减法的核心假设是噪声在整个音频中平稳存在,通过静音段估计噪声谱,再从带噪语音的幅度谱中减去。它计算量小、实现简单,但处理不好会在语音中留下音乐噪声,听起来像流水声。
比谱减法更稳健的是维纳滤波,它不是简单相减,而是根据信噪比估计一个增益系数,逐帧调整频点的衰减量。信噪比高的频点几乎不衰减,信噪比低的频点衰减更多,整体听感更平滑。维纳滤波可以配合先验信噪比估计使用,对稳态底噪的抑制效果通常比朴素谱减法好3到5个分贝,且音乐噪声明显减少。在工程实践中,把陷波滤波和维纳滤波串联使用,先去窄带干扰再做宽带降噪,是一个性价比很高的组合。
频谱修复:用掩蔽与重建处理非稳态噪声
当底噪的频谱特性随时间变化,或者噪声能量与语音重叠严重时,传统滤波就显得力不从心,这时需要频谱修复技术。核心思路是在时频域上逐帧逐频点地分析,对被噪声污染的区域要么衰减(掩蔽),要么根据周围干净区域重新估计(重建)。最经典的实现是时频掩蔽,比如理想比率掩蔽IRM,它为每个时频单元计算一个0到1之间的增益,噪声主导的单元增益接近0,语音主导的单元接近1。
import numpy as np
import librosa
# 短时傅里叶变换
y, sr = librosa.load('output.wav', sr=None)
D = librosa.stft(y, n_fft=1024, hop_length=256)
mag = np.abs(D)
# 取前10帧估计噪声谱
noise_profile = np.median(mag[:, :10], axis=1, keepdims=True)
# 计算掩蔽增益,floor防止过度衰减
snr = mag / (noise_profile + 1e-8)
mask = snr / (snr + 1.0)
mask = np.maximum(mask, 0.1)
# 应用掩蔽并重构
D_clean = D * mask
y_clean = librosa.istft(D_clean, hop_length=256, length=len(y))
librosa.output.write_wav('masked.wav', y_clean, sr)上面的代码实现了一个简化版的统计掩蔽,其中mask设置下限0.1很关键。如果把噪声频点完全置零,语音会听起来断续、发空,因为这个频点上其实还残留着语音的谐波成分。保留最低10%的能量,听感自然度会好很多,这也符合人耳的听觉掩蔽效应——弱信号在强信号附近本来就不容易被察觉,过度清洗反而暴露了处理痕迹。
对于噪声特性复杂的场景,还可以借助预训练的深度学习降噪模型,比如基于RNNoise的轻量方案或者FullSubNet、DCCRN这类语音增强模型。它们在大量噪声数据上训练过,对非稳态噪声的适应能力远超传统方法,且推理开销可控,RNNoise在CPU上单帧耗时不到一毫秒,完全能满足实时合成的需求。一个实用的做法是先用轻量模型做粗降噪,再用维纳滤波做精细收尾,两层处理各自的残留问题可以互补。
方案对比与工程建议
不同方法各有取舍,下面从抑制能力、语音损伤和计算开销三个维度做个对比。陷波滤波开销最小但只针对固定频率;谱减法简单快速,可音乐噪声问题明显;维纳滤波效果均衡,是传统方法里的首选;时频掩蔽效果更好,但需要合理估计噪声谱;深度学习方案能力最强,代价是部署复杂度上升。
| 方案 | 降噪能力 | 语音损伤 | 计算开销 |
|---|---|---|---|
| 陷波滤波 | 窄带噪声强 | 极小 | 极低 |
| 谱减法 | 稳态噪声中等 | 音乐噪声 | 低 |
| 维纳滤波 | 稳态噪声较强 | 较小 | 低 |
| 时频掩蔽 | 较强 | 依赖参数 | 中 |
| 深度学习模型 | 强,适应非稳态 | 可能过处理 | 中到高 |
落地时还有几点经验值得参考。第一,后处理链路要放在重采样之后、响度归一化之前,顺序不对会让滤波效果打折扣。第二,所有增益下限都不要设为绝对零,宁可留一点点底噪也不要让语音出现空洞感,人耳对轻微底噪的容忍度远高于对断续语音的容忍度。第三,上线前务必用多组不同音色、不同语速的合成样本做听感测试,有些过处理问题只在特定音素上暴露。最后,如果条件允许,把噪声抑制的思路前移到训练阶段,比如清洗训练数据、在训练中加噪增强,往往比事后修复的收益更大,后处理应当作为最后一道保险,而不是唯一的手段。
总结
合成语音的底噪治理是一个系统工程,后处理滤波和频谱修复是其中投入产出比最高的环节。窄带干扰交给陷波滤波,稳态宽带底噪用维纳滤波压制,复杂非稳态噪声靠时频掩蔽或轻量深度模型兜底,按噪声类型分层处理,既能把底噪压到可接受的水平,又能保住合成语音的自然度。记住核心原则:处理的目标不是彻底消灭噪声,而是让噪声低于人耳的感知阈值,同时不留任何处理痕迹。掌握这个平衡,合成语音的听感就能上一个大台阶。