1. macOS屏幕录制开发概述
在macOS平台上开发屏幕录制功能,过去主要依赖CGDisplayStream和CGWindowList等传统API。随着macOS Monterey的发布,Apple推出了全新的ScreenCaptureKit框架,为开发者提供了更强大、更高效的屏幕内容捕获解决方案。这个框架特别适合需要实现屏幕共享、视频会议、游戏直播等功能的应用程序开发。
ScreenCaptureKit的核心优势在于它能够以原生分辨率和帧率捕获屏幕内容,同时保持较低的CPU占用率。与旧方案相比,新框架提供了更精细的控制能力,允许开发者选择特定的窗口、应用程序或整个显示器进行捕获,并且可以实时调整捕获参数。
2. ScreenCaptureKit框架详解
2.1 框架核心组件
ScreenCaptureKit框架主要由以下几个核心类组成:
- SCShareableContent:表示可捕获的内容集合,包括显示器、应用程序和窗口
- SCContentFilter:定义捕获内容的过滤规则
- SCStreamConfiguration:配置捕获流的参数
- SCStream:管理捕获流的主要对象
- SCStreamOutput:处理捕获到的媒体样本
这些组件协同工作,为开发者提供了完整的屏幕捕获解决方案。框架采用现代Swift API设计,支持async/await异步编程模式,使得代码更加简洁易读。
2.2 权限与隐私考虑
在开始开发前,需要特别注意macOS的隐私权限设置。ScreenCaptureKit要求应用程序必须获得用户的明确授权才能捕获屏幕内容。系统会在首次尝试捕获时弹出权限请求对话框,用户需要在系统设置的"屏幕录制"权限中授予相应权限。
开发者应该优雅地处理权限被拒绝的情况,并提供清晰的指导说明如何重新启用权限。值得注意的是,音频捕获需要单独的麦克风权限,这一点经常被开发者忽略。
3. 实现基础屏幕捕获
3.1 获取可共享内容
首先需要获取当前系统中可捕获的内容列表。以下代码展示了如何获取屏幕上所有可见窗口的内容:
let content = try await SCShareableContent.excludingDesktopWindows( false, onScreenWindowsOnly: true )这个方法返回一个SCShareableContent对象,包含当前可捕获的所有显示器(SCDisplay)、应用程序(SCRunningApplication)和窗口(SCWindow)。通过设置onScreenWindowsOnly参数为true,可以只获取当前可见的窗口内容。
3.2 创建内容过滤器
获取可共享内容后,需要创建内容过滤器来决定具体捕获哪些内容。ScreenCaptureKit提供两种主要过滤方式:
- 显示器无关的窗口过滤器:捕获特定窗口,即使它跨显示器移动
- 显示器相关的过滤器:捕获特定显示器上的内容,可选择包含或排除某些窗口/应用
// 排除当前应用自身窗口,避免"镜厅效应" let excludedApps = content.applications.filter { app in Bundle.main.bundleIdentifier == app.bundleIdentifier } let filter = SCContentFilter(display: display, excludingApplications: excludedApps, exceptingWindows: [])这个例子创建了一个过滤器,捕获指定显示器上的所有内容,但排除了当前应用自身的窗口。这种设置常见于屏幕录制工具,避免录制到自身的界面。
4. 配置捕获流参数
4.1 视频捕获配置
SCStreamConfiguration类允许精细控制捕获流的各项参数。对于视频捕获,可以设置分辨率、帧率等关键参数:
let streamConfig = SCStreamConfiguration() // 设置输出分辨率为1080p streamConfig.width = 1920 streamConfig.height = 1080 // 设置捕获帧率为60fps streamConfig.minimumFrameInterval = CMTime(value: 1, timescale: 60) // 隐藏鼠标光标 streamConfig.showsCursor = false开发者应根据实际应用场景选择合适的参数。例如,对于静态内容展示可以降低帧率提高分辨率,而对于游戏直播则应优先保证高帧率。
4.2 音频捕获配置
ScreenCaptureKit同样支持音频捕获,可以配置采样率、通道数等参数:
// 启用音频捕获 streamConfig.capturesAudio = true // 设置音频采样率为48kHz,立体声 streamConfig.sampleRate = 48000 streamConfig.channelCount = 2音频捕获默认会捕获系统音频和麦克风输入(如果有权限)。开发者可以通过SCContentFilter进一步细化音频捕获范围,例如只捕获特定应用的音频。
5. 创建和管理捕获流
5.1 初始化捕获流
配置好过滤器和流参数后,就可以创建SCStream实例了:
stream = SCStream(filter: filter, configuration: streamConfig, delegate: self)这里的delegate参数需要实现SCStreamDelegate协议,用于处理流状态变化和错误通知。特别是应该实现stream(_:didStopWithError:)方法来处理捕获过程中可能出现的错误。
5.2 启动和停止捕获
启动捕获是一个异步操作,可以使用Swift的async/await语法:
try await stream?.startCapture()停止捕获同样简单:
stream?.stopCapture()在实际应用中,应该将这些操作放在适当的上下文中,并处理好可能的错误和异常情况。
6. 处理捕获的媒体样本
6.1 接收媒体样本
要实现SCStreamOutput协议来接收捕获到的媒体样本:
func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) { switch type { case .screen: handleVideoSample(sampleBuffer) case .audio: handleAudioSample(sampleBuffer) @unknown default: break } }视频样本以CMSampleBuffer形式传递,包含一个IOSurface引用实际的图像数据。音频样本同样使用CMSampleBuffer格式,可以直接使用Core Audio框架进行处理。
6.2 添加流输出
在启动捕获前,需要添加流输出:
// 创建专用队列处理样本 let videoQueue = DispatchQueue(label: "video.sample.queue") let audioQueue = DispatchQueue(label: "audio.sample.queue") try stream?.addStreamOutput(self, type: .screen, sampleHandlerQueue: videoQueue) try stream?.addStreamOutput(self, type: .audio, sampleHandlerQueue: audioQueue)为视频和音频使用不同的队列可以提高处理效率,避免音频处理阻塞视频处理或反之。
7. 性能优化与最佳实践
7.1 资源管理
屏幕捕获可能消耗大量系统资源,特别是当捕获高分辨率、高帧率内容时。开发者应该:
- 只在需要时保持捕获流活动
- 根据应用场景选择适当的参数
- 及时释放不再需要的资源
- 处理好应用进入后台等情况
7.2 实时参数调整
ScreenCaptureKit允许在流运行期间动态调整某些参数,这为实现自适应码率等高级功能提供了可能。例如,可以根据网络状况动态调整分辨率或帧率。
// 运行时调整分辨率 streamConfig.width = 1280 streamConfig.height = 720 // 应用配置变更 stream?.updateConfiguration(streamConfig)这种动态调整能力使得应用程序可以更好地适应不同的运行环境和用户需求。
8. 常见问题与解决方案
8.1 权限问题
用户可能拒绝授予屏幕录制权限,或者后来在系统设置中禁用了权限。应用程序应该:
- 检查当前权限状态
- 优雅地处理权限不足的情况
- 提供清晰的指导说明如何启用权限
可以使用以下代码检查权限状态:
let hasPermission = CGPreflightScreenCaptureAccess()8.2 性能问题
如果发现捕获过程中CPU使用率过高或帧率不稳定,可以尝试:
- 降低输出分辨率
- 减少帧率
- 检查是否有其他进程占用大量资源
- 优化样本处理代码
提示:使用Instruments工具的Time Profiler可以帮助定位性能瓶颈。
8.3 音频同步问题
当同时捕获视频和音频时,可能会遇到音视频不同步的问题。解决方案包括:
- 使用样本的时间戳进行同步
- 考虑系统处理延迟
- 实现适当的缓冲机制
ScreenCaptureKit提供的CMSampleBuffer包含精确的时间戳信息,可以用来保持音视频同步。
9. 高级应用场景
9.1 选择性窗口捕获
除了捕获整个屏幕,ScreenCaptureKit还支持选择特定窗口进行捕获。这在实现"窗口选择"功能时非常有用:
// 选择特定窗口进行捕获 let windowFilter = SCContentFilter(desktopIndependentWindow: window)这种模式下,即使用户移动窗口,捕获也会继续跟踪该特定窗口。
9.2 排除敏感内容
对于需要保护隐私的场景,可以排除包含敏感信息的窗口:
let sensitiveWindows = content.windows.filter { window in // 根据窗口标题或其他特征识别敏感窗口 return window.title.contains("密码") || window.title.contains("私密") } let filter = SCContentFilter(display: display, excludingApplications: [], exceptingWindows: sensitiveWindows)这种功能在开发企业级屏幕共享工具时特别有用。
9.3 与AVFoundation集成
捕获到的样本可以方便地与AVFoundation框架集成,用于录制、直播等场景。例如,可以使用AVAssetWriter将样本写入文件:
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings) let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings) // 处理样本时 if videoInput.isReadyForMoreMediaData { videoInput.append(sampleBuffer) }这种组合为开发功能丰富的屏幕录制应用提供了强大基础。
10. 实际开发中的经验分享
在实际项目中使用ScreenCaptureKit时,有几个关键点值得注意:
内存管理:IOSurface对象需要正确管理,避免内存泄漏。特别是在长时间运行的捕获过程中,要确保及时释放不再需要的资源。
线程安全:样本处理通常在后台队列进行,任何UI更新都必须切换到主线程。同时要注意多个流输出之间的线程同步。
错误恢复:网络波动、权限变化等情况可能导致流中断。实现健壮的错误恢复机制可以显著提升用户体验。
性能监控:建议在开发阶段实现性能监控,实时显示CPU使用率、内存占用等指标,帮助优化资源使用。
兼容性考虑:虽然ScreenCaptureKit是未来方向,但如果需要支持旧版macOS,可能需要保留CGDisplayStream的实现作为后备方案。
一个实用的技巧是使用SCStreamFrameInfo检查帧状态,可以避免处理未变化的帧内容,节省处理资源:
if let frameInfo = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: false)?[0] as? [SCStreamFrameInfo: Any], frameInfo[.status] as? SCStreamFrameInfo.Status == .idle { // 跳过未变化的帧 return }这些实践经验来自于实际项目中的教训,能够帮助开发者避免常见的陷阱和问题。