• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![dual-preview-streams-instructions](figures/dual-preview-streams-instructions.png)
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```