Java層
接著上一篇文章(http://www.reibang.com/p/f7f548c2c0e7
)的分析踊沸。
在成功調(diào)用UVCCamera的一系列open操作之后,我們就可以進(jìn)入startPreview階段仪壮。這個階段的上層調(diào)用邏輯相對比較簡單,我們先看一下一個大概的時序圖:
我們在USBMonitor.OnDeviceConnectListener的onConnect回調(diào)中(openCamra之后)先調(diào)用了UVCCameraHandler的startPreview方法。
查看源碼發(fā)現(xiàn)UVCCameraHandler中實際是調(diào)用了super的startPreview臼节,那么我們再看到super也就是AbstractUVCCameraHandler中:
//UVCCameraHandler
@Override
public void startPreview(final Object surface) {
super.startPreview(surface);
}
//AbstractUVCCameraHandler
public void startPreview(final Object surface) {
checkReleased();
if (!((surface instanceof SurfaceHolder) || (surface instanceof Surface) || (surface instanceof SurfaceTexture))) {
throw new IllegalArgumentException("surface should be one of SurfaceHolder, Surface or SurfaceTexture: " + surface);
}
sendMessage(obtainMessage(MSG_PREVIEW_START, surface));
}
相機(jī)使用的整個流程大致可以分為“數(shù)據(jù)采集”+“渲染”,而startPreview就是這兩個過程結(jié)合之處,我們可以看到startPreview需要傳入一個類型為Object的surface网缝,這個就是我們渲染所需要的紋理巨税,相機(jī)采集到的每一幀數(shù)據(jù)都需要繪制到這塊紋理上,后面實現(xiàn)拍照粉臊、視頻錄制甚至是圖像編輯(裁剪草添、濾鏡等)都需要與這塊紋理打交道,我們將在后續(xù)講到“渲染”流程的時候再詳細(xì)介紹扼仲。這里我們只需要知道從外部傳入一塊紋理即可远寸。在Android UI中對于紋理的封裝就是SurfaceView或者TextureView,而在UVCCamera中就是UVCCameraTextureView屠凶,它繼承自android.view.TextureView驰后。
我們繼續(xù)回到預(yù)覽流程,可以看到AbstractUVCCameraHandler中拿到外部傳入的surface之后矗愧,執(zhí)行了與openCamera相同的流程——通過消息機(jī)制調(diào)度相機(jī)操作灶芝,這里就發(fā)送了一個MSG_PREVIEW_START消息通知AbstractUVCCameraHandler.CameraThread來開啟預(yù)覽。
//AbstractUVCCameraHandler
case MSG_PREVIEW_START:
thread.handleStartPreview(msg.obj);
public void handleStartPreview(final Object surface) {
if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:");
if ((mUVCCamera == null) || mIsPreviewing) return;
try {
mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor);
// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21);
mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP);
} catch (final IllegalArgumentException e) {
try {
// fallback to YUV mode
mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, UVCCamera.DEFAULT_PREVIEW_MODE, mBandwidthFactor);
} catch (final IllegalArgumentException e1) {
callOnError(e1);
return;
}
}
if (surface instanceof SurfaceHolder) {
mUVCCamera.setPreviewDisplay((SurfaceHolder) surface);
}
if (surface instanceof Surface) {
mUVCCamera.setPreviewDisplay((Surface) surface);
} else {
mUVCCamera.setPreviewTexture((SurfaceTexture) surface);
}
mUVCCamera.startPreview();
mUVCCamera.updateCameraParams();
synchronized (mSync) {
mIsPreviewing = true;
}
callOnStartPreview();
}
CameraThread中的handleStartPreview大概就是做了設(shè)置預(yù)覽相關(guān)參數(shù)唉韭、設(shè)置預(yù)覽數(shù)據(jù)回調(diào)監(jiān)聽夜涕、綁定紋理、開啟預(yù)覽等這幾部操作属愤。根據(jù)代碼中方法的名字基本也能了解整個調(diào)用的邏輯女器。之后的代碼就進(jìn)入到了jni層。我們一個個來分析住诸。
C層
- setPreviewSize
在Java層的UVCCamera中晓避,該方法實際調(diào)用了jni的方法——nativeSetPreviewSize。在上一篇文章中只壳,我們發(fā)現(xiàn)在UVCCamera的open方法中也調(diào)用了nativeSetPreviewSize俏拱。nativeSetPreviewSize是用來設(shè)置預(yù)覽所需要的一些相關(guān)參數(shù)的,如寬吼句、高锅必、fps等等。而在open方法中調(diào)用時惕艳,只是為這些參數(shù)賦了默認(rèn)值搞隐,在setPreviewSize中則是從外部傳入的上層業(yè)務(wù)中實際的值。至于c層代碼也沒什么好說的远搪,就是一通賦值操作
int UVCPreview::setPreviewSize(int width, int height, int min_fps, int max_fps, int mode, float bandwidth) {
ENTER();
int result = 0;
if ((requestWidth != width) || (requestHeight != height) || (requestMode != mode)) {
requestWidth = width;
requestHeight = height;
requestMinFps = min_fps;
requestMaxFps = max_fps;
requestMode = mode;
requestBandwidth = bandwidth;
uvc_stream_ctrl_t ctrl;
result = uvc_get_stream_ctrl_format_size_fps(mDeviceHandle, &ctrl,
!requestMode ? UVC_FRAME_FORMAT_YUYV : UVC_FRAME_FORMAT_MJPEG,
requestWidth, requestHeight, requestMinFps, requestMaxFps);
}
RETURN(result, int);
}
- setFrameCallback
在Java層的UVCCamera中劣纲,該方法傳入的第一個參數(shù)是IFrameCallback接口,能讓我們拿到相機(jī)采集的每一幀原始數(shù)據(jù)谁鳍。另外的一個參數(shù)則是指定色彩格式癞季,這邊我們使用的是YUV420劫瞳。我們在使用usb攝像頭過程中如果出現(xiàn)一些色彩不正確的問題,可以嘗試調(diào)整這個參數(shù)绷柒,如換成 NV21志于。而對于色彩格式的詳細(xì)介紹,網(wǎng)上隨便一搜就會有很多废睦,這里就不展開了伺绽。
再轉(zhuǎn)到C層代碼,這邊調(diào)用的是nativeSetPreviewSize
int UVCPreview::setFrameCallback(JNIEnv *env, jobject frame_callback_obj, int pixel_format) {
//... 省略代碼
if (!env->IsSameObject(mFrameCallbackObj, frame_callback_obj)) {
iframecallback_fields.onFrame = NULL;
if (mFrameCallbackObj) {
env->DeleteGlobalRef(mFrameCallbackObj);
}
mFrameCallbackObj = frame_callback_obj;
if (frame_callback_obj) {
// get method IDs of Java object for callback
jclass clazz = env->GetObjectClass(frame_callback_obj);
if (LIKELY(clazz)) {
iframecallback_fields.onFrame = env->GetMethodID(clazz,
"onFrame", "(Ljava/nio/ByteBuffer;)V");
} else {
LOGW("failed to get object class");
}
//··· 省略代碼
}
if (frame_callback_obj) {
mPixelFormat = pixel_format;
callbackPixelFormatChanged();
}
}
//··· 省略代碼
}
可以看到這個方法中嗜湃,Java層的IFrameCallback賦值給了C層的mFrameCallbackObj奈应。并且通過env->GetObjectClass以及env->GetMethodID將該接口的onFrame方法綁定到iframecallback_fields.onFrame。便于后續(xù)調(diào)用购披。在代碼的最后又調(diào)用了callbackPixelFormatChanged杖挣,該方法會根據(jù)傳入的色彩格式來申請對應(yīng)的內(nèi)存空間。
- setPreviewDisplay
該方法調(diào)用流程是:
- Java層:UVCCamera.startPreview
- C層:serenegiant_usb_UVCCamera->nativeSetPreviewDisplay
- C層:UVCCamera->setPreviewDisplay
- C層:UVCPreview->setPreviewDisplay
我們直接看UVCPreview類中的:
int UVCPreview::setPreviewDisplay(ANativeWindow *preview_window) {
ENTER();
pthread_mutex_lock(&preview_mutex);
{
if (mPreviewWindow != preview_window) {
if (mPreviewWindow)
ANativeWindow_release(mPreviewWindow);
mPreviewWindow = preview_window;
if (LIKELY(mPreviewWindow)) {
ANativeWindow_setBuffersGeometry(mPreviewWindow,
frameWidth, frameHeight, previewFormat);
}
}
}
pthread_mutex_unlock(&preview_mutex);
RETURN(0, int);
}
這個方法中今瀑,傳入的ANativeWindow就是我們之前提到的紋理程梦,轉(zhuǎn)回Java層就是UVC庫里封裝的UVCCameraTextureView点把。然后這個方法主要作用就是將傳入的紋理保存到mPreviewWindow這個全局變量中橘荠。之后有調(diào)用了ANativeWindow_setBuffersGeometry方法設(shè)置緩沖區(qū)的寬高,這個方法在安卓的文檔就就介紹得比較詳細(xì)了:
Change the format and size of the window buffers.
The width and height control the number of pixels in the buffers, not the dimensions of the window on screen. If these are different than the window's physical size, then its buffer will be scaled to match that size when compositing it to the screen. The width and height must be either both zero or both non-zero.
For all of these parameters, if 0 is supplied then the window's base value will come back in force.
翻譯過來大概就是:
更改窗口緩沖區(qū)的顏色格式和尺寸郎逃。
寬度和高度控制緩沖區(qū)中的像素哥童,而不是屏幕上窗口的尺寸。如果這些大小與窗口的物理大小不同褒翰,則在將其合成到屏幕時贮懈,將縮放其緩沖區(qū)以匹配該大小。寬度和高度必須同時為零或同時為非零优训。
對于所有這些參數(shù)朵你,如果設(shè)置為0,則窗口的默認(rèn)值將重新生效揣非。
小結(jié)
在做完一堆參數(shù)設(shè)置之后抡医,接下來會調(diào)用startPreview,來真正開啟預(yù)覽早敬,這個過程相對比較復(fù)雜忌傻,我們將放到下一篇中在仔細(xì)介紹