Windows桌面采集技术

在进入具体的方式讨论前,我们先看看 Windows 桌面图形界面的简化架构,如下图:

在 Windows Vista 之前,Windows 界面的复合画面经由 Graphics Device Interface(以下简称 GDI)技术直接渲染到桌面上。

在 Windows Vista 以及之后的版本,Desktop Composition 的工作就交由一个新的模块Desktop Window Manager(以下简称 DWM) 来完成了。

如上图所示,应用程序画完自己的界面后,提交给 DWM 把它合成到桌面上,而 DWM 经过一系列演进后,为了提效,基于微软自己的 Direct3D(以下简称 D3D) 实现了整套技术,在 D3D 这层的下面是 Windows Display Driver Model(以下简称 WDDM,Windows 图形驱动程序模型)。

                            

所以在 Windows 下实现录屏采集,基本上可以从最基本的 GDI 技术和 D3D 技术两方面考虑。

GDI:第一代桌面采集

Windows 图形设备接口(GDI)是为与设备无关的图形设计的。基于 Windows 的应用程序不能直接访问图形硬件,应用程序通过 GDI 来与设备驱动程序进行交互。GDI 截图就是通过屏幕的DC获取到当前屏幕的位图数据。

调用过程
GetDC(GetDesktokWindow() )获取桌面的DC
然后使用CreateDIBSection创建一个设备无关位图以及内存DC
使用BitBlt把桌面DC的复制到内存DC,这样通过内存DC就能直接获取到原始RGB数据。

基本采样流程

在 Windows 平台上有过图形开发经验的开发者,应该都知道 BitBlt 这个 API, 它为我们实现了 Windows DC 间的内容拷贝,假如将 Source DC 指定为 Window DC 或是 Destop DC,这就实现了对屏幕指定源的画面截取。下图说明了基于 GDI 技术录屏采集的大致调用过程:

                            

以整个桌面作为采集源举例,通用做法是调用 GetDC(GetDesktokWindow()) 获取桌面 DC,通过 CreateDIBSection 创建一个设备无关的位图对象以及内存 DC,最后调用 BitBlt 把桌面 DC 的原始数据翻转到内存 DC 上,这样从内存 DC 上就能直接获取到桌面的原始 RGB 数据。

需要注意的是,创建一个设备无关的位图时,CreateDIBSection 的第4个参数 ppvBits 是提前分配好的位图数据缓冲区。而在以实时视频流的方式共享屏幕的场景下,需要以每秒十几次甚至几十次的频率进行采样,从效率的要求考虑,这里自然不可能每次都重新分配缓冲区,所以可以根据源的分辨率,在采集前就分配一个足够大的空间。

CreateDIBSection的函数原型如下图所示:

HBITMAP CreateDIBSection(HDC              hdc,const BITMAPINFO *pbmi,UINT             usage,VOID             **ppvBits,HANDLE           hSection,DWORD            offset
);

        经过以上基本采样流程,得到的画面内容是不含鼠标的,如果需要将鼠标还原到画面中

CURSORINFO ci = { 0 };ci.cbSize = sizeof(ci);ZeroMemory(&ci, sizeof(CURSORINFO));ci.cbSize = sizeof(CURSORINFO);if (::GetCursorInfo(&ci)){POINT ptCursorPos = { 0, 0 };ptCursorPos = ci.ptScreenPos;::DrawIconEx(m_hCompDC,ptCursorPos.x , ptCursorPos.y,ci.hCursor,0, 0, 0, 0,DI_NORMAL | DI_DEFAULTSIZE | DI_COMPAT);}


优缺点


优点:GDI函数实现的通用做法,能在所有windows平台实现

缺点:通用归通用,截取的效率则是有点低,尤其是要达到每秒20帧以上的截取,占用CPU有点高,GDI不能获取鼠标,需要在截取的图像中把鼠标画上去。

        由于整体的运算、拷贝过程都在 CPU 中完成,导致采样效率偏低,尤其在高频采样(> 20fps)时,对 CPU资源的消耗过高,而且后续还要处理为实时视频流,经过同样高频的编码、网络包发送,多方面因素叠加,自然对机器性能有更高的要求。 GDI 则可以作为一种保底方案。

在具体实现中,还有两点需要特别提醒:

1)在 Windows XP 下,可以通过 BitBlt 函数最后的参数,来控制是否拷贝 Layered Window。只有 SRCCPY 标识,表示拷贝内容不包含 Layered Window;如果是SRCCPY | CAPTUREBLT,则表示拷贝包括 Layered Window 在内的所有窗口。而这个标识,在 Windows Vista 之后的系统版本开启 DWM 的情况下,已经无效,因为这种情况下所有的窗口都是 Layered Window;

2)在 Windows Vista 之后的系统版本开启 DWM 的情况下,单次抓取速度变得非常慢(作者机器实测 30ms +)

且无法采集到使用gpu渲染的窗口,显示黑屏:

DXGI:高性能桌面采集技术

DXGI(Microsoft DirectX Graphics Infrastructure)是微软提供的一种可以在win8及以上系统使用的图形设备接口。它负责枚举图形适配器、枚举显示模式、选择缓冲区格式、在进程之间(例如,在应用程序和桌面窗口管理器(DWM)之间)共享资源,以及将呈现的帧传给窗口或监视器以供显示。其直接和硬件设备进行交互,具有很高的效率和性能。

基本采样流程

除了用 GDI 技术实现录屏,实际上在 Windows 平台上,微软提供了多种录屏方案,相对 GDI 技术来说,其大多数接口的处理性能并不理想,或存在诸多限制,通用性不足。

从 Windows 8 开始,微软引入了一套新技术叫 Desktop Duplication API,应用程序可以通过这套 API 请求桌面的图形数据。由于 Desktop Duplication API 是通过 DirectX Graphics Infrastructure(以下简称 DXGI)来提供桌面图像的,竞争的是 GPU 流水线资源,所以 CPU 占用率很低,采集性能非常高。

由于这套能力整合在 DirextX 中提供,所以与大部分 DirectX 接口的使用方式基本一致,其流程概括如下图。

如图所示,使用 DXGI 需要一些简单的 DirectX 基础知识,通过各种 DirectX COM 接口的查询,最终获取 IDXGIOutputDuplication 接口指针,截屏时使用其中核心的AcquireNextFrame API 获取当前桌面图像,此外,它还提供 GetFrameDirtyRects 等 API,可以获取经过 GPU 计算后发生了变化的脏矩形区域。

绘制鼠标

和 GDI 面临相同的问题,直接通过 AcquireNextFrame API 获取到的画面中,也是不含鼠标图像的。想要将鼠标绘制到画面中,我们需要和GDI相关的API配合使用。

在采集之前创建数据缓冲区时,将缓冲区关联到设备无关的位图上,并将位图选入临时的内存DC(一般由桌面DC生成的临时内存DC),再将通过 AcquireNextFrame获取的画面拷贝到缓冲区后,这时可以使用GDI绘制鼠标的方法,将鼠标绘制到位图上,这样数据缓冲区中的图像数据就包含了鼠标了。

优劣势分析

在 Windows 平台上,从现有的录屏采集方式(包括GDI采集和放大镜采集)来看,DXGI 是性能最好的。

其劣势是只在 Windows 8 系统版本及以上才支持,所以在整体方案中,一般要与 GDI 共同组合提供。此外,它无法指定某个程序窗口进行采集

Ø 特点:Win Vista 以后支持,使用GPU直接处理纹理,效率最高;

Ø 缺点:根据Direct3D 版本不同,存在硬件的支持以及调用特性 的区别,且因为采集需要获取设备的adapter,所以无法采集桌面窗口。、       

        如果用户机器有A卡和N卡,出现了跨卡问题导致采集失效,禁用AMD显卡后问题解决

Magnification:弯道超车的采集技术

Magnification API 使用于放大屏幕某个区域的 辅助应用技术,初衷是用于协助视力存在问题或者色弱的用户能跟方便的看到桌面内容的api

Ø 特点:能实现放大缩小颜色转换等操作,能过滤窗口

基本采样流程

前面所述的两种方式都可以实现录屏采集,也是最常用的两种方式。

但有时,我们要指定源来采集,并且希望采集到的画面不被其他内容干扰。

在 Windows XP 时代,用 GDI 的 BitBlt API 进行采集时,指定一个 Window DC,并且对最后一个参数去掉 CAPTUREBLT 标识,即可排除掉其他 Layered Window 的干扰。但如今 Windows XP 已成过去式,这项措施无法解决现有系统的过滤问题,必须找到另一替代方案。

从 Windows Vista 开始,微软新引入了一个新的 Magnification API(放大镜效果),当我们将放大倍率设置成1(默认倍率就是1)亦可以用它来截取屏幕图像。MSDN上提供了该库的完整文档。根据文档,可以通过以下步骤简单地完成录屏采集过程:

在初始化相关模块后,首先创建放大镜控件的主窗口,并且将其设置为全屏不可见,因为我们要使用它来捕获图像,它只是一个工具,所以不能也不需要在用户侧显示它。因此,设置窗口扩展属性 WS_EX_LAYERED,调用 SetLayeredWindowAttributes 设置全透明。

::SetLayeredWindowAttributes(hwnd, 0 ,255, LWA_ALPHA);

接着创建放大镜窗口作为主窗口的子窗口,窗口类名必须为“Magnifier”。如果要捕获鼠标光标,还要设置窗口属性为 MS_SHOWMAGNIFIEDCURSOR。

hwndMag = ::CreateWindow(WC_MAGNIFIER, TEXT("Magnifier"), WS_CHILD /*| MS_SHOWMAGNIFIEDCURSOR */| WS_VISIBLE,0, 0, m_ScreenX, m_ScreenY, hostDlg->GetSafeHwnd(), NULL, hInstance, NULL );

最关键的部分,利用 MagSetWindowFilterList 这个神奇的 API,它能够指定一些窗口,在我们截取指定源目标时,从采集到的图像中将 FilterList 中的窗口过滤掉,好像这些窗口根本没有显示一样。这就是我们使用这放大镜方案的主要原因。

那么如何获得录屏图像呢?每当我们调用 MagSetWindowSource 时,都会触发回调MagSetImageScalingCallback数据回调。原型如下:

BOOL MagImageScaling(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)

其中第二个参数 srcdata 就是指向录屏结果图像的原始数据,srcheader 则包含数据的长度,以及图像长宽等元信息,至此,我们可以使用这两个参数来构建位图了。

Magnification

Window Graphics Capturer:新世代采集技术

WGC 全称为 Windows Graphics Capture 是微软目前主推的一个桌面/窗口采集技术,使用 D3D11 库实现。该采集技术最早在Windows 10 18年3月份的更新中提供。WGC 对比放大镜采集(Magnification Capture) 具有更高的性能、更低CPU及GPU消耗。但是在 使用方面比起其他采集方式会更复杂。

  • 系统版本不低于10.0.17134.0 (Windows 10, version 1803)
  • 相关接口均基于微软的新一代运行时库接口C++/WinRT,而且是最低要求 C++17

        鼠标支持,自Windows 10, version 2004 (introduced in 10.0.19041.0)才开始支持捕获鼠标。
GraphicsCaptureSession.IsCursorCaptureEnabled Property
        黄色边框去除,自Windows 10, version 2104 (introduced in 10.0.20348.0)才开始去除采集目标的黄色边框。
GraphicsCaptureSession.IsBorderRequired

FFMPEG录屏(15)---- WGC 捕获桌面(三) WGC(Windows Graphics Capture)采集_ffmpeg采集桌面-CSDN博客

Ø 特点:效率高,拓展屏采集支持高,1080p采集消耗gpu达到个位数;

Ø 缺点:

1. 当Capture Session开始采集后,在刚开始采集的时候可能存在 HRESULT 为S_OK ,但是画面数据为空,因为这个时候采集Engine可能处于启动中状态;

2. 当开始采集是,Windows会在采集源(窗口或桌面)区域增加一个黄色边框去标识正在采集的区域,目前无法设置该边框的样式或者去除该边框;

3. WGC 使用SetWindowDisplayAffinity 实现窗口过滤,但是设置的窗口必须为当前进程创建的子窗口才能设置成功,否则无法实现过滤。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39505.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# BULK INSERT导入大数据文件数据到SqlServer

BULK INSERT 的核心原理 BULK INSERT 是一种通过数据库原生接口高效批量导入数据的技术,其核心原理是绕过逐条插入的 SQL 解析和执行开销,直接将数据以二进制流或批量记录的形式传输到数据库。 在.NET中,主要通过 ​SqlBulkCopy 类​&#x…

Power BI嵌入应用:常见问题与调试技巧

将Power B 嵌入应用时的常见问题与调试技巧 Power BI Embedded 是一项 Microsoft Azure 服务,允许开发人员将交互式 Power BI 报表和仪表板嵌入到外部自定义应用程序或网站中。将Power BI嵌入应用程序能有效提升用户体验,但实施过程中可能面临一些典型问…

Android Studio编译问题

文章目录 GradleJDK版本不兼容 Gradle JDK版本不兼容 Incompatible because this component declares an API of a component compatible with Java 11 and the consumer needed a runtime of a component compatible with Java 8 查看module内gradle文件是否设置jdk版本&…

Four.meme是什么,一篇文章读懂

一、什么是Four.meme? Four.meme 是一个运行在 BNB 链的去中心化平台旨在为 meme 代币供公平启动服务。它允许用户以极低的成本创建和推出 meme 代币,无需预售或团队分配,它消除了传统的预售、种子轮和团队分配,确保所有参与者有…

解决PHP内存溢出问题的讨论和分析

PHP作为一种广泛使用的服务器端脚本语言,在处理大量数据或复杂任务时,常常会遇到内存溢出的问题。内存溢出不仅会导致程序崩溃,还可能影响服务器的稳定性。本文将探讨解决PHP内存溢出问题的最佳实践,并通过代码示例进行详细说明。…

git,openpnp - 根据安装程序打包名称找到对应的源码版本

文章目录 git,openpnp - 根据安装程序打包名称找到对应的源码版本概述笔记备注 - 提交时间不可以作为查找提交记录的依据END git,openpnp - 根据安装程序打包名称找到对应的源码版本 概述 想在openpnp官方最新稳定版上改一改,首先就得知道官方打包的安装程序对应的…

基于Spring Boot的停车场管理系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…

基于Spring Boot + Vue的银行管理系统设计与实现

基于Spring Boot Vue的银行管理系统设计与实现 一、引言 随着金融数字化进程加速,传统银行业务向线上化转型成为必然趋势。本文设计并实现了一套基于Spring Boot Vue的银行管理系统,通过模块化架构满足用户、银行职员、管理员三类角色的核心业务需求…

Unity | Tag、Layer常量类生成工具

在项目开发中我们可以对诸如Layer、Tag等编辑器数据进行常量生成,来代替在代码中通过输入字符串生成常量的形式以提高开发效率。 Layer的生成可以通过LayerMask.LayerToName获取层名称(也可以从TagManager.asset中获得 ),Tag的生成…

两个手机都用流量,IP地址会一样吗?深入解析

在日常生活中,我们常常会同时使用多台手机设备上网,尤其是在流量充足的情况下。你是否曾好奇过,当两台手机同时使用流量上网时,它们的IP地址会是一样的吗?这个问题看似简单,却涉及移动网络的技术原理。本文…

后端——AOP异步日志

需求分析 在SpringBoot系统中,一般会对访问系统的请求做日志记录的需求,确保系统的安全维护以及查看接口的调用情况,可以使用AOP对controller层的接口进行增强,作日志记录。日志保存在数据库当中,为了避免影响接口的响…

Qt的内存管理机制

在Qt中,显式使用new创建的对象通常不需要显式调用delete来释放内存,这是因为Qt提供了一种基于对象树(Object Tree)和父子关系(Parent-Child Relationship)的内存管理机制。这种机制可以自动管理对象的生命周期,确保在适当的时候释放内存&…

React:React主流组件库对比

1、Material-UI | 官网 | GitHub | GitHub Star: 94.8k Material-UI 是一个实现了 Google Material Design 规范的 React 组件库。 Material UI 包含了大量预构建的 Material Design 组件,覆盖导航、滑块、下拉菜单等各种常用组件,并都提供了高度的可定制…

排序算法(插入,希尔,选择,冒泡,堆,快排,归并)

1.插入排序 插入排序的主要思想是额外申请一个空间cur,让cur一开始等于数组的第1号位置,设置i1,让i-1的元素与其比较,如果arr[i-1]>arr[i],就让arr[i1] arr[i],当进行到最后一次对比结束,i-1,再让arr[…

python学习笔记--实现简单的爬虫(二)

任务:爬取B站上最爱欢迎的编程课程 网址:编程-哔哩哔哩_bilibili 打开网页的代码模块,如下图: 标题均位于class_"bili-video-card__info--tit"的h3标签中,下面通过代码来实现,需要说明的是URL中…

Vue3 实现pdf预览

1.使用到的插件 vue3-pdf-app 以及预览效果 2.下载依赖 // 可以使用npm 以及pnpm // 下载版本1.0.3 pnpm install vue3-pdf-app^1.0.3 3.封装pdfModel组件复用 <template><VuePdfApp :page-scale"pageScale" :theme"theme" :style"width: …

SpringBoot集成Elasticsearch 7.x spring-boot-starter-data-elasticsearch 方式

SpringBoot集成Elasticsearch 7.x | spring-boot-starter-data-elasticsearch 方式 前言添加maven依赖配置application.properties测试实体类 方式一&#xff1a;继承 ElasticsearchRepository&#xff08;适合简单查询&#xff09; 直接使用想自定义自己的Repository接口 方式…

【Clang AST】基于 Clang 获取分析 AST

The Clang AST AST&#xff08;Abstract Syntax Tree&#xff09;抽象语法树 AST是什么 抽象语法树&#xff08;Abstract Syntax Tree, AST&#xff09;是源代码的抽象表示&#xff0c;广泛用于编译器和分析工具中。 AST将源代码的语法结构转换为树形结构&#xff0c;其中每…

onedav一为导航批量自动化导入网址(完整教程)

OneNav作为一个功能强大的导航工具,支持后台管理、加密链接、浏览器书签批量导入等功能,能够帮助用户轻松打造专属的导航页面。今天,我将为大家详细介绍如何实现OneNav导航站的批量自动化导入网址。 1、建立要批量导入的表格 格局需要创建表格,表格的要求是一定要有需要,…

文档处理控件Aspose.Words 教程:.NET版中增强的 AI 文档摘要功能

Aspose.Words是一个功能强大的 Word 文档处理库。它可以帮助开发人员自动编辑、转换和处理文档。 自 24.11 版以来&#xff0c;Aspose.Words for .NET 提供了 AI 驱动的文档摘要功能&#xff0c;使用户能够从冗长的文本中快速提取关键见解。在 25.2 版中&#xff0c;我们通过使…