• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 录像实践(ArkTS)
2
3在开发相机应用时,需要先参考开发准备[申请相关权限](camera-preparation.md)。
4
5当前示例提供完整的录像流程介绍,方便开发者了解完整的接口调用顺序。
6
7在参考以下示例前,建议开发者查看[相机开发指导(ArkTS)](camera-preparation.md)的具体章节,了解[设备输入](camera-device-input.md)、[会话管理](camera-session-management.md)、[录像](camera-recording.md)等单个流程。
8
9如需要将视频保存到媒体库中可参考[保存媒体库资源](../medialibrary/photoAccessHelper-savebutton.md#保存媒体库资源)。
10## 开发流程
11
12在获取到相机支持的输出流能力后,开始创建录像流,开发流程如下。
13
14![Recording Development Process](figures/recording-development-process.png)
15
16
17## 完整示例
18Context获取方式请参考:[获取UIAbility的上下文信息](../../application-models/uiability-usage.md#获取uiability的上下文信息)。
19
20```ts
21import { camera } from '@kit.CameraKit';
22import { BusinessError } from '@kit.BasicServicesKit';
23import { media } from '@kit.MediaKit';
24import { common } from '@kit.AbilityKit';
25import { fileIo as fs } from '@kit.CoreFileKit';
26
27async function videoRecording(context: common.Context, surfaceId: string): Promise<void> {
28  // 创建CameraManager对象。
29  let cameraManager: camera.CameraManager = camera.getCameraManager(context);
30  if (!cameraManager) {
31    console.error("camera.getCameraManager error");
32    return;
33  }
34
35  // 监听相机状态变化。
36  cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {
37    if (err !== undefined && err.code !== 0) {
38      console.error('cameraStatus with errorCode = ' + err.code);
39      return;
40    }
41    console.info(`camera : ${cameraStatusInfo.camera.cameraId}`);
42    console.info(`status: ${cameraStatusInfo.status}`);
43  });
44
45  // 获取相机列表。
46  let cameraArray: Array<camera.CameraDevice> = [];
47  try {
48    cameraArray = cameraManager.getSupportedCameras();
49  } catch (error) {
50    let err = error as BusinessError;
51    console.error(`getSupportedCameras call failed. error code: ${err.code}`);
52  }
53
54  if (cameraArray.length <= 0) {
55    console.error("cameraManager.getSupportedCameras error");
56    return;
57  }
58
59  // 获取支持的模式类型。
60  let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]);
61  let isSupportVideoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_VIDEO) >= 0;
62  if (!isSupportVideoMode) {
63    console.error('video mode not support');
64    return;
65  }
66
67  // 获取相机设备支持的输出流能力。
68  let cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_VIDEO);
69  if (!cameraOutputCap) {
70    console.error("cameraManager.getSupportedOutputCapability error")
71    return;
72  }
73  console.info("outputCapability: " + JSON.stringify(cameraOutputCap));
74
75  let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles;
76  if (!previewProfilesArray) {
77    console.error("createOutput previewProfilesArray == null || undefined");
78  }
79
80  let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles;
81  if (!photoProfilesArray) {
82    console.error("createOutput photoProfilesArray == null || undefined");
83  }
84
85  let videoProfilesArray: Array<camera.VideoProfile> = cameraOutputCap.videoProfiles;
86  if (!videoProfilesArray || videoProfilesArray.length === 0) {
87    console.error("createOutput videoProfilesArray == null || undefined");
88  }
89
90  // videoProfile的宽高需要与AVRecorderProfile的宽高保持一致,并且需要使用AVRecorderProfile所支持的宽高。
91  // 示例代码默认选择第一个videoProfile,实际开发需根据所需筛选videoProfile。
92  let videoProfile: camera.VideoProfile = videoProfilesArray[0];
93  let isHdr = videoProfile.format === camera.CameraFormat.CAMERA_FORMAT_YCBCR_P010 || videoProfile.format === camera.CameraFormat.CAMERA_FORMAT_YCRCB_P010;
94  // 配置参数以实际硬件设备支持的范围为准。
95  let aVRecorderProfile: media.AVRecorderProfile = {
96    audioBitrate: 48000,
97    audioChannels: 2,
98    audioCodec: media.CodecMimeType.AUDIO_AAC,
99    audioSampleRate: 48000,
100    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
101    videoBitrate: 2000000,
102    videoCodec: isHdr ? media.CodecMimeType.VIDEO_HEVC : media.CodecMimeType.VIDEO_AVC,
103    videoFrameWidth: videoProfile.size.width,
104    videoFrameHeight: videoProfile.size.height,
105    videoFrameRate: 30,
106    isHdr: isHdr
107  };
108  let videoUri: string = `file://${context.filesDir}/${Date.now()}.mp4`; // 本地沙箱路径。
109  let file: fs.File = fs.openSync(videoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
110  let aVRecorderConfig: media.AVRecorderConfig = {
111    audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
112    videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
113    profile: aVRecorderProfile,
114    url: `fd://${file.fd.toString()}`, // 文件需先由调用者创建,赋予读写权限,将文件fd传给此参数,eg.fd://45--file:///data/media/01.mp4
115    rotation: 0, // 合理值0、90、180、270,非合理值prepare接口将报错。
116    location: { latitude: 30, longitude: 130 }
117  };
118
119  let avRecorder: media.AVRecorder | undefined = undefined;
120  try {
121    avRecorder = await media.createAVRecorder();
122  } catch (error) {
123    let err = error as BusinessError;
124    console.error(`createAVRecorder call failed. error code: ${err.code}`);
125  }
126
127  if (avRecorder === undefined) {
128    return;
129  }
130
131  try {
132    await avRecorder.prepare(aVRecorderConfig);
133  } catch (error) {
134    let err = error as BusinessError;
135    console.error(`prepare call failed. error code: ${err.code}`);
136  }
137
138  let videoSurfaceId: string | undefined = undefined; // 该surfaceID用于传递给相机接口创造videoOutput。
139  try {
140    videoSurfaceId = await avRecorder.getInputSurface();
141  } catch (error) {
142    let err = error as BusinessError;
143    console.error(`getInputSurface call failed. error code: ${err.code}`);
144  }
145  if (videoSurfaceId === undefined) {
146    return;
147  }
148  // 创建VideoOutput对象。
149  let videoOutput: camera.VideoOutput | undefined = undefined;
150  try {
151    videoOutput = cameraManager.createVideoOutput(videoProfile, videoSurfaceId);
152  } catch (error) {
153    let err = error as BusinessError;
154    console.error(`Failed to create the videoOutput instance. error: ${err}`);
155  }
156  if (videoOutput === undefined) {
157    return;
158  }
159  // 监听视频输出错误信息。
160  videoOutput.on('error', (error: BusinessError) => {
161    console.error(`Preview output error code: ${error.code}`);
162  });
163
164  //创建会话。
165  let videoSession: camera.VideoSession | undefined = undefined;
166  try {
167    videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
168  } catch (error) {
169    let err = error as BusinessError;
170    console.error(`Failed to create the session instance. error: ${err}`);
171  }
172  if (videoSession === undefined) {
173    return;
174  }
175  // 监听session错误信息。
176  videoSession.on('error', (error: BusinessError) => {
177    console.error(`Video session error code: ${error.code}`);
178  });
179
180  // 开始配置会话。
181  try {
182    videoSession.beginConfig();
183  } catch (error) {
184    let err = error as BusinessError;
185    console.error(`Failed to beginConfig. error: ${err}`);
186  }
187
188  // 创建相机输入流。
189  let cameraInput: camera.CameraInput | undefined = undefined;
190  try {
191    cameraInput = cameraManager.createCameraInput(cameraArray[0]);
192  } catch (error) {
193    let err = error as BusinessError;
194    console.error(`Failed to createCameraInput. error: ${err}`);
195  }
196  if (cameraInput === undefined) {
197    return;
198  }
199  // 监听cameraInput错误信息。
200  let cameraDevice: camera.CameraDevice = cameraArray[0];
201  cameraInput.on('error', cameraDevice, (error: BusinessError) => {
202    console.error(`Camera input error code: ${error.code}`);
203  });
204
205  // 打开相机。
206  try {
207    await cameraInput.open();
208  } catch (error) {
209    let err = error as BusinessError;
210    console.error(`Failed to open cameraInput. error: ${err}`);
211  }
212
213  // 向会话中添加相机输入流。
214  try {
215    videoSession.addInput(cameraInput);
216  } catch (error) {
217    let err = error as BusinessError;
218    console.error(`Failed to add cameraInput. error: ${err}`);
219  }
220
221  // 创建预览输出流,其中参数 surfaceId 参考下面 XComponent 组件,预览流为XComponent组件提供的surface。
222  let previewOutput: camera.PreviewOutput | undefined = undefined;
223  let previewProfile = previewProfilesArray.find((previewProfile: camera.Profile) => {
224    return Math.abs((previewProfile.size.width / previewProfile.size.height) - (videoProfile.size.width / videoProfile.size.height)) < Number.EPSILON;
225  }); // 筛选与录像分辨率宽高比一致的预览分辨率。
226  if (previewProfile === undefined) {
227    return;
228  }
229  try {
230    previewOutput = cameraManager.createPreviewOutput(previewProfile, surfaceId);
231  } catch (error) {
232    let err = error as BusinessError;
233    console.error(`Failed to create the PreviewOutput instance. error: ${err}`);
234  }
235  if (previewOutput === undefined) {
236    return;
237  }
238
239  // 向会话中添加预览输出流。
240  try {
241    videoSession.addOutput(previewOutput);
242  } catch (error) {
243    let err = error as BusinessError;
244    console.error(`Failed to add previewOutput. error: ${err}`);
245  }
246
247  // 向会话中添加录像输出流。
248  try {
249    videoSession.addOutput(videoOutput);
250  } catch (error) {
251    let err = error as BusinessError;
252    console.error(`Failed to add videoOutput. error: ${err}`);
253  }
254
255  // 提交会话配置。
256  try {
257    await videoSession.commitConfig();
258  } catch (error) {
259    let err = error as BusinessError;
260    console.error(`videoSession commitConfig error: ${err}`);
261  }
262
263  // 启动会话。
264  try {
265    await videoSession.start();
266  } catch (error) {
267    let err = error as BusinessError;
268    console.error(`videoSession start error: ${err}`);
269  }
270
271  // 启动录像输出流。
272  videoOutput.start((err: BusinessError) => {
273    if (err) {
274      console.error(`Failed to start the video output. error: ${err}`);
275      return;
276    }
277    console.info('Callback invoked to indicate the video output start success.');
278  });
279
280  // 开始录像。
281  try {
282    await avRecorder.start();
283  } catch (error) {
284    let err = error as BusinessError;
285    console.error(`avRecorder start error: ${err}`);
286  }
287
288  // 停止录像输出流。
289  videoOutput.stop((err: BusinessError) => {
290    if (err) {
291      console.error(`Failed to stop the video output. error: ${err}`);
292      return;
293    }
294    console.info('Callback invoked to indicate the video output stop success.');
295  });
296
297  // 停止录像。
298  try {
299    await avRecorder.stop();
300  } catch (error) {
301    let err = error as BusinessError;
302    console.error(`avRecorder stop error: ${err}`);
303  }
304
305  // 停止当前会话。
306  await videoSession.stop();
307
308  // 关闭文件。
309  fs.closeSync(file);
310
311  // 释放相机输入流。
312  await cameraInput.close();
313
314  // 释放预览输出流。
315  await previewOutput.release();
316
317  // 释放录像输出流。
318  await videoOutput.release();
319
320  // 释放会话。
321  await videoSession.release();
322
323  // 会话置空。
324  videoSession = undefined;
325}
326```
327