Click here to Skip to main content
Licence 
First Posted 27 Sep 2004
Views 145,452
Bookmarked 59 times

Using DirectSound to Play Audio Stream Data

By | 9 Mar 2005 | Article
An article on how to play audio stream data with DirectSound.

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:

//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;
}

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
|                  |               |                     |                 |
---------------------------------------------------------------------------
|                                  |                                       |
|--------------------------------------------------------------------------|
//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;
}

When you call the Play method, a timer will be triggered. The "TimerProcess" function will be called every 300 milliseconds.

//Beging Play
m_lpDSB->Play(0, 0, DSBPLAY_LOOPING);

//timeSetEvent
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();  
}
//<\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. How the following callback function works?

//Get Audio Buffer
  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;
}
//<\SetCallback>

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;
}
//<\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>

You can write your own "GetAudioSamples" to get the audio stream data.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Cai Tao



China China

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionLarger buffer Pinmemberrahnz8hrs 33mins ago 
Buga little bug during play/pause switch Pinmembertonyjiang882152:17 19 Dec '11  
Questiona small problem with slider-position Pinmemberdumbledorehoho18:14 25 Jan '10  
Generalit is no good when work with windows media palyer 10 Pinmembernofengyu20:22 23 Jan '10  
Generalthanks Pinmembergj_code4:55 13 Aug '09  
Generala few question about play wave file with DirectSound and display spectrum. Pinmemberjacky_zz15:02 4 Sep '08  
QuestionAbout "GetAudioSamples" PinmemberHuangAnbang18:16 10 Sep '07  
GeneralUnresolved Error for Release Build PinmemberSeenu Reddi15:03 21 Feb '07  
QuestionThe Primary buffer is redundant???? Pinmembercnxhwy16:11 23 Nov '06  
QuestionHow do handle block mode audio ? PinmemberMusong16:54 22 Aug '06  
QuestionCalculation error in GetSamplesPlayed() ? PinmemberAzonacun8:38 8 May '06  
AnswerRe: Calculation error in GetSamplesPlayed() ? PinmemberKedzo11:51 6 Jul '06  
Question'dmusici.h' missing! PinmemberWilliamZhou5:29 24 Mar '06  
AnswerRe: 'dmusici.h' missing! PinmemberSreekanth Muralidharan19:49 27 Apr '06  
GeneralMeasuring the sound level Pinmembermsorc6:08 24 Feb '06  
QuestionUsing a Stream in stead of of File ? PinmemberYossef23:07 30 Nov '05  
GeneralBug report and my fixes PinsussIKedzo1:54 25 Oct '05  
Hi Cai Tao!
 
First I want to thank you on your article.It was very helpfull since this is my first time
playing with sounds.
While looking thru code and playing some waves I found few bugs.
If you think I got something wrong please correct me.
Also, anyone with advice is welcomed.
 

Noitce: all testing I did was with very short wave files, max 5 sec(like the ones you can find in WINDOWS/MEDIA directory Smile | :) )
 
Bugs:
 
1) When pausing a wave file that is currently playing and then moving scroll to some other position,will result in playing wave file incorrectly.
 
2) When opening mono wave file and after that opening stereo wave file, file will be played incorretly
Notice: you may not notice this if you first play stereo file and then mono
 
3) If there is an error when opening file, you want be able to play any file after that.
 
4) Sometimes(depending on pause position) when you play file and pause it and then play it again you will hear strange sound(bip)
Notice: This might be hard to discover(read 4th fix for more information)
 
5) Hitting play botton mulitiple times while file is currently playing causes scroll to go faster and can result with strange sounds
 

My fixes and comments:
 
1)
This happens because you dont call Stop function and you dont kill timers
 
I fixed this by changing OnHScroll function to look like this:
 
void CDirectSoundTestDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
m_iBeginPlayPos = m_ctrlPlaySlider.GetPos();
if (NULL != m_pMyDS) {
 
m_pMyDS->Stop(); /*ADDED CODE
timeKillEvent(m_timerID);
m_timerID = 0; */
m_pWavFile->SetBeginSamples(m_iBeginPlayPos);
}
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
 
2)
I think this has something to do with COM objects(dont know much about them).Since you sometimes want to create new secondary sound buffer(with different waveformat)I think you should just release IDirectSound pointer to COM before you want to create a new secondary buffer.In your code you took the old secondary sound buffer reference and called CreateSoundBuffer passing the new waveformat to function.You didnt get any error and it seems that buffer is created right(I checked it with GetFormat) but for some "COM"(or whatever) reason it doesnt play secondary buffer correctly.It might have to do something with creating COM with DirectSoundCreate metod.Remember,this is only my teory!
 

I fixed this by releasing the m_lpDS (pointer to directsound COM object) befor reusing it.
Begining of your SetFormat should look like this:
 
void CMyDirectSound::SetFormat(WAVEFORMATEX WFE)
{
m_WFE = WFE;

if(m_lpDS!=NULL) /*ADDED CODE
{
m_lpDS->Release();
} */
 
...
 
3)
Well this one was easy.You just frogot to clear lastError so program thinks error is still there.
 
I fixed this by adding this incredibly complicated function in CMyDirectSound.cpp
 
void CMyDirectSound::SetLastError (CString setlasterror)
{
m_strLastError = setlasterror;
}
 
Of course dont froget to add declaration in CMyDirectSound.h Smile | :)
 
void CMyDirectSound::SetLastError (CString setlasterror);
 
then I called CMyDirectSound::SetLastError near the end of CDirectSoundTestDlg::OnOpen function
 
//Initilize CMyDirectSound
if (NULL == m_pMyDS) {
m_pMyDS = new CMyDirectSound;
}

m_pMyDS->SetLastError (""); //clear last error //ADDED FUNCTION CALL!
m_pMyDS->SetFormat(formatWav);
m_pMyDS->SetCallback(GetSamples, this);

}//End of OnOpen
 

 
4)
This thing took me some time to figure out but I think I fixed it.
I noticed this bug when I was playing Windows XP ShutDown wave from upper mentioned directory.When I paused the play somewhere near middle of the file and then when I let it play I would hear strange bip.Its not easy to discover this,you will probably need to play and pause file(Windows XP ShutDown) 10 times(or more),but its there.
I think it has to do something with timers, at least that is the way I fixed it. MY teory is this,when you play the file and then pause it you killed timers,so, if you did this just when notification point has been reached(or little after,
but must be before timer callback ocured) and your timer didnt done callback (because you paused and killed timer) to fill the new data to buffer,when you hit play it takes some time(in your program minimum is 300 miliseconds)for timer to make
callback again and call TimerCallback(which will refill buffer with new data) and that time might not be enough.Again,just my teory!
 
My fix:
I just lowerd the timer period to 200 miliseconds and it seems to work
 
You should just change this in CMyDirectSound::Play :
 

m_timerID = timeSetEvent(200, 100, TimerProcess, (DWORD)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION );
 

5)
Hiting play multiple times can cause strange things(your scroll goes faster and your wave file might be played incorrectly).
I think reason why this happens is because you reset timers mulitiple times or whatever.My advise is to control thiswith flags.
 
My fix:I just added bool playflag variable as private member of CDirectSoundTestDlg class.I initialized it to TRUE in CDirectSoundTestDlg constructor and than added it in following functions:
 
void CDirectSoundTestDlg::OnPlay()
{
if(playflag) //ADDED FLAG!
{
if (NULL != m_pMyDS) {
 
if (0 == m_ctrlPlaySlider.GetPos()) {

m_pWavFile->ResetFile();
}
 
playflag = false; //ADDED FLAG!

m_pMyDS->Play();
m_timerID = timeSetEvent(500, 100, TimerProc, (DWORD)this, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
}
}
}
//
 
void CDirectSoundTestDlg::OnPause()
{
if (NULL != m_pMyDS) {

m_pMyDS->Pause();

playflag = true; //ADDED FLAG!
timeKillEvent(m_timerID);
m_timerID = 0;


}
}
//
 
void CDirectSoundTestDlg::OnStop()
{
if (NULL != m_pMyDS) {
 
m_pMyDS->Stop();
timeKillEvent(m_timerID);
m_timerID = 0;
}
 
playflag = true; //ADDED FLAG!
m_ctrlPlaySlider.SetPos(0);
m_iBeginPlayPos = 0;
}
//
 

 

That's it with bugs.Ill report if I find anything new.
 
I just want to add few more comments and ask you few quetions.
I read somewhere that you should not relay on DirectSoundCreate metod to create the COM object and releasing them for you when your aplication ends.You should do these things by yourself.I fallowed instructions from MSDN on how to do it with CoInitializeEx and so on, but it throws some exception so I didnt look at it anymore.Did you try it maybe?
 
Also I wonder why you went with this "notify points" approach. For me, it seems better not to depend on notification events(I say this because bug number 4),but then again if you do it rigth way,its not important.I read in some page where they use timer and set it to report every 1/4 of secondary sound buffer time length(if buffer is 2sec,
it will report every 500 milisec.) and then they just check how much free space is there in secondary sound buffer and they fill free space. So,this way they dont depend on DirectSound notification events to tell them when to fill new data because they check secondary sound buffer by themselves.Do you have some experience with this metod?
 
p.s.What is m_iDB variable in CMyDirectSound class for? You frogot to erase it?
 
Best regards,
 
Ivan
GeneralConflicts with other applications PinmemberStevenDorning22:19 6 Oct '05  
GeneralSomething wrong when load different BitsPerSample WAV file! Pinmemberikkiu200123:51 30 Jun '05  
Generalabout mfc42u.lib Pinmembercaoxiang18:10 31 May '05  
GeneralRe: about mfc42u.lib PinmemberYesterdayOncemore20:13 13 Feb '06  
GeneralRe: about mfc42u.lib PinmemberDuncan1234521:59 29 Mar '06  
GeneralAudio Format.. Pinmembermpallavi18:08 28 Apr '05  
GeneralRe: Audio Format.. PinmemberCai Tao21:27 28 Apr '05  
GeneralRe: Audio Format.. Pinmembermpallavi1:45 29 Apr '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120529.1 | Last Updated 10 Mar 2005
Article Copyright 2004 by Cai Tao
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid