Windows Graphics Capture APIを活用したデスクトップ画面キャプチャ実装

Windows Graphics Capture APIの利用

Windows 10以降のグラフィックスキャプチャ機能を活用したデスクトップ画面のキャプチャ実装を示します。本実装はWindows Graphics Capture APIを基盤とし、SharpDXとDirect3D11を介して画面データを取得します。

基本インターフェース定義

internal interface IScreenCaptureApi : IDisposable
{
    PixelFormat OutputFormat { get; set; }
    Size OutputResolution { get; set; }
    event EventHandler<FrameData> NewFrameAvailable;
    void Start();
    void Stop();
    bool TryGetNextFrame(out FrameData frame);
    CaptureSource SourceType { get; }
    Size SourceResolution { get; }
}

抽象クラス実装

public abstract class ScreenCaptureBase : IScreenCaptureApi, IDisposable
{
    protected PixelFormat OutputFormat = PixelFormat.Bgra32;
    protected Size OutputResolution;
    protected GraphicsCaptureItem TargetItem;
    protected CaptureSource SourceType;
    protected Size SourceResolution;

    protected Device d3dDevice;
    protected Direct3D11CaptureFramePool framePool;
    protected Texture2D frameBuffer;
    protected GraphicsCaptureSession captureSession;

    protected void Initialize(GraphicsCaptureItem item, bool subscribeEvents)
    {
        if (!GraphicsCaptureSession.IsSupported())
            throw new PlatformNotSupportedException("WGC API未サポート");

        TargetItem = item;
        d3dDevice = CreateD3DDevice();
        framePool = Direct3D11CaptureFramePool.Create(
            d3dDevice,
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            2,
            item.Size);
        
        captureSession = framePool.CreateCaptureSession(item);
        SourceResolution = new Size(item.Size.Width, item.Size.Height);
        
        if (subscribeEvents)
            framePool.FrameArrived += OnFrameArrived;
        
        item.Closed += OnItemClosed;
    }

    private void OnItemClosed(GraphicsCaptureItem sender, object args)
    {
        framePool.FrameArrived -= OnFrameArrived;
        Stop();
        ItemClosed?.Invoke(this, sender);
    }

    public void Start() => captureSession.StartCapture();
    
    public void Stop()
    {
        captureSession.Dispose();
        framePool.Dispose();
        frameBuffer?.Dispose();
        d3dDevice?.Dispose();
    }
    
    private byte[] ProcessFrame(Direct3D11CaptureFrame frame)
    {
        var contentSize = frame.ContentSize;
        if (contentSize.Width != frameBuffer.Description.Width || 
            contentSize.Height != frameBuffer.Description.Height)
        {
            frameBuffer.Dispose();
            frameBuffer = CreateTexture2D(d3dDevice, contentSize);
        }

        var texture = Direct3D11Helper.CreateTexture2D(frame.Surface);
        d3dDevice.ImmediateContext.CopyResource(texture, frameBuffer);
        
        var mapped = d3dDevice.ImmediateContext.MapSubresource(
            frameBuffer, 0, MapMode.Read, MapFlags.None);
        
        var pixelSize = OutputFormat == PixelFormat.Bgra32 ? 4 : 3;
        var buffer = new byte[OutputResolution.Width * OutputResolution.Height * pixelSize];
        
        if (OutputResolution != SourceResolution)
        {
            var resized = new Mat(SourceResolution.Height, SourceResolution.Width, MatType.CV_8UC4);
            Cv2.Resize(mapped, resized, OutputResolution);
            Marshal.Copy(resized.Data, buffer, 0, buffer.Length);
        }
        else
        {
            for (var y = 0; y < contentSize.Height; y++)
            {
                var srcOffset = y * mapped.RowPitch;
                var destOffset = y * OutputResolution.Width * pixelSize;
                Marshal.Copy(mapped.DataPointer + srcOffset, buffer, destOffset, OutputResolution.Width * pixelSize);
            }
        }
        
        d3dDevice.ImmediateContext.UnmapSubresource(frameBuffer, 0);
        return buffer;
    }
    
    private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
    {
        if (TryGetNextFrame(out var frameData))
            NewFrameAvailable?.Invoke(this, frameData);
    }
}

モニタキャプチャの実装

internal class MonitorCapture : ScreenCaptureBase
{
    public MonitorCapture(IntPtr monitorHandle)
    {
        SourceType = CaptureSource.Screen;
        var item = ScreenCaptureHelper.CreateForMonitor(monitorHandle);
        Initialize(item, true);
        OutputResolution = SourceResolution;
    }
}

使用例

var monitor = MonitorHelper.GetPrimaryMonitor();
var capture = new MonitorCapture(monitor.Handle);
capture.OutputFormat = PixelFormat.Bgra32;
capture.Start();
capture.NewFrameAvailable += (s, e) => {
    // フレームデータ処理
    ProcessFrameData(e.Data);
};

必須設定

本実装にはWindows 10 22000以降のAPIが必要です。プロジェクトファイルで以下を指定してください:

<TargetFrameworks>net6.0-windows10.0.22000.0;net48</TargetFrameworks>

依存パッケージ

<ItemGroup>
  <PackageReference Include="SharpDX" Version="4.2.0" />
  <PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
  <PackageReference Include="OpenCvSharp4.Windows" Version="4.8.0.20230708" />
</ItemGroup>

タグ: Windows.Graphics.Capture SharpDX Direct3D11 ScreenCapture DirectX

8月6日 23:43 投稿