解決Camera HAL層無法多次回調數據到App

問題背景

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

解決思路

在解決問題前, 我們已知的信息是: 回調接口使用肯定沒有問題, 因爲數據的確可以回調, 只是每個回調接口只能回調一次數據而已. 因此最大可能性是回調的數據在某個位置被丟棄了, 所以要解決問題, 就得一步一步看數據回調流程.
關於數據回調流程, 可以參考一下我之前寫的Camera架構流程: Android Camera架構

我們主要關注的文件有 :
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
這幾個文件中都有數據回調相關的函數(dataCallback), 具體是那些函數就不一一介紹了, 主要講下解決的思路,
至此, 我們一般有兩種方法來定位此問題:

  1. 詳細閱讀每個文件中和數據回調相關的函數, 看看那些地方會丟棄回到數據
  2. 在每個文件的回調函數中關鍵位置打印一些Log, 編譯刷機, 跑下原始流程, 看Log在哪裏出現異常

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

問題分析

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

關鍵函數如下:

注: 代碼均爲 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);
 

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

#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時, 函數返回false, 導致dataCallback()直接返回, 不往下走了, 而 mMsgEnabled & msgTypefalse表明此時 msgType 沒有被啓用, 拍照返回jpeg數據使用的msgTypeCAMERA_MSG_COMPRESSED_IMAGE,因此要分析 CAMERA_MSG_COMPRESSED_IMAGE何時被啓用和禁用, 搜索關鍵字後, 可以看到 CAMERA_MSG_COMPRESSED_IMAGE啓用是在takePicture()時, 禁用是在拍照數據返回時handleCompressedPicture()

相關代碼如下:

    //啓用, takePicture()函數中的代碼片段
    // 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);
    }

到此可以看出問題產生的原因是camera framework 本身設計就是拍一次照, 只接受一次數據返回, postview callback也是如此, 多次回調默認是不允許的

解決方案

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

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

方法1缺點是要自己組裝數據, 並且記錄各個數據的大小, 不然app端無法解析數據
方法2高通已經幫我們實現了, 代碼如下:

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

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

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

因此我們如果要回調多個數據, 則只需在拍照前, 設置num-snaps-per-shutter這個Camera參數, 其值爲我們回調數據的數量, 此功能也會用到高通的一些AOST Feature上, 比如 高通的UbiFocus模式下, num-snaps-per-shutter的值爲 7 , 也就是說這個功能會回調7次jpeg數據到app, 高通只修改了jpeg 回調這部分內容, 像postview 和其他數據回調則沒有修改, 還是隻能拍一次照, 回調一次數據.

總結

  1. Google原始設計對拍照數據回調有限制, 一次takePicture()每個callback只能返回一次數據, 數據返回後, 相應MSG會被Disable, 導致後面再回調的數據不會回調App.
  2. 高通對默認流程做了修改, 可通過設置Camera參數, 控制jpeg callback回調的次數.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章