在实现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的方法。