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在参考以下示例前,建议开发者查看[分段式拍照(ArkTS)](camera-deferred-capture.md)的具体章节,了解[设备输入](camera-device-input.md)、[会话管理](camera-session-management.md)、[拍照](camera-shooting.md)等单个流程。 14 15## 开发流程 16 17在获取到相机支持的输出流能力后,开始创建拍照流,开发流程如下。 18 19 20 21## 完整示例 22 23Context获取方式请参考:[获取UIAbility的上下文信息](../../application-models/uiability-usage.md#获取uiability的上下文信息)。 24 25```ts 26import { camera } from '@kit.CameraKit'; 27import { BusinessError } from '@kit.BasicServicesKit'; 28import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; 29import { photoAccessHelper } from '@kit.MediaLibraryKit'; 30 31let photoSession: camera.PhotoSession | undefined = undefined; 32let cameraInput: camera.CameraInput | undefined = undefined; 33let previewOutput: camera.PreviewOutput | undefined = undefined; 34let photoOutput: camera.PhotoOutput | undefined = undefined; 35 36function getPhotoAccessHelper(context: Context): photoAccessHelper.PhotoAccessHelper { 37 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 38 return phAccessHelper; 39} 40 41class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler<ArrayBuffer> { 42 onDataPrepared(data: ArrayBuffer) { 43 if (data === undefined) { 44 console.error('Error occurred when preparing data'); 45 return; 46 } 47 console.info('on image data prepared'); 48 // 请在获取到拍照buffer后,再释放session,提前释放session,会导致无法正常出图。 49 releaseCamSession(); 50 } 51} 52 53async function mediaLibRequestBuffer(photoAsset: photoAccessHelper.PhotoAsset, context: Context) { 54 let requestOptions: photoAccessHelper.RequestOptions = { 55 deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE, 56 } 57 const handler = new MediaDataHandler(); 58 await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler); 59 console.info('requestImageData successfully'); 60} 61 62async function mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset, 63 phAccessHelper: photoAccessHelper.PhotoAccessHelper): Promise<void> { 64 try { 65 let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = 66 new photoAccessHelper.MediaAssetChangeRequest(photoAsset); 67 assetChangeRequest.saveCameraPhoto(); 68 await phAccessHelper.applyChanges(assetChangeRequest); 69 console.info('apply saveCameraPhoto successfully'); 70 } catch (err) { 71 console.error(`apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`); 72 } 73} 74 75function setPhotoOutputCb(photoOutput: camera.PhotoOutput, context: Context): void { 76 // 监听回调之后,调用photoOutput的capture方法,低质量图上报后触发回调。 77 photoOutput.on('photoAssetAvailable', async (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): Promise<void> => { 78 console.info('getPhotoAsset start'); 79 console.error(`err: ${err}`); 80 if ((err !== undefined && err.code !== 0) || photoAsset === undefined) { 81 console.error('getPhotoAsset failed'); 82 return; 83 } 84 // 调用媒体库落盘接口保存一阶段低质量图,二阶段真图就绪后媒体库会主动帮应用替换落盘图片。 85 await mediaLibSavePhoto(photoAsset, getPhotoAccessHelper(context)); 86 // 调用媒体库接口注册低质量图或高质量图buffer回调,自定义处理。 87 // mediaLibRequestBuffer(photoAsset, context); 88 }); 89} 90 91 92async function deferredCaptureCase(context: Context, surfaceId: string): Promise<void> { 93 // 创建CameraManager对象。 94 let cameraManager: camera.CameraManager = camera.getCameraManager(context); 95 if (!cameraManager) { 96 console.error('camera.getCameraManager error'); 97 return; 98 } 99 // 监听相机状态变化。 100 cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => { 101 if (err !== undefined && err.code !== 0) { 102 console.error('cameraStatus with errorCode = ' + err.code); 103 return; 104 } 105 console.info(`camera : ${cameraStatusInfo.camera.cameraId}`); 106 console.info(`status: ${cameraStatusInfo.status}`); 107 }); 108 109 // 获取相机列表。 110 let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras(); 111 if (cameraArray.length <= 0) { 112 console.error('cameraManager.getSupportedCameras error'); 113 return; 114 } 115 116 for (let index = 0; index < cameraArray.length; index++) { 117 console.info('cameraId : ' + cameraArray[index].cameraId); // 获取相机ID。 118 console.info('cameraPosition : ' + cameraArray[index].cameraPosition); // 获取相机位置。 119 console.info('cameraType : ' + cameraArray[index].cameraType); // 获取相机类型。 120 console.info('connectionType : ' + cameraArray[index].connectionType); // 获取相机连接类型。 121 } 122 123 // 创建相机输入流。 124 try { 125 cameraInput = cameraManager.createCameraInput(cameraArray[0]); 126 } catch (error) { 127 let err = error as BusinessError; 128 console.error('Failed to createCameraInput errorCode = ' + err.code); 129 } 130 if (cameraInput === undefined) { 131 return; 132 } 133 134 // 监听cameraInput错误信息。 135 let cameraDevice: camera.CameraDevice = cameraArray[0]; 136 cameraInput.on('error', cameraDevice, (error: BusinessError) => { 137 console.error(`Camera input error code: ${error.code}`); 138 }) 139 140 // 打开相机。 141 await cameraInput.open(); 142 143 // 获取支持的模式类型。 144 let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]); 145 let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; 146 if (!isSupportPhotoMode) { 147 console.error('photo mode not support'); 148 return; 149 } 150 // 获取相机设备支持的输出流能力。 151 let cameraOutputCap: camera.CameraOutputCapability = 152 cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_PHOTO); 153 if (!cameraOutputCap) { 154 console.error('cameraManager.getSupportedOutputCapability error'); 155 return; 156 } 157 console.info('outputCapability: ' + JSON.stringify(cameraOutputCap)); 158 159 let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles; 160 if (!previewProfilesArray) { 161 console.error('createOutput previewProfilesArray == null || undefined'); 162 } 163 164 let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles; 165 if (!photoProfilesArray) { 166 console.error('createOutput photoProfilesArray == null || undefined'); 167 } 168 169 // 创建预览输出流,其中参数surfaceId参考上文XComponent组件,预览流为XComponent组件提供的surface。 170 try { 171 previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId); 172 } catch (error) { 173 let err = error as BusinessError; 174 console.error(`Failed to create the PreviewOutput instance. error code: ${err.code}`); 175 } 176 if (previewOutput === undefined) { 177 return; 178 } 179 180 // 监听预览输出错误信息。 181 previewOutput.on('error', (error: BusinessError) => { 182 console.error(`Preview output error code: ${error.code}`); 183 }); 184 185 // 创建拍照输出流。 186 try { 187 photoOutput = cameraManager.createPhotoOutput(photoProfilesArray[0]); 188 } catch (error) { 189 let err = error as BusinessError; 190 console.error('Failed to createPhotoOutput errorCode = ' + err.code); 191 } 192 if (photoOutput === undefined) { 193 return; 194 } 195 196 // 注册监听photoAssetAvailable回调。 197 setPhotoOutputCb(photoOutput, context); 198 199 // 创建会话。 200 try { 201 photoSession = cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; 202 } catch (error) { 203 let err = error as BusinessError; 204 console.error('Failed to create the session instance. errorCode = ' + err.code); 205 } 206 if (photoSession === undefined) { 207 return; 208 } 209 // 监听session错误信息。 210 photoSession.on('error', (error: BusinessError) => { 211 console.error(`Capture session error code: ${error.code}`); 212 }); 213 214 // 开始配置会话。 215 try { 216 photoSession.beginConfig(); 217 } catch (error) { 218 let err = error as BusinessError; 219 console.error('Failed to beginConfig. errorCode = ' + err.code); 220 } 221 222 // 向会话中添加相机输入流。 223 try { 224 photoSession.addInput(cameraInput); 225 } catch (error) { 226 let err = error as BusinessError; 227 console.error('Failed to addInput. errorCode = ' + err.code); 228 } 229 230 // 向会话中添加预览输出流。 231 try { 232 photoSession.addOutput(previewOutput); 233 } catch (error) { 234 let err = error as BusinessError; 235 console.error('Failed to addOutput(previewOutput). errorCode = ' + err.code); 236 } 237 238 // 向会话中添加拍照输出流。 239 try { 240 photoSession.addOutput(photoOutput); 241 } catch (error) { 242 let err = error as BusinessError; 243 console.error('Failed to addOutput(photoOutput). errorCode = ' + err.code); 244 } 245 246 // 提交会话配置。 247 await photoSession.commitConfig(); 248 249 // 启动会话。 250 await photoSession.start().then(() => { 251 console.info('Promise returned to indicate the session start success.'); 252 }); 253 // 判断设备是否支持闪光灯。 254 let flashStatus: boolean = false; 255 try { 256 flashStatus = photoSession.hasFlash(); 257 } catch (error) { 258 let err = error as BusinessError; 259 console.error('Failed to hasFlash. errorCode = ' + err.code); 260 } 261 console.info('Returned with the flash light support status:' + flashStatus); 262 263 if (flashStatus) { 264 // 判断是否支持自动闪光灯模式。 265 let flashModeStatus: boolean = false; 266 try { 267 let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO); 268 flashModeStatus = status; 269 } catch (error) { 270 let err = error as BusinessError; 271 console.error('Failed to check whether the flash mode is supported. errorCode = ' + err.code); 272 } 273 if (flashModeStatus) { 274 // 设置自动闪光灯模式。 275 try { 276 photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO); 277 } catch (error) { 278 let err = error as BusinessError; 279 console.error('Failed to set the flash mode. errorCode = ' + err.code); 280 } 281 } 282 } 283 284 // 判断是否支持连续自动变焦模式。 285 let focusModeStatus: boolean = false; 286 try { 287 let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO); 288 focusModeStatus = status; 289 } catch (error) { 290 let err = error as BusinessError; 291 console.error('Failed to check whether the focus mode is supported. errorCode = ' + err.code); 292 } 293 294 if (focusModeStatus) { 295 // 设置连续自动变焦模式。 296 try { 297 photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO); 298 } catch (error) { 299 let err = error as BusinessError; 300 console.error('Failed to set the focus mode. errorCode = ' + err.code); 301 } 302 } 303 304 // 获取相机支持的可变焦距比范围。 305 let zoomRatioRange: Array<number> = []; 306 try { 307 zoomRatioRange = photoSession.getZoomRatioRange(); 308 } catch (error) { 309 let err = error as BusinessError; 310 console.error('Failed to get the zoom ratio range. errorCode = ' + err.code); 311 } 312 if (zoomRatioRange.length <= 0) { 313 return; 314 } 315 316 // 设置可变焦距比。 317 try { 318 photoSession.setZoomRatio(zoomRatioRange[0]); 319 } catch (error) { 320 let err = error as BusinessError; 321 console.error('Failed to set the zoom ratio value. errorCode = ' + err.code); 322 } 323 let photoCaptureSetting: camera.PhotoCaptureSetting = { 324 quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高。 325 rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0。 326 } 327 328 // 使用当前拍照设置触发一次拍照。 329 photoOutput.capture(photoCaptureSetting, (err: BusinessError) => { 330 if (err) { 331 console.error(`Failed to capture the photo ${err.message}`); 332 return; 333 } 334 console.info('Callback invoked to indicate the photo capture request success.'); 335 }); 336} 337 338async function releaseCamSession() { 339 // 停止当前会话。 340 await photoSession?.stop(); 341 342 // 释放拍照输出流。 343 await photoOutput?.release(); 344 345 // 释放预览输出流。 346 await previewOutput?.release(); 347 348 // 释放相机输入流。 349 await cameraInput?.close(); 350 351 // 释放会话。 352 await photoSession?.release(); 353 354 // 会话置空。 355 photoSession = undefined; 356} 357 358@Entry 359@Component 360struct Index { 361 @State message: string = 'PhotoAssetDemo'; 362 @State isShow: boolean = false; 363 private mXComponentController: XComponentController = new XComponentController(); 364 private surfaceId = ''; 365 private uiContext: UIContext = this.getUIContext(); 366 private context: Context | undefined = this.uiContext.getHostContext(); 367 private cameraPermission: Permissions = 'ohos.permission.CAMERA'; // 申请权限相关问题可参考本篇开头的申请相关权限文档 368 369 async requestPermissionsFn(): Promise<void> { 370 let atManager = abilityAccessCtrl.createAtManager(); 371 if (this.context) { 372 let res = await atManager.requestPermissionsFromUser(this.context, [this.cameraPermission]); 373 for (let i =0; i < res.permissions.length; i++) { 374 if (this.cameraPermission.toString() === res.permissions[i] && res.authResults[i] === 0) { 375 this.isShow = true; 376 } 377 } 378 } 379 } 380 381 aboutToAppear(): void { 382 this.requestPermissionsFn(); 383 } 384 385 build() { 386 Column() { 387 Column() { 388 if (this.isShow) { 389 XComponent({ 390 id: 'componentId', 391 type: XComponentType.SURFACE, 392 controller: this.mXComponentController 393 }) 394 .onLoad(async () => { 395 console.info('onLoad is called'); 396 if (this.context) { 397 this.surfaceId = this.mXComponentController.getXComponentSurfaceId(); 398 console.info(`onLoad surfaceId: ${this.surfaceId}`); 399 deferredCaptureCase(this.context, this.surfaceId); 400 } 401 }) 402 .renderFit(RenderFit.RESIZE_CONTAIN) 403 } 404 } 405 .height('95%') 406 .justifyContent(FlexAlign.Center) 407 408 Text(this.message) 409 .id('PhotoAssetDemo') 410 .fontSize(38) 411 .fontWeight(FontWeight.Bold) 412 .alignRules({ 413 center: { anchor: '__container__', align: VerticalAlign.Center }, 414 middle: { anchor: '__container__', align: HorizontalAlign.Center } 415 }) 416 } 417 .height('100%') 418 .width('100%') 419 } 420} 421```