HoloLens 2 Unity项目如何从IDirect3DSurface获取NV12像素数据?
从HoloLens 2的IDirect3DSurface读取NV12像素数据的解决方案
核心问题原因
你的应用崩溃确实是IDirect3DSurface的跨语言封送逻辑错误导致的:C#通过UnmanagedType.Interface封送的WinRT接口是原生COM指针,和C++/WinRT封装后的winrt::IDirect3DSurface对象内存布局完全不同,直接用C++/WinRT的指针类型接收会触发非法内存访问。
修正步骤
1. 修改C++端导出函数逻辑
不要直接用winrt::IDirect3DSurface*作为参数接收,先接收到原生ABI指针后再转换为C++/WinRT封装对象:
#include <windows.graphics.directx.direct3d11.interop.h> #include <winrt/Windows.Graphics.DirectX.Direct3D11.h> void __stdcall Direct3DSurfaceAccess(void* d3dSurfaceAbi) { winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface d3dSurface{ nullptr }; // 将原生ABI指针绑定到C++/WinRT对象,不额外增加引用计数 winrt::attach_abi(d3dSurface, d3dSurfaceAbi); try { // 现在可以正常调用接口方法 auto surfaceDesc = d3dSurface.Description(); auto dxgiAccess = d3dSurface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); winrt::com_ptr<IDXGISurface> nativeDxgiSurface; winrt::check_hresult(dxgiAccess->GetInterface(IID_PPV_ARGS(nativeDxgiSurface.put()))); // 映射纹理到CPU内存 DXGI_MAPPED_RECT mappedRect{}; winrt::check_hresult(nativeDxgiSurface->Map(&mappedRect, DXGI_MAP_READ)); // NV12数据读取:Y平面在前,UV交错平面在后 const BYTE* yPlaneData = mappedRect.pBits; const BYTE* uvPlaneData = mappedRect.pBits + surfaceDesc.Height * mappedRect.Pitch; // 此处可将数据拷贝到自有缓冲区后续处理 // 取消映射,必须执行否则会造成GPU资源泄漏 nativeDxgiSurface->Unmap(); } catch (winrt::hresult_error const& err) { // 可通过OutputDebugStringW输出错误信息排查问题 OutputDebugStringW(err.message().c_str()); } // 解除ABI指针绑定,避免内存泄漏 winrt::detach_abi(d3dSurface); }
2. 修改C#端调用逻辑
调整DllImport声明,先获取WinRT接口的原生COM指针再传递到C++:
using System.Runtime.InteropServices; using Windows.Graphics.DirectX.Direct3D11; [DllImport("Direct3DSurfaceAccess")] private static extern void Direct3DSurfaceAccess(IntPtr d3dSurfaceAbi); private void Nv12FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args) { using var mediaFrameReference = sender.TryAcquireLatestFrame(); var d3dSurface = mediaFrameReference?.VideoMediaFrame?.Direct3DSurface; if (d3dSurface == null) return; // 获取IDirect3DSurface的原生COM指针 IntPtr surfacePtr = Marshal.GetComInterfaceForObject( d3dSurface, typeof(IDirect3DSurface)); Direct3DSurfaceAccess(surfacePtr); // 释放临时增加的引用计数 Marshal.Release(surfacePtr); }
额外注意事项
- 不要在FrameArrived回调中做耗时的像素处理操作,会直接导致相机采集帧卡顿,建议将像素数据拷贝到自有缓冲区后异步处理
- 如果你是要把帧传递给Microsoft.MixedReality.WebRTC,可以直接传递GPU纹理指针,不需要拷贝到CPU内存,能大幅降低性能损耗
- 需确保C++项目的编译配置和Unity导出的UWP项目配置一致,目标架构为ARM64,Windows SDK版本匹配HoloLens 2的系统版本
内容的提问来源于stack exchange,提问作者Uyaw
相关产品推荐
相关产品推荐

