linux平臺實現USB虛擬總線驅動二(把驅動移植到Android系統)

by fanxiushu 2019-11-07  轉載或引用請註明原始作者。
接上文,把上文中開發的驅動移植到android系統中來。
因爲我身邊沒有Android系統的設備,很疑惑,目前確實沒有。
因此只好在模擬器上打主意了, 我想模擬器跟真機其實差不多的,沒道理模擬器能移植成功,而真機無法移植成功。
要移植驅動,需要重新編譯Android系統的linux kernel源碼,重新替換原來的kernel內核。
對真實機器來說,這叫刷機,而模擬器只需替換裏邊的某個文件,或者啓動emulator模擬器指定 -kernel 參數。
因爲linux內核是遵循GPL開源協議的,因此任何Android手機廠商,必須提供對應的linux內核源碼,
因此不用擔心無法獲取對應手機的android系統的linux 內核源碼來重新編譯刷新。

首先,需要獲取模擬器對應的 linux 內核源碼,
可以使用git到https://android.googlesource.com下載,
但是android.googlesource.com中國境內無法訪問,可以替換成 aosp.tuna.tsinghua.edu.cn 。
模擬器內核源碼地址是 https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git , 取名叫goldfish,多怪異的名字。
裏邊有2.6,  3.4, 3.10, 3.18,4.4, 4.9, 4.14所有版本,可是我編譯了全部版本,只找到4.4版本 能好用,
首先,2.6和3.4版本太老,在最新的模擬器中基本已經不再支持了,最低都是 3.10的內核版本,
3.10的我編譯過ARM模式,可是把新編譯的內核放到模擬器中,卻是黑屏,3.10編譯成ARM64,倒是可以編譯成功而且能正常運行。
可是無法設置USB_ARCH_HAS_HCD,這個標誌是內核支持USB主機端驅動,
要編譯虛擬USB總線驅動,必須要開啓 USB_ARCH_HAS_HCD支持。
無論怎麼設置,最終編譯成ARM64的時候,都會被自動重置 USB_ARCH_HAS_HCD的設置,沒辦法,只好放棄。
這時只有3.18以上的源碼可以選擇了,發現3.18也是同樣的問題。於是只好從4.x以後版本打主意。
4.x以後的版本只能運行在Android8以上的模擬器中,而且模擬器只支持x86模式,不再支持arm了。
因此就只有編譯成x86這一條路了。

以下是下載內核源碼和編譯器的過程:
準備一臺linux系統,我使用的是CentOS7, 當然也可以使用utubun等其他系統,反正linux版本是夠多的。
 git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git ;下載內核,
然後使用git branch -a查看版本,使用git checkout 檢出對應版本。如果不想下載全部版本,比如只想下載 
android-goldfish-4.4-dev這個分支,使用如下命令:
git clone -b android-goldfish-4.4-dev --single-branch https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git
使用如下命令下載編譯器
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6 ; (arm編譯器)
git clone https://aosp.tuna.tsinghua.edu.cn/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9 (arm64編譯器)
本來是從上面的鏡像網站中下載x86編譯器,可是發現這個網站提供的x86編譯器有問題,
搞的來編譯的每個版本都不成功,開始還以爲是哪裏配置錯誤了。最後只好去其他網站下載了對應的x86編譯器。
下載源碼之後,進入對應的源碼目錄,假設編譯成x86平臺64位,編譯器路徑 /home/android/x86_64-linux-android-4.9,
編譯器前綴是 x86_64-linux-android-, 如下調用
export PATH="/home/android/x86_64-linux-android-4.9/bin":$PATH
export CROSS_COMPILE=x86_64-linux-android-
export ARCH=x86_64
make x86_64_ranchu_defconfig
make -j8
就這麼簡單,只要不出問題,編譯總是會成功的。
經常折騰嵌入式系統的編譯裁剪的對這些流程應該會比較熟悉。

雖然看起來簡單,可是一開始的時候,折騰的時間太長了,因爲總是編譯失敗。
而且每次clone和checkout源碼太慢了,浪費了太多時間。
網上的資料是很多,但都挺亂,而且基本都是挺老的資料。

 爲了把我們的虛擬USB 總線驅動加入到內核中,需要做些配置。
在調用完 make x86_64_ranchu_defconfig,會在內核源碼目錄下出現  .config,打開這個文件。
確保CONFIG_USB_ARCH_HAS_HCD 已經開啓,
同時因爲我們這裏是需要模擬 USB攝像頭,因此需要支持UVC的,因此確保 
CONFIG_MEDIA_SUPPORT 和 CONFIG_MEDIA_USB_SUPPORT 開啓,
否則因爲沒有UVC驅動,模擬的USB攝像頭也沒法被android系統識別。
 
然後就是把我們的驅動目錄(假設目錄名是usb_host)加入到 內核源碼的 drivers目錄的usb子目錄中,
在usb_host裏邊添加Kconfig和Makefile, 在usb_host所在的usb目錄中修改 Kconfig,添加 

source "drivers/usb/usb_host/Kconfig"

 修改usb目錄中的Makefile ,添加
obj-$(CONFIG_XXX) += usb_host/
CONFIG_XXX對應中usb_host目錄內的Makefile配置。
這些配置方式,其實可以借鑑drivers目錄中其他驅動的配置參數,所以這裏也不再過多介紹。

這些配置完成之後,make -j8, 編譯完成,這樣就把我們的虛擬USB總線驅動集成到內核中去了。
然後運行Android模擬器,把內核替換成自己編譯的,會發現內核版本變化了,我們的驅動也在裏邊正常運行了。

接下來是應用層程序的編譯,因爲我們的驅動是把URB請求轉發到應用層來處理,然後在應用層模擬USB攝像頭數據再返回給驅動。
因此需要實現應用層程序。
首先需要從google官網下載NDK,
當然可以先直接下載 Android Studio開發環境,然後裏邊的Android模擬器,NDK等等,都可以自動下載。
如果要單獨下載,可以如下:
wget https://dl.google.com/android/repository/android-ndk-r20-linux-x86_64.zip
下載的是 NDK r20的版本,
下載之後,配置好環境,就可以直接使用clang++編譯代碼了。

本來這裏編譯的代碼,
應該以 so 動態庫提供,並且提供 JNI 接口給上層java程序調用,
這樣android的應用層開發者就可以調用java接口來操作我們的驅動了。
但是這裏爲了簡單,就直接編譯成可執行程序,使用adb登錄到shell來執行。
本來以爲這樣就大功告成了,如下圖以打印系統日誌方式啓動模擬器,
系統打印的日誌表示已經找到了我們的虛擬USB設備,並且正確識別出了UVC攝像頭。



但是Android系統卻不能識別,Android上面的App程序也不能識別。
一開始的想法是隻要內核正確識別出了UVC攝像頭,Android應用層就能正確識別攝像頭,而且還以爲能代替前置或後置攝像頭,
能被Android中自帶的camera程序識別到。
仔細研究後發現,這基本是兩回事,
前置和後置攝像頭,與我們的USB 攝像頭,內部處理方式都不同。
前置和後置攝像頭,是Android提供一套標準的HAL層接口,各個廠商根據接口實現對應攝像頭驅動,
然後Android在內部處理一系列數據交互,最後在java應用層提供一套 Camera API給app程序使用。
而USB攝像頭,則是標準的在USB通訊上實現的UVC協議,Android並沒有在java應用層提供對應的UVC接口。
通常需要我們自己實現,github上有個實現類似功能的開源庫:
https://github.com/saki4510t/UVCCamera
 

而且這裏還有一個問題,USB分爲主機端(host)和設備端(device),Android系統默認是處於usb設備端的狀態,
而我們開發的USB虛擬總線驅動是運行在主機端,因此必須讓Android系統轉換到USB主機端狀態,否則還是無法正確識別UVC攝像頭。
具體做法就是創建一個 android.hardware.usb.host.xml 文件,內容如下:
<permissions>
<feature name="android.hardware.usb.host"/>
</permissions>
然後把它複製到 /system/etc/permissions 目錄下,重啓Android系統,
經過這麼一通折騰,這個虛擬USB攝像頭才能被正常使用。

本來以爲能用虛擬USB攝像頭這種辦法來替換Android系統中的默認攝像頭,結果是願望落空。
看來要替換Android中默認攝像頭的視頻,使用hook可能是最好的,
一種辦法在掌握了攝像頭部分HAL接口協議的之後,實現另一個驅動接口來代替它,從而HOOK住裏邊的視頻數據。
或者乾脆直接HOOK Android上層的Camera API接口, 因爲Android系統的所有java app都是通過app_process啓動的。
因此直接改寫app_process程序,就能幾乎HOOK住所有APP的行爲,有個框架xPosed就是實現這樣的功能的。
這個比起windows中的HOOK輕鬆得多,因爲就只有一個非常單一的入口,就能HOOK所有APP進程。
而windows中的HOOK是非常零散的,往往要東一塊西一塊的查漏補缺。

目前並不清楚這個USB虛擬攝像頭能在Android系統中有什麼用處,既然是實現了,就當做來玩玩。
下圖的app是從網上下載的,能識別UVC攝像頭的app。圖中已經在運行的USB攝像頭,就跟上一篇文章攝像頭效果一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章