- Rongsen.Com.Cn 版权所有 2008-2010 京ICP备08007000号 京公海网安备11010802026356号 朝阳网安编号:110105199号
- 北京黑客防线网安工作室-黑客防线网安服务器维护基地为您提供专业的
服务器维护
,企业网站维护
,网站维护
服务 - (建议采用1024×768分辨率,以达到最佳视觉效果) Powered by 黑客防线网安 ©2009-2010 www.rongsen.com.cn
作者:黑客防线网安网站维护基地 来源:黑客防线网安网站维护基地 浏览次数:0本篇关键词:Visual
|
一、前言
当前Visual C++相关的编程资料中,无论是大部头的参考书,还是一些计算机杂志,对声音文件的处理都是泛泛的涉及一下,许多编程爱好者都感到对该部分的内容了解不是很透彻,本文希望能够给刚刚涉及到声音处理领域的朋友们起到一个引路的作用,帮助他们尽快进入声音处理的更深奥空间。
当前计算机系统处理声音文件有两种办法:一是使用现成的软件,如微软的录音机、SoundForge、CoolEdit等软件可以实现对声音信号进行录音、编辑、播放的处理,但它们的功能是有限的,为了更灵活,更大限度地处理声音数据,就不得不使用另外一种方法,既利用微软提供的多媒体服务,在Windows环境下自己编写程序来进行声音处理来实现一些特定的功能。下面就开始介绍声音文件的格式和在Windows环境下使用Visual C++开发工具进行声音文件编程处理的方法,本文所有的程序代码都在Windows2000、Visual C++6.0环境下编译通过,运行正常。
二、RIFF文件结构和WAVE文件格式
Windows支持两种RIFF(Resource Interchange File Format,"资源交互文件格式")格式的音频文件:MIDI的RMID文件和波形音频文件格式WAVE文件,其中在计算机领域最常用的数字化声音文件格式是后者,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav",因而该类文件也被称为WAVE文件。为了突出重点,有的放矢,本文涉及到的声音文件所指的就是WAVE文件。常见的WAVE语音文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。这里的采样率是指声音信号在进行"模→数"转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。WAVE文件数据块包含以脉冲编码调制(PCM)格式表示的样本。在进行声音编程处理以前,首先让我们来了解一下RIFF文件和WAVE文件格式。
RIFF文件结构可以看作是树状结构,其基本构成是称为"块"(Chunk)的单元,每个块有"标志符"、"数据大小"及"数据"所组成,块的结构如图1所示:
块的标志符(4BYTES) |
数据大小 (4BYTES) |
数据 |
RIFF/LIST标志符 | |
数据1大小 | |
数据1 | 格式/列表类型 |
数据 |
标志符(RIFF) |
数据大小 |
格式类型("WAVE") |
"fmt" |
Sizeof(PCMWAVEFORMAT) |
PCMWAVEFORMAT |
"data" |
声音数据大小 |
声音数据 |
Typedef struct { WAVEFORMAT wf;//波形格式; WORD wBitsPerSample;//WAVE文件的采样大小; }PCMWAVEFORMAT; WAVEFORMAT结构定义如下: typedef struct { WORD wFormatag;//编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等 WORD nChannls;//声道数,单声道为1,双声道为2; DWORD nSamplesPerSec;//采样频率; DWORD nAvgBytesperSec;//每秒的数据量; WORD nBlockAlign;//块对齐; }WAVEFORMAT; |
采样一 | 采样二 | …… | ||
低字节 | 高字节 | 低字节 | 高字节 | …… |
采样一 …… | ||||
左声道 | 右声道 | …… | ||
低字节 | 高字节 | 低字节 | 高字节 | …… |
BYTE * GetData(Cstring *pString) //获取声音文件数据的函数,pString参数指向要打开的声音文件; { if (pString==NULL) return NULL; HMMIO file1;//定义HMMIO文件句柄; file1=mmioOpen((LPSTR)pString,NULL,MMIO_READWRITE);//以读写模式打开所给的WAVE文件; if(file1==NULL) { MessageBox("WAVE文件打开失败!"); Return NULL; } char style[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格式的文件!"); Return NULL; } PCMWAVEFORMAT format; //定义PCMWAVEFORMAT结构对象,用来判断WAVE文件格式; mmioSeek(file1,20,SEEK_SET); //对打开的文件进行定位,此时指向WAVE文件的PCMWAVEFORMAT结构的数据; mmioRead(file1,(char*)&format,sizeof(PCMWAVEFORMAT));//获取该结构的数据; if(format.wf.nChannels!=2)//判断是否是立体声声音; { MessageBox("该声音文件不是双通道立体声文件"); return NULL; } mmioSeek(file1,24+sizeof(PCMWAVEFORMAT),SEEK_SET); //获取WAVE文件的声音数据的大小; long size; mmioRead(file1,(char*)&size,4); BYTE *pData; pData=(BYTE*)new char[size];//根据数据的大小申请缓冲区; mmioSeek(file1,28+sizeof(PCMWAVEFORMAT),SEEK_SET);//对文件重新定位; mmioRead(file1,(char*)pData,size);//读取声音数据; mmioClose(file1, MMIO_FHOPEN);//关闭WAVE文件; return pData; } |
OpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_WAVEFORM_AUDIO;//WAVE类型 OpenParms.lpstrElementName = (LPCSTR) Filename;//打开的声音文件名; OpenParms.wDeviceID = 0;//打开的音频设备的ID |
void CTest1View::OnMciPlayWave() { // TODO: Add your command handler code here MCI_OPEN_PARMS mciOpenParms; MCI_PLAY_PARMS PlayParms; 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);//关闭音频设备; } |
void CPlaysoundView::OnPlaySound() { // TODO: Add your command handler code here LPVOID lpPtr1;//指针1; LPVOID lpPtr2;//指针2; HRESULT hResult; DWORD dwLen1,dwLen2; LPVOID m_pMemory;//内存指针; LPWAVEFORMATEX m_pFormat;//LPWAVEFORMATEX变量; LPVOID m_pData;//指向语音数据块的指针; DWORD m_dwSize;//WAVE文件中语音数据块的长度; CFile File;//Cfile对象; DWORD dwSize;//存放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 (); LPDWORD pdw,pdwEnd; DWORD dwRiff,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); bool m_bend=false; while ((pdw < pdwEnd)&&(!m_bend)) //pdw文件没有指到文件末尾并且没有获取到声音数据时继续; { dwType = *pdw++; dwLength = *pdw++; switch (dwType) { case mmioFOURCC('f', 'm', 't', ' ')://如果为 "fmt"标志; if (!m_pFormat)//获取LPWAVEFORMATEX结构数据; { if (dwLength < sizeof (WAVEFORMAT)) return ; m_pFormat = (LPWAVEFORMATEX) pdw; } break; case mmioFOURCC('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指针,继续循环; } DSBUFFERDESC BufferDesc;//定义DSUBUFFERDESC结构对象; memset ( &BufferDesc, 0, sizeof (BufferDesc)); BufferDesc.lpwfxFormat = (LPWAVEFORMATEX)m_pFormat; BufferDesc.dwSize = sizeof (DSBUFFERDESC); BufferDesc.dwBufferBytes = m_dwSize; BufferDesc.dwFlags = 0; HRESULT hRes; LPDIRECTSOUND m_lpDirectSound; hRes = ::DirectSoundCreate(0, &m_lpDirectSound, 0);//创建DirectSound对象; if( hRes != DS_OK ) return; m_lpDirectSound- >SetCooperativeLevel(this->GetSafeHwnd(), DSSCL_NORMAL); //设置声音设备优先级别为 "NORMAL"; //创建声音数据缓冲; LPDIRECTSOUNDBUFFER m_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); } DWORD dwFlags = 0; m_pDSoundBuffer- >Play (0, 0, dwFlags); //播放WAVE声音数据; } |
为了更好的说明DiretSound编程的实现,笔者使用了一个函数来实现所有的操作,当然读者可以将上面的内容包装到一个类中,从而更好的实现程序的封装性,至于如何实现就不需要笔者多说了,真不明白的话,找本C++的书看看。如果定义了类,那么就可以一次声明多个对象来实现多个WAVE声音文件的混合播放。也许细心的读者会发现,在介绍WAVE文件格式的时候我们介绍了PCMWAVEFORMAT结构,但是在代码的实现读取WAVE文件数据部分,我们使用的却是LPWAVEFORMATEX结构,那末是不是我们有错误呢?其实没有错,对于PCM格式的WAVE文件来说,这两个结构是完全一样的,使用LPWAVEFORMATEX结构不过是为了方便设置DSBUFFERDESC对象罢了。
操作WAVE声音文件的方法很多,灵活的运用它们可以灵活地操作WAVE文件,这些函数的详细用途读者可以参考MSDN。本文只是对WAVE文件的操作作了一个肤浅的介绍,希望可以对读者起到抛砖引玉的作用。
我要申请本站:N点 | 黑客防线官网 | |
专业服务器维护及网站维护手工安全搭建环境,网站安全加固服务。黑客防线网安服务器维护基地招商进行中!QQ:29769479 |