Search code examples
c#directxms-media-foundationsharpdx

How to use MMFCreateDXGISurfaceBuffer in MediaFundation.Net?


I've searched in Microsoft Website about MMFCreateDXGISurfaceBuffer but it was all written in c++. I'm trying to use MMFCreateDXGISurfaceBuffer in c# but can't find any reference to how to correctly used the method.

in c++ below is the code.

MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), pAcquiredDesktopImage, 0, FALSE, &pMediaBuffer)

I tried converting the above code to c# and below is my code but seems not right because I'm getting E_NOINTERFACE

MFExtern.MFCreateDXGISurfaceBuffer(typeof(SharpDX.Direct3D11.Texture2D).GUID, texture, 0, false, out buffer);

I'm using SharpDX DXGI and MediaFoundation.Net in my application. I'm not sure if

typeof(SharpDX.Direct3D11.Texture2D).GUID

is Equivalent to

__uuidof(ID3D11Texture2D)

Below is the wrapper for MMFCreateDXGISurfaceBuffer

 [DllImport("mfplat.dll", ExactSpelling = true), SuppressUnmanagedCodeSecurity]
    public static extern HResult MFCreateDXGISurfaceBuffer(
        [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
        [MarshalAs(UnmanagedType.Interface)] object punkSurface,
        int uSubresourceIndex,
        [MarshalAs(UnmanagedType.Bool)] bool fBottomUpWhenLinear,
        out IMFMediaBuffer ppBuffer
    );

Can someone teach me how to use MFCreateDXGISurfaceBuffer in MediaFOundation.Net. Thanks

Edit: I used MFTrace and below is the log where it failed

1716,2494 03:19:20.59013 CMFTransformDetours::SetInputType @02E96B4C Failed MT: MF_MT_FRAME_SIZE=5866925327104 (1366,768);MF_MT_MAJOR_TYPE=MEDIATYPE_Video;MF_MT_FRAME_RATE=257698037761 (60,1);MF_MT_PIXEL_ASPECT_RATIO=4294967297 (1,1);MF_MT_INTERLACE_MODE=2;MF_MT_SUBTYPE=MFVideoFormat_RGB32

Im not sure if I'm setting the input type correctly since I'm feeding the sink with direct3d surface.


Solution

  • I think this is your post (https://sourceforge.net/p/mfnet/discussion/711229/thread/9814e58b/#c149); I answered over there but saw your question on StackOverflow and thought I should reproduce my answer.


    MFCreateDXGISurfaceBuffer accepts an IUnknown as the second parameter, so you can pass it anything that looks like a COM object, whether native or managed. However, internally MFCreateDXGISurfaceBuffer is actually looking for an object that implements the ID3D11Texture2D interface, which is why you have to pass the IID of that interface as the first parameter.

    Your code is probably failing because you're passing in the SharpDX Texture2D managed object itself to MFCreateDXGISurfaceBuffer. You'd think this would fail on the .NET side (i.e. compiler or marshalling error), but the .NET interop marshaller actually makes the P/Invoke function call possible. This is because any .NET class can be casted to object, and all .NET objects can be marshalled into a native IUnknown COM object via a CCW (COM callable wrapper).

    The reason you're getting an E_NOINTERFACE from MFCreateDXGISurfaceBuffer is because the .NET interop marshaller wraps the SharpDX Texture2D object with a native CCW, hands the CCW off to that function, and then the function tries to see if the object (which it sees as a native IUnknown) supports the ID3D11Texture2D interface. MFCreateDXGISurfaceBuffer does this by using the COM QueryInterface function that IUnknown provides. However, the SharpDX Texture2D class does not implement the ID3D11Texture2D interface, so when the marshalled version of Texture2D is queried the lookup fails and QueryInterface will return E_NOINTERFACE.

    To solve this problem you need to get the native COM pointer the SharpDX Texture2D is encapsulating instead of passing in the SharpDX object itself. All the SharpDX objects are derived from a base ComObject class that provides a NativePointer property accessor, which gives you an IntPtr to the actual wrapped COM object. This is the actual COM ID3D11Texture2D pointer in your case, but now we need to get an object out of it rather than an IntPtr. That looks like this:

    HResult hr;
    IMFMediaBuffer buffer;
    
    // "Marshal" is from the System.Runtime.InteropServices namespace
    
    // This line converts the IntPtr texture.NativePointer in to a .NET RCW 
    // (runtime callable wrapper) so that it can be used like a native .NET
    // object.
    object texNativeObject = Marshal.GetObjectForIUnknown(texture.NativePointer);
    
    // When texNativeObject is passed as an argument to MFCreateDXGISurfaceBuffer,
    // the .NET marshaller will simply unwrap the RCW and provide a native COM 
    // pointer to the function.
    hr = MFExtern.MFCreateDXGISurfaceBuffer(
        typeof(SharpDX.Direct3D11.Texture2D).GUID, 
        texNativeObject, 
        0, false, out buffer
    );
    
    // check "hr" here for success or failure
    
    // "COMBase" is from the MediaFoundation.Misc namespace
    // If this line causes problems at runtime, just remove it. SafeRelease
    // forces the .NET runtime to release the RCW now, rather than later when
    // a GC happens. It calls Marshal.ReleaseComObject(...) internally.
    COMBase.SafeRelease(texNativeObject); 
    texNativeObject = null;
    

    Alternatively, you can add another definition of MFCreateDXGISurfaceBuffer that accepts an IntPtr as the second argument. You can add this to your own code instead of modifying MediaFoundation.NET.

    class NativeMethods
    {
        // EntryPoint must be provided since we are naming the alternate
        // version MFCreateDXGISurfaceBuffer2 to separate it from the original
        [DllImport("mfplat.dll", ExactSpelling = true, EntryPoint = "MFCreateDXGISurfaceBuffer")]
        public static extern HResult MFCreateDXGISurfaceBuffer2(
            [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
            IntPtr punkSurface,
            int uSubresourceIndex,
            [MarshalAs(UnmanagedType.Bool)] bool fBottomUpWhenLinear,
            out IMFMediaBuffer ppBuffer
        );
    }
    

    It's okay to use an alternate function signature like this, only because the Texture2D.NativePointer is an IntPtr referencing unmanaged memory. In other words, you're handing an unmanaged memory address to an unmanaged function, and that's okay.

    Once you have the alternate version in your code, you can use the new MFCreateDXGISurfaceBuffer2 function like this:

    HResult hr;
    IMFMediaBuffer buffer;
    hr = NativeMethods.MFCreateDXGISurfaceBuffer2(
        typeof(SharpDX.Direct3D11.Texture2D).GUID, 
        texture.NativePointer, 
        0, false, out buffer
    );
    
    // check "hr" here
    

    Hope this helps!