macOS平台下虚拟摄像头的研发总结

一、背景介绍

虚拟摄像头,顾名思义,就是利用软件技术虚拟出一个摄像头硬件设备供用户使用。当我们需要对视频图像进行处理再输出时,虚拟摄像头就具备非常大的价值了。关于如何在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

时间: 2024-08-11 19:01:32

macOS平台下虚拟摄像头的研发总结的相关文章

研发范围和时间的“信息透明化”之多项目多平台下的协作与流程

这是研发范围和时间"信息透明化"系列的第三篇文章,在<研发范围和时间的"信息透明化"之Redmine统一平台>中我们讨论了信息透明化的一种实现平台Redmine,在<研发范围和时间的"信息透明化"之协作与流程>中我们对如何基于一个产品/项目和一套信息管理平台进行信息透明化管理的协作与流程做了详细阐述.对研发信息透明化而言,现实中情况可能会比较复杂: 由于历史遗留问题等因素,团队中可能会使用一种以上的平台进行研发信息和过程管

二十四、V4L2框架分析和虚拟摄像头驱动编写

一.V4L2框架分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括四个部分: 1. 字符设备驱动程序核心:V4L2本身就是一个字符设备,上层连接用户空间 2. V4L2驱动核心:构造通用的视频设备驱动框架,为上层操作提供统一接口 3. 平台V4L2驱动:在V4L2框架下,根据平台自身特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev 4.

Hyper-V 2016 系列教程43 虚拟化平台下国产杀毒软件介绍 瑞星

瑞星虚拟化系统安全软件FOR 桌面产品概述 瑞星桌面虚拟化系统是瑞星虚拟化系统安全软件的子系统之一,其主要功能是为企业内部虚拟化桌面系统提供安全防护.该系统包括瑞星桌面虚拟化系统中心.企业私有云查杀服务器(ScanServer)及轻量级桌面客户端三部分.在实现多中心统一管理,企业私有云查杀等功能基础上,极大地减少桌面客户端的资源占用,提高病毒查杀率,使桌面病毒扫描速度比传统杀软提升5至10倍. 产品组成 瑞星虚拟化系统安全软件整个防病毒体系是由以下几个相互关联的子系统组成.每一个子系统均包括若干

X86平台下基于grub2+busybo+linux-2.6.36制作linux系统

X86平台下基于grub2+busybo+linux-2.6.36制作linux系统 一.下载内核源码,grub2源码,以及busybox源码: 下载grub2源码 ftp://ftp.gnu.org/gnu/grub/ 找到需要的版本就好了,这里选择grub-1.99.tar.gz 下载busybox源码 http://www.busybox.net/downloads/ 找到需要的版本就好了,这里选择busybox-1.22.1.tar.bz2 下载linux-2.6.36内核源码 http

(四) 虚拟摄像头vivi体验

目录 虚拟摄像头vivi体验 源码下载 修改Makefile 安装xawtv 测试体验 title: 虚拟摄像头vivi体验 date: 2019/4/23 19:20:00 toc: true --- 虚拟摄像头vivi体验 vivid是虚拟的摄像头驱动.可以理解等同于UVC,只是说不需要USB总线驱动控制等 源码下载 # 查看内核版本 $ uname -a Linux 100ask 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:

在 Linux 平台下使用 JNI

引言 Java 的出现给大家开发带来的极大的方便.但是,如果我们有大量原有的经过广泛测试的非 Java 代码,将它们全部用 Java 来重写,恐怕会带来巨大的工作量和长期的测试:如果我们的应用中需要访问到特定的设备,甚至是仅符合公司内部信息交互规范的设备,或某个特定的操作系统才有的特性,Java 就显得有些力不从心了.面对这些问题,Sun 公司在 JDK1.0 中就定义了 JNI 规范,它规定了 Java 应用程序对本地方法的调用规则. 实现步骤及相关函数使用 本文将一步步说明在 Linux 平

Windows 和 Linux 平台下的端口转发工具

原文地址: http://unmi.cc/windows-linux-port-forwarding/ 这里记录一下我曾经使用过的几个端口转发工具,即端口映射.端口重定向,和 NAT 也是差不多的概念. Linux 下用过 iptables,rinetd:Windows 下用过某个防火墙的 NAT 功能.RemoteAnywhere 的端口重定向.FPipe,还有最近刚找到的 PassPort.试着去设置一下 Windows 2000 的 NAT  功能,但未成功,还是特定的软件简单易用,下面介

Windows平台下利用APM来做负载均衡方案 - 负载均衡(下)

概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效果,可以说还是非常的理想的.同时我们也收集到了不少的问题,比如说如何在这种分布式的架构下使用Session,NLB中有一台服务器挂掉了会导致对外暴露的地址无法访问,如果实现服务器之间的同步,如果更好的进行热修复等等,还有我们在上一篇中也提到了NLB所提供的功能是非常简单的,为了回答我们前面提到的问题

Linux平台下裸设备的绑定:

Description Given a n × n matrix A and a positive integer k, find the sum S = A + A2 + A3 + - + Ak. Input The input contains exactly one test case. The first line of input contains three positive integers n (n ≤ 30), k (k ≤ 109) and m (m < 104). Then