基于Visual C++60的声音文件操作文档格式.docx
《基于Visual C++60的声音文件操作文档格式.docx》由会员分享,可在线阅读,更多相关《基于Visual C++60的声音文件操作文档格式.docx(12页珍藏版)》请在冰点文库上搜索。
LIST"
等,指定块的标志ID;
数据大小用来指定块的数据域大小,它的尺寸也为4个字符;
数据用来描述具体的声音信号,它可以由若干个子块构成,一般情况下块与块是平行的,不能相互嵌套,但是有两种类型的块可以嵌套子块,他们是"
或"
标志的块,其中RIFF块的级别最高,它可以包括LIST块。
另外,RIFF块和LIST块与其他块不同,RIFF块的数据总是以一个指定文件中数据存储格式的四个字符码(称为格式类型)开始,如WAVE文件有一个"
WAVE"
的格式类型。
LIST块的数据总是以一个指定列表内容的4个字符码(称为列表类型)开始,例如扩展名为"
.AVI"
的视频文件就有一个"
strl"
的列表类型。
RIFF和LIST的块结构如下:
RIFF/LIST标志符
数据1大小
数据1
格式/列表类型
图二、RIFF/LIST块结构
WAVE文件是非常简单的一种RIFF文件,它的格式类型为"
。
RIFF块包含两个子块,这两个子块的ID分别是"
fmt"
和"
data"
其中"
子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。
WAVE文件的结构如下图三所示:
标志符(RIFF)
数据大小
格式类型("
)
"
Sizeof(PCMWAVEFORMAT)
PCMWAVEFORMAT
声音数据大小
声音数据
图三、WAVE文件结构图
PCMWAVEFORMAT结构定义如下:
Typedefstruct
{
WAVEFORMATwf;
//波形格式;
WORDwBitsPerSample;
//WAVE文件的采样大小;
}PCMWAVEFORMAT;
WAVEFORMAT结构定义如下:
typedefstruct
WORDwFormatag;
//编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORDnChannls;
//声道数,单声道为1,双声道为2;
DWORDnSamplesPerSec;
//采样频率;
DWORDnAvgBytesperSec;
//每秒的数据量;
WORDnBlockAlign;
//块对齐;
}WAVEFORMAT;
子块包含WAVE文件的数字化波形声音数据,其存放格式依赖于"
子块中wFormatTag成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。
如16bit的单声道WAVE文件和双声道WAVE文件的数据采样格式分别如图四所示:
16位单声道:
采样一
采样二
……
低字节
高字节
高字节
16位双声道:
采样一……
左声道
右声道
图四、WAVE文件数据采样格式
三、声音文件的声音数据的读取操作
操作声音文件,也就是将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格式的文件!
PCMWAVEFORMATformat;
//定义PCMWAVEFORMAT结构对象,用来判断WAVE文件格式;
mmioSeek(file1,20,SEEK_SET);
//对打开的文件进行定位,此时指向WAVE文件的PCMWAVEFORMAT结构的数据;
mmioRead(file1,(char*)&
format,sizeof(PCMWAVEFORMAT));
//获取该结构的数据;
if(format.wf.nChannels!
=2)//判断是否是立体声声音;
该声音文件不是双通道立体声文件"
mmioSeek(file1,24+sizeof(PCMWAVEFORMAT),SEEK_SET);
//获取WAVE文件的声音数据的大小;
longsize;
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;
}
四、使用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;
//打开的音频设备的ID
mciSendCommand(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)。
下面的代码实现了WAVE声音文件的播放:
voidCTest1View:
:
OnMciPlayWave()
//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文件的方法
MCI虽然调用简单,功能强大,可以满足声音文件处理的基本需要,但是MCI也有它的缺点,那就是它一次只能播放一个WAVE文件,有时在实际应用中,为了实现混音效果,需要同时播放两个或两个以上的WAVE文件时,就需要使用微软DirectX技术中的DirectSound了,该技术直接操作底层声卡设备,可以实现八个以上WAV文件的同时播放。
实现DirectSound需要以下几个步骤:
1.创建及初始化DirectSound;
2.设定应用程序的声音设备优先级别方式,一般为DSSCL_NORMAL;
2.将WAV文件读入内存,找到格式块、数据块位置及数据长度;
3.创建声音缓冲区;
4.载入声音数据;
5.播放及停止:
下面的函数利用DirectSound技术实现了一个WAVE声音文件的播放(注意项目设置中要包含"
dsound.lib、dxguid.lib"
的内容),代码和注释如下:
voidCPlaysoundView:
OnPlaySound()
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("
//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();
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'
))
//判断文件头是否为"
字符;
if(dwType!
//判断文件格式是否为"
;
//寻找格式块,数据块位置及数据长度
pdwEnd=(DWORD*)((BYTE*)m_pMemory+dwLength-4);
boolm_bend=false;
while((pdw<
pdwEnd)&
&
(!
m_bend))
//pdw文件没有指到文件末尾并且没有获取到声音数据时继续;
switch(dwType)
casemmioFOURCC('
f'
m'
t'
'
):
//如果为"
标志;
m_pFormat)//获取LPWAVEFORMATEX结构数据;
if(dwLength<
sizeof(WAVEFORMAT))
m_pFormat=(LPWAVEFORMATEX)pdw;
break;
d'
a'
m_pData||!
m_dwSize)
m_pData=(LPBYTE)pdw;
//得到指向声音数据块的指针;
m_dwSize=dwLength;
//获取声音数据块的长度;
if(m_pFormat)
m_bend=TRUE;
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;
Play(0,0,