WAV文件格式分析详解_第1页
WAV文件格式分析详解_第2页
WAV文件格式分析详解_第3页
WAV文件格式分析详解_第4页
WAV文件格式分析详解_第5页
已阅读5页,还剩33页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

WAV文件格式分析详解一、综述WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文ResourceInterchangeFileFormat的缩写,每个WAVE文件的头四个字节便是“RIFF〞。

WAVE文件是由假设干个Chunk组成的。按照在文件中的出现位置包括:RIFFWAVEChunk,FormatChunk,FactChunk(可选),DataChunk。具体见以下图:------------------------------------------------

|

RIFFWAVEChunk

|

|

ID

='RIFF'

|

|

RiffType='WAVE'

|

------------------------------------------------

|

FormatChunk

|

|

ID='fmt'

|

------------------------------------------------

|

FactChunk(optional)

|

|

ID='fact'

|

------------------------------------------------

|

DataChunk

|

|

ID='data'

|

------------------------------------------------

图1

Wav格式包含Chunk例如

其中除了FactChunk外,其他三个Chunk是必须的。每个Chunk有各自的ID,位于Chunk最开始位置,作为标示,而且均为4个字节。并且紧跟在ID后面的是Chunk大小〔去除ID和Size所占的字节数后剩下的其他字节数目〕,4个字节表示,低字节表示数值低位,高字节表示数值高位。下面具体介绍各个Chunk内容。

PS:

所有数值表示均为低字节表示低位,高字节表示高位。二、具体介绍

RIFFWAVEChunk

==================================

|

|所占字节数|

具体内容

|

==================================

|ID

|

4Bytes|

'RIFF'

|

----------------------------------

|Size

|

4Bytes|

|

----------------------------------

|Type

|

4Bytes|

'WAVE'

|

----------------------------------

图2

RIFFWAVEChunk

以'FIFF'作为标示,然后紧跟着为size字段,该size是整个wav文件大小减去ID和Size所占用的字节数,即FileLen-8=Size。然后是Type字段,为'WAVE',表示是wav文件。

结构定义如下:

structRIFF_HEADER

{

charszRiffID[4];

//'R','I','F','F'

DWORDdwRiffSize;

charszRiffFormat[4];//'W','A','V','E'

};FormatChunk

===============================================================

|

|

字节数

|

具体内容

|

=================================================================

|ID

|

4Bytes

|

'fmt'

|

-----------------------------------------------------------------

|Size

|

4Bytes

|数值为16或18,18那么最后又附加信息

|

-----------------------------------------------------------

----

|FormatTag

|

2Bytes

|编码方式,一般为0x0001

|

|

--------------------------------------------------------------------

|

|Channels

|

2Bytes

|声道数目,1--单声道;2--双声道

|

|

--------------------------------------------------------------------

|

|SamplesPerSec|

4Bytes

|采样频率

|

|

--------------------------------------------------------------------

|

|AvgBytesPerSec|

4Bytes

|每秒所需字节数

|

|===>WAVE_FORMAT

--------------------------------------------------------------------

|

|BlockAlign

|

2Bytes

|数据块对齐单位(每个采样需要的字节数)|

|

--------------------------------------------------------------------

|

|BitsPerSample|

2Bytes

|每个采样需要的bit数

|

|

--------------------------------------------------------------------

|

|

|

2Bytes

|附加信息〔可选,通过Size来判断有无〕|

|

--------------------------------------------------------------------

----

图3

FormatChunk

以'fmt'作为标示。一般情况下Size为16,此时最后附加信息没有;如果为18

那么最后多了2个字节的附加信息。主要由一些软件制成的wav格式中含有该2个字节的

附加信息。

结构定义如下:

structWAVE_FORMAT

{

WORDwFormatTag;

WORDwChannels;

DWORDdwSamplesPerSec;

DWORDdwAvgBytesPerSec;

WORDwBlockAlign;

WORDwBitsPerSample;

};

structFMT_BLOCK

{

char

szFmtID[4];//'f','m','t',''

DWORD

dwFmtSize;

WAVE_FORMATwavFormat;

};

FactChunk

==================================

|

|所占字节数|

具体内容

|

==================================

|ID

|

4Bytes|

'fact'

|

----------------------------------

|Size

|

4Bytes|

数值为4

|

----------------------------------

|data

|

4Bytes|

|

----------------------------------

图4

FactChunk

FactChunk是可选字段,一般当wav文件由某些软件转化而成,那么包含该Chunk。

结构定义如下:

structFACT_BLOCK

{

char

szFactID[4];//'f','a','c','t'

DWORD

dwFactSize;

};DataChunk

==================================

|

|所占字节数|

具体内容

|

==================================

|ID

|

4Bytes|

'data'

|

----------------------------------

|Size

|

4Bytes|

|

----------------------------------

|data

|

|

|

----------------------------------

图5DataChunk

DataChunk是真正保存wav数据的地方,以'data'作为该Chunk的标示。然后是

数据的大小。紧接着就是wav数据。根据FormatChunk中的声道数以及采样bit数,

wav数据的bit位置可以分成以下几种形式:

---------------------------------------------------------------------

|

单声道

|

取样1

|

取样2

|

取样3

|

取样4

|

|

|--------------------------------------------------------

|

8bit量化|

声道0

|

声道0

|

声道0

|

声道0

|

---------------------------------------------------------------------

|

双声道

|

取样1

|

取样2

|

|

|--------------------------------------------------------

|

8bit量化|

声道0(左)

|

声道1(右)

|

声道0(左)

|

声道1(右)

|

---------------------------------------------------------------------

|

|

取样1

|

取样2

|

|

单声道

|--------------------------------------------------------

|16bit量化|

声道0

|

声道0

|

声道0

|

声道0

|

|

|(低位字节)

|(高位字节)

|(低位字节)

|(高位字节)

|

---------------------------------------------------------------------

|

|

取样1

|

|

双声道

|--------------------------------------------------------

|16bit量化|

声道0(左)

|

声道0(左)

|

声道1(右)

|

声道1(右)

|

|

|(低位字节)

|(高位字节)

|(低位字节)

|(高位字节)

|

---------------------------------------------------------------------

图6wav数据bit位置安排方式

DataChunk头结构定义如下:

structDATA_BLOCK

{

charszDataID[4];//'d','a','t','a'

DWORDdwDataSize;

};

三、小结

因此,根据上述结构定义以及格式介绍,很容易编写相应的wav格式解析代码。

这里具体的代码就不给出了。WAV文件格式研究笔记WAV文件格式是(WAVFromformat)的简写。WAV是指文件格式,而数据编码格式是多样的,目前微软提供的数据格式只有一种PCM-脉派编码调变(PulseCodeModulation也就是最常见的无压缩WAV〕。其他的数据格式有G.723.1、ACELP、CCITTA-Law、CCITTu-Law、TrueSpeed(TM)、GSM6.10等,这些格式大多数是为或调制解调器等低速语音为主的设备而使用,它们一般采用比拟窄的采样范围来产生比拟大的压缩比,并没有统一标准。不在本文讨论范围。·RIFF文件和WAV文件格式在Windows环境下,大局部的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(ResourceslnterchangeFileFormat),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其根本构成单位为chunk,犹如树状结构中的节点,每个chunk由"区分码"、"数据大小"及"数据"所组成。区分码〔ID〕由4个ASCII码所构成,数据大小那么标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"LIST"为区分码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。此4个Byte称为"格式区分码",然而RIFF又规定文件中仅能有一个以"RIFF"为区分码的chunk。·文件结构WAV文件是chunk的集合,其中有两个chunk是不可缺少的,分别是“fmt〞〔format〕和“data〞chunk,fmt装载的是wav文件的各项参数,如采样率。datachunk装载的是音频数据。其他的chunk那么是可选的。所有音频应用程序必须能读取这两个主要的chunk,所有音频复制程序必须能复制所有chunk。chunk在文件中的顺序是不受限制的,除了一条规那么:fmtchunk必须在datachunk前面。绝大局部人写程序的时候都以为fmtchunk必须放在文件的开头,紧跟riff标识。实际上这并不是必须的,因为微软白皮书没要求如此。但这样搞也没错。以下图显示一个简单的WAV文件结构__________________________|riffwavechunk||groupid='riff'||rifftype='wave'||__________________|||formatchunk||||ckid='fmt'||||__________________|||__________________|||sounddatachunk||||ckid='data'||||__________________|||__________________________|·数据类型所有数据存储在8bit字节中,按照intel80x86(ie,littleendian)方式排列,多字节排列顺序如下。76543210+-----------------------+char:|lsbmsb|+-----------------------+7654321015141312111098+-----------------------+-----------------------+short:|lsbbyte0|byte1msb|+-----------------------+-----------------------+765432101514131211109823222120191817163130292827262524+-----------------------+-----------------------+-----------------------+-----------------------+long:|lsbbyte0|byte1|byte2|byte3msb|+-----------------------+-----------------------+-----------------------+-----------------------+·采样频率,采样精度,声道数量采样频率〔samplerate〕:1秒钟采样的次数。采样次数越多,越能细分声音的频率。音质越好。采样精度〔bitresolution〕:〔采样精度、采样位数、采样值或取样值,随便你怎么叫〕用来描述每次采样结果的空间大小。也就是用多大的取值范围去描述一个采样点的值〔振幅〕。精度越高,对声音的分辨力越强。声道数量〔channels〕:那就是声道数量...·采样点和采样帧〔samplepointandsampleframe〕采样点是描述某个时刻的声音样本。采样精度就是这个时间上声音的振幅可以用多大的取值空间描述。16bit的采样精度取值范围是-32768(0x8000)到32767(0x7fff)。中点,也就是静音点是0。但对于8bit的采样精度来说,范围却是从0-255,静音点在128。〔为何有这样的差异?问比盖去。他的人弄的〕因为cpu读数据都是用8bit的byte做单位。所以如果你用的是9-16bit的精度,每个采样点用2byte。17-24bit用3byte,25-32bit就用4byte双word。按左对齐,空出的bit补0〔pad局部〕。具体排列方式按以下图。________________________________________________||||||||||||||||||1010000101110000||___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|<---------------------------------------------><------------->12bit采样点按照左对齐方式先排满左边右面4bit补0〔pad〕注意,根据intellittleendian存储顺序,按byte为单位从左到右从小到大,因此存到介质上面应该是下面这个顺序:________________________________________________|||||||||||||||||||01110000||10100001||___|___|___|___|___|___|___|___||___|___|___|___|___|___|___|___|<-------------><-------------><----------------------------->bits0to34padbitsbits4to11如果当前的WAV文件是多声道的怎么办?假设是2声道的,先放左边声道的采样点,然后放右面声道的。接着是下一个时刻的左声道采样点。这样用两个采样点分别表示左右声道,在播放的时候同一时间播放出来。那么一个时刻上的采样点的集合就成为采样帧。单声道文件每个采样帧包括1个采样点,两声道的采样帧就有2个采样点。如此类推。samplesamplesampleframe0frame1framen______________________________|ch1|ch2|ch1|ch2|...|ch1|ch2||_____|_____|_____|_____||_____|_____|_____||=onesamplepoint|_____|下面是大于两声道的情况下的排列标准。channels12__________________|left|right|stereo||||_________|_________|123___________________________|left|right|center|3channel|||||_________|_________|_________|1234____________________________________|front|front|rear|rear|quad|left|right|left|right||_________|_________|_________|_________|1234____________________________________|left|center|right|surround|4channel||||||_________|_________|_________|_________|123456______________________________________________________|left|left|center|right|right|surround|6channel|center|||center||||_________|_________|_________|_________|_________|_________|note:以上为无压缩WAVE格式排列方式。显然这样的方式可压缩空间很大〔否那么也不会跑出ape这类无损压缩格式了〕。如果是有压缩格式,那么不一定适用上述规那么。·theformatchunkformat〔fmt〕chunk描述了WAVEFROM数据的根本参数,例如采样频率,精度,声道数量等等。#defineformatid'fmt'/*chunkidforformatchunk.note:因为ID是4字节,所以fmt后面有个空格!切记*/typedefstruct...{idchunkid;longchunksize;shortwformattag;unsignedshortwchannels;unsignedlongdwsamplespersec;unsignedlongdwavgbytespersec;unsignedshortwblockalign;unsignedshortwbitspersample;/**//*note:theremaybeadditionalfieldshere,dependinguponwformattag.*/}formatchunk;chunkid:永远是'fmt'chunksize:大小,不包括chunkid和chunksize占用的8个字节,单位是bytewformattag:如果datachunk里面的数据是无压缩的话,那么每个采样帧和每个采样点的长度应该是固定并且符合上面的采样点规定的,但如果是有压缩的话,这就不一定了。wformattag就是用来标识是否有压缩的。在无压缩的情况下,wformattag的值是1,并且没有fmtchunk没有附加fields〔furtherfields〕。wformattag不等于1的其他情况下的处理方式要看微软网站。wchannels:就是声道数量。1、2、3、4、6...dwsamplespersec:采样频率、每秒采样次数。单位赫兹。标准的取值如11025,22050,and44100。dwavgbytespersec:每秒采样数据的空间大小,也就是正常播放的话,每秒要读多少数据。用来方便程序评估留多大缓冲区。dwavgbytespersec的值必须根据以下公式。dwavgbytespersec=round(dwsamplespersec*wblockalign)wblockalign:块对齐位,值从以下公式得出。wblockalign=round(wchannels*(wbitspersample%8))根本上说,wblockalign就等于采样帧大小。wbitspersample就是采样率。每个合法的WAV文件都必须有唯一的一个fmtchunk。·datachunk放声音数据的地方了。#definedataid'data'/*chunkidfordatachunk*/typedefstruct...{idchunkid;longchunksize;unsignedcharwaveformdata[];}datachunk;chunkid:永远是datachunksize:不包括chunkid,和chunksize占用的8个字节。waveformdata:这个数组里面每个元素就是一个采样帧,大小看wblockalign每个合法的WAV文件都必须有唯一的一个datachunk。运用多媒体WAV文件格式二三例多媒体技术近年来开展很快,较好品质的声卡可以提供16位的立体声及44KHZ的播放录制能力,它不仅可以提供原音逼真的取样,其合成的音质也十分理想,有的声卡还参加了数字信号处理器,可编程控制的DSP具有强大的运算能力,它可以用来作声音信息的压缩和一些特殊效果的处理。具有此功能的声卡提供的WAV文件提供的语音信息可以满足语音特征识别的要求。1.1RIFF文件和WAV文件格式在Windows环境下,大局部的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(ResourceslnterchangeFileFormat),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其根本构成单位为chunk,犹如树状结构中的节点,每个chunk由"区分码"、"数据大小"及"数据"所组成。区分码由4个ASCII码所构成,数据大小那么标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"L1ST"为区分码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。此4个Byte称为"格式区分码",然而RIFF又规定文件中仅能有一个以"RIFF"为区分码的chunk。只要依循此一结构的文件,我们均称之为RIFF档。此种结构提供了一种系统化的分类。如果和MS一DOS文件系统作比拟,"RIFF"chunk就好比是一台硬盘的根目录,其格式区分码便是此硬盘的逻辑代码(C:或D:),而"L1ST"chunk即为其下的子目录,其他的chunk那么为一般的文件。至于在RIFF文件的处理方面,微软提供了相关的函数。视窗下的各种多媒体文件格式就如同在磁盘机下规定仅能放怎样的目录,而在该目录下仅能放何种数据。WAV为WAVEFORM(波形)的缩写。声音文件的结构如图1所示,"RIFF"的格式区分码为"WAVE"。整个文件由两个chunk所组成:区分码"fmt"(注意,最后一个是空白字符!)及"data"。在"fmt"的chunk下包含了一个PCMWAVEFORMAT数据结构,其定义如下:typedefstructpcmwaveformat-tag{WAVEFORMATwf;WORDwBitsPerSample;}PCMWAVEFORMAT;typedefstructwaveformat-tag{WORDwFormatTag;WORDnChannels;DWORDnSamplesPerSec;DWORDnAvgBytesperSec;WORDnBlockAlign;}WAVEFORMAT;其意义分别为:wFormatTag:记录着此声音的格式代号,例如WAVE_FORMAT_PCM,WAVE_F0RAM_ADPCM等等。nChannels:记录声音的频道数。nSamp1esPerSec:记录每秒取样数。nAvgBytesPerSec:记录每秒的数据量。nBlockA1ign:记录区块的对齐单位。wBitsPerSample:记录每个取样所需的位元数。"data"Chunk包含真正的声音数据。Window目前仅提供WAVE_FORMAT_PCM一种数据格式,所代表的意义是脉派编码调变(Pu1seCodeModulation)。针对此格式,Windows定义了在"data"的chunk中数据的存放情形,图2中列出了四种不同频道数及取样所需的位元数以及位元位置的安排。"RIFF"频道0频道0频道0频道0xxxxnChannels=1,wBitsPerSample=8"WAVE"频0(左)频道1(右)频道0(左)频道1(右)"fmt"nChannels=2,wBitsPerSample=8sizeof(PCMWAVEFORMAT)structofPCMWAVEFORMAT频道0(低位)频道0(高位)频道0(低位)频道0(高位)"data"nChannels=1,wBitsPerSample=16xxxx频道0(低位)频道0(高位)频道0(低位)频道0(高位)(低位)(高位)(低位)(高位)waveformdatanChannels=2,wBitsPerSample=16图1WAV文件结构图2PCM文件中位元安排方式第一排表示单声道8位元,第二排表示双声道8位元,第三排表示单声道16位元,第四排表示双声道16位元。8位元代表音量大小由8个位元所表示,16位元那么代表音量大小由16个位元所表示。理论上8位元可以表示0~255,16位元可表示0~65536,不过windows却定16位元其值的范围从-32168~32167。此外尚有一点要注意的是,0并不一定代表无声,而是由中间的数值来决定,也就是在8位元时为128,16位元时为0才是无声。所以,假设程序设计时需放入无声的数据,糯特别注意声音格式是16或是8位元,以放入适当的值。1.2WAV文件信息的具体应用WAV文件中包括了对原始声音的高速率采样,并且以WAVE_PCM_FORMAT脉派编码调变格式,我们可以在VISUALC++程序中实现,在读出WAVEHDR文件头之后,下面就是原始声音的高速率采样信息,我们可以对它作多方面的信息处理。波形显示。我们可以以时域-幅度的方式显示出原始声音的波形,这是最简单同时也是最直接的信息处理方式。在时域范围内,我们可以观察该信号波形是否连续,中间是否有跳变等。频谱显示我们可以以频域-幅度的方式显示出原始声音的频谱,在对原始信号经过FFT变换之后,可以得到该信号的频谱,进而得到该信号的能量集中带,分布特征,谱对称系数等等。用于语音信号识别讲话者的个体识别是语音信号处理的一个重要内容,但它的一个前提条件是必须提供语音信号的数字波形,通常的方法是将原始的语音信号进行放大、抗混叠滤波、A/D采样、数值编码,最终得到语音信号的数字波形,通常多采用硬件处理,费时费力,如果我们借助非常成熟的声卡技术,将WAV文件翻开,就非常方便地得到语音信号的数字波形,为下一步进行语音信号识别提供良好的前端预处理。基于VisuaC++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"〕

"fmt"

Sizeof(PCMWAVEFORMAT)

PCMWAVEFORMAT

"data"

声音数据大小

声音数据图三、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_WAIT|MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID|MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&OpenParms)函数调用发送MCI_OPEN命令后,返回的参数OpenParms中成员变量的wDeviceID指明翻开了哪个设备。需要关闭音频设备时只要调用mciSendCommand(m_wDeviceID,MCI_CLOSE,NULL,NULL)就可以了。

播放WAVE文件时,需要定义MCI_PLAY_PARMS变量PlayParms,对该变量进行如下设置:PlayParms.dwFrom=0,这是为了指定从什么地方〔时间〕播放WAVE文件,设置好以后,调用函数mciSendCommand(m_wDeviceID,MCI_PLAY,MCI_FROM,(DWORD)(LPVOID)&PlayParms));就实现了WAVE声音文件的播放。

另外,调用mciSendCommand(m_wDeviceID,MCI_PAUSE,0,(DWORD)(LPVOID)&PlayParms)实现了暂停功能。调用mciSendCommand(m_wDeviceID,MCI_STOP,NULL,NULL)实现停止功能等,可以看出,这些不同的功能实现都是依靠参数"Message"取不同的值来实现的。不同的Message和dwParam1、dwParam2的组合还可以实现文件的跳跃功能。如下面的代码实现了跳转到WAVE文件末端的操作:mciSendCommand(m_wDeviceID,MCI_SEEK,MCI_SEEK_TO_END,NULL)。

4、DirectSound操作WAVE文件的方法

MCI虽然调用简单,功能强大,可以满足声音文件处理的根本需要,但是MCI也有它的缺点,那就是它一次只能播放一个WAVE文件,有时在实际应用中,为了实现混音效果,需要同时播放两个或两个以上的WAVE文件时,就需要使用微软DirectX技术中的DirectSound了,该技术直接操作底层声卡设备,可以实现八个以上WAV文件的同时播放。

实现DirectSound需要以下几个步骤:1.创立及初始化DirectSound;2.设定应用程序的声音设备优先级别方式,一般为DSSCL_NORMAL;2.将WAV文件读入内存,找到格式块、数据块位置及数据长度;3.创立声音缓冲区;4.载入声音数据;5.播放及停止:

二、编程步骤

1、启动VisualC++6.0生成一个单文档视图结构的应用程序,将该程序命名为"playsound";

2、在程序的主菜单中添加"MCIPlay"、"PlaySound"菜单,并使用ClassWizard添加相应的消息响应函说,分别用不同的方法来处理声音文件;

3、在程序的"Link"设置中添加"dsound.lib、dxguid.lib、winmm.lib"库,程序的视图类中包含"mmsystem.h"文件,程序的Debug目录下添加待播放的声音文件"chimes.wav和sound.wav";

4、添加代码,编译运行程序;三、程序代码

////////////////////////////////////////////////////

voidCPlaysoundView::OnMciplay()//下面的代码实现了WAVE声音文件的播放:

{

//TODO:Addyourcommandhandlercodehere

MCI_OPEN_PARMSmciOpenParms;

MCI_PLAY_PARMSPlayParms;

mciOpenParms.dwCallback=0;

mciOpenParms.lpstrElementName="d:\\chimes.wav";

mciOpenParms.wDeviceID=0;

mciOpenParms.lpstrDeviceType="waveaudio";

mciOpenParms.lpstrAlias="";

PlayParms.dwCallback=0;

PlayParms.dwTo=0;

PlayParms.dwFrom=0;

mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE|MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&mciOpenParms);//翻开音频设备;

mciSendCommand(mciOpenParms.wDeviceID,MCI_PLAY,MCI_WAIT,(DWORD)(LPVOID)&PlayParms);//播放WAVE声音文件;

mciSendCommand(mciOpenParms.wDeviceID,MCI_CLOSE,NULL,NULL);//关闭音频设备;

}

//////////////////////////////////////////////////////////////////////////////

/*下面的函数利用DirectSound技术实现了一个WAVE声音文件的播放〔注意工程设置中要包含"dsound.lib、dxguid.lib"的内容〕,代码和注释如下:*/

voidCPlaysoundView::OnPlaySound()

{

//TODO:Addyourcommandhandlercodehere

LPVOIDlpPtr1;//指针1;

LPVOIDlpPtr2;//指针2;

HRESULThResult;

DWORDdwLen1,dwLen2;

LPVOIDm_pMemory;//内存指针;

LPWAVEFORMATEXm_pFormat;//LPWAVEFORMATEX变量;

LPVOIDm_pData;//指向语音数据块的指针;

DWORDm_dwSize;//WAVE文件中语音数据块的长度;

CFileFile;//Cfile对象;

DWORDdwSize;//存放WAV文件长度;

//翻开sound.wav文件;

if(!File.Open("d://sound.wav",Cfile::modeRead|Cfile::shareDenyNone))

return;

dwSize=File.Seek(0,Cfile::end);//获取WAVE文件长度;

File.Seek(0,Cfile::begin);//定位到翻开的WAVE文件头;

//为m_pMemory分配内存,类型为LPVOID,用来存放WAVE文件中的数据;

m_pMemory=GlobalAlloc(GMEM_FIXED,dwSize);

if(File.ReadHuge(m_pMemory,dwSize)!=dwSize)//读取文件中的数据;

{

File.Close();

return;

}

File.Close();

LPDWORDpdw,pdwEnd;

DWORDdwRiff,dwType,dwLength;

if(m_pFormat)//格式块指针

m_pFormat=NULL;

if(m_pData)//数据块指针,类型:LPBYTE

m_pData=NULL;

if(m_dwSize)//数据长度,类型:DWORD

m_dwSize=0;

pdw=(DWORD*)m_pMemory;

dwRiff=*pdw++;

dwLength=*pdw++;

dwType=*pdw++;

if(dwRiff!=mmioFOURCC('R','I','F','F'))

return;//判断文件头是否为"RIFF"字符;

if(dwType!=mmioFOURCC('W','A','V','E'))

return;//判断文件格式是否为"WAVE";

//寻找格式块,数据块位置及数据长度

pdwEnd=(DWORD*)((BYTE*)m_pMemory+dwLength-4);

boolm_bend=false;

while((pdw<pdwEnd)&&(!m_bend))

//pdw文件没有指到文件末尾并且没有获取到声音数据时继续;

{

dwType=*pdw++;

dwLength=*pdw++;

switch(dwType)

{

casemmioFOURCC('f','m','t','')://如果为"fmt"标志;

if(!m_pFormat)//获取LPWAVEFORMATEX结构数据;

{

if(dwLength<sizeof(WAVEFORMAT))

return;

m_pFormat=(LPWAVEFORMATEX)pdw;

}

break;

casemmioFOURCC('d','a','t','a')://如果为"data"标志;

if(!m_pData||!m_dwSize)

{

m_pData=(LPBYTE)pdw;//得到指向声音数据块的指针;

m_dwSize=dwLength;//获取声音数据块的长度;

if(m_pFormat)

m_bend=TRUE;

}

break;

}

pdw=(DWORD*)((BYTE*)pdw+((dwLength+1)&~1));//修改pdw指针,继续循环;

}

DSBUFFERDESCBufferDesc;//定义DSUBUFFERDESC结构对象;

memset(&BufferDesc,0,sizeof(BufferDesc));

BufferDesc.lpwfxFormat=(LPWAVEFORMATEX)m_pFormat;

BufferDesc.dwSize=sizeof(DSBUFFERDESC);

BufferDesc.dwBufferBytes=m_dwSize;

BufferDesc.dwFlags=0;

HRESULThRes;

LPDIRECTSOUNDm_lpDirectSound;

hRes=::DirectSoundCreate(0,&m_lpDirectSound,0);//创立DirectSound对象;

if(hRes!=DS_OK)

return;

m_lpDirectSound->SetCooperativeLevel(this->GetSafeHwnd(),DSSCL_NORMAL);

//设置声音设备优先级别为"NORMAL";

//创立声音数据缓冲;

LPDIRECTSOUNDBUFFERm_pDSoundBuffer;

if(m_lpDirectSound->CreateSoundBuffer(&BufferDesc,&m_pDSoundBuffer,0)==DS_OK)

//载入声音数据,这里使用两个指针lpPtr1,lpPtr2来指向DirectSoundBuffer缓冲区的数据,这是为了处理大型WAVE文件而设计的。dwLen1,dwLen2分别对应这两个指针所指向的缓冲区的长度。

hResult=m_pDSoundBuffer->Lock(0,m_dwSize,&lpPtr1,&dwLen1,&lpPtr2,&dwLen2,0);

if(hResult==DS_OK)

{

memcpy(lpPtr1,m_pData,dwLen1);

if(dwLen2>0)

{

BYTE*m_pData1=(BYTE*)m_pData+dwLen1;

m_pData=(void*)m_pData1;

memcpy(lpPtr2,m_pData,dwLen2);

}

m_pDSoundBuffer->Unlock(lpPtr1,dwLen1,lpPtr2,dwLen2);

}

DWORDdwFlags=0;

m_pDSoundBuffer->Play(0,0,dwFlags);//播放WAVE声音数据;

}四、小结

为了更好的说明DiretSound编程的实现,笔者使用了一个函数来实现所有的操作,当然读者可以将上面的内容包装到一个类中,从而更好的实现程序的封装性,至于如何实现就不需要笔者多说了,真不明白的话,找本C++的书看看〔呵呵〕。如果定义了类,那么就可以一次声明多个对象来实现多个WAVE声音文件的混合播放。也许细心的读者朋友会发现,在介绍WAVE文件格式的时候我们介绍了PCMWAVEFORMAT结构,但是在代码的实现读取WAVE文件数据局部,我们使用的却是LPWAVEFORMATEX结构,那末是不是我们有错误呢?其实没有错,对于PCM格式的WAVE文件来说,这两个结构是完全一样的,使用LPWAVEFORMATEX结构不过是为了方便设置DSBUFFERDESC对象罢了。

操作WAVE声音文件的方法很多,灵活的运用它们可以灵活地操作WAVE文件,这些函数的详细用途读者可以参考MSDN。本实例只是对WAVE文件的操作作了一个浅薄的介绍,希望可以对读者朋友起到抛砖

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论