小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Android照相功能驅(qū)動(dòng)層中HAL的實(shí)現(xiàn)(基于OK6410開發(fā)板+OV9650攝像頭)

 dwlinux 2014-04-24

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)容:

  1. Android 模塊編譯
  2. Android 模塊的板上加載及調(diào)試
  3. Android Camera 模塊的改寫

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中。 

附件下載:

makelibcamera.zip 


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但可能失敗了):

復(fù)制代碼
void CameraHardware::initDefaultParameters()
{
    CameraParameters p;

    p.set(CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES, "320x240");
    p.setPreviewSize(320240);
    p.setPreviewFrameRate(15);
//    p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_YUV420SP);
    p.setPreviewFormat(CameraParameters::PIXEL_FORMAT_RGB565);
    p.set(CameraParameters::KEY_ROTATION, 0);//90

// 其余代碼省略
    }
    
}
復(fù)制代碼

現(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的引用:

復(fù)制代碼
int CameraHardware::previewThread() 

{
    mLock.lock();
        // the attributes below can change under our feet...

        int previewFrameRate = mParameters.getPreviewFrameRate();

        // Find the offset within the heap of the current buffer.
        ssize_t offset = mCurrentPreviewFrame * mPreviewFrameSize;

        sp<MemoryHeapBase> heap = mPreviewHeap;

        // this assumes the internal state of fake camera doesn't change
        
// (or is thread safe)
        Ov965xCamera* Ov965xCamera = mOv965xCamera;
        USBCamera* USBCamera = mUSBCamera;
        sp<MemoryBase> buffer = mBuffers[mCurrentPreviewFrame];

    mLock.unlock();

    // TODO: here check all the conditions that could go wrong
    if (buffer != 0) {
        // Calculate how long to wait between frames.
        int delay = (int)(1000000.0f / float(previewFrameRate));

        // This is always valid, even if the client died -- the memory
        
// is still mapped in our process.
        void *base = heap->base();

        // Fill the current frame with the fake camera.
        uint8_t *frame = ((uint8_t *)base) + offset;
        //Ov965xCamera->getNextFrameAsYuv420(frame);

    if(mCamType == CAMTYPE_CMOS) 
        Ov965xCamera->getNextFrameAsRgb565((uint16_t *)frame);//獲取一個(gè)幀的數(shù)據(jù),放入frame
    else if (mCamType == CAMTYPE_USB)
        USBCamera->getNextFrameAsRgb565((uint16_t *)frame);

        //LOGV("previewThread: generated frame to buffer %d", mCurrentPreviewFrame);

        
// Notify the client of a new frame.
        if (mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME)
            mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie);

        // Advance the buffer pointer.
        mCurrentPreviewFrame = (mCurrentPreviewFrame + 1) % kBufferCount;

        // Wait for it...
        usleep(delay);
    }

    return NO_ERROR;
}

復(fù)制代碼

這樣分析過后,問題就變得很明了了,要將圖片存儲(chǔ)下來,只要獲取其一幀數(shù)據(jù)(getNextFrameAsRGB565),在takePicture函數(shù)中將其存儲(chǔ)下來即可。好,讓我們看看takePicture的實(shí)現(xiàn):它啟動(dòng)了一個(gè)線程來調(diào)用pictureThread方法,這里就是我們大顯身手的地方了!

復(fù)制代碼
int CameraHardware::pictureThread()
{
    if (mMsgEnabled & CAMERA_MSG_SHUTTER)
        mNotifyCb(CAMERA_MSG_SHUTTER, 00, 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;
復(fù)制代碼

每個(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ū):

復(fù)制代碼
    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è)像素
復(fù)制代碼

 

寫B(tài)MP文件頭:

復(fù)制代碼
        uint8_t header[54] = { 0x42// identity : B
        0x4d// identity : M
        0000// file size
        00// reserved1
        00// reserved2
        54000// RGB data offset
        40000// struct BITMAPINFOHEADER size
        0000// bmp height
        0000// bmp width
        10// planes
        160// bit per pixel
        0000// compression
        0000// data size
        0000// h resolution
        0000// v resolution
        0000// used colors
        0000 // 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;  

復(fù)制代碼

 

Step2.  獲取當(dāng)前幀,進(jìn)行RGB565到RGB555的轉(zhuǎn)換,將轉(zhuǎn)換后的數(shù)據(jù)放入MemoryHeap中

復(fù)制代碼
    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;
    }
復(fù)制代碼
  

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上查看沒任何問題:

 

 

附件下載: 

libcamera.so和相關(guān)源代碼 


待解決的問題:

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:

  1. Android Camera Hal 的初步實(shí)現(xiàn)1(http://blog.csdn.net/flyingpipi/article/details/5773666)
  2. Android硬件抽象層(HAL)概要介紹和學(xué)習(xí)計(jì)劃 (http://blog.csdn.net/luoshengyang/article/details/6567257 )
  3. 真OO無雙之真亂舞書 (http://www.cnblogs.com/oomusou/) 

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多