問題背景
在一個雙攝項目中, 需要在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ù)就不一一介紹了, 主要講下解決的思路,
至此, 我們一般有兩種方法來定位此問題:
- 詳細閱讀每個文件中和數(shù)據(jù)回調(diào)相關(guān)的函數(shù), 看看那些地方會丟棄回到數(shù)據(jù)
- 在每個文件的回調(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 & msgTyp
為false
時, 函數(shù)返回false
, 導致dataCallback()
直接返回, 不往下走了, 而 mMsgEnabled & msgType
為false
表明此時 msgType
沒有被啟用, 拍照返回jpeg數(shù)據(jù)使用的msgType
為 CAMERA_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ù)的,所以解決方法有兩種:
- 將所有數(shù)據(jù)組裝到一起, 一次性回到到app
- 修改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é)
- Google原始設(shè)計對拍照數(shù)據(jù)回調(diào)有限制, 一次
takePicture()
每個callback只能返回一次數(shù)據(jù), 數(shù)據(jù)返回后, 相應MSG會被Disable, 導致后面再回調(diào)的數(shù)據(jù)不會回調(diào)App. - 高通對默認流程做了修改, 可通過設(shè)置Camera參數(shù), 控制jpeg callback回調(diào)的次數(shù).