Click here to Skip to main content
15,888,454 members
Articles / Desktop Programming / MFC
Article

Using DirectSound to Play Audio Stream Data

Rate me:
Please Sign up or sign in to vote.
3.12/5 (17 votes)
9 Mar 20052 min read 302.7K   10.3K   66   51
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


Written By
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThank you Pin
skupena19-Dec-18 11:17
skupena19-Dec-18 11:17 
GeneralUsing DirectSound to Play Audio Stream Data Pin
khosrow parizi31-Aug-12 2:52
khosrow parizi31-Aug-12 2:52 
QuestionLarger buffer Pin
Rhanz29-May-12 15:17
Rhanz29-May-12 15:17 
Buga little bug during play/pause switch Pin
tonyjiang8821519-Dec-11 2:17
tonyjiang8821519-Dec-11 2:17 
Questiona small problem with slider-position Pin
dumbledorehoho25-Jan-10 18:14
dumbledorehoho25-Jan-10 18:14 
Generalit is no good when work with windows media palyer 10 Pin
nofengyu23-Jan-10 20:22
nofengyu23-Jan-10 20:22 
Generalthanks Pin
gj_code13-Aug-09 4:55
gj_code13-Aug-09 4:55 
Generala few question about play wave file with DirectSound and display spectrum. Pin
jackyxinli4-Sep-08 15:02
jackyxinli4-Sep-08 15:02 
QuestionAbout "GetAudioSamples" Pin
HuangAnbang10-Sep-07 18:16
HuangAnbang10-Sep-07 18:16 
GeneralUnresolved Error for Release Build Pin
Seenu Reddi21-Feb-07 15:03
Seenu Reddi21-Feb-07 15:03 
QuestionThe Primary buffer is redundant???? Pin
cnxhwy23-Nov-06 16:11
cnxhwy23-Nov-06 16:11 
QuestionHow do handle block mode audio ? Pin
Musong22-Aug-06 16:54
Musong22-Aug-06 16:54 
QuestionCalculation error in GetSamplesPlayed() ? Pin
Azonacun8-May-06 8:38
Azonacun8-May-06 8:38 
AnswerRe: Calculation error in GetSamplesPlayed() ? Pin
8ki6-Jul-06 11:51
8ki6-Jul-06 11:51 
Question'dmusici.h' missing! Pin
WilliamZhou24-Mar-06 5:29
WilliamZhou24-Mar-06 5:29 
AnswerRe: 'dmusici.h' missing! Pin
Sreekanth Muralidharan27-Apr-06 19:49
Sreekanth Muralidharan27-Apr-06 19:49 
GeneralMeasuring the sound level Pin
msorc24-Feb-06 6:08
msorc24-Feb-06 6:08 
QuestionUsing a Stream in stead of of File ? Pin
Yossef30-Nov-05 23:07
Yossef30-Nov-05 23:07 
GeneralBug report and my fixes Pin
8ki25-Oct-05 1:54
8ki25-Oct-05 1:54 
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 Pin
StevenDorning6-Oct-05 22:19
StevenDorning6-Oct-05 22:19 
GeneralSomething wrong when load different BitsPerSample WAV file! Pin
ikkiu200130-Jun-05 23:51
ikkiu200130-Jun-05 23:51 
Generalabout mfc42u.lib Pin
caoxiang31-May-05 18:10
caoxiang31-May-05 18:10 
GeneralRe: about mfc42u.lib Pin
YesterdayOncemore13-Feb-06 20:13
YesterdayOncemore13-Feb-06 20:13 
GeneralRe: about mfc42u.lib Pin
Duncan1234529-Mar-06 21:59
Duncan1234529-Mar-06 21:59 
GeneralAudio Format.. Pin
mpallavi28-Apr-05 18:08
mpallavi28-Apr-05 18:08 

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

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