Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

i am starting to play a sound with XAudio2 and SourceVoice and once its finished, it should be correctly disposed to not have any leaks.

i was expecting it to be something like this:

        sourceVoice.Start();
        sourceVoice.StreamEnd += delegate
            {
                if (!sourceVoice.IsDisposed)
                {
                    sourceVoice.DestroyVoice();
                    sourceVoice.Dispose();
                }
            };

but that crashes with a read access violation in native code deep in XAudio2.dll which i cant debug.

share|improve this question
    
Can you post the error? –  XiaoChuan Yu Jul 3 '13 at 15:14
    
sure i can, but i dont think you will get much out of it, since its just a random address somewhere in native code of XAudio2.dll First-chance exception at 0x05aef5b8 in game.exe: 0xC0000005: 0xC0000005: Access violation. –  clamp Jul 3 '13 at 15:18
    
the stacktrace doesnt even start in my code (C# via SharpDX) and also i have Native Code Debugging enabled. –  clamp Jul 3 '13 at 15:19

2 Answers 2

up vote 6 down vote accepted

I'm not familiar with SharpDX but I know a bit about the native xaudio C++ API.

You can't call DestroyVoice() in a callback at least in the native API which I assume StreamEnd delegate is a just a thin wrapper for OnStreamEnd() callback in the native API. A workaround may be queuing the sourceVoice to a "deleteList" in the OnStreamEnd callback and deleting everything in that list later somewhere in your game loop. Also, keep in mind that you probably don't want to create a source voice each time you play the same sound as it can be reused and deleted only after when you never need that sound ever again.

share|improve this answer
    
Yeah, the XAudio2 API documentation mentions that it can't be called from a callback. Nice catch! –  Shotgun Ninja Jul 3 '13 at 15:59
    
I've incorporated this into my answer, and upvoted yours. –  Shotgun Ninja Jul 3 '13 at 16:05

I think I might have the answer. According to this MSDN article:

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 application can safely free up these resources as soon as the call returns.

My suspicions were that the reason there was a separate DestroyVoice method in the first place was that there was some lazy destruction going on, which is fairly common in real-time audio APIs because of the asynchronous real-time nature of audio playback.

So, by calling Dispose so quickly after DestroyVoice, you might be causing the deferred destruction of the voice to fail, since it is being released from managed memory by Dispose before whatever deferment mechanism has a chance to attempt to destroy the voice in DestroyVoice. This would also explain why you're not getting a stack trace; if it is being destroyed from a separate thread or on a delegate callback, you'd see the stack trace from that instead of from the DestroyVoice call.

While I'm no expert on this, this seems to be a problem big enough to merit a mention on MSDN, but the problem is that it only mentions this in the documentation for an underlying interface, IXAudio2Voice, making it a bit more difficult to find. I'm also not answering this from experience, so reader beware; this might not be the correct answer.

As a side note, the article also mentions this little gem:

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

Which mentions nothing about how it will fail... I'd advise you check up on this as well. Good luck!

EDIT: The article also mentions this:

It is invalid to call DestroyVoice from within a callback (that is, IXAudio2EngineCallback or IXAudio2VoiceCallback).

As @XiaoChuan-Yu mentions in his answer, StreamEnd is a thin wrapper to the OnStreamEnd callback, and it would appear this might be causing your issue.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.