openharmony摄像头驱动到应用浏览显示 第2章 驱动与硬件基线
本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第2章 驱动与硬件基线
代码基线:~/work/ohos5.1/nt_backclip_vendor(基于 OpenHarmony 5.1.0 Release 移植)
发布说明:使用ai codex移植调试开源鸿蒙摄像头的任务全流程在链接https://github.com/chenlong3388/codex_fix_OpenHarmony_Camera
第2章 驱动与硬件基线
本章只解决一个问题
为什么“驱动能跑、App却不稳”在相机联调里这么常见,以及如何把 DTS -> defconfig -> 驱动 -> HCS -> IQ 这 5 段链路串成一条可验证、可回滚、可复用的硬件准备主线。
先给你一个整体心智图
你可以把本章理解为在做一件事:
- 给相机系统做“入场体检”。
- 体检不是只看单项指标,而是看“跨层是否一致”。
- 只要有一层和其他层语义不一致,就会在上层表现为随机黑屏、参数非法、偏色、方向错乱等“看似应用问题”。
三句流程版
DTS + defconfig决定“硬件是否可被发现、驱动是否可被加载、sensor 是否可被真正启流”。HCS把“逻辑相机ID + 物理相机映射 + 流能力描述”注入 metadata,决定上层最终看到的 profile 集。IQ决定 ISP 的画质与 3A 行为基线;因此硬件链打通不代表画面正确,能力链正确也不代表颜色正确。
主链流程图(硬件到应用可见能力)
[S1 DTS 节点定义]
ov5648@36/status/facing/endpoint/data-lanes
说明: 定义“有没有这颗 sensor、接到哪条 CSI、朝向是什么”
->
[S2 内核配置开关]
nt_backclip_vendor_oh_defconfig: CONFIG_VIDEO_OV5648=m
说明: 决定 ov5648 驱动是否参与构建
->
[S3 Kbuild 目标生成]
drivers/media/i2c/Makefile: obj-$(CONFIG_VIDEO_OV5648) += ov5648.o
说明: 把配置开关变成真实编译对象
->
[S4 设备探测与注册]
ov5648_probe -> 读取 OF 属性/endpoint -> 注册 subdev
说明: 把 DTS 文本信息变成驱动运行态
->
[S5 格式与模式协商]
ov5648_enum_* / ov5648_set_fmt / ov5648_state_configure
说明: 决定可用分辨率/帧率/mbus_code 与当前生效 mode
->
[S6 启停流]
ov5648_s_stream(enable)
说明: runtime PM + standby 切换,决定是否真实出帧
->
[S7 HCS 能力注入]
camera_host_config.hcs -> logicCameraId/physicsCameraIds/basic/extend
说明: 定义上层可见 cameraId 与流能力
->
[S8 HCS 解析入 metadata]
HcsDeal::DealCameraAbility/DealMetadata/DealAvaliable*Configurations
说明: 把 HCS 数组写入 metadata tag
->
[S9 Open 上电门禁]
CameraHostVdiImpl::OpenCamera -> CameraPowerUp(physicsCameraIds)
说明: 按逻辑-物理映射逐路上电,失败则 open 失败
->
[S10 Framework 能力生成]
CameraManager::ParseCapability(ProfileLevel > Extend > Basic)
说明: 生成 ArkTS 可见 preview/photo/video profiles
->
[S11 ArkTS 侧能力消费]
getSupportedOutputCapability -> 选 profile -> createPreviewOutput
说明: 用 profile 创建输出流,进入会话/显示链
->
[S12 IQ 画质链]
BUILD.gn 安装 iqfiles + iqfil.json -> ispserver 运行时加载
说明: 主选按三元组命中 `iqfiles/ov5648_LMM248_YXC-M804A2.json`,`iqfil.json` 作为入口/兜底
主链每一步说明(速查版)
| 步骤 | 关键输入 | 关键输出 | 失败常见信号 | 源码锚点 |
|---|---|---|---|---|
| S1 DTS 节点定义 | ov5648@36、remote-endpoint、camera-module-facing |
可被驱动探测的硬件描述 | 枚举不到目标相机 | device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi:223-249,283-289 |
| S2 内核配置开关 | CONFIG_VIDEO_OV5648 |
驱动参与编译 | 驱动不在镜像/模块未启用 | device/board/gkhbd/nt_backclip_5cun_v1/kernel/configs/nt_backclip_vendor_oh_defconfig:3798 |
| S3 Kbuild 目标生成 | obj-$(CONFIG_VIDEO_OV5648) |
ov5648.o 构建目标 |
配置开了但对象未进构建 | kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/Makefile:90 |
| S4 设备探测与注册 | DTS OF 属性、endpoint、供电/时钟 | subdev 注册成功、运行态字段落地 | probe 失败、endpoint parse 失败 | kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:3138-3298 |
| S5 格式与模式协商 | 上游请求 width/height/code | 最接近 mode + mbus_code | -EINVAL/-EBUSY/输出参数错位 |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2924-2979,1956-1981 |
| S6 启停流 | enable=1/0 |
硬件出帧开关状态 | start 成功但无帧、停流异常 | kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2614-2650 |
| S7 HCS 能力注入 | logicCameraId/physicsCameraIds/basic/extend |
逻辑ID与能力配置文本 | 上层 cameraId/profile 异常 | vendor/gkhos/nt_backclip_vendor/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs |
| S8 HCS 解析入 metadata | HCS 节点数据 | metadata tags + cameraIdMap_ | metadata 缺失、extend 结构错误 | drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp:94-192,704-733,967-998 |
| S9 Open 上电门禁 | logic -> physics[] 映射 |
物理设备逐路上电 | open 失败、powerup 失败 | drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:165-205,248-275 |
| S10 Framework 能力生成 | metadata tags | preview/photo/video profiles | createPreviewOutput 7400101 前置条件形成 |
foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:2253-2270 |
| S11 ArkTS 能力消费 | outputCapability + profile 选择 | PreviewOutput/Session 配置 | profile 为空、创建输出失败 | applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts |
| S12 IQ 画质链 | iqfiles/ov5648_LMM248_YXC-M804A2.json + iqfil.json |
ISP 运行时画质策略(主选+兜底) | 偏色/曝光漂移/3A 不稳 | device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gn:37-70、device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json、device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfil.json |
驱动全功能调用图(函数职责版)
[DTS 节点装配]
ov5648@36/status/facing/endpoint
-> ov5648_probe
作用: 读取 OF 属性、解析 CSI endpoint、初始化 ctrl/state/subdev
-> ov5648_state_init
作用: 以默认 mode+mbus 初始化运行态
-> ov5648_ctrls_init
作用: 创建 exposure/gain/link_freq/pixel_rate 等控制项
[格式与模式协商]
-> ov5648_enum_mbus_code
作用: 枚举支持的 mbus 像素编码
-> ov5648_enum_frame_size / ov5648_enum_frame_interval
作用: 枚举分辨率与帧率能力
-> ov5648_set_fmt
作用: 选择最近 mode + mbus_code,触发状态重配置
-> ov5648_state_configure
作用: 写入 mode/mipi 时序到运行态,更新 sensor->state
[启停流]
-> ov5648_s_stream(enable=1/0)
作用: 运行时上电/待机切换,真正控制 sensor 出帧开关
[能力注入]
camera_host_config.hcs
-> HcsDeal::DealCameraAbility
作用: 读 logicCameraId + physicsCameraIds,建立 cameraIdMap_
-> HcsDeal::DealMetadata
作用: 收敛 metadata 参数并写入 cameraMetadataMap_
-> DealAvaliableBasicConfigurations / DealAvaliableExtendConfigurations
作用: 注入 basic/extend 流能力 tag
-> CameraHostVdiImpl::OpenCamera -> CameraPowerUp
作用: 按 physicsCameraIds 逐路上电,决定 open 能否继续
[上层能力消费]
-> CameraManager::ParseCapability
作用: 按 ProfileLevel > Extend > Basic 优先级产出 profile
函数调用覆盖检查(驱动侧是否“全功能”)
| 功能簇 | 关键函数 | 代码锚点 | 它回答什么问题 |
|---|---|---|---|
| probe 与硬件入场 | ov5648_probe |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:3138 |
DTS 属性是否真正进入驱动运行态 |
| 模式状态初始化 | ov5648_state_init / ov5648_state_configure |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:1983,1956 |
默认 mode 与 mbus 是否被设置 |
| 控制项初始化 | ov5648_ctrls_init |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:3279 |
AE/AGC/link_freq/pixel_rate 控件是否可用 |
| 能力枚举 | ov5648_enum_mbus_code / ov5648_enum_frame_size / ov5648_enum_frame_interval |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2874,2981,2998 |
上游能否看到正确格式/尺寸/fps 集 |
| 参数协商 | ov5648_get_fmt / ov5648_set_fmt |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2903,2924 |
create 输出前的 fmt 是否可被协商 |
| 真正启停流 | ov5648_s_stream |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2614 |
“能 open”是否等价于“能出帧” |
| V4L2 回调挂接 | ov5648_subdev_video_ops/pad_ops |
kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:2859-3070 |
调用链是否真能被 V4L2 框架触发 |
| HCS 映射注入 | DealCameraAbility / DealMetadata |
drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp:94,145 |
逻辑ID和metadata是否入库 |
| Open 上电门禁 | OpenCamera / CameraPowerUp |
drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:165,248 |
physicsCameraIds 是否可落到真实上电 |
| Framework 能力解析 | ParseCapability |
foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:2253 |
ArkTS 最终看到哪些 profile |
设备与配置清单(先看全景,再下钻)
| 层级 | 文件 | 本工程关键值 | 它解决的问题 |
|---|---|---|---|
| DTS | device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi |
ov5648@36 status="okay"、camera-module-facing="front"、remote-endpoint=&dphy0_in |
是否真的有这颗 sensor、是否连到正确 PHY |
| defconfig | device/board/gkhbd/nt_backclip_5cun_v1/kernel/configs/nt_backclip_vendor_oh_defconfig |
CONFIG_VIDEO_OV5648=m,同时 CONFIG_VIDEO_GC8034=y |
哪些 sensor 驱动会被编译进系统 |
| Kbuild | kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/Makefile |
obj-$(CONFIG_VIDEO_OV5648) += ov5648.o |
把配置开关转成真实编译目标 |
| 驱动 | kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c |
probe/set_fmt/s_stream 路径完整 |
是否能完成模式协商与真实启停流 |
| HCS | vendor/gkhos/nt_backclip_vendor/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs |
logicCameraId/physicsCameraIds/basic/extend |
上层能否拿到正确 cameraId 与能力集 |
| HCS解析 | drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp |
DealCameraAbility + DealAvaliable*Configurations |
HCS 参数是否真正进 metadata |
| IQ | device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json + device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfil.json |
ov5648 对应 IQ 主选文件存在,且有入口/兜底文件 | 画质是否与 sensor 身份一致 |
关键三件套(结构体/类变量/函数)
| 类型 | 名称 | 为什么关键 | 锚点 |
|---|---|---|---|
| 关键结构体 | struct ov5648_sensor |
把 GPIO/clock/state/ctrls 全部收敛到一个运行上下文 | ov5648.c:715-743 |
| 关键类变量 | cameraIdMap_(HcsDeal) |
决定逻辑相机 ID 到物理 sensor 的映射正确性 | hcs_deal.cpp:106-110,123-127 |
| 关键函数 | CameraManager::ParseCapability |
决定最终到底按 ProfileLevel/Extend/Basic 哪条能力链生效 |
camera_manager.cpp:2253-2270 |
关键源码摘录与逐行分析(补齐)
片段1:HCS 逻辑相机与物理相机映射写入
源码路径:drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp
RetCode HcsDeal::DealCameraAbility(const struct DeviceResourceNode &node)
{
const char *cameraId = nullptr;
int32_t ret = pDevResIns->GetString(&node, "logicCameraId", &cameraId, nullptr);
if (ret != 0) {
return RC_ERROR;
}
std::vector<std::string> phyCameraIds;
(void)DealPhysicsCameraId(node, phyCameraIds);
if (!phyCameraIds.empty() && cameraId != nullptr) {
cameraIdMap_.insert(std::make_pair(std::string(cameraId), phyCameraIds));
}
const struct DeviceResourceNode *metadataNode = pDevResIns->GetChildNode(&node, "metadata");
if (metadataNode == nullptr || cameraId == nullptr) {
return RC_ERROR;
}
RetCode rc = DealMetadata(cameraId, *metadataNode);
if (rc != RC_OK) {
return RC_ERROR;
}
return RC_OK;
}
逐行分析:
GetString(...logicCameraId...):从 HCS 读逻辑相机 ID,失败直接返回,说明这是硬前提。DealPhysicsCameraId:把physicsCameraIds读成数组。cameraIdMap_.insert(...):建立logic -> physics[]映射,后续OpenCamera上电链依赖它。GetChildNode(...metadata):要求 metadata 节点必须存在,否则能力注入链中断。DealMetadata(...):把 basic/extend 等能力参数写入 metadata,供 Framework 解析。
片段2:DTS 中 front/back 语义如何进入驱动运行态
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c
sensor->module_facing = "back";
if (node) {
of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, &sensor->module_index);
of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, &sensor->module_facing);
of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, &sensor->module_name);
of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, &sensor->len_name);
}
逐行分析:
module_facing = "back":先给默认值。of_property_read_string(...FACING...):如果 DTS 有camera-module-facing,就覆盖默认值。- 这意味着“方向语义”并非只存在于 DTS 文本,而会落到驱动运行态字段中。
片段3:能力解析优先级(ProfileLevel > Extend > Basic)
源码路径:foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp
if (g_isCapabilitySupported(metadata, item, OHOS_ABILITY_AVAILABLE_PROFILE_LEVEL)) {
ParseProfileLevel(profilesWrapper, mode, item);
} else if (g_isCapabilitySupported(metadata, item, OHOS_ABILITY_STREAM_AVAILABLE_EXTEND_CONFIGURATIONS)) {
ParseExtendCapability(profilesWrapper, mode, item);
} else if (g_isCapabilitySupported(metadata, item, OHOS_ABILITY_STREAM_AVAILABLE_BASIC_CONFIGURATIONS)) {
ParseBasicCapability(profilesWrapper, metadata, item);
} else {
MEDIA_ERR_LOG("Failed get stream info");
}
逐行分析:
- 第一分支优先吃
ProfileLevel,信息最完整。 - 第二分支吃
Extend,用于 mode/streamType/fps 细粒度能力。 - 第三分支才回退
Basic,能力表达最粗。 - 三者都没有才报错,因此 basic/extend 缺失时会直接影响 ArkTS 可见 profile 集。
片段4:set_fmt 如何把“请求尺寸”落成“实际 mode”
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c
static int ov5648_set_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *format)
{
...
if (sensor->state.streaming) {
ret = -EBUSY;
goto complete;
}
...
mode = v4l2_find_nearest_size(ov5648_modes, ARRAY_SIZE(ov5648_modes),
output_size_x, output_size_y,
mbus_format->width, mbus_format->height);
...
else if (sensor->state.mode != mode ||
sensor->state.mbus_code != mbus_code)
ret = ov5648_state_configure(sensor, mode, mbus_code);
...
}
逐行分析:
- 流正在跑时直接
-EBUSY,避免“边流边改格式”破坏状态机。 - 用
v4l2_find_nearest_size选择最近 mode,而不是盲目硬匹配。 - mode 或 mbus 变化才调用
ov5648_state_configure,避免无效重配。 - 这就是“App 选了一个尺寸,上层最后拿到另一个可用尺寸”的底层原因之一。
片段5:s_stream 才是“是否真的出帧”的物理开关
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c
static int ov5648_s_stream(struct v4l2_subdev *subdev, int enable)
{
...
if (enable) {
ret = pm_runtime_resume_and_get(sensor->dev);
if (ret < 0)
return ret;
}
...
ret = ov5648_sw_standby(sensor, !enable);
...
state->streaming = !!enable;
if (!enable)
pm_runtime_put(sensor->dev);
return 0;
}
逐行分析:
enable=1先做 runtime PM 上电,失败就不可能有帧。ov5648_sw_standby(sensor, !enable)是真正切换 sensor standby 的点。state->streaming只是在软件态记录,不代表硬件必然成功出帧。- 因此必须把“Start success 日志”与 V4L2 出帧/显示链信号一起看。
片段6:OpenCamera -> CameraPowerUp 的映射生效点
源码路径:drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp
RetCode rc = config->GetPhysicCameraIds(cameraId, phyCameraIds);
if (rc != RC_OK) {
return DEVICE_ERROR;
}
if (CameraPowerUp(cameraId, phyCameraIds) != RC_OK) {
CameraPowerDown(phyCameraIds);
return DEVICE_ERROR;
}
...
for (auto &phyCameraId : phyCameraIds) {
auto itr = CameraHostConfig::enumCameraIdMap_.find(phyCameraId);
...
rc = deviceManager->PowerUp(itr->second);
if (rc != RC_OK) {
return RC_ERROR;
}
}
逐行分析:
- 先由
logicCameraId查出phyCameraIds。 - 再逐个
PowerUp,这一步直接受physicsCameraIds配置影响。 - 任何一路上电失败都会中止 open。
- 这就是“逻辑 ID 看起来正确,但 open 失败”的典型根因路径。
片段7:IQ 安装链的硬证据(BUILD + 配置)
源码路径:
device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gndevice/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfil.json
ohos_prebuilt_etc("ov5648_LMM248_YXC-M804A2") {
source = "$ISP_DIR/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json"
relative_install_dir = "iqfiles"
}
ohos_prebuilt_etc("iqfil") {
source = "$ISP_DIR/etc/iqfil.json"
}
group("isp") {
deps = [ ":gc8034_RK-CMK-8M-2-v1_CK8401", ":ov5648_LMM248_YXC-M804A2", ":iqfil", ":ispserver", ":librkaiq" ]
}
逐行分析:
ov5648_*.json与iqfil.json都被打包进isp组。- 说明仓内“可安装资产”齐全,但不等于板端运行时一定已切到目标
iqfil。 - 所以 IQ 问题要做“仓内文件 + 板端文件”双检查,不能只看源码目录。
片段8:ov5648_LMM248_YXC-M804A2.json 为什么会被选中(全流程)
这是你关心的核心问题,结论先给:
- 选择键来自
sensor + module + lens三元组。 - 在本板上三元组是
ov5648 + LMM248 + YXC-M804A2。 - AIQ 侧按该三元组拼接并匹配
iqfiles/%s_%s_%s.json,最终命中ov5648_LMM248_YXC-M804A2.json。
8.1 全流程图(从 DTS 到 IQ 文件命中)
[DTS]
rockchip,camera-module-name = "LMM248"
rockchip,camera-module-lens-name = "YXC-M804A2"
->
[ov5648_probe]
读取 module_name / len_name 到 sensor 运行态
->
[RKMODULE_GET_MODULE_INFO ioctl]
ov5648_get_module_inf 返回:
base.sensor = "ov5648"
base.module = "LMM248"
base.lens = "YXC-M804A2"
->
[ispserver + librkaiq 启动]
init 脚本启动 /vendor/bin/ispserver,链接 librkaiq.so
->
[librkaiq 选档]
CamHwIsp20::selectIqFile(...)
使用 "/etc/iqfiles/" + "%s_%s_%s.json" 规则匹配
->
[命中文件]
/vendor/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json
8.2 可见源码摘选A:DTS 提供 module/lens 名称
源码路径:device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi
rockchip,camera-module-name = "LMM248";
rockchip,camera-module-lens-name = "YXC-M804A2";
说明:
- 这是三元组中的
module/lens来源。 - 若这两个字段改名,IQ 文件名匹配路径会跟着变化。
8.3 可见源码摘选B:驱动把 DTS 字段读入运行态
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c
of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, &sensor->module_name);
of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, &sensor->len_name);
说明:
- 这一步把 DTS 文本变成驱动内存中的实时字段。
- 后续
GET_MODULE_INFO返回的就是这里的值。
8.4 可见源码摘选C:GET_MODULE_INFO 返回 sensor/module/lens
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c
static void ov5648_get_module_inf(struct ov5648_sensor *sensor, struct rkmodule_inf *inf)
{
strscpy(inf->base.sensor, OV5648_NAME, sizeof(inf->base.sensor));
strscpy(inf->base.module, sensor->module_name, sizeof(inf->base.module));
strscpy(inf->base.lens, sensor->len_name, sizeof(inf->base.lens));
}
...
case RKMODULE_GET_MODULE_INFO:
ov5648_get_module_inf(sensor, arg);
break;
说明:
sensor/module/lens三元组由这个 ioctl 对外暴露。- AIQ/ISP 用户态若要按模组选 IQ,必须依赖这组字段。
8.5 可见源码摘选D:uapi 定义了模块信息结构
源码路径:kernel/linux/linux-6.6-nt_backclip_5cun_v1/include/uapi/linux/rk-camera-module.h
#define RKMODULE_GET_MODULE_INFO _IOR('V', BASE_VIDIOC_PRIVATE + 0, struct rkmodule_inf)
struct rkmodule_base_inf {
char sensor[RKMODULE_NAME_LEN];
char module[RKMODULE_NAME_LEN];
char lens[RKMODULE_NAME_LEN];
};
说明:
- 接口协议明确告诉我们:模块信息包含
sensor/module/lens。 - 这就是 IQ 文件按三元组命名的协议基础。
8.6 可见源码摘选E:ispserver 由 init 拉起并使用 librkaiq.so
源码路径:device/board/gkhbd/nt_backclip_5cun_v1/cfg/nt_backclip_vendor/init.nt_backclip_vendor.cfg
"start ispserver"
...
"name" : "ispserver",
"path" : ["/vendor/bin/ispserver"]
说明:
- 运行时 IQ 选择发生在
ispserver进程内。 - 该进程会加载
librkaiq.so(见构建与二进制字符串证据)。
8.7 预编译库证据(闭源实现的“可验证锚点”)
这里是“实际选择函数”证据,虽然函数体不在开源源码中,但证据足够闭环:
- 动态符号存在:
CamHwIsp20::selectIqFile- 锚点:
device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/lib64/librkaiq.so(nm -D可见)
- 字符串规则存在:
"/etc/iqfiles/""%s_%s_%s.json"- 锚点:同一
librkaiq.so(strings可见)
这两条说明:librkaiq 内部确实按三段式文件名在 iqfiles 目录选档。
8.8 设计取舍(为什么是三元组选档)
- 只按 sensor 选档不够:同一 sensor 不同模组/镜头会有不同标定数据。
- 加入 module/lens 后可精确到具体模组批次配置,降低偏色/曝光不稳风险。
- 代价是命名与 DTS 字段必须严格一致,否则会回退到默认或错误文件。
8.9 最小验证动作(现场可复核)
- 核对 DTS 三元组:
rg -n "camera-module-name|camera-module-lens-name" device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi -S
- 核对驱动是否实现模块信息 ioctl:
rg -n "RKMODULE_GET_MODULE_INFO|ov5648_get_module_inf|base.module|base.lens" kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c -S
- 核对 AIQ 选档符号和模板:
nm -D device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/lib64/librkaiq.so | rg "selectIqFile" -n -Sstrings -a device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/lib64/librkaiq.so | rg "/etc/iqfiles/|%s_%s_%s.json" -n -S
8.10 双证据结论(避免“只凭日志”)
- 前序知识点判据:IQ 选档必须绑定“具体模组”,否则同 sensor 多模组时标定会错位。
- 实际源码/二进制锚点:
- 源码:DTS 字段 +
GET_MODULE_INFO返回sensor/module/lens。 - 二进制:
selectIqFile符号 +"/etc/iqfiles/"+"%s_%s_%s.json"模板。
- 源码:DTS 字段 +
- 因此当前
ov5648_LMM248_YXC-M804A2.json被选中是符合机制的,不是偶然命中。
参数卡(在哪、谁读、哪生效、错配后果)
| 参数名 | 参数位置 | 作用 | 典型取值 | 谁读取 | 哪里生效 | 错配信号 | 最小验证动作 |
|---|---|---|---|---|---|---|---|
status(sensor节点) |
DTS ov5648@36 |
控制节点启停 | okay/disable |
OF + ov5648_probe |
决定是否能枚举并 probe | getSupportedCameras 缺失目标 ID |
`rg -n "ov5648@36 |
camera-module-facing |
DTS | 定义前后摄方向语义 | front/back |
ov5648_probe 读取模块属性 |
影响方向相关元数据与上层语义 | 前后摄逻辑反转、旋转补偿异常 | rg -n "camera-module-facing" device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi -S |
CONFIG_VIDEO_OV5648 |
defconfig | 控制驱动编译 | y/m/not set |
Kbuild | 生成并装载 ov5648.o |
驱动路径不存在或无法匹配 | rg -n "CONFIG_VIDEO_OV5648" device/board/gkhbd/nt_backclip_5cun_v1/kernel/configs/nt_backclip_vendor_oh_defconfig -S |
logicCameraId |
camera_host_config.hcs |
上层逻辑相机主键 | 例如 lcam001 |
DealCameraAbility |
GetCameraIds/OpenCamera 的入口键 |
API 层找不到目标 cameraId | rg -n "logicCameraId" vendor/gkhos/nt_backclip_vendor/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs -S |
physicsCameraIds |
camera_host_config.hcs |
逻辑->物理映射 | 字符串数组 | DealPhysicsCameraId |
OpenCamera -> CameraPowerUp 逐个上电 |
OpenCamera 前后 powerup 失败 |
结合 OpenCamera/PowerUp 日志核对 |
basicAvailableConfigurations |
camera_host_config.hcs |
基础流能力(format,w,h) | 三元组数组 | DealAvaliableBasicConfigurations |
写入 basic metadata tag | 预览/拍照 profile 缺失 | 对照 GetSupportedOutputCapability |
extendAvailableConfigurations |
camera_host_config.hcs |
模式化能力(mode/stream/fps) | 分层数组 + -1 分隔 |
DealAvaliableExtendConfigurations |
写入 extend metadata tag,Framework 优先解析 | 7400101、profile 错选、mode 错配 |
运行 check_camera_extend_config.sh |
ov5648_LMM248_YXC-M804A2.json + iqfil.json |
device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json + device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfil.json |
前者是主选 IQ,后者是入口/兜底 | JSON 场景配置 | ispserver + librkaiq 运行时加载 |
影响 AE/AWB/色彩/曝光策略 | 偏色、曝光漂移、画质不稳 | 先比对板端 /vendor/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json,再比对 /vendor/etc/iqfil.json |
枚举/编码桥接表(打通“数字 -> 语义”)
| 枚举或编码 | 语义 | 影响链路 | 常见误区 |
|---|---|---|---|
basic format=3 |
YCRCB_420_SP(常对应 NV21) |
预览/视频能力声明 | 以为它等于所有 YUV420,导致误匹配 |
basic format=4 |
YCBCR_420_SP(常对应 NV12) |
预览能力可选格式 | 忽略 NV12/NV21 差异导致颜色异常 |
basic format=5 |
JPEG |
拍照流能力 | 把 JPEG 当预览格式使用 |
mode=0 |
NORMAL 模式 |
能力选择的默认主路径 | 忽略 mode fallback 行为 |
streamType=0/1/2 |
预览/视频/拍照 | 决定 profile 进入哪个集合 | streamType 标注错会让上层挑错 profile |
参数读取链(谁在读)
| 来源 | 读取函数 | 锚点 | 读取后的中间形态 |
|---|---|---|---|
logicCameraId |
HcsDeal::DealCameraAbility |
hcs_deal.cpp:94-105 |
逻辑相机ID字符串 |
physicsCameraIds |
HcsDeal::DealPhysicsCameraId |
hcs_deal.cpp:133-140 |
物理ID数组 |
basicAvailableConfigurations |
DealAvaliableBasicConfigurations |
hcs_deal.cpp:704-723 |
basic int32 流配置数组 |
extendAvailableConfigurations |
DealAvaliableExtendConfigurations |
hcs_deal.cpp:967-998 |
extend int32 扩展能力数组 |
| DTS 模组方向 | ov5648_probe |
ov5648.c:3161-3169 |
module_facing 运行态字符串 |
| 模式/总线格式 | ov5648_set_fmt |
ov5648.c:2924-2973 |
state.mode + state.mbus_code |
参数生效链(最终影响在哪里)
| 参数 | 生效函数 | 锚点 | 直接结果 | 间接结果 |
|---|---|---|---|---|
physicsCameraIds |
OpenCamera -> CameraPowerUp |
camera_host_vdi_impl.cpp:195-205,248-272 |
是否上电成功 | 决定后续 open 能否继续 |
basic/extend |
CameraManager::ParseCapability |
camera_manager.cpp:2253-2270 |
生成 profile 集 | 影响 ArkTS 选型与 createPreviewOutput 成败 |
mbus_code/size |
ov5648_state_configure(由 set_fmt 触发) |
ov5648.c:2968-2973 |
sensor 输出模式稳定 | 影响后续缓冲尺寸和帧处理 |
streaming |
ov5648_s_stream |
ov5648.c:2614-2649 |
真实启停流 | 决定上层是否能收到帧 |
iqfil + iqfiles |
isp 安装组 + ispserver 运行时加载 |
BUILD.gn:37-51,65-73 |
画质策略生效 | 影响用户侧色彩与曝光体验 |
HCS + IQ 扩展联动(你要求的拓展块)
| 联动点 | 前序知识点判据 | 源码锚点证据 | 为什么要联动看 |
|---|---|---|---|
| 流能力声明与实际 sensor mode | “能力声明必须落在驱动可枚举范围内,否则只是纸面配置” | ov5648_modes 仅含 2592x1944@15 与 1296x972@30:kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:863-943;basic/extend 在 camera_host_config.hcs |
HCS 若宣称超出驱动能力,最终会在 create/commit 阶段失败 |
| 前后摄方向语义 | “方向字段要跨 DTS/HCS 同向,否则预览和拍照旋转逻辑错位” | DTS camera-module-facing="front":device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi:240;驱动读入 module_facing:ov5648.c:3161-3169;HCS cameraPosition/lensFacing |
方向语义是跨层传递值,不是单文件自洽即可 |
| 画质策略是否可交付 | “IQ 文件既要存在也要进入镜像,缺一都不算完成” | BUILD.gn 同时打包 ov5648_*.json 与 iqfil.json:device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gn:37-70 |
只改 IQ 文件不进构建,不会影响运行时 |
错误检查双证据(前序判据 + 源码锚点)
| 错误信号 | 前序知识点判据 | 实际源码锚点 | 最小动作 |
|---|---|---|---|
枚举不到 lcam001 |
“先证实能力是否注入,再看调用层是否消费” | DealCameraAbility/DealMetadata:drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp:94-192;ParseCapability:foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:2253-2270 |
先查 HCS 的 logicCameraId 与 metadata,再查 framework 分支命中 |
OpenCamera 失败并伴随 powerup 错 |
“逻辑到物理映射必须可在板级枚举表中闭合” | OpenCamera->CameraPowerUp:drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:195-205,266-275 |
对齐 physicsCameraIds 与 project_hardware.h 枚举 |
createPreviewOutput 7400101 |
“open 成功不等于 profile 可用;先看能力声明与解析优先级” | basic/extend 注入:hcs_deal.cpp:704-733,967-998;优先级解析:camera_manager.cpp:2256-2267 |
核对 extend 分隔合法性与 basic 格式三元组 |
| 颜色/曝光长期异常 | “链路通与画质对是两条线,IQ 必须独立验收” | IQ 安装链:device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gn:37-70;主选文件:device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json;入口文件:device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfil.json |
先比对主选 IQ,再比对 iqfil.json,最后做场景回归 |
从现象反推配置层(你会更容易定位)
| 现象 | 第一怀疑层 | 第二怀疑层 | 为什么这样排 |
|---|---|---|---|
| 根本枚举不到相机 | DTS/HCS | defconfig/Kbuild | 先确认“有没有”,再确认“编没编” |
| 能 open 但 createPreviewOutput 失败 | HCS能力 | ArkTS选型参数 | open 成功说明电源和驱动主路已通 |
| 可预览但颜色长期异常 | IQ | 格式映射/缓冲 | 先看调参文件,再看格式语义 |
| 方向错乱或镜像异常 | DTS facing + HCS方向字段 | 上层旋转逻辑 | 方向语义应先在底层统一 |
典型错配案例(含最小修复思路)
| 案例 | 触发条件 | 关键信号 | 最小修复动作 |
|---|---|---|---|
| 逻辑-物理映射错配 | physicsCameraIds 有不存在或多余 ID |
OpenCamera 后立即 powerup 失败 |
保持 physicsCameraIds 与板级真实连线一一对应 |
| 能力声明不完整 | extend/basic 缺少预览主用格式/尺寸 |
7400101 或 previewProfile undefined |
补齐 mode/streamType/fps 结构并做分隔合法性检查 |
| 方向基线不一致 | DTS facing 与 HCS/应用假设相反 | 预览方向或镜像不符合预期 | 统一 DTS/HCS/应用的“前后摄”语义 |
| 双 sensor 并存噪声 | defconfig 同时开多个 sensor 且映射不清 | 枚举结果或能力集出现干扰 | 固化主路径 sensor,旁路 sensor 降优先 |
| IQ 指向错误 | 主选 ov5648_LMM248_YXC-M804A2.json 未命中或 iqfil 回退异常 |
颜色/曝光持续不稳 | 先核验 /vendor/etc/iqfiles/ov5648_LMM248_YXC-M804A2.json,再核验 /vendor/etc/iqfil.json |
本章知识点覆盖核查(防漏项)
| 知识点簇 | 本章是否覆盖 | 章节位置 | 仍需联动章节 |
|---|---|---|---|
| 硬件是否存在(DTS 节点 + endpoint) | 是 | 设备与配置清单、参数卡 | 第3章(能力注入) |
| 驱动是否可编译可装载(defconfig + Kbuild) | 是 | 设备与配置清单、参数读取链 | 第6章(函数级启流) |
| 能力是否可被上层消费(HCS basic/extend) | 是 | 参数卡、参数生效链 | 第4章、第5章 |
| 画质基线是否对齐(iqfil + iqfiles) | 是 | 设备与配置清单、错配案例 | 第7章(发散异常) |
| 错误信号到最小动作映射 | 是 | 速查块 | 第8章、第9章 |
常见错误与纠偏(本章新增)
- 只改
camera_host_config.hcs不改 DTS:会产生“能力看似存在但硬件链不稳定”的半成功状态。 - 只看
Start success不看显示链:可能把Bufferqueue -5误判成偶发噪声,实际是显示链断裂。 - 只校验仓内
iqfil.json不校验主选ov5648_LMM248_YXC-M804A2.json与板端文件:运行时仍可能加载旧配置,导致“源码改了但画质没变”。 - 将
CONFIG_VIDEO_OV5648=m当成“必然可用”:若模块未被正确打包或加载,运行时仍会失效,需结合日志确认。
为什么这样设计 + 优点与取舍
| 设计点 | 为什么这样设计 | 优点 | 代价/取舍 | 不这样会怎样 |
|---|---|---|---|---|
logicCameraId -> physicsCameraIds 分层映射 |
把上层稳定接口和底层可变硬件解耦 | 上层 API 稳定、硬件替换灵活 | 映射维护要求高 | 硬件一变更就会牵动全栈调用方 |
ProfileLevel > Extend > Basic 分级解析 |
信息量越高的能力描述优先级越高 | 模式与流类型匹配更准确 | extend 配置规范严格 | 只用 basic 时复杂场景易退化 |
| DTS/defconfig/HCS/IQ 职责分离 | 让“连线、编译、能力、画质”各司其职 | 定位快、回滚清晰 | 需要跨层理解成本 | 全部糅在一层会导致排障混乱 |
知识点依赖与跨章连接
| 当前知识点 | 依赖前置 | 依赖理由 | 建议下一跳 |
|---|---|---|---|
| 硬件节点到驱动启流 | 第1章总旅程 | 先有全链路时序,再看局部才不迷路 | 第3章(HDF/HDI/VDI) |
| 能力注入到 profile 生成 | 第3章能力桥接 | HCS 是输入,解析器是决定性节点 | 第4章(会话门禁)+ 第5章(输出绑定) |
| IQ 与画质稳定性 | 第6章函数级链路 | 要区分“链路问题”与“调参问题” | 第8章调试技巧 |
信号 -> 判断 -> 动作(速查块)
| 信号 | 判断 | 动作 |
|---|---|---|
getSupportedCameras 无 lcam001 |
logicCameraId 注入或枚举链异常 |
先核对 HCS logicCameraId,再看 GetCameraIds 输出 |
OpenCamera 失败且出现 powerup 问题 |
物理映射不一致 | 对齐 physicsCameraIds 与板级真实物理相机定义 |
createPreviewOutput 7400101 |
profile 与能力集不匹配 | 联查 basic/extend + ArkTS 侧尺寸与格式选择 |
repeatStream is nullptr 但非 PREVIEW 场景 |
可能是正常分支日志 | 结合 StreamType 和 AddOutput 顺序再判定 |
| 颜色/曝光持续异常 | IQ 未生效或 sensor-IQ 对不齐 | 先核验 ov5648_LMM248_YXC-M804A2.json,再核验 iqfil 并回归场景验证 |
本章三元结论
- 现象:相机链路“时通时不通”或“能开但不稳”。
- 根因:跨层配置语义不一致,而不是单点代码必然错误。
- 最小动作:先做“DTS/defconfig/HCS/ov5648_LMM248_YXC-M804A2.json+iqfil”一致性核验,再进入会话与应用层。
本章最小动作
- 执行并记录三条静态核验:
rg -n "ov5648@36|camera-module-facing|remote-endpoint" device/board/gkhbd/nt_backclip_5cun_v1/kernel/dts/nt_backclip_vendor/rk3568-gkh-nt_backclip_5cun.dtsi -Srg -n "CONFIG_VIDEO_OV5648|CONFIG_VIDEO_GC8034" device/board/gkhbd/nt_backclip_5cun_v1/kernel/configs/nt_backclip_vendor_oh_defconfig -Srg -n "logicCameraId|physicsCameraIds|basicAvailableConfigurations|extendAvailableConfigurations" vendor/gkhos/nt_backclip_vendor/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs -S
- 再做一条动态核验:同窗确认
GetCameraIds -> OpenCamera -> createPreviewOutput是否同向成功。 - 补一条运行时核验:同窗对照
Start success与Bufferqueue -5是否共现,避免把显示链问题误归因到驱动层。
下一跳建议
读第3章:你会看到本章的静态配置如何在 HDF/HDI/VDI 层变成可调用、可建流、可提交的运行时能力。
发布后补链
- 上一篇:https://laval.csdn.net/69d8b75f54b52172bc686020.html
- 下一篇:https://laval.csdn.net/69d8b86154b52172bc68609a.html
全章导航
- 第0章 导读学习指南:https://laval.csdn.net/69d77a0554b52172bc6802aa.html
- 第1章 主链调用总览(图表):https://laval.csdn.net/69d8b75f54b52172bc686020.html
- 第2章 驱动与硬件基线:https://laval.csdn.net/69d8b7fd0a2f6a37c59e6f33.html
- 第3章 能力注入与流桥接(HDF/HDI/VDI):https://laval.csdn.net/69d8b86154b52172bc68609a.html
- 第4章 会话编排与门禁(Framework/Service):https://laval.csdn.net/69d8b8d154b52172bc6860ae.html
- 第5章 输出创建与显示绑定(ArkTS/HAP):https://laval.csdn.net/69d8b91954b52172bc6860be.html
- 第6章 函数级链路走读(驱动到显示):https://laval.csdn.net/69d8b96854b52172bc6860cd.html
- 第7章 异常挂点与主链归因:https://laval.csdn.net/69d8b9d054b52172bc6860dc.html
- 第8章 调试方法与证据模板:https://laval.csdn.net/69d8ba1454b52172bc6860e4.html
- 第9章 实战演练与复盘:https://laval.csdn.net/69d8ba4754b52172bc6860f3.html
更多推荐
所有评论(0)