Jump to content
  • Advertisement
Sign in to follow this  
ThePPK

[SlimDX] XAudio2 crashes

This topic is 3703 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

[/quote]
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!