使用 DirectSound 播放音频流数据






3.12/5 (17投票s)
2004年9月27日
2分钟阅读

311762

10356
一篇关于如何使用 DirectSound 播放音频流数据的文章。
引言
本文及其代码展示了如何使用 DirectSound 播放音频流数据。它提供了一种更灵活的方法来控制流数据。演示展示了如何播放、暂停、停止、以及寻找小或大的 WAV 文件。
背景
在阅读代码之前,您应该了解一些关于 DirectSound 的知识。您可以在以下位置找到相关资料:
MSDN\Graphics and Multimedia\DirectX\SDK Documentation\DiretX8.1(C++)\DirectX Audio
使用代码
CMyDirectSound
具有以下公共方法:
HRESULT SetFormat(const WAVEFORMATEX WFE); HRESULT SetCallback(LPGETAUDIOSAMPLES_PROGRESS Function_Callback, LPVOID lpData); typedef HRESULT (WINAPI *LPGETAUDIOSAMPLES_PROGRESS)(int iAudioChannel, LPBYTE lpDesBuf, const DWORD dwRequiredSamples, DWORD &dwRetSamples, LPVOID lpData); void Play(); void Pause(); void Stop(); void Release(); DWORD GetSamplesPlayed();
首先,您应该使用“SetFormat
”提供您想要播放的音频数据的信息。
WAVEFORMATEX formatWav; m_pWavFile->Open((LPTSTR)(LPCTSTR)m_strFileName, &formatWav, WAVEFILE_READ); formatWav = *m_pWavFile->GetFormat(); if (NULL == m_pDYDS) { m_pDYDS = new CDYDirectSound; } m_pDYDS->SetFormat(formatWav);
其次,设置用于获取音频流数据的回调函数。
m_pMyDS->SetCallback(GetSamples, this);
当然,回调函数的具体实现需要您自行编写。然后您可以播放、暂停、停止音频数据。
“GetSamplesPlayed
”方法将在您开始播放后提供播放的音频样本数量。您可以使用此方法来提供播放位置。
关注点
我认为这个类中有两个关键点:
1. 如何控制 DirectSound 缓冲区循环?
我创建了一个持续时间为两秒的第二个 DirectSound 缓冲区
//Create Second Sound Buffer dsbd.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY; dsbd.dwBufferBytes = 2*m_WFE.nAvgBytesPerSec; //2-Second Buffer dsbd.lpwfxFormat = &m_WFE; if ( FAILED(m_lpDS->CreateSoundBuffer(&dsbd, &m_lpDSB, NULL)) ) { OutputDebugString(_T("Create Second Sound Buffer Failed!")); m_strLastError = _T("MyDirectSound SetFormat Failed!"); return; }
并在 0.5 秒和 1.5 秒处设置了两个 DirectSound 通知
(1st Notify) (2nd Notify)
0 0.5Second 1Second 1.5Second 2Second
| | | | |
---------------------------------------------------------------------------
| | |
|--------------------------------------------------------------------------|
//Set Direct Sound Buffer Notify Position DSBPOSITIONNOTIFY pPosNotify[2]; pPosNotify[0].dwOffset = m_WFE.nAvgBytesPerSec/2 - 1; pPosNotify[1].dwOffset = 3*m_WFE.nAvgBytesPerSec/2 - 1; pPosNotify[0].hEventNotify = m_pHEvent[0]; pPosNotify[1].hEventNotify = m_pHEvent[1]; if ( FAILED(lpDSBNotify->SetNotificationPositions(2, pPosNotify)) ) { OutputDebugString(_T("Set NotificationPosition Failed!")); m_strLastError = _T("MyDirectSound SetFormat Failed!"); return; }
当您调用 Play
方法时,将触发一个计时器。 “TimerProcess
”函数将每 300 毫秒调用一次。
//Beging Play m_lpDSB->Play(0, 0, DSBPLAY_LOOPING); //timeSetEvent m_timerID = timeSetEvent(300, 100, TimerProcess, (DWORD)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
在“TimerProcess
”函数中,当当前播放光标到达第 1 个或第 2 个通知点时,将获取下一秒的音频流数据。
void CALLBACK TimerProcess(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) { CMyDirectSound *pDDS = (CMyDirectSound *)dwUser; pDDS->TimerCallback(); } //<\TimerProcess> void CMyDirectSound::TimerCallback() { LPVOID lpvAudio1 = NULL, lpvAudio2 = NULL; DWORD dwBytesAudio1 = 0, dwBytesAudio2 = 0; DWORD dwRetSamples = 0, dwRetBytes = 0; HRESULT hr = WaitForMultipleObjects(2, m_pHEvent, FALSE, 0); if(WAIT_OBJECT_0 == hr) { m_dwCircles1++; //Lock DirectSoundBuffer Second Part HRESULT hr = m_lpDSB->Lock(m_WFE.nAvgBytesPerSec, m_WFE.nAvgBytesPerSec, &lpvAudio1, &dwBytesAudio1,&lpvAudio2, &dwBytesAudio2, 0); if ( FAILED(hr) ) { m_strLastError = _T("Lock DirectSoundBuffer Failed!"); OutputDebugString(m_strLastError); return; } } else if (WAIT_OBJECT_0 + 1 == hr) { m_dwCircles2++; //Lock DirectSoundBuffer First Part HRESULT hr = m_lpDSB->Lock(0, m_WFE.nAvgBytesPerSec, &lpvAudio1, &dwBytesAudio1, &lpvAudio2, &dwBytesAudio2, 0); if ( FAILED(hr) ) { m_strLastError = _T("Lock DirectSoundBuffer Failed!"); OutputDebugString(m_strLastError); return; } } else { return; } //Get 1 Second Audio Buffer m_lpGETAUDIOSAMPLES(m_lpAudioBuf, m_WFE.nSamplesPerSec, dwRetSamples, m_lpData); dwRetBytes = dwRetSamples*m_WFE.nBlockAlign; //If near the end of the audio data if (dwRetSamples < m_WFE.nSamplesPerSec) { DWORD dwRetBytes = dwRetSamples*m_WFE.nBlockAlign; memset(m_lpAudioBuf+dwRetBytes, 0, m_WFE.nAvgBytesPerSec - dwRetBytes); } //Copy AudioBuffer to DirectSoundBuffer if (NULL == lpvAudio2) { memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1); } else { memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1); memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2); } //Unlock DirectSoundBuffer m_lpDSB->Unlock(lpvAudio1, dwBytesAudio1, lpvAudio2, dwBytesAudio2); } //<\TimerCallback>
2. 以下回调函数是如何工作的?
//Get Audio Buffer
m_lpGETAUDIOSAMPLES(m_lpAudioBuf, m_WFE.nSamplesPerSec, dwRetSamples, m_lpData);
您还记得在设置回调函数时做了什么吗?
void CDYDirectSound::SetCallback(LPGETAUDIOSAMPLES_PROGRESS Function_Callback, LPVOID lpData) { m_lpGETAUDIOSAMPLES = Function_Callback; m_lpData = lpData; } //<\SetCallback>
是的,您将 GETAUDIOSAMPLES_PROGRESS
函数的指针传递给 m_lpGETAUDIOSAMPLES
。
m_pMyDS->SetCallback(GetSamples, this);
并且 GetSamples
定义为:
HRESULT CALLBACK GetSamples(LPBYTE lpDesBuf, const DWORD dwRequiredSamples, DWORD &dwRetSamples, LPVOID lpData) { CDirectSoundTestDlg *pDlg = (CDirectSoundTestDlg *)lpData; pDlg->GetAudioSamples(lpDesBuf, dwRequiredSamples, dwRetSamples); return 0; } //<\GetSamples> HRESULT CDirectSoundTestDlg::GetAudioSamples(LPBYTE lpDesBuf, const DWORD dwRequiredSamples, DWORD &dwRetSamples) { DWORD dwRequiredBytes = 0, dwRetBytes = 0; WAVEFORMATEX *pWFE = m_pWavFile->GetFormat(); dwRequiredBytes = dwRequiredSamples*pWFE->nBlockAlign; m_pWavFile->Read(lpDesBuf, dwRequiredBytes, &dwRetBytes); dwRetSamples = dwRetBytes/pWFE->nBlockAlign; return dwRetBytes; } //<\GetAudioSamples>
您可以编写自己的“GetAudioSamples
”来获取音频流数据。