Introduction
This article with its code shows how to play audio stream data with DirectSound. It gives a more flexible method to control the stream data. The demo shows how to play, pause, stop, seek a small or a big WAV file.
Background
Before you read the code, you should know something about DirectSound. You can find the related material in:
MSDN\Graphics and Multimedia\DirectX\SDK Documentation\DiretX8.1(C++)\DirectX Audio
Using the code
The CMyDirectSound
has the following public method:
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();
First, you should give the information of the audio data you want to play, with "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);
Second, set the callback function which is to get the audio stream data:
m_pMyDS->SetCallback(GetSamples, this);
Certainly, the body of the callback function should be written by yourself. Then you can play, pause, stop the audio data.
The "GetSamplesPlayed
" method will give the number of audio samples played after you begin play. You can use this method to give the playing position.
Points of Interest
I think there're two key points in this class.
1. How to control the DirectSound buffer circle?
I create a second DirectSound buffer with two seconds duration:
dsbd.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY;
dsbd.dwBufferBytes = 2*m_WFE.nAvgBytesPerSec;
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;
}
And I set two DirectSound notify at 0.5 second and 1.5 second:
(1st Notify) (2nd Notify)
0 0.5Second 1Second 1.5Second 2Second
| | | | |
---------------------------------------------------------------------------
| | |
|--------------------------------------------------------------------------|
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;
}
When you call the Play
method, a timer will be triggered. The "TimerProcess
" function will be called every 300 milliseconds.
m_lpDSB->Play(0, 0, DSBPLAY_LOOPING);
m_timerID = timeSetEvent(300, 100, TimerProcess,
(DWORD)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
In the "TimerProcess
" function, the next second audio stream data will be gotten when the current play cursor arrives the 1st or 2nd notify point.
void CALLBACK TimerProcess(UINT uTimerID, UINT uMsg,
DWORD dwUser, DWORD dw1, DWORD dw2)
{
CMyDirectSound *pDDS = (CMyDirectSound *)dwUser;
pDDS->TimerCallback();
}
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++;
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++;
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;
}
m_lpGETAUDIOSAMPLES(m_lpAudioBuf, m_WFE.nSamplesPerSec, dwRetSamples, m_lpData);
dwRetBytes = dwRetSamples*m_WFE.nBlockAlign;
if (dwRetSamples < m_WFE.nSamplesPerSec) {
DWORD dwRetBytes = dwRetSamples*m_WFE.nBlockAlign;
memset(m_lpAudioBuf+dwRetBytes, 0, m_WFE.nAvgBytesPerSec - dwRetBytes);
}
if (NULL == lpvAudio2) {
memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
}
else {
memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2);
}
m_lpDSB->Unlock(lpvAudio1, dwBytesAudio1, lpvAudio2, dwBytesAudio2);
}
2. How the following callback function works?
m_lpGETAUDIOSAMPLES(m_lpAudioBuf, m_WFE.nSamplesPerSec, dwRetSamples, m_lpData);
Do you remember what you have done when you set the callback function?
void CDYDirectSound::SetCallback(LPGETAUDIOSAMPLES_PROGRESS Function_Callback,
LPVOID lpData)
{
m_lpGETAUDIOSAMPLES = Function_Callback;
m_lpData = lpData;
}
Yes, you transfer the GETAUDIOSAMPLES_PROGRESS
function's pointer to m_lpGETAUDIOSAMPLES
.
m_pMyDS->SetCallback(GetSamples, this);
And the GetSamples
is defined as:
HRESULT CALLBACK GetSamples(LPBYTE lpDesBuf,
const DWORD dwRequiredSamples,
DWORD &dwRetSamples,
LPVOID lpData)
{
CDirectSoundTestDlg *pDlg = (CDirectSoundTestDlg *)lpData;
pDlg->GetAudioSamples(lpDesBuf, dwRequiredSamples, dwRetSamples);
return 0;
}
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;
}
You can write your own "GetAudioSamples
" to get the audio stream data.