Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition
ptoinson
So I went round and round staring both at Chris's code and Microsoft's example for quite a while and once I finally was able to shift my thinking, I decided that, based on the real event notification mechanism, like Tim, I think that both Chris and MS are taking the wrong approach. I think the best way to stream audio in DirectSound is to setup notifications at some regular interval, as is done in the examples, but on the notification fill the area between the last play cursor and the current play cursor and then update the last play cursot to the current play cursor. This should account for any extraneous notifications as long as the play position never moves backward. Of course there is a little extra code to manage the circular buffers. Below is my first crack at the code. I will need to test it more, but it works so far and it works better that what I had before. Please note that you will have to make a few adjustments to fit this in with Chris's or MS's code as I use a different Audio Source that handles more than just waves. You also no longer need the m_dwNextWriteOffset variable as the next offset is the last play cursor position. With those two fixes this should drop right in which I'm not going to do as I've spent more time than I should have trying to figure this out.
Thank you Microsoft.
//-----------------------------------------------------------------------------
// Name: CStreamingBuffer::HandleWaveStreamNotification()
// Desc: Handle the notification that tells us to put more wav data in the
// circular buffer
//-----------------------------------------------------------------------------
HRESULT CStreamingBuffer::HandleWaveStreamNotification( BOOL bLoopedPlay )
{
HRESULT hr;
DWORD dwCurrentPlayPos;
DWORD dwCurrentWritePos;
DWORD dwPlayDelta;
sf_count_t dwBytesWrittenToBuffer;
VOID* pDSLockedBuffer = NULL;
VOID* pDSLockedBuffer2 = NULL;
VOID* pDSCurrentBuffer = NULL;
DWORD dwDSLockedBufferSize;
DWORD dwDSLockedBufferSize2;
DWORD dwDSCurrentBufferSize;
DWORD dwBytesRead;
if( m_apDSBuffer == NULL || m_pAudio == NULL )
return CO_E_NOTINITIALIZED;
// Restore the buffer if it was lost
BOOL bRestored;
if( FAILED( hr = RestoreBuffer( m_apDSBuffer[0], &bRestored ) ) )
return DXUT_ERR( L"RestoreBuffer", hr );
if( bRestored )
{
printf("Buffer Restored!");
// The buffer was restored, so we need to fill it with new data
if( FAILED( hr = FillBufferWithSound( m_apDSBuffer[0], FALSE ) ) )
return DXUT_ERR( L"FillBufferWithSound", hr );
return S_OK;
}
if( FAILED( hr = m_apDSBuffer[0]->GetCurrentPosition( &dwCurrentPlayPos, &dwCurrentWritePos ) ) )
return DXUT_ERR( L"GetCurrentPosition", hr );
// Check to see if the position counter looped
if( dwCurrentPlayPos < m_dwLastPlayPos )
dwPlayDelta = ( m_dwDSBufferSize - m_dwLastPlayPos ) + dwCurrentPlayPos;
else
dwPlayDelta = dwCurrentPlayPos - m_dwLastPlayPos;
// Perform some error checking to make sure we are not writing to the
// area between the current play cursor and the write cursor. IF this happens
// it is likely that we need to increase our buffer size.
if( dwCurrentPlayPos < m_dwLastPlayPos )
{
if(dwCurrentWritePos >= m_dwLastPlayPos)
{
return DXUT_ERR( L"BufferError", hr );
}
}
else if(dwCurrentWritePos < dwCurrentPlayPos && dwCurrentWritePos >= m_dwLastPlayPos)
{
return DXUT_ERR( L"BufferError", hr );
}
// Lock the DirectSound buffer
if( FAILED( hr = m_apDSBuffer[0]->Lock( m_dwLastPlayPos, dwPlayDelta,
&pDSLockedBuffer, &dwDSLockedBufferSize,
&pDSLockedBuffer2, &dwDSLockedBufferSize2, 0L ) ) )
return DXUT_ERR( L"Lock", hr );
pDSCurrentBuffer = pDSLockedBuffer;
dwDSCurrentBufferSize = dwDSLockedBufferSize;
// Use a loop so as to not duplicate the code for the second locked buffer.
for(int nBuffer = 0; nBuffer < 2 && pDSCurrentBuffer != NULL; nBuffer++)
{
if( m_bFillNextNotificationWithSilence )
{
// Fill the DirectSound buffer with silence
FillMemory( pDSCurrentBuffer,
dwDSCurrentBufferSize,
m_pAudio->GetSilentByte());
dwBytesWrittenToBuffer = dwDSCurrentBufferSize;
}
else
{
// Fill the DirectSound buffer with wav data
if((dwBytesWrittenToBuffer = m_pAudio->readRaw(pDSCurrentBuffer, dwDSCurrentBufferSize)) < 0)
return DXUT_ERR( L"Read", hr );
// If the number of bytes written is less than the
// amount we requested, we have a short file.
if( dwBytesWrittenToBuffer < dwDSCurrentBufferSize )
{
if( !bLoopedPlay )
{
// Fill in silence for the rest of the buffer.
FillMemory( (BYTE*) pDSCurrentBuffer + dwBytesWrittenToBuffer,
dwDSCurrentBufferSize - dwBytesWrittenToBuffer,
m_pAudio->GetSilentByte());
dwBytesWrittenToBuffer = dwDSCurrentBufferSize;
// Any future notifications should just fill the buffer with silence
m_bFillNextNotificationWithSilence = TRUE;
}
else
{
// We are looping, so reset the file and fill the buffer with wav data
while( dwBytesWrittenToBuffer < dwDSCurrentBufferSize )
{
// This will keep reading in until the buffer is full (for very short files).
if( m_pAudio->seek(0, SEEK_SET) == -1)
return DXUT_ERR( L"ResetFile", hr );
if((dwBytesRead = m_pAudio->readRaw(
(BYTE*)pDSCurrentBuffer + dwBytesWrittenToBuffer,
dwDSCurrentBufferSize - dwBytesWrittenToBuffer)) < 0)
return DXUT_ERR( L"Read", hr );
dwBytesWrittenToBuffer += dwBytesRead;
}
}
}
}
pDSCurrentBuffer = pDSLockedBuffer2;
dwDSCurrentBufferSize = dwDSLockedBufferSize2;
}
// Unlock the DirectSound buffer
m_apDSBuffer[0]->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, pDSLockedBuffer2, dwDSLockedBufferSize2 );
m_dwPlayProgress += dwPlayDelta;
m_dwLastPlayPos = dwCurrentPlayPos;
// If we are now filling the buffer with silence, then we have found the end so
// check to see if the entire sound has played, if it has then stop the buffer.
if( m_bFillNextNotificationWithSilence )
{
// We don't want to cut off the sound before it's done playing.
if( m_dwPlayProgress >= m_pAudio->getSizeOfAudioData() )
{
m_apDSBuffer[0]->Stop();
}
}
return S_OK;
}