一,什么是VAD
VAD,也就是语音端点检测技术,是Voice Activity Detection的缩写。这个技术的主要任务是从带有噪声的语音中准确的定位出语音的开始和结束点,因为语音中含有很长的静音,也就是把静音和实际语音分离开来,因为是语音数据的原始处理,所以VAD是语音信号处理过程的关键技术之一。它的好坏,直接影响成败,由于技术本身的特殊性,所以在涉及语音信号处理的领域,端点检测技术的应用非常广泛。语音识别系统在识别或者声学模型训练阶段所遇到的第一个技术就是端点检测,把静音和噪声作为干扰信号从原始数据中去除,并且端点检测对于语音识别系统的性能至关重要。
二,VAD的作用
现在流行的语音识别系统大部分,或者是相当一部分都是基于统计和训练的原理所构建的系统,因此对数据来源和训练环境都是很敏感的。在识别的过程中,经常存在实际语音因背景噪声的干扰而与训练失配的情况,实际这也是造成语音识别系统鲁棒性差的一个根本原因(另一个主要的是无法处理非预期的输入),从而导致识别错误,性能下降。哪怕是两段内容上是完全一致的语音信号,可能由于语速不一样,所以语音信号的时间也不相同,音素之间的时间间隙也就不一样,对于时变而非平稳的语音信号来说,其特征就完全不相同了。有音素之间的间隙,也有静音和语音本身的间隙,为了对数据从时间上进行相对的校准,语音端点检测技术就应运而生了,因此端点检测技术可以决定这种校准的相对精度,使得同一内容的特征更趋于相同,当然,一般情况下是不可能完全相同的。大量研究表明,如果环境是安静的环境,没有太多背景噪声,此时语音识别系统的主要错误来源于端点检测技术不精确。
但在实际应用中,不可能没有背景噪声,另外由于麦克风的录制和信号增益也会带来噪声,所以语音识别系统的错误是由多方面影响的,至少包括:端点检测、特征提取、语音模型、声学模型、解码器等多个方面。
假定读取的语音数据是WAV文件格式的数据。
三,WAV数据头结构定义
typedef struct{
char FLAG[4]; // 'RIFF'资源交换文件标识
int resource_length;//从下个字节开始,到资源结尾的总长度。
char WAVE_FLAG[4]; // 'WAVE' //WAVE文件标识
char format_flag[4]; // 'fmt '//波形格式标识
int pcm_header_len; // varies...过滤字节
short int format_tag;//格式种类,值为1时,表示数据为线性PCM编码
short int channel_count; // 单声道为1,双声道为2.
int sample_rate;//采样频率
int bytespersec;//波形数据传输速率
short int block_align;// 字节对齐
short int bits_per_samples;//样本数据在存储时所使用的位数。
} wav_header;
上面是WAV文件格式的头定义 ,数据定义如下
typedef struct{
char data_flag[4]; // 数据标识符,'data' or 'fact'
int length;//采样数据长度
}data_header;
在该结构之后,就是采样数据了。
下面读取文件代码:
/**
*
* 传入参数:WAV文件名(IN),数据(OUT),采样频率(OUT)
* 参数返回:返回采样样本数
*
**/
int read_wave_file(char *filename, short ** v_data, int *sampleRate){
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
fprintf(stderr, "open wav file %s error\n", wfn);
exit(-1);
}
char buf[20];
wav_header *wav = new wav_header;
data_header *data = new data_header;
//读取WAV文件头
if (fread((void *) wav, sizeof (wav_header), 1, fp) != 1){
fprintf(stderr, "cant read wav header\n");
exit(-1);
}
for(int i=0;i<4;i++)
buf[i] = wav->FLAG[i];
buf[4] = 0;
if(strcmp(buf,"RIFF")!=0){
fprintf(stderr, "%s bad RIFF format\n", buf);
exit(-1);
}
for(int i=0;i<4;i++)
buf[i] = wav->WAVE_FLAG[i];
buf[4] = 0;
if(strcmp(buf,"WAVE")!=0){
fprintf(stderr, "%s bad WAVE format\n", buf);
exit(-1);
}
for(int i=0;i<3;i++)
buf[i] = wav->format_flag[i];
buf[3] = 0;
if(strcmp(buf,"fmt")!=0){
fprintf(stderr, "%s bad fmt format\n", buf);
exit(-1);
}
//要求数据为线性PCM编码
if(wav->format_tag!=1){
fprintf(stderr, "bad wav format tag\n");
exit(-1);
}
if(wav->bits_per_samples != 16){
fprintf(stderr, "bad wav bits per sample\n");
exit(-1);
}
if (sampleRate != NULL)
*sampleRate = wav->sample_rate;//需要返回采样频率
//读取数据,如果不是数据块就跳过,直到"data"块被找到
int skip = 1;
while(skip!=0){
if(skip>10){
fprintf(stderr, "too many chunks\n");
exit(-1);
}
//读取数据头
if (fread((void *)data,sizeof(data_header),(size_t)1,fp) != 1){
fprintf(stderr, "cant read chunk\n");
exit(-1); }
// check chunk type
//读取数据标识
for(int i=0;i<4;i++)
buf[i] = data->data_flag[i];
buf[4] = 0;
if(strcmp(buf,"data")==0)
break;
//跳过当前非数据块
skip++;
fseek(fp,data->length,SEEK_CUR);
}
// 现在可以读取数据了,因为前面已经找到数据块
int wbuff_len = data->length;
char * wbuff = new char [wbuff_len];
if(wbuff==NULL){
fprintf(stderr, "alloc memory failed\n");
exit(-1);
}
// 计算采样数。数据采样存储总长度/(每个采样的存储占用字节)
int nsample = data->length/(wav->bits_per_samples / 8);
//读取信号数据
if (fread((void *)wbuff,wbuff_len,(size_t)1,fp) != 1){
fprintf(stderr, "cant read wbuff\n");
exit(-1);
}
// 将数据作为引用参数传回调用
*v_data = (short *) wbuff;
fclose(fp);
//返回采样数
return nsample;
}