本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第9章 实战演练与复盘
代码基线:~/work/ohos5.1/nt_backclip_vendor(基于 OpenHarmony 5.1.0 Release 移植)
发布说明:使用ai codex移植调试开源鸿蒙摄像头的任务全流程在链接https://github.com/chenlong3388/codex_fix_OpenHarmony_Camera

第9章 实战演练与复盘

本章只解决一个问题

完整复盘这条问题链:最初问题是什么,为什么那样分析与修复,每一轮引入了什么新问题,又是如何继续收敛。

0. 起点问题

起点现象:

  1. v4l2-ctl 已可录像,说明底层链路不是完全断开。
  2. 默认相机 App 出现黑屏与 7400101,系统 API 路径不稳定。

核心矛盾:

  • 同时出现“部分成功”与“最终不可见”。
  • 单看任意一层都会误判。

1. 第一轮:先证明确实是参数门禁,不是驱动挂死

1.1 当时为什么先看这里

7400101 是高优先级参数门禁信号,必须先确认发生点。

1.2 源码证据

源码:foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp

CHECK_ERROR_RETURN_RET_LOG((serviceProxy == nullptr) || (surface == nullptr), CameraErrorCode::INVALID_ARGUMENT,
    "CreatePreviewOutput serviceProxy is null or previewOutputSurface/profile is null");
CHECK_ERROR_RETURN_RET_LOG((profile.GetCameraFormat() == CAMERA_FORMAT_INVALID) || (profile.GetSize().width == 0)
    || (profile.GetSize().height == 0), CameraErrorCode::INVALID_ARGUMENT,
    "CreatePreviewOutput invalid fomrat or width or height is zero");

分析:

  1. 这里明确把 surface/profile/size/format 当成硬门禁。
  2. 一旦不满足直接返回 INVALID_ARGUMENT

1.3 历史日志证据

日志:tmp_test/ov5648_api_debug_0402_091325/hilog_x.txt

04-02 09:13:35.701 ... createPreviewOutput failed: {"code":"7400101"}
04-02 09:13:35.725 ... mPreviewOutput: undefined
04-02 09:13:35.764 ... createSession failed: {"code":"7400101"}

1.4 本轮结论

  • 这轮优先结论是“预览输出创建参数不成立”。
  • 不是“驱动完全不可用”。

2. 第二轮:进入模式能力错配定位

2.1 为什么会进入第二轮

第一轮解释了 7400101,但没有解释所有黑屏窗口。

2.2 源码证据

源码:foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp

auto modeName = GetMode();
...
CHECK_ERROR_PRINT_LOG(!result, "CaptureSession::ValidateOutputProfile fail! Not in the profiles set.");
if (modeName != SceneMode::NORMAL) {
    auto normalIt = profileMap.find(SceneMode::NORMAL);
    ...
}

分析:

  1. 当前 mode 下 profile 先匹配。
  2. 失败后才尝试 NORMAL fallback。
  3. 说明该问题本质是“能力语义错配”。

2.3 历史日志证据

日志:tmp_test/ov5648_api_debug_0402_162846/hilog_x.txt

04-02 16:28:56.240 ... ValidateOutputProfile in mode(1): w(1280),h(960),f(2000), profiles size is:1
04-02 16:28:56.240 ... CaptureSession::ValidateOutputProfile fail!

2.4 本轮动作与结果

动作:收敛 HCS 能力定义(mode/format/size 组合)。
结果:参数非法类问题下降,但仍出现“会话半成功”。

3. 第三轮:定位输出对象链断点

3.1 为什么转到对象链

mode fail 收敛后,仍出现黑屏,说明还存在“对象绑定问题”。

3.2 源码证据

源码:foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp

if (output->GetOutputType() == CAPTURE_OUTPUT_TYPE_PREVIEW) {
    repeatStream = static_cast<IStreamRepeat*>(stream.GetRefPtr());
}
if (repeatStream) {
    errItemCode = repeatStream->SetCameraApi(apiCompatibleVersion);
} else {
    MEDIA_ERR_LOG("PreviewOutput::SetCameraApi() repeatStream is nullptr");
}

分析:

  1. 预览输出必须能转成 IStreamRepeat
  2. 转换失败就会出现 repeatStream is nullptr

3.3 历史日志证据

日志:tmp_test/ov5648_api_debug_0402_162846/hilog_x.txt

04-02 16:28:56.241 ... CaptureSession::AddOutput StreamType = 1
04-02 16:28:56.241 ... PreviewOutput::SetCameraApi() repeatStream is nullptr
04-02 16:28:56.310 ... HCaptureSession::Start execute success, sessionID: 3

3.4 本轮结论

  • “Start 成功”和“预览绑定失败”可以同时出现。
  • 这正是黑屏“假成功”窗口。

4. 第四轮:确认底层缓冲是否也在出错

4.1 为什么查到 V4L2

有些窗口里,显示问题前还伴随建缓冲异常。

4.2 源码证据

源码:drivers/peripheral/camera/vdi_base/common/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp

buf.type = bufferType_;
buf.memory = memoryType_;
...
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
    CAMERA_LOGE("error: ioctl VIDIOC_QUERYBUF failed: %{public}s\n", strerror(errno));
    return RC_ERROR;
}

分析:

  1. type/memory/plane/index 任一语义错配都会在 QUERYBUF 失败。
  2. 这是“产帧前失败”,会直接影响后续可见性。

4.3 历史日志证据

日志:tmp_test/ov5648_api_debug_0402_122831/hilog_x.txt

04-02 12:28:41.537 ... V4L2AllocBuffer() V4L2AllocBuffer enter fd 9
04-02 12:28:41.537 ... ioctl VIDIOC_QUERYBUF failed: Invalid argument
04-02 12:28:41.537 ... Creatbuffer: V4L2AllocBuffer error

4.4 本轮结论

  • 该类窗口不是单纯 App 层问题,底层缓冲协商也可能成为并行风险。

5. 第五轮:明确显示消费链独立失败

5.1 为什么要单拉显示链

只盯控制链会忽略“帧到了显示侧但消费失败”的窗口。

5.2 源码证据

源码:foundation/graphic/graphic_surface/surface/src/surface_buffer_impl.cpp

auto dRet = displayBuffer->SetMetadata(*handle_, key, value);
if (dRet == GRAPHIC_DISPLAY_SUCCESS) {
    return GSERROR_OK;
}
BLOGE("SetMetadata Failed with %{public}d", dRet);
return GSERROR_HDI_ERROR;

分析:

  1. 显示元数据写入失败会直接返回错误。
  2. 这类失败不要求控制链同时失败。

5.3 历史日志证据

日志:tmp_test/ov5648_api_debug_0402_091325/hilog_x.txt

04-02 09:13:35.757 ... Bufferqueue: <surface_buffer_impl.cpp:610-SetMetadata>: SetMetadata Failed with -5
04-02 09:13:35.763 ... PreviewOutput::SetCameraApi() repeatStream is nullptr

5.4 本轮结论

  • 黑屏结论必须拆成双链:
    • 控制链可推进。
    • 显示链可单独失败。

6. 最终收敛方法(不是“一次修完”,而是“可重复收敛”)

最终形成的有效路径:

  1. 先判 createPreviewOutput/7400101(输入语义)。
  2. 再判 ValidateOutputProfile(模式能力)。
  3. 再判 repeatStream is nullptr(对象绑定)。
  4. 并行观察 VIDIOC_QUERYBUF failed(缓冲协商风险)。
  5. 最后单独判 SetMetadata Failed with -5(显示消费链)。

这个顺序的价值:

  • 每一层都有明确源码锚点。
  • 每一层都有历史日志证据。
  • 每一步都能解释“为什么下一步这样做”。

7. 关键注意点(复盘必须写)

  1. Start execute success 不是终局成功信号。
  2. 同窗共现不自动等于直接因果。
  3. 没有源码锚点的结论不能升级为主结论。
  4. 运行时路径与仓内源路径必须对照,避免“改了源码但跑的是旧包”。

8. 可直接复用的复盘模板

8.1 阶段模板

  • 阶段问题:
  • 为什么先看这里:
  • 源码证据(文件+片段):
  • 日志证据(文件+时间行):
  • 动作:
  • 新问题:
  • 阶段结论:

8.2 总结模板

  • 控制链结论:
  • 显示链结论:
  • 联合结论:
  • 下一轮最小动作:

本章最小动作

tmp_test 任取一个历史窗口,按“阶段模板”写至少 3 个连续阶段,不允许跳过“为什么先看这里”。

知识点依赖与跨章连接

本章阶段 前置章节 回跳章节 连接理由
阶段1 参数门禁收敛 第5章 输出创建与显示绑定 第8章 调试方法与证据模板 先确认输入语义,再套命令模板复核
阶段2 模式能力收敛 第3章 能力注入与流桥接 + 第4章 会话编排与门禁 第6章 函数级链路走读 能力来源与会话门禁需函数级核验
阶段3 输出对象链收敛 第5章 输出创建与显示绑定 第7章 异常挂点与主链归因 输出链断点需挂回主链节点防误判
阶段4 缓冲协商收敛 第2章 驱动与硬件基线 第8章 调试方法与证据模板 先判底层语义,再固化排障命令
阶段5 显示消费收敛 第1章 主链调用总览 第0章 导读学习指南 最终回到双链并行结论与通用判据

信号 -> 判断 -> 动作

信号 判断 动作
createPreviewOutput failed: 7400101 输出创建参数不成立 回到阶段1:核 profile/surface 与门禁函数
ValidateOutputProfile ... fail mode/profile 能力错配 回到阶段2:对齐 HCS 能力并核 fallback
repeatStream is nullptr 预览输出对象链未闭环 回到阶段3:按 AddOutput -> SetCameraApi 定点
VIDIOC_QUERYBUF failed 缓冲协商失败 回到阶段4:先做 type/memory/plane/index 收敛
SetMetadata Failed with -5 显示消费链失败 回到阶段5:并列输出双链结论,禁止单链覆盖

发布后补链

全章导航

Logo

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

更多推荐