webrtc-agc 自动增益控制算法

0x13 2024-08-13 11:33:15 阅读 66

最近又开始调 webrtc-agc 算法,这里记录自适应模拟增益模式下音量反馈调节的过程。

AGC算法里面相关的函数:

<code>WebRtcAgc_AddMic:用于将来自麦克风的音频帧输入 AGC 处理流程。这是原始音频帧的输入点。

WebRtcAgc_AddFarend:用于添加来自远端音频的音频帧,以考虑远端声音对 AGC 处理的影响。这通常用于处理回声的情况。

WebRtcAgc_GetAddFarendError:获取 WebRtcAgc_AddFarend 函数的错误状态,以检测是否成功添加了远端音频。

WebRtcAgc_VirtualMic:用于模拟一个虚拟的麦克风输入,以用于 AGC 的测试和调试。

WebRtcAgc_UpdateAgcThresholds:更新 AGC 的阈值参数,以根据音频场景的变化来调整 AGC 的行为。

WebRtcAgc_SaturationCtrl:控制 AGC 处理中的饱和度,以确保音频信号不会过于放大,以防止失真。

WebRtcAgc_ZeroCtrl:用于控制 AGC 的零值处理,以确保输出的音频不会有过多的静音。

WebRtcAgc_SpeakerInactiveCtrl:控制 AGC 在检测到说话者不活跃时的处理,以减小噪声的放大。

WebRtcAgc_ExpCurve:处理音频信号的增益曲线,以根据音频能量来调整增益。

WebRtcAgc_ProcessAnalog:执行 AGC 的模拟处理,用于处理模拟音频信号。

WebRtcAgc_Process:执行 AGC 的数字处理,用于处理数字音频信号。

WebRtcAgc_set_config:用于设置 AGC 的配置参数,如目标能量、阈值等。

WebRtcAgc_get_config:用于获取当前 AGC 的配置参数。

WebRtcAgc_Create:创建 AGC 的实例。

WebRtcAgc_Free:释放 AGC 的实例。

WebRtcAgc_Init:初始化 AGC,包括分配内存和设置初始参数。

WebRtcAgc_CalculateGainTable:用于计算 AGC 的增益表,以在处理音频时快速查找所需的增益值。

WebRtcAgc_InitDigital:初始化 AGC 的数字处理部分。

WebRtcAgc_AddFarendToDigital:将远端音频添加到数字 AGC 处理中。

WebRtcAgc_ProcessDigital:执行数字 AGC 处理,处理数字音频信号。

WebRtcAgc_InitVad:初始化 AGC 的语音活动检测(VAD)部分,用于检测语音活动。

WebRtcAgc_ProcessVad:执行 VAD 处理,用于检测语音活动

下面是音量调节流程:

webrtc-agc 算法自适应模拟增益音量调节流程:

0.WebRtcAgc_set_config 设置参数,有几个参数在后续调节音量会用到。

1.WebRtcAgc_UpdateAgcThresholds 参数设置,自适应参数设置,后面会计算一帧的能量判断处于哪个区间来确定音量增大还是减小

#define ANALOG_TARGET_LEVEL 11

#define OFFSET_ENV_TO_RMS 9

# targetIdx 一直是20

stt->;targetIdx = ANALOG_TARGET_LEVEL + OFFSET_ENV_TO_RMS;

# 下面是

static const int32_t kTargetLevelTable[64] = {

134209536, 106606424, 84680493, 67264106, 53429779, 42440782, 33711911,

26778323, 21270778, 16895980, 13420954, 10660642, 8468049, 6726411,

5342978, 4244078, 3371191, 2677832, 2127078, 1689598, 1342095,

1066064, 846805, 672641, 534298, 424408, 337119, 267783,

212708, 168960, 134210, 106606, 84680, 67264, 53430,

42441, 33712, 26778, 21271, 16896, 13421, 10661,

8468, 6726, 5343, 4244, 3371, 2678, 2127,

1690, 1342, 1066, 847, 673, 534, 424,

337, 268, 213, 169, 134, 107, 85,

67};

#ifdef MIC_LEVEL_FEEDBACK

stt->targetIdx += stt->targetIdxOffset;

#endif

/* kTargetLevelTable[20]=1342095 */

/* analogTargetLevel = round((32767*10^(-targetIdx/20))^2*16/2^7) */

stt->analogTargetLevel = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx]; /* ex. -20 dBov */

stt->startUpperLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx - 1]; /* -19 dBov */

stt->startLowerLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx + 1]; /* -21 dBov */

stt->upperPrimaryLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx - 2]; /* -18 dBov */

stt->lowerPrimaryLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx + 2]; /* -22 dBov */

stt->upperSecondaryLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx - 5]; /* -15 dBov */

stt->lowerSecondaryLimit = RXX_BUFFER_LEN * kTargetLevelTable[stt->targetIdx + 5]; /* -25 dBov */

stt->upperLimit = stt->startUpperLimit;

stt->lowerLimit = stt->startLowerLimit;

1.WebRtcAgc_AddMic模拟增益需要调用这个函数

1.上一次 micVol 调到最大了都不满足目标音量,自动乘0~3.16倍,也就是在输入音量基础上做一个放大

if (stt->micVol > stt->maxAnalog) {

...

// 对输入音频样本应用增益

for (i = 0; i < samples; i++) {

size_t j;

for (j = 0; j < num_bands; ++j) {

// 对输入音频样本应用增益 并限制范围

// 经过右移之后,数组被量化到0~3.16.

sample = (in_mic[j][i] * gain) >> 12;

if (sample > 32767) {

in_mic[j][i] = 32767;

} else if (sample < -32768) {

in_mic[j][i] = -32768;

} else {

in_mic[j][i] = (int16_t) sample;

}

}

}

}

else {

stt->gainTableIdx = 0;

}

2.计算当前帧音频信号的包络值,保存在 env[2][10]中,后续会用来判断是否饱和、是否长时间静音

if (stt->inQueue > 0) {

ptr = stt->env[1];

} else {

ptr = stt->env[0];

}

for (i = 0; i < kNumSubframes; i++) {

/* iterate over samples */

max_nrg = 0;

for (n = 0; n < L; n++) {

nrg = in_mic[0][i * L + n] * in_mic[0][i * L + n];

if (nrg > max_nrg) {

max_nrg = nrg;

}

}

ptr[i] = max_nrg;

}

3.计算当前帧音频信号的能量值,保存到 Rxx16w32_array 中,后续用来调音

if (stt->inQueue > 0) {

ptr = stt->Rxx16w32_array[1];

} else {

ptr = stt->Rxx16w32_array[0];

}

// 一帧又分为5个子帧

for (i = 0; i < kNumSubframes / 2; i++) {

// 16k 采样 1帧为 160 点 5个子帧每个子帧为 32个点 ,也就是每32个点计算一次能量

if (stt->fs == 16000) {

downsampleBy2(&in_mic[0][i * 32], 32, tmp_speech,stt->filterState);

}

// 8k采样 1帧为 80 点,5个子帧每个子帧为 16个点,也就是每16个点计算一次能量

else {

memcpy(tmp_speech, &in_mic[0][i * 16], 16 * sizeof(short));

}

/* Compute energy in blocks of 16 samples */

ptr[i] = DotProductWithScale(tmp_speech, tmp_speech, 16, 4);

}

4.WebRtcAgc_ProcessVad 执行一次VAD, 每1毫秒降采样到4k然后计算信噪比,保存 state->meanLongTerm(长期能量均值)、state->varianceLongTerm(长期能量方差)、state->stdLongTerm(长期能量标准差)等参数,后面是通过 logRatio < vadThreshold 认为有语音活动

for (subfr = 0; subfr < 10; subfr++) {

// downsample to 4 kHz

if (nrSamples == 160) {

for (k = 0; k < 8; k++) {

tmp32 = (int32_t) in[2 * k] + (int32_t) in[2 * k + 1];

tmp32 >>= 1;

buf1[k] = (int16_t) tmp32;

}

in += 16;

downsampleBy2(buf1, 8, buf2, state->downState);

} else {

downsampleBy2(in, 8, buf2, state->downState);

in += 8;

}

// 高通滤波器与计算能量

for (k = 0; k < 4; k++) {

out = buf2[k] + HPstate;

tmp32 = 600 * out;

HPstate = (int16_t) ((tmp32 >> 10) - buf2[k]);

// Add 'out * out / 2**6' to 'nrg' in a non-overflowing

// way. Guaranteed to work as long as 'out * out / 2**6' fits in

// an int32_t.

nrg += out * (out / (1 << 6));

nrg += out * (out % (1 << 6)) / (1 << 6);

}

}

// 确定信号级别

// energy level (range {-32..30}) (Q10)

dB = (15 - zeros) * (1 << 11);

if (state->counter < kAvgDecayTime) {

// decay time = AvgDecTime * 10 ms

state->counter++;

}

// 后面是计算信噪比、以及其他能量参数

// ...

2.WebRtcAgc_Process

1.WebRtcAgc_ProcessDigital每一帧都会先进行数字增益

2.WebRtcAgc_ProcessAnalog只有模拟增益才会进入

1.首次调用时 51/512=0.099,确保首次音量初始化时设置音量不低于音量范围的 0.099倍。

if (stt->firstCall == 0) {

int32_t tmpVol;

stt->firstCall = 1; // 将 firstCall 标记为已调用过

// tmp32是整个音量范围的0.099倍

tmp32 = ((stt->maxLevel - stt->minLevel) * 51) >> 9;

// (minLevel有可能不等于0所以要加偏移)

tmpVol = (stt->minLevel + tmp32);

if ((inMicLevelTmp < tmpVol) && (stt->agcMode == kAgcModeAdaptiveAnalog)) {

inMicLevelTmp = tmpVol;

}

// 确保首次音量初始化时设置音量不低于音量范围的 0.099倍

stt->micVol = inMicLevelTmp;

}

2.如果前面应用了数字增益,确保不会将模拟麦克风的音量提高到超过数字增益的最大级别。

if ((inMicLevelTmp == stt->maxAnalog) && (stt->micVol > stt->maxAnalog)) {

inMicLevelTmp = stt->micVol;

}

3.当麦克风音量 inMicLevelTmp 被手动设置为非常低的值将麦克风音量提高

if ((inMicLevelTmp != stt->micVol) && (inMicLevelTmp < stt->minOutput)) {

tmp32 = ((stt->maxLevel - stt->minLevel) * 51) >> 9;

inMicLevelTmp = (stt->minLevel + tmp32);

stt->micVol = inMicLevelTmp;

}

4.判断信号是否过饱和 WebRtcAgc_SaturationCtrl(stt, &saturated, stt->env[0]), 计算结果 saturated。

// 包络数组

for (i = 0; i < 10; i++) {

tmpW16 = (int16_t) (env[i] >> 20);

if (tmpW16 > 875) {

stt->envSum += tmpW16;// 将信号包络值压缩后累加

}

}

if (stt->envSum > 25000) {// 总的超过25000认为是饱和

*saturated = 1;

stt->envSum = 0; // stt->envSum 是一个累积变量,用于跟踪多个帧中的信号过饱和情况。检测到过饱和之后重置

}

5.如果过饱和将音量 micVol 缩减到0.903倍,重置一些阈值参数。zeroCtrlMax保存饱和时的音量值,保证后续长时间静音时增大音量不会超过这个值

if (saturated == 1) {

stt->Rxx160_LPw32 = (stt->Rxx160_LPw32 / 8) * 7; // 自相关系数降低0.875倍数,

stt->zeroCtrlMax = stt->micVol;

// 29591/32768 = 0.903 将当前音量缩减到 0.903 倍,并确保和上一次相差不超过2

tmp32 = inMicLevelTmp - stt->minLevel;

tmpU32 = ((uint32_t) ((uint32_t) (29591) * (uint32_t) (tmp32)));

stt->micVol = (tmpU32 >> 15) + stt->minLevel;

if (stt->micVol > lastMicVol - 2) {

stt->micVol = lastMicVol - 2;

}

inMicLevelTmp = stt->micVol;

if (stt->micVol < stt->minOutput) {

*saturationWarning = 1; // 过饱和警告

}

stt->msTooHigh = -100;

stt->activeSpeech = 0;

stt->Rxx16_LPw32Max = 0;

stt->msecSpeechInnerChange = kMsecSpeechInner;

stt->msecSpeechOuterChange = kMsecSpeechOuter;

stt->changeToSlowMode = 0;

stt->muteGuardMs = 0;

stt->upperLimit = stt->startUpperLimit;

stt->lowerLimit = stt->startLowerLimit;

#ifdef MIC_LEVEL_FEEDBACK

// stt->numBlocksMicLvlSat = 0;

#endif

}

6.判断信号是否几乎全为0,也是通过包络数组计算。小于500表示非完全静音,大于500表示连续500ms内处于静音,音量增到到1.1倍,最大不能超过饱和时记录的音量

void WebRtcAgc_ZeroCtrl(LegacyAgc *stt, int32_t *inMicLevel, const int32_t *env) {

int16_t i;

int64_t tmp = 0;

int32_t midVal;

for (i = 0; i < 10; i++) {

tmp += env[i];

}

if (tmp < 500) {// 非完全静音,累加到 msZero

stt->msZero += 10;

} else {

stt->msZero = 0;

}

if (stt->muteGuardMs > 0) {

stt->muteGuardMs -= 10;

}

if (stt->msZero > 500) {// 完全静音

stt->msZero = 0;

midVal = (stt->maxAnalog + stt->minLevel + 1) / 2; // 计算中等水平的音量

if (*inMicLevel < midVal) {

// 增加到 1.1 倍数, 最大不超过上一次饱和时计算的音量

*inMicLevel = (1126 * *inMicLevel) >> 10;

*inMicLevel = MIN(*inMicLevel, stt->zeroCtrlMax);

stt->micVol = *inMicLevel;

}

stt->activeSpeech = 0;// 不活跃信号

stt->Rxx16_LPw32Max = 0;

stt->muteGuardMs = kMuteGuardTimeMs;

}

}

7.根据当前信号活跃状态调整VAD阈值,长时间静音阈值为15000、活跃状态阈值2500,后新旧VAD阈值做滑动平均之后保存,用于下一次的VAD判断

// stdLongTerm 是长期能量标准差在上一次VAD判决中计算得到,越大表示越可能有语音活动

if (stt->vadMic.stdLongTerm < 2500) {

stt->vadThreshold = 1500;

} else {

vadThresh = kNormalVadThreshold;

if (stt->vadMic.stdLongTerm < 4500) {

/* Scale between min and max threshold */

vadThresh += (4500 - stt->vadMic.stdLongTerm) / 2;

}

/* stt->vadThreshold = (31 * stt->vadThreshold + vadThresh) / 32; */

tmp32 = vadThresh + 31 * stt->vadThreshold;

stt->vadThreshold = (int16_t) (tmp32 >> 5);

}

8.下面根据 vadLogRatio 的值,判断是否检测到语音活动。如果检测到语音活动则进行调音,会动态调整 AGC 阈值和麦克风级别。根据全帧能量Rxx160_LPw32 所在4个范围如下:

如果 stt->Rxx160_LPw32 大于 stt->upperSecondaryLimit 会降低录音级别,以避免饱和。

如果 stt->Rxx160_LPw32 大于 stt->upperLimit,会降低录音级别,以避免饱和。

如果 stt->Rxx160_LPw32 小于 stt->lowerSecondaryLimit ,会提高录音级别。

如果 stt->Rxx160_LPw32 小于 stt->lowerLimit,会提高录音级别。

如果不在4中情况范围内,lowerLimit < Rxx160_LP/640 < upperLimit 4000ms后可以触发慢变模式(changeToSlowMode)

部分代码如下:

// 音量缩减为 0.95倍

if (stt->Rxx160_LPw32 > stt->upperSecondaryLimit) {

stt->msTooHigh += 2; // 递增,记录音频信号能量过强的时间

stt->msTooLow = 0; // 音频信号过低清零

stt->changeToSlowMode = 0; // 停止慢速模式

if (stt->msTooHigh > stt->msecSpeechOuterChange) { // 音频信号过强持续时间达到上线

stt->msTooHigh = 0;// 重新信号能量过强记时

/* Lower the recording level */

/* Multiply by 0.828125 which corresponds to decreasing ~0.8dB */

tmp32 = stt->Rxx160_LPw32 >> 6;

stt->Rxx160_LPw32 = tmp32 * 53;

/* Reduce the max gain to avoid excessive oscillation

* (but never drop below the maximum analog level).

*/

stt->maxLevel = (15 * stt->maxLevel + stt->micVol) / 16;

stt->maxLevel = MAX(stt->maxLevel, stt->maxAnalog);

stt->zeroCtrlMax = stt->micVol;

/* 0.95 in Q15 */

tmp32 = inMicLevelTmp - stt->minLevel;

tmpU32 = ((uint32_t) ((uint32_t) (31130) * (uint32_t) (tmp32)));

stt->micVol = (tmpU32 >> 15) + stt->minLevel;

if (stt->micVol > lastMicVol - 1) {

stt->micVol = lastMicVol - 1;

}

inMicLevelTmp = stt->micVol;

stt->activeSpeech = 0;

stt->Rxx16_LPw32Max = 0;

}

}

// 音量缩减为 0.95倍

else if (stt->Rxx160_LPw32 > stt->upperLimit) {

stt->msTooHigh += 2;

stt->msTooLow = 0;

stt->changeToSlowMode = 0;

if (stt->msTooHigh > stt->msecSpeechInnerChange) {

/* Lower the recording level */

stt->msTooHigh = 0;

/* Multiply by 0.828125 which corresponds to decreasing ~0.8dB */

stt->Rxx160_LPw32 = (stt->Rxx160_LPw32 / 64) * 53;

/* Reduce the max gain to avoid excessive oscillation

* (but never drop below the maximum analog level).

*/

stt->maxLevel = (15 * stt->maxLevel + stt->micVol) / 16;

stt->maxLevel = MAX(stt->maxLevel, stt->maxAnalog);

stt->zeroCtrlMax = stt->micVol;

/* 0.965 in Q15 */ // 音量缩减为 0.965 倍数

//tmp32 = inMicLevelTmp - stt->minLevel;

tmpU32 = ((uint32_t) ((uint32_t) (31621) * (uint32_t) ((inMicLevelTmp - stt->minLevel))));

stt->micVol = (tmpU32 >> 15) + stt->minLevel;

if (stt->micVol > lastMicVol - 1) {

stt->micVol = lastMicVol - 1;

}

inMicLevelTmp = stt->micVol;

}

}

// 音量增大为 1.047倍数

else if (stt->Rxx160_LPw32 < stt->lowerSecondaryLimit) {

stt->msTooHigh = 0;// 重置强音量持续时长

stt->changeToSlowMode = 0;

stt->msTooLow += 2; // 低音量持续时长递增

if (stt->msTooLow > stt->msecSpeechOuterChange) { // 低音量持续时长达到一段时间则进行音量放大

/* Raise the recording level */

int16_t index, weightFIX;

int16_t volNormFIX = 16384; // =1 in Q14.

stt->msTooLow = 0;

/* Normalize the volume level */

tmp32 = (inMicLevelTmp - stt->minLevel) << 14;

if (stt->maxInit != stt->minLevel) {

volNormFIX = tmp32 / (stt->maxInit - stt->minLevel);

}

/* Find correct curve */

WebRtcAgc_ExpCurve(volNormFIX, &index);

weightFIX = kOffset1[index] - (int16_t) ((kSlope1[index] * volNormFIX) >> 13);

/* 增大为 1.047 倍数 */

stt->Rxx160_LPw32 = (stt->Rxx160_LPw32 / 64) * 67;

//tmp32 = inMicLevelTmp - stt->minLevel;

tmpU32 =((uint32_t) weightFIX * (uint32_t) (inMicLevelTmp - stt->minLevel));

stt->micVol = (tmpU32 >> 14) + stt->minLevel;

if (stt->micVol < lastMicVol + 2) {

stt->micVol = lastMicVol + 2;

}

inMicLevelTmp = stt->micVol;

}

// 音量增大为 1.047倍数

else if (stt->Rxx160_LPw32 < stt->lowerLimit) {

stt->msTooHigh = 0;

stt->changeToSlowMode = 0;

stt->msTooLow += 2;

if (stt->msTooLow > stt->msecSpeechInnerChange) {

int16_t index, weightFIX;

int16_t volNormFIX = 16384; // =1 in Q14.

stt->msTooLow = 0;

tmp32 = (inMicLevelTmp - stt->minLevel) << 14;

if (stt->maxInit != stt->minLevel) {

volNormFIX = tmp32 / (stt->maxInit - stt->minLevel);

}

WebRtcAgc_ExpCurve(volNormFIX, &index);

weightFIX = kOffset2[index] - (int16_t) ((kSlope2[index] * volNormFIX) >> 13);

stt->Rxx160_LPw32 = (stt->Rxx160_LPw32 / 64) * 67;

tmpU32 = ((uint32_t) weightFIX * (uint32_t) (inMicLevelTmp - stt->minLevel));

stt->micVol = (tmpU32 >> 14) + stt->minLevel;

if (stt->micVol < lastMicVol + 1) {

stt->micVol = lastMicVol + 1;

}

inMicLevelTmp = stt->micVol;

}

}

// 慢速模式

else {

if (stt->changeToSlowMode > 4000) {

stt->msecSpeechInnerChange = 1000;

stt->msecSpeechOuterChange = 500;

stt->upperLimit = stt->upperPrimaryLimit;

stt->lowerLimit = stt->lowerPrimaryLimit;

} else {

stt->changeToSlowMode += 2; // in milliseconds

}

stt->msTooLow = 0;

stt->msTooHigh = 0;

stt->micVol = inMicLevelTmp;

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。