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

第6章 函数级链路走读(驱动到显示)

这章解决什么

第1章已经告诉你“路是通的”。这一章把这条路拆成“函数级台阶”:每个关键函数做什么、吃什么输入、产出什么状态、失败会出现什么信号。

读完本章,你应该能做到两件事:

  1. 看到一条错误日志时,立刻定位到对应函数层。
  2. 从 App 一路单步追到 V4L2,知道每一步参数如何传递和生效。

这章和前后章节的关系(避免“单章孤岛”)

  1. 第1章给你“阶段地图”,第6章给你“函数落脚点”。
  2. 第3章解释“能力从哪来”,第6章解释“能力怎么被函数消费”。
  3. 第4章与第5章解释“会话与输出策略”,第6章把它们合并成可追踪的函数链。

换句话说:第6章不是重复第1章,而是把第1章每个阶段拆成可调试的代码坐标。

全链函数时序图(函数名级别)

App(ArkTS)
  CameraService.initCamera(cameraId)                      // 初始化管理器并缓存相机列表
  -> camera.getCameraManager(context)                     // 进入 NAPI 静态入口
  -> CameraManager.getSupportedCameras()                  // 获取可用 cameraId 与能力入口
  -> CameraService.createCameraInput(cameraId)
  -> CameraManager.createCameraInput(targetCamera)        // 创建 CameraInput 包装对象
  -> CameraInput.open()                                   // 触发服务侧真实开机
  -> CameraService.createPreviewOutput(surfaceId, mode)   // 创建预览输出对象
  -> CameraService.createSession(...)                     // 组会话并启动预览
      |
      v
JS/NAPI
  CameraNapi::CreateCameraManagerInstance()               // 导出 JS CameraManager 对象
  CameraManagerNapi::GetSupportedCameras()                // JS->Native 枚举能力
  CameraManagerNapi::CreateCameraInputInstance()          // JS->Native 创建输入
      |
      v
Framework Native
  CameraManager::GetInstance()                            // 建立 CameraManager 单例并连 SA
  CameraManager::GetSupportedCameras()                    // 拉取服务侧 cameraId/ability
  CameraManager::CreateCameraInput(...)                   // 调 CreateCameraDevice
  CameraInput::Open()                                     // 调远端 device->Open
  CameraManager::CreatePreviewOutput(Profile, Surface)    // 预览参数门禁
  CaptureSession::AddOutput(...)                          // 挂输出流
  CaptureSession::ValidateOutputProfile(...)              // mode/profile 匹配校验
      |
      v
Camera Service
  HCameraService::CreateCameraDevice(cameraId, device)    // 创建服务侧相机设备对象
  HCameraDevice::Open()
  -> HCameraDevice::OpenDevice()
  -> HCameraHostManager::OpenCameraDevice(...)            // 进入 HDI Host 开机链
  HCaptureSession::CommitConfig()                         // 提交会话事务
  -> HCaptureSession::LinkInputAndOutputs()               // 绑定 input/output
  -> HStreamCommon::SetStreamInfo()                       // 落地 w/h/format/queue
  -> HStreamOperator::CreateStreams()                     // 建流
  -> HStreamOperator::CommitStreams()                     // 提交流配置
  HCaptureSession::Start()                                // 迁移到 STARTED
  -> HStreamOperator::StartPreviewStream()                // 仅启动 PREVIEW repeat 流
      |
      v
HDI Host
  CameraHostService::GetCameraIds()                       // 逻辑 cameraId 枚举
  CameraHostService::OpenCamera()                         // 转发到 VDI OpenCamera
      |
      v
VDI
  CameraHostVdiImpl::GetCameraIds()                       // 读取 camera_host_config 能力
  CameraHostVdiImpl::OpenCamera()
  -> CameraHostVdiImpl::CameraPowerUp(...)                // 按 physicsCameraIds 上电
  StreamOperatorVdiImpl::CreateStreams()                  // streamInfo -> streamMap
  StreamOperatorVdiImpl::CommitStreams()                  // mode/settings 提交
  StreamOperatorVdiImpl::Capture(..., isStreaming=true)   // 连续采集请求下发
      |
      v
V4L2 Adapter
  HosV4L2Buffers::V4L2ReqBuffers()                        // 向内核申请缓冲
  HosV4L2Buffers::V4L2AllocBuffer()  (VIDIOC_QUERYBUF)   // 查询并映射缓冲
  HosV4L2Buffers::V4L2QueueBuffer()  (VIDIOC_QBUF)       // 入队等待驱动填帧
      |
      v
Surface Consumer / Display

为什么函数链这样编排(优点与取舍)

设计点 为什么这样设计 优点 代价/取舍 不这样会怎样
AddOutput 前先做 ValidateOutputProfile 先验证能力匹配,再把输出挂会话 避免错误输出污染会话状态 校验逻辑更复杂 会话可能带着无效输出进入 commit
CommitConfig 内再做 ValidateSession + LinkInputAndOutputs 在提交瞬间做全量一致性检查 保证提交态是可运行态 commit 阶段耗时更长 start 才发现缺输入/输出,问题更隐蔽
SetStreamInfo -> CreateStreams -> CommitStreams 三段式 先填参数,再创流,再提交流 参数、创建、能力校验分层清晰 需要维护多份对象映射 出错时难分是参数错还是建流错
StartPreviewStream 仅启动 PREVIEW 避免其它流干扰预览链启动判断 预览问题定位更纯粹 需额外区分流类型 会混入拍照/视频流信号导致误判
V4L2 REQBUFS/QUERYBUF/QBUF 把缓冲问题拆到最细粒度 能精确定位到申请/查询/入队哪一步 适配代码复杂 只有“黑屏”结果,缺中间证据

输入输出链(按函数段)

前半链开机段:枚举 -> 创建输入 -> 开机

源码锚点:

  • applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:136
  • applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:225
  • foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_napi.cpp:85
  • foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_manager_napi.cpp:957
  • foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_manager_napi.cpp:1336
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1129
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1809
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1976
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_input.cpp:181
  • foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp:483
  • foundation/multimedia/camera_framework/services/camera_service/src/hcamera_device.cpp:311
  • foundation/multimedia/camera_framework/services/camera_service/src/hcamera_device.cpp:397
  • foundation/multimedia/camera_framework/services/camera_service/src/hcamera_host_manager.cpp:872
  • drivers/peripheral/camera/hdi_service/v1_0/src/camera_host_service.cpp:171
  • drivers/peripheral/camera/hdi_service/v1_0/src/camera_host_service.cpp:206
  • drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:112
  • drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:165
  • drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:248

源码摘选(ArkTS 发起枚举与 open):

this.mCameraManager = camera.getCameraManager(GlobalContext.get().getCameraAbilityContext());
const cameras = this.mCameraManager.getSupportedCameras();
...
this.mCameraInput = this.mCameraManager.createCameraInput(targetCamera);
await this.mCameraInput.open();

逐行解析:

  1. 第1行:通过 GlobalContext 获取能力上下文,创建 CameraManager,这是后续所有能力查询和对象创建的入口。
  2. 第2行:同步拉取系统可见相机列表,结果直接决定 targetCamera 能否被定位。
  3. 第3行:省略的是摄像头筛选与映射逻辑,目标是得到最终要开的 targetCamera
  4. 第4行:依据目标设备创建 CameraInput,此时只是拿到输入对象,尚未真正开机。
  5. 第5行:open() 才会触发服务侧 CreateCameraDevice/OpenCameraDevice 真实开机链。

源码摘选(NAPI 静态导出与枚举/建输入):

DECLARE_NAPI_STATIC_FUNCTION("getCameraManager", CreateCameraManagerInstance),
...
std::vector<sptr<CameraDevice>> cameraObjList = cameraManagerNapi->cameraManager_->GetSupportedCameras();
...
int retCode = cameraManagerNapi->cameraManager_->CreateCameraInput(cameraInfo, &cameraInput);

逐行解析:

  1. 第1行:把 getCameraManager 暴露给 JS 层,ArkTS 的 camera.getCameraManager(...) 就是走这条导出。
  2. 第2行:省略的是 NAPI 参数检查和 unwrap,确保 cameraManagerNapi 对象有效。
  3. 第3行:NAPI 直接调用 native GetSupportedCameras(),把底层设备对象集合返回给 JS。
  4. 第4行:省略的是 cameraInfo 解析(按 cameraId 或 position/type)。
  5. 第5行:调用 native CreateCameraInput 并返回码,失败会在 NAPI 层抛错给上层。

源码摘选(Framework 到 Service 的开机桥):

sptr<CameraManager>& CameraManager::GetInstance()
...
std::vector<sptr<CameraDevice>> CameraManager::GetSupportedCameras()
...
int32_t retCode = CreateCameraDevice(camera->GetID(), &deviceObj);
...
retCode = deviceObj->Open();

逐行解析:

  1. 第1行:获取 CameraManager 单例,并在首次调用时建立到 camera_service 的连接。
  2. 第2行:省略的是单例初始化和服务代理可用性恢复逻辑。
  3. 第3行:从服务拉取支持设备,形成当前进程可用的 CameraDevice 列表。
  4. 第4行:省略的是 camera 参数合法性与兼容性分支处理。
  5. 第5行:按 cameraId 创建远端设备服务对象,失败说明开机链在 service 入口前就断开。
  6. 第6行:省略的是 CameraInput 封装和错误转换。
  7. 第7行:调用远端 Open,进入 HCameraDevice::OpenDevice -> OpenCameraDevice

源码摘选(Service/HDI/VDI 实际开机):

errorCode = cameraHostManager_->OpenCameraDevice(cameraID_, this, hdiCameraDevice_, isEnableSecCam);
...
int32_t ret = cameraHostVdi->OpenCamera(vdiCameraId, vdiCallbackObj, deviceVdi);
...
if (CameraPowerUp(cameraId, phyCameraIds) != RC_OK) {

逐行解析:

  1. 第1行:Service 层把开机请求交给 HostManager,输出 hdiCameraDevice_ 供后续流操作使用。
  2. 第2行:省略的是 HostManager 内部按 HDI 版本分发 OpenCamera_V1_x 的逻辑。
  3. 第3行:HDI Host 再把逻辑相机 ID 映射到 vendor ID,转给 VDI 的 OpenCamera
  4. 第4行:省略的是回调对象绑定和设备对象包装。
  5. 第5行:VDI 在 open 过程中执行 CameraPowerUp,按 physicsCameraIds 逐路上电。

函数级输入输出:

  • 输入:cameraId/targetCamera、权限态、physicsCameraIds
  • 输出:CameraInput 可用且设备已完成 host/vdi power-up。
  • 失败信号:CreateCameraDevice ... failedHCameraDevice::OpenDevice Failed to open cameracamera powerup failed
  • 设计优点:把“枚举能力”和“真实开机”拆成清晰台阶,前半链失败可在 preview 前直接收敛,不会误判为显示链问题。

App会话组装段(把“选择”变成“请求”)

源码锚点:

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

源码摘选(选预览 profile 并创建输出):

previewProfile = previewProfiles.find(item => item.size.width === size.width &&
  item.size.height === size.height && item.format === 1003);
this.mPreviewOutput = this.mCameraManager.createPreviewOutput(previewProfile, surfaceId);

逐行解析:

  1. 第1-2行:先按 width/height/format=1003 精确匹配预览档位,优先拿到 YUV 预览组合。
  2. 第3行:把选中的 previewProfile + surfaceId 交给框架创建 PreviewOutput,这是会话 addOutput 的前提对象。

源码摘选(会话主流程):

this.mCaptureSession?.beginConfig();
this.mCaptureSession?.addInput(this.mCameraInput);
this.mCaptureSession?.addOutput(this.mPreviewOutput);
await this.mCaptureSession?.commitConfig();
await this.mCaptureSession?.start();

逐行解析:

  1. 第1行:会话进入配置窗口,后续输入输出挂接必须在此窗口内完成。
  2. 第2行:把已 open 的 CameraInput 绑定到会话,建立输入源。
  3. 第3行:把预览输出绑定到会话,建立显示目标流。
  4. 第4行:提交配置,触发 Service 侧 ValidateSession/LinkInputAndOutputs/CreateStreams/CommitStreams
  5. 第5行:启动会话,触发 StartPreviewStream 进入持续预览。

函数级输入输出:

  • 输入:modepreviewProfilessurfaceIdcameraInput
  • 输出:mPreviewOutputmCaptureSession 已提交并启动。
  • 失败信号:previewOutput is nullcreateSession abort: preview output not added
  • 设计优点:App 先兜底 previewOutput,可阻止“空输出却继续 commit”的伪成功。

Framework门禁段(校验输出并下发到 Service)

源码锚点:

  • foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp:841
  • foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp:4448

源码摘选(CreatePreviewOutput 参数门禁):

CHECK_ERROR_RETURN_RET_LOG((profile.GetCameraFormat() == CAMERA_FORMAT_INVALID) ||
    (profile.GetSize().width == 0) || (profile.GetSize().height == 0),
    CameraErrorCode::INVALID_ARGUMENT, "CreatePreviewOutput invalid fomrat...");

逐行解析:

  1. 第1行:检查 format 是否无效;无效即不允许继续创建输出对象。
  2. 第2行:检查宽高是否为 0;任一为 0 说明 profile 语义不完整。
  3. 第3行:命中条件就直接返回 INVALID_ARGUMENT,并记录门禁日志。

源码摘选(AddOutput + SetCameraApi):

int32_t errCode = captureSession->AddOutput(output->GetStreamType(), output->GetStream());
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. 第1行:先把输出流挂入会话,返回值反映挂载动作是否成功。
  2. 第2行:仅当输出类型是 PREVIEW 才尝试取 IStreamRepeat,其它输出类型不走预览 API 设置。
  3. 第3行:执行类型转换,stream 能否转成 repeatStream 是预览链闭环关键检查点。
  4. 第4行:结束预览类型分支。
  5. 第5行:若转换成功,设置 API 兼容版本,保证后续预览调用语义一致。
  6. 第6行:调用真正的 SetCameraApi
  7. 第7-8行:若 repeatStream 为空则输出关键错误日志,典型表现是“start success 但预览无效”。

源码摘选(ValidateOutputProfile + NORMAL 回退):

auto modeIt = profileMap.find(modeName);
if (modeIt != profileMap.end() && validateOutputProfileFunc(validateProfile, modeIt->second)) {
    return true;
}
if (modeName != SceneMode::NORMAL) {
    auto normalIt = profileMap.find(SceneMode::NORMAL);
    ... fallback to NORMAL ...
}

逐行解析:

  1. 第1行:先在当前 mode 的 profile 集合里查找能力项。
  2. 第2行:若当前 mode 下匹配成功,直接通过门禁。
  3. 第3行:命中成功路径立即返回,不再做 fallback。
  4. 第4行:当前 mode 失败后,只有“非 NORMAL 模式”才允许继续回退。
  5. 第5行:查找 NORMAL 模式能力作为兜底集合。
  6. 第6行:省略的是回退验证细节,核心语义是“当前 mode 不行时再试 NORMAL”。

函数级输入输出:

  • 输入:Profile(width/height/format)outputTypecurrent mode
  • 输出:输出流是否允许加入会话。
  • 失败信号:INVALID_ARGUMENTCanAddOutput check failedrepeatStream is nullptr
  • 设计优点:Framework 把“参数合法性”和“能力匹配”前置,缩短错误反馈闭环。

Service提交流段(提交配置并构建可运行流)

源码锚点:

  • foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:651
  • foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:550
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_common.cpp:170
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1609
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1063

源码摘选(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);

逐行解析:

  1. 第1行:先做会话完整性校验(输入/输出/状态合法性)。
  2. 第2行:校验失败直接返回,阻止脏状态进入后续流程。
  3. 第3行:校验通过后执行输入输出绑定。
  4. 第4行:绑定失败直接返回并打印 commit 失败日志。
  5. 第5行:前两步成功后,状态机迁移到 COMMITTED,允许 Start

源码摘选(SetStreamInfo 把参数写进 StreamInfo):

streamInfo.v1_0.width_ = width_;
streamInfo.v1_0.height_ = height_;
streamInfo.v1_0.format_ = pixelFormat;
streamInfo.v1_0.bufferQueue_ = new BufferProducerSequenceable(producer_);

逐行解析:

  1. 第1行:把目标宽度写入 streamInfo
  2. 第2行:把目标高度写入 streamInfo
  3. 第3行:把像素格式写入 streamInfo,后续会下沉到 VDI/V4L2。
  4. 第4行:绑定生产者队列,建立上层输出到下层缓冲的桥。

源码摘选(CreateStreams / CommitStreams):

hdiRc = (CamRetCode)(streamOperatorV1_1->CreateStreams_V1_1(streamInfos));
...
hdiRc = (CamRetCode)(streamOperatorV1_1->CommitStreams_V1_1(
    static_cast<OHOS::HDI::Camera::V1_1::OperationMode_V1_1>(operationMode), setting));

逐行解析:

  1. 第1行:先按 streamInfos 创建设备侧流对象。
  2. 第2行:省略的是返回码检查与错误转换。
  3. 第3-4行:再提交流配置(含 operationMode 与 metadata setting),使流进入可运行状态。

函数级输入输出:

  • 输入:session input/outputoperationModemetadata settingstreamInfo[]
  • 输出:HDI 层已创建并提交流。
  • 失败信号:ValidateSessionOutputs No outputs presentGetStreamOperator is nullCommitStreams failed
  • 设计优点:Service 层统一会话事务,保证跨层提交时状态一致、可追踪。

预览启动段(Service 启动预览流)

源码锚点:

  • foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:1063
  • foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:864

源码摘选(Start 状态迁移):

bool isTransferSupport = stateMachine_.CheckTransfer(CaptureSessionState::SESSION_STARTED);
...
errorCode = hStreamOperatorSptr->StartPreviewStream(settings, cameraPosition);
if (errorCode == CAMERA_OK) {
    isSessionStarted_ = true;
}
stateMachine_.Transfer(CaptureSessionState::SESSION_STARTED);

逐行解析:

  1. 第1行:先检查状态机是否允许迁移到 SESSION_STARTED
  2. 第2行:省略的是状态非法时的提前返回逻辑。
  3. 第3行:启动预览流,携带当前 settings 和 cameraPosition。
  4. 第4行:仅当启动成功时才把 isSessionStarted_ 置为 true。
  5. 第5行:写入启动成功标记。
  6. 第6行:状态机迁移到 SESSION_STARTED

源码摘选(StartPreview 只拉 PREVIEW 流):

auto repeatType = curStreamRepeat->GetRepeatStreamType();
if (repeatType != RepeatStreamType::PREVIEW) {
    continue;
}
errorCode = curStreamRepeat->Start(settings);

逐行解析:

  1. 第1行:读取当前 repeat 流类型。
  2. 第2-3行:非 PREVIEW 流直接跳过,不参与预览启动。
  3. 第4行:仅对预览流执行 Start(settings)

函数级输入输出:

  • 输入:settings metadatacameraPositionrepeatStreams
  • 输出:预览流进入 running。
  • 失败信号:Need to call after committing configurationFailed to start preview
  • 设计优点:预览启动单独过滤 PREVIEW 流,避免被 LIVEPHOTO/其它流噪声干扰。

VDI缓冲链段(VDI / V4L2 建缓冲并进队)

源码锚点:

  • 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
  • drivers/peripheral/camera/vdi_base/v4l2/src/stream_operator/stream_operator_vdi_impl.cpp:463
  • 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:358
  • drivers/peripheral/camera/vdi_base/common/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp:185

源码摘选(VDI 把 VdiStreamInfo 转成 StreamConfiguration):

scg.width = info.width_;
scg.height = info.height_;
scg.format = BufferAdapter::PixelFormatToCameraFormat(pf);

逐行解析:

  1. 第1行:把上游 streamInfo 的宽度映射到 VDI 配置。
  2. 第2行:把高度映射到 VDI 配置。
  3. 第3行:把像素格式转换为 camera HAL 识别格式,供后续缓冲协商使用。

源码摘选(VDI Capture 请求分发):

auto request = std::make_shared<CaptureRequest>(captureId, info.streamIds_.size(), captureSetting,
    info.enableShutterCallback_, isStreaming);
for (auto id : info.streamIds_) {
    if (streamMap_[id] == nullptr || streamMap_[id]->AddRequest(request)) {
        return DEVICE_ERROR;
    }
}

逐行解析:

  1. 第1-2行:构造一次捕获请求,包含 captureId、目标流数、setting 与流模式。
  2. 第3行:遍历本次请求涉及的每个 streamId。
  3. 第4行:若 stream 未注册或加请求失败,立即返回设备错误。
  4. 第5行:失败分支返回,阻止无效请求继续下发。
  5. 第6行:结束循环,表示所有目标流都接收了请求。

源码摘选(V4L2 关键 ioctl):

if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) { return RC_ERROR; }
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { return RC_ERROR; }
int rc = ioctl(fd, VIDIOC_QBUF, &buf);
if (rc < 0) { return RC_ERROR; }

逐行解析:

  1. 第1行:先向内核申请缓冲池;失败说明缓冲模型与驱动能力不匹配。
  2. 第2行:查询单个缓冲参数;失败常见于 plane/type/index 语义错误。
  3. 第3行:把缓冲入队,等待驱动写帧。
  4. 第4行:入队失败则直接返回错误,后续不会有稳定预览帧。

函数级输入输出:

  • 输入:streamInfoscaptureSettingbufferType/memoryType
  • 输出:buffer 可用并入队,驱动开始产帧。
  • 失败信号:DYNAMIC_STREAM_SWITCH_NOT_SUPPORTVIDIOC_QUERYBUF failedVIDIOC_QBUF failed
  • 设计优点:把“能力不支持”和“缓冲不一致”拆开,利于分层定位与修复。

各函数段之间怎么互相影响(关键因果桥)

上游函数段 下游受影响段 影响机制 常见误解
前半链开机段 App会话组装段 / Framework门禁段 / Service提交流段 前半链 open 未成功,后续输出链一定不稳定 只盯 preview 日志,忽略 open 失败上下文
App会话组装段 Framework门禁段 profile 进入 CreatePreviewOutput 参数校验 只看 Framework 日志,忽略 App 选错 profile
Framework门禁段 Service提交流段 无效输出会让 Commit 阶段失败或进入伪成功 以为 Commit 报错就是 Service 独立问题
Service提交流段 VDI缓冲链段 width/height/format 下沉到 stream 配置 把 QBUF 错误全归咎于驱动
预览启动段 VDI缓冲链段 未真正启动 PREVIEW 时下游不会持续有帧 看到 session started 就认为预览已有效启动
VDI缓冲链段 显示链可见性 QBUF/DQBUF 决定能否持续出帧 把显示黑屏误判为 UI 绘制问题

这张表的目的:遇到下游错误时,先回看最近的上游输入来源,不要只盯报错点本身。

状态变更点(最容易定位错位)

位置 状态变量/容器 变化 触发函数 失败后果
App hasPreviewOutput false -> true createSession add preview output 为 false 时会话直接 abort
Native Session 输出集合 插入 preview/photo 输出 CaptureSession::AddOutput 后续 CommitConfig 无输出可提交流
Service Session stateMachine_ IN_PROGRESS -> COMMITTED -> STARTED CommitConfig/Start 状态迁移失败会报 invalid state
Service Session isSessionStarted_ false -> true HCaptureSession::Start UI 可能看到启动但无帧
Service Stream streamInfo 字段 写入 w/h/format/bufferQueue HStreamCommon::SetStreamInfo 下游格式错位
VDI streamMap_ 新增 streamId->stream StreamOperatorVdiImpl::CreateStreams capture 时找不到 streamId
V4L2 内核队列状态 REQBUFS -> QUERYBUF -> QBUF HosV4L2Buffers::* 黑屏或无帧

关键参数卡(函数级追踪)

参数卡1:previewProfile.width/height/format

  • 参数位置:ArkTS 运行时对象(来自能力集 previewProfiles)。
  • 读取位置:CameraService.ts:269-274 选型。
  • 生效位置:
    1. CameraManager::CreatePreviewOutput 传给 service;
    2. HStreamCommon::SetStreamInfo 写入 streamInfo.v1_0
    3. StreamOperatorVdiImpl::StreamInfoToStreamConfiguration 落到 scg
  • 常见误配后果:createPreviewOutput 失败或后续 QUERYBUF/QBUF 失败。
  • 最小验证动作:查日志 selected profilestreamId width/height/format 是否一致。

参数卡2:operationMode(mode)

  • 参数位置:会话模式(NORMAL/VIDEO 等)。
  • 读取位置:CaptureSession::ValidateOutputProfileCommitStreams
  • 生效位置:
    1. profile 校验优先匹配当前 mode;
    2. CommitStreams_V1_1 以 mode 下发到 HDI/VDI。
  • 常见误配后果:当前 mode 校验失败,只能 fallback NORMAL,甚至直接失败。
  • 最小验证动作:同窗观察 ValidateOutputProfile in mode(...) 与 commit mode。

参数卡3:streamType / outputType

  • 参数位置:AddOutput(output->GetStreamType())
  • 读取位置:CaptureSession::AddOutput
  • 生效位置:
    1. Service/VDI 选择 REPEAT/CAPTURE 流路径;
    2. StartPreviewStream 仅启动 RepeatStreamType::PREVIEW
  • 常见误配后果:流被加入但不是预览流,导致“start success 但无显示”。
  • 最小验证动作:检查 AddOutput StreamTypeStartPreview 中 repeatType 过滤是否一致。

枚举速查(本章最常用)

枚举/值 含义 在哪起作用 常见风险
SceneMode::NORMAL 普通模式 ValidateOutputProfile fallback 与 CommitStreams mode 不一致导致 profile 判错
CaptureOutputType::PREVIEW 预览输出类型 AddOutput / SetCameraApi 非预览类型误进预览诊断
StreamType::REPEAT 重复流(预览/录像类) StartPreviewStream 获取流集合 REPEAT 集合为空导致无预览
RepeatStreamType::PREVIEW 预览重复流 StartPreviewStream 过滤条件 类型不匹配被跳过
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 多平面采集缓冲 V4L2AllocBuffer plane 数配置不一致 -> QUERYBUF EINVAL

断点定位建议(按信号反推函数)

1. createSession abort: preview output not added
  -> 查 CameraService.createPreviewOutput / createSession

2. CanAddOutput check failed / INVALID_ARGUMENT
  -> 查 CaptureSession::ValidateOutputProfile + mode/profile

3. Need to call BeginConfig before committing
  -> 查 HCaptureSession::CommitConfig 前置状态

4. Need to call after committing configuration
  -> 查 HCaptureSession::Start 状态迁移

5. Failed to start preview
  -> 查 HStreamOperator::StartPreviewStream 中 PREVIEW 过滤与 Start 返回码

6. VIDIOC_QUERYBUF failed / VIDIOC_QBUF failed
  -> 查 HosV4L2Buffers 的 bufferType/plane/size 一致性

函数级排障剧本(可直接照着走)

Step1: 先锁“前半链是否开机成功”
  看 getSupportedCameras/createCameraInput/open 是否完成

Step2: 再锁“入口参数”
  看 createPreviewOutput 的 profile(width/height/format)

Step3: 再锁“会话门禁”
  看 ValidateOutputProfile + CommitConfig 的状态迁移

Step4: 再锁“流启动对象”
  看 StartPreviewStream 是否真的命中 PREVIEW repeat stream

Step5: 最后锁“驱动缓冲”
  看 REQBUFS/QUERYBUF/QBUF 三段哪一段首次报错

Step6: 回溯最近上游输入
  用上一节因果桥表,回到最可能的上游函数段修参数

这个剧本的价值是把“函数级定位”从经验动作变成可复现流程。

模块卡(本章函数密集,按核心函数卡片化)

模块名 它是干什么的 输入 输出 失败信号 下一跳
CameraManager::GetSupportedCameras 向服务拉可用相机与能力入口 serviceProxy + cameraId list CameraDevice[] serviceProxy null / 返回空集 CreateCameraInput
CameraManager::CreateCameraInput 创建 CameraInput 并绑定 ICameraDeviceService CameraDevice CameraInput CreateCameraDevice 失败 CameraInput::Open
HCameraDevice::OpenDevice 完成权限检查并触发 host 开机 cameraId + callerToken hdiCameraDevice_ 可用 Failed to open camera OpenCameraDevice
CaptureSession::ValidateOutputProfile 判断输出 profile 是否被当前 mode 能力集接受 outputProfile + outputType + mode bool Not in the profiles set AddOutput
HCaptureSession::CommitConfig 将会话从配置态切到已提交态 input/output + streamOperator state=COMMITTED ValidateSessionOutputs No outputs present Create/CommitStreams
HStreamCommon::SetStreamInfo 把宽高格式和 queue 填入 streamInfo width/height/format/producer StreamInfo_V1_1 format 映射异常默认回退 CreateStreams
StreamOperatorVdiImpl::CreateStreams 在 VDI 创建并登记 stream 对象 VdiStreamInfo[] streamMap_ 填充 stream already created/invalid argument CommitStreams
HosV4L2Buffers::V4L2AllocBuffer 查询并校验内核缓冲 fd + frameSpec 可映射 buffer VIDIOC_QUERYBUF failed V4L2QueueBuffer

知识点依赖与跨章连接(函数级连接)

本章函数段 依赖章节 为什么要跳 回到本章后的收益
ParseCapability -> ValidateOutputProfile 第3章、第4章 先理解能力注入与会话门禁语义 可准确判断“参数错”还是“状态错”
createPreviewOutput -> AddOutput -> SetCameraApi 第5章 先理解输出对象链的创建与绑定 可快速定位 repeatStream 类异常
CreateStreams -> QUERYBUF/QBUF 第2章 先理解格式/缓冲/plane 的底层约束 可把错误收敛到驱动缓冲层
函数级信号归口到动作 第8章、第9章 需要调试剧本和复盘模板 函数断点可直接转化为可执行交付

信号 -> 判断 -> 动作(函数级速查)

信号 判断 动作
ValidateOutputProfile ... fail 函数门禁拒绝当前 profile 回溯 ParseCapability 输入并对齐 HCS 能力
Need to call after committing configuration Start 调用时序错误 先修 CommitConfig 顺序,再复测 Start
repeatStream is nullptr PREVIEW 输出对象链断裂 回查 createPreviewOutput -> AddOutput -> SetCameraApi
VIDIOC_QUERYBUF/QBUF failed 缓冲参数与驱动能力不一致 降档 + 核 bufferType/plane/size 一致性

本章最小动作

拿一段“start success 但黑屏”的日志,按下面 8 个函数顺序定位,一步一判:

  1. CameraService.initCamera/getSupportedCameras(是否拿到目标 cameraId);
  2. CameraService.createCameraInput -> open(是否真实开机成功);
  3. CameraService.createSession(preview 是否真 add);
  4. CaptureSession::ValidateOutputProfile(profile/mode 是否匹配);
  5. HCaptureSession::CommitConfig(是否真正 commit);
  6. HStreamOperator::StartPreviewStream(是否启动到 PREVIEW 流);
  7. StreamOperatorVdiImpl::CreateStreams/CommitStreams(stream 是否有效注册);
  8. HosV4L2Buffers::V4L2AllocBuffer/QBUF(缓冲是否能进队)。

只要这 8 步走完,故障就会稳定落到“前半链开机问题”“会话门禁问题”或“缓冲显示问题”其中之一。

本章一句话小结

函数级学习的关键不是“记住更多函数名”,而是建立“输入从哪来、状态何时变、错误往哪传”的稳定因果链,这条链一旦建立,复杂问题也能被拆成可执行的小步排查。


发布后补链

全章导航

Logo

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

更多推荐