Windows Multimedia API 提供 PlaySound、MCI、低階 Wave API 三種播放方式,但前二者無法同時播放多個 WAV,低階 API 又過於複雜。DirectSound 可同時混音播放 8 個以上 WAV 檔,API 簡單易用,是遊戲與多媒體應用的首選。本文詳細說明從初始化、載入 WAV、建立聲音緩衝區、播放控制到資源釋放的完整流程。
LPDIRECTSOUND m_pDirectObject;
if (DirectSoundCreate(NULL, &m_pDirectObject, NULL) == DS_OK) {
// 設定合作層級:DSSCL_NORMAL (一般)、DSSCL_PRIORITY (優先)、DSSCL_EXCLUSIVE (獨佔)
m_pDirectObject->SetCooperativeLevel(m_hWnd, DSSCL_NORMAL);
} else {
AfxMessageBox(_T("DirectSound 建立失敗"));
}
WAV 檔為 RIFF 格式,包含 fmt (格式區塊) 與 data (資料區塊)。
BOOL CDirectWave::LoadFile(CString Filename) {
CFile File;
if (!File.Open(Filename, CFile::modeRead | CFile::shareDenyNone)) return FALSE;
DWORD dwSize = File.Seek(0, CFile::end);
File.Seek(0, CFile::begin);
m_pMemory = GlobalAlloc(GMEM_FIXED, dwSize);
File.ReadHuge(m_pMemory, dwSize);
File.Close();
LPDWORD pdw = (LPDWORD)m_pMemory;
DWORD dwRiff = *pdw++; // 'RIFF'
DWORD dwLength = *pdw++;
DWORD dwType = *pdw++; // 'WAVE'
if (dwRiff != mmioFOURCC('R','I','F','F') || dwType != mmioFOURCC('W','A','V','E')) return FALSE;
LPDWORD pdwEnd = (LPDWORD)((BYTE*)pdw + dwLength - 4);
while (pdw < pdwEnd) {
DWORD dwChunkType = *pdw++;
DWORD dwChunkSize = *pdw++;
switch (dwChunkType) {
case mmioFOURCC('f','m','t',' '): // 格式區塊
if (!m_pFormat && dwChunkSize >= sizeof(WAVEFORMAT))
m_pFormat = (LPWAVEFORMATEX)pdw;
break;
case mmioFOURCC('d','a','t','a'): // 資料區塊
if (!m_pData) {
m_pData = (LPBYTE)pdw;
m_dwSize = dwChunkSize;
}
break;
}
pdw = (LPDWORD)((BYTE*)pdw + ((dwChunkSize + 1) & ~1)); // 字對齊
}
return (m_pFormat && m_pData);
}
DSBUFFERDESC BufferDesc = {0};
BufferDesc.dwSize = sizeof(DSBUFFERDESC);
BufferDesc.lpwfxFormat = m_pFormat;
BufferDesc.dwBufferBytes = m_dwSize;
BufferDesc.dwFlags = 0; // 靜態緩衝區
if (m_pDirectObject->CreateSoundBuffer(&BufferDesc, &m_pDSoundBuffer, NULL) != DS_OK)
return FALSE;
BOOL CDirectWave::LoadData() {
LPVOID lpPtr1, lpPtr2; DWORD dwLen1, dwLen2;
HRESULT hr = m_pDSoundBuffer->Lock(0, m_dwSize, &lpPtr1, &dwLen1, &lpPtr2, &dwLen2, 0);
if (hr == DS_OK) {
memcpy(lpPtr1, m_pData, dwLen1);
if (lpPtr2) memcpy(lpPtr2, m_pData + dwLen1, dwLen2);
m_pDSoundBuffer->Unlock(lpPtr1, dwLen1, lpPtr2, dwLen2);
return TRUE;
} else if (hr == DSERR_BUFFERLOST) {
m_pDSoundBuffer->Restore();
return LoadData(); // 遞迴重試
}
return FALSE;
}
void CDirectWave::Play(BOOL bLoop) {
DWORD dwFlags = bLoop ? DSBPLAY_LOOPING : 0;
HRESULT hr = m_pDSoundBuffer->Play(0, 0, dwFlags);
if (hr == DSERR_BUFFERLOST) {
if (LoadData()) Play(bLoop); // 重新載入後重試
}
}
void CDirectWave::Stop() {
m_pDSoundBuffer->Stop();
}
// 解構子或明確釋放
if (m_pDSoundBuffer) { m_pDSoundBuffer->Release(); m_pDSoundBuffer = NULL; }
if (m_pDirectObject) { m_pDirectObject->Release(); m_pDirectObject = NULL; }
if (m_pMemory) { GlobalFree(m_pMemory); m_pMemory = NULL; }
#include <mmsystem.h>#include <dsound.h>dsound.lib、winmm.libDSERR_BUFFERLOST) 必須處理 Restore() 並重新載入資料。