1. 程式人生 > >Android Camera fw學習(六)-takepicture(ZSL)流程分析

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 &params) {
    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__);
        }
        ......
}