2、Android Camera 架构基础:Camera HAL3、Camera Device、Camera Provider、Camera Service 的层次结构与数据流

Android Camera 系统自 Android 8.0(O)起完成了从传统 HAL1 到 Treble 架构下的 HAL3 全面迁移,其核心设计目标是将 相机硬件抽象层(HAL)、框架服务(Camera Service) 与 应用进程(App) 解耦,同时为上层提供统一的、支持多摄像头并发与高级后处理的编程接口。理解这一层次结构,是后续深入 JPEG 编码器集成、硬件编码器调优以及 HAL 层软件编码回退机制的前提。本章将系统拆解 Camera HAL3、Camera Device、Camera Provider 与 Camera Service 四者的职责边界、通信协议(Binder/HIDL/AIDL)以及从应用请求到帧输出的完整数据流。

Android Camera 层次结构与数据流(HAL3 / Treble) App 进程 CameraManager / CameraDevice / CaptureRequest / CaptureResult Framework (Java/Native) CameraService (native) — 会话管理 / 请求分发 / 结果回调 CameraDeviceClient / CameraDeviceUser (Binder 接口) Camera Provider 进程(独立于 cameraserver) CameraProviderHIDL / AIDL — 枚举设备、打开/关闭 CameraDevice ICameraProvider / ICameraDeviceCallback / ICameraDevice Camera HAL3 实现(Vendor 进程) camera3_device_t / camera3_stream_t / camera3_capture_request_t 硬件 ISP / 传感器驱动 / JPEG 编码器(硬件或软件) Binder: 请求/回调 HIDL/AIDL: open/configure HAL 接口调用 数据流方向 ① App 提交 CaptureRequest ② Service 分发至 HAL ③ HAL 填充输出 buffer ④ 回调 metadata + buffer ⑤ App 获取 JPEG/RAW (JPEG 编码位于 HAL 内) App/框架 Provider 进程 HAL 实现 请求/回调 数据/控制

上图展示了 Android Camera 在 Treble 架构下的核心分层。需要特别强调的是,Camera Provider 进程 是 Android 8.0 之后引入的关键中间层,它将 HAL 实现从 cameraserver 进程中剥离,使得 Vendor 实现的 HAL 可以在独立进程中运行,从而支持系统框架与 Vendor 实现之间的版本独立更新。JPEG 编码器无论是硬件 ISP 内置还是独立编码器 IP,均位于 HAL 层之下,由 HAL 统一管理。

2.1 核心原理:四层职责边界与接口契约

Android Camera 架构自上而下可划分为四个逻辑层次,每一层都有严格的接口定义与职责边界。理解这些边界是进行编码器集成与调试的基础。

层次进程/组件核心职责关键接口与 JPEG 编码的关系
应用层App (Java/Kotlin)通过 CameraManager 获取相机列表,创建 CameraDevice,提交 CaptureRequest,接收 CaptureResult 与 ImageCameraManager, CameraDevice, CaptureRequest, ImageReader仅消费 JPEG 或 YUV 格式的 Image,不感知编码细节
框架层CameraService (native)管理 CameraDeviceClient 会话,协调多个客户端访问,分发请求,回调结果与错误ICameraService, ICameraDeviceUser, ICameraDeviceCallback透传 JPEG 格式的 buffer,不参与编码逻辑
Provider 层Camera Provider 进程枚举 HAL 设备,创建/销毁 CameraDevice,转发框架请求至 HAL,转发 HAL 回调至框架ICameraProvider, ICameraDevice, ICameraDeviceCallback (HIDL/AIDL)作为桥梁,传递 JPEG 编码相关的 stream 配置与 buffer
HAL 层Vendor HAL (camera3)实现 camera3_device_ops,管理 ISP pipeline,处理 capture request,填充输出 buffer(含 JPEG 编码)camera3_device_t, camera3_stream_t, camera3_capture_request_tJPEG 编码器集成点:HAL 负责调用硬件编码器或软件编码器生成 JPEG

在 HAL3 架构下,camera3_device_t 是 HAL 暴露给上层的主要设备对象。它包含一组操作函数指针(ops),如 initialize()、configure_streams()、process_capture_request() 等。其中 configure_streams() 是编码器集成的关键入口,上层在此告知 HAL 需要配置的 stream 集合(如预览 YUV、拍照 JPEG、RAW),HAL 需要根据这些 stream 的格式与大小决定是否启用硬件 JPEG 编码器,以及如何分配内部 buffer。

关键理解:HAL3 的 process_capture_request() 是同步调用,但 HAL 内部可以异步处理。对于 JPEG 编码请求,HAL 通常会在收到 request 后,将 YUV 数据送入硬件编码器,并在编码完成后通过 process_capture_result() 回调返回填充了 JPEG 数据的 buffer。这个异步过程是保证相机连拍性能的关键。

2.2 判断方法:如何定位编码器集成层次

在实际工程中,当需要集成新的 JPEG 编码器或调试编码问题时,首先需要判断问题发生在哪一层。以下是一个系统化的定位流程:

  1. 检查 HAL 版本与接口:确认设备使用的是 HAL3 还是 HAL1。通过 adb shell dumpsys media.camera 查看当前相机 HAL 版本。HAL3 支持多 stream 并发与 per-frame 控制,是编码器集成的基础。
  2. 确认 Provider 进程状态:执行 adb shell ps -A | grep camera,查看 camera.provider 进程是否存在。如果 Provider 进程崩溃,HAL 无法被枚举,上层会报 CAMERA_DISCONNECTED 错误。
  3. 抓取 HAL 层日志:在 process_capture_request() 与 process_capture_result() 中增加 ALOGV/ALOGD 日志,打印 request ID、stream 格式、buffer 地址与大小。通过对比请求与返回的 buffer 内容,可以判断编码器是否被正确调用。
  4. 验证 JPEG 编码器输出:使用 adb pull 获取 JPEG 文件,检查文件头(FFD8)与 EXIF 信息。如果文件头正确但图像花屏,可能是编码器输入 YUV 数据格式不匹配(如 NV12 vs NV21);如果文件头错误,则编码器可能未正确初始化。
  5. 使用 systrace 分析时序:在编码器集成后,使用 systrace 抓取 processCaptureRequest 到 onCaptureCompleted 的耗时。正常硬件编码应在 30ms 内完成 1080p JPEG 编码,若超过 100ms 则可能存在 buffer 等待或编码器时钟未开启的问题。
经验提醒:在 HAL3 中,JPEG 编码器通常与 camera3_stream_t 的 format 字段相关。当上层请求 HAL_PIXEL_FORMAT_BLOB(JPEG)格式的 stream 时,HAL 需要确保该 stream 的 max_buffers 至少为 2,以支持双缓冲流水线,避免编码器等待 buffer 释放而阻塞。

2.3 案例拆解:JPEG 编码器在 HAL3 中的集成路径

假设某设备使用高通骁龙平台,需要将第三方硬件 JPEG 编码器(通过 V4L2 M2M 接口)集成到 HAL3 中。以下是完整的集成路径与关键决策点:

背景与约束:平台已有 ISP 输出 NV12 格式的 YUV 数据,但 ISP 内置 JPEG 编码器不支持 48MP 高分辨率输出(受限于时钟频率与功耗)。因此需要外接一个支持 48MP 的硬件编码器,通过 V4L2 内存到内存(M2M)设备节点 /dev/video-jpeg0 访问。

初始现象:在 HAL 中直接调用 V4L2 编码器时,发现拍照预览正常,但高分辨率拍照时出现 ENOBUFS(无可用 buffer)错误,且系统 CPU 占用率飙升至 80%。

分析过程:

  1. 通过 v4l2-ctl --list-formats-ext -d /dev/video-jpeg0 确认编码器支持 NV12 输入与 JPEG 输出,且支持 48MP 尺寸。
  2. 检查 HAL 中 configure_streams() 的调用,发现上层同时配置了 YUV 预览 stream 与 JPEG 拍照 stream。HAL 为 JPEG stream 分配了 4 个 buffer,但 V4L2 编码器驱动内部只维护了 2 个 capture buffer,导致请求积压。
  3. 进一步分析发现,HAL 在 process_capture_request() 中同步等待 V4L2 编码完成,阻塞了后续请求的处理。

实际操作步骤:

// 1. 在 configure_streams 中,为 JPEG stream 设置 max_buffers = 2
if (stream->format == HAL_PIXEL_FORMAT_BLOB) {
    stream->max_buffers = 2;
    stream->usage |= GRALLOC_USAGE_HW_CAMERA_WRITE;
}

// 2. 在 process_capture_request 中,将编码操作放入工作线程
//    避免阻塞 HAL 主线程
pthread_create(&encoder_thread, NULL, jpeg_encode_worker, request);

// 3. 在 jpeg_encode_worker 中,使用 V4L2 多平面 API
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.m.planes[0].m.fd = input_dmabuf_fd;  // YUV buffer
buf.m.planes[0].length = yuv_size;
ioctl(fd, VIDIOC_QBUF, &buf);

// 4. 等待编码完成,获取输出 buffer
select(fd + 1, &fds, NULL, NULL, &timeout);
ioctl(fd, VIDIOC_DQBUF, &buf);
// 将 buf.m.planes[0].m.fd 填充到 camera3_stream_buffer 中返回

结果对比:修改后,48MP 拍照耗时从 1.2s 降至 350ms,CPU 占用率降至 15%。连拍速度从 0.5fps 提升至 2fps。

风险与代价:引入工作线程后,需要处理线程同步与生命周期管理。在 HAL 卸载时,必须确保编码线程退出并释放所有 V4L2 buffer,否则会导致内核驱动崩溃。

风险提示:V4L2 M2M 设备在 HAL 中集成时,必须注意 DMABUF 的导入与导出。如果 HAL 与编码器不在同一个硬件隔离域(如不同 IOMMU 域),需要确保 buffer 的物理连续性与 cache 一致性。建议使用 dma_buf_sync 或 ion_sync 操作保证数据可见性。

2.4 常见误区与总结复盘

在 Camera HAL3 架构下进行编码器集成时,工程师常陷入以下误区:

  • 误区一:将 JPEG 编码器视为独立模块。实际上,编码器必须与 HAL 的 stream 配置、buffer 管理、request 生命周期紧密耦合。编码器的输入 buffer 必须来自 ISP 的输出,且需要与 HAL 的 gralloc buffer 共享。
  • 误区二:忽略 Provider 进程的隔离性。在 Treble 架构下,HAL 运行在独立的 Provider 进程中,不能直接访问 cameraserver 的内存。所有 buffer 传递必须通过 HIDL/AIDL 接口的句柄传递,且需要处理跨进程的 buffer 同步。
  • 误区三:同步调用编码器。在 process_capture_request() 中同步等待编码完成会严重阻塞 HAL 的请求处理流水线,导致预览卡顿。正确做法是使用异步工作队列,并利用 camera3_callback_ops 中的 notify() 上报请求状态。
  • 误区四:忽视 JPEG 编码器的功耗与温升。硬件编码器在高分辨率连续拍照时会产生显著热量,HAL 需要监控编码器温度,并在过热时降级到软件编码或降低帧率。