在实现VAD算法之前,先给出在这里VAD算法的实现流程和算法框架。
调用关系依次是detect_wav -> detect_frame->process_vad->energy_detect,energy_detect比较复杂,所以暂时不在这里,这只是表示出基本的计算流程和框架
int frame_size = 256 ;
int sample_rate = 8000 ;// 采样频率
int frame_step = 80;
int msec_per_frame = (int) 1000 * frame_size/sample_rate; // 一帧占据多少毫秒
int silence_filter_len = 30 ; //由语音进入静音之前的过渡区域,这是一个灰度区域
int speech_filter_len = 30 ; // 由静音进入语音前的过渡区域
int number_of_silence = 0; // 当前处理的静音长度。
int number_of_speech = 0; // 当前处理的语音长度。
int total_of_silence = 0; //状态切换之前的静音长度
int total_of_speech = 0; //状态切换之前的语音长度
int silence_count = 0; //在当前处理时,合法的数据被接受,可以正式计数
int speech_count = 0;
int frame_count = 0;
上面是申明的一些全局变量。
/**
*
* data:整个WAV文件的数据。nsample:WAV文件的采样数。这两个值都是可以从返回的
*
**/
int detect_wav(short *data,int nsample){
int state = state_SILENCE ; //初始状态:静音
int index = 0,offset = 0;
int cst = 0; // 当前状态时间,暂时以帧表示
//index会返回当前是处理的第几帧,这和第几个采样点是不一样的
int ret = detect_frame(data,&index); // 从第一个帧开始处理,配置每一帧256个采样点,ret返回当前帧的状态,静音、结束、语音等。
while(ret != state_OVER){ // 直到最后一帧,也就是处理结束。
if(ret != state_WAIT){ //是否返回静音或者语音段
if(ret != st ){ // 返回状态和上一状态不相同
int k ;
if(ret == state_SILENCE){
k = index * frame_step - silence_filter_len * frame_step ;//过渡区域计为本次的静音。
if(k <= cst){ //
ret = st;
continue;
}
//输出静音段
printf("%3.2f %3.2f Speech\n",cst/(float)sample_rate,k/(float)sample_rate);
}else{
k = index * frame_step - speech_filter_len * frame_step;//之前的过渡区域计为本次的语音段
if(k <= cst){
ret = st;
continue;
}
printf("%3.2f %3.2f Silence\n", cst/(float)sample_rate, ((k/(float)sample_rate)>0?(k/(float)sample_rate):0.00));
}
cst = k; //保留上次计算的K 值。
st = ret ; //保留上次状态值
}
}
// 准备处理下一帧
offset += frame_step ; // 移动帧的起始位置,可以由此计算帧的重叠
if(offset <= nsample - frame_size){
ret = detect_frame(data+offset,&index);
}else{
ret = detect_frame(NULL,&index);
}
}
// 这里需要输出最后的段
if (st == state_SPEEACH){
printf("%3.2f %3.2f SPeech\n", cst/(float)sample_rate, nSample/(float)sample_rate);
}else{
printf("%3.2f %3.2f Silence\n", cst/(float)sample_rate, nSample/(float)sample_rate);
}
return 0;
}
上面的函数会对所有的数据进行循环调用,每调用一帧都会调用detect_frame进行处理:
/**
*
* 处理一帧数据,就是从data开始的frame_size个采样。state表示当前状态
* 返回当前帧的处理后的状态
*
**/
int detect_frame(short *data,int *index,int state){
short *pcm_data = data ;
int silence_flag = process_vad(pcm_data,index); //处理具体的数据,并返回当前帧的处理结果
//结束
if(silence_flag == state_OVER || silence_flag == state_WAIT )
return silence_flag ;
if(silence_flag == state_SILENCE){ // 静音部分段
number_of_silence ++ ;
total_of_silence ++ ;
number_of_speech = 0;
if(number_of_silence > 5){ // 五个以上的静音帧,就相当于50ms
speech_count = 0; // 开始静音统计,语音为0;
}
// 如果之前已经是静音,没有问题,这里处理如果之前是语音的情况。
if(state == state_SPEECH )
silence_count ++ ;
/**
* 这里的处理是为了使得如果静音段过长,就是超过了300ms。此时状态切换
**/
if((state == state_SPEECH) && (silence_count > silence_filter_len )){
state = ((total_of_speech > total_of_silence) ? state_SPEECH : state_SILENCE) ;
number_of_silence = number_of_speech =total_of_silence = total_of_speech = 0;
}
}else{
total_of_speech ++ ;
number_of_speech ++ ;
number_of_silence = 0;
if(number_of_speech > 5){
silence_count = 0;
}
if(state == state_SILENCE )
speech_count ++;
if((state == state_SILENCE ) && ( speech_count > speech_filter_len)){
state = ((total_of_speech > total_of_silence) ? state_SPEECH : state_SILENCE) ;
number_of_silence = number_of_speech =total_of_silence = total_of_speech = 0;
}
}
frame_count ++;
return state;
}
上面的detect_frame其实是一个后处理函数,就是返回当前帧的可能状态之后,进行状态变更和计数的,其数据处理的操作为:
int process_vad(short *data,int *index){
if(data == NULL)
return state_OVER;
//数据去除直流。
//数据加窗 并返回total_rms
energy_detect(total_rms,flag); // 根据均方差计算当前帧的状态,并从flag返回,该参数为引用参数。
*index = *index++ ;// 这里是简化,有可能多帧处理。
return flag == 0 ? state_SPEECH : state_SILENCE ;
}
现在就剩下最关键的实现了,就是energy_detect,基于能量的端点检测,这里采用track energy的方法。