HarmonyOS技术精讲-Camera Kit相机服务第10篇动态调整预览帧率1. 开篇预览帧率不是越高越好HarmonyOS NEXT 的 Camera Kit 里预览帧率这个参数经常被开发者忽略。很多人拿到相机预览流之后直接用默认帧率跑结果要么画面卡顿要么设备发热严重。其实大部分场景下我们根本不需要 60fps 的预览——扫码只需要 15fps视频通话 30fps 足够只有游戏或运动场景才需要高帧率。官方文档虽然提供了setFrameRateRange接口但实际项目中很多人不知道怎么查当前设备支持哪些帧率、怎么动态切换、切换后怎么确认生效。这篇文章就把这些细节讲清楚。2. 它解决什么问题相机预览帧率直接影响了两个关键指标流畅度帧率越高预览画面越平滑但功耗也越高功耗帧率越低CPU/GPU 负载越小电池续航越长适用场景扫码界面10-15fps 就够没必要上 30fps普通拍照预览30fps 是合理选择视频录制预览或运动场景60fps 能带来更流畅的取景体验低功耗模式降到 15-20fps 延长设备使用时间不适合场景不需要实时取景的后台应用对功耗极度敏感但取景质量要求不高的设备与其他方案对比方案特点缺点固定最高帧率实现简单但不灵活功耗高设备容易发热固定最低帧率省电预览体验差动态调整帧率根据场景自动切换平衡流畅度和功耗需要额外判断逻辑推荐使用动态调整方案。3. 环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机真机测试模拟器对相机支持有限4. 核心实现动态调整预览帧率4.1 权限与准备工作首先确认module.json5中已添加相机权限{module:{requestPermissions:[{name:ohos.permission.CAMERA}]}}4.2 核心代码帧率查询与动态切换这一段代码用于获取当前相机支持的帧率范围、动态设置帧率、监听帧率变化确认生效。// FrameRateManager.etsimport{camera}fromkit.CameraKit;import{common}fromkit.AbilityKit;exportclassFrameRateManager{privatecameraInput:camera.CameraInput|nullnull;privatepreviewOutput:camera.PreviewOutput|nullnull;privatecurFpsRange:camera.FpsRange|nullnull;// 查询当前预览输出支持的帧率范围asyncquerySupportedFrameRates(previewOutput:camera.PreviewOutput):Promisecamera.FpsRange[]{try{constrangeArrawaitpreviewOutput.getActiveFrameRateRange();if(!rangeArr||rangeArr.length0){console.warn(未查询到帧率范围使用默认值);return[];}returnrangeArr;}catch(err){console.error(查询帧率范围失败:${JSON.stringify(err)});return[];}}// 动态设置帧率范围asyncsetFrameRange(previewOutput:camera.PreviewOutput,min:number,max:number):Promisevoid{if(!previewOutput){console.error(previewOutput 为空无法设置帧率);return;}// 构造 FpsRange 对象constfpsRange:camera.FpsRange{min:min,max:max};try{awaitpreviewOutput.setFrameRateRange(fpsRange);this.curFpsRangefpsRange;console.info(帧率设置成功:${min}-${max});}catch(err){console.error(设置帧率失败:${JSON.stringify(err)});// 常见的失败原因设备不支持该范围、设置时机不对}}// 监听帧率变化回调registerFrameRateCallback(previewOutput:camera.PreviewOutput,callback:(range:camera.FpsRange)void):void{if(!previewOutput)return;previewOutput.on(frameRateChange,(range:camera.FpsRange){console.info(帧率已变化:${range.min}-${range.max});this.curFpsRangerange;callback(range);});}// 解除监听unregisterFrameRateCallback(previewOutput:camera.PreviewOutput):void{if(!previewOutput)return;previewOutput.off(frameRateChange);}}代码说明getActiveFrameRateRange()查询当前设备支持的帧率范围返回一个数组每个元素包含min和max。不同设备的支持范围不同常见的有[15, 30]、[30, 30]固定 30fps、[15, 60]等。setFrameRateRange()设置帧率范围。注意这里的min和max是帧率最小值/最大值系统会在这个范围内动态调整不一定固定在某一个值上。on(frameRateChange)监听帧率实际变化。设置后不一定立即生效系统会根据光照、CPU 负载等因素动态调整监听到回调才能确认生效。为什么推荐用setFrameRateRange而不是直接固定一个值因为setFrameRateRange允许系统在给定范围内自适应调整既保证了流畅度又兼顾功耗。如果强行固定为某个值比如固定 60fps系统不会自动降帧设备容易发热。4.3 页面中集成// FrameRateDemo.etsimport{camera}fromkit.CameraKit;import{common}fromkit.AbilityKit;import{FrameRateManager}from./FrameRateManager;EntryComponentstruct FrameRateDemo{StatecurrentFpsText:string未选择;StateselectedMode:number0;// 0: 低帧率, 1: 中帧率, 2: 高帧率privateframeRateManager:FrameRateManagernewFrameRateManager();privatepreviewOutput:camera.PreviewOutput|nullnull;aboutToAppear(){this.initCamera();}asyncinitCamera(){// 标准相机初始化流程篇幅原因省略完整初始化重点看帧率部分// 假设已通过 cameraManager 创建 cameraInput 和 previewOutput// ...// 注册帧率变化监听this.frameRateManager.registerFrameRateCallback(this.previewOutput!,(range){// 更新 UIthis.currentFpsText${range.min}-${range.max};});}// 切换帧率模式switchFrameRate(mode:number){this.selectedModemode;switch(mode){case0:// 低功耗模式: 15-20fpsthis.frameRateManager.setFrameRange(this.previewOutput!,15,20);break;case1:// 平衡模式: 20-30fpsthis.frameRateManager.setFrameRange(this.previewOutput!,20,30);break;case2:// 流畅模式: 30-60fpsthis.frameRateManager.setFrameRange(this.previewOutput!,30,60);break;}}build(){Column(){Text(帧率控制).fontSize(24).fontWeight(FontWeight.Bold).margin({bottom:20})Text(当前帧率:${this.currentFpsText}).fontSize(18).margin({bottom:30})// 三个模式按钮Button(低帧率(15-20fps) - 省电模式).width(300).height(50).backgroundColor(this.selectedMode0?#007DFF:#666666).onClick(()this.switchFrameRate(0)).margin({bottom:10})Button(中帧率(20-30fps) - 平衡模式).width(300).height(50).backgroundColor(this.selectedMode1?#007DFF:#666666).onClick(()this.switchFrameRate(1)).margin({bottom:10})Button(高帧率(30-60fps) - 流畅模式).width(300).height(50).backgroundColor(this.selectedMode2?#007DFF:#666666).onClick(()this.switchFrameRate(2))}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}关键点按钮切换时调用setFrameRange系统会重新调整帧率范围通过on(frameRateChange)实时更新 UI让用户看到切换是否生效不同模式使用不同的帧率范围模拟了真实场景中的帧率自适应逻辑5. 踩坑记录坑1设置帧率后预览画面黑屏现象调用setFrameRange后预览画面突然黑屏过几秒才恢复。原因部分设备在帧率切换时底层相机流水线需要重新配置导致短暂中断。特别是从高帧率突然降到低帧率时缓冲队列可能需要清空。解决方案调用setFrameRange前先检查previewOutput的状态是否为Running如果处于Stopped状态先调用start()再设置帧率不建议在预览流刚启动时就立即切换帧率等待 500ms 以上再操作// 安全设置帧率的示例asyncsafeSetFrameRange(output:camera.PreviewOutput,min:number,max:number){// 等待预览流稳定awaitnewPromise(resolvesetTimeout(resolve,600));conststateoutput.getState();if(state!camera.PreviewState.RUNNING){console.warn(预览流未运行先启动);awaitoutput.start();// 再等待一次awaitnewPromise(resolvesetTimeout(resolve,200));}awaitthis.frameRateManager.setFrameRange(output,min,max);}坑2帧率变化监听不触发现象设置了帧率范围但frameRateChange回调从来没触发过。原因on(frameRateChange)不是实时回调系统只在帧率发生实际变化时才触发。如果设置的min和max与当前范围完全一致或者设备没有足够的资源变化比如一直处于稳定光照回调可能不触发。解决方案不要依赖监听做状态判断应该同时也通过 UI 反馈给用户设置后主动更新本地状态作为兜底如果必须确认是否生效可以用getActiveFrameRateRange()查询当前范围// 确认帧率设置是否生效asyncconfirmFrameRate(output:camera.PreviewOutput):Promiseboolean{constrangesawaitthis.frameRateManager.querySupportedFrameRates(output);if(ranges.length0)returnfalse;constcurrentRangeranges[0];// 取第一个// 这里可以跟期望的 range 做对比console.info(当前实际帧率范围:${currentRange.min}-${currentRange.max});returntrue;}6. 最佳实践不要在 onPageShow 中直接设置帧率页面显示时预览流可能还没完全就绪建议放在aboutToAppear或首次预览回调之后。否则设置可能被忽略。多次切换帧率时先查询支持的帧率范围不同设备、不同分辨率下支持的帧率范围可能不同。硬编码[15, 60]在低端设备上可能失败。帧率切换频率不要太快每次切换都会触发底层流重建频繁切换比如每秒切一次可能导致预览卡顿甚至崩溃。建议至少间隔 2 秒。结合场景自动切换更合理比如用传感器或业务状态判断当前是扫码、预览还是录制自动选择合适的帧率而不是让用户手动调整。7. Demo 完整入口EntryComponentstruct Index{build(){FrameRateDemo()}}8. FAQQ为什么真机 60fps 跑不满A实际帧率受光照、CPU 负载、摄像头传感器能力等多方面影响。比如夜间模式或低光环境下系统会自动降帧保证画质。设置[30, 60]的范围不意味着一定能跑到 60fps只能保证在条件允许时能达到。Q设置固定帧率比如 [30, 30]和范围帧率比如 [30, 60]有什么区别A固定帧率[30, 30]是锁死帧率系统不会自适应调整功耗相对较高但可预测。范围帧率[30, 60]系统会在该范围内动态调整负载低时跑 60负载高时降为 30。推荐使用范围帧率。Q为什么模拟器上setFrameRateRange一直报错A模拟器对相机硬件的模拟有限很多高级功能如帧率动态调整在模拟器中不支持。调试相机相关功能强烈建议真机。Q动态调整帧率会影响录像质量吗A会。预览帧率的改动会影响录像底层的数据源部分设备在录像时预览帧率需要跟录制帧率保持一致性。如果需要同时录像建议在录像开始前调整好预览帧率录像过程中尽量不要切换。