RTC(Real-time Communications) 技术,广义指实时通信,狭义一般称为实时音视频,在这次全球大爆发的新冠肺炎疫情中,作为视频会议、视频通话、远程办公、远程医疗和互动直播等应用的底层技术,为全社会的尽力运转提供了巨大的支持。
WebRTC 是一个由 Google 发起的 P2P 实时通讯解决方案,提供了实时音视频中的一些核心技术,包括音视频的采集、编解码、网络传输、显示等功能。
基于WebRTC技术,OPPO安第斯系统RTC团队攻克了超大并发、抗弱网、低延时、带宽自适应、区域中继等众多技术难关,自研了ORTC实时音视频平台,包含Andes-RTNet实时通信网络和丰富的终端SDK,以云服务的形式对外赋能,致力于帮助开发者快速构建低成本、低延时、高品质的音视频产品。
实时音视频技术主要包含视频编解码技术、音频3A技术、网络传输技术等方面,本文就WebRTC音频3A (Acoustic Echo Cancelling(AEC)、Automatic Gain Control (AGC)、Active Noise Control (ANC)) 中的 ANC噪声抑制模块进行深入解读,为后续传统降噪算法的优化和AI降噪算法的落地打好技术积累的基石。 今后也会在OPPO互联网技术公众号,就ORTC平台一些自研核心技术进行定期分享。 噪声抑制模块主要可以分为三部分:模块初始化、噪声分析和噪声消除。模块的初始化主要设置NS模块相关的参数,初始化一些状态值。噪声分析部分,主要计算语音噪声的概率,更新噪声估计值等。噪声消除部分则根据计算的语音概率以及噪声能量值等,利用维纳滤波进行噪声消除。 WebRTC代码中采用似然比的方法判断语音和噪声的概率,使用分位数的方法估计噪声,使用维纳滤波进行噪声的消除。由于代码中的实现对公式进行了一定的近似/修改,因此先进行算法原理的推导,然后结合代码进行分析。 1. WebRTC 降噪算法推导假设麦克风采集到的信号为 y(t),语音数据为 x(t),噪声数据为 n(t),假定噪声为加性噪声,则有: 式中, 为mic采集信号的频域表示; 为语音的频谱; 为噪声谱。 降噪的目的是,将mic采集到的信号 中的噪声 尽可能地消除掉,只保留语音数据 ,从而提高语音的清晰度和可懂度。因此,对噪声的估计准确性是至关重要的,噪声估计的越准,得到的结果就越好。 WebRTC没有采用基于VAD检测的噪声估计(VAD对Y(w)进行检测,如果检测没有语音,则认为噪声)、基于全局幅度谱最小原理(该估计认为幅度谱最小的情况必然对应没有语音的时候)、基于矩阵奇异值分解原等的方法,而是对似然比 (WebRTC的VAD检测就用了该方法)函数进行改进,将多个语音/噪声分类特征合并到一个模型中形成一个多特征综合概率密度函数,对输入的每帧频谱进行分析。其可以有效抑制风扇/办公设备等噪声。 对接收到的每一帧带噪语音信号,以对该帧的初始噪声估计为前提,定义语音概率函数,测量每一帧带噪信号的分类特征,使用测量出来的分类特征,计算每一帧基于多特征的语音概率,再对计算出的语音概率进行动态因子(信号分类特征和阈值参数)加权,根据计算出的每帧基于特征的语音概率,修改多帧中每一帧的语音概率函数,以及使用修改后每帧语音概率函数,更新每帧中的初始噪声(连续多帧中每一帧的分位数噪声)估计。 1.1 信噪比的计算 信噪比的计算主要是指计算先验信噪比和后验信噪比,这两个信噪比将用于似然比(LRT)的计算,在WebRTC中,语音与噪声的判断就是通过似然比来进行判别的。 (1)后验信噪比:观测到的能量与噪声功率相关的输入功率相比的瞬态SNR。其公式表示为: (2)先验信噪比:与噪声功率相关的纯净(未必是语音)信号功率的期望值,公式: 在WebRTC中,为了降低运算资源的消耗,并未严格按照公式定义的能量比计算,而是采用了幅度的比值计算,故公式变为: 由于纯净信号 是未知的,因此在WebRTC中,先验SNR的计算采用上一帧估计的先验SNR和当前帧的瞬态SNR的平滑值,如式(1-7)所示: 式中, 为上一帧的维纳滤波器系数,因此H与Y的乘积相当于将Y信号中的噪声滤除,剩下的就是X信号,即 。 因此,式(1-7)等号右边的第一项是上一帧的先验SNR, 。 等号右边的第二项为当前帧的先验SNR的瞬态估计,其推导过程如下: 通过判决引导(Decision Directed,DD)的方法进行更新,其中, 为时间平滑因子,其值设置的越大,流畅度就越高,但也意味着 更新的也越慢,延迟就会越大。WebRTC中, 该值设置为0.98。 1.2 WebRTC语音/噪声概率模型的特征选取 早期WebRTC预留的用于描述语音的特征量有,Spectral flatness(频谱平坦度)、Spectral entropy(谱熵)、Spectral variance(谱方差)、Average LRT factor(似然比)、Spectral template diff(频谱模板差异)、Normalization for spectral difference、Window time-average of input magnitude spectrum等几种。实际应用到WebRTC代码实现中的只有Spectral flatness(基于语音谐波模型得出) 、LRT均值(基于SNR计算得出)、Spectral template diff三种。因此,这部分先简单介绍这三种特征的计算方法。 1.2.1 LRT 均值的计算经过时间平滑处理的似然比(LR)因子的几何平均数是语音/噪声状态的可靠指标,计算公式如式(1-8)所示: 其中,经过时间处理的LR因子会在1.3部分进行推导。 使用LRT均值特征时,映射函数M(z)宜采用非线性函数,如S函数。具体来说,映射函数的一个示例可表示为: 函数映射的变量可取 ,其中 是特征, 是一个过渡/宽度参数,用于控制从0到1的映射的平滑性,阈值参数 需要根据参数设置来确定。 语音比噪声的谐波更多,其表现是语音频谱通常在基频和谐波中出现能量峰值,而噪声频谱则相对平坦。因此,频谱平坦度可以用于区分语音和噪声。 在计算频谱平坦度时,可以将语音分成若干子带,在子带上计算平坦度,如256点实信号FFT变化,可以得到128点非直流频点。可以将其分成4个频率带(低、中低、中高、高),每个频率带可以包含相等或不相等个数的频点。频谱平坦度计算公式如下: 其中,N为子带中的频点数。可以看出,对于噪声,计算出的 偏大且为常数;而对于语音,计算出的 偏小且为变量。 1.2.3频谱模板差异的计算 稳态噪声频谱比语音频谱更稳定,由此假设噪声频谱包络在任何给定阶段都倾向于和先前若干帧保持相同。通过更新频谱(初始被设为0)中极有可能是噪声或语音停顿的区段来获得模板频谱估计。模板频谱只选取了噪声概率较高的信号,其只在语音概率低于阈值段更新噪声。模板的方法还可以处理非稳态噪声(通过将非稳态噪声预先做出模板),如人的笑声、掌声等。 式中, 为输入频谱, 为模板频谱,(α,μ)为形状参数,包括线性位移和振幅参数,是通过将J最小化获得的。 频谱模板差异特征可以测量出模板或习得噪声频谱与输入频谱的差异/偏差。如果 较小,意味着输入帧频谱“接近”与模板频谱,则该输入帧很有可能是噪声,若是瞬态噪声情况,则需要更大胆的进行噪声更新。如果 较大,表示输入帧的频谱与噪声模板频谱差异很大,输入帧可判为语音。 可在频谱模板差异计算中加入 ,以增加频谱中特定频带所占权重,如下式: 多个特征(LRT均值、频谱平坦度和频谱模板差异)可以在语音/噪声概率的更新模板中同时出现,如式(1-13)所示: 不同的特征代表不同的信息,使用加权 和 Ti 的目的是防止不可靠的特征模型更新,以提供一个更稳定、更具适应性的语音/噪声概率更新模型。 1.3 语音噪声概率的计算 推导语音/噪声概率的计算方法,需要先看语音/噪声的概率模型。定义语音状态为 ,噪声状态为 ,则语音/噪声的概率为 ,这一概率取决于观测到的输入频谱系数 ,以及所处理信号的特征数据集合 ,特征数据可以选择噪声输入频谱。根据贝叶斯准则,语音/噪声的概率可以表示为: 式中, 是基于信号特征的先验概率,在WebRTC中该值被设置为常数; 是基于特征数据 的语音/噪声概率,忽略先验概率中的特征集 ,其可以简化为 和 ,则式(1-14)可以重写为: 将式(1-17)和(1-18)代入式(1-16),得到基于高斯模型的似然比Δk,如下: 为了防止帧间频变似然比因子Δk波动较大,对似然比因子Δk进行平滑处理: 利用式(1-5)、(1-6)、(1-19)、(1-20)并代入(1-15)式就可以计算出语音噪声的概率。WebRTC的代码实现中,并没有完全按照上述公式的推导来实现,而是进行了适当的简化处理。后续内容中有详细介绍。 1.4 噪声估计更新函数 语音/噪声概率确定后,将执行噪声估计的更新,噪声估计更新公式如下: 式中, 为m帧,频点为k的噪声频谱估计值; 是噪声更新的平滑参数; 当前帧为语音帧的概率; 当前帧为噪声帧的概率。式(1-22)表示噪声估计模型会对噪声可能性较大的每帧每个频点的噪声进行更新。对于噪声可能性不大的帧和频点,则将信号值上一帧的估计作为噪声估计。 1.5 噪声消除 完成噪声估计更新后,采用维纳增益滤波器来减少或者消除输入帧中的估计噪声量。标准维纳滤波器表达式如下: 对滤波器直接应用时间平均法以减少帧间波动,维纳滤波器用先验SNR表示,而判决引导(DD)更新则用于对先验SNR进行时间平均计算。维纳滤波器使用先验SNR表示: 按照DD更新估计先验SNR,该增益滤波器通过取底和过相相减,得到式(1-25): 式中,β参数是根据噪声抑制系统中噪声抑制器主动配置定义的。将上述维纳滤波器应用到输入频谱中得到经过抑制后的信号: 1.6 信号合成 噪声消除的过程是在频域进行的,因此还需要转换到时域。在应用维纳滤波器后,使用下式将信号变换到时域: 2. NS模块的初始化 在modules\audio_processing\ns\ns_common.h中: constexpr size_t kFftSize = 256 ;
constexpr size_t kFftSizeBy2Plus1 = kFftSize / 2 + 1 ;
constexpr size_t kNsFrameSize = 160 ;
constexpr size_t kOverlapSize = kFftSize - kNsFrameSize;
constexpr int kShortStartupPhaseBlocks = 50 ;
constexpr int kLongStartupPhaseBlocks = 200 ;
constexpr int kFeatureUpdateWindowSize = 500 ;
constexpr float kLtrFeatureThr = 0.5f ;
constexpr float kBinSizeLrt = 0.1f ;
constexpr float kBinSizeSpecFlat = 0.05f ;
constexpr float kBinSizeSpecDiff = 0.1f ;
在modules\audio_processing\ns\ prior_signal_model.h中: float lrt;
float flatness_threshold = .5 f;
float template_diff_threshold = .5 f;
float lrt_weighting = 1.f ;
float flatness_weighting = 0.f ;
float difference_weighting = 0.f ;
在modules \audio _processing\ns\noise_estimator.h中: float white_noise_level_ = 0.f ;
float pink_noise_numerator_ = 0.f ;
float pink_noise_exp_ = 0.f ;
在modules\audio_processing\ns\ns_config.h中,噪声抑制程度,默认设置为12dB: enum class SuppressionLevel { k6dB, k12dB, k18dB, k21dB };
SuppressionLevel target_level = SuppressionLevel::k12dB;
在modules\audio_processing\ns\ speech_probability_estimator.h中: float prior_speech_prob_ = .5 f;
3. 噪声分析 噪声分析是通过调用\modules\audio_processing\ns\noise_suppressor.cc文件中的NoiseSuppressor::Analyze(const AudioBuffer& audio)函数来实现的。这个函数的主要任务是计算似然比LRT,频谱平坦度、频谱差异,通过这些特征计算语音帧的概率。最后更新了噪声功率谱的估计值。噪声分析模块的基本处理流程图如图3-1所示: 3.1 数据的准备 (1) 针对每个channel,调用noise_estimator.PrepareAnalysis()函数,将数据从noise_spectrum_拷贝到prev_noise_spectrum_。noise_spectrum_的size=129。 在时域上,对每个channel的数据计算能量,计算公式: 其中,x(i)为当前帧的信号输入, 为上一帧输入信号的后96个采样点。在代码中,y_band0为当前帧的输入,analyze_analysis_memory为上一帧的数据,代码如下: float energy = ComputeEnergyOfExtendedFrame( y_band0, channels_[ch]->analyze_analysis_memory);
如果energy不大于0,则不进行统计数据的更新,直接从函数中返回。这样做的原因是因为,当信号为0时去更新统计信息,会认为噪声非常小,导致噪声判断的阈值向0的方向靠近。导致的后果就是,一旦当输入信号不为0(或者输入很小的噪声),就会被误判断为语音而进行保留,从而失去了噪声消除的效果。 3.2 数据的Analyze 3.2.1 组建extended frame数据 extended frame由上一帧的96个数据+当前帧的160个数据组成256点的新数组,以便进行256点的FFT变换。在代码中,由函数FormExtendedFrame来实现,如下: FormExtendedFrame (y_band0, ch_p->analyze_analysis_memory, extended_frame);
y_band0为当前帧160点数据,analyze_analysis_memory存放着上一帧的96点数据。组合成extended_frame数据后,会将y_band0中的最后96个采样点复制到analyze_analysis_memory中,以供下一帧使用。 3.2.2 对extended_frame数据加窗(分析窗) 分析窗函数使用的是混合Hanning和flat窗函数,即前后各96点数据使用的是Hanning窗,中间的64点数据使用系数为1的flat窗函数。
void ApplyFilterBankWindow (rtc::ArrayView<float , kFftSize> x) {
for (size_t i = 0 ; i < 96 ; ++i) {
x[i] = kBlocks160w256FirstHalf[i] * x[i];
}
for (size_t i = 161 , k = 95 ; i < kFftSize; ++i, --k) {
RTC_DCHECK_NE(0 , k);
x[i] = kBlocks160w256FirstHalf[k] * x[i];
}
}
i =[0 ,95 ] x[i]= kBlocks160w256FirstHalf[i] * x[i]
i =[96 ,160 ] x[i]= x[i]
i =[161 ,256 ] x[i]= kBlocks160w256FirstHalf[k] * x[i] k=95 ,94 ,…,0 ;i=161 ,162 ,…,256
3.2.3 FFT变换 WebRTC的FFT变换使用的是第三方的FFT函数。采用256点FFT变换,变换后的实部数据存放在real中,虚部数据存放在imag中。real和imag均为256点的数组。 fft_.Fft(extended_frame, real , imag );
3.2.4计算信号的Magnitude Spectrum Magnitude Spectrum计算公式如下: 根据FFT变换的对称性以及语音更多关注8000Hz以下的部分,这里(以及后续处理)只需要计算一半的数据,代码如下:
void ComputeMagnitudeSpectrum(
rtc::ArrayView<const float, kFftSize> real ,
rtc::ArrayView<const float, kFftSize> imag ,
rtc::ArrayView<float, kFftSizeBy2Plus1> signal_spectrum) {
signal_spectrum[0 ] = fabsf(real [0 ]) + 1.f ;
signal_spectrum[kFftSizeBy2Plus1 - 1 ] =
fabsf(real [kFftSizeBy2Plus1 - 1 ]) + 1.f ;
for (size_t i = 1 ; i < kFftSizeBy2Plus1 - 1 ; ++i) {
signal_spectrum[i] =
SqrtFastApproximation(real [i] * real [i] + imag [i] * imag [i]) + 1.f ;
}
}
3.2.5 计算信号能量计算信号的均方值(signal_energy)与信号幅度谱的和值(signal_spectral_sum) 4. 噪声分析之噪声谱分析 本章节主要介绍如何估计噪声谱以及语音存在的概率,按理应该放在第3章节,但由于篇幅较长,独立出一个章节描述。 4.1 NoiseEstimator::PreUpdate 这个函数主要的任务就是对初始噪声谱进行估计,主要过程可以分为两部分:对初始噪声谱的估计和噪声模型的计算。其中,噪声模型的搭建是为了当针对开始处理的信号帧不足50时,利用噪声模型和实际噪声帧联合进行噪声估计。 4.1.1 第一部分:初始噪声谱的估计 初始噪声谱估计采用语音开始时前200帧(kLongStartupPhaseBlocks,时长2s)的数据,初始噪声估计通过调用Estimate函数进行估计: quantile_noise_estimator_ .Estimate (signal_spectrum , noise_spectrum_ );
WebRTC通过分位数噪声估计(QBNE,Quantile Based Noise Estimation),分位数噪声估计的想法是建立这样一个共识,即使是语音段,输入信号在某些频带分量也可能没有信号能量。那么假设将某个频带上所有语音帧的能量做一个统计,设定一个分位数值,低于分位数值的认为是噪声,高于分位数值的认为是语音,相比于逐帧判断(VAD),这样就进一步细化的噪声统计的粒度,即使语音帧也能提取有效的噪声信息进行平滑。 WebRTC在Estimate函数中对初始噪声的估计运算流程为: 第一步:计算出log_spectrum的值。这个值将用于后续的q分位数值的更新。 第二步:更新分位数log_quantile_和噪声的概率密度density_ log_quantile_和density_的size都是387=3*129,即对每一个频点的数据计算三组不同的分位数,quantile_和density__的初始值为:quantile_[387]=0.f; density__[387]=0.3f。三组不同的分位数通过counter_来控制。counter_将用于初始估计的200帧数据分为3份,其初始化值为counter_=[67,134,1]。从第一帧数据开始,对每一帧输入的数据分别更新这三组分位数: 更新初始分位数:当log_spectrum[i] > log_quantile_[j]时,意味着log_quantile_的初始值设置的偏小,需要增加;反之,则需要减小。更新公式如下,其中i为遍历129个bin: 更新噪声的概率密度函数:当 时,意味着当前的噪声估计比较准确了,因此更新当前噪声的概率密度函数,更新公式如下: 当129个bin的当前帧数据都完成了log_quantile_和density_的更新后,然后再利用当前帧的数据更新counter_[1]和counter_[2]对应log_quantile_和density_值。每完成一次counter_[s]对应的更新后,counter_[s]都是自增1,比如,初始值counter_=[67,134,1],更新完counter_[0]对应的129个分位数和概率密度后,counter_=[68,134,1];更新完counter_[1]对应的129个分位数和概率密度后,counter_=[68,135,1];更新完counter_[5]对应的129个分位数和概率密度后,counter_=[68,135,2]。当counter_[s]==200时,将其置0,counter_[s]=0。即,每处理完一帧噪声估计的更新,counter_的三个数值都会增加1,当初始的200帧噪声处理后,counter_[s]也完成了0~200的遍历。 初始的200噪声处理之后,取出counter_[2]对应的129个log_quantile_值,将其转换为指数值quantile_,并将该数值存放到noise_spectrum中,return。 4.1.2 第二部分:噪声模型的计算 当处理信号帧数小于50(kShortStartupPhaseBlocks)时,为了更准确的估计噪声,WebRTC利用已经处理的噪声数据(num_analyzed_frames)搭建了一个噪声模型,然后对50-num_analyzed_frames的未知噪声利用搭建的噪声模型进行估计,并与实际计算处理的噪声分位数一起完全当前噪声的估计。下面介绍了详细的估计过程。准备工作如下: 注意,上面的计算都是舍弃了前5个bin,即,不考虑约300Hz以下的频率。 white_noise_level_计算前50帧的数据,signal_spectral_sum为每帧数据前129个bin的幅度值之和(见公式3-4),over_subtraction_factor是参数suppression_level的设置决定的。suppression_level参数对应的设置如表2.1所示: // Calculate the frequency-independent parts of parametric noise estimate. ③ 利用估计的白噪声和pink噪声的参数估计背景噪声 公式如下,当没有pink噪声时,使用白噪声估计;否则使用pink噪声进行估计: 使用num_analyzed_frames帧数据计算的噪声分位数和50-num_analyzed_frames帧利用噪声模型估计的噪声分位数来计算更新每个bin的噪声分位数。 注意,只有在开始处理的信号数据不足50帧时,按照上面的过程进行处理,当处理数据帧超过50后,只在Estimate函数中更新噪声的分位数。 4.2 计算信噪比 通过调用函数ComputeSnr来完成先验信噪比和后验信噪比的计算函数原型如下: void ComputeSnr (rtc::ArrayView<const float , kFftSizeBy2Plus1> filter,
rtc::ArrayView<const float > prev_signal_spectrum,
rtc::ArrayView<const float > signal_spectrum,
rtc::ArrayView<const float > prev_noise_spectrum,
rtc::ArrayView<const float > noise_spectrum,
rtc::ArrayView<float > prior_snr,
rtc::ArrayView<float > post_snr )
计算先验信噪比和后验信噪比是为了后面计算似然比(LRT),并进一步计算语音/噪声的概率而准备的。prior_snr和post_snr的计算如下: 4.3 数据更新 在这部分利用上一步计算的prior_snr和post_snr进行语音模型的更新,计算似然比LRT,最后计算语音的概率。这些处理是在speech_probability_estimator.cc中的update函数中实现的,函数原型如下: void SpeechProbabilityEstimator::Update(
int32_t num_analyzed_frames,
rtc::ArrayView<const float , kFftSizeBy2Plus1> prior_snr,
rtc::ArrayView<const float , kFftSizeBy2Plus1> post_snr,
rtc::ArrayView<const float , kFftSizeBy2Plus1> conservative_noise_spectrum,
rtc::ArrayView<const float , kFftSizeBy2Plus1> signal_spectrum,
float signal_spectral_sum,
float signal_energy)
4.3.1 更新语音/噪声的概率model 这部分的更新就是为了更新Spectral flatness 、LRT、Spectral template diff这三个特征量。分为两种情况: 情况1:num_analyzed_frames <200,即认为当前仍为噪声,重在初始噪声的估计,此时只更新特征量Spectral template diff,更新公式如下: 情景2:num_analyzed_frames ≥200,此时可能是有语音的,且初始噪声估计已经完成,因此三个特征量都是需要更新的: void UpdateSpectralFlatness (
rtc::ArrayView<const float , kFftSizeBy2Plus1> signal_spectrum,
float signal_spectral_sum,
float * spectral_flatness )
式中,kAveraging=0.3f 是固定值。这里的计算看着复杂,实际上就是式(1-9)的计算过程。只不过,为了避免直接计算开方的乘累积运算,在代码中将开方的乘累积运算转换成对数的累加和求平均的运算。大大降低了运算的复杂度。也正是由于运算中需要对输入信号进行对数运算,所以将所有频点信号中任一频谱为0的情况单独进行处理。 ② 计算Spectral template diff Spectral template diff是衡量输入频谱与模板/已学习过的噪声频谱之间的差异均值。其实现的函数原型为:
float ComputeSpectralDiff (
rtc::ArrayView<const float , kFftSizeBy2Plus1> conservative_noise_spectrum,
rtc::ArrayView<const float , kFftSizeBy2Plus1> signal_spectrum,
float signal_spectral_sum,
float diff_normalization )
更新最终的、经时间平均后的spectral_diff: 直方图的计算是用于提供参数判决的(门限和权重)。直方图中的参数每500帧提取一次,用于更新参数模型。因此这部分分为两部分处理,当迭代小于500帧时,更新直方图中的数据;迭代到500帧时,更新参数模型并重置直方图中的参数,为接下来的500次迭代做准备。 直方图参数的更新:更新lrt、spectral_flatness_和spectral_diff_三个直方图。这三个都是大小为kHistogramSize=1000的数组,更新方式如下:
prior_model_estimator_.Update(histograms_);
上式中lrt_histogram= histograms.lrt_。
*low_lrt_fluctuations = average_squared - average * average_compl < 0.05f ;
首先,调用FindFirstOfTwoLargestPeaks函数,找到spectral flatness 和 spectral difference的直方图中的最高峰值及其位置。如果最高峰和第二高峰相邻的话,且第二高峰值大于第一高峰值的一半的话,则认为最高峰的位置为两个最高峰值的中间,权重值为两个峰值的和。其次,判别找到恰当的权重值,最后更新模型参数。具体流程如下:
histograms_.Clear();
histogram_analysis_counter_ = kFeatureUpdateWindowSize;
void Histograms::Clear() {
lrt_.fill(0 );
spectral_flatness_.fill(0 );
spectral_diff_.fill(0 );
}
第三步:为下次估计过程计算归一化的spectral difference // Update every window:
// Compute normalization for the spectral difference for next estimation.
signal_energy_sum_ = signal_energy_sum_ / kFeatureUpdateWindowSize;
diff_normalization_ = 0.5f * (signal_energy_sum_ + diff_normalization_);
signal_energy_sum_ = 0.f;
根据之前的推导,将(1-19)和(1-20)式写在下面: 在WebRTC中,似然比的计算并没有完全按照上面的公式进行计算,而是进行了改动。由式1-19,有: 经过时间平滑处理的似然比因子的几何平均数(包括所有频率),可以作为对基于帧的语音/噪声分类的可靠测量结果: 除了先验SNR和瞬态SNR的引导,还将语音模型、认知内容并入语音/噪声概率的确认中,这可以让噪声抑制流程更好地处理和区分不稳定的噪声。如果仅依靠先验SNR,则可能产生估计偏差,故需对先验SNR、语音特征/模型数据信息进行逐帧更新,然后计算基于特征的概率 。因此,这里以帧为基础对数量 建模和更新,以抑制变量k的波动。基于特征概率更新模型可以采用下式: 式中, 为平滑常数,M(z,w)是给定时间和频率的映射函数(如在0和1之间),此映射函数中的变量Z=F-T,其中F是被测特征,T是阈值,参数w代表映射函数的形状和宽度特征。映射函数更加测量出的特征以及阈值和宽度参数,将时频点划分为语音(M接近1)或噪声(M接近0)。 4.3.2 计算映射函数和语音概率 基于特征的语音概率函数通过使用映射函数(sigmoid/tanh又称S函数,在神经元分类算法中常用为种子函数)将每帧的信号分类特征映射到一个概率值而得出的。 在webRTC中映射函数的宽度分情况,有两个值,分别如下:
constexpr float kWidthPrior0 = 4.f ;
constexpr float kWidthPrior1 = 2.f * kWidthPrior0;
float width_prior = model.lrt < prior_model.lrt ? kWidthPrior1 : kWidthPrior0;
width_prior = model.spectral_flatness > prior_model.flatness_threshold
? kWidthPrior1 : kWidthPrior0;
③ template spectrum-difference的映射函数 width_prior = model.spectral_diff < prior_model.template_diff_threshold
? kWidthPrior1 : kWidthPrior0;
从代码中看,在WebRTC中,Spectral flatness和template spectrum-difference目前的比重都为0,即,对语音概率的判断完全取决于LRT的计算结果。 4.4 noise_estimator.PostUpdate 后处理就是利用计算出来的语音概率来进行噪声谱数据的更新。噪声谱更新的公式如下: 式中, 是时间为m帧,频点为k的噪声谱估计; 是噪声更新的平滑因子; 为当前帧是语音的帧的概率, 是基于模型或者基于特征的语音/噪声概率。因此,上式表示的噪声估计模型对噪声可能性大(即语音概率较小)的每帧每个频点,(频带)的噪声估计进行更新。对噪声可能性不大的帧和频点(频带),则将信号中上一帧的估计作为噪声估计。 如果噪声的更新值小于先前的噪声更新值,则计算一个应用于语音帧的临时噪声更新值: 在WebRTC中,γ=0.9,上式表明,当信号帧为语音时,主要利用先前的噪声进行当前帧的噪声估计。接下来更新γ的值: 保守的噪声谱更新:当prob_speech < kProbRange(=0.2f)时,有: 至此,以上都处理完成后,将当前信号数据复制到prev对应的buffer中,以便下帧数据处理使用:
std ::copy(signal_spectrum.begin(), signal_spectrum.end(),
ch_p->prev_analysis_signal_spectrum.begin());
5. 噪声消除 噪声消除是通过调用modules\audio_processing\ns\noise_suppressor.cc文件中的函数接口NoiseSuppressor::Process(AudioBuffer* audio)来实现的。噪声消除处理的基本流程图如图4-1所示: 5.1 组建extended frame数据 extended frame由上一帧的96个数据+当前帧的160个数据组成256点的新数组,以便进行256点的FFT变换。在代码中,由函数FormExtendedFrame来实现,如下: FormExtendedFrame (y_band0 , channels_ [ch] - >process_analysis_memory , filter_bank_states [ch] .extended_frame );
y_band0为当前帧160点数据,process_analysis_memory存放着上一帧的96点数据。 组合成extended_frame数据后,会将y_band0中的最后96个采样点复制到process_analysis_memory中,以供下一帧使用。 5.2 对extended_frame数据加窗(分析窗)并计算滤波前的能量 分析窗函数使用的是混合Hanning和flat窗函数,即前后各96点数据使用的是Hanning窗,中间的64点数据使用系数为1的flat窗函数。即: i =[0 ,95 ] x[i]= kBlocks160w256FirstHalf[i] * x[i]
i =[96 ,160 ] x[i]= x[i]
i =[161 ,256 ] x[i]= kBlocks160w256FirstHalf[k] * x[i] k=95 ,94 ,…,0 ;i=161 ,162 ,…,256
5.3 FFT变换 采用256点FFT变换,变换后的实部数据存放在filter_bank_states[ch].real中,虚部数据存放在filter_bank_states[ch].imag中。real和imag均为256点的数组。
fft_.Fft(filter_bank_states[ch].extended_frame, filter_bank_states[ch].real , filter_bank_states[ch].imag );
5.4 计算信号的Magnitude Spectrum Magnitude Spectrum计算公式如下: 5.5 计算维纳滤波器增益 噪声消除模块采用维纳滤波器以减少或消除信号帧中的估计噪声量。因此,先进行维纳滤波器的更新。WebRTC中更新维纳滤波器的代码如下:
channels_[ch]->wiener_filter.Update(
num_analyzed_frames_,
channels_[ch]->noise_estimator.get_noise_spectrum(),
channels_[ch]->noise_estimator.get_prev_noise_spectrum(),
channels_[ch]->noise_estimator.get_parametric_noise_spectrum(),
signal_spectrum);
(3) 基于先前估计和当前估计的DD(Directed decision)估计滤波器增益 式中,over_subtraction_factor和minimum_attenuating_gain的取值是通过参数设置的,详细的参数设置,参照表3.1。 若信号处理的帧数小于50,即,认为当前还是噪声帧。此时,维纳滤波器的更新采用当前信号数据与搭建的噪声参数模型联合更新的方式进行,具体过程如下: (b) Weight the two suppression filters (5) 更新spectrum_prev_process_中的数据 std::copy(signal_spectrum.begin (), signal_spectrum.end (), spectrum_prev_process_ .begin ());
5.6 计算upper bands噪声衰减增益(针对多band情况,非必做) 对the lowest band的最后32个频率点计算平均语音概率和滤波器增益: 如果在Analyze 和 Process处理之间,信号帧已经被其他模块处理过(比如AEC处理),那么该帧语音就不应该被认为是需要进行high band抑制的语音。此时,对语音概率应当进行适当的调整: 5.7 进行维纳滤波,消除噪声 若num_channels_ == 1,filter = channels_[0]->wiener_filter.get_filter() 否则,综合所有通道的滤波器增益,针对每个频点取最小的滤波器增: 对每个channel的数据应用维纳滤波器,消除信号中的噪声: for (size_t ch = 0 ; ch < num_channels_; ++ch) {
for (size_t i = 0 ; i < kFftSizeBy2Plus1; ++i) {
filter_bank_states[ch].real[i] *= filter[i];
filter_bank_states[ch].imag[i] *= filter[i];
}
}
5.8 时域处理 经滤波后的信号进行IFFT,将频域信号转换为时域信号,处理后的时域信号保存到extended_frame中: for (size_t ch = 0 ; ch < num_channels_; ++ch) { fft_.Ifft(filter_bank_states[ch].real , filter_bank_states[ch].imag , filter_bank_states[ch].extended_frame); }
对滤波后的时域信号应用综合窗,得到经过降噪处理后的正常时域信号,代码如下:
ApplyFilterBankWindow(filter_bank_states[ch].extended_frame);
(c) 基于消噪的效果计算维纳滤波器调整的比例因子 ① 若!suppression_params_.use_attenuation_adjustment||num_analyzed_frames<=200: gain_adjustments[ch] = 1.f // Threshold in final energy gain factor calculation. 对于语音间隔处,scale不应该减少的太多,衰减程度应该有下限控制: 结合scale_factor1、scale_factor2以及语音概率,计算最终的调整因子。注意,这里的语音概率与频率无关: 调用OverlapAndAdd函数,将256点的数据转换为160点数据,并存放到输出buffer中。数据的转换过程示意图如下: ① 选择应用于upper band的噪声衰减增益:从各channel中选择最小的增益值。 将upper bands的数据进行时延,以匹配对the lowest band进行滤波器组处理而带来的时延,时延的处理如下: DelaySignal(y_band, channels_[ch]->process_delay_memory[b - 1 ], delayed_frame);
void DelaySignal (rtc::ArrayView<const float , kNsFrameSize> frame,
rtc::ArrayView<float , kFftSize - kNsFrameSize> delay_buffer,
rtc::ArrayView<float , kNsFrameSize> delayed_frame) {
constexpr size_t kSamplesFromFrame = kNsFrameSize - (kFftSize - kNsFrameSize);
std ::copy(delay_buffer.begin(), delay_buffer.end(), delayed_frame.begin());
std ::copy(frame.begin(), frame.begin() + kSamplesFromFrame,
delayed_frame.begin() + delay_buffer.size());
std ::copy(frame.begin() + kSamplesFromFrame, frame.end(),
delay_buffer.begin());
}
时延对齐之后,将上一步获得的upper band的gain进行应用:
for (size_t j = 0 ; j < kNsFrameSize; j++) {
y_band[j] = upper_band_gain * delayed_frame[j];
}
为了防止削波带来的失真,将输出数据(每个channel,每个band)限制在特定范围内: 在后续文章中,我们将继续分享 “ORTC 实时音视频平台”的相关技术,欢迎关注OPPO互联网技术公众号。