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相机应用通过控制相机,实现图像显示(预览)、照片保存(拍照)、视频录制(录像)等基础操作。相机开发模型为Surface模型,即应用通过Surface进行数据传递,通过ImageReceiver的surface获取拍照流的数据、通过XComponent的surface获取预览流的数据。 14 15如果要实现双路预览,即将拍照流改为预览流,将拍照流中的surface改为预览流的surface,通过ImageReceiver的surface创建previewOutput,其余流程与拍照流和预览流一致。 16 17详细的API说明请参考[Camera API参考](../../reference/apis-camera-kit/arkts-apis-camera.md)。 18 19## 约束与限制 20 21- 暂不支持动态添加流,即不能在没有调用[session.stop](../../reference/apis-camera-kit/arkts-apis-camera-Session.md#stop11)的情况下,调用[addOutput](../../reference/apis-camera-kit/arkts-apis-camera-Session.md#addoutput11)添加流。 22- 对ImageReceiver组件获取到的图像数据处理后,需要将对应的图像Buffer释放,确保Surface的BufferQueue正常轮转。 23 24## 调用流程 25 26双路方案调用流程图建议如下: 27 28 29 30## 开发步骤 31 32- 用于处理图像的第一路预览流:创建ImageReceiver对象,获取SurfaceId创建第一路预览流,注册图像监听,按需处理预览流每帧图像。 33- 用于显示画面的第二路预览流:创建XComponent组件,获取SurfaceId创建第二路预览流,预览流画面直接在组件内渲染。 34- 创建预览流获取数据:创建上述两路预览流,配置进相机会话,启动会话后,两路预览流同时获取数据。 35 36### 用于处理图像的第一路预览流 37 381. 导入依赖,本篇文档需要用到图片和相机框架等相关依赖包。 39 40 ```ts 41 import { image } from '@kit.ImageKit'; 42 import { camera } from '@kit.CameraKit'; 43 import { BusinessError } from '@kit.BasicServicesKit'; 44 ``` 45 462. 获取第一路预览流SurfaceId:创建ImageReceiver对象,通过ImageReceiver对象可获取其SurfaceId。 47 48 ```ts 49 let imageWidth: number = 1920; // 请使用设备支持profile的size的宽。 50 let imageHeight: number = 1080; // 请使用设备支持profile的size的高。 51 52 async function initImageReceiver():Promise<void>{ 53 // 创建ImageReceiver对象。 54 let size: image.Size = { width: imageWidth, height: imageHeight }; 55 let imageReceiver: image.ImageReceiver | undefined; 56 try { 57 imageReceiver = image.createImageReceiver(size, image.ImageFormat.JPEG, 8); 58 } catch (error) { 59 let err = error as BusinessError; 60 console.error(`Init image receiver failed. error code: ${err.code}`); 61 return; 62 } 63 // 获取第一路流SurfaceId。 64 let imageReceiverSurfaceId = await imageReceiver.getReceivingSurfaceId(); 65 console.info(`initImageReceiver imageReceiverSurfaceId:${imageReceiverSurfaceId}`); 66 } 67 ``` 683. ImageReceiver接收预览流图像数据获取图像格式请参考[Image](../../reference/apis-image-kit/arkts-apis-image-Image.md)中的format参数,[PixelMap](../../reference/apis-image-kit/arkts-apis-image-PixelMap.md)格式请参考[PixelMapFormat](../../reference/apis-image-kit/arkts-apis-image-e.md#pixelmapformat7)。 69 70 ```ts 71 // Image格式与PixelMap格式映射关系。 72 let formatToPixelMapFormatMap = new Map<number, image.PixelMapFormat>([ 73 [12, image.PixelMapFormat.RGBA_8888], 74 [25, image.PixelMapFormat.NV21], 75 [35, image.PixelMapFormat.YCBCR_P010], 76 [36, image.PixelMapFormat.YCRCB_P010] 77 ]); 78 // PixelMapFormat格式的单个像素点大小映射关系。 79 let pixelMapFormatToSizeMap = new Map<image.PixelMapFormat, number>([ 80 [image.PixelMapFormat.RGBA_8888, 4], 81 [image.PixelMapFormat.NV21, 1.5], 82 [image.PixelMapFormat.YCBCR_P010, 3], 83 [image.PixelMapFormat.YCRCB_P010, 3] 84 ]); 85 ``` 86 874. 注册监听处理预览流每帧图像数据:通过ImageReceiver组件中imageArrival事件监听获取底层返回的图像数据,详细的API说明请参考[Image API参考](../../reference/apis-image-kit/arkts-apis-image-ImageReceiver.md)。 88 89 > **说明:** 90 > 91 > - 在通过[createPixelMap](../../reference/apis-image-kit/arkts-apis-image-f.md#imagecreatepixelmap8)接口创建[PixelMap](../../reference/apis-image-kit/arkts-apis-image-PixelMap.md)实例时,设置的Size、srcPixelFormat等属性必须和相机预览输出流previewProfile中配置的Size、Format属性保持一致,ImageReceiver图片像素格式请参考[PixelMapFormat](../../reference/apis-image-kit/arkts-apis-image-e.md#pixelmapformat7),相机预览输出流previewProfile输出格式请参考[CameraFormat](../../reference/apis-camera-kit/arkts-apis-camera-e.md#cameraformat)。 92 > - 由于一多设备产品差异性,应用开发者在创建相机预览输出流前,必须先通过[getSupportedOutputCapability](../../reference/apis-camera-kit/arkts-apis-camera-CameraManager.md#getsupportedoutputcapability11)方法获取当前设备支持的预览输出流previewProfile,再根据实际业务需求选择[CameraFormat](../../reference/apis-camera-kit/arkts-apis-camera-e.md#cameraformat)和[Size](../../reference/apis-camera-kit/arkts-apis-camera-i.md#size)适合的预览输出流previewProfile。 93 > - ImageReceiver接收预览流图像数据实际format格式由应用开发者在创建预览输出流相机预览输出流时,根据实际业务需求选择的previewProfile中format格式参数影响,详细步骤请参考[创建预览流获取数据](camera-dual-channel-preview.md#创建预览流获取数据)。 94 95 96 ```ts 97 function onImageArrival(receiver: image.ImageReceiver): void { 98 // 注册imageArrival监听。 99 receiver.on('imageArrival', () => { 100 // 获取图像。 101 receiver.readNextImage((err: BusinessError, nextImage: image.Image) => { 102 if (err || nextImage === undefined) { 103 console.error('readNextImage failed'); 104 return; 105 } 106 // 解析图像内容。 107 nextImage.getComponent(image.ComponentType.JPEG, async (err: BusinessError, imgComponent: image.Component) => { 108 if (err || imgComponent === undefined) { 109 console.error('getComponent failed'); 110 } 111 if (imgComponent.byteBuffer) { 112 // 详情见下方解析图片buffer数据参考,本示例以方式一为例。 113 let width = nextImage.size.width; // 获取图片的宽。 114 let height = nextImage.size.height; // 获取图片的高。 115 let stride = imgComponent.rowStride; // 获取图片的stride。 116 let imageFormat = nextImage.format; // 获取图片的format。 117 let pixelMapFormat = formatToPixelMapFormatMap.get(imageFormat) ?? image.PixelMapFormat.NV21; 118 let mSize = pixelMapFormatToSizeMap.get(pixelMapFormat) ?? 1.5; 119 console.info(`getComponent with width:${width} height:${height} stride:${stride}`); 120 // pixelMap创建时使用的size、srcPixelFormat需要与相机预览输出流previewProfile中的size、format保持一致。 121 // stride与width一致。 122 let pixelMap : image.PixelMap; 123 if (stride == width) { 124 pixelMap = await image.createPixelMap(imgComponent.byteBuffer, { 125 size: { height: height, width: width }, 126 srcPixelFormat: pixelMapFormat, 127 }) 128 } else { 129 // stride与width不一致。 130 const dstBufferSize = width * height * mSize 131 const dstArr = new Uint8Array(dstBufferSize) 132 for (let j = 0; j < height * mSize; j++) { 133 const srcBuf = new Uint8Array(imgComponent.byteBuffer, j * stride, width) 134 dstArr.set(srcBuf, j * width) 135 } 136 pixelMap = await image.createPixelMap(dstArr.buffer, { 137 size: { height: height, width: width }, 138 srcPixelFormat: pixelMapFormat, 139 }) 140 } 141 // 确保当前pixelMap没有在使用的情况下,可进行资源释放。 142 if (pixelMap != undefined) { 143 await pixelMap.release().then(() => { 144 console.info('Succeeded in releasing pixelMap object.'); 145 }).catch((error: BusinessError) => { 146 console.error(`Failed to release pixelMap object. code is ${error.code}, message is ${error.message}`); 147 }) 148 } 149 } else { 150 console.error('byteBuffer is null'); 151 } 152 // 确保当前buffer没有在使用的情况下,可进行资源释放。 153 // 如果对buffer进行异步操作,需要在异步操作结束后再释放该资源(nextImage.release())。 154 nextImage.release(); 155 }) 156 }) 157 }) 158 } 159 ``` 160 161 通过 [image.Component](../../reference/apis-image-kit/arkts-apis-image-i.md#component9) 解析图片buffer数据参考: 162 163 > **注意:** 164 > 需要确认图像的宽width是否与行距rowStride一致,如果不一致可参考以下方式处理: 165 166 方式一:去除imgComponent.byteBuffer中stride数据,拷贝得到新的buffer,调用不支持stride的接口处理buffer。 167 168 ```ts 169 // pixelMap创建时使用的size、srcPixelFormat需要与相机预览输出流previewProfile中的size、format保持一致. 170 const dstBufferSize = width * height * mSize; 171 const dstArr = new Uint8Array(dstBufferSize); 172 // 逐行读取buffer数据。 173 for (let j = 0; j < height * mSize; j++) { 174 // imgComponent.byteBuffer的每行数据拷贝前width个字节到dstArr中。 175 const srcBuf = new Uint8Array(imgComponent.byteBuffer, j * stride, width); 176 dstArr.set(srcBuf, j * width); 177 } 178 let pixelMap = await image.createPixelMap(dstArr.buffer, { 179 size: { height: height, width: width }, srcPixelFormat: pixelMapFormat 180 181 }); 182 ``` 183 184 方式二:根据stride*height创建pixelMap,然后调用pixelMap的cropSync方法裁剪掉多余的像素。 185 186 ```ts 187 // 创建pixelMap,width宽传行距stride的值。 188 let pixelMap = await image.createPixelMap(imgComponent.byteBuffer, { 189 size:{height: height, width: stride}, srcPixelFormat: pixelMapFormat}); 190 // 裁剪多余的像素。 191 pixelMap.cropSync({size:{width:width, height:height}, x:0, y:0}); 192 ``` 193 194 方式三:将原始imgComponent.byteBuffer和stride信息一起传给支持stride的接口处理。 195 196 197 198### 用于显示画面的第二路预览流 199 200获取第二路预览流SurfaceId:创建XComponent组件用于预览流显示,获取surfaceId请参考XComponent组件提供的[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)。 201 202```ts 203@Component 204struct example { 205 xComponentCtl: XComponentController = new XComponentController(); 206 surfaceId:string = ''; 207 imageWidth: number = 1920; 208 imageHeight: number = 1080; 209 private uiContext: UIContext = this.getUIContext(); 210 211 build() { 212 XComponent({ 213 id: 'componentId', 214 type: XComponentType.SURFACE, 215 controller: this.xComponentCtl 216 }) 217 .onLoad(async () => { 218 console.info('onLoad is called'); 219 this.surfaceId = this.xComponentCtl.getXComponentSurfaceId(); // 获取组件surfaceId。 220 // 使用surfaceId创建预览流,开启相机,组件实时渲染每帧预览流数据。 221 }) 222 // surface的宽、高设置与XComponent组件的宽、高设置相反,或使用.renderFit(RenderFit.RESIZE_CONTAIN)自动填充显示无需设置宽、高。 223 .width(this.uiContext.px2vp(this.imageHeight)) 224 .height(this.uiContext.px2vp(this.imageWidth)) 225 } 226} 227``` 228 229 230 231### 创建预览流获取数据 232 233通过两个SurfaceId分别创建两路预览流输出,加入相机会话,启动相机会话,获取预览流数据。 234 235```ts 236function createDualPreviewOutput(cameraManager: camera.CameraManager, previewProfile: camera.Profile, 237 session: camera.Session, imageReceiverSurfaceId: string, xComponentSurfaceId: string): void { 238 // 使用imageReceiverSurfaceId创建第一路预览。 239 let previewOutput1 = cameraManager.createPreviewOutput(previewProfile, imageReceiverSurfaceId); 240 if (!previewOutput1) { 241 console.error('createPreviewOutput1 error'); 242 } 243 // 使用xComponentSurfaceId创建第二路预览。 244 let previewOutput2 = cameraManager.createPreviewOutput(previewProfile, xComponentSurfaceId); 245 if (!previewOutput2) { 246 console.error('createPreviewOutput2 error'); 247 } 248 // 添加第一路预览流输出。 249 session.addOutput(previewOutput1); 250 // 添加第二路预览流输出。 251 session.addOutput(previewOutput2); 252} 253``` 254 255 256 257## 完整示例 258 259```ts 260import { camera } from '@kit.CameraKit'; 261import { image } from '@kit.ImageKit'; 262import { BusinessError } from '@kit.BasicServicesKit'; 263import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; 264 265@Entry 266@Component 267struct Index { 268 private imageReceiver: image.ImageReceiver | undefined = undefined; 269 private imageReceiverSurfaceId: string = ''; 270 private xComponentCtl: XComponentController = new XComponentController(); 271 private xComponentSurfaceId: string = ''; 272 @State imageWidth: number = 1920; 273 @State imageHeight: number = 1080; 274 private cameraManager: camera.CameraManager | undefined = undefined; 275 private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = []; 276 private cameraInput: camera.CameraInput | undefined = undefined; 277 private previewOutput1: camera.PreviewOutput | undefined = undefined; 278 private previewOutput2: camera.PreviewOutput | undefined = undefined; 279 private session: camera.VideoSession | undefined = undefined; 280 private uiContext: UIContext = this.getUIContext(); 281 private context: Context | undefined = this.uiContext.getHostContext(); 282 private cameraPermission: Permissions = 'ohos.permission.CAMERA'; // 申请权限相关问题可参考本篇开头的申请相关权限文档 283 @State isShow: boolean = false; 284 285 async requestPermissionsFn(): Promise<void> { 286 let atManager = abilityAccessCtrl.createAtManager(); 287 if (this.context) { 288 let res = await atManager.requestPermissionsFromUser(this.context, [this.cameraPermission]); 289 for (let i =0; i < res.permissions.length; i++) { 290 if (this.cameraPermission.toString() === res.permissions[i] && res.authResults[i] === 0) { 291 this.isShow = true; 292 } 293 } 294 } 295 } 296 297 aboutToAppear(): void { 298 this.requestPermissionsFn(); 299 } 300 301 onPageShow(): void { 302 console.info('onPageShow'); 303 this.initImageReceiver(); 304 if (this.xComponentSurfaceId !== '') { 305 this.initCamera(); 306 } 307 } 308 309 onPageHide(): void { 310 console.info('onPageHide'); 311 this.releaseCamera(); 312 } 313 314 /** 315 * 获取ImageReceiver的SurfaceId 316 * @param receiver 317 * @returns 318 */ 319 async initImageReceiver(): Promise<void> { 320 if (!this.imageReceiver) { 321 // 创建ImageReceiver。 322 let size: image.Size = { width: this.imageWidth, height: this.imageHeight }; 323 try { 324 this.imageReceiver = image.createImageReceiver(size, image.ImageFormat.JPEG, 8); 325 } catch (error) { 326 let err = error as BusinessError; 327 console.error(`Init image receiver failed. error code: ${err.code}`); 328 return; 329 } 330 // 获取第一路流SurfaceId。 331 this.imageReceiverSurfaceId = await this.imageReceiver.getReceivingSurfaceId(); 332 console.info(`initImageReceiver imageReceiverSurfaceId:${this.imageReceiverSurfaceId}`); 333 // 注册监听处理预览流每帧图像数据。 334 this.onImageArrival(this.imageReceiver); 335 } 336 } 337 338 // Image格式与PixelMap格式映射关系。 339 private formatToPixelMapFormatMap = new Map<number, image.PixelMapFormat>([ 340 [12, image.PixelMapFormat.RGBA_8888], 341 [25, image.PixelMapFormat.NV21], 342 [35, image.PixelMapFormat.YCBCR_P010], 343 [36, image.PixelMapFormat.YCRCB_P010] 344 ]); 345 // PixelMapFormat格式的单个像素点大小映射关系。 346 private pixelMapFormatToSizeMap = new Map<image.PixelMapFormat, number>([ 347 [image.PixelMapFormat.RGBA_8888, 4], 348 [image.PixelMapFormat.NV21, 1.5], 349 [image.PixelMapFormat.YCBCR_P010, 3], 350 [image.PixelMapFormat.YCRCB_P010, 3] 351 ]); 352 353 /** 354 * 注册ImageReceiver图像监听 355 * @param receiver 356 */ 357 onImageArrival(receiver: image.ImageReceiver): void { 358 // 注册imageArrival监听。 359 receiver.on('imageArrival', () => { 360 console.info('image arrival'); 361 // 获取图像。 362 receiver.readNextImage((err: BusinessError, nextImage: image.Image) => { 363 if (err || nextImage === undefined) { 364 console.error('readNextImage failed'); 365 return; 366 } 367 // 解析图像内容。 368 nextImage.getComponent(image.ComponentType.JPEG, async (err: BusinessError, imgComponent: image.Component) => { 369 if (err || imgComponent === undefined) { 370 console.error('getComponent failed'); 371 } 372 if (imgComponent.byteBuffer) { 373 // 请参考步骤7解析buffer数据,本示例以方式一为例。 374 let width = nextImage.size.width; // 获取图片的宽。 375 let height = nextImage.size.height; // 获取图片的高。 376 let stride = imgComponent.rowStride; // 获取图片的stride。 377 let imageFormat = nextImage.format; // 获取图片的format。 378 let pixelMapFormat = this.formatToPixelMapFormatMap.get(imageFormat) ?? image.PixelMapFormat.NV21; 379 let mSize = this.pixelMapFormatToSizeMap.get(pixelMapFormat) ?? 1.5; 380 console.info(`getComponent with width:${width} height:${height} stride:${stride}`); 381 // pixelMap创建时使用的size、srcPixelFormat需要与相机预览输出流previewProfile中的size、format保持一致。此处format以NV21格式为例。 382 // stride与width一致。 383 let pixelMap: image.PixelMap; 384 if (stride == width) { 385 pixelMap = await image.createPixelMap(imgComponent.byteBuffer, { 386 size: { height: height, width: width }, 387 srcPixelFormat: pixelMapFormat, 388 }) 389 } else { 390 // stride与width不一致。 391 const dstBufferSize = width * height * mSize // 以NV21为例(YUV_420_SP格式的图片)YUV_420_SP内存计算公式:长x宽+(长x宽)/2。 392 const dstArr = new Uint8Array(dstBufferSize) 393 for (let j = 0; j < height * mSize; j++) { 394 const srcBuf = new Uint8Array(imgComponent.byteBuffer, j * stride, width) 395 dstArr.set(srcBuf, j * width) 396 } 397 pixelMap = await image.createPixelMap(dstArr.buffer, { 398 size: { height: height, width: width }, 399 srcPixelFormat: pixelMapFormat, 400 }) 401 } 402 // 确保当前pixelMap没有在使用的情况下,注意要进行资源释放。 403 if (pixelMap != undefined) { 404 await pixelMap.release().then(() => { 405 console.info('Succeeded in releasing pixelMap object.'); 406 }).catch((error: BusinessError) => { 407 console.error(`Failed to release pixelMap object. code is ${error.code}, message is ${error.message}`); 408 }) 409 } 410 } else { 411 console.error('byteBuffer is null'); 412 } 413 // 确保当前buffer没有在使用的情况下,可进行资源释放。 414 // 如果对buffer进行异步操作,需要在异步操作结束后再释放该资源(nextImage.release())。 415 nextImage.release(); 416 console.info('image process done'); 417 }) 418 }) 419 }) 420 } 421 422 build() { 423 Column() { 424 if (this.isShow) { 425 XComponent({ 426 id: 'componentId', 427 type: XComponentType.SURFACE, 428 controller: this.xComponentCtl 429 }) 430 .onLoad(async () => { 431 console.info('onLoad is called'); 432 this.xComponentSurfaceId = this.xComponentCtl.getXComponentSurfaceId(); // 获取组件surfaceId。 433 // 初始化相机,组件实时渲染每帧预览流数据。 434 this.initCamera() 435 }) 436 .width(this.uiContext.px2vp(this.imageHeight)) 437 .height(this.uiContext.px2vp(this.imageWidth)) 438 } 439 } 440 .justifyContent(FlexAlign.Center) 441 .height('100%') 442 .width('100%') 443 } 444 445 // 初始化相机。 446 async initCamera(): Promise<void> { 447 console.info(`initCamera imageReceiverSurfaceId:${this.imageReceiverSurfaceId} xComponentSurfaceId:${this.xComponentSurfaceId}`); 448 try { 449 // 获取相机管理器实例。 450 this.cameraManager = camera.getCameraManager(this.context); 451 if (!this.cameraManager) { 452 console.error('initCamera getCameraManager'); 453 } 454 // 获取当前设备支持的相机device列表。 455 this.cameras = this.cameraManager.getSupportedCameras(); 456 if (!this.cameras) { 457 console.error('initCamera getSupportedCameras'); 458 } 459 // 选择一个相机device,创建cameraInput输出对象。 460 this.cameraInput = this.cameraManager.createCameraInput(this.cameras[0]); 461 if (!this.cameraInput) { 462 console.error('initCamera createCameraInput'); 463 } 464 // 打开相机。 465 await this.cameraInput.open().catch((err: BusinessError) => { 466 console.error(`initCamera open fail: ${err}`); 467 }) 468 // 获取相机device支持的profile。 469 let capability: camera.CameraOutputCapability = 470 this.cameraManager.getSupportedOutputCapability(this.cameras[0], camera.SceneMode.NORMAL_VIDEO); 471 if (!capability) { 472 console.error('initCamera getSupportedOutputCapability'); 473 } 474 let minRatioDiff : number = 0.1; 475 let surfaceRatio : number = this.imageWidth / this.imageHeight; // 最接近16:9宽高比。 476 let previewProfile: camera.Profile = capability.previewProfiles[0]; 477 // 应用开发者根据实际业务需求选择一个支持的预览流previewProfile。 478 // 此处以选择CAMERA_FORMAT_YUV_420_SP(NV21)格式、满足限定条件分辨率的预览流previewProfile为例。 479 for (let index = 0; index < capability.previewProfiles.length; index++) { 480 const tempProfile = capability.previewProfiles[index]; 481 let tempRatio = tempProfile.size.width >= tempProfile.size.height ? 482 tempProfile.size.width / tempProfile.size.height : tempProfile.size.height / tempProfile.size.width; 483 let currentRatio = Math.abs(tempRatio - surfaceRatio); 484 if (currentRatio <= minRatioDiff && tempProfile.format == camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP) { 485 previewProfile = tempProfile; 486 break; 487 } 488 } 489 this.imageWidth = previewProfile.size.width; // 更新xComponent组件的宽。 490 this.imageHeight = previewProfile.size.height; // 更新xComponent组件的高。 491 console.info(`initCamera imageWidth:${this.imageWidth} imageHeight:${this.imageHeight}`); 492 // 使用imageReceiverSurfaceId创建第一路预览。 493 this.previewOutput1 = this.cameraManager.createPreviewOutput(previewProfile, this.imageReceiverSurfaceId); 494 if (!this.previewOutput1) { 495 console.error('initCamera createPreviewOutput1'); 496 } 497 // 使用xComponentSurfaceId创建第二路预览。 498 this.previewOutput2 = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId); 499 if (!this.previewOutput2) { 500 console.error('initCamera createPreviewOutput2'); 501 } 502 // 创建录像模式相机会话。 503 this.session = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession; 504 if (!this.session) { 505 console.error('initCamera createSession'); 506 } 507 // 开始配置会话。 508 this.session.beginConfig(); 509 // 添加相机设备输入。 510 this.session.addInput(this.cameraInput); 511 // 添加第一路预览流输出。 512 this.session.addOutput(this.previewOutput1); 513 // 添加第二路预览流输出。 514 this.session.addOutput(this.previewOutput2); 515 // 提交会话配置。 516 await this.session.commitConfig(); 517 // 开始启动已配置的输入输出流。 518 await this.session.start(); 519 } catch (error) { 520 console.error(`initCamera fail: ${error}`); 521 } 522 } 523 524 // 释放相机。 525 async releaseCamera(): Promise<void> { 526 console.info('releaseCamera E'); 527 try { 528 // 停止当前会话。 529 await this.session?.stop(); 530 // 释放相机输入流。 531 await this.cameraInput?.close(); 532 // 释放预览输出流。 533 await this.previewOutput1?.release(); 534 // 释放拍照输出流。 535 await this.previewOutput2?.release(); 536 // 释放会话。 537 await this.session?.release(); 538 } catch (error) { 539 console.error(`initCamera fail: ${error}`); 540 } 541 } 542} 543```