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