System & Services 进阶 20m
将 ScreenCaptureKit 推向新高度
Take ScreenCaptureKit to the next level
2022年6月6日
一句话判断
ScreenCaptureKit 的高级功能——单窗口捕获、内容增减、逐帧元数据——让屏幕共享 App 的画质和性能可以上一个台阶。
这场 Session 讲了什么
这是 ScreenCaptureKit 的进阶 Session(基础内容在 “Meet ScreenCaptureKit” 中)。屏幕共享是 Zoom、Google Meet、SharePlay、Twitch 等应用的核心功能。ScreenCaptureKit 是 Apple 全新构建的高性能屏幕捕获框架,具备 GPU 内存缓冲、硬件加速缩放和色彩转换等特性。
Session 聚焦五个核心主题:单窗口捕获(独立于显示器)、显示捕获中的内容增减、流配置的动态调整、逐帧元数据的应用,以及 OBS Studio 的实际集成案例。
单窗口捕获模式用 SCContentFilter 的 desktopIndependentWindow 初始化,输出只包含目标窗口。当窗口被遮挡、移出屏幕或最小化时,捕获行为各有不同——了解这些边界情况对构建稳定的屏幕共享体验至关重要。
值得深挖的点
- 单窗口捕获的边界行为:窗口被遮挡时输出仍然包含完整内容;移出屏幕时同上;最小化时输出暂停,恢复后自动继续。窗口缩放时,输出尺寸不变,系统自动做硬件缩放。
- 逐帧元数据:每帧包含 dirty rects(变化区域)、content rect(内容区域)、content scale(缩放比例)和 scale factor(显示缩放因子)。利用这些数据可以只编码变化区域,大幅降低编码开销。这对低带宽场景(如远程桌面)的性能提升非常明显。
- 内容增减:显示捕获模式下,可以用
SCContentFilter排除特定窗口或 App。比如共享整个桌面但排除你自己的聊天窗口。音频捕获始终在 App 级别工作——即使只捕获一个窗口,该 App 所有窗口的音频都会被捕获。 - 动态流配置:分辨率、帧率、像素格式可以在不重建流的情况下动态修改。但在流运行时频繁修改输出尺寸会触发额外的内存分配,不推荐。
代码片段
// 单窗口捕获
let content = try await SCShareableContent.excludingDesktopWindows(
false, onScreenWindowsOnly: false
)
let targetWindow = content.windows.first { $0.windowID == windowID }!
let filter = SCContentFilter(
desktopIndependentWindow: targetWindow
)
// 配置流——可以包含音频
let config = SCStreamConfiguration()
config.queueDepth = 5
config.showsCursor = true
config.capturesAudio = true
let stream = SCStream(filter: filter, configuration: config, delegate: self)
try stream.addStreamOutput(self, type: .screen, sampleBufferQueue: captureQueue)
// 处理帧数据和元数据
func stream(_ stream: SCStream, didOutputSampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
guard let attachments = didOutputSampleBuffer.attachments else { return }
// 获取 dirty rects——只编码变化区域
if let dirtyRects = attachments[.dirtyRects] as? [CGRect] {
// 用 dirtyRects 优化编码,只传输变化部分
}
// 获取 content rect 和 scale
if let contentRect = attachments[.contentRect] as? CGRect,
let contentScale = attachments[.contentScale] as? Float {
// 裁剪并缩放到原始分辨率
// contentRect 指示捕获内容在输出帧中的位置
// contentScale 指示缩放比例
}
// scale factor 用于适配目标显示器的 Retina
if let scaleFactor = attachments[.scaleFactor] as? Float {
// 按目标显示器的 scale factor 调整渲染
}
}
最佳实践
- 避免在流运行时频繁修改输出尺寸,会导致额外内存分配
- 利用 dirty rects 做增量编码,减少网络带宽消耗
- 单窗口捕获的音频是 App 级别的,不是窗口级别的——向用户说明这一点
- 流的 queueDepth 建议设为 5,平衡延迟和流畅度
- 音频捕获策略需要明确告知用户哪些声音会被共享
还有什么值得关注
- “Meet ScreenCaptureKit” 是本 Session 的前置内容
- OBS Studio 已集成 ScreenCaptureKit,可以参考其实现
- ScreenCaptureKit 支持 SharePlay 场景,是构建协作类 App 的基础
- 框架在 macOS 12.3 及以上可用,注意版本兼容
WWDC 2022