Android Camera數據流分析全程記錄(overlay方式)
https://blog.chinaunix.net/uid-26215986-id-3573400.html
這裏為什麼要研究overlay方式呢?android camera需要driver和app層需要有大量數據需要傳輸,如果使用非overlay方式進行數據從driver到app層的傳輸,使係統性能受到很到影響,使係統速度變慢,同時會影響功耗等,而在camera preview module時,通常我們是不必要將采集的數據保存下來的,而不像錄像module下,需要將數據保存下來,所以overlay方式就是不經過數據回傳,直接顯示從driver的數據方式,采用這種方式app從無法獲取到數據,所以這種方式應用在preview方式下
這裏我是針對android4.0版本的,相對android2.x版本的overlay已經發生了很大的變化,想要研究這方麵的可以自己去了解一下,這裏不再多說了
開始部分我就直接在這裏帶過了,係統初始打開camera時,調用到app的onCreate方法,這裏主要做了一下工作:
1.開始一個openCamera線程打開camera
2.實例化很多的對象,用於camera工作使用
3.實例化surfaceview和surfaceholder,並且填充了其中的surfacechanged,surfacedestoryed和surfacecreated這三個方式
4.開始一個preview線程用於preview過程
這其中3.4是我們這裏要關注的重點,上麵實例化了這個surfaceview將決定了我們到底是否使用overlay方式
在這裏第三遍完成之後,係統會自動執行surfacechanged這個方式,每次顯示區域發生改變都會自動調用這個方法,剛開始打開camera時,顯示區域從無到有,因此必要這裏會想調用到surfacechanged方法
我們就還是看看在這裏都做了些什麼事情
-
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
-
// Make sure we have a surface in the holder before proceeding.
-
if (holder.getSurface() == null) {
-
Log.d(TAG, "holder.getSurface()
== null");
-
return;
-
}
-
-
Log.v(TAG, "surfaceChanged.
w=" + w + ". h=" + h);
-
-
// We need to save the holder for later
use, even when the mCameraDevice
-
// is null. This
could happen if onResume() is invoked
after this
-
// function.
-
mSurfaceHolder = holder;
-
-
// The mCameraDevice will be null if it
fails to connect to the camera
-
// hardware. In this case we
will show a dialog and then finish the
-
// activity, so it's
OK to ignore it.
-
if (mCameraDevice == null) return;
-
-
// Sometimes surfaceChanged is called after onPause or before
onResume.
-
// Ignore it.
-
if (mPausing || isFinishing()) return;
-
-
setSurfaceLayout();
-
-
// Set preview display if the
surface is being created. Preview was
-
// already started. Also restart the preview if display
rotation has
-
// changed. Sometimes this happens when the device is held in portrait
-
// and camera app is opened. Rotation
animation takes some time and
-
// display rotation in onCreate may not be
what we want.
-
if (mCameraState == PREVIEW_STOPPED) {//這裏表示第一次打開camera時,那麼調用startpreview
-
startPreview(true);
-
startFaceDetection();
-
} else {//這裏則表示camera已經打開過程中發生的顯示變化,比如橫屏豎頻轉換,所以zheli隻需要重新設置previewdisplay
-
if (Util.getDisplayRotation(this) != mDisplayRotation) {
-
setDisplayOrientation();
-
}
-
if (holder.isCreating()) {
-
// Set preview display if the
surface is being created and preview
-
// was already started. That means preview display was set to null
-
// and we need to set it now.
-
setPreviewDisplay(holder);
-
}
-
}
-
-
// If first time initialization is not finished, send
a message to do
-
// it later. We want to finish
surfaceChanged as soon as possible to let
-
// user see preview first.
-
if (!mFirstTimeInitialized) {
-
mHandler.sendEmptyMessage(FIRST_TIME_INIT);
-
} else {
-
initializeSecondTime();
-
}
-
-
SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
-
CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
-
boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
-
int displayRotation = Util.getDisplayRotation(this);
-
int displayOrientation = Util.getDisplayOrientation(displayRotation, mCameraId);
-
-
mTouchManager.initialize(preview.getHeight() / 3, preview.getHeight() / 3,
-
preview, this, mirror, displayOrientation);
-
- }
-
private void startPreview(boolean updateAll) {
-
if (mPausing || isFinishing()) return;
-
-
mFocusManager.resetTouchFocus();
-
-
mCameraDevice.setErrorCallback(mErrorCallback);
-
-
// If we're
previewing already, stop the preview first (this will blank
-
// the screen).
-
if (mCameraState != PREVIEW_STOPPED) stopPreview();
-
-
setPreviewDisplay(mSurfaceHolder);
-
setDisplayOrientation();
-
-
if (!mSnapshotOnIdle) {
-
// If the focus mode is continuous
autofocus, call cancelAutoFocus to
-
// resume it because it may have been paused by autoFocus call.
-
if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
-
mCameraDevice.cancelAutoFocus();
-
}
-
mFocusManager.setAeAwbLock(false); // Unlock
AE and AWB.
-
}
-
-
if ( updateAll ) {
-
Log.v(TAG, "Updating
all parameters!");
-
setCameraParameters(UPDATE_PARAM_INITIALIZE | UPDATE_PARAM_ZOOM | UPDATE_PARAM_PREFERENCE);
-
} else {
-
setCameraParameters(UPDATE_PARAM_MODE);
-
}
-
-
//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();
-
} catch (Throwable ex) {
-
closeCamera();
-
throw new RuntimeException("startPreview failed", ex);
-
}
-
-
mZoomState = ZOOM_STOPPED;
-
setCameraState(IDLE);
-
mFocusManager.onPreviewStarted();
-
if ( mTempBracketingEnabled ) {
-
mFocusManager.setTempBracketingState(FocusManager.TempBracketingStates.ACTIVE);
-
}
-
-
if (mSnapshotOnIdle) {
-
mHandler.post(mDoSnapRunnable);
-
}
- }
這裏過程如下:app-->frameworks-->JNI-->camera client-->camera service-->hardware interface-->HAL
1.setPreviewDisplay方法調用時在app層最初的傳入的參數是surfaceholder結構
2.到了JNI層setPreviewDisplay方法傳入的參數已經是surface結構了
3.到了camera service層
sp<IBinder> binder(surface != 0 ? surface->asBinder() : 0);
sp<ANativeWindow> window(surface);
return setPreviewWindow(binder, window);
通過上麵的轉換調用同名不同參數的另外一個方法,到這裏調用的參數已經轉變為IBinder和ANativeWindow
4.調用hardware interface的setPreviewWindow(window),這裏隻有一個ANativeWindow類型的參數
5.到了camerahal_module中轉站時又發生了變化,看看下麵的定義,參數變為preview_stream_ops 這個類型的結構
int camera_set_preview_window(struct camera_device * device, struct preview_stream_ops *window)
上麵過程參數類型一直在變化,不過從app層一直傳到這裏,其實是對同一個內存地址的傳輸,就像張三換了身衣服,但是他還是張三一樣
現在我們就直接看看HAL層的實現
-
/**
-
@brief Sets ANativeWindow object.
-
-
Preview buffers provided to CameraHal via this object. DisplayAdapter will be interfacing with it
-
to render buffers to display.
-
-
@param[in] window The
ANativeWindow object created by Surface flinger
-
@return NO_ERROR If the ANativeWindow object passes validation criteria
-
@todo Define validation criteria for ANativeWindow object. Define error codes for scenarios
-
-
*/
-
status_t CameraHal::setPreviewWindow(struct preview_stream_ops *window)
-
{
-
status_t ret = NO_ERROR;
-
CameraAdapter::BuffersDescriptor desc;
-
-
LOG_FUNCTION_NAME;
-
mSetPreviewWindowCalled = true;
-
-
///If the
Camera service passes a null window, we destroy existing window and free
the DisplayAdapter
-
if(!window)//這種情況下,window是null,表示不采用overlay方式,則不需要新建displayadapter
-
{
-
if(mDisplayAdapter.get() != NULL)
-
{
-
///NULL window passed, destroy
the display adapter if present
-
CAMHAL_LOGD("NULL window passed, destroying display adapter");
-
mDisplayAdapter.clear();
-
///@remarks If there
was a window previously existing, we usually expect another valid window to be
passed by the client
-
///@remarks
so, we will wait until it passes a valid window to begin
the preview again
-
mSetPreviewWindowCalled = false;
-
}
-
CAMHAL_LOGD("NULL ANativeWindow passed to setPreviewWindow");
-
return NO_ERROR;
-
}else if(mDisplayAdapter.get() == NULL)//傳入的window不是null,但是還沒有未使用overlay方式創建displayadapter,創建displayadapter
-
{
-
// Need to create the display adapter since it has not been
created
-
// Create display adapter
-
mDisplayAdapter = new ANativeWindowDisplayAdapter();
-
ret = NO_ERROR;
-
if(!mDisplayAdapter.get() || ((ret=mDisplayAdapter->initialize())!=NO_ERROR))
-
{
-
if(ret!=NO_ERROR)
-
{
-
mDisplayAdapter.clear();
-
CAMHAL_LOGEA("DisplayAdapter initialize failed");
-
LOG_FUNCTION_NAME_EXIT;
-
return ret;
-
}
-
else
-
{
-
CAMHAL_LOGEA("Couldn't create DisplayAdapter");
-
LOG_FUNCTION_NAME_EXIT;
-
return NO_MEMORY;
-
}
-
}
-
-
// DisplayAdapter needs to know where to get the
CameraFrames from inorder to display
-
// Since CameraAdapter is the one that provides the frames, set it
as the frame provider for DisplayAdapter
-
mDisplayAdapter->setFrameProvider(mCameraAdapter);
-
-
// Any dynamic errors that happen during the camera use case has to be
propagated back to the application
-
// via CAMERA_MSG_ERROR. AppCallbackNotifier is the class that
notifies such errors to the application
-
// Set it as the error handler for the
DisplayAdapter
-
mDisplayAdapter->setErrorHandler(mAppCallbackNotifier.get());
-
-
// Update the display adapter with the new window that is passed
from CameraService
-
ret = mDisplayAdapter->setPreviewWindow(window);
-
if(ret!=NO_ERROR)
-
{
-
CAMHAL_LOGEB("DisplayAdapter setPreviewWindow returned error %d", ret);
-
}
-
-
if(mPreviewStartInProgress)
-
{
-
CAMHAL_LOGDA("setPreviewWindow called when preview running");
-
// Start the preview since the window is now available
-
ret = startPreview();
-
}
-
} else {//傳入的window不是null,並且displaadaper已經創建好,那麼這裏隻需要將新的window與已經創建好的displayadapter關聯即可
-
// Update the display adapter with the new window that is passed
from CameraService
-
ret = mDisplayAdapter->setPreviewWindow(window);
-
if ( (NO_ERROR == ret) && previewEnabled() ) {
-
restartPreview();
-
} else if (ret == ALREADY_EXISTS) {
-
// ALREADY_EXISTS should be treated as a noop in this case
-
ret = NO_ERROR;
-
}
-
}
-
LOG_FUNCTION_NAME_EXIT;
-
-
return ret;
-
- }
1.實例化一個ANativeWindowDisplayAdapter對象
2.mDisplayAdapter->initialize()
3.mDisplayAdapter->setFrameProvider(mCameraAdapter)//這一步是關鍵,之後會遇到的
4.mDisplayAdapter->setErrorHandler(mAppCallbackNotifier.get())
5.mDisplayAdapter->setPreviewWindow(window);
做完了上麵這些步驟之後,就是startpreview了
-
/**
-
@brief Start preview mode.
-
-
@param none
-
@return NO_ERROR Camera switched to VF mode
-
@todo Update function header with the different errors that are possible
-
-
*/
-
status_t CameraHal::startPreview() {
-
LOG_FUNCTION_NAME;
-
-
// When tunneling is enabled during VTC, startPreview
happens in 2 steps:
-
// When the application sends the command CAMERA_CMD_PREVIEW_INITIALIZATION,
-
// cameraPreviewInitialization() is called, which in turn
causes the CameraAdapter
-
// to move from loaded to idle
state. And when the application calls startPreview,
-
// the CameraAdapter moves from idle to executing state.
-
//
-
// If the application calls startPreview() without
sending the command
-
// CAMERA_CMD_PREVIEW_INITIALIZATION, then the function cameraPreviewInitialization()
-
// AND startPreview() are
executed. In other words, if the
application calls
-
// startPreview() without
sending the command CAMERA_CMD_PREVIEW_INITIALIZATION,
-
// then the CameraAdapter moves from loaded to idle to executing
state in one shot.
-
status_t ret = cameraPreviewInitialization();
-
-
// The flag mPreviewInitializationDone is set to true at
the end of the function
-
// cameraPreviewInitialization(). Therefore, if everything
goes alright, then the
-
// flag will be set. Sometimes, the function cameraPreviewInitialization() may
-
// return prematurely if all the resources are not available for starting
preview.
-
// For example, if the
preview window is not set, then it
would return NO_ERROR.
-
// Under such circumstances, one should return from startPreview as
well and should
-
// not continue execution. That is why, we
check the flag and not the return value.
-
if (!mPreviewInitializationDone) return
ret;
-
-
// Once startPreview is called, there is no
need to continue to remember whether
-
// the function cameraPreviewInitialization() was
called earlier or not. And so
-
// the flag mPreviewInitializationDone is reset here. Plus, this
preserves the
-
// current behavior of startPreview under the circumstances where the application
-
// calls startPreview twice or more.
-
mPreviewInitializationDone = false;
-
-
///Enable
the display adapter if present, actual
overlay enable happens when we post the buffer
-
if(mDisplayAdapter.get() != NULL) {
-
CAMHAL_LOGDA("Enabling display");
-
int width, height;
-
mParameters.getPreviewSize(&width, &height);
-
-
#if PPM_INSTRUMENTATION || PPM_INSTRUMENTATION_ABS
-
ret = mDisplayAdapter->enableDisplay(width, height, &mStartPreview);
-
#else
-
ret = mDisplayAdapter->enableDisplay(width, height, NULL);
-
#endif
-
-
if ( ret != NO_ERROR ) {
-
CAMHAL_LOGEA("Couldn't enable display");
-
-
// FIXME: At this stage mStateSwitchLock is locked and unlock is supposed to be
called
-
// only from mCameraAdapter->sendCommand(CameraAdapter::CAMERA_START_PREVIEW)
-
// below. But this will never happen because of goto error. Thus
at next
-
// startPreview() call CameraHAL
will be deadlocked.
-
// Need to revisit mStateSwitch lock, for now just
abort the process.
-
CAMHAL_ASSERT_X(false,
-
"At this stage mCameraAdapter->mStateSwitchLock is still locked, "
-
"deadlock is guaranteed");
-
-
goto error;
-
}
-
-
}
-
-
///Send START_PREVIEW command to adapter
-
CAMHAL_LOGDA("Starting CameraAdapter preview mode");
-
-
ret = mCameraAdapter->sendCommand(CameraAdapter::CAMERA_START_PREVIEW);
-
-
if(ret!=NO_ERROR) {
-
CAMHAL_LOGEA("Couldn't start preview w/ CameraAdapter");
-
goto error;
-
}
-
CAMHAL_LOGDA("Started preview");
-
-
mPreviewEnabled = true;
-
mPreviewStartInProgress = false;
-
return ret;
-
-
error:
-
-
CAMHAL_LOGEA("Performing cleanup after error");
-
-
//Do all the cleanup
-
freePreviewBufs();
-
mCameraAdapter->sendCommand(CameraAdapter::CAMERA_STOP_PREVIEW);
-
if(mDisplayAdapter.get() != NULL) {
-
mDisplayAdapter->disableDisplay(false);
-
}
-
mAppCallbackNotifier->stop();
-
mPreviewStartInProgress = false;
-
mPreviewEnabled = false;
-
LOG_FUNCTION_NAME_EXIT;
-
-
return ret;
- }
Enable the display adapter if present, actual overlay enable happens when we post the buffer
說明如果display adapter不是null,這裏會enable,overlay方式就啟動了
我們接著往下看,看看driver獲取的數據到底是怎樣處理的,startpreview會通過camerahal-->cameraapapter-->V4Lcameradapter
調用到v4l2層的startpreview,下麵看看他的具體是實現
-
status_t V4LCameraAdapter::startPreview()
-
{
-
status_t ret = NO_ERROR;
-
-
LOG_FUNCTION_NAME;
-
Mutex::Autolock lock(mPreviewBufsLock);
-
-
if(mPreviewing) {
-
ret = BAD_VALUE;
-
goto EXIT;
-
}
-
-
for (int i = 0; i < mPreviewBufferCountQueueable; i++) {
-
-
mVideoInfo->buf.index = i;
-
mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-
mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
-
-
ret = v4lIoctl(mCameraHandle, VIDIOC_QBUF, &mVideoInfo->buf);//請求分配內存
-
if (ret < 0) {
-
CAMHAL_LOGEA("VIDIOC_QBUF Failed");
-
goto EXIT;
-
}
-
nQueued++;
-
}
-
-
ret = v4lStartStreaming();
-
-
// Create and start preview thread for receiving
buffers from V4L Camera
-
if(!mCapturing) {
-
mPreviewThread = new
PreviewThread(this);//開啟PreviewThread
-
CAMHAL_LOGDA("Created preview thread");
-
}
-
-
//Update the flag to indicate we are previewing
-
mPreviewing = true;
-
mCapturing = false;
-
-
EXIT:
-
LOG_FUNCTION_NAME_EXIT;
-
return ret;
- }
-
int V4LCameraAdapter::previewThread()
-
{
-
status_t ret = NO_ERROR;
-
int width, height;
-
CameraFrame frame;
-
void *y_uv[2];
-
int index = 0;
-
int stride = 4096;
-
char *fp = NULL;
-
-
mParams.getPreviewSize(&width, &height);
-
-
if (mPreviewing) {
-
-
fp = this->GetFrame(index);
-
if(!fp) {
-
ret = BAD_VALUE;
-
goto EXIT;
-
}
-
CameraBuffer *buffer = mPreviewBufs.keyAt(index);//獲取camerabuffer
-
CameraFrame *lframe = (CameraFrame *)mFrameQueue.valueFor(buffer);//獲取cameraframe
-
if (!lframe) {
-
ret = BAD_VALUE;
-
goto EXIT;
-
}
-
-
debugShowFPS();
-
-
if ( mFrameSubscribers.size() == 0 ) {
-
ret = BAD_VALUE;
-
goto EXIT;
-
}
-
y_uv[0] = (void*) lframe->mYuv[0];
-
//y_uv[1] = (void*) lframe->mYuv[1];
-
//y_uv[1] = (void*) (lframe->mYuv[0] + height*stride);
-
convertYUV422ToNV12Tiler ( (unsigned char*)fp, (unsigned
char*)y_uv[0], width, height);//convert
the data
-
CAMHAL_LOGVB("##...index= %d.;camera buffer= 0x%x; y= 0x%x; UV= 0x%x.",index, buffer, y_uv[0], y_uv[1] );
-
-
#ifdef SAVE_RAW_FRAMES
-
unsigned char* nv12_buff = (unsigned char*) malloc(width*height*3/2);
-
//Convert yuv422i to yuv420sp(NV12) & dump
the frame to a file
-
convertYUV422ToNV12 ( (unsigned char*)fp, nv12_buff, width, height);
-
saveFile( nv12_buff, ((width*height)*3/2) );//if
you want to save the data,save it
-
free (nv12_buff);
-
#endif
-
//填充frame結構,用於數據處理
-
frame.mFrameType = CameraFrame::PREVIEW_FRAME_SYNC;
-
frame.mBuffer = buffer;
-
frame.mLength = width*height*3/2;
-
frame.mAlignment = stride;
-
frame.mOffset = 0;
-
frame.mTimestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-
frame.mFrameMask = (unsigned int)CameraFrame::PREVIEW_FRAME_SYNC;
-
-
if (mRecording)
-
{
-
frame.mFrameMask |= (unsigned int)CameraFrame::VIDEO_FRAME_SYNC;
-
mFramesWithEncoder++;
-
}
-
-
//這裏是重點,數據回調,或者使用overlay方式顯示這裏是決定性調用
-
ret = setInitFrameRefCount(frame.mBuffer, frame.mFrameMask);
-
if (ret != NO_ERROR) {
-
CAMHAL_LOGDB("Error in setInitFrameRefCount %d", ret);
-
} else {
-
ret = sendFrameToSubscribers(&frame);
-
}
-
}
-
EXIT:
-
-
return ret;
- }
-
int BaseCameraAdapter::setInitFrameRefCount(CameraBuffer * buf, unsigned int mask)
-
{
-
int ret = NO_ERROR;
-
unsigned int lmask;
-
-
LOG_FUNCTION_NAME;
-
-
if (buf == NULL)
-
{
-
return -EINVAL;
-
}
-
-
for( lmask = 1; lmask < CameraFrame::ALL_FRAMES; lmask <<= 1){
-
if( lmask & mask ){
-
switch( lmask ){
-
-
case CameraFrame::IMAGE_FRAME:
-
{
-
setFrameRefCount(buf, CameraFrame::IMAGE_FRAME, (int) mImageSubscribers.size());
-
}
-
break;
-
case CameraFrame::RAW_FRAME:
-
{
-
setFrameRefCount(buf, CameraFrame::RAW_FRAME, mRawSubscribers.size());
-
}
-
break;
-
case CameraFrame::PREVIEW_FRAME_SYNC:
-
{
-
setFrameRefCount(buf, CameraFrame::PREVIEW_FRAME_SYNC, mFrameSubscribers.size());//這裏這個mFrameSubscribers對應的key上保存著響應的callback方法
-
}
-
break;
-
case CameraFrame::SNAPSHOT_FRAME:
-
{
-
setFrameRefCount(buf, CameraFrame::SNAPSHOT_FRAME, mSnapshotSubscribers.size());
-
}
-
break;
-
case CameraFrame::VIDEO_FRAME_SYNC:
-
{
-
setFrameRefCount(buf,CameraFrame::VIDEO_FRAME_SYNC, mVideoSubscribers.size());
-
}
-
break;
-
case CameraFrame::FRAME_DATA_SYNC:
-
{
-
setFrameRefCount(buf, CameraFrame::FRAME_DATA_SYNC, mFrameDataSubscribers.size());
-
}
-
break;
-
case CameraFrame::REPROCESS_INPUT_FRAME:
-
{
-
setFrameRefCount(buf,CameraFrame::REPROCESS_INPUT_FRAME, mVideoInSubscribers.size());
-
}
-
break;
-
default:
-
CAMHAL_LOGEB("FRAMETYPE NOT SUPPORTED 0x%x", lmask);
-
break;
-
}//SWITCH
-
mask &= ~lmask;
-
}//IF
-
}//FOR
-
LOG_FUNCTION_NAME_EXIT;
-
return ret;
- }
同樣的通過disableMsgType方法實現mFrameSubscribers.removeItem的,具體在哪裏調用enableMsgType和disableMsgType之後再給予說明
-
void BaseCameraAdapter::setFrameRefCount(CameraBuffer * frameBuf, CameraFrame::FrameType
frameType, int refCount)
-
{
-
-
LOG_FUNCTION_NAME;
-
-
switch ( frameType )
-
{
-
case CameraFrame::IMAGE_FRAME:
-
case CameraFrame::RAW_FRAME:
-
{
-
Mutex::Autolock lock(mCaptureBufferLock);
-
mCaptureBuffersAvailable.replaceValueFor(frameBuf, refCount);
-
}
-
break;
-
case CameraFrame::SNAPSHOT_FRAME:
-
{
-
Mutex::Autolock lock(mSnapshotBufferLock);
-
mSnapshotBuffersAvailable.replaceValueFor( ( unsigned int ) frameBuf, refCount);
-
}
-
break;
-
case CameraFrame::PREVIEW_FRAME_SYNC:
-
{
-
Mutex::Autolock lock(mPreviewBufferLock)
-
mPreviewBuffersAvailable.replaceValueFor(frameBuf, refCount);//這裏我的理解是refCount和frameBuf實現了綁定,即camerabuf保存在mPreviewBuffersAvailable對應的key處
-
}
-
break;
-
case CameraFrame::FRAME_DATA_SYNC:
-
{
-
最後更新:2017-04-03 18:52:05
上一篇:
Android 動態添加控件
下一篇:
Java中的HashMap和ConcurrentHashMap的並發性能測試