在平时工作中,我们常常会遇到视频开发相关的业务需求,不过好在鸿蒙SDK提供了相关的解决方案:AVRecorder 是一个强大的视频录制工具,它集成了音频捕获、音频编码、视频编码和音视频封装功能,能够帮助开发者实现简单的视频录制功能,并直接生成本地视频文件。
本文将详细介绍如何在鸿蒙 Next 中使用 AVRecorder 进行视频录制,包括开发步骤、注意事项以及具体的代码示例。
一、AVRecorder 概述
AVRecorder 是鸿蒙系统提供的一个用于视频录制的 API,它支持音频和视频的同步录制,并能够将录制的内容保存为本地文件。它适用于需要实现简单视频录制功能的场景,例如录制视频日记、教学视频等。通过 AVRecorder,开发者可以轻松地控制录制的开始、暂停、恢复和停止,并且可以通过监听事件实时获取录制状态。
二、开发步骤
(一)申请权限
在使用 AVRecorder 进行视频录制之前,需要申请以下权限:
- 麦克风权限:如果需要录制音频,需要申请 ohos.permission.MICROPHONE 权限。
- 相机权限:如果需要使用相机录制视频,需要申请 ohos.permission.CAMERA 权限。
- 文件读写权限 :如果需要保存录制的视频文件到图库,需要申请ohos.permission.READ_IMAGEVIDEO
和ohos.permission.WRITE_IMAGEVIDEO 权限。
示例代码如下:
typescript
import { Context } from '@ohos.app.ability.UIAbility';
import { fileIo as fs } from '@kit.CoreFileKit';
const context: Context = getContext(this);
context.requestPermissions(['ohos.permission.MICROPHONE', 'ohos.permission.CAMERA'], (err, result) => {
if (result[0] === 'ohos.permission.MICROPHONE' && result[1] === 'ohos.permission.CAMERA') {
console.log('Permissions granted');
} else {
console.error('Permissions denied');
}
});
(二)创建 AVRecorder 实例
调用 media.createAVRecorder() 方法创建 AVRecorder 实例,实例创建完成后进入 idle 状态。示例代码如下:
typescript
import { media } from '@kit.MediaKit';
let avRecorder: media.AVRecorder;
media.createAVRecorder().then((recorder: media.AVRecorder) => {
avRecorder = recorder;
}, (error) => {
console.error('createAVRecorder failed: ' + error.message);
});
(三)设置监听事件
AVRecorder 提供了多种监听事件,开发者可以根据实际需求设置相应的监听事件。常用的监听事件包括:
stateChange:监听录制器状态变化。error:监听录制器错误信息。
示例代码如下:
typescript
avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
console.info('current state is: ' + state);
});
avRecorder.on('error', (err) => {
console.error('error happened, error message is ' + err.message);
});
(四)配置录制参数
调用 prepare() 方法配置录制参数,此时 AVRecorder 进入 prepared 状态。配置参数时需要注意以下几点:
- 确保已经申请了相关权限。
prepare()方法的入参avConfig中仅设置视频相关的配置参数。- 录制输出的文件路径需要使用
fd://格式,可以通过调用基础文件操作接口(Core File Kit的ohos.file.fs)实现应用文件访问能力。
示例代码如下:
typescript
import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';
let avProfile: media.AVRecorderProfile = {
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 200000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 640,
videoFrameHeight: 480,
videoFrameRate: 30
};
const context: Context = getContext(this);
let filePath: string = context.filesDir + '/example.mp4';
let videoFile: fs.File = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
let fileFd = videoFile.fd;
let avConfig: media.AVRecorderConfig = {
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: avProfile,
url: 'fd://' + fileFd.toString(),
rotation: 0
};
avRecorder.prepare(avConfig).then(() => {
console.info('avRecorder prepare success');
}, (error) => {
console.error('avRecorder prepare failed: ' + error.message);
});
(五)获取 SurfaceID
调用 getInputSurface() 方法获取录制所需的 SurfaceID,该 SurfaceID 用于传递给视频数据输入源模块(如相机)。示例代码如下:
typescript
avRecorder.getInputSurface().then((surfaceId: string) => {
console.info('avRecorder getInputSurface success: ' + surfaceId);
}, (error) => {
console.error('avRecorder getInputSurface failed: ' + error.message);
});
(六)初始化视频数据输入源
该步骤需要在输入源模块完成,以相机为例,需要创建录像输出流,包括创建 Camera 对象、获取相机列表、创建相机输入流等。示例代码如下:
typescript
import { camera } from '@kit.CameraKit';
async function prepareCamera() {
let cameraDevice: camera.CameraDevice;
let cameraIdList: string[] = await camera.getCameraIdList();
if (cameraIdList.length > 0) {
cameraDevice = await camera.createCameraDevice(cameraIdList[0]);
await cameraDevice.open();
}
return cameraDevice;
}
async function startCameraOutput(cameraDevice: camera.CameraDevice, surfaceId: string) {
let videoOutput: camera.VideoOutput = cameraDevice.createVideoOutput(surfaceId);
await videoOutput.start();
}
async function stopCameraOutput(videoOutput: camera.VideoOutput) {
await videoOutput.stop();
}
async function releaseCamera(cameraDevice: camera.CameraDevice) {
await cameraDevice.close();
}
(七)开始录制
调用 start() 方法开始录制,此时 AVRecorder 进入 started 状态。示例代码如下:
typescript
avRecorder.start().then(() => {
console.info('avRecorder start success');
}, (error) => {
console.error('avRecorder start failed: ' + error.message);
});
(八)暂停录制
调用 pause() 方法暂停录制,此时 AVRecorder 进入 paused 状态。示例代码如下:
typescript
avRecorder.pause().then(() => {
console.info('avRecorder pause success');
}, (error) => {
console.error('avRecorder pause failed: ' + error.message);
});
(九)恢复录制
调用 resume() 方法恢复录制,此时 AVRecorder 再次进入 started 状态。示例代码如下:
typescript
avRecorder.resume().then(() => {
console.info('avRecorder resume success');
}, (error) => {
console.error('avRecorder resume failed: ' + error.message);
});
(十)停止录制
调用 stop() 方法停止录制,此时 AVRecorder 进入 stopped 状态。示例代码如下:
typescript
avRecorder.stop().then(() => {
console.info('avRecorder stop success');
}, (error) => {
console.error('avRecorder stop failed: ' + error.message);
});
(十一)重置资源
调用 reset() 方法重新进入 idle 状态,允许重新配置录制参数。示例代码如下:
typescript
avRecorder.reset().then(() => {
console.info('avRecorder reset success');
}, (error) => {
console.error('avRecorder reset failed: ' + error.message);
});
(十二)销毁实例
调用 release() 方法销毁实例,释放相关资源。示例代码如下:
typescript
avRecorder.release().then(() => {
console.info('avRecorder release success');
}, (error) => {
console.error('avRecorder release failed: ' + error.message);
});
三、完整代码示例
以下是一个完整的示例代码,展示了如何使用 AVRecorder 完成"开始录制-暂停录制-恢复录制-停止录制"的完整流程:
typescript
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
const TAG = 'VideoRecorderDemo:';
export class VideoRecorderDemo {
private context: Context;
constructor() {
this.context = getContext(this);
}
private avRecorder: media.AVRecorder | undefined = undefined;
private videoOutSurfaceId: string = "";
private avProfile: media.AVRecorderProfile = {
fileFormat : media.ContainerFormatType.CFT_MPEG_4, // 视频文件封装格式,只支持MP4。
videoBitrate : 100000, // 视频比特率。
videoCodec : media.CodecMimeType.VIDEO_AVC, // 视频文件编码格式,支持avc格式。
videoFrameWidth : 640, // 视频分辨率的宽。
videoFrameHeight : 480, // 视频分辨率的高。
videoFrameRate : 30 // 视频帧率。
};
private avConfig: media.AVRecorderConfig = {
videoSourceType : media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV, // 视频源类型,支持YUV和ES两种格式。
profile : this.avProfile,
url : 'fd://35', // 参考应用文件访问与管理开发示例新建并读写一个文件。
rotation : 0 // 视频旋转角度,默认为0不旋转,支持的值为0、90、180、270。
};
private uriPath: string = ''; // 文件uri,可用于安全控件保存媒体资源。
private filePath: string = ''; // 文件路径。
private fileFd: number = 0;
// 创建文件以及设置avConfig.url。
async createAndSetFd() {
const path: string = this.context.filesDir + '/example.mp4'; // 文件沙箱路径,文件后缀名应与封装格式对应。
const videoFile: fs.File = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
this.avConfig.url = 'fd://' + videoFile.fd; // 设置url。
this.fileFd = videoFile.fd; // 文件fd。
this.filePath = path;
}
// 注册avRecorder回调函数。
setAvRecorderCallback() {
if (this.avRecorder != undefined) {
// 状态机变化回调函数。
this.avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {
console.info(TAG + 'current state is: ' + state);
})
// 错误上报回调函数。
this.avRecorder.on('error', (err: BusinessError) => {
console.error(TAG + 'error ocConstantSourceNode, error message is ' + err);
})
}
}
// 相机相关准备工作。
async prepareCamera() {
// 具体实现查看相机资料。
}
// 启动相机出流。
async startCameraOutput() {
// 调用VideoOutput的start接口开始录像输出。
}
// 停止相机出流。
async stopCameraOutput() {
// 调用VideoOutput的stop接口停止录像输出。
}
// 释放相机实例。
async releaseCamera() {
// 释放相机准备阶段创建出的实例。
}
// 开始录制对应的流程。
async startRecordingProcess() {
if (this.avRecorder === undefined) {
// 1.创建录制实例。
this.avRecorder = await media.createAVRecorder();
this.setAvRecorderCallback();
}
// 2. 获取录制文件fd;获取到的值传递给avConfig里的url,实现略。
// 3.配置录制参数完成准备工作。
await this.avRecorder.prepare(this.avConfig);
this.videoOutSurfaceId = await this.avRecorder.getInputSurface();
// 4.完成相机相关准备工作。
await this.prepareCamera();
// 5.启动相机出流。
await this.startCameraOutput();
// 6. 启动录制。
await this.avRecorder.start();
}
// 暂停录制对应的流程。
async pauseRecordingProcess() {
if (this.avRecorder != undefined && this.avRecorder.state === 'started') { // 仅在started状态下调用pause为合理状态切换。
await this.avRecorder.pause();
await this.stopCameraOutput(); // 停止相机出流。
}
}
// 恢复录制对应的流程。
async resumeRecordingProcess() {
if (this.avRecorder != undefined && this.avRecorder.state === 'paused') { // 仅在paused状态下调用resume为合理状态切换。
await this.startCameraOutput(); // 启动相机出流。
await this.avRecorder.resume();
}
}
async stopRecordingProcess() {
if (this.avRecorder != undefined) {
// 1. 停止录制。
if (this.avRecorder.state === 'started'
|| this.avRecorder.state === 'paused' ) { // 仅在started或者paused状态下调用stop为合理状态切换。
await this.avRecorder.stop();
await this.stopCameraOutput();
}
// 2.重置。
await this.avRecorder.reset();
// 3.释放录制实例。
await this.avRecorder.release();
// 4.文件录制完成后,关闭fd,实现略。
await fs.close(this.fileFd);
// 5.释放相机相关实例。
await this.releaseCamera();
}
}
// 安全控件保存媒体资源至图库。
async saveRecorderAsset() {
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context);
// 需要确保uriPath对应的资源存在。
this.uriPath = fileUri.getUriFromPath(this.filePath); // 获取录制文件的uri,用于安全控件保存至图库。
let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest =
photoAccessHelper.MediaAssetChangeRequest.createVideoAssetRequest(this.context, this.uriPath);
await phAccessHelper.applyChanges(assetChangeRequest);
}
// 一个完整的【开始录制-暂停录制-恢复录制-停止录制】示例。
async videoRecorderDemo() {
await this.startRecordingProcess(); // 开始录制。
// 用户此处可以自行设置录制时长,例如通过设置休眠阻止代码执行。
await this.pauseRecordingProcess(); //暂停录制。
await this.resumeRecordingProcess(); // 恢复录制。
await this.stopRecordingProcess(); // 停止录制。
// 安全控件保存媒体资源至图库。
await this.saveRecorderAsset();
}
}