基于VisualC++6.0工具下的声音文件操作声音是人类传递信息的重要途径,如果应用程序中包含声音信息,就可以大大增强它的亲合力;另外在科研开发过程中,声音信号的处理也是一个很重要的科学研究领域。VisualC++作为一个强大的开发工具,当然是声音处理的首选工具,但是在当前VisualC++相关的编程资料中,无论是大部头的参考书,还是一些计算机杂志,对声音文件的处理都是泛泛的涉及一下,许多编程爱好者都感到对该部分的内容了解不是很透彻,笔者结合自己的学习和开发过程中积累的一些经验,在本实例中来和广大编程爱好者们探讨一下声音文件的处理,当然本实例也不可能包括声音处理内容的方方面面,只是希望它能够对刚刚涉及到声音处理领域的朋友们起到一个引路的作用,帮助他们尽快进入声音处理的更深奥空间。当前计算机系统处理声音文件有两种办法:一是使用现成的软件,如微软的录音机、SoundForge、CoolEdit等软件可以实现对声音信号进行录音、编辑、播放的处理,但它们的功能是有限的,为了更灵活,更大限度地处理声音数据,就不得不使用另外一种方法,既利用微软提供的多媒体服务,在Windows环境下自己编写程序来进行声音处理来实现一些特定的功能。下面就开始介绍声音文件的格式和在Windows环境下使用VisualC++开发工具进行声音文件编程处理的方法。一、实现方法1、RIFF文件结构和WAVE文件格式Windows支持两种RIFF(ResourceInterchangeFileFormat,资源交互文件格式)格式的音频文件:MIDI的RMID文件和波形音频文件格式WAVE文件,其中在计算机领域最常用的数字化声音文件格式是后者,它是微软专门为Windows系统定义的波形文件格式(WaveformAudio),由于其扩展名为*.wav,因而该类文件也被称为WAVE文件。为了突出重点,有的放矢,本文涉及到的声音文件所指的就是WAVE文件。常见的WAVE语音文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。这里的采样率是指声音信号在进行模→数转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为八位的短整数(shortint00H-FFH);而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。WAVE文件数据块包含以脉冲编码调制(PCM)格式表示的样本。在进行声音编程处理以前,首先让我们来了解一下RIFF文件和WAVE文件格式。RIFF文件结构可以看作是树状结构,其基本构成是称为块(Chunk)的单元,每个块有标志符、数据大小及数据所组成,块的结构如图1所示:块的标志符(4BYTES)数据大小(4BYTES)数据图一、块的结构示意图从上图可以看出,其中标志符为4个字符所组成的代码,如RIFF,LIST等,指定块的标志ID;数据大小用来指定块的数据域大小,它的尺寸也为4个字符;数据用来描述具体的声音信号,它可以由若干个子块构成,一般情况下块与块是平行的,不能相互嵌套,但是有两种类型的块可以嵌套子块,他们是RIFF或LIST标志的块,其中RIFF块的级别最高,它可以包括LIST块。另外,RIFF块和LIST块与其他块不同,RIFF块的数据总是以一个指定文件中数据存储格式的四个字符码(称为格式类型)开始,如WAVE文件有一个WAVE的格式类型。LIST块的数据总是以一个指定列表内容的4个字符码(称为列表类型)开始,例如扩展名为.AVI的视频文件就有一个strl的列表类型。RIFF和LIST的块结构如下:RIFF/LIST标志符数据1大小数据1格式/列表类型数据图二、RIFF/LIST块结构WAVE文件是非常简单的一种RIFF文件,它的格式类型为WAVE。RIFF块包含两个子块,这两个子块的ID分别是fmt和data,其中fmt子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。WAVE文件的结构如下图三所示:标志符(RIFF)数据大小格式类型(WAVE)fmtSizeof(PCMWAVEFORMAT)PCMWAVEFORMATdata声音数据大小声音数据图三、WAVE文件结构PCMWAVEFORMAT结构定义如下:Typedefstruct{WAVEFORMATwf;//波形格式;WORDwBitsPerSample;//WAVE文件的采样大小;}PCMWAVEFORMAT;WAVEFORMAT结构定义如下:typedefstruct{WORDwFormatag;//编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等WORDnChannls;//声道数,单声道为1,双声道为2;DWORDnSamplesPerSec;//采样频率;DWORDnAvgBytesperSec;//每秒的数据量;WORDnBlockAlign;//块对齐;}WAVEFORMAT;data子块包含WAVE文件的数字化波形声音数据,其存放格式依赖于fmt子块中wFormatTag成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。如16bit的单声道WAVE文件和双声道WAVE文件的数据采样格式分别如图四所示:16位单声道:采样一采样二……低字节高字节低字节高字节……16位双声道:采样一……左声道右声道……低字节高字节低字节高字节……图四、WAVE文件数据采样格式2、声音文件的声音数据的读取操作操作声音文件,也就是将WAVE文件打开,获取其中的声音数据,根据所需要的声音数据处理算法,进行相应的数学运算,然后将结果重新存储与WAVE格式的文件中去。可以使用CFILE类来实现读取操作,也可以使用另外一种方法,拿就是使用Windows提供的多媒体处理函数(这些函数都以mmino打头)。这里就介绍如何使用这些相关的函数来获取声音文件的数据,至于如何进行处理,那要根据你的目的来选择不同的算法了。WAVE文件的操作流程如下:1)调用mminoOpen函数来打开WAVE文件,获取HMMIO类型的文件句柄;2)根据WAVE文件的结构,调用mmioRead、mmioWrite和mmioSeek函数实现文件的读、写和定位操作;3)调用mmioClose函数来关闭WAVE文件。下面的函数代码就是根据WAVE文件的格式,实现了读取双声道立体声数据,但是在使用下面的代码过程中,注意需要在程序中链接Winmm.lib库,并且包含头文件Mmsystem.h。BYTE*GetData(Cstring*pString)//获取声音文件数据的函数,pString参数指向要打开的声音文件;{if(pString==NULL)returnNULL;HMMIOfile1;//定义HMMIO文件句柄;file1=mmioOpen((LPSTR)pString,NULL,MMIO_READWRITE);//以读写模式打开所给的WAVE文件;if(file1==NULL){MessageBox(WAVE文件打开失败!);ReturnNULL;}charstyle[4];//定义一个四字节的数据,用来存放文件的类型;mmioSeek(file1,8,SEEK_SET);//定位到WAVE文件的类型位置mmioRead(file1,style,4);if(style[0]!='W'style[1]!='A'style[2]!='V'style[3]!='E')//判断该文件是否为WAVE文件格式{MessageBox(该文件不是WAVE格式的文件!);ReturnNULL;}PCMWAVEFORMATformat;//定义PCMWAVEFORMAT结构对象,用来判断WAVE文件格式;mmioSeek(file1,20,SEEK_SET);//对打开的文件进行定位,此时指向WAVE文件的PCMWAVEFORMAT结构的数据;mmioRead(file1,(char*)&format,sizeof(PCMWAVEFORMAT));//获取该结构的数据;if(format.wf.nChannels!=2)//判断是否是立体声声音;{MessageBox(该声音文件不是双通道立体声文件);returnNULL;}mmioSeek(file1,24+sizeof(PCMWAVEFORMAT),SEEK_SET);//获取WAVE文件的声音数据的大小;longsize;mmioRead(file1,(char*)&size,4);BYTE*pData;pData=(BYTE*)newchar[size];//根据数据的大小申请缓冲区;mmioSeek(file1,28+sizeof(PCMWAVEFORMAT),SEEK_SET);//对文件重新定位;mmioRead(file1,(char*)pData,size);//读取声音数据;mmioClose(file1,MMIO_FHOPEN);//关闭WAVE文件;returnpData;}3、使用MCI方法操作声音文件WAVE声音文件一个最基本的操作就是将文件中的声音数据播放出来,用Windows提供的API函数BOOLsndPlaySound(LPCSTRlpszSound,UINTfuSound)可以实现小型WAV文件的播放,其中参数lpszSound为所要播放的声音文件,fuSound为播放声音文件时所用的标志位。例如实现Sound.wav文件的异步播放,只要调用函数sndPlaySound(c:\windows\Sound.wav,SND_ASYNC)就可以了,由此可以看到sndPlaySound函数使用是很简单的。但是当WAVE文件大于100K时,这时候系统无法将声音数据一次性的读入内存,sndPlaySound函数就不能进行播放了。为了解决这个问题,你的一个选择就是用MCI方法来操作声音文件了。在使用MCI方法之前,首先需要在你开发的项目设置Project-Setting-Link-Object/librarymodules中加入winmm.lib。并在头文件中包括mmsystem.h头文件。MicroSoftAPI提供了MCI(TheMediaControlInterface)的方法mciSendCommand()和mciSendString()来完成WAVE文件的播放,这里仅介绍mciSendCommand()函数的使用。原型:DWORDmciSendCommand(UINTwDeviceID,UINTwMessage,DWORDdwParam1,DWORDdwParam2);参数:wDeviceID:接受消息的设备ID;Message:MCI命令消息;wParam1:命令的标志位;wParam2:所使用参数块的指针返值:调用成功,返回零;否则,返回双字中的低字存放有错误信息。在使用MCI播放声音文件时,首先要打开音频设备,为此要定义MCI_OPEN_PARMS变量OpenParms,并设置该结构的相应分量:OpenParms.lpstrDeviceType=(LPCSTR)MCI_DEVTYPE_WAVEFORM_AUDIO;//WAVE类型OpenParms.lpstrElementName=(LPCSTR)Filename;//打开的声音文件名;OpenParms.wDeviceID=0;//打开的音频设备的IDmciSendCommand(NULL,MCI_OPEN,MCI_WAITMCI_OPEN_TYPEMCI_OPEN_TYPE_IDMCI_OPEN_ELEMENT,(DWORD)(LPVOID)&OpenParms)函数调用发送MCI_OPEN命令后,返回的参数OpenParms中成员变量的wDeviceID指明打