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硬件解码失败"); } }; }
我现在的疑问
- FFmpeg返回的D3D11Texture是不是没有实现IDXGISurface接口?如果是这样的话,应该怎么把它转换成Win2D能使用的Surface?
- 有没有正确的流程,能把FFmpeg的D3D11硬件帧直接交给Win2D渲染,避免额外的CPU拷贝?
- 会不会是FFmpeg创建的D3D设备和WinUI/Win2D使用的设备不是同一个导致的?如果是,应该怎么让它们共享设备上下文?
折腾了好几天都没搞定,希望有做过类似硬件解码+Win2D渲染的朋友给点思路,万分感谢!




