[SlimDX] XAudio2 crashes

Started by
12 comments, last by ThePPK 15 years, 9 months ago
Hello! I tried to switch my DirectSound multithreaded game application to XAudio2 engine. I got two recurrent exceptions: While constructing SourceVoice object: System.NullReferenceException was unhandled Message="Object reference not set to an instance of an object." Source="SlimDX" StackTrace: at SlimDX.XAudio2.SourceVoice.InvokeVoiceProcessingPassStart(StartProcessingEventArgs e) at SlimDX.XAudio2.VoiceCallbackShim.OnVoiceProcessingPassStart(VoiceCallbackShim* , UInt32 bytesRequired) And in a call to SourceVoice.Dispose() method: System.InvalidOperationException was unhandled Message="Handle is not initialized." Source="mscorlib" StackTrace: at System.Runtime.InteropServices.GCHandle.FromIntPtr(IntPtr value) at System.Runtime.InteropServices.GCHandle.op_Explicit(IntPtr value) at SlimDX.XAudio2.VoiceCallbackShim.OnVoiceProcessingPassEnd(VoiceCallbackShim*) All calls to SourceVoice control methods(Start, Stop, Dispose) use sync primitives. Are these exceptions show a bug in SlimDX? P.S. It's mentioned in DX SDK that SourceVoice.Stop() method is always asynchronous. So is it in SlimDX?
Advertisement
I wrote a small sample that shows how to crash XAudio2.


class Program
{
static AudioBuffer _SoundBuffer;
static SourceVoice _Voice;

static void Main()
{
// initialize XAudio2
XAudio2 device = new XAudio2();

// create a mastering voice
MasteringVoice masteringVoice = new MasteringVoice(device);

while (true)
{
Play(device, "MusicMono.wav");
Thread.Sleep(300);
Dispose();
}

// cleanup XAudio2
masteringVoice.Dispose();
device.Dispose();
}

static void Play(XAudio2 device, string fileName)
{
byte[] data;
WaveFormat format;

// read in the wav file
using (WaveFile file = new WaveFile(fileName))
{
format = file.Format;
data = new byte[file.Size];

file.Read(data, file.Size);
}

// build the wave sample data
_SoundBuffer = new AudioBuffer();
_SoundBuffer.AudioData = data;
_SoundBuffer.AudioBytes = data.Length;
_SoundBuffer.Flags = BufferFlags.EndOfStream;

// create the source voice
_Voice = new SourceVoice(device, format);

// submit the data
_Voice.SubmitSourceBuffer(_SoundBuffer);

// start the sample
_Voice.Start();
}

static void Dispose()
{
// cleanup the voice
_Voice.Dispose();
_SoundBuffer.Dispose();
}
}


Just run and wait for a little bit.
Another way to crash the program is to set a breakpoint on _Voice.Dispose() call in Dispose() function and press F5 for several times.

According to the debugger it crashes at Voice::~Voice() and sometimes at two calls mentioned in the previous post.

If you try to play sounds in a separate thread (even the only thread) you'll get the error on the first _Voice.Dispose() call.
Hey, sorry I didn't post earlier. I've been looking into this, just haven't had a lot of time recently. Give me a few days to work it out [smile]
Mike Popoloski | Journal | SlimDX
OK, the bug has been found and neutralized. Turns out the callback interface used to connect the managed events with the unmanaged interface was getting deleted just a few lines before the voice was shut down. Depending on what was happening, in between that period the XAudio2 thread could try to access the now deleted callback pointer and BOOM!

The fix is in the repository.
Mike Popoloski | Journal | SlimDX
Wow! That works! Thank you, Mike!

I've been exhausting the engine with multithreads, random starts and stops but it seems to be very reliable!

I found a very strange issue that could only happen in unmanaged C++ code in case of memory corruption. The issue is that when applying OutputMatrix everything works fine but if you add a variable declaration between the matrix variable and actual SetOutputMatrix() call you get loud clicking sounds during playback. If you remove the variable everything goes right. I have no idea why this can happen in managed C#.
However I can't write a sample to represent it so far.

Thank you again, Mike, you do great job!
In a long run in my multithreaded application I found that SourceVoice.Dispose() hangs regularly.

Can this method wait for an event or sync object in SlimDX code?
First off, what do you mean by hang? Does the whole application freeze, or does it just have a hiccup while it waits for the call to complete? Here is a snippet from the native documentation on the method:

Quote:If any other voice is currently sending audio to this voice, the method fails.

DestroyVoice waits for the audio processing thread to be idle, so it can take a little while (typically no more than a couple of milliseconds). This is necessary to guarantee that the voice will no longer make any callbacks or read any audio data, so the app can safely free up these resources as soon as the call returns.

It is illegal to call DestroyVoice from within a callback. If DestroyVoice is called within a callback XAUDIO2_E_INVALID_CALL will be returned.


Either way, I think we need to think about making the XAudio2 stuff thread safe, since it's unlikely to be used any other way. How did you see this working out?
Mike Popoloski | Journal | SlimDX
I'm sorry that's my bad. The hang was caused by a deadlock in my specific app design.
If a callback is called just before a call to Dispose() it seems that Dispose() waits for the callback to complete.
Thank you for your help!

As for thread safety I think that is a great idea. I agree that most developers face this problem very often and invent "the bicycle" over and over again.


In native DX SDK XAudio2 design there are some drawbacks that are not very convinient (have no idea why they were not implemented there). Here are some that I had to implement myself:
1. get actual playing state of a voice (playing, stopped, paused)
2. safe dispose (both buffer and voice as an atomic operation)
3. thread safety etc
Of course I should mention WaveFile class that you kindly implemented.

I think that it would be very convinient to leave the original XAudio2 design intact but add some helper classes so that you can complete the following tasks just in a line of code:
1. play a file from disk (with no need to worry about opening and consequent resource disposal)
2. control play state of a sound from any thread safely
3. play several files consequently (background sound player)
4. play large files from disk with automatic streaming
I'm just now learning XA2 myself, so I wanted to get some clarification.
Quote:Original post by ThePPK
1. get actual playing state of a voice (playing, stopped, paused)
Why is GetState and checking the buffers queued not adequate?
Quote:Original post by ThePPK
2. safe dispose (both buffer and voice as an atomic operation)
If you destroy all of the source voices using a buffer, isn't it then safe to destroy the buffer? I feel like I'm not understanding your complaint.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Quote:Original post by Promit
Quote:Original post by ThePPK
1. get actual playing state of a voice (playing, stopped, paused)
Why is GetState and checking the buffers queued not adequate?

Say, you need to play a sound file. You just load it to a single buffer with WaveFile class and submit to SourceVoice. Then _MySourceVoice.State.BuffersQueued will be equal to 1 until the whole buffer is played. Then it'll become 0. So you can't distinguish two situations:
1. buffer is queued and Source Voice is playing
2. buffer is queued and Source Voice is stopped
In DirectSound you can easily distinguish it without any user variable with SecondaryBuffer.Status.Playing.

Quote:Original post by Promit
Quote:Original post by ThePPK
2. safe dispose (both buffer and voice as an atomic operation)
If you destroy all of the source voices using a buffer, isn't it then safe to destroy the buffer? I feel like I'm not understanding your complaint.


Yes, of course it is safe. But you need to keep track of using 2 variables: AudioBuffer and SourceVoice and that'll potentially lead to leaks as all of us can forget disposing either.

This topic is closed to new replies.

Advertisement