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