Highlight
macOS 12.3 用基于 SystemExtensions 的 Camera Extensions 取代 DAL plug-ins,让第三方软件摄像头、硬件摄像头和创意摄像头以沙盒 daemon 形式被 FaceTime 等所有相机 App 使用。
核心内容
在 macOS 上做一个第三方摄像头,过去常用 DAL plug-in。它从 macOS 10.7 开始存在,可以驱动硬件摄像头,也可以发布虚拟摄像头。
问题在安全边界。DAL plug-in 会把第三方代码直接加载到 App 进程里。插件有 bug,App 可能崩溃;插件有恶意行为,App 也会暴露在攻击面里。因此 FaceTime、QuickTime Player、Photo Booth 不支持它,许多第三方相机 App 也只有在关闭 library validation 或系统完整性保护后才会加载它(00:56)。
Camera Extensions 是 macOS 12.3 引入的新架构。扩展代码运行在独立的沙盒 daemon 进程中,buffer 在交给 App 前会被验证,框架负责扩展进程和 App 之间的 IPC,也负责把 buffer 分发给多个客户端(01:50)。
对用户来说,它仍然像普通摄像头。Camera Extension 会通过现有 AVFoundation capture API 暴露出来,在相机 App 里显示得像内建摄像头,也可以出现在 FaceTime 的相机选择器里(03:36)。
Session 把用法分成三类。
第一类是纯软件摄像头。它可以生成 color bars、测试图案、不同帧率和分辨率的程序化画面,或播放预渲染内容来测试音视频同步(04:02)。
第二类是硬件摄像头驱动。它可以配合 DriverKit Extension 访问硬件,也可以在确有需要时走 legacy IOVideoFamily kext。对于非标准 USB Video Class 摄像头,扩展还可以覆盖 Apple 的默认 UVC extension,声明特定 vendor ID 和 product ID(04:30)。
第三类是创意摄像头。扩展读取另一台物理摄像头的画面,加滤镜、合成多路画面,再把结果发布成一个新的摄像头 stream。演示里的 CIFilterCam 就是这种模式:配置 App 选择源摄像头和 Core Image 滤镜,FaceTime 和 Photo Booth 同时看到更新后的虚拟摄像头画面(06:05)。
详细内容
1. 安装模型:扩展跟随 App 分发
Camera Extension 建在 SystemExtensions 框架之上。扩展可执行文件放在 App bundle 里。App 调用 SystemExtensions 框架来安装、升级、降级扩展;删除 App 时,SystemExtensions 也会为所有用户卸载这个 Camera Extension(07:29)。
这个模型省掉了单独的安装器,也可以通过 App Store 分发。
概念流程:安装按钮触发 system extension 激活请求
1. 找到 App bundle 内嵌的 Camera Extension
2. 创建用于 activate extension 的 OSSystemExtensionRequest
3. 设置 OSSystemExtensionRequestDelegate 记录安装状态
4. 把请求交给 SystemExtensions 框架执行
关键点:
OSSystemExtensionRequest对应 Session 中安装和卸载按钮创建的激活/停用请求。- 请求目标是嵌入在 App bundle 里的 Camera Extension。
OSSystemExtensionRequestDelegate用来接收安装状态,Session 的示例在 delegate 方法里记录结果。- 这里写的是概念流程;具体 identifier 和错误处理要按你的 target 配置填写。
开发时有一个容易踩的坑:system extension 只能由位于 /Applications 的 App 安装。Session 演示中直接运行 App 后安装失败,把 App 移到 /Applications 后才成功(12:57)。
Info.plist 里也有关键配置。Camera Extension 的 Info.plist 通过 CMIOExtensionMachServiceName 标识自己;CoreMedia IO 的 registerassistant 不会启动缺少这个键的扩展。扩展的 entitlements 需要启用 App Sandbox,并且 App Group 前缀要匹配 Mach service name,才能通过校验(11:01)。
概念配置清单:
- Info.plist: CMIOExtensionMachServiceName
- Info.plist: 系统扩展用途描述
- Entitlements: App Sandbox = YES
- Entitlements: App Group 前缀匹配 MachServiceName
关键点:
CMIOExtensionMachServiceName是 registerassistant 查找并启动扩展的入口。- 用途描述会出现在系统扩展授权流程中。
- App Sandbox 是扩展运行的前提。
- App Group 命名不匹配会导致扩展验证失败。
2. Provider、Device、Stream:三个对象组成一台摄像头
CoreMedia IO Extension 的主要类是 Provider、Device 和 Stream。Provider 拥有 Device,Device 拥有 Stream,三者都可以有 properties(22:04)。
创建它们时,你分别提供 ProviderSource、DeviceSource 和 StreamSource。Source 对象负责协议实现,框架负责样板代码。
概念流程:Provider 是服务入口
1. 实现一个符合 CMIOExtensionProviderSource 的 source 对象
2. 用这个 source 创建 CMIOExtensionProvider
3. 调用 Provider 的 startService,并传入 provider instance
4. 在 provider 下按需发布 device 和 stream
关键点:
CMIOExtensionProviderSource是你实现扩展行为的 source 协议之一。CMIOExtensionProvider是扩展的底层对象,负责管理设备发布。startService是启动服务的入口,Session 明确指出要把 provider 传给它。- 真正项目中还要创建 device source、stream source,并把 stream 加到 device 上。
Provider 可以按需添加和移除设备,例如处理热插拔事件。它也会收到客户端进程连接通知,你可以借此决定是否向某些 App 发布设备(22:35)。
Device 负责管理 stream。它有本地化名称、UUID 形式的 device ID,以及可选的 legacy ID。localizedName 会一路传到 AVFoundation,成为 AVCaptureDevice.localizedName;device ID 会成为 AVCaptureDevice.uniqueIdentifier。如果你在迁移旧 DAL plug-in,并且要维持已经发布过的 identifier,可以提供 legacy device ID(23:40)。
概念配置:Device 层需要稳定的名称和标识
- localizedName: 用户在相机选择器中看到的名称
- deviceID: UUID 形式的设备标识
- legacyDeviceID: 迁移 DAL plug-in 时保留旧 uniqueIdentifier
关键点:
localizedName会影响用户在相机选择器中看到的名称。deviceID是新架构下的稳定设备标识。legacyDeviceID只在迁移旧 DAL plug-in 且需要保持兼容时使用。- AVFoundation 只处理第一个 input stream,多个输入 stream 不会都被它消费。
Stream 是真正发送 sample buffer 的对象。它发布 CMIOExtensionStreamFormat,定义有效帧率,配置 active format,使用标准 clock 或自定义 clock 来驱动 buffer timing,并把 sample buffer 送给客户端(25:44)。
概念配置:Stream 层描述格式、帧率和时间
- stream formats: 发布给客户端的可用视频格式
- active format index: 当前使用的视频格式
- frame duration: 最大帧率
- max frame duration: 最小帧率
- clock: 驱动每个 sample buffer 的时间来源
关键点:
CMIOExtensionStreamFormat会映射成AVCaptureDeviceFormat。- 客户端可以读写 active format index 来切换当前格式。
- frame duration 对应最大帧率,max frame duration 对应最小帧率。
- sample buffer 的时间由标准 clock 或自定义 clock 驱动。
3. 创意摄像头:配置 App 通过 property 控制扩展
CIFilterCam 演示展示了创意摄像头的交互方式。配置 App 里有源摄像头选择、滤镜选择、bypass 按钮和实时预览。预览视图由 AVCaptureVideoPreviewLayer 支撑(15:02)。
真正有用的部分在扩展。FaceTime 和 Photo Booth 都选择 CIFilterCam 后,配置 App 改变滤镜,两个 App 同时看到新效果;配置 App 改变源摄像头,所有使用 CIFilterCam 的 App 都跟着变化(16:29)。
概念流程:配置 App 控制创意摄像头
1. 用户在配置 App 中选择源摄像头
2. 用户选择 Core Image 滤镜
3. 配置 App 通过 CoreMedia IO C property interface 写入扩展属性
4. 扩展用新属性处理后续 buffer
5. 所有正在使用 CIFilterCam 的 App 同步看到变化
关键点:
- 源摄像头选择对应演示里“use this camera”的配置变化。
- 滤镜选择对应演示里“use this other filter”的配置变化。
- 这是概念流程;Session 没有提供可直接复制的 property 写入代码。
- Session 明确说明 AVFoundation 不支持自定义 properties,配置 App 要走 CoreMedia IO C API。
CMIOExtension 没有提供 DAL controls 的替代物。自动曝光、亮度、锐化、平移缩放等能力都应建模成 property。自定义 property 可以桥接到旧的 CoreMedia IO C property interface:名称以 4cc_ 开头,后面把 selector、scope、element 编码进去,中间用下划线分隔(26:46)。
概念示例:自定义 property 命名
4cc_cust_glob_0000
关键点:
4cc_表示这是桥接到旧 C property address 的自定义名称。cust是四字符 selector,Session 用它举例表示 custom。glob表示 global scope;scope 也可以是 input 或 output。0000表示 element;main element 通常是 0。
如果创意摄像头要在扩展里访问另一台硬件摄像头,需要 macOS Ventura。扩展 entitlements 里要加入 com.apple.security.device.camera,Info.plist 里要提供 NSCameraUsageDescription,用户也会收到授权提示(17:31)。
4. 安全边界:daemon、registerassistantservice 和 TCC
Camera Extension 的扩展代码不会进入 App 进程。Apple 的 registerassistantservice 会通过 CMIOExtensionMachServiceName 识别扩展,并以 _cmiodalassistants 这个 role user 启动它。sandboxd 会套用为摄像头场景定制的 sandbox profile(19:25)。
这个 sandbox profile 允许常见硬件通信:USB、Bluetooth、Wi-Fi 客户端模式、FireWire,也允许读写扩展自己的 container 和 tmp。它比普通 App 更严格:不能 fork、exec、posix spawn 子进程,不能访问 window server,不能连接前台用户账号,也不能在全局命名空间注册自己的 Mach services(19:59)。
Camera Extension 运行边界:
App process
-> CoreMedia IO / AVFoundation compatibility layers
-> registerassistantservice
-> sandboxed camera extension daemon
关键点:
- App 仍通过 AVFoundation 或 CoreMedia IO 看见摄像头。
registerassistantservice是 App 和扩展之间的代理服务。- 扩展 daemon 被沙盒隔离,不能把第三方代码加载进 App。
- buffer 在交给 App 前会经过验证和权限控制。
registerassistantservice 还负责透明度、同意和控制策略。某个 App 第一次使用摄像头时,系统会询问用户;这个 consent 对内建摄像头和第三方摄像头都适用。用户拒绝授权时,代理服务会阻止 buffer 传给该 App。它还负责 attribution,让系统知道某个 App 正在使用某个摄像头,从而把 daemon 消耗的电量归因到使用它的 App(21:04)。
5. Output device:CoreMedia IO 也能消费视频
DAL plug-in 还有一个较少被提到的能力:发布 output device。它和摄像头相反,不向 App 提供画面,而是实时消费 App 送来的画面。典型场景是 print-to-tape,或者把实时预览送到带 SDI 输入的专业 deck(28:44)。
Output device 没有 AVFoundation 等价 API。要向 output device 送帧,客户端必须直接使用 CoreMedia IO C API(29:25)。
概念流程:sink stream 消费视频
1. stream direction 设置为 sink
2. 客户端把 sample buffer 放入简单队列
3. 扩展通过 consumeSampleBuffer 消费 buffer
4. 消费完成后调用 notifyScheduledOutputChanged 通知客户端
关键点:
sourcestream 像摄像头一样产生数据。sinkstream 像输出设备一样消费数据。consumeSampleBuffer是 Session 提到的扩展侧消费调用。notifyScheduledOutputChanged用来通知客户端队列状态变化。
Session 最后给出迁移信号:macOS 12.3 已经把 DAL plug-ins 标记为 deprecated,构建时会出现 warning。Apple 计划在 macOS Ventura 之后的下一个 major release 中完全禁用 DAL plug-ins。仍在维护 DAL plug-in 的团队,需要开始迁移到 Camera Extension(30:28)。
核心启发
-
做什么:做一个会议专用虚拟摄像头。 为什么值得做:Camera Extension 可以出现在 FaceTime 等所有相机 App 中,配置 App 改一次滤镜,所有客户端都看到同一结果。 怎么开始:用软件摄像头模板建立 Provider、Device、Stream,再用 Core Image 处理源摄像头 buffer;配置 App 通过 CoreMedia IO 自定义 property 写入滤镜名称。
-
做什么:为非标准 USB 摄像头做 macOS 驱动。 为什么值得做:Apple 的 UVC extension 适合标准 UVC 设备;非标准协议或 UVC 之外的额外功能可以用 Camera Extension 覆盖默认 UVC extension。 怎么开始:阅读 “Overriding the default USB video class extension”,在最小 DEXT bundle 的 Info.plist 里声明对应 vendor ID 和 product ID。
-
做什么:做一台自动播放测试图案的软件摄像头。 为什么值得做:纯软件摄像头可以生成固定图案、不同帧率和分辨率,用于验证视频会议、录制或音视频同步流程。 怎么开始:从 Xcode 的 Camera Extension target 模板开始,保留软件 camera stream,替换 sample buffer 生成逻辑。
-
做什么:把旧 DAL plug-in 迁移成 Camera Extension。 为什么值得做:DAL plug-ins 已被 deprecated,并计划在 Ventura 之后的 major release 中禁用;Camera Extension 还能被 Apple App 使用。 怎么开始:复用旧设备的 unique identifier 作为 legacy device ID,逐步把 DAL controls 映射成 provider、device 或 stream properties。
关联 Session
- Capture machine-readable codes and text with VisionKit — 同样从摄像头输入出发,但把捕获和识别封装成 VisionKit 的 Data Scanner。
- Add Live Text interaction to your app — 讲解静态图片和暂停视频帧里的文字交互,适合与相机捕获流程组合。
- What’s new in the Photos picker — 展示系统照片入口如何在跨平台 UI 中保护用户隐私。
- Create parametric 3D room scans with RoomPlan — 展示另一种系统封装的相机体验,把房间扫描变成可集成的 App 功能。
评论
GitHub Issues · utterances