基於Android5.0的Camera Framework原始碼分析 (二)

易冬發表於2017-05-08

上一次講解了一下CameraService的啟動過程,今天梳理一下Camera預覽的過程

StartPreview過程

首先,我們還是從應用層的使用入手
Camera.java (packages\apps\legacycamera\src\com\android\camera)

    Thread mCameraPreviewThread = new Thread(new Runnable() {
        public void run() {
            initializeCapabilities(); //初始化引數
            startPreview(); //啟動預覽
        }
    });複製程式碼

針對相機應用,採用了單獨的執行緒來處理預覽,猜測是為了加快預覽顯示的速度

private void startPreview() {
        ......
        // If we're previewing already, stop the preview first (this will blank
        // the screen).
        if (mCameraState != PREVIEW_STOPPED) stopPreview();

        setPreviewDisplay(mSurfaceHolder); //設定SurfaceHolder
        setDisplayOrientation();       //設定顯示方向
        ......
        setCameraParameters(UPDATE_PARAM_ALL); //設定引數

        // Inform the mainthread to go on the UI initialization.
        if (mCameraPreviewThread != null) {
            synchronized (mCameraPreviewThread) {
                mCameraPreviewThread.notify();
            }
        }

        try {
            Log.v(TAG, "startPreview");
            mCameraDevice.startPreview(); //啟動預覽,若失敗,關閉Camera
        } catch (Throwable ex) {
            closeCamera();
            throw new RuntimeException("startPreview failed", ex);
        }
        ......
    }複製程式碼

這裡就是APP中啟動預覽的過程,過程必然會是
java -> JNI -> cpp,
然後通過Binder機制執行到CameraClient中的
CameraClient.cpp (av\services\camera\libcameraservice\api1)

status_t CameraClient::startPreview() {
    LOG1("startPreview (pid %d)", getCallingPid());
    return startCameraMode(CAMERA_PREVIEW_MODE);
}複製程式碼

這裡傳入的是CAMERA_PREVIEW_MODE,列舉型別是在CameraClient.h中定義的

// camera operation mode
enum camera_mode {
        CAMERA_PREVIEW_MODE   = 0,  // frame automatically released
        CAMERA_RECORDING_MODE = 1,  // frame has to be explicitly released by releaseRecordingFrame()
    };複製程式碼

第一種是針對普通的預覽,第二種是針對錄影

// start preview or recording
status_t CameraClient::startCameraMode(camera_mode mode) {
    LOG1("startCameraMode(%d)", mode);
    Mutex::Autolock lock(mLock);
    status_t result = checkPidAndHardware();
    if (result != NO_ERROR) return result;

    switch(mode) {
        case CAMERA_PREVIEW_MODE:
            if (mSurface == 0 && mPreviewWindow == 0) { 
                LOG1("mSurface is not set yet.");
                // still able to start preview in this case.
            }
            return startPreviewMode(); //開始預覽模式
        case CAMERA_RECORDING_MODE:
            if (mSurface == 0 && mPreviewWindow == 0) {
                ALOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
                return INVALID_OPERATION;
            }
            return startRecordingMode(); //開始錄影模式
        default:
            return UNKNOWN_ERROR;
    }
}複製程式碼

這裡我們走的是預覽模式

status_t CameraClient::startPreviewMode() {
    LOG1("startPreviewMode"); //LOG1,一直忘記說了,這是有log開關用過setprop可以使用
    status_t result = NO_ERROR;

    // if preview has been enabled, nothing needs to be done
    if (mHardware->previewEnabled()) { //如果已經啟動預覽,不必重複
        return NO_ERROR;
    }

    if (mPreviewWindow != 0) {
        //適配顯示視窗的大小
        native_window_set_scaling_mode(mPreviewWindow.get(),
                NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
        //調整幀資料的方向
        native_window_set_buffers_transform(mPreviewWindow.get(),
                mOrientation);
    }
    mHardware->setPreviewWindow(mPreviewWindow); //設定mPreviewWindow為顯示視窗
    result = mHardware->startPreview();   //HAL層啟動預覽

    return result; //返回結果
}複製程式碼

這裡面涉及到native_window_set_scaling_mode,native_window_set_buffers_transform,直接跟程式碼,看註釋就可以理解,這部分涉及到顯示的一些內容,這裡暫時不做講解,native_window_set_scaling_mode設定模式為NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,native_window_set_buffers_transform是用來調整方向。
這裡有一個問題,mPreviewWindow是從何而來的呢?
還記得我們在應用層的startPreview()方法中會有這麼一個過程

        setPreviewDisplay(mSurfaceHolder);
        setDisplayOrientation();複製程式碼

這裡的setPreviewDisplay(mSurfaceHolder),中間的過程大家可以自己跟一下,最後會執行到

status_t CameraClient::setPreviewTarget(
        const sp<IGraphicBufferProducer>& bufferProducer) {
    ......
    sp<IBinder> binder;
    sp<ANativeWindow> window;
    if (bufferProducer != 0) {
        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.
        window = new Surface(bufferProducer, /*controlledByApp*/ true); //這個傢伙
    }
    return setPreviewWindow(binder, window);
}複製程式碼
status_t CameraClient::setPreviewWindow(const sp<IBinder>& binder,
        const sp<ANativeWindow>& window) {
    Mutex::Autolock lock(mLock);
    ......
    if (window != 0) {
        result = native_window_api_connect(window.get(), NATIVE_WINDOW_API_CAMERA);
        if (result != NO_ERROR) {
            ALOGE("native_window_api_connect failed: %s (%d)", strerror(-result),
                    result);
            return result;
        }
    }

    // If preview has been already started, register preview buffers now.
    if (mHardware->previewEnabled()) {
        if (window != 0) {
            native_window_set_scaling_mode(window.get(),
                    NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
            native_window_set_buffers_transform(window.get(), mOrientation);
            result = mHardware->setPreviewWindow(window);
        }
    }
    if (result == NO_ERROR) {
        // Everything has succeeded.  Disconnect the old window and remember the
        // new window.
        disconnectWindow(mPreviewWindow);
        mSurface = binder;
        mPreviewWindow = window; //這裡便是賦值的操作了,後面我們操作的mPreviewWindow 
    } else {
        // Something went wrong after we connected to the new window, so
        // disconnect here.
        disconnectWindow(window);
    }
    return result;
}複製程式碼

是不是看的很眼熟,和startPreviewMode()的過程有點相似。這就是mPreviewWindow的賦值過程。
回到setPreviewMode()函式中,其中主要的過程是這兩個
mHardware->setPreviewWindow(mPreviewWindow);
result = mHardware->startPreview();
這裡都是HAL層的處理,將視窗傳下去,然後啟動預覽,最後資料就可以投射到這個預覽視窗上了。
我們繼續往下看一下,
CameraHardwareInterface.h (av\services\camera\libcameraservice\device1)

    /** Set the ANativeWindow to which preview frames are sent */
    status_t setPreviewWindow(const sp<ANativeWindow>& buf)
    {
        ALOGV("%s(%s) buf %p", __FUNCTION__, mName.string(), buf.get());

        if (mDevice->ops->set_preview_window) {
            mPreviewWindow = buf;
            mHalPreviewWindow.user = this;
            ALOGV("%s &mHalPreviewWindow %p mHalPreviewWindow.user %p", __FUNCTION__,
                    &mHalPreviewWindow, mHalPreviewWindow.user);
            return mDevice->ops->set_preview_window(mDevice,
                    buf.get() ? &mHalPreviewWindow.nw : 0);
        }
        return INVALID_OPERATION;
    }複製程式碼
   /**
     * Start preview mode.
     */
    status_t startPreview()
    {
        ALOGV("%s(%s)", __FUNCTION__, mName.string());
        if (mDevice->ops->start_preview)
            return mDevice->ops->start_preview(mDevice);
        return INVALID_OPERATION;
    }複製程式碼

這是一個空殼,我們去看具體的實現,這裡我們看下android5.1原始碼中Qcom的實現,由於針對HAL層的不同廠商有不同的處理方式,在這裡我們就隨便找個目錄下的進行分析,旨在看流程,理解一些基礎的內容,
QCamera2Hal.cpp (\device\asus\flo\camera\qcamera2\hal)

#include "QCamera2Factory.h"

static hw_module_t camera_common = {
    tag: HARDWARE_MODULE_TAG,
    module_api_version: CAMERA_MODULE_API_VERSION_1_0,
    hal_api_version: HARDWARE_HAL_API_VERSION,
    id: CAMERA_HARDWARE_MODULE_ID,
    name: "QCamera Module",
    author: "Qualcomm Innovation Center Inc",
    methods: &qcamera::QCamera2Factory::mModuleMethods,
    dso: NULL,
    reserved:  {0},
};

camera_module_t HAL_MODULE_INFO_SYM = {
    common: camera_common,
    get_number_of_cameras: qcamera::QCamera2Factory::get_number_of_cameras,
    get_camera_info: qcamera::QCamera2Factory::get_camera_info,
    set_callbacks: NULL,
    get_vendor_tag_ops: NULL,
    open_legacy: NULL,
    reserved: {0}
};複製程式碼

這裡提一下HAL_MODULE_INFO_SYM這個東西,本身就是一個定義在hardware.h下的一個巨集,看註釋,意思很明顯

define HAL_MODULE_INFO_SYM HMI 
//.so中將一個符號HMI,獲取此符號的地址,就獲取到了對應的hw_module_t地址複製程式碼

HAL_MODULE_INFO_SYM,這個是HAL 編譯生成的so的入口,CameraService會獲取這個來操作so
camera_common是針對HAL規範定義的一些內容。
Camera的open指向的是&qcamera::QCamera2Factory::mModuleMethods中的open方法,如下

struct hw_module_methods_t QCamera2Factory::mModuleMethods = {
    open: QCamera2Factory::camera_device_open,
};複製程式碼

這個方法中開啟裝置節點,我們可以看到HAL層中open的過程是很有講究的,也不能這麼說,應為HAL的處理方式基本上都是如此。

int QCamera2Factory::camera_device_open(
    const struct hw_module_t *module, const char *id,
    struct hw_device_t **hw_device)
{
    if (module != &HAL_MODULE_INFO_SYM.common) {
        ALOGE("Invalid module. Trying to open %p, expect %p",
            module, &HAL_MODULE_INFO_SYM.common);
        return INVALID_OPERATION;
    }
    if (!id) {
        ALOGE("Invalid camera id");
        return BAD_VALUE;
    }
    return gQCamera2Factory.cameraDeviceOpen(atoi(id), hw_device);
}複製程式碼
int QCamera2Factory::cameraDeviceOpen(int camera_id,
                    struct hw_device_t **hw_device)
{
    int rc = NO_ERROR;
    if (camera_id < 0 || camera_id >= mNumOfCameras)
        return BAD_VALUE;
    //到這裡才是真正的HAL層的建立,可見HAL層的建立和open操作是相關的
    QCamera2HardwareInterface *hw = new QCamera2HardwareInterface(camera_id);
    if (!hw) {
        ALOGE("Allocation of hardware interface failed");
        return NO_MEMORY;
    }
    rc = hw->openCamera(hw_device);
    if (rc != NO_ERROR) {
        delete hw;
    }
    return rc;
}複製程式碼

之前在CameraService過程中提到的CameraHardwareInterface空殼就是為這個QCamera2HardwareInterface準備的,具體實現全部都在這個類中。
關於QCamera2HardwareInterface的內容我們在後面會講到,這裡暫且先放一下,接著上面的
mHardware->setPreviewWindow(mPreviewWindow);
result = mHardware->startPreview();
經過CameraHardwareInterface後
mDevice->ops->set_preview_window(mDevice, buf.get() ? &mHalPreviewWindow.nw : 0);
mDevice->ops->start_preview(mDevice);
然後經過QCamera2HardwareInterface中的mCameraOps函式指標對應表
set_preview_window: QCamera2HardwareInterface::set_preview_window
start_preview: QCamera2HardwareInterface::start_preview
所以會呼叫到

int QCamera2HardwareInterface::set_preview_window(struct camera_device *device,
        struct preview_stream_ops *window)
{
    int rc = NO_ERROR;
    QCamera2HardwareInterface *hw =
        reinterpret_cast<QCamera2HardwareInterface *>(device->priv);
    if (!hw) {
        ALOGE("%s: NULL camera device", __func__);
        return BAD_VALUE;
    }

    hw->lockAPI();
    rc = hw->processAPI(QCAMERA_SM_EVT_SET_PREVIEW_WINDOW, (void *)window);
    if (rc == NO_ERROR) {
        hw->waitAPIResult(QCAMERA_SM_EVT_SET_PREVIEW_WINDOW);
        rc = hw->m_apiResult.status;
    }
    hw->unlockAPI();

    return rc;
}複製程式碼

這裡會經過一輪狀態機,暫時先不講,然後執行到

int QCamera2HardwareInterface::setPreviewWindow(
        struct preview_stream_ops *window)
{
    mPreviewWindow = window;
    return NO_ERROR;
}複製程式碼

同理,startPreview也會執行到

int QCamera2HardwareInterface::startPreview()
{
    int32_t rc = NO_ERROR;
    ALOGD("%s: E", __func__);
    // start preview stream
    if (mParameters.isZSLMode() && mParameters.getRecordingHintValue() !=true) {
        rc = startChannel(QCAMERA_CH_TYPE_ZSL);
    } else {
        rc = startChannel(QCAMERA_CH_TYPE_PREVIEW);
    }
    ALOGD("%s: X", __func__);
    return rc;
}複製程式碼

這裡開啟通道,可以理解成資料通道,ZSL是之前沒有的,所謂ZSL就是觸發拍照後不停止預覽。
這裡看到會根據當前是都支援ZSL模式而進入不同的通道,我們這裡就看QCAMERA_CH_TYPE_PREVIEW,startChannel

int32_t QCamera2HardwareInterface::startChannel(qcamera_ch_type_enum_t ch_type)
{
    int32_t rc = UNKNOWN_ERROR;
    if (m_channels[ch_type] != NULL) {
        rc = m_channels[ch_type]->start();
    }

    return rc;
}複製程式碼

m_channels是不同的通道的例項的陣列,這裡如果沒有PREVIEW的channel就直接return,豈不是無法啟動預覽,這個流程感覺有點不對勁。但是這整個過程跟下來也沒有看到m_channels相關的初始化過程。

這個問題出在我剛才從CameraHardwareInterface跟到QCamera2HardwareInterface的時候跳過的一個內容-----狀態機,在狀態機中會執行一個preparePreview()的操作

int32_t QCamera2HardwareInterface::preparePreview()
{
    int32_t rc = NO_ERROR;

    if (mParameters.isZSLMode() && mParameters.getRecordingHintValue() !=true) {
        rc = addChannel(QCAMERA_CH_TYPE_ZSL); //這裡我們就新增了一個channel,當然這裡是ZSL的
        if (rc != NO_ERROR) {
            return rc;
        }
    } else {
        bool recordingHint = mParameters.getRecordingHintValue(); //recording
        if(recordingHint) {
            rc = addChannel(QCAMERA_CH_TYPE_SNAPSHOT);  //錄影中是可以拍照的,需要snapshot channel
            if (rc != NO_ERROR) {
                return rc;
            }

            rc = addChannel(QCAMERA_CH_TYPE_VIDEO); //video channel
            if (rc != NO_ERROR) {
                delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
                return rc;
            }
        }

        rc = addChannel(QCAMERA_CH_TYPE_PREVIEW); //新增preview  channel
        if (rc != NO_ERROR) {
            if (recordingHint) {
                delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
                delChannel(QCAMERA_CH_TYPE_VIDEO);
            }
            return rc;
        }

    }

    return rc;
}複製程式碼

在addchannel()的過程中會根據不同的channel型別建立不同的例項,這裡我們直接看從addChannel()轉到的addPreviewChannel()函式

int32_t QCamera2HardwareInterface::addPreviewChannel()
{
    int32_t rc = NO_ERROR;
    QCameraChannel *pChannel = NULL; //初始化一個QCameraChanel,後面要使用
    if (m_channels[QCAMERA_CH_TYPE_PREVIEW] != NULL) {
        // if we had preview channel before, delete it first
        delete m_channels[QCAMERA_CH_TYPE_PREVIEW]; //如果之前preview channel存在,幹掉
        m_channels[QCAMERA_CH_TYPE_PREVIEW] = NULL;
    }
    pChannel = new QCameraChannel(mCameraHandle->camera_handle,
                                  mCameraHandle->ops); 
    //new 一個新的channel
    .....
    // meta data stream always coexists with preview if applicable
    rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_METADATA,
                            metadata_stream_cb_routine, this);
    //新增metadata stream cb
    if (rc != NO_ERROR) {
        ALOGE("%s: add metadata stream failed, ret = %d", __func__, rc);
        delete pChannel;
        return rc;
    }

    if (isNoDisplayMode()) { //判斷是否為不需要顯示的模式
        rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_PREVIEW,
                                nodisplay_preview_stream_cb_routine, this);
    } else {
        //這裡新增preview stream cb到channel中
        rc = addStreamToChannel(pChannel, CAM_STREAM_TYPE_PREVIEW,
                                preview_stream_cb_routine, this);
    }
    if (rc != NO_ERROR) {
        ALOGE("%s: add preview stream failed, ret = %d", __func__, rc);
        delete pChannel;
        return rc;
    }

    m_channels[QCAMERA_CH_TYPE_PREVIEW] = pChannel; //維護m_channels資料
    return rc;
}複製程式碼

這裡註冊的preview_stream_cb_routine回撥,這之後的過程我們暫時先不去看,瞭解這部分之後,回到之前chanel 的start(),最後會執行到QCameraChannel::start()方法,這裡往下的內容我們暫時不往下看,知道這個過程中會執行資料採集,然後返回給HAL層就行了,HAL針對底層返回的資料,我們在哪裡獲取,做什麼對應的處理呢?找到之前註冊的Callback.
QCamera2HWICallbacks.cpp (\device\asus\flo\camera\qcamera2\hal)

void QCamera2HardwareInterface::preview_stream_cb_routine(mm_camera_super_buf_t *super_frame,
                                                          QCameraStream * stream,
                                                          void *userdata)
{
    ALOGD("[KPI Perf] %s : BEGIN", __func__);
    int err = NO_ERROR;
    QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
    QCameraGrallocMemory *memory = (QCameraGrallocMemory *)super_frame->bufs[0]->mem_info;
    ......
    mm_camera_buf_def_t *frame = super_frame->bufs[0];
    ......
    if (!pme->needProcessPreviewFrame()) {
        ALOGE("%s: preview is not running, no need to process", __func__);
        stream->bufDone(frame->buf_idx);
        free(super_frame);
        return;
    }
    if (pme->needDebugFps()) {
        pme->debugShowPreviewFPS();
    }
    int idx = frame->buf_idx;
    pme->dumpFrameToFile(frame->buffer, frame->frame_len,
                         frame->frame_idx, QCAMERA_DUMP_FRM_PREVIEW);
    //這裡的註釋很明顯,displayer buffer而這個buffer就是我們需要投射到螢幕上的資料
    // Display the buffer.
    int dequeuedIdx = memory->displayBuffer(idx); //這部分涉及到顯示的過程,這裡不做贅述
    if (dequeuedIdx < 0 || dequeuedIdx >= memory->getCnt()) {
        ALOGD("%s: Invalid dequeued buffer index %d from display",
              __func__, dequeuedIdx);
    } else {
        // Return dequeued buffer back to driver
        err = stream->bufDone(dequeuedIdx);
        if ( err < 0) {
            ALOGE("stream bufDone failed %d", err);
        }
    }
    //針對上層設定的datacallback過程做些處理
    // Handle preview data callback
    if (pme->mDataCb != NULL && pme->msgTypeEnabledWithLock(CAMERA_MSG_PREVIEW_FRAME) > 0) {
        ......
        qcamera_callback_argm_t cbArg;
        memset(&cbArg, 0, sizeof(qcamera_callback_argm_t));
        cbArg.cb_type = QCAMERA_DATA_CALLBACK;
        cbArg.msg_type = CAMERA_MSG_PREVIEW_FRAME;
        cbArg.data = data;
        if ( previewMem ) {
            cbArg.user_data = previewMem;
            cbArg.release_cb = releaseCameraMemory;
        }
        cbArg.cookie = pme;
        pme->m_cbNotifier.notifyCallback(cbArg); //封裝完之後往上甩
    }

    free(super_frame);
    ALOGD("[KPI Perf] %s : END", __func__);
    return;
}複製程式碼

這就是在addPreviewChannel的過程中新增的preview stream callback,當然還有metadata的,暫時先看preview的這個。這裡面作的操作就是顯示預覽資料到視窗中,然後對設定下面的preview callback做對應的callback處理.

講到這裡,Camera的預覽過程基本上就結束了,關於底層如果採集資料以及HAL中一些其他的內容,在這裡沒有講解,主要是要理解這個過程,之後再每一個過程中在往下學習。

備註

本文中程式碼使用的是Android5.0原始程式碼,最新的Android N版本除了把CameraService單獨拎出來,其他的內容基本上大同小異。

版權宣告:本文為博主原創文章,未經博主允許不得轉載。

個人微信公眾號,歡迎大家掃碼關注,Android技術交流或者諮詢。


基於Android5.0的Camera Framework原始碼分析 (二)
個人微信公眾號

相關文章