ビデオに画面の取り込み

この記事では、Windows.Graphics.Capture API を使用して画面からキャプチャされたフレームをビデオ ファイルにエンコードするメソッドについて説明します。 静止画像の画面キャプチャの詳細については、「画面キャプチャ」を参照してください。 この記事に示す概念と手法を利用する簡単なエンド ツー エンドのサンプル アプリについては、「SimpleRecorder」を参照してください。

ビデオ キャプチャ プロセスの概要

この記事では、ウィンドウの内容をビデオ ファイルに記録するサンプル アプリのチュートリアルを提供します。 このシナリオを実装するために必要なコードが多いように思えるかもしれませんが、スクリーン レコーダー アプリの高レベルの構造は非常に単純です。 画面キャプチャ プロセスでは、次の 3 つの主要な UWP 機能が使用されます。

  • Windows.GraphicsCapture API は、実際に画面からピクセルを取り込む作業を行います。 GraphicsCaptureItem クラスは、キャプチャされるウィンドウまたはディスプレイを表します。 GraphicsCaptureSession は、キャプチャ操作を開始および停止するために使用されます。 Direct3D11CaptureFramePool クラスは、画面の内容がコピーされるフレームのバッファーを保持します。
  • MediaStreamSource クラスは、キャプチャされたフレームを受け取り、ビデオ ストリームを生成します。
  • MediaTranscoder クラスは、MediaStreamSource によって生成されたストリームを受け取り、ビデオ ファイルにエンコードします。

この記事に示すコード例は、いくつかの異なるタスクに分類できます。

  • 初期化 - これには、前述の UWP クラスの構成、グラフィックス デバイス インターフェイスの初期化、キャプチャするウィンドウの選択、解像度やフレーム レートなどのエンコード パラメーターの設定が含まれます。
  • イベント ハンドラーとスレッド処理 - メイン キャプチャ ループの主要なドライバーは、SampleRequested イベントを通じてフレームを定期的に要求する MediaStreamSource です。 この例では、イベントを使用して、例のさまざまなコンポーネント間で新しいフレームの要求を調整します。 フレームを同時にキャプチャおよびエンコードするには、同期が重要です。
  • フレームのコピー - フレームはキャプチャ フレーム バッファーから別の Direct3D サーフェスにコピーされ、エンコード中にリソースが上書きされないように MediaStreamSource に渡すことができます。 Direct3D API は、このコピー操作を迅速に実行するために使用されます。

Direct3D API について

上で説明したように、キャプチャされた各フレームのコピーは、おそらくこの記事で示す実装の最も複雑な部分です。 低レベルでは、この操作は Direct3D を使用して行われます。 この例では、SharpDX ライブラリを使用して C# から Direct3D 操作を実行します。 このライブラリは正式にサポートされなくなりましたが、低レベルのコピー操作でのパフォーマンスがこのシナリオに適しているため、選びました。 Direct3D 操作を可能な限り個別に保ち、これらのタスクに独自のコードや他のライブラリを置き換えやすくするよう試みてきました。

プロジェクトの設定

このチュートリアルのコード例は、Visual Studio 2019 の 空白のアプリ (ユニバーサル Windows) C# プロジェクト テンプレートを使用して作成しました。 アプリで Windows.Graphics.Capture API を使用するには、プロジェクトの Package.appxmanifest ファイルにグラフィックス キャプチャ機能を含める必要があります。 この例では、生成されたビデオ ファイルをデバイスのビデオ ライブラリに保存します。 このフォルダーにアクセスするには、ビデオ ライブラリ機能を含める必要があります。

SharpDX Nuget パッケージをインストールするには、Visual Studio で [Nuget パッケージの管理] を選択します。 [参照] タブで、"SharpDX.Direct3D11" パッケージを検索し、[インストール] をクリックします。

この記事のコード リストのサイズを小さくするために、以下のチュートリアルのコードでは、明示的な名前空間参照と、先頭にアンダースコア "_" を使用して名前が付けられた MainPage クラス メンバー変数の宣言が省略されています。

エンコードを設定する

このセクションで説明する SetupEncoding メソッドによって、ビデオ フレームのキャプチャとエンコードに使用される主なオブジェクトの一部が初期化され、キャプチャされたビデオのエンコード パラメーターが設定されます。 このメソッドは、プログラムで呼び出したり、ボタン クリックのようなユーザーの操作に応じて呼び出したりすることもできます。 SetupEncoding のコード リストは、初期化手順の説明の後で示します。

  • キャプチャのサポートを確認します。 キャプチャ プロセスを開始する前に GraphicsCaptureSession.IsSupported を呼び出して、現在のデバイスで画面キャプチャ機能がサポートされている必要があります。

  • Direct3D インターフェイスを初期化します。 このサンプルでは、Direct3D を使用して、画面からキャプチャされたピクセルをビデオ フレームとしてエンコードされたテクスチャにコピーします。 Direct3D インターフェイスの初期化に使用されるヘルパー メソッド CreateD3DDeviceCreateSharpDXDevice は、この記事の後半で説明します。

  • GraphicsCaptureItem を初期化します。 GraphicsCaptureItem は、ウィンドウまたは画面全体でキャプチャされる画面上の項目を表します。 GraphicsCapturePicker を作成し、PickSingleItemAsync を呼び出すことによって、ユーザーがキャプチャする項目を選択できるようにします。

  • コンポジション テクスチャを作成します。 テクスチャ リソースと、各ビデオ フレームのコピーに使用される、関連付けられたレンダー ターゲット ビューを作成します。 このテクスチャは GraphicsCaptureItem が作成され、そのディメンションがわかるまで作成できません。 このコンポジション テクスチャの使用方法については、WaitForNewFrame の説明を参照してください。 このテクスチャを作成するためのヘルパー メソッドも、この記事の後半で説明します。

  • MediaEncodingProfile と VideoStreamDescriptor を作成します。 MediaStreamSource クラスのインスタンスは、画面からキャプチャされた画像を取得し、ビデオ ストリームにエンコードします。 その後、MediaTranscoder クラスによってビデオ ストリームがビデオ ファイルにトランスコードされます。 VideoStreamDecriptor は、MediaStreamSource に対して解像度やフレーム レートなどのエンコード パラメーターを提供します。 MediaTranscoder のビデオ ファイル エンコード パラメーターは、MediaEncodingProfile で指定されます。 ビデオ エンコードに使用されるサイズは、キャプチャされるウィンドウのサイズと同じである必要はありません。ただし、この例を単純に保つため、キャプチャ項目の実際のディメンションを使用するためにエンコード設定はハードコーディングされます。

  • MediaStreamSource および MediaTranscoder オブジェクトを作成します。 前述のように、MediaStreamSource オブジェクトは個々のフレームをビデオ ストリームにエンコードします。 このクラスのコンストラクターを呼び出し、前の手順で作成した MediaEncodingProfile を渡します。 バッファー時間を 0 に設定し Starting および SampleRequested イベントのハンドラーを登録します。これは、この記事の後半で説明します。 次に、MediaTranscoder クラスの新しいインスタンスを構築し、ハードウェア アクセラレーションを有効にします。

  • 出力ファイルを作成する このメソッドの最後の手順では、ビデオのトランスコードを行うファイルを作成します。 この例では、デバイスの [ビデオ ライブラリ] フォルダーに一意の名前のファイルを作成します。 このフォルダーにアクセスするには、アプリ マニフェストで "ビデオ ライブラリ" 機能を指定する必要があります。 ファイルが作成された後、読み取りおよび書き込みのためにファイルを開き、次に示す EncodeAsync メソッドに結果のストリームを渡します。

private async Task SetupEncoding()
{
    if (!GraphicsCaptureSession.IsSupported())
    {
        // Show message to user that screen capture is unsupported
        return;
    }

    // Create the D3D device and SharpDX device
    if (_device == null)
    {
        _device = Direct3D11Helpers.CreateD3DDevice();
    }
    if (_sharpDxD3dDevice == null)
    {
        _sharpDxD3dDevice = Direct3D11Helpers.CreateSharpDXDevice(_device);
    }
    


    try
    {
        // Let the user pick an item to capture
        var picker = new GraphicsCapturePicker();
        _captureItem = await picker.PickSingleItemAsync();
        if (_captureItem == null)
        {
            return;
        }

        // Initialize a blank texture and render target view for copying frames, using the same size as the capture item
        _composeTexture = Direct3D11Helpers.InitializeComposeTexture(_sharpDxD3dDevice, _captureItem.Size);
        _composeRenderTargetView = new SharpDX.Direct3D11.RenderTargetView(_sharpDxD3dDevice, _composeTexture);

        // This example encodes video using the item's actual size.
        var width = (uint)_captureItem.Size.Width; 
        var height = (uint)_captureItem.Size.Height;

        // Make sure the dimensions are are even. Required by some encoders.
        width = (width % 2 == 0) ? width : width + 1;
        height = (height % 2 == 0) ? height : height + 1;


        var temp = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);
        var bitrate = temp.Video.Bitrate;
        uint framerate = 30;

        _encodingProfile = new MediaEncodingProfile();
        _encodingProfile.Container.Subtype = "MPEG4";
        _encodingProfile.Video.Subtype = "H264";
        _encodingProfile.Video.Width = width;
        _encodingProfile.Video.Height = height;
        _encodingProfile.Video.Bitrate = bitrate;
        _encodingProfile.Video.FrameRate.Numerator = framerate;
        _encodingProfile.Video.FrameRate.Denominator = 1;
        _encodingProfile.Video.PixelAspectRatio.Numerator = 1;
        _encodingProfile.Video.PixelAspectRatio.Denominator = 1;

        var videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, width, height);
        _videoDescriptor = new VideoStreamDescriptor(videoProperties);

        // Create our MediaStreamSource
        _mediaStreamSource = new MediaStreamSource(_videoDescriptor);
        _mediaStreamSource.BufferTime = TimeSpan.FromSeconds(0);
        _mediaStreamSource.Starting += OnMediaStreamSourceStarting;
        _mediaStreamSource.SampleRequested += OnMediaStreamSourceSampleRequested;

        // Create our transcoder
        _transcoder = new MediaTranscoder();
        _transcoder.HardwareAccelerationEnabled = true;


        // Create a destination file - Access to the VideosLibrary requires the "Videos Library" capability
        var folder = KnownFolders.VideosLibrary;
        var name = DateTime.Now.ToString("yyyyMMdd-HHmm-ss");
        var file = await folder.CreateFileAsync($"{name}.mp4");
        
        using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))

        await EncodeAsync(stream);
        
    }
    catch (Exception ex)
    {
        
        return;
    }
}

エンコードを開始する

メイン オブジェクトが初期化されたので、EncodeAsync メソッドが実装され、キャプチャ操作が開始されます。 このメソッドは最初に、まだ記録していないか確認します。記録されていない場合は、ヘルパー メソッド StartCapture を呼び出して、画面からのフレームのキャプチャを開始します。 このメソッドは、この記事の後半で説明します。 次に、PrepareMediaStreamSourceTranscodeAsync が呼び出され、前のセクションで作成したエンコード プロファイルを使用して、MediaStreamSource オブジェクトによって生成されたビデオ ストリームを出力ファイル ストリームにトランスコードする準備が整った MediaTranscoder が取得されます。 トランスコーダーの準備が完了したら、TranscodeAsync を呼び出してトランスコードを開始します。 MediaTranscoder の使用の詳細については、「メディア ファイルをトランスコードする」を参照してください。


private async Task EncodeAsync(IRandomAccessStream stream)
{
    if (!_isRecording)
    {
        _isRecording = true;

        StartCapture();

        var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, _encodingProfile);

        await transcode.TranscodeAsync();
    }
}

MediaStreamSource イベントを処理する

MediaStreamSource オブジェクトは、画面からキャプチャしたフレームを受け取り、MediaTranscoder を使用してファイルに保存できるビデオ ストリームに変換します。 オブジェクトのイベントのハンドラーを介してフレームを MediaStreamSource に渡します。

SampleRequested イベントは、MediaStreamSource で新しいビデオ フレームの準備ができているときに発生します。 現在記録中であることを確認した後、ヘルパー メソッド WaitForNewFrame が呼び出され、画面からキャプチャされた新しいフレームが取得されます。 このメソッドは、この記事の後半で示すように、キャプチャされたフレームを含む ID3D11Surface オブジェクトを返します。 この例では、フレームがキャプチャされたシステム時刻も格納するヘルパー クラスに IDirect3DSurface インターフェイスをラップします。 フレームとシステム時刻の両方が MediaStreamSample.CreateFromDirect3D11Surface ファクトリ メソッドに渡され、結果として得られる MediaStreamSampleMediaStreamSourceSampleRequestedEventArgsMediaStreamSourceSampleRequest.Sample プロパティに設定されます。 これは、キャプチャされたフレームが MediaStreamSource に提供される方法です。

private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
    if (_isRecording && !_closed)
    {
        try
        {
            using (var frame = WaitForNewFrame())
            {
                if (frame == null)
                {
                    args.Request.Sample = null;
                    Stop();
                    Cleanup();
                    return;
                }

                var timeStamp = frame.SystemRelativeTime;

                var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
                args.Request.Sample = sample;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            Debug.WriteLine(e.StackTrace);
            Debug.WriteLine(e);
            args.Request.Sample = null;
            Stop();
            Cleanup();
        }
    }
    else
    {
        args.Request.Sample = null;
        Stop();
        Cleanup();
    }
}

Starting イベントのハンドラーでは、WaitForNewFrame を呼び出しますが、フレームがキャプチャされたシステム時刻のみを MediaStreamSourceStartingRequest.SetActualStartPosition メソッドに渡します。これは、MediaStreamSource が後続のフレームのタイミングを適切にエンコードするために使用されます。

private void OnMediaStreamSourceStarting(MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)
{
    using (var frame = WaitForNewFrame())
    {
        args.Request.SetActualStartPosition(frame.SystemRelativeTime);
    }
}

キャプチャを開始する

この手順で示した StartCapture メソッドは、前の手順で示した EncodeAsync ヘルパー メソッドから呼び出されます。 まず、このメソッドは、キャプチャ操作のフローを制御するために使用される一連のイベント オブジェクトを初期化します。

  • _multithread は、SharpDX ライブラリの Multithread オブジェクトをラップするヘルパー クラスです。これは、コピー中に他のスレッドが SharpDX テクスチャにアクセスしないようにするために使用されます。
  • _frameEvent では、新しいフレームがキャプチャされ MediaStreamSource に渡される可能性があると通知するために使用されます
  • _closedEvent では、記録が停止し、新しいフレームを待つ必要はないことを通知します。

フレーム イベントと closed イベントが配列に追加され、キャプチャ ループ内のいずれかのイベントを待機できます。

StartCapture メソッドの残りの部分では、実際の画面キャプチャを実行する Windows.Graphics.Capture API が設定されます。 最初に、CaptureItem.Closed イベントにイベントが登録されます。 次に、Direct3D11CaptureFramePool が作成されます。これにより、キャプチャされた複数のフレームを一度にバッファー処理できます。 CreateFreeThreaded メソッドを使用してフレーム プールを作成し、FrameArrived イベントがアプリのメイン スレッドではなく、プールの独自のワーカー スレッドで呼び出されます。 次に、FrameArrived イベントのハンドラーが登録されます。 最後に、選択した CaptureItem に対して GraphicsCaptureSession が作成され、フレームのキャプチャは StartCapture を呼び出すことによって開始されます。

public void StartCapture()
{

    _multithread = _sharpDxD3dDevice.QueryInterface<SharpDX.Direct3D11.Multithread>();
    _multithread.SetMultithreadProtected(true);
    _frameEvent = new ManualResetEvent(false);
    _closedEvent = new ManualResetEvent(false);
    _events = new[] { _closedEvent, _frameEvent };

    _captureItem.Closed += OnClosed;
    _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
        _device,
        DirectXPixelFormat.B8G8R8A8UIntNormalized,
        1,
        _captureItem.Size);
    _framePool.FrameArrived += OnFrameArrived;
    _session = _framePool.CreateCaptureSession(_captureItem);
    _session.StartCapture();
}

グラフィックス キャプチャ イベントを処理する

前の手順では、グラフィックス キャプチャ イベント用に 2 つのハンドラーを登録し、キャプチャ ループのフローを管理するためにいくつかのイベントを設定しました。

FrameArrived イベントは、Direct3D11CaptureFramePool に使用可能な新しいキャプチャ フレームがある場合に発生します。 このイベントのハンドラーで、送信側の TryGetNextFrame を呼び出して、次のキャプチャされたフレームを取得します。 フレームを取得した後、使用可能な新しいフレームがキャプチャ ループに認識されるように _frameEvent を設定します。

private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
    _currentFrame = sender.TryGetNextFrame();
    _frameEvent.Set();
}

Closed イベント ハンドラーでは、停止するタイミングをキャプチャ ループが認識できるように、_closedEvent を通知します。

private void OnClosed(GraphicsCaptureItem sender, object args)
{
    _closedEvent.Set();
}

新しいフレームを待機する

このセクションで説明する WaitForNewFrame ヘルパー メソッドは、キャプチャ ループの負荷が発生する場所です。 このメソッドは、MediaStreamSource がビデオ ストリームに新しいフレームを追加する準備が整うと常に、OnMediaStreamSourceSampleRequested イベント ハンドラーから呼び出されます。 大まかに言えば、この関数は、新しいフレームがキャプチャされている間にエンコードのために MediaStreamSource に渡すことができるように、画面からキャプチャされた各ビデオ フレームを 1 つの Direct3D サーフェイスから別の Direct3D サーフェイスにコピーするだけです。 この例では、SharpDX ライブラリを使用して、実際のコピー操作を実行します。

新しいフレームを待機する前に、メソッドは、クラス変数に格納されている前のフレーム _currentFrame を破棄し、_frameEvent をリセットします。 次に、メソッドは、_frameEvent または _closedEvent が通知されるのを待機します。 Closed イベントが設定されている場合、アプリは、キャプチャ リソースをクリーンアップするヘルパー メソッドを呼び出します。 このメソッドは、この記事の後半で説明します。

フレーム イベントが設定されている場合は、前の手順で定義されている FrameArrived イベント ハンドラーが呼び出されたことがわかります。MediaStreamSource に渡される Direct3D 11 サーフェイスにキャプチャされたフレーム データをコピーする処理が開始されます。

この例では、ヘルパー クラス SurfaceWithInfo を使用しています。これにより、ビデオ フレームとフレームのシステム時刻を (どちらも MediaStreamSource で必要) 1 つのオブジェクトとして渡すことができます。 フレームのコピー処理の最初の手順は、このクラスをインスタンス化し、システム時刻を設定することです。

次の手順は、特に SharpDX ライブラリに依存するこの例の一部です。 ここで使用されているヘルパー関数は、この記事の最後に定義されています。 まず、MultiThreadLock を使用して、コピーの作成中に他のスレッドがビデオ フレーム バッファーにアクセスしないようにします。 次に、ヘルパー メソッド CreateSharpDXTexture2D を呼び出して、ビデオ フレームから SharpDX Texture2D オブジェクトを作成します。 これはコピー操作のソース テクスチャになります。

次に、前の手順で作成した Texture2D オブジェクトから、プロセスで前に作成したコンポジション テクスチャにコピーします。 エンコード プロセスで次のフレームがキャプチャされている間にピクセルの操作ができるようにするために、このコンポジション テクスチャはスワップ バッファーとして機能します。 コピーを実行するには、コンポジション テクスチャに関連付けられているレンダー ターゲット ビューをクリアし、コピーするテクスチャ内の領域 (この場合はテクスチャ全体) を定義します。次に、CopySubresourceRegion を呼び出して、ピクセルをコンポジション テクスチャに実際にコピーします。

ターゲット テクスチャを作成するときに使用するテクスチャの説明のコピーを作成しますが、説明を変更して、新しいテクスチャに書き込みアクセスが許可されるように BindFlagsRenderTarget に設定します。 CpuAccessFlagsNone に設定すると、システムはコピー操作を最適化できます。 テクスチャの説明は、新しいテクスチャ リソースを作成するために使用され、CopyResource を呼び出すことによって、コンポジション テクスチャ リソースがこの新しいリソースにコピーされます。 最後に、CreateDirect3DSurfaceFromSharpDXTexture を呼び出して、このメソッドから返される IDirect3DSurface オブジェクトを作成します。

public SurfaceWithInfo WaitForNewFrame()
{
    // Let's get a fresh one.
    _currentFrame?.Dispose();
    _frameEvent.Reset();

    var signaledEvent = _events[WaitHandle.WaitAny(_events)];
    if (signaledEvent == _closedEvent)
    {
        Cleanup();
        return null;
    }

    var result = new SurfaceWithInfo();
    result.SystemRelativeTime = _currentFrame.SystemRelativeTime;
    using (var multithreadLock = new MultithreadLock(_multithread))
    using (var sourceTexture = Direct3D11Helpers.CreateSharpDXTexture2D(_currentFrame.Surface))
    {

        _sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(_composeRenderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));

        var width = Math.Clamp(_currentFrame.ContentSize.Width, 0, _currentFrame.Surface.Description.Width);
        var height = Math.Clamp(_currentFrame.ContentSize.Height, 0, _currentFrame.Surface.Description.Height);
        var region = new SharpDX.Direct3D11.ResourceRegion(0, 0, 0, width, height, 1);
        _sharpDxD3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, _composeTexture, 0);

        var description = sourceTexture.Description;
        description.Usage = SharpDX.Direct3D11.ResourceUsage.Default;
        description.BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget;
        description.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None;
        description.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;

        using (var copyTexture = new SharpDX.Direct3D11.Texture2D(_sharpDxD3dDevice, description))
        {
            _sharpDxD3dDevice.ImmediateContext.CopyResource(_composeTexture, copyTexture);
            result.Surface = Direct3D11Helpers.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture);
        }
    }

    return result;
}

キャプチャの停止とリソースのクリーンアップ

Stop メソッドは、キャプチャ操作を停止する方法を提供します。 アプリではこれをプログラムで呼び出したり、ボタン クリックのようなユーザー操作に応じて呼び出したりすることもできます。 このメソッドは、単に _closedEvent を設定します。 前の手順で定義した WaitForNewFrame メソッドは、このイベントを検索します。設定されている場合は、キャプチャ操作をシャットダウンします。

private void Stop()
{
    _closedEvent.Set();
}

Cleanup メソッドは、コピー操作中に作成されたリソースを適切に破棄するために使用されます。 これには、以下のことが含まれます。

  • キャプチャ セッションによって使用される Direct3D11CaptureFramePool オブジェクト
  • GraphicsCaptureSessionGraphicsCaptureItem
  • Direct3D および SharpDX デバイス
  • コピー操作で使用される SharpDX テクスチャとレンダー ターゲット ビュー。
  • 現在のフレームを格納するために使用される Direct3D11CaptureFrame
private void Cleanup()
{
    _framePool?.Dispose();
    _session?.Dispose();
    if (_captureItem != null)
    {
        _captureItem.Closed -= OnClosed;
    }
    _captureItem = null;
    _device = null;
    _sharpDxD3dDevice = null;
    _composeTexture?.Dispose();
    _composeTexture = null;
    _composeRenderTargetView?.Dispose();
    _composeRenderTargetView = null;
    _currentFrame?.Dispose();
}

ヘルパー ラッパー クラス

次のヘルパー クラスは、この記事のコード例を参照するために定義されています。

MultithreadLock ヘルパー クラスは、コピー中に他のスレッドがテクスチャ リソースにアクセスしないようにする SharpDX Multithread クラスをラップします。

class MultithreadLock : IDisposable
{
    public MultithreadLock(SharpDX.Direct3D11.Multithread multithread)
    {
        _multithread = multithread;
        _multithread?.Enter();
    }

    public void Dispose()
    {
        _multithread?.Leave();
        _multithread = null;
    }

    private SharpDX.Direct3D11.Multithread _multithread;
}

SurfaceWithInfo は、キャプチャされたフレームとキャプチャされた時間を表す SystemRelativeTimeIDirect3DSurface を関連付けるために使用されます。

public sealed class SurfaceWithInfo : IDisposable
{
    public IDirect3DSurface Surface { get; internal set; }
    public TimeSpan SystemRelativeTime { get; internal set; }

    public void Dispose()
    {
        Surface?.Dispose();
        Surface = null;
    }
}

Direct3D と SharpDX ヘルパー API

次のヘルパー API は、Direct3D と SharpDX リソースの作成を抽象化するために定義されています。 これらのテクノロジの詳細については、この記事では説明しませんが、このチュートリアルで示したコード例を実装できるように、ここでコードを提供します。

[ComImport]
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
interface IDirect3DDxgiInterfaceAccess
{
    IntPtr GetInterface([In] ref Guid iid);
};

public static class Direct3D11Helpers
{
    internal static Guid IInspectable = new Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
    internal static Guid ID3D11Resource = new Guid("dc8e63f3-d12b-4952-b47b-5e45026a862d");
    internal static Guid IDXGIAdapter3 = new Guid("645967A4-1392-4310-A798-8053CE3E93FD");
    internal static Guid ID3D11Device = new Guid("db6f6ddb-ac77-4e88-8253-819df9bbf140");
    internal static Guid ID3D11Texture2D = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c");

    [DllImport(
        "d3d11.dll",
        EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
        SetLastError = true,
        CharSet = CharSet.Unicode,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall
        )]
    internal static extern UInt32 CreateDirect3D11DeviceFromDXGIDevice(IntPtr dxgiDevice, out IntPtr graphicsDevice);

    [DllImport(
        "d3d11.dll",
        EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
        SetLastError = true,
        CharSet = CharSet.Unicode,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall
        )]
    internal static extern UInt32 CreateDirect3D11SurfaceFromDXGISurface(IntPtr dxgiSurface, out IntPtr graphicsSurface);

    public static IDirect3DDevice CreateD3DDevice()
    {
        return CreateD3DDevice(false);
    }

    public static IDirect3DDevice CreateD3DDevice(bool useWARP)
    {
        var d3dDevice = new SharpDX.Direct3D11.Device(
            useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
            SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
        IDirect3DDevice device = null;

        // Acquire the DXGI interface for the Direct3D device.
        using (var dxgiDevice = d3dDevice.QueryInterface<SharpDX.DXGI.Device3>())
        {
            // Wrap the native device using a WinRT interop object.
            uint hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.NativePointer, out IntPtr pUnknown);

            if (hr == 0)
            {
                device = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DDevice;
                Marshal.Release(pUnknown);
            }
        }

        return device;
    }


    internal static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
    {
        IDirect3DSurface surface = null;

        // Acquire the DXGI interface for the Direct3D surface.
        using (var dxgiSurface = texture.QueryInterface<SharpDX.DXGI.Surface>())
        {
            // Wrap the native device using a WinRT interop object.
            uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out IntPtr pUnknown);

            if (hr == 0)
            {
                surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
                Marshal.Release(pUnknown);
            }
        }

        return surface;
    }



    internal static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
    {
        var access = (IDirect3DDxgiInterfaceAccess)device;
        var d3dPointer = access.GetInterface(ID3D11Device);
        var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
        return d3dDevice;
    }

    internal static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
    {
        var access = (IDirect3DDxgiInterfaceAccess)surface;
        var d3dPointer = access.GetInterface(ID3D11Texture2D);
        var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
        return d3dSurface;
    }


    public static SharpDX.Direct3D11.Texture2D InitializeComposeTexture(
        SharpDX.Direct3D11.Device sharpDxD3dDevice,
        SizeInt32 size)
    {
        var description = new SharpDX.Direct3D11.Texture2DDescription
        {
            Width = size.Width,
            Height = size.Height,
            MipLevels = 1,
            ArraySize = 1,
            Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
            SampleDescription = new SharpDX.DXGI.SampleDescription()
            {
                Count = 1,
                Quality = 0
            },
            Usage = SharpDX.Direct3D11.ResourceUsage.Default,
            BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget,
            CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
            OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None
        };
        var composeTexture = new SharpDX.Direct3D11.Texture2D(sharpDxD3dDevice, description);
       

        using (var renderTargetView = new SharpDX.Direct3D11.RenderTargetView(sharpDxD3dDevice, composeTexture))
        {
            sharpDxD3dDevice.ImmediateContext.ClearRenderTargetView(renderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
        }

        return composeTexture;
    }
}

こちらもご覧ください