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 后,先做一次格式转换和校验。

⚠️ 注意: open() 函数里不要做耗时操作,比如打开硬件传感器、分配大块内存。这些应该放到 configure_streams() 或者第一次 process_capture_request() 时再做。否则上层会超时,直接判定你死机。

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,确保所有请求都处理完了。

💡 小技巧: 可以在 close() 里加一个超时机制,比如等待 2 秒,如果还有请求没完成,就强制取消并打印警告日志。这样既保证了稳定性,又方便调试。

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 能提供更好的容错性。

🔑 关键点: configure_streams() 可能会被多次调用。比如用户切换分辨率、切换拍照模式时,都会重新配置。所以你的实现必须支持「热切换」——在不关闭硬件的情况下,动态调整管线参数。

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 泄漏或者数据错乱。

⚠️ 注意: process_capture_request() 不能阻塞!它必须立即返回。实际的硬件操作(比如 ISP 处理、DMA 传输)应该放到另一个线程里异步执行。否则你会阻塞整个相机框架,导致 ANR。

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、应用层之间高效流转,不丢帧、不卡顿。