编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题

UVC相机设计和实践

目录介绍

  • 01.Camera基础理论
    • 1.1 Camera工作原理
    • 1.2 相机系统介绍
    • 1.3 什么是相机模组
    • 1.4 图像处理器ISP
    • 1.5 常见双摄方案
  • 02.UVC基础概念介绍
    • 2.1 UVC概念须知
    • 2.2 Android UVC标准
    • 2.3 UVCCamera相机功能
    • 2.4 UVCCamera项目介绍
    • 2.5 UVCCamera库介绍
  • 03.UVC库设计架构思想
    • 3.1 UVC整体架构设计
    • 3.2 UVC中USB连接设计
    • 3.3 UVC协议解析设计
    • 3.4 UVC图像数据采集设计
    • 3.5 UVC图像渲染设计
  • 04.UVCCamera实践
    • 4.1 UVC最简单实践
    • 4.2 USB驱动连接和断开
    • 4.3 UVC相机视图渲染
    • 4.4 UVC相机采集数据
    • 4.5 UVC相机数据格式
    • 4.6 改变相机预览参数
    • 4.7 开启和改变灯光
    • 4.8 如何编译jni为so库
  • 05.UVCCamera原理分析
    • 5.1 USB连接流程分析
    • 5.2 打开UVC相机流程
    • 5.3 开始渲染View
    • 5.4 相机预览流程分析
    • 5.5 UVC相机如何采集
    • 5.6 UVC图像数据类型
    • 5.7 高效高频传数据
  • 06.遇到的疑难杂症
    • 6.1 开启灯光遇到异常

01.Camera基础理论

1.1 Camera工作原理

  • Camera工作原理
    • 光线---->镜头Lens---->IR Filter过滤红外光---->sensor将光信号转化为电信号---->ADC转为数字信号---->DSP处理---->RGB/YUV格数输出
  • 镜头:
    • 常用的镜头结构有1P、2P、1G1P、1G2P、。。5G等,透镜越多,成本越高,相对成像效果越好。
  • 红外滤光片IR Filter
    • 人眼无法看到红外光,但是sensor能感受到,所以需过滤掉红外光,使得图像更接近人眼所看到的效果。
  • 其他一些相机基础原理
    • https://blog.csdn.net/qq_40405527/article/details/108131655

1.2 相机系统介绍

  • 相机系统可以分为两个部分
    • 一个是相机模组,一个是图像处理器ISP,相机模组是用来进行进行光电转换的,而图像处理器是用于处理图像的。
    • image
      image

1.3 什么是相机模组

  • 相机模组往往做得十分精致小巧
    • 里面主要包括了镜头、对焦马达、滤光片以及感光器(Sensor)。
  • 镜头主要存在以下几个参数
    • 视场角FOV,该参数表明了通过镜头可以成像多大范围的场景,一般FOV越大就越能看到大范围的景物,但是有可能会带来严重的畸变,通常使用后期的畸变矫正算法来修正大FOV所带来的畸变。
    • 焦距F ,规定所有平行于透镜主轴的光线汇聚到的那点叫做焦点,而焦点到透镜中心的距离便是这里的焦距,一般焦距越大,镜头的FOV也就越小。而越短的焦距,往往FOV越大。
    • 光圈值f,通过镜头焦距与实际光圈的直径比值来指定,该值越小,说明进光量也就越大,手机镜头一般采用f/2.0的固定光圈。

1.4 图像处理器ISP

  • ISP(Image Signal Processor)是一种专门用于图像处理的芯片或模块。
    • 它通常嵌入在数字相机、智能手机、摄像头和其他图像设备中,用于处理从图像传感器捕获的原始图像数据。
  • ISP的主要功能是对原始图像数据进行各种处理和优化,以提高图像质量和增强图像特性。
    • ISP在图像设备中起着关键作用,它能够实时处理图像数据,并根据设备和应用的需求进行优化和增强。通过ISP的处理,用户可以获得更好的图像质量和更多的图像处理选项。

1.5 常见双摄方案

  • 双摄技术如何理解
    • 采用了两个摄像头模组分别成像,并通过特定的算法处理,融合成一张图像,达到特定成像需求的目的。
    • 普遍地,现在双摄方案主要用于实现背景虚化、提升暗光/夜景条件下成像质量、光学变焦。
  • 什么场景会用到双摄
    • 景观拍摄:通过使用一个广角镜头和一个超广角镜头的组合,双摄可以捕捉更广阔的景观,并提供更大的视野范围。
    • 背景虚化:通过使用一个主摄和一个辅助摄像头,双摄可以实现背景虚化效果,也称为景深效果。主摄用于捕捉主体,而辅助摄像头用于感知深度信息,从而模拟出背景模糊的效果。

02.UVC基础概念介绍

2.1 UVC概念须知

  • UVC全称为USB Video Class,直接翻译过来的意思就是:USB视频类,它是一种专门为USB视频捕获设备定义的协议标准。
    • 这个标准是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已经成为USB org标准之一。
    • 它定义了一组规范和命令,使得视频设备(如摄像头)可以以通用的方式与主机设备进行交互。
    • 现在的主流操作系统,都已提供UVC设备驱动,因此符合UVC规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。是的,目前Android系统已经支持uvc设备。
  • UVC协议的一些关键特点
    • image
      image
    • 标准化接口:UVC定义了一组标准化的接口和命令,使得视频设备可以与主机设备进行通信,而无需特定的驱动程序。这使得UVC设备可以在不同的操作系统和平台上通用使用。
    • 视频格式支持:UVC支持多种视频格式,包括常见的MJPEG(Motion JPEG)和YUV(YCbCr)格式。这使得不同类型的摄像头可以通过UVC协议进行通信,并提供兼容性。
    • 分辨率和帧率:UVC支持不同的视频分辨率和帧率设置。这使得用户可以根据需要选择适当的设置,以满足特定的应用需求。
    • 控制命令:UVC协议还定义了一组控制命令,用于控制和配置视频设备的各种参数,如亮度、对比度、饱和度等。这使得用户可以通过UVC协议与设备进行交互,并进行必要的调整和配置。
    • 插拔支持:UVC设备可以在运行时进行插拔,而无需重新启动或重新配置。这使得用户可以方便地连接和断开UVC设备,而不会中断正在进行的视频通信。
  • 关于更加详细UVC协议和概念
    • UVC介绍:https://www.usbzh.com/article/detail-80.html

2.2 Android UVC标准

  • Android UVC(USB Video Class)是指Android设备通过USB接口支持外部摄像头的标准。
    • UVC是一种通用的视频设备类别,它允许摄像头以一种标准化的方式与计算机和移动设备进行通信。
    • 通过UVC,Android设备可以通过USB连接外部摄像头,并使用相应的应用程序进行视频捕捉、录制和实时传输等操作。
    • 这为用户提供了更多的摄像头选择,以满足不同的需求和应用场景。
    • image
      image
  • 使用UVC外部摄像头的步骤如下:
    • 确保外部摄像头支持UVC标准,并具有与Android设备兼容的接口(通常是USB)。
    • 将外部摄像头通过USB连接到Android设备。
    • 在Android设备上安装支持UVC的应用程序,如视频通话应用、摄像头应用或其他需要使用外部摄像头的应用。
    • 通过应用程序,选择并且打开外部摄像头作为视频输入设备。
    • 根据应用程序的功能和需求,进行视频捕捉、录制、实时传输或其他操作。

2.3 UVCCamera相机功能

  • 为什么Android要使用UVCCamera?
    • 在Android手机上 如果我们使用 USBCamera设备 就得需要OTG功能,大部分手机的OTG功能都被厂商屏蔽掉了,如果想用就得ROOT设备,这个不现实。
    • 而开源项目UVCCamera,实现了手机无需root就支持USBCamera设备的检测、连接、预览和音视频数据采集等功能。
    • image
      image
  • OTG,什么是OTG?
    • OTG是一种让宿主设备(手机、平板)作为usb host的角色,允许其他usb设备(U盘、鼠标、UVC设备)连接上去。什么是OTG
  • UVC相机的功能介绍
    • (1) 支持USB Camera设备检测,画面实时预览;
    • (2) 支持抓拍jpg格式图片,可设置图片压缩质量;
    • (3) 支持录制mp4格式视频,可屏蔽音频,可设置视频和音频的录制参数;
    • (4) 支持获取camera支持的分辨率,和分辨率切换;
    • (5) 支持预览自动识别各种相机的分辨率;
    • (6) 支持旋转摄像头90度、180度、270度;
    • (7) 支持调整对比度、亮度、色调、饱和度、白平衡等等一些相机控制参数;
    • (8) 支持多预览和多摄像头;

2.4 UVCCamera项目介绍

  • 项目介绍
    • https://github.com/saki4510t/UVCCamera
  • 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控件。目前这只支持亮度和对比度。
  • 工程主要依赖库有
    • libuvccamera和usbCameraCommon
  • libuvccamera库介绍
    • 其中libuvccamera库是底层库,包括c++实现部分,外加几个基本控制类,如USBMonitor和UVCCamera类等。
    • USBMonitor类负责管理usb设备类,从该类可以选出自己需要的usb camera设备。
    • UVCCamera则表示该类就是一个usb的摄像头,基本方法均调用native,通过jni方式直接调用c++实现。该类是一个桥接类,连接上层控制到底层实现的基本事件转发。比如聚焦,亮度,缩放,白平衡,预览,打开,关闭等,均实际通过该类来实现。
    • CameraDialog则是以对话框的形式提供对USBMonitor的调用,如发现并找寻自己需要操作的设备,里面用到了DeviceFilter类,主要用来过滤设备,如果不过滤设备,则认为是所有usb设备,主要还是通过USBManager来获取设备,然后进行筛选。
    • DeviceFilter类,这个是实际的筛选类,主要依据xml中的filter进行筛选,也就是说可以在xml中定义自己想要的设备的class,或者说不想要的设备的class,然后用xmlparser进行解析该xml文件,之后用此filter就可以过滤出实际想要的deviceList。
  • usbCameraCommon库介绍
    • usbCameraCommon库是上层封装库,主要用来操作调用摄像头,也就是USBMonitor和UVCCamera等。
    • AbstractUVCCameraHandler类,该类基本提供了对于UVCCamera的调用,然后同时开放api给Activity使用。是一个Handler类,也就是说它主要负责消息发送接收,任何调用它的方法它都会转发给内部类,也就是其实内部主要维护的是一个Thread类,叫CameraThread。
    • CameraThread类为static类。任何发送给AbstractUVCCameraHandler的方法都会以消息的方式发送给CameraThread进行处理,当没有消息的时候,就什么也不做。CameraThread类内部持有UVCCamera类实例,所有调用CameraThread的方法又会发送给UVCCamera实例进行处理

2.5 UVCCamera库介绍

  • 关于核心so库文件介绍
    • libjpeg-turbo1500.so,这个是jpeg图像编码,解码和转码的库
    • libusb100.so,usb通信库
    • libuvc.so,建立在libusb库上的跨平台的usb视频设备库
    • libUVCCamera.so,UVCCamera库

03.UVC库设计架构思想

3.1 UVC整体架构设计

  • UVCCamera是一个用于Android平台的开源库,用于支持通过USB连接的UVC(USB Video Class)摄像头。下面是UVCCamera的整体架构设计:
    • USB连接管理:UVCCamera库通过Android的USB API与USB设备进行通信。它负责检测和管理USB设备的连接和断开,并处理USB设备的插拔事件。
    • UVC协议解析:UVCCamera库实现了UVC协议的解析和处理。它能够与UVC摄像头进行通信,并解析UVC协议中的命令和数据,以实现对摄像头的控制和数据传输。
    • 视频数据采集:UVCCamera库通过UVC协议从摄像头中获取原始的视频数据。它使用Android的Camera2 API或Camera API来采集视频数据,并将其传递给后续的处理模块。
    • 图像处理和渲染:UVCCamera库提供了一系列图像处理和渲染功能,用于对采集到的视频数据进行处理和显示。这包括白平衡校正、曝光控制、色彩校正、锐化、降噪、图像稳定等功能。
    • 预览和录制:UVCCamera库支持实时预览和视频录制功能。它能够将处理后的视频数据显示在Android设备的屏幕上,并提供录制功能,将视频数据保存为文件。
    • 回调和事件处理:UVCCamera库通过回调机制提供了与应用程序交互的接口。它可以通知应用程序有关连接状态、摄像头参数变化、图像数据可用等事件,并提供相应的回调方法供应用程序处理。
    • 用户界面和控制:UVCCamera库可以与应用程序的用户界面进行交互,以提供用户控制和设置摄像头参数的功能。它可以显示预览视图、提供拍照和录制按钮等用户界面元素。
  • 在Windows和Linux等常用操作系统上都能免驱对接UVC摄像头,从而免去驱动开发的麻烦。通过通用的应用程序就能对UVC摄像头进行控制和流预览。
    • USB 摄像头VUC部分内部主要分为一个 VC (Video Control Interface) 接口和一个 VS (Video Streaming Interface) 接口。
    • VC 接口主要起控制作用,内部有许多 unit 和 terminal 用来“控制”摄像头,可以通过 Process unit 设置视频流图像属性,比如白平衡、曝光等等。
    • VS 接口往往含有许多个设置,每一个设置都包含一个实时传输端点,虽然它们的端点地址可能相同,但是最大传输包大小不同,在 Class specific VS 接口中,包含多个 Format ,每一个 Format 包含多个 Frame。
    • Format 指的 YUV等,Frame 是各种分辨率如640 * 480、 1280*720等等。以上这些信息,都通过分析描述符来获得。
    • image
      image
  • UVC功能实现包括驱动部分和应用2部分:
    • A.UVC驱动部分主要实现UVC协议相关控制接口供上层调用,打通USB通道,准备好流传输。ZCU104的USB接口默认是主控模式,需要修改内核配置和设备树使其工作在设备模式。
    • B.UVC应用部分实现控制消息响应和流传递,将前级视频流转发到UVC视频流通道,通过底层驱动将视频流传输给USB主控端,同时接收用户配置参数,将参数分发给各个驱动模块。
    • image
      image

3.2 UVC中USB连接设计

  • USB设备开发思路步骤:
    • 第一步:发现设备。通过UsbManager调用getDeviceList可以获取当前连接的所有usb设备。
    • 第二步:打开设备。可以将机具与usb外设之间的连接想象成一个通道,只有把通道的门打开后,两边才能进行通信。
    • 第三步:数据传输。已经可以与usb外设进行数据传输
  • USB开发流程图如下所示
    • image
      image
  • 在UVCCamera库中,USB连接管理是通过Android的USB API实现的。UVCCamera库使用以下步骤来管理USB连接:
    • 初始化USB功能:在应用程序启动时,UVCCamera库会初始化USB功能,并获取USB设备的权限。这是通过请求USB权限和注册USB连接广播接收器来完成的。
    • 检测USB设备连接:UVCCamera库会监听USB连接广播,以检测USB设备的连接和断开事件。当USB设备连接时,库会获取USB设备的信息,如设备ID、接口等。
    • 打开和关闭USB设备:一旦检测到USB设备连接,UVCCamera库会尝试打开USB设备,并与之建立通信。这是通过Android的UsbManager和UsbDeviceConnection来实现的。
    • UVC协议通信:一旦USB设备打开成功,UVCCamera库会使用UVC协议与USB设备进行通信。它会发送UVC命令和请求,以控制摄像头的参数和功能。
    • 轮训监听USB设备断开:UVCCamera库会持续监听USB连接广播,以检测USB设备的断开事件。一旦USB设备断开,库会关闭设备连接,并释放相关资源。
    • image
      image

3.3 UVC协议解析设计

  • 在UVCCamera库中,UVC协议解析是通过与UVC摄像头进行通信来实现的。以下是UVCCamera库中UVC协议解析的一般步骤:
    • 打开UVC摄像头:在UVCCamera库中,首先需要打开UVC摄像头设备。这是通过与USB设备建立连接,并获取与UVC摄像头通信的接口。
    • 发送和接收UVC命令:一旦UVC摄像头打开成功,UVCCamera库可以通过发送UVC命令与摄像头进行通信。这些命令可以用于控制摄像头的各种参数和功能,如曝光、白平衡、对焦等。
    • 解析UVC命令响应:UVCCamera库会等待UVC摄像头对发送的命令进行响应。一旦收到响应,库会解析响应数据,以获取摄像头的当前状态和参数设置。
    • 获取和处理视频数据:除了控制命令,UVCCamera库还可以通过UVC协议获取摄像头传输的视频数据。它会解析视频数据的格式和帧率,并进行相应的处理和渲染,以显示或保存视频。
    • 关闭UVC摄像头:当不再需要与UVC摄像头通信时,UVCCamera库会关闭摄像头连接,并释放相关资源。

3.4 UVC图像数据采集设计

  • UVC相机采集全流程涉及到的核心类是:
    • com.serenegiant.usb.USBMonitor
    • com.serenegiant.usb.USBMonitor.UsbControlBlock
    • com.serenegiant.usb.USBMonitor.OnDeviceConnectListener
    • com.serenegiant.usb.common.UVCCameraHandler
    • com.serenegiant.usb.common.AbstractUVCCameraHandler.CameraThread
  • 核心关键流程如下所示:
    • image
      image
  • 在UVCCamera库中,图像数据采集是通过使用UVC协议与UVC摄像头进行通信来实现的。以下是图像数据采集的一般步骤:
    • 打开UVC摄像头:首先,UVCCamera库会打开UVC摄像头设备,建立与摄像头的连接。这是通过与USB设备进行通信,并获取与UVC摄像头通信的接口来实现的。
    • 发送UVC命令:一旦UVC摄像头打开成功,UVCCamera库可以通过发送UVC命令与摄像头进行通信。这些命令可以用于控制摄像头的各种参数和功能,如曝光、白平衡、对焦等。
    • 获取图像数据:通过UVC协议,UVCCamera库可以获取UVC摄像头传输的图像数据。这些数据可以是原始的YUV或RGB格式,也可以是经过压缩的数据。库会接收和缓存图像数据,以便后续的处理和显示。
    • 图像数据处理:一旦图像数据被获取,UVCCamera库可以对其进行处理。这包括解析图像数据的格式、调整图像的亮度、对比度、饱和度等,以及应用其他图像处理算法。
    • 图像数据显示或保存:处理后的图像数据可以通过UVCCamera库进行显示或保存。库可以将图像数据渲染到Android设备的屏幕上,或将其保存为图像文件。
    • 关闭UVC摄像头:当不再需要与UVC摄像头通信时,UVCCamera库会关闭摄像头连接,并释放相关资源。

3.5 UVC图像渲染设计

  • 在UVCCamera库中,图像渲染是通过使用Android的图像渲染API来实现的。以下是UVCCamera库中图像渲染的一般步骤:
    • image
      image
    • 获取图像数据:UVCCamera库会从UVC摄像头获取图像数据,这可以是原始的YUV或RGB格式,也可以是经过压缩的数据。
    • 创建渲染表面:UVCCamera库会创建一个用于图像渲染的表面。这是一个SurfaceView的Surface。
    • 图像数据转换:如果图像数据的格式与渲染表面的格式不匹配,UVCCamera库会进行图像数据的格式转换。这可以包括颜色空间转换、像素格式转换等。
    • 图像数据传输:一旦图像数据准备就绪,UVCCamera库会将图像数据传输到渲染表面。这可以通过SurfaceView的绘制等方式来实现。
    • 图像渲染:一旦图像数据传输完成,UVCCamera库会使用图像渲染API将图像数据渲染到Android设备的屏幕上。这可以通过SurfaceView的绘制等方式来实现。

04.UVCCamera介绍

4.1 UVC最简单实践

  • 关于UVC最简单的调用如下琐事
    CameraConfig config = new CameraConfig.Builder()
            .bindDisplayView(textureView)
            .setCameraId(cameraRgbId)
            .setDisplayOrientation(270)
            .setPreviewSize(mDefaultPreviewWidth, mDefaultPreviewHeight)
            .setDataCallback(mRgbCallBack)
            .build();
    rgbCamera = new AndroidCameraUVC(config);
    rgbCamera.init(activity);

4.2 USB驱动连接和断开

  • 在什么时候注册usb监听,什么时候取消
    • 在开启相机和渲染的时候,开始注册usb监听,主要是监听usb设备attached连接和detached断开操作。
    • 在关闭相机和停止渲染的时候,开始解绑usb广播监听。
  • 通过广播自动监听usb连接和断开
    • 广播监听到设备连接时,就开始通过接口回调监听,调用onAttach方法告知外部开发者。可以在该回调中请求设备权限。
    • 广播监听到设备断开时,就开始通过接口回调监听,调用onDettach方法告知外部开发者。可以在该回调中关闭相机。
  • 通过OnDeviceConnectListener监听事件,即可监听到usb连接和断开事件
    mUSBMonitor = new USBMonitor(this, new OnDeviceConnectListener() {
        @Override
        public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew) {
            Log.e(TAG, "OnDeviceConnectListener onConnect " + device.getDeviceName() + " , " + device.getDeviceId());
        }
    
        @Override
        public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock) {
            Log.e(TAG, "OnDeviceConnectListener onDisconnect " + device.getDeviceName() + " , " + device.getDeviceId());
        }
    });

4.3 UVC相机视图渲染

  • 接上一步在设备attach时,开始请求设备权限
    • 当请求设备权限成功之后,就开始跟设备进行连接。先是通过openDevice打开对应的设备获取连接的对象mConnection!
  • 视频渲染这块,UVC相机选择的是:TextureView。当USB连接成功后,即开始进行视频渲染逻辑操作
    • 在Android UI中对于纹理的封装就是SurfaceView或者TextureView,而在UVCCamera中就是UVCCameraTextureView,它继承自android.view.TextureView。
    mUSBMonitor = new USBMonitor(this, new OnDeviceConnectListener() {
        @Override
        public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew) {
            Log.e(TAG, "OnDeviceConnectListener onConnect " + device.getDeviceName() + " , " + device.getDeviceId());
            //打开相机
            openCamera(ctrlBlock);
            new Thread(() -> {
                // 休眠50ms,等待Camera创建完毕
                UVCCameraLogger.logWrite("onConnect 休眠50ms,等待Camera创建完毕");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 开启预览
                UVCCameraLogger.logWrite("onConnect 开启预览");
                startPreview(mCamView);
            }).start();
        }
    
        @Override
        public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock) {
            Log.e(TAG, "OnDeviceConnectListener onDisconnect " + device.getDeviceName() + " , " + device.getDeviceId());
        }
    });

4.4 UVC相机采集数据

  • 如何监听相机采集的图片数据,外部开发者设置监听
    private final ICamera.CameraCallBack mRgbCallBack = (data, config) -> {
        //处理相机回调数据
    };
  • 那么这个是怎么监听相机采集frame数据呢?
    // 具体看:AbstractUVCCameraHandler
    private final IFrameCallback mIFrameCallback = new IFrameCallback() {
      @Override
      public void onFrame(final ByteBuffer frame) {
          //这个是相机数据的回调
          //UVCLogger.logWrite("CameraThread IFrameCallback onFrame " + frame);
          int len = frame.capacity();
          //创建yuv数据
          final byte[] yuv = new byte[len];
          frame.get(yuv);
          if (CameraThread.this.mOnPreViewResultListener != null) {
              CameraThread.this.mOnPreViewResultListener.onPreviewResult(yuv);
          }
      }
    };

4.5 UVC相机数据格式

  • UVC相机采集的数据一般是什么格式的?
    • 一般视频采集芯片采集到数据都是按照YUV格式输出,包括很多现在很多的相机。
  • 如何理解YUV
    • YUV也是一种颜色空间,一种编码格式,当初是为了兼容黑白电视和彩色电视机设计的。Y、U、V分别占用8位
    • Y 表示明亮度(Luminance、Luma),也就是灰阶值。
    • U、V 表示色度(Chrominance 或 Chroma),描述的是色调和饱和度
  • YUV采样格式有3种:
    • YUV444 采样: 每1个Y分量对应一组UV分量,一个像素共占用(8+8+8)bit = 3byte
    • YUV422 采样: 每2个Y分量对应一组UV分量,一个像素共占用(8+81/2 + 81/2)bit = 2byte
    • YUV420 采样: 每4个Y分量对应一组UV分量,一个像素共占用(8+81/4+81/4) = 1.5byte

4.6 改变相机预览参数

  • 改变摄像机预览参数(包括帧格式、宽度、高度、FPS)
    //停止相机预览
    mCameraHelper.stopPreview();
    //设置摄像机预览参数
    mCameraHelper.setPreviewSize(size);
    //开始相机预览
    mCameraHelper.startPreview();
    //需要自适应摄像头分辨率的话,设置新的宽高比
    mCameraViewMain.setAspectRatio(mPreviewWidth, mPreviewHeight);

4.7 开启和改变灯光

  • 这块针对AndroidCameraUVC封装一个api控制灯光
    • 大概调用api在UVCCamera类中的,nativeSetBrightness方法则是控制灯光的api,范围在0-255之间。
  • 遇到调用api调用后没有反应,网上查阅资源,对c++代码改造和放开判断条件
    • https://cloud.tencent.com/developer/article/2344014
    • 还是没有反应,该问题待排查中

4.8 如何编译jni为so库

  • 现在 so 库的编译,已经非常的方便了,我们在 as 的 Terminal 终端界面,切到 jni 目录下,直接ndk-build,就可以生成我们需要的 so 库文件了。
  • 把 Application.mk 这个文件的位置圈出来了。如果是 32 位,这里边 APP_ABI 的内容修改为 armeabi-v7a即可,64 位则是 arm64-v8a,其它平台的类推。

05.UVCCamera原理分析

5.1 USB连接流程分析

  • USB驱动连接和断开大概的实现思路是:
    • 第一步:通过注册广播,监听usb连接attach和detach操作。attach操作,当设备连接时调用,然后开始申请权限
    • 第二步:通过UsbManager类中的requestPermission申请权限,这一步很主要,要不然后面肯定是读取不到数据的。
    • 第三步:当获取到权限后,广播会收到回调,然后开始打开专用USB设备
  • 当我们启动相机的时候,第一件要做的事情就是要连接上摄像头,依然是usb摄像头,那么自然我们会需要尝试建立usb连接。而连接usb设备要做的第一件事就是获取权限:
    //USBMonitor类中
    @Override
    public synchronized boolean requestPermission(final UsbDevice device) {
        boolean hasPermission = UsbManagerHelper.getInstance().getUsbManager().hasPermission(device);
        if (hasPermission) {
            processConnect(device);
        } else {
            try {
                //如果没有权限,需要先申请权限,这一步很主要,要不然后面肯定是读取不到串口的数据的。
                UsbManagerHelper.getInstance().getUsbManager().requestPermission(device, mPermissionIntent);
            } catch (final Exception e) {
                processCancel(device);
                result = true;
            }
        }
        return result;
    }
  • 在获取到权限之后继而调用了processConnect方法来尝试建立usb连接:
    • 在USBMonitor#processConnect(),可以看到在第一次建立连接的时候会新建一个UsbControlBlock,这个类主要是用来管理USBMonitor、UsbDevice以及诸如vendorId等参数。
    • 在UsbControlBlock#(构造函数),在它的构造函数里会调用USBMonitor中mUsbManager的openDevice方法来创建连接。

5.2 打开UVC相机流程

  • 继续回到processConnect方法,在usb连接建立之后,会调用USBMonitor中的监听接口:mOnDeviceConnectListener
    • 这个接口是从外部创建USBMonitor时候实现的,而在该接口的onConnect方法里我们就可以拿到usb连接建立成功的回调,继续往下看
  • 接着我们看一下onConnect回调中做了什么事情
    • 在该回调里就可以调用UVCCameraHandler的open方法来准备真正启动相机。UVCCameraHandler是一个Handler,在其内部是通过Android的消息机制来管理整个相机的生命周期。
      @Override
      public void onConnect(final UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew) {
          mCtrlBlock = ctrlBlock;
          openCamera(ctrlBlock);
          new Thread(() -> {
              // 休眠50ms,等待Camera创建完毕
              UVCCameraLogger.logWrite("onConnect 休眠50ms,等待Camera创建完毕");
              try {
                  Thread.sleep(50);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              // 开启预览
              UVCCameraLogger.logWrite("onConnect 开启预览");
              startPreview(mCamView);
          }).start();
          if (listener != null) {
              listener.onConnectDev(device, true);
          }
      }
  • 然后开始分析打开相机的操作流程,最终会通过Java调用到native开启相机
    • UVCCameraHandler#openCamera(),UVCCameraHandler是一个Handler,在其内部是通过Android的消息机制来管理整个相机的生命周期。
    • AbstractUVCCameraHandler#open(),调用open方法的时候,其实是发送了一个message
    • CameraThread#handleOpen(),在handleMessage中会调用创建UVCCameraHandler时候同时创建的CameraThread的handleOpen方法。
      public void handleOpen(final UsbControlBlock ctrlBlock) {
      UVCLogger.logWrite("CameraThread handleOpen 打开相机");
      handleClose();
      try {
          final UVCCamera camera = new UVCCamera();
          camera.open(ctrlBlock);
          synchronized (mSync) {
              mUVCCamera = camera;
          }
          //通知外部开启相机
          callOnOpen();
      } catch (final Exception e) {
          callOnError(e);
          UVCLogger.logWrite("CameraThread handleOpen 异常 " + e.getMessage());
      }
      UVCLogger.logWrite("CameraThread handleOpen supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null));
      }
    • UVCCamera#open(),可以看到UVCCamera的open方法中调用了nativeConnect、nativeGetSupportedSize、nativeSetPreviewSize 这三个native的方法来真正启动相机。
    • CameraThread#callOnOpen(),相机启动之后会继续回到CameraThread的handleOpen方法,在该方法中又调用了callOnOpen来通知外部相机开启继而完成整个相机的启动过程。

5.3 开始渲染View

  • 具体定位到OnDeviceConnectListener回调onConnect方法中。这里面打开相机后,就开始渲染view
    mUSBMonitor = new USBMonitor(this, new OnDeviceConnectListener() {
        @Override
        public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew) {
            //开始渲染view操作
            startPreview(mCamView);
        }
    });
  • 在开启相机之后会接着调用startPreview方法来将采集到的数据绑定到一块surface上
    protected 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");
        }
        sendMessage(obtainMessage(MSG_PREVIEW_START, surface));
    }
  • 往下分析可以知道,关于设置渲染熟悉,开始渲染,最终都通过jni调用到c++中实现

5.4 相机预览流程分析

  • 首先看一段代码,如下所示:
    private CameraViewInterface.Callback mCallback = new CameraViewInterface.Callback() {
        @Override
        public void onSurfaceCreated(CameraViewInterface cameraViewInterface, Surface surface) {
            if (!isPreview && mCameraHelper.isCameraOpened()) {
                mCameraHelper.startPreview(mPreviewView);
                isPreview = true;
            }
        }
    };
  • 开始预览的事件传递
    • ---->Activity,在这个里面创建UVC相机然后初始化,这个时候直接调用AndroidCameraUVC,具体看该类中初始化方法
    • ---->AndroidCameraUVC#init,然后在接着看onSurfaceCreated方法回调的处理逻辑
    • ---->UVCCameraHelper#startPreview,在画面创建后,开始准备加载View渲染,接着往下看
    • ---->UVCCameraHandler#startPreview,然后接着调用super,看父类代码
    • ---->AbstractUVCCameraHandler#startPreview,发送一个handler消息,开始准备相机渲染
    • ---->AbstractUVCCameraHandler.CameraThread#handleStartPreview,发送给UVCCameraHandler的事件都经过消息发送给了CameraThread
    • ---->UVCCamera#startPreview(),这个最终调用native方法,最后调用nativeStartPreview方法开始渲染
  • 然后看看native层调用次序
    • native层由java类UVCCamera发起调用,调用传递为jni-->UVCCamera-->UVCCamera.cpp--->UVCPreview.cpp
  • 关于相机初始化到预览的时序图
    • image
      image

5.5 UVC相机如何采集

5.6 UVC图像数据类型

  • UVC(可以理解为USB摄像头)支持什么信号格式
    • 信号格式一般就图像的数据类型,压缩信号(H.264\HEVC\MJPG等),裸数据(YUV系),图像分辨率,图像帧率。
  • YUV和MJPG
    • 在绝大多数摄像头所采用的是免驱摄像头,一般有这几种传输格式,YUV和MJPG和RGB。
    • YUV是无压缩图像格式的视频,系统资源占用少(因为不用解码),不需要解码器,缺点是帧率稍慢(受限于USB分配的带宽),
    • MJPG是相当于JPEG图像压缩格式,优点是帧率高(视频开启快,曝光快),缺点是影像有马赛克,并且需要解码器,会占用系统资源。
  • 裸数据(YUV系)主要包含哪些?

5.7 高效高频传数据

  • 先来看一下相机采集数据的背景
    • UVC目前有两个相机采集,rgb相机,ir相机。相机的帧率是30fps/秒【实际上打印数据发现相机帧率在25~28fps/秒】,这样就可以理解为1秒钟,相机采集的图片是50~56张。
    • 相机采集的图片宽高是800600,这样一张NV21图片的存储大小是:(8006008 + 80060081/4 + 8006008*1/4)/8/1024/1024 = 0.68M
  • 图片是非常占用内存的,图片占用内存的计算公式是:
    • 比如Android中bitmap(将二进制数据转化为位图对象)的内存计算方式:占用的内存 = width * height * nTargetDensity/inDensity 一个像素所占的内存。
    • 例如,一个手机拍摄的 2700 * 1900 像素的照片,需要 5.1M 的存储空间,但是在图像解码配置 ARGB_8888 时,它加载到内存需要 19.6M 内存空间(2592 * 1936 * 4 bytes),从而迅速消耗掉该应用的剩余内存空间。
  • 在相机提取时,需要把二进制采集的图像数据转化为对象,这个时候就要注意高频率创建对象(包含图片)导致内存迅速增加的问题
    • 这里选择使用ByteBuffer存储图片数据,进行数据的传递。主要有以下几个优势
    • 1.内存映射文件或网络数据时,ByteBuffer可以作为传输数据的容器,避免了不必要的数据拷贝。
    • 2.在通道(Channel)之间转移数据时,ByteBuffer可以作为数据传输的载体,提高了数据传输的效率。
  • ByteBuffer的优势和应用场景:
    • 优势:ByteBuffer适用于处理二进制数据,具有高效的读写操作和灵活的数据处理能力。
    • 应用场景:网络通信中的数据传输、文件IO操作、加密解密、图像处理等。

06.遇到的疑难杂症

6.1 开启灯光遇到异常

  • 调用开启灯光的api遇到异常,异常原因如下所示:
    时间:2024-07-17 13:56:39.986-线程id:20533-tag:PalmSdk |: -打印消息:PSensor-Vendor关闭休眠模式,打开相机;
    时间:2024-07-17 13:56:39.986-线程id:20533-tag:PalmSdk |: -打印消息:PSensor-Vendor调用开启相机 onResumeCamera 1;
    时间:2024-07-17 13:56:39.995-线程id:20533-tag:PalmSdk |: -打印消息:UVCCamera :: 开始注册usb连接,然后恢复视图渲染 com.serenegiant.widget.UVCCameraTextureView{7e0fa1e V.ED..... ........ 0,-90-400,90 #7f080125 app:id/id_rgbView};
    时间:2024-07-17 13:56:40.004-线程id:20533-tag:PalmSdk |: -打印消息:UVCCamera :: 开始注册usb连接,然后恢复视图渲染 com.serenegiant.widget.UVCCameraTextureView{894b8cc V.ED..... ........ 400,-90-800,90 #7f080123 app:id/id_irView};
    时间:2024-07-17 13:56:40.004-线程id:20533-tag:PalmSdk |: -打印消息:PSensor-Vendor调用开启相机 onResumeCamera 2;
    时间:2024-07-17 13:56:40.005-线程id:20533-tag:PalmSdk |: -打印消息:PSensor 距离感应器,有物体靠近呢;
    时间:2024-07-17 13:56:40.005-线程id:20533-tag:PalmSdk |: -打印消息:UVCCamera :: setBrightness Exception java.lang.IllegalStateException;
    时间:2024-07-17 13:56:40.005-线程id:20533-tag:PalmSdk |: -打印消息:UVCCamera :: setBrightness Exception java.lang.IllegalStateException;
    时间:2024-07-17 13:56:40.005-线程id:20533-tag:PalmSdk |: -打印消息:BrightCallBack打开灯光,3;
    时间:2024-07-17 13:56:40.999-线程id:20588-tag:PalmSdk |: -打印消息:UVCCamera :: requestPermission 申请相机权限 2 相机索引 0;
    时间:2024-07-17 13:56:41.004-线程id:20592-tag:PalmSdk |: -打印消息:UVCCamera :: requestPermission 申请相机权限 2 相机索引 1;
    时间:2024-07-17 13:56:41.023-线程id:20588-tag:PalmSdk |: -打印消息:UVCCamera :: ConnectListener uvc相机 Connect camera id 0, isConnected = true;
    时间:2024-07-17 13:56:41.027-线程id:20592-tag:PalmSdk |: -打印消息:UVCCamera :: ConnectListener uvc相机 Connect camera id 1, isConnected = true;
    时间:2024-07-17 13:56:41.029-线程id:20591-tag:PalmSdk |: -打印消息:UVCCamera :: open 打开并开始连接相机;
    时间:2024-07-17 13:56:41.032-线程id:20595-tag:PalmSdk |: -打印消息:UVCCamera :: open 打开并开始连接相机;
  • 遇到该异常分析:
    • 开启灯光和调用相机开启几乎是同一时刻。但这里有一点要注意:必须等到相机连接成功后,才可以调用灯光设置api成功。相机从初始化到预览大概过程花费至少50毫秒!
    • 相机开启流程:【初始化->监听usb广播->attach->申请权限->open相机->connect连接->preview预览->frame回调】
  • 解决办法如下:
    • 调用灯光api时,判断相机是否已经预览成功,如果未成功则延迟50毫秒再设置灯光,只有这样灯光才可以OK

参考博客

  • 关于uvc相机灯光的技术博客
    • V4L2 driver -整体架构:https://www.cnblogs.com/linhaostudy/p/9486511.html
  • Camera的基础理论和工作原理
    • https://blog.csdn.net/qq_40405527/article/details/108131655
  • Android Camera开发系列(干货满满)
    • https://zhuanlan.zhihu.com/p/465126122
  • Uvc Usb Camera 调节亮度无效问题,搞定
    • https://cloud.tencent.com/developer/article/2344014
贡献者: yangchong211