这可能是介绍Android UvcCamera最详细的文章了

设备外接usb摄像头,进行基本的预览、拍照、录像。相信有些同学在工作中有遇到类似的需求。

usb摄像头在Android设备上,应用的确很广泛,我之前公司做的是车载产品,众所周知,现在汽车上是安装了越来越多的摄像头,通过摄像头采集的实时信息,能协助我们进行更加安全的驾驶。而这些摄像头,除了比较常见的mipi摄像头,很多也开始用的usb摄像头。

除了车载产品,类似安防、医疗等各方面,和监控、视频相关的很多领域,也都会用到usb摄像头。

uvc camera?有camera不管你之前有没用过,有没遇到过,相信看完这篇文章,一定会带给你不一样的收获。

这篇文章将从下面几点展开讲解:

一、什么是UVC?
二、UVCCamera开源项目?
三、开源项目集成?
四、遇到的问题及解决?

一、什么是UVC?

UVC全称为USB Video Class,直接翻译过来的意思就是:USB视频类,它是一种专门为USB视频捕获设备定义的协议标准。

这个标准是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已经成为USB org标准之一。

现在的主流操作系统,都已提供UVC设备驱动,因此符合UVC规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。是的,目前Android系统已经支持uvc设备。

小结:

讲到这里大家应该有这么个概念了,uvc是一种协议,不同的设备可能会支持不同的协议。如果我们的usb摄像头,需要在Android设备上获得支持的话,那这个摄像头就得是支持uvc协议的摄像头。

二、UVCCamera开源项目?

https://github.com/saki4510t/UVCCamera

现在我们在网上搜索uvc camera相关的文章,能查找到的uvc camera相关的项目,可以毫不夸张的说,基本都是基于上面这个开源项目来改的,这个开源项目的确比较牛逼,而且类封装的很好,代码逻辑比较清晰,使用起来也是非常的方便,而且关于摄像头基本的预览、拍照、录像功能都实现了,是个比较完整的工程项目。

我们通过git pull先把代码拉到本地,导入到AndroidStudio中,(不通过git pull 也行,直接下载代码也是可以的。github 网站在国内不翻墙的话,可能有时访问不了,如何访问不了,大家也可以尝试在gitee上去搜索这个项目下载)。

整个工程的目录结构如下图所示。当然导入的过程中,会遇到一些报错的问题,其实主要是gradle版本的问题,导入报错的问题,我们统一在这个文章后面再给大家做详细的讲解,包括遇到的问题以及是如何去解决这些问题的。



这个开源项目,除了sdk库的源码,作者还提供了8个demo。这8个demo的具体功能介绍如下:

1)USBCameraTest0
            显示如何使用SurfaceView来启动/停止预览。

2)USBCameraTest
            显示如何启动/停止预览。这与USBCameraTest0几乎相同,
            但是使用自定义的TextureView来显示相机图像而不是使用SurfaceView。

3)USBCameraTest2
            演示如何使用MediaCodec编码器将UVC相机(无音频)的视频记录为.MP4文件。
            此示例需要API>=18,因为MediaMuxer仅支持API>=18。

4)USBCameraTest3
            演示如何将音频(来自内部麦克风)的视频(来自UVC相机)录制为.MP4文件。
            这也显示了几种捕捉静止图像的方式。此示例可能最适用于您的定制应用程序的基础项目。

5)USBCameraTest4
            显示了访问UVC相机并将视频图像保存到后台服务的方式。
            这是最复杂的示例之一,因为这需要使用AIDL的IPC。

6)USBCameraTest5
            和USBCameraTest3几乎相同,但使用IFrameCallback接口保存视频图像,
            而不是使用来自MediaCodec编码器的输入Surface。
            在大多数情况下,您不应使用IFrameCallback来保存图像,因为IFrameCallback比使用Surface要慢很多。
            但是,如果您想获取视频帧数据并自行处理它们或将它们作为字节缓冲区传递给其他外部库,
            则IFrameCallback将非常有用。

7)USBCameraTest6
            这显示了如何将视频图像分割为多个Surface。你可以在这个应用程序中看到视频图像并排观看。
            这个例子还展示了如何使用EGL来渲染图像。
            如果您想在添加视觉效果/滤镜效果后显示视频图像,则此示例可能会对您有所帮助。

8)USBCameraTest7
            这显示了如何使用两个摄像头并显示来自每个摄像头的视频图像。这仍然是实验性的,可能有一些问题。

9)usbCameraTest8
            这显示了如何设置/获取uvc控件。目前这只支持亮度和对比度。

提供的demo,代码逻辑都很清晰,大家可以根据自己的需求去看对应的demo。这些demo包含了预览、录像、拍照这些基本的功能。关于调节亮度、对比度这点,可能是不同摄像头的原因,我本地验证了下,看实际上并没有效果,如果有哪位同学后面试试到有效果的,欢迎给我留言,大家交流交流。

Demo7我们可以看到是一个支持2个摄像头的Demo。有多摄像头支持需求的,可以参考这个里面的逻辑。记得之前做车载的时候,公司内部在高通8953上的一个预研项目,是挂了6个摄像头,那会驱动的同事也是花了不少的时间去点亮这些摄像头,现在想想,如果采用uvc摄像头,再参考这个开源项目,那应该很快就可以搞个简单的demo出来。

三、开源项目UVCCaemra的编译、集成?

UVCCamera的核心代码都在libuvccamera里面了。

我们要在我们的项目工程中集成这个项目的话,需要2个东西,一个是so库,一个可以调用的java sdk源码。

从上面的截图我们可以很清楚的看到,代码里面主要是包含了jni和java两大部分的内容。编译jni,就可以得到我们需要的so库,java代码可以打包成aar,或者之间直接把整个代码复制到我们的工程目录下,作为库引用也是可以的。

1) so库的编译

现在so库的编译,已经非常的方便了,如下图所示,我们在as的Terminal终端界面,切到jni目录下,直接ndk-build,就可以生成我们需要的so库文件了。



这里有个地方我们得注意下,就是我们需要Android 32位还是64位的库文件,这个是在Application.mk里面配的,上面的截图我已经把Application.mk这个文件的位置圈出来了。如果是32位,这里边APP_ABI的内容修改为armeabi-v7a即可,64位则是arm64-v8a,其它平台的类推。

2)打包aar

我们项目要集成这个开源项目,那肯定得提供java代码我们才能调用。我这里采用的方式是,把UVCCamera的核心代码(也就是不包含8个demo)的内容,打包成aar,然后在我自己的工程目录中引用打包好的aar.
打包成aar在AS里面操作也是非常简单的。先贴下图片。



从上面的截图,我们可以看到,需要打包的module有2个,分别是libuvccamera和usbCameraCommon。按照截图上的操作顺序来,从1到3。先是点击as界面右侧的Gradle,在腾出的界面中,双击执行assembleRelease,执行完没有啥报错的话,在module的build output路径下,就可以看到生成的aar文件了。

最后就是把生成的so库文件,已经生成的aar文件,都拷贝到我们自己工程的libs目录下,导入到项目中使用即可。

3)将UVCCamera sdk集成到自己项目
通过上面的步骤,我们已经成功的编译出了so库文件以及aar文件。下图显示的就是我们把生成的文件导入到我们自己的工程项目中。


四、demo小改动:录像的同时,获取实时yuv流?

关于uvcCamea的文章,我之前也写过一篇,这里贴下地址,连同demo地址也一并给出,感兴趣的同学也可以看看。

这个demo,除了基本的预览、拍照、录像功能,我根据自己的需求,还添加了个返回实时yuv流的接口。对有需求将实时流视频流进行类似人脸识别、上传后台之类的,相信能给你带来帮助。

“一篇文章带你了解Android Usb摄像头”
https://www.jianshu.com/p/35124f098c24

demo地址:
https://github.com/yorkZJC/UvcCameraDemo

关于我自己的这个demo,如果需要修改分辨率,统一在如下图所示的MyConstants.java文件中修改即可。


下面截图展示的是yuv流回调的接口。

五、遇到的问题及解决?

1)sdk、ndk配置?

第一步,我们需要把sdk、nkd先配置好,sdk相信很多同学都会配,另外就是这里需要用ndk-build来编译so库,所以ndk一定得配好,我本地的ndk版本是r17,相信这个ndk的版本影响不是很大。

ndk的配置,有2种方式,可以直接在local.properties文件种修改,也可以在视图界面,Project Structure中选择我们本地的nkd路径来配置。下面截图分别对应的是这2种不同的修改方式,采用其中任意一种即可。



2)导入Android Studio,gradle版本配置?

下面是我遇到的一些问题,按照我的修改操作来,相信大家都能成功运行起来。
【error 1】

Caused by: org.apache.http.conn.HttpHostConnectException: Connect to maven.google.com:443 
[maven.google.com/142.250.204.46] failed: Connection timed out: connect

【error2】

ERROR: The minSdk version should not be declared in the android manifest file. You can move the version from the manifest to the defaultConfig in the build.gradle file.
Remove minSdkVersion and sync project
Affected Modules: libuvccamera

【error3】

* What went wrong:
Execution failed for task ':libuvccamera:ndkBuild'.
> A problem occurred starting process 'command 'null/ndk-build.cmd''
Android NDK: The armeabi ABI is no longer supported. Use armeabi-v7a.    
Android NDK: NDK Application 'local' targets unknown ABI(s): armeabi mips    
D:/APPS/sdk/android-ndk-r17b/build//../build/core/setup-app.mk:79: *** Android NDK: Aborting    .  Stop.
2021-06-11 10:08:11.386 3105-3105/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.serenegiant.usbcameratest0, PID: 3105
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.serenegiant.usbcameratest0/com.serenegiant.usbcameratest0.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3432)

我是通过git pull拉代码到本地的,所以本地的每笔修改都可以通过git 来追踪记录到。关于编译报错的,我们来看下我一共是修改了哪些内容。



上面截图,我们看到一共是修改了5个地方。
i、工程根目录下build.gradle的修改;
ii、libuvccamera/build.gradle的修改;

3)拔出usb摄像头,crash异常导致应用退出?

原生的库文件,有个bug,就是在使用usb摄像头的过程中,我们去把摄像头拔掉,这时so库有个crash,到导致我们的应用直接异常退出。

这个问题,网上的其它大神已经给出了解决方案,我这里贴下修改的地方。我自己也是亲自修改验证了。


diff --git a/libuvccamera/src/main/jni/libusb/libusb/os/android_usbfs.c b/libuvccamera/src/main/jni/libusb/libusb/os/android_usbfs.c
index 8626595..c4842c4 100644
--- a/libuvccamera/src/main/jni/libusb/libusb/os/android_usbfs.c
+++ b/libuvccamera/src/main/jni/libusb/libusb/os/android_usbfs.c
@@ -2726,6 +2726,12 @@ static int handle_iso_completion(struct libusb_device_handle *handle,    // XXX add

        usbi_mutex_lock(&itransfer->lock);
        for (i = 0; i < num_urbs; i++) {
+           //+Add by york.zhou on 2021.05.19,fix issue app crash on remove usb device
+           if (tpriv->iso_urbs == NULL){
+            break;
+        }
+        //-Add by york.zhou on 2021.05.19,fix issue app crash on remove usb device
+
                if (urb == tpriv->iso_urbs[i]) {
                        urb_idx = i + 1;
                        break;
diff --git a/libuvccamera/src/main/jni/libuvc/src/stream.c b/libuvccamera/src/main/jni/libuvc/src/stream.c
index 8a1e90a..b7cedcc 100644
--- a/libuvccamera/src/main/jni/libuvc/src/stream.c
+++ b/libuvccamera/src/main/jni/libuvc/src/stream.c
@@ -641,7 +641,8 @@ static void _uvc_delete_transfer(struct libusb_transfer *transfer) {
                                libusb_cancel_transfer(strmh->transfers[i]);    // XXX 20141112追加
                                UVC_DEBUG("Freeing transfer %d (%p)", i, transfer);
                                free(transfer->buffer);
-                               libusb_free_transfer(transfer);
+                               //+Add york.zhou 2021.05-19,fix remove usb devices,app crash
+                               //libusb_free_transfer(transfer);
                                strmh->transfers[i] = NULL;
                                break;
                        }

4)有些usb摄像头识别不了?

有些同学可能还会遇到有些usb摄像头识别不了的问题。这里面有个前提,就是确认这个usb摄像头,插到电脑上是能正常识别使用的,只是插到我们的设备上,识别不了。

遇到这种问题,可以抓个完整的logcat日志,然后在日志中全局搜索subclass,将搜索到的subclass相关信息,按照下面截图的格式,在xml 目录下的device_filter.xml中配置下。


关于uvcCamera的内容,讲到这里就结束了。感谢大家的阅读。也欢迎大家一起交流。

推荐阅读更多精彩内容