Motivation前些日子買了塊飛凌OK6410的開發(fā)板+OV9650攝像頭模塊準(zhǔn)備做Android應(yīng)用開發(fā)。自己手里雖有現(xiàn)成的Android手機(jī),但考慮到日后裁減硬件,不得不從最原始的開發(fā)板著手。但不知飛凌出于什么原因,沒有完善Android的照相驅(qū)動(dòng),每次拍照后,返回的照片都是一張綠色的小機(jī)器人。之前沒有寫過Android的驅(qū)動(dòng),在飛凌的技術(shù)支持論壇提問也沒得到什么幫助,嘗試刷前幾個(gè)飛凌提供的Android版本,都沒有解決這個(gè)問題...看來要等官方完善得有些時(shí)日了。與其指望他人,還不如自己動(dòng)手,于是著手開發(fā)了這個(gè)模塊。 本文涉及到以下幾個(gè)方面的內(nèi)容:
Android 模塊編譯每次為了一個(gè)模塊而編譯整個(gè)Android系統(tǒng)是一個(gè)災(zāi)難(4個(gè)小時(shí)一次),這里會(huì)展示如何僅僅編譯一個(gè)模塊而節(jié)省大量的寶貴時(shí)間。網(wǎng)上多數(shù)的方法是通過執(zhí)行envsetup.sh,接著運(yùn)行mmm <directory>命令來編譯一個(gè)文件夾下的模塊,但在編譯libcamera這個(gè)模塊時(shí)一直沒能成功,顯示編譯依賴于其他幾個(gè)模塊。這里介紹另一種方法,每個(gè)模塊的文件夾下都必須有一個(gè)Android.mk文件,在其中有一項(xiàng)LOCAL_MODULE用于定義模塊名稱,以照相模塊為例,即被定義為L(zhǎng)OCAL_MODULE:=libcamera,記下這個(gè)模塊名稱,跳轉(zhuǎn)到Android源碼的根目錄下,執(zhí)行以下操作: Step 1. 進(jìn)入宿主機(jī)linux終端,輸入以下命令:
<name>@<machine>:<folder>#source ./build/envsetup.sh
<name>@<machine>:<folder>#choosecombo
執(zhí)行效果如圖:
Step 2. 選擇Device->Release->鍵入OK6410->eng Step 3. 輸入make <libname>編譯特定模塊,如攝像頭模塊:
<name>@<machine>:<folder>#make libcamera
執(zhí)行效果如圖:
編譯完成效果圖:
Step 4. 經(jīng)過以上幾個(gè)步驟后,攝像頭模塊就開始編譯了,生成后的動(dòng)態(tài)連接庫文件(*.so)會(huì)存放在out/target/product/OK6410/system/lib/下,本文我們僅需要libcamera.so 我把上述步驟做成了一個(gè)shell腳本,每次修改照相模塊的HAL后會(huì)自動(dòng)編譯,并將更新后的libcamera.so拷貝到Android源碼根目錄下,如果愿意,也可以自行修改腳本將libcamera.so拷貝到SD中。 附件下載: Android 模塊的板上加載及調(diào)試libcamera.so已經(jīng)生成了,那怎么調(diào)試呢?一種辦法是加載到模擬的Android系統(tǒng)中,但這種方法對(duì)于硬件調(diào)試往往行不通,那剩下的方法就是板上調(diào)試了。如果板子已經(jīng)能夠和PC進(jìn)行adb連接,那就用adb push把libcamera.so推到目標(biāo)機(jī)/system/lib/中。但可能是OK6410 USB接口設(shè)計(jì)的問題,與MacOSX總是無法建立起連接,于是每次我只能通過SD卡進(jìn)行中轉(zhuǎn)...手動(dòng)從SD卡上把照相模塊cp到lib目錄下,然后reboot。 嵌入式開發(fā)比起應(yīng)用開發(fā),其開發(fā)環(huán)境往往要惡劣許多。就拿調(diào)試而言,往往要通過代碼中插入類printf的語句來查看運(yùn)行狀態(tài)。android中提供了一個(gè)很好的工具logcat,在用戶空間中,通過LOGV(Verbose),LOGE(Error),LOGD(Debug)等提供類似printf的功能。假定在程序中#define LOG_TAG "CameraHardware",那通過如LOGE("%s, Hello World!", LOG_TAG)就可以記錄在系統(tǒng)日志中。系統(tǒng)日志雜亂繁多,要查看特定的日志就要限定范圍,在目標(biāo)機(jī)上定義ANDROID_LOG_TAGS環(huán)境變量就可以通過logcat -d來查看CameraHardware的“錯(cuò)誤”日志了:
export ANDROID_LOG_TAGS="CameraHardware:E *:S"
logcat -d
目標(biāo)機(jī)和宿主機(jī)相連后,通過超級(jí)終端來執(zhí)行以上命令后的結(jié)果: Android Camera 模塊的改寫這是本文的重點(diǎn),展示如何在驅(qū)動(dòng)層實(shí)現(xiàn)拍照功能。通過查看飛凌的Android源代碼會(huì)發(fā)現(xiàn),其OV9650和USB攝像頭HAL的實(shí)現(xiàn)就是Android Fake Camera的改寫,前者位于<android root folder>/hardware/forlinx/libcamera,后者位于<android root folder>/frameworks/base/services/camera/libcameraservice中。在Android中,OV9650已經(jīng)有了基本的預(yù)覽功能,這證明至少Preview函數(shù)已經(jīng)完善,我們就從preview功能切入,來分析它的實(shí)現(xiàn)。打開libcamera下的S3C6410CameraHardware.cpp,在initDefaultParapeters方法中,可以看到preview的格式是RGB565,是一種常用于TFT顯示的格式。原FakeCamera中是YUV420SP(從Ov965xCamera.cpp中看得出,飛凌嘗試過用YUV420SP但可能失敗了):
void CameraHardware::initDefaultParameters()
{ CameraParameters p; 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 // 其余代碼省略 } } 現(xiàn)在讓我們看看Preview功能的實(shí)現(xiàn),也許可以給我們啟發(fā),我們不難注意到previewThread方法,其中mPreviewHeap存儲(chǔ)著n個(gè)幀的緩沖,這塊區(qū)域被分割為n個(gè)mBuffers。buffer為當(dāng)前幀的引用,通過mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie)就可以將buffer輸出到屏幕。那每一個(gè)幀是怎么存到mPreviewHeap上的呢?關(guān)鍵的一句就是Ov965xCamera->getNextFrameAsRgb565((uint16_t *)frame) ,通過看它的實(shí)現(xiàn)可以知道(在Ov96xCamera.app中),一個(gè)幀的數(shù)據(jù)以16位的格式寫入frame中,這里的frame即是對(duì)mPreviewHeap上某個(gè)mBuffer的引用: int CameraHardware::previewThread()
{ 這樣分析過后,問題就變得很明了了,要將圖片存儲(chǔ)下來,只要獲取其一幀數(shù)據(jù)(getNextFrameAsRGB565),在takePicture函數(shù)中將其存儲(chǔ)下來即可。好,讓我們看看takePicture的實(shí)現(xiàn):它啟動(dòng)了一個(gè)線程來調(diào)用pictureThread方法,這里就是我們大顯身手的地方了! int CameraHardware::pictureThread()
{ if (mMsgEnabled & CAMERA_MSG_SHUTTER) mNotifyCb(CAMERA_MSG_SHUTTER, 0, 0, mCallbackCookie); // 對(duì)應(yīng)ShutterCallback if (mMsgEnabled & CAMERA_MSG_RAW_IMAGE) { mDataCb(CAMERA_MSG_RAW_IMAGE, mem, mCallbackCookie); // 對(duì)應(yīng)原始圖片(RAW)的PictureCallback } if (mMsgEnabled & CAMERA_MSG_COMPRESSED_IMAGE) { mDataCb(CAMERA_MSG_COMPRESSED_IMAGE, mem, mCallbackCookie); // 對(duì)應(yīng)JPEG圖片的PictureCallback } return NO_ERROR; } 每個(gè)if都對(duì)應(yīng)了一個(gè)takePicture函數(shù)的callback,第二第三個(gè)就是圖片要輸出的地方!Android上的照相應(yīng)用程序并不管RAW圖片的輸出,我們直接聚焦到第三個(gè)if。這里我們就不難理解為什么老是輸出“小機(jī)器人”了,原來在第三個(gè)if中,飛凌并沒有改原FakeCamera的代碼,F(xiàn)akeCamera在這里直接讀入一個(gè)CannedJpeg.h中的數(shù)據(jù),而這里面存的就是那個(gè)“可愛”的機(jī)器人....好,那我們就改這里!首先先不管RAW到JPEG的轉(zhuǎn)換,我們把RAW的數(shù)據(jù)直接寫成BMP格式輸出,看看是否工作。BMP格式文件頭有54個(gè)字節(jié),16位的數(shù)據(jù)格式為RGB555,所以完成的流程就三步: Step1. 在MemoryHeap上申請(qǐng)一塊BMP數(shù)據(jù)暫存區(qū),并寫文件頭 Step2. 將原始數(shù)據(jù)從RGB565轉(zhuǎn)換到RGB555,并存儲(chǔ)到BMP數(shù)據(jù)暫存區(qū) Step3. 將BMP暫存區(qū)數(shù)據(jù)傳遞給mDataCb輸出
具體代碼如下: Step1. 首先申請(qǐng)BMP暫存區(qū): int w, h;
unsigned int DATA_OFFSET = 54; uint16_t WIDTH = w; uint16_t HEIGHT = h; mParameters.getPictureSize(&w, &h); Ov965xCamera* Ov965xCamera = mOv965xCamera; sp<MemoryHeapBase> heap = new MemoryHeapBase(DATA_OFFSET+w * h* 2); sp<MemoryBase> mem = new MemoryBase(heap, 0, DATA_OFFSET+w * h* 2); // 2個(gè)字節(jié)構(gòu)成一個(gè)像素
寫B(tài)MP文件頭: uint8_t header[54] = { 0x42, // identity : B
0x4d, // identity : M 0, 0, 0, 0, // file size 0, 0, // reserved1 0, 0, // reserved2 54, 0, 0, 0, // RGB data offset 40, 0, 0, 0, // struct BITMAPINFOHEADER size 0, 0, 0, 0, // bmp height 0, 0, 0, 0, // bmp width 1, 0, // planes 16, 0, // bit per pixel 0, 0, 0, 0, // compression 0, 0, 0, 0, // data size 0, 0, 0, 0, // h resolution 0, 0, 0, 0, // v resolution 0, 0, 0, 0, // used colors 0, 0, 0, 0 // important colors }; // file size offset 54 uint16_t file_size = WIDTH * HEIGHT * 2 + DATA_OFFSET; header[2] = (uint8_t)(file_size & 0x000000ff); header[3] = (file_size >> 8) & 0x000000ff; header[4] = (file_size >> 16) & 0x000000ff; header[5] = (file_size >> 24) & 0x000000ff; // height header[18] = HEIGHT & 0x000000ff; header[19] = (HEIGHT >> 8) & 0x000000ff; header[20] = (HEIGHT >> 16) & 0x000000ff; header[21] = (HEIGHT >> 24) & 0x000000ff; // width header[22] = WIDTH & 0x000000ff; header[23] = (WIDTH >> 8) & 0x000000ff; header[24] = (WIDTH >> 16) & 0x000000ff; header[25] = (WIDTH >> 24) & 0x000000ff;
Step2. 獲取當(dāng)前幀,進(jìn)行RGB565到RGB555的轉(zhuǎn)換,將轉(zhuǎn)換后的數(shù)據(jù)放入MemoryHeap中 unsigned int i;
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_t pixel_data; uint8_t tail_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. 調(diào)用callback,將數(shù)據(jù)存儲(chǔ)到設(shè)備,并釋放MemoryHeap mDataCb(CAMERA_MSG_COMPRESSED_IMAGE, mem, mCallbackCookie);
heap=NULL;
到這里,OK6410的Android系統(tǒng)就真正可以拍照了,通過之前介紹編譯的方法,將編譯好的libcamera.so放入目標(biāo)機(jī)的/system/lib中,重啟就能看到效果了,這是我用OV9650拍的照片,在PC上查看建議把jpg后綴名改為bmp,在Android上查看沒任何問題:
附件下載: 待解決的問題:1. 目前輸出的文件雖然是jpg后綴,但實(shí)際是BMP格式的,想法是使用external/jpeg庫中的函數(shù)來解決。 2. 目前輸出為320*240,若要使用更大分辨率,勢(shì)必要更大的MemoryHeap。比如需要1024*768*2字節(jié)的空間,雖然可以申請(qǐng),但根本無法訪問到后面的空間,目前還沒想到解決方案。 希望大家能一起把以上這兩個(gè)問題解決,這樣我們的開發(fā)板就能在圖像應(yīng)用上有用武之地了! 另:轉(zhuǎn)文請(qǐng)注明出處,謝謝!
References:
|
|
|