Android Camera 多路同采的探索实践

MCtalk 技术文章/2021.11.24 文|Iven 网易云信资深音视频客户端开发工程师
导读:
目前主流的 andriod 手机基本上都是配备了2个以上 Camera 设备。那么针对越来越多的 Camera 设备,对于开发者,该如何使用呢 ?本文主要对如何同时采集多路数据进行了探索实践。
Android Camera 基础
Camera 与 Android 系统架构的联系
Android 系统按照分层设计原理,可以分为应用框架,进程间通信(BinderIPC), 系统服务,硬件抽象(HAL),以及 Linux 内核。
Camera Service:Linux 系统的用户空间 init 进程创建以后,派生出 Java 的第一个孵化进程 zygote, 由 zygote 孵化出 system_server 进程,托起整个 Java framework 的所有 service;mediaserver 进程也由 init 进程创建,托起整个 C++ framework 的所有 service,其中包括了 Camera service。Camera service 向 Java framework 提供 device,session,stream 相关的能力接口。
Camera HAL:处在 HAL 层,封装各个 SOC 的 Camera 算法核心。比如高通某芯片平台(SM8350)的 Camera 3A 算法,图像处理算法,都是针对该平台的 Camera sensor 或者 ISP 进行了参数调优,这个模块单独存在与 HAL 层,通过 HAL 层的接口,向上层提供能力调用。
Camera Driver:是 Linux Kernel 中 驱动 Camera 设备的驱动程序, 一般使用 V4L2 驱动框架,这是业界公认的关于视频采集框架。为 Linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的 API 操作不同的视频设备。通过数据流和控制流,来驱动 Camera sensor。
Project Treble 对于 Camera 系统进程的影响
Android 7.x 及更早版本中没有正式的供应商接口,因此设备制造商必须更新大量 Android 代码才能将设备更新到新版 Android 系统:
Android 8.0 版本的一项新元素是 Project Treble。旨在让制造商以更低的成本更轻松、更快速地将设备更新到新版 Android 系统:
新架构加入带来了 Camera 系统进程的变化:系统启动时,先会启动 Cameraprovider进程:android.hardware.camera.provider@2.4-service 来控制 HAL,然后启动 Cameraserver 进程 Android FrameWork 和 HAL 层的交互;而在老版本中,并没有这两个进程,Camera service 都是在 mediaserver 进程中。
老版本:
新版本:
Android Camera 多路同采
从两个方面来阐述多路同采方案:
单个 Camera 硬件设备,同时输出多路数据流;
多个 Camera 硬件设备,同时打开,并采集多路数据流;
单个 Camera 硬件设备,同时输出多路数据流
Camera 作为视频数据源的生产者, 可以为 Android 系统不同消费者提供视频数据。这些消费者可以包括:SurfaceView,ImageReader,SurfaceTexture,MediaCodec,RenderScript Allocation,MediaRecord。
-
RenderScript Allocation 可以提供并行的密集型计算,特别适合执行图像处理的计算任务;
-
ImageReader 可以直接从surface 中读取指定格式的数据,比如 YUV_420_888,但并非所有 Android 数据格式都能提供,有些数据格式就无法获取,比如 ImageFormat.NV21。这使得 Android 在 Camera2 上无法通过 ImageReader 获取 NV21 格式的数据,从而跟 Camera1 保持功能对齐。
-
SurfaceTexture 提供输出到 OpenGL Es 的 OES 纹理,在 GPU 内部转换成 RGB 纹理进行渲染。
CameraHAL 层,可以向 Camera service 同时提供两路 Camera 数据,Camera Stream1 和 Camera Stream2。Camera Stream1 的数据最终可以直接通过 SurfaceView 进行渲染。Camera Stream2 通过共享的方式拆分成了两路, 分别往不同的两个 surface 中填充数据,也就是下图中对应的 ImageReader,和 SurfaceTexture,这两路数据内容一模一样。
所以 Android 系统目前头同时输出的最高路数,可以达到3路。并且因为采用了共享数据流拆分的方式,其中两路的内容是完全一致的,也就是可以提供两路内容完全一致的 buffer 数据和 OES 纹理数据。适合需要从 Camera 同时获取两路不同格式的数据,进行处理的情况。
笔者尝试了2路同采的实现,以验证 buffer 和纹理数据是否同步输出。而实验数据证明,buffer 数据和 OES 的纹理数据,并非都是同步输出的, 因为在打开 Camera,数据刚回调的时候,有几帧纹理数据没有同步回调的情况。
通过丢弃前几帧数据的逻辑,看到了预期结果:buffer 和纹理同步输出,并且内容一致。
性能对比
-
CPU:双采集和单采对于系统的 CPU 消耗基本是持平的;
-
Memory:双采集和单采对于系统的 Memory 消耗基本是持平的;即使在2G 内存的老手机上,Memroy 消耗双采也只比单采多1%。
多个 Camera 硬件设备,同时输出多路数据流
多个摄像头分别输出多路,从以下两个方面讲解:
-
打开 Android 设备自带的多个 Camera, 每个 Camera的数据单独采集;
-
打开 Android 外界 USB Camera;
打开多个设备自带 Camera:
Android 手机中自带的 Camera 设备, 往往功能侧重点不同:比如超广角,不同景深,不同采集分辨率等。通过多摄像头同时采集的方式, 可以用于背景虚化,超广角拍摄,拍摄数据的景深计算等。这个功能需要使用 Camera2 架构,且 Android API 28+,并且虽然 google 强烈推荐,但是真正支持这个功能的手机非常少,目前测试中发现两款支持该功能的手机:Huawei P30 pro/Google Pixel 4;
- 逻辑和物理摄像头 :手机自带的物理摄像头越来越多,比如前置双摄,后置4个摄像头。但是在大多数 Android 开发场景中,不需要去了解操作这么多的物理摄像头设备,往往只需要知道前置和后置摄像头的设备名字即可,比如一般来说,前置摄像头名字是“0”,后置摄像头名字是“1”。这个“0”,“1” 其实就是逻辑摄像头的概念。“1”的后置摄像头,一般对应后置的多个物理摄像头, 多个物理摄像头共同工作,在系统层面将采集到的多流数据进行融合,最终通过数据回调,给应用开发者。
- 多个物理摄像头单独配置:Android 给一些特殊场景提供了多流定制的能力。也就是说单独配置多个物理摄像头,并且获取多个物理摄像头的数据流。开发者通过设置对应摄像头的目标输出,也就是前面提到的数据流消费者,比如ImageReader/SurfaceTexture,得到对应摄像头的数据。每个摄像头的格式,长宽,可以独立指定。
下图是通过对3个不同物理摄像头的配置,最终通过 SessionConfiguration, 与 CameraDevice 建立会话联系。
打开 USB WebCamera:
笔者基于开源代码 libuvccamera.so,尝试发现在 USB WebCamera 打开的情况下,同时也可以打开自带 Camera。可以看到 WebCamera 数据输出和 Android 自带的 Camera可以同时输出数据。
如何打开并获取 USB WebCamera 数据:
-
方法1: 通过 Android 系统API:获取LENS_FACING_EXTERNAL 的 Camera, 依赖 SOC 实现,目前使用场景以及支持的设备很少。
-
方法2:使用开源库:https://github.com/saki4510t/UVCCamera。 这个开源项目通过对 UVC 驱动的访问,来获取 USB Camera 的数据。开发者可以通过集成 libuvccamera.so 库,访问没有 root 过的手机 USBCamera。
在 Xiaomi mix4(Android 11) 和 google pix 4 (Android 12)上,测试都可以采集成功,但是测试过程发现,采集得到的画质不太稳定,存在闪烁的情况,并且在部分手机上无法访问。
总结
本文从单个摄像头同时采集多路,以及多个摄像头同时采集两个方面阐述了多路同采的实现方法,以及遇到的一些兼容性问题。
从单个摄像头返回多路数据的方案,比较成熟,老的 Camera1 架构和新的 Camera2 架构都支持。并且从性能消耗来看,单摄像头双路同采,性能增加不大。同时采集两路数据, 可以是 buffer 数据,也可以是 Texture 数据, 这对于需要不同数据格式同时使用的场景,提供了非常便利的方式。
比如在 RTC 场景中,通过纹理数据和 buffer 数据的策略使用, 可以尽可能的发挥设备 GPU 和 CPU 的能力,缓解性能问题。比如纹理直接用来本地渲染和硬件编码或者图像算法,buffer 作为输入,传递给软件编码或者图像算法等。不过在多路输出的时候,需要关注下数据是否同步, 尤其在刚打开摄像头的时候,存在两路数据不同步的情况。
从多个摄像头同时采集的方案,多个摄像头的同时采集,可以提供不同景深,不同焦距下的视频数据,为图像算法处理提供了更多的可能性。但是支持该功能的机型上比较欠缺,依赖于各手机厂商的支持。
最后本文还稍加讨论了Android 手机对于 USB Camera 的支持情况。Android 系统底层是基于 linux 系统的,linux 系统可以通过 V4L2 采集框架,访问 UVC 驱动。但是还是由于碎片化,虽然 Android 系统定义了 LENS_FACING_EXTERNAL,但是能获取到 USB Camera 设备的数据依然是很少。通过对开源库 UVCCamera 的依赖,在没有 root 过的手机上,可以访问 USB Camera, 但是从采集图像质量和稳定性上,UVCCamera 的方案依然还需要进一步优化。