简介语音增强旨在从带噪语音中恢复纯净信号本包面向信号处理方向的研究者与学生提供基于人耳掩蔽效应的语音增强算法实现与信噪比计算方法可用于加性噪声、环境干扰等场景下的语音质量改善实验帮助理解听觉感知特性在降噪中的作用。压缩包共7个文件以Matlab脚本m文件为主辅以一篇研究文档caa、一段测试音频wav及运行结果截图png整体体积仅83KB结构轻量、便于快速运行。资源包含完整的算法主程序、掩蔽谱减处理、分帧函数及分段信噪比计算脚本结合测试语音可直观对比增强前后的效果适合初学者理解掩蔽效应在语音增强中的应用也为进阶者提供了可修改的代码基础与对照实验的起点。已有406人学习下载适合在课程设计、毕业设计或科研入门中作为参考实现。1. 语音增强里的人耳掩蔽效应与信噪比为什么算法追求「听不见」而不是「零噪声」在助听器和免提通话的实景里背景噪声不是白噪声而是人声、餐具、空调混在一起的复杂声场。传统谱减法把噪声功率谱按固定系数减掉结果语音一停残留的随机峰值就变成刺耳的「音乐噪声」。基于人耳掩蔽效应的语音增强算法给了另一个思路人耳对噪声的敏感程度随频率和当前声音内容变化只要把残余噪声压到掩蔽阈值之下主观上就听不见了并不需要把噪声功率减到零。这篇文章不绕弯子直接讲清楚掩蔽阈值怎么算、信噪比计算怎么驱动增益修正以及拿到Matlab源码后如何落地调试。适合正在做语音增强、助听器或语音前端降噪的开发者也适合拿它跟谱减法做客观对比的研究者。2. 从人耳掩蔽效应到掩蔽增益原理决定参数2.1 临界频带与掩蔽阈值人耳噪声敏感度的非均匀分布人耳基底膜对频率的解析并不是均匀的。频率越高同样的赫兹间隔在听觉上越「挤」所以语音增强里习惯用 Bark 尺度描述听觉频率常见近似公式为 z 13 * atan(0.76f/1000) 3.5 * atan((f/7500)^2)f 单位是 Hz。整个可听范围大约被分成 24 个临界频带每个临界频带内部的噪声会被听觉系统当作一个整体来处理。这个非均匀分布直接决定了掩蔽阈值的长相一个频谱峰值并不是只影响它所在的那一个频点而是按照临界频带向四周扩散把附近频带的听觉门槛整体抬高。在噪声环境下只要残余噪声的功率低于当前帧的掩蔽阈值人耳就感知不到它的存在。反过来说如果某个频带的语音能量本身就低于掩蔽阈值那么强行把它放大反而会放大噪声和失真。掩蔽阈值有一个重要特性不对称。低频声更容易掩蔽高频声也就是「向上掩蔽」的斜率比较平缓高频声要掩蔽低频声则需要更大的强度衰减斜率更陡。这个不对称性决定了扩展函数的形状也决定了我们在代码里要把两个方向上的斜率设成不同的值。把掩蔽阈值应用到增强算法里核心变化在于增益的构造逻辑。传统谱减法关心噪声功率谱有多大然后一刀切掩蔽增强关心的是「当前帧人耳听不见的边界在哪里」增益只负责把残余压到边界以下。这里要注意掩蔽阈值本身是从带噪语音估计出来的噪声越大、带噪语音能量越高阈值也会被抬得越高算法就越容易把噪声当成「可以被掩蔽掉」的成分放过。因此掩蔽增强不是简单地替换增益公式而是引入了一个连锁反应噪声估计 → 信噪比计算 → 掩蔽阈值 → 增益每一个环节都会影响最终效果。2.2 把掩蔽阈值落进增益函数谱减法与维纳滤波的掩蔽改进先看传统谱减法的增益形式。假设带噪语音功率谱为 P_y(k)噪声功率谱为 P_n(k)常见增益是 G(k) max(1 - a * sqrt(P_n(k) / P_y(k)), 0)其中 a 是过减因子。这个公式的问题在于当语音停止时P_y(k) 里只剩下噪声的随机起伏减完之后的残余峰值在时间轴上忽大忽小听感上就是音乐噪声。a 调大能压音乐噪声但会把弱语音一起减掉。掩蔽增强的改进是把减的对象从「固定噪声谱」换成「逐帧变化的掩蔽阈值」。某个频点的增益不再是「有没有噪声」而是「噪声是否超过掩蔽阈值」。我常用的掩蔽增益形式是 G(k) max(1 - sqrt(alpha_safe * T(k) / P_y(k)), 0)其中 T(k) 是当前帧该频点的掩蔽阈值alpha_safe 是安全因子。当 P_y(k) 远大于 T(k)也就是带噪语音能量明显高于掩蔽边界时增益趋近于 1几乎不处理当 P_y(k) 接近或低于阈值时增益被压得很低但不会直接置零因为衰减目标是「压到阈值以下」而不是「完全消音」。这么设计的好处是增益曲线随语音内容变化比固定系数谱减法平滑得多。缺点是掩蔽阈值会被带噪语音里的强噪声成分抬高尤其在低信噪比场景阈值一旦偏大语音本身也会被压坏。所以实际实现里通常会跟维纳滤波做一个组合先算维纳增益 G_w(k) xi(k) / (1 xi(k))xi(k) 是先验信噪比再用掩蔽阈值计算一个上限增益 G_mask(k)最终增益取 G(k) min(G_w(k), G_mask(k))。这样既能享受维纳滤波对平稳噪声的压制能力又能用掩蔽阈值卡住残余噪声的听感下限。2.3 信噪比计算为什么是掩蔽增强的命门信噪比计算在这个算法里承担两个角色。第一个角色是门控先验信噪比高说明当前帧大概率是语音掩蔽阈值应该主要反映语音自身的掩蔽能力先验信噪比低说明可能是噪声段这时要对阈值做保守处理避免把噪声误判成掩蔽声。第二个角色是增益计算本身维纳增益、谱减增益都直接依赖信噪比掩蔽增益虽然表面上用的是阈值但阈值又由带噪频谱推算而带噪频谱的信噪比分布决定了哪些频点能信任。具体到实现后验信噪比定义为 SNR_post(k) P_y(k) / P_n(k)直接用当前帧功率谱除以噪声功率谱估计。先验信噪比常用 Decision-Directed 方法迭代更新公式为 xi(k) alpha * (G_prev^2 * xi_prev(k)) (1 - alpha) * max(SNR_post(k) - 1, 0)。alpha 一般取 0.95 到 0.99它的作用是把信噪比估计从上一帧往当前帧平滑避免增益在帧与帧之间剧烈抖动。alpha 越大增益越稳但对语音起始段的跟随越慢alpha 太小音乐噪声又会回来。这里有一个容易忽略的耦合掩蔽阈值用到的也是同一份频谱如果噪声功率谱估计偏小后验信噪比就会虚高先验信噪比跟着偏高维纳增益偏大同时掩蔽阈值也被高估。结果就是噪声段被放过、语音段被过度增强听起来「又吵又破」。所以跑通算法后不要急着调增益先验证噪声估计和信噪比是否靠谱这也是下一章代码实现里要把噪声更新单独做成一个模块的原因。3. 用 Matlab 跑通整套算法模块划分、核心代码与参数说明3.1 拿到源码后的文件结构与处理流程一个典型的语音增强 Matlab 工程通常不会把所有逻辑堆在一个脚本里而是按功能拆成几个文件。收到这类源码包时先看目录里有没有主脚本、掩蔽阈值函数、信噪比更新函数和评估脚本这四样缺一样都不好调试。文件模块主要职责输入/输出main_demo.m读取 wav、分帧加窗、逐帧调用处理函数、重叠相加、输出增强语音输入带噪 wav输出增强 wavcalc_mask_threshold.m计算单帧频域掩蔽阈值输入单帧功率谱、fs、nfft输出阈值向量calc_snr.m用 Decision-Directed 更新先验/后验信噪比输入当前帧功率谱、噪声谱、上一帧增益输出先验信噪比est_noise.m初始化噪声谱并在语音间隙更新输入分帧矩阵或逐帧功率谱输出噪声功率谱整体处理流程是读 wav → 分帧加窗 → FFT → 估计噪声谱 → 计算后验信噪比 → 更新先验信噪比 → 计算掩蔽阈值 → 计算增益 → 频域滤波 → IFFT → 重叠相加 → 输出。每一步之间都是向量或矩阵传递建议先跑通主循环再单独调某个模块。3.2 掩蔽阈值计算的 Matlab 实现掩蔽阈值计算是整套算法里最容易写错的部分因为它涉及频率到 Bark 尺度的转换、临界频带分组、扩展函数和安静听阈叠加。下面是一个能直接落地的实现function mask_thr calc_mask_threshold(Y_pow, fs, nfft) % Y_pow: 单边功率谱长度 nfft/21 % fs: 采样率单位 Hz % mask_thr: 与该帧频谱等长的掩蔽阈值(线性功率) nfreq length(Y_pow); freq (0:nfreq-1). * fs / nfft; % 每个频点的实际频率 freq_khz max(freq, 1) / 1000; % 转 kHz0Hz 用 1Hz 代替防除零 % Hz - Bark bark 13 * atan(0.76 * freq_khz) 3.5 * atan((freq_khz / 7.5).^2); % 按 1 Bark 宽度划分临界频带上限取实际最大 Bark band_center 0.5:1:ceil(max(bark)); nband length(band_center); % 每个临界频带内的平均功率 band_pow zeros(nband, 1); for b 1:nband idx bark band_center(b)-0.5 bark band_center(b)0.5; if any(idx) band_pow(b) mean(Y_pow(idx)); end end % 扩展函数掩蔽声向邻近频带扩散方向不对称 slope_up 12; % 向上掩蔽掩蔽声频率低于被掩蔽声每 Bark 衰减 12dB slope_down 25; % 向下掩蔽掩蔽声频率高于被掩蔽声每 Bark 衰减 25dB expand zeros(nband, nband); for i 1:nband for j 1:nband dz band_center(i) - band_center(j); % 0: i 比 j 低频 expand(i, j) 10^( (-slope_up * max(dz,0) - slope_down * max(-dz,0)) / 10 ); end end % 带级掩蔽阈值 所有掩蔽声贡献之和 mask_band sum(band_pow .* expand, 1).; % 叠加安静听阈防止阈值低于物理听阈 abs_thr_db 3.64 * freq_khz.^(-0.8) - 6.5*exp(-0.6*(freq_khz-3.3).^2) 1e-3*(freq_khz).^4; abs_thr 10.^(abs_thr_db/10); % 插值回频点级阈值 mask_thr interp1(band_center, mask_band, bark, linear, extrap); mask_thr max(mask_thr, abs_thr); end这个函数的逻辑分四步。第一步把 FFT 频点映射到 Bark 尺度因为人耳临界频带在 Bark 域近似等宽第二步在每个临界频带里取平均功率作为该带「可能的掩蔽源强度」第三步用不对称扩展函数把每个带的掩蔽贡献扩散到其他带这一步是模拟基底膜激励的扩散第四步把带级阈值插值回频点并叠加安静听阈避免在极低频或极高频出现不合理的低阈值。参数说明里最值得关注的是 slope_up 和 slope_down。slope_up 控制低频声对高频区域的掩蔽范围设得越小低频能量越容易把高频噪声「盖住」增强后高频残余噪声越少但语音高频细节也可能被压掉。slope_down 控制高频声向低频的掩蔽实际使用中它对音色影响较小主要影响齿音和摩擦音附近的处理强度。调试时不要同时改两个斜率一次只动一个否则阈值曲线变化太大很难定位问题。3.3 先验信噪比与后验信噪比决策导向法的迭代代码信噪比更新模块是算法稳定性的核心。我见过很多翻车案例问题不在掩蔽阈值而在信噪比迭代时初值或平滑因子的处理不当。下面给出一个稳妥的更新函数function [snr_prior, snr_post] update_snr(Y_pow, noise_pow, snr_prior, G_prev, alpha) % Y_pow: 当前帧单边功率谱 % noise_pow: 当前噪声功率谱估计 % snr_prior: 上一帧先验信噪比(线性功率比) % G_prev: 上一帧增益 % alpha: 决策导向平滑因子, 典型 0.95~0.99 % 后验信噪比 snr_post Y_pow ./ (noise_pow eps); % 上一帧估计语音功率与当前帧后验信噪比的加权 snr_prior_dd alpha * (G_prev.^2 .* snr_prior) (1-alpha) * max(snr_post - 1, 0); % 限制下限防止低噪段增益爆炸 snr_prior max(snr_prior_dd, 10^(-25/10)); % 下限 -25dB end代码里的 G_prev.^2 .* snr_prior 表示上一帧增强后语音功率与噪声功率的比值它比直接拿上一帧带噪功率算更接近真实语音。alpha 取 0.98 时先验信噪比的更新主要依赖历史轨迹增益连续性好alpha 取 0.9 时算法对语音起始段反应更快但噪声段的增益起伏也更明显。我的建议是先用 0.98 跑通听感发闷再往 0.94 方向调。初值 snr_prior 最好不要给 0给 10^(-25/10) 这个下限值即可避免前几帧语音被吞。运行过程中如果某帧平均先验信噪比很低说明大概率是噪声段这时可以顺手更新噪声谱形成软 VAD 的效果。3.4 主循环与核心参数表主循环负责把分帧、加窗、FFT、信噪比更新、掩蔽阈值、增益、IFFT 和重叠相加串起来。下面是一个经过简化但可直接运行的主循环框架% 参数设置 nfft 512; % 帧长(样本数), 16kHz 下 32ms hop 128; % 帧移(样本数), 8ms, 重叠 75% win hann(nfft, periodic); alpha 0.98; % 决策导向平滑因子 alpha_safe 1.2; % 掩蔽增益安全因子 % 读取音频 [x, fs] audioread(noisy_speech.wav); x x(:); % 分帧: 每列一帧, 重叠 nfft-hop frames buffer(x, nfft, nfft-hop, nodelay); % 用前 10 帧初始化噪声功率谱 noise_pow mean(abs(fft(frames(:,1:10), nfft)).^2, 2); noise_pow noise_pow(1:nfft/21); % 取单边 % 状态变量 snr_prior 10^(-25/10) * ones(nfft/21, 1); G_prev ones(nfft/21, 1); y_hat zeros(size(x)); win_sum zeros(size(x)); for i 1:size(frames, 2) seg frames(:, i) .* win; Y fft(seg, nfft); Y_pow abs(Y(1:nfft/21)).^2; % 单边功率谱 % 信噪比更新 [snr_prior, ~] update_snr(Y_pow, noise_pow, snr_prior, G_prev, alpha); % 掩蔽阈值 mask_thr calc_mask_threshold(Y_pow, fs, nfft); % 掩蔽增益: 压到阈值以下, 但不完全消音 G max(1 - sqrt(alpha_safe * mask_thr ./ (Y_pow eps)), 0); G min(G, 1); % 频域滤波与反变换 Y_enh G .* Y(1:nfft/21); Y_full [Y_enh; conj(Y_enh(end-1:-1:2))]; y_frame real(ifft(Y_full, nfft)); % 重叠相加(加窗补偿) idx (i-1)*hop 1 : (i-1)*hop nfft; y_hat(idx) y_hat(idx) y_frame .* win; win_sum(idx) win_sum(idx) win.^2; G_prev G; % 软VAD噪声更新: 平均先验SNR较低时更新噪声谱 if mean(snr_prior) 2 noise_pow 0.9 * noise_pow 0.1 * Y_pow; end end % 归一化并输出 y_hat y_hat ./ max(win_sum, 1e-10); audiowrite(enhanced.wav, y_hat, fs);主循环的关键参数都集中在开头我建议把参数表抄下来贴在显示器旁边。帧长 512 在 16kHz 采样下对应 32ms能覆盖绝大多数语音帧的准平稳假设帧移 128 对应 8ms重叠 75%这个配置是谱减法和掩蔽增强都验证过的常用值。alpha_safe 是掩蔽增益的余量因子设 1.0 表示刚好压到阈值设 1.2 表示比阈值再压低一点补偿阈值估计误差设太大会导致语音听起来「闷」。软 VAD 更新里的门限 2 是线性功率比对应约 3dB如果测试环境安静可以降到 1.5噪声环境建议提到 3否则噪声谱更新太频繁。需要提醒的是buffer 和 hann 都依赖信号处理工具箱。如果目标环境没有该工具箱分帧需要手写循环FFT 用 Matlab 基础函数即可。重叠相加里的win_sum包含了每次加窗的平方和用于最后归一化补偿窗函数造成的幅度损失这一步不能省否则每帧重叠处的幅度会周期性波动。4. 信噪比计算与掩蔽效应结合的避坑记录5 个真实翻车现场4.1 帧长一改掩蔽阈值全部错位现象把采样率从 16kHz 换成 8kHz或者把 nfft 从 512 改成 256 之后增强语音整体发闷高频像隔着一层布甚至出现明显的哨音。原因掩蔽阈值函数里如果写死了频点频率比如直接写(0:257)*16000/512那么 nfft 改变后频点对应的实际频率全部错位。8kHz 采样下 256 点 FFT 的频点间隔是 31.25Hz而代码里还在按 31.25Hz 算高频带被错误地对应到了更低的频率阈值曲线整体向低频偏移。解决掩蔽阈值函数的频率向量必须从输入反推写成freq (0:nfreq-1)*fs/nfft。每一次更改采样率或帧长之前先打印freq和bark的最大值确认 Bark 上限和临界频带划分是否覆盖实际带宽。4.2 先验信噪比初值设成 0前几十帧语音被吞现象跑出来的增强语音开头第一两个字明显变轻像是被压扁了语谱图上起始段有很深的能量空洞。原因先验信噪比第一次迭代时如果取 0维纳增益和掩蔽增益在初始帧都会趋近于 0增强后的输出接近静音。因为 Decision-Directed 是递归结构第一帧的错误会通过 alpha 的平滑作用延续到后续几十帧语音起始段被整体吞掉。解决初始snr_prior不要给 0给一个功率下限10^(-25/10)让第一帧增益至少能通过后验信噪比启动。也可以跑一个预热阶段前 20 帧只用后验信噪比不启用决策导向平滑等状态稳定后再切到完整算法。4.3 噪声估计跟不上非平稳噪声掩蔽阈值被高估现象在餐厅、公交这类非平稳噪声环境里测试增强后噪声不但没消掉反而变得「一鼓一鼓」的听感比原始噪声更难受。原因只用前 10 帧初始化噪声谱对平稳噪声没问题但非平稳噪声的能量在时间轴上波动很大。噪声估计偏低时后验信噪比虚高先验信噪比跟着偏高掩蔽阈值也被抬高算法误以为「这个噪声可以被掩蔽掉」于是放过了本应被压制的噪声段。解决给噪声估计加软 VAD 更新逻辑主循环里的mean(snr_prior) 2判断就是干这个的。更稳妥的做法是同时维护一个最小值统计噪声估计取「当前噪声谱」和「带噪谱逐频点最小值」的较大者避免噪声谱在语音段被污染。4.4 低频段掩蔽阈值设置过高增强后声音发闷现象增强后的语音干净是干净了但男声明显发闷中低频像蒙了一层被元音变得不自然。原因低频能量强而 slope_up 设置得过于平缓导致低频段的掩蔽贡献扩散到很高频把 500Hz 到 1.5kHz 的阈值整体抬高。这个区间恰好是语音第一、第二共振峰所在的区域阈值高增益被压低元音的谐波结构就被破坏了。解决把 slope_up 从默认的偏小值往大调减小低频掩蔽对高频段的扩散范围或者在掩蔽阈值函数里对 300Hz 以下频带单独乘一个衰减系数限制低频阈值向中频扩散。调试时拿一段男声朗读音频反复听重点听「a、o、e」这类元音是否发闷。4.5 分段信噪比统计窗口不一致对比实验数据失真现象客观指标算出信噪比提升了 8dB但 AB 试听时没觉得差多少甚至觉得增强版不如原版舒服。原因评估分段信噪比时如果干净语音和增强语音没有先做时间对齐或者在分帧时一个用帧长 256、一个用帧长 512统计口径不一致计算出的每帧 SNR 会失真。另外静音帧的信号能量接近于零噪声能量稍大就会算出很大的负 SNR把平均值拉低如果评估脚本把静音帧剔掉而对比实验里剔的标准不一致数据就更没法比了。解决评估脚本里第一件事做互相关对齐第二件事统一帧长和帧移第三件事明确静音帧处理策略。要么所有帧都算要么用统一的能量门限剔除报告里必须写清楚。分段信噪比结果只能用来做同条件对比不能跨数据集直接比较。5. 参数调优与效果验证如何判断掩蔽增强比谱减法更值得用5.1 三个必调参数平滑因子、安全因子、掩蔽斜率先验信噪比平滑因子 alpha 是第一优先级。alpha 太大接近 0.99音乐噪声几乎消失但语音起始段会变钝alpha 太小低于 0.9噪声段增益起伏明显音乐噪声回潮。我的习惯是从 0.98 开始听感发闷就往 0.95 降听到音乐噪声就往 0.99 升每次只调 0.01。第二个调的是掩蔽增益安全因子 alpha_safe。这个参数本质上是对掩蔽阈值模型不信任程度的度量1.0 表示完全信任阈值1.3 表示比阈值多压 30%。alpha_safe 调大能压住残余噪声但语音能量也会被连坐建议先在安静环境下试听把 alpha_safe 从 1.0 每次加 0.1直到噪声段听不到明显残留为止。第三个才是掩蔽斜率 slope_up 和 slope_down。这两个参数影响的是音色而不是噪声底调的时候不要追求「数值最优」而是要拿特定的测试音频听。男声、女声、齿音重的语音各准备一段斜率只在安全因子定下来之后再动否则两个变量交叉影响很难定位是谁导致的失真。5.2 用分段信噪比做客观对比一个可直接用的评估函数客观验证里最常用的指标是分段信噪比它比全局信噪比更接近人耳对时间变化的感知。下面给出一个评估函数注意它做了对齐和静音帧处理统计口径是固定的function seg_snr calc_seg_snr(clean, enh, fs, frame_len, hop) % 分段信噪比(dB), 统计前先对齐并统一分帧参数 % clean: 干净参考语音 % enh: 增强后语音 % frame_len: 帧长样本数 % hop: 帧移样本数 % 时间对齐: 用互相关找延迟 n min(length(clean), length(enh)); c clean(1:n); e enh(1:n); [r, lag] xcorr(c, e, 100, coeff); [~, idx] max(r); delay lag(idx); if delay 0 e e(delay1:end); elseif delay 0 e [zeros(-delay,1); e(1:enddelay)]; end % 统一长度 n min(length(c), length(e)); c c(1:n); e e(1:n); % 分帧 fc buffer(c, frame_len, frame_len-hop, nodelay); fe buffer(e, frame_len, frame_len-hop, nodelay); % 每帧信号能量和残余噪声能量 sig_pow sum(fc.^2, 1); noi_pow sum((fe - fc).^2, 1); % 每帧SNR, 限制到[-10, 35]dB snr_frame 10 * log10(sig_pow ./ (noi_pow eps)); snr_frame max(min(snr_frame, 35), -10); seg_snr mean(snr_frame); end对齐这一步必不可少。很多算法流水线里自带滤波延迟少则几个样本多则一个帧移不对齐直接计算会低估增强效果。帧长、帧移必须跟主循环保持一致否则静音帧的比值会随机波动。SNR 限幅到 [-10, 35] 是为了防止个别极端帧主导平均值这个范围也是语音增强论文里常见做法。除了分段信噪比有条件的话建议同时算 PESQ 和 STOI。PESQ 关注语音质量STOI 关注可懂度两者和分段信噪比的侧重点不同。掩蔽增强通常会出现在分段信噪比提升不太大、但 PESQ 提升明显的情况因为残余噪声被压到了掩蔽阈值以下听感更干净。如果三个指标里有一个明显掉队就要警惕是不是增益过度压制导致语音失真。5.3 语谱图检查与 AB 试听别只盯着数字客观指标只能说明趋势最终拍板还是要看语谱图和试听。增强后的语谱图重点看三个位置噪声段是否有零散的深色竖条那是音乐噪声的典型痕迹语音段共振峰是否连续如果出现横向断裂说明增益在某些频点被错误压低语谱图底噪是否比原图更均匀掩蔽增强的目标是让噪声底变得「平」而不是「黑」。AB 试听注意用同一副耳机、同一音量不要睁眼看文件名让同事或同学帮忙盲听。我自己的习惯是准备三段材料干净语音加白噪声、加 babble 噪声、加空调噪声每段包含男声和女声各 10 秒左右。盲听时只问三个问题背景吵不吵、人声自不自然、有没有奇怪的声音。三个问题里只要有一个明显劣化就算客观指标再好也要回去调参。6. 从离线脚本到实时方案三个进阶技巧第一个技巧是给掩蔽阈值加帧间递归平均。当前帧阈值直接用于增益时阈值随语音能量抖动增益也跟着抖这是残余音乐噪声的来源之一。我一般会在掩蔽阈值函数外面做一次平滑T_smooth(k) beta * T_smooth(k) (1-beta) * T_cur(k)beta 取 0.7 到 0.8只平滑阈值不平滑频谱语音细节不会丢失太多。第二个技巧是限制先验信噪比的最大值防止过增强。安静环境下语音段的先验信噪比容易飙到 30dB 以上此时维纳增益接近 1掩蔽增益也接近 1逻辑上没问题但一旦帧间估计抖动增益会出现可听见的「喘气」声。给先验信噪比加一个上限比如线性值不超过 300约 25dB能显著减少这种动态波动。第三个技巧是子带处理降低复杂度。掩蔽阈值计算里最费时间的扩展函数是 nband×nband 的循环nband 最多 24算起来不慢但主循环每帧都要插值回频点在实时实现时可以改成只在临界频带中心频点上算增益再用线性插值扩展到每个 FFT 频点。这样既保留了掩蔽模型的不对称性又把每帧的计算量降了约三分之一对 Cortex-M 级别的实时设备很友好。这套算法我做过不止一次每次踩坑几乎都集中在信噪比估计和掩蔽阈值的耦合上信噪比偏乐观掩蔽阈值就偏乐观最终听感全是噪声残留。后来养成的习惯是拿到任何一版参数先跑一段 10 秒的纯噪声样本如果纯噪声段增强后还有明显起伏说明信噪比门控出了问题先修这个再谈音质。希望这篇笔记能帮你把「掩蔽效应」从论文公式变成一台能跑出干净语音的程序也希望你跑通之后回头来看这章的踩坑记录能少走一段弯路希望帮到你。本文还有配套的精品资源点击获取