大多数浏览器和
Developer App 均支持流媒体播放。
-
将 ScreenCaptureKit 提升到全新境界
了解如何利用 ScreenCaptureKit 为您 App 的用户提供复杂屏幕截图体验。我们将探索众多您可以添加的高级选项,包括对内容过滤器进行微调、帧元数据解释,以及窗口挑选器,等等。我们还将向您介绍如何配置您的流媒体,以实现最优性能。
资源
相关视频
WWDC23
WWDC22
-
下载
♪ 柔和乐器演奏的嘻哈音乐 ♪ ♪ Meng Yang: 嗨 我是 Meng Yang Apple GPU 软件团队的工程师 今天我将介绍有关 ScreenCaptureKit 的 几个高阶主题 以及它将如何 让您 App 的屏幕 共享体验更上一层楼 稍后 我的同事 Drew 将演示 如何实际运用 这个令人兴奋的新 API 屏幕捕捉是屏幕共享 App 的核心 这些 App 包括 Zoom Google Meet、SharePlay 还有广受欢迎的 游戏串流服务 比如 Twitch 这已成为过去几年中我们工作、学习 合作和社交的新常态 ScreenCaptureKit 是全新构建的 高性能屏幕捕捉框架 拥有强大的功能集 它丰富的功能集里包含 高度定制化的 内容控制功能 让您可以轻松挑选 任意窗口、App 或显示器组合 并进行捕捉 它还具备捕捉屏幕内容 原生分辨率和帧率的能力 它拥有针对分辨率、帧率和 像素格式的动态流属性控制 这些控制可以随时修改 无需重新创建流 它配备了由 GPU 内存支持的捕捉缓存 用于减少内存副本 它还可以实现 硬件加速的内容捕捉、缩放 和像素及颜色格式转换 以降低 CPU 使用率 实现高性能捕捉 最后 它还支持视频和音频捕捉 在正式开始之前 这个讲座假设您已经熟悉 其基本概念、构建模块 以及框架运行工作流的假设 您可以查看入门讲座 “ScreenCaptureKit 简介” 了解更多信息 在今天的讲座中 我将介绍 如何捕捉和显示单个窗口 接下来 如何将屏幕内容添加到全屏捕捉 如何从显示捕捉中删除内容 然后 我将展示一些针对不同用例的 流配置方法 在最后的演示中 您将 看到 ScreenCaptureKit 如何彻底改变了一款 受欢迎的开源屏幕捕捉 App OBS Studio 的屏幕 和音频捕捉体验 现在 让我们从第一个例子 可能也是最常见的用例开始 捕捉单个窗口 这个例子将用于讲解 如何设置单窗口筛过滤器 当捕捉的窗口正在进行大小调整 受到遮挡、移出屏幕或最小化时 流输出将会受到怎样的影响 您还将了解如何使用逐帧元数据 以及如何正确显示捕捉的窗口 让我们开始吧 要对不受所在显示器制约的 单个窗口进行捕捉 您可以首先使用单窗口过滤器 并仅针对一个窗口初始化过滤器 在这个例子中 过滤器的配置为仅包含 单个 Safari 浏览器窗口 视频输出只针对该窗口进行 不影响其他部分 Safari 浏览器的子窗口 弹出窗口和其他窗口 都不会包含在内 另一方面 ScreenCaptureKit 的音频捕捉规则 始终在 App 这一级别运行 当使用单窗口过滤器时 包含此窗口的 App 中 所有音频内容 都将被捕捉 即使音频来自 视频输出中不存在的窗口 现在让我们来看看代码示例 要创建具有单个窗口的流 首先要通过 SCShareableContent 获取所有可用的共享内容 接下来 通过匹配 windowID 从 SCShareableContent 中 获取想要共享的窗口 然后 用指定的 SCWindow 创建类型为 desktopIndependentWindow 的 SCContentFilter 您可以进一步配置流 让音频成为 流输出的一部分 现在您已做好准备 可以使用 contentFilter 和 streamConfig 创建流 然后您可以添加 StreamOutput 并启动流 接下来我们查看一下流输出 在这个例子里 左侧是源显示 右侧是流输出 流过滤器只保留了 一个 Safari 浏览器窗口 现在我要开始滚动被捕捉的 Safari 浏览器窗口 流输出也覆盖了 这个 Safari 浏览器 窗口中的实时内容 并实现与源窗口的同步更新 最高可达源显示的原生帧率 比如说 如果源窗口在 在 120Hz 显示器上不断更新 那么流输出也可以 实现最高每秒 120 帧的更新 您可能想知道当 窗口大小变化时会发生什么 请记住 经常变更 流输出的尺寸 可能导致额外的内存分配 因此不推荐这么做 流输出的尺寸大多是固定的 并且不会随着源窗口调整大小 现在我要开始调整源窗口的大小 看看流输出会发生什么变化 ScreenCaptureKit 始终在捕捉的窗口上 执行硬件缩放 因此 无论源窗口如何放大缩小 它永远不会超过输出帧 那么如果窗口被 其他窗口遮挡会怎么样呢? 当源窗口被完全遮挡或部分遮挡时 流输出始终包括 窗口的全部内容 同理 当窗口完全 移到屏幕之外 或移至其他显示器时也是如此 而对于最小化的窗口 当源窗口最小化时 流输出暂停 并在源窗口不再 处于最小化状态时恢复 接下来 让我们谈谈音频输出 在这个例子中 两个 Safari 浏览器窗口 都带有音轨 而正在进行捕捉的是左侧的窗口 视频输出只包含第一个窗口 而两个 Safari 浏览器窗口的音轨 都会出现在音频输出中 让我们来看一看听一听 ♪ 电子舞曲 ♪ 厨师:我写下了最爱的鳄梨酱食谱 需要四个鳄梨 Meng:随着流的启动和运行 您的 App 会在每次 出现可用的新画面时 收到帧更新 帧的输出中含有 IOSurface 代表捕捉的帧和逐帧元数据 我想花一点时间谈谈元数据 我将演示可能 对您的 App 非常有用的 元数据的示例 包括脏矩形、内容矩形 内容比例和比例因数 让我们从脏矩形开始 脏矩形指示了新内容 位于前一帧哪个位置 在这个例子中脏矩形被突出显示 来显示帧更新的区域 不需要始终对整帧进行编码 或计算编码器中两帧之间的差量 您可以简单地使用脏矩形 仅对发生更新的区域进行编码和传输 并将更新复制到前一帧 在接收端生成新帧 脏矩形可以从输出 CMSampleBuffer 中检索 使用匹配键值的元数据字典 现在让我们来看看 内容矩形和内容比例 左侧是要捕捉的源窗口 右侧是流输出 由于可以调整窗口大小 源窗口的原生外观尺寸 通常与流输出的尺寸不匹配 在这个例子中 捕捉的窗口与 与帧输出的画幅比例不同且更大 因此捕捉窗口被缩小以适应输出 内容矩形 这里以绿色突出显示 表示捕捉内容在流输出中的 兴趣区域 而内容比例表示内容 缩放以进行适应的程度 这里捕捉的 Safari 浏览器窗口按比例缩小 至 0.77 以适应输出帧 现在您可以使用刚才谈到的元数据 来正确显示捕捉的窗口 使其尽可能接近原生外观 首先 让我们使用内容矩形 将内容从输出中裁剪出来 接下来 通过除以 内容比例来重新放大内容 现在捕捉的内容经缩放按一比一 像素大小匹配源窗口 但捕捉的窗口在目标显示器上 看起来如何呢? 为了回答这个问题 我想先描述一下 比例因数的工作原理 显示器的比例因数表示 显示器或窗口的逻辑点大小 和其支持表面的像素大小之间的比例 比例因数为 2 也称为 2x 模式 表示屏幕上的每一点 都等于支持表面中的四个像素 在进行捕捉时 窗口可以从 像本例中这样比例因数 为 2 的视网膜显示器 移动到比例因数为 1 的 非视网膜显示器 比例因数为 1 时 屏幕中的每个逻辑点 都对应后表面中的一个像素 此外 源显示器可能与 显示捕捉内容的目标显示器 比例因数不匹配 在这个例子中 窗口在从左侧 比例因数为 2 的 视网膜显示屏中进行捕捉 并显示在右侧的非视网膜显示屏上 如果捕捉的窗口按原样显示 而不在目标的非视网膜显示器上 进行一点对一像素映射 窗口看起来会是原来的四倍大 要解决这个问题 您应该始终检查 帧元数据中的比例因数 并与目标显示器的比例因数进行对比 当两者不匹配时 需要按照比例因数 在显示之前缩放捕捉的内容 缩放后 捕捉的窗口在目标显示器上 看起来与源窗口尺寸相同 现在让我们来看一下代码 这里的代码很简单 内容矩形、内容比例和比例因数 也可以从输出 CMSampleBuffer 中检索 元数据附件 然后您可以使用这些元数据 对捕捉内容进行 裁剪和缩放 使其正确显示 简单总结一下 单窗口过滤器 始终包含完整的窗口内容 即使源窗口移出屏幕或被遮挡 它不受显示器和空间影响 输出总是以左上角为基准偏移 捕捉不包含弹出窗口或子窗口 您可用考虑使用元数据 来获得显示内容的最佳效果 音频输出将包含 整个 App 中的所有音轨 现在您已经了解了如何捕捉 并显示单一窗口 让我继续介绍下一类 基于显示的内容过滤器 在下一个例子中 您将了解如何使用窗口或 App 创建基于显示的过滤器 我也会演示 视频和音频过滤规则之间的一些差异 基于显示的包含过滤器将指定 您想从哪些显示中捕捉内容 默认情况下 不对任何窗口进行捕捉 您可以通过窗口选择要捕捉的内容 在这个示例中 Safari 浏览器窗口 和 Keynote 讲演窗口 被添加到了显示过滤器中 视频输出仅包括这两个 放置于显示空间的窗口 音频输出则包括 来自 Keynote 讲演 和 Safari 浏览器 App 的所有音轨 这段代码示例展示了如何使用窗口 创建基于显示的过滤器 首先使用 SCShareableContent 和 windowID 创建 SCWindows 列表 接下来 使用给定的显示和包含的窗口列表 创建基于显示的 SCContentFilter 然后您可以用和桌面单一窗口 同样的方式 使用过滤器和配置 创建并启动流 流启动和运行后 我们来看看流输出 过滤器的配置为包含 两个 Safari 浏览器窗口 菜单栏和墙纸窗口
如果窗口被移出屏幕 它也将从流输出中移除 创建新的 Safari 浏览器窗口时 新窗口不会显示在流输出中 因为这个新窗口不在过滤器里 子窗口和弹出窗口也一样 它们不会出现在流输出中 如果要确保子窗口 自动包含在流输出中 您可以通过被包含的 App 使用基于显示的过滤器 在这里 将 Safari 浏览器 和 Keynote 讲演 App 添加到过滤器 确保这两个 App 的 所有窗口和声道的音视频输出 包含在流输出中 窗口例外过滤器是一种有效的方法 可以在过滤器被指定为 包含某些 App 的一个显示器时 将特定窗口从输出中排除 例如 可以将某一个 Safari 浏览器窗口 从输出中删除 ScreenCaptureKit 在 App 级别启用音频捕捉 因此从单个 Safari 浏览器 窗口中排除音频 相当于删除了整个 Safari 浏览器 App 的音轨 虽然视频输出流 依然包含了 Safari 浏览器窗口 但 Safari 浏览器 App 中的 所有音轨都已删除 并且音频输出仅包括 来自 Keynote 讲演的音轨 在这里的代码示例中 我们更改了 SCContentFilter 以包含 SCRunningApplications 列表 而非 SCWindows 如果您需要进一步排除个别窗口 可以先构建 SCWindows 列表 然后创建 SCContentFilter 配合 SCApplications 列表与 与例外窗口列表进行排除 我们来看一看 输出流会是什么样 在通过指定包含的 App 创建新窗口或子窗口时 这一次 Safari 浏览器 App 和系统窗口被添加到过滤器中 现在新的 Safari 浏览器窗口会 自动包含在流输出中 同样的规则也适用于 子窗口和弹出窗口 这在您做教程并想展示 展示完整的操作 包括启动 弹出窗口或新窗口时非常有用 刚才我演示了如何用 几种不同的方式向流输出添加内容 我的下一个例子将向您展示 如何从流输出中删除内容 这个例子包括一个模拟 视频会议 App 的测试 App 包含正在共享的显示器预览 因为测试 App以递归方式 将自己显示在预览中 它造成了所谓的镜厅效应 即使在全屏共享期间 屏幕共享 App 也经常 删除自己的窗口、捕捉的预览和 参与者相机视图 以避免镜厅效应 它也会删除其他系统 UI 例如通知窗口 ScreenCaptureKit 为您提供 一组基于排除的过滤器 使您可以 从显示捕捉中快速删除内容 基于排除的显示过滤器在默认情况下 从给定的显示中捕捉所有窗口 您之后就可以将单个窗口或 App 添加到排除过滤器中 开始对它们进行删除 例如 您可以 将内容捕捉测试 App 和“通知中心”添加 到需要排除的 App 列表中 要创建基于显示的过滤器 用于排除 App 列表 首先通过匹配 Bundle ID 检索 需要排除的 SCApplication 如果您想挑选个别窗口 回到流输出中 也可以创建 例外 SCWindow 的可选列表 然后使用给定的显示器 要排除的 App 列表 以及例外窗口列表 来创建内容过滤器 让我们看看结果如何 导致镜厅问题的内容捕捉测试 App 和通知窗口 都从流输出中删除了 这些 App 的新窗口或子窗口 也将被自动删除 如果被删除的 App 中 包含任何音频 这些音频也将从音频输出中删除 我们刚刚了解了如何捕捉单个窗口 如何在显示过滤器中添加和删除窗口 接下来让我们谈谈流配置 在以下几个示例中 您将了解 可供配置的不同流属性 如何为屏幕捕捉和流式传输设置流 以及如何创建带有 实时预览的窗口选择器 让我们从配置属性开始 这是一些常见的可供配置的 流属性 例如流输出尺寸 源和目标矩形 色彩空间、色彩矩阵和像素格式 是否包含游标 以及帧率控制 接下来 我们将详细了解每个属性 让我们从输出尺寸开始 可以为它指定以像素为单位的宽和高 源显示器的尺寸和画幅比例 并不总是与输出尺寸相匹配 如果在捕捉完整画面时 发生不匹配的情况 流输出中会出现纵向或横向黑边 您也可以指定源矩形 捕捉框定区域 结果将被缩放并渲染到 帧输出上的目标矩形 ScreenCaptureKit 支持硬件加速的 色彩空间、色彩矩阵和像素格式转换 也支持常见的 BGRA 和 YUV 格式 请访问我们的开发者 页面获取完整列表 启用显示游标时 流输出中将包含 预渲染到画面中的游标 这适用于所有系统游标 甚至是这种相机形状的自定义光标 您可以使用最小帧间隔 来控制理想的输出帧率 例如 当请求 60 帧每秒时 可以将最小间隔设置为 1/60 您将收到不超过 60 帧每秒并且 不超过内容的原生帧率的的帧更新 可以通过指定队列深度来确定 服务器侧表面池中的表面数 池中的表面数更多 可以让帧率和性能更好 但也会导致更高的系统内存占用 还可能因取舍而出现延迟 稍后我会更详细地进行讨论 ScreenCaptureKit 接受的队列深度范围 为 3 到 8 默认队列深度为 3 在这个例子中 表面池经过配置 包含了四个 可用于 ScreenCaptureKit 渲染的表面 当前的活跃表面是表面 1 ScreenCaptureKit 正在渲染它的下一帧 一旦表面 1 完成 ScreenCaptureKit 将把表面 1 发送到您的 App 中 在您的 App 处理 并持有表面 1 时 ScreenCaptureKit 正在渲染到表面 2 表面 1 现在 在池中被标记为不可用 因为您的 App 仍在使用它 表面 2 完成后 将发送到您的 App 同时 ScreenCaptureKit 开始渲染到表面 3 但如果您的 App 仍在处理表面 1 它将开始落后 因为现在帧的提供 比处理速度更快 如果表面池中包含大量表面 新的表面将开始堆积 您可能需要考虑通过掉帧 来跟上速度 在这种情况下 池中的表面越多 可能会导致越高的延迟 池中剩余的可供 ScreenCaptureKit 使用的表面数量等于队列深度 减去您 App 持有的表面数量 在这个例子中 表面 1 和 2 仍由您的 App 持有 表面池中还剩下 2 个表面 在表面 3 完成 并发送到您的 App 后 池中唯一可用的表面 只剩下了表面 4 如果您的 App 继续持有表面 1、2 和 3 ScreenCaptureKit 很快就将用完可供渲染的表面 您也将开始看到掉帧和瑕疵 您的 App 必须在 ScreenCaptureKit 开始渲染表面 4 后一帧之前 完成并释放表面 1 避免发生掉帧 现在您的 App 释放了表面 1 它就可以供 ScreenCaptureKit 再次使用 回顾一下:您的 App 需要遵循两条规则 来避免帧延迟和掉帧 为避免帧延迟 您要能够在 MinimumFrameInterval 内处理完一帧 为避免丢帧 您的 App 所需要的 将表面释放回池中的时间必须小于 MinimumFrameInterval 乘以 QueueDepth 减 1 超过这个时间 ScreenCaptureKit 会消耗完可使用的表面 进入停滞 并开始错过新的帧 现在您了解了可供配置的 各种属性 让我们深入探讨几个 配置流以进行屏幕捕捉 和流式传输的例子 视频、游戏和动画等屏幕内容 都处于不间断地更新中 这就需要更高的帧率 而主要包括静态文本的屏幕内容 比如 Keynote 讲演窗口 优先考虑更高的分辨率而不是帧率 您可以根据分享内容和网络状况 实时调整流配置 在这段代码示例中 您将看到 如何进行捕捉配置 来实现 4K 分辨率、60 帧每秒的游戏流传输 首先您可以将流输出尺寸设定为 像素表示的 4K 分辨率 然后通过 将最小帧间隔设置为 1/60 将输出帧率设置为 60 帧每秒 接下来 使用像素格式 YUV420 进行编码和流式传输 将可选的源矩形设置为 仅捕捉屏幕的一部分 接下来 将背景填充颜色更改为黑色 并在帧输出中包含一个光标 将表面队列深度配置为 5 以获得最优帧率和性能 最后 在输出流上启用音频 您在之前的例子中看到的所有流配置 都可以随时进行变更 无需重新创建流 比如说 您可以实时调整一些属性 例如输出尺寸、动态调整帧率 以及更新流过滤器 这个例子中 输出尺寸 从 4K 下降到 720p 帧率也从 每秒 60 帧降到每秒 15 帧 然后您可以简单地 调用 updateConfiguration 即时应用新设置 而不会中断流传输 在最后一个例子中 我想向您介绍 如何构建一个 带实时预览的窗口选择器 这是一个 典型的窗口选择器外观示例 网络会议屏幕共享 App 经常为用户提供选择 具体需要共享的窗口这一功能 ScreenCaptureKit 提供了一种高效的 高性能的解决方案来创建大量 进行实时内容更新的缩略图大小的流 而且实施起来非常容易 让我们对它进行分解 看看要如何 构建一个像这样的 使用 ScreenCaptureKit 的窗口选择器 要设置选择器 首先您可以为 每个符合条件的窗口 即您的 App 允许用户选择的窗口 都创建单窗口过滤器 选用桌面无关的窗口作为过滤器类型 接下来 设置流配置为 缩略图大小、5 帧每秒 BGRA 像素格式 用于屏幕显示、 默认队列深度、无光标无音频 在此处使用单窗口过滤器和流配置 为每个窗口创建流 要在代码中执行此操作 首先您可以通过 排除桌面和系统窗口来获得 SCShareableContent 接下来 为每个符合条件的窗口 创建类型为 桌面无关的窗口内容过滤器 接下来 进入流配置部分 选择合适的缩略图大小 在这个例子中 尺寸是 284 x 182 然后将最小帧间隔设置为 1/5 采用 BGRA 像素格式进行屏幕显示 禁用音频和光标 因为我们不需要 在预览中使用它们 接下来将队列深度设置为 3 因为我们不希望更新 过于频繁 使用流内容过滤器和创建的配置 您已经做好了创建流的准备 为每个窗口创建一个流 为每个流添加流输出 然后启用流 最后 将其附加到流列表中 这就是使用我们之前看到的示例代码 创建的带实时预览的窗口选择器 每个缩略图都实时更新 由带有单窗口过滤器的 单独流进行支持 使用 ScreenCaptureKit 您可以轻松构建 像这样的实时预览选择器 让您可以同时捕捉大量实时屏幕内容 同时 不会使系统负担过重 现在让我把时间 交给我的同事 Drew 他将进行一次令人兴奋的演示 关于如何在 OBS 上采用 ScreenCaptureKit Drew Mills:谢谢你 Meng 嗨 我是 Drew 我是 Apple 的合作伙伴工程师 OBS Studio 是一个开源 App 让用户可以从电脑对内容录制 和流式传输进行管理 它包含了一项我们在今年春天 合作整合的 ScreenCaptureKit 实现 ScreenCaptureKit 的实现非常轻松 因为它的代码与 OBS 现有的基于 CGDisplayStream 的捕捉类似 ScreenCaptureKit 的实现展示了 “ScreenCaptureKit 简介” 讲座中 讨论到的许多功能 包括:捕捉整个桌面 捕捉 App 的所有窗口 或是一个特定的窗口 ScreenCaptureKit 的开销低于 OBS 基于 CGWindowListCreateImage 的捕捉 这意味着在捕捉屏幕的一部分时 您还拥有更多剩余资源 可以用于产出内容 让我们进入演示 来看看我们讨论内容的实际应用 左侧是一个最糟糕的 OBS 窗口捕捉案例 这种捕捉使用 CGWindowListCreateImage API 并且存在明显的卡顿 在测试中 我们发现 帧率已经下滑至最低每秒 7 帧 同时 右侧的 ScreenCaptureKit 实现 结果更加平滑 它提供的输出视频中 动作明显更加顺畅 在这个案例中 帧率达到了 60 帧每秒 实现这些的同时 OBS 使用的 RAM 相比于 “窗口捕捉”最多可以减少 15% OBS 的 CPU 使用率可以降低一半 如果使用 ScreenCaptureKit 而非 OBS 的“窗口捕捉” 让我们再来看看 ScreenCaptureKit 为 OBS 用户带来的其他改进 我仍在努力 想获得《再见狂野之心》中 所有的黄金等级 我想炫耀自己最好的表现 所以一直在玩游戏的时候录屏 有了 ScreenCaptureKit 我现在可以直接捕捉 游戏本身的音频流 所以当我在 Mac 上收到通知时 它不会破坏我录制的音频和视频 而这一切都可以在无需安装 任何额外音频路由软件的前提下实现 ♪ 现在 使用所有 由 Apple 芯片上的 ScreenCaptureKit 提供的增强功能 我可以将《太鼓达人 Pop Tap Beat》 这样的游戏 从我的 Mac 传输到 常用的串流服务 Apple 芯片硬件编码器的 新恒定比特率选项意味着我可以 将流内容编码为恒定比特率 而不对游戏性能产生显著影响 现在 因为有了 ScreenCaptureKit 的 更低资源消耗和编码负载降低 我甚至可以将更多性能用于 重要的内容 现在交给你了 Meng Meng:谢谢你 Drew 通过演示和案例 您对高阶屏幕内容 过滤器有了一定的认知 还了解了针对不同用例 配置流传输的几种方法 以及如何使用逐帧元数据并正确显示 捕捉的内容 您还了解了一些可以 帮助您实现最优性能的最佳实践 最后 Drew 展示了 ScreenCaptureKit 为 OBS 带来的 卓越性能和效能改善 我迫不及待地想看看您如何借助 ScreenCaptureKit 的功能 重新定义您 App 的屏幕共享 流式传输和协作体验 感谢您的观看! ♪
-
-
4:36 - Create a single window filter
// Get all available content to share via SCShareableContent let shareableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false) // Get window you want to share from SCShareableContent guard let window : [SCWindow] = shareableContent.windows.first( where: { $0.windowID == windowID }) else { return } // Create SCContentFilter for Independent Window let contentFilter = SCContentFilter(desktopIndependentWindow: window) // Create SCStreamConfiguration object and enable audio capture let streamConfig = SCStreamConfiguration() streamConfig.capturesAudio = true // Create stream with config and filter stream = SCStream(filter: contentFilter, configuration: streamConfig, delegate: self) stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: serialQueue) stream.startCapture()
-
9:38 - Get dirty rects
// Get dirty rects from CMSampleBuffer dictionary metadata func streamUpdateHandler(_ stream: SCStream, sampleBuffer: CMSampleBuffer) { guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: false) as? [[SCStreamFrameInfo, Any]], let attachments = attachmentsArray.first else { return } let dirtyRects = attachments[.dirtyRects] } } // Only encode and transmit the content within dirty rects
-
13:34 - Get content rect, content scale and scale factor
/* Get and use contentRect, contentScale and scaleFactor (pixel density) to convert the captured window back to its native size and pixel density */ func streamUpdateHandler(_ stream: SCStream, sampleBuffer: CMSampleBuffer) { guard let attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: false) as? [[SCStreamFrameInfo, Any]], let attachments = attachmentsArray.first else { return } let contentRect = attachments[.contentRect] let contentScale = attachments[.contentScale] let scaleFactor = attachments[.scaleFactor] /* Use contentRect to crop the frame, and then contentScale and scaleFactor to scale it */ } }
-
15:37 - Create display filter with included windows
// Get all available content to share via SCShareableContent let shareableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false) // Create SCWindow list using SCShareableContent and the window IDs to capture let includingWindows = shareableContent.windows.filter { windowIDs.contains($0.windowID)} // Create SCContentFilter for Full Display Including Windows let contentFilter = SCContentFilter(display: display, including: includingWindows) // Create SCStreamConfiguration object and enable audio let streamConfig = SCStreamConfiguration() streamConfig.capturesAudio = true // Create stream stream = SCStream(filter: contentFilter, configuration: streamConfig, delegate: self) stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: serialQueue) stream.startCapture()
-
18:13 - Create display filter with included apps
// Get all available content to share via SCShareableContent let shareableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false) /* Create list of SCRunningApplications using SCShareableContent and the application IDs you’d like to capture */ let includingApplications = shareableContent.applications.filter { appBundleIDs.contains($0.bundleIdentifier) } // Create SCWindow list using SCShareableContent and the window IDs to except let exceptingWindows = shareableContent.windows.filter { windowIDs.contains($0.windowID) } // Create SCContentFilter for Full Display Including Apps, Excepting Windows let contentFilter = SCContentFilter(display: display, including: includingApplications, exceptingWindows: exceptingWindows)
-
20:46 - Create display filter with excluded apps
// Get all available content to share via SCShareableContent let shareableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false) /* Create list of SCRunningApplications using SCShareableContent and the app IDs you’d like to exclude */ let excludingApplications = shareableContent.applications.filter { appBundleIDs.contains($0.bundleIdentifier) } // Create SCWindow list using SCShareableContent and the window IDs to except let exceptingWindows = shareableContent.windows.filter { windowIDs.contains($0.windowID) } // Create SCContentFilter for Full Display Excluding Windows let contentFilter = SCContentFilter(display: display, excludingApplications: excludingApplications, exceptingWindows: exceptingWindows)
-
28:46 - Configure 4k 60FPS capture for streaming
let streamConfiguration = SCStreamConfiguration() // 4K output size streamConfiguration.width = 3840 streamConfiguration.height = 2160 // 60 FPS streamConfiguration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(60)) // 420v output pixel format for encoding streamConfiguration.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange // Source rect(optional) streamConfiguration.sourceRect = CGRectMake(100, 200, 3940, 2360) // Set background fill color to black streamConfiguration.backgroundColor = CGColor.black // Include cursor in capture streamConfiguration.showsCursor = true // Valid queue depth is between 3 to 8 streamConfiguration.queueDepth = 5 // Include audio in capture streamConfiguration.capturesAudio = true
-
30:08 - Live downgrade 4k 60FPS to 720p 15FPS
// Update output dimension down to 720p streamConfiguration.width = 1280 streamConfiguration.height = 720 // 15FPS streamConfiguration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(15)) // Update the configuration try await stream.updateConfiguration(streamConfiguration)
-
31:57 - Build a window picker with live preview
// Get all available content to share via SCShareableContent let shareableContent = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true) // Create a SCContentFilter for each shareable SCWindows let contentFilters = shareableContent.windows.map { SCContentFilter(desktopIndependentWindow: $0) } // Stream configuration let streamConfiguration = SCStreamConfiguration() // 284x182 frame output streamConfiguration.width = 284 streamConfiguration.height = 182 // 5 FPS streamConfiguration.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(5)) // BGRA pixel format for on screen display streamConfiguration.pixelFormat = kCVPixelFormatType_32BGRA // No audio streamConfiguration.capturesAudio = false // Does not include cursor in capture streamConfiguration.showsCursor = false // Valid queue depth is between 3 to 8 // Create a SCStream with each SCContentFilter var streams: [SCStream] = [] for contentFilter in contentFilters { let stream = SCStream(filter: contentFilter, streamConfiguration: streamConfig, delegate: self) try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: serialQueue) try await stream.startCapture() streams.append(stream) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。