• 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## 开发步骤
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```