Android Camera fw學習(六)-takepicture(ZSL)流程分析
備註:博文仍然是分析Android5.1 API1程式碼的學習筆記。
這次筆記主要是來分析ZSL流程的。ZSL(zear shutter lag)即零延時,就是在拍照時不停預覽就可以拍照.由於有較好的使用者體驗度,該feature是現在大部分手機都擁有的功能。
下面不再貼出大量程式碼來描述過程,直接上圖。下圖是畫了2個小時整理出來的Android5.1 Zsl的基本流程,可以看到與ZSL密切相關的有5個執行緒frameprocessor、captureSequencer、ZslProcessor3、JpegProcessor、Camera3Device:requestThread。其實還有一個主執行緒用於更新引數。針對Android5.1看程式碼所得,ZSL過程中大概分成下面7個流程.
更正:圖中左上角的FrameProcessor執行緒起來後會在waitForNextFrame中執行mResultSignal.waitRelative(),圖中沒有更改過來。
0.註冊幀監聽物件
1.captureSequence執行緒註冊幀監聽物件
- 1.註冊時機
當上層發出ZSL拍照請求時,底層就會觸發拍照捕獲狀態機,改狀態機的基本流程圖在上篇筆記中已經整理出來過,這裡就不多說了。由於camera2Client與其它處理執行緒物件基本符合金字塔形的架構,可以看到這裡是通過camera2Client的物件將幀可用監聽物件註冊到FrameProcess物件中的List<RangeListener> mRangeListeners;
CaptureSequencer::CaptureState CaptureSequencer::manageZslStart(
sp<Camera2Client> &client) {
ALOGV("%s", __FUNCTION__);
status_t res;
sp<ZslProcessorInterface> processor = mZslProcessor.promote();
// We don't want to get partial results for ZSL capture.
client->registerFrameListener(mCaptureId, mCaptureId + 1,
this,
/*sendPartials*/false);
// TODO: Actually select the right thing here.
res = processor->pushToReprocess(mCaptureId);
//.......
}
特別注意:可以看到在註冊幀監聽物件時,傳入的兩個引數是mCaptureId, mCaptureId + 1
,為什麼會是這樣呢,因為這個就是標記我們想抓的是哪一幀,當拍照buffer從hal上來之後,Camera3Device就會回撥幀可用監聽物件,然後得到拍照幀的時間戳,緊接著根據時間戳從ZSL RingBuffer中找到最理想的inputBuffer,然後下發給hal進行Jpeg編解碼。對比下面ZSL執行緒的CaptureId,應該就理解了.
- 2.捕獲時機
void CaptureSequencer::onResultAvailable(const CaptureResult &result) {
ATRACE_CALL();
ALOGV("%s: New result available.", __FUNCTION__);
Mutex::Autolock l(mInputMutex);
mNewFrameId = result.mResultExtras.requestId;
mNewFrame = result.mMetadata;
if (!mNewFrameReceived) {
mNewFrameReceived = true;
.signal();
}
}
上面即是拍照狀態機註冊的回撥函式,其中當ZSL拍照幀上來之後,機會啟用正在等待中的CaptureSequencer執行緒,以進行後續的操作。
2.ZslProcess3執行緒註冊幀監聽物件
- 1.註冊時機
status_t ZslProcessor3::updateStream(const Parameters ¶ms) {
if (mZslStreamId == NO_STREAM) {
// Create stream for HAL production
// TODO: Sort out better way to select resolution for ZSL
// Note that format specified internally in Camera3ZslStream
res = device->createZslStream(
params.fastInfo.arrayWidth, params.fastInfo.arrayHeight,
mBufferQueueDepth,
&mZslStreamId,
&mZslStream);
// Only add the camera3 buffer listener when the stream is created.
mZslStream->addBufferListener(this);//這裡是在BufferQueue註冊的callback,暫時不用關心。
}
client->registerFrameListener(Camera2Client::kPreviewRequestIdStart,
Camera2Client::kPreviewRequestIdEnd,
this,
/*sendPartials*/false);
return OK;
}
上面的即為更新zsl流時呼叫的函式,可以看到其中使用registerFrameListener註冊了RingBuffer可用監聽物件,這裡我們要特別注意的是下面2個巨集。這個是專門為預覽預留的requestId,考慮這樣也會有錄影和拍照的requestId,每次更新引數後,這個requestId會有+1操作,沒有引數更新,則不會+1,這個可以在各自的Debug手機上發現。
static const int32_t kPreviewRequestIdStart = 10000000;
static const int32_t kPreviewRequestIdEnd = 20000000;
- 2.捕獲時機
void ZslProcessor3::onResultAvailable(const CaptureResult &result) {
ATRACE_CALL();
ALOGV("%s:", __FUNCTION__);
Mutex::Autolock l(mInputMutex);
camera_metadata_ro_entry_t entry;
entry = result.mMetadata.find(ANDROID_SENSOR_TIMESTAMP);
nsecs_t timestamp = entry.data.i64[0];
entry = result.mMetadata.find(ANDROID_REQUEST_FRAME_COUNT);
int32_t frameNumber = entry.data.i32[0];
// Corresponding buffer has been cleared. No need to push into mFrameList
if (timestamp <= mLatestClearedBufferTimestamp) return;
mFrameList.editItemAt(mFrameListHead) = result.mMetadata;
mFrameListHead = (mFrameListHead + 1) % mFrameListDepth;
}
去掉錯誤檢查程式碼,上面由於CaptureID是下面2個,也就是ZSL的所有預覽Buffer可用之後都會回撥這個方法,當佇列滿之後,新buffer會覆蓋舊buffer位置。上面可以看到mFrameList
中會儲存每一幀的metadata資料,mFrameListHead
用來標識下一次存放資料的位置。
static const int32_t kPreviewRequestIdStart = 10000000;
static const int32_t kPreviewRequestIdEnd = 20000000;
1.查詢ZSL拍照最合適的buffer
一開始我以為是是根據想要抓取那幀的captureId來找到zsl拍照buffer的,但是現在看來就是找時間戳最近的那個buffer來進行jpeg編解碼(而且google工程師在原始碼中註釋也是這樣說的).
status_t ZslProcessor3::pushToReprocess(int32_t requestId) {
ALOGV("%s: Send in reprocess request with id %d",
__FUNCTION__, requestId);
Mutex::Autolock l(mInputMutex);
status_t res;
sp<Camera2Client> client = mClient.promote();
//下面是就是在mFrameList查詢時間戳最近的幀。
size_t metadataIdx;
nsecs_t candidateTimestamp = getCandidateTimestampLocked(&metadataIdx);
//根據上一次查詢的時間戳,從ZSL BufferQueue中查詢時間最接近的Buffer,並將
//buffer儲存到mInputBufferQueue佇列中。
res = mZslStream->enqueueInputBufferByTimestamp(candidateTimestamp,
/*actualTimestamp*/NULL);
//-----------------
{//獲取zsl 編解碼的metadataId,稍後會傳入給hal編解碼。
CameraMetadata request = mFrameList[metadataIdx];
// Verify that the frame is reasonable for reprocessing
camera_metadata_entry_t entry;
entry = request.find(ANDROID_CONTROL_AE_STATE);
if (entry.data.u8[0] != ANDROID_CONTROL_AE_STATE_CONVERGED &&
entry.data.u8[0] != ANDROID_CONTROL_AE_STATE_LOCKED) {
ALOGV("%s: ZSL queue frame AE state is %d, need full capture",
__FUNCTION__, entry.data.u8[0]);
return NOT_ENOUGH_DATA;
}
//這中間會更新輸入stream的流ID、更新捕獲意圖為靜態拍照、判斷這一幀是否AE穩定、
//獲取jpegStreamID並更新到metadata中、更新請求ID,最後根據更新後的request metadata
//更新jpeg metadata。最後一步啟動Camera3Device抓取圖片。
// Update post-processing settings
res = updateRequestWithDefaultStillRequest(request);
mLatestCapturedRequest = request;
res = client->getCameraDevice()->capture(request);
mState = LOCKED;
}
return OK;
}
還記得在啟動狀態機器時,註冊的幀監聽物件吧。這裡引數requestId就是我們想要抓拍的圖片的請求ID,目前發現該請求ID後面會更新到metadata中。這裡只要知道該函式功能就可以了。
- 1.從mFrameList中查詢時間戳最小的metadata。
- 2.根據從第一步獲取到時間戳,從ZSL BufferQueue選擇時間最接近Buffer.
- 3.將Buffer放到mInputBufferQueue中,更新jpeg編解碼metadata,啟動Capture功能。
2.設定zsl input buffer和 jpeg out buffer
其實這一步之前已經討論過,inputBuffer是ZslProcess3執行緒查詢到最合適的用於jpeg編解碼的buffer。outputBuffer為JpegProcessor執行緒更新的buffer用於存放hal編解碼之後的jpeg圖片。其中準備jpeg OutBuffer的操作就是在下面操作的。可以看到將outputStream的ID,儲存到metadata中了。這樣就會在Camera3Device中根據這項metadata來新增outputBuffer到hal。
status_t ZslProcessor3::pushToReprocess(int32_t requestId) {
// TODO: Shouldn't we also update the latest preview frame?
int32_t outputStreams[1] =
{ client->getCaptureStreamId() };
res = request.update(ANDROID_REQUEST_OUTPUT_STREAMS,
outputStreams, 1);
}
3.歸還jpeg Buffer幹了什麼.
當framework將ZSL inputBuffer和jpeg outputBuffer,傳給hal後,hal就會啟動STLL_CAPTURE流程,將inputBuffer中的影象資料,進行一系列的後處理流程。當後處理完成後,hal則會將臨時Buffer拷貝到outPutBuffer中(注意:這裡要記得做flush操作,即重新整理Buffer,要不然圖片有可能會出現綠條).
因為JpegBuffer也是從BufferQueue Dequeue出來的buffer,而且在建立BufferQueue時,也註冊了幀監聽物件(即:onFrameAvailable()回撥).這樣的話當幀可用(即:進行了enqueue操作),就會回撥onFrameAvailable()方法,這樣當hal歸還jpegBuffer時就是要進行enqueue()操作。在onFrameAvailable()方法中,會啟用jpegproces執行緒,進行後續的處理,最後啟用captureSequeue拍照狀態機執行緒。
4儲存ZSLBuffer.
這裡由於ZSL Buffer一直會從hal上來,所以當zslBuffer上來後,就會啟用FrameProcesor執行緒儲存這一ZslBuffer,目前FrameWork那邊預設是4個buffer,這樣的話當佇列滿之後,就會覆蓋之前最老的buffer,如此反覆操作。
5.獲取拍照jpeg Buffer
當hal上來jpeg幀後,就會啟用jpegProcess執行緒,並從BufferQueue中拿到jpegbuffer,下面可以發現進行lockNextBuffer
,unlockBuffer
操作。
status_t JpegProcessor::processNewCapture() {
res = mCaptureConsumer->lockNextBuffer(&imgBuffer);
mCaptureConsumer->unlockBuffer(imgBuffer);
sp<CaptureSequencer> sequencer = mSequencer.promote();
//......
if (sequencer != 0) {
sequencer->onCaptureAvailable(imgBuffer.timestamp, captureBuffer);
}
}
上面可以發現最後回調了captureSequencer執行緒的onCaptureAvailable()回撥方法。該回調方法主要作用就是將時間戳和jpeg buffer的傳送到CaptureSequencer執行緒中,然後啟用CaptureSequencer執行緒。最後將Buffer CallBack到應用層。
void CaptureSequencer::onCaptureAvailable(nsecs_t timestamp,
sp<MemoryBase> captureBuffer) {
ATRACE_CALL();
ALOGV("%s", __FUNCTION__);
Mutex::Autolock l(mInputMutex);
mCaptureTimestamp = timestamp;
mCaptureBuffer = captureBuffer;
if (!mNewCaptureReceived) {
mNewCaptureReceived = true;
mNewCaptureSignal.signal();
}
}
6.拍照幀可用回撥
當拍照幀回到Framework後,就會回撥CaptureSequencer的onResultAvailable()介面,用於設定captureSequencer狀態機的標誌位和條件啟用,如下程式碼所示。條件變數和標誌位的使用可以在狀態機方法manageStandardCaptureWait()看到使用。
void CaptureSequencer::onResultAvailable(const CaptureResult &result) {
ATRACE_CALL();
ALOGV("%s: New result available.", __FUNCTION__);
Mutex::Autolock l(mInputMutex);
mNewFrameId = result.mResultExtras.requestId;
mNewFrame = result.mMetadata;
if (!mNewFrameReceived) {
mNewFrameReceived = true;
mNewFrameSignal.signal();
}
}
7.jpeg buffer回撥到app
該callback是應用註冊過來的一個代理物件,下面就是通過binder程序間呼叫將jpeg Buffer傳送到APP端,注意這裡的msgTyep = CAMERA_MSG_COMPRESSED_IMAGE
,就是告訴上層這是一個壓縮的影象資料。
CaptureSequencer::CaptureState CaptureSequencer::manageDone(sp<Camera2Client> &client) {
status_t res = OK;
ATRACE_CALL();
mCaptureId++;
......
Camera2Client::SharedCameraCallbacks::Lock
l(client->mSharedCameraCallbacks);
ALOGV("%s: Sending still image to client", __FUNCTION__);
if (l.mRemoteCallback != 0) {
l.mRemoteCallback->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE,
mCaptureBuffer, NULL);
} else {
ALOGV("%s: No client!", __FUNCTION__);
}
......
}