Camera Hardware
分析,主要是分析 HAL 3
。
简述
术语表
HAL: Hardware abstraction layer
硬件抽象层CPP: Camera Post Processor
相机后处理VFE:VIDEO front-end
视频前端VPE:Video preprocessing
视频预处理
类文件速查表
1 | 1. HAL AOSP: hardware/interfaces/camera |
从 Android 8.0
开始,由于 Google
引入 Treble
架构,Framework
和 vendor
之间需要通过 HIDL
来通信,所以重写了 hardware
的代码结构。HAL
分为 AOSP
:Google
标准代码部分;以及 HAL qcom
:厂商具体实现部分。
HIDL
相关
HIDL
是复用了 Binder
机制,用于跨进程通信;所有跨进程通信相关的接口,都集中在 HIDL
这几个文件中。
1 | // hardware/interfaces/camera |
文件生成规则
.hal
文件可以生成对应的 .h, .cpp
文件,编译规则通常在同级目录的 Android.bp
中定义,这里选取 ICameraProvider.hal
为例:
1 | // Android.bp |
源文件只有两个 ICameraProvider.hal, ICameraProviderCallback.hal
,通过 hidl-gen
工具来生成对应的 .h, .cpp
文件,生成文件的目录为 out/soong/.intermediates/hardware/interfaces/camera/provider/2.4
目录下:
- 头文件
目录名android.hardware.camera.provider@2.4_genc++_headers
,每个.hal
会生成 4 个头文件,其中三个是HIDL
通信机制对应的文件。 - 源文件
目录名android.hardware.camera.provider@2.4_genc++
,每个.hal
文件生成一个对应的*All.cpp
文件。
types.hal
这类 HIDL
文件用于定义数据类型,而其他 HIDL
文件参数中可能会使用到这些数据结构。
ICameraProvider.hal
相关
ICameraProvider.hal
中的函数为 Framework
向 HAL
层发起的请求;ICameraProviderCallback.hal
中函数为 HAL
向 Framework
返回的回调。
1 | // ICameraProvider.hal |
ICameraDevice.hal
相关
1 | // ICameraDevice.hal |
ICameraDeviceSession.hal
相关
ICameraDeviceSession.hal
有两个版本: 3.2 和 3.3 ,而 3.3 版本是继承自 3.2 版本的,唯一不同的是使用 configureStreams_3_3
替代 configureStreams
。
1 | // V3.2 ICameraDeviceSession.hal |
HAL 1
对应文件
ICameraDevice.hal, ICameraDevicePreviewCallback.hal, ICameraDeviceCallback.hal
三个文件是 HAL 1
对应的文件,暂不做分析。
Hardware AOSP
对应文件目录为 hardware/interface/camera
,该文件夹主要是定义了很多 hal
文件及对应实现。
1 | ├── Android.bp |
common
目录
1 | ├── 1.0 |
CameraModule.cpp
封装了 qcom camera
库中相关函数,所有的交互都在这个文件中处理。
device
目录
1 | ├── 1.0 |
device
目录有三个版本:1.0, 3.2, 3.3
,分别实现对应的 HAL
版本。注意:当前代码中并没有 3.4 版本!!!
3.3 版本的类都是继承了 3.2 版本,区别仅仅是针对 CameraDeviceSession
,以下是 3.3 版本差异部分:
1 | // CameraDevice.cpp |
从实际代码比对中, 3.2 和 3.3 版本的代码,并没有什么差异!查看官网版本支持 , 3.3 版本主要有以下不同:
OPAQUE
和YUV
重新处理API
更新- 对深度输出缓冲区的基本支持
- 为
camera3_stream_t
添加了data_space
缓存字段 - 为
camera3_stream_t
添加了rotation
旋转字段 - 为
camera3_stream_configuration_t
添加了camera3
流配置操作模式operation_mode
metadata
目录
1 | metadata/ |
有效文件为 types.hal
,定义了 CameraMetadata
相关的数据结构。
provider
目录
1 | provider/ |
provider
中包含进程启动文件 service.cpp
,以及 CameraProvider.cpp
文件。
camera.provider
进程
进程全名为: android.hardware.camera.provider@2.4-service
,可以通过 adb shell "ps -A | grep camera.provider"
中查看到。
进程源码文件所在目录:hardware/interfaces/camera/provider/2.4/default
,主要是 .rc, service.cpp
两个文件。
rc
文件
1 | // android.hardware.camera.provider@2.4-service.rc |
main
进程的 main
方法很简单,仅有两条有效语句:
1 | // service.cpp |
initWithDriver
Binder
通信时驱动设备名为/dev/vndbinder
。defaultPassthroughServiceImplementation
默认的直通式HAL
服务注册。
这里先简单介绍一个概念:Binder
化直通式 HAL
(摘选自官网)。它实际是直通模式的 HAL
实现 Binder
化。比如 HAL
接口 a.b.c.d@M.N::IFoo
,系统会创建两个软件包:
a.b.c.d@M.N::IFoo-impl
包含HAL
的实现,并暴露函数IFoo* HIDL_FETCH_IFoo(const char* name)
。在旧版设备上,此软件包经过dlopen
处理,且实现使用HIDL_FETCH_IFoo
进行了实例化。也可以使用hidl-gen
和-Lc++-impl
以及-Landroidbp-impl
来生成基础代码。a.b.c.d@M.N::IFoo-service
打开直通式HAL
,并将其自身注册为Binder
化服务,从而使同一HAL
实现能够同时以直通模式和Binder
化模式使用。
如果有一个IFoo
,可以调用sp<IFoo> IFoo::getService(string name, bool getStub)
,以获取对IFoo
实例的访问权限。如果getStub
为True
,则getService
会尝试仅在直通模式下打开HAL
。如果getStub
为False
,则getService
会尝试找到Binder
化服务;如果未找到,则它会尝试找到直通式服务。除了在defaultPassthroughServiceImplementation
中,其余情况一律不得使用getStub
参数。(搭载Android O
的设备是完全Binder
化的设备,因此不得在直通模式下打开服务。)
从 main
源码来看,defaultPassthroughServiceImplementation
注册的是 Binder
化的直通式 HAL
,从 ICameraProvider.hal
生成的头文件中,可以看到 getStub
默认为 false
,即在 Framework
中 CameraProviderManager
通过 getService
找到的是 Binder
化服务。
1 | // android/hardware/camera/provider/2.4/ICameraProvider.h |
服务注册流程图
CameraProvider
初始化
从 main
方法中的 defaultPassthroughServiceImplementation
展开:
1 | // LegacySupport.h |
除了配置 Binder
机制中 RPC
线程池,就是注册直通式 HAL
:
1 | // LegacySupport.h |
传入模板的类为 ICameraProvider
,我们来看获取服务的代码,CameraProviderAll.cpp
文件是根据 ICameraProvider.hal
自动生成的 :
1 | // CameraProviderAll.cpp |
getStub
的值决定 getService
是 Binder
式或者直通式来获取 HAL
,主要区别在于获取 IServiceManager
方式及对应实例会不一样:
Binder
通过defaultServiceManager
获取,实际实例为ServiceManager
。- 直通式
通过getPassthroughServiceManager
获取,实际实例为ServiceManagement
。
getStub
为 false
,所以采用直通式,get
方法获取服务实例分析:
1 | // ServiceManagement.cpp |
IServiceManager
直通式对应的实例为 PassthroughServiceManager
,它的 get
方法会先通过 openLibs
打开 HAL
库文件,并从库文件中 dlsym
调用以 HIDL_FETCH_
开头的函数,获取对应实例:
1 | // ICameraProvider.h |
HIDL_FETCH_ICameraProvider
主要任务是新建 ICameraProvider
对象。
整个流程下来,从 service.cpp
开启进程,注册 Binder
化直通式 HAL
时,通过 dlopen
方式加载 HAL
库,并通过 HIDL_FETCH_ICameraProvider
来新建 ICameraProvider
对象。
Hardware AOSP
和 Hardware qcom
关联过程
hardware/interfaces/camera
是 Android
系统的标准接口,即 Hardware AOSP
;但是每个芯片厂商的具体实现都会不一样,高通平台在 hardware/qcom/camera
中实现了对 vendor camera
代码中的封装,即 Hardware qcom
。hardware/qcom/camera
代码生成对应库文件为 camera.msm8937.so
,hardware/interfaces/camera
是通过 dlopen
来加载这个库的,具体由 CameraProvider.cpp
加载,我们分析加载及函数指针关联的过程:
1 | // Camera_common.h |
CameraProvider::initialize
初始化过程中,通过 hw_get_module
来加载库,并拿到 camera_module_t
结构体对应的函数指针;CAMERA_HARDWARE_MODULE_ID
是在 Camera_common.h
中定义的 camera
:
1 | // hardware.c |
整个代码流程,是逐步查找库文件的过程,如果找到了再加载,这里重点看下几个可能存在的变种命名方式,规则是 <MODULE_ID>.variant.so
:
1 | // hardware.c |
当前平台,camera
库文件变种可能为:caemra.qcom.so, camera.QC_Reference_Phone.so, camera.msm8937.so
,实际上只有 camera.8937.so
是存在的:
1 | // hardware.c |
根据平台是否为 64 位,分别从 /system, /vendor/, /odm
几个目录下去搜索,直到找到为止,当前平台库文件绝对路径为:/vendor/lib/hw/camera.msm8937.so
,找到后加载:
1 | // hardware.h |
通过 dlopen
的方式加载库文件 /vendor/lib/hw/camera.msm8937.so
,并找到 HAL_MODULE_INFO_SYM
结构体变量,它是一组函数指针的关联,返回的是 hw_module_t
结构体指针,我们看头文件的定义:
1 | // hardware.h |
从注释中可以看到,hardware
中任何模块,必须有一个命名为 HAL_MODULE_INFO_SYM
数据结构(dlsym
查找时固定命名),并且结构体的第一个数据结构必须是 hw_module_t
的。
在 CameraProvider::initialize
中 camera_module_t, hw_module_t
这两个结构体直接转换了,我们来看 camera_module_t
的定义:
1 | // Camera_common.h |
camera_module_t
结构体的定义完全符合 hardware.h
中对 hw_module_t
的注释,因为是第一个数据结构,所以可以直接转换。
我们到 camera.msm8937.so
对应代码模块 hardware/qcom/camera
中去查找定义了 HAL_MODULE_INFO_SYM
的文件 QCamera2Hal.cpp
:
1 | // QCamera2Hal.cpp |
至此:CameraProvider::initialize
中,通过 hw_get_module
加载了 camera.msm8937.so
库,并拿到了 HAL_MODULE_INFO_SYM
对应的数据结构指针,最终会保存到 CameraModule
中。
Hardware qcom
目录
1 | camera/ |
QCamera_Intf.h
定义了很多数据结构(接口层); QCameraParameters.h
是 HAL 1
中使用的参数; mm-image-codec
目录涉及到高通平台图片的软硬解码; usbcamcore
是 usb
摄像头相关代码; 而 QCamera2
目录用来封装高通平台私有代码,也是需要重点熟悉的目录。
QCamera2
目录
1 | QCamera2 |
QCamera2
目录中,QCamera2Hal.cpp
函数指针和结构体赋值,用于关联 hardware AOSP
和 hardware qcom
; QCamera2Factory.h/cpp
工厂类,封装了 HAL 1/3
作为统一入口;子目录有如下几个:
HAL
HAL 1
对应代码目录,其中QCamera2HWI.cpp
为统一的接口文件。HAL3
HAL 3
对应代码目录,其中QCamera3HWI.cpp
为统一的接口文件;QCamera3Channel
信道处理对应的流QCamera3Stream
。stack
common
目录仅仅定义了头文件,其他三个interface
目录分别为:mm-camera-interface
用于和vendor/qcom/mm-camera
目录通信的接口目录;mm-jpeg-interface
软硬解码的接口目录;mm-lib2d-interface
没有参与编译。util
工具目录。
stack
目录
1 | stack/ |
这个目录的核心代码主要是 mm-camera-interface
目录下的代码:
- 接口层
mm_camera_interface.c
不管HAL 1
还是HAL 3
,高通平台vendor
下的代码都是同一套,是没有区别的,mm_camera_interface.c
是对HAL
统一的接口。 - 实现层
mm_camera.c
管理channel, stream, thread, socket
等,实现和vendor
的通信。当前高通平台msm8937
中HAL
和vendor
的通信有两部分:一是通过socket
来映射内存信息;二是ioctl
向v4l2
驱动发送命令做信息交互(查看高通网站上的文档,后续会取消socket
通信,统一使用mshim
管理;在mm_camera
中,这两套方式都实现了:如果定义了DAEMON_PRESENT
则socket
通信,否则mshim
方式管理)。
接口层的几个基本概念
Channel
信道,一个松散的概念,用于将多个图像流捆绑在一起处理;即一个信道包含多个流(这句话更准确的说法是:一个信道ID
对应多个信道实例QCamera3MetadataChannel, QCamera3PicChannel
等,而每个信道实例仅对应一个流QCamera3Stream
;也就是说实际上是一个信道ID
包含多个流)。常见信道:ZSL, Capture, Preview, Snapshot, Video, Raw, Metadata
。Stream
流,最小流元素,每个流只能有一种格式;它是在相机硬件和应用程序之间交换捕获图像缓冲区的接口。常见流有:Preview, Postview, Metadata, Raw, Snapshot, Video, Reprocessing
。Stream bundling
流集合,在信道内捆绑的多个流:在打开所有捆绑流之前,硬件不会启动流;当第一个捆绑流关闭时,硬件停止。在捕获请求开始时,会查询信道ID
中所有的流,并捆绑设置到每个信道实例中。
Socket
通信的意义
Socket
在这里主要功能是在 Hardware qcom
和 vendor qcom
两个进程间共享缓存信息。交换的缓存信息有如下几种类型:
CAM_MAPPING_BUF_TYPE_CAPABILITY
:缓存区大小CAM_MAPPING_BUF_TYPE_PARM_BUF
:参数缓存区CAM_MAPPING_BUF_TYPE_STREAM_BUF
:流缓存区CAM_MAPPING_BUF_TYPE_STREAM_INFO
:流信息缓存区CAM_MAPPING_BUF_TYPE_OFFLINE_INPUT_BUF
:离线重新处理输入的缓存区
Socket
相关代码位置及对应功能:
- 实现类:
QCamera2\stack\mm-camera-interface\src\mm_camera_sock.c
- 消息发送者:
QCamera2\stack\mm-camera-interface\src\mm_camera_stream.c
- 消息接收者:
mm-camera\mm-camera2\server-imaging\server.c
ioctl
支持的命令
高通 Camera HAL
和 kernel
通信是通过 V4L2 IOCTLs
命令来和 /dev/videoX
节点通信的,支持的命令列表如下:
VIDIOC_S_FMT
:设置预览和拍照的格式为YUV420
VIDIOC_S_CTRL
:向设备传输控制信息VIDIOC_G_CTRL
:从设备获取控制信息VIDIOC_QBUF
:入队或分配预览缓存区Buffers
VIDIOC_DQBUF
:出队或释放预览缓存区Buffers
VIDIOC_STREAMON
开始预览VIDIOC_STREAMOFF
:停止预览VIDIOC_QUERYCAP
:查询设备支持的能力VIDIOC_QUERYBUF
:查询缓存区Buffers
的信息VIDIOC_REQBUFS
:请求注册缓存区Buffers
VIDIOC_DQEVENT
:注册到kernel
的事件出队
HAL 1/3
差异
API 1/2
设计的目的
Camera API 1
子系统被设计为具备高级控制功能的黑盒- 应用程序能够发出请求,但无法控制图像缓冲区和元数据
- 应用程序无法在帧层面控制传感器特性
- 应用程序无法通过访问和修改元数据信息(如
3A
信息)对捕获的帧应用任何增强功能
Camera API 2
旨在大幅提高应用程序控制摄像头子系统的能力- 应用程序框架向摄像头子系统请求一帧图像,摄像头子系统将请求结果与相关元数据信息(如色彩空间)一起返回到输出流;同时为每个结果数据流生成镜头阴影信息
- 应用程序对整个摄像头管道的控制能力得到提升。每个拍摄请求会产生一个带有拍摄元数据的结果对象和一些图像数据缓冲区
- 元数据信息有助于应用程序了解摄像头管道的当前状态(
3A
和ISP
状态)以对缓冲区进行相关处理
通常情况下搭配原则:API 1 + HAL 1
和 API 2 + HAL 3
。
HAL 1/3
对应功能概述
HAL 1
被设计为具有高级控件和以下三种运行模式的黑盒子:预览、视频录制、静态拍摄。三种模式具有略有不同又相互重叠的功能,这样就难以实现介于其中两种运行模式之间的新功能,例如连拍模式等。HAL 3
子系统将多个运行模式整合为一个统一的视图,可以使用这种视图实现之前的任何模式以及一些其他模式,例如连拍模式。相机子系统被塑造为一个管道,该管道可按照 1:1 的基准将传入的帧捕获请求转化为帧。这些请求会封装有关帧的捕获和处理的所有配置信息:分辨率、像素格式、手动传感器、镜头、闪光灯控件、3A
运行模式、RAW->YUV
处理控件/统计信息生成等等。这样一来,便可以提高用户对聚焦、曝光以及更多后期处理(例如降噪、对比度和锐化)效果的控制能力。简单来说,应用框架从相机子系统请求帧,然后相机子系统将结果返回到输出流。此外系统还会针对每组结果生成包含色彩空间和镜头阴影等信息的元数据。
可以将 HAL 3
看作 HAL 1
的单向流管道,它会将每个捕获请求转化为传感器捕获的一张图像,这张图像将被处理成:
- 包含有关捕获的元数据的结果对象
- 图像数据的 1 到
N
个缓冲区,每个缓冲区会进入自己的目的地Surface
可能的输出 Surface
组经过预配置:
- 每个
Surface
都是一个固定分辨率的图像缓冲区流的目标位置 - 一次只能将少量
Surface
配置为输出(约 3 个)
一个请求中包含所需的全部捕获设置,以及要针对该请求将图像缓冲区(从总配置组)推送到其中的输出 Surface
的列表。请求可以只发生一次 capture()
,也可以无限重复 setRepeatingRequest()
,捕获的优先级高于重复请求的优先级。
HAL 3
原理总结
- 异步拍摄请求来源于框架
- 硬件抽象层
HAL
按顺序处理这些请求。收到请求后,HAL
产生输出结果元数据以及一个或多个输出图像缓冲区 - 该进程遵循先进先出
FIFO
原则处理请求、结果以及后续请求参考的流 - 给定请求的所有输出的时间戳必须相同。如有需要,框架会一同匹配这些时间戳
- 所有拍摄配置和状态均属于拍摄请求和结果的一部分
- 应用程序可以请求带有特定设置的帧,例如,曝光设置和
HAL3
(FULL
模式),从而确保拍摄请求设置和产生的实际图像保持精确同步
接口区别
HAL, DEVICE, MODULE
各版本
这些版本号都是在 Camera_common.h
文件中定义的。
Module
版本
Module
没有兼容性问题,总是使用最新版本;当前为 CAMERA_MODULE_API_VERSION_2_4
。
1 |
HAL
版本
HAL
版本在代码中实际指的是 Device
的版本。因为要兼容早期的 Camera API
,通常必须支持 1.0
版本;2.x
版本已经废弃不再支持;而 3.0
及以上版本是为 Camera API 2
使用的。
1 | #define CAMERA_DEVICE_API_VERSION_1_0 HARDWARE_DEVICE_API_VERSION(1, 0) // DEPRECATED |
每个 DEVICE_API_VERSION
对应的值为:
1 | CAMERA_DEVICE_API_VERSION_1_0 = 256 |
我们通常所说的 HAL 1
指的是 CAMERA_DEVICE_API_VERSION_1_0
版本;HAL 3
泛指 CAMERA_DEVICE_API_VERSION_3_0
及以上版本。
HIDL
中生成的 deviceName
返回值格式:'device@<major>.<minor>/<type>/<id>'
,比如:
device@1.0/legacy/1
:表示HAL
版本号为CAMERA_DEVICE_API_VERSION_1_0
,id=1
表示摄像头为前置摄像头device@3.3/legacy/0
:表示HAL
版本号为CAMERA_DEVICE_API_VERSION_3_3
,id=0
表示摄像头为后置摄像头
源码分析:
1 | // CameraProvider.cpp |
从代码中可以看出,通过 CameraProvider::getHidlDeviceName
对外提供的 HAL
版本只有两种:CAMERA_DEVICE_API_VERSION_1_0
和 CAMERA_DEVICE_API_VERSION_3_3
。
平台当前使用哪个 HAL
版本
平台当前使用哪个 HAL
版本,是在 QCamera2Factory
的构造函数中决定的,当 isHAL3Enabled
并且 is_yuv_sensor
当前摄像头不是 yuv sensor
时,才会设置为 HAL 3
。
1 | QCamera2Factory::QCamera2Factory() |
高通平台开启 HAL 3
的情况下,为了兼容性同时也会支持 HAL 1
。
高通代码中混乱的 HAL
版本号
高通代码中 HAL/DEVICE
的具体版本号,不同的函数返回的版本号不一样,有点混乱:
QCamera2Factory
中mHalDescriptors[camera_id].device_version
保存的版本号,在构造函数中初始化为CAMERA_DEVICE_API_VERSION_3_0 (768)
;mHalDescriptors
并不对外提供信息,仅在QCamera2Factory::cameraDeviceOpen
时会用来区分HAL1/3
。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
31
32
33
34
35
36
37
38
39
40
41int QCamera2Factory::cameraDeviceOpen(int camera_id,
struct hw_device_t **hw_device)
{
...
if ( mHalDescriptors[camera_id].device_version
== CAMERA_DEVICE_API_VERSION_3_0 ) {
// HAL 3 使用 QCamera3HardwareInterface
QCamera3HardwareInterface *hw = new QCamera3HardwareInterface(
mHalDescriptors[camera_id].cameraId, mCallbacks);
if (!hw) {
LOGE("Allocation of hardware interface failed");
return NO_MEMORY;
}
rc = hw->openCamera(hw_device);
if (rc != 0) {
delete hw;
}
}
else if (mHalDescriptors[camera_id].device_version
== CAMERA_DEVICE_API_VERSION_1_0) {
// HAL 1 使用 QCamera2HardwareInterface
QCamera2HardwareInterface *hw =
new QCamera2HardwareInterface((uint32_t)camera_id);
if (!hw) {
LOGE("Allocation of hardware interface failed");
return NO_MEMORY;
}
rc = hw->openCamera(hw_device);
if (rc != NO_ERROR) {
delete hw;
}
}
else {
LOGE("Device version for camera id %d invalid %d",
camera_id, mHalDescriptors[camera_id].device_version);
return BAD_VALUE;
}
...
}QCamera3HWI
中
函数getCamInfo
获取到的版本号为CAMERA_DEVICE_API_VERSION_3_4 (772)
;这导致所有通过CameraModule.getCameraInfo
获取到的版本号都将是该值。1
2
3
4
5
6
7
8
9
10
11
12
13
14// QCamera3HWI.cpp
int QCamera3HardwareInterface::getCamInfo(uint32_t cameraId,
struct camera_info *info)
{
...
// 不管之前 HAL 版本是多少,在这都会被更新为 3.4 或者 3.3
// 这里代码实际走到的是 3.4
info->device_version = CAMERA_DEVICE_API_VERSION_3_4;
info->device_version = CAMERA_DEVICE_API_VERSION_3_3;
..
}CameraProviderManager
中
函数getHighestSupportedVersion
获取到的版本号为CAMERA_DEVICE_API_VERSION_3_3 (771)
,它是根据CameraProvider::getHidlDeviceName
来解析的,而这个名字默认的只提供两种版本号 3.3 和 1.0 。
所以在 Framework
中 CameraService.cpp
将 halVersion
和 deviceVersion
做了区分(实际上如果代码全部统一,就没必要区分了);而 Hardware
中通常简化为 HAL 1, HAL 3
。
1 | // cam_types.h |
高通 HAL
架构
整体架构图
API
和Framework
之间
基于AIDL
的Binder
跨进程通信。Framework
和HAL AOSP
之间Android 8
开始,Framework
和HAL
是基于HIDL
的Binder
跨进程通信的。HAL AOSP
和HAL qcom
之间
通过dlopen, dlsym
动态加载库文件,直接调用方法的。HAL qcom
和vendor qcom
之间
主要是通过ioctl
和v4l2
驱动交互的(老版本平台两者之间会通过socket
映射缓存区,或者mshim
层加载库文件直接调用)。hal
到vendor
主要是在v4l2(stream)
中事件通知方式进行;对于map/unmap
则直接通过domain socket
方式进行vendor
到hal
是通过v4l2(control)
的事件通知机制进行的v4l2(stream)
是通过msm-camera
获取到相关信息;v4l2(control)
是通过msm-config
获取到相关信息
高通文档对 HAL, vendor
的解释:
- CameraService (libcamerservice)
Camera service layer interacts with:- HAL for camera control APIs
- SurfaceFlinger for delivering preview frames on LCD
- CameraHardwareInterface (derived object)
- HAL for actual camera hardware
- QTI’s HAL implements Google HAL APIs to hook up QTI native camera driver in kernel
- QTI Linux V4L2 camera driver (kernel)
- Controls access to camera hardware
- Proxy to access the QTI-proprietary modules on user space
Camera
子系统架构图
Camera
前端子系统
HAL 1
流程图
HAL 3
流程图
kernel
架构图
HAL 3
请求和回调流程图
预览
拍照
录像
请求流程
CameraProvider
初始化
configureStreams
配置流
捕获请求流程 processCaptureRequest
捕获请求流程 processCaptureRequest
,查看大图
回调流程
捕获结果 Callback
关联过程
当 HAL qcom
产生捕获结果后,将数据一路传递到 Framework
,我们重点分析下回调函数的关联过程!先从 hal
文件入手分析:
1 | // ICameraDeviceCallback.hal |
在 Framework
层,由 Camera3Device
实现并完成回调:
1 | // Camera3Device.h |
从头文件中可以看出, Camera3Device
继承并实现了 ICameraDeviceCallback.hal
以及 camera3_callback_ops
,实际上这两个都是实现了相同的回调函数。从 LOG
上看,主要是 hal
文件接口回调的。在 open
流程中,这个 ICameraDeviceCallback
会经过如下流程;
1 | Camera3Device -> |
传入 HAL AOSP
的 CameraDeviceSession
中,并保存在 ResultBatcher
中:
1 | CameraDeviceSession::CameraDeviceSession( |
从构造函数中,同时看到将 camera3_callback_ops
中函数指针进行了关联,它定义的函数指针对应的是 ICameraDeviceCallback.hal
的函数:
1 | // camera3.h |
而 camera3_callback_ops
是 HAL qcom
向 HAL AOSP
发出的回调;在初始化过程中,传递到 QCamera3HardwareInterface
中:
1 | // CameraDeviceSession.h |
至此,在 QCamera3HardwareInterface
中通过 mCallbackOps
可以直接回调到 Framework
中,关联完毕。
processCaptureResult
触发流程图
从回调流程图中可以看出,由三个线程负责处理回调:
mm_camera_poll_fn
线程
负责轮训处理pipe
管道事件,如果是数据事件MM_CAMERA_POLL_TYPE_DATA
触发回调。mm_camera_cmd_thread
线程
负责循环处理cmd
命令,如果有数据结果MM_CAMERA_CMD_TYPE_DATA_CB
触发回调。QCameraCmdThread
线程
负责循环处理流命令,如果拿到了数据结果CAMERA_CMD_TYPE_DO_NEXT_JOB
触发回调。
回调函数指针关联
流程图中三个线程的回调函数关联过程,我们先从 QCamera3HardwareInterface
逐步分析:
1 | // QCamera3HWI.cpp |
查找 QCamera3HardwareInterface
整个文件,只有 orchestrateResult
函数中调用了回调 process_capture_result
。从 LOG
中查看的调用关系,该函数是在 captureResultCb
中调用的,而它会在新建信道对象时传入到 QCamera3Channel
中:
1 | // QCamera3Channel.cpp |
channel_cb_routine
是一个函数指针,指向 QCamera3HardwareInterface::captureResultCb
函数;而 mChannelCB
大部分都是在 QCamera3Channel
子类的 streamCbRoutine
函数中调用的,而 streamCbRoutine
会在 QCamera3Stream
初始化时传入:
1 | // QCamera3Channel.cpp |
而在 QCamera3Stream::init
初始化时,会将 stream_cb
保存在 mDataCB
中, hal3_stream_cb_routine
是一个函数指针,指向 QCamera3Channel::streamCbRoutine
:
1 | // mm_camera_interface.h |
同时,在流配置时将 dataNotifyCB
函数作为流回调赋值给了 stream_config.stream_cb
,stream_cb
是指向它的函数指针,在流配置时向下传递到 mm_camera_stream
:
1 | // mm_camera.h |
最终回调函数实际保存在了 buf_cb[cb_index].cb
中,而它是在 mm_stream_dispatch_app_data
中触发调用的:
1 | // mm_camera_stream.c |
在开启流过程中,如果有回调函数则启动 CAM_StrmAppData
回调线程,并将 mm_stream_dispatch_app_data
加到线程中:
1 | // mm_camera_stream.c |
再到 mm_camera_thread
线程中查看:
1 | // mm_camera_thread.c |
线程 launch
时,指定了 mm_camera_cmd_thread
函数,而该函数的主要功能就是执行回调 cmd_thread->cb(node, cmd_thread->user_data);
,当满足执行条件: cmd_sem
通知以及 cmd_queue
中有数据时,就会执行回调。继续跟踪代码,查看哪里触发满足执行条件:
1 | // mm_camera_stream.c |
mm_stream_handle_rcvd_buf
触发了回调流程,由 mm_stream_data_notify
发起,而在信道开启流程过程中,会为每个流分配及注册 Buffer
:
1 | // mm_camera_stream.c |
在注册 Buffer
的过程中,将 mm_stream_data_notify
添加到 mm_camera_poll_thread
线程中,而这个线程的功能是监听 pipe
管道,如果管道中有数据则触发回调,这个线程循环执行的函数为:
1 | // mm_camera.h |
而代码中的 notify_cb
函数指针指向的就是 mm_stream_data_notify
函数。至此,回调过程全部关联完毕!
其他
类关联及 hal
文件
下面这幅图是使用 UML
组件图画的,仅仅是因为组件的方框和依赖的箭头画起来方便,并不是体现组件关系。该图的意义:
- 体现
hal
文件调用及回调关系 - 体现各个类之间通过变量连接的关系(代码中有较多的函数指针,类中变量在调用函数时有时候不记得指向的是哪个类了,通过该图能直观的找到对应关系)
常见数据结构图
摄像头默认属性
摄像头的默认属性,比如 ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL
硬件支持级别等,都是在 QCamera3HardwareInterface::initStaticMetadata
中初始化的。
1 | // Camera3HWI.cpp |
resource_cost
的计算方法
1 | // Camera3HWI.cpp |
poll
机制
参考转载:camera 中的 poll 机制
这里的 poll
机制里面是嵌套了一个 pipe
机制;每次添加一个 poll thread
时,会给这个 poll thread
创建一个 pipe
,对于这个 poll thread
来说 pipe
也是文件 fd
,调用一次 poll
时可以传递给该函数 fd
数组,poll
会去查看每一个 fd
,一旦哪个 fd
的 poll
有返回,则该 poll thread
就会对其进行处理。那么 qcom
这里期望做一个可以动态添加 poll fd
的机制,他们利用了 pipe
,这个 pipe
就是 fds[0]
,也就是每个 poll thread
自带 fds[0]
,该 fd
响应添加新 fd
的操作,所以当 open
一个 dev
就可以把这个 dev
的 fd
通过 write fds[0]
添加进来。
信道 Channel
与流 Stream
先看一段代码,在信道中添加流时的注释:
1 | int32_t QCamera3Channel::addStream(cam_stream_type_t streamType, |
注释为: HAL 3
中一个信道实例中只能有一个流!每个信道都有一个信道 ID
,它是在 QCamera3HardwareInterface
初始化时创建的 ID
号,也就是只会存在同一个信道 ID
。信道 QCamera3Channel
有很多实现类: QCamera3MetadataChannel, QCamera3PicChannel
等等,它们都共享了同一个 channel id
。
示例:在预览和拍照时,设置两个输出 Surface
,这个过程中会创建一个信道 id
,5 个信道实例,4 条流:
QCamera3MetadataChannel
获取Metadata
元信息,对应的流类型和格式为stream type: CAM_STREAM_TYPE_METADATA 7, format: 124
。QCamera3SupportChannel
HAL
内部消费会使用到,对应的流类型和格式为stream type: CAM_STREAM_TYPE_ANALYSIS 11, format: CAM_FORMAT_YUV_420_NV12_VENUS 7
。QCamera3RegularChannel
预览,对应的流类型和格式为stream type: CAM_STREAM_TYPE_PREVIEW 1, format: CAM_FORMAT_YUV_420_NV12_VENUS 7
。QCamera3PicChannel
拍照,对应的流类型和格式为stream type: CAM_STREAM_TYPE_SNAPSHOT 3, format: CAM_FORMAT_YUV_420_NV21 2
。QCamera3ProcessingChannel
不需要添加流,参考其头文件注释:是用来处理直接从HAL
发回给Framework
所有的流,这些流并没有经过HAL
后处理QCamera3PostProc
。
对应的 Log
为,公共信道 ID: 2048
:
1 | E QCamera : mm_camera_intf_add_stream: 773: E handle = 1792 ch_id = 2048 |
常用 LOG
开关
- 开启
HAL
层LOG
信息adb root;adb shell setprop persist.camera.hal.debug 1
persist.camera.dumpimg
设置为 1 时,每次拍照预览都会在/data/misc/camera
目录保存当前帧图片
Buffer
管理
Camera Buffer
相关管理,需要先搞清楚 Android
的图形显示系统;简单来讲:图形显示系统是按照生产者和消费者模型来架构的,在打开 Camera
时,需要创建需要显示(消费者)的 Surface
,当 Camera
设备捕获到帧数据时(生产者),直接将缓存区 Buffers
交给 Surface
来显示。
buffer_handle_t
的作用
先看几个数据结构:
1 | // native_handle.h |
简单来说:native_handle_t, buffer_handle_t, private_handle_t
都是用来描述一块缓存区的,可以将它们理解为一个句柄,用于跨进程传输数据;将 Camera
设备捕获到的帧数据缓存区,传递给显示系统直接显示。buffer_handle_t
是一个 native_handle_t
型指针;这里重点关注成员 data
,它是一个大小为 0 的数组,data
指向成员 numInts
后的一个地址,这个地址通常表示 private_handle_t
结构中的数据;numFds
表示 private_handle_t
中有几个文件描述符;numInts
表示 private_handle_t
中有几个整形数据。下图为一个大致的描述:
所以 buffer_handle_t
可以理解为是传递给 Framework
中的 Surface
来消费的;因为涉及跨进程,直接拿到数据帧的地址指针并没有实际意义。
携带 Buffer
的数据结构
HAL qcom
中携带捕获帧数据的数据结构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// mm_camera_interface.h
typedef struct mm_camera_buf_def {
uint32_t stream_id;
cam_stream_type_t stream_type;
cam_stream_buf_type buf_type;
uint32_t buf_idx;
uint8_t is_uv_subsampled;
struct timespec ts;
uint32_t frame_idx;
union {
mm_camera_plane_buf_def_t planes_buf;
mm_camera_user_buf_def_t user_buf;
};
int fd;
void *buffer; // 捕获的帧数据指针
size_t frame_len;
void *mem_info;
uint32_t flags;
} mm_camera_buf_def_t;
typedef struct {
uint32_t camera_handle;
uint32_t ch_id;
uint32_t num_bufs;
uint8_t bUnlockAEC;
uint8_t bReadyForPrepareSnapshot;
mm_camera_buf_def_t* bufs[MAX_STREAM_NUM_IN_BUNDLE];
} mm_camera_super_buf_t;HAL AOSP
中携带捕获帧数据的数据结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// camera3.h
typedef struct camera3_capture_result {
uint32_t frame_number;
const camera_metadata_t *result; // 元信息
uint32_t num_output_buffers;
const camera3_stream_buffer_t *output_buffers; // 输出缓冲区
const camera3_stream_buffer_t *input_buffer;
uint32_t partial_result;
} camera3_capture_result_t;
typedef struct camera3_stream_buffer {
camera3_stream_t *stream; // 流信息
buffer_handle_t *buffer; // 句柄,指向帧数据
int status;
int acquire_fence;
int release_fence;
} camera3_stream_buffer_t;
从数据结构中可以看出,在 HAL qcom
中,mm_camera_buf_def_t->buffer
可以直接拿到设备捕获帧数据的地址;而 HAL AOSP
因为需要将数据返回给 Framework
,涉及到跨进程通信,所以并不是直接地址,而是一个 buffer_handle_t
句柄 camera3_stream_buffer_t->buffer
。Framework
中 CaptureResult
只保留了这个句柄以及图像的元信息。
QCamera3Mem
这个文件中有三个类:
QCamera3Memory
基类QCamera3HeapMemory
派生类,主要用于HAL qcom
内部交互,getPtr
获取捕获的每帧数据地址mm_camera_buf_def_t->buffer
,拿到这个地址可以直接做图像处理。QCamera3GrallocMemory
派生类,用于传递给Framework
中,因为并不会直接向Framework
返回帧地址,而是返回buffer_handle_t
指针,即camera3_stream_buffer->buffer
,图形显示时需要的数据结构。它实际对应的是同一块内存中的帧数据,只是把这块内存封装好了(暂时不清楚如何通过它获取帧数据)。
HAL AOSP, Framework
中拿到的都是 buffer_handle_t
指针,会直接在 Surface
中使用,我理解如果想对 Camera
捕获帧数据做实时处理,只能在 HAL qcom, vendor
这两层处理。
示例:灰度化帧数据
整个数据回调过程中,图像帧数据是以指针的形式传回来的,所以不会超过 Binder 1M
大小;但并不是每个 result
都包含图片 buffer
信息,按照信道不同,回调的数据是不一样的,比如 QCamera3MetadataChannel::streamCbRoutine
回调的结果中,只向 Framework
上报元信息,数据 output_buffers
是空的。
示例将预览的实时图像灰度化,预览流的格式为 CAM_FORMAT_YUV_420_NV12_VENUS
,即帧数据存储是 YUV_420_NV12
格式的,而当前平台这个格式存储特点(以 QCIF: 176*144
为例)是:
plane
有两条:一条为Y
数据,一条为UV
数据stride
行步进值,因为会数据对齐,所以每行并不是实际的宽度width
想把 YUV
格式像素数据变成灰度图像,只需要将 U, V
分量设置成 128 即可。这是因为 U, V
是图像中的经过偏置处理的色度分量,色度分量在偏置处理前的取值范围是 -128~127
,这时候的无色对应的是 0 值。经过偏置后色度分量取值变成了 0~255
,因此无色对应的就是 128 了。
算法结果就是找到 UV
分量的地址,全部设置为 128 ,如下为代码:
1 | // QCamera3Channel.cpp |
后续
- 画中画
- 多屏显示
- 如何利用平台的硬件来做图像识别和处理,都使用软件效率是否能达到?
参考文档
- Binder 化直通式 HAL
- camera 版本支持
- CameraProvider 注册
- camera provider启动
- [高通 HAL 1 代码流 80-NF499-2 C: Camera Frontend Code Walkthrough]
- msm8996平台的 camera 框架笔记
- Android GDI之共享缓冲区机制-native_handle_t
- Android buffer_handle_t 和 Surface 的关系
- 图文详解YUV420数据格式
- 高通camera结构
- openCamera-HAL 启动过程
- CameraService 到 HAL Service
- MSM8940 Camera 架构以及移植手册
- 高通第三方算法(单帧数据)添加流程
- 图形显示系统:surface 生产者和消费者间的处理框架
- HAL 常见数据结构
- HAL3中预览preview模式下的数据流