HarmonyOS趣味相机实战第11篇:从单摄切换升级前后双摄同开、能力探测与降级设计

📅 2026/7/15 11:12:28
HarmonyOS趣味相机实战第11篇:从单摄切换升级前后双摄同开、能力探测与降级设计
HarmonyOS趣味相机实战第11篇从单摄切换升级前后双摄同开、能力探测与降级设计摘要当前D:/APP/1quweixiangji趣味相机已经完成前后镜头发现、单镜头预览、镜头切换和 PhotoOutput 拍照但它一次只维护一个 CameraInput、一个 PhotoSession 和一个 XComponent Surface。若要升级为“前后双摄同开”不能简单地连续调用两次createCameraInput()设备可能没有前后两个逻辑摄像头也可能不支持并发支持并发的机型还需要使用并发能力结果提供的输出配置并以受限能力模式打开输入。本文以当前单摄代码为起点给出一套 API 18 前后双摄升级设计。文章明确区分“项目已经实现”和“下一阶段设计”当前版本1.0.2仍是前后镜头切换尚未上线双摄 UI 与双路保存本文提供的是可以落地的架构、代码骨架、降级策略和真机验收清单不把设计稿描述成现成功能。工程背景与源码定位文件当前作用双摄升级后的责任entry/src/main/ets/service/CameraDeviceService.ets发现前后镜头并选择单个 activePosition增加前后设备组合和并发能力探测entry/src/main/ets/service/CameraPreviewService.ets维护单路 CameraInput、PhotoSession、PreviewOutput、PhotoOutput拆分成双路显式资源状态与统一释放entry/src/main/ets/pages/Index.ets单 XComponent 预览、镜头切换和拍照按钮增加主画面与画中画双 Surface 布局entry/src/main/ets/model/DecorationModels.ets照片、水印、识别目标模型增加双路照片关联和合成记录模型entry/src/main/module.json5声明ohos.permission.CAMERA拍照双摄仍只需要相机权限entry/src/test/LocalUnit.test.ets水印快照测试增加能力路由、降级和双路结果聚合测试环境、版本与官方边界项目当前值说明工程路径D:/APP/1quweixiangji当前源码为单摄实现应用版本1.0.2来自AppScope/app.json5target SDK6.0.2(22)高于前后双摄并发所需的 API 18 起点应用模型HarmonyOS Stage 模型EntryAbility加载pages/Index当前相机模式单 CameraInput 单 PhotoSession前后镜头之间重建预览升级目标前后双摄同时预览与分别拍照必须以真机并发能力为准受限模式CAMERA_LIMITED_CAPABILITY并发模式不是完整单摄控制能力集合华为 CameraKit 官方入口Camera Kit 简介https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/camera-overview相机权限https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/camera-preparationCameraManager APIhttps://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-camera-cameramanagerCameraInput APIhttps://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-camera-camerainput前后双摄同开从 API 18 起提供相关能力但最终是否可用取决于当前设备返回的并发信息。即使应用 target SDK 满足要求也不能跳过真机能力探测。一、当前项目是怎样完成单摄切换的当前设备服务记录exporttypeCameraPositionKeyfront|back;exportinterfaceCameraDeviceState{available:boolean;totalCount:number;hasFront:boolean;hasBack:boolean;activePosition:CameraPositionKey;message:string;}页面只维护一个当前方向StateactiveCameraPosition:CameraPositionKeyback;StatepreviewSurfaceId:string;切换时重新发现设备并启动单路预览constnextPosition:CameraPositionKeyCameraDeviceService.nextPosition(this.activeCameraPosition,this.hasFrontCamera,this.hasBackCamera);this.refreshCameraDevices(context,nextPosition);CameraPreviewService.startPreview()开头先释放旧会话再按方向取一个摄像头awaitCameraPreviewService.stopPreview();constcameraPosition:camera.CameraPositionpositionfront?camera.CameraPosition.CAMERA_POSITION_FRONT:camera.CameraPosition.CAMERA_POSITION_BACK;constdevice:camera.CameraDevicemanager.getCameraDevice(cameraPosition,camera.CameraType.CAMERA_TYPE_DEFAULT);这是一套稳定的“二选一”架构但不能直接承载双摄因为服务内部资源都是单例字段。二、双摄升级的第一条规则先确认两个逻辑设备不要只看getSupportedCameras().length 2。设备列表里可能包含多个后置物理摄像头不等于一定存在一前一后两个目标设备。应按位置明确获取constmanager:camera.CameraManagercamera.getCameraManager(context);constfrontDevice:camera.CameraDevicemanager.getCameraDevice(camera.CameraPosition.CAMERA_POSITION_FRONT,camera.CameraType.CAMERA_TYPE_DEFAULT);constbackDevice:camera.CameraDevicemanager.getCameraDevice(camera.CameraPosition.CAMERA_POSITION_BACK,camera.CameraType.CAMERA_TYPE_DEFAULT);任意一个方向不可用就不能进入前后并发路径。此时应回到项目已经稳定实现的单摄切换。三、必须调用 getCameraConcurrentInfos拿到两个设备后继续查询constconcurrentInfos:Arraycamera.CameraConcurrentInfomanager.getCameraConcurrentInfos([frontDevice,backDevice]);判断if(concurrentInfos.length0){return{supported:false,reason:当前设备不支持前后双摄同时预览};}这个结果才是双摄入口的开关探测结果产品行为前置或后置缺失保留单摄模式不显示双摄入口concurrentInfos为空提供顺序双拍或普通镜头切换返回有效并发信息继续匹配输出能力并建立双路会话查询抛异常记录错误安全退回单摄不能先以并发模式打开 CameraInput再补查能力。能力查询必须在打开输入之前完成。四、定义清晰的双摄状态模型当前CameraPreviewService只有一套静态资源。升级时建议显式定义每一路exportinterfaceCameraStreamState{position:CameraPositionKey;surfaceId:string;device?:camera.CameraDevice;input?:camera.CameraInput;previewOutput?:camera.PreviewOutput;photoOutput?:camera.PhotoOutput;session?:camera.PhotoSession;}exportinterfaceDualCameraState{supported:boolean;front:CameraStreamState;back:CameraStreamState;concurrentInfos:Arraycamera.CameraConcurrentInfo;generation:number;}这样释放、重建、拍照和错误日志都能明确指出是哪一路而不是依赖activeCameraPosition猜测。五、使用受限能力模式打开两路输入通过并发能力检查后再创建输入constfrontInput:camera.CameraInputmanager.createCameraInput(frontDevice);constbackInput:camera.CameraInputmanager.createCameraInput(backDevice);awaitfrontInput.open(camera.CameraConcurrentType.CAMERA_LIMITED_CAPABILITY);awaitbackInput.open(camera.CameraConcurrentType.CAMERA_LIMITED_CAPABILITY);这里的CAMERA_LIMITED_CAPABILITY很重要。前后并发模式不是把两套完整单摄能力简单叠加控制面和输出组合受到设备并发能力约束。根据当前官方指南并发受限模式应重点在以下基础控制范围内验证闪光灯。曝光。变焦。曝光补偿。对焦。防抖。色彩空间。即使某个控制在普通单摄会话可用也不能默认它在并发会话同样可用。六、输出 Profile 必须来自并发能力结果当前单摄服务通过普通输出能力选择预览和拍照 Profile。双摄路径不能直接复用默认 Profile因为并发模式下可用的分辨率、格式和输出组合可能更少。正确原则从匹配设备组合的CameraConcurrentInfo中读取输出能力。为前后两路分别选择真实存在的 PreviewProfile。只有在并发结果支持时才创建 PhotoOutput。不要写死两路都使用单摄最高分辨率。可以先实现“预览优先”的最小闭环探测并发能力 - 选择两路低风险 PreviewProfile - 双路预览稳定 - 再增加前后 PhotoOutput - 最后增加合成与装饰先上双路最高画质并同时开启人体识别容易在真机上同时遇到带宽、内存和功耗问题。七、页面需要两个独立 Surface当前页面只有privatepreviewController:XComponentControllernewXComponentController();StatepreviewSurfaceId:string;双摄需要两个控制器和 Surface IDprivatebackPreviewController:XComponentControllernewXComponentController();privatefrontPreviewController:XComponentControllernewXComponentController();StatebackSurfaceId:string;StatefrontSurfaceId:string;布局可以先采用主画面加画中画Stack(){XComponent({id:backPreview,type:XComponentType.SURFACE,controller:this.backPreviewController}).width(100%).height(100%)XComponent({id:frontPreview,type:XComponentType.SURFACE,controller:this.frontPreviewController}).width(112).height(148).position({x:188,y:18})}只有两个 Surface 都完成onLoad后才进入双摄启动。任一路 Surface 销毁都应停止双摄会话不能让 CameraInput 继续向失效 Surface 输出。八、双 Surface 就绪要有统一闸门推荐方法privatecanStartDualPreview():boolean{returnthis.cameraPermissionStatusgrantedthis.backSurfaceId.length0this.frontSurfaceId.length0!this.isDualStarting;}每个 SurfaceonLoad后都调用同一个尝试启动方法privatetryStartDualPreview():void{if(!this.canStartDualPreview()){return;}this.startDualPreview();}这样不依赖前置或后置 Surface 谁先加载也不会启动两次。九、双路拍照不要假设一个回调返回两张图如果目标是分别保存前后两张照片建议每一路维护自己的 PhotoOutput 和回调exportinterfaceDualCaptureResult{frontPixelMap?:image.PixelMap;backPixelMap?:image.PixelMap;frontReceived:boolean;backReceived:boolean;startedAt:number;}拍照入口记录 captureStartedAt - 清空前后上一轮结果 - 触发 frontPhotoOutput.capture() - 触发 backPhotoOutput.capture() - 分别等待两个 photoAvailable - 两路成功后进入合成 - 任一路失败则按产品策略降级不要因为后置先返回就把上一轮前置结果配对进本次。每一路回调都要用时间边界或 capture generation 判断归属。十、合成照片应在应用层完成如果产品需要一张“前后同框”图片推荐先拿到两张独立照片再在应用层做画中画或上下拼接。这样资源归属清楚也便于选择模板。业务模型可以记录exportinterfaceDualCapturedPhoto{id:string;createdAt:string;frontPhotoId:string;backPhotoId:string;layout:pictureInPicture|verticalSplit|horizontalSplit;watermark?:WatermarkSnapshot;}合成时的关键点两个 PixelMap 的方向和镜像分别处理。前置通常需要按产品预期镜像后置通常不镜像。先统一到目标画布尺寸再计算裁剪区域。合成完成后释放前后原始 PixelMap。如果用户取消合成仍要释放两路结果。十一、不支持双摄时的三级降级双摄不是所有机型的基础能力产品必须把降级设计放在入口而不是失败后只弹“拍摄失败”。降级一普通前后切换直接复用项目当前nextPosition()和单路预览是最稳定的兜底。降级二顺序双拍用户先拍后置应用自动切前置再拍第二张最后合成。它不是同时拍摄UI 文案必须写“顺序双拍”不能误导为双摄同开。降级三单路拍照加历史图片当前摄像头拍一张另一块区域从本地相册选择历史照片。此路径不依赖双摄并发但需要额外设计图库选择和隐私说明。能力路由示例exporttypeDualCameraModeconcurrent|sequential|single;exportfunctionresolveDualCameraMode(hasFront:boolean,hasBack:boolean,concurrentCount:number):DualCameraMode{if(hasFronthasBackconcurrentCount0){returnconcurrent;}if(hasFronthasBack){returnsequential;}returnsingle;}十二、权限与隐私控制前后双摄拍照仍使用name: ohos.permission.CAMERA如果只拍照片不应因为“双摄”额外申请麦克风权限。只有加入带声音的视频录制时才需要重新评估ohos.permission.MICROPHONE。隐私说明需要明确双摄模式会同时处理前后画面。是否保存两张原图还是只保存合成图。人体关键点或分割轮廓是否落盘。用户关闭双摄后是否立即停止并释放两路资源。当前项目不把人体目标持久化升级时应延续最小留存原则。十三、双路资源释放顺序建议为每一路统一释放取消回调和采样任务 - session.stop() - session.release() - photoOutput.release() - previewOutput.release() - cameraInput.close()双摄总释放要做到先把静态引用或状态引用置空防止新操作拿到旧对象。前后两路分别 try/catch某一路失败不阻止另一条释放。页面退出、任一 Surface 销毁、权限撤销和模式降级都走同一入口。迟到的前后 PixelMap 按 generation 判断失效结果立即释放。十四、错误码与恢复策略双摄比单摄更容易遇到资源冲突。需要重点识别错误信号含义方向建议恢复7400102操作不允许检查调用顺序、会话状态和能力边界7400107相机冲突释放旧会话确认没有其他应用占用7400108安全原因禁用相机提示用户检查系统安全设置7400201相机服务致命错误完整释放后重建仍失败则退回单摄不要在concurrentInfos为空时反复重试打开双摄。空结果是能力不支持不是瞬时网络错误。十五、建议补充的测试纯函数测试expect(resolveDualCameraMode(true,true,1)).assertEqual(concurrent);expect(resolveDualCameraMode(true,true,0)).assertEqual(sequential);expect(resolveDualCameraMode(false,true,0)).assertEqual(single);真机集成测试场景操作验收点支持并发机型打开双摄入口两路 Surface 都有稳定预览不支持并发机型打开双摄入口自动进入顺序双拍不报崩溃缺少前置启动应用只显示单摄模式连续双拍连续执行 10 次前后结果不会跨轮次配对快速切后台双摄预览时切后台两路会话都释放返回可重建Surface 销毁关闭画中画组件双摄立即停止不保留悬空输入前后合成保存画中画方向、镜像、水印和裁剪正确内存验证连续拍摄并取消PixelMap 不持续增长十六、常见问题排查现象可能原因排查方式两个摄像头都存在却不能同开设备组合不支持并发查getCameraConcurrentInfos()打开第二路报冲突未使用受限并发模式或旧会话未释放查 open 模式与资源释放双路预览一黑一亮某个 Surface 未就绪或 Profile 不匹配查两个 Surface ID 和并发输出能力单摄 Profile 可用双摄失败错用了普通输出能力从 CameraConcurrentInfo 选择 Profile前置方向反了没有单独处理镜像前后 PixelMap 分别处理方向两张照片错配没有 capture generation以同一轮次聚合前后回调退出页面仍占相机只释放一路资源前后流统一 stop/release/close不支持机型反复报错把空并发信息当瞬时失败直接进入顺序双拍降级双摄卡顿严重两路分辨率或识别负载过高先降低预览 Profile再逐步增加能力闪光灯在双摄失效并发受限控制面不同重新探测目标模式支持模拟器能跑真机失败模拟器不能代表并发硬件能力必须真机覆盖UI 宣称同时拍但实际顺序拍降级文案不诚实明确标注“顺序双拍”十七、上线前验收清单当前单摄实现与双摄升级边界已明确。前后设备使用位置 API 分别获取。打开输入前调用getCameraConcurrentInfos()。空并发结果直接进入降级不盲目重试。两路输入使用CAMERA_LIMITED_CAPABILITY打开。PreviewProfile 和 PhotoProfile 来自并发能力结果。页面拥有两个独立 XComponent Surface。两个 Surface 都就绪后只启动一次双摄。任一 Surface 销毁都会停止双摄。前后 PhotoOutput 回调按 capture generation 聚合。前置镜像和后置方向分别验证。合成取消、失败和成功路径都释放 PixelMap。不支持并发时提供顺序双拍或单摄模式。降级 UI 不把顺序拍摄描述为同时拍摄。拍照双摄不额外申请麦克风权限。双摄原图和合成图保存策略写入隐私说明。7400102、7400107、7400108、7400201有明确恢复路径。支持机型、不支持机型和单摄设备都完成真机回归。总结从单摄切换升级到前后双摄同开真正的难点不是多放一个 XComponent而是把能力探测、输出 Profile、两路资源所有权、异步回调配对和不支持设备降级设计清楚。当前1quweixiangji的单摄服务已经提供权限、设备发现、预览、拍照和释放底座双摄升级应在此基础上新增并发能力路由而不是破坏稳定的单摄主流程。可复用的核心顺序是先确认前后设备再查询getCameraConcurrentInfos()根据并发结果选择输出能力以CAMERA_LIMITED_CAPABILITY打开输入双 Surface 稳定后再接双路拍照最后做应用层合成。任何一步不满足都应诚实退回顺序双拍或普通单摄。这样双摄能力才是可用的产品功能而不是只在少数设备偶然运行的演示代码。