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

第5章 输出创建与显示绑定(ArkTS/HAP)

本章只解决一个问题

当驱动与服务层都已基本可用时,为什么应用侧仍会出现黑屏、7400101、输出对象为空,以及如何把 profile + surface + session 串成一条稳定可显示的预览链。

先建立一个读者视角

你在这一章需要掌握的不是“某个函数怎么写”,而是“输出对象什么时候才算真正可用”:

  • 不是 createSession 返回就算可用。
  • 不是 start() 调用成功就算可用。
  • 必须满足:能力可选、profile可匹配、surface可绑定、stream可挂载、运行包版本一致。

三句流程版

  1. ArkTS 在 onSurfacePrepare 拿到 surfaceId 后,按 outputCapability.previewProfiles 选出 previewProfile
  2. Framework 用 CreatePreviewOutput(profile, surface) 向服务层创建 HStreamRepeat,再由 CaptureSession::AddOutput 绑定。
  3. 只有当 profile、surface、session 三者语义一致且运行包确实更新,预览帧才会持续进入显示队列;否则就会出现“控制链成功、显示链失败”的假成功。

端到端输出绑定总图(流程 + 模块 + 函数 + 作用)

模块1:UI Surface事件模块(定义:产生并维护可渲染surface句柄)
  函数:CameraBasicFunction.onSurfacePrepare(data)
  作用:接收XComponent回调,把 data.surfaceId 写入 mSurfaceId
  关键参数:surfaceId
        |
        | surfaceId
        v
模块2:ArkTS 预览编排模块(定义:组织预览启动顺序)
  函数:CameraBasicFunction.startPreview()
  作用:串联 createPreviewOutput -> createPhotoOutput(可选) -> createSession
  关键参数:mSurfaceId, mCurrentMode
        |
        | surfaceId + mode
        v
模块3:ArkTS 输出构建模块(定义:选择profile并创建输出对象)
  函数:CameraService.createPreviewOutput(surfaceId, mode)
  作用:
    1) 读取 previewProfiles
    2) 选择 previewProfile(format/size)
    3) 调 mCameraManager.createPreviewOutput(previewProfile, surfaceId)
  关键参数:previewProfiles, previewProfile, surfaceId
        |
        | profile + surfaceId
        v
模块4:NAPI/Framework 输出桥接模块(定义:参数门禁+surface落地+服务调用)
  函数:
    - CameraManagerNapi::CreatePreviewOutputInstance
    - PreviewOutputNapi::CreatePreviewOutput
    - CameraManager::CreatePreviewOutput
  作用:
    1) surfaceId -> Surface(SurfaceUtils/GetSurface)
    2) profile/surface 参数合法性检查
    3) surface->GetProducer + metaFormat + width/height 下发服务
  关键参数:Surface, IBufferProducer, metaFormat, width, height
        |
        | producer + format + width/height
        v
模块5:Service 预览流创建模块(定义:创建服务端PREVIEW流对象)
  函数:HCameraService::CreatePreviewOutput
  作用:创建 HStreamRepeat(..., RepeatStreamType::PREVIEW) 并回传
  关键参数:producer, format, width, height
        |
        | PreviewOutput stream
        v
模块6:Session 编排模块(定义:把输入与输出绑定成可运行会话)
  函数:
    - CameraService.createSession
    - CameraManager::CreateCaptureSession / HCameraService::CreateCaptureSession
    - CaptureSession::BeginConfig/AddInput/AddOutput/CommitConfig/Start
  作用:
    1) 创建会话对象
    2) 绑定 input/output
    3) 提交配置并启动
  关键参数:opMode, streamType, settings
        |
        | streamInfos + operationMode + settings
        v
模块7Stream 启动与显示模块(定义:建流、提交流、启动预览并驱动显示)
  函数:
    - HStreamOperator::CreateStreams
    - HStreamOperator::CommitStreams
    - HStreamOperator::StartPreviewStream
    - HStreamRepeat::Start
  作用:让PREVIEW流进入running,持续把预览帧送入consumer queue显示
  关键参数:StreamInfo, operationMode, settings, cameraPosition

模块定义与函数归属(图中对应关系)

模块名 模块定义 归属函数 模块产出
UI Surface事件模块 接收并保存可渲染surface句柄 onSurfacePrepare mSurfaceId
ArkTS 预览编排模块 组织预览启动主顺序 startPreview 触发输出创建与会话创建
ArkTS 输出构建模块 选 profile 并创建输出对象 createPreviewOutput mPreviewOutput
NAPI/Framework 输出桥接模块 把 JS 参数桥接到服务调用 CreatePreviewOutputInstance / PreviewOutputNapi::CreatePreviewOutput / CameraManager::CreatePreviewOutput PreviewOutput + streamRepeat
Service 预览流创建模块 服务端创建 PREVIEW repeat stream HCameraService::CreatePreviewOutput HStreamRepeat(PREVIEW)
Session 编排模块 完成配置事务与输入输出绑定 CreateCaptureSession / BeginConfig / AddInput / AddOutput / CommitConfig / Start 已提交并启动的会话
Stream 启动与显示模块 建流、提交流并拉起预览流 CreateStreams / CommitStreams / StartPreviewStream 持续预览帧显示

绑定流程细化(函数调用关系 + 每步说明)

每一步做什么(按真实调用顺序)

步骤 调用函数 输入 输出 这一步具体作用 失败信号 源码锚点
1 onSurfacePrepare surfaceId mSurfaceId 把显示面 ID 存到应用状态,后续所有输出绑定都依赖它 mSurfaceId is null applications/standard/camera/common/src/main/ets/default/function/CameraBasicFunction.ts:149-152
2 startPreview mSurfaceId/mCurrentMode 触发创建输出与会话 串起预览输出、拍照输出、会话启动的总入口 UI 可用但无图像 applications/standard/camera/common/src/main/ets/default/function/CameraBasicFunction.ts:155-180
3 createPreviewOutput(ArkTS) surfaceId/mode size、候选 previewProfiles 先拿预览目标尺寸,再设置 XComponent surface 尺寸 previewProfiles is empty applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:251-260
4 createPreviewOutput(ArkTS选型) previewProfiles + size + format previewProfile 先精确匹配 width/height/format,再按尺寸 fallback,最后首项兜底 previewProfile is undefined applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:263-283
5 createPreviewOutput(ArkTS创建) previewProfile + surfaceId mPreviewOutput CameraManager.createPreviewOutput 生成预览输出对象 7400101 applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:285-289
6 CameraManager::CreatePreviewOutput(Native) Profile + Surface PreviewOutput + streamRepeat 做 format/size 参数门禁,转换 metaFormat,向 Service 发起创建 INVALID_ARGUMENT foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530-553
7 HCameraService::CreatePreviewOutput producer/format/width/height HStreamRepeat(PREVIEW) 服务端创建真实 PREVIEW repeat stream 对象 CAMERA_INVALID_ARG/CAMERA_ALLOC_ERROR foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp:621-647
8 createPhotoOutput(非视频模式) photoProfiles + imageReceiverSurfaceId mPhotoOutPut 为拍照模式补充 photo 输出,和 preview 一起加入会话 createPhotoOutput failed applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:309-337
9 createSession(ArkTS) mCameraInput/mPreviewOutput/(mPhotoOutPut) mCaptureSession 已配置 beginConfig,再 addInput/addOutput,并强校验 preview 输出必须存在 createSession abort: preview output not added applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:358-402
10 CaptureSession::AddOutput(Native) CaptureOutput 会话内输出集合 挂载 stream,预览输出分支执行 SetCameraApi repeatStream is nullptr foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp:841-888
11 commitConfig -> CreateStreams/CommitStreams streamInfos + operationMode + settings 设备侧流完成创建与提交 把输出参数落地到 HDI/VDI,完成可运行流配置 CreateStreams failed/CommitStreams failed applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:403-409 + foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1609-1666 + foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1088-1106
12 start -> StartPreviewStream settings + cameraPosition + repeatStreams PREVIEW 流启动并出帧 仅启动 RepeatStreamType::PREVIEW,触发持续预览帧进入显示链 Failed to start preview applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:409 + foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:864-889

绑定流程关键断点(实用判定)

  1. 如果第4步未选出 previewProfile,后面流程全部是“无效推进”。
  2. 如果第7步未创建 HStreamRepeat(PREVIEW),第10步通常会出现 repeatStream is nullptr
  3. 如果第11步 CreateStreams/CommitStreams 失败,start 可能调用了但不会形成稳定显示链。
  4. 第12步成功只代表“控制链启动成功”,仍需显示消费链持续收帧才算最终成功。

Surface 专项:定义、实现、参数说明

Surface 在这条链里到底是什么

  1. 在 ArkTS 侧,surfaceId 是显示面或接收面的字符串句柄。
  2. 在 NAPI/Native 侧,surfaceId 会被解析并映射为 sptr<Surface>
  3. 真正下发到服务层的是 surface->GetProducer()IBufferProducer),不是 surfaceId 文本本身。

针对性源码摘抄(含注释)

源码路径:applications/standard/camera/common/src/main/ets/default/function/CameraBasicFunction.ts

private async onSurfacePrepare(data) {
  this.mSurfaceId = data.surfaceId // [说明] UI层把可渲染surface句柄写入业务状态
}

源码路径:foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_manager_napi.cpp

std::string surfaceId; // [说明] 从JS参数接收字符串surfaceId
...
CameraNapiParamParser jsParamParser(env, info, cameraManagerNapi, profileNapiOjbect, surfaceId);
return PreviewOutputNapi::CreatePreviewOutput(env, profile, surfaceId); // [说明] surfaceId继续传到PreviewOutput构造

源码路径:foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/output/preview_output_napi.cpp

uint64_t iSurfaceId;
std::istringstream iss(surfaceId);
iss >> iSurfaceId; // [说明] 字符串surfaceId转整数句柄
sptr<Surface> surface = SurfaceUtils::GetInstance()->GetSurface(iSurfaceId); // [说明] 先从SurfaceUtils查
if (!surface) {
    surface = Media::ImageReceiver::getSurfaceById(surfaceId); // [说明] 查不到则走ImageReceiver兜底
}
surface->SetUserData(CameraManager::surfaceFormat, std::to_string(profile.GetCameraFormat())); // [说明] 写入格式元数据
int retCode = CameraManager::GetInstance()->CreatePreviewOutput(profile, surface, &sPreviewOutput_);

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

camera_format_t metaFormat = GetCameraMetadataFormat(profile.GetCameraFormat()); // [说明] 框架格式转HDI格式
int32_t retCode = serviceProxy->CreatePreviewOutput(
    surface->GetProducer(), metaFormat, profile.GetSize().width, profile.GetSize().height, streamRepeat);
// [说明] 真正下发的是IBufferProducer + format + width + height

逐行说明(Surface 参数怎么起作用):

  1. surfaceId:只负责“定位 Surface 对象”,本身不携带宽高格式。
  2. surface->SetUserData(surfaceFormat, ...):把格式语义提前写入 surface 元数据,供下游消费链读取。
  3. surface->GetProducer():把 Surface 转成可被服务层使用的缓冲生产端接口。
  4. width/height:不是从 surfaceId 自动推导,而是来自 profile 选型结果并显式传入。

Surface 参数卡(最容易错的点)

参数 定义位置 读取位置 生效位置 典型错误 最小验证动作
surfaceId ArkTS onSurfacePrepare NAPI 参数解析 SurfaceUtils::GetSurface(...) ID 无效/未准备好 对照 onSurfacePrepare 日志与 CreatePreviewOutputInstance surfaceId
surfaceFormat(userData) PreviewOutputNapi::CreatePreviewOutput 下游 surface 消费链 影响格式语义对齐 格式元数据不一致 同窗抓 profile.formatsurfaceFormat
producer surface->GetProducer() HCameraService::CreatePreviewOutput HStreamRepeat(producer,...) producer 为空 观察 producer is null 错误
width/height profile.size Framework->Service 入参 建流尺寸 尺寸和surface预期不一致 对照 createPreviewOutput size 与 profile size

Session 专项:定义、实现、参数说明

Session 的定义是什么

源码路径:foundation/multimedia/camera_framework/interfaces/inner_api/native/camera/include/session/capture_session.h

class CaptureSession : public RefBase {
public:
    int32_t BeginConfig();   // [说明] 进入配置窗口
    virtual int32_t CommitConfig(); // [说明] 提交配置并冻结会话结构
    int32_t AddInput(sptr<CaptureInput>& input); // [说明] 绑定输入设备
    virtual int32_t AddOutput(sptr<CaptureOutput> &output); // [说明] 绑定输出流
    int32_t Start(); // [说明] 启动会话
    int32_t Stop();  // [说明] 停止会话
};

逐行说明:

  1. BeginConfig/CommitConfig 定义了配置事务边界。
  2. AddInput/AddOutput 只负责“挂接关系”,不是直接出帧。
  3. Start 才触发实际流启动。

Session 的实现链(从 ArkTS 到 Service)

源码路径:applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts

this.mCaptureSession = this.mCameraManager.createCaptureSession() // [说明] 先拿会话对象
this.mCaptureSession?.beginConfig() // [说明] 开始配置
this.mCaptureSession?.addInput(this.mCameraInput) // [说明] 绑定输入
this.mCaptureSession?.addOutput(this.mPreviewOutput) // [说明] 绑定预览输出
await this.mCaptureSession?.commitConfig() // [说明] 提交配置
await this.mCaptureSession?.start() // [说明] 启动会话

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

int32_t retCode = serviceProxy->CreateCaptureSession(session, opMode); // [说明] 向服务创建远端会话
captureSession = CreateCaptureSessionImpl(mode, session); // [说明] 按mode包装成具体Session类
captureSession->SetMode(mode); // [说明] 写入当前会话模式

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

errCode = captureSession->BeginConfig(); // [说明] 远端BeginConfig
...
errCode = captureSession->CommitConfig(); // [说明] 远端CommitConfig
...
int32_t errCode = captureSession->AddOutput(output->GetStreamType(), output->GetStream()); // [说明] 挂stream
...
errCode = captureSession->Start(); // [说明] 远端Start

源码路径:foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp

hdiRc = (CamRetCode)(streamOperatorV1_1->CreateStreams_V1_1(streamInfos)); // [说明] 建流
hdiRc = (CamRetCode)(streamOperatorV1_1->CommitStreams_V1_1(operationMode, setting)); // [说明] 提交流配置
errorCode = curStreamRepeat->Start(settings); // [说明] 启动PREVIEW流

Session 参数卡(参数怎么影响结果)

参数 定义位置 读取位置 生效位置 作用机制 失败信号
opMode CreateCaptureSession(mode) serviceProxy->CreateCaptureSession(..., opMode) CommitStreams(operationMode, setting) 决定会话模式和能力门禁路径 ValidateOutputProfile ... fail
streamType AddOutput(output->GetStreamType()) CaptureSession::AddOutput Service AddOutput / StartPreviewStream 决定输出被当作 PREVIEW/PHOTO/VIDEO 哪类流处理 repeatStream is nullptr(需结合类型)
settings(metadata) commit/start 阶段 CommitStreams / StartPreviewStream HDI stream operator 与 repeat stream 决定启动参数与运行时控制项 CommitStreams failed / Failed to start preview
cameraPosition StartPreviewStream(settings, cameraPosition) SetUsedAsPosition Repeat stream 启动前 影响预览流的位置信息语义 位置相关行为异常

Session 逐步判定口诀(落地排查)

  1. 先看 createCaptureSession 是否成功拿到对象。
  2. 再看 beginConfig -> addInput -> addOutput 是否完整。
  3. 再看 commitConfig 是否成功触发 CreateStreams/CommitStreams
  4. 最后看 start 是否真正命中 StartPreviewStream 且预览流启动成功。

章节导航(先总后细)

  1. 先看模块职责矩阵,知道每一层做什么。
  2. 再看参数卡,知道每个关键参数“在哪、谁读、哪生效”。
  3. 最后看信号速查块,把日志直接映射到动作。

模块职责矩阵(谁负责什么)

模块 核心职责 输入 输出 失败信号 源码锚点
onSurfacePrepare 接收可显示 surface 标识 surfaceId mSurfaceId mSurfaceId is null applications/standard/camera/common/src/main/ets/default/function/CameraBasicFunction.ts:149-153
startPreview 串联预览输出、拍照输出、会话启动 mSurfaceId/mCurrentMode createPreviewOutput/createSession 调用链 UI可交互但无图像 applications/standard/camera/common/src/main/ets/default/function/CameraBasicFunction.ts:155-180
createPreviewOutput 基于能力集挑选 previewProfile 并创建输出 previewProfiles + surfaceId mPreviewOutput previewProfiles is empty / previewProfile is undefined applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:251-293
CameraManager::CreatePreviewOutput 校验 profile/surface 合法性并创建 stream Profile + Surface PreviewOutput + IStreamRepeat INVALID_ARGUMENT foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530-553
HCameraService::CreatePreviewOutput 服务端分配 HStreamRepeat producer/format/size previewOutput 远端对象 producer is null 或 size=0 foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp:621-647
CaptureSession::AddOutput 把 stream 正式挂进会话并设置 API 兼容信息 output->GetStream() session 中有效输出 repeatStream is nullptr foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp:865-888

关键三件套(结构体/类变量/函数)

类型 名称 为什么关键 锚点
关键结构/对象 Profile 预览能力单元,定义 format+size 的语义边界 foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530-543
关键类变量 mPreviewOutput 应用层“预览输出是否已建立”的最直接判定点 applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:287(创建赋值);applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:397(会话前校验)
关键函数 createSession 把 input/output 变成可启动捕获会话 applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:358-420

Profile结构与生成路径(补齐结构细节)

结构定义(Native 侧真实字段)

源码路径:foundation/multimedia/camera_framework/interfaces/inner_api/native/camera/include/output/camera_output_capability.h

typedef struct {
    uint32_t width;
    uint32_t height;
} Size;

typedef struct {
    uint32_t fixedFps;
    uint32_t minFps;
    uint32_t maxFps;
} Fps;

class Profile {
public:
    CameraFormat format_ = CAMERA_FORMAT_INVALID;
    Size size_ = { 0, 0 };
    Fps fps_ = { 0, 0, 0 };
    std::vector<uint32_t> abilityId_ = {};
    int32_t specId_;
};

class VideoProfile : public Profile {
public:
    std::vector<int32_t> framerates_ = {};
};

逐行分析:

  1. Size 明确尺寸语义只包含 width/height,没有旋转与裁剪信息。
  2. Fps 同时有 fixed/min/max,说明能力既支持固定帧率也支持区间帧率。
  3. Profile.format_ 是输出格式主键,预览/拍照/视频都会依赖它做门禁校验。
  4. Profile.size_ 是建流时直接下沉到 Service/VDI 的尺寸参数。
  5. Profile.fps_ 在能力集构造时就写入,后续用于模式匹配和可设帧率判断。
  6. abilityId_/specId_ 用于能力分组与规格索引,不是显示层直接参数,但影响 profile 归属。
  7. VideoProfileProfile 基础上补了 framerates_,对应 ArkTS 可见的 frameRateRange

ArkTS 侧可见对象形态(读者在日志里看到的结构)

源码路径:foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/camera_napi_object_types.cpp

CameraNapiObject::CameraNapiObjFieldMap { { "width", &size_.width }, { "height", &size_.height } }
...
CameraNapiObject::CameraNapiObjFieldMap { { "min", &frameRateRange_[0] }, { "max", &frameRateRange_[1] } }
...
CameraNapiObject::CameraNapiObjFieldMap { { "format", format }, { "size", &sizeObj->GetCameraNapiObject() } }
...
CameraNapiObject::CameraNapiObjFieldMap {
    { "format", format },
    { "size", &sizeObj->GetCameraNapiObject() },
    { "frameRateRange", &frameRateRange->GetCameraNapiObject() } }

逐行分析:

  1. size 字段被序列化成 {width,height}
  2. frameRateRange 字段被序列化成 {min,max}
  3. 普通 profile 暴露字段是 {format,size}
  4. video profile 额外暴露 {frameRateRange},这就是 ArkTS 里 item.frameRateRange.min/max 的来源。

Profile是如何被生成的(能力解析到对象落地)

源码路径: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);
}
...
Profile previewProfile = Profile(format, size, fps, abilityId);
profilesWrapper.previewProfiles.push_back(previewProfile);
...
std::vector<int32_t> frameRates = { fps.minFps, fps.maxFps };
VideoProfile vidProfile = VideoProfile(format, size, frameRates);
profilesWrapper.vidProfiles.push_back(vidProfile);

逐行分析:

  1. 能力解析有优先级:ProfileLevel > Extend > Basic
  2. 命中哪种能力结构,决定 profile 来源和字段完整度。
  3. 预览/拍照 profile 都由 format+size+fps+abilityId 组装后入表。
  4. 视频 profile 会额外写入帧率区间,最终影响 frameRateRange 匹配。

关键源码摘录与逐行分析(补齐)

片段1:ArkTS 侧 profile 选择与 fallback

源码路径:applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts

if (!this.outputCapability || !this.outputCapability.previewProfiles || this.outputCapability.previewProfiles.length === 0) {
  Log.error(`${TAG} createPreviewOutput failed: previewProfiles is empty`);
  return;
}
previewProfile = previewProfiles.find(item => item.size.width === size.width &&
  item.size.height === size.height && item.format === 1003);
if (!previewProfile) {
  previewProfile = previewProfiles.find(item => item.size.width === size.width && item.size.height === size.height);
}
if (!previewProfile && previewProfiles.length > 0) {
  previewProfile = previewProfiles[0];
}
if (!previewProfile) {
  Log.error(`${TAG} createPreviewOutput failed: previewProfile is undefined`);
  return;
}
this.mPreviewOutput = this.mCameraManager.createPreviewOutput(previewProfile, surfaceId);

逐行分析:

  1. 先判空 previewProfiles,避免无能力时继续下钻。
  2. 先按 width+height+format=1003 精确匹配。
  3. 未命中则回退“仅尺寸”。
  4. 仍未命中再回退首项,最后还有一次 previewProfile 判空。
  5. 只有拿到有效 profile 才调用 createPreviewOutput(...)

片段2:Framework 参数门禁

源码路径: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");
int32_t retCode = serviceProxy->CreatePreviewOutput(
    surface->GetProducer(), metaFormat, profile.GetSize().width, profile.GetSize().height, streamRepeat);
CHECK_ERROR_RETURN_RET_LOG(retCode != CAMERA_OK, ServiceToCameraError(retCode),
    "Failed to get stream repeat object from hcamera service! %{public}d", retCode);

逐行分析:

  1. serviceProxy/surface 为空直接判 INVALID_ARGUMENT
  2. format/width/height 非法同样直接判 INVALID_ARGUMENT
  3. 只有参数合法才会向 Service 发起 CreatePreviewOutput
  4. Service 返回失败会转换成上层可见错误码。

片段3:Service 创建 HStreamRepeat

源码路径:foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp

if ((producer == nullptr) || (width == 0) || (height == 0)) {
    rc = CAMERA_INVALID_ARG;
    return rc;
}
streamRepeatPreview = new (nothrow) HStreamRepeat(producer, format, width, height, RepeatStreamType::PREVIEW);
if (streamRepeatPreview == nullptr) {
    rc = CAMERA_ALLOC_ERROR;
    return rc;
}
previewOutput = streamRepeatPreview;

逐行分析:

  1. Service 再次检查 producer/尺寸参数,形成二次防线。
  2. 明确创建的是 RepeatStreamType::PREVIEW
  3. 分配失败返回 CAMERA_ALLOC_ERROR,成功后把 previewOutput 回传 Framework。

片段4:createSession 为什么会出现“控制链成功但显示链失败”

源码路径:applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts

if (!this.mCameraInput) {
  throw new Error('cameraInput is null');
}
if (!this.mPreviewOutput) {
  throw new Error('previewOutput is null');
}
this.mCaptureSession?.beginConfig();
this.mCaptureSession?.addInput(this.mCameraInput);
this.mCaptureSession?.addOutput(this.mPreviewOutput);
...
await this.mCaptureSession?.commitConfig();
await this.mCaptureSession?.start();

逐行分析:

  1. 先检查 mCameraInput,确保输入源存在。
  2. 再检查 mPreviewOutput,确保显示输出对象存在。
  3. beginConfig 进入配置窗口。
  4. addInput/addOutput 只表示对象挂接,不代表已经稳定出帧。
  5. commitConfig 成功只说明会话配置被接受。
  6. start 成功只说明控制链推进,仍需显示消费链持续收帧才算“画面成功”。

片段5:视频 profile 的帧率参数如何起作用

源码路径:applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts

profileVideo = videoProfiles.find(item =>
  item.size.width === size.width && item.size.height === size.height
  && item.frameRateRange.min === DEFAULT_VIDEO_FRAME_RATE && item.frameRateRange.max === DEFAULT_VIDEO_FRAME_RATE
);
...
this.mVideoOutput = this.mCameraManager.createVideoOutput(profileVideo, videoId);

逐行分析:

  1. 先按 width/height 匹配视频尺寸。
  2. 再按 frameRateRange.min/max 匹配目标帧率档位。
  3. 只有匹配成功的 profileVideo 才会用于 createVideoOutput
  4. 如果帧率筛选条件过窄,容易出现“有视频能力但找不到匹配 profile”的隐性失败。

参数卡(profile/surface/session)

参数名 参数位置 作用 典型值 谁读取 哪里生效 错配后果 最小验证动作
item.format(previewProfile筛选) CameraService.ts 决定格式优先匹配策略 1003 (CAMERA_FORMAT_YUV_420_SP) ArkTS 侧 profile 选择逻辑 影响传入 CreatePreviewOutput 的 format fallback 到非预期 profile 或直接参数非法 rg -n "CAMERA_FORMAT_YUV_420_SP = 1003" foundation/multimedia/camera_framework/interfaces -S
surfaceId onSurfacePrepare/startPreview 显示面绑定主键 非空字符串 ArkTS createPreviewOutput(previewProfile, surfaceId) 为空时预览链直接中断 mSurfaceId is null
previewProfiles outputCapability 候选预览能力集 多组 size+format ArkTS 影响 mPreviewOutput 是否能创建 空集合导致后续 session 无预览输出 selected profile
width/height size 选择 + surface size 定义 stream 配置尺寸 由 SettingManager 给出 ArkTS + Service HStreamRepeat(producer,format,width,height) 尺寸不匹配导致建流或显示异常 对照 createPreviewOutput sizeselected profile
frameRateRange.min/max(videoProfile筛选) CameraService.ts:createVideoOutput 决定视频输出帧率档位匹配 常见 30/30 ArkTS 视频 profile 选择逻辑 影响 createVideoOutput(profileVideo, videoId) 的 profile 入参 找不到匹配视频 profile,导致视频输出创建失败或退化 createVideoOutput profileVideo 并核对 videoProfiles
metaFormat(Framework转HDI格式) CameraManager::GetCameraMetadataFormat 将框架格式映射成 HDI 元数据格式 例如 CAMERA_FORMAT_YUV_420_SP -> OHOS_CAMERA_FORMAT_YCRCB_420_SP Framework serviceProxy->CreatePreviewOutput(...) 的 format 入参 格式映射偏差会导致下游能力错配或显示异常 format/metaFormat 同窗日志并对照映射表
apiCompatibleVersion CaptureSession::AddOutput 标记 stream 的 API 兼容版本 uint32 Framework repeatStream->SetCameraApi(...) 非 PREVIEW 输出也可能出现 repeatStream is nullptr 日志 同窗查看 StreamType 后再判定

枚举/语义桥接表

语义 对本章流程的作用 常见误解
1003 CAMERA_FORMAT_YUV_420_SP ArkTS 常用预览格式筛选键 误以为设备一定提供该格式
CAPTURE_OUTPUT_TYPE_PREVIEW 预览输出类型 决定是否应拿到 IStreamRepeat 把 PHOTO/VIDEO 日志混当 PREVIEW 结论
SceneMode::NORMAL 默认能力模式 profile fallback 的常见落点 忽略 mode fallback 带来的能力差异

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

设计点 为什么这样设计 优点 代价/取舍 不这样会怎样
profile 三层回退(格式+尺寸 -> 尺寸 -> 首项) 兼容不同设备能力差异 降低空 profile 崩溃风险 可能把“硬失败”转成“软异常” 严格匹配会高频 previewProfile is undefined
会话前强校验 mPreviewOutput 预览流是显示链必需前提 避免“会话成功但无图”假象 错误暴露更早 不校验会把问题拖到更后层,定位更难
profile/surface 双参数合法性校验 防止非法输入进入 service/hdi 快速拦截明显参数错误 对配置变化更敏感 放任非法输入会在底层随机失败

从日志看输出绑定是否真实成功

日志信号 说明 结论级别
createPreviewOutput selected profile: ... ArkTS 已完成 profile 选择 必要非充分
HCameraService::CreatePreviewOutput execute success 服务端成功创建 repeat stream 对象 必要非充分
createSession commitConfig + start 会话进入启动阶段 必要非充分
持续出现预览帧相关成功信号 显示链真正收帧 充分信号

运行包一致性(这一步经常被忽略)

applications/standard/camera 是源码参考;设备常跑 applications/standard/hap/Camera.hap 预编译包。

检查项 参考点 通过信号 失败动作
预编译入口 applications/standard/hap/BUILD.gn:154-159 camera_hap 安装到 app/com.ohos.camera 若行为与源码不符,优先怀疑包未更新
板端包时间戳 hdc shell ls -l /system/app/com.ohos.camera/Camera.hap(运行时路径)↔ applications/standard/hap/Camera.hap(仓内源路径) 与本次构建时间一致 先替换运行包再复测
行为指纹 createPreviewOutput selected profile 等新日志 新增保护逻辑日志可见 看不到说明仍跑旧包

误区纠偏(高频)

  1. PreviewOutput::SetCameraApi() repeatStream is nullptr 不能单独断言“预览输出失败”。必须先看这条日志对应的 StreamType,因为非 PREVIEW 输出也可能经过这段路径。
  2. createSession endHCaptureSession::Start success 不能单独断言“画面一定有”。这只说明控制链推进成功,显示链仍依赖 surface 队列是否持续进帧。

错误检查(双证据判定)

检查信号 前序知识点判据(本书内) 实际源码锚点(仓根相对路径) 判定结论 最小动作
7400101 第7章发散知识点1:它是“参数/能力语义不成立”触发信号,不是驱动层结论 foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cppCreatePreviewOutput 参数门禁) + applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts(profile 选型) 高概率是 profile/surface 组合非法 同窗输出 selected profile 并对齐能力集后重试
repeatStream is nullptr 第7章发散知识点3:该词可出现在非 PREVIEW 分支,不能单点定性 foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cppAddOutput 分支) 需结合 StreamType 判断是否真预览断链 同窗补抓 StreamTypeAddOutput 顺序
Start success 但黑屏 第1/2章双主线判据:控制链成功不等于显示链成功 foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp(Start 侧) + applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts(输出创建侧) 结论必须拆成“控制链通过 + 显示链待证” 补抓 createPreviewOutput 与显示消费信号,不重复 open 试错

知识点依赖与跨章连接

本章知识点 依赖章节 依赖原因 下一跳
profile 选择与 fallback 第4章会话门禁 先懂 Validate/fallback 语义,才能正确理解 ArkTS 选型策略 第6章函数级走读
surface 绑定与 stream 挂载 第1章旅程总览 先有双主线全景,才能区分控制链/显示链 第6章 CreateStreams 深挖
运行包一致性检查 第0章导读 源码与运行态不一致是常见误判源 第8章调试技巧章节

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

信号 判断 动作
createPreviewOutput failed: previewProfiles is empty 能力解析链没有把可用预览能力交给 ArkTS 回查 HCS basic/extendParseCapability 解析分支
createPreviewOutput failed: previewProfile is undefined 当前 mode/size/format 在能力集中找不到匹配项 先核对 selected profile 日志,再调整尺寸或补能力
createSession abort: preview output not added mPreviewOutput 未有效建立或提前释放 顺查 surfaceId、createPreviewOutput 异常、release 时序
{"code":"7400101"} 参数非法(多见 profile/surface 组合不合法) 核对 profile 尺寸格式与能力集,并验证运行包是否最新
repeatStream is nullptr 可能为非 PREVIEW 分支日志 结合同窗 StreamType 与 AddOutput 顺序再下结论

本章三元结论

  • 现象:App 能进入会话流程,但画面仍黑或不稳定。
  • 根因:输出绑定链路(profile/surface/session)至少有一处语义不一致,或运行包未切到新版本。
  • 最小动作:先做三项核验:selected profilesurfaceIdCamera.hap 时间戳

本章最小动作

  1. 抓一窗日志并筛:selected profilecreateSession abort7400101repeatStream is nullptr
  2. 对照参数卡确认三件事:surfaceId 非空、profile 合法、板端运行包已更新。

下一跳建议

读第6章:把本章的“现象级链路”下钻到函数级输入输出与状态变更点,定位效率会明显提高。


发布后补链

全章导航

Logo

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

更多推荐