大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Core Media IO 创建相机扩展
了解如何利用 Core Media IO 轻松为软件相机、硬件相机和创意相机构建 macOS 系统扩展。我们将向您介绍用于替代旧式 DAL 插件的现代化扩展。这些扩展既安全又快速,而且完全兼容使用相机输入的任何 App。我们将带您了解 Core Media IO API,并且分享它们将如何利用特效功能和新颖的 App 创意等,为相机制造商和视频会议 App 提供支持。
资源
- Capture setup
- Core Media I/O
- Creating a camera extension with Core Media I/O
- Overriding the default USB video class extension
- System Extensions and DriverKit
相关视频
WWDC19
-
下载
♪ ♪
Brad Ford: 欢迎大家 我是来自 Camera Software Engineering 团队的 Brad Ford 在这一讲座中 我将向您介绍 CoreMedia IO 的相机扩展 这是一种用于 macOS 的 现代相机驱动程序架构 是 DAL plug-ins 的替代品
DAL plug-ins 这一技术 允许你为插入 Mac 的硬件 创建相机驱动程序 或者虚拟相机 从 MacOS 10.7 开始问世 已有很长一段时间了
DAL plug-ins 提供了将 macOS 扩展为富媒体平台的能力 为专业人士和消费者 提供第三方相机产品的支持
这也是 Mac 出类拔萃的 原因之一
但 DAL plug-ins 还存在一些问题 它们会将不受信任的代码 直接加载到 App 的进程中 导致 App 会容易因插件漏洞而崩溃 或者受到恶意软件的攻击 因此 它们不能与 FaceTime 通话 QuickTime 播放器和 PhotoBoth 等 Apple App 一起使用 也不能与许多第三方 相机 App 一起使用 除非这些 App 有意 禁用资料库验证 或者用户关闭系统完整性保护 而这两种做法都不推荐 因为会降低系统的安全性和稳定性 同时这种插件也很难开发 它们带一个 大约 2011 年的 C API 和一套繁多的 C++ 辅助类 SDK 需要您学习 最重要的是 它们的文档很少
是升级换代的时候了 macOS 12.3 推出了完全现代化 的 DAL plug-ins 替代品 称为 Camera Extensions (相机扩展)
一个将用户安全性放在首位的架构 我们来了解一下它是如何工作的 首先 我将提供一段技术概述 接下来 我将向您展示 如何从头开始构建相机扩展 然后 介绍 API 的 主要类型和函数 解释如何将 CoreMedia IO 扩展 当作输出设备使用 最后 介绍我们的 DAL plug-in 弃用计划 我们开始吧 相机扩展 也称为 CoreMedia IO 扩展 是一种将相机驱动程序打包 并交付给 Mac app 的新方法
它们很安全 您的扩展代码 被隔离到自己的守护进程中 该进程被沙盒化 并以用户角色运行 您的扩展提供的所有缓冲区 在交付给 App 之前都会经过验证 它们速度快 该框架处理扩展进程 和 App 之间的 IPC 层 以性能为重点 该框架还负责将缓冲区 同时传递给多个客户端 它们很现代 该扩展可以用 Swift 或 Objective-C 书写
它们很简单 只要学习几个类别 实现几个协议就可以启动和运行 该框架会处理样板代码
它们很容易部署 您可以将它们作为 App 在 App Store 商店中发布
相机扩展 100% 向后兼容 现有的 AVFoundation 拍摄 API
相机扩展如内置相机一样 支持所有相机 App 包括 Apple 的 App 下面是如何在 FaceTime 通话的 摄像头选择器中 显示相机扩展的示例 相机扩展可以提供怎样的体验 我们先来研究三种常见的用法 最简单的用途是纯软件相机 例如显示色彩条纹的相机 显示特定的测试图案的相机 以不同的帧率或分辨率 程序化生成图像的相机 或用于流式传输 预渲染内容的相机 如电影中的帧 以测试声画同步
第二个用例是为实际插入 Mac 或以无线方式发现的 相机提供驱动 相机扩展完全支持热插拔 处理硬件有几种选择 首选方法是使用完全 在用户空间运行的 DriverKit 扩展或 DEXT 如硬件必须在内核级进行寻址 则可以使用传统的 IOVideoFamily kext 方式 不鼓励再开发新的 kext 代码 因为 kext 本质上不太安全 且会导致系统不稳定
Apple 为 USB 视频类 或 UVC 摄像头 提供了一个类兼容的扩展 它非常适合符合 UVC 规范的相机
然而 如果您需要支持 使用非标准协议的 USB 摄像头 具有 UVC 规范之外的 附加功能 可以创建一个覆盖 Apple UVC 扩展的相机扩展 允许您声明特定的产品 和供应商 ID 如果您有兴趣了解更多 这方面的信息 请参考 developer.apple.com 上 标题为 “Overriding the default USB video class extension” 的文章 这篇文章解释了 如何创建最小的 DEXT bundle 以及需要在 Info.plist 中 覆盖哪些 IOKitPersonalities 键
第三种常见用途是创意相机 一种软件和硬件的混合体
您的扩展从连接在 Mac 上的 另一个物理相机 访问视频流 对这些缓冲区添加效果 并将它们作为一个新的相机流 发送给客户端
或者一个创意相机 从几个相机获取视频流 合成这些视频流 并发送到 App
像这样的创意相机 可能会使用配置 App 来控制合成或参数化滤镜 创意相机的可能性可谓无穷无尽
现在我们已经了解了 主要使用案例 我们来分析一下 CoreMedia IO 扩展 首先是 CoreMedia IO 部分
CoreMedia IO 是用于 发布或发现摄像头驱动程序的 底层框架 您已经知道 它包含了旧版 DAL API 和替代它的新相机扩展 API 但也包含了一组 强大的底层 C API 供 App 开发人员查找和检查 系统上的摄像头
那关于扩展部分是怎样的呢
CoreMedia IO 扩展建立在 macOS Catalina 首次引入的 SystemExtensions 框架之上 避免了一次性安装程序的需要 相反 您可以在 App 中 发布您的扩展 扩展可执行文件 位于 App bundle 中 通过调用 SystemExtensions 框架 您的 App 可以为 系统上的所有用户 安装 升级或降级您的扩展 卸载非常简单 删除该 App SystemExtensions 框架 就会对所有用户卸载您的相机扩展 这种分发机制允许 在 App Store 商店中使用 让您的相机扩展可轻松部署到 更广泛的受众中
要了解更多关于系统扩展框架的信息 请参阅 developer.apple.com/ documentation/systemextensions 网站
请务必查看 WWDC 2019 中标题为 “System Extensions and DriverKit” 的视频
这是我们对相机扩展的技术概述 现在 我们来实际构建一个扩展 下面是如何在几分钟内启动 并运行相机扩展的快速演示
我已经用 Xcode 创建了一个名为 ExampleCam 的单窗口 MacOS app 至此 我只添加了几行代码
App Delegate 保持不变 在主 storyboard 中 我添加了两个按钮 一个用于安装 一个用于卸载扩展 还有一个用于显示状态的文本字段
在 ViewController 类中 我添加了 IBActions 来连接安装和卸载按钮
这些函数 创建 OSSystemExtensionRequest 来激活 或停用 App bundle 中的扩展 在底部 我添加了记录状态的 OSSystemExtensionRequestDelegate 函数的基础实现
App 的 entitlement 文件包括 App Sandbox = YES 定义了一个 AppGroup
我在这里只添加了一个新的键 即 “System Extension” 只要您的 App 要安装系统扩展 就需要该键值 此时 如果我运行此 App 并点击安装扩展按钮 会出现一个致命错误 因为 App 正在查找 bundle 包中 尚不存在的扩展
为了创建和嵌入系统扩展 我点击 File (文件) New (新建) Target (目标) 然后在 macOS 下 一直滚动到底部 System Extension (系统扩展) 处 然后选择 “Camera Extension (相机扩展)” 点击下一步 命名 我选择 “Extension” 确保 “Embedded in Application” 已设置 然后点击 “Finish” 在新的扩展文件夹中 我得到了四个新文件 Info.plist 通过定义 它的 MachServiceName 将其标识为 CMIOExtension
这条信息很关键 只有它存在 CoreMedia IO 注册助手才会启动您的扩展
到了这一步 我们给系统扩展提供一个 使用说明 entitlement 文件显示 它是沙盒化 App 我需要确保扩展的 App group 以 MachServiceName 为前缀 以便通过验证
因此 我将其从 App 扩展 复制并粘贴到扩展 entitlement 文件 就是这样
Main.swift 文件作为扩展的入口点 并启动服务 ExtensionProvider.swift 文件 为我们提供了一个功能齐全的相机 它包含一个 DeviceSource 一个 StreamSource 和一个 ProviderSource 所有这些都是 创建纯软件相机所需的 不错的小模板
在这个文件中 我搜索 “SampleCapture” 并将其替换为 “ExampleCam” 这样我的相机名称 型号 和制造商都有了合适的名称
好了 我们可以编译并运行了
当我按下 Install (安装) 按钮时 啊哦 失败了 这是因为系统扩展只能由 驻留在 /Applications 中的 App 安装 我们移动后再试一次
这次成功了 系统提示我通过在 “System Settings (系统设置)” 中 进行身份验证 来安装被阻止的扩展 在 “System Settings (系统设置)” 中 我找到了 “Privacy & Security (隐私与安全)” 然后单击 “Allow (允许)” 按钮
使用密码进行身份验证 然后看到我的结果已更改为0 表示“no error (无错误)” 如果我使用 systemextensionsctl list 工具 就确认成功了 现在我的系统上 就激活了一个的扩展 现在我可以打开任何相机 App 找到并欣赏我的作品
我们来启动 FaceTime 通话 ExampleCam 出现在 相机选择器中 这有点像 70 年代的 老式乒乓游戏 画一条水平白线 以每秒 60 帧的速度 上下移动
要移除相机 我只需要删除 App
系统会提示我确认 是否连同 App 一起卸载该扩展程序
ExampleCam 演示展示了从头开始 制作软件相机是多么容易 现在我们提升一个档次 把软件相机 变成一个创意相机
我把第二个例子 称为 CIFilterCam CI 代表 CoreImage 这是一个带有各种滤镜效果的框架 您可以将其应用于静态图片或视频
为了创建 CIFilterCam 我从 ExampleCam 壳程序开始 但决定将该 App 作为配置 App 和安装程序 我添加了一个相机选择器按钮 一个滤镜选择器按钮 和一个忽略效果的按钮 我还添加了一个实时视频预览视图 这是一个 由 AVCaptureVideoPreviewLayer 支持的标准视图 可以显示滤镜相机当前工作 通过取消选中 bypass 按钮 可以看到应用在视频上的各种滤镜 从色彩效果 到变形滤镜
我比较喜欢凹凸变形的效果
我可以将这些应用于内置 FaceTime 通话相机 或连接到 Mac 的 任何物理相机
我把附近的 iPhone 设置为连续互通相机
我们用用看
CIFilterCam App 本身并 没有什么特别之处 只是一个特效相机 App 真正有趣的地方是 这款 App 是一个 所有 App 都可以使用的 虚拟滤镜相机的前端 我启动 FaceTime 通话 和 PhotoBooth 确保它们都对应 CIFilterCam 现在 当在我的配置 App 中 更改滤镜时 每个使用 CIFilterCam 的 App 都会随之更改
如果我选择了不同来源的相机 每个相机 App 都会接受更改
App 中的每一个按钮 都会转化为一个简单的属性调用 通知滤镜相机扩展 告诉它 “嗨 扩展 用这个相机” 或者 “嗨 扩展 使用其它的滤镜”
或者这个滤镜
或者这个滤镜
支持在扩展内运行硬件相机 需要 MacOS Ventura 您还需要将 com.apple.security.device.camera 键值 添加到扩展的 entitlement 文件中 以表明您将使用另一台相机 由于您要使用相机 系统会提示用户授予扩展的权限 因此您必须在 Info.plist 中 提供 NSCameraUsageDescription
这就完成了构建相机扩展的基础 现在我们继续讨论 API
技术栈的底部是守护程序进程 每个守护程序进程对应 一个第一方或第三方相机扩展
在相机 App 进程中 有几个层在起作用 首先是私有框架代码 通过 IPC 与您的相机扩展对话 再上一层是另一个私有层 它将 CoreMedia IO 扩展 调用转换为 传统 DAL plug-in 调用
再往上 我们可以找到发布 DAL plug-ins 的 公共 CoreMedia IO API 对于此接口的客户端来说 CoreMedia IO 扩展 和 DAL plug-ins 之间没有区别 所有东西看起来 都像是 DAL plug-in 最后 位于顶部的 是 AVFoundation 它是 CoreMedia IO 的客户端 它将 DAL plug-ins 重新发布为 AVCaptureDevices
与传统的 DAL plug-in 架构 相比 DAL plug-ins 可能包括 也可能不包括守护进程 但它们都直接在 App 进程中运行 由 CoreMedia IO 框架加载的代码 这使得该 App 易受到 恶意软件的攻击 相机扩展完全消除了这种攻击媒介 您的扩展必须经过沙盒 App 保护 否则不允许运行
Apple 的 RegisterAssistantService 通过它的 CMIOExtensionMachServiceName 来识别 并将其作为一个名为 _cmiodalassistors 的 用户角色帐户启动 Sandboxd 将自定义沙盒配置文件 应用于您的进程中 它是为相机使用量身定制的
自定义沙盒配置文件允许您通过 预期的通用硬件界面进行通信 USB 蓝牙 WiFi 作为客户端而不是开放端口的服务器 甚至 Firewire 它还允许您的扩展从自己的 容器和 tmp 中读取和写入
相机扩展沙盒配置文件 比一般的 App 更加封闭 例如 派生 执行或 POSIX 派生子进程 访问 Windows 服务器 连接到前台用户帐户 或在全局命名空间中 注册您自己的 MACH 服务 这些事情都不被允许
如果在开发扩展的过程中 发现沙盒 对于合法的拍摄案例来说限制太多 请通过反馈助理向我们提供反馈 我们会认真考虑放宽限制 早期的架构图 显示了相机扩展的守护进程 它将缓冲区直接传递到应用层 实际上还有一层安全措施
在守护程序和 App 之间有一个 名为 RegisterAsistantService 的 代理服务 其将强制执行透明 同意和控制策略 当一款 App 第一次尝试使用相机时 系统会询问用户是否可以使用 所有的相机都要获得这种许可 而不仅仅是内置相机 代理服务代表您处理同意操作 如果用户拒绝相机访问 代理将停止将缓冲区传入该 App 它还将处理归因 让系统知道 特定的 App 正在使用特定的相机 因此守护进程消耗的电量 可以归因于使用相机的 App
CoreMedia IO 扩展 有四个主要类别 提供商 设备和流
提供商有设备 设备有流 这三个设备都可以有属性
您可以通过提供源来创建这三个主类 它们分别是 ProviderSource DeviceSource 和 StreamSource
ExtensionProvider 是 最低级别的对象 允许您根据需要添加和删除设备 例如热插拔事件
当客户端进程尝试连接时 它会得到通知 从而使您有机会将设备发布限制到 某些 App 上 它还会向您的提供程序源对象 查询属性实现
下面是扩展的主要入口点 您可以创建 自己的 ExtensionProviderSource 符合 CMIOExtensionProviderSource 协议 并创建一个 ExtensionProvider 您需要调用提供者类方法以启动服务 startService 并传递您的提供者实例
ExtensionProvider 实现了 两个只读属性 这两个属性在扩展的生命周期内 不会更改 制造商和供应商名称 这两个都是字符串
接下来 是 CMIOExtensionDevice 它可以管理数据流 根据需要添加或删除它们 您的设备可以呈现多个流 但注意 AVFoundation 会忽略 除第一个输入流之外的所有流
创建设备时 您需要提供设备源 本地化名称 UUID 形式的设备 ID 以及可选的 legacyID 字符串 这些属性一直 渗透到 AVFoundation
您的设备的 LocalizedName 将成为 AVCaptureDevice 的 localizedName 除非您还 提供了 legacyDeviceID 否则您指定的 deviceID 将成为 AVCaptureDevice 的唯一标识符 如果您正在更新 DAL plug-in 并且需要保持与以前发布的 uniqueIdentifier 向后兼容性时 才需要提供此功能
如果您提供了 LegacyDeviceID 则 AVCaptureDevice 会将其用作唯一标识
您可用 CMIOExtensionDeviceSource 创建 CMIOExtensionDevice 该 CMIOExtensionDeviceSource 可以选择性地实现其它属性 如 deviceModel 这些属性对于同一型号的所有相机 应该是相同的 如果您的设备可以进入挂起状态 例如如果有隐私虹膜 则应实现 isSuspended 当翻盖合上时 Apple 笔记本电脑上的内置相机 进入暂停状态 设备的传输类型显示其连接方式 例如通过 USB 蓝牙或 Firewire
最后 如果您的相机 与麦克风物理配对 则可以将其作为链接设备公开 所有这些属性都是只读的 接下来是非常 重要的 MIOExtensionStream 它在 CMIOExtension 中 执行繁重的工作 可以发布视频格式 定义有效的帧率 并配置激活的格式 使用标准时钟 如主机时钟 或提供自己的定制时钟 来驱动其产生的每个缓冲区的计时 最重要的是 它可以向客户端发送缓冲区采样
扩展流源 发布 CMIOExtensionStreamFormats 这些成为 AVCaptureDeviceFormats 客户端可以读写活动格式索引 以更改活动格式
帧持续时间 相当于最大帧率 最大帧持续时间 与最小帧率相同
DAL plug-in 方式提供了 名为 DAL 控制的第四个接口 Plug-in 开发人员使用这些 来展示诸如自动曝光 亮度 清晰度 平移和缩放等功能 虽然功能强大 但它们的实现并不一致 因此 App 开发人员很难使用它们 在 CMIOExtension 架构中 我们不提供 DAL 控制替代品 相反 所有东西都是属性
您已经了解了提供程序 设备和流级别的许多标准属性 您还可以创建自定义属性 并将其传播到 App 层 就像我在 CIFilterCam 演示中所做的那样
CoreMedia IO 的 C 属性接口 使用 C 结构 来标识属性的选择符 作用域和元素 这些都被视为其地址
选择符是由四个字符组成的 代码形式的属性名称 例如 CUST 表示自定义 作用域可以是全局的 输入的或输出的 元素可以是您想要的任何数字 主元素始终为零 CMIOExtensions 通过将属性地址元素 编码到一个自定义属性名中 将属性连接到旧世界 首先 字符 4cc_ 然后是选择符 作用域和元素 由下划线分隔的四字符代码 使用此方法 您可以将任何字符串 或数据值传递给 App 层
AVFoundation 不能使用 自定义属性 因此如果您的配置 App 需要 使用自定义属性 则必须坚持 使用 CoreMedia IO C API 这是我们对 API 的高级描述 现在我们来谈谈输出设备
DAL plug-ins 一个鲜为人知的功能 是它们能够呈现相机的 对立面 一种输出设备 消费 App 实时产生的视频 而不是提供视频 这是 CoreMedia IO 中 “O” 的部分 输入和输出 输出设备在专业视频领域很常见 一些常见的用途是输出录影带 将视频信号发送到 外部录像机 或实时预览监控 例如在带有 SDI 输入的 专业面板上
需要注意的一点是 输出设备没有对应的 AVFoundation API 要将帧发送到输出设备 必须直接 使用 CoreMedia IO C API
CMIOExtension 流 是以源 或接收器方向创建的 Sink 流使用 来自 App 的数据 客户端通过将样本缓冲区 插入到一个简单的队列产生 sink 流 这将在您的扩展中转换成 consumeSampleBuffer 调用 一旦您使用了该缓冲区 就可 通过 notifyScheduledOutputChanged 通知它们
有一些专门针对输出设备的流属性 它们主要处理队列大小 启动前缓冲多少帧 以及当所有数据都被消耗时 发出信号
现在进入我们今天的第五个 也是最后一个话题
在前面的演示中 我展示了 DAL plug-in 架构的图表 并强调了它的许多安全问题 我们已经用相机扩展功能 解决了这些缺点 并完全致力于其持续发展 它们是今后的路径 那么这对 DAL plug-ins 意味着什么呢 这意味着即将结束
从 macOS 12.3 开始 DAL 插件已经被弃用 所以在构建时会收到编译警告 这是个好的开始 但还不够 只要允许加载传统的 DAL plug-ins 相机 App 仍然处于风险之中
为了全面解决安全漏洞 并使所有用户的系统更加强大 我们计划在 macOS Ventura 之后的 下一个主要版本中完全禁用
这对您意味着什么 我们希望这信息足够清晰了 如果您目前维护的 是 DAL plug-in 那么是时候将您的代码 移植到相机扩展了
另外 请告诉我们您遇到的问题 我们希望能解决这些问题 并提供丰富功能 非常期待与您的合作 今天关于 macOS 相机扩展的介绍就到这里 我们迫不及待地想知道 您会给 Mac 带来 哪些新鲜而有创意的相机体验 并希望您能享受这个过程
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。