Android照相功能驱动层中HAL的实现(基于OK6410开发板+OV9650摄像头)
摘要:@importurl(http://www.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@importurl(/css/cuteeditor.css);@importurl(http://www.cnblogs.com/Load.ashx?type=style&file=SyntaxHighligh...
Motivation
前些日子买了块飞凌OK6410的开发板+OV9650摄像头模块准备做Android应用开发。自己手里虽有现成的Android手机,但考虑到日后裁减硬件,不得不从最原始的开发板着手。但不知飞凌出于什么原因,没有完善Android的照相驱动,每次拍照后,返回的照片都是一张绿色的小机器人。之前没有写过Android的驱动,在飞凌的技术支持论坛提问也没得到什么帮助,尝试刷前几个飞凌提供的Android版本,都没有解决这个问题...看来要等官方完善得有些时日了。与其指望他人,还不如自己动手,于是着手开发了这个模块。
本文涉及到以下几个方面的内容:
Android模块编译
Android模块的板上加载及调试
AndroidCamera模块的改写
--------------------------------------------------------------------------------
Android模块编译
每次为了一个模块而编译整个Android系统是一个灾难(4个小时一次),这里会展示如何仅仅编译一个模块而节省大量的宝贵时间。网上多数的方法是通过执行envsetup.sh,接着运行mmm<directory>命令来编译一个文件夹下的模块,但在编译libcamera这个模块时一直没能成功,显示编译依赖于其他几个模块。这里介绍另一种方法,每个模块的文件夹下都必须有一个Android.mk文件,在其中有一项LOCAL_MODULE用于定义模块名称,以照相模块为例,即被定义为LOCAL_MODULE:=libcamera,记下这个模块名称,跳转到Android源码的根目录下,执行以下操作:
Step1.进入宿主机linux终端,输入以下命令:
<name>@<machine>:<folder>#source./build/envsetup.sh
<name>@<machine>:<folder>#choosecombo
执行效果如图:
Step2.选择Device->Release->键入OK6410->eng
Step3.输入make<libname>编译特定模块,如摄像头模块:
<name>@<machine>:<folder>#makelibcamera
执行效果如图:
编译完成效果图:
Step4.经过以上几个步骤后,摄像头模块就开始编译了,生成后的动态连接库文件(*.so)会存放在out/target/product/OK6410/system/lib/下,本文我们仅需要libcamera.so
我把上述步骤做成了一个shell脚本,每次修改照相模块的HAL后会自动编译,并将更新后的libcamera.so拷贝到Android源码根目录下,如果愿意,也可以自行修改脚本将libcamera.so拷贝到SD中。
附件下载:
makelibcamera.zip
--------------------------------------------------------------------------------
Android模块的板上加载及调试
libcamera.so已经生成了,那怎么调试呢?一种办法是加载到模拟的Android系统中,但这种方法对于硬件调试往往行不通,那剩下的方法就是板上调试了。如果板子已经能够和PC进行adb连接,那就用adbpush把libcamera.so推到目标机/system/lib/中。但可能是OK6410USB接口设计的问题,与MacOSX总是无法建立起连接,于是每次我只能通过SD卡进行中转...手动从SD卡上把照相模块cp到lib目录下,然后reboot。
嵌入式开发比起应用开发,其开发环境往往要恶劣许多。就拿调试而言,往往要通过代码中插入类printf的语句来查看运行状态。android中提供了一个很好的工具logcat,在用户空间中,通过LOGV(Verbose),LOGE(Error),LOGD(Debug)等提供类似printf的功能。假定在程序中#defineLOG_TAG"CameraHardware",那通过如LOGE("%s,HelloWorld!",LOG_TAG)就可以记录在系统日志中。系统日志杂乱繁多,要查看特定的日志就要限定范围,在目标机上定义ANDROID_LOG_TAGS环境变量就可以通过logcat-d来查看CameraHardware的“错误”日志了:
exportANDROID_LOG_TAGS="CameraHardware:E*:S"
logcat-d
目标机和宿主机相连后,通过超级终端来执行以上命令后的结果:
--------------------------------------------------------------------------------
AndroidCamera模块的改写
这是本文的重点,展示如何在驱动层实现拍照功能。通过查看飞凌的Android源代码会发现,其OV9650和USB摄像头HAL的实现就是AndroidFakeCamera的改写,前者位于<androidrootfolder>/hardware/forlinx/libcamera,后者位于<androidrootfolder>/frameworks/base/services/camera/libcameraservice中。在Android中,OV9650已经有了基本的预览功能,这证明至少Preview函数已经完善,我们就从preview功能切入,来分析它的实现。打开libcamera下的S3C6410CameraHardware.cpp,在initDefaultParapeters方法中,可以看到preview的格式是RGB565,是一种常用于TFT显示的格式。原FakeCamera中是YUV420SP(从Ov965xCamera.cpp中看得出,飞凌尝试过用YUV420SP但可能失败了):
voidCameraHardware::initDefaultParameters()
{
CameraParametersp;
p.set(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES,"320x240");
p.setPreviewSize(320,240);
p.setPreviewFrameRate(15);
//p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_YUV420SP);
p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_RGB565);
p.set(CameraParameters::KEY_ROTATION,0);//90
//其余代码省略
}
}
现在让我们看看Preview功能的实现,也许可以给我们启发,我们不难注意到previewThread方法,其中mPreviewHeap存储着n个帧的缓冲,这块区域被分割为n个mBuffers。buffer为当前帧的引用,通过mDataCb(CAMERA_MSG_PREVIEW_FRAME,buffer,mCallbackCookie)就可以将buffer输出到屏幕。那每一个帧是怎么存到mPreviewHeap上的呢?关键的一句就是Ov965xCamera->getNextFrameAsRgb565((uint16_t*)frame),通过看它的实现可以知道(在Ov96xCamera.app中),一个帧的数据以16位的格式写入frame中,这里的frame即是对mPreviewHeap上某个mBuffer的引用:
intCameraHardware::previewThread()
{
mLock.lock();
//theattributesbelowcanchangeunderourfeet...
intpreviewFrameRate=mParameters.getPreviewFrameRate();
//Findtheoffsetwithintheheapofthecurrentbuffer.
ssize_toffset=mCurrentPreviewFrame*mPreviewFrameSize;
sp<MemoryHeapBase>heap=mPreviewHeap;
//thisassumestheinternalstateoffakecameradoesn'tchange
//(oristhreadsafe)
Ov965xCamera*Ov965xCamera=mOv965xCamera;
USBCamera*USBCamera=mUSBCamera;
sp<MemoryBase>buffer=mBuffers[mCurrentPreviewFrame];
mLock.unlock();
//TODO:herecheckalltheconditionsthatcouldgowrong
if(buffer!=0){
//Calculatehowlongtowaitbetweenframes.
intdelay=(int)(1000000.0f/float(previewFrameRate));
//Thisisalwaysvalid,eveniftheclientdied--thememory
//isstillmappedinourprocess.
void*base=heap->base();
//Fillthecurrentframewiththefakecamera.
uint8_t*frame=((uint8_t*)base)+offset;
//Ov965xCamera->getNextFrameAsYuv420(frame);
if(mCamType==CAMTYPE_CMOS)
Ov965xCamera->getNextFrameAsRgb565((uint16_t*)frame);//获取一个帧的数据,放入frame
elseif(mCamType==CAMTYPE_USB)
USBCamera->getNextFrameAsRgb565((uint16_t*)frame);
//LOGV("previewThread:generatedframetobuffer%d",mCurrentPreviewFrame);
//Notifytheclientofanewframe.
if(mMsgEnabled&CAMERA_MSG_PREVIEW_FRAME)
mDataCb(CAMERA_MSG_PREVIEW_FRAME,buffer,mCallbackCookie);
//Advancethebufferpointer.
mCurrentPreviewFrame=(mCurrentPreviewFrame+1)%kBufferCount;
//Waitforit...
usleep(delay);
}
returnNO_ERROR;
}
这样分析过后,问题就变得很明了了,要将图片存储下来,只要获取其一帧数据(getNextFrameAsRGB565),在takePicture函数中将其存储下来即可。好,让我们看看takePicture的实现:它启动了一个线程来调用pictureThread方法,这里就是我们大显身手的地方了!
intCameraHardware::pictureThread()
{
if(mMsgEnabled&CAMERA_MSG_SHUTTER)
mNotifyCb(CAMERA_MSG_SHUTTER,0,0,mCallbackCookie);//对应ShutterCallback
if(mMsgEnabled&CAMERA_MSG_RAW_IMAGE){
mDataCb(CAMERA_MSG_RAW_IMAGE,mem,mCallbackCookie);//对应原始图片(RAW)的PictureCallback
}
if(mMsgEnabled&CAMERA_MSG_COMPRESSED_IMAGE){
mDataCb(CAMERA_MSG_COMPRESSED_IMAGE,mem,mCallbackCookie);//对应JPEG图片的PictureCallback
}
returnNO_ERROR;
}
每个if都对应了一个takePicture函数的callback,第二第三个就是图片要输出的地方!Android上的照相应用程序并不管RAW图片的输出,我们直接聚焦到第三个if。这里我们就不难理解为什么老是输出“小机器人”了,原来在第三个if中,飞凌并没有改原FakeCamera的代码,FakeCamera在这里直接读入一个CannedJpeg.h中的数据,而这里面存的就是那个“可爱”的机器人....好,那我们就改这里!首先先不管RAW到JPEG的转换,我们把RAW的数据直接写成BMP格式输出,看看是否工作。BMP格式文件头有54个字节,16位的数据格式为RGB555,所以完成的流程就三步:
Step1.在MemoryHeap上申请一块BMP数据暂存区,并写文件头
Step2.将原始数据从RGB565转换到RGB555,并存储到BMP数据暂存区
Step3.将BMP暂存区数据传递给mDataCb输出
具体代码如下:
Step1.首先申请BMP暂存区:
intw,h;
unsignedintDATA_OFFSET=54;
uint16_tWIDTH=w;
uint16_tHEIGHT=h;
mParameters.getPictureSize(&w,&h);
Ov965xCamera*Ov965xCamera=mOv965xCamera;
sp<MemoryHeapBase>heap=newMemoryHeapBase(DATA_OFFSET+w*h*2);
sp<MemoryBase>mem=newMemoryBase(heap,0,DATA_OFFSET+w*h*2);//2个字节构成一个像素
写BMP文件头:
uint8_theader[54]={0x42,//identity:B
0x4d,//identity:M
0,0,0,0,//filesize
0,0,//reserved1
0,0,//reserved2
54,0,0,0,//RGBdataoffset
40,0,0,0,//structBITMAPINFOHEADERsize
0,0,0,0,//bmpheight
0,0,0,0,//bmpwidth
1,0,//planes
16,0,//bitperpixel
0,0,0,0,//compression
0,0,0,0,//datasize
0,0,0,0,//hresolution
0,0,0,0,//vresolution
0,0,0,0,//usedcolors
0,0,0,0//importantcolors
};
//filesizeoffset54
uint16_tfile_size=WIDTH*HEIGHT*2+DATA_OFFSET;
header[2]=(uint8_t)(file_size&0x000000ff);
header[3]=(file_size>>&0x000000ff;
header[4]=(file_size>>16)&0x000000ff;
header[5]=(file_size>>24)&0x000000ff;
//height
header[18]=HEIGHT&0x000000ff;
header[19]=(HEIGHT>>&0x000000ff;
header[20]=(HEIGHT>>16)&0x000000ff;
header[21]=(HEIGHT>>24)&0x000000ff;
//width
header[22]=WIDTH&0x000000ff;
header[23]=(WIDTH>>&0x000000ff;
header[24]=(WIDTH>>16)&0x000000ff;
header[25]=(WIDTH>>24)&0x000000ff;
Step2.获取当前帧,进行RGB565到RGB555的转换,将转换后的数据放入MemoryHeap中
unsignedinti;
for(i=0;i<DATA_OFFSET;i++){
*((uint8_t*)heap->base()+i)=header[i];
}
Ov965xCamera->getNextFrameAsRgb565((uint16_t*)heap->base()+DATA_OFFSET/2);
uint16_t*heap_base=(uint16_t*)heap->base();
uint16_tpixel_data;
uint8_ttail_data;
for(i=DATA_OFFSET/2;i<DATA_OFFSET/2+WIDTH*HEIGHT;i++){
pixel_data=*(heap_base+i);
tail_data=(uint8_t)(pixel_data&0x001f);
pixel_data=(pixel_data&0xffc0)>>1|tail_data;
*(heap_base+i)=pixel_data;
}
Step3.调用callback,将数据存储到设备,并释放MemoryHeap
mDataCb(CAMERA_MSG_COMPRESSED_IMAGE,mem,mCallbackCookie);
heap=NULL;
到这里,OK6410的Android系统就真正可以拍照了,通过之前介绍编译的方法,将编译好的libcamera.so放入目标机的/system/lib中,重启就能看到效果了,这是我用OV9650拍的照片,在PC上查看建议把jpg后缀名改为bmp,在Android上查看没任何问题:
附件下载:
libcamera.so和相关源代码
--------------------------------------------------------------------------------
待解决的问题:
1.目前输出的文件虽然是jpg后缀,但实际是BMP格式的,想法是使用external/jpeg库中的函数来解决。
2.目前输出为320*240,若要使用更大分辨率,势必要更大的MemoryHeap。比如需要1024*768*2字节的空间,虽然可以申请,但根本无法访问到后面的空间,目前还没想到解决方案。
希望大家能一起把以上这两个问题解决,这样我们的开发板就能在图像应用上有用武之地了!
另:转文请注明出处,谢谢!
References:
AndroidCameraHal的初步实现1(http://blog.csdn.net/flyingpipi/article/details/5773666)
Android硬件抽象层(HAL)概要介绍和学习计划(http://blog.csdn.net/luoshengyang/article/details/6567257)
真OO无双之真乱舞书(http://www.cnblogs.com/oomusou/)