这可能是介绍 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 库的源码,作者还提供了 9 个 demo。这 9 个 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’’

【error4】

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.

【error5】

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 的内容,讲到这里就结束了。感谢大家的阅读。也欢迎大家一起交流。