openharmony摄像头驱动到应用浏览显示 第6章 函数级链路走读(驱动到显示)
本文为《开源鸿蒙相机:驱动到应用预览显示》系列发布版。
当前章节:第6章 函数级链路走读(驱动到显示)
代码基线:~/work/ohos5.1/nt_backclip_vendor(基于 OpenHarmony 5.1.0 Release 移植)
发布说明:使用ai codex移植调试开源鸿蒙摄像头的任务全流程在链接https://github.com/chenlong3388/codex_fix_OpenHarmony_Camera
第6章 函数级链路走读(驱动到显示)
这章解决什么
第1章已经告诉你“路是通的”。这一章把这条路拆成“函数级台阶”:每个关键函数做什么、吃什么输入、产出什么状态、失败会出现什么信号。
读完本章,你应该能做到两件事:
- 看到一条错误日志时,立刻定位到对应函数层。
- 从 App 一路单步追到 V4L2,知道每一步参数如何传递和生效。
这章和前后章节的关系(避免“单章孤岛”)
- 第1章给你“阶段地图”,第6章给你“函数落脚点”。
- 第3章解释“能力从哪来”,第6章解释“能力怎么被函数消费”。
- 第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:136applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:225foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_napi.cpp:85foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_manager_napi.cpp:957foundation/multimedia/camera_framework/frameworks/js/camera_napi/src/input/camera_manager_napi.cpp:1336foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1129foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1809foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:1976foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_input.cpp:181foundation/multimedia/camera_framework/services/camera_service/src/hcamera_service.cpp:483foundation/multimedia/camera_framework/services/camera_service/src/hcamera_device.cpp:311foundation/multimedia/camera_framework/services/camera_service/src/hcamera_device.cpp:397foundation/multimedia/camera_framework/services/camera_service/src/hcamera_host_manager.cpp:872drivers/peripheral/camera/hdi_service/v1_0/src/camera_host_service.cpp:171drivers/peripheral/camera/hdi_service/v1_0/src/camera_host_service.cpp:206drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:112drivers/peripheral/camera/vdi_base/v4l2/src/camera_host/camera_host_vdi_impl.cpp:165drivers/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行:通过
GlobalContext获取能力上下文,创建CameraManager,这是后续所有能力查询和对象创建的入口。 - 第2行:同步拉取系统可见相机列表,结果直接决定
targetCamera能否被定位。 - 第3行:省略的是摄像头筛选与映射逻辑,目标是得到最终要开的
targetCamera。 - 第4行:依据目标设备创建
CameraInput,此时只是拿到输入对象,尚未真正开机。 - 第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行:把
getCameraManager暴露给 JS 层,ArkTS 的camera.getCameraManager(...)就是走这条导出。 - 第2行:省略的是 NAPI 参数检查和 unwrap,确保
cameraManagerNapi对象有效。 - 第3行:NAPI 直接调用 native
GetSupportedCameras(),把底层设备对象集合返回给 JS。 - 第4行:省略的是
cameraInfo解析(按 cameraId 或 position/type)。 - 第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行:获取
CameraManager单例,并在首次调用时建立到camera_service的连接。 - 第2行:省略的是单例初始化和服务代理可用性恢复逻辑。
- 第3行:从服务拉取支持设备,形成当前进程可用的
CameraDevice列表。 - 第4行:省略的是
camera参数合法性与兼容性分支处理。 - 第5行:按
cameraId创建远端设备服务对象,失败说明开机链在 service 入口前就断开。 - 第6行:省略的是
CameraInput封装和错误转换。 - 第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行:Service 层把开机请求交给 HostManager,输出
hdiCameraDevice_供后续流操作使用。 - 第2行:省略的是 HostManager 内部按 HDI 版本分发
OpenCamera_V1_x的逻辑。 - 第3行:HDI Host 再把逻辑相机 ID 映射到 vendor ID,转给 VDI 的
OpenCamera。 - 第4行:省略的是回调对象绑定和设备对象包装。
- 第5行:VDI 在 open 过程中执行
CameraPowerUp,按physicsCameraIds逐路上电。
函数级输入输出:
- 输入:
cameraId/targetCamera、权限态、physicsCameraIds。 - 输出:
CameraInput可用且设备已完成 host/vdi power-up。 - 失败信号:
CreateCameraDevice ... failed、HCameraDevice::OpenDevice Failed to open camera、camera powerup failed。 - 设计优点:把“枚举能力”和“真实开机”拆成清晰台阶,前半链失败可在 preview 前直接收敛,不会误判为显示链问题。
App会话组装段(把“选择”变成“请求”)
源码锚点:
applications/standard/camera/common/src/main/ets/default/camera/CameraService.ts:251applications/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-2行:先按
width/height/format=1003精确匹配预览档位,优先拿到 YUV 预览组合。 - 第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行:会话进入配置窗口,后续输入输出挂接必须在此窗口内完成。
- 第2行:把已 open 的
CameraInput绑定到会话,建立输入源。 - 第3行:把预览输出绑定到会话,建立显示目标流。
- 第4行:提交配置,触发 Service 侧
ValidateSession/LinkInputAndOutputs/CreateStreams/CommitStreams。 - 第5行:启动会话,触发
StartPreviewStream进入持续预览。
函数级输入输出:
- 输入:
mode、previewProfiles、surfaceId、cameraInput。 - 输出:
mPreviewOutput、mCaptureSession已提交并启动。 - 失败信号:
previewOutput is null、createSession abort: preview output not added。 - 设计优点:App 先兜底
previewOutput,可阻止“空输出却继续 commit”的伪成功。
Framework门禁段(校验输出并下发到 Service)
源码锚点:
foundation/multimedia/camera_framework/frameworks/native/camera/src/input/camera_manager.cpp:530foundation/multimedia/camera_framework/frameworks/native/camera/src/session/capture_session.cpp:841foundation/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行:检查 format 是否无效;无效即不允许继续创建输出对象。
- 第2行:检查宽高是否为 0;任一为 0 说明 profile 语义不完整。
- 第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行:先把输出流挂入会话,返回值反映挂载动作是否成功。
- 第2行:仅当输出类型是
PREVIEW才尝试取IStreamRepeat,其它输出类型不走预览 API 设置。 - 第3行:执行类型转换,
stream能否转成repeatStream是预览链闭环关键检查点。 - 第4行:结束预览类型分支。
- 第5行:若转换成功,设置 API 兼容版本,保证后续预览调用语义一致。
- 第6行:调用真正的
SetCameraApi。 - 第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行:先在当前
mode的 profile 集合里查找能力项。 - 第2行:若当前 mode 下匹配成功,直接通过门禁。
- 第3行:命中成功路径立即返回,不再做 fallback。
- 第4行:当前 mode 失败后,只有“非 NORMAL 模式”才允许继续回退。
- 第5行:查找
NORMAL模式能力作为兜底集合。 - 第6行:省略的是回退验证细节,核心语义是“当前 mode 不行时再试 NORMAL”。
函数级输入输出:
- 输入:
Profile(width/height/format)、outputType、current mode。 - 输出:输出流是否允许加入会话。
- 失败信号:
INVALID_ARGUMENT、CanAddOutput check failed、repeatStream is nullptr。 - 设计优点:Framework 把“参数合法性”和“能力匹配”前置,缩短错误反馈闭环。
Service提交流段(提交配置并构建可运行流)
源码锚点:
foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:651foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:550foundation/multimedia/camera_framework/services/camera_service/src/hstream_common.cpp:170foundation/multimedia/camera_framework/services/camera_service/src/hstream_operator.cpp:1609foundation/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行:先做会话完整性校验(输入/输出/状态合法性)。
- 第2行:校验失败直接返回,阻止脏状态进入后续流程。
- 第3行:校验通过后执行输入输出绑定。
- 第4行:绑定失败直接返回并打印 commit 失败日志。
- 第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行:把目标宽度写入
streamInfo。 - 第2行:把目标高度写入
streamInfo。 - 第3行:把像素格式写入
streamInfo,后续会下沉到 VDI/V4L2。 - 第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行:先按
streamInfos创建设备侧流对象。 - 第2行:省略的是返回码检查与错误转换。
- 第3-4行:再提交流配置(含 operationMode 与 metadata setting),使流进入可运行状态。
函数级输入输出:
- 输入:
session input/output、operationMode、metadata setting、streamInfo[]。 - 输出:HDI 层已创建并提交流。
- 失败信号:
ValidateSessionOutputs No outputs present、GetStreamOperator is null、CommitStreams failed。 - 设计优点:Service 层统一会话事务,保证跨层提交时状态一致、可追踪。
预览启动段(Service 启动预览流)
源码锚点:
foundation/multimedia/camera_framework/services/camera_service/src/hcapture_session.cpp:1063foundation/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行:先检查状态机是否允许迁移到
SESSION_STARTED。 - 第2行:省略的是状态非法时的提前返回逻辑。
- 第3行:启动预览流,携带当前 settings 和 cameraPosition。
- 第4行:仅当启动成功时才把
isSessionStarted_置为 true。 - 第5行:写入启动成功标记。
- 第6行:状态机迁移到
SESSION_STARTED。
源码摘选(StartPreview 只拉 PREVIEW 流):
auto repeatType = curStreamRepeat->GetRepeatStreamType();
if (repeatType != RepeatStreamType::PREVIEW) {
continue;
}
errorCode = curStreamRepeat->Start(settings);
逐行解析:
- 第1行:读取当前 repeat 流类型。
- 第2-3行:非
PREVIEW流直接跳过,不参与预览启动。 - 第4行:仅对预览流执行
Start(settings)。
函数级输入输出:
- 输入:
settings metadata、cameraPosition、repeatStreams。 - 输出:预览流进入 running。
- 失败信号:
Need to call after committing configuration、Failed to start preview。 - 设计优点:预览启动单独过滤 PREVIEW 流,避免被 LIVEPHOTO/其它流噪声干扰。
VDI缓冲链段(VDI / V4L2 建缓冲并进队)
源码锚点:
drivers/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:312drivers/peripheral/camera/vdi_base/v4l2/src/stream_operator/stream_operator_vdi_impl.cpp:463drivers/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:358drivers/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行:把上游
streamInfo的宽度映射到 VDI 配置。 - 第2行:把高度映射到 VDI 配置。
- 第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-2行:构造一次捕获请求,包含 captureId、目标流数、setting 与流模式。
- 第3行:遍历本次请求涉及的每个 streamId。
- 第4行:若 stream 未注册或加请求失败,立即返回设备错误。
- 第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行:先向内核申请缓冲池;失败说明缓冲模型与驱动能力不匹配。
- 第2行:查询单个缓冲参数;失败常见于 plane/type/index 语义错误。
- 第3行:把缓冲入队,等待驱动写帧。
- 第4行:入队失败则直接返回错误,后续不会有稳定预览帧。
函数级输入输出:
- 输入:
streamInfos、captureSetting、bufferType/memoryType。 - 输出:buffer 可用并入队,驱动开始产帧。
- 失败信号:
DYNAMIC_STREAM_SWITCH_NOT_SUPPORT、VIDIOC_QUERYBUF failed、VIDIOC_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选型。 - 生效位置:
CameraManager::CreatePreviewOutput传给 service;HStreamCommon::SetStreamInfo写入streamInfo.v1_0;StreamOperatorVdiImpl::StreamInfoToStreamConfiguration落到scg。
- 常见误配后果:
createPreviewOutput失败或后续QUERYBUF/QBUF失败。 - 最小验证动作:查日志
selected profile与streamId width/height/format是否一致。
参数卡2:operationMode(mode)
- 参数位置:会话模式(NORMAL/VIDEO 等)。
- 读取位置:
CaptureSession::ValidateOutputProfile与CommitStreams。 - 生效位置:
- profile 校验优先匹配当前 mode;
CommitStreams_V1_1以 mode 下发到 HDI/VDI。
- 常见误配后果:当前 mode 校验失败,只能 fallback NORMAL,甚至直接失败。
- 最小验证动作:同窗观察
ValidateOutputProfile in mode(...)与 commit mode。
参数卡3:streamType / outputType
- 参数位置:
AddOutput(output->GetStreamType())。 - 读取位置:
CaptureSession::AddOutput。 - 生效位置:
- Service/VDI 选择 REPEAT/CAPTURE 流路径;
StartPreviewStream仅启动RepeatStreamType::PREVIEW。
- 常见误配后果:流被加入但不是预览流,导致“start success 但无显示”。
- 最小验证动作:检查
AddOutput StreamType与StartPreview中 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 个函数顺序定位,一步一判:
CameraService.initCamera/getSupportedCameras(是否拿到目标 cameraId);CameraService.createCameraInput -> open(是否真实开机成功);CameraService.createSession(preview 是否真 add);CaptureSession::ValidateOutputProfile(profile/mode 是否匹配);HCaptureSession::CommitConfig(是否真正 commit);HStreamOperator::StartPreviewStream(是否启动到 PREVIEW 流);StreamOperatorVdiImpl::CreateStreams/CommitStreams(stream 是否有效注册);HosV4L2Buffers::V4L2AllocBuffer/QBUF(缓冲是否能进队)。
只要这 8 步走完,故障就会稳定落到“前半链开机问题”“会话门禁问题”或“缓冲显示问题”其中之一。
本章一句话小结
函数级学习的关键不是“记住更多函数名”,而是建立“输入从哪来、状态何时变、错误往哪传”的稳定因果链,这条链一旦建立,复杂问题也能被拆成可执行的小步排查。
发布后补链
- 上一篇:https://laval.csdn.net/69d8b91954b52172bc6860be.html
- 下一篇:https://laval.csdn.net/69d8b9d054b52172bc6860dc.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)