openharmony摄像头驱动到应用浏览显示 第1章 主链调用总览
本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第1章 主链调用总览(图表)
代码基线:~/work/ohos5.1/nt_backclip_vendor(基于 OpenHarmony 5.1.0 Release 移植)
发布说明:使用ai codex移植调试开源鸿蒙摄像头的任务全流程在链接https://github.com/chenlong3388/codex_fix_OpenHarmony_Camera
第1章 主链调用总览(图表)
本章目标
这章只做一件事:把“应用点预览”到“屏幕看到画面”的主链走通。
你读完后要能做到:
- 说清楚每一层做了什么(不是只记函数名)。
- 拿着日志能快速判断断在 App、Service、VDI、还是 V4L2。
- 按源码路径直接跳转到关键函数。
这一章怎么读最省力
推荐阅读节拍:
- 先看“主链一图看全”,建立空间感。
- 再看“参数流向图”,建立参数的时间感。
- 最后按 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、能力集previewProfiles、surfaceId。 - 输出:
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:651foundation/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:1055foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1609foundation/multimedia/camera_framework/services/camera_service/src/hstream_common.cpp:170drivers/peripheral/camera/vdi_base/v4l2/src/stream_operator/stream_operator_vdi_impl.cpp:213drivers/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:864drivers/peripheral/camera/vdi_base/common/adapter/platform/v4l2/src/driver_adapter/src/v4l2_buffer.cpp:75drivers/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 绑定与尺寸一致性(显示链章节)
知识点连接(把本章和后续章节绑紧)
- 本章解决“主链时序与层间职责”。
- 第3章会细化“能力注入为什么会影响这里的 profile 分叉”。
- 第4章会细化“Commit/Start 门禁为什么决定时序是否可持续”。
- 第5章会细化“createPreviewOutput/surface 为什么决定最后是否可见”。
- 第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 步:
- 找
selected profile; - 找
CommitConfig前后错误; - 找
StartPreview是否报错; - 找
REQBUFS/QBUF是否失败。
四步就能把问题先定位到“配置链”还是“缓冲显示链”。
下一跳建议
- 想看每个函数更细粒度输入输出:去第6章。
- 想系统看会话门禁和状态机:去第4章。
- 想专攻黑屏/尺寸不一致:去第2章和第3章。
本章一句话小结
本章把“现象 -> 层级 -> 函数段 -> 最小动作”这条判断链建立起来,后续章节不再重复讲全旅程,而是各自把其中一个关键段讲深讲透。
发布后补链
- 上一篇:https://laval.csdn.net/69d77a0554b52172bc6802aa.html
- 下一篇:https://laval.csdn.net/69d8b7fd0a2f6a37c59e6f33.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)