李宇成 梁宗希
(北方工業大學電氣與控制工程學院 北京 100144)
?
Linux攝像頭驅動的設計優化及其對應的Android下HAL封裝設計方法探究
李宇成梁宗希*
(北方工業大學電氣與控制工程學院北京 100144)
針對Android系統下攝像頭驅動程序不開源的現狀,將借鑒Linux下的相關驅動程序,提出一體化設計方案,對驅動加以改進設計和優化,特別是在FIMC驅動中,將FIMC和ov9650合成為了一個設備,顯著提高了驅動的運行效率。然后,通過對Android框架的分析,設計一種HAL模塊,采用三個線程并行完成攝像頭功能,并用此模塊對驅動進行封裝。內容包括Linux攝像頭驅動的開發與優化,Android HAL模塊的設計,以及將Linux攝像頭驅動程序進行HAL封裝的具體步驟。最后,給出實驗結果。
Linux攝像頭驅動設計與優化Android HAL設計HAL封裝方法
雖然在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下的巨大開源資源提供了一個范例。
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驅動的調用。
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]。
測試選用的是友善的tiny210開發板,處理器芯片是三星的s5pv210,Android版本為2.3,根文件系統采用nfs掛載的方式,把HAL的.so庫放在nfs的根文件目錄中,開機打開攝像頭設備,驅動加載如圖8所示。

圖8 Linux攝像頭驅動加載
在eclipse調試中可查看HAL載入情況。
運行Android的攝像頭應用程序,使之處在預覽模式當中,應用程序作為客戶端會向服務端發送消息,服務端獲得消息后經過層層調用,最終調用到HAL封裝中的預覽線程。預覽線程內封裝了對V4L2進行初始化的各種函數,這樣,就把Android上層應用與Linux下層攝像頭驅動連接了起來。實驗效果如圖9所示。

圖9 ov9650預覽效果(視頻分辨率800×480)
本文首先在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