HarmonyOS技术精讲-Camera Kit(相机服务)第8篇:曝光控制深入解析
曝光控制,比你想象中复杂一点
很多人在用 Camera Kit 开发相机应用时,会卡在一个问题上:自动曝光在某些场景下表现不佳。
比如逆光拍人像,脸部过暗;夜景拍灯光,高光溢出。自动曝光算法再聪明,也没法懂你的构图意图。这时候就需要手动干预曝光参数。
但 HarmonyOS 的曝光控制 API 有个特点:看起来简单,实际用起来限制不少。官方文档只写了“支持设置曝光模式和补偿值”,但没说清楚这几个 API 在什么设备上生效、什么场景下会失效。这篇文章就把这些边界情况讲清楚。
这篇文章主要解决三个场景:
- 手动控制曝光时间(比如长曝光拍夜景)
- 调整曝光补偿(适应不同光照环境)
- 锁定自动曝光(对焦完成后不让画面变亮变暗)
环境说明
DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机(建议真机测试,模拟器在某些曝光 API 上行为不一致)核心实现
1. 获取并配置曝光模式
相机服务提供三种曝光模式:自动曝光、锁定曝光、手动曝光。
import{camera}from'@kit.CameraKit';// 在 session 创建完成后调用functionsetupExposure(session:camera.PhotoSession):void{// 检查设备是否支持手动曝光constexposureModeRange:camera.ExposureMode[]=session.getSupportedExposureModes();if(exposureModeRange.includes(camera.ExposureMode.MANUAL)){// 切换到手动曝光模式session.setExposureMode(camera.ExposureMode.MANUAL);}else{// 不支持手动曝光,回退到自动session.setExposureMode(camera.ExposureMode.AUTO);console.warn('当前设备不支持手动曝光模式');}}这段代码的核心逻辑很直接:先查能力,再设置模式。很多人直接调用setExposureMode(camera.ExposureMode.MANUAL),结果在某些机型上直接抛异常。
实际项目里,getSupportedExposureModes()返回的数组长度在不同设备上可能不同。低端机型可能只支持自动曝光和锁定曝光两种模式。
2. 设置曝光时间
手动模式下,曝光时间的单位是纳秒。
functionsetManualExposure(session:camera.PhotoSession,durationNs:number):void{// 获取支持的最小曝光时长和最大曝光时长const{min,max}=session.getSupportedExposureDurationRange();// 限制范围,防止设置超出硬件能力的值constclampedDuration=Math.min(Math.max(durationNs,min),max);// 设置曝光时长session.setExposure(clampedDuration);// 验证是否生效constactualDuration=session.getExposure();console.info(`已设置曝光时长:${actualDuration}ns`);}这里有个容易踩坑的地方:setExposure只在手动曝光模式下生效。如果在自动模式下调用,API 不会报错,但不会产生任何效果。
曝光时长范围一般是 1/10000 秒(100,000 ns)到 30 秒(30,000,000,000 ns),但这个范围因设备而异。建议在 UI 初始化时读取这个范围,用来约束滑块的最大最小值。
3. 曝光补偿( Exposure bias)
曝光补偿用于在自动曝光的基础上微调整体亮度。
functionsetExposureCompensation(session:camera.PhotoSession,compensation:number):void{// 获取支持的最小补偿值和最大补偿值const{min,max,step}=session.getSupportedExposureBiasRange();// 确保补偿值在有效范围内// 步进值一般为 0.1 或 0.3,需要对齐constalignedCompensation=Math.round(compensation/step)*step;constfinalCompensation=Math.min(Math.max(alignedCompensation,min),max);// 单位是 EV(曝光值)session.setExposureBias(finalCompensation);// 建议配置完成后读取以验证constactualBias=session.getExposureBias();console.info(`曝光补偿值:${actualBias}EV`);}补偿值的范围通常是 -4 EV 到 +4 EV,步进 0.1 EV 或 0.3 EV。但不同设备硬件差异较大,不要硬编码补偿范围。
自动曝光模式下,曝光补偿才能生效。如果在手动模式下设置 compensation,API 会直接返回错误码。
4. 曝光锁定( Auto Exposure Lock)
曝光锁定的作用是让相机保持在当前亮度水平,即使场景发生变化也不会重新调整。
functiontoggleExposureLock(session:camera.PhotoSession,lock:boolean):void{if(lock){// 锁定自动曝光session.lockAutoExposure();console.info('自动曝光已锁定');}else{// 解锁自动曝光session.unlockAutoExposure();console.info('自动曝光已解锁');}}// 检查当前锁定状态functionisExposureLocked(session:camera.PhotoSession):boolean{returnsession.isAutoExposureLocked();}曝光锁定适合在用户完成对焦和构图后使用。例如短视频录制过程中,场景内容在变化,但用户不希望画面忽明忽暗。
注意:曝光锁定只在自动曝光模式下有意义。如果已经在手动曝光模式下,锁定调用不会报错但不会改变行为。
5. 曝光调节滑块控件
把上面几个能力整合到一个 UI 组件中:
@Componentstruct ExposureControlPanel{@StateprivateexposureMode:camera.ExposureMode=camera.ExposureMode.AUTO;@StateprivateexposureValue:number=0;// 曝光时间,单位纳秒@StateprivateexposureBias:number=0;// 曝光补偿,单位 EV@StateprivateexposureLocked:boolean=false;privatesession:camera.PhotoSession|null=null;build(){Column({space:12}){// 曝光模式切换Row(){Text('曝光模式')Toggle({type:ToggleType.Switch,isOn:this.exposureMode===camera.ExposureMode.MANUAL}).onChange((isOn:boolean)=>{this.exposureMode=isOn?camera.ExposureMode.MANUAL:camera.ExposureMode.AUTO;this.session?.setExposureMode(this.exposureMode);})}// 手动曝光时长滑块(仅手动模式下可用)if(this.exposureMode===camera.ExposureMode.MANUAL){Slider({value:this.exposureValue,min:100000,// 最小 1/10000 秒max:30000000000,// 最大 30 秒step:100000}).onChange((value:number)=>{this.exposureValue=value;this.session?.setExposure(value);})}else{// 自动模式 + 曝光补偿Slider({value:this.exposureBias,min:-4,max:4,step:0.1}).onChange((value:number)=>{this.exposureBias=value;this.session?.setExposureBias(value);})// 曝光锁定开关Toggle({type:ToggleType.Switch,isOn:this.exposureLocked}).onChange((isOn:boolean)=>{this.exposureLocked=isOn;if(isOn){this.session?.lockAutoExposure();}else{this.session?.unlockAutoExposure();}})}}.padding(16).backgroundColor('#1A000000').borderRadius(12)}}这段代码的逻辑核心是:根据曝光模式动态切换 UI 控件。
手动模式下只显示曝光时长滑块;自动模式下显示曝光补偿滑块和锁定开关。这种设计避免了用户配置无效参数。
常见问题
问题 1:setExposure 设置了值但画面没变化
现象:在手动曝光模式下调用session.setExposure(value),API 返回成功,但预览画面亮度没有变化。
原因:这个 API 需要搭配正确的曝光模式才能生效。如果先调用了setExposureMode切换到手动模式,但在后续操作中因为某些原因回调又切换回了自动模式(比如restart()或重新配置 session),那么设置的曝光时长会被忽略。
解决方案:每次配置曝光参数前,先读取当前的曝光模式进行校验。
functionsafeSetExposure(session:camera.PhotoSession,duration:number):void{constcurrentMode=session.getExposureMode();if(currentMode!==camera.ExposureMode.MANUAL){console.warn('当前不是手动曝光模式,设置曝光时长无效');return;}session.setExposure(duration);}问题 2:曝光锁定在页面返回后失效
现象:用户锁定曝光后,按 Home 键返回桌面再回到应用,或者页面发生路由跳转,曝光锁定状态自动丢失。
原因:相机 session 在页面不可见时会被系统自动释放。页面重新可见后需要重新创建 session,之前的状态全部丢失。
解决方案:在页面的生命周期回调中保存和恢复曝光锁定状态。
@StateprivatelockState:boolean=false;privatesession:camera.PhotoSession|null=null;onPageShow():void{// 页面显示时,尝试恢复锁定状态if(this.lockState&&this.session){this.session.lockAutoExposure();}}onPageHide():void{// 页面隐藏时,状态保存在 @State 中}最佳实践
先查能力再设置:每次设置曝光参数前,调用
getSupportedExposureModes()、getSupportedExposureDurationRange()、getSupportedExposureBiasRange()获取设备能力。不同设备硬件差异大,硬编码范围会导致 API 调用异常。曝光补偿值的步进对齐:
setExposureBias传参必须是对齐 step 的值。比如 step=0.3 的设备,传入 1.0 会取整为 0.9。建议在 UI 中直接限制用户滑动到对齐值。锁定状态与 UI 同步:曝光锁定在 session 层面是一个一次性操作,没有 state 概念。UI 的锁定开关状态必须手动维护,不要在
lockAutoExposure或unlockAutoExposure之后直接读取 session 状态来更新 UI。
完整 Demo 入口
// entry/src/main/ets/pages/ExposureDemo.etsimport{camera}from'@kit.CameraKit';import{media}from'@kit.MediaKit';@Entry@Componentstruct ExposureDemo{privatesession:camera.PhotoSession|null=null;privatecameraManager:camera.CameraManager|null=null;aboutToAppear():void{this.initCamera();}asyncinitCamera():Promise<void>{// 获取相机管理器实例this.cameraManager=camera.getCameraManager();// 获取相机设备constcameras:camera.CameraDevice[]=this.cameraManager.getSupportedCameras();if(cameras.length===0){console.error('没有找到摄像头');return;}// 创建会话this.session=awaitthis.cameraManager.createCameraSession(camera.ControllerMode.PHOTO);awaitthis.session.beginConfig();awaitthis.session.addInput(cameras[0]);// 添加预览输出constpreviewOutput=awaitthis.session.createPreviewOutput();awaitthis.session.addOutput(previewOutput);// 提交配置awaitthis.session.commitConfig();awaitthis.session.start();}build(){Stack(){// 相机预览流(省略实际预览组件的实现)XComponent({id:'preview',type:XComponentType.SURFACE}).width('100%').height('100%')// 曝光控制面板ExposureControlPanel({session:this.session}).position({x:0,y:'70%'})}.width('100%').height('100%')}aboutToDisappear():void{this.session?.release();}}FAQ
Q:为什么真机正常,模拟器上曝光补偿值设置后画面不变化?
A:模拟器对曝光硬件的模拟不完整,特别是补偿值响应。建议真机调试曝光相关功能。
Q:为什么页面返回后曝光锁定失效了?
A:相机 session 在页面不可见时被系统自动释放。重新创建 session 后,之前锁定的状态不会自动恢复。需要在页面onPageShow回调中根据 UI 状态重新锁定。
Q:设置曝光补偿后,预览画面亮度没有立即变化,需要关闭重新打开 camera 吗?
A:不需要。setExposureBias是实时生效的,不需要重启 session。如果设置后 1-2 秒画面无变化,检查传入的值是否在设备支持的范围内。另一个可能性是当前处于手动曝光模式,补偿值不生效。