Simple DSound streaming question

Started by
8 comments, last by thorpe 22 years, 2 months ago
I create my buffers 200 ms long and fill them with wave data and start playing them looping. When 100 ms has played, I fill up the just played 100 ms section with new wave data. The sounds however just loop and loop forever (with 200 ms interval). Does the buffer need to be the same size as the wave length or is my solution possible (and appropriate)? I''d really appreciate some replies, I''ve been trying to get this working for quite a while now. Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
Advertisement
Well, since you''re playing the buffer looped, I assume that''s why the sound just loops forever. My question would be is it looping the same sound over and over again? From your post, maybe your buffer size is too small. Have you tried using a 2 second buffer instead? This will give you a little more time to process the sound data. Anyhow, you need to supply a little more information on your problem. Are you using the notification events? Do you have some code to look at?
when modify the buffer, you are positive that your locks() are actually working? check for error codes. also 200ms is a decent sized buffer, but something closer to 1-2seconds is proabally better if what you are playing does not have to be low latency (which i am assuming its not. you really should the buffer more then 2ways as well. 30-100ms chunks out of 1000-2000ms buffer would probally work better (make sure your chunks are all the same size, if they are not you will have to deal with the inconsistency)
After many hours of checking my code I found some errors.

Now, long sounds play correctly, others seem to loop at a 1 s interval.

I have commented my code for you and I really hope you can find something wrong. Here goes nothing...


  // SoundBuffer.cpp: implementation of the SoundBuffer class.////////////////////////////////////////////////////////////////////////#include "SoundBuffer.h"#include "SoundEngine.h"#include "SoundSource.h"#include "Game.h"#define DESIRED_BUFFER_LENGTH 500#define DESIRED_PIECE_LENGTH 100#define DESIRED_PIECES_PER_S (1000/(DESIRED_BUFFER_LENGTH/PIECES_PER_BUFFER))//////////////////////////////////////////////////////////////////////// Construction/Destruction//////////////////////////////////////////////////////////////////////SoundBuffer::SoundBuffer(Game * theGame, CWaveSoundRead* iTheWaveSoundRead,SoundSource * iTheSource, int Loop){	TheGame=theGame;	TheSource=iTheSource;	TheWave=iTheWaveSoundRead;	TheBuffer=NULL;	LOOPING=Loop;	BufferSize      = 0;    PieceSize       = 0;	PieceProgress	= 0;    NextWriteOffset = 0;    Progress        = 0;    LastPos         = 0;    FoundEnd        = FALSE;	CreateStreamingBuffer();	nextSoundBuffer=TheGame->TheEngine->TheBuffers;	if(nextSoundBuffer)		nextSoundBuffer->prevSoundBuffer=this;	TheGame->TheEngine->TheBuffers=this;	prevSoundBuffer=NULL;}SoundBuffer::~SoundBuffer(){	TheWave=NULL;	if(nextSoundBuffer)		nextSoundBuffer->prevSoundBuffer=prevSoundBuffer;	if(prevSoundBuffer)		prevSoundBuffer->nextSoundBuffer=nextSoundBuffer;	if(TheGame->TheEngine->TheBuffers==this)		TheGame->TheEngine->TheBuffers=nextSoundBuffer;	if(TheBuffer){		TheSource->StopPlaying();		delete TheBuffer;	}}//-----------------------------------------------------------------------------// Name: IsPlaying()// Desc: Returns TRUE if playing, FALSE otherwise//-----------------------------------------------------------------------------BOOL SoundBuffer::IsPlaying(){	if(!TheBuffer)		return FALSE;	DWORD BufferStatus;	TheBuffer->GetStatus(&BufferStatus);	// We want to return a 1 or 0	if(BufferStatus&DSBSTATUS_PLAYING)		return TRUE;	return FALSE;}//-----------------------------------------------------------------------------// Name: UpdateBuffer()// Desc: This must be called called on regular intervals.//-----------------------------------------------------------------------------void SoundBuffer::UpdateBuffer(){	if(TheBuffer){		if(BufferPieces==1){			// We do no need to fill up more wave data since the buffer already			// contains all wave data.			if(!IsPlaying())				TheSource->StopPlaying();			return;		}		if(UpdateProgress()!=S_OK)			RestoreBuffers();	}}//-----------------------------------------------------------------------------// Name: UpdateProgress()// Desc: Checks if more wave data needs to be written and does that if needed.//-----------------------------------------------------------------------------HRESULT SoundBuffer::UpdateProgress(){    HRESULT hr;    DWORD   dwPlayPos;    DWORD   dwWritePos;    DWORD   dwPlayed;    if( FAILED( hr = TheBuffer->GetCurrentPosition( &dwPlayPos, &dwWritePos ) ) )        return hr;    if( dwPlayPos < LastPos ){        dwPlayed = BufferSize - LastPos + dwPlayPos;    }else        dwPlayed = dwPlayPos - LastPos;    Progress += dwPlayed;    LastPos = dwPlayPos;	// While we have played a whole piece, fill the space up	// with new wave data.	while(Progress>(PieceProgress+1-BufferPieces)*PieceSize && 		  PieceProgress<Pieces){		// Write a piece		WriteMoreWaveData();		PieceProgress++;	}	if(	Progress>=Pieces*PieceSize && !LOOPING ){		TheSource->StopPlaying();	}    return S_OK;}//-----------------------------------------------------------------------------// Name: RestoreBuffers()// Desc: Restore lost buffers and fill them up with sound if possible//-----------------------------------------------------------------------------HRESULT SoundBuffer::RestoreBuffers(){    HRESULT hr;    if( NULL == TheBuffer )        return S_OK;    DWORD dwStatus;    if( FAILED( hr = TheBuffer->GetStatus( &dwStatus ) ) )        return hr;    if( dwStatus & DSBSTATUS_BUFFERLOST )    {        // Since the app could have just been activated, then        // DirectSound may not be giving us control yet, so         // the restoring the buffer may fail.          // If it does, sleep until DirectSound gives us control.        do         {            hr = TheBuffer->Restore();            if( hr == DSERR_BUFFERLOST )                Sleep( 10 );        }        while( hr = TheBuffer->Restore() );        if( FAILED( hr = FillBuffer( ) ) )            return hr;    }    return S_OK;}//-----------------------------------------------------------------------------// Name: StopBuffer()// Desc: Stop the DirectSound buffer//-----------------------------------------------------------------------------HRESULT SoundBuffer::StopBuffer() {    if( NULL != TheBuffer ){		TheBuffer->Stop();  	}    return S_OK;}//-----------------------------------------------------------------------------// Name: WriteToBuffer()// Desc: Writes wave data to the streaming DirectSound buffer //-----------------------------------------------------------------------------HRESULT SoundBuffer::WriteToBuffer( VOID* pbBuffer, DWORD dwBufferLength ){    HRESULT hr;    UINT nActualBytesWritten;    if( !FoundEnd )    {        // Fill the DirectSound buffer with WAV data        if( FAILED( hr = TheWave->Read(dwBufferLength,                                                  (BYTE*) pbBuffer,                                                  &nActualBytesWritten ) ) )            return hr;    }    else    {        // Fill the DirectSound buffer with silence        FillMemory( pbBuffer, dwBufferLength,                     (BYTE)( TheWave->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );        nActualBytesWritten = dwBufferLength;    }    // If the number of bytes written is less than the     // amount we requested, we have a short file.    if( nActualBytesWritten < dwBufferLength )    {        if( !LOOPING )         {            FoundEnd = TRUE;            // Fill in silence for the rest of the buffer.            FillMemory( (BYTE*) pbBuffer + nActualBytesWritten,                         dwBufferLength - nActualBytesWritten,                         (BYTE)(TheWave->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );        }        else        {            // We are looping.            UINT nWritten = nActualBytesWritten;    // From previous call above.            while( nWritten < dwBufferLength )            {                  // This will keep reading in until the buffer is full. For very short files.                if( FAILED( hr = TheWave->Reset() ) )                    return hr;                if( FAILED( hr = TheWave->Read( (UINT)dwBufferLength - nWritten,                                                          (BYTE*)pbBuffer + nWritten,                                                          &nActualBytesWritten ) ) )                    return hr;                nWritten += nActualBytesWritten;            }         }     }    return S_OK;}//-----------------------------------------------------------------------------// Name: CreateStreamingBuffer()// Desc: Creates a streaming buffer, and the notification events to handle//       filling it as sound is played//-----------------------------------------------------------------------------HRESULT SoundBuffer::CreateStreamingBuffer(){    HRESULT hr; 	TheGame->Beacon(18001);    // This engine works by dividing a streaming buffer into approximately pieces    // of DESIRED_PIECE_LENGTH unless the buffer is shorter than 	// DESIRED_BUFFER_LENGTH ms. If that is the case then that sound is played 	// without looping.	// Otherwise it starts by filling up the entire buffer with wave data. Once	// it has played a piece, it fills the played piece up with new wave data which	// will be played once the buffer has looped around.	// UpdateSound needs to be called at least once every DESIRED_PIECE_LENGTH ms.	// Smallest atomic piece of the buffer that is a sample (size/sample)    DWORD nBlockAlign = (DWORD)(TheWave->m_pwfx->nBlockAlign);    INT nSamplesPerSec = TheWave->m_pwfx->nSamplesPerSec;    // Get the total size	DWORD TotalSize	= (TheWave->m_ckInRiff.cksize+nBlockAlign-1);	DWORD ms = (1000*TotalSize)/(nSamplesPerSec*nBlockAlign);	if(ms>DESIRED_BUFFER_LENGTH){		BufferPieces=DESIRED_BUFFER_LENGTH/DESIRED_PIECE_LENGTH;		// (Size per second / Piece per second)		PieceSize = (nSamplesPerSec * nBlockAlign) / BufferPieces;	}else{		// If buffer pieces == 1, we will ignore the piece process and play without looping		BufferPieces=1;		PieceSize=TotalSize;	}	// The PieceSize should be an integer multiple of nBlockAlign	PieceSize -= PieceSize % nBlockAlign;	// Calculate number of pieces to play	Pieces = (TotalSize+PieceSize-1)/PieceSize;    // The buffersize should approximately DESIRED_BUFFER_LENGTH ms of wav data	// unless the wave is shorter than that.    BufferSize  = PieceSize * BufferPieces;    FoundEnd    = FALSE;    Progress    = 0;    LastPos     = 0;    // Set up the direct sound buffer, and only request the flags needed    // since each requires some overhead and limits if the buffer can     // be hardware accelerated    DSBUFFERDESC dsbd;    ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );    dsbd.dwSize        = sizeof(DSBUFFERDESC);    dsbd.dwFlags       = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;    dsbd.dwBufferBytes = BufferSize;    dsbd.lpwfxFormat   = TheWave->m_pwfx;    // Create a DirectSound buffer    if( FAILED( hr = TheGame->TheEngine->lpDS->CreateSoundBuffer( &dsbd, &TheBuffer, NULL ) ) )        return hr;	TheGame->Beacon(18002);    return S_OK;}//-----------------------------------------------------------------------------// Name: PlayBuffer()// Desc: Play the DirectSound buffer//-----------------------------------------------------------------------------HRESULT SoundBuffer::PlayBuffer(){	TheGame->Beacon(17001);    HRESULT hr;    VOID*   pbBuffer = NULL;    if( NULL == TheBuffer )        return E_FAIL;    // Restore the buffers if they are lost    if( FAILED( hr = RestoreBuffers() ) )        return hr;	TheGame->Beacon(17002);    // Fill the entire buffer with wave data    if( FAILED( hr = FillBuffer( ) ) )        return hr;	TheGame->Beacon(17003);    // Play with the LOOPING flag since the streaming buffer    // wraps around before the entire WAV is played unless	// there is only one buffer piece	DWORD Flags = DSBPLAY_LOOPING;	if(BufferPieces==1)		Flags=0;    if( FAILED( hr = TheBuffer->Play( 0, 0, Flags ) ) )        return hr;	TheGame->Beacon(17004);    return S_OK;}//-----------------------------------------------------------------------------// Name: FillBuffer()// Desc: Fills the DirectSound buffer completely up //-----------------------------------------------------------------------------HRESULT SoundBuffer::FillBuffer(){    HRESULT hr;    VOID*   pbBuffer = NULL;    DWORD   dwBufferLength;    if( NULL == TheBuffer )        return E_FAIL;    FoundEnd = FALSE;    NextWriteOffset = 0; // Start writing from zero as soon as the first piece has played    Progress = 0;	PieceProgress=BufferPieces;    LastPos  = 0;    // Reset the wav file to the beginning    TheWave->Reset();    TheBuffer->SetCurrentPosition( 0 );    // Lock the buffer down,     if( FAILED( hr = TheBuffer->Lock( 0, BufferSize,                                         &pbBuffer, &dwBufferLength,                                         NULL, NULL, 0L ) ) )        return hr;    // Fill the buffer with wav data     if( FAILED( hr = WriteToBuffer( pbBuffer, dwBufferLength ) ) )        return hr;    // Now unlock the buffer    TheBuffer->Unlock( pbBuffer, dwBufferLength, NULL, 0 );    return S_OK;}//-----------------------------------------------------------------------------// Name: WriteMoreWaveData()// Desc: Writes another piece of data to the buffer//-----------------------------------------------------------------------------HRESULT SoundBuffer::WriteMoreWaveData() {    HRESULT hr;    VOID* pbBuffer  = NULL;    DWORD dwBufferLength;    // Lock the buffer down    if( FAILED( hr = TheBuffer->Lock( NextWriteOffset, PieceSize,                                         &pbBuffer, &dwBufferLength, NULL, NULL, 0L ) ) )        return hr;    // Fill the buffer with wav data     if( FAILED( hr = WriteToBuffer( pbBuffer, dwBufferLength ) ) )        return hr;    // Now unlock the buffer    TheBuffer->Unlock( pbBuffer, dwBufferLength, NULL, 0 );    pbBuffer = NULL;    NextWriteOffset += PieceSize;     NextWriteOffset %= BufferSize; // Circular buffer    return S_OK;}void SoundBuffer::SetVolume(long Volume){	TheBuffer->SetVolume(Volume);}void SoundBuffer::SetPan(long Pan){	TheBuffer->SetPan(Pan);}  


Btw, am I supposed to use m_ckInRiff instead of m_ckIn? When the same sound plays twice, m_ckIn.ckSize is 0 for some reason so I used m_cKinRiff.

Thanks for your help, I''ve spent about a month trying to get my sound engine working and I''m so close now.

Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
Nevermind! I got it working! The error was in another object which adjusted pan and volume and restarted the sound as soon it quit playing.... thanks for your help!

Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
No, I didn''t get it working =(

When I call Stop(IDirectSoundBuffer) the buffer doesn''t actually stop for at least one certain sound (which is 2,4 s), It keeps looping in 1s intervals (at least I think) after it is supposed to have been stop. The stop() method however returns DS_OK and still the sound keeps on looping (I''m not sure whether it''s the beginning or the end of the sound since it sounds so alike).

Any clues???

Also I''mhaving problems playing several sounds at a time but I think that''s a different problem

Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
Well, I can''t say for sure what is wrong. You have a lot of stuff going on. I did notice that you''re polling the buffer to see if has finished playing one of the chunk, then filling that chunk with data. This method is fine, but can you be certain that the fill chunk routine is being called often enough? To determine the interval its being called, use timeGetTime() to get the CPU time (in milliseconds) then calculate the delta between calls. Sometimes, other parts of the code may be using too much time and not allowing this part of the code to fill the buffers with new data hence you get repetition. A better way to fill the buffer is to use the notification events in IDirectSoundBuffer8. Windows will interrupt your code and allow you to fill more data when the play cursor has reach the specified location. This method ensures that the fill data routine is being called. If you insist on using the polling method, you should make your buffers larger to give your code time to come back around and do the polling. If you have graphics and other things going on, 100ms may not be enough.
Typically I have a buffer of about 500 ms and about that''s divided into 10 pieces. I start by filling in a pieces, and once the first piece has played, I fill it up with new data so I always have 450 ms to play with.

Could there be any problem with the fact that I use the same CWaveSoundRead for several buffers? Should I perhaps create a new one for each buffer?

You don''t have to bother reading the code too much, the error might as well be in another object, I just want to be sure that I got the principles right.

Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
After looking a bit on CWaveSoundREad, I realize that I should create several instances of them. However won''t that cause the same file being read into memory several times or does it stream data from the file?

Johan Torp - http://www.destruction.nu
Johan Torp - http://www.destruction.nu
Each DirectSound buffer you create is basically a separate entity and they will all play at the same time. If this is your goal, then its ok. If you just want to play one audio file, then only one streaming buffer is needed. For multiple audio files, multiple buffers will be needed. Did you check to see if your refill function was doing its job often enough so that the play cursor doesn''t overtake the write cursor.

This topic is closed to new replies.

Advertisement