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"的內容),代碼和注釋如下:
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文件的操作作了一個膚淺的介紹,希望可以對讀者起到拋磚引玉的作用。
摘自 yum2006的專欄