本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第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 段链路串成一条可验证、可回滚、可复用的硬件准备主线。

先给你一个整体心智图

你可以把本章理解为在做一件事:

  • 给相机系统做“入场体检”。
  • 体检不是只看单项指标,而是看“跨层是否一致”。
  • 只要有一层和其他层语义不一致,就会在上层表现为随机黑屏、参数非法、偏色、方向错乱等“看似应用问题”。

三句流程版

  1. DTS + defconfig 决定“硬件是否可被发现、驱动是否可被加载、sensor 是否可被真正启流”。
  2. HCS 把“逻辑相机ID + 物理相机映射 + 流能力描述”注入 metadata,决定上层最终看到的 profile 集。
  3. 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@36remote-endpointcamera-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-70device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/etc/iqfiles/ov5648_LMM248_YXC-M804A2.jsondevice/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;
}

逐行分析:

  1. GetString(...logicCameraId...):从 HCS 读逻辑相机 ID,失败直接返回,说明这是硬前提。
  2. DealPhysicsCameraId:把 physicsCameraIds 读成数组。
  3. cameraIdMap_.insert(...):建立 logic -> physics[] 映射,后续 OpenCamera 上电链依赖它。
  4. GetChildNode(...metadata):要求 metadata 节点必须存在,否则能力注入链中断。
  5. 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);
}

逐行分析:

  1. module_facing = "back":先给默认值。
  2. of_property_read_string(...FACING...):如果 DTS 有 camera-module-facing,就覆盖默认值。
  3. 这意味着“方向语义”并非只存在于 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");
}

逐行分析:

  1. 第一分支优先吃 ProfileLevel,信息最完整。
  2. 第二分支吃 Extend,用于 mode/streamType/fps 细粒度能力。
  3. 第三分支才回退 Basic,能力表达最粗。
  4. 三者都没有才报错,因此 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);
    ...
}

逐行分析:

  1. 流正在跑时直接 -EBUSY,避免“边流边改格式”破坏状态机。
  2. v4l2_find_nearest_size 选择最近 mode,而不是盲目硬匹配。
  3. mode 或 mbus 变化才调用 ov5648_state_configure,避免无效重配。
  4. 这就是“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;
}

逐行分析:

  1. enable=1 先做 runtime PM 上电,失败就不可能有帧。
  2. ov5648_sw_standby(sensor, !enable) 是真正切换 sensor standby 的点。
  3. state->streaming 只是在软件态记录,不代表硬件必然成功出帧。
  4. 因此必须把“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;
    }
}

逐行分析:

  1. 先由 logicCameraId 查出 phyCameraIds
  2. 再逐个 PowerUp,这一步直接受 physicsCameraIds 配置影响。
  3. 任何一路上电失败都会中止 open。
  4. 这就是“逻辑 ID 看起来正确,但 open 失败”的典型根因路径。

片段7:IQ 安装链的硬证据(BUILD + 配置)

源码路径:

  • device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gn
  • device/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" ]
}

逐行分析:

  1. ov5648_*.jsoniqfil.json 都被打包进 isp 组。
  2. 说明仓内“可安装资产”齐全,但不等于板端运行时一定已切到目标 iqfil
  3. 所以 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";

说明:

  1. 这是三元组中的 module/lens 来源。
  2. 若这两个字段改名,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);

说明:

  1. 这一步把 DTS 文本变成驱动内存中的实时字段。
  2. 后续 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;

说明:

  1. sensor/module/lens 三元组由这个 ioctl 对外暴露。
  2. 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];
};

说明:

  1. 接口协议明确告诉我们:模块信息包含 sensor/module/lens
  2. 这就是 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"]

说明:

  1. 运行时 IQ 选择发生在 ispserver 进程内。
  2. 该进程会加载 librkaiq.so(见构建与二进制字符串证据)。

8.7 预编译库证据(闭源实现的“可验证锚点”)

这里是“实际选择函数”证据,虽然函数体不在开源源码中,但证据足够闭环:

  1. 动态符号存在:
    • CamHwIsp20::selectIqFile
    • 锚点:device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/lib64/librkaiq.sonm -D 可见)
  2. 字符串规则存在:
    • "/etc/iqfiles/"
    • "%s_%s_%s.json"
    • 锚点:同一 librkaiq.sostrings 可见)

这两条说明:librkaiq 内部确实按三段式文件名在 iqfiles 目录选档。

8.8 设计取舍(为什么是三元组选档)

  1. 只按 sensor 选档不够:同一 sensor 不同模组/镜头会有不同标定数据。
  2. 加入 module/lens 后可精确到具体模组批次配置,降低偏色/曝光不稳风险。
  3. 代价是命名与 DTS 字段必须严格一致,否则会回退到默认或错误文件。

8.9 最小验证动作(现场可复核)

  1. 核对 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
  2. 核对驱动是否实现模块信息 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
  3. 核对 AIQ 选档符号和模板:
    • nm -D device/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/lib64/librkaiq.so | rg "selectIqFile" -n -S
    • strings -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" 模板。
  • 因此当前 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@151296x972@30kernel/linux/linux-6.6-nt_backclip_5cun_v1/drivers/media/i2c/ov5648.c:863-943basic/extendcamera_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_facingov5648.c:3161-3169;HCS cameraPosition/lensFacing 方向语义是跨层传递值,不是单文件自洽即可
画质策略是否可交付 “IQ 文件既要存在也要进入镜像,缺一都不算完成” BUILD.gn 同时打包 ov5648_*.jsoniqfil.jsondevice/soc/gkhsoc/rk3568/hardware/isp-linux-6.6/BUILD.gn:37-70 只改 IQ 文件不进构建,不会影响运行时

错误检查双证据(前序判据 + 源码锚点)

错误信号 前序知识点判据 实际源码锚点 最小动作
枚举不到 lcam001 “先证实能力是否注入,再看调用层是否消费” DealCameraAbility/DealMetadatadrivers/peripheral/camera/vdi_base/v4l2/src/camera_host/hcs_deal.cpp:94-192ParseCapabilityfoundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:2253-2270 先查 HCS 的 logicCameraId 与 metadata,再查 framework 分支命中
OpenCamera 失败并伴随 powerup 错 “逻辑到物理映射必须可在板级枚举表中闭合” OpenCamera->CameraPowerUpdrivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:195-205,266-275 对齐 physicsCameraIdsproject_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 缺少预览主用格式/尺寸 7400101previewProfile 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章

常见错误与纠偏(本章新增)

  1. 只改 camera_host_config.hcs 不改 DTS:会产生“能力看似存在但硬件链不稳定”的半成功状态。
  2. 只看 Start success 不看显示链:可能把 Bufferqueue -5 误判成偶发噪声,实际是显示链断裂。
  3. 只校验仓内 iqfil.json 不校验主选 ov5648_LMM248_YXC-M804A2.json 与板端文件:运行时仍可能加载旧配置,导致“源码改了但画质没变”。
  4. 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章调试技巧

信号 -> 判断 -> 动作(速查块)

信号 判断 动作
getSupportedCameraslcam001 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”一致性核验,再进入会话与应用层。

本章最小动作

  1. 执行并记录三条静态核验:
    • 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 -S
    • rg -n "CONFIG_VIDEO_OV5648|CONFIG_VIDEO_GC8034" device/board/gkhbd/nt_backclip_5cun_v1/kernel/configs/nt_backclip_vendor_oh_defconfig -S
    • rg -n "logicCameraId|physicsCameraIds|basicAvailableConfigurations|extendAvailableConfigurations" vendor/gkhos/nt_backclip_vendor/hdf_config/uhdf/camera/hdi_impl/camera_host_config.hcs -S
  2. 再做一条动态核验:同窗确认 GetCameraIds -> OpenCamera -> createPreviewOutput 是否同向成功。
  3. 补一条运行时核验:同窗对照 Start successBufferqueue -5 是否共现,避免把显示链问题误归因到驱动层。

下一跳建议

读第3章:你会看到本章的静态配置如何在 HDF/HDI/VDI 层变成可调用、可建流、可提交的运行时能力。


发布后补链

全章导航

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐