14、Camera子系统:Camera HAL3架构、ISP处理、预览与拍照流程

Camera子系统,说白了就是Android设备上那块“眼睛”的驱动层。我做了这么多年嵌入式开发,Camera HAL3的改动可以说是Android系统里最折腾人的部分之一。从HAL1到HAL3,不光是接口变了,整个设计思路都翻了个个儿。

今天咱们就聊聊Camera HAL3的架构、ISP怎么处理图像,以及预览和拍照的完整流程。嗯,这里头坑不少,我一个个给你拆开讲。

14.1 Camera HAL3架构概览

HAL3的核心思想,是把Camera当成一个“流水线”。你想想看,每一帧图像从传感器出来,经过ISP处理,再送到应用层,这中间每一步都可以独立控制。HAL3就是把这个流水线暴露给了上层。

我个人习惯把HAL3架构分成三层:

  • 应用层:Camera2 API,开发者直接调用的接口
  • 框架层:CameraService,负责协调多个Client的请求
  • HAL层:Camera HAL3实现,直接跟硬件打交道

这里有个关键点:HAL3引入了Capture RequestCapture Result的概念。每次拍照或预览,应用层下发一个Request,HAL层处理完后返回一个Result。这跟HAL1那种“打开-预览-拍照-关闭”的线性流程完全不同。

核心变化:HAL3支持同时处理多个Request,而且每个Request可以指定不同的参数(比如分辨率、帧率、对焦区域)。这在HAL1时代是想都不敢想的。

我在项目中遇到过一个问题:某个平台升级到HAL3后,预览帧率反而下降了。查了半天,发现是Request队列没处理好,导致ISP一直在切换参数。后来加了个缓冲区,问题就解决了。嗯,细节决定成败。

14.2 ISP处理流程

ISP(Image Signal Processor)是Camera的灵魂。传感器出来的原始数据是Bayer格式的,说白了就是每个像素只记录一种颜色(红、绿或蓝)。ISP要把它变成我们能看的RGB图像。

ISP的处理管线大致如下:

  1. 黑电平校正:去掉传感器本身的暗电流噪声
  2. 去马赛克:把Bayer格式插值成RGB
  3. 白平衡:调整色温,让白色看起来是白的
  4. 色彩校正:把传感器色彩空间映射到标准色彩空间
  5. Gamma校正:调整亮度曲线,适应人眼感知
  6. 降噪:去掉各种噪声(亮度噪声、色度噪声)
  7. 锐化:增强边缘细节

你可能会问:“这些步骤能不能跳过?” 嗯,有些可以,但大部分不行。比如黑电平校正如果跳过了,暗部区域会偏色。我调试过一款低端传感器,黑电平校正参数没配好,拍出来的夜景全是紫色的。折腾了两天才找到原因。

我的经验:ISP参数调试是个细活。每个传感器都有自己的特性,同一套参数换到另一个传感器上可能完全不能用。建议先用厂商提供的参考参数,再逐步微调。

下面这张图展示了ISP处理的核心流程:

ISP处理管线 Bayer输入 黑电平校正 去马赛克 白平衡 色彩校正 Gamma校正 降噪 锐化 RGB输出 每个步骤都可以独立配置参数,HAL3通过Request/Result机制控制

14.3 预览流程

预览流程,说白了就是让Camera持续输出帧数据到Surface上。在HAL3里,这个过程是这样的:

  1. 应用层创建CaptureSession,并配置输出Surface(比如TextureView或SurfaceView)
  2. 应用层下发Repeating Request,告诉HAL“我要持续预览”
  3. HAL层收到Request后,驱动Sensor开始采集数据
  4. ISP处理完一帧后,把数据送到指定的Surface
  5. 应用层从Surface拿到数据,渲染到屏幕上

这里有个容易踩的坑:Repeating Request的模板选择。HAL3定义了多种模板,比如TEMPLATE_PREVIEW、TEMPLATE_STILL_CAPTURE、TEMPLATE_VIDEO_RECORD等。每个模板的默认参数不一样。

注意:预览时一定要用TEMPLATE_PREVIEW模板。我曾经见过有人用TEMPLATE_STILL_CAPTURE做预览,结果帧率只有5fps,因为那个模板默认开启了全尺寸输出和最高画质处理。预览根本不需要那么高的质量,够用就行。

预览流程的代码示例(简化版):

// 创建CameraCaptureSession
cameraDevice.createCaptureSession(
    Arrays.asList(previewSurface),
    new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            // 下发Repeating Request
            CaptureRequest.Builder builder = 
                cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            builder.addTarget(previewSurface);
            session.setRepeatingRequest(builder.build(), null, null);
        }
    }, null);

嗯,代码看着简单,但实际调试时问题不少。比如Surface的格式必须匹配,否则HAL会报错。我遇到过Surface配置成RGB_565,但HAL只支持NV21的情况,折腾了半天才找到原因。

14.4 拍照流程

拍照跟预览最大的区别是:拍照需要单次触发,而且通常需要更高分辨率和更高质量的处理。

拍照流程如下:

  1. 应用层下发一个单次Capture Request,参数通常用TEMPLATE_STILL_CAPTURE
  2. HAL层收到Request后,可能会先做一次预对焦(如果开启了自动对焦)
  3. Sensor采集一帧数据,ISP做完整处理(包括降噪、锐化等)
  4. 处理完的数据送到指定的输出Surface(比如ImageReader)
  5. 应用层从ImageReader拿到JPEG或YUV数据,保存到文件

这里有个细节:拍照时通常会同时输出两路数据——一路是预览用的低分辨率流,一路是拍照用的高分辨率流。HAL3支持多路输出,但要注意性能开销。

我的建议:拍照时不要停止预览流。很多开发者习惯先停止预览再拍照,拍完再重新预览。其实完全没必要,HAL3支持同时处理多个Request。保持预览流不断,用户体验会好很多。

拍照流程的代码示例:

// 创建ImageReader,用于接收拍照数据
ImageReader reader = ImageReader.newInstance(
    width, height, ImageFormat.JPEG, 1);
reader.setOnImageAvailableListener(reader -> {
    Image image = reader.acquireLatestImage();
    // 保存JPEG数据
    saveImage(image);
    image.close();
}, null);

// 下发拍照Request
CaptureRequest.Builder builder = 
    cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(reader.getSurface());
// 同时保持预览流
builder.addTarget(previewSurface);
session.capture(builder.build(), null, null);

你可能会问:“为什么ImageReader的maxImages设成1?” 嗯,这是为了避免内存占用过高。拍照数据通常很大,一张JPEG可能几MB,如果缓存太多帧,内存会爆。设成1就够了,拍完一帧马上取走。

14.5 预览与拍照的协同

在实际应用中,预览和拍照是同时进行的。HAL3通过Stream Configuration来管理多路输出。配置Stream时,需要指定每个Stream的格式、分辨率和用途。

常见的配置组合:

Stream类型 分辨率 格式 用途
预览流 1920x1080 NV21 实时显示
拍照流 4000x3000 JPEG 保存照片
分析流 640x480 YUV_420_888 人脸检测等

配置Stream时有个限制:所有Stream的帧率必须一致。你不能让预览流跑30fps,拍照流跑15fps。HAL3会取所有Stream的最大帧率作为统一帧率。

避坑指南:我曾经在一个项目里配置了三个Stream(预览、拍照、分析),结果发现预览帧率从30fps掉到了15fps。查了半天,发现是分析流的分辨率设得太高,ISP处理不过来。后来把分析流降到320x240,问题解决了。记住:Stream越多,性能开销越大。

下面这张图展示了预览和拍照的协同流程:

预览与拍照协同流程 应用层 下发Request CameraService 管理Request队列 Camera HAL3 驱动Sensor+ISP 预览Request(持续) 拍照Request(单次) ISP处理 预览Surface 拍照ImageReader 分析流(可选) 预览流持续输出,拍照流单次触发,两者互不干扰

14.6 常见问题与调试技巧

做Camera HAL3开发,有几个问题几乎每个人都会遇到:

  • 预览黑屏:通常是Surface配置不对,或者HAL没收到Request。先检查logcat里有没有“configureStreams”相关的错误。
  • 拍照卡顿:可能是JPEG编码太慢。试试降低JPEG质量,或者用YUV格式输出,让应用层自己编码。
  • 帧率不稳定:检查ISP的处理时间。如果一帧处理时间超过33ms,帧率就上不了30fps。
  • 颜色偏色:白平衡参数没调好。建议先用AWB(自动白平衡),稳定后再切手动。

重要提醒:调试Camera HAL3时,一定要看logcat的“CameraService”和“CameraHal”标签。很多错误信息会打印在这里。我曾经花了一整天查一个预览黑屏的问题,最后发现是HAL层没实现configureStreams接口。logcat里清清楚楚地打印了“not implemented”,只是我没注意看。

好了,Camera HAL3的内容就聊到这儿。记住:HAL3的核心是Request/Result模型,ISP处理是图像质量的关键,预览和拍照可以并行进行。把这些搞明白了,Camera开发就成功了一大半。

公众号:蓝海资料掘金营,微信deep3321