前言 先贴上采用的开源库链接:https://github.com/saki4510t/UVCCamera
业余时间捣鼓了下Android 板子连接多个usb摄像头的方案,一开始使用系统的CameraV1的api,但是取到的摄像头数量一直不对(api: Camera.getNumberOfCameras()),然后又去网上查了方案(传送门:https://blog.csdn.net/xiangzhihong8/article/details/82877901 )发现Android P之后原生就支持多摄像头,心里美滋滋,这么快就大结局了,但是果然天不遂人愿,于是改用CameraV2的api,但还是识别不到完整的摄像头列表。没查到具体原因,但是猜测是跟Android板子有关,虽然在软件上已经支持多摄像头,但是底层可能还是限制了最大连接数量。之后去应用市场下了一个usb摄像头app,发现居然是可以正常识别出所有的摄像头,遂反编译之,发现是使用了UVCCamera。这个开源库貌似已经很久没有维护,并且根据之前使用的经验来看也有不少bug(主要是兼容性方面),但是项目中用到的硬件只有一个型号,并不需要做太多设备兼容性的适配,因此还是可以拿来一用。下面就分享一下UVCCamera的接入过程。
无论使用哪种Camera的api,Camera的封装都可以大致分为两个流程:数据采集、渲染。于是我们就可以定义出这两块功能的接口:
数据采集 1 2 3 4 5 6 7 8 9 10 11 12 13 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) ; }
渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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) ; }
虽然是多个摄像头,但是我们可以用一个单例类来统一管理——UVCCameraHelper。在这个类中我们要做的事有:连接上usb摄像头设备,开启摄像头采集数据并将数据渲染到surface上,此外还需要提供拍照、视频录制等api。那么这里就需要用到UVCCcamera这个开源库了。我们需要在UVCCameraHelper定义一个USBMonitor对象,这个类的核心接口提供了USB设备的连接状态维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public interface OnDeviceConnectListener { public void onAttach (UsbDevice device) ; public void onDettach (UsbDevice device) ; public void onConnect (UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew) ; public void onDisconnect (UsbDevice device, UsbControlBlock ctrlBlock) ; public void onCancel (UsbDevice device) ; }
UVCCameraHelper中开启USB摄像头预览的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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方法中先根据指定的index从usb设备列表中拿到设备,然后再调用了USBMonitor的requestPermission方法。我们继续看USBMonitor的requestPermission方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public synchronized boolean requestPermission (final UsbDevice device) { boolean result = false ; if (isRegistered()) { if (device != null ) { if (mUsbManager.hasPermission(device)) { processConnect(device); } else { try { mUsbManager.requestPermission(device, mPermissionIntent); } catch (final Exception e) { Log.w(TAG, e); processCancel(device); result = true ; } } } else { processCancel(device); result = true ; } } else { processCancel(device); result = true ; } return result; }
该方法主要就是先判断是否已经对当前USB设备授权,如果未授权则先弹授权弹窗,如果已经授权那么就执行processConnect方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); } } }); }
在该方法中会创建一个UsbControlBlock对象,在该对象中会根据传入的UsbDevice维护一个UsbDeviceConnection,这个类是被用来对USB设备做收发数据以及控制命令的(This class is used for sending and receiving data and control messages to a USB device.)然后就会回调到我们之前定义的OnDeviceConnectListener接口的onConnect方法。之后在onConnect中就会真正开启摄像头预览。
我们定义一个CameraThread继承自Thread,将所有操作放到子线程中,然后再定一个Handler类来维护所有预览(数据采集+渲染)相关操作,这样我们就可以通过消息机制来控制所有操作的执行顺序了。
我们先来看onConnect中调用的openCamera(USBMonitor.UsbControlBlock ctrlBlock),它实际就是通过Handler来执行消息的发送与处理:
1 2 3 4 5 private void openCamera (USBMonitor.UsbControlBlock ctrlBlock) { if (mCameraHandler != null ) { mCameraHandler.open(ctrlBlock); } }
1 2 3 4 public void open (final USBMonitor.UsbControlBlock ctrlBlock) { checkReleased(); sendMessage(obtainMessage(MSG_OPEN, ctrlBlock)); }
1 2 case MSG_OPEN: thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj);
这里需要提的是在thread的handleOpen方法中会创建UVCCamera这个核心类,它会调用到jni层的api去做真正的相机相关操作。 我们继续回到onConnect,在开启相机之后会接着调用startPreview方法来将采集到的数据绑定到一块surface上:
1 2 3 4 5 6 7 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)); }
1 2 case MSG_PREVIEW_START: thread.handleStartPreview(msg.obj);
预览开启之后对于拍照等操作也是类似:
1 2 3 4 5 6 public void captureStill (final String path, AbstractUVCCameraHandler.OnCaptureListener listener) { AbstractUVCCameraHandler.mCaptureListener = listener; checkReleased(); sendMessage(obtainMessage(MSG_CAPTURE_STILL, path)); isCaptureStill = true ; }
1 2 case MSG_CAPTURE_STILL: thread.handleStillPicture((String) msg.obj);
这样一个基本的摄像头开启、拍照流程就走完了,当然还有很多细节处理没有一一列出来,如异常处理、各种状态判断等等,这些需要在实际项目开发中根据自己的业务来做处理即可。
未来我会继续去扒jni层的代码,看看底层开启相机具体又做了哪些事情,就请继续期待后续的分享文章。
相关内容
参考链接:https://www.jianshu.com/p/9108ddfd0a0d