4、Camera HAL3核心架构:深入剖析camera3_device_t、camera3_callback_ops_t等关键结构体与接口
Camera HAL3(即Android Camera API2的HAL层)的设计核心在于将复杂的相机硬件能力抽象为一组清晰、稳定的C/C++接口,使上层框架(Camera Service)能够以统一的方式控制不同厂商的ISP、传感器和镜头模组。与HAL1的“黑盒”式操作不同,HAL3要求HAL实现者显式管理每一个请求(Capture Request)、每一帧输出(Capture Result)以及每一路数据流(Stream)。本章将深入剖析HAL3中最关键的两个结构体——camera3_device_t与camera3_callback_ops_t,并串联起请求管线、流配置、错误处理等核心机制,帮助开发者建立从接口定义到工程落地的完整认知。
上图展示了HAL3接口层的核心关系:camera3_device_t作为HAL设备对象,承载了框架对HAL的所有调用入口;而camera3_callback_ops_t则是HAL向框架主动上报数据与事件的通道。两者一“呼”一“应”,构成了完整的请求-结果循环。下面我们将逐一拆解这两个结构体的每个字段、函数指针的语义,以及在实际开发中的调用约定与陷阱。
4.1 camera3_device_t:HAL设备的“门面”与生命周期管理
camera3_device_t是HAL3设备在框架视角下的抽象句柄,它继承自Android硬件模块的通用结构hw_device_t。在camera_module_t的open()调用成功后,框架会获得一个指向camera3_device_t的指针,后续所有操作都通过该指针进行。其定义位于hardware/camera3.h中,核心字段包括:
- common:
hw_device_t类型,包含版本号、模块句柄、关闭函数指针等通用信息。HAL3要求tag必须为HARDWARE_DEVICE_TAG,version必须为HAL_DEVICE_API_VERSION(3, x)。 - ops:指向
camera3_device_ops_t结构体的指针,这是真正的函数指针集合,包含initialize()、configure_streams()、process_capture_request()等关键方法。 - priv:私有数据指针,HAL实现者可以将其指向自己的设备上下文结构体,框架不会修改该字段。
在camera3_device_ops_t中,最核心的函数指针及其语义如下表所示:
| 函数指针 | 调用时机 | 关键职责 | 注意事项 |
|---|---|---|---|
initialize() |
设备打开后、任何流配置之前 | 传入camera3_callback_ops_t回调指针,完成HAL内部资源初始化 |
必须保存回调指针,后续所有结果上报都依赖它;失败则返回错误码 |
configure_streams() |
每次相机重新配置时(如切换分辨率、添加新流) | 接收camera3_stream_configuration_t,包含流数组、操作模式(正常/受限)等 |
HAL必须在此函数中完成流的内存分配与硬件管线重配置;若流参数不支持,需返回-EINVAL |
process_capture_request() |
每次拍照/预览请求下发时 | 接收camera3_capture_request_t,包含帧号、settings、输入/输出buffer等 |
该函数必须非阻塞,HAL应异步处理请求;若buffer无效可返回错误,但不应阻塞框架 |
flush() |
框架需要快速取消所有未完成请求时 | 丢弃所有排队中的请求,并立即回调所有未完成请求的错误通知 | flush()必须快速返回,且不能等待硬件操作完成;通常配合notify()发送ERROR_REQUEST |
dump() |
系统调试时(如dumpsys) | 输出HAL内部状态、请求队列、流配置等信息到fd | 建议输出结构化文本,便于问题定位 |
核心原理:camera3_device_t的生命周期与相机设备绑定。从open()到close(),框架会保证同一时刻只有一个线程调用HAL的ops函数(除了dump()可能并发),但HAL内部仍需处理请求的异步完成。因此,HAL实现通常需要维护一个请求队列和多个工作线程,而process_capture_request()只负责将请求入队并立即返回。
4.2 camera3_callback_ops_t:结果上报与事件通知的“咽喉”
camera3_callback_ops_t是框架定义的一组函数指针,由HAL在initialize()时保存。HAL通过调用这些回调,将捕获结果、错误事件、快门时间等异步信息传递给框架。其定义同样位于camera3.h中,核心成员包括:
- process_capture_result:上报一帧的完整结果,包含帧号、输出buffer的填充状态、metadata(如3A结果、传感器时间戳)等。该回调是HAL3数据通路的核心,每个请求必须最终产生一个结果回调(成功或失败)。
- notify:上报异步事件,包括请求错误(
ERROR_REQUEST)、结果错误(ERROR_RESULT)、设备错误(ERROR_DEVICE)、快门事件(SHUTTER)等。快门事件必须优先于对应的结果回调发送。
在实现process_capture_result时,HAL必须遵循以下约定:
- 帧号一致性:结果中的
frame_number必须与请求中的一致,否则框架会丢弃该结果。 - Buffer状态:每个输出buffer必须通过
camera3_stream_buffer结构体返回,并设置status字段(CAMERA3_BUFFER_STATUS_OK或CAMERA3_BUFFER_STATUS_ERROR)。若为ERROR,框架会将该buffer标记为无效。 - Metadata部分更新:HAL可以只返回部分metadata(通过
partial_result字段),但最终必须返回完整的metadata集合。框架会合并多次partial结果。 - 线程安全:回调可能从HAL的任意工作线程调用,但框架保证同一帧的结果回调不会并发。HAL应确保不同帧的回调顺序与请求顺序一致(除非显式标记为partial)。
风险提示:一个常见的HAL实现错误是“结果回调顺序错乱”。例如,HAL先发送了第N+1帧的结果,再发送第N帧的结果,这会导致框架内部的时间戳错乱和预览卡顿。正确做法是:HAL内部按帧号排序结果队列,确保回调顺序严格递增。另外,notify()中的SHUTTER事件必须在结果回调之前发送,否则框架无法正确计算曝光延迟。
4.3 请求-结果闭环:从process_capture_request到process_capture_result
理解HAL3的关键在于掌握一次完整拍照请求的生命周期。以下是一个典型的单帧请求处理流程,涉及两个结构体的协同工作:
// 框架侧调用(伪代码)
camera3_capture_request_t req;
req.frame_number = 100;
req.settings = &metadata; // 包含曝光、增益、AE/AF/AWB设置
req.input_buffer = NULL; // 非ZSL模式
req.num_output_buffers = 1;
req.output_buffers[0].stream = &stream; // 已配置的预览流
req.output_buffers[0].buffer = &buffer_handle; // 由框架分配的gralloc buffer
// HAL侧实现(伪代码)
int process_capture_request(const camera3_capture_request_t *request) {
// 1. 校验请求参数
if (request->frame_number <= last_frame) return -EINVAL;
// 2. 将请求加入内部队列
my_device->request_queue.push(*request);
// 3. 唤醒工作线程处理
my_device->worker_thread.signal();
return 0; // 立即返回
}
// 工作线程处理完成后
void worker_thread::on_capture_done(int frame_number, buffer_handle_t *buffer) {
// 4. 填充结果结构体
camera3_capture_result_t result;
result.frame_number = frame_number;
result.num_output_buffers = 1;
result.output_buffers[0].status = CAMERA3_BUFFER_STATUS_OK;
result.output_buffers[0].buffer = buffer;
result.partial_result = 1; // 一次性返回全部metadata
result.result = &metadata; // 包含传感器时间戳、3A状态等
// 5. 通过回调上报
my_device->callback_ops->process_capture_result(my_device->callback_ops, &result);
}
上述流程中,process_capture_request的快速返回是HAL3性能的关键。如果HAL在该函数中执行了硬件操作(如等待ISP完成),会直接阻塞框架的请求下发线程,导致预览帧率下降。正确的做法是:请求入队后立即返回,由HAL内部的工作线程负责实际的硬件操作和结果生成。
4.4 实战案例:多流并发场景下的接口协同与排障
假设我们正在开发一款支持“预览+拍照+深度”三路流的HAL3实现。设备约束如下:ISP支持同时处理两路YUV流和一路RAW流,但深度流需要额外的DSP资源。初始现象是:当三路流同时开启时,预览帧率从30fps掉到15fps,且拍照后深度图出现花屏。
分析过程:
- 首先检查
configure_streams()的调用参数。通过日志发现,框架下发的流配置中,深度流格式为HAL_PIXEL_FORMAT_BLOB,尺寸为640x480,而我们的ISP硬件不支持该尺寸的BLOB输出,需要内部转换。 - 进一步检查
process_capture_request()的调用频率。发现框架在预览+深度模式下,每帧会下发两个请求(一个用于预览流,一个用于深度流),而我们的HAL实现将两个请求串行处理,导致深度请求阻塞了预览请求。 - 通过
dump()输出请求队列状态,确认深度请求的平均处理时间为25ms,而预览请求仅需8ms。瓶颈在于深度流的DSP处理是同步的,且未使用双缓冲。
解决方案:
- 在
configure_streams()中,为深度流分配专用的DSP缓冲区,并启用硬件双缓冲机制,使DSP处理与ISP输出并行。 - 修改
process_capture_request()的实现,将深度请求和预览请求分别放入两个独立队列,由两个工作线程并行处理。同时,在结果回调中,确保深度帧的frame_number与预览帧的frame_number保持同步(即使处理完成时间不同)。 - 增加
flush()的实现,当框架发起快速取消时,立即丢弃深度队列中的请求,并发送ERROR_REQUEST通知。
结果对比:优化后,预览帧率恢复至30fps,深度图花屏消失。通过dumpsys media.camera确认,HAL的请求处理延迟从平均25ms降至10ms,且未出现帧号错乱。
经验复盘:多流并发是HAL3开发中最容易出问题的场景。建议在configure_streams()阶段就明确每路流的硬件路径和资源需求,避免在请求处理时动态分配。同时,务必实现flush(),因为框架在切换分辨率或关闭相机时会依赖它快速清理请求队列。
4.5 常见误区与设计建议
在HAL3接口实现中,以下误区需要特别警惕:
- 误区一:在
process_capture_request()中同步等待硬件完成。这会导致框架线程阻塞,严重时引发ANR。正确做法是异步处理,并确保函数在1ms内返回。 - 误区二:忽略
partial_result的语义。如果HAL分多次返回metadata,必须保证partial_result从1开始递增,且最后一次返回时包含所有剩余字段。否则框架会一直等待,导致请求超时。 - 误区三:错误处理不完整。当硬件出错时,HAL必须通过
notify()发送ERROR_DEVICE,并停止所有后续请求。如果只返回错误码而不通知,框架会认为HAL仍然正常,导致状态不一致。 - 误区四:流配置时未检查buffer的
usage标志。框架可能要求HAL支持特定的buffer使用方式(如GPU纹理、视频编码器输入),HAL应在configure_streams()中验证stream->usage与自身硬件能力是否匹配。
总结而言,camera3_device_t与camera3_callback_ops_t是HAL3架构的“任督二脉”:前者定义了HAL能做什么,后者定义了HAL如何通知框架。掌握这两个结构体的每个字段、每个回调的时序要求,是开发稳定、高性能相机HAL的基础。建议开发者在编码前,先画出类似本章开头的接口关系图,明确数据流和控制流的走向,再动手实现。