解決Camera HAL層無法多次回調(diào)數(shù)據(jù)到App

問題背景

在一個雙攝項目中, 需要在HAL(使用 API1/HAL1)層集成Bokeh功能(雙攝虛化), 同時需要將相關(guān)雙攝數(shù)據(jù)回調(diào)到App存儲, 用于集成Refocus功能(即可以在相冊中重新選擇虛化焦點和虛化強度). 因此需要回傳的數(shù)據(jù)有, bokeh效果圖, 主攝原圖, depth數(shù)據(jù), bokeh效果圖可以走主攝jpeg回調(diào)直接返回到App,剩余的兩個數(shù)據(jù)當初考慮使用jpeg callback或者postview callback分兩次傳回App, 但在實際實施過程中過, 使用postview 回傳數(shù)據(jù)時, App始終只能接受到一次數(shù)據(jù), 使用jpeg回傳除了bokeh效果圖, 其他數(shù)據(jù)也接收不到,也就是兩個回調(diào)接口都只能回調(diào)一次數(shù)據(jù), 這就導致功能沒法完成, 這篇文章主要是講解決這個問題的思路.

解決思路

在解決問題前, 我們已知的信息是: 回調(diào)接口使用肯定沒有問題, 因為數(shù)據(jù)的確可以回調(diào), 只是每個回調(diào)接口只能回調(diào)一次數(shù)據(jù)而已. 因此最大可能性是回調(diào)的數(shù)據(jù)在某個位置被丟棄了, 所以要解決問題, 就得一步一步看數(shù)據(jù)回調(diào)流程.
關(guān)于數(shù)據(jù)回調(diào)流程, 可以參考一下我之前寫的Camera架構(gòu)流程: Android Camera架構(gòu)

我們主要關(guān)注的文件有 :
frameworks/base/core/java/android/hardware/Camera.java
frameworks/base/core/jni/android_hardware_Camera.cpp
frameworks/av/camera/Camera.cpp
frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp
frameworks/av/services/camera/libcameraservice/device1/CameraHardwareInterface.h
這幾個文件中都有數(shù)據(jù)回調(diào)相關(guān)的函數(shù)(dataCallback), 具體是那些函數(shù)就不一一介紹了, 主要講下解決的思路,
至此, 我們一般有兩種方法來定位此問題:

  1. 詳細閱讀每個文件中和數(shù)據(jù)回調(diào)相關(guān)的函數(shù), 看看那些地方會丟棄回到數(shù)據(jù)
  2. 在每個文件的回調(diào)函數(shù)中關(guān)鍵位置打印一些Log, 編譯刷機, 跑下原始流程, 看Log在哪里出現(xiàn)異常

顯然第一種方式更利于理解整體流程, 而第二種方式則能更快定位并解決問題, 但都是不錯的解決方案.

問題分析

由于我當時項目比較緊急, 所以用的第二種方式解決問題, 通過打印Log, 最終定位到是在CameraClient.cpp中, 回調(diào)數(shù)據(jù)被丟棄了.

關(guān)鍵函數(shù)如下:

注: 代碼均為 Android 8.0 高通SDM450平臺

void CameraClient::dataCallback(int32_t msgType,
        const sp<IMemory>& dataPtr, camera_frame_metadata_t *metadata, void* user) {
    LOG2("dataCallback(%d)", msgType);

    sp<CameraClient> client = getClientFromCookie(user);
    if (client.get() == nullptr) return;

    if (!client->lockIfMessageWanted(msgType)) return;
    if (dataPtr == 0 && metadata == NULL) {
        ALOGE("Null data returned in data callback");
        client->handleGenericNotify(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0);
        return;
    }

    switch (msgType & ~CAMERA_MSG_PREVIEW_METADATA) {
        case CAMERA_MSG_PREVIEW_FRAME:
            client->handlePreviewData(msgType, dataPtr, metadata);
            break;
        case CAMERA_MSG_POSTVIEW_FRAME:
            client->handlePostview(dataPtr);
            break;
        case CAMERA_MSG_RAW_IMAGE:
            client->handleRawPicture(dataPtr);
            break;
        case CAMERA_MSG_COMPRESSED_IMAGE:
            client->handleCompressedPicture(dataPtr);
            break;
        default:
            client->handleGenericData(msgType, dataPtr, metadata);
 

可以看到, 此函數(shù)中會造成數(shù)據(jù)不往上傳有兩部分 lockIfMessageWanted()函數(shù)和本身數(shù)據(jù)是否為空的判斷, 由于數(shù)據(jù)為空會有error log打印出來, 所以能確定是lockIfMessageWanted()造成數(shù)據(jù)被丟棄.

#define CHECK_MESSAGE_INTERVAL 10 // 10ms
bool CameraClient::lockIfMessageWanted(int32_t msgType) {
    int sleepCount = 0;
    while (mMsgEnabled & msgType) {
        if (mLock.tryLock() == NO_ERROR) {
            if (sleepCount > 0) {
                LOG1("lockIfMessageWanted(%d): waited for %d ms",
                    msgType, sleepCount * CHECK_MESSAGE_INTERVAL);
            }

            // If messages are no longer enabled after acquiring lock, release and drop message
            if ((mMsgEnabled & msgType) == 0) {
                mLock.unlock();
                break;
            }

            return true;
        }
        if (sleepCount++ == 0) {
            LOG1("lockIfMessageWanted(%d): enter sleep", msgType);
        }
        usleep(CHECK_MESSAGE_INTERVAL * 1000);
    }
    ALOGW("lockIfMessageWanted(%d): dropped unwanted message", msgType);
    return false;
}

可以直觀的看到當mMsgEnabled & msgTypfalse時, 函數(shù)返回false, 導致dataCallback()直接返回, 不往下走了, 而 mMsgEnabled & msgTypefalse表明此時 msgType 沒有被啟用, 拍照返回jpeg數(shù)據(jù)使用的msgTypeCAMERA_MSG_COMPRESSED_IMAGE,因此要分析 CAMERA_MSG_COMPRESSED_IMAGE何時被啟用和禁用, 搜索關(guān)鍵字后, 可以看到 CAMERA_MSG_COMPRESSED_IMAGE啟用是在takePicture()時, 禁用是在拍照數(shù)據(jù)返回時handleCompressedPicture()

相關(guān)代碼如下:

    //啟用, takePicture()函數(shù)中的代碼片段
    // We only accept picture related message types
    // and ignore other types of messages for takePicture().
    int picMsgType = msgType
                        & (CAMERA_MSG_SHUTTER |
                           CAMERA_MSG_POSTVIEW_FRAME |
                           CAMERA_MSG_RAW_IMAGE |
                           CAMERA_MSG_RAW_IMAGE_NOTIFY |
                           CAMERA_MSG_COMPRESSED_IMAGE);
    enableMsgType(picMsgType);
//--------------------------------------------------
    //禁用 handleCompressedPicture()中的代碼片段
    if (!mBurstCnt && !mLongshotEnabled) {
        LOG1("handleCompressedPicture mBurstCnt = %d", mBurstCnt);
        disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE);
    }

到此可以看出問題產(chǎn)生的原因是camera framework 本身設(shè)計就是拍一次照, 只接受一次數(shù)據(jù)返回, postview callback也是如此, 多次回調(diào)默認是不允許的

解決方案

從上面分析可以看出, 如果不做任何修改, 是沒法使用同一個callback在拍照后回調(diào)多次數(shù)據(jù)的,所以解決方法有兩種:

  1. 將所有數(shù)據(jù)組裝到一起, 一次性回到到app
  2. 修改framework代碼, 去除此部分的限制

方法1缺點是要自己組裝數(shù)據(jù), 并且記錄各個數(shù)據(jù)的大小, 不然app端無法解析數(shù)據(jù)
方法2高通已經(jīng)幫我們實現(xiàn)了, 代碼如下:

 if (!mBurstCnt && !mLongshotEnabled) {
        LOG1("handleCompressedPicture mBurstCnt = %d", mBurstCnt);
        disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE);
    }

高通修改過后, 如果是連拍或者mBurstCnt > 1, 則不會去disable CAMERA_MSG_COMPRESSED_IMAGE這個msg, 而mBurstCnt的值是通過參數(shù)獲取的, 代碼如下:

mBurstCnt = mHardware->getParameters().getInt("num-snaps-per-shutter");
    if(mBurstCnt <= 0)
        mBurstCnt = 1;

因此我們?nèi)绻卣{(diào)多個數(shù)據(jù), 則只需在拍照前, 設(shè)置num-snaps-per-shutter這個Camera參數(shù), 其值為我們回調(diào)數(shù)據(jù)的數(shù)量, 此功能也會用到高通的一些AOST Feature上, 比如 高通的UbiFocus模式下, num-snaps-per-shutter的值為 7 , 也就是說這個功能會回調(diào)7次jpeg數(shù)據(jù)到app, 高通只修改了jpeg 回調(diào)這部分內(nèi)容, 像postview 和其他數(shù)據(jù)回調(diào)則沒有修改, 還是只能拍一次照, 回調(diào)一次數(shù)據(jù).

總結(jié)

  1. Google原始設(shè)計對拍照數(shù)據(jù)回調(diào)有限制, 一次takePicture()每個callback只能返回一次數(shù)據(jù), 數(shù)據(jù)返回后, 相應MSG會被Disable, 導致后面再回調(diào)的數(shù)據(jù)不會回調(diào)App.
  2. 高通對默認流程做了修改, 可通過設(shè)置Camera參數(shù), 控制jpeg callback回調(diào)的次數(shù).
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容