rk3128 适配 USB 摄像头

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定律 来解释。并且这种方式才是真正高效率的方式。

相关文章
相关标签/搜索