1、简介
静默检测对于语音识别有这很重要作用,什么是静默检测?顾名思义就是检测语音的状态,静默状态还是激活状态,这样才能保证送进语音识别模型的是一句完整语音数据,排除一些噪音的干扰。如同下图所示,当然这里存在一个问题,就是多长时间的静默状态才当作语音的结束,以及多大的语音能量以及多长时间的状态持续才当作语音的开始。
2、算法简介
2.1 语音激活状态检测
一般情况下,麦克风录入的音量肯定存在或多或少的噪音,例如撞击声、敲击声等,一般需要排除的噪音是端、快类型,防止造成检测为误激活状态。噪音具有时效性,也就是说不同时间不同地点的噪音肯定都不同,所以这里就需要对噪音有一个取样评判的过程。
语音在短时间内都是为平稳状态,一般10到30ms,也就是这一段时间的语音状态近乎一致。故对录入的音频数据要有一个分帧的过程,当然一般情况下也需要帧移,例如:每次录入30ms,但是我每次只取20ms的音频,为保证每次处理的音频数据与上次的变化不会过于跳变,所以下次处理的数据包含上次数据的后10ms。
|————10ms——————|—————10ms—————|—————10ms—————|
|<------------------------第一次数据处理--------------------------->|
|<------------------------第二次数据处理--------------------------->|
每次处理数据的最小单位为一帧,也就是上图所说的状态,当然对于一般情况就取10ms一帧不进行帧移也可以。首先获取背景噪音的能量,每帧的能量可以取平方均值,可以取前n帧作为背景噪音的能量值,噪音的能量值需要设置一个最小门限,防止出现很安静的状态下误处理。仅仅比较当前的语音能量与噪音能量,显然不够,还需要监控语音的变化状态,是否跳动以及此状态的维持时间。当这几个条件同时满足方可判断为激活状态。常用方法:过零检测与门限控制。
例程如下:
//能量计算
float CalculateEnergy(const float *frames, size_t frames_count)
{
float energy = 0;
for (size_t i = 0; i < frames_count; i++) {
energy += frames[i] * frames[i];
}
return energy / (float)frames_count;
}
//过零检测
short CalculateZeroCross(const short *frame, size_t frames_count) {
short zero_cross_cnt = 0;
short cur_status = 0; //本次状态
short last_status = 0; //上次状态
for (size_t i = 0; i < frames_count; i++) //循环判断一帧里的所有数据
{
cur_status = (frame[i] > 0) ? 1 : -1;
if ((last_status != 0) && (cur_status != last_status)) {
zero_cross_cnt++;
}
last_status = cur_status;
}
return zero_cross_cnt; //返回跳动次数
}
每次判断为激活状态后,需要记录此时的时间或者帧数,作为之后的结束判断。
2.1 语音结束状态检测
语音结束判断较为简单,只要判断目前的时间与上次的激活时间差值,大于一定阈值,即可判断为语句结束。
3、总结
以上的VAD检测方法适用于要求不高的情况,一般噪音状态检测效果还不错,如果需要更高要求的VAD检测,就不能仅局限于时域的特征,还需要再频域上进行判断各自带的一些特征,可以参考webrtc的vad检测。