10、CameraService 架构:Camera HAL3 模型、Camera 设备会话、预览与拍照流程

各位同学,今天我们来啃一块硬骨头——CameraService 的架构。说实话,Camera 子系统在 Android 里一直是最复杂、坑最多的模块之一。我当年刚接手相机项目时,光是把 HAL3 的 pipeline 理清楚就花了两周。但只要你把今天这几个核心概念吃透,后面写相机应用或者做相机定制,就会顺手很多。

10.1 Camera HAL3 模型:从“黑盒”到“管道”

先聊聊 HAL3 模型。Android 相机 HAL 经历过两次大迭代:HAL1 和 HAL3。HAL1 是个典型的“黑盒”模型——你调用 open(),它给你返回一个 preview 流,内部怎么处理你完全不知道。说白了,HAL1 就是一把大锤子,你只能砸,不能精细控制。

HAL3 就不一样了。它引入了 pipeline 模型,把相机处理流程拆成了一个个可配置的 stage。每个 stage 可以独立设置参数,比如曝光、增益、白平衡。你想想看,这就像从“点外卖”变成了“自己下厨”——你可以控制每一道工序。

核心概念:HAL3 中,一个 camera device 对应一个 pipeline。每个 pipeline 包含多个 stream(数据流),每个 stream 有自己独立的 buffer queue。

HAL3 的架构可以用下面这张图来理解。我习惯把它画成三层:App 层、Framework 层、HAL 层。App 通过 Camera2 API 下发请求,Framework 里的 CameraService 负责调度,HAL 层真正干活。

Camera HAL3 架构分层 App 层 (Camera2 API) CameraManager → CameraDevice → CaptureRequest / CaptureResult 每个 CaptureRequest 对应一次拍照或一帧预览 Framework 层 (CameraService) CameraService → CameraDeviceClient → Camera3Device 负责请求排队、buffer 管理、结果回调 HAL 层 (Camera HAL3) camera3_device_t → process_capture_request() → process_capture_result() 真正的 ISP 处理、3A 算法、帧合成都在这里

嗯,这里要注意:HAL3 的 pipeline 是 异步 的。你下发一个 request,HAL 处理完后通过回调通知你。这个设计让相机可以连续处理多帧,但也带来了同步的复杂性。我在项目中遇到过一个问题:连续下发 30 帧 request,结果 HAL 返回的顺序乱了,导致预览画面抖动。后来发现是 buffer 复用没做好。

10.2 Camera 设备会话:Session 的生命周期

接下来是设备会话。在 Camera2 API 里,你要先创建一个 CameraCaptureSession。这个 session 说白了就是你和相机硬件之间的一条“专线”。

创建 session 的流程大致如下:

// 1. 获取 CameraDevice
CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
String cameraId = manager.getCameraIdList()[0];
manager.openCamera(cameraId, stateCallback, handler);

// 2. 在 onOpened 回调中创建 session
@Override
public void onOpened(CameraDevice camera) {
    List<Surface> outputs = new ArrayList<>();
    outputs.add(surfaceView.getHolder().getSurface()); // 预览输出
    outputs.add(imageReader.getSurface());             // 拍照输出
    
    camera.createCaptureSession(outputs, sessionCallback, handler);
}

// 3. 在 onConfigured 回调中开始预览
@Override
public void onConfigured(CameraCaptureSession session) {
    CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    builder.addTarget(surfaceView.getHolder().getSurface());
    session.setRepeatingRequest(builder.build(), captureCallback, handler);
}
我的经验:创建 session 时,outputs 列表的顺序会影响性能。我习惯把预览 Surface 放在第一个,拍照 Surface 放在后面。因为 HAL 通常会优先保证第一个 stream 的帧率。

session 有三种状态:Active(正在处理请求)、Idle(空闲)、Closed(已关闭)。你可以在 onCaptureSequenceCompleted 回调里判断是否所有请求都完成了。

状态 触发条件 常见问题
Active 有未完成的 capture 或 repeating request 连续拍照时 session 可能卡在 Active
Idle 所有请求都已完成,没有新的 request 切换场景时最好先等 Idle
Closed 调用了 session.close() 关闭后不能再提交 request
避坑指南:我曾经在切换前后摄像头时,没有等旧 session 完全关闭就创建新 session,结果 HAL 报错 "camera3_device_t is busy"。正确的做法是:先 close() 旧 session,在 onClosed 回调里再 openCamera 和 createCaptureSession。

10.3 预览流程:从 Sensor 到屏幕

预览流程,说白了就是相机 sensor 不断采集数据,经过 ISP 处理,最后显示到屏幕上。这个过程是 循环 的——每一帧都走一遍同样的 pipeline。

具体步骤:

  1. App 下发 RepeatingRequest:通过 session.setRepeatingRequest() 告诉 HAL 持续输出预览帧。
  2. HAL 处理 Sensor 数据:HAL 驱动 sensor 曝光、读出 raw 数据,然后做 ISP 处理(去噪、颜色校正、缩放)。
  3. HAL 填充 Output Buffer:处理完的数据写入 Surface 对应的 buffer 里。
  4. SurfaceFlinger 合成显示:Surface 的 buffer 被 SurfaceFlinger 取走,合成到屏幕上。

这里有个关键点:预览帧率。Android 默认预览帧率是 30fps,但如果你在 HAL 里做了太多处理(比如人脸检测),帧率可能掉到 15fps。我建议你在 HAL 的 process_capture_request 里加个计时器,监控每帧的处理时间。

// HAL 层的伪代码
int process_capture_request(const camera3_capture_request_t *request) {
    uint64_t start = get_timestamp_ns();
    
    // 1. 获取 sensor 数据
    buffer_handle_t input_buffer = request->input_buffers[0];
    
    // 2. ISP 处理
    isp_process(input_buffer, request->settings);
    
    // 3. 填充输出 buffer
    for (int i = 0; i < request->num_output_buffers; i++) {
        fill_output_buffer(request->output_buffers[i]);
    }
    
    uint64_t elapsed = get_timestamp_ns() - start;
    if (elapsed > 33 * 1000000) { // 超过 33ms 说明掉帧了
        ALOGW("Preview frame took %lld ms, dropping frame rate", elapsed / 1000000);
    }
    
    return 0;
}
核心要点:预览流程中,每一帧的 CaptureResult 都会回调到 App 的 onCaptureCompleted()。你可以在这里拿到帧的时间戳、3A 状态等信息。但注意,不要在回调里做耗时操作,否则会阻塞 pipeline。

10.4 拍照流程:从 Request 到 JPEG

拍照和预览最大的区别是:预览是 持续 的,拍照是 单次 的。拍照时,App 下发一个 capture() 请求,HAL 处理完一帧后返回结果,然后 pipeline 就停了(除非你继续发 repeating request)。

拍照的完整流程:

  1. App 下发 CaptureRequest:设置好拍照参数(闪光灯、对焦模式、输出格式等)。
  2. HAL 执行 3A 收敛:如果设置了 AE/AF/AWB,HAL 会先做自动曝光、自动对焦、自动白平衡。这个过程可能需要几帧。
  3. HAL 捕获一帧:sensor 曝光、读出数据,ISP 处理成 YUV 或 RAW 格式。
  4. JPEG 编码:如果输出格式是 JPEG,HAL 或 Framework 会做 JPEG 压缩。
  5. 回调 CaptureResult:App 在 onCaptureCompleted() 里拿到 JPEG 数据。

嗯,这里有个坑:拍照时如果闪光灯开了,HAL 需要先做预闪(pre-flash)来确定曝光参数。预闪会额外消耗 1-2 帧的时间。我遇到过一个问题:用户连拍时,闪光灯每拍一次都做预闪,导致连拍速度很慢。后来我们改成了只在第一次拍照时做预闪,后续复用参数。

// 拍照的 CaptureRequest 设置示例
CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(imageReader.getSurface());

// 设置闪光灯模式
builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);

// 设置对焦模式
builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);

// 设置输出格式(通过 ImageReader 的配置)
// ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);

// 触发拍照
session.capture(builder.build(), captureCallback, handler);
我的建议:拍照时最好先停止 repeating request,等拍照完成后再恢复。否则 HAL 可能同时处理预览和拍照两路请求,导致 buffer 冲突。我习惯这样写:
session.stopRepeating();
session.capture(stillCaptureRequest, new CaptureCallback() {
    @Override
    public void onCaptureCompleted(CaptureResult result) {
        // 拍照完成,恢复预览
        session.setRepeatingRequest(previewRequest, null, null);
    }
}, handler);

最后,总结一下今天的内容。Camera HAL3 的核心是 pipeline 模型,它把相机处理拆成了可配置的 stage。设备会话是 App 和 HAL 之间的桥梁,管理着请求的生命周期。预览和拍照本质上是同一套 pipeline,只是一个是循环执行,一个是单次执行。

我个人觉得,理解 CameraService 架构的关键在于“异步”和“buffer 管理”这两个点。你只要把这两个点想通了,后面看 CameraService 的源码就会轻松很多。好了,今天的课就到这里,大家回去可以自己写个 demo,试试用 Camera2 API 实现预览和拍照,感受一下 pipeline 的流转。

课后思考:如果我想在预览的同时做人脸检测,应该怎么设计 pipeline?是加一个额外的 output stream,还是在 HAL 里直接处理?两种方式各有什么优缺点?
公众号:蓝海资料掘金营,微信 deep3321