Linux摄像头驱动的设计优化及其对应的Android下HAL封装设计方法探究
2016-11-09李宇成梁宗希
李宇成 梁宗希
(北方工业大学电气与控制工程学院 北京 100144)
Linux摄像头驱动的设计优化及其对应的Android下HAL封装设计方法探究
李宇成梁宗希*
(北方工业大学电气与控制工程学院北京 100144)
针对Android系统下摄像头驱动程序不开源的现状,将借鉴Linux下的相关驱动程序,提出一体化设计方案,对驱动加以改进设计和优化,特别是在FIMC驱动中,将FIMC和ov9650合成为了一个设备,显著提高了驱动的运行效率。然后,通过对Android框架的分析,设计一种HAL模块,采用三个线程并行完成摄像头功能,并用此模块对驱动进行封装。内容包括Linux摄像头驱动的开发与优化,Android HAL模块的设计,以及将Linux摄像头驱动程序进行HAL封装的具体步骤。最后,给出实验结果。
Linux摄像头驱动设计与优化Android HAL设计HAL封装方法
0 引 言
虽然在Linux下有一些摄像头驱动的资料,但是这些驱动都不能够直接应用到Android系统下。导致这种现象的原因是:(1) Linux系统结构与Android系统本身有很大区别;(2) Android框架底层有HAL(硬件抽象层),它将摄像头驱动与Android系统隔离。因而,只有将Linux摄像头驱动进行重新设计和优化,方能在Android系统下使用。而适用于Android系统的摄像头驱动的设计与优化方法,鲜见有文献涉及。
另一方面,随着Android系统的推广,手机、移动设备以及各种智能终端大量采用Android作为操作系统[1]。在Android系统中,与硬件相关的驱动程序大都以HAL形式封装起来了,目的是保护软件开发商的利益。例如,HAL中的Camera HAL,是联系上层摄像头接口与下层Linux摄像头驱动的纽带[2],它的设计好坏,直接影响到摄像头功能的发挥[3]。目前,很多Android硬件厂商如三星、高通等都有自己的Camera HAL,别人无法获得他们的源代码,这就使得普通开发者无从研究HAL,给在Android下提高摄像头效能和进一步开发摄像头程序带来了很大的困难。
鉴于上述现状,很有必要研究适用于Android系统的Linux摄像头驱动设计与优化方法以及Android Camera HAL的封装方法,以便设计者在此基础之上开发自己的应用程序。本文首先借鉴Linux资料,提取Linux下的摄像头驱动的部分代码加以补充设计和优化,然后根据对Android架构的分析,设计了一个s5pv210 Camera HAL模块,并在模块内实现了一种Camera HAL的封装方法。为在Android系统中利用和获取Linux下的巨大开源资源提供了一个范例。
1 Linux摄像头模块设计与优化
Linux下摄像头模块主要包括四个方面[4-6]:V4L2驱动,FIMC模块,摄像头驱动以及i2c驱动。本文摄像头模块设计架构如图1所示,FIMC模块调用摄像头驱动并为V4L2提供接口,摄像头驱动获取视频数据并传给FIMC,i2c驱动完成摄像头与开发板之间的通信。
图1 Linux摄像头驱动设计框架
1.1Linux摄像头模块与Android Camera HAL的一体化设计
Linux为建立视频设备工作环境,需要借助V4L2架构,建立struct video_device,而struct video_device的控制函数struct v4l2_ioctl_ops和文件操作函数struct v4l2_file_operations等由FIMC驱动实现。本文建议的一体化设计,是将struct v4l2_ioctl_ops和struct v4l2_file_operations的成员函数videoc_reqbufs(),videoc_querybuf(),open(),release()等封装进V4L2驱动核心,再由Android HAL对V4L2进行封装。Android上层应用程序访问Camera HAL时,根据本设计,会调用到V4L2,进而调用摄像头驱动。
根据Linux驱动原理,V4L2为调用摄像头驱动提供统一接口,本设计中将V4L2直接嵌入HAL,使Linux摄像头驱动与Android HAL联为一体。同时,V4L2与HAL之间并未额外添加新的结构层,而是直接把V4L2嵌入到HAL之中,这使整个摄像头模块获得了一个较高的运行效率。摄像头驱动与Android Camera HAL一体化设计方案如图2所示。
图2 Linux摄像头模块与Android Camera HAL一体化方案
1.2i2c驱动设计
摄像头的工作方式需要通过i2c总线来进行设置,因此,本质上可以认为摄像头是一个i2c设备,而在i2c驱动当中,最重要的结构体是i2c_client和i2c_adapter,前者表示一个i2c从器件,后者代表i2c总线控制器和访问i2c总线必须的算法。
图3所示的驱动设计框图是整个i2c设备数据结构的信息。设备i2c_client,最初是被i2c_new_device()所创建的,并被device_register()在内部调用,挂载到i2c_bus上。挂载上之后,相应的设备文件会在/sys/bus/i2c文件夹下生成。如果设备名称和ov9650驱动匹配成功,对应的probe()函数会被调用,而且系统会为图3所示的ov9650结构分配内存,并且初始化各种数据变量。
图3 SCCB驱动设计
1.3FIMC驱动设计与优化
FIMC主要用于实现视频数据接口,同时,它也完成图像数据颜色空间的转换操作。由图4可知,左边FIMC驱动的fimc_vid_cap包含了video_device,并且实现了控制函数v4l2_ioctl_ops与文件操作函数v4l2_file_operations等,video_device由图2中的V4L2驱动进行调用。
图4 优化后的fimc驱动架构
在Linux下,fimc与ov9650是两个独立的设备模块,各自拥有不能互访的内存空间。通常需要映射一块共享内存来完成两者之间的数据交换,其好处是,保证了两个驱动的相互独立性,缺点是信息交换不如在同一设备内部来的方便。一种简单的技巧是,在fimc_dev所属的media_entity以及ov9650所属的media_entity之间添加pads和list_head变量,使它们同时都挂载在了图4右侧的media_device设备上,当FIMC和ov9650中的media_entity都被初始化之后,fimc_md结构体中的fimc_md_probe()函数将把fimc与ov9650模块绑定在一起,初始化之后,FIMC和ov9650就合成为一个大的设备。由此,这两个模块就能进行无内外差别的数据完全共享。由于两个设备之间的数据访问同步机制要比同一设备内(抽象的大设备)访问的同步机制复杂,因此采用合成大设备的技巧能略微提高数据交换效率,在本文的实验环境下,优化后的驱动给摄像头模块的帧率带来了2-3FPS的提升。
完成Linux下驱动的优化设计之后,需要进行Android下HAL模块的设计与实现,HAL模块没有统一公开的设计方法。本文通过对Android框架的仔细研究,设计了s5pv210 HAL模块,并在其中采用自定义函数来封装V4L2接口,实现Android对Linux驱动的调用。
2 Camera HAL的设计与实现
Android Camera系统使用Linux摄像头模块需要Camera HAL[7-9],本文在Android Camera系统的基础上,首先设计s5pv210 HAL模块,并在其内部嵌入V4L2接口函数。s5pv210 HAL用于封装V4L2接口的函数如图5所示,其中,startPreview()和stopPreview()用于控制预览的启/停,startRecording()与stopRecording()用于控制视频的启/停,takePicture()与cancelPicture()用于拍照和删除照片,其他内容如分配内存,摄像头参数设置等,都需要用相应的封装函数来实现。
图5 s5pv210 HAL中定义的函数
本文设计的Camera HAL,根据摄像头的功能需求实现了三个线程。
1) 预览线程:预览是拍照和摄像的基础。
2) 拍照线程:该线程需要实现图像格式数据转换。
3) 摄像线程:屏幕显示可以调用预览线程,同时,要进行格式转换并在本地保存视频文件。
S5pv210 HAL的总体设计框图如图6所示,s5pv210 HAL的框架由预览、拍照、摄像三个部分组成,各部分封装的过程类似。图7中预览模块的startpreview()接口函数,其中封装的是Linux下编写的V4L2完成摄像头预览的过程。而图5中的startRecording()接口函数,则属于摄像模块,其中封装的是调用V4L2完成摄像的过程。与预览过程不同的是,摄像的过程包含了格式转换与本地文件的保存功能。各个模块虽然功能有差异,但HAL封装方法相同。下面以预览模式为例说明本文HAL的函数封装方法。
图6 s5pv210 hal总体设计
事实上,开始预览之前,上层应用在做完准备工作(设置各种参数)之后,仅仅会调用android::startpreview()这一个函数,随后经过层层调用,最终调用到s5pv210 HAL模块中的函数s5pv210 hal::startpreview()。因此,HAL预览模块要做的工作,就是把从设置V4L2到获得视频帧地址的所有过程都封装到s5pv210 HAL的startpreview()函数之中,如图7所示。
图7 hal预览模块startpreview()函数封装
本设计中,s5pv210 HAL预览模块所包含的具体流程有:1) 打开视频设备、设置视频格式;2) 分配内存、读取数据;3) 数据回调。从程序的健壮性考虑,上述步骤不应直接整体封装到startpreview()函数之中,而应该分成多个自定义函数,最后再把这些函数嵌入进预览模块startpreview()中。
下面以OV9650为例,来实现预览模块的三个部分。
(1) 打开视频设备、设置视频格式的实现
打开视频设备使用的是Linux驱动下的v4l2接口中的open()函数,此函数会调用fimc驱动中的open()函数的实现体,最终实现设备文件的打开,视频格式的设置使用ioctl()函数,该函数经过V4L2,fimc最终调用摄像头驱动,通过i2c对摄像头进行参数设置。
void Ov965xCamera::init() {
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer b;
captureBuf =
(videobuffer*)calloc(1,sizeof(videobuffer));
//分配缓存
enum v4l2_buf_type type =
V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fd =
open(″/dev/video0″, O_RDWR | O_NONBLOCK);
//打开视频设备
……
}
这是嵌入到预览模块startpreview()函数内的一个自定义函数,其中声明了V4L2视频格式变量,内存变量等,并打开了摄像头设备文件,得到了文件句柄。
其中,省略的部分里还包含设置视频格式,检查V4L2兼容性等其他操作。设置视频的长、宽和格式的方法如下所示。
if(v4l2_fd != -1){
memset(&fmt, 0, sizeof fmt);
//设置摄像头输出参数
fmt.type =
V4L2_BUF_TYPE_VIDEO_CAPTURE;
//设置v4l2模式
fmt.fmt.pix.width = mWidth;
//设置宽
fmt.fmt.pix.height = mHeight;
//设置高
fmt.fmt.pix.pixelformat =
V4L2_PIX_FMT_RGB565;
//设置视频格式
fmt.fmt.pix.sizeimage =
(fmt.fmt.pix.width * fmt.fmt.pix.height * 2);
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt) < 0) {
LOGE(″cannot set fmts ″);
return;
}
……
}
(2) 分配内存、读取数据的实现
进程之间传递大量数据时,一般使用共享内存的方式。在Android Binder IPC中,Android系统实现了一个匿名共享内存子系统。这种Android特有的进程间共享内存的方式避免了内存拷贝,代之以在进程间传递内存基址和偏移值。
在Android的应用程序框架层,提供了两个C++类MemoryBase和MemoryHeapBase来使用匿名共享内存。下述代码段的initheap()函数,是本文封装到预览模块的另一个自定义函数,用于建立程序的缓存。initheap()函数首先创建堆内存MemoryHeapBase,然后创建kBufferCount块分割内存,MemoryBase将堆内存分割,每一块用来存储一帧视频。开辟好内存,就可以读取,处理数据了。
void CameraHardware::initHeap(){
//开辟Android下内存空间
int preview_width, preview_height;
mParameters.getPreviewSize
int how_big =
preview_width * preview_height*2;
//rgb565
mPreviewFrameSize = how_big;
mPreviewHeap = new MemoryHeapBase
(mPreviewFrameSize * kBufferCount);
for (int i = 0; i < kBufferCount; i++) {
//开辟4块缓存
mBuffers[i] =
new MemoryBase(mPreviewHeap, i *
}
mOv9650Camera = new Ov9650Camera
(preview_width, preview_height);
}
读取数据的过程被封装到如下自定义函数Ov965x Camera::getNextFrameAsRgb565()中,该函数通过调用V4L2的接口,采用标准的V4L2获取数据的方式获取数据,如下:
void Ov965xCamera::getNextFrameAsRgb565(uint16_t *buffer) {
struct v4l2_buffer camb;
memset(&camb, 0, sizeof camb);
camb.type =
V4L2_BUF_TYPE_VIDEO_CAPTURE;
camb.memory = V4L2_MEMORY_MMAP;
camb.index = 0;
ioctl(v4l2_fd, VIDIOC_DQBUF, &camb);
memcpy(buffer,(uint16_t *)
captureBuf[camb.index].ldata,mWidth*mHeight*2);
//把数据从内核空间复制到用户空间
ioctl (v4l2_fd, VIDIOC_QBUF, &camb);
}
(3) 数据回调的实现
在startpreview()函数中,还包含一部分功能语句。例如,上述读取完成之后,直接将帧数据的内存地址传给系统的回调函数mDataCb(),并由上层service对数据进行处理。如:
if (mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME)
mDataCb(CAMERA_MSG_PREVIEW_FRAME
//系统数据回调
这时,回调函数会通知cameraservice预览准备工作已做好,接下来cameraservice会把数据帧送到屏幕显示。
由此可见,预览模式的封装过程是,从Linux驱动中,找出V4L2关于摄像头初始化以及其它操作的接口,同时添加必要的功能语句,然后将其嵌入到s5pv210 HAL模块的预览函数中,最后,在函数startpreview()中完成向cameraservice传送数据的功能。
拍照模块和录像模块与预览模块类似,拍照模块需要编写图片格式的数据头,录像模块需要编写视频的格式转换与本地文件的保存,二者的HAL封装过程都与预览模式类似。封装完成之后,编译HAL成.so库的形式,放在Android的文件系统的system/lib目录下即可[10]。
3 实验结果
测试选用的是友善的tiny210开发板,处理器芯片是三星的s5pv210,Android版本为2.3,根文件系统采用nfs挂载的方式,把HAL的.so库放在nfs的根文件目录中,开机打开摄像头设备,驱动加载如图8所示。
图8 Linux摄像头驱动加载
在eclipse调试中可查看HAL载入情况。
运行Android的摄像头应用程序,使之处在预览模式当中,应用程序作为客户端会向服务端发送消息,服务端获得消息后经过层层调用,最终调用到HAL封装中的预览线程。预览线程内封装了对V4L2进行初始化的各种函数,这样,就把Android上层应用与Linux下层摄像头驱动连接了起来。实验效果如图9所示。
图9 ov9650预览效果(视频分辨率800×480)
4 结 语
本文首先在Linux系统下设计实现了一款适用于Android系统的ov9650摄像头驱动,并优化了FIMC驱动使之与摄像头驱动成为一个整体,提高了二者之间的数据传输效率。然后又重点探究了与该摄像头驱动对应的Android下的Camera HAL的封装方法,详细讲述了Camera HAL的封装过程。针对Android系统下摄像头驱动程序不开源的现状,本文具体示范了将Linux下的开源驱动程序封装到Android下的方法,供开发者参考。最后实验结果证明,该方法正确可靠,根据方法设计的HAL能够满足摄像头正常工作。
[1] 温敏,艾丽蓉,王志国.Android智能手机系统中文件实时监控的研究与实现[J].科学技术与工程,2009, 9(7):1716-1719.
[2] 杨长刚.深入剖析Android系统[M].北京:电子工业出版社,2013.
[3] James Talbot,Justin McLean.Learning Android Application Programming:A Hands-On Guide to Building Android Applications[M].Addison-Wesley Professional,2013.
[4] 李宇成,黄堂猛.基于S5PV210的超高清视频系统设计[J].计算机工程与设计,2014,35(11):3813-3819.
[5] Greg Milette,Adam Stroud.Professional Android Sensor Programming[M].Wrox Press,2012.
[6] 童方圆,于强.基于Android的实时视频流传输系统[J].计算机工程与设计,2012,33(12):4639-4642.
[7] 罗升阳.Android系统源代码情景分析[M].北京:电子工业出版社,2012.
[8] 余成锋,李代平,毛永华.Android 3.0内存管理机制分析[J].计算机应用与软件,2013,30(2):229-230.
[9] 李宇成,李聪.基于DM368的视频处理及软件设计[J].计算机测量与控制,2013,21(10):2865-2867,2871.
[10] 农丽萍,王力虎,黄一平.Android在嵌入式车载导航系统的应用研究[J].计算机工程与设计,2010,31(11):2473-2476.
DESIGN AND OPTIMISATION OF LINUX CAMERA DRIVER AND PROBING CORRESPONDING METHOD OF HAL ENCAPSULATION AND DESIGN UNDER ANDROID
Li YuchengLiang Zongxi*
(College of Electrical and Control Engineering,North China University of Technology,Beijing 100144,China)
For current status of camera driver in Android system not being the open-source, by learning form the related driver in Linux, we proposed an integrated design scheme to improve the design of driver and optimise it as well, especially in FIMC driver, we composed the FIMC and ov9650 into a single device, this significantly improved the operation efficiency of camera driver. After that, by analysing Android framework, we designed a HAL module, which completes the functions of camera with three threads in parallel, we also used it to encapsulate Linux camera driver. The content of the paper includes the design and optimisation of Linux camera driver, the design of Android HAL module, and the specific procedure of encapsulating the Linux camera driver with HAL. In end of the paper we submit the experimental result.
Linux camera driver design and optimisationAndroid HAL designEncapsulation method with HAL
2015-03-18。李宇成,教授,主研领域:智能控制,图像处理,嵌入式开发。梁宗希,硕士。
TP311.5
A
10.3969/j.issn.1000-386x.2016.09.059