Tim in STL

Environment:

OS Name Microsoft Windows XP Professional
Version 5.1.2600 Service Pack 2 Build 2600

Processor x86 Family 15 Model 2 Stepping 9 GenuineIntel ~2793 Mhz
Visual Studio 2005

C#

DirectSound library info:

Microsoft DirectX SDK (August 2007)

DirectX for Managed Code\Microsoft.DirectX.DirectSound.DLL

Runtime version: V1.1.4322

Version: 1.0.2902.0

I have a test program that I believe demonstrates that a notification event is being generated when the WritePosition returned by GetCurrentPosition reaches the notification point. The correct operation would be to generate an event based of the currentPlayPosition.

//========================Begin Code Snippet=====================

protected void PrintInfo(

string id,

int interruptNum,

int currentPlayPosition,

int currentWritePosition)

{

if (currentPlayPosition == -1)

{

_outputBuffer.GetCurrentPosition(

out currentPlayPosition,

out currentWritePosition);

}

Console.WriteLine(

"{0,6},{1,5},{2,6},{3,6}",

id,

interruptNum.ToString(),

currentPlayPosition,

currentWritePosition);

}

private void PlayThread()

{

const int LOOP_COUNT = 3;

int interruptNum = 0;

_outputBuffer.Play(0, BufferPlayFlags.Looping);

for (interruptNum = 0; interruptNum < LOOP_COUNT; ++interruptNum)

{

_notificationEvent.WaitOne();

PrintInfo("int-a", interruptNum, -1, -1);

_notificationEvent.Reset(); _notificationEvent.Reset();

}

PrintInfo("b4 stop", interruptNum, -1, -1);

_outputBuffer.Stop();

PrintInfo("stop", interruptNum, -1, -1);

_playing = false;

}

}//class PlayDeviceTest

//========================End Code Snippet=====================

Output below was realized with following setup parameters:

Samples per second: 8000

Bytes per sample: 1

Buffer size: 800 bytes (small for low latency)

Notification Pts: 399 and 799

--------------------------------Begin Output---------------------

int-a, 0, 200, 400

int-a, 1, 576, 0

int-a, 2, 176, 400

b4 stop, 3, 177, 400

stop, 3, 184, 184

------------------------------------End Output---------------------

Among other things, note that the value of the PlayPosition is 184 after the stop.

Can anyone see an error in my analysis

How do I get Microsoft's attention to fix this problem

Tim



Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Chris P.

Try emailing directx@microsoft.com. You won't get an immediate response but you might eventually.

Is the bug limited to .NET






Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Tim in STL

I have not tried it outside of .Net. I did try it with the Microsoft DirectX SDK (June 2007) and the problem exists there as well.





Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Tim in STL

Chris,

You suggested that I email DirectX@Microsoft.com.

I did that more than a week ago but never got any kind of response. I don't know if they even received the email.

If I can't get this problem resolved the only option I feel I have is to rewrite my application in C++ which is a MAJOR deal.

Do you have any other suggestions

Thanks for your help.

Tim





Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Chris P.

You don't get timely responses from directx@microsoft.com.

For timely solutions I suggest opening a support incident. You have to pay (unless you have MSDN Premium) but it's a lot cheaper than rewriting code. If it turns out to be a MS bug you are usually credited.

If you can't afford that let me know and I'll try a different avenue.




Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Tim in STL

Chris,

Thanks for your offer of help. I can afford the support call so I guess that is the next step.

I am working on the assumption that the bug lies in the .net interface to DirectSound. I can't imagine that the problem would have gone unaddressed for so long if the bug was in the underlying DirectX code.

I just stumbled across the anouncement that Microsoft is "Releasing the Source Code for the .NET Framework Libraries" http://weblogs.asp.net/scottgu/archive/2007/10/03/releasing-the-source-code-for-the-net-framework-libraries.aspx CommentPosted=true#commentmessage

If they did this for DirectX I could see for my self what is happening.

Does anybody in this forum have any information on this to share





Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

ptoinson

I have been writing a DirectX 9 (February 2005 release) DirectSound application in unmanaged C++ and I've noticed the same thing; the notification seems to be based on the write cursor rather than the play cursor. Not sure the .NET source code would do you much good when the problem seems to be in the native code. Please post back here if you find out anything new as I'm interested to see what happens. I'll do the same.

Regards,

-rick




Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Chris P.

I'll take another look at my C++ code as well. It "appeared" to be working correctly but honestly I haven't analyzed it in a while.






Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Tim in STL

I haven't been able to find a working example in C++ or C# that uses currentPlayPosition and currentWritePosition to determine where to (or not to) write into the play buffer. Most examples use streaming from a file and let the underlying code decide to load the next block of streamed data.





Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

ptoinson

"Most examples use streaming from a file and let the underlying code decide to load the next block of streamed data."

Same is true for me. Burried Deep in the Documentation for DirectSound is the following:

"Notifications for hardware-controlled buffers are not provided reliably by all drivers; some may generate false notifications. To work around this problem, you can call IDirectSoundBuffer8::GetCurrentPosition whenever your application receives a notification, to be sure that the notification position has actually been reached. Another solution is to create the buffer in software."


I am trying to use the current play cursor position to determine if the event is an extraneous event. It appears that the above flaw only generates extraneous false notifications, the correct notifications are always there. My code attempts to tell them apart. However, my calls to GetCurrentPosition were consistently returning a play cursor position that is before the postion wired to the notification. The write cursor position is consistently just after that position precisely where I would expect to play cursor to be. I am calling GetCurrentPosition as soon as possible after the notification is triggered.

I am also currently making some modification to Microsoft's sample code to use CStreamingSound rather than just CSound in order to verify my problem and to provide a stripped down sample for others to try. Microsoft apparently did not seem to think that folks would require a streaming example. Although, I must give them props for putting the hooks in their sample code in the form of the CStreamingSound class.

-r




Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Chris P.

I have 2 relevant samples on my site. Both use streaming buffers, one from file, one from dynamic object (tone generator).

http://www.chrisnet.net/code.htm






Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

ptoinson

Thank you Chris. I will check these out and see if I get different results.

-rick




Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

Tim in STL

Chris:

I looked at the StreamDataTone3 example on your web site. My take on that code is that it is not checking explicitly that the region of the buffer between dwPlaybackCurrentPlayCursor and dwPlaybackCurrentWriteCursor is not written into. If notifications worked as advertised I think the code would be OK.

HOWEVER: (using software buffers)

1) I have seen instances in my application where notifications were skipped (i.e. never generated). I put in extensive Asserts and found a case (approx 60 sec. execution time) where one of the two notification points never happened.

2) If the notifications come early (which I believe they do because the event is based on the WritePos getting to the notify pt and not on PlayPos as they are supposed to be) I think your code writes the data where it is supposed to go anyway since it always just ads more onto the end of what it wrote before. I think that this would result in occasional writes into the forbidden area but I have never seen it documented what the effect of that is.

I don't mean to criticize your code. To the contrary, I would like to thank you for your efforts to share your expertise and knowledge with others.

Tim





Re: DirectShow Development DirectSound Play Buffer Notify generating a notification based on WritePosition rather than PlayPosition

ptoinson



I have done a bit of analysis on Chris's code and my findings are in line with those I found within my own code. It's nice, when that happens, to know that I am not alone.

Specifically:


1) I added the following line of code to the top of CStreamingSound::HandleWaveStreamNotification( BOOL) method:

DWORD dwCurrentWritePos;

m_apDSBuffer[0]->GetCurrentPosition(&dwCurrentPlayPos, &dwCurrentWritePos);
fprintf(g_hOut, " handle %10ld %10ld %10ld %10ld %10ld %10ld\n",
dwCurrentPlayPos, // 1
dwCurrentWritePos, // 2
m_dwNextWriteOffset, // 3
m_dwNextWriteOffset + m_dwNotifySize, // 4
m_dwNextWriteOffset / m_dwNotifySize, // 5
dwCurrentPlayPos / m_dwNotifySize, // 6
dwCurrentWritePos / m_dwNotifySize); // 7


1.Indicates the current play pos via GetCurrentPosition.
2.Indicates the current write position via GetCurrentPosition.
3.Indicates the next position at which data will be written to the buffer
4.Indicates the last address of the next position at which data will be written to the buffer.
5.Indicates the notification segment where the next write will take place. There are 8 notification segments in your code as there are 8 notifications set up. A notification segment is m_dwNotifySize bytes in size whose last byte is at the notification point. Thus the first notification segment is from (0 -- (m_dwNotifySize - 1)) the second is (m_dwNotifySize -- (m_dwNotifySize * 2 - 1), etc.
6.Indicates the notification segment in which the current play cursor is located.
7.Indicates the notification segment in which the current write cursor is located.

The only other mod to the code was to create the file and to pump up the buffer size to 1024
samples (4096 bytes with 16 bits in 2 channels) per notification segment.

Below is an excerpt from the data taken after the code has run for a few seconds. In the first line you see that the first notification happened when the play cursor was in the first notification segment (segment 0). In fact the code is written such that (given that notifications work properly) the current play cursor (column 6) should always be one ahead of the segment we are about to write to (column 5). However, it is not. Further, in this instance the write cursor is well beyond the the next notification and sometimes two beyond. At lest the write cursor is at regular intervals.


0 1 2 3 4 5 6 7
handle 3636 8836 0 4096 0 0 2
handle 7156 12364 4096 8192 1 1 3
handle 12448 17656 8192 12288 2 3 4
handle 15976 21184 12288 16384 3 3 5
handle 19504 24712 16384 20480 4 4 6
handle 24796 30004 20480 24576 5 6 7
handle 28328 764 24576 28672 6 6 0
handle 31852 4292 28672 32768 7 7 1
handle 4376 9584 0 4096 0 1 2
handle 7904 13112 4096 8192 1 1 3
handle 11436 16640 8192 12288 2 2 4
handle 16724 21932 12288 16384 3 4 5
handle 20252 25460 16384 20480 4 4 6
handle 23780 28988 20480 24576 5 5 7
handle 29072 1512 24576 28672 6 7 0
handle 32600 5040 28672 32768 7 7 1
handle 3360 8568 0 4096 0 0 2
handle 8652 13860 4096 8192 1 2 3
handle 12180 17388 8192 12288 2 2 4
handle 15708 20916 12288 16384 3 3 5
handle 21000 26208 16384 20480 4 5 6
handle 24528 29736 20480 24576 5 5 7
handle 28120 496 24576 28672 6 6 0
handle 580 5788 28672 32768 7 0 1
handle 4108 9316 0 4096 0 1 2
handle 7636 12844 4096 8192 1 1 3
handle 12932 18136 8192 12288 2 3 4
handle 16456 21664 12288 16384 3 4 5
handle 19984 25192 16384 20480 4 4 6
handle 23512 28720 20480 24576 5 5 7
handle 28804 1244 24576 28672 6 7 0
handle 32332 4772 28672 32768 7 7 1
handle 3092 8300 0 4096 0 0 2
handle 8384 13592 4096 8192 1 2 3
handle 11912 17120 8192 12288 2 2 4
handle 15440 20648 12288 16384 3 3 5
handle 20732 25940 16384 20480 4 5 6





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