屏幕捕获

您所在的位置:网站首页 windows7桌面截屏 屏幕捕获

屏幕捕获

2023-12-19 16:16| 来源: 网络整理| 查看: 265

屏幕捕获 项目 10/18/2023

从 Windows 10 版本 1803 开始,Windows.Graphics.Capture 命名空间提供用于从屏幕或应用程序窗口获取帧的 API,以创建用于生成协作和交互式体验的视频流或快照。

通过屏幕捕获,开发人员调用安全系统 UI 以便最终用户选取要捕获的屏幕或应用程序窗口,然后系统会在当前正在捕获的项目四周绘制黄色通知边框。 如果同时存在多个捕获会话,系统会在每个正在捕获的项目四周绘制黄色边框。

注意

仅 Windows 设备和 Windows Mixed Reality 沉浸式头戴显示设备支持屏幕捕获 API。

本文介绍如何捕获屏幕或应用程序窗口的单个图像。 有关将从屏幕捕获的帧编码为视频文件的信息,请参阅对视频的屏幕捕获

添加屏幕捕获功能

在 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 线程上调用;但如果不是 UI 代码,则可以使用以下代码强制其在 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 内容时出现像素过度剪裁(即捕获的内容看似褪色),请考虑对捕获管道中的每个组件使用 DXGI_FORMAT_R16G16B16A16_FLOAT,包括 Direct3D11CaptureFramePool、目标目的地(如 CanvasBitmap)。 视具体需求而定,可能需要进行其他处理,例如保存为 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 线程,因为每次出现可用的新帧(出现次数频繁)时都会引发此事件。 如果选择在 UI 线程上侦听 FrameArrived,请注意每次事件触发时所执行的工作量。

或者,可以使用 Direct3D11CaptureFramePool.TryGetNextFrame 方法手动请求帧,直到获取所有需要的帧。

Direct3D11CaptureFrame 对象包含 ContentSize、Surface 和 SystemRelativeTime 属性。 SystemRelativeTime 是可用于同步其他媒体元素的 QPC (QueryPerformanceCounter) 时间。

处理捕获帧

调用 TryGetNextFrame 时会签出 Direct3D11CaptureFramePool 中的每一帧,然后根据 Direct3D11CaptureFrame 对象的生存期重新签入。 对于本机应用程序,发布 Direct3D11CaptureFrame 对象便可以将帧重新签入到帧池中。 对于托管的应用程序,建议使用 Direct3D11CaptureFrame.Dispose(C++ 中的 Close)方法。 Direct3D11CaptureFrame 实现 IClosable 界面,该界面投射为 C# 调用方的 IDisposable。

应用程序不应将引用保存到 Direct3D11CaptureFrame 对象,也不应在重新签入帧之后将引用保存到基础 Direct3D 图面。

处理帧时,建议应用程序在与 Direct3D11CaptureFramePool 对象关联的相同设备上采用 ID3D11Multithread 锁定。

基础 Direct3D 图面始终为创建(或重新创建)Direct3D11CaptureFramePool 时所指定的大小。 如果内容超过帧的大小,内容将剪裁为帧的大小。 如果内容小于帧的大小,帧的剩余部分会包含未定义的数据。 建议应用程序使用 ContentSize 属性为该 Direct3D11CaptureFrame 拷贝出一个 sub-rect,以避免显示未定义的内容。

拍摄屏幕截图

在本示例中,我们将每个 Direct3D11CaptureFrame 转换为 CanvasBitmap,后者是 Win2D API 的一部分。

// Convert our D3D11 surface into a Win2D object. CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface( _canvasDevice, frame.Surface);

获得 CanvasBitmap 后,就可以将其保存为图像文件。 在以下示例中,我们将其作为 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 设备、更改帧缓冲区的大小或甚至更改池中的缓冲区的数量。 在上述每种方案中,均建议使用 Direct3D11CaptureFramePool 对象上的 Recreate 方法。

调用 Recreate 时,将丢弃所有现有的帧。 这是为了防止将具有后列特征的帧分发出去:这些帧的基础 Direct3D 图面归属于应用程序无法再访问的设备。 为此,建议在调用 Recreate 之前处理完所有待处理的帧。

汇总

以下代码片段是一个端到端示例,展示如何在 UWP 应用程序中实现屏幕捕获。 在本示例中,前端有两个按钮:一个调用 Button_ClickAsync,另一个调用 ScreenshotButton_ClickAsync。

注意

此代码片段使用 Win2D(一个用于 2D 图形渲染的库)。 有关如何为项目设置该库的信息,请参阅相关文档。

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 { /// /// An empty page that can be used on its own or navigated to within a Frame. /// 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 录制视频

如需录制应用程序的视频,可以按照对视频的屏幕捕获一文中的演练进行操作。 或者,可以使用 Windows.Media.AppRecording 命名空间。 它是桌面扩展 SDK 的一部分,因此仅适用于 Windows 桌面,并且需要你从项目中添加对它的引用。 有关详细信息,请参阅使用扩展 SDK 编程。

另请参阅 Windows.Graphics.Capture 命名空间 屏幕捕获到视频


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3