1. 程式人生 > >Android Camera fw學習(三)-startPreview流程分析

Android Camera fw學習(三)-startPreview流程分析

Android Camera fw學習(三)-startPreview流程分析

備註:本文是Android5.1學習筆記。博文按照軟體啟動流程分析。
  如果看過前面的兩篇博文,我們應該已經知道,在進行preview之前,我們建立了客戶端的java和native Camera物件,在mediaServer程序建立了對應客戶端的本地物件(Camera2Client),此外也獲取到底層HAL3預設資訊,現在上層app 根據這個預設引數,更新引數,設定preview surface,為後面的startPreview準備。

一、Camera app初步接觸

  為了更方便的分析,這裡貼出部分app部分程式碼,例子是Android原生PDK,程式碼路徑在
pdk/apps/TestingCamera/

。目前需要關注都在下面的setUpCamera()函式中。

1.camera – setUpCamera

//原始碼路徑:pdk/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
void setUpCamera() {
        if (mCameraId == NO_CAMERA_ID) return;

        log("Setting up camera " + mCameraId);
        logIndent(1);

        if (mState < CAMERA_OPEN) {
            log("Opening camera "
+ mCameraId); try { //這是第一步準備工作,前面博文已經分析過了。 mCamera = Camera.open(mCameraId); } catch (RuntimeException e) { logE("Exception opening camera: " + e.getMessage()); resetCamera(); mCameraSpinner.setSelection(0
); logIndent(-1); return; } mState = CAMERA_OPEN; }     //設定error回撥,供camera.java中回撥。 mCamera.setErrorCallback(this);     //這是顯示方向 setCameraDisplayOrientation(); //獲取底層預設配置引數,這個下面會做進一步說明 mParams = mCamera.getParameters(); // Set up preview size selection log("Configuring camera"); logIndent(1);     //下面都是在更新當前Camera app引數,然後會把新的引數發給hal3 updatePreviewSizes(mParams); updatePreviewFrameRate(mCameraId); updatePreviewFormats(mParams); updateAfModes(mParams); updateFlashModes(mParams); updateSnapshotSizes(mParams); updateCamcorderProfile(mCameraId); updateVideoRecordSize(mCameraId); updateVideoFrameRate(mCameraId); updateColorEffects(mParams); //這裡省略一些程式碼,主要是上層app會根據底層獲取到引數,更新部分控制元件的屬性, //這裡我們可以不用關心,感興趣的可以檢視原始碼。 // Update parameters based on above updates mCamera.setParameters(mParams); if (mPreviewHolder != null) { log("Setting preview display"); try { //可以理解這個就是設定preview buffer的地方,下面有進一步分析。 mCamera.setPreviewDisplay(mPreviewHolder); } catch(IOException e) { Log.e(TAG, "Unable to set up preview!"); } } logIndent(-1); enableOpenOnlyControls(true); resizePreview(); if (mPreviewToggle.isChecked()) { log("Starting preview" ); //引數都準備好了,啟動preview mCamera.startPreview();  mState = CAMERA_PREVIEW; } else { mState = CAMERA_OPEN; enablePreviewOnlyControls(false); } }

這裡設定camera引數,

  • 1.open camera:這個操作我們前面已經講過,主要建立各種底層物件
  • 2.getParameters:獲取底層配置,緊接著native 就調到hal層的construct_default_request_settings()介面,該介面構建底層預設引數,如果想新增資訊給app,可以新增到這裡。
  • 3.setPreviewDisplay():設定preview預覽buffer,camera是生產者,SurfaceFlinger是消費者.這裡呼叫這個介面,就把一個java層的surface傳給camera應用native 物件,native拿到java層的surface物件,會轉化成native層的surface物件。這樣的話,底層就拿到了對應預覽surface的的生產者代理物件了。後面就可以進行dequeue,queue buffer了。如果想簡單瞭解BufferQueue,可以檢視前一篇博文。下面我們會詳細介紹preivew如何建立,併發送給hal的。
  • 4.startpreview:啟動preview請求。

2.設定preview buffer-setPreviewDisplay(SurfaceHolder holder)

  上面引數中的holder物件為SurfaceHolder類物件,該類為一個抽象介面類,我們可以用它來控制surface的大小,格式,畫素格式(native 層管理surface是surfacecontrol類,這倆類穿一條褲子的)。下面是google官方的介紹。

Abstract interface to someone holding a display surface. Allows you to
control the surface size and format, edit the pixels in the surface, and
monitor changes to the surface. This interface is typically available
through the {@link SurfaceView} class.
When using this interface from a thread other than the one running
its {@link SurfaceView}, you will want to carefully read the
methods
{@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}.

  該函式中主要設定preview buffer的。下面我麼可以看到函式內部呼叫了SurfaceHolder的getSurface()方法獲取surface物件。surface由graphic那邊統一管理。目前不用關心surface怎麼建立,這裡面牽扯到的東西太多了。

public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewSurface(holder.getSurface());
        } else {
            setPreviewSurface((Surface)null);
        }
    }

但是這裡在setUpCamera()函式中,mPreviewHolder還是NULL,所以根本沒有設定preview buffer,這樣的話preview環境還沒準備好,startPreviw肯定會失敗。難道真的會失敗

if (mPreviewHolder != null) {
            log("Setting preview display");
            try {
            //可以理解這個就是設定preview buffer的地方,下面有進一步分析。
                mCamera.setPreviewDisplay(mPreviewHolder);
            } catch(IOException e) {
                Log.e(TAG, "Unable to set up preview!");
            }

當然不會讓它失敗,在setUpCamera()中跟著設定setPreviewDisplay()下面有一個resizePreview();呼叫,該函式實現如下,不過閱讀下注釋應該就明白了。

    void resizePreview() {
      //為了觸發佈局操作,重新設定preview佈局引數,
        // Reset preview layout parameters, to trigger layout pass
        // This will eventually call layoutPreview below
        Resources res = getResources();
        mPreviewView.setLayoutParams(
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0,
                        mCallbacksEnabled ?
                        res.getInteger(R.integer.preview_with_callback_weight):
                        res.getInteger(R.integer.preview_only_weight) ));
    }

  該該函式功能就是重新觸發佈局操作,那為什麼要觸發佈局呢。我們看看觸發佈局時做了些什麼我們需要關注的。這裡我們先來關注一個surfaceHolder的介面。

/**
* This is called immediately after any structural changes (format or
* size) have been made to the surface.  You should at this point update
* the imagery in the surface.  This method is always called at least
* once, after {@link #surfaceCreated}.
* 
* @param holder The SurfaceHolder whose surface has changed.
* @param format The new PixelFormat of the surface.
* @param width The new width of the surface.
* @param height The new height of the surface.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height);

  註釋內容表明的意思就是,當surface物件的格式和大小發生變化時,這個介面就會被呼叫。那麼上面重新設定surface的佈局引數,明擺著要觸發surfaceChanged函式呼叫。來看看該函式在camera應用中如何實現的。

public void surfaceChanged(SurfaceHolder holder,
            int format,
            int width,
            int height) {
//直接去getHolder,這裡mPreviewView呼叫了getHolder()方法,獲取到一個SurfaceHolder物件,這裡暫時不要去考慮如何拿到SurfaceHolder,我們只需要知道我們拿到preview buffer了。
        if (holder == mPreviewView.getHolder()) {
            if (mState >= CAMERA_OPEN) {
                final int previewWidth =
                        mPreviewSizes.get(mPreviewSize).width;
                final int previewHeight =
                        mPreviewSizes.get(mPreviewSize).height;

                if ( Math.abs((float)previewWidth / previewHeight -
                        (float)width/height) > 0.01f) {
                    Handler h = new Handler();
                    h.post(new Runnable() {
                        @Override
                        //建立一個執行緒去設定佈局引數,重新整理UI
                        public void run() {
                            layoutPreview();
                        }
                    });
                }
            }
            //下面這裡還是NULL
            if (mPreviewHolder != null) {
                return;
            }
            log("Surface holder available: " + width + " x " + height);
            //這裡儲存從previewSurface拿到的surfaceHolder物件
            mPreviewHolder = holder;
            try {
                if (mCamera != null) {
                //重新設定preview buffer生產者
                    mCamera.setPreviewDisplay(holder);
                }
            } catch (IOException e) {
                logE("Unable to set up preview!");
            }
        } else if (holder == mCallbackView.getHolder()) {
            mCallbackHolder = holder;
        }
    }

  針對當前應用程式在setupCamera()沒有直接設定preview buffer(其它Camera應用可能就不這麼幹了),而是在surfaceChanged()回撥函式中設定的。這裡不必過多關注上面程式碼的細節,只需要瞭解到當前的preview buffer是在這裡設定的。同時建立一個執行緒重新整理UI.下面已經將preview buffer送到了native了。由於他們語言不通,不能直接呼叫,中間隔了個虛擬機器,可以理解成這個樣子。

  • 1.紅色代表camera應用在建立surfaceView物件時,通過呼叫surface jni 在graphic那邊申請surface本地物件,同時返回一個surface引用物件儲存到java surface物件中的mNativeObject成員中。而java層surface物件是surfaceView類的成員,如下所示:
public class SurfaceView extends View {
    static private final String TAG = "SurfaceView";
    static private final boolean DEBUG = false;
    //.......
    final Surface mSurface = new Surface();       // Current surface in use
    final Surface mNewSurface = new Surface();    // New surface we are switching to
  • 2.綠色表示camera應用將java層的surface物件設到jni中,同時呼叫surface的jni函式,將該java層的surface翻譯成native的引用物件。通過這個surface引用物件,我們就可以找到對應的生產者代理物件了。

二、native 設定preview surface生產者代理物件

1.android_hardware_Camera_setPreviewSurface()

static void android_hardware_Camera_setPreviewSurface(JNIEnv *env, jobject thiz, jobject jSurface)
{
    ALOGV("setPreviewSurface");
    //獲取客戶端camaera本地物件
    sp<Camera> camera = get_native_camera(env, thiz, NULL);
    if (camera == 0) return;

    sp<IGraphicBufferProducer> gbp;
    sp<Surface> surface;
    if (jSurface) {
        surface = android_view_Surface_getSurface(env, jSurface);
        if (surface != NULL) {
            gbp = surface->getIGraphicBufferProducer();
        }
    }

    if (camera->setPreviewTarget(gbp) != NO_ERROR) {
        jniThrowException(env, "java/io/IOException", "setPreviewTexture failed");
    }
}
  • 1.首先獲取camera客戶端本地物件,
  • 2.通過surface jni介面將該java surface翻譯成本地surface引用物件。
  • 3.通過該surface引用物件找到對應的生產者代理物件。
  • 4.camera本地物件通過setPreviewTarget()將生產者物件,通過匿名binder通訊傳送到CameraService中(這裡要注意設定到CameraService中的也是生產者代理物件)。

2.設定preview buffer生產者到CameraService中

status_t Camera::setPreviewTarget(const sp<IGraphicBufferProducer>& bufferProducer)
{
    ALOGV("setPreviewTarget(%p)", bufferProducer.get());
    sp <ICamera> c = mCamera; 
    if (c == 0) return NO_INIT;
    ALOGD_IF(bufferProducer == 0, "app passed NULL surface");
    return c->setPreviewTarget(bufferProducer);
}

這裡mCamera就是前面博文講到的實現Icamera介面的camera2Client本地物件。本地物件會通過標準介面將buffer生產者設定到CameraService中。請看下面程式碼

status_t Camera2Client::setPreviewTarget(
        const sp<IGraphicBufferProducer>& bufferProducer) {
//去掉一些錯誤檢查程式碼,這裡省略
    sp<IBinder> binder;
    sp<ANativeWindow> window;
    if (bufferProducer != 0) { //這裡顯然不為NULL.
        binder = bufferProducer->asBinder();
        // Using controlledByApp flag to ensure that the buffer queue remains in
        // async mode for the old camera API, where many applications depend
        // on that behavior.
        //在前一篇博文中,我們瞭解到拿到生產者代理物件後,建立了一個用於管理surface的SurfaceCtrl物件。
        //這裡直接就建立了本地Surface物件。
        window = new Surface(bufferProducer, /*controlledByApp*/ true);
    }
    //binder就是生產者代理物件,而window就是剛剛建立的surface物件。繼續往下看。
    return setPreviewWindowL(binder, window);
}

上面函式主要根據preview生產者代理物件,建立一個本地surface物件,然後將該Surface和生產者物件傳遞給下一個介面。

status_t Camera2Client::setPreviewWindowL(const sp<IBinder>& binder,
        sp<ANativeWindow> window) {
    ATRACE_CALL();
    status_t res;
    //這裡第一次設定生產者物件,mPreviewSurface = NULL,下面為假
    if (binder == mPreviewSurface) {
        ALOGV("%s: Camera %d: New window is same as old window",
                __FUNCTION__, mCameraId);
        return NO_ERROR;
    }
    Parameters::State state;
    {
        SharedParameters::Lock l(mParameters);
        //這裡在建立Camera2Client本地物件後,進行裝置和引數初始化後會將state設定為STOPPED。詳情
        //請看Parameter.cpp中的Parameters::initialize()實現。
        state = l.mParameters.state;
    }
    switch (state) {
        case Parameters::DISCONNECTED:
        case Parameters::RECORD:
        case Parameters::STILL_CAPTURE:
        case Parameters::VIDEO_SNAPSHOT:
            ALOGE("%s: Camera %d: Cannot set preview display while in state %s",
                    __FUNCTION__, mCameraId,
                    Parameters::getStateName(state));
            return INVALID_OPERATION;
        case Parameters::STOPPED://這裡是stoped狀態
        case Parameters::WAITING_FOR_PREVIEW_WINDOW:
            // OK
            break;
        case Parameters::PREVIEW:
        //省略一部分無關程式碼
            break;
    }

    mPreviewSurface = binder;//儲存當前生產者代理物件。
    //將preview surface本地物件設定到preview流處理執行緒中
    res = mStreamingProcessor->setPreviewWindow(window);
    //次數省略與當前分析無干的程式碼,後面待分析到時,在貼出來。
    return OK;
}

  mStreamingProcessor為preview和Recording流處理物件,函式的主要功能是根據狀態機的狀態,走不同的流程,這裡一開始狀態機為STOPPED狀態,直接儲存生產者代理物件,並將預覽的的surface物件傳給流 mStreamingProcessor流處理物件。

//frameworks/av/services/camera/libcameraservice/api1/client2/StreamingProcessor.cpp
status_t StreamingProcessor::setPreviewWindow(sp<ANativeWindow> window) {
    ATRACE_CALL();
    status_t res;
    //如果已經存在有效的,preview流,就需要先刪除預覽流物件
    res = deletePreviewStream();
    if (res != OK) return res;
    Mutex::Autolock m(mMutex);
    mPreviewWindow = window;//儲存preview surface物件。
    return OK;
}

到這一步,預覽的surface生產者代理物件已經傳給了preview預覽流處理執行緒。執行緒會不斷的dequeue,enqueue buffer.下面在starpreview會繼續分析。

三、開啟startpreview

1.java startPrevew介面介紹

這裡現貼出,java framework的startPreview說明吧。

    /**
     * Starts capturing and drawing preview frames to the screen.
     * Preview will not actually start until a surface is supplied
     * with {@link #setPreviewDisplay(SurfaceHolder)} or
     * {@link #setPreviewTexture(SurfaceTexture)}.
     *
     * <p>If {@link #setPreviewCallback(Camera.PreviewCallback)},
     * {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}, or
     * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)} were
     * called, {@link Camera.PreviewCallback#onPreviewFrame(byte[], Camera)}
     * will be called when preview data becomes available.
     */
    public native final void startPreview();

上面是camera java fw startPreview()介面,可以發現該介面宣告的是一個jni介面。那麼應用端呼叫startPreviw其實直接調到jni中。重點是官方註釋,大概要注意的就是下面2條。

  • 1.startPreview之前,必須設定一個surafce到底層。一般上層通過setPreviewDisplay()和setPreviewTexture設定。
  • 2.如果需要註冊幀可用回撥函式的話,那麼就需要在應用層實現相應的回撥函式,並註冊到camera.java camera物件中。

2.native jni startPrevew介面

static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
{
    ALOGV("startPreview");
    //下面首先獲取應用端camra native物件,這裡不知道大家還記得前面介紹過的。
    //這裡Camera實現了ICamera介面,它正是一個代理物件,能與CameraService中的camera2Client本地物件通訊的。
    sp<Camera> camera = get_native_camera(env, thiz, NULL);
    if (camera == 0) return;
    //在本地方法中
    if (camera->startPreview() != NO_ERROR) {
        jniThrowRuntimeException(env, "startPreview failed");
        return;
    }
}
// start preview mode
status_t Camera::startPreview()
{
    ALOGV("startPreview");
    sp <ICamera> c = mCamera;
    if (c == 0) return NO_INIT;
    return c->startPreview();
}

  上面首先獲取camera本地物件,然後呼叫camera本地物件中實現Icamera介面的代理物件mCamera,程序間binder通訊呼叫到CameraService中的startPreview()介面。下面就不貼出ICamera中實現的處理binder訊息的程式碼,直接跳到Camera2Client.cpp中的startPreview.

status_t Camera2Client::startPreview() {
    ATRACE_CALL();
    ALOGV("%s: E", __FUNCTION__);
    Mutex::Autolock icl(mBinderSerializationLock);
    status_t res;
    if ( (res = checkPid(__FUNCTION__) ) != OK) return res;
    SharedParameters::Lock l(mParameters);
    return startPreviewL(l.mParameters, false);
}

由於下面的startPreviewL會在其它地方呼叫到,所以上面又對它做了一次封裝,並傳入restart = false.這裡我們是第一次開啟Camera,必須是false。繼續看下面這段程式碼,這段程式碼是startPreviw的核心地帶。

status_t Camera2Client::startPreviewL(Parameters &params, bool restart) {
    ATRACE_CALL();
    status_t res;

    ALOGV("%s: state == %d, restart = %d", __FUNCTION__, params.state, restart);
    //如果不是第一次呼叫StartPreviewL,而且傳進來的restart = false 就會直接返回,這裡只是做了保護
    //防止誤呼叫。
    if ( (params.state == Parameters::PREVIEW ||
                    params.state == Parameters::RECORD ||
                    params.state == Parameters::VIDEO_SNAPSHOT)
            && !restart) {
        // Succeed attempt to re-enter a streaming state
        ALOGI("%s: Camera %d: Preview already active, ignoring restart",
                __FUNCTION__, mCameraId);
        return OK;
    }
//   下面是camera狀態機,如果要置狀態機的狀態為PREVIEW之後的狀態,必須要先進行preview,
//   才能進行錄影,拍照,錄影拍照等操作。這裡就是為了防止進行了拍照,錄影等操作,而preview沒有啟動的
//    狀態。即restart = true(1), params.state = RECORD,STILL_CAPTURE,VIDEO_SNAPSHOT。
//    enum State {
//        DISCONNECTED,
//        STOPPED,
//        WAITING_FOR_PREVIEW_WINDOW,
//        PREVIEW,
//        RECORD,
//        STILL_CAPTURE,
//        VIDEO_SNAPSHOT
//    } state;
    if (params.state > Parameters::PREVIEW && !restart) {
        ALOGE("%s: Can't start preview in state %s",
                __FUNCTION__,
                Parameters::getStateName(params.state));
        return INVALID_OPERATION;
    }
  //判斷preview流處理物件是否已經設定過了有效的預覽Surface物件,我們上面已經設定過了。
    if (!mStreamingProcessor->haveValidPreviewWindow()) {
        params.state = Parameters::WAITING_FOR_PREVIEW_WINDOW;
        return OK;
    }
  //將狀態機的狀態設定為SOPPED
    params.state = Parameters::STOPPED;
 //過程1:獲取preview stareamId,下面有詳細介紹。
    int lastPreviewStreamId = mStreamingProcessor->getPreviewStreamId();
 //過程2:根據引數,更新流處理物件。
    res = mStreamingProcessor->updatePreviewStream(params);
    if (res != OK) {
        ALOGE("%s: Camera %d: Unable to update preview stream: %s (%d)",
                __FUNCTION__, mCameraId, strerror(-res), res);
        return res;
    }
    //
//TODO:find a better commadf 
//如果當前的previewStreamId和上一次的不同,就將previewStreamChanged置為true。
    bool previewStreamChanged = mStreamingProcessor->getPreviewStreamId() != lastPreviewStreamId;

    // We could wait to create the JPEG output stream until first actual use
    // (first takePicture call). However, this would substantially increase the
    // first capture latency on HAL3 devices, and potentially on some HAL2
    // devices. So create it unconditionally at preview start. As a drawback,
    // this increases gralloc memory consumption for applications that don't
    // ever take a picture.
    // TODO: Find a better compromise, though this likely would involve HAL
    // changes.
    //下面為了減少拍照延遲,這裡首先會建立好Jpeg流。詳情等我們分析拍照時在細說。
    int lastJpegStreamId = mJpegProcessor->getStreamId();
    res = updateProcessorStream(mJpegProcessor, params);
    if (res != OK) {
        ALOGE("%s: Camera %d: Can't pre-configure still image "
                "stream: %s (%d)",
                __FUNCTION__, mCameraId, strerror(-res), res);
        return res;
    }
    bool jpegStreamChanged = mJpegProcessor->getStreamId() != lastJpegStreamId;

//這裡建立了一個int32_t型別的陣列,用來記錄各種輸出流的ID。
    Vector<int32_t> outputStreams;

//--------------------------------------------------------------------------
//這中間有一段分析處理回撥引數,更新回撥流處理物件程式碼,這裡為了簡潔,先去掉這部分程式碼,等後面在分析
//callback stream時在好好分析。
//--------------------------------------------------------------------------
//這裡如果是ZSL模式,非錄影模式、沒有建立錄影流的話。就更新Zsl流,同樣這裡我們也不做分析,現在只關心
//normal preview
    if (params.zslMode && !params.recordingHint &&
            getRecordingStreamId() == NO_STREAM) {
        res = updateProcessorStream(mZslProcessor, params);
        if (res != OK) {
            ALOGE("%s: Camera %d: Unable to update ZSL stream: %s (%d)",
                    __FUNCTION__, mCameraId, strerror(-res), res);
            return res;
        }

        if (jpegStreamChanged) {
            ALOGV("%s: Camera %d: Clear ZSL buffer queue when Jpeg size is changed",
                    __FUNCTION__, mCameraId);
            mZslProcessor->clearZslQueue();
        }
        outputStreams.push(getZslStreamId());
    } else {
        mZslProcessor->deleteStream();
    }

//注意則個地方非常重要,儲存preview預覽流的StreamId,便於後面根據索引查詢。
    outputStreams.push(getPreviewStreamId());

    if (!params.recordingHint) {//如果不是recording模式,
        if (!restart) {//第一次startPreview
        //過程3,更新preview預覽請求,非常重要,下面有詳細分析。
            res = mStreamingProcessor->updatePreviewRequest(params);
            if (res != OK) {
                ALOGE("%s: Camera %d: Can't set up preview request: "
                        "%s (%d)", __FUNCTION__, mCameraId,
                        strerror(-res), res);
                return res;
            }
        }
    //過程4.啟動預覽流,下面有詳細分析。注意已經將outputStreams陣列傳到startStream介面中。
    //是為了確定是否將對應的請求下發給HAL。
        res = mStreamingProcessor->startStream(StreamingProcessor::PREVIEW,
                outputStreams);
    } else {
        if (!restart) {
        //根據引數,更新錄影請求請求,後面分析錄影時,在好好分析。
            res = mStreamingProcessor->updateRecordingRequest(params);
            if (res != OK) {
                ALOGE("%s: Camera %d: Can't set up preview request with "
                        "record hint: %s (%d)", __FUNCTION__, mCameraId,
                        strerror(-res), res);
                return res;
            }
        }
        res = mStreamingProcessor->startStream(StreamingProcessor::RECORD,
                outputStreams);
    }
    if (res != OK) {
        ALOGE("%s: Camera %d: Unable to start streaming preview: %s (%d)",
                __FUNCTION__, mCameraId, strerror(-res), res);
        return res;
    }
//camera狀態機置成Parameters::PREVIEW。
    params.state = Parameters::PREVIEW;
    return OK;
}

該函式中主要做了,下面幾件事情。

  • 1.檢查輸入引數,camera狀態機,判斷camera現在工作狀態是否存在問題。
  • 2.獲取預覽StreamId,根據引數建立並更新previewStream。
  • 3.預先根據Camera的狀態建立jpeg流處理物件。
  • 4.根據上層傳下來的metadata引數,更新prview請求資訊。
  • 5.啟動該流,其實是後續的建立request請求物件,並激活request處理執行緒。
1).過程1-獲取StreamId。
int StreamingProcessor::getPreviewStreamId() const {
    Mutex::Autolock m(mMutex);
    return mPreviewStreamId;
}
//--------------------------------------
//這裡有下面幾個成員需要講一下。
    // Preview-related members
    Vector<int32_t> mActiveStreamIds;
    int32_t mPreviewRequestId;
    int mPreviewStreamId;
    CameraMetadata mPreviewRequest;
    sp<ANativeWindow> mPreviewWindow;

    int32_t mRecordingRequestId;
    int mRecordingStreamId;
    CameraMetadata mRecordingRequest;
    int mRecordingFrameCount;
    sp<ANativeWindow>  mRecordingWindow;
//------------------------------------

上面獲取PreviewStreamId地方直接返回了成員變數。mPreviewStreamId成員是在建立preview stream時,由Camera3Device 類中的createStream介面來賦值的。下面CreateStream會介紹到。下面有幾個非常重要的成員在這裡現做一下介紹。

  • 1.mActiveStreamIds:記錄系統中當前正在處於啟用狀態的StreamId,由於StreamingProcessor類,所以這裡一般只有預覽和錄影兩大Stream的id。
  • 2.mPreviewStreamId:對應預覽StreamId,在查詢流物件時,就需要通過這個id,找到對應的流物件,後面有介紹。
  • 3.mPreviewRequestId:預覽請求Id,就是service不斷的向HAL傳送Requeus請求,每一次請求都會將mPreviewRequestId加1,所以加到最後一旦超過了最大值,就會被重新賦值為對應的起始ID。例如:下面以preview為例,最大為20000000,最小為10000000,那麼最大下發10000000個請求,假如每一個請求都有幀回來,而且底層是以30fps幀率輸出的,這裡這麼多請求需要的總時間大概就是10000000 * 33ms =91.666h=3.819d。差不多如果底層以30fps幀率輸出,那麼要加到RequestId的終點,需要將近4天時間.
    static const int32_t kPreviewRequestIdStart = 10000000;
    static const int32_t kPreviewRequestIdEnd   = 20000000;

    static const int32_t kRecordingRequestIdStart  = 20000000;
    static const int32_t kRecordingRequestIdEnd    = 30000000;

    static const int32_t kCaptureRequestIdStart = 30000000;
    static const int32_t kCaptureRequestIdEnd   = 40000000;
  • 4.mPreviewRequest:preview的引數資訊,裡面包含了各種上層應用針對preview場景的配置引數,下發Request時,會將該metadata傳送到hal,解析成hal對應的配置引數。這個非常重要。(有必先要現學習一下CameraMetadata這個類)
  • 5.mPreviewWindow:預覽的buffer生產者代理物件,後面preview的buffer都是靠它來dequeue得到的。
  • 6.mRecordingRequestId:同preview一樣,recording也有自己的請求id,同樣也有起始和終點。
  • 7.mRecordingStreamId:同preview stream一樣,必須要有一個表示錄影流的id。
  • 8.mRecordingRequest:錄影場景中,上層下發下來的引數配置資訊,隨request一起下發給hal3,生成hal3對應的配置引數。
  • 9.mRecordingFrameCount:錄影時,回傳到cameraservie中的幀數量。(這裡錄影時,在cameraServcie本地建立一個BufferQueue,並且註冊了一個幀監聽物件,幀來了就會將mRecordingFrameCount+1)
  • 10.mRecordingWindow:錄影時根據bufferQueue得到的生產者本地物件建立的Surface物件,該surface物件。注意這裡是在CameraService本地建立了一個BufferQueue,不是在SurfaceFlinger,只是根據SurfaceFlinger代理物件獲取一個graphics buffer分配代理物件。
2).過程2-updatePreviewStream更新建立預覽Stream物件。
status_t StreamingProcessor::updatePreviewStream(const Parameters &params) {
    ATRACE_CALL();
    Mutex::Autolock m(mMutex);
ALOGD("%s, params.previewWidth = %d, params.previewHeight = %d", __FUNCTION__, params.previewWidth, params.previewHeight);
    status_t res;
    sp<CameraDeviceBase> device = mDevice.promote();
    if (device == 0) {
        ALOGE("%s: Camera %d: Device does not exist", __FUNCTION__, mId);
        return INVALID_OPERATION;
    }
    //這裡由於是第一次startPreview,而且mPreviewStreamId = NO_STREAM 
    if (mPreviewStreamId != NO_STREAM) {
        // Check if stream parameters have to change
        uint32_t currentWidth, currentHeight;
        //獲取流資訊,這裡獲取寬,高
        res = device->getStreamInfo(mPreviewStreamId,
                &currentWidth, &currentHeight, 0);
        if (res != OK) {
            ALOGE("%s: Camera %d: Error querying preview stream info: "
                    "%s (%d)", __FUNCTION__, mId, strerror(-res), res);
            return res;
        }
        //如果當前APP請求的寬高,和當前流資訊中寬高不一樣
        //就需要刪除當前stream,重新建立Stream。
        if (currentWidth != (uint32_t)params.previewWidth ||
                currentHeight != (uint32_t)params.previewHeight) {
            ALOGV("%s: Camera %d: Preview size switch: %d x %d -> %d x %d",
                    __FUNCTION__, mId, currentWidth, currentHeight,
                    params.previewWidth, params.previewHeight);
            res = device->waitUntilDrained();
            if (res != OK) {
                ALOGE("%s: Camera %d: Error waiting for preview to drain: "
                        "%s (%d)", __FUNCTION__, mId, strerror(-res), res);
                return res;
            }
            //刪除當前Stream
            res = device->deleteStream(mPreviewStreamId);
            if (res != OK) {
                return res;
            }
            //將mPreviewStreamId置為NO_STREAM,下面就重新建立STREAM。
            mPreviewStreamId = NO_STREAM;
        }
    }

    if (mPreviewStreamId == NO_STREAM) {
    //這裡直接呼叫Camera3Device中createStream介面。不過這裡要非常注意它建立的stream格式是CAMERA2_HAL_PIXEL_FORMAT_OPAQUE
        res = device->createStream(mPreviewWindow,
                params.previewWidth, params.previewHeight,
                CAMERA2_HAL_PIXEL_FORMAT_OPAQUE, &mPreviewStreamId);
    }
    //更新stream是否需要轉變,這裡是顯示方向,用來後面通知SurfaceFlinger旋轉方向。
    res = device->setStreamTransform(mPreviewStreamId,
            params.previewTransform);
    if (res != OK) {
        ALOGE("%s: Camera %d: Unable to set preview stream transform: "
                "%s (%d)", __FUNCTION__, mId, strerror(-res), res);
        return res;
    }

    return OK;
}

函式字面意思就是更新流資訊。

  • 1.如果previewStream已經存在,則先獲取當前PreviewStream流資訊的寬高和當前請求的寬高是否一致,如果一致的話,只設置顯示方向的標記。反之,等待Camera3Devie空閒下來,刪除當前previewStream,並將mPreviewStreamId置為NO_STREAM,緊接著下面就會直接建立一個新的previewStream。
  • 2.如果previewStream不存在,而且mPreviewStreamId = NO_STREAM,則需要重新建立PreviewStream。然後設定顯示方向的標記。這裡createStream程式碼太多,只貼出部分關鍵程式碼。

程式碼片段1

status_t Camera3Device::createStream(sp<ANativeWindow> consumer,
        uint32_t width, uint32_t height, int format, int *id) {
//------------------
    sp<Camera3OutputStream> newStream;
    //當formt == HAL_PIXEL_FORMAT_BLOB只在建立jpeg流的時候,才會走到。
    if (format == HAL_PIXEL_FORMAT_BLOB) {
    //此處省略
    } else {
    //mNextStreamId用來標記當前已經建立流的種類,該變數在camera3Device初始化已經初始化為0.
    //所以下面PreviewStream的id = 0,其中consumer就是我們前面說過的本地surface物件。
    //到這裡,應該已經猜到了dequeue,enqueue buffer操作都是在流物件中做的。感興趣的可以
    //可以檢視原始碼,確實是這樣來的。
        newStream = new Camera3OutputStream(mNextStreamId, consumer,
                width, height, format);
    }
    newStream->setStatusTracker(mStatusTracker);//設定流狀態跟蹤器,暫時不關心
    //mOutputStreams是camera3Device的stream倉庫管理器。
    res = mOutputStreams.add(mNextStreamId, newStream);
    if (res < 0) {
        SET_ERR_L("Can't add new stream to set: %s (%d)", strerror(-res), res);
        return res;
    }

    *id = mNextStreamId++;
    mNeedConfig = true;
//------------------------------
}

  到這裡上面程式碼片段res = mOutputStreams.add(mNextStreamId, newStream);,*id = mNextStreamId++;讓人深思。可以在這裡推斷出,各種streamId都是統一編號的,而且都是唯一的。每建立一個Stream物件都會根據StreamId儲存到mOutputStreams物件中,統一管理。下圖是常用的stream型別他們都是Camera3OutputStream物件。

序號 stream_id stream_object
1 mPreviewStreamId Camera3OutputStream(….,format)
2 mRecordingStreamId Camera3OutputStream(….,format)
3 mCaptureStreamId Camera3OutputStream(….,format)
4 mZslStreamId Camera3OutputStream(….,format),這裡要說名下的就是ZSL Stream,google工程師重新定義了ZSLStream的類Camera3ZslStream,該類繼承了Camera3OutputStream。只不過是是在在建立Stream的同時,建立了一個BufferQueue為後續ZSL拍照做準備。

  由於CameraService在同一個時刻只能有一個Client工作,下面的Camera2Client物件中包含的6個執行緒物件。它們各司其職,過程有點複雜,這裡只要知道各線層物件負責什麼功能就行了,目前可以不必深究。

  上圖中需要深入研究的是Camera3Device物件。該類物件會註冊到工廠類CameraDeviceFactory.cpp中,上面的各個執行緒使用的device物件就是通過工廠類獲取的。camera3Device中維護了一個流倉庫mOutputStream,所有輸出流都會根據索引儲存到這個流程庫中,緊接著將這些流打包到請求流佇列mRequestQueue佇列中。然後由RequestTread線層將幀請求傳送給hal.

3).過程3-根據預覽引數,更新previewStream
status_t StreamingProcessor::updatePreviewRequest(const Parameters &params) {
    ATRACE_CALL();
    status_t res;
    sp<CameraDeviceBase> device = mDevice.promote();
  //-------省略錯誤檢查程式碼
    Mutex::Autolock m(mMutex);
    if (mPreviewRequest.entryCount() == 0) {
        sp<Camera2Client> client = mClient.promote();
 //這裡省略很多程式碼,就是如果hal的api版本超過了CAMERA_DEVICE_API_VERSION_3_0
 //就會建立預設對應CAMERA3的preview metadata資料,反之建立CAMERA2的previewMetadata資料,其實還是從hal構建的預設的資料。緊接著,在下面在更新一下就行了。
// Use CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG for ZSL streaming case.
        if (client->getCameraDeviceVersion() >= CAMERA_DEVICE_API_VERSION_3_0) {
      }
  }
    //這裡會根據應用端下發下來的metadata更新預覽流Stream中的metadata資料
    res = params.updateRequest(&mPreviewRequest);
    //-------這裡省略一些錯誤檢查程式碼,下面這個操作非常重要
    res = mPreviewRequest.update(ANDROID_REQUEST_ID,
            &mPreviewRequestId, 1);
    //-------省略錯誤檢查程式碼
    return OK;
}

這裡主要完成建立更新preview metadata資料的工作,具體就下面兩點。

  • 1.根據hal的版本號,建立對應的preview metadata物件。
  • 2.根據上層app下發的metadata資料更新,preview metadata中的資料。
4)過程4.啟動預覽流,
status_t StreamingProcessor::startStream(StreamType type,
        const Vector<int32_t> &outputStreams) {
    ATRACE_CALL();
    status_t res;

    if (type == NONE) return INVALID_OPERATION;

    sp<CameraDeviceBase> device = mDevice.promote();
    ALOGV("%s: Camera %d: type = %d", __FUNCTION__, mId, type);
    Mutex::Autolock m(mMutex);

    // If a recording stream is being started up and no recording
    // stream is active yet, free up any outstanding buffers left
    // from the previous recording session. There should never be
    // any, so if there are, warn about it.
    //如果之前已經存在錄影流,但是沒有在啟用狀態,就釋放掉他們的buffer.
    bool isRecordingStreamIdle = !isStreamActive(mActiveStreamIds, mRecordingStreamId);
    bool startRecordingStream = isStreamActive(outputStreams, mRecordingStreamId);
    if (startRecordingStream && isRecordingStreamIdle) {
        releaseAllRecordingFramesLocked();
    }
    //根據是預覽還是錄影的型別,選擇對應的metadata物件,這裡我們傳進來的是PREVIEW,
    //拿到了之前我們更新的預覽metadata物件
    CameraMetadata &request = (type == PREVIEW) ?
            mPreviewRequest : mRecordingRequest;
    //下面這個也非常重要,記住目前我們只啟動了preview,那麼outputStreams只保留了
    //preview stream id.
    res = request.update(
        ANDROID_REQUEST_OUTPUT_STREAMS,
        outputStreams);

    res = request.sort();
    //根據preview metadta建立request請求,詳情請看下面解釋。
    res = device->setStreamingRequest(request);

    mActiveRequest = type;//當前的狀態是preview.
    mPaused = false;
    mActiveStreamIds = outputStreams;//當前啟用狀態只有preview Stream.
    return OK;
}

這裡是啟動流的前期處理,真正的操作還是在Camera3Device中。該函式主要做了下面3件事情

  • 1.判斷之前是否已經有錄影流,而且不是在啟用狀態,不是啟用狀態就釋放錄影流buffer.
  • 2.將儲存到outputStreams物件中的Stream ID儲存到預覽流中metadata中的ANDROID_REQUEST_OUTPUT_STREAMS資料項。該資料項會在camera3Device用來獲取獲取流id,進而獲取到Stream物件。
  • 3.呼叫camera3Device物件的setStreamingRequest介面,根據preview metadata物件建立request物件。

程式碼片段2-設定request物件(其實底層是建立)


status_t Camera3Device::setStreamingRequest(const CameraMetadata &request,
                                            int64_t* /*lastFrameNumber*/) {
    ATRACE_CALL();

    List<const CameraMetadata> requests;
    requests.push_back(request);
    return setStreamingRequestList(requests, /*lastFrameNumber*/NULL);
}

status_t Camera3Device::setStreamingRequestList(const List<const CameraMetadata> &requests,
                                                int64_t *lastFrameNumber) {
    ATRACE_CALL();
  //第二個引數告訴requeset執行緒這是一個迴圈請求的流物件。
    return submitRequestsHelper(requests, /*repeating*/true, lastFrameNumber);
}

status_t Camera3Device::submitRequestsHelper(
        const List<const CameraMetadata> &requests, bool repeating,
        /*out*/
        int64_t *lastFrameNumber) {
    ATRACE_CALL();
    Mutex::Autolock il(mInterfaceLock);
    Mutex::Autolock l(mLock);

    status_t res = checkStatusOkToCaptureLocked();
    if (res != OK) {
        // error logged by previous call
        return res;
    }
    //這裡requestList其實是一個List物件,可以理解成陣列,可惜這裡我們只有一個元素
    RequestList requestList;
  //關鍵是下面這個函式,程式碼在下面程式碼片段3,
    res = convertMetadataListToRequestListLocked(requests, /*out*/&requestList);
    if (res != OK) {
        // error logged by previous call
        return res;
    }
  //這preview要求是迴圈的
    if (repeating) {
        res = mRequestThread->setRepeatingRequests(requestList, lastFrameNumber);
    } else {
        res = mRequestThread->queueRequestList(requestList, lastFrameNumber);
    }

    if (res == OK) {
        waitUntilStateThenRelock(/*active*/true, kActiveTimeout);
        if (res != OK) {
            SET_ERR_L("Can't transition to active in %f seconds!",
                    kActiveTimeout/1e9);
        }
        ALOGV("Camera %d: Capture request %" PRId32 " enqueued", mId,
              (*(requestList.begin()))->mResultExtras.requestId);
    } else {
        CLOGE("Cannot queue request. Impossible.");
        return BAD_VALUE;
    }

    return res;
}

程式碼片段3-由metadata建立request物件列表

status_t Camera3Device::convertMetadataListToRequestListLocked(
        const List<const CameraMetadata> &metadataList, RequestList *requestList) {
    //-------
    int32_t burstId = 0;
    for (List<const CameraMetadata>::const_iterator it = metadataList.begin();
            it != metadataList.end(); ++it) {
        //上面引數陣列中只有一個previewStream,但是在錄影時,這裡還會有錄影Stream
        //如果是video snapshot,這裡還會有jpeg Stream.
        //這裡就是根據對應的metadata 包中包含的資料建立請求物件CaptureRequest.
        //函式精華請查閱下面程式碼。最好跳轉過去瞅瞅
        sp<CaptureRequest> newRequest = setUpRequestLocked(*it);
        if (newRequest == 0) {
            CLOGE("Can't create capture request");
            return BAD_VALUE;
        }

        // Setup burst Id and request Id
        //目前還不清楚這裡是幹嘛用的???????連拍場景嗎???
        newRequest->mResultExtras.burstId = burstId++;
        //找到previewRequest