openharmony摄像头驱动到应用浏览显示 第5章 输出创建与显示绑定(ArkTS/HAP)
本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第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可挂载、运行包版本一致。
三句流程版
- ArkTS 在
onSurfacePrepare拿到surfaceId后,按outputCapability.previewProfiles选出previewProfile。 - Framework 用
CreatePreviewOutput(profile, surface)向服务层创建HStreamRepeat,再由CaptureSession::AddOutput绑定。 - 只有当 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
模块7:Stream 启动与显示模块(定义:建流、提交流、启动预览并驱动显示)
函数:
- 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 |
绑定流程关键断点(实用判定)
- 如果第4步未选出
previewProfile,后面流程全部是“无效推进”。 - 如果第7步未创建
HStreamRepeat(PREVIEW),第10步通常会出现repeatStream is nullptr。 - 如果第11步
CreateStreams/CommitStreams失败,start可能调用了但不会形成稳定显示链。 - 第12步成功只代表“控制链启动成功”,仍需显示消费链持续收帧才算最终成功。
Surface 专项:定义、实现、参数说明
Surface 在这条链里到底是什么
- 在 ArkTS 侧,
surfaceId是显示面或接收面的字符串句柄。 - 在 NAPI/Native 侧,
surfaceId会被解析并映射为sptr<Surface>。 - 真正下发到服务层的是
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 参数怎么起作用):
surfaceId:只负责“定位 Surface 对象”,本身不携带宽高格式。surface->SetUserData(surfaceFormat, ...):把格式语义提前写入 surface 元数据,供下游消费链读取。surface->GetProducer():把 Surface 转成可被服务层使用的缓冲生产端接口。width/height:不是从surfaceId自动推导,而是来自 profile 选型结果并显式传入。
Surface 参数卡(最容易错的点)
| 参数 | 定义位置 | 读取位置 | 生效位置 | 典型错误 | 最小验证动作 |
|---|---|---|---|---|---|
surfaceId |
ArkTS onSurfacePrepare |
NAPI 参数解析 | SurfaceUtils::GetSurface(...) |
ID 无效/未准备好 | 对照 onSurfacePrepare 日志与 CreatePreviewOutputInstance surfaceId |
surfaceFormat(userData) |
PreviewOutputNapi::CreatePreviewOutput |
下游 surface 消费链 | 影响格式语义对齐 | 格式元数据不一致 | 同窗抓 profile.format 与 surfaceFormat |
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(); // [说明] 停止会话
};
逐行说明:
BeginConfig/CommitConfig定义了配置事务边界。AddInput/AddOutput只负责“挂接关系”,不是直接出帧。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 逐步判定口诀(落地排查)
- 先看
createCaptureSession是否成功拿到对象。 - 再看
beginConfig -> addInput -> addOutput是否完整。 - 再看
commitConfig是否成功触发CreateStreams/CommitStreams。 - 最后看
start是否真正命中StartPreviewStream且预览流启动成功。
章节导航(先总后细)
- 先看模块职责矩阵,知道每一层做什么。
- 再看参数卡,知道每个关键参数“在哪、谁读、哪生效”。
- 最后看信号速查块,把日志直接映射到动作。
模块职责矩阵(谁负责什么)
| 模块 | 核心职责 | 输入 | 输出 | 失败信号 | 源码锚点 |
|---|---|---|---|---|---|
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_ = {};
};
逐行分析:
Size明确尺寸语义只包含width/height,没有旋转与裁剪信息。Fps同时有fixed/min/max,说明能力既支持固定帧率也支持区间帧率。Profile.format_是输出格式主键,预览/拍照/视频都会依赖它做门禁校验。Profile.size_是建流时直接下沉到 Service/VDI 的尺寸参数。Profile.fps_在能力集构造时就写入,后续用于模式匹配和可设帧率判断。abilityId_/specId_用于能力分组与规格索引,不是显示层直接参数,但影响 profile 归属。VideoProfile在Profile基础上补了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() } }
逐行分析:
size字段被序列化成{width,height}。frameRateRange字段被序列化成{min,max}。- 普通 profile 暴露字段是
{format,size}。 - 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);
逐行分析:
- 能力解析有优先级:
ProfileLevel > Extend > Basic。 - 命中哪种能力结构,决定 profile 来源和字段完整度。
- 预览/拍照 profile 都由
format+size+fps+abilityId组装后入表。 - 视频 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);
逐行分析:
- 先判空
previewProfiles,避免无能力时继续下钻。 - 先按
width+height+format=1003精确匹配。 - 未命中则回退“仅尺寸”。
- 仍未命中再回退首项,最后还有一次
previewProfile判空。 - 只有拿到有效 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);
逐行分析:
serviceProxy/surface为空直接判INVALID_ARGUMENT。format/width/height非法同样直接判INVALID_ARGUMENT。- 只有参数合法才会向 Service 发起
CreatePreviewOutput。 - 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;
逐行分析:
- Service 再次检查 producer/尺寸参数,形成二次防线。
- 明确创建的是
RepeatStreamType::PREVIEW。 - 分配失败返回
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();
逐行分析:
- 先检查
mCameraInput,确保输入源存在。 - 再检查
mPreviewOutput,确保显示输出对象存在。 beginConfig进入配置窗口。addInput/addOutput只表示对象挂接,不代表已经稳定出帧。commitConfig成功只说明会话配置被接受。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);
逐行分析:
- 先按
width/height匹配视频尺寸。 - 再按
frameRateRange.min/max匹配目标帧率档位。 - 只有匹配成功的
profileVideo才会用于createVideoOutput。 - 如果帧率筛选条件过窄,容易出现“有视频能力但找不到匹配 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 size 与 selected 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 等新日志 |
新增保护逻辑日志可见 | 看不到说明仍跑旧包 |
误区纠偏(高频)
PreviewOutput::SetCameraApi() repeatStream is nullptr不能单独断言“预览输出失败”。必须先看这条日志对应的StreamType,因为非 PREVIEW 输出也可能经过这段路径。createSession end或HCaptureSession::Start success不能单独断言“画面一定有”。这只说明控制链推进成功,显示链仍依赖 surface 队列是否持续进帧。
错误检查(双证据判定)
| 检查信号 | 前序知识点判据(本书内) | 实际源码锚点(仓根相对路径) | 判定结论 | 最小动作 |
|---|---|---|---|---|
7400101 |
第7章发散知识点1:它是“参数/能力语义不成立”触发信号,不是驱动层结论 | foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp(CreatePreviewOutput 参数门禁) + 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.cpp(AddOutput 分支) |
需结合 StreamType 判断是否真预览断链 |
同窗补抓 StreamType、AddOutput 顺序 |
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/extend 与 ParseCapability 解析分支 |
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 profile、surfaceId、Camera.hap 时间戳。
本章最小动作
- 抓一窗日志并筛:
selected profile、createSession abort、7400101、repeatStream is nullptr。 - 对照参数卡确认三件事:
surfaceId非空、profile 合法、板端运行包已更新。
下一跳建议
读第6章:把本章的“现象级链路”下钻到函数级输入输出与状态变更点,定位效率会明显提高。
发布后补链
- 上一篇:https://laval.csdn.net/69d8b8d154b52172bc6860ae.html
- 下一篇:https://laval.csdn.net/69d8b96854b52172bc6860cd.html
全章导航
- 第0章 导读学习指南:https://laval.csdn.net/69d77a0554b52172bc6802aa.html
- 第1章 主链调用总览(图表):https://laval.csdn.net/69d8b75f54b52172bc686020.html
- 第2章 驱动与硬件基线:https://laval.csdn.net/69d8b7fd0a2f6a37c59e6f33.html
- 第3章 能力注入与流桥接(HDF/HDI/VDI):https://laval.csdn.net/69d8b86154b52172bc68609a.html
- 第4章 会话编排与门禁(Framework/Service):https://laval.csdn.net/69d8b8d154b52172bc6860ae.html
- 第5章 输出创建与显示绑定(ArkTS/HAP):https://laval.csdn.net/69d8b91954b52172bc6860be.html
- 第6章 函数级链路走读(驱动到显示):https://laval.csdn.net/69d8b96854b52172bc6860cd.html
- 第7章 异常挂点与主链归因:https://laval.csdn.net/69d8b9d054b52172bc6860dc.html
- 第8章 调试方法与证据模板:https://laval.csdn.net/69d8ba1454b52172bc6860e4.html
- 第9章 实战演练与复盘:https://laval.csdn.net/69d8ba4754b52172bc6860f3.html
更多推荐
所有评论(0)