Problem with deadlock waveOutWrite and waveOutGetPosition

I am working on an application that continuously plays audio using the waveOut... API waveOut... from winmm.dll . The application uses β€œleapfrog” buffers, which are basically a collection of arrays of samples that you upload to the audio queue. Windows plays them sequentially sequentially, and as each buffer exits, Windows calls the callback function. Inside this function, I load the next set of samples into a buffer, process them, and then delete the buffer back into the audio queue. Thus, the sound is played endlessly.

For the purpose of animation, I am trying to include waveOutGetPosition in the application (since buffer callbacks are irregular enough to cause jerky animation). waveOutGetPosition returns the current playback position, so it is hyper-accurate.

The problem is that in my application, the waveOutGetPosition calls ultimately cause the application to block - the sound stops and the call never returns. I swallowed everything to a simple application that demonstrates the problem. You can run the application here:

http://www.musigenesis.com/SO/waveOut%20demo.exe

If you just hear the tiny piano over and over, it works. It just demonstrated the problem. The source code for this project is here (all meat is in LeapFrogPlayer.cs):

http://www.musigenesis.com/SO/WaveOutDemo.zip

The first button launches the application in jump mode without making calls to waveOutGetPosition . If you click on this, the application will play forever without hacking (the X button will close it and turn it off). The second button launches scapperger and also starts a form timer that calls waveOutGetPosition and displays the current position. Press this button, and the application will start for a short time, and then block. On my laptop, it usually blocks after 15-30 seconds; at best it took a minute.

I have no idea how to fix this, so any help or suggestions would be most welcome. I have found very few posts on this issue, but it seems that there is a potential dead end, whether from multiple calls to waveOutGetPosition or from calls to this and waveOutWrite that occur simultaneously. Perhaps I too often call this to process the system.

Edit : forgot to mention, I am running this on Windows Vista. This may not happen at all on other OSs.

Change 2 . I have little knowledge of this problem on the Internet, except for these (unanswered) messages:

http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/c6a1e80e-4a18-47e7-af11-56a89f638ad7

Change 3 . Well, now I can reproduce this problem on my own. If I call waveOutGetPosition immediately after waveOutWrite (in the next line of code), the application freezes every time. It also hangs especially poorly - it seems that it blocks my entire OS for a while, and not just on the application itself. Thus, it turns out that waveOutGetPosition deadlocked, if it happens almost at the same time as waveOutWrite , and not just literally at the same time, which may explain why the locks do not work for me. Ish.

+2
c # audio
source share
3 answers

The solution to this was very simple (thanks to Larry Osterman): replace the WndProc callback.

The waveOutOpen method can accept a delegate (for a callback) or a window handle. I used the delegate approach, which is apparently inherently prone to deadlock (it makes sense, especially in managed code). I was able to simply inherit the class class on Control and override the WndProc method and do the same in this method as in the callback. Now I can call waveOutGetPosition forever and never blocks.

0
source share

It blocks the mmsys API code. Calling waveOutGetPosition () inside the callback stubs when the main thread is busy executing waveOutWrite (). This fix, you will need a lock so that these two functions could not be executed at the same time. Add this field to LeapFrogPlayer:

  private object mLocker = new object(); 

And use it in GetElapsedMilliseconds ():

  if (!noAPIcall) { lock (mLocker) { ret = WaveOutX.waveOutGetPosition(_hWaveOut, ref _timestruct, _timestructsize); } } 

and HandleWaveCallback ():

  // play the next buffer lock (mLocker) { int ret = WaveOutX.waveOutWrite(_hWaveOut, ref _header[_currentBuffer], Marshal.SizeOf(_header[_currentBuffer])); if (ret != WaveOutX.MMSYSERR_NOERROR) { throw new Exception("error writing audio"); } } 

This can have side effects, but I did not notice it. Take a look at the NAudio project.

Please use Build + Clean the next time you create a downloadable .zip of your project.

+3
source share

I often use NAudio and often request WaveOut.GetPosition() , and also see frequent deadlocks when using the Callback strategy. This is essentially the same problem as the OP, so I believe this solution can help someone else.

I tried using window-based strategies (as indicated in the answer), but the sound stutters when a lot of messages break through the message queue. So I switched to Callback strategy. Then I started getting dead ends.

I request a position of sound at a speed of 60 frames per second to synchronize the animation, so I regularly pick up a dead end (on average about 20 seconds). Note. I'm sure I can reduce the amount I call the API, but that is not my point here!

It seems that winmm.dll calls winmm.dll all blocked inside the same object / descriptor. If this assumption is true, then in NAudio I am almost guaranteed a dead end. Here's a scenario with two threads: A (UI thread); and B (feedback in winmm.dll ) and two waveOutLock (as in NAudio) and mmdll (lock that I accept winmm.dll):

  • A β†’ lock (waveOutLock) --- acquired
  • B -> lock (mmdll) for callback --- acquired
  • B β†’ callback to user code
  • B β†’ attempt to block (waveOutLock) - wait for exit A
  • A β†’ resumed due to waiting B
  • A β†’ call waveOutGetPosition
  • A β†’ attempt to block (mmdll) - deadlock

My solution was to delegate the work done in the callback to my own thread so that the callback could immediately return and release the (hypothetical) mmdll lock. This seems to work fine for me as the dead end is gone.

For those who are interested, I forked and modified the NAudio source to include my changes. I used a thread pool, and the sound is sometimes a bit hacked. This may be due to thread pool thread control, so there may be a solution that works better.

+1
source share

All Articles