WinRT_WindowsGraphicsCapture - HexagramNM/NM_WindowCaptureVirtualCamera GitHub Wiki

WinRTのWindowsGraphicsCaptureAPI

WinRTのWindows.Graphics.Captureではハードウェアアクセラレーションの機能でGPUで描画処理を行っているウィンドウをキャプチャし、DirectXのテクスチャという形で取得することができます。

以下の流れでキャプチャされたウィンドウを取得できます。

  1. DirectXのデバイスであるIDirect3DDeviceを作成
  2. ウィンドウピッカーの結果もしくはウィンドウハンドルからキャプチャ対象のウィンドウ情報をもつGraphicsCaptureItemを取得
  3. IDirect3DDeviceGraphicsCaptureItemからDirect3D11CaptureFramePoolを作成
  4. Direct3D11CaptureFramePoolのイベントFrameArrivedで、引数からDirectXのテクスチャに変換する。

1. IDirect3DDeviceの作成

UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL d3dFeatures[1] = 
{
    D3D_FEATURE_LEVEL_11_1
};

// _dxDevice: com_ptr<ID3D11Device>
// _dxDeviceContext: com_ptr<ID3D11DeviceContext>
D3D_FEATURE_LEVEL resultFeature;
check_hresult(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE,
    nullptr, createDeviceFlags, d3dFeatures, 1, D3D11_SDK_VERSION,
    _dxDevice.put(), &resultFeature, _dxDeviceContext.put()));

com_ptr<IDXGIDevice> dxgiDevice = _dxDevice.as<IDXGIDevice>();
com_ptr<::IInspectable> device = nullptr;
check_hresult(::CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), device.put()));

// _dxDeviceForCapture: IDirect3DDevice
_dxDeviceForCapture = device.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();

DirectXの開発では、テクスチャといったリソースの管理などを行うデバイス(ID3D11Device)と描画命令を管理するデバイスコンテキスト(ID3D11DeviceContext)を最初に作成します。この作成はD3D11CreateDevice関数で行うことが可能です。デバイスコンテキストはWindowsCaptureAPIでは使用しませんが、この後のオフスクリーンレンダリングの描画で使用します。

さらに、DirectXのデバイスをWindowsCaptureAPIで使用できるように、CreateDirect3D11DeviceFromDXGIDevice関数を使ってIDirect3DDeviceに変換します。

2. GraphicsCaptureItemの作成

GraphicsCaptureItemはウィンドウピッカーから取得したり、ウィンドウハンドルから直接作成したりできます。

ウィンドウピッカーから取得する

winrt::Windows::Foundation::IAsyncAction NM_WindowCapture::OpenWindowPicker()
{
    init_apartment(winrt::apartment_type::single_threaded);
    SetWindowDisplayAffinity(_baseHwnd, WDA_EXCLUDEFROMCAPTURE);

    GraphicsCapturePicker picker;
    auto interop = picker.as<::IInitializeWithWindow>();
    interop->Initialize(_baseHwnd);
    GraphicsCaptureItem pickerResult = co_await picker.PickSingleItemAsync();
    if (pickerResult != nullptr) 
    {
        _graphicsCaptureItem = pickerResult;
    }

    SetWindowDisplayAffinity(_baseHwnd, WDA_NONE);
    ChangeWindow();
}

WindowCaptureAPIにはウィンドウを選択するピッカーの機能があります。このピッカー機能を利用する場合は最初にinit_apartment(winrt::apartment_type::single_threaded);を呼び出しておく必要があります。この呼び出しで、WinRTのCOMを初期化し、ピッカーが使える状態になるようです。

このピッカーは他のウィンドウに紐づいている必要があります。NM_WindowCaputureVirtualCameraでは_baseHwndのウィンドウハンドルに対応するウィンドウを紐づけています。最初にそのウィンドウをIInitializeWithWindowInitializeでピッカーに紐づけています。

ピッカーの結果はPickSingleItemAsyncで受け取れます。非同期関数であるので、co_awaitで結果が来るまで待機することになります。

キャプチャの対象から特定のウィンドウを外すこともできます。SetWindowDisplayAffinity関数で、ウィンドウにWDA_EXCLUDEFROMCAPTUREフラグを指定します。こうすることで、ピッカーからもそのウィンドウが除外されます。

Note

ピッカーに紐づけているウィンドウはフォアグラウンドにある状態である必要があります。NM_WindowCaputureVirtualCameraでは、ピッカーを開く処理を起動するためにGUIのボタンを押しているので、すでにフォアグラウンドにあります。コード内で新たにウィンドウを作成する場合は、デフォルトではフォアグラウンドになっていないので、一度最小化してから復元し、フォアグラウンド化する必要があります。

ウィンドウハンドルから直接作成する

void NM_WindowCapture::SetTargetWindowForCapture(HWND targetWindow)
{
    namespace abi = ABI::Windows::Graphics::Capture;
    if (targetWindow == NULL) 
    {
        return;
    }

    auto factory = get_activation_factory<GraphicsCaptureItem>();
    auto interop = factory.as<::IGraphicsCaptureItemInterop>();
    check_hresult(interop->CreateForWindow(targetWindow, guid_of<abi::IGraphicsCaptureItem>(),
        reinterpret_cast<void**>(put_abi(_graphicsCaptureItem))));

    ChangeWindow();
}

上記のようにget_activation_factoryGraphicsCaptureItemを作るためのファクトリを作成します。対象となるウィンドウハンドルを引数に、このファクトリのCreateForWindowを呼び出すことで、そのウィンドウを対象にしたGraphicsCaptureItemを作成することができます。

3. Direct3D11CaptureFramePoolの作成

void NM_WindowCapture::ChangeWindow() 
{
    if (_graphicsCaptureItem == nullptr) 
    {
        return;
    }

    // 中略

    // FramePoolの作成
    // _framePoolForCapture: Direct3D11CaptureFramePool
    _capWinSize = _graphicsCaptureItem.Size();
    _framePoolForCapture = Direct3D11CaptureFramePool::CreateFreeThreaded(_dxDeviceForCapture, 
        DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, _capWinSize);

    // FrameArrivedイベントの設定
    // _frameArrivedForCaptureevent_revoker<IDirect3D11CaptureFramePool>
    _frameArrivedForCapture = _framePoolForCapture.FrameArrived(auto_revoke, { this, &NM_WindowCapture::OnFrameArrived });

    // CaptureSessionの作成と開始
    // _captureSession: GraphicsCaptureSession
    _captureSession = _framePoolForCapture.CreateCaptureSession(_graphicsCaptureItem);
    //IsCursorCaptureEnabledでカーソルもキャプチャするか指定できる。
    _captureSession.IsCursorCaptureEnabled(_captureCursor);
    _captureSession.StartCapture();
}

最初にFramePoolをCreateFreeThreadedで作成します。その際に1で作成したIDirect3DDeviceを引数に渡します。次にそのFramePoolにFrameArrivedイベントを設定します。最後にFramePoolからCaptureSessionを作成し、そのCaptureSessionでのキャプチャを開始します。CaptureSessionの作成時に2で取得、作成したGraphicsCaptureItemを引数に渡します。

4. イベントFrameArrivedでキャプチャしたウィンドウの取得

template<typename T>
auto getDXGIInterfaceFromObject(winrt::Windows::Foundation::IInspectable const& object) 
{
    auto access = object.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
    com_ptr<T> result;
    check_hresult(access->GetInterface(guid_of<T>(), result.put_void()));
    return result;
}

// 中略

void NM_WindowCapture::OnFrameArrived(Direct3D11CaptureFramePool const& sender,
    winrt::Windows::Foundation::IInspectable const& args)
{
    // 中略

    // キャプチャされるフレームを引数から取得
    auto frame = sender.TryGetNextFrame();

    // 取得されたフレームから現在のウィンドウのサイズを取得できる。
    SizeInt32 itemSize = frame.ContentSize();
    if (itemSize.Width <= 0) 
    {
        itemSize.Width = 1;
    }
    if (itemSize.Height <= 0) 
    {
        itemSize.Height = 1;
    }

    // ウィンドウがリサイズされた場合、FramePoolのRecreateを呼び出す必要がある。
    if (itemSize.Width != _capWinSize.Width
        || itemSize.Height != _capWinSize.Height) 
    {
        _capWinSize = itemSize;

        // 中略

        _framePoolForCapture.Recreate(_dxDeviceForCapture,
            DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, _capWinSize);
    }

    // 取得されたフレームからキャプチャされた画像を含むテクスチャ(ID3D11Texture2D)を取得できる。
    // このテクスチャはDirectXでの描画にも使用できる。
    com_ptr<ID3D11Texture2D> currentTexture = getDXGIInterfaceFromObject<::ID3D11Texture2D>(frame.Surface());

    // 中略
}

ウィンドウの映像がキャプチャされる度、FrameArrivedイベントに設定されたメソッドが呼び出されます。イベントの引数から現在のウィンドウのサイズやキャプチャされた画像のテクスチャを取得できます。ここで取得されたテクスチャはDirectXの描画に使用できます。

ウィンドウサイズが変化した場合は、FramePoolのRecreateメソッドを呼び出します。これにより変化したサイズに合わせて、以降のフレームがキャプチャされるようになります。


戻る

⚠️ **GitHub.com Fallback** ⚠️