WebRTC_ANS——noise_suppression.c基础讲解(2)

计算基于分位数噪声估计的先验和后验信噪比,以及先验SNR的DD估计

// 输入:
//   * |magn| 是信号幅度谱估计。
//   * |noise| 是幅度噪声谱估计。
// 输出:
//   * |snrLocPrior| 是计算得到的先验SNR。
//   * |snrLocPost| 是计算得到的后验SNR。
static void ComputeSnr(const NoiseSuppressionC *self,
	const float *magn,
	const float *noise,
	float *snrLocPrior, float *logSnrLocPrior,
	float *snrLocPost) {
	size_t i;

	for (i = 0; i < self->magnLen; i++) {
		// 前一帧的后验SNR。
		// 前一次估计:基于上一帧和增益滤波器。
		float previousEstimateStsa = (self->magnPrevAnalyze[i] * self->smooth[i]) / (self->noisePrev[i] + epsilon);
		// 后验SNR。
		snrLocPost[i] = 0.f;
		if (magn[i] > noise[i]) {
			snrLocPost[i] = (magn[i] - noise[i]) / (noise[i] + epsilon);
		}
		// DD估计是两个项的和:当前估计和前一估计。
		// 针对snrPrior的定向决策更新。
		snrLocPrior[i] = 2.f * (
			DD_PR_SNR * previousEstimateStsa + (1.f - DD_PR_SNR) * snrLocPost[i]);
		logSnrLocPrior[i] = log1pf(snrLocPrior[i]);
	}  // 结束频率循环。
}

计算输入频谱与模板/学习到的噪声频谱之间的差异测量

// |magnIn| 是输入频谱。
// 参考/模板频谱是self->magnAvgPause[i]。
// 返回在self->featureData[4]中的(标准化的)频谱差异。
static void ComputeSpectralDifference(NoiseSuppressionC *self,
	const float *magnIn) {
	// avgDiffNormMagn = var(magnIn) - cov(magnIn, magnAvgPause)^2 /
	// var(magnAvgPause)
	size_t i;
	float avgPause, avgMagn, covMagnPause, varPause, varMagn, avgDiffNormMagn;

	avgPause = 0;
	avgMagn = self->sumMagn;
	// 计算平均量。
	for (i = 0; i < self->magnLen; i++) {
		// 从暂停帧中保守平滑噪声频谱。
		avgPause += self->magnAvgPause[i];
	}
	avgPause *= self->normMagnLen;
	avgMagn *= self->normMagnLen;

	covMagnPause = 0;
	varPause = 0;
	varMagn = 0;
	// 计算方差和协方差量。
	for (i = 0; i < self->magnLen; i++) {
		const float avgPauseDiff = self->magnAvgPause[i] - avgPause;
		const float avgMagnDiff = magnIn[i] - avgMagn;
		covMagnPause += avgMagnDiff * avgPauseDiff;
		varPause += avgPauseDiff * avgPauseDiff;
		varMagn += avgMagnDiff * avgMagnDiff;
	}
	covMagnPause *= self->normMagnLen;
	varPause *= self->normMagnLen;
	varMagn *= self->normMagnLen;
	// 更新平均幅度频谱。
	self->featureData[6] += self->signalEnergy;

	avgDiffNormMagn =
		varMagn - (covMagnPause * covMagnPause) / (varPause + epsilon);
	// 标准化和计算差异特征的时间平均更新。
	avgDiffNormMagn = avgDiffNormMagn / (self->featureData[5] + epsilon);
	self->featureData[4] +=
		SPECT_DIFF_TAVG * (avgDiffNormMagn - self->featureData[4]);
}

计算语音/噪声概率

// 语音/噪声概率返回在|probSpeechFinal|中。
// |magn| 是输入的幅度频谱。
// |noise| 是噪声频谱。
// |snrLocPrior| 是每个频率的先验SNR。
// |snrLocPost| 是每个频率的后验SNR。
static void SpeechNoiseProb(NoiseSuppressionC *self,
	float *probSpeechFinal,
	const float *snrLocPrior, const float *logSnrLocPrior,
	const float *snrLocPost) {
	size_t i;
	int sgnMap;
	float invLrt, gainPrior, indPrior;
	float logLrtTimeAvgKsum, besselTmp;
	float indicator0, indicator1, indicator2;

	float weightIndPrior0, weightIndPrior1, weightIndPrior2;
	float threshPrior0, threshPrior1, threshPrior2;
	float widthPrior, widthPrior0, widthPrior1, widthPrior2;

	// 初始化宽度参数
	widthPrior0 = WIDTH_PR_MAP;
	// 在暂停区域使用更大的宽度增加在tanh映射中的宽度。
	widthPrior1 = 2.f * WIDTH_PR_MAP;
	widthPrior2 = 2.f * WIDTH_PR_MAP;  // 用于频谱差异测量。

	// 阈值参数
	threshPrior0 = self->priorModelPars[0];
	threshPrior1 = self->priorModelPars[1];
	threshPrior2 = self->priorModelPars[3];

	// 平坦度特征的符号。
	sgnMap = (int)(self->priorModelPars[2]);

	// 特征权重参数。
	weightIndPrior0 = self->priorModelPars[4];
	weightIndPrior1 = self->priorModelPars[5];
	weightIndPrior2 = self->priorModelPars[6];

	// 计算基于平均LR因子的特征。
	// 这是平滑后的对数LRT在所有频率上的平均值。
	logLrtTimeAvgKsum = 0;
	for (i = 0; i < self->magnLen; i++) {
		besselTmp = (snrLocPost[i] * snrLocPrior[i] + snrLocPrior[i]) / (snrLocPrior[i] + 1.f + epsilon);
		self->logLrtTimeAvg[i] += LRT_TAVG * (besselTmp - logSnrLocPrior[i] - self->logLrtTimeAvg[i]);
		logLrtTimeAvgKsum += self->logLrtTimeAvg[i];
	}
	// 对数LRT时间平均的累积和,乘以标准化系数。
	logLrtTimeAvgKsum = logLrtTimeAvgKsum * self->normMagnLen;
	// 将计算结果保存到特征数据中。
	self->featureData[3] = logLrtTimeAvgKsum;

	// 计算指示函数。
	// 平均LRT特征。
	widthPrior = widthPrior0;
	if (logLrtTimeAvgKsum < threshPrior0) {
		// 在暂停区域使用更大的宽度。
		widthPrior = widthPrior1;
	}
	// 计算指示函数:sigmoid映射。
	indicator0 = 0.5f * tanhf(widthPrior * (logLrtTimeAvgKsum - threshPrior0)) + 0.5f;

	// 频谱平坦度特征。
	widthPrior = widthPrior0;
	if ((sgnMap == 1 && (self->featureData[0] > threshPrior1)) ||
		(sgnMap == -1 && (self->featureData[0] < threshPrior1))) {
		widthPrior = widthPrior1;
	}
	// 计算指示函数:sigmoid映射。
	indicator1 = 0.5f * tanhf((float)sgnMap * widthPrior * (threshPrior1 - self->featureData[0])) + 0.5f;

	// 模板频谱差异。
	widthPrior = widthPrior0;
	if (self->featureData[4] < threshPrior2) {
		widthPrior = widthPrior2;
	}
	// 计算指示函数:sigmoid映射。
	indicator2 = 0.5f * tanhf(widthPrior * (self->featureData[4] - threshPrior2)) + 0.5f;

	// 将指示函数与特征权重结合。
	indPrior = weightIndPrior0 * indicator0 + weightIndPrior1 * indicator1 +
		weightIndPrior2 * indicator2;

	// 计算先验概率。
	self->priorSpeechProb += PRIOR_UPDATE * (indPrior - self->priorSpeechProb);
	if (self->priorSpeechProb > 1.f) {
		self->priorSpeechProb = 1.f;
	}
	if (self->priorSpeechProb < 0.01f) {
		self->priorSpeechProb = 0.01f;
	}

	// 最终语音概率:将先验模型与LR因子结合。
	gainPrior = (1.f - self->priorSpeechProb) / (self->priorSpeechProb + epsilon);
	for (i = 0; i < self->magnLen; i++) {
		invLrt = expf(-self->logLrtTimeAvg[i]);
		invLrt = gainPrior * invLrt;
		probSpeechFinal[i] = 1.f / (1.f + invLrt);
	}
}

更新噪声特征

// 输入:
//   * |magn| 是信号幅度频谱估计。
//   * |updateParsFlag| 是参数更新标志。
static void FeatureUpdate(NoiseSuppressionC *self,
	const float *magn, const float *lmagn,
	int updateParsFlag) {
	// 计算输入频谱的频谱平坦度。
	ComputeSpectralFlatness(self, magn, lmagn);
	// 计算输入频谱与学习到的/估计的噪声频谱的差异。
	ComputeSpectralDifference(self, magn);
	// 计算参数决策的直方图(特征的阈值和权重)。
	// 参数每过一段窗口时间就提取一次。
	// (=self->modelUpdatePars[1])
	if (updateParsFlag >= 1) {
		// 计数器更新。
		self->modelUpdatePars[3]--;
		// 如果计数器大于0,则更新直方图。
		if (self->modelUpdatePars[3] > 0) {
			FeatureParameterExtraction(self, 0);
		}
		// 如果计数器为0,计算模型参数。
		if (self->modelUpdatePars[3] == 0) {
			FeatureParameterExtraction(self, 1);
			// 重置计数器为窗口时间间隔。
			self->modelUpdatePars[3] = self->modelUpdatePars[1];
			// 如果希望只更新一次,将标志置为零。
			if (updateParsFlag == 1) {
				self->modelUpdatePars[0] = 0;
			}
			else {
				// 每个窗口更新:
				// 获取下一个窗口估计的频谱差异的归一化值。
				self->featureData[6] =
					self->featureData[6] / ((float)self->modelUpdatePars[1]);
				// 将当前窗口和下一个窗口的频谱差异归一化值平均,以便平滑过渡。
				self->featureData[5] =
					0.5f * (self->featureData[6] + self->featureData[5]);
				// 重置频谱差异归一化值。
				self->featureData[6] = 0.f;
			}
		}
	}
}

更新噪声估计

// 输入:
//   * |magn| 是信号幅度频谱估计。
//   * |snrLocPrior| 是先验SNR。
//   * |snrLocPost| 是后验SNR。
// 输出:
//   * |noise| 是更新后的噪声幅度频谱估计。
static void UpdateNoiseEstimate(NoiseSuppressionC *self,
	const float *magn,
	float *noise) {
	size_t i;
	float probSpeech, probNonSpeech;
	// 噪声更新的时间平均参数。
	float gammaNoiseTmp = NOISE_UPDATE;
	float gammaNoiseOld;
	float noiseUpdateTmp;

	for (i = 0; i < self->magnLen; i++) {
		// 语音概率。
		probSpeech = self->speechProb[i];
		// 非语音概率。
		probNonSpeech = 1.f - probSpeech;
		// 临时噪声更新:
		// 如果更新值小于之前的值,则用于语音帧。
		noiseUpdateTmp = gammaNoiseTmp * self->noisePrev[i] +
			(1.f - gammaNoiseTmp) * (probNonSpeech * magn[i] +
				probSpeech * self->noisePrev[i]);
		// 基于语音/噪声状态的时间常数。
		gammaNoiseOld = gammaNoiseTmp;
		gammaNoiseTmp = NOISE_UPDATE;
		// 对于可能是语音的帧,增加gamma(即,减少噪声更新)。
		if (probSpeech > PROB_RANGE) {
			gammaNoiseTmp = SPEECH_UPDATE;
		}
		// 保守的噪声更新。
		if (probSpeech < PROB_RANGE) {
			// 对于暂停帧,更新平均暂停幅度频谱。
			self->magnAvgPause[i] += GAMMA_PAUSE * (magn[i] - self->magnAvgPause[i]);
		}
		// 噪声更新。
		if (gammaNoiseTmp == gammaNoiseOld) {
			// 如果gamma没有变化,使用临时噪声更新结果。
			noise[i] = noiseUpdateTmp;
		}
		else {
			// 如果gamma变化了,根据新的gamma计算噪声更新。
			noise[i] = gammaNoiseTmp * self->noisePrev[i] +
				(1.f - gammaNoiseTmp) * (probNonSpeech * magn[i] +
					probSpeech * self->noisePrev[i]);
			// 允许噪声向下更新:
			// 如果噪声更新导致噪声减小,则认为是安全的,因此允许这种更新发生。
			if (noiseUpdateTmp < noise[i]) {
				noise[i] = noiseUpdateTmp;
			}
		}
	}  // 结束频率循环。
}

使用新的|frame|更新|buffer|

// 输入:
//   * |frame| 是一个新的语音帧,或者为NULL时设置为零。
//   * |frame_length| 是新帧的长度。
//   * |buffer_length| 是缓冲区的长度。
// 输出:
//   * |buffer| 是更新后的缓冲区。
static void UpdateBuffer(const int16_t *frame,
	size_t frame_length,
	size_t buffer_length,
	float *buffer) {
	// 确保缓冲区长度小于两倍帧长,这是一个前提条件检查。
	assert(buffer_length < 2 * frame_length);

	// 将缓冲区中剩余的数据(即,不被新帧覆盖的部分)向前移动。
	memcpy(buffer,
		buffer + frame_length,
		sizeof(*buffer) * (buffer_length - frame_length));
	// 如果frame不是NULL,即有新的帧要添加。
	if (frame) {
		int i = 0;
		// 将新帧的数据复制到缓冲区的末尾。
		for (i = 0; i < frame_length; ++i) {
			buffer[buffer_length - frame_length + i] = frame[i];
		}
	}
	else {
		// 如果frame为NULL,将要更新的缓冲区部分设置为零。
		memset(buffer + buffer_length - frame_length,
			0,
			sizeof(*buffer) * frame_length);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值