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

第1章 主链调用总览(图表)

本章目标

这章只做一件事:把“应用点预览”到“屏幕看到画面”的主链走通。
你读完后要能做到:

  1. 说清楚每一层做了什么(不是只记函数名)。
  2. 拿着日志能快速判断断在 App、Service、VDI、还是 V4L2。
  3. 按源码路径直接跳转到关键函数。

这一章怎么读最省力

推荐阅读节拍:

  1. 先看“主链一图看全”,建立空间感。
  2. 再看“参数流向图”,建立参数的时间感。
  3. 最后按 1~5 步时序把源码锚点串起来。

这样读的好处是:不会陷入“每个函数都懂一点,但连不成完整因果链”的常见困境。

主链一图看全(先建立空间感)

App(ArkTS)
  CameraService.createPreviewOutput()
  -> createSession(begin/addInput/addOutput/commit/start)
      |
      v
Framework(Native)
  CameraManager::CreatePreviewOutput()
      |
      v
Camera Service
  HCaptureSession::CommitConfig()
  -> HStreamOperator::CreateStreams()
  -> HStreamOperator::CommitStreams()
  -> HStreamOperator::StartPreviewStream()
      |
      v
VDI
  StreamOperatorVdiImpl::CreateStreams/CommitStreams
      |
      v
V4L2
  VIDIOC_REQBUFS -> VIDIOC_QBUF -> DQBUF
      |
      v
Surface/XComponent -> 屏幕显示

参数流向图(回答“值从哪来,到哪生效”)

[ArkTS previewProfile]
  width/height/format
      |
      v
CameraManager::CreatePreviewOutput(profile,...)
  -> metaFormat + w/h 传给 serviceProxy->CreatePreviewOutput
      |
      v
HStreamCommon::SetStreamInfo
  streamInfo.width_/height_/format_
      |
      v
VDI StreamInfoToStreamConfiguration
  config.width/height/format
      |
      v
V4L2 buffer与format配置生效
  不一致 -> QUERYBUF/QBUF失败

术语桥(把同一个概念在不同层的名字对齐)

你在日志里看到的词 在代码里的常见对象 这两个词为什么会被混淆
previewProfile Profile / StreamInfo / StreamConfiguration 都在描述“预览规格”,只是层级不同
mode SceneMode / operationMode 一个偏业务场景,一个偏提交流程
output CaptureOutput / IStreamRepeat / streamId 上层是对象,下层是流句柄
start success SESSION_STARTED 会话启动成功不等于显示链已经可见

为什么这样分层设计(优点与取舍)

设计点 为什么这样设计 优点 代价/取舍 不这样会怎样
App 先选 profile 再建输出 把“业务目标分辨率/格式”前置,避免后面盲建流 错误尽量早暴露,定位更快 App 侧要处理更多能力匹配逻辑 容易在 Service/VDI 才爆参数错,排障成本高
Framework 做参数合法性门禁 在跨进程前拦住明显非法参数 减少无效 IPC 与脏状态传播 多一层校验代码 非法参数会在更下层才触发,日志更难读
Service 使用 CommitConfig -> Start 状态机 把“配置正确”和“开始运行”拆成两个确定阶段 状态清晰、可回滚、可并发管理 状态机理解成本更高 直接 start 容易出现半配置态运行
VDI 再做一次 CheckStreamsSupported 让设备能力做最终裁决 避免把不支持配置压到驱动崩溃 提交路径更长 驱动层才发现不支持,错误更重
V4L2 用 REQBUFS/QUERYBUF/QBUF 三段式 先申请、再核验、再入队,保证缓冲闭环 缓冲问题可分段定位 需要维护更多中间状态 黑屏时只能看到“无帧”,定位粒度差

按时间顺序走一遍(每步都给源码锚点)

1) App入口:先选 profile,再绑 surface

作用:决定“要什么规格的预览流”。

源码锚点:

  • applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:251

源码截取:

public async createPreviewOutput(surfaceId: string, mode: string) {
  const size = SettingManager.getInstance().getPreviewSize(mode);
  let previewProfiles = this.outputCapability.previewProfiles;
  previewProfile = previewProfiles.find(item => item.size.width === size.width &&
    item.size.height === size.height && item.format === 1003);
  Log.info(`${TAG} createPreviewOutput selected profile: ${JSON.stringify(previewProfile)}`);
  this.mPreviewOutput = this.mCameraManager.createPreviewOutput(previewProfile, surfaceId);
}

这一段在整体里的意义:

  • 输入:mode、能力集 previewProfilessurfaceId
  • 输出:mPreviewOutput(后续会话要用)。
  • 典型失败:createPreviewOutput failed: 7400101,常见是 profile 不匹配。
  • 设计优点:把“业务选择”前置在 App,错误能更早暴露,不会延迟到驱动侧。

2) Framework:把 profile 变成可下发的 native 流参数

作用:把 ArkTS 选到的 profile 转给 service 层。

源码锚点:

  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530

源码截取:

int CameraManager::CreatePreviewOutput(Profile &profile, sptr<Surface> surface, sptr<PreviewOutput> *pPreviewOutput)
{
    CHECK_ERROR_RETURN_RET_LOG((profile.GetCameraFormat() == CAMERA_FORMAT_INVALID) ||
        (profile.GetSize().width == 0) || (profile.GetSize().height == 0),
        CameraErrorCode::INVALID_ARGUMENT, "CreatePreviewOutput invalid fomrat...");

    camera_format_t metaFormat = GetCameraMetadataFormat(profile.GetCameraFormat());
    int32_t retCode = serviceProxy->CreatePreviewOutput(
        surface->GetProducer(), metaFormat, profile.GetSize().width, profile.GetSize().height, streamRepeat);
}

这一段在整体里的意义:

  • 输入:profile.format/width/height
  • 输出:serviceProxy->CreatePreviewOutput(...) 参数。
  • 门禁:宽高为 0、format 无效会直接返回。
  • 设计优点:跨层前做合法性收敛,避免错误参数污染后续状态机。

3) Service会话编排:commit 时做校验并链接输入输出

作用:把“配置态”转成“可运行态”。

源码锚点:

  • foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:651
  • foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:550

源码截取A(Commit主干):

int32_t HCaptureSession::CommitConfig()
{
    errorCode = ValidateSession();
    CHECK_ERROR_RETURN(errorCode != CAMERA_OK);
    errorCode = LinkInputAndOutputs();
    CHECK_ERROR_RETURN_LOG(errorCode != CAMERA_OK, "...Failed to commit config...");
    stateMachine_.Transfer(CaptureSessionState::SESSION_CONFIG_COMMITTED);
}

源码截取B(输出门禁):

int32_t HCaptureSession::ValidateSessionOutputs()
{
    auto hStreamOperatorSptr = GetStreamOperator();
    CHECK_ERROR_RETURN_RET_LOG((hStreamOperatorSptr == nullptr || hStreamOperatorSptr->GetStreamsSize() == 0),
        CAMERA_INVALID_SESSION_CFG, "HCaptureSession::ValidateSessionOutputs No outputs present");
    return CAMERA_OK;
}

这一段在整体里的意义:

  • 输入:已 add 的 input/output。
  • 输出:提交成功后的 session 状态迁移。
  • 门禁:没输出流、mode 不合法、链接失败都会阻断。
  • 设计优点:将“能不能运行”与“开始运行”分离,便于定位 commit 期与 start 期问题。

4) Service->HDI/VDI:创建流并提交流

作用:把逻辑流变成设备可执行流。

源码锚点:

  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1055
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1609
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_common.cpp:170
  • drivers/peripheral/camera/vdi_base/v4l2/src/stream_operator/stream_operator_vdi_impl.cpp:213
  • drivers/peripheral/camera/vdi_base/v4l2/src/stream_operator/stream_operator_vdi_impl.cpp:312

源码截取A(Service侧 Create+Commit):

int32_t HStreamOperator::CreateAndCommitStreams(...)
{
    int retCode = CreateStreams(streamInfos);
    CHECK_ERROR_RETURN_RET(retCode != CAMERA_OK, retCode);
    return CommitStreams(deviceSettings, operationMode);
}

源码截取B(VDI参数落地):

void StreamOperatorVdiImpl::StreamInfoToStreamConfiguration(StreamConfiguration &scg, const VdiStreamInfo info)
{
    scg.width = info.width_;
    scg.height = info.height_;
    PixelFormat pf = static_cast<PixelFormat>(info.format_);
    scg.format = BufferAdapter::PixelFormatToCameraFormat(pf);
}

源码截取C(Service侧把流规格写入 StreamInfo):

void HStreamCommon::SetStreamInfo(StreamInfo_V1_1 &streamInfo)
{
    streamInfo.v1_0.width_ = width_;
    streamInfo.v1_0.height_ = height_;
    streamInfo.v1_0.format_ = pixelFormat;
    streamInfo.v1_0.bufferQueue_ = new BufferProducerSequenceable(producer_);
}

这一段在整体里的意义:

  • 输入:StreamInfo(width/height/format/type)
  • 输出:StreamConfiguration + CommitStreams
  • 门禁:CheckStreamsSupported 返回不支持时,直接失败。
  • 设计优点:设备能力做最终裁决,减少“上层以为能跑、底层实际不支持”的错位。

5) 启动预览与V4L2缓冲:真正决定“有没有帧”

作用:流启动成功后,缓冲队列是否正常决定是否能显示。

源码锚点:

  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:864
  • drivers/peripheral/camera/vdi_base/common/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp:75
  • drivers/peripheral/camera/vdi_base/common/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp:185

源码截取A(StartPreview):

int32_t HStreamOperator::StartPreviewStream(...)
{
    for (auto& item : repeatStreams) {
        auto curStreamRepeat = CastStream<HStreamRepeat>(item);
        if (curStreamRepeat->GetRepeatStreamType() != RepeatStreamType::PREVIEW) continue;
        errorCode = curStreamRepeat->Start(settings);
        if (errorCode != CAMERA_OK) {
            MEDIA_ERR_LOG("HStreamOperator::Start(), Failed to start preview, rc: %{public}d", errorCode);
            break;
        }
    }
}

源码截取B(V4L2缓冲门禁):

if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
    CAMERA_LOGE("does not support memory mapping %{public}s\n", strerror(errno));
    return RC_ERROR;
}
...
int rc = ioctl(fd, VIDIOC_QBUF, &buf);
if (rc < 0) {
    CAMERA_LOGE("ioctl VIDIOC_QBUF failed: %{public}s\n", strerror(errno));
    return RC_ERROR;
}

这一段在整体里的意义:

  • 输入:已提交流 + buffer 配置。
  • 输出:有帧进入 surface。
  • 门禁:REQBUFS/QBUF 任一失败,显示链直接断。
  • 设计优点:缓冲链路分段可观测,能把“格式问题”和“队列问题”拆开定位。

一眼判断“断在哪层”(章节级决策表)

观测信号 优先怀疑层级 第一检查点 第二检查点 下一章建议
createPreviewOutput failed: 7400101 App/Framework 参数匹配 App profile 选型 Framework 参数门禁 第5章
ValidateSessionOutputs No outputs present Service 会话组装 addOutput 是否执行 Commit 前状态机 第4章
repeatStream is nullptr Framework 到 Service 输出绑定 AddOutput 返回值 outputType/streamType 对齐 第6章
VIDIOC_QUERYBUF/QBUF failed V4L2 缓冲链 format/plane 一致性 memory type 匹配 第2章 + 第8章
Start success 但黑屏 显示链或缓冲链 Surface 绑定 BufferQueue 投递 第5章 + 第8章

关键参数速查(初学者高频)

参数 是什么 从哪读 在哪生效 常见坑
previewProfile.width/height/format 预览输出规格 ArkTS 能力集 previewProfiles Service/VDI 建流与 V4L2 缓冲 选了不支持组合
mode(operationMode) 会话工作模式 会话配置阶段 CommitStreams / CheckStreamsSupported mode 与能力表不一致
streamType 流用途(预览/录像/拍照) addOutput 时确定 Start 路径选择 把拍照流异常误判成预览链异常

故障分叉图(从日志到动作)

现象:start 成功但黑屏
  -> A. 看 App 是否出现 createPreviewOutput failed/7400101
     -> 是:先修 profile 选型(能力集 + width/height/format)
     -> 否:继续
  -> B. 看 CommitConfig 前后是否有 ValidateSessionOutputs / LinkInputAndOutputs 错误
     -> 是:先修会话组装(input/output/mode)
     -> 否:继续
  -> C. 看 V4L2 是否有 REQBUFS/QBUF 错误
     -> 是:先修格式与缓冲模型一致性
     -> 否:继续
  -> D. 查 surface 绑定与尺寸一致性(显示链章节)

知识点连接(把本章和后续章节绑紧)

  1. 本章解决“主链时序与层间职责”。
  2. 第3章会细化“能力注入为什么会影响这里的 profile 分叉”。
  3. 第4章会细化“Commit/Start 门禁为什么决定时序是否可持续”。
  4. 第5章会细化“createPreviewOutput/surface 为什么决定最后是否可见”。
  5. 第6章会把这章的 5 个阶段下钻到函数级输入输出。

知识点依赖与跨章连接(本章显式块)

本章阶段 前置依赖 下一跳章节 跳转理由
驱动与能力准备阶段 第2章参数基线 第3章 需要把 HCS 参数注入链讲透
会话组装与门禁阶段 第3章能力结果 第4章 需要理解 Commit/Start 的状态机门禁
输出绑定与可见性阶段 第4章会话状态 第5章 需要理解 profile/surface 绑定为何影响黑屏
函数级归因阶段 第1章整体时序 第6章 需要把阶段断点压缩成函数断点
发散异常归口阶段 第1章分叉图 第7章、第8章 需要把异常挂点化并固化为可执行剧本

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

信号 判断 动作
createPreviewOutput failed: 7400101 旅程断在“输出创建”阶段 先核 previewProfile.width/height/format,再跳第5章
ValidateSessionOutputs No outputs present 旅程断在“会话组装”阶段 回查 addOutput 顺序与输出对象有效性,跳第4章
repeatStream is nullptr 旅程断在“预览流绑定”阶段 检查 PREVIEW 输出类型与绑定对象,跳第6章
VIDIOC_QUERYBUF/QBUF failed 旅程断在“缓冲建队”阶段 降档验证并核对格式/plane,跳第2章+第8章
Start success + Bufferqueue -5 控制链已推进,显示链断裂 进入显示链分支排障,跳第5章+第7章

本章最小动作

拿一段你自己的日志,只做 4 步:

  1. selected profile
  2. CommitConfig 前后错误;
  3. StartPreview 是否报错;
  4. REQBUFS/QBUF 是否失败。
    四步就能把问题先定位到“配置链”还是“缓冲显示链”。

下一跳建议

  1. 想看每个函数更细粒度输入输出:去第6章。
  2. 想系统看会话门禁和状态机:去第4章。
  3. 想专攻黑屏/尺寸不一致:去第2章和第3章。

本章一句话小结

本章把“现象 -> 层级 -> 函数段 -> 最小动作”这条判断链建立起来,后续章节不再重复讲全旅程,而是各自把其中一个关键段讲深讲透。


发布后补链

全章导航

Logo

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

更多推荐