基于https://www.doczj.com/doc/e46413006.html, 2003 的声音信号采集与分析处理
叶峰
河海大学电气学院,南京(210098)
E-mail:Yefeng_n@https://www.doczj.com/doc/e46413006.html,
摘要:本文讨论在https://www.doczj.com/doc/e46413006.html,环境下利用低层音频服务API函数基于多线程程序设计方法实现一种基于计算机普通声卡的数据采集系统, 可以实时显示采样信号的波形,本文介绍了利用VC ++ 实现声音信号的采集与分析处理过程,并结合实例程序进行分析说明。
关键词:https://www.doczj.com/doc/e46413006.html,;声音信号采集;频谱分析;声卡;低层音频服务API函数;实时
1 引言
硬件方面,数据采集系统是指将特定的物理信号真实的记录,一般基于计算机的数据采集系统包括传感器、信号调理器、数据采集卡和控制软件等。其中数据采集卡是核心部件,但其价格昂贵。而一般计算机配备的16位精度,44.1kHz采样频率的声卡本身就是一块性价就比较高的数据采集转换卡。声卡处理信号的上限频率理论上达到了22kHz左右,对于大部分低频电路的采样分析已经满足性能要求。
软件方面,采用https://www.doczj.com/doc/e46413006.html, 多线程程序设计方法,使单CPU系统似乎能够同时执行不同的线程,对于多CPU系统,操作系统就会将不同的线程交给不同的CPU进行处理,真正的实现同时处理,无论对于单CPU、多CPU来说,多线程程序设计提高了应用程序的执行以及响应速度,这对于进行实时采集声音具有重要的意义。本文中在声音信号采集与分析处理过程中采用在主线程中连续采集声音信号,在工作者线程中对采集的数据进行处理操作。
2 声音信号的采集
在采样过程中, 为避免出现混叠现象,根据奈奎斯特采样定理,必须使 Ws≥2Wr,其中Ws为采样频率, Wr为信号的最高频率。
由于各种客观因素的影响,采集到的信号中不可避免的混有噪声,因此,在对信号分析之前,需要进行一些处理,减小噪声对有用信号的干扰,提高信号的信噪比。由于外界干扰、仪器故障等原因,有时会出现异常数据,一般剔除方法是基于正态分布的肖维涅(W.Chauvennent) 法。在信号分析中,常常会存在一些高频噪声成分,这就是说,在信号分析时应首先对信号进行滤波,分离出我们感兴趣的频段。
3 声音信号采集的软件实现
在Windows 环境下,对波形音频设备进行处理有3 种方法,高层音频服务的MCI函数、DirectX的音频服务技术、低层音频服务API函数[5]。
MCI提供了一组与设备无关的控制命令,使用简便,灵活性较差,在录音的过程中不能直接访问内存中的采样数据,难以满足实时性要求。DirectSound 是DirectX的声音组件,DirectX具有DOS 的直接硬件访问特性,同时又具有硬件独立性,避免了添加新硬件时的硬件识别问题,另外,DirectX具有利用硬件加速的能力,不需要写任何专用代码,就能自动地实现硬件加速。低层音频API函数可直接与声卡驱动程序进行通信,提供了对声卡直接灵活操作,因此本文使用低层音频API函数进行采集软件的设计。
3. 1 波形音频文件的格式
图1 波形音频文件的存储格式
资源交互文件(RIFF)是面向部分(Chunk)的,如图1,一个RIFF文件是由一个或多个部分组成的,其中每一个部分指向下一个部分,每一个部分都有一个类型,后面跟随一些数据。波形音频文件为RIFF文件(资源交互文件格式)文件的一种,一个基本的波形文件的实际结构是一个WAVE部分,包括fmt部分和data部分。
FMT部分的结构声明如下:
typedef struct{
WORD wFormatTag; //格式标识,这里是WA VE_FORMAT_PCM
WORD nChannels; //通道数,单声道为1,双声道为2
WORD nSamplesPerSec; //采样频率,常用的采样频率为8.0 kHz, 11.025 kHz, 22.05 kHz, 44.1 kHz.
WORD nAvgBytesPerSec;//音频的每秒字节数,对于格式为WA VE_FORMAT_PCM
//其值是nBlockAlign * nSamplesPerSec
WORD nBlockAlign; //一个采样所占据数据块的最小字节数,对于格式为WA VE_FORMAT_PCM
//其值是WBitsPerSample* nChannels / 8
WORD wBitsPerSample; //表示量化的比特数,通常为8或16
WORD cbSize; //对于WA VE_FORMAT_PCM,其为0
}WAVEFORMATEX;
从波形文件中读出、写入信息需要使用多媒体I/O函数[1],这些函数都具有前缀mmio,可以通过查阅[2]MSDN得到这些函数的具体使用。
3. 2 实现声音信号采集类的实现
为了能够实现复用,便于维护升级以及扩充函数,采用面向对象的编程的方法,将音频的具体操作放入一个固定的CWAVE类中,主要参数和函数定义如下:
class CWave{
HWAVEIN hWaveIn; //指向打开的输入波形音频设备的的句柄
hWaveOut;
HWAVEOUT
//定义波形音频的数据格式
waveForm;
WAVEFORMATEX
HANDLE hData; // allocate memory for Play function
HANDLE
hWaveHdr;
HANDLE hBuffer0, hBuffer1, hWaveHdr0, hWaveHdr1 ;// allocate memory for Record function
public:
BOOL GetDeviceCapacity(void); // 获得音频输入设备的属性
BOOL Record(HWND pHwnd); // 录音程序段,pHwnd 为指定传递消息的父窗口的句柄
BOOL Play(HWND pHwnd, LPCSTR path); //播放程序段,path 为文件路径
Play(HWND
pHwnd); //播放内存中的音频程序段
BOOL
BOOL Stop(void);
Pause(void);
BOOL
显示错误信息
//
void ErrorWaveInMsg(MMRESULT dwError);
void ErrorWaveOutMsg(MMRESULT dwError);
BOOL SaveStream(LPTSTR path ,LPSTR data_Chunk, DWORD length_Chunk); //将数据块转存到硬盘 …………};
这里仅列出录音程序段主要内容,在此函数中采用双缓冲区循环技术,保证实现声音信号采集不丢失。BOOL CWave::Record(HWND pHwnd){
…………//定义局部变量,设置录音格式,如同上面介绍的FMT部分的结构,
waveForm.wFormatTag = WAVE_FORMAT_PCM;……
//打开波形音频设备
mmResult=
waveInOpen((LPHWAVEIN)&hWaveIn,WAVE_MAPPER,&waveForm,(DWORD)pHwnd,0,CALLBACK_WIND OW);……
//分配第一块缓存及波形数据头WAVEHDR分配、锁定内存
hBuffer0 = GlobalAlloc( GMEM_MOVEABLE ,BUFFER_SIZE);……
lpBuffer0 = (HPSTR)GlobalLock(hBuffer0); …… //省略部分为检测程序部分
hWaveHdr0 = (LPWAVEHDR)GlobalAlloc(GMEM_MOVEABLE, (long)sizeof(WAVEHDR)); ……
waveHdrFst = (LPWAVEHDR)GlobalLock(hWaveHdr0); ……
waveHdrFst->lpData = lpBuffer0;
waveHdrFst->dwBufferLength = BUFFER_SIZE;
waveHdrFst->dwBytesRecorded = 0l;
waveHdrFst->dwFlags = 0l;
mmResult = waveInPrepareHeader(hWaveIn,waveHdrFst,sizeof(WAVEHDR)); ……
mmResult = waveInAddBuffer(hWaveIn,waveHdrFst,sizeof(WAVEHDR)); ……
//同上面的过程分配第二块缓存
……
mmResult = waveInStart(hWaveIn); //启动录音设备
……}
3. 3 输入缓冲区的分配和管理
从上面的录音程序可以看出其简单流程,在开始采集前, GlobalAlloc函数申请两块较小的内存空间,并获取内存对象的句柄,然后锁定内存,并为每一块缓冲区填写相应的缓冲区头结构体WAVEHDR。接着调用waveInPrepareHeader函数准备缓冲区,它会对数据缓冲区作Page Lock操作,以保证缓冲区不会被置换到硬盘中, 使采集工作更加流畅。最后调用waveInAddBuffer函数把两块缓冲区全部传递给声卡驱动程序。添加好输入缓冲区后,直接调用waveInStart 函数就可以开始数据采集。
3. 4 消息的处理
在录音或播放时,主线程可以捕捉到从CWave类发出的六类消息,主要有二条(见表1),在MFC主框架下简单重写函数WindowProc(UINT message, WPARAM wParam, LPARAM lParam),捕捉具体消息。如在录音程序中,当一块缓冲区录满时,驱动程序就会向相应的窗口发送
MM_WIM_DATA消息,同时自动使用另一块缓冲区来保存新采集的数据。这时,在主线程中,
主要将缓冲区的内容转移到一段公共内存中,然后将缓冲区清零,然后再把该缓冲区重新传递给驱动程序,在辅助线程中就显示和保存已录制设定一定长度的数据。如此循环交替,使得声卡驱动程序在任何时候都至少有一个缓冲区可用。
表1 引用消息说明
消息函数说明
MM_WIM_DATA 缓冲区满或者在调用waveInReset函数后发出
MM_WOM_DONE 缓冲区的数据播放完毕或者调用waveOutReset函数后发出此消息
3. 5 波形的显示
每当一个输入缓冲区存满数据后,采用调用线程的办法实现波形的显示,并采用双缓冲技术消除闪屏现象,即只需绘图时使用GDI绘制到内存上,最用使用一次BitBlt函数快速调用。
波形显示时应注意下面几个问题:1) A/D 转化后的数据是PCM 格式,即:若用8位量化,则对应着8位无符号数,若用16位量化,则表示为有符号数的补码形式。 2) 若用双通道采集,先存储左声道的一个采样点,然后再存储右声道的一个采样点。3)声卡采样数据是无量纲的,显示时需要采用对应的工程单位对信号幅度进行标定。4)由于线程处理的数据块始终滞后,所以在最后录音结束后,使用WaitForSingleObject函数等待线程调用结束后才能释放内存,其次由于主线程与辅助线程公用一段内存,所以要注意在读写内存时互斥信号量的设置。
4 声音信号的分析与处理
4. 1 设计滤波器
在采集得到的声音信号中,有些信号是研究中不需要的,通过滤波器[3]对所采集的声音信号进行滤波,以提高研究的精确性与针对性。
滤波器可分为两大类,即经典滤波器和现代滤波器。经典滤波器是假定输入信号x ( n)中的有用成分和希望去除的成分各自占有不同的频带,这样,当x ( n)通过一个线性系统(即滤波器) 后可将欲去除的成分有效地去除。如果信号和噪声的频谱相互重叠,那么经典滤波器将无能为力。现代滤波器把信号和噪声都视为随机信号,利用它们的统计特征(如自相关函数、谱功率等) 导出分离的计算方法,然后用软件或硬件予以分离。
4. 2 频谱分析
频谱分析的方法较多,在此仅以常见的傅立叶变换[3]进行说明,即做快速傅立叶变换FFT(Fast Fourier Transform),通常FFT有两种基本方式,第一种为时间抽选,它将x(n) (n常表示和时间有
关的量) 逐次分解成较短的子序列,第二种为频率抽选。下面的FFT程序按照时间抽取的方法设计,通过对声音信号进行快速傅立叶变换,得到频域特征向量,再作进一步的分析则可提取所需要的特征变量值。
/* 按时间抽取的快速傅立叶变换 */
/* pTimeHdr 指向时域数组的指针*/
/* pFreHdr 指向频域数组的指针*/
void CWaveAPIDlg::FastFourierTran_DIT(Cplex * pTimeHdr, Cplex * pFreHdr, int L)
{
int iDotCnt = 1< Cplex *pTmp1,*pTmp2,pWn; double angle;// 角度 //将自然顺序输入改变为倒位序 pTmp1 = new Cplex[sizeof(Cplex)*iDotCnt]; pTmp2 = new Cplex[sizeof(Cplex)*iDotCnt]; pTmp1[0] = pTimeHdr[0]; pTmp1[iDotCnt/2] = pTimeHdr[1]; int preWidth = iDotCnt /2; int z = 0; for(int i=L-1,j =0;i< 0;i--,j++) { for(int k = 0;k< (1< pTmp1[++z] = pTimeHdr[preWidth*k + preWidth/2]; pTmp1[z+iDotCnt/2] = pTimeHdr[preWidth*k + preWidth/2+1];} preWidth /=2; } // 采用蝶形算法进行快速傅立叶变换 int r =0; //计算系数 int iMask = 1; for(int i =0;i for(int i =0; i pWn.re = cos(angle); pWn.im =sin(angle); //下面运算需要重载运算符 pTmp2[i+k] = pTmp1[i+k] + pTmp1[i+k+interval]* pWn; pTmp2[i+interval+k] = pTmp1[i+k] - pTmp1[i+k+interval]*pWn;} i+= interval*2;} CopyMemory(pTmp1,pTmp2,sizeof(Cplex)*iDotCnt); } CopyMemory(pFreHdr,pTmp1,sizeof(Cplex)*iDotCnt); delete []pTmp1; delete []pTmp2;} 4. 3 特征提取 声音信号的分析与处理一般情况下都是进行频谱分析[4],再从频谱图中提取出一些特征值。特征提取的任务就是,在保证满足分类识别的正确率要求的条件下,尽量选用对分类识别作用较大的特征,使得用较少的特征就能完成分类识别任务。 声音信号是一种随机数据,完整的描述需要从幅域、时域和频域进行分析,提取特征,它主要是需要考虑概率统计的因素进行数据处理。时域分析的最重要特点是信号的时间顺序。时域分析通常研究信号的相关分析,以及概率密度、概率分布。频谱分析是把复杂的时间历程波形,经傅立叶变换分解为若干单一的谐波分量来研究,以获得信号的频率结构以及各谐波幅值和相位信息。 5 结束语 本文讨论了运用Visual C ++ 实现对声音信号的采集与分析处理,多线程并发处理技术可以满足响应实时的需求,设计软件滤波器,提高了研究的精确性与针对性。这样基于声卡的信号采集系统的解决方案的主要优点是:硬件和软件的设计简单,开发成本低,灵活性好,可用于 中低频模拟信号的长时间连续采集,还可以同时使用多块声卡组成多通道采集系统, 可以应用于声音信号检测的各种领域,具有广泛的应用前景,但如果信号分析的测量需要高精度、长时间校准、高性能的结果,最好选用通用数据采集卡和专有软件。 参考文献 [1] 李博轩. Visual C++ 6.0 多媒体开发指南[M] 清华大学出版社,2000. [2] Microsoft MSDN Library for Visual https://www.doczj.com/doc/e46413006.html, 2003 [3] 程佩青. 数字滤波与快速傅里叶变换[M] . 北京:清华大学出版社,1990. [4] 易克初. 语音信号处理[M] . 北京: 国防工业出版社,2002. [5] Richard C. Leinecher&TomArcher 著张艳等译 Visual C++ Bible ,北京,电子工业出版社,1999。 [6] (美)AshokAmbardar 著, 冯博琴, 冯岚等译. 信号、系统与信号处理[M] . 北京:机械工业出版社,2001. Collecting and Processing of Sound Signal based on https://www.doczj.com/doc/e46413006.html, Ye Feng Hohai university,Nanjing (210098) Abstract The aim of this paper is to establish a collecting sound system based on the real time acquisition with the low level of sound API function of windows and the multithread method , it can display the wave pattern of sound on the computer in time , the paper introduced the process of how to design and analyze the program . Keywords:https://www.doczj.com/doc/e46413006.html,;collecting the signal of sound;Analyzing of frequency spectrum;audio card;the low level of sound function;real time