一、背景介紹
虛擬攝像頭,顧名思義,就是利用軟件技術虛擬出一個攝像頭硬件設備供用戶使用。當我們需要對視頻圖像進行處理再輸出時,虛擬攝像頭就具備非常大的價值了。關於如何在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文件的上上之選。
六、參考鏈接
2. https://superuser.com/questions/47233/how-can-i-force-a-mac-os-x-kext-to-load-prior-to-login
5. https://forums.developer.apple.com/thread/18019