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 |
initWithDriverBinder通信时驱动设备名为/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 作为统一入口;子目录有如下几个:
HALHAL 1对应代码目录,其中QCamera2HWI.cpp为统一的接口文件。HAL3HAL 3对应代码目录,其中QCamera3HWI.cpp为统一的接口文件;QCamera3Channel信道处理对应的流QCamera3Stream。stackcommon目录仅仅定义了头文件,其他三个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:设置预览和拍照的格式为YUV420VIDIOC_S_CTRL:向设备传输控制信息VIDIOC_G_CTRL:从设备获取控制信息VIDIOC_QBUF:入队或分配预览缓存区BuffersVIDIOC_DQBUF:出队或释放预览缓存区BuffersVIDIOC_STREAMON开始预览VIDIOC_STREAMOFF:停止预览VIDIOC_QUERYCAP:查询设备支持的能力VIDIOC_QUERYBUF:查询缓存区Buffers的信息VIDIOC_REQBUFS:请求注册缓存区BuffersVIDIOC_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。QCamera3SupportChannelHAL内部消费会使用到,对应的流类型和格式为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模式下的数据流

