桌面端屏幕分享实践

本篇主要介绍 Windows 端和 macOS 端上屏幕分享的实现方式与注意事项。这两套系统都是闭源的,主要信息来源于官方文档,以及加上各位技术前辈和个人的一些摸索,如有不当或者错误的地方,还请诸位不吝指正。

作者:刘国元 网易资深开发工程师

一、前言

实时音视频通信的整个流程,可以大致分为数据采集、编码、传输、解码、渲染几个环节。各类平板、手机、电脑等终端是数据采集和渲染的重要环节。鉴于目前市面上介绍桌面端的屏幕数据采集资料总结的比较少,本文抛砖引玉讨论一下,在开发桌面端屏幕数据采集过程中的一些实践经验。

在 Windows 和 macOS 两大桌面操作系统的应用层上实现屏幕采集,主要工作是针对这类基于窗口的系统实现窗口图像的抓取。不同桌面端的图形系统提供了不同的系统级实现,以及不同的系统 API,我们无法通过一套标准的接口来实现该功能,只能针对性地给出不同的封装,下面分条细述。

二、Windows

Windows 上实现屏幕分享算是几个平台最复杂的,单单是 Windows 提供的抓取屏幕图像的技术就有好多种,简单列举概述如下:

  • Windows GDI:这是 Windows 系统上兼容性较好的一套方案,从  Windows 2000 开始就有,现在还在继续提供服务,效率也还不错,部分接口底层支持了硬件加速功能,至于缺点我们后面叙述。
  • Microsoft DirectX Graphics Infrastructure(DXGI):这套 API 是为了封装不同版本 DirectX 部分接口而出现的,首次出现是在 Direct 3D 10,现在还在更新。本文关注的是 DirectX 提供的显示器数据抓取技术。
  • DWM:这项技术是微软在 Vista 上,为了提高桌面窗口显示效果而提供的一套 API,其基本思路是:改变过去直接绘制窗口到屏幕的做法,在视频内存中开辟一块离屏渲染区域,用来做各种像素处理,之后再交给屏幕显示,以此实现 Vista 有的毛玻璃效果,以及按下窗口键 +Tab 键来做桌面窗口切换时的 3D 转换效果等。通过这套 API 我们可以实现窗口缩略图的渲染,在 Windows 7 上,DWM 可以通过切换系统主题来控制其打开和关闭,即 Aero 主题。从 Windows 8 开始,系统默认都是打开 DWM 。这套 API 一般可以用来获取目标窗口的缩略图(thumbnail,支持动态更新),用来分享画面的话,分辨率偏低,不太能满足需求。
  • Magnification API:我们姑且称之为“放大镜 API”,这个是 Windows 上预装的放大镜程序底层使用的技术,我们正好可以利用这个技术,同时还可以实现窗口过滤功能。这套 API 也是目前兼容范围较广,各类 App 使用非常多的窗口显示数据抓取技术,我们后面会详细分析。
  • Windows Grapics Capture(WGC API):微软最新提供的一套正式用来抓取屏幕数据的接口,遗憾的是最初是给 UWP 提供的,而非 Win32 App,且要求必须是 Windows 10, Update(1809)及之后的系统才支持,但是官方提供的,我们也要予以足够的重视。
  • Windows Media API,以及各种 hook 技术 …… 可用范围较窄,因此这里不讨论。

虽然可以列举的方案有很多种,但是没有一种方案可以覆盖从 Windows 7 到 Windows 10 上运行的所有 GUI 程序窗口(且不论 Windows XP 系统),从这一点可以看到微软最近这二十年在窗口系统上“激进”的“创新”。

屏幕分享功能常见场景可以大致分为两种:

  • 分享某个应用的单个窗口;
  • 分享指定显示器的内容。如果有多块外接显示器,可能还要考虑是否要分享整个虚拟桌面(把几块显示器图像拼接起来),不过目前几乎没看到这类应用场景。

我们要做的就是组合前面提到的可用的技术,来覆盖这两种场景。接下来我们分别讨论。

(一) 应用窗口采集 

在详细介绍应用窗口采集实现方案之前,我们先看一下要这个场景下还有哪些细节需要考虑进来。

我们开始屏幕分享之后,为了让分享者能够做一些常用的操作,比如开始/停止/暂停/恢复/分享、邀请联系人、打开一个 IM 窗口进行交流等。一般情况下,我们在应用层会做一个操作的浮窗,在上面暴露多个操作入口,但观看端是不想看到这些画面的,且这些窗口还可能挡住分享者桌面上一些重要的信息。这就引出我们需要考虑的最重要,也是最难实现的一个点:有一些桌面上的窗口需要在分享过程中被过滤掉。

针对屏幕分享场景,上述支持窗口过滤的方案基本上只有两种:Magnification API 和 WGC API。

1. Magnification API

Magnification API 方案,因为其覆盖范围较广,所支持的功能又正好满足我们需要,所以我们将其作为优选方案,其他方案作为补充。

在 Magnification API 官方资料Magnification API Overview中有这么一段话:

The Magnification API is not supported under WOW64; that is, a 32-bit magnifier application will not run correctly on 64-bit Windows.

意思是说目前的 32-bit 的应用程序运行在 64-bit 系统上可能出问题。但是实践下来,32-bit 与 64-bit 的应用程序没有什么差异,个人认为微软应该可以重新整理一下这部分文档了。

主要的 API 中,MagSetWindowFilterList 支持设置一组待过滤的窗口句柄,在上面的场景中,表现为前文提到的浮窗,MagSetWindowSource 用来启动一次窗口数据抓取,具体数据通过 MagSetImageScalingCallback 设置的回调函数 Magimagescalingcallback 同步返回,我们可以在自定义的 Magimagescalingcallback 函数中拿到抓取的数据做进一步处理,理想情况下,这样就足够了。

当然,实际情况没有这么理想。还有好多“杂事儿”需要处理:

  1. 比如被分享的窗口被不小心关闭了,最小化了,大小改变了,隐藏了,甚至整个被分享的应用程序直接 crash 了。
  2. 每次返回的数据都需要申请一大块内存来保存,然后转发给其他处理环节,那么就需要维护内存申请的问题。对于 32-bit 的应用程序,用户内存空间仅有 2G(非常多 3G)大小,而一般涉及到音视频的应用程序中难免会有大量内存操作,这会进一步导致内存碎片的产生,如果碰到分享者在分享一个 4K 屏上全屏窗口的场景,那么很可能刚开始分享的一段时间内运行正常,在未来某个时间点,突然发生大块内存申请不到的情况。这就需要根据实际情况做具体的处理。
  3. 在某些 Nvidia 显卡(尤其是一些笔记本)上该接口会认真完成全套流程调用,但问题是返回的数据是纯黑屏数据,有时候是一个4×4大小的数据,于是我们需要检测返回的数据。
  4. 支持过滤的窗口有一个最大数值,测试下来大概是 24 个窗口左右,所以应该提示应用层的开发同学,使用过滤窗口的时候保持克制。
  5. Magnification API 接口对非主屏支持的比较差,还有主屏显示分辨率低于非主屏的场景,这时候不出意外是会出现 crash。

对于情况 1、2 我们可以写一些逻辑代码处理;对于情况 3、4、5 我们就不得不另外找保底方案来处理;尤其是情况5往往会直接 crash,这时候需要提前判断,采用保底方案。市面上大部分会议 App 都是创建一个独立的屏幕分享进程,这样即使这个进程崩溃也不会影响主进程的功能,大不了让用户重启一下分享功能。

2. Windwos GDI

GDI 方案的核心函数有两个: PrintWindow 和 BitBlt。

PrintWindow 内部实现是向目标窗口发送了一个消息(WM_PRINT 或者 WM_PRINTCLIENT)。PrintWindow 有一个优点是即使目标窗口被遮挡,也会把目标窗口的内容绘制出来,但缺点是这个函数在Windows8.1以下操作系统上无法获取到硬件加速区域的渲染数据,而且可能会造成目标窗口闪烁。Windows 8.1 及以上版本的系统,支持把 nFlags 设置为 PW_RENDERFULLCONTENT。这样即便是使用硬件加速区域的渲染数据也能获取到: PrintWindow(window_, mem_dc, PW_RENDERFULLCONTENT)。

相比 PrintWindow 而言, BitBlt 函数的优点是速度较快,从 Windows 7 开始支持了硬件加速,但其缺点是无法绘制被其他窗口遮挡的区域。

测试下来发现,Win8.1以上的系统,使用PrintWindow要比Magnification API性能高,可以获得较高得采集帧率。

针对无法使用 Magnification API 的情况,回退到 GDI 方案之后,又无法采集使用硬件加速渲染的应用,我们还有最后一个办法:通过采集目标窗口所在显示器的画面,计算目标窗口区域,再从截取到的显示器画面中把目标区域裁剪出来,从而得到最后的画面,但这个方案无法处理目标窗口被遮挡的场景。

3. Windows Grapics Capture(WGC API)

这种方案放在最后说,是因为微软推出的时间很晚,参考 New Ways to do Screen Capture。这个是千呼万唤始出来的技术,仅支持 Windows 10, Update(1809)(时间是2018-11-13)以及更高版本的操作系统。如果要使用 WGC,开发环境需要安装 Windows 10 SDK, version 1809(10.0.17763.0)及以上版本,这套 API 原本是给 UWP 程序使用的,如果想在 Win32 程序上使用,可以通过 C++/WinRT 来做。关于 C++/WinRT 的内容读者需要单独了解,这里就不做介绍了。

这套方案诞生之初,会给目标应用的窗口带一个的高亮的黄框,麻烦的是没有提供 API 控制这个黄框的开关,也不能换颜色,总之是无法控制,在这个问题被人们诟病了近3年之后,终于在 Windows 10,Update(21H1)(时间是2021-05-25)中加了一个控制开关  GraphicsCaptureSession.IsBorderRequired Property,开发环境需要更新 Windows SDK 到 10.0.20348.0 及以上版本。

同样地,WGC 也需要像放大镜 API 一样,手动处理目标窗口移动、大小变化、最小化、恢复、隐藏、关闭的场景。

WGC 可以根据产品的策略,决定 Windows 10, Update(1809)以上的系统是否都使用这种方案,或者是 Windows 10, Update(21H1)以上的系统才使用。

最后提一下,WGC 方案除了是官方针对屏幕分享场景而出的一套解决方案外,采集速度也是其他方案不能比拟的,这对于采集一些画面变化较快的场景(比如游戏,视频)来说,是一个很好的选择,可以比较完美地解决屏幕分享画面卡顿的问题。当然,天下没有免费的午餐,伴随高采集帧率而来的是较高的性能消耗。

(二)应用窗口采集策略 

针对上面介绍的技术,网易云信 SDK 在实现的时候做了如下策略:

  1. 如果系统高于 Windows 10, Update(1809),我们使用 WGC 方案;
  2. 其次是GDI 方案,如果采集对象是UWP窗口,或者当前运行在Win7系统上、没有开启Aero、并且采集窗口所在进程是黑名单项,那么执行回退策略;否则,继续使用 GDI 方案;
  3. Magnication API方案,在检测到采集的是黑屏数据,或者发现返回的数据格式不正确的时候,执行回退策略;
  4. 最后是通过 GDI 先采集指定显示器,然后裁剪目标区域的方式模拟应用采集。

(三) 显示器的采集 

这里,我们只讨论分享某一块显示器的场景,虚拟桌面场景方案类似。

1. Magnification API

同应用分享一样,我们优先考虑 Magnification API,因为这种方案支持过滤窗口,并且使用方法也是完全一样的,只不过是前面设置的是目标应用窗口的位置坐标,在这里换成要抓取的显示器坐标值。

2. DXGI

DXGI 用来管理独立于 Direct3D 图形运行时的低级任务。DXGI 为多个版本的 Direct3D 提供了一个通用框架。DXGI 的 Desk较好 Duplication 方案只能抓取显示器画面,正好可以使用在这种场景。DXGI 的架构图如下:

https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/images/dxgi-dll.png

Figure 1:DXGI架构

相比应用分享,有一些特殊的情况,比如正在分享的显示器(有意或者无意地)突然被移除了,这时候是直接退出分享?还是先暂停,然后等待显示器重新接上并继续分享呢?

3. GDI

GDI 也可以用在在采集屏幕上,而且兼容性很好,只是同样也不支持过滤窗口。

4. WGC

WGC 方案在使用上,相比与前面的应用窗口画面抓取,只需要注意把为窗口创建的 IGraphicsCaptureItem 换为显示器目标的即可,其余流程一致。这种方案只能通过SetWindowDisplayAffinity过滤本进程内的窗口。

(四) 显示器采集策略 

显示器采集的策略按照下面的原则执行:

如果系统高于 Windows 10, Update(20H1),我们使用 WGC 方案;

然后尝试使用 Magnification API,如果失败,执行回退策略;

尝试使用 DXGI 的方式采集显示器,如果不支持,那么执行回退策略;

最后一步尝试使用 GDI 方案。

三、macOS

Mac 上的取屏由于系统原本就提供了支持,比 Windows 上的简单很多,也不需要回退策略,故在此简单介绍,取屏的几个核心的方法是 Mac 上 CG 库提供的。

 (一)应用窗口采集 

核心方法是 CGWindowListCreateImage,为指定的一组窗口生成图片,同 Windows 系统上的应用窗口采集一样,需要处理窗口的变化。

 (二)显示器的采集 

显示器采集的核心方法是:

  1. CGWindowListCreateImageFromArray 支持从一组 Windows id 中生成一张图片;
  2. CGDisplayCreateImage 支持获取指定显示器的图片。

显示器分享时,对于过滤窗口的实现,大致思路是:先枚举屏幕上所有窗口,并把过滤窗口排除在外,得到一个窗口 id 数组;接着通过

CGWindowListCreateImageFromArray 得到这组窗口的一个截图 img_orig,然后通过计算过滤窗口的位置 rect,在 img_orig 中扣出 rect 处的图像 img_exclude。接着通过 CGDisplayCreateImage 得到目标显示器的截图 img_monitor,然后按照相对位置,把 img_exclude 盖在 img_monitor 上,这样就得到了一个看起来过滤掉某些窗口的显示器截图。

四、还有一些场景

在屏幕分享场景中,还有一些地方没有提及,比如 Windows 和 macOS 系统都支持一些视觉辅助功能,比如整屏图像放大,以及高清DPI。

macOS 和 Windows 10 上还有一个多桌面的概念,允许用户把一些程序拖动到这些桌面上运行。

Windows 上的 Office 和 WPS 的幻灯片应用,以及 macOS 中的 Keynote,开始播放幻灯片时,一般是新创建一个窗口,这时候如果不做特殊的处理,抓取到的就还是老的窗口显示数据,或者老的窗口可能已经被销毁了。

随着云信 G2 SDK 跟客户产品不断迭代更新,我们会为客户持续提供更加稳定易用的技术和产品,帮助各行各业的组织,连接和服务10亿人。

作者介绍

刘国元,网易云信资深开发工程师,长期从事桌面端开发,现专注于 RTC 方面的开发工作。

关于网易云信

网易云信:网易智企旗下融合通信云服务专家、通信与视频 PaaS 平台。集网易 24 年 IM 以及音视频技术打造的融合通信云服务专家,稳定易用的通信与视频 PaaS 平台。提供融合通信与视频的核心能力与组件,包含 IM 即时通讯、5G 消息平台、一键登录、信令、短信与号码隐私保护等通信服务,音视频通话、直播、点播、互动直播与互动白板等音视频服务,视频会议等组件服务,并联合网易易盾推出一站式安全通信方案「安全通」。目前,网易云信已经成功发送 1.6 万亿条消息,覆盖智能终端 SDK 数累计超过 186 亿,我们期待每个智能终端都有云信的融合通信能力。

文章标题:桌面端屏幕分享实践,发布者:网易智企,转载请注明出处:https://worktile.com/kb/p/6009

(3)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
网易智企的头像网易智企认证作者
上一篇 2022年3月17日 上午12:44
下一篇 2022年3月17日 上午12:49

相关推荐

  • 如何管理好基建项目建设

    基建项目建设的管理是一个复杂而详尽的过程,它涉及项目策划、资源配置、时间管理、成本控制、质量保证、风险应对以及沟通协调。成功管理基建项目建设核心要素包括严密的项目计划、全面的风险评估、高效的资源调配、严格的成本控制、精细的施工监督、以及有效的沟通协作。特别值得强调的是,全面的风险评估能够帮助项目管理…

    2024年4月10日
    6200
  • 车间管理系统的设计与实现

    车间管理系统的设计与实现是为了优化生产流程、提高生产效率以及保证产品质量。车间管理系统应包括生产计划编制、现场作业管理、数据采集与分析、设备维护等模块,且需要与企业的其他管理系统如ERP、SCM等无缝对接。在这些模块中,实时数据采集与分析功能至关重要,它能够为管理层提供生产过程中的实时信息,辅助决策…

    2024年1月9日
    32800
  • DevOps项目中的需求管理

    在DevOps项目中,需求管理是保证软件开发流程高效性与软件质量关键的一环。需求管理的核心观点包括:1、持续集成与持续交付、2、自动化测试、3、实时反馈机制、4、跨功能团队合作、5、灵活性与适应性强。持续集成与持续交付确保需求快速准确地被转化为产品特性;自动化测试守卫软件质量,提高需求实现的效率;实…

    2023年12月13日
    33100
  • 数据库到底指的什么

    数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。除了文本类型的数据,图像、音乐、声音都是数据。 一、数据库到底指的什么 数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条…

    2023年7月22日
    45100
  • oa怎么销假

    工作流程自动化系统(OA)撤销休假的方法主要包括:1、登录系统、2、定位至撤销申请模块、3、填写撤销原因、4、提交审批、5、等待审核。 具体来说,OA系统通常允许员工以电子方式提出休假申请,并在计划变更时,通过系统撤销或修改原先的申请。一些系统可能会要求提供详尽的撤销原因,以便管理层更好地理解情况。…

    2024年1月11日
    54800
  • 研发文档系统有哪些功能

    研发文档系统的功能有:1、用户分类;2、文档管理设置;3、设置权限。企业需要一套完整的文档管理系统,可以帮助企业在互联网时代提高工作效率。一套文档管理系统可以帮助其轻松地处理多个部门共同处理的日常工作资料。 研发文档系统有哪些功能 当前,随着互联网技术的发展,企业需要一套完整的文档管理系统,可以帮助…

    2022年11月14日
    57900
  • devops中ops是什么意思

    DevOps为开发与运营的融合所创设的术语,OPS旨在指代IT运营(OPERATIONS)。1、IT运营职责涵盖系统部署、监管、维护与支持,确保平稳运行及优化资源使用。2、另外强化安全与合规性同样属于运营的重要分支。DevOps文化中,运营专家参与整个软件生命周期,尤其致力于持续集成与持续交付(CI…

    2024年3月26日
    13500
  • vscode为什么编辑不了

    Visual Studio Code(VSCode)可能编辑不了文件的原因包括:文件权限问题、插件冲突、软件版本不兼容、损坏的安装文件和配置错误。其中,文件权限问题是最常见的原因之一。当操作系统限制了对某个文件或目录的访问时,VSCode作为一个应用程序也会受到这些限制。例如,在Linux或macO…

    2024年4月3日
    12200
  • 注册测试用例怎么写

    步骤是:1、确定测试目的;2、收集测试数据;3、编写测试用例;4、执行测试用例;5、整理测试结果。首先,需要确定测试目的。在编写测试用例之前,需要明确要测试什么。例如,测试注册表单的正确性、测试注册流程的可用性等。 1、确定测试目的 首先,需要确定测试目的。在编写测试用例之前,需要明确要测试什么。例…

    2023年2月27日
    58801
  • cpu的基本时间单位是什么

    cpu的基本时间单位是线程。线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一、线程简介 cpu的基本时间单位是线程。线程(thread)是操作系…

    2023年1月9日
    1.2K00

发表回复

登录后才能评论

评论列表(1条)

  • 7985
    7985 2023年7月7日 下午6:15

    这个方案如下向dxgi一样满足如下需求
    1)如何拿单独鼠标(而不是将鼠标嵌入图里面)
    2)如何拿到赃区域(即变化区域,而不是全图)
    谢谢

注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部