2019-07-03java
關鍵字:攝像頭適配、相機花屏、USB攝像頭花屏android
rk3128 是一套定位於低成本 Android 商用開發板芯片的解決方案。它雖價格低廉、性能通常,但卻依然該有的功能都支持。ide
像攝像頭適配在這款芯片中就是有一套完善的支持策略的。函數
這款芯片支持傳統手機形式的前置、後置類型攝像頭,也支持市面上常見的USB攝像頭。性能
筆者此次的需求是要適配一款 USB攝像頭。筆者手裏的 rk3128 開發板運行的是 Android4.4 操做系統。下面是適配簡要步驟測試
其實整個適配過程很簡單,由於主要的接口都有現成的。惟一須要咱們作的就是確保一些節點文件的存在與權限而已。spa
首先來確保 /dev/video* 節點的權限。USB攝像頭都會被識別成 /dev/video0 ~ /dev/video* 節點,一個頭映射成一個節點。操作系統
device/rockchip/rksdk/ueventd.rk30board.rc
在這裏要作的也很少,根據自身業務須要修改一下權限便可。3d
/dev/video0 0666 system camera /dev/video1 0666 system camera /dev/video2 0666 system camera
接下來還能夠瞄一眼下面這個文件code
\system\core\rootdir\ueventd.rc
這個文件裏若是也有關於 /dev/video* 的權限限定操做,建議將它刪了。咱們一切以上面的 ueventd.rk30board.rc 爲準。
其次能夠關注一下 Camera 的 HAL 層。這一層是相當重要的。它起到對接驅動與上層服務的之間樞紐的做用。
/hardware/rk29/camera/CameraHal/
還有諸如以下兩個目錄
.\frameworks\av\services\camera\libcameraservice\
.\frameworks\av\camera
再更上層一點的還有如下目錄內的代碼文件
.\frameworks\base\core\java\android\hardware\
最後再往上就到 APK 層了。
其實筆者這一路適配下來都沒遇到什麼阻礙,rk 原廠 SDK 對這塊的功能就已經很完善了。基本上是改了一下權限之後開機插上攝像頭就能看到對應節點了。筆者惟一遇到的比較頭疼的問題在於點亮攝像頭之後。
前面一路的適配工做都異常的順利。但就在點亮攝像頭之後發現了一個大問題:在畫面處於 0 度旋轉方向上時自帶相機的預覽畫面出現了花屏現象。以下圖所示
這是一個很詭異的問題。筆者的開發板是經過系統屬性 ro.sf.hwrotation 來控制畫面旋轉方向的。共能夠設置 4 個值:0、90、180、270,分別表明四個畫面旋轉方向。這四個方向中,僅 0 度會花屏,其它 3 個都正常。並且,花屏狀態下拍出來的照片也是正常的。因此這個問題很是詭異。
但這個問題其實要肯定緣由也不難。咱們根據前面的已知條件,能夠排除掉攝像頭和開發板的硬件問題。因此很快就能將矛頭指向軟件層面了。
而咱們又已肯定了花屏狀態下拍出來的照片也正常,這也肯定了花屏和咱們對於相機設定的參數無關。咱們的 APK 對於相機的調用也無非就是打開與關閉攝像頭以及設置一下圖像參數而已。所以這一步咱們也能夠排除掉是上層 APK 的問題。而且在下載了第三方相機 APK 之後也發現一樣會有問題。這更進一步撇除了上層 APK 的緣由了。
因此剩下來的就是中間服務層的嫌疑最大了。
中間服務層有 frameworks 層的以及 HAL 層的。筆者這邊憑直覺就排除掉 framework 層服務的嫌疑了,直接分析 HAL 層。
那到這,咱們來思考一下:一個相機應用從打開到咱們能看到圖像並能拍照,它背後的實現原理是什麼?
確定得有一個模塊專門負責採集數據,從驅動那裏將原始畫面數據拿上來。也確定得有一個模塊專門負責拍照,在按下快門之後,將當前畫面數據截取下來,打包成圖片格式並存儲起來。必須也少不了有一個模塊專門負責畫面預覽,咱們拍照的時機是不肯定的,所以屏幕上必須實時預覽攝像頭當前採集到的畫面,這在本質上就是一個實時視頻流的播放,說白了就是得有一個播放器來爲咱們播放實時畫面。確定也還有其它模塊,可是一個相機最核心的幾個模塊估計就這仨了。
捋清楚了相機背後的原理之後,咱們再來根據前面的已知條件來分析。花屏狀態下拍照功能正常,並且其它旋轉角度下顯示正常。這就將前面說的模塊一和模塊二的嫌疑給排除掉了。那就只剩下一個預覽模塊了,而事實上咱們也確實就是預覽出現了問題。
那預覽模塊的核心又是什麼呢?是解碼!攝像頭採集出來的數據是不能直接拿給播放器播放的,這中間須要解一下碼。同時還得根據當前畫面的旋轉角度做一下轉換。所以,這個問題擁有最大嫌疑的就是預覽模塊的解碼功能。
rk3128 的 HAL 層裏負責預覽解碼的代碼在哪呢?
./hardware/rk29/camera/CameraHal/DisplayAdapter.cpp
在這個代碼裏有這麼一個無限循環函數的定義
void DisplayAdapter::displayThread() { //... }
咱們跟蹤一下這個函數,能夠發如今預覽過程當中,對於每一畫面幀都有一個調用解碼函數的操做,以下所示
case CMD_DISPLAY_FRAME: { // ... if((frame->frame_fmt == V4L2_PIX_FMT_YUYV) && (strcmp((mDisplayFormat),CAMERA_DISPLAY_FORMAT_YUV420P)==0)) { if((frame->frame_width == mDisplayWidth) && (frame->frame_height== mDisplayHeight)) arm_yuyv_to_yv12(frame->frame_width, frame->frame_height, (char*)(frame->vir_addr), (char*)mDisplayBufInfo[queue_display_index].vir_addr); } else if((frame->frame_fmt == V4L2_PIX_FMT_YUYV) && (strcmp((mDisplayFormat),CAMERA_DISPLAY_FORMAT_YUV420SP)==0)) { if((frame->frame_width == mDisplayWidth) && (frame->frame_height== mDisplayHeight)) arm_yuyv_to_nv12(frame->frame_width, frame->frame_height, (char*)(frame->vir_addr), (char*)mDisplayBufInfo[queue_display_index].vir_addr); } else if((frame->frame_fmt == V4L2_PIX_FMT_NV12) && (strcmp((mDisplayFormat),CAMERA_DISPLAY_FORMAT_RGB565)==0)) { arm_nv12torgb565(frame->frame_width, frame->frame_height, (char*)(frame->vir_addr), (short int*)mDisplayBufInfo[queue_display_index].vir_addr, mDisplayWidth); } else if((frame->frame_fmt == V4L2_PIX_FMT_NV12) && (strcmp(mDisplayFormat, CAMERA_DISPLAY_FORMAT_YUV420SP)==0)) { #if 1 arm_camera_yuv420_scale_arm(V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_NV12, (char*)(frame->vir_addr), (char*)mDisplayBufInfo[queue_display_index].vir_addr, frame->frame_width, frame->frame_height, mDisplayWidth, mDisplayHeight, false, frame->zoom_value); #else rga_nv12_scale_crop(frame->frame_width, frame->frame_height, (char*)(frame->vir_addr), (short int *)(mDisplayBufInfo[queue_display_index].vir_addr), mDisplayWidth,mDisplayWidth,mDisplayHeight,frame->zoom_value,false,true,false); #endif } // ... }break;
上面標紅加粗部分就是不一樣類型的畫面解碼函數了。不過很惋惜的是,這些代碼都是閉源的。它們被封裝在下面這個庫裏
./device/rockchip/common/vpu/lib/libjpeghwenc.so
因此最終的解決辦法就是更換一個解碼函數便可。若是這上面全部的解碼函數都不能解決您的問題,那隻能辛苦一點,本身去外部找解碼函數來使用了。反正問題的根源就在這。
解決問題的時候切忌不可一看到問題立馬就撲上去看代碼、抓打印,這是屬於蠻幹型,常常會形成有勞無功的結果的。
正確的解決問題流程應該是像下面這樣子的
一、肯定問題
二、明確現象與復現方法
三、初步分析,肯定該問題背後的代碼組成。
四、進一步分析,梳理該問題背後涉及技術的類別,根據現象縮小分析範圍
五、開始分析
六、不斷思考與總結
咱們在解決一個問題的過程當中,極可能大多數時候咱們接觸代碼時間只佔不多一部分時間,大多數時間都應該用來做前期分析,甚至能夠簡單粗暴地套用 8020定律 來解釋。並且這種方式纔是真正高效率的方式。