720P / 1080P / 4K / 高画质——HarmonyOS PreconfigType 和 Preconfig
文章目录
- 什么是 preconfig?
- 支持的档位
- 使用流程
- 完整的 CameraService 实现
- 页面里加个画质选择器
- 录像模式下也可以用
- preconfig vs 手动配置,代码量对比
- 几个注意点
- 写在最后
写过相机分辨率适配代码的人都知道这有多烦:要遍历设备支持的所有 profile,筛宽高比,防止拉伸,预览/拍照/录像还要分别处理,不同手机支持的分辨率也不一样……
光分辨率配置就能写几百行。
后来发现了 preconfig,代码量直接缩到 6 行。
什么是 preconfig?
preconfig(预配置)是 HarmonyOS 的"一键设定画质档位"方案:
- 你只需要告诉系统"我要 1080P 的画质"
- 系统自动帮你选好预览/拍照/录像的最优分辨率组合
- 不同手机会自动适配
类比一下:就像点外卖时选"小份/中份/大份",不需要你指定"我要 200g 鸡腿、50g 米饭……"
支持的档位
// 画质档位enumPreconfigType{PRECONFIG_720P=0,// 720×1280,省流量,速度快PRECONFIG_1080P=1,// 1080×1920,日常使用推荐PRECONFIG_4K=2,// 2160×3840,高清,但文件大PRECONFIG_HIGH_QUALITY=3// 最高画质(支持 HDR),色彩最丰富}// 画面比例enumPreconfigRatio{PRECONFIG_RATIO_1_1=0,// 1:1 方形(适合头像、朋友圈)PRECONFIG_RATIO_4_3=1,// 4:3(传统照片比例)PRECONFIG_RATIO_16_9=2// 16:9(全面屏手机默认比例)}组合起来就是:你说要"1080P + 16:9",系统帮你配好一切。
使用流程
关键点:preconfig必须在beginConfig之前调用。
完整的 CameraService 实现
这里为了避免命名冲突 在文件名后面多加一个D 注意区分
// model/CameraServiceD.etsimport{camera}from'@kit.CameraKit';import{BusinessError}from'@kit.BasicServicesKit';import{display}from'@kit.ArkUI';constTAG='CameraService';classCameraServiceD{privatecameraManager:camera.CameraManager;privatecameraInput:camera.CameraInput|undefined=undefined;privatepreviewOutput:camera.PreviewOutput|undefined=undefined;privatephotoOutput:camera.PhotoOutput|undefined=undefined;privatesession:camera.PhotoSession|undefined=undefined;constructor(context:Context){this.cameraManager=camera.getCameraManager(context);}/** * 用 preconfig 初始化相机,指定画质档位和画面比例 */asyncinitCameraWithPreconfig(surfaceId:string,preconfigType:camera.PreconfigType,configRatio:camera.PreconfigRatio):Promise<void>{try{awaitthis.releaseAll();constcameras=this.cameraManager.getSupportedCameras();constdevice=cameras.find(d=>d.cameraPosition===camera.CameraPosition.CAMERA_POSITION_BACK);if(!device)return;this.cameraInput=this.cameraManager.createCameraInput(device);awaitthis.cameraInput.open();// 不传 profile,让 preconfig 决定分辨率this.previewOutput=this.cameraManager.createPreviewOutput(surfaceId);this.photoOutput=this.cameraManager.createPhotoOutput();awaitthis.buildSessionWithPreconfig(this.cameraInput,this.previewOutput,this.photoOutput,preconfigType,configRatio);}catch(e){console.error(TAG,`initCameraWithPreconfig failed:${JSON.stringify(e)}`);}}privateasyncbuildSessionWithPreconfig(cameraInput:camera.CameraInput,previewOutput:camera.PreviewOutput,photoOutput:camera.PhotoOutput,preconfigType:camera.PreconfigType,configRatio:camera.PreconfigRatio):Promise<void>{constsession=this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO)ascamera.PhotoSession;// 先检查是否支持,不检查直接调 preconfig 会抛异常letisSupported=false;try{isSupported=session.canPreconfig(preconfigType,configRatio);}catch(e){console.error(TAG,`canPreconfig error:${(easBusinessError).code}`);return;}if(!isSupported){console.warn(TAG,`preconfig not supported, should fallback to manual config`);return;}// 一行搞定分辨率配置session.preconfig(preconfigType,configRatio);console.info(TAG,`preconfig applied: type=${preconfigType}, ratio=${configRatio}`);// 之后按正常流程走session.beginConfig();session.addInput(cameraInput);session.addOutput(previewOutput);session.addOutput(photoOutput);awaitsession.commitConfig();// 顺手设一下预览旋转角try{constdisplayRotation=display.getDefaultDisplaySync().rotation;constimageRotation=displayRotation*camera.ImageRotation.ROTATION_90;constpreviewRotation=previewOutput.getPreviewRotation(imageRotation);previewOutput.setPreviewRotation(previewRotation,false);}catch(e){console.error(TAG,`setPreviewRotation failed`);}awaitsession.start();this.session=session;console.info(TAG,'preconfig session started');}asyncreleaseAll():Promise<void>{awaitthis.session?.stop();awaitthis.previewOutput?.release();awaitthis.photoOutput?.release();awaitthis.cameraInput?.close();awaitthis.session?.release();this.session=undefined;this.previewOutput=undefined;this.photoOutput=undefined;this.cameraInput=undefined;}}exportdefaultCameraServiceD;页面里加个画质选择器
// pages/CameraServicePage.etsimportCameraServiceDfrom'../model/CameraServiceD';import{camera}from'@kit.CameraKit';@Entry@Componentstruct CameraServicePage{privatepreconfigOptions:SelectOption[]=[{value:'720P 省流量'},{value:'1080P 推荐'},{value:'4K 高清'},{value:'最高画质 HDR'}];privateratioOptions:SelectOption[]=[{value:'1:1 方形'},{value:'4:3 传统'},{value:'16:9 全屏'}];@StateselectedPreconfig:number=1;// 默认 1080P@StateselectedRatio:number=2;// 默认 16:9privatemXComponentController:XComponentController=newXComponentController();privatesurfaceId:string='';privateCameraServiceD:CameraServiceD|undefined=undefined;privategetPreconfigType():camera.PreconfigType{constmap=[camera.PreconfigType.PRECONFIG_720P,camera.PreconfigType.PRECONFIG_1080P,camera.PreconfigType.PRECONFIG_4K,camera.PreconfigType.PRECONFIG_HIGH_QUALITY];returnmap[this.selectedPreconfig]??camera.PreconfigType.PRECONFIG_1080P;}privategetPreconfigRatio():camera.PreconfigRatio{constmap=[camera.PreconfigRatio.PRECONFIG_RATIO_1_1,camera.PreconfigRatio.PRECONFIG_RATIO_4_3,camera.PreconfigRatio.PRECONFIG_RATIO_16_9];returnmap[this.selectedRatio]??camera.PreconfigRatio.PRECONFIG_RATIO_16_9;}privateasyncreinitCamera():Promise<void>{constcontext=this.getUIContext().getHostContext()!;this.CameraServiceD=newCameraServiceD(context);awaitthis.CameraServiceD.initCameraWithPreconfig(this.surfaceId,this.getPreconfigType(),this.getPreconfigRatio());}build(){Column(){// 顶部画质和比例选择Row({space:16}){Text('画质:').fontSize(14).fontColor(Color.White)Select(this.preconfigOptions).selected(this.selectedPreconfig).onSelect(async(index)=>{this.selectedPreconfig=index;if(this.surfaceId)awaitthis.reinitCamera();})Text('比例:').fontSize(14).fontColor(Color.White)Select(this.ratioOptions).selected(this.selectedRatio).onSelect(async(index)=>{this.selectedRatio=index;if(this.surfaceId)awaitthis.reinitCamera();})}.width('100%').padding({left:16,top:8,right:16,bottom:8})// 相机预览XComponent({type:XComponentType.SURFACE,controller:this.mXComponentController}).width('100%').layoutWeight(1).onLoad(async()=>{this.surfaceId=this.mXComponentController.getXComponentSurfaceId();awaitthis.reinitCamera();})}.width('100%').height('100%').backgroundColor(Color.Black)}}录像模式下也可以用
asyncfunctionbuildVideoSessionWithPreconfig(cameraManager:camera.CameraManager,cameraInput:camera.CameraInput,previewOutput:camera.PreviewOutput,videoOutput:camera.VideoOutput,preconfigType:camera.PreconfigType,configRatio:camera.PreconfigRatio):Promise<void>{constsession=cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO)ascamera.VideoSession;if(!session.canPreconfig(preconfigType,configRatio))return;session.preconfig(preconfigType,configRatio);session.beginConfig();session.addInput(cameraInput);session.addOutput(previewOutput);session.addOutput(videoOutput);awaitsession.commitConfig();awaitsession.start();}preconfig vs 手动配置,代码量对比
手动配置分辨率(传统方式)大概要写这么多:
// 手动配置:遍历 profiles,匹配宽高比,选最优分辨率……constcapability=cameraManager.getSupportedOutputCapability(device,sceneMode);constpreviewProfiles=capability.previewProfiles;letoptimalProfile:camera.Profile|undefined;letmaxHeight=0;for(constprofileofpreviewProfiles){if(profile.format!==1003)continue;constratio=profile.size.width/profile.size.height;if(Math.abs(ratio-targetRatio)>0.2)continue;if(profile.size.height>=maxHeight){optimalProfile=profile;maxHeight=profile.size.height;}}// 还需要对 photoProfiles 和 videoProfiles 做同样的事……preconfig 方式:
// 就这两行,其他不管if(session.canPreconfig(camera.PreconfigType.PRECONFIG_1080P,camera.PreconfigRatio.PRECONFIG_RATIO_16_9)){session.preconfig(camera.PreconfigType.PRECONFIG_1080P,camera.PreconfigRatio.PRECONFIG_RATIO_16_9);}几个注意点
必须先canPreconfig再preconfig:跳过检查直接调会抛异常,别省这一行。
preconfig要在beginConfig之前调用:顺序不能乱。
旧设备可能不支持:API 20 以下的设备canPreconfig可能返回false,要做 fallback 处理。
切换画质需要重建 Session:调了preconfig后需要重新commitConfig+start,不能在 Session 运行中途切换。
写在最后
preconfig 真的让我省了很多时间。如果不是需要精确控制分辨率(比如 AI 摄像头要求特定输入尺寸),直接用 preconfig 就好了。
嗯这里得说下 哪个尺寸设计有点问题 各位同学在写的时候注意下哈 ,我是真的不想调试了
