webrtc-agc 自动增益控制算法

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

AGC算法里面相关的函数:

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;
				}
		 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值