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## 开发流程
10
11在获取到相机支持的输出流能力后,开始创建录像流,开发流程如下。
12
13![Recording Development Process](figures/recording-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 { media } from '@kit.MediaKit';
23import { common } from '@kit.AbilityKit';
24import { photoAccessHelper } from '@kit.MediaLibraryKit';
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) {
87    console.error("createOutput videoProfilesArray == null || undefined");
88  }
89  // videoProfile的宽高需要与AVRecorderProfile的宽高保持一致,并且需要使用AVRecorderProfile锁支持的宽高
90  let videoSize: camera.Size = {
91    width: 640,
92    height: 480
93  }
94  let videoProfile: undefined | camera.VideoProfile = videoProfilesArray.find((profile: camera.VideoProfile) => {
95    return profile.size.width === videoSize.width && profile.size.height === videoSize.height;
96  });
97  if (!videoProfile) {
98    console.error('videoProfile is not found');
99    return;
100  }
101  // 配置参数以实际硬件设备支持的范围为准
102  let aVRecorderProfile: media.AVRecorderProfile = {
103    audioBitrate: 48000,
104    audioChannels: 2,
105    audioCodec: media.CodecMimeType.AUDIO_AAC,
106    audioSampleRate: 48000,
107    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
108    videoBitrate: 2000000,
109    videoCodec: media.CodecMimeType.VIDEO_AVC,
110    videoFrameWidth: videoSize.width,
111    videoFrameHeight: videoSize.height,
112    videoFrameRate: 30
113  };
114  let options: photoAccessHelper.CreateOptions = {
115    title: Date.now().toString()
116  };
117  let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
118  let videoUri: string = await accessHelper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4', options);
119  let file: fs.File = fs.openSync(videoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
120  let aVRecorderConfig: media.AVRecorderConfig = {
121    audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
122    videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
123    profile: aVRecorderProfile,
124    url: `fd://${file.fd.toString()}`, // 文件需先由调用者创建,赋予读写权限,将文件fd传给此参数,eg.fd://45--file:///data/media/01.mp4
125    rotation: 0, // 合理值0、90、180、270,非合理值prepare接口将报错
126    location: { latitude: 30, longitude: 130 }
127  };
128
129  let avRecorder: media.AVRecorder | undefined = undefined;
130  try {
131    avRecorder = await media.createAVRecorder();
132  } catch (error) {
133    let err = error as BusinessError;
134    console.error(`createAVRecorder call failed. error code: ${err.code}`);
135  }
136
137  if (avRecorder === undefined) {
138    return;
139  }
140
141  try {
142    await avRecorder.prepare(aVRecorderConfig);
143  } catch (error) {
144    let err = error as BusinessError;
145    console.error(`prepare call failed. error code: ${err.code}`);
146  }
147
148  let videoSurfaceId: string | undefined = undefined; // 该surfaceID用于传递给相机接口创造videoOutput
149  try {
150    videoSurfaceId = await avRecorder.getInputSurface();
151  } catch (error) {
152    let err = error as BusinessError;
153    console.error(`getInputSurface call failed. error code: ${err.code}`);
154  }
155  if (videoSurfaceId === undefined) {
156    return;
157  }
158  // 创建VideoOutput对象
159  let videoOutput: camera.VideoOutput | undefined = undefined;
160  try {
161    videoOutput = cameraManager.createVideoOutput(videoProfile, videoSurfaceId);
162  } catch (error) {
163    let err = error as BusinessError;
164    console.error(`Failed to create the videoOutput instance. error: ${JSON.stringify(err)}`);
165  }
166  if (videoOutput === undefined) {
167    return;
168  }
169  // 监听视频输出错误信息
170  videoOutput.on('error', (error: BusinessError) => {
171    console.error(`Preview output error code: ${error.code}`);
172  });
173
174  //创建会话
175  let videoSession: camera.VideoSession | undefined = undefined;
176  try {
177    videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
178  } catch (error) {
179    let err = error as BusinessError;
180    console.error(`Failed to create the session instance. error: ${JSON.stringify(err)}`);
181  }
182  if (videoSession === undefined) {
183    return;
184  }
185  // 监听session错误信息
186  videoSession.on('error', (error: BusinessError) => {
187    console.error(`Video session error code: ${error.code}`);
188  });
189
190  // 开始配置会话
191  try {
192    videoSession.beginConfig();
193  } catch (error) {
194    let err = error as BusinessError;
195    console.error(`Failed to beginConfig. error: ${JSON.stringify(err)}`);
196  }
197
198  // 创建相机输入流
199  let cameraInput: camera.CameraInput | undefined = undefined;
200  try {
201    cameraInput = cameraManager.createCameraInput(cameraArray[0]);
202  } catch (error) {
203    let err = error as BusinessError;
204    console.error(`Failed to createCameraInput. error: ${JSON.stringify(err)}`);
205  }
206  if (cameraInput === undefined) {
207    return;
208  }
209  // 监听cameraInput错误信息
210  let cameraDevice: camera.CameraDevice = cameraArray[0];
211  cameraInput.on('error', cameraDevice, (error: BusinessError) => {
212    console.error(`Camera input error code: ${error.code}`);
213  });
214
215  // 打开相机
216  try {
217    await cameraInput.open();
218  } catch (error) {
219    let err = error as BusinessError;
220    console.error(`Failed to open cameraInput. error: ${JSON.stringify(err)}`);
221  }
222
223  // 向会话中添加相机输入流
224  try {
225    videoSession.addInput(cameraInput);
226  } catch (error) {
227    let err = error as BusinessError;
228    console.error(`Failed to add cameraInput. error: ${JSON.stringify(err)}`);
229  }
230
231  // 创建预览输出流,其中参数 surfaceId 参考下面 XComponent 组件,预览流为XComponent组件提供的surface
232  let previewOutput: camera.PreviewOutput | undefined = undefined;
233  try {
234    previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId);
235  } catch (error) {
236    let err = error as BusinessError;
237    console.error(`Failed to create the PreviewOutput instance. error: ${JSON.stringify(err)}`);
238  }
239
240  if (previewOutput === undefined) {
241    return;
242  }
243  // 向会话中添加预览输出流
244  try {
245    videoSession.addOutput(previewOutput);
246  } catch (error) {
247    let err = error as BusinessError;
248    console.error(`Failed to add previewOutput. error: ${JSON.stringify(err)}`);
249  }
250
251  // 向会话中添加录像输出流
252  try {
253    videoSession.addOutput(videoOutput);
254  } catch (error) {
255    let err = error as BusinessError;
256    console.error(`Failed to add videoOutput. error: ${JSON.stringify(err)}`);
257  }
258
259  // 提交会话配置
260  try {
261    await videoSession.commitConfig();
262  } catch (error) {
263    let err = error as BusinessError;
264    console.error(`videoSession commitConfig error: ${JSON.stringify(err)}`);
265  }
266
267  // 启动会话
268  try {
269    await videoSession.start();
270  } catch (error) {
271    let err = error as BusinessError;
272    console.error(`videoSession start error: ${JSON.stringify(err)}`);
273  }
274
275  // 启动录像输出流
276  videoOutput.start((err: BusinessError) => {
277    if (err) {
278      console.error(`Failed to start the video output. error: ${JSON.stringify(err)}`);
279      return;
280    }
281    console.info('Callback invoked to indicate the video output start success.');
282  });
283
284  // 开始录像
285  try {
286    await avRecorder.start();
287  } catch (error) {
288    let err = error as BusinessError;
289    console.error(`avRecorder start error: ${JSON.stringify(err)}`);
290  }
291
292  // 停止录像输出流
293  videoOutput.stop((err: BusinessError) => {
294    if (err) {
295      console.error(`Failed to stop the video output. error: ${JSON.stringify(err)}`);
296      return;
297    }
298    console.info('Callback invoked to indicate the video output stop success.');
299  });
300
301  // 停止录像
302  try {
303    await avRecorder.stop();
304  } catch (error) {
305    let err = error as BusinessError;
306    console.error(`avRecorder stop error: ${JSON.stringify(err)}`);
307  }
308
309  // 停止当前会话
310  await videoSession.stop();
311
312  // 关闭文件
313  fs.closeSync(file);
314
315  // 释放相机输入流
316  await cameraInput.close();
317
318  // 释放预览输出流
319  await previewOutput.release();
320
321  // 释放录像输出流
322  await videoOutput.release();
323
324  // 释放会话
325  await videoSession.release();
326
327  // 会话置空
328  videoSession = undefined;
329}
330```
331