Android中多USB攝像頭解決方案——UVCCamera源碼分析(二)

Java層

接著上一篇文章(http://www.reibang.com/p/f7f548c2c0e7
)的分析踊沸。
在成功調(diào)用UVCCamera的一系列open操作之后,我們就可以進(jìn)入startPreview階段仪壮。這個階段的上層調(diào)用邏輯相對比較簡單,我們先看一下一個大概的時序圖:

Java層時序圖

我們在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層

  1. 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);
}

  1. 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)存空間。

  1. 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ì)介紹

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搞监,隨后出現(xiàn)的幾起案子水孩,更是在濱河造成了極大的恐慌,老刑警劉巖琐驴,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俘种,死亡現(xiàn)場離奇詭異秤标,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)安疗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門抛杨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荐类,你說我怎么就攤上這事怖现。” “怎么了玉罐?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵屈嗤,是天一觀的道長。 經(jīng)常有香客問我吊输,道長饶号,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任季蚂,我火速辦了婚禮茫船,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扭屁。我一直安慰自己算谈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布料滥。 她就那樣靜靜地躺著然眼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葵腹。 梳的紋絲不亂的頭發(fā)上栈戳,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天坚嗜,我揣著相機(jī)與錄音作郭,去河邊找鬼究驴。 笑死,一個胖子當(dāng)著我的面吹牛阻肩,可吹牛的內(nèi)容都是我干的带欢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼磺浙,長吁一口氣:“原來是場噩夢啊……” “哼洪囤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撕氧,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瘤缩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伦泥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剥啤,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡锦溪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了府怯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刻诊。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牺丙,靈堂內(nèi)的尸體忽然破棺而出则涯,到底是詐尸還是另有隱情,我是刑警寧澤冲簿,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布粟判,位于F島的核電站,受9級特大地震影響峦剔,放射性物質(zhì)發(fā)生泄漏档礁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一吝沫、第九天 我趴在偏房一處隱蔽的房頂上張望呻澜。 院中可真熱鬧,春花似錦惨险、人聲如沸羹幸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睹欲。三九已至供炼,卻和暖如春一屋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袋哼。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工冀墨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涛贯。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓诽嘉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弟翘。 傳聞我的和親對象是個殘疾皇子虫腋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容