3. Camera3Stream 结构体详解

在 Camera HAL3 中,camera3_stream_t 是连接 Framework 与 HAL 的图像数据通道描述符。每一个 Stream 代表一条独立的图像数据流,其配置直接决定了 HAL 输出的图像格式、分辨率、缓冲池大小以及使用场景。理解该结构体的每个字段,是正确配置 Stream 的基础。

核心要点: Stream 配置错误是 HAL 开发中最常见的 crash 来源之一。格式不匹配、分辨率超出限制、usage 标志遗漏,都会导致 configure_streams() 调用失败或预览黑屏。

3.1 结构体总览

camera3_stream_t 定义在 hardware/libhardware/include/hardware/camera3.h 中,其核心字段如下表所示:

字段名 类型 方向 说明
stream_type int Framework → HAL 流类型:输出、输入、双向
width uint32_t Framework → HAL 图像宽度(像素)
height uint32_t Framework → HAL 图像高度(像素)
format int Framework → HAL 像素格式(HAL_PIXEL_FORMAT_*)
usage uint32_t Framework → HAL Gralloc 使用标志位,描述 buffer 用途
max_buffers uint32_t HAL → Framework HAL 请求的最大 buffer 数量
priv void * HAL 私有 HAL 内部关联的私有数据指针
data_space android_dataspace_t Framework → HAL 色彩空间与动态范围描述
rotation int Framework → HAL 输出旋转角度(0/90/180/270)
physical_camera_id const char * Framework → HAL 物理 camera ID(多摄逻辑相机使用)
开发提示: 在 configure_streams() 回调中,HAL 可以修改 max_buffers 和 priv 字段,但 不得修改 stream_type、width、height、format、usage 等输入字段。若 HAL 无法支持请求的配置,应返回 -EINVAL 或 -ENODEV。

3.2 核心字段深度解析

3.2.1 stream_type —— 流的方向与角色

stream_type 定义了 Stream 的数据流向,取值如下:

  • CAMERA3_STREAM_OUTPUT (0):输出流。数据从 HAL 流向 Framework,用于预览、拍照、录像。
  • CAMERA3_STREAM_INPUT (1):输入流。数据从 Framework 流向 HAL,用于 reprocessing(如美颜、HDR 合成)。
  • CAMERA3_STREAM_BIDIRECTIONAL (2):双向流。数据可双向流动,用于零快门延迟(ZSL)等场景。
常见错误: 将 preview 流错误配置为 CAMERA3_STREAM_INPUT 会导致 process_capture_result() 无法输出数据,造成预览黑屏。务必根据实际用途选择正确的类型。

3.2.2 width / height —— 分辨率约束

分辨率必须严格匹配 Sensor 输出能力或 ISP 缩放范围。HAL 在 configure_streams() 中应检查:

  • 是否在 android.scaler.availableStreamConfigurations 列出的分辨率范围内。
  • width 和 height 是否满足对齐要求(通常为 16 像素对齐,部分平台要求 32 或 64 像素对齐)。
  • 多个 Stream 之间是否存在分辨率依赖(如 YUV reprocessing 流需要与 RAW 流分辨率一致)。
// 典型的分辨率合法性检查
if (width % 16 != 0 || height % 16 != 0) {
    ALOGE("Stream resolution %dx%d not 16-pixel aligned", width, height);
    return -EINVAL;
}
if (width > sensor_max_width || height > sensor_max_height) {
    ALOGE("Stream resolution %dx%d exceeds sensor limit %dx%d",
          width, height, sensor_max_width, sensor_max_height);
    return -EINVAL;
}

3.2.3 format —— 像素格式的选择

format 使用 Android 定义的 HAL_PIXEL_FORMAT_* 常量。常见格式如下:

格式常量 值 典型用途 BPP(每像素比特)
HAL_PIXEL_FORMAT_YCbCr_420_888 0x23 预览、录像(灵活 YCbCr) 12
HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED 0x22 GPU/Display 直接消费(平台优化格式) 平台相关
HAL_PIXEL_FORMAT_BLOB 0x21 JPEG 编码输出 可变
HAL_PIXEL_FORMAT_RAW16 0x20 RAW 数据输出(DNG) 16
HAL_PIXEL_FORMAT_RAW10 0x25 RAW10 压缩格式 10
HAL_PIXEL_FORMAT_Y16 0x20363159 深度图输出 16
格式选择策略: 对于直接送显的预览流,优先使用 IMPLEMENTATION_DEFINED,让 Gralloc 选择平台最优格式(如 NV12、NV21 或 P010)。对于需要 CPU 访问的流,使用 YCbCr_420_888 保证可移植性。

3.2.4 usage —— Gralloc 使用标志

usage 是 Gralloc buffer 分配时的标志位组合,描述了 buffer 将被如何使用。常见标志:

  • GRALLOC_USAGE_SW_READ_OFTEN:CPU 频繁读取(如 JPEG 编码器)。
  • GRALLOC_USAGE_SW_WRITE_OFTEN:CPU 频繁写入(如图像处理算法)。
  • GRALLOC_USAGE_HW_TEXTURE:GPU 纹理采样(如预览显示)。
  • GRALLOC_USAGE_HW_VIDEO_ENCODER:视频编码器输入。
  • GRALLOC_USAGE_HW_CAMERA_WRITE:Camera ISP 直接写入。
  • GRALLOC_USAGE_HW_CAMERA_READ:Camera ISP 读取(用于 reprocessing)。

Framework 会根据 Stream 的用途自动组合这些标志。HAL 可以在 configure_streams() 中通过 usage 判断 buffer 的最终消费者,从而选择适当的 ISP 输出格式。

// 解析 usage 判断消费者
if (stream->usage & GRALLOC_USAGE_HW_TEXTURE) {
    // 送显:优先使用平台原生格式
    stream->format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
} else if (stream->usage & GRALLOC_USAGE_HW_VIDEO_ENCODER) {
    // 录像:使用 NV12 或 YV12
    stream->format = HAL_PIXEL_FORMAT_YCbCr_420_888;
}

3.2.5 max_buffers —— 缓冲池深度

max_buffers 是 HAL 向 Framework 返回的值,表示该 Stream 需要的最大 buffer 数量。Framework 会根据此值分配 buffer 队列深度。

  • 典型值范围: 3 ~ 8 个 buffer。
  • 预览流: 通常 3~4 个 buffer(双缓冲 + 1 个备用)。
  • 拍照流: 通常 4~6 个 buffer(考虑 JPEG 编码延迟)。
  • ZSL 流: 可能需要 6~8 个 buffer 以维持环形缓冲区。
性能陷阱: max_buffers 设置过大浪费内存,设置过小会导致 dequeue 阻塞,造成帧率下降。建议根据 pipeline 深度 + 1 计算:max_buffers = pipeline_depth + 1。

3.2.6 priv —— HAL 私有数据指针

priv 是 HAL 用于关联私有数据结构的指针。在 configure_streams() 中,HAL 可以分配自定义的 Stream 上下文(如 ISP 通道句柄、格式转换器状态),并将指针赋值给 priv。后续在 process_capture_request() 和 process_capture_result() 中,通过 camera3_stream_t * 即可取出私有数据。

// HAL 端典型的 priv 使用模式
typedef struct hal_stream_ctx {
    int isp_channel_id;
    int format_converter_handle;
    int buffer_count;
    // ... 其他私有状态
} hal_stream_ctx_t;

// configure_streams() 中初始化
for (int i = 0; i < num_streams; i++) {
    camera3_stream_t *stream = stream_list[i];
    hal_stream_ctx_t *ctx = calloc(1, sizeof(hal_stream_ctx_t));
    ctx->isp_channel_id = allocate_isp_channel(stream->width, stream->height);
    stream->priv = ctx;
}

// process_capture_request() 中使用
void process_capture_request(camera3_capture_request_t *request) {
    for (int i = 0; i < request->num_output_buffers; i++) {
        camera3_stream_buffer_t *buf = &request->output_buffers[i];
        hal_stream_ctx_t *ctx = (hal_stream_ctx_t *)buf->stream->priv;
        // 使用 ctx->isp_channel_id 配置 ISP
    }
}

3.3 Stream 配置流程与字段交互

下图展示了从 Framework 下发 Stream 配置到 HAL 处理完成的完整流程,以及各字段在流程中的角色:

Stream 配置流程与字段交互 Framework 构建 stream_type/width/height Framework 设置 format/usage/data_space HAL configure_streams() 传递配置 下发配置 HAL 校验 分辨率/格式/usage 合法性 HAL 计算 设置 max_buffers HAL 分配 初始化 priv 私有数据 进入 HAL 处理 校验通过 计算完成 配置完成 max_buffers / priv 回写至 stream 结构体 返回配置 运行时使用 process_capture_request/result 中通过 priv 访问上下文 开始数据流 ⚠ 注意 HAL 不可修改 stream_type/format/usage

3.4 字段配置避坑指南

字段 常见错误 后果 正确做法
stream_type 预览流误配为 INPUT 预览黑屏 预览/录像/拍照使用 OUTPUT
width/height 未对齐 16 像素 Gralloc 分配失败 始终检查对齐要求
format 使用不支持的格式 configure_streams 返回 -EINVAL 查询 availableStreamConfigurations
usage 遗漏 GRALLOC_USAGE_HW_CAMERA_WRITE ISP 无法写入 buffer 确保包含 Camera 写入标志
max_buffers 设置过小(如 2) 帧率下降、dequeue 阻塞 pipeline_depth + 1
priv 未初始化或重复释放 内存泄漏或野指针 crash configure_streams 分配,close 时释放
调试技巧: 在 configure_streams() 入口处打印所有 Stream 字段,使用 ALOGD 输出到 logcat。对比 Framework 期望值与 HAL 实际处理值,可以快速定位配置不匹配问题。

3.5 多 Stream 配置示例

以下是一个典型的 3 路 Stream 配置场景(预览 + 拍照 + 录像),展示各字段的实际取值:

// 典型 3 路 Stream 配置
camera3_stream_t preview_stream = {
    .stream_type = CAMERA3_STREAM_OUTPUT,
    .width = 1920,
    .height = 1080,
    .format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
    .usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE,
    .max_buffers = 4,        // HAL 回填
    .priv = NULL,            // HAL 回填
    .data_space = HAL_DATASPACE_UNKNOWN,
    .rotation = CAMERA3_STREAM_ROTATION_0,
};

camera3_stream_t snapshot_stream = {
    .stream_type = CAMERA3_STREAM_OUTPUT,
    .width = 3840,
    .height = 2160,
    .format = HAL_PIXEL_FORMAT_BLOB,   // JPEG 输出
    .usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_CAMERA_WRITE,
    .max_buffers = 6,        // JPEG 编码需要更多 buffer
    .priv = NULL,
    .data_space = HAL_DATASPACE_V0_SRGB,
    .rotation = CAMERA3_STREAM_ROTATION_0,
};

camera3_stream_t video_stream = {
    .stream_type = CAMERA3_STREAM_OUTPUT,
    .width = 1920,
    .height = 1080,
    .format = HAL_PIXEL_FORMAT_YCbCr_420_888,
    .usage = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_HW_CAMERA_WRITE,
    .max_buffers = 5,
    .priv = NULL,
    .data_space = HAL_DATASPACE_BT709,
    .rotation = CAMERA3_STREAM_ROTATION_0,
};

// 配置数组
camera3_stream_t *stream_list[3] = {
    &preview_stream,
    &snapshot_stream,
    &video_stream
};
关键观察: 预览流使用 IMPLEMENTATION_DEFINED 格式 + HW_TEXTURE usage,让平台选择最优显示格式;拍照流使用 BLOB 格式 + SW_READ_OFTEN 标志,便于 CPU 读取 JPEG 数据;录像流使用 YCbCr_420_888 格式 + HW_VIDEO_ENCODER 标志,直接喂给硬件编码器。