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 ¶ms, 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 ¶ms) {
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,
¤tWidth, ¤tHeight, 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 ¶ms) {
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