概述
AEC非线性处理模块
非线性处理是对残留会生进行处理,包括回声抑制和舒适噪音产生,它的原理是将基于块的信号转换为频域,针对每个频带计算至少2个信号之间的一个或者多个相干性,基于这些相干性的值,计算每个频带相对应的抑制因子,通过抑制因子与误差信号相乘来抑制语音信号中残留的回声。
流程图如下:
源码如下所示:
//非线性处理模块
static void NonLinearProcessing(AecCore* aec,
float* output,
float* const* outputH) {
//通过上边的加窗操作得到频谱efw,xfw(防止频谱泄露)
float efw[2][PART_LEN1], xfw[2][PART_LEN1];
complex_t comfortNoiseHband[PART_LEN1];
float fft[PART_LEN2];
float scale, dtmp;
//NLP h波段增益
float nlpGainHband;
int i;
size_t j;
// 相干和非线性滤波器
//cohde表示误差和近端信号相关性,值越大会回声越小,
//cohxd:误差和远端信号的相关性,值越大,回声越大
float cohde[PART_LEN1], cohxd[PART_LEN1];
//hNlXdAvg 表示参考信号与mic接收信号的不相关性;
// hNlDeAvg 表示aec输出信号与mic接收信号的相关性。
float hNlDeAvg, hNlXdAvg;
float hNl[PART_LEN1];
float hNlPref[kPrefBandSize];
//过载变量设置
float hNlFb = 0, hNlFbLow = 0;
const float prefBandQuant = 0.75f, prefBandQuantLow = 0.5f;
const int prefBandSize = kPrefBandSize / aec->mult;
const int minPrefBand = 4 / aec->mult;
// 功率估计平滑系数。
const float* min_overdrive = aec->extended_filter_enabled
? kExtendedMinOverDrive
: kNormalMinOverDrive;
// Filter energy
//延迟估计间隔
const int delayEstInterval = 10 * aec->mult;
float* xfw_ptr = NULL;
aec->delayEstCtr++;
if (aec->delayEstCtr == delayEstInterval) {
aec->delayEstCtr = 0;
}
// 初始化H波段的舒适噪音
memset(comfortNoiseHband, 0, sizeof(comfortNoiseHband));
nlpGainHband = (float)0.0;
dtmp = (float)0.0;
// 我们应该至少在| far_buf |中存储至少一个元素。
assert(WebRtc_available_read(aec->far_buf_windowed) > 0);
// NLP
WebRtc_ReadBuffer(aec->far_buf_windowed, (void**)&xfw_ptr, &xfw[0][0], 1);
// TODO(bjornv):研究是否可以重用| far_buf_windowed | 代替
// | xfwBuf |。
//远端缓冲远。
memcpy(aec->xfwBuf, xfw_ptr, sizeof(float) * 2 * PART_LEN1);
//自带相干性
WebRtcAec_SubbandCoherence(aec, efw, xfw, fft, cohde, cohxd);
hNlXdAvg = 0;
for (i = minPrefBand; i < prefBandSize + minPrefBand; i++) {
hNlXdAvg += cohxd[i];
}
hNlXdAvg /= prefBandSize;
hNlXdAvg = 1 - hNlXdAvg;
hNlDeAvg = 0;
for (i = minPrefBand; i < prefBandSize + minPrefBand; i++) {
hNlDeAvg += cohde[i];
}
hNlDeAvg /= prefBandSize;
/*主要用于更新hNlXdAvg的最小值hNlXdAvgMin。
// 数值0.75控制了该更新的频率,如果或者数值越大,
//表面hNlXdAvgMin的更新频率越快,对残留回声也会越敏感*/
if (hNlXdAvg < 0.75f && hNlXdAvg < aec->hNlXdAvgMin) {
aec->hNlXdAvgMin = hNlXdAvg;
}
/*主要用于更新hNlXdAvg的最小值hNlXdAvgMin。
数值0.75控制了该更新的频率,如果或者数值越大,
表面hNlXdAvgMin的更新频率越快,对残留回声也会越敏感*/
if (hNlDeAvg > 0.98f && hNlXdAvg > 0.9f) {
//由于几乎只有近端语音则判定近端状态为1
aec->stNearState = 1;
} else if (hNlDeAvg < 0.95f || hNlXdAvg < 0.8f) {
/*aec输出信号与mic接收信号相关性较小,
或者参考信号与mic接收信号的不相关性较小(相关性较大),说
明此时存在残留回声需要抑制*/
aec->stNearState = 0;
}
if (aec->hNlXdAvgMin == 1) {
/* 说明一段时间内的hNlXdAvgMin一直没有更新,
也即hNlXdAvg较大(这里大于0.75),
说明在较长的时间内,参考信号与mic接收信号的相关性较弱,
也即回声残留程度较小*/
aec->echoState = 0;
aec->overDrive = min_overdrive[aec->nlp_mode];
if (aec->stNearState == 1) {
//说明回声很小或者没有
memcpy(hNl, cohde, sizeof(hNl));
//由于不存在回声则也就不存在过载的情况
hNlFb = hNlDeAvg;
hNlFbLow = hNlDeAvg;
} else {
//处理过载情况
for (i = 0; i < PART_LEN1; i++) {
hNl[i] = 1 - cohxd[i];
}
hNlFb = hNlXdAvg;
hNlFbLow = hNlXdAvg;
}
} else {
if (aec->stNearState == 1) {
aec->echoState = 0;
memcpy(hNl, cohde, sizeof(hNl));
hNlFb = hNlDeAvg;
hNlFbLow = hNlDeAvg;
} else {
aec->echoState = 1;
for (i = 0; i < PART_LEN1; i++) {
hNl[i] = WEBRTC_SPL_MIN(cohde[i], 1 - cohxd[i]);
}
//从首选频段中选择顺序统计信息。
// TODO:现在使用quicksort,但是选择算法可能是首选。
memcpy(hNlPref, &hNl[minPrefBand], sizeof(float) * prefBandSize);
qsort(hNlPref, prefBandSize, sizeof(float), CmpFloat);
hNlFb = hNlPref[(int)floor(prefBandQuant * (prefBandSize - 1))];
hNlFbLow = hNlPref[(int)floor(prefBandQuantLow * (prefBandSize - 1))];
}
}
// 跟踪本地滤波器最小值以确定抑制过载。
/*检测一段时间内是否出现了更小的hNlFbMin, hNlFbMin用来更新overd的抑制程度。
数值0.6用来控制参数更新频率,该数值越大hNlFbMin更新越频繁,对于残留回声会越敏感*/
if (hNlFbLow < 0.6f && hNlFbLow < aec->hNlFbLocalMin) {
aec->hNlFbLocalMin = hNlFbLow;
aec->hNlFbMin = hNlFbLow;
//更新最小值和
aec->hNlNewMin = 1;
aec->hNlMinCtr = 0;
}
/*以下两个参数以固定的步长更新,为的是hNlXdAvgMin与hNlFbMin不会陷入死锁状态无法更新。
当然这里的步长因子也可以控制上述两个数值的更新频率,一般是步长因子越大更新越频繁*/
aec->hNlFbLocalMin =
WEBRTC_SPL_MIN(aec->hNlFbLocalMin + 0.0008f / aec->mult, 1);
aec->hNlXdAvgMin = WEBRTC_SPL_MIN(aec->hNlXdAvgMin + 0.0006f / aec->mult, 1);
if (aec->hNlNewMin == 1) {
aec->hNlMinCtr++;
}
/*hNlMinCtr == 2表明hNlFbMin只在当前帧更新,而下一帧不更新。
也即,当前帧找到最小数值需要连续满足hnlMinCtr - 1帧,防止误触发*/
if (aec->hNlMinCtr == 2) {
aec->hNlNewMin = 0;
aec->hNlMinCtr = 0;
/*kTargetSupp[aec->nlp_mode]用来设置当前帧抑制多少dB*/
aec->overDrive =
WEBRTC_SPL_MAX(kTargetSupp[aec->nlp_mode] /
((float)log(aec->hNlFbMin + 1e-10f) + 1e-10f),
min_overdrive[aec->nlp_mode]);
}
//平滑过载。
if (aec->overDrive < aec->overDriveSm) {
aec->overDriveSm = 0.99f * aec->overDriveSm + 0.01f * aec->overDrive;
} else {
aec->overDriveSm = 0.9f * aec->overDriveSm + 0.1f * aec->overDrive;
}
WebRtcAec_OverdriveAndSuppress(aec, hNl, hNlFb, efw);
//增加舒适噪音
WebRtcAec_ComfortNoise(aec, efw, comfortNoiseHband, aec->noisePow, hNl);
// TODO(bjornv): 研究在以下情况下如何考虑以下窗口
//需要。
if (aec->metricsMode == 1) {
// 注意,我们在时域| eBuf |中将比例缩放为2。
//另外,在转换前将时域信号加窗,
//平均损失一半的能量。 我们先考虑仅在UpdateMetrics()中缩放。
UpdateLevel(&aec->nlpoutlevel, efw);
}
// 逆 error fft.
fft[0] = efw[0][0];
fft[1] = efw[0][PART_LEN];
for (i = 1; i < PART_LEN; i++) {
fft[2 * i] = efw[0][i];
// Ooura fft要求更信号。
fft[2 * i + 1] = -efw[1][i];
}
aec_rdft_inverse_128(fft);
// 重叠并相加以获得输出。
/*
由于语音信号进行处理时都是进行分帧操作,每一帧之间都有加窗和重叠,
因此在频域处理后采用重叠的操作,在重叠下哦ian感觉hi啊时需要乘以窗函数
原因是:fft加加窗之前相当于对原始信号进行缩放了,ifft操作之后,将得到已经进行缩放的信号,
再加窗相当于把缩放的信号进行恢复。
*/
scale = 2.0f / PART_LEN2;
for (i = 0; i < PART_LEN; i++) {
fft[i] *= scale; // fft scaling
fft[i] = fft[i] * WebRtcAec_sqrtHanning[i] + aec->outBuf[i];
fft[PART_LEN + i] *= scale; // fft scaling
aec->outBuf[i] = fft[PART_LEN + i] * WebRtcAec_sqrtHanning[PART_LEN - i];//乘以加窗函数
// 饱和输出以使其保持在允许范围内。
output[i] = WEBRTC_SPL_SAT(
WEBRTC_SPL_WORD16_MAX, fft[i], WEBRTC_SPL_WORD16_MIN);
}
// For H band
if (aec->num_bands > 1) {
// H波段增益
//低频段的平均nlp:频率频谱后半段的平均值
//(4-> 8khz)
GetHighbandGain(hNl, &nlpGainHband);
// 逆舒适噪音
if (flagHbandCn == 1) {
fft[0] = comfortNoiseHband[0][0];
fft[1] = comfortNoiseHband[PART_LEN][0];
for (i = 1; i < PART_LEN; i++) {
fft[2 * i] = comfortNoiseHband[i][0];
fft[2 * i + 1] = comfortNoiseHband[i][1];
}
aec_rdft_inverse_128(fft);
scale = 2.0f / PART_LEN2;
}
// 计算增益因子
for (j = 0; j < aec->num_bands - 1; ++j) {
for (i = 0; i < PART_LEN; i++) {
dtmp = aec->dBufH[j][i];
dtmp = dtmp * nlpGainHband; // 可变增益
// 在Hband衰减的地方添加一些舒适噪音
if (flagHbandCn == 1 && j == 0) {
fft[i] *= scale; // fft scaling
dtmp += cnScaleHband * fft[i];
}
// 饱和输出以使其保持在允许范围内。
outputH[j][i] = WEBRTC_SPL_SAT(
WEBRTC_SPL_WORD16_MAX, dtmp, WEBRTC_SPL_WORD16_MIN);
}
}
}
//将当前块复制到旧位置。
memcpy(aec->dBuf, aec->dBuf + PART_LEN, sizeof(float) * PART_LEN);
memcpy(aec->eBuf, aec->eBuf + PART_LEN, sizeof(float) * PART_LEN);
// 将当前块复制到H波段的旧位置
for (j = 0; j < aec->num_bands - 1; ++j) {
memcpy(aec->dBufH[j], aec->dBufH[j] + PART_LEN, sizeof(float) * PART_LEN);
}
memmove(aec->xfwBuf + PART_LEN1,
aec->xfwBuf,
sizeof(aec->xfwBuf) - sizeof(complex_t) * PART_LEN1);
}
由于AEC3的非线性处理远比AEC的非线性处理复杂的多,AEC3我暂时还不深很清晰具体的消除原理,因此大家要是有对AEC3NLP原理有见解的欢迎随时交流!!!
最后
以上就是舒适曲奇为你收集整理的AEC非线性处理模块的全部内容,希望文章能够帮你解决AEC非线性处理模块所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复