Windows 10 バージョン 1803 以降では、Windows.Graphics.Capture 名前空間は、ディスプレイまたはアプリケーション ウィンドウからフレームを取得する API を提供し、ビデオ ストリームやスナップショットを作成してコラボレーションと対話型のエクスペリエンスを構築します。
画面キャプチャを使用すると、開発者はセキュリティで保護されたシステム UI を呼び出して、エンド ユーザーがキャプチャする表示ウィンドウまたはアプリケーション ウィンドウを選択し、アクティブにキャプチャされた項目の周囲に黄色の通知境界線がシステムによって描画されます。 複数の同時キャプチャ セッションの場合、キャプチャされる各項目の周囲に黄色の境界線が描画されます。
注
画面キャプチャ API は、Windows デバイスと Windows Mixed Reality イマーシブ ヘッドセットでのみサポートされます。
この記事では、表示ウィンドウまたはアプリケーション ウィンドウの 1 つの画像をキャプチャする方法について説明します。 画面からビデオ ファイルにキャプチャされたフレームをエンコードする方法については、「画面からビデオへのキャプチャ」を参照
画面キャプチャ機能を追加する
Windows.Graphics.Capture 名前空間にある API は、アプリケーションのマニフェストで一般的な機能を宣言する必要があります。
- Package.appxmanifest を ソリューション エクスプローラーで開きます。
- 機能タブ を選択します。
- グラフィックスキャプチャを確認します。
システム UI を起動して画面キャプチャを開始する
システム UI を起動する前に、アプリケーションが現在画面キャプチャを実行できるかどうかを確認できます。 デバイスがハードウェア要件を満たしていない場合や、キャプチャ ブロックの画面キャプチャを対象とするアプリケーションなど、アプリケーションで画面キャプチャを使用できない理由はいくつかあります。 GraphicsCaptureSession クラスの IsSupported メソッドを使用して、UWP 画面キャプチャがサポートされているかどうかを確認します。
// This runs when the application starts.
public void OnInitialization()
{
if (!GraphicsCaptureSession.IsSupported())
{
// Hide the capture UI if screen capture is not supported.
CaptureButton.Visibility = Visibility.Collapsed;
}
}
Public Sub OnInitialization()
If Not GraphicsCaptureSession.IsSupported Then
CaptureButton.Visibility = Visibility.Collapsed
End If
End Sub
画面キャプチャがサポートされていることを確認したら、GraphicsCapturePicker クラスを使用してシステム ピッカー UI を呼び出します。 エンド ユーザーはこの UI を使用して、画面キャプチャを取得する表示ウィンドウまたはアプリケーション ウィンドウを選択します。 ピッカーは、GraphicsCaptureSessionの作成に使用される GraphicsCaptureItem を返します。
public async Task StartCaptureAsync()
{
// The GraphicsCapturePicker follows the same pattern the
// file pickers do.
var picker = new GraphicsCapturePicker();
GraphicsCaptureItem item = await picker.PickSingleItemAsync();
// The item may be null if the user dismissed the
// control without making a selection or hit Cancel.
if (item != null)
{
// We'll define this method later in the document.
StartCaptureInternal(item);
}
}
Public Async Function StartCaptureAsync() As Task
' The GraphicsCapturePicker follows the same pattern the
' file pickers do.
Dim picker As New GraphicsCapturePicker
Dim item As GraphicsCaptureItem = Await picker.PickSingleItemAsync()
' The item may be null if the user dismissed the
' control without making a selection or hit Cancel.
If item IsNot Nothing Then
StartCaptureInternal(item)
End If
End Function
これは UI コードであるため、UI スレッドで呼び出す必要があります。 アプリケーションのページ (MainPage.xaml.csなど) の分離コードから呼び出す場合、これは自動的に行われますが、呼び出さない場合は、次のコードを使用して UI スレッドで強制的に実行できます。
CoreWindow window = CoreApplication.MainView.CoreWindow;
await window.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await StartCaptureAsync();
});
Dim window As CoreWindow = CoreApplication.MainView.CoreWindow
Await window.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
Async Sub() Await StartCaptureAsync())
キャプチャ フレーム プールとキャプチャ セッションを作成する
GraphicsCaptureItemを使用すると、D3D デバイス、サポートされているピクセル形式 (DXGI_FORMAT_B8G8R8A8_UNORM)、目的のフレーム数 (任意の整数)、フレーム サイズを使用して、Direct3D11CaptureFramePool を作成します。 GraphicsCaptureItem クラスの ContentSize プロパティは、フレームのサイズとして使用できます。
注
Windows HD カラーが有効になっているシステムでは、コンテンツ ピクセル形式が必ずしも DXGI_FORMAT_B8G8R8A8_UNORMとは限りません。 HDR コンテンツをキャプチャするときにピクセル オーバークリップ (つまり、キャプチャされたコンテンツが洗い流される) を回避するには、Direct3D11CaptureFramePool、CanvasBitmapなどのターゲット変換先など、キャプチャ パイプライン内のすべてのコンポーネントに DXGI_FORMAT_R16G16B16A16_FLOAT を使用することを検討してください。 必要に応じて、HDR コンテンツ形式への保存や HDR から SDR へのトーン マッピングなどの追加処理が必要になる場合があります。 この記事では、SDR コンテンツのキャプチャに焦点を当てます。 詳細については、「ダイナミック レンジディスプレイと高度なカラーで DirectX を使用する」を参照してください。
private GraphicsCaptureItem _item;
private Direct3D11CaptureFramePool _framePool;
private CanvasDevice _canvasDevice;
private GraphicsCaptureSession _session;
public void StartCaptureInternal(GraphicsCaptureItem item)
{
_item = item;
_framePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, // D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, // Pixel format
2, // Number of frames
_item.Size); // Size of the buffers
}
WithEvents CaptureItem As GraphicsCaptureItem
WithEvents FramePool As Direct3D11CaptureFramePool
Private _canvasDevice As CanvasDevice
Private _session As GraphicsCaptureSession
Private Sub StartCaptureInternal(item As GraphicsCaptureItem)
CaptureItem = item
FramePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, ' D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, ' Pixel format
2, ' Number of frames
CaptureItem.Size) ' Size of the buffers
End Sub
次に、GraphicsCaptureItem を CreateCaptureSession メソッドに渡して、Direct3D11CaptureFramePool の GraphicsCaptureSession クラスのインスタンスを取得します。
_session = _framePool.CreateCaptureSession(_item);
_session = FramePool.CreateCaptureSession(CaptureItem)
ユーザーがアプリケーション ウィンドウのキャプチャまたはシステム UI での表示に明示的に同意すると、GraphicsCaptureItem を複数の CaptureSession オブジェクトに関連付けることができます。 これにより、アプリケーションは、さまざまなエクスペリエンスに対して同じ項目をキャプチャすることを選択できます。
複数の項目を同時にキャプチャするには、キャプチャする項目ごとにキャプチャ セッションを作成する必要があります。このセッションでは、キャプチャするアイテムごとにピッカー UI を呼び出す必要があります。
キャプチャ フレームを取得する
フレーム プールとキャプチャ セッションが作成されたら、GraphicsCaptureSession インスタンスで StartCapture メソッドを呼び出して、キャプチャ フレームのアプリへの送信を開始するようシステムに通知します。
_session.StartCapture();
_session.StartCapture()
これらの Direct3D11CaptureFrame オブジェクトであるキャプチャ フレームを取得するには、Direct3D11CaptureFramePool.FrameArrived イベントを使用できます。
_framePool.FrameArrived += (s, a) =>
{
// The FrameArrived event fires for every frame on the thread that
// created the Direct3D11CaptureFramePool. This means we don't have to
// do a null-check here, as we know we're the only one
// dequeueing frames in our application.
// NOTE: Disposing the frame retires it and returns
// the buffer to the pool.
using (var frame = _framePool.TryGetNextFrame())
{
// We'll define this method later in the document.
ProcessFrame(frame);
}
};
Private Sub FramePool_FrameArrived(sender As Direct3D11CaptureFramePool, args As Object) Handles FramePool.FrameArrived
' The FrameArrived event is raised for every frame on the thread
' that created the Direct3D11CaptureFramePool. This means we
' don't have to do a null-check here, as we know we're the only
' one dequeueing frames in our application.
' NOTE Disposing the frame retires it And returns
' the buffer to the pool.
Using frame = FramePool.TryGetNextFrame()
ProcessFrame(frame)
End Using
End Sub
このイベントは、新しいフレームが使用可能になるたびに発生するため、可能な場合は、FrameArrivedで UI スレッドを使用しないことをお勧めします。これは頻繁に発生します。 もし FrameArrived を UI スレッドでリッスンすることを選ぶ場合は、イベントが発生するたびに行う作業の量に注意してください。
または、必要なすべてのフレームを取得するまで、Direct3D11CaptureFramePool.TryGetNextFrame メソッドを使用して手動でフレームをプルすることもできます。
プロセス キャプチャ フレーム
アプリケーションでは、Direct3D11CaptureFrame オブジェクトへの参照を保存したり、フレームが再びチェックインされた後に基になる Direct3D サーフェスへの参照を保存したりしないでください。
フレームの処理中に、アプリケーションは、Direct3D11CaptureFramePool オブジェクトに関連付けられているのと同じデバイスで、ID3D11Multithread ロックを取ることをお勧めします。
基になる Direct3D サーフェスは常に、Direct3D11CaptureFramePoolを作成 (または再作成) するときに指定されたサイズになります。 コンテンツがフレームより大きい場合、コンテンツはフレームのサイズにクリップされます。 コンテンツがフレームよりも小さい場合、フレームの残りの部分には未定義のデータが含まれます。 未定義のコンテンツが表示されないように、アプリケーションでは、その Direct3D11CaptureFrame の ContentSize プロパティを使用してサブレクトをコピーアウトすることをお勧めします。
スクリーンショットを撮る
この例では、各 Direct3D11CaptureFrame を CanvasBitmapに変換します。これは、Win2D APIの一部です。
// Convert our D3D11 surface into a Win2D object.
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface);
CanvasBitmapしたら、イメージ ファイルとして保存できます。 次の例では、ユーザーの Saved Pictures フォルダーに PNG ファイルとして保存します。
StorageFolder pictureFolder = KnownFolders.SavedPictures;
StorageFile file = await pictureFolder.CreateFileAsync("test.png", CreationCollisionOption.ReplaceExisting);
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await canvasBitmap.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
}
アイテムのサイズ変更やデバイスの紛失に対応する
キャプチャ プロセス中に、アプリケーションで Direct3D11CaptureFramePool
再作成 が呼び出されると、既存のすべてのフレームが破棄されます。 これは、基になる Direct3D サーフェスが、アプリケーションがアクセスできなくなったデバイスに属しているフレームを渡さないようにするためです。 このため、再作成を呼び出す前に、保留中のすべてのフレーム
すべてをまとめる
次のコード スニペットは、UWP アプリケーションで画面キャプチャを実装する方法のエンドツーエンドの例です。 このサンプルでは、フロントエンドに 2 つのボタンがあります。1 つは Button_ClickAsync呼び出し、もう 1 つの呼び出しは ScreenshotButton_ClickAsync。
注
このスニペットでは、2D グラフィックス レンダリング用のライブラリである Win2D
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI.Composition;
using System;
using System.Numerics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Storage;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
namespace ScreenCaptureTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
// Capture API objects.
private SizeInt32 _lastSize;
private GraphicsCaptureItem _item;
private Direct3D11CaptureFramePool _framePool;
private GraphicsCaptureSession _session;
// Non-API related members.
private CanvasDevice _canvasDevice;
private CompositionGraphicsDevice _compositionGraphicsDevice;
private Compositor _compositor;
private CompositionDrawingSurface _surface;
private CanvasBitmap _currentFrame;
private string _screenshotFilename = "test.png";
public MainPage()
{
this.InitializeComponent();
Setup();
}
private void Setup()
{
_canvasDevice = new CanvasDevice();
_compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
Window.Current.Compositor,
_canvasDevice);
_compositor = Window.Current.Compositor;
_surface = _compositionGraphicsDevice.CreateDrawingSurface(
new Size(400, 400),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied); // This is the only value that currently works with
// the composition APIs.
var visual = _compositor.CreateSpriteVisual();
visual.RelativeSizeAdjustment = Vector2.One;
var brush = _compositor.CreateSurfaceBrush(_surface);
brush.HorizontalAlignmentRatio = 0.5f;
brush.VerticalAlignmentRatio = 0.5f;
brush.Stretch = CompositionStretch.Uniform;
visual.Brush = brush;
ElementCompositionPreview.SetElementChildVisual(this, visual);
}
public async Task StartCaptureAsync()
{
// The GraphicsCapturePicker follows the same pattern the
// file pickers do.
var picker = new GraphicsCapturePicker();
GraphicsCaptureItem item = await picker.PickSingleItemAsync();
// The item may be null if the user dismissed the
// control without making a selection or hit Cancel.
if (item != null)
{
StartCaptureInternal(item);
}
}
private void StartCaptureInternal(GraphicsCaptureItem item)
{
// Stop the previous capture if we had one.
StopCapture();
_item = item;
_lastSize = _item.Size;
_framePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, // D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, // Pixel format
2, // Number of frames
_item.Size); // Size of the buffers
_framePool.FrameArrived += (s, a) =>
{
// The FrameArrived event is raised for every frame on the thread
// that created the Direct3D11CaptureFramePool. This means we
// don't have to do a null-check here, as we know we're the only
// one dequeueing frames in our application.
// NOTE: Disposing the frame retires it and returns
// the buffer to the pool.
using (var frame = _framePool.TryGetNextFrame())
{
ProcessFrame(frame);
}
};
_item.Closed += (s, a) =>
{
StopCapture();
};
_session = _framePool.CreateCaptureSession(_item);
_session.StartCapture();
}
public void StopCapture()
{
_session?.Dispose();
_framePool?.Dispose();
_item = null;
_session = null;
_framePool = null;
}
private void ProcessFrame(Direct3D11CaptureFrame frame)
{
// Resize and device-lost leverage the same function on the
// Direct3D11CaptureFramePool. Refactoring it this way avoids
// throwing in the catch block below (device creation could always
// fail) along with ensuring that resize completes successfully and
// isn’t vulnerable to device-lost.
bool needsReset = false;
bool recreateDevice = false;
if ((frame.ContentSize.Width != _lastSize.Width) ||
(frame.ContentSize.Height != _lastSize.Height))
{
needsReset = true;
_lastSize = frame.ContentSize;
}
try
{
// Take the D3D11 surface and draw it into a
// Composition surface.
// Convert our D3D11 surface into a Win2D object.
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface);
_currentFrame = canvasBitmap;
// Helper that handles the drawing for us.
FillSurfaceWithBitmap(canvasBitmap);
}
// This is the device-lost convention for Win2D.
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
// We lost our graphics device. Recreate it and reset
// our Direct3D11CaptureFramePool.
needsReset = true;
recreateDevice = true;
}
if (needsReset)
{
ResetFramePool(frame.ContentSize, recreateDevice);
}
}
private void FillSurfaceWithBitmap(CanvasBitmap canvasBitmap)
{
CanvasComposition.Resize(_surface, canvasBitmap.Size);
using (var session = CanvasComposition.CreateDrawingSession(_surface))
{
session.Clear(Colors.Transparent);
session.DrawImage(canvasBitmap);
}
}
private void ResetFramePool(SizeInt32 size, bool recreateDevice)
{
do
{
try
{
if (recreateDevice)
{
_canvasDevice = new CanvasDevice();
}
_framePool.Recreate(
_canvasDevice,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
2,
size);
}
// This is the device-lost convention for Win2D.
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
_canvasDevice = null;
recreateDevice = true;
}
} while (_canvasDevice == null);
}
private async void Button_ClickAsync(object sender, RoutedEventArgs e)
{
await StartCaptureAsync();
}
private async void ScreenshotButton_ClickAsync(object sender, RoutedEventArgs e)
{
await SaveImageAsync(_screenshotFilename, _currentFrame);
}
private async Task SaveImageAsync(string filename, CanvasBitmap frame)
{
StorageFolder pictureFolder = KnownFolders.SavedPictures;
StorageFile file = await pictureFolder.CreateFileAsync(
filename,
CreationCollisionOption.ReplaceExisting);
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await frame.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
}
}
}
}
Imports System.Numerics
Imports Microsoft.Graphics.Canvas
Imports Microsoft.Graphics.Canvas.UI.Composition
Imports Windows.Graphics
Imports Windows.Graphics.Capture
Imports Windows.Graphics.DirectX
Imports Windows.UI
Imports Windows.UI.Composition
Imports Windows.UI.Xaml.Hosting
Partial Public NotInheritable Class MainPage
Inherits Page
' Capture API objects.
WithEvents CaptureItem As GraphicsCaptureItem
WithEvents FramePool As Direct3D11CaptureFramePool
Private _lastSize As SizeInt32
Private _session As GraphicsCaptureSession
' Non-API related members.
Private _canvasDevice As CanvasDevice
Private _compositionGraphicsDevice As CompositionGraphicsDevice
Private _compositor As Compositor
Private _surface As CompositionDrawingSurface
Sub New()
InitializeComponent()
Setup()
End Sub
Private Sub Setup()
_canvasDevice = New CanvasDevice()
_compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(Window.Current.Compositor, _canvasDevice)
_compositor = Window.Current.Compositor
_surface = _compositionGraphicsDevice.CreateDrawingSurface(
New Size(400, 400), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied)
Dim visual = _compositor.CreateSpriteVisual()
visual.RelativeSizeAdjustment = Vector2.One
Dim brush = _compositor.CreateSurfaceBrush(_surface)
brush.HorizontalAlignmentRatio = 0.5F
brush.VerticalAlignmentRatio = 0.5F
brush.Stretch = CompositionStretch.Uniform
visual.Brush = brush
ElementCompositionPreview.SetElementChildVisual(Me, visual)
End Sub
Public Async Function StartCaptureAsync() As Task
' The GraphicsCapturePicker follows the same pattern the
' file pickers do.
Dim picker As New GraphicsCapturePicker
Dim item As GraphicsCaptureItem = Await picker.PickSingleItemAsync()
' The item may be null if the user dismissed the
' control without making a selection or hit Cancel.
If item IsNot Nothing Then
StartCaptureInternal(item)
End If
End Function
Private Sub StartCaptureInternal(item As GraphicsCaptureItem)
' Stop the previous capture if we had one.
StopCapture()
CaptureItem = item
_lastSize = CaptureItem.Size
FramePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, ' D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, ' Pixel format
2, ' Number of frames
CaptureItem.Size) ' Size of the buffers
_session = FramePool.CreateCaptureSession(CaptureItem)
_session.StartCapture()
End Sub
Private Sub FramePool_FrameArrived(sender As Direct3D11CaptureFramePool, args As Object) Handles FramePool.FrameArrived
' The FrameArrived event is raised for every frame on the thread
' that created the Direct3D11CaptureFramePool. This means we
' don't have to do a null-check here, as we know we're the only
' one dequeueing frames in our application.
' NOTE Disposing the frame retires it And returns
' the buffer to the pool.
Using frame = FramePool.TryGetNextFrame()
ProcessFrame(frame)
End Using
End Sub
Private Sub CaptureItem_Closed(sender As GraphicsCaptureItem, args As Object) Handles CaptureItem.Closed
StopCapture()
End Sub
Public Sub StopCapture()
_session?.Dispose()
FramePool?.Dispose()
CaptureItem = Nothing
_session = Nothing
FramePool = Nothing
End Sub
Private Sub ProcessFrame(frame As Direct3D11CaptureFrame)
' Resize and device-lost leverage the same function on the
' Direct3D11CaptureFramePool. Refactoring it this way avoids
' throwing in the catch block below (device creation could always
' fail) along with ensuring that resize completes successfully And
' isn't vulnerable to device-lost.
Dim needsReset As Boolean = False
Dim recreateDevice As Boolean = False
If (frame.ContentSize.Width <> _lastSize.Width) OrElse
(frame.ContentSize.Height <> _lastSize.Height) Then
needsReset = True
_lastSize = frame.ContentSize
End If
Try
' Take the D3D11 surface and draw it into a
' Composition surface.
' Convert our D3D11 surface into a Win2D object.
Dim bitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface)
' Helper that handles the drawing for us.
FillSurfaceWithBitmap(bitmap)
' This is the device-lost convention for Win2D.
Catch e As Exception When _canvasDevice.IsDeviceLost(e.HResult)
' We lost our graphics device. Recreate it and reset
' our Direct3D11CaptureFramePool.
needsReset = True
recreateDevice = True
End Try
If needsReset Then
ResetFramePool(frame.ContentSize, recreateDevice)
End If
End Sub
Private Sub FillSurfaceWithBitmap(canvasBitmap As CanvasBitmap)
CanvasComposition.Resize(_surface, canvasBitmap.Size)
Using session = CanvasComposition.CreateDrawingSession(_surface)
session.Clear(Colors.Transparent)
session.DrawImage(canvasBitmap)
End Using
End Sub
Private Sub ResetFramePool(size As SizeInt32, recreateDevice As Boolean)
Do
Try
If recreateDevice Then
_canvasDevice = New CanvasDevice()
End If
FramePool.Recreate(_canvasDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, size)
' This is the device-lost convention for Win2D.
Catch e As Exception When _canvasDevice.IsDeviceLost(e.HResult)
_canvasDevice = Nothing
recreateDevice = True
End Try
Loop While _canvasDevice Is Nothing
End Sub
Private Async Sub Button_ClickAsync(sender As Object, e As RoutedEventArgs) Handles CaptureButton.Click
Await StartCaptureAsync()
End Sub
End Class
ビデオを録画する
アプリケーションのビデオを録画する場合は、ビデオへのスクリーン キャプチャ