Как получить пиксельные данные из ВидеоМедиаФрама.Direct3DSurface?

#c# #c #unity3d #windows-runtime #hololens

Вопрос:

Я создаю приложение Unity для Micorsoft HoloLens 2.

Приложение захватывает и делится видеокадрами с камеры с помощью Windows.Медиафайлы.Захватить.MediaCapture и Microsoft.смешанная реальность.WebRTC.

У меня есть видеокадр.Direct3DSurface(Windows.Графика.DirectX.Direct3D11.IDirect3DSurface), отформатированный «NV12» в MediaFrameReader.Событие, вызванное кадром.

Но я не знаю, как получить пиксельные данные из объекта IDirect3DSurface.

Я попробовал ниже:

Код на C#

 [DllImport("Direct3DSurfaceAccess")]
private static extern void Direct3DSurfaceAccess([MarshalAs(UnmanagedType.Interface)]Windows.Graphics.DirectX.Direct3D11.IDirect3DSurface d3dSurface);

// MediaFrameReader.FrameArrived event.
private void Nv12FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args) {
  using var mediaFrameReference = sender.TryAcquireLatestFrame();

  // I could't find how to get pixel data from IDirect3DSurface using C# only.
  // Use C  /WinRT for access IDirect3DSurface as below.
  Direct3DSurfaceAccess(mediaFrameReference.VideoMediaFrame.Direct3DSurface);
}
 

Код на C

 void __stdcall Direct3DSurfaceAccess(const winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface* d3dSurface) {
  // Here, confirmed that d3dSurface is not null.

  try {
    // Issue is here.

    winrt::com_ptr<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess;
    d3dSurface->try_as(dxgiInterfaceAccess);
      // App crashes on d3dSurface->try_as.

    // Call other d3dSurface method as a test.
    winrt::Windows::Graphics::DirectX::Direct3D11::Direct3DSurfaceDescription desc = d3dSurface->Description();
      // Same above. App crashes on d3dSurface->Description.

    // Unreachable to here...
    // When succeeded to get the IDirect3DDxgiInterfaceAccess, read pixel data by below code.

    winrt::com_ptr<::IDXGISurface> nativeSurface;
    dxgiInterfaceAccess->GetInterface(IID_PPV_ARGS(nativeSurface.put()));

    ::DXGI_MAPPED_RECT rect;
    nativeSurface->Map(amp;rect, DXGI_MAP_READ);
    const BYTE* pixels = rect.pBits; // Read pixel data from the pointer.
    nativeSurface->Unmap();
  } catch (...) {
    // Can't catch any exceptions.
    // App freezes and downs on HoloLens 2.
  }
}
 

Я думаю, что неправильно, как маршалировать IDirect3DSurface.

Как получить пиксельные данные с IDirect3DSurface?

Воспроизводящий образец

Комментарии:

1. Первое, что нужно сделать, это проверить ошибки HRESULR во всех вызовах методов (GetInterface, Map и т.д.)

2. @SimonMourier Спасибо. Для упрощения в этом коде опущена проверка HRESULT. В реальном коде я это проверяю. В этом случае приложение завершает работу до того, как возвращает HRESULT при вызове метода d3dSurface(например, try_as, Описание).

3. Можете ли вы попробовать Direct3DSurfaceAccess([MarshalAs(UnmanagedType.IInspectable)] object d3dSurface); вместо этого

4. @SimonMourier Спасибо. Я попробовал неуправляемый тип. IInspectable(и неуправляемый тип. Я тоже не знал), но это не сработало. Приложение разбилось.

5. Вы должны опубликовать образец воспроизведения.

Ответ №1:

Вы не можете определить указатель на объект C /WinRT на границе библиотеки dll, так как на двоичном уровне это не COM-объект (поэтому C# передает COM-объект, сопоставленный объекту C /WinRT, что плохо). Вместо этого вы можете сделать что-то вроде этого:

 void __stdcall Direct3DSurfaceAccess(
    IUnknown* surface, // pass a raw COM object (here I use IUnknown)
    char* message,
    size_t messageLength
) {
    try {
        // map to a C  /WinRT object here
        auto d3dSurface = convert_from_abi<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>(surface);

        winrt::com_ptr<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess;
        if (!d3dSurface.try_as(dxgiInterfaceAccess)) {
            ::sprintf_s(message, messageLength, "d3dSurface->try_as failed.");
            return;
        }

        // Call other d3dSurface method as a test.
        auto desc = d3dSurface.Description();
 

convert_from_abi отсюда берется функция convert_from_abi

и в этом случае вы можете определить функцию взаимодействия следующим образом:

 [DllImport(@"Direct3DSurfaceAccess.dll")]
private static extern void Direct3DSurfaceAccess([MarshalAs(UnmanagedType.IUnknown)] object d3dSurface);
 

Комментарии:

1. Это здорово! Я хорошо справился с вашим ответом. Также обновлен пример кода. Но IDXGISurface::Map возвращает E_INVALIDARG, однако я продолжаю исследование и при необходимости задам еще один вопрос. Спасибо!