MCI虽然调用简单,功能强大,可以满足声音文件处理的基本需要,但是MCI也有它的缺点,那就是它一次只能播放一个WAVE文件,有时在实际应用中,为了实现混音效果,需要同时播放两个或两个以上的WAVE文件时,就需要使用微软DirectX技术中的DirectSound了,该技术直接操作底层声卡设备,可以实现八个以上WAV文件的同时播放。
实现DirectSound需要以下几个步骤:1.创建及初始化DirectSound;2.设定应用程序的声音设备优先级别方式,一般为DSSCL_NORMAL;2. 将WAV文件读入内存,找到格式块、数据块位置及数据长度;3.创建声音缓冲区;4.载入声音数据;5.播放及停止:
下面的函数利用DirectSound技术实现了一个WAVE声音文件的播放(注意项目设置中要包含"dsound.lib、dxguid.lib"的内容),代码和注释如下:
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文件的操作作了一个肤浅的介绍,希望可以对读者起到抛砖引玉的作用。