OpenHarmony Camera 子系统架构与预览、拍照流程分析:从 Camera Framework 到 HDI
文章由移远通信技术股份有限公司提供。
声明: 以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除。
摘要
Camera 是 OpenHarmony 多媒体子系统的重要组成部分,主要提供相机设备管理、预览、拍照、录像以及相机参数控制等功能。
从应用层来看,Camera 功能似乎只是打开摄像头、创建预览窗口和调用拍照接口。但从系统实现角度来看,一次完整的 Camera 操作需要经过应用接口、Camera Framework、Camera Service、Camera HDI、相机驱动以及底层硬件等多个层次。
在实际开发中,Camera 问题通常具有明显的分层特征。例如:应用无法枚举摄像头、Camera 设备能够打开但预览黑屏、预览正常但拍照失败、输出图像出现偏色或异常条纹等。
这些现象虽然都表现为相机功能异常,但可能发生在完全不同的处理阶段。
本文基于 OpenHarmony 公开源码和官方 Camera API 文档,梳理 Camera 子系统整体架构,分析 CameraManager、CaptureSession、Camera Service 和 Camera HDI 之间的职责关系,并重点介绍预览流、拍照流、Surface、BufferQueue 及数据格式的工作原理。
同时结合典型故障现象,建立一套从应用层到 Camera HDI 的分层问题定位方法。
关键词: OpenHarmony、Camera、Camera Framework、Camera Service、Camera HDI、CaptureSession、StreamOperator、Surface、BufferQueue、预览、拍照
目录
- Camera 子系统概述
- Camera 系统分层架构
- Camera Framework 核心模块与源码目录
- CameraManager 设备发现与打开流程
- CaptureSession 会话管理机制
- Camera 预览业务流程分析
- Camera 拍照业务流程分析
- Camera HDI 接口与底层流管理
- Surface 与 BufferQueue 数据传递机制
- 图像格式、Stride 与内存布局
- Camera 常见故障及分层定位
- Camera 典型问题分析
- Camera 调试与验证方法
- 工程设计思想与适用边界
- 总结
- 参考资料
1. Camera 子系统概述
1.1 Camera 的主要功能
OpenHarmony Camera 子系统为应用提供统一的相机访问能力。
常见业务主要包括:
| 功能 | 主要作用 | 典型应用场景 |
|---|---|---|
| Camera 设备管理 | 枚举、选择和打开相机设备 | 前后摄切换 |
| Preview | 持续采集并输出预览画面 | 相机取景 |
| Photo | 执行拍照并输出图像 | 拍摄照片 |
| Video | 连续输出录像数据 | 视频录制 |
| Focus | 控制对焦状态 | 自动对焦 |
| Exposure | 控制曝光相关参数 | 自动曝光 |
| Zoom | 控制变焦 | 远近景切换 |
| Flash | 管理闪光灯 | 暗光拍摄 |
这些功能虽然都由 Camera 系统提供,但内部依赖的流类型、设备能力和数据处理过程并不完全相同。
例如,预览需要连续获取图像数据,而拍照通常涉及独立的拍照请求及对应图像输出。
因此,Camera 不能简单理解成一个持续读取图像的函数。
1.2 Camera 的基本工作模式
从系统角度,可以将 Camera 工作过程概括为以下步骤:
- 查询系统支持的 Camera 设备;
- 获取设备能力,选择合适的 Camera;
- 创建 CameraInput;
- 创建 Session,并配置输入、输出;
- 将会话配置提交给 Camera Service;
- Camera Service 通过 Camera HDI 配置底层设备与数据流;
- 底层 Camera Pipeline 开始采集图像;
- 图像数据通过相应 Buffer 传递到目标模块;
- 应用进行预览显示、照片处理或视频录制;
- 业务结束后停止数据流并释放资源。
这里有一个需要特别注意的地方:
Camera 的控制流程与图像数据流不是同一条链路。
控制流程负责创建设备、配置会话和启动采集。
数据流负责将采集到的图像帧传递给显示、拍照或录像模块。
这两条链路相互关联,但需要分别分析。
1.3 本文的源码范围
本文主要基于以下 OpenHarmony 公共仓库:
multimedia_camera_frameworkdrivers_interfacedrivers_peripheral- OpenHarmony 官方 Camera Kit 文档
其中,Camera Framework 用于分析上层业务与 Camera Service,Camera HDI 用于理解系统与底层设备之间的标准接口。
本文不分析任何特定芯片平台的私有 Camera OEM、ISP 算法或 Sensor 寄存器配置。
需要说明:公共仓 master 分支中的接口可能随 OpenHarmony 版本演进而变化。本文着重讨论稳定的分层设计与核心调用关系,具体产品开发应以目标版本实际采用的接口为准。
2. Camera 系统分层架构
2.1 Camera 系统可以划分为哪些层次?
从整体设计来看,可以将 OpenHarmony Camera 分为以下几个层次。
| 层次 | 主要模块 | 核心职责 |
|---|---|---|
| 应用层 | Camera 应用、Camera Kit | 发起预览、拍照、录像请求 |
| Framework 层 | CameraManager、CameraInput、Session、Output | 提供统一的应用访问接口 |
| Service 层 | Camera Service | 管理设备、会话、流及资源 |
| HDI 接口层 | ICameraHost、ICameraDevice、IStreamOperator | 定义标准设备访问接口 |
| HAL/驱动层 | Camera HAL、Pipeline、设备适配 | 对接实际相机设备 |
| 硬件层 | Sensor、CSI、ISP、图像处理硬件 | 图像采集及硬件处理 |
从应用到硬件,整体逻辑可以概括为:
Camera App → Camera Framework → Camera Service → Camera HDI → Camera HAL → Camera Hardware
这里的箭头代表逻辑调用和控制关系,并非表示图像数据必须逐层复制到应用。
实际上,Camera 图像数据通常使用缓冲区及生产者—消费者机制传递,以减少不必要的数据复制和处理开销。
2.2 Camera Framework 负责什么?
Camera Framework 主要为应用提供统一的相机能力。
从应用开发角度,开发者通常不需要直接操作 Sensor 或 ISP,而是通过 CameraManager 和 Session 等对象完成:
- 相机设备查询;
- 输入设备创建;
- 预览流配置;
- 拍照流配置;
- 相机参数控制;
- 设备资源释放。
Framework 的主要意义在于屏蔽底层 Camera 设备实现差异。
应用只需要按照公开接口及设备能力进行配置,而不需要知道具体硬件通过哪一种 CSI 控制器或者 ISP 完成图像采集。
2.3 Camera Service 负责什么?
Camera Service 位于 Framework 与 HDI 之间,是 Camera 业务资源的重要管理层。
其职责包括:
- Camera 设备管理;
- 会话生命周期管理;
- 输入和输出流的组织;
- Camera 设备访问协调;
- 相机资源及冲突管理;
- 向 Camera HDI 发起设备和流操作;
- 处理来自底层的状态、数据流和错误事件。
例如,当多个应用希望同时使用相机时,不能简单地让所有应用无条件操作同一个物理设备。
Camera Service 需要根据设备能力、资源状态和系统策略处理相应请求。
2.4 Camera HDI 负责什么?
HDI 的全称是 Hardware Device Interface。
Camera HDI 提供统一的硬件设备接口,负责连接上层 Camera Service 与下层 Camera 设备实现。
Camera HDI 的关键接口主要包括:
ICameraHostICameraDeviceIStreamOperator
它们分别对应设备管理、设备控制和数据流操作等能力。
Camera Service 通过这些接口管理设备,而具体硬件操作由相应的设备实现负责。
需要注意,HDI 接口定义是公开的,并不意味着实现这些接口的所有厂商 HAL 代码都是公开的。
这一点在技术文档和源码分析中需要明确区分。
3. Camera Framework 核心模块与源码目录
3.1 Framework 源码目录
Camera Framework 的公共源码仓库为:
https://gitee.com/openharmony/multimedia_camera_framework
其主要源码位于:
foundation/multimedia/camera_framework
可以按照职责理解以下目录:
| 目录 | 主要内容 |
|---|---|
frameworks/js | JS / ArkTS 对应的接口实现 |
frameworks/native | Native Camera Framework |
interfaces | 对外及内部接口定义 |
services/camera_service | Camera Service 实现 |
sa_profile | 系统服务配置 |
test | 测试相关内容 |
不同版本可能调整部分子目录和实现文件,阅读时应以实际分支为准。
3.2 Camera Service 关键对象
在 Camera Service 中,可以重点关注以下对象:
| 对象 | 作用 |
|---|---|
HCameraService | Camera Service 的主要服务实现 |
HCameraDevice | Camera 设备对象及设备生命周期管理 |
HCameraDeviceManager | Camera 设备资源与冲突管理 |
HCaptureSession | Camera 会话及输入输出管理 |
HStreamOperator | Camera 流管理与 HDI 流操作协调 |
HStreamRepeat | 连续数据流相关实现 |
HStreamCapture | 拍照采集流相关实现 |
这些对象并不是简单的一对一调用关系。
例如,一个 CaptureSession 可能包含一个 CameraInput 和多个 CameraOutput。
Camera Service 需要将这些输出转换为底层可以处理的数据流配置,再交给 HDI 执行。
3.3 建议的源码阅读顺序
对于初次阅读 OpenHarmony Camera 源码的开发者,建议按以下顺序进行。
第一步:CameraManager
了解如何查询相机设备、创建输入和输出对象。
第二步:CaptureSession
了解 Camera 如何配置输入、输出和工作模式。
第三步:Camera Service
了解会话配置如何转换为服务端操作。
第四步:Camera HDI
了解设备如何被打开,以及流如何创建、提交和启动。
第五步:Surface 与 Buffer
理解图像数据最终如何到达预览窗口、拍照处理模块或录像模块。
这种从应用入口向下跟踪的方法,比直接从 Sensor 驱动开始阅读更容易建立完整的 Camera 业务模型。
4. CameraManager 设备发现与打开流程
4.1 获取 CameraManager
在 OpenHarmony Camera Kit 中,CameraManager 是应用管理 Camera 设备的重要入口。
ArkTS 应用可以通过 Camera Kit 获取相机管理对象,并查询系统支持的设备。
其主要流程为:
获取 CameraManager → 获取设备列表 → 查询设备能力 → 创建 CameraInput
例如,应用调用:
let cameras = cameraManager.getSupportedCameras();
该接口用于获取系统支持的相机设备对象。
需要注意,返回的设备列表可能为空。
此时不能直接访问 cameras[0],而应该先检查列表长度。
4.2 创建 CameraInput
得到设备对象之后,可以通过:
let cameraInput = cameraManager.createCameraInput(cameras[0]);
创建相机输入对象。
实际应用还需要处理权限、设备状态和异常返回。
OpenHarmony Camera Kit 对相关接口规定了相机权限要求:
ohos.permission.CAMERA
没有相应权限、设备不可用或 Camera Service 出现异常时,创建 CameraInput 可能失败。
4.3 设备创建成功代表什么?
这里需要区分三个阶段。
| 阶段 | 表示什么 | 不能说明什么 |
|---|---|---|
| 获取设备列表成功 | 系统能够报告相机设备信息 | 物理设备已打开 |
| 创建 CameraInput 成功 | 应用建立相机输入对象 | 预览已经正常输出 |
| Session 启动成功 | 相机会话启动请求完成 | 显示端已经收到有效首帧 |
因此,应用能够枚举到 Camera 设备,并不能证明整个预览链路正常。
如果后续发生黑屏,还需要继续检查 Session、HDI、Buffer 和显示端。
4.4 设备能力查询的重要性
Camera 设备不一定支持所有分辨率和图像格式。
例如,有的设备支持特定尺寸的预览流,但不支持相同尺寸的 JPEG 拍照流。
所以,应用在创建输出流之前,应查询设备支持的输出能力,并从支持列表中选择相应的 Profile。
典型需要关注:
- 输出图像格式;
- 预览分辨率;
- 拍照分辨率;
- 录像分辨率;
- 帧率范围;
- 特定场景模式支持情况。
不能仅仅因为 Sensor 硬件支持某种分辨率,就认定 Camera Framework 一定可以直接输出该分辨率。
5. CaptureSession 会话管理机制
5.1 CaptureSession 是什么?
CaptureSession 是 Camera Framework 中的重要概念。
它负责组织一次 Camera 业务所需要的输入和输出资源,并管理这些资源的配置与运行状态。
可以将它理解为相机业务的运行上下文。
例如,一个典型的拍照会话可能包含:
- 一个 CameraInput;
- 一个 PreviewOutput;
- 一个 PhotoOutput。
CameraInput 指定从哪个相机设备采集数据。
PreviewOutput 指定预览画面输出到哪里。
PhotoOutput 指定拍照图像如何输出。
Camera 会话将这些资源组合在一起,形成一次完整的相机业务。
5.2 为什么需要 BeginConfig 和 CommitConfig?
在 OpenHarmony 公开的 Native Camera 示例中,可以看到一条典型的会话配置顺序:
captureSession->BeginConfig();
captureSession->AddInput(cameraInput);
captureSession->AddOutput(previewOutput);
captureSession->CommitConfig();
以上是从公开示例中提取的关键调用,用于说明配置顺序,省略了实际的对象创建、参数检查和返回值处理。
这里需要区分两个动作。
BeginConfig:
开始对会话进行配置或重新配置。
CommitConfig:
提交当前会话的配置,使相关输入输出关系进入后续配置处理阶段。
如果将 CameraInput、PreviewOutput 和 PhotoOutput 看作会话中的资源,那么 BeginConfig 与 CommitConfig 就提供了配置这些资源的明确边界。
5.3 CommitConfig 为什么重要?
提交配置并不只是简单地保存几个参数。
从整体架构来看,Camera Service 需要根据当前会话中包含的输入和输出,检查设备能力与配置关系,并进一步组织底层流。
在 Camera HDI 层,相关流程会涉及流创建和流配置操作。
因此,CommitConfig 是连接 Framework 会话配置与底层流管理的重要阶段。
如果输出分辨率、格式或流组合不符合设备实际支持的能力,就可能在配置阶段出现错误。
5.4 Session 配置与 Session 启动的区别
两者并不是同一个动作。
Session 配置:
决定本次 Camera 业务使用哪个输入设备,以及需要哪些输出流。
Session 启动:
请求已配置的会话进入工作状态,开始执行相应的图像采集和输出流程。
例如,在公开 Native Camera 示例中,配置提交后会调用:
captureSession->Start();
而应用结束业务时,可以调用:
captureSession->Stop();
不同 API 版本下,接口是否同步、异步以及具体返回类型可能不同,需要参考对应接口文档。
5.5 新旧 API 的区别
在早期公开示例中,常见:
CreateCaptureSession()
在较新的 Camera Kit API 中,推荐通过带场景模式的 createSession() 创建对应 Session。
例如:
- PhotoSession
- VideoSession
Camera Kit 中旧的 createCaptureSession() 接口从 API version 11 开始被标记为废弃。
本文保留早期 Native 示例的函数名称,是为了说明 Camera 配置与启动的核心思想,不建议在新项目中不加区分地照搬旧接口。
6. Camera 预览业务流程分析
6.1 Preview 的基本特点
Camera Preview 用于持续输出图像帧。
它与拍照的最大区别在于:
预览通常需要连续、稳定地输出图像;拍照则重点关注一次采集请求及其输出结果。
预览链路需要同时满足:
- Camera 设备能够正常工作;
- 预览流配置有效;
- 图像帧能够持续产生;
- 输出 Buffer 能够正常传递;
- 显示端能够正确消费并显示 Buffer。
其中任何一个环节出现问题,都可能导致预览黑屏、卡顿或者图像异常。
6.2 PreviewOutput 的作用
在 Camera Kit 中,PreviewOutput 用于指定预览输出。
创建预览流时,需要提供与设备能力匹配的 Profile,以及对应的 Surface 标识。
例如,公开 CameraManager API 中提供:
createPreviewOutput(profile, surfaceId)
其中:
profile 描述预览流支持的分辨率和图像格式等信息。
surfaceId 标识预览数据输出到哪个 Surface。
这个设计使 Camera 数据采集和应用界面显示能够通过 Surface 建立关联。
6.3 预览启动的典型流程
从应用层看,可以归纳为:
- 获取 CameraManager;
- 枚举 Camera 设备;
- 创建 CameraInput;
- 查询可用预览输出能力;
- 创建 PreviewOutput;
- 创建 Session;
- 添加 CameraInput;
- 添加 PreviewOutput;
- 提交 Session 配置;
- 启动 Session。
从服务层看,Camera Service 需要进一步协调设备和数据流配置。
从 HDI 层看,需要完成相关流的创建、提交与采集控制。
需要特别注意,这些步骤包含异步操作和状态管理,并非保证所有操作完成之后,显示端立即出现第一帧。
6.4 Preview 正常运行需要哪些条件?
可以将预览成功划分为五个阶段。
| 阶段 | 需要确认的内容 |
|---|---|
| 设备阶段 | Camera 能否枚举、打开 |
| 会话阶段 | Session 能否配置、提交、启动 |
| HDI 阶段 | 预览流能否建立并产生帧 |
| Buffer 阶段 | 图像 Buffer 能否正常传递 |
| 显示阶段 | 图形系统能否正确显示预览帧 |
这套分类对于预览黑屏问题非常重要。
如果底层 Camera 已经持续产生有效图像帧,但屏幕仍然黑屏,就不能仅凭显示结果认定 Sensor 没有工作。
此时应重点分析 Buffer 数据传递及显示端。
7. Camera 拍照业务流程分析
7.1 PhotoOutput 的基本作用
PhotoOutput 用于管理 Camera 的拍照输出。
在典型 Camera 应用中,通常会同时创建 PreviewOutput 和 PhotoOutput。
用户可以通过 PreviewOutput 查看实时取景画面,通过 PhotoOutput 发起拍照操作并获得图像结果。
二者虽然可以共用 CameraInput,但在底层可能对应不同类型的流及处理过程。
7.2 拍照与预览的区别
| 对比项目 | Preview | Photo |
|---|---|---|
| 主要目的 | 实时取景 | 获取照片 |
| 输出特点 | 连续帧 | 按拍照请求输出 |
| 主要对象 | PreviewOutput | PhotoOutput |
| 常见格式 | YUV 等预览格式 | JPEG 或其他支持格式 |
| 关注重点 | 帧率、延迟、稳定性 | 图像质量、拍照成功率 |
| 典型异常 | 黑屏、卡顿、花屏 | 拍照失败、图片损坏、结果异常 |
需要注意,不同 Camera 硬件及框架版本可能采用不同的流组合和处理方式,不能假定所有设备都具有完全一致的拍照 Pipeline。
7.3 拍照请求如何发起?
在公开 Native Camera 示例中,PhotoOutput 通过 Capture() 发起拍照操作。
其业务含义可以理解为:
用户触发拍照 → PhotoOutput 接收请求 → Camera Service 处理请求 → HDI 执行采集 → 输出图像
对于 HDI 层而言,相关能力由 IStreamOperator 的 Capture 接口提供。
但拍照请求返回成功,不一定意味着最终照片已经完成保存。
需要区分:
- 拍照请求被接受;
- 硬件完成采集;
- 拍照数据生成;
- 图像数据送达上层;
- 应用完成照片处理或保存。
这几个阶段可以发生在不同的时间点。
7.4 为什么预览正常,拍照却可能失败?
因为预览和拍照虽然共享 Camera 设备,但输出链路不完全相同。
可能原因包括:
- PhotoOutput 的 Profile 不被设备支持;
- 预览流与拍照流的组合超出设备能力;
- Session 配置或提交失败;
- 拍照请求参数异常;
- 底层拍照流创建失败;
- 图像编码或输出环节出现异常;
- 应用没有正确处理拍照结果。
所以,当预览正常而拍照失败时,不应该立即把问题定位到 Sensor 或整个 Camera 驱动。
更合理的办法是先检查 PhotoOutput、Session 配置和 HDI 拍照流的结果。
7.5 预览正常具有哪些诊断价值?
如果预览持续正常,通常说明至少存在一条可工作的设备采集与预览输出链路。
但不能由此直接证明:
- 所有相机输出格式均正常;
- 所有分辨率均得到支持;
- JPEG 输出链路正常;
- 拍照流能够成功建立;
- 图像保存逻辑没有问题。
因此,预览正常可以帮助缩小问题范围,却不能替代对拍照链路的独立验证。
8. Camera HDI 接口与底层流管理
8.1 Camera HDI 的基本架构
Camera HDI 提供标准化的设备控制和数据流管理接口。
公共驱动框架对上实现 Camera HDI,对下通过设备适配层连接实际 Camera 硬件。
从职责上主要可以分为:
- HDI 接口实现;
- Camera 流与设备管理框架;
- 设备平台适配。
这有助于不同硬件平台通过统一的设备接口接入 OpenHarmony Camera Service。
8.2 ICameraHost:相机设备管理
ICameraHost 主要负责相机设备管理。
在公开 Camera HDI 接口中,可以找到:
| 接口 | 主要作用 |
|---|---|
GetCameraIds() | 获取 Camera 设备 ID |
GetCameraAbility() | 查询指定 Camera 的能力 |
OpenCamera() | 打开指定 Camera 设备 |
SetCallback() | 设置 Camera Host 回调 |
其中,GetCameraAbility 的作用特别重要。
它描述设备能够提供的能力信息。
上层配置分辨率、流组合和相关控制参数时,应当结合设备实际上报的能力,而不能假定设备支持任意参数。
8.3 ICameraDevice:设备控制
通过 CameraHost 打开设备后,上层可以取得相应的 CameraDevice 对象。
CameraDevice 主要负责具体设备的访问和控制。
例如,相关 HDI 接口提供获取 StreamOperator 的能力。
其设计思想是将 Camera 设备控制与流操作进一步拆分。
这样,设备管理不需要把所有流创建、采集和释放操作集中到同一个接口中。
8.4 IStreamOperator:数据流管理
IStreamOperator 是 Camera HDI 中非常重要的接口。
在公开 HDI 接口中,可以找到:
| 接口 | 作用 |
|---|---|
IsStreamsSupported() | 查询流配置是否受支持 |
CreateStreams() | 创建流 |
CommitStreams() | 提交流配置 |
Capture() | 发起图像采集 |
CancelCapture() | 取消指定采集 |
ReleaseStreams() | 释放流 |
这些接口构成了 Camera HDI 数据流控制的重要基础。
8.5 CreateStreams 与 CommitStreams 有什么区别?
CreateStreams:
主要用于根据 StreamInfo 等信息创建对应的流对象。
CommitStreams:
主要用于提交已经创建的流配置,使底层设备按照相应参数组织和配置采集流程。
这两个接口虽然连续出现,但解决的问题并不相同。
可以理解为:
创建流对象 → 配置并提交流 → 执行采集
其中,创建流成功不一定代表完整的流配置已经生效。
8.6 Capture 为什么有 isStreaming 参数?
在公开 Camera HDI 接口中,Capture 带有采集相关参数,其中包括 isStreaming。
这个参数用于区分连续采集和非连续采集的行为。
在典型 Camera 工作模型中:
- 预览、录像需要持续产生图像帧;
- 拍照通常由特定的采集请求触发。
因此,Camera HDI 需要能够表示不同的数据采集模式。
这里不能简单认为所有预览、录像和拍照最终采用完全相同的流处理策略。
实际实现应根据流类型、设备能力和目标 OpenHarmony 版本判断。
8.7 为什么不能随意下发超出能力范围的参数?
Camera HDI 中的流配置需要与设备上报的能力相匹配。
如果上层尝试使用设备不支持的分辨率、帧率或流组合,就可能产生异常。
需要特别注意:
接口调用返回成功,不一定能证明设备能够稳定输出符合预期的图像数据。
实际验证还需要检查:
- 是否成功创建并提交流;
- 是否有持续帧输出;
- 输出分辨率是否符合预期;
- Buffer 布局是否正确;
- 采集与停止是否正常。
这也是 Camera 功能测试不能仅检查接口返回值的重要原因。
9. Surface 与 BufferQueue 数据传递机制
9.1 为什么 Camera 使用 BufferQueue?
Camera Preview 需要持续处理大量图像数据。
例如,在 1920×1080、30 FPS 的典型 YUV420 图像场景中,即使不考虑对齐和额外元数据,原始图像吞吐量也已经相当可观。
如果每一帧都经过多次大块内存复制,会增加内存带宽消耗和处理延迟。
因此,Camera 系统通常通过共享 Buffer 和生产者—消费者机制,尽量减少不必要的数据复制。
9.2 BufferQueue 的基本概念
BufferQueue 可以理解为连接生产者和消费者的缓冲区管理机制。
Producer(生产者):
负责申请、填充和提交图像 Buffer。
Consumer(消费者):
负责获取、处理和释放已经提交的 Buffer。
在预览链路中,可以将 Camera 输出端视为图像生产方,将预览显示或相关图像处理模块视为消费方。
具体的 Buffer 生产、消费实现以及对象传递方式由对应图形与媒体框架决定。
9.3 Buffer 生命周期
一个典型的 Buffer 使用过程包含:
- 生产者申请或获取可用 Buffer;
- 生产者向 Buffer 写入图像数据;
- 生产者提交完成的 Buffer;
- 消费者取得可读取的 Buffer;
- 消费者使用图像数据;
- 消费者释放 Buffer;
- Buffer 重新进入可复用状态。
需要注意,Buffer 的状态变化必须符合相应的同步和所有权约束。
如果生产者还没有完成写入,消费者就开始读取,就可能出现不完整图像。
如果消费者尚未完成使用,Buffer 就被错误覆盖,也可能产生图像异常。
9.4 Surface 的作用
Surface 为图像数据生产和消费提供相应接口及缓冲区关联能力。
在 Camera 场景中:
- PreviewOutput 可以关联用于预览显示的 Surface;
- PhotoOutput 可以向指定的图像处理或接收路径输出结果;
- VideoOutput 可以关联录像组件使用的 Surface。
因此,Surface 并不等同于屏幕画面本身。
它更重要的作用是组织图像 Buffer 在不同模块之间的传递。
9.5 Buffer 同步为什么重要?
现代 Camera 数据链路通常涉及多个硬件或软件执行单元。
例如:
- Camera ISP;
- DMA;
- CPU;
- GPU;
- 显示硬件。
这些执行单元对 Buffer 的访问可能存在时间顺序和缓存一致性要求。
因此,分析图像异常时,需要关注:
- Buffer 是否已经完成写入;
- 读取前是否完成必要的同步;
- Buffer 是否被过早复用;
- CPU 缓存是否按对应内存机制正确维护;
- 生产者和消费者是否使用一致的布局信息。
这里不能将所有图像异常都归结为缓存同步问题。
更合理的方法是先确定异常出现在哪一个数据处理阶段。
10. 图像格式、Stride 与内存布局
10.1 Camera 图像格式为什么重要?
Camera 图像采集后,底层可能使用不同的像素格式进行处理和输出。
常见格式包括:
| 格式 | 主要特点 | 常见用途 |
|---|---|---|
| RAW10 | 10-bit RAW 像素数据 | Sensor 原始图像传输 |
| NV12 | YUV420 半平面,UV 顺序 | 视频和图像处理 |
| NV21 | YUV420 半平面,VU 顺序 | 预览和图像处理 |
| YUV420P | Y、U、V 分离平面 | 编码或软件处理 |
| RGBA8888 | RGB 加 Alpha 通道 | 图形显示、合成 |
| JPEG | 压缩图像格式 | 拍照结果 |
这些格式的内存布局不同。
即使两张图片分辨率相同,也不能认为它们可以使用相同的方式直接解析。
10.2 NV12 与 NV21 的区别
NV12 和 NV21 都属于 YUV420 半平面格式。
它们的 Y 平面布局类似,主要区别在于 UV 数据的存储顺序。
NV12:
UVUVUV……
NV21:
VUVUVU……
如果实际 Buffer 是 NV21,但处理模块按 NV12 解析,图像就可能产生明显的颜色异常。
因此,当预览画面有正常轮廓,但颜色明显不对时,应检查实际 Buffer 格式与消费端的格式解释是否一致。
10.3 什么是 Stride?
Stride 通常表示图像缓冲区中相邻两行对应存储起点之间的字节跨度。
它不一定等于图像的可见宽度。
例如,对于某些格式和硬件要求,Buffer 每一行可能包含额外的对齐或填充空间。
因此,需要区分:
width:图像有效宽度;height:图像有效高度;stride:行存储跨度;alignedHeight:满足对齐规则后的分配高度;offset:平面或数据在 Buffer 中的起始位置。
这些参数不能任意相互替换。
10.4 为什么内存布局不一致会出现绿条或花屏?
以典型的双平面 YUV420 图像为例,Y 平面和 UV 平面可能使用不同的地址偏移和对齐规则。
如果生产者按照一种布局写入,消费者按照另一种布局读取,就可能发生:
- UV 地址偏移;
- 图像边缘出现异常颜色;
- 部分区域花屏;
- 图像发生错行;
- 裁剪或旋转后异常更加明显。
因此,遇到偏色或花屏时,需要确认:
- Buffer 的真实格式;
- 各平面的起始位置;
- 行 Stride;
- 实际分配高度;
- 裁剪区域;
- 消费端使用的布局参数。
不能仅仅根据图片宽高计算所有平面的地址。
10.5 为什么旋转后可能更容易暴露布局问题?
当图像需要旋转、裁剪或者缩放时,处理模块通常需要根据图像布局重新计算像素位置。
如果输入 Buffer 的行跨度或平面偏移不正确,经过变换后,原本位于特定行列的错误数据可能映射到图像边缘或其他区域。
所以,当图像只有在旋转、缩放或者特定分辨率下异常时,需要重点检查图像布局和处理参数的一致性。
但仍应先通过中间数据对比确定异常发生的位置,再判断是否属于格式或对齐问题。
11. Camera 常见故障及分层定位
Camera 问题涉及的模块比较多,建议根据故障现象进行分类。
| 故障现象 | 优先检查层次 | 重点排查内容 |
|---|---|---|
| Camera 设备列表为空 | Camera Service / HDI | 设备枚举、Host、Ability |
| Camera 无法打开 | Camera Service / HDI | 权限、资源冲突、OpenCamera |
| Session 配置失败 | Framework / Service | 输入输出、Profile、流组合 |
| 预览一直黑屏 | Service / HDI / Buffer | 采集是否启动、帧是否输出 |
| 首帧黑屏 | Buffer / 显示 | 首帧状态、同步、初始化 |
| 预览卡顿 | Pipeline / Buffer | 帧率、耗时、队列阻塞 |
| 画面偏色 | 图像处理 | NV12/NV21、色彩格式 |
| 画面花屏或绿条 | Buffer 布局 | Stride、Offset、对齐 |
| 预览正常但拍照失败 | PhotoOutput / HDI | 拍照流、请求、结果处理 |
| 录像失败 | VideoOutput / Recorder | 输出流、编码器、录制资源 |
11.1 故障一:Camera 设备列表为空
首先检查 CameraManager 是否能够查询到相机设备。
如果应用无法获取任何 Camera 设备,需要进一步确认:
- Camera Service 是否正常;
- Camera HDI 服务是否可用;
- CameraHost 是否能够报告设备;
- 设备能力查询是否成功;
- 目标产品是否包含 Camera 相关组件。
对于这种问题,不建议优先分析图像颜色或者预览 Buffer。
因为系统还没有成功建立完整的设备访问基础。
11.2 故障二:Camera 能打开,但预览黑屏
建议把问题划分为以下三个阶段。
第一阶段:控制链路
检查 CameraInput、Session、PreviewOutput 是否正确创建,Session 是否成功提交和启动。
第二阶段:数据生产链路
检查 Camera HDI 是否启动相应采集,底层是否产生有效图像数据。
第三阶段:数据消费链路
检查 Buffer 是否正确传递给预览端,以及显示模块是否能够正确读取和显示数据。
如果第二阶段能够持续获得有效帧,而第三阶段无法显示,问题就更可能集中在数据传递、图像格式或显示端。
11.3 故障三:预览出现明显偏色
优先确认输出图像格式。
尤其需要区分:
- NV12 与 NV21;
- YUV420SP 与 YUV420P;
- YUV 与 RGB;
- 色彩空间和图像处理参数。
如果实际格式与处理模块预期不一致,即使图像采集本身正常,也可能出现明显颜色异常。
11.4 故障四:Camera 预览卡顿
卡顿问题应先确认是采集端帧率下降,还是消费端处理过慢。
例如:
- Sensor 输出帧率是否符合预期;
- ISP 或图像处理是否耗时增加;
- BufferQueue 是否出现积压;
- 显示端是否无法及时消费图像;
- CPU/GPU 负载是否过高。
不应该仅根据屏幕显示帧率较低,就直接认定 Camera Sensor 输出帧率不足。
11.5 故障五:预览正常但拍照失败
这种情况下,建议优先排查:
- PhotoOutput 是否正确创建;
- 拍照 Profile 是否受支持;
- PhotoOutput 是否成功加入 Session;
- Session 是否正确提交和运行;
- HDI 拍照流是否成功创建;
- 拍照请求是否执行;
- 图像结果是否正常返回;
- 上层是否正确处理和保存图像。
如果预览流正常,但拍照流无法建立,就应重点关注两种流之间的能力差异,而不是反复修改已经正常工作的预览流程。
12. Camera 典型问题分析
12.1 场景:预览黑屏,但 Camera 设备可以正常打开
本节使用通用故障模型说明排查过程,不代表某款产品的实际故障日志。
假设应用能够枚举相机设备,也能够创建 CameraInput,但打开预览页面后始终显示黑屏。
此时最容易出现的误判是:
“相机硬件坏了”或者“Sensor 没有图像输出”。
实际上,仅凭屏幕黑屏,还无法直接得出这个结论。
12.2 第一步:确认应用请求是否正确
先检查:
- CameraInput 是否创建成功;
- PreviewOutput 是否创建成功;
- Session 是否成功添加输入和输出;
- CommitConfig 是否成功;
- Session Start 是否返回错误。
如果配置阶段已经失败,就应优先解决配置错误。
12.3 第二步:确认 HDI 是否建立预览流
检查底层是否成功执行相应流操作。
重点关注:
- CreateStreams;
- CommitStreams;
- Capture;
- 流错误回调;
- 设备状态变化。
如果 Camera Service 没有成功配置底层流,应进一步检查输出能力和 HDI 接口返回结果。
12.4 第三步:检查是否产生实际图像帧
当流创建和启动成功后,需要确认底层是否真正产生有效帧。
建议围绕采集启动、帧完成事件和 Buffer 提交建立时间顺序。
如果设备端没有输出帧,应重点检查 Camera 采集与数据生产路径。
如果设备端已经输出有效帧,则继续查看 Buffer 传递链路。
12.5 第四步:检查 Surface 与 Buffer
需要确认:
- Surface 是否正确关联;
- Buffer 是否成功提交;
- 消费端是否获取到 Buffer;
- Buffer 格式和布局是否符合预期;
- 是否存在未完成同步或资源释放问题。
此时最有价值的证据,是能够明确判断图像在哪一个处理阶段开始出现异常。
例如,如果底层输出帧完整,但预览显示数据异常,就应优先分析两者之间的数据处理过程。
12.6 第五步:检查显示端
如果预览消费者已经拿到正确 Buffer,但画面仍然没有正常显示,则需要继续检查:
- 显示 Surface 状态;
- 图像格式支持;
- Buffer 导入;
- 裁剪与变换;
- 显示窗口状态。
这一阶段的分析重点已经不再是 Camera 设备枚举,而是数据消费和显示。
12.7 分析结论
通过上述方法,可以逐层判断:
| 检查结果 | 主要判断方向 |
|---|---|
| CameraInput 创建失败 | 设备或权限问题 |
| Session 配置失败 | 参数与流组合问题 |
| HDI 流创建失败 | 流配置或设备能力问题 |
| 流启动成功但无帧 | 采集或数据生产链路问题 |
| 已有有效帧但预览黑屏 | Buffer 或显示链路问题 |
| Buffer 数据本身异常 | 更早的数据生产或处理阶段问题 |
最重要的原则是:先确认异常出现的位置,再分析具体原因。
这比只根据屏幕现象直接修改底层驱动更加可靠。
13. Camera 调试与验证方法
13.1 Camera 测试不能只检查返回值
在 Camera 开发中,一个接口返回成功,通常只能够说明对应阶段的调用已经按接口约定完成。
但对于最终用户,Camera 功能成功还意味着:
- 能够看到正确预览画面;
- 帧率和流畅度满足需求;
- 拍照能够输出有效图像;
- 停止和重新打开 Camera 正常;
- 反复切换摄像头不会出现资源异常。
因此,需要同时关注控制链路和数据链路。
13.2 推荐的验证项目
| 测试项目 | 检查内容 |
|---|---|
| 设备枚举 | Camera 列表及设备能力 |
| 设备打开 | 打开和关闭是否正常 |
| 预览启动 | 首帧能否显示 |
| 持续预览 | 是否稳定、有无异常帧 |
| 拍照 | 输出图片是否有效 |
| 前后摄切换 | 资源是否正常切换 |
| 分辨率切换 | Profile 与实际输出是否一致 |
| 重复开关 | 是否出现资源释放异常 |
| 异常恢复 | 设备或服务异常后的处理情况 |
13.3 Camera 常见错误码
OpenHarmony Camera Kit 定义了相关错误码,能够辅助判断错误发生的位置。
| 错误码 | 主要含义 | 排查方向 |
|---|---|---|
7400101 | 参数错误 | 检查 Profile、参数和设备能力 |
7400102 | 不允许操作 | 检查调用顺序及状态 |
7400103 | Session 未配置 | 检查会话配置 |
7400104 | Session 未运行 | 检查是否已启动会话 |
7400105 | Session 配置被锁定 | 检查配置操作状态 |
7400107 | Camera 资源冲突 | 检查其他相机占用 |
7400109 | Camera 被抢占 | 检查资源竞争 |
7400201 | Camera Service 异常 | 检查服务和跨进程通信 |
需要注意,各个接口能够返回的错误码不完全相同,具体应以目标 API 文档和系统版本为准。
13.4 如何建立有效的 Camera 调试日志?
建议围绕以下关键阶段记录日志:
设备阶段:
Camera 设备列表、设备打开结果、能力查询结果。
会话阶段:
Session 创建、添加 Input、添加 Output、CommitConfig、Start。
HDI 阶段:
OpenCamera、CreateStreams、CommitStreams、Capture 以及相应错误回调。
数据阶段:
图像帧完成事件、Buffer 获取与提交状态、时间戳、格式和布局信息。
显示阶段:
消费端是否获取到有效 Buffer,是否完成正常显示。
记录日志时,建议优先保留:
- 关键函数;
- 对应错误码;
- 时间戳;
- 流 ID;
- 帧序号;
- 图像格式;
- 分辨率与必要的布局信息。
但不应为了打印日志而对每一帧执行大量同步输出,否则可能影响 Camera 数据链路的实时性能。
13.5 分层定位为什么比盲目修改更有效?
因为 Camera 是一个跨多个执行层次的数据处理系统。
同一个显示异常可能来源于:
- 上层参数配置错误;
- Camera Service 资源问题;
- HDI 流管理异常;
- 底层采集异常;
- Buffer 数据格式错误;
- 显示端处理异常。
如果没有证据证明错误发生在哪一层,就直接修改底层实现,既可能无法解决问题,也可能引入新的兼容性问题。
采用分层验证,可以将一个范围很大的 Camera 问题逐步缩小到具体模块或处理阶段。
14. 工程设计思想与适用边界
14.1 Camera Framework 的设计核心是分层解耦
Camera 应用通过统一 Framework 接口操作设备。
Camera Service 负责统一资源管理。
HDI 定义标准设备接口。
HAL 与驱动负责适配具体硬件。
这种设计使上层应用能够在一定程度上复用同一套 Camera 接口,而不需要依赖具体芯片实现。
14.2 Camera 会话与数据流分离
Session 负责组织业务配置和运行状态。
底层 StreamOperator 负责数据流创建和采集操作。
这使 Camera 系统能够根据不同应用需求组织预览、拍照和录像等输出。
也意味着:同一个 Camera 设备可用,并不代表所有流组合都能够正常运行。
14.3 Camera 数据处理需要关注生产者与消费者
对于持续输出的图像帧,仅仅让采集端正确生成数据是不够的。
消费者还需要按照正确的同步关系、内存布局和格式要求处理图像。
因此,BufferQueue、Surface 和图像格式在 Camera 调试中具有重要作用。
14.4 不同平台的 Camera HDI 实现可能不同
OpenHarmony 统一规定了 Camera HDI 接口,但不同 SoC 的底层硬件能力可能存在差异。
例如:
- Sensor 接口及数据格式不同;
- CSI 和 ISP 资源不同;
- DMA 及内存分配机制不同;
- 图像格式和对齐约束不同;
- Camera Pipeline 的处理能力不同;
- 厂商 HAL 的实现方式不同。
因此,本文中的架构分析可以作为理解 Camera 的基础,但不能代替具体平台的公开适配文档和实际验证。
14.5 关于源码公开与版权
本文所涉及的关键 Camera Framework、HDI 接口及技术术语,均以 OpenHarmony 公共仓库和公开技术文档为基础。
对于不同厂商实现的 Camera HAL、ISP 算法、Sensor 驱动、调校参数以及内部适配代码,不应因为它们位于 OpenHarmony 工程中就默认可以公开。
公开技术文章应当:
- 仅引用确认允许公开的源码;
- 标明必要的源码出处;
- 遵循原代码的开源许可证;
- 不公开厂商或客户的私有补丁;
- 不公开未经允许的调试日志与产品配置;
- 不将通用分析描述为特定客户项目的实际测试结果。
15. 总结
本文从 OpenHarmony Camera 子系统架构出发,对 CameraManager、CaptureSession、Camera Service、Camera HDI、PreviewOutput、PhotoOutput、Surface 和 BufferQueue 等核心模块进行了梳理。
通过分析可以归纳出以下几个结论。
第一,Camera Framework 负责对应用提供统一的相机访问能力。
应用通过 CameraManager 获取设备信息,并通过 Session 配置相机输入和输出资源。
第二,CaptureSession 是 Camera 业务的重要组织单元。
输入、输出以及具体场景模式需要按照有效的配置流程组织和提交。
第三,Camera Service 是连接上层业务与 Camera HDI 的重要管理层。
它负责协调设备、会话及数据流,并对接底层 Camera HDI 接口。
第四,Camera HDI 通过 ICameraHost、ICameraDevice 和 IStreamOperator 等接口定义设备及流操作能力。
具体硬件实现由对应的平台设备适配层完成。
第五,预览与拍照虽然共用 Camera 设备,但具有不同的数据流特点。
预览需要连续输出图像,拍照则涉及相应的采集请求和结果处理,因此不能因为预览正常就直接认为拍照一定正常。
第六,Surface、BufferQueue 以及图像内存布局是 Camera 数据通路中不可忽视的部分。
Buffer 的生产、消费、格式解释及同步状态都可能影响最终的显示或拍照结果。
在工程排查中,建议按照:
设备枚举 → CameraInput → Session 配置 → HDI 流创建 → 图像采集 → Buffer 传递 → 图像处理与显示
的顺序逐层分析。
这种方法能够帮助开发者区分控制链路与数据链路问题,减少不必要的调试和错误修改。
希望本文能够为 OpenHarmony Camera 子系统的源码阅读、预览与拍照业务理解以及系统问题定位提供参考。
16. 参考资料
本文主要参考以下公开源码仓库和官方文档。
[1] OpenHarmony Camera Framework 公共仓库
https://gitee.com/openharmony/multimedia_camera_framework
[2] OpenHarmony Camera Framework GitHub 镜像
https://github.com/openharmony/multimedia_camera_framework
[3] OpenHarmony Camera HDI 接口仓库
https://gitee.com/openharmony/drivers_interface
[4] OpenHarmony 外设驱动框架仓库
https://gitee.com/openharmony/drivers_peripheral
[5] OpenHarmony CameraManager API 文档
https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-camera-kit/arkts-apis-camera-CameraManager.md
[6] OpenHarmony PhotoSession API 文档
https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-camera-kit/arkts-apis-camera-PhotoSession.md
[7] OpenHarmony Camera C API 及错误码说明
https://github.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-camera-kit/capi-camera-h.md
[8] OpenHarmony 标准系统 Camera 驱动移植参考
https://github.com/openharmony/docs/blob/master/zh-cn/device-dev/porting/porting-dayu200-on_standard-demo.md
版权与说明:
本文基于 OpenHarmony 公共仓库及公开 API 文档整理,涉及的源码版权归原版权持有人所有,并遵循对应的开源许可证。
文中部分流程为基于公开架构归纳的逻辑说明,代码片段主要用于解释接口调用关系,不代表可直接编译运行的完整项目。
本文未引用具体客户项目的 Camera 私有 HAL、ISP tuning、Sensor 寄存器配置或内部驱动修改。不同 OpenHarmony 版本及产品的实际行为,请以对应版本源码和测试结果为准。
更多推荐



所有评论(0)