Android 5.0 Camera系統原始碼分析(3):Camera預覽流程控制流
1. 前言
本文分析的是Android系統原始碼,從frameworks層到hal層,記錄了Camera進入預覽模式的重點程式碼,主要為控制流程的程式碼,有關影象buffer的傳遞暫不涉及,硬體平臺基於mt6735。由於某些函式比較複雜,在貼出程式碼時會適當對其進行簡化。
2. APP層
這裡將分析app層令Camera進入預覽模式的兩個重點api:setPreviewDisplay和startPreview
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
3. setPreviewDisplay函式分析
預覽影象最終是要在lcd上顯示的,想要在lcd上顯示影象就需要用到Surface 。填充Surface有兩種方法,一種是註冊callback函式,預覽資料將在callback函式中返回,得到資料後再把它送到Surface裡面;另一種是在開始預覽之前就為底層設定好Surface,底層獲取資料後直接把資料送到Surface裡面,為底層設定好Surface就是setPreviewDisplay的作用,
3.1 frameworks層
先來看frameworks層的實現
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
if (holder != null) {
setPreviewSurface(holder.getSurface());
} else {
setPreviewSurface((Surface)null);
}
}
setPreviewSurface是一個jni函式,它的實現在android_hardware_Camera.cpp中
static void android_hardware_Camera_setPreviewSurface(JNIEnv *env, jobject thiz, jobject jSurface)
{
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");
}
}
// pass the buffered IGraphicBufferProducer to the camera service
status_t Camera::setPreviewTarget(const sp<IGraphicBufferProducer>& bufferProducer)
{
sp <ICamera> c = mCamera;
return c->setPreviewTarget(bufferProducer);
}
// set the buffer consumer that the preview will use
status_t CameraClient::setPreviewTarget(
const sp<IGraphicBufferProducer>& bufferProducer) {
sp<IBinder> binder;
sp<ANativeWindow> window;
if (bufferProducer != 0) {
binder = bufferProducer->asBinder();
window = new Surface(bufferProducer, /*controlledByApp*/ true);
}
return setPreviewWindow(binder, window);
}
ANativeWindow顧名思義“本地視窗”,Surface類繼承了ANativeWindow類。按照網上的說法,ANativeWindow類是連線OpenGL和Android視窗系統的橋樑,即OpenGL需要通過ANativeWindow類來間接地操作Android視窗系統。但我們接下來要操作ANativeWindow的不是OpenGL,而是CameraClient
status_t CameraClient::setPreviewWindow(const sp<IBinder>& binder,
const sp<ANativeWindow>& window) {
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);
}
}
return result;
}
/** Set the ANativeWindow to which preview frames are sent */
status_t setPreviewWindow(const sp<ANativeWindow>& buf)
{
mPreviewWindow = buf;
mHalPreviewWindow.user = this;
return mDevice->ops->set_preview_window(mDevice,
buf.get() ? &mHalPreviewWindow.nw : 0);
}
ANativeWindow最終儲存在mPreviewWindow變數中,而傳到Hal層的則是mHalPreviewWindow.nw 操作集,Hal層將通過它來間接的操作mPreviewWindow。
mDevice就是上篇博文Camera開啟流程中最後講到的從Hal返回的mDevice物件,而它的ops指標指向的是gCameraDevOps結構體,從這裡開始進入Hal層
3.2 Hal層
gCameraDevOps就在Cam1Device.cpp中定義
static mtk_camera_device_ops const
gCameraDevOps =
{
#define OPS(name) name: camera_##name
{
OPS(set_preview_window),
OPS(set_callbacks),
OPS(enable_msg_type),
OPS(disable_msg_type),
OPS(msg_type_enabled),
OPS(start_preview),
OPS(stop_preview),
OPS(preview_enabled),
OPS(store_meta_data_in_buffers),
OPS(start_recording),
OPS(stop_recording),
OPS(recording_enabled),
OPS(release_recording_frame),
OPS(auto_focus),
OPS(cancel_auto_focus),
OPS(take_picture),
OPS(cancel_picture),
OPS(set_parameters),
OPS(get_parameters),
OPS(put_parameters),
OPS(send_command),
OPS(release),
OPS(dump)
},
OPS(mtk_set_callbacks),
#undef OPS
};
可以看到有關Camera的所有操作都這這裡,接著看函式set_preview_window的實現
// Implementation of camera_device_ops
static int camera_set_preview_window(
struct camera_device * device,
struct preview_stream_ops *window
)
{
int err = -EINVAL;
Cam1Device*const pDev = Cam1Device::getDevice(device);
if ( pDev )
{
err = pDev->setPreviewWindow(window);
}
return err;
}
Cam1Device::getDevice函式獲取到的將是DefaultCam1Device物件,而setPreviewWindow函式則在它的父類Cam1DeviceBase中實現
/******************************************************************************
* Set the preview_stream_ops to which preview frames are sent.
******************************************************************************/
status_t
Cam1DeviceBase::
setPreviewWindow(preview_stream_ops* window)
{
status_t status = initDisplayClient(window);
if ( OK == status && previewEnabled() && mpDisplayClient != 0 )
{
status = enableDisplayClient();
}
return status;
}
第9行,初始化DisplayClient
第11行,通知DisplayClient開始工作
重點關注下函式initDisplayClient 的實現
status_t
Cam1DeviceBase::
initDisplayClient(preview_stream_ops* window)
{
status_t status = OK;
Size previewSize;
// [1] Check to see whether the passed window is NULL or not.
if ( ! window )
{
if ( mpDisplayClient != 0 )
{
mpDisplayClient->uninit();
mpDisplayClient.clear();
}
status = OK;
goto lbExit;
}
// [2] Get preview size.
if ( ! queryPreviewSize(previewSize.width, previewSize.height) )
{
status = DEAD_OBJECT;
goto lbExit;
}
// [3] Initialize Display Client.
if ( mpDisplayClient != 0 )
{
......
}
// [3.1] create a Display Client.
mpDisplayClient = IDisplayClient::createInstance();
if ( mpDisplayClient == 0 )
{
MY_LOGE("Cannot create mpDisplayClient");
status = NO_MEMORY;
goto lbExit;
}
// [3.2] initialize the newly-created Display Client.
if ( ! mpDisplayClient->init() )
{
MY_LOGE("mpDisplayClient init() failed");
mpDisplayClient->uninit();
mpDisplayClient.clear();
status = NO_MEMORY;
goto lbExit;
}
// [3.3] set preview_stream_ops & related window info.
if ( ! mpDisplayClient->setWindow(window, previewSize.width, previewSize.height, queryDisplayBufCount()) )
{
status = INVALID_OPERATION;
goto lbExit;
}
// [3.4] set Image Buffer Provider Client if it exist.
if ( mpCamAdapter != 0 && ! mpDisplayClient->setImgBufProviderClient(mpCamAdapter) )
{
status = INVALID_OPERATION;
goto lbExit;
}
status = OK;
lbExit:
if ( OK != status )
{
MY_LOGD("Cleanup...");
......
}
return status;
}
initDisplayClient函式都做了些什麼事情註釋已經寫得很清楚
第31-47行,建立並初始化DisplayClient,其中DisplayClient是影象消費者,由它負責將影象資料送往Surface
第48-53行,DisplayClient想要操作Surface只能通過preview_stream_ops,也就是從上層傳下來mHalPreviewWindow.nw操作集,setWindow函式會通過preview_stream_ops對Surface設定一些引數,並把preview_stream_ops儲存在DisplayClient的mpStreamOps變數中,以後用到的時候才找得到。
第54-59行,DisplayClient作為消費者,那麼就會有生產者,也就是CamAdapter。由CamAdapter提供影象資料,再由DisplayClient將資料送往Surface。但由於這個時候的 mpCamAdapter 為空,所以這裡的setImgBufProviderClient函式暫時不會被呼叫。
4. startPreview函式分析
app層通過呼叫startPreview函式來進入預覽模式,與setPreviewWindow的流程一樣,最終會調到Cam1DeviceBase的startPreview函式
4.1 Cam1DeviceBase::startPreview函式分析
/******************************************************************************
* Start preview mode.
******************************************************************************/
status_t
Cam1DeviceBase::
startPreview()
{
status_t status = OK;
if ( ! onStartPreview() )
{
MY_LOGE("onStartPreviewLocked() fail");
status = INVALID_OPERATION;
goto lbExit;
}
if ( mpDisplayClient == 0 )
{
MY_LOGD("DisplayClient is not ready.");
}
else if ( OK != (status = enableDisplayClient()) )
{
goto lbExit;
}
......
// startPreview in Camera Adapter.
{
status = mpCamAdapter->startPreview();
if ( OK != status )
{
MY_LOGE("startPreview() in CameraAdapter returns: [%s(%d)]", ::strerror(-status), -status);
goto lbExit;
}
}
......
status = OK;
lbExit:
if ( OK != status )
{
......
}
MY_LOGI("- status(%d)", status);
return status;
}
第10行, onStartPreview函式主要就是建立並初始化 CameraAdapter
第21行, 通知DisplayClient開始工作
第30行, mpCamAdapter->startPreview函式工作量巨大,包含了初始化buffer、3A,設定ISP和sensor驅動進入預覽模式等工作。
先看CameraAdapter的初始化
DefaultCam1Device::
onStartPreview()
{
bool ret = false;
......
// (2) Initialize Camera Adapter.
if ( ! initCameraAdapter() )
{
MY_LOGE("NULL Camera Adapter");
goto lbExit;
}
//
ret = true;
lbExit:
return ret;
}
bool
Cam1DeviceBase::
initCameraAdapter()
{
bool ret = false;
// Create & init a new CamAdapter.
mpCamAdapter = ICamAdapter::createInstance(mDevName, mi4OpenId, mpParamsMgr);
if ( mpCamAdapter != 0 && mpCamAdapter->init() )
{
// (.1) init.
mpCamAdapter->setCallbacks(mpCamMsgCbInfo);
mpCamAdapter->enableMsgType(mpCamMsgCbInfo->mMsgEnabled);
// (.2) Invoke its setParameters
if ( OK != mpCamAdapter->setParameters() )
{
// If fail, it should destroy instance before return.
MY_LOGE("mpCamAdapter->setParameters() fail");
goto lbExit;
}
// (.3) Send to-do commands.
{
Mutex::Autolock _lock(mTodoCmdMapLock);
for (size_t i = 0; i < mTodoCmdMap.size(); i++)
{
CommandInfo const& rCmdInfo = mTodoCmdMap.valueAt(i);
MY_LOGD("send queued cmd(%#x),args(%d,%d)", rCmdInfo.cmd, rCmdInfo.arg1, rCmdInfo.arg2);
mpCamAdapter->sendCommand(rCmdInfo.cmd, rCmdInfo.arg1, rCmdInfo.arg2);
}
mTodoCmdMap.clear();
}
// (.4) [DisplayClient] set Image Buffer Provider Client if needed.
if ( mpDisplayClient != 0 && ! mpDisplayClient->setImgBufProviderClient(mpCamAdapter) )
{
MY_LOGE("mpDisplayClient->setImgBufProviderClient() fail");
goto lbExit;
}
}
ret = true;
lbExit:
return ret;
}
建立CamAdapter例項並對它進行初始化。其中第35-40行,之前在setPreviewWindow裡沒機會呼叫的mpDisplayClient->setImgBufProviderClient函式將在這裡呼叫。DisplayClient和CamAdapter將會通過setImgBufProviderClient函式關聯起來,也就是告訴DisplayClient影象資料將由CamAdapter提供。至於CamAdpter如何獲取影象資料和DisplayClient如何將資料送往Surface將在以後解析。
4.2 mpCamAdapter->startPreview函式分析
既然資料由CamAdapter提供,那麼怎麼告訴它開始向DisplayClient提供資料呢,還的繼續分析mpCamAdapter->startPreview函式
status_t
CamAdapter::
startPreview()
{
return mpStateManager->getCurrentState()->onStartPreview(this);
}
status_t
StateIdle::
onStartPreview(IStateHandler* pHandler)
{
......
status = pHandler->onHandleStartPreview();
......
return status;
}
mpStateManager->getCurrentState函式獲取到的是idle狀態,在上文提到 mpCamAdapter->init函式中設定。而 StateIdle::onStartPreview函式將會回撥CamAdapter的onHandleStartPreview函式,這個函式很長,非常長,相當長。
/******************************************************************************
* CamAdapter::startPreview() -> IState::onStartPreview() ->
* IStateHandler::onHandleStartPreview() -> CamAdapter::onHandleStartPreview()
*******************************************************************************/
status_t
CamAdapter::
onHandleStartPreview()
{
......
mpPass2Node = Pass2Node::createInstance(PASS2_FEATURE);
mpCamGraph = ICamGraph::createInstance(
getOpenId(),
mUserName.string());
mpPass1Node = Pass1Node::createInstance(p1NodeInitCfg);
mpCamGraph->setBufferHandler( PASS1_RESIZEDRAW, mpAllocBufHdl);
mpCamGraph->setBufferHandler( PASS1_FULLRAW, mpAllocBufHdl);
mpCamGraph->connectData( PASS1_RESIZEDRAW, CONTROL_RESIZEDRAW, mpPass1Node, mpDefaultCtrlNode);
mpCamGraph->connectData( CONTROL_PRV_SRC, PASS2_PRV_SRC, mpDefaultCtrlNode, mpPass2Node);
mpCamGraph->connectNotify( PASS1_START_ISP, mpPass1Node, mpDefaultCtrlNode);
mpCamGraph->connectNotify( PASS1_STOP_ISP, mpPass1Node, mpDefaultCtrlNode);
mpCamGraph->connectNotify( PASS1_EOF, mpPass1Node, mpDefaultCtrlNode);
if ( !mpCamGraph->init() ) {
......
}
if ( !mpCamGraph->start() ) {
......
}
lbExit:
......
return ret;
}
暫時先把那些亂七八糟的引數設定的程式碼忽略掉,重點關注下 Pass1Node、 Pass2Node和DefaultCtlNode,以及作為各個Node通訊的橋樑的CamGraph。
CamGraph代表了整個系統,而使用不同的Node來描述不同的buffer處理, 所有的Node都需要連線到CamGraph。各個Node之間的通訊就需要用到 connectData和 connectNotify函式, connectData為兩個node之間buffer傳輸的連線,而 connectNotify為兩個node之間訊息傳輸的連線。
例如第18行呼叫了connectData(PASS1_RESIZEDRAW, CONTROL_RESIZEDRAW, mpPass1Node,mpDefaultCtrlNode)之後Pass1Node和DefaultCtrlNode就連線在一起,事件是 PASS1_RESIZEDRAW,也就是說當Pass1Node呼叫handlePostBuffer(PASS1_RESIZEDRAW, buffer)的時候,DefaultCtrlNode裡面的onPostBuffer函式將會接受到Pass1Node的buffer。
同理第20行呼叫了connectNotify( PASS1_START_ISP, mpPass1Node, mpDefaultCtrlNode),事件是 PASS1_START_ISP,當Pass1Node呼叫handleNotify(PASS1_START_ISP)的時候,DefaultCtrlNode裡面的onNotify函式將會接收到 PASS1_START_ISP訊息。
connectData和connectNotify的不同之處在於,一個可以傳輸整個buffer,但只能一對一連線,一個只能傳輸訊息,但可以一對多連線,這兩個函式的實現這裡就不解析了,裡面各種子類、父類的關係比較複雜,整理起來比較麻煩。需要關注的是 mpCamGraph->init和 mpCamGraph->start這兩個函式,先來看看init
MBOOL
ICamGraph::
init()
{
return mpImpl->init();
}
這裡的 mpImpl指的是ICamGraphImpl
MBOOL
ICamGraphImpl::
init()
{
Mutex::Autolock _l(mLock);
MY_LOGD("init +");
MY_ASSERT_STATE( mState == State_Connected, mState );
MBOOL ret = MTRUE;
vector< ICamNodeImpl* >::const_iterator iter;
for( iter = mvNodeImpls.begin(); iter != mvNodeImpls.end(); iter++ )
{
MY_ASSERT_NODE_OP( ret, (*iter), init );
}
lbExit:
if( !ret )
{
......
}
else
{
mState = State_Initiated;
}
MY_LOGD("init -");
return ret;
}
mvNodeImpls裡儲存的是ICamThreadImpl物件, 每一個ICamThreadImpl代表一個CamNode,例如Pass1Node。這個函式所做的事情就是迴圈遍歷所有的ICamThreadImpl,並且呼叫它們的init函式
MBOOL
ICamThreadImpl::
init()
{
Mutex::Autolock _l(mLock);
MY_ASSERT_STATE( mState == State_Connected, mState );
MY_LOGV("init");
MY_ASSERT( mpSelf->onInit() );
MY_ASSERT( mpThread->createThread()
&& mpThread->sendThreadCmd(TCmd_Sync)
&& mpThread->sendThreadCmd(TCmd_Init)
&& mpThread->sendThreadCmd(TCmd_Sync));
mState = State_Initiated;
return MTRUE;
}
ICamThreadImpl裡的mySelf成員就指向了它所代表的CamNode,例如Pass1Node。也就是說接下來所有儲存在 mvNodeImpls裡面的CamNode的onInit函式都會被呼叫。儲存在mvNodeImpls裡面的CamNode有很多,例如Pass1Node、Pass2Node、DefaultCtrlNode等。Pass1Node負責和Sensor Driver、ISP Driver打交道,進入預覽模式的重點工作都由它來完成,所以這裡只分析Pass1Node,來看看Pass1Node的onInit函式
MBOOL
Pass1NodeImpl::
onInit()
{
......
mpIspSyncCtrlHw = IspSyncControlHw::createInstance(getSensorIdx());
mpIspSyncCtrlHw->setIspEnquePeriod(mIspEnquePeriod);
mpIspSyncCtrlHw->setSensorInfo(
mInitCfg.muScenario,
sensorSize.w,
sensorSize.h,
mSensorInfo.sensorType);
......
mpCamIO = (IHalCamIO*)INormalPipe::createInstance(getSensorIdx(), getName(), mIspEnquePeriod);
if( !mpCamIO )
{
MY_LOGE("create NormalPipe failed");
goto lbExit;
}
if( !mpCamIO->init() )
{
MY_LOGE("camio init failed");
goto lbExit;
}
ret = MTRUE;
lbExit:
return ret;
}
主要就是對IspSyncCtrl和CamIO進行初始化,一個用來和ISP打交道,另一個用來和驅動打交道
回到onHandleStartPreview函式,在執行完mpCamGraph->init函式之後就到 mpCamGraph->start函數了。和mpCamGraph->init的流程一樣,mpCamGraph->start所做的事情就是迴圈遍歷所有的CamNode,並且回撥它們的onStart函式,直接看Pass1Node的onStart函式
MBOOL
Pass1NodeImpl::
onStart()
{
list<HwPortConfig_t> lHwPortCfg;
if( !getHwPortConfig(&lHwPortCfg) )
{
MY_LOGE("getHwPortConfig failed");
goto lbExit;
}
if( !startHw(lHwPortCfg) )
{
MY_LOGE("startHw failed");
goto lbExit;
}
ret = MTRUE;
lbExit:
FUNC_END;
return ret;
}
接著看startHw函式的實現
MBOOL
Pass1NodeImpl::
startHw(list<HwPortConfig_t> & plPortCfg)
{
// 1. Allocated ring buffers.
if( pthread_create(&mThreadHandle, NULL, doThreadAllocBuf, &th_data) != 0 )
{
MY_LOGE("pthread create failed");
goto lbExit;
}
// 2. Lock Pass1 HW
if( !mpIspSyncCtrlHw->lockHw(IspSyncControlHw::HW_PASS1) )
{
MY_LOGE("isp sync lock pass1 failed");
goto lbExit;
}
......
// 3. Configure RRZO and IMGO
if( !mpCamIO->configPipe(halCamIOinitParam) ) {
MY_LOGE("configPipe failed");
goto lbExit;
}
newMagicNum = mpIspSyncCtrlHw->getMagicNum(MTRUE);
if( !configFrame(newMagicNum) ) {
MY_LOGE("configFrame failed");
goto lbExit;
}
// 4. Send PASS1_START_ISP event
handleNotify(PASS1_START_ISP, newMagicNum, 0);
......
// 5. Enque buffer
if( !mpCamIO->enque(halCamIOQBuf) ) {
MY_LOGE("enque failed");
goto lbExit;
}
// 6. Start ISP
if( !mpCamIO->start() ) {
MY_LOGE("start failed");
goto lbExit;
}
ret = MTRUE;
lbExit:
if( !ret ) {
......
}
return ret;
}
這個函式做的事情比較多,上面標記的每個步驟都很複雜
第5-10行:建立一個執行緒來分配ring buffers,用於存放從驅動獲取到的影象資料
第20-24行:配置ISP和Sensor驅動預覽相關的引數,記得sensor驅動中(例如imx214mipiraw_Sensor.c)的preview_setting函式嗎,就是在這個時候被呼叫的
第33行:傳送PASS1_START_ISP事件,其它的CamNode接收到該事件後會做相應的處理,例如DefaultCtlNode,會通知Hal3A進入CameraPreview狀態
第42-46行:讓ISP開始工作,到這裡準備工作都已經完成,Camera已經進入了預覽模式,接下來就是不斷獲取影象資料,並將它送到顯示器了。
5. 總結
setPreviewWindow函式就是為hal層準備好Surface,hal層只能通過上層傳下來的mHalPreviewWindow.nw來間接的操作Surface,而mHalPreviewWindow.nw儲存在DisplayClient裡面,也就是說DisplayClient是lcd顯示影象的關鍵
startPreview函式的工作重點在CamAdapter,它代表Camera硬體,由它提供影象資料給DisplayClient。CamAdapter包含了多個CamNode,不同的CamNode用來描述不同的buffer處理,例如Pass1Node,它負責和驅動打交道,進入預覽模式的重點工作都在它的startHw函式裡面完成。