Skip to content

系列一 | iOS 实时屏幕采集与 VideoToolbox 硬件编码深度解析

在构建高吞吐、超低延迟的跨端远程协助系统时,控制端与受控端之间的音视频流传输是决定整体体验的核心命题。要实现低于 150 毫秒的端到端画面延迟,必须保证数据源头 —— 屏幕采集与视频编码 阶段的绝对高效。

iOS 系统由于其严苛的安全沙箱机制和内存限制,对开发者提出了极高挑战。例如,用于系统级全局截屏的 Broadcast Upload Extension(广播上传扩展进程)被系统限制了仅仅 50MB 的最大内存使用量,超出该限制进程会被系统直接强杀。

本文将为您深度解构易连系统(Easy Connect Suite)在 iOS 平台上,如何借助原生 ReplayKitVideoToolbox 架构构建零拷贝、低功耗的高性能屏幕采集与硬编管线。


1. iOS 屏幕采集双轨制:应用内 capture 与系统级广播

在 iOS 平台,根据受控角色的业务场景,采集方案分为两种:

                ┌────────────────────────────────────────┐
                │             iOS Screen Capture         │
                └───────────────────┬────────────────────┘

                  ┌─────────────────┴─────────────────┐
                  ▼                                   ▼
        【 应用内采集 (In-App) 】               【 系统级广播 (Global) 】
         使用 RPScreenRecorder                 使用 Broadcast Extension
         直接截取宿主 Window 画面                RPBroadcastSampleHandler 跨进程采集
         无内存强杀隐忧                         最大内存限制 50MB (高挑战)

应用内采集(In-App Capture)

适用于仅协助操作主 App 界面的轻量协助场景。我们直接通过 ReplayKit 框架下的 RPScreenRecorder 单例开启录制。它在主进程中直接运行,无需跨进程通信,也没有严苛的 50MB 内存阈值,稳定性高。

系统级全局采集(Broadcast Extension)

当用户需要将整个 iOS 设备(包括桌面和其他第三方 App)共享给协助者时,必须利用 iOS 的 Broadcast Upload Extension(广播上传扩展进程)。

这是一种典型的跨进程架构:

  1. 启动广播:用户在系统控制中心长按录屏按钮,选择启动我们注册的广播扩展。
  2. 帧推送:系统底层采集屏幕像素后,以 CMSampleBuffer 包的形式,跨进程推送给扩展进程中的入口类 RPBroadcastSampleHandler 的回调方法。
  3. 极速转发:由于扩展进程随时面临 50MB 的“内存生存线”,我们不能在此进程中进行任何复杂的像素缓冲拷贝或软件编码。必须直接把获取到的原始 CVPixelBuffer 瞬间送入硬件压缩通道,或通过共享内存/Unix Domain Socket 将数据极速路由。

2. 零拷贝硬件压缩管线:VideoToolbox 深度融合

当 ReplayKit 回调触发并抛出包含原始帧的 CMSampleBuffer 时,我们必须以最快的速度将其转换为 H.264 视频流。这里我们调用了苹果原生的硬件加速编解码框架 —— VideoToolbox,并建立了一条零拷贝(Zero-Copy)数据通道

VTCompressionSession 的初始化与配置

我们利用 VTCompressionSessionCreate 创建硬件压缩会话,并进行如下核心配置:

  • 编码格式:指定为 kCMVideoCodecType_H264
  • 编码 Profile:通过设置 kVTCompressionPropertyKey_ProfileLevelkVTProfileLevel_H264_Main_AutoLevelkVTProfileLevel_H264_Baseline_AutoLevel。Baseline 模式不包含 B 帧,能够将编码时延降到绝对最低;而在网络条件较好时,切换到 Main 或 High profile 可以获得更高的图像保真度。
  • 实时编码属性:开启 kVTCompressionPropertyKey_RealTime 属性,强制编码器放弃为了追求压缩率而进行的缓存队列,实现输入一帧、产出一帧的极速处理。

零拷贝的精髓:像素缓冲区直通

普通的软件编码方案需要将采集到的画面数据从 GPU 显存拷贝到 CPU 内存,转换成 YUV 格式,然后再送去编码。而 VideoToolbox 支持直接读取 GPU 物理贴图:

[ ReplayKit 采集 ]

       ▼ (CMSampleBuffer 内部包装)
[ CoreVideo CVPixelBuffer (GPU 纹理内存) ]

       ▼ (无任何内存拷贝或 CPU 格式转换)
[ VTCompressionSessionEncodeFrame ]

       ▼ (GPU 硬件电路极速压缩)
[ H.264 Annex-B 裸流 ]

在 ReplayKit 的回调中,我们直接调用 CMSampleBufferGetImageBuffer(sampleBuffer) 提取出底层的 CVPixelBufferRef(苹果平台代表 GPU 显存贴图的结构体),并将其直接传参给 VTCompressionSessionEncodeFrame。整个流程中,像素数据自始至终停留在 GPU 物理显存中,CPU 仅负责指令调度,使手机的整体功耗和发热量降到了最低,完美规避了内存强杀。


3. 规避“死亡粉绿”:色彩空间校正(BT.601 limited-range)

在多端互连的远程协助系统中,iOS 采集的流通常要发送给 Android、Windows 或 Web 端的网页播放器解码渲染。许多开发者在此阶段会遇到一个诡异的 Bug:iOS 端投射出的画面,在 Android 或 Chrome 浏览器中显示时,颜色发生严重失真,偏向绿色或粉色。

这其实是由于色彩矩阵系数(Color Matrix Coefficients)配置不一致导致的 YUV 转换偏差。

默认情况下,当 iOS 设备屏幕分辨率大于或等于 720p 时,VideoToolbox 在编码时会自动将视频可用信息(VUI)的色彩空间标记为 Rec. 709(高清晰度视频标准)。但许多接收端(尤其是 Android MediaCodec 解码器和 WebCanvas 转换上下文)在进行 YUV 到 RGB 的转换时,会默认假定接收到的视频帧采用的是 BT.601(标准清晰度视频标准) 系数。

为了彻底抹平这一底层色彩偏差,易连助手在初始化编码会话时进行了显式的色彩参数纠偏

swift
// 强制 VideoToolbox 编码器在生成 H.264 VUI 头时使用 BT.601 限制范围色彩属性
let specDict: [CFString: Any] = [
    kCVImageBufferColorPrimariesKey: kCVImageBufferColorPrimaries_SMPTE_C,
    kCVImageBufferYCbCrMatrixKey: kCVImageBufferYCbCrMatrix_ITU_R_601_4,
    kCVImageBufferTransferFunctionKey: kCVImageBufferTransferFunction_ITU_R_709_2
]
// 将参数应用到 VTCompressionSession
VTSessionSetProperties(compressionSession, propertyDictionary: specDict as CFDictionary)

强制对编码器注入 BT.601 的限定配置后,VideoToolbox 产生的 H.264 SPS/PPS 描述符中会包含标准的色域标记,接收端解码器在还原 RGB 色彩时便能精准还原,告别粉绿失真。


4. 旋转与缩放:VTPixelTransferSession 硬件变换

当用户在使用 iOS 远程协助时,经常会将手机从竖屏转为横屏。由于屏幕分辨率宽高比发生反转,直接编码会导致画面拉伸变形或接收端黑屏。

为了解决这个问题,我们引入了苹果专为视频帧像素格式转换设计的硬件加速引擎 —— VTPixelTransferSession

在将 CVPixelBuffer 送入编码器之前,我们首先检测当前设备的物理朝向:

  1. 会话复用:创建一个 VTPixelTransferSessionRef 传输会话。
  2. 设置硬件转角:当检测到方向发生逆时针或顺时针旋转时,设置传输会话的旋转属性:
    swift
    // 设置旋转 90 / 180 / 270 度
    VTSessionSetProperty(transferSession, 
                         key: kVTPixelTransferPropertyKey_RotationAngle, 
                         value: rotationAngleInDegrees as CFTypeRef)
  3. 输出缓冲分配:利用 CVPixelBufferPool 极速分配一个转换后分辨率的空目标缓冲区。
  4. 硬件转码:执行 VTPixelTransferSessionTransferImage,该操作会直接调用 GPU 的专用缩放与旋转电路(VDA),在 1-2 毫秒内完成旋转和宽高缩放,然后将干净的目标缓冲区直接递交给 VTCompressionSession 进行编码。

通过上述一系列的原生零拷贝硬件优化,易连助手在 iOS 端实现了极低的屏幕采集能耗与极佳的画面保真度,为后续的多路复用网络传输奠定了坚实的基础。在下一篇中,我们将深入 Android 端,剖析 MediaProjection 以及 Surface 模式编码的底层实践。

Released under the MIT License. Terms | Privacy