4、HAL3接口实现:open、close、process_capture_request、configure_streams等关键函数
好,咱们接着往下聊。上一章我们把 HAL3 的整体架构和初始化流程捋了一遍,这一章要动真格的了——实现那几个核心接口函数。说白了,HAL3 能不能跑起来,就看这几个函数写得对不对。
我个人习惯,每次移植新平台时,都会先把这几个函数的骨架搭好,再往里填逻辑。为什么?因为它们是相机 HAL 的「命门」,任何一个出问题,上层框架直接就崩给你看。
4.1 open()——打开相机设备
这个函数是 HAL3 的入口。上层调用 camera_device_open() 时,实际上就是在找你这个函数指针。
int camera_device_open(const hw_module_t* module, const char* id,
hw_device_t** device)
{
// 1. 参数检查
if (module == NULL || id == NULL || device == NULL) {
ALOGE("Invalid parameters");
return -EINVAL;
}
// 2. 分配 camera_device 结构体
camera3_device_t* cam_dev = (camera3_device_t*)malloc(sizeof(camera3_device_t));
if (cam_dev == NULL) {
ALOGE("Failed to allocate camera device");
return -ENOMEM;
}
// 3. 初始化 ops 函数指针表
cam_dev->ops = &g_camera3_ops;
// 4. 初始化私有数据(你的硬件抽象层)
cam_dev->priv = new CameraDevice(id);
if (cam_dev->priv == NULL) {
free(cam_dev);
return -ENOMEM;
}
*device = (hw_device_t*)cam_dev;
return 0;
}
嗯,这里要注意一点:id 参数代表的是相机 ID,比如 "0" 代表后置摄像头,"1" 代表前置。我在项目中遇到过,有些平台把 ID 写死成字符串,结果上层传的是整数,一比对就崩了。所以建议你拿到 ID 后,先做一次格式转换和校验。
4.2 close()——优雅地关闭
close() 看似简单,其实坑不少。我见过太多人直接 free 内存就完事了,结果下次 open 时各种残留状态。
int camera_device_close(hw_device_t* device)
{
if (device == NULL) {
return -EINVAL;
}
camera3_device_t* cam_dev = (camera3_device_t*)device;
// 1. 停止所有正在进行的请求
// 2. 释放硬件资源
// 3. 清理内部状态
CameraDevice* dev = (CameraDevice*)cam_dev->priv;
if (dev) {
dev->stopStream();
dev->flush();
delete dev;
}
free(cam_dev);
return 0;
}
我曾经踩过一个坑:close() 时没有等待所有 pending 的 capture request 完成,结果上层紧接着又 open,硬件还在忙,直接报错。所以我的建议是——close 之前先 flush,确保所有请求都处理完了。
4.3 configure_streams()——配置数据流
这个函数是 HAL3 里最复杂的之一。上层会告诉你:我要多少个 stream,每个 stream 的格式、分辨率、用途是什么。你需要根据这些信息,配置你的 ISP 和硬件管线。
int configure_streams(const camera3_device_t* device,
camera3_stream_configuration_t* stream_list)
{
// 1. 检查参数
if (device == NULL || stream_list == NULL) {
return -EINVAL;
}
// 2. 遍历所有 stream
for (uint32_t i = 0; i < stream_list->num_streams; i++) {
camera3_stream_t* stream = stream_list->streams[i];
// 3. 检查格式是否支持
if (!isFormatSupported(stream->format)) {
ALOGE("Unsupported format: 0x%x", stream->format);
return -EINVAL;
}
// 4. 检查分辨率是否支持
if (!isResolutionSupported(stream->width, stream->height)) {
ALOGE("Unsupported resolution: %dx%d", stream->width, stream->height);
return -EINVAL;
}
// 5. 分配 buffer(如果是 IMPLEMENTATION_DEFINED 格式)
if (stream->format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
stream->format = selectBestFormat(stream->usage);
}
// 6. 设置 stream 的 usage 和 max_buffers
stream->usage = GRALLOC_USAGE_HW_CAMERA_WRITE;
stream->max_buffers = 4; // 建议至少 4 个 buffer
}
// 7. 重新配置 ISP 管线
CameraDevice* dev = (CameraDevice*)device->priv;
return dev->reconfigureISP(stream_list);
}
你想想看,为什么 max_buffers 建议设成 4?因为 Android 的 buffer 管理机制是生产者-消费者模型,至少需要 3 个 buffer 才能流畅运行(一个在 ISP 里写,一个在 HAL 里处理,一个在应用层显示)。4 个 buffer 能提供更好的容错性。
4.4 process_capture_request()——处理捕获请求
这是 HAL3 的核心中的核心。上层每按一次快门、每帧预览,都会调用这个函数。说白了,它就是相机 HAL 的「心脏」。
int process_capture_request(const camera3_device_t* device,
camera3_capture_request_t* request)
{
// 1. 参数检查
if (device == NULL || request == NULL) {
return -EINVAL;
}
CameraDevice* dev = (CameraDevice*)device->priv;
// 2. 检查 frame number 是否连续
if (request->frame_number != dev->getNextFrameNumber()) {
ALOGW("Frame number mismatch: expected %d, got %d",
dev->getNextFrameNumber(), request->frame_number);
}
// 3. 处理 settings(3A 参数等)
if (request->settings != NULL) {
dev->applySettings(request->settings);
}
// 4. 处理每个 output buffer
for (uint32_t i = 0; i < request->num_output_buffers; i++) {
const camera3_stream_buffer_t* buf = &request->output_buffers[i];
// 5. 将 buffer 加入硬件处理队列
dev->enqueueBuffer(buf);
}
// 6. 触发硬件捕获
dev->triggerCapture();
return 0;
}
嗯,这里有个容易忽略的点:frame number 必须严格递增。我曾经在调试时发现,上层偶尔会重发同一个 frame number(比如在异常恢复时),如果你不做检查,直接覆盖之前的请求,就会导致 buffer 泄漏或者数据错乱。
4.5 其他关键函数
除了上面四个,还有几个函数也值得提一下:
| 函数名 | 作用 | 注意事项 |
|---|---|---|
| flush() | 清空所有 pending 请求 | 必须在 close() 之前调用,否则可能丢帧 |
| construct_default_request_settings() | 返回默认的 capture settings | 必须包含所有 mandatory 的 metadata tag |
| get_metadata_vendor_tag_ops() | 注册 vendor tag 操作 | 如果你有自定义的 metadata,必须实现这个 |
| dump() | 打印调试信息 | 建议把内部状态、buffer 队列、帧率等信息都打印出来 |
4.6 避坑指南
最后,分享几个我实际踩过的坑:
- buffer 管理:我曾经在 process_capture_request() 里直接操作 buffer 的指针,结果上层还在用这个 buffer,导致画面撕裂。后来我改用引用计数,确保 buffer 被上层释放后才还给硬件。
- metadata 完整性:Android 的 CTS 测试会检查每个 frame 的 metadata 是否包含所有 mandatory 的 tag。少一个就 fail。我的建议是——写一个工具函数,自动填充所有 mandatory tag 的默认值。
- 多线程安全:HAL3 的接口可能被多个线程同时调用(比如 preview 线程和 capture 线程)。所以你的内部状态必须加锁保护。我习惯用
std::mutex配合std::lock_guard,简单可靠。
好了,这一章的内容就到这里。下一章我们会深入讲解 buffer 管理机制——说白了,就是怎么让 buffer 在 HAL、ISP、应用层之间高效流转,不丢帧、不卡顿。