虚拟摄像头,顾名思义,就是利用软件技术虚拟出一个摄像头硬件设备供用户使用。当我们需要对视频图像进行处理再输出时,虚拟摄像头就具备非常大的价值了。关于如何在 Windwos 上实现一个虚拟设备的资料已经非常丰富了,Windows Driver Kit 里面也有非常多的帮助文档。这篇博文主要总结了在 Mac 下开发虚拟摄像头的一些经验。Mac 下的虚拟摄像头产品其实也有不少,例如 CamTwist, CamMask, CamWiz, ManyCam 等。但是关于如何在 Mac 下开发虚拟摄像头设备的资料却是异常匮乏。通过一番搜索后才找到一个关键字:CoreMediaIO. 经过了解,CoreMediaIO 是 Mac 下的一个 framework,主要用于对视频图像进行处理。而 CoreMediaIO framework 有一个 Device Abstraction Layer(DAL),它类似与 Mac 下 CoreAudio 的 Hardware Abstraction Layer(HAL)。HAL 主要是用来处理音频硬件发送的音频流的,而 DAL 则是用来处理视频设备的视频流的。因此,利用 DAL 插件框架,可以模拟出一个摄像头设备供上层用户使用。
CoreMediaIO DAL 有一个示例项目,这个项目模拟出了一个名为 "Sample" 的设备,通过底层 kext 模块提供的模拟数据实现视频帧的传递。这个 DEMO 要真正使用起来的话,有一些需要注意的地方。在 Demo 中的 README 文件中推荐使用如下命令安装预编译好的程序:
- // Debug
- sudo darwinup install {
- path to CoreMediaIO folder
- }
- /Prebuilts/Sample - Debug.tar.gz
- // Release
- sudo darwinup install {
- path to CoreMediaIO folder
- }
- /Prebuitls/Sample - Release.tar.gz
这样安装之后,并不能马上就能找到虚拟摄像头。一方面是因为 kext 提供的模拟数据超过 800MB,加载到内存中需要一定的时间;另一方面是因为 Prebuilts 中的 kext 模块是未签名的。而 OSX 自从 Mavericks 开始要求 kext 模块必须经过签名,系统才会自动加载。否则的话需要关闭 System Integirty Protection(SIP),手动加载 Kext 模块才能让 Demo 正常工作。
在我们着手开发定制自己的虚拟摄像头之前,第一步就是要搞清楚 Demo 工程的组织结构。在 Demo 工程中包括两个文档,分别说明了 DAL 插件的工程结构、工作原理。这两个文档在开发之前应该要好好看看,对于了解整个代码结构和工作机制具有较大的帮助。另外,如何编译整个 Demo 工程也是个大问题。因为下载下来的工程中缺少了 CoreAudio 模块,需要手动下载 CoreAudio 模块加入到工程中去。然后可能还有一些语法错误需要修改,这个根据系统版本和 XCode 版本视情况而定。
1. 添加 CoreAudio 模块。默认工程是不包含 CoreAudio 模块的,因此直接编译会有很多链接错误:
下载地址:Core Audio Utility Classes.(可能需要 Apple ID 登陆)。下载好把整个文件夹加入到 Demo 工程中去进行编译。如果编译还是有错的话,可以将 CoreAudio 模块单独编译出一个静态库,然后在 Demo 工程中加入 CoreAudio 头文件和静态库进行编译,这样应该就可以解决掉编译问题了。
2. 语法修改。可能是因为 macOS SDK 的版本问题,编译过程中需要修改一些语法错误,如下:
解决方法倒也简单:
还有一些赋值的问题,不过基本上都是编译标准的问题,不难解决掉。
1. 分辨率。官方 Demo 工程提供了几个分辨率:720x480, 1280x720, 1920x1080。如果要增加自己的分辨率的话,可以直接把这几个分辨率改掉。
在这里将分辨率的宽高修改掉,同时注意还有帧率也是在这里修改的。注意 Demo 工程三种分辨率使用的颜色模式是 UVVY422 哦!因此如果要采用 Demo 工程的颜色模式的话,需要将图像转换为 UYVY422 格式。
2. 颜色模式。如何修改颜色模式呢?总共有两个地方需要修改,除了上面那个 kCMVideoCodecType_422YpCbCr8 需要修改,在 CMIO_DPA_Sample_Server_Stream.cpp 中还有个地方需要修改:
这里指定 Plugin 支持的颜色模式。那么,Plugin 总共支持哪些颜色模式呢?看看 enum 里的枚举成员就知道了:
3. 设备基本信息。设备基本信息主要就是设备的名称,方便用户进行识别选择使用。这个只有一个地方需要修改:
4. Create server failed 的问题。在 plugin 的入口函数中,有这样一段代码:
开发人员在注释中详细解释了,为了调试的目的这里尝试使用了手动方式启动 Assistant。而在实际测试时,bootstrap_create_server() 会经常失败抛出异常,导致入口函数提前结束执行,因而创建虚拟设备失败。注释中还解释道,一般是使用 plist 文件在系统启动时创建 assistant 服务。这样,bootstrap_loop_up() 在查找到 Assistant 服务后,就会跳过手动创建 Assistant 服务。
由此看来,位于 / Library/LaunchDaemons 中的 plist 文件是必不可少的。/Library/LaunchDaemons / 下的 plist 文件其实就是指定哪些程序随系统启动自运行。其内容如下:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
- <plist version="1.0">
- <dict>
- <key>
- Label
- </key>
- <string>
- com.apple.cmio.DPA.Sample
- </string>
- <key>
- ProgramArguments
- </key>
- <array>
- <string>
- /Library/CoreMediaIO/Plug-Ins/DAL/Sample.plugin/Contents/Resources/SampleAssistant
- </string>
- </array>
- <key>
- MachServices
- </key>
- <dict>
- <key>
- com.apple.cmio.DPA.Sample
- </key>
- <true/>
- </dict>
- </dict>
- </plist>
1. 移除 Demo 中的模拟帧数据。Demo 工程提供了三个分辨率的模拟数据,每个分辨率都有 30 帧的数据。三个分辨率的数据加起来有八百多兆,定制的时候有必要把他们去掉。根据 Demo 工程配置来看,模拟帧数据是作为 section data 编译到了 kext 模块中去了:
因此这里去掉模拟数据的方式非常简单,把 "Other Linker Flags" 里面的内容全部删掉即可。当然,代码中使用这些 section data 的部分也要跟着删掉。
2. 内核签名。OSX 自从 Mavericks 开始,对 Kext 开发引入了签名机制。所有未签名的 kext 模块系统不会再自动加载。因此,要让系统自动加载第三方开发的 kext 模块,开发者需要向苹果申请能够对 kext 进行签名的证书(看这里)。一般的开发者证书即使正常签名了,也不能被系统正常识别。签名过后,可以对 kext 模块进行签名验证:
在没有能够对 kext 进行签名的证书时,可以把 SIP 关掉进入测试模式。这样即便 kext 未签名也可以手动进行加载,方便对程序进行测试。
3. kext 模块自动加载。这里有一点奇怪的是:经过签名的 kext 模块在系统重启时会被系统自动加载,但是通过 Plugin 访问不到 kext 模块服务。必须要先调用 kextunload 一次,再 kextload 一次才能起作用。暂时没有搞清楚这是什么原因,不过根据其他虚拟摄像头产品的解决方案来看,也是存在这个问题的。比如 CamWiz 的解决方案就是:
(1)编写一个 plist 文件放到 / Library/LaunchDaemons / 文件下,其内容为:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
- <plist version="1.0">
- <dict>
- <key>
- Disabled
- </key>
- <false/>
- <key>
- Label
- </key>
- <string>
- com.apple.vcam.DriverReloader
- </string>
- <key>
- ProgramArguments
- </key>
- <array>
- <string>
- /Library/CoreMediaIO/Plug-ins/DAL/Sample.plugin/Contents/Resources/reloadKext.sh
- </string>
- </array>
- <key>
- RunAtLoad
- </key>
- <true/>
- </dict>
- </plist>
(2)编写一个 shell 脚本 reloadKext.sh,并打包到 Sample.plugin 的 Resources 文件夹下,其内容如下:
- #!/bin/sh
- /sbin/kextunload "/Library/Extensions/IOVideoSample.kext"
- /sbin/kextload "/Library/Extensions/IOVideoSample.kext"
这样,每次系统重启的时候都会去调用这个脚本,卸载 kext 模块然后再加载。这就解决了 Kext 模块的问题。事实上,如果没有硬件层的需要,去掉 kext 模块是最好的。但是整个 Demo 工程代码繁杂,文档又是极其匮乏,想要剥离 Kext 模块难度较大。但是仍然有不少的产品实现了这一点,如 CamTwist、Cammask 和 ManyCam。CamTwist 更牛逼的是,在一个插件中虚拟出了两个设备。一个是 YUV 颜色模式,另外一个是 BGRA 颜色模式。
1. 程序打包及安装路径。根据职能需要,整个项目的安装程序分成三个部分:
(1)*.plugin 安装到 / Library/CoreMediaIO/Plug-Ins/DAL/.
(2)*.kext 安装到 / Library/Extensions/. 苹果官方规定:第三方开发的 kext 模块只能放在这里,而苹果自带的 kext 模块则放在 / System/Library/Extensions/.
(3)*.plist 安装到 / Library/LaunchDaemons/. 放在这里的 plist 文件都是为了开机启动。因此在制作安装包时,记得要求用户安装完毕时重启系统。
2. 文件权限问题。安装包中的所有文件最好修改所有者权限,否则有可能无法使用:
- $ sudo chown -R root:wheel *
3. 相关命令
(1)otool 搭配 install_name_tool 使用
(2)kext * 族命令(kextload, kextunload, kextstat, kextutil)
4. 安装包打包程序还是推荐使用 packages,这是一款良心工具。使用简单、界面美观、功能强大实用,实在是制作 pkg 文件的上上之选。
1. https://developer.apple.com/library/content/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html
2. https://superuser.com/questions/47233/how-can-i-force-a-mac-os-x-kext-to-load-prior-to-login
3. http://apple.stackexchange.com/questions/163059/how-can-i-disable-kext-signing-in-mac-os-x-10-10-yosemite
4. https://developer.apple.com/library/content/samplecode/CoreMediaIO/Listings/Prebuilts_Prebuilts_ReadMe_txt.html
5. https://forums.developer.apple.com/thread/18019
6. https://macwish.com/kext-signing-for-mac-yosemite/
7. https://developer.apple.com/library/content/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html
来源: http://www.cnblogs.com/csuftzzk/p/macos_virtual_camera.html