I've implemented this method in c#:
HRESULT CreateSourceVoice(
[out] IXAudio2SourceVoice **ppSourceVoice,
[in] const WAVEFORMATEX *pSourceFormat,
[in] UINT32 Flags = 0,
[in] float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,
[in, optional] IXAudio2VoiceCallback *pCallback = NULL,
[in, out] const XAUDIO2_VOICE_SENDS *pSendList = NULL,
[in, optional] const XAUDIO2_EFFECT_CHAIN *pEffectChain = NULL
);
But I've got a problem with marshaling the 5th parameter. But first this is my implementation of the IXAudio2VoiceCallback
interface:
[SuppressUnmanagedCodeSecurity]
public interface IXAudio2VoiceCallback
{
void OnVoiceProcessingPassStart([In] Int32 bytesRequired);
void OnVoiceProcessingPassEnd();
void OnStreamEnd();
void OnBufferStart([In] IntPtr bufferContextPtr);
void OnBufferEnd([In] IntPtr bufferContextPtr);
void OnLoopEnd([In] IntPtr bufferContextPtr);
void OnVoiceError([In] IntPtr bufferContextPtr, [In] int error);
}
public class VoiceCallback : IXAudio2VoiceCallback
{
//... events ...
void IXAudio2VoiceCallback.OnVoiceProcessingPassStart(int bytesRequired)
{
if(ProcessingPassStart != null)
ProcessingPassStart(bytesRequired);
}
void IXAudio2VoiceCallback.OnVoiceProcessingPassEnd()
{
if(ProcessingPassEnd != null)
ProcessingPassEnd();
}
void IXAudio2VoiceCallback.OnStreamEnd()
{
if (StreamEnd != null)
StreamEnd();
}
void IXAudio2VoiceCallback.OnBufferStart(IntPtr bufferContextPtr)
{
if(BufferStart != null)
BufferStart(bufferContextPtr);
}
void IXAudio2VoiceCallback.OnBufferEnd(IntPtr bufferContextPtr)
{
if(BufferEnd != null)
BufferEnd(bufferContextPtr);
}
void IXAudio2VoiceCallback.OnLoopEnd(IntPtr bufferContextPtr)
{
if(LoopEnd != null)
LoopEnd(bufferContextPtr);
}
void IXAudio2VoiceCallback.OnVoiceError(IntPtr bufferContextPtr, int error)
{
if(VoiceError != null)
VoiceError(bufferContextPtr, error);
}
}
But now my actual problem: I can call CreateSourceVoice and it returns 0 (-> S_OK) but at any time, the callback should be called, the process just stops. There are no error messages (I've also checked the windows event log). Since I am using a quite similar concept as SharpDX does (see here), I've checked the SharpDX source. As far as I could see, SharpDX builds a vtable (see here).
But I am asking myself, whether it is possible to avoid building my own vtable? Isn't there a easier way?
I just would need to be able to get the vtable of the VoiceCallback
-class.
The code for CreateSourceVoice looks like this:
public unsafe int CreateSourceVoiceNative(out IntPtr pSourceVoice, IntPtr sourceFormat, VoiceFlags flags, float maxFrequencyRatio, IXAudio2VoiceCallback voiceCallback, VoiceSends? sendList, EffectChain? effectChain)
{
VoiceSends value0 = sendList.HasValue ? sendList.Value : default(VoiceSends);
EffectChain value = effectChain.HasValue ? effectChain.Value : default(EffectChain);
return calli(System.Int32(System.Void*,System.Void*,System.IntPtr,CSCore.XAudio2.VoiceFlags,System.Single,CSCore.XAudio2.IXAudio2VoiceCallback,System.Void*,System.Void*), this._basePtr, &pSourceVoice, sourceFormat, flags, maxFrequencyRatio, voiceCallback, sendList.HasValue ? ((void*)(&value0)) : ((void*)IntPtr.Zero), effectChain.HasValue ? ((void*)(&value)) : ((void*)IntPtr.Zero), *(*(IntPtr*)this._basePtr + (IntPtr)5 * (IntPtr)sizeof(void*)));
}
I've actually found a solution. The solution is to override the function pointers of the IUnknown
interface in the vtalbe of the com object. I've written a quite ugly but working utils method which patches the vtable:
private static readonly List<IntPtr> _patchedVtables = new List<IntPtr>();
public unsafe static IntPtr GetComInterfaceForObjectWithAdjustedVtable(IntPtr ptr, int finalVtableLength, int replaceCount)
{
var pp = (IntPtr*) (void*) ptr;
pp = (IntPtr*)pp[0];
IntPtr z = new IntPtr((void*)pp);
//since the same vtable applies to all com objects of the same type -> make sure to only patch it once
if (_patchedVtables.Contains(z))
{
return ptr;
}
_patchedVtables.Add(z);
for (int i = 0; i < finalVtableLength; i++)
{
IntPtr prev = pp[i];
pp[i] = pp[i + replaceCount];
IntPtr after = pp[i];
//Console.WriteLine("{0} -> {1}", prev, after); //just for debugging
}
return ptr;
}
And just use it like this:
p = Marshal.GetComInterfaceForObject(voiceCallback, typeof (IXAudio2VoiceCallback));
p = Utils.Utils.GetComInterfaceForObjectWithAdjustedVtable(p, 7, 3);
The second parameter (in this case 7) is the number of methods, the IXAudio2VoiceCallback contains and the third paramter (in this case 3) is the number of methods to override -> the number of methods of the IUnknown which is three (QueryInterface, AddRef, Release).
Again, big thanks to xoofx.