Android中多USB攝像頭解決方案——UVCCamera

??????先貼上采用的開源庫鏈接:https://github.com/saki4510t/UVCCamera

??????業(yè)余時(shí)間搗鼓了下Android 板子連接多個usb攝像頭的方案屉凯,一開始使用系統(tǒng)的CameraV1的api器躏,但是取到的攝像頭數(shù)量一直不對(api: Camera.getNumberOfCameras())哈街,然后又去網(wǎng)上查了方案(傳送門:https://blog.csdn.net/xiangzhihong8/article/details/82877901)發(fā)現(xiàn)Android P之后原生就支持多攝像頭弦悉,心里美滋滋男公,這么快就大結(jié)局了,但是果然天不遂人愿寺谤,于是改用CameraV2的api榛搔,但還是識別不到完整的攝像頭列表。沒查到具體原因说贝,但是猜測是跟Android板子有關(guān)议惰,雖然在軟件上已經(jīng)支持多攝像頭,但是底層可能還是限制了最大連接數(shù)量乡恕。之后去應(yīng)用市場下了一個usb攝像頭app言询,發(fā)現(xiàn)居然是可以正常識別出所有的攝像頭,遂反編譯之傲宜,發(fā)現(xiàn)是使用了UVCCamera运杭。這個開源庫貌似已經(jīng)很久沒有維護(hù),并且根據(jù)之前使用的經(jīng)驗(yàn)來看也有不少bug(主要是兼容性方面)函卒,但是項(xiàng)目中用到的硬件只有一個型號辆憔,并不需要做太多設(shè)備兼容性的適配,因此還是可以拿來一用报嵌。下面就分享一下UVCCamera的接入過程虱咧。

??????無論使用哪種Camera的api,Camera的封裝都可以大致分為兩個流程:數(shù)據(jù)采集锚国、渲染腕巡。于是我們就可以定義出這兩塊功能的接口:

數(shù)據(jù)采集

public interface ICamera {
    List<String> getCameras();

    interface OnPhotoTake {
        void onPhotoTake(Bitmap reader,String path);
    }

    boolean open(String id);

    void close();

    void takePicture(OnPhotoTake onPhotoTake,String id);
}

渲染

public interface CameraViewInterface extends IAspectRatioView {
    interface Callback {
        void onSurfaceCreated(CameraViewInterface view, Surface surface);

        void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height);

        void onSurfaceDestroy(CameraViewInterface view, Surface surface);
    }

    void onPause();

    void onResume();

    void setCallback(Callback callback);

    SurfaceTexture getSurfaceTexture();

    Surface getSurface();

    boolean hasSurface();

    void setVideoEncoder(final IVideoEncoder encoder);

    Bitmap captureStillImage(int width, int height);
}

??????雖然是多個攝像頭,但是我們可以用一個單例類來統(tǒng)一管理——UVCCameraHelper血筑。在這個類中我們要做的事有:連接上usb攝像頭設(shè)備绘沉,開啟攝像頭采集數(shù)據(jù)并將數(shù)據(jù)渲染到surface上,此外還需要提供拍照豺总、視頻錄制等api车伞。那么這里就需要用到UVCCcamera這個開源庫了。我們需要在UVCCameraHelper定義一個USBMonitor對象喻喳,這個類的核心接口提供了USB設(shè)備的連接狀態(tài)維護(hù)另玖。

public interface OnDeviceConnectListener {
        /**
         * called when device attached
         * @param device
         */
        public void onAttach(UsbDevice device);
        /**
         * called when device dettach(after onDisconnect)
         * @param device
         */
        public void onDettach(UsbDevice device);
        /**
         * called after device opend
         * @param device
         * @param ctrlBlock
         * @param createNew
         */
        public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew);
        /**
         * called when USB device removed or its power off (this callback is called after device closing)
         * @param device
         * @param ctrlBlock
         */
        public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock);
        /**
         * called when canceled or could not get permission from user
         * @param device
         */
        public void onCancel(UsbDevice device);
    }

??????UVCCameraHelper中開啟USB攝像頭預(yù)覽的方法:

 public void requestPermission(int index) {
        List<UsbDevice> devList = getUsbDeviceList();
        if (devList == null || devList.size() == 0) {
            return;
        }
        int count = devList.size();
        if (index >= count)
            new IllegalArgumentException("index illegal,should be < devList.size()");
        if (mUSBMonitor != null) {
            mUSBMonitor.requestPermission(getUsbDeviceList().get(index));
        }
    }

    public int getUsbDeviceCount() {
        List<UsbDevice> devList = getUsbDeviceList();
        if (devList == null || devList.size() == 0) {
            return 0;
        }
        return devList.size();
    }

    public List<UsbDevice> getUsbDeviceList() {
        List<DeviceFilter> deviceFilters = DeviceFilter
                .getDeviceFilters(mActivity.getApplicationContext(), R.xml.device_filter_uvc);
        if (mUSBMonitor == null || deviceFilters == null)
            return null;
        return mUSBMonitor.getDeviceList(deviceFilters.get(0));
    }

??????可以看到在requestPermission方法中先根據(jù)指定的index從usb設(shè)備列表中拿到設(shè)備,然后再調(diào)用了USBMonitor的requestPermission方法沸枯。我們繼續(xù)看USBMonitor的requestPermission方法:

public synchronized boolean requestPermission(final UsbDevice device) {
//      if (DEBUG) Log.v(TAG, "requestPermission:device=" + device);
        boolean result = false;
        if (isRegistered()) {
            if (device != null) {
                if (mUsbManager.hasPermission(device)) {
                    // call onConnect if app already has permission
                    processConnect(device);
                } else {
                    try {
                        // パーミッションがなければ要求する
                        mUsbManager.requestPermission(device, mPermissionIntent);
                    } catch (final Exception e) {
                        // Android5.1.xのGALAXY系でandroid.permission.sec.MDM_APP_MGMTという意味不明の例外生成するみたい
                        Log.w(TAG, e);
                        processCancel(device);
                        result = true;
                    }
                }
            } else {
                processCancel(device);
                result = true;
            }
        } else {
            processCancel(device);
            result = true;
        }
        return result;
    }

??????在該方法中注解居然是日文的(因?yàn)樽髡呤侨毡救耍尤蛔屛腋械接蟹N莫名的中二感……言歸正傳赂弓,該方法主要就是先判斷是否已經(jīng)對當(dāng)前USB設(shè)備授權(quán)绑榴,如果未授權(quán)則先彈授權(quán)彈窗,如果已經(jīng)授權(quán)那么就執(zhí)行processConnect方法:

private final void processConnect(final UsbDevice device) {
        if (destroyed) return;
        updatePermission(device, true);
        mAsyncHandler.post(new Runnable() {
            @Override
            public void run() {
                if (DEBUG) Log.v(TAG, "processConnect:device=" + device);
                UsbControlBlock ctrlBlock;
                final boolean createNew;
                ctrlBlock = mCtrlBlocks.get(device);
                if (ctrlBlock == null) {
                    ctrlBlock = new UsbControlBlock(USBMonitor.this, device);
                    mCtrlBlocks.put(device, ctrlBlock);
                    createNew = true;
                } else {
                    createNew = false;
                }
                if (mOnDeviceConnectListener != null) {
                    mOnDeviceConnectListener.onConnect(device, ctrlBlock, createNew);
                }
            }
        });
    }

??????在該方法中會創(chuàng)建一個UsbControlBlock對象盈魁,在該對象中會根據(jù)傳入的UsbDevice維護(hù)一個UsbDeviceConnection翔怎,這個類是被用來對USB設(shè)備做收發(fā)數(shù)據(jù)以及控制命令的(This class is used for sending and receiving data and control messages to a USB device.)然后就會回調(diào)到我們之前定義的OnDeviceConnectListener接口的onConnect方法。之后在onConnect中就會真正開啟攝像頭預(yù)覽。
我們定義一個CameraThread繼承自Thread赤套,將所有操作放到子線程中飘痛,然后再定一個Handler類來維護(hù)所有預(yù)覽(數(shù)據(jù)采集+渲染)相關(guān)操作,這樣我們就可以通過消息機(jī)制來控制所有操作的執(zhí)行順序了容握。
??????我們先來看onConnect中調(diào)用的openCamera(USBMonitor.UsbControlBlock ctrlBlock)宣脉,它實(shí)際就是通過Handler來執(zhí)行消息的發(fā)送與處理:

private void openCamera(USBMonitor.UsbControlBlock ctrlBlock) {
        if (mCameraHandler != null) {
            mCameraHandler.open(ctrlBlock);
        }
}
 public void open(final USBMonitor.UsbControlBlock ctrlBlock) {
        checkReleased();
        sendMessage(obtainMessage(MSG_OPEN, ctrlBlock));
}

 case MSG_OPEN:
    thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj);

??????這里需要提的是在thread的handleOpen方法中會創(chuàng)建UVCCamera這個核心類,它會調(diào)用到j(luò)ni層的api去做真正的相機(jī)相關(guān)操作剔氏。
我們繼續(xù)回到onConnect塑猖,在開啟相機(jī)之后會接著調(diào)用startPreview方法來將采集到的數(shù)據(jù)綁定到一塊surface上:

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));
}
 case MSG_PREVIEW_START:
    thread.handleStartPreview(msg.obj);

??????預(yù)覽開啟之后對于拍照等操作也是類似:

public void captureStill(final String path, AbstractUVCCameraHandler.OnCaptureListener listener) {
        AbstractUVCCameraHandler.mCaptureListener = listener;
        checkReleased();
        sendMessage(obtainMessage(MSG_CAPTURE_STILL, path));
        isCaptureStill = true;
}
case MSG_CAPTURE_STILL:
    thread.handleStillPicture((String) msg.obj);

??????這樣一個基本的攝像頭開啟、拍照流程就走完了谈跛,當(dāng)然還有很多細(xì)節(jié)處理沒有一一列出來羊苟,如異常處理、各種狀態(tài)判斷等等感憾,這些需要在實(shí)際項(xiàng)目開發(fā)中根據(jù)自己的業(yè)務(wù)來做處理即可蜡励。
未來我會繼續(xù)去扒jni層的代碼,看看底層開啟相機(jī)具體又做了哪些事情阻桅,就請繼續(xù)期待后續(xù)的分享文章凉倚。
??????從事的工作剛好是自己的愛好,是一件無比幸福的事情鳍刷。哪怕在業(yè)余時(shí)間也會像在打游戲一樣樂在其中占遥。慢慢找回自己吧。

UVCCamera源碼分析(一):
http://www.reibang.com/p/f7f548c2c0e7
UVCCamera源碼分析(二):
http://www.reibang.com/p/225734c143ba
UVCCamera源碼分析(三):
http://www.reibang.com/p/313e6e4ca418
UVCCamera源碼分析(四):
http://www.reibang.com/p/e7e370011775
UVCCamera源碼分析(五):
http://www.reibang.com/p/3b7f3ff6ab45

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末输瓜,一起剝皮案震驚了整個濱河市瓦胎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尤揣,老刑警劉巖搔啊,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異北戏,居然都是意外死亡负芋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門嗜愈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旧蛾,“玉大人,你說我怎么就攤上這事蠕嫁∠翘欤” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵剃毒,是天一觀的道長病袄。 經(jīng)常有香客問我搂赋,道長,這世上最難降的妖魔是什么益缠? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任脑奠,我火速辦了婚禮,結(jié)果婚禮上幅慌,老公的妹妹穿的比我還像新娘宋欺。我一直安慰自己,他們只是感情好欠痴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布迄靠。 她就那樣靜靜地躺著,像睡著了一般喇辽。 火紅的嫁衣襯著肌膚如雪掌挚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天菩咨,我揣著相機(jī)與錄音吠式,去河邊找鬼。 笑死抽米,一個胖子當(dāng)著我的面吹牛特占,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播云茸,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼是目,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了标捺?” 一聲冷哼從身側(cè)響起懊纳,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亡容,沒想到半個月后嗤疯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡闺兢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年茂缚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屋谭。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡脚囊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桐磁,到底是詐尸還是另有隱情悔耘,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布所意,位于F島的核電站淮逊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扶踊。R本人自食惡果不足惜泄鹏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秧耗。 院中可真熱鬧备籽,春花似錦、人聲如沸分井。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尺锚。三九已至珠闰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘫辩,已是汗流浹背伏嗜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伐厌,地道東北人承绸。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像挣轨,于是被迫代替她去往敵國和親军熏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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