You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

WinUI3中FFmpeg D3D11VA硬件解码转Win2D渲染的问题求助

WinUI3中FFmpeg D3D11VA硬件解码转Win2D渲染的问题求助

各位大佬好,我正在开发WinUI3的AirPlay投屏应用,目前用FFmpeg.AutoGen做H264解码,搭配Win2D的CanvasAnimatedControl来渲染画面。软件解码的版本已经跑通了,在我自己的开发机上CPU占用只有5%左右,但放到低配置的笔记本上就会冲到30%,所以想换成D3D11VA硬件解码来降低CPU负载,结果卡在了FFmpeg硬件帧转Win2D可识别的Surface这一步。

目前的进展

我已经成功用av_hwdevice_ctx_create创建了D3D11VA设备上下文,也能解码出AV_PIX_FMT_D3D11格式的AVFrame,从_frame->data[0]拿到了ID3D11Texture的指针。但尝试把这个纹理转换成Win2D支持的Windows.Graphics.DirectX.Direct3D11.IDirect3DSurface时,QueryInterface一直报错,提示找不到对应的接口。

关键代码片段

硬件解码器实现

using FFmpeg.AutoGen.Abstractions;
using System;
using System.Runtime.InteropServices;

namespace AirPlay.App.FFmpeg;

public unsafe partial class H264HardwareDecoder : IDisposable
{
    private readonly AVCodecContext* _codecContext;
    private readonly AVFrame* _frame;
    private readonly AVPacket* _packet;
    private readonly AVBufferRef* _hwDeviceCtx;
    public IntPtr _d3d11Device;
    public IntPtr _d3d11DeviceContext;
    public bool Disposed { get; private set; }

    public H264HardwareDecoder()
    {
        AVCodec* codec = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_H264);
        _codecContext = ffmpeg.avcodec_alloc_context3(codec);

        AVBufferRef* hwCtx = null;
        int ret = ffmpeg.av_hwdevice_ctx_create(&hwCtx, AVHWDeviceType.AV_HWDEVICE_TYPE_D3D11VA, null, null, 0);
        if (ret < 0) throw new ApplicationException("Failed to create D3D11VA device");
        _hwDeviceCtx = hwCtx;
        _codecContext->hw_device_ctx = ffmpeg.av_buffer_ref(_hwDeviceCtx);

        AVHWDeviceContext* hwDeviceCtx = (AVHWDeviceContext*)_hwDeviceCtx->data;
        AVD3D11VADeviceContext* d3d11vaCtx = (AVD3D11VADeviceContext*)hwDeviceCtx->hwctx;
        _d3d11Device = (IntPtr)d3d11vaCtx->device;
        _d3d11DeviceContext = (IntPtr)d3d11vaCtx->device_context;

        ffmpeg.avcodec_open2(_codecContext, codec, null);
        _frame = ffmpeg.av_frame_alloc();
        _packet = ffmpeg.av_packet_alloc();
    }

    public bool DecodeFrame(byte[] h264Data, out IntPtr d3d11Texture, out int textureIndex, out int width, out int height)
    {
        d3d11Texture = IntPtr.Zero;
        textureIndex = 0;
        width = height = 0;

        fixed (byte* p = h264Data)
        {
            ffmpeg.av_packet_unref(_packet);
            _packet->data = p;
            _packet->size = h264Data.Length;

            if (ffmpeg.avcodec_send_packet(_codecContext, _packet) < 0) return false;
            if (ffmpeg.avcodec_receive_frame(_codecContext, _frame) < 0) return false;

            width = _frame->width;
            height = _frame->height;

            if (_frame->format == (int)AVPixelFormat.AV_PIX_FMT_D3D11)
            {
                // 这里拿到的是FFmpeg输出的D3D11纹理指针
                d3d11Texture = (IntPtr)_frame->data[0];
                textureIndex = (int)_frame->data[1];
                return true;
            }
            return false;
        }
    }

    public IntPtr GetD3D11Device() => _d3d11Device;
    public IntPtr GetD3D11DeviceContext() => _d3d11DeviceContext;

    public void Dispose()
    {
        if (_frame != null)
        {
            fixed (AVFrame** f = &_frame) ffmpeg.av_frame_free(f);
        }
        if (_packet != null)
        {
            fixed (AVPacket** p = &_packet) ffmpeg.av_packet_free(p);
        }
        if (_codecContext != null)
        {
            fixed (AVCodecContext** c = &_codecContext) ffmpeg.avcodec_free_context(c);
        }
        if (_hwDeviceCtx != null)
        {
            fixed (AVBufferRef** h = &_hwDeviceCtx) ffmpeg.av_buffer_unref(h);
        }
        Disposed = true;
    }
}

D3D11转IDirect3DSurface的interop代码

public static class D3D11Interop
{
    [DllImport("d3d11.dll", ExactSpelling = true)]
    public static extern int CreateDirect3D11SurfaceFromDXGISurface(
        IntPtr dxgiSurface, out IntPtr direct3DSurface);

    public static IDirect3DSurface CreateDirect3DSurfaceFromDXGISurface(IntPtr d3d11Texture)
    {
        // 尝试QueryInterface获取IDXGISurface,这里总是失败
        Texture2D texture2D = CppObject.FromPointer<Texture2D>(d3d11Texture);
        texture2D.QueryInterface(new Guid("cafcb56c-6ac3-4889-bf47-9e23bbd260ec"), out var outPtr);

        IntPtr direct3DSurfacePtr;
        int hr = CreateDirect3D11SurfaceFromDXGISurface(outPtr, out direct3DSurfacePtr);
        if (hr < 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }

        var direct3DSurface = Marshal.GetObjectForIUnknown(direct3DSurfacePtr) as IDirect3DSurface;
        Marshal.Release(direct3DSurfacePtr);
        return direct3DSurface;
    }
}

调用逻辑

if (UseGpuDecoder)
{
    H264HardwareDecoder h264HardwareDecoder = new();
    decoder = h264HardwareDecoder;

    session.MirrorController!.H264DataReceived += (_, e) =>
    {
        if (h264HardwareDecoder.DecodeFrame(e.Data, out IntPtr d3d11Texture, out int textureIndex, out var width, out var height))
        {
            try
            {
                var surface = D3D11Interop.CreateDirect3DSurfaceFromDXGISurface(d3d11Texture);
                mirrorWindow?.OnFrameDataReceived(surface);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"转换Surface失败: {ex.Message}");
            }
        }
        else
        {
            Debug.WriteLine("H264硬件解码失败");
        }
    };
}

我现在的疑问

  1. FFmpeg返回的D3D11Texture是不是没有实现IDXGISurface接口?如果是这样的话,应该怎么把它转换成Win2D能使用的Surface?
  2. 有没有正确的流程,能把FFmpeg的D3D11硬件帧直接交给Win2D渲染,避免额外的CPU拷贝?
  3. 会不会是FFmpeg创建的D3D设备和WinUI/Win2D使用的设备不是同一个导致的?如果是,应该怎么让它们共享设备上下文?

折腾了好几天都没搞定,希望有做过类似硬件解码+Win2D渲染的朋友给点思路,万分感谢!

火山引擎 最新活动