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在参考以下示例前,建议开发者查看[相机开发指导(ArkTS)](camera-device-management.md)的具体章节,了解[设备输入](camera-device-input.md)、[会话管理](camera-session-management.md)、[录像](camera-recording.md)等单个流程。 14 15如需要将视频保存到媒体库中可参考[保存媒体库资源](../medialibrary/photoAccessHelper-savebutton.md)。 16## 开发流程 17 18在获取到相机支持的输出流能力后,开始创建录像流,开发流程如下。 19 20 21 22 23## 完整示例 24Context获取方式请参考:[获取UIAbility的上下文信息](../../application-models/uiability-usage.md#获取uiability的上下文信息)。 25 26```ts 27import { camera } from '@kit.CameraKit'; 28import { BusinessError } from '@kit.BasicServicesKit'; 29import { media } from '@kit.MediaKit'; 30import { common } from '@kit.AbilityKit'; 31import { fileIo as fs } from '@kit.CoreFileKit'; 32 33async function videoRecording(context: common.Context, surfaceId: string): Promise<void> { 34 // 创建CameraManager对象。 35 let cameraManager: camera.CameraManager = camera.getCameraManager(context); 36 if (!cameraManager) { 37 console.error("camera.getCameraManager error"); 38 return; 39 } 40 41 // 监听相机状态变化。 42 cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => { 43 if (err !== undefined && err.code !== 0) { 44 console.error('cameraStatus with errorCode = ' + err.code); 45 return; 46 } 47 console.info(`camera : ${cameraStatusInfo.camera.cameraId}`); 48 console.info(`status: ${cameraStatusInfo.status}`); 49 }); 50 51 // 获取相机列表。 52 let cameraArray: Array<camera.CameraDevice> = []; 53 try { 54 cameraArray = cameraManager.getSupportedCameras(); 55 } catch (error) { 56 let err = error as BusinessError; 57 console.error(`getSupportedCameras call failed. error code: ${err.code}`); 58 } 59 60 if (cameraArray.length <= 0) { 61 console.error("cameraManager.getSupportedCameras error"); 62 return; 63 } 64 65 // 获取支持的模式类型。 66 let sceneModes: Array<camera.SceneMode> = cameraManager.getSupportedSceneModes(cameraArray[0]); 67 let isSupportVideoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_VIDEO) >= 0; 68 if (!isSupportVideoMode) { 69 console.error('video mode not support'); 70 return; 71 } 72 73 // 获取相机设备支持的输出流能力。 74 let cameraOutputCap: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(cameraArray[0], camera.SceneMode.NORMAL_VIDEO); 75 if (!cameraOutputCap) { 76 console.error("cameraManager.getSupportedOutputCapability error") 77 return; 78 } 79 console.info("outputCapability: " + JSON.stringify(cameraOutputCap)); 80 81 let previewProfilesArray: Array<camera.Profile> = cameraOutputCap.previewProfiles; 82 if (!previewProfilesArray) { 83 console.error("createOutput previewProfilesArray == null || undefined"); 84 } 85 86 let photoProfilesArray: Array<camera.Profile> = cameraOutputCap.photoProfiles; 87 if (!photoProfilesArray) { 88 console.error("createOutput photoProfilesArray == null || undefined"); 89 } 90 91 let videoProfilesArray: Array<camera.VideoProfile> = cameraOutputCap.videoProfiles; 92 if (!videoProfilesArray || videoProfilesArray.length === 0) { 93 console.error("createOutput videoProfilesArray == null || undefined"); 94 } 95 96 // videoProfile的宽高需要与AVRecorderProfile的宽高保持一致,并且需要使用AVRecorderProfile所支持的宽高。 97 // 示例代码默认选择第一个videoProfile,实际开发需根据所需筛选videoProfile。 98 let videoProfile: camera.VideoProfile = videoProfilesArray[0]; 99 let isHdr = videoProfile.format === camera.CameraFormat.CAMERA_FORMAT_YCBCR_P010 || videoProfile.format === camera.CameraFormat.CAMERA_FORMAT_YCRCB_P010; 100 // 配置参数以实际硬件设备支持的范围为准。 101 let aVRecorderProfile: media.AVRecorderProfile = { 102 audioBitrate: 48000, 103 audioChannels: 2, 104 audioCodec: media.CodecMimeType.AUDIO_AAC, 105 audioSampleRate: 48000, 106 fileFormat: media.ContainerFormatType.CFT_MPEG_4, 107 videoBitrate: 2000000, 108 videoCodec: isHdr ? media.CodecMimeType.VIDEO_HEVC : media.CodecMimeType.VIDEO_AVC, 109 videoFrameWidth: videoProfile.size.width, 110 videoFrameHeight: videoProfile.size.height, 111 videoFrameRate: 30, 112 isHdr: isHdr 113 }; 114 115 let avMetadata: media.AVMetadata = { 116 videoOrientation: '0', // 合理值0、90、180、270,非合理值prepare接口将报错。 117 location: { latitude: 30, longitude: 130 } 118 } 119 let videoUri: string = context.filesDir + '/' + 'VIDEO_' + Date.parse(new Date().toString()) + '.mp4'; // 本地沙箱路径。 120 let file: fs.File = fs.openSync(videoUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); 121 let aVRecorderConfig: media.AVRecorderConfig = { 122 audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, 123 videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV, 124 profile: aVRecorderProfile, 125 url: `fd://${file.fd.toString()}`, // 文件需先由调用者创建,赋予读写权限,将文件fd传给此参数,eg.fd://45--file:///data/media/01.mp4 126 metadata: avMetadata 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: ${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: ${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: ${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: ${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: ${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: ${err}`); 229 } 230 231 // 创建预览输出流,预览流为XComponent组件提供的surface。 232 let previewOutput: camera.PreviewOutput | undefined = undefined; 233 let previewProfile = previewProfilesArray.find((previewProfile: camera.Profile) => { 234 return Math.abs((previewProfile.size.width / previewProfile.size.height) - (videoProfile.size.width / videoProfile.size.height)) < Number.EPSILON; 235 }); // 筛选与录像分辨率宽高比一致的预览分辨率。 236 if (previewProfile === undefined) { 237 return; 238 } 239 try { 240 previewOutput = cameraManager.createPreviewOutput(previewProfile, surfaceId); 241 } catch (error) { 242 let err = error as BusinessError; 243 console.error(`Failed to create the PreviewOutput instance. error: ${err}`); 244 } 245 if (previewOutput === undefined) { 246 return; 247 } 248 249 // 向会话中添加预览输出流。 250 try { 251 videoSession.addOutput(previewOutput); 252 } catch (error) { 253 let err = error as BusinessError; 254 console.error(`Failed to add previewOutput. error: ${err}`); 255 } 256 257 // 向会话中添加录像输出流。 258 try { 259 videoSession.addOutput(videoOutput); 260 } catch (error) { 261 let err = error as BusinessError; 262 console.error(`Failed to add videoOutput. error: ${err}`); 263 } 264 265 // 提交会话配置。 266 try { 267 await videoSession.commitConfig(); 268 } catch (error) { 269 let err = error as BusinessError; 270 console.error(`videoSession commitConfig error: ${err}`); 271 return; 272 } 273 274 // 启动会话。 275 try { 276 await videoSession.start(); 277 } catch (error) { 278 let err = error as BusinessError; 279 console.error(`videoSession start error: ${err}`); 280 } 281 282 // 启动录像输出流。 283 videoOutput.start((err: BusinessError) => { 284 if (err) { 285 console.error(`Failed to start the video output. error: ${err}`); 286 return; 287 } 288 console.info('Callback invoked to indicate the video output start success.'); 289 }); 290 291 // 开始录像。 292 try { 293 await avRecorder.start(); 294 } catch (error) { 295 let err = error as BusinessError; 296 console.error(`avRecorder start error: ${err}`); 297 } 298 299 // 停止录像输出流。 300 videoOutput.stop((err: BusinessError) => { 301 if (err) { 302 console.error(`Failed to stop the video output. error: ${err}`); 303 return; 304 } 305 console.info('Callback invoked to indicate the video output stop success.'); 306 }); 307 308 // 停止录像。 309 try { 310 await avRecorder.stop(); 311 } catch (error) { 312 let err = error as BusinessError; 313 console.error(`avRecorder stop error: ${err}`); 314 } 315 316 // 停止当前会话。 317 await videoSession.stop(); 318 319 // 关闭文件。 320 try { 321 fs.closeSync(file); 322 } catch (error) { 323 let err = error as BusinessError; 324 console.error(`closeSync failed, error: ${err}`); 325 } 326 327 328 // 释放相机输入流。 329 await cameraInput.close(); 330 331 // 释放预览输出流。 332 try { 333 await previewOutput.release(); 334 } catch (error) { 335 let err = error as BusinessError; 336 console.error(`release previewOutput failed, error: ${err.code}`); 337 } 338 339 340 // 释放录像输出流。 341 try { 342 await videoOutput.release(); 343 } catch (error) { 344 let err = error as BusinessError; 345 console.error(`release videoOutput failed, error: ${err.code}`); 346 } 347 348 // 释放会话。 349 try { 350 await videoSession.release(); 351 } catch (error) { 352 let err = error as BusinessError; 353 console.error(`release videoSession failed, error: ${err.code}`); 354 } 355 356 // 会话置空。 357 videoSession = undefined; 358} 359``` 360