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
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.