在macOS平台做音视频相关开发时,Core Media IO(简称CMIO)是最贴近系统的设备输入输出框架。它负责管理摄像头、麦克风等硬件设备,也允许开发者创建虚拟设备,让系统和其他应用误以为存在真实的音视频源。理解CMIO的工作方式,能够帮助我们在不依赖第三方库的情况下,直接控制数据采集与投放。

CMIO基础概念与对象模型
CMIO采用面向对象的设计思路,核心概念包括系统、设备、流、控制和时钟。系统(System)是入口,通过它可以获取当前连接的所有设备。设备(Device)代表一个物理或虚拟的音视频输入源,比如内置摄像头、外接麦克风或者我们自己创建的虚拟摄像头。每个设备下面包含一个或多个流(Stream),流是真正传输采样数据的通道,视频流推送图像帧,音频流推送声音采样。
控制和时钟用于描述设备的参数与同步关系。例如摄像头可能有曝光、分辨率等控制项,时钟则保证音视频流在时间上对齐。CMIO用Core Foundation风格的对象引用(CMIODeviceID、CMIOStreamID等)来标识这些实体,开发者通过对应的API函数读取属性、监听通知、启动或停止流。
设备与流的属性读取
在CMIO中,几乎所有信息都通过属性(Property)机制获取。每个对象都有属性作用域和元素,常见作用域有全局、输入和输出。以读取设备名称举例,我们需要构造CMIOObjectPropertyAddress,指定选择器为kCMIODevicePropertyDeviceName,然后调用CMIOObjectGetPropertyData。这种方式虽然比上层框架繁琐,但能拿到最完整的设备信息。
流属性里最关键的是格式描述。视频流会提供像素格式、宽高、帧率,音频流会提供采样率、声道数和位深。只有先查询到流支持的格式,才能正确配置捕获参数,否则启动流会失败。实践中建议把设备支持的所有格式打印出来,再根据业务需求选择最匹配的一项。
枚举并打开真实摄像头与麦克风
要捕获真实设备数据,第一步是枚举系统里的CMIO设备。调用CMIOObjectGetPropertyData并传入kCMIOHardwarePropertyDevices,可以得到设备ID数组。遍历数组时,再通过kCMIODevicePropertyDeviceUID和kCMIODevicePropertyDeviceName区分哪些是摄像头,哪些是麦克风。通常视频设备带有摄像头相关的名称,音频设备则归类到声音输入。
找到目标设备后,需要取得它下面的流。用kCMIODevicePropertyStreams获取CMIOStreamID列表,然后针对视频流或音频流分别配置。启动流之前,必须设置好回调函数或者缓冲区监听,否则系统推送的数据无处接收。macOS要求我们在独立线程或run loop里处理流数据,避免阻塞主线程导致丢帧。
配置流格式并启动捕获
配置格式时,先查询流当前支持的格式数组,挑选需要的分辨率与帧率。通过kCMIOStreamPropertyFormatDescription写入新格式,系统会重新协商设备。成功后调用CMIODeviceStartStream,并在回调中拿到CMSAMPLEBUFFER。视频帧可从采样缓冲里提取CVImageBuffer,音频则从声音描述里读取 PCM 数据。
很多初学者忽略权限问题。从较新的macOS版本开始,访问摄像头和麦克风必须在系统设置里授予终端或宿主App权限,否则枚举不到设备或者启动流被拒绝。开发阶段要引导用户去隐私与安全性面板开启对应开关,同时正确处理授权回调,避免程序无声崩溃。
构建虚拟摄像头输出源
CMIO不仅消费设备,也能生产设备。所谓虚拟摄像头,就是向系统注册一个CMIO设备,但它背后没有真实硬件,而是由我们的代码提供帧数据。系统里的视频会议软件、浏览器会像对待普通摄像头一样去打开它,从而实现画面替换、特效合成等效果。
创建虚拟设备需要借助CMIO的扩展机制,通常涉及生成一个被系统信任的插件或守护进程,向CMIOService发送注册请求。注册时要填写设备UID、名称、支持的流格式。之后系统会把该设备列入可用摄像头,其他App通过正常API就能发现并启动流。
向虚拟设备喂入图像帧
虚拟摄像头启动后,CMIO会向我们注册的回调请求数据。我们需要按约定格式准备好CVPixelBuffer,包装成CMSampleBuffer回传。如果画面来自之前捕获的真实摄像头,只要把真实流里的图像做处理再转发即可;如果来自电影文件或合成画面,也要保证时间戳连续,否则接收端会出现卡顿或跳跃。
音频虚拟设备同理,只不过回传的是声音采样缓冲。要注意虚拟设备的时钟必须稳定,建议以主机时钟为基准生成呈现时间。当消费者切换分辨率时,我们的代码要能动态更换输出格式,不然会导致连接中断。
真实捕获与虚拟输出的联动示例
一个典型场景是:先按前文方法打开真实摄像头,拿到原始视频帧;接着创建虚拟摄像头,把每一帧加上水印后推送给虚拟设备。这样用户在Zoom里选择我们的虚拟摄像头,就能看到带水印的画面,而真实摄像头的控制仍掌握在自己程序里。
实现联动时要管理两条流的生命周期。真实流负责输入,虚拟设备负责输出,中间用线程安全的队列缓冲帧。若真实摄像头掉线,虚拟设备应输出最后一帧或特定占位图,避免消费端黑屏。下表列出关键组件与职责:
| 组件 | 类型 | 主要职责 |
|---|---|---|
| 真实设备流 | 输入 | 从硬件捕获音视频帧 |
| 处理队列 | 中间层 | 转码、加水印、同步时钟 |
| 虚拟摄像头 | 输出 | 向系统注册并提供帧给其它App |
调试与常见故障
开发CMIO程序时,控制台日志非常关键。系统会打印设备注册失败、授权缺失、格式不支持等信息。若虚拟摄像头不显示,先确认插件是否已加载到CMIOService,再检查UID是否重复。真实捕获无画面时,多半是流格式不匹配或权限未开。
另一个常见问题是帧率不稳。务必在回调里使用系统提供的时间戳,而不是自己随便计数。若处理耗时过长,应降低分辨率或把运算移到专用线程,保证回传不超时。经过充分测试,CMIO方案能在多数Mac机型上实现低延迟的捕获与虚拟输出。
小结与进阶方向
通过CMIO捕获摄像头与麦克风,并构建虚拟摄像头,本质是把系统的设备抽象玩透。我们既做消费者,也做生产者。掌握对象模型、属性读取、流启动和虚拟注册,就能脱离QuickTime或AVFoundation的限制,写出更底层的音视频工具。
进阶方向包括:利用多个设备做多视角合成、把虚拟麦克风与虚拟摄像头绑定成同一设备组、在虚拟设备里实现硬件加速转码。随着对CMIO理解的加深,你还可以参与系统音频路由、屏幕采集等更复杂的需求,打造专业级Mac多媒体应用。
Core_Media_IOCMIO设备虚拟摄像头修改时间:2026-08-11 09:24:40