1# 预览(ArkTS) 2<!--Kit: Camera Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @qano--> 5<!--Designer: @leo_ysl--> 6<!--Tester: @xchaosioda--> 7<!--Adviser: @zengyawen--> 8 9在开发相机应用时,需要先[申请相关权限](camera-preparation.md)。 10 11预览是启动相机后看见的画面,通常在拍照和录像前执行。 12 13## 开发步骤 14 15详细的API说明请参考[Camera API参考](../../reference/apis-camera-kit/arkts-apis-camera.md)。 16 171. 导入camera接口,接口中提供了相机相关的属性和方法,导入方法如下。 18 19 ```ts 20 import { camera } from '@kit.CameraKit'; 21 import { BusinessError } from '@kit.BasicServicesKit'; 22 ``` 23 242. 创建Surface。 25 26 相机开发模型为Surface模型,该模型主要通过Surface实现数据交互。在开发相机应用界面时,首先需要通过创建XComponent组件为预览流提供Surface,再通过获取XComponent组件对应Surface的ID创建预览流,预览流画面即可直接在XComponent组件内渲染,详细获取surfaceId请参考[getXComponentSurfaceId](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md#getxcomponentsurfaceid9)方法。而XComponent的能力由UI提供,相关介绍可参考[XComponent组件参考](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)。 27 28 > **说明:** 29 > 预览流与录像输出流的分辨率的宽高比要保持一致,如果设置XComponent组件中的Surface显示区域宽高比为1920:1080 = 16:9,则需要预览流中的分辨率的宽高比也为16:9,如分辨率选择640:360,或960:540,或1920:1080,以此类推。 30 31 ```ts 32 @Entry 33 @Component 34 struct example { 35 xComponentCtl: XComponentController = new XComponentController(); 36 surfaceId:string = ''; 37 imageWidth: number = 1920; 38 imageHeight: number = 1080; 39 private uiContext: UIContext = this.getUIContext(); 40 41 build() { 42 XComponent({ 43 id: 'componentId', 44 type: XComponentType.SURFACE, 45 controller: this.xComponentCtl 46 }) 47 .onLoad(async () => { 48 console.info('onLoad is called'); 49 this.surfaceId = this.xComponentCtl.getXComponentSurfaceId(); // 获取组件surfaceId。 50 // 使用surfaceId创建预览流,开启相机,组件实时渲染每帧预览流数据。 51 }) 52 // surface的宽、高设置与XComponent组件的宽、高设置相反,或使用.renderFit(RenderFit.RESIZE_CONTAIN)自动填充显示无需设置宽、高。 53 .width(this.uiContext.px2vp(this.imageHeight)) 54 .height(this.uiContext.px2vp(this.imageWidth)) 55 } 56 } 57 ``` 58 593. 通过[CameraOutputCapability](../../reference/apis-camera-kit/arkts-apis-camera-i.md#cameraoutputcapability)中的previewProfiles属性获取当前设备支持的预览能力,返回previewProfilesArray数组 。通过[createPreviewOutput](../../reference/apis-camera-kit/arkts-apis-camera-CameraManager.md#createpreviewoutput)方法创建预览输出流,其中,[createPreviewOutput](../../reference/apis-camera-kit/arkts-apis-camera-CameraManager.md#createpreviewoutput)方法中的两个参数分别是当前设备支持的预览配置信息previewProfile和步骤二中获取的surfaceId。 60 61 ```ts 62 function getPreviewOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability, surfaceId: string): camera.PreviewOutput | undefined { 63 let previewProfilesArray: Array<camera.Profile> = cameraOutputCapability.previewProfiles; 64 let previewOutput: camera.PreviewOutput | undefined = undefined; 65 try { 66 //previewProfilesArray要选择与步骤二设置宽高比一致的previewProfile配置信息,此处选择数组第一项仅供接口使用示例参考。 67 previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId); 68 } catch (error) { 69 let err = error as BusinessError; 70 console.error("Failed to create the PreviewOutput instance. error code: " + err.code); 71 } 72 return previewOutput; 73 } 74 ``` 75 764. 使能。通过[Session.start](../../reference/apis-camera-kit/arkts-apis-camera-Session.md#start11)方法输出预览流,接口调用失败会返回相应错误码,错误码类型参见[Camera错误码](../../reference/apis-camera-kit/arkts-apis-camera-e.md#cameraerrorcode)。 77 78 ```ts 79 async function startPreviewOutput(cameraManager: camera.CameraManager, previewOutput: camera.PreviewOutput): Promise<void> { 80 let cameraArray: Array<camera.CameraDevice> = []; 81 cameraArray = cameraManager.getSupportedCameras(); 82 if (cameraArray.length == 0) { 83 console.error('no camera.'); 84 return; 85 } 86 // 获取支持的模式类型。 87 let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]); 88 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 89 if (!isSupportPhotoMode) { 90 console.error('photo mode not support'); 91 return; 92 } 93 let cameraInput: camera.CameraInput | undefined = undefined; 94 cameraInput = cameraManager.createCameraInput(cameraArray[0]); 95 if (cameraInput === undefined) { 96 console.error('cameraInput is undefined'); 97 return; 98 } 99 // 打开相机。 100 await cameraInput.open(); 101 let session: camera.PhotoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 102 session.beginConfig(); 103 session.addInput(cameraInput); 104 session.addOutput(previewOutput); 105 await session.commitConfig(); 106 await session.start(); 107 } 108 ``` 109 110 111## 状态监听 112 113在相机应用开发过程中,可以随时监听预览输出流状态,包括预览流启动、预览流结束、预览流输出错误。 114 115- 通过注册固定的frameStart回调函数获取监听预览启动结果,previewOutput创建成功时即可监听,预览第一次曝光时触发,有该事件返回结果则认为预览流已启动。 116 117 ```ts 118 function onPreviewOutputFrameStart(previewOutput: camera.PreviewOutput): void { 119 previewOutput.on('frameStart', (err: BusinessError) => { 120 if (err !== undefined && err.code !== 0) { 121 return; 122 } 123 console.info('Preview frame started'); 124 }); 125 } 126 ``` 127 128- 通过注册固定的frameEnd回调函数获取监听预览结束结果,previewOutput创建成功时即可监听,预览完成最后一帧时触发,有该事件返回结果则认为预览流已结束。 129 130 ```ts 131 function onPreviewOutputFrameEnd(previewOutput: camera.PreviewOutput): void { 132 previewOutput.on('frameEnd', (err: BusinessError) => { 133 if (err !== undefined && err.code !== 0) { 134 return; 135 } 136 console.info('Preview frame ended'); 137 }); 138 } 139 ``` 140 141- 通过注册固定的error回调函数获取监听预览输出错误结果,回调返回预览输出接口使用错误时对应的错误码,错误码类型参见[Camera错误码](../../reference/apis-camera-kit/arkts-apis-camera-e.md#cameraerrorcode)。 142 143 ```ts 144 function onPreviewOutputError(previewOutput: camera.PreviewOutput): void { 145 previewOutput.on('error', (previewOutputError: BusinessError) => { 146 console.error(`Preview output error code: ${previewOutputError.code}`); 147 }); 148 } 149 ``` 150 151## 完整示例 152 153```ts 154import { camera } from '@kit.CameraKit'; 155import { BusinessError } from '@kit.BasicServicesKit'; 156import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; 157 158 159@Entry 160@Component 161struct Index { 162 private xComponentCtl: XComponentController = new XComponentController(); 163 private xComponentSurfaceId: string = ''; 164 @State imageWidth: number = 1920; 165 @State imageHeight: number = 1080; 166 private cameraManager: camera.CameraManager | undefined = undefined; 167 private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = []; 168 private cameraInput: camera.CameraInput | undefined = undefined; 169 private previewOutput: camera.PreviewOutput | undefined = undefined; 170 private session: camera.VideoSession | undefined = undefined; 171 private uiContext: UIContext = this.getUIContext(); 172 private context: Context | undefined = this.uiContext.getHostContext(); 173 private cameraPermission: Permissions = 'ohos.permission.CAMERA'; // 申请权限相关问题可参考本篇开头的申请相关权限文档 174 @State isShow: boolean = false; 175 176 177 async requestPermissionsFn(): Promise<void> { 178 let atManager = abilityAccessCtrl.createAtManager(); 179 if (this.context) { 180 let res = await atManager.requestPermissionsFromUser(this.context, [this.cameraPermission]); 181 for (let i =0; i < res.permissions.length; i++) { 182 if (this.cameraPermission.toString() === res.permissions[i] && res.authResults[i] === 0) { 183 this.isShow = true; 184 } 185 } 186 } 187 } 188 189 aboutToAppear(): void { 190 this.requestPermissionsFn(); 191 } 192 193 onPageShow(): void { 194 console.info('onPageShow'); 195 if (this.xComponentSurfaceId !== '') { 196 this.initCamera(); 197 } 198 } 199 200 onPageHide(): void { 201 console.info('onPageHide'); 202 this.releaseCamera(); 203 } 204 205 build() { 206 Column() { 207 if (this.isShow) { 208 XComponent({ 209 id: 'componentId', 210 type: XComponentType.SURFACE, 211 controller: this.xComponentCtl 212 }) 213 .onLoad(async () => { 214 console.info('onLoad is called'); 215 this.xComponentSurfaceId = this.xComponentCtl.getXComponentSurfaceId(); // 获取组件surfaceId。 216 // 初始化相机,组件实时渲染每帧预览流数据。 217 this.initCamera() 218 }) 219 .width(this.uiContext.px2vp(this.imageHeight)) 220 .height(this.uiContext.px2vp(this.imageWidth)) 221 } 222 } 223 .justifyContent(FlexAlign.Center) 224 .height('100%') 225 .width('100%') 226 } 227 228 229 // 初始化相机。 230 async initCamera(): Promise<void> { 231 console.info(`initCamera previewOutput xComponentSurfaceId:${this.xComponentSurfaceId}`); 232 try { 233 // 获取相机管理器实例。 234 this.cameraManager = camera.getCameraManager(this.context); 235 if (!this.cameraManager) { 236 console.error('initCamera getCameraManager'); 237 } 238 // 获取当前设备支持的相机device列表。 239 this.cameras = this.cameraManager.getSupportedCameras(); 240 if (!this.cameras) { 241 console.error('initCamera getSupportedCameras'); 242 } 243 // 选择一个相机device,创建cameraInput输出对象。 244 this.cameraInput = this.cameraManager.createCameraInput(this.cameras[0]); 245 if (!this.cameraInput) { 246 console.error('initCamera createCameraInput'); 247 } 248 // 打开相机。 249 await this.cameraInput.open().catch((err: BusinessError) => { 250 console.error(`initCamera open fail: ${err}`); 251 }) 252 // 获取相机device支持的profile。 253 let capability: camera.CameraOutputCapability = 254 this.cameraManager.getSupportedOutputCapability(this.cameras[0], camera.SceneMode.NORMAL_VIDEO); 255 if (!capability) { 256 console.error('initCamera getSupportedOutputCapability'); 257 } 258 let minRatioDiff : number = 0.1; 259 let surfaceRatio : number = this.imageWidth / this.imageHeight; // 最接近16:9宽高比。 260 let previewProfile: camera.Profile = capability.previewProfiles[0]; 261 // 应用开发者根据实际业务需求选择一个支持的预览流previewProfile。 262 // 此处以选择CAMERA_FORMAT_YUV_420_SP(NV21)格式、满足限定条件条件分辨率的预览流previewProfile为例。 263 for (let index = 0; index < capability.previewProfiles.length; index++) { 264 const tempProfile = capability.previewProfiles[index]; 265 let tempRatio = tempProfile.size.width >= tempProfile.size.height ? 266 tempProfile.size.width / tempProfile.size.height : tempProfile.size.height / tempProfile.size.width; 267 let currentRatio = Math.abs(tempRatio - surfaceRatio); 268 if (currentRatio <= minRatioDiff && tempProfile.format == camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP) { 269 previewProfile = tempProfile; 270 break; 271 } 272 } 273 this.imageWidth = previewProfile.size.width; // 更新xComponent组件的宽。 274 this.imageHeight = previewProfile.size.height; // 更新xComponent组件的高。 275 console.info(`initCamera imageWidth:${this.imageWidth} imageHeight:${this.imageHeight}`); 276 277 // 使用xComponentSurfaceId创建预览。 278 this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId); 279 if (!this.previewOutput) { 280 console.error('initCamera createPreviewOutput'); 281 } 282 // 创建录像模式相机会话。 283 this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession; 284 if (!this.session) { 285 console.error('initCamera createSession'); 286 } 287 // 开始配置会话。 288 this.session.beginConfig(); 289 // 添加相机设备输入。 290 this.session.addInput(this.cameraInput); 291 // 添加预览流输出。 292 this.session.addOutput(this.previewOutput); 293 // 提交会话配置。 294 await this.session.commitConfig(); 295 // 开始启动已配置的输入输出流。 296 await this.session.start(); 297 } catch (error) { 298 console.error(`initCamera fail: ${error}`); 299 } 300 } 301 302 303 // 释放相机。 304 async releaseCamera(): Promise<void> { 305 console.info('releaseCamera E'); 306 try { 307 // 停止当前会话。 308 await this.session?.stop(); 309 // 释放相机输入流。 310 await this.cameraInput?.close(); 311 // 释放预览输出流。 312 await this.previewOutput?.release(); 313 // 释放会话。 314 await this.session?.release(); 315 } catch (error) { 316 console.error(`initCamera fail: ${error}`); 317 } 318 } 319} 320```