鸿蒙 Next 如何使用 AVRecorder 从0到1实现视频录制功能(ArkTS)

在平时工作中,我们常常会遇到视频开发相关的业务需求,不过好在鸿蒙SDK提供了相关的解决方案:AVRecorder 是一个强大的视频录制工具,它集成了音频捕获、音频编码、视频编码和音视频封装功能,能够帮助开发者实现简单的视频录制功能,并直接生成本地视频文件。

本文将详细介绍如何在鸿蒙 Next 中使用 AVRecorder 进行视频录制,包括开发步骤、注意事项以及具体的代码示例。

一、AVRecorder 概述

AVRecorder 是鸿蒙系统提供的一个用于视频录制的 API,它支持音频和视频的同步录制,并能够将录制的内容保存为本地文件。它适用于需要实现简单视频录制功能的场景,例如录制视频日记、教学视频等。通过 AVRecorder,开发者可以轻松地控制录制的开始、暂停、恢复和停止,并且可以通过监听事件实时获取录制状态。

二、开发步骤

(一)申请权限

在使用 AVRecorder 进行视频录制之前,需要申请以下权限:

  • 麦克风权限:如果需要录制音频,需要申请 ohos.permission.MICROPHONE 权限。
  • 相机权限:如果需要使用相机录制视频,需要申请 ohos.permission.CAMERA 权限。
  • 文件读写权限 :如果需要保存录制的视频文件到图库,需要申请ohos.permission.READ_IMAGEVIDEOohos.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 Kitohos.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();
  }
}
相关推荐
小正太浩二2 小时前
视频去动态水印软件HitPaw安装和使用教程
音视频·视频无水印软件
骄傲的心别枯萎2 小时前
RV1126 NO.47:RV1126+OPENCV对视频流进行视频腐蚀操作
人工智能·opencv·计算机视觉·音视频·rv1126
骄傲的心别枯萎2 小时前
RV1126 NO.48:RV1126+OPENCV在视频中添加时间戳
人工智能·opencv·计算机视觉·音视频·视频编解码·rv1126
沉迷单车的追风少年2 小时前
Diffusion Models与视频超分(3): 解读当前最快和最强的开源模型FlashVSR
人工智能·深度学习·计算机视觉·aigc·音视频·视频生成·视频超分
CV实验室2 小时前
CV论文速递:覆盖视频理解与生成、跨模态与定位、医学与生物视觉、图像数据集等方向(11.03-11.07)
人工智能·计算机视觉·音视频
全球通史4 小时前
鸿蒙开发之鸿蒙应用深色模式适配完整指南(上架过程之适配手机深色模式)
华为·harmonyos
EasyGBS10 小时前
智能安防新篇章:EasyGBS助力重塑物业视频管理服务
音视频
一叶难遮天11 小时前
快速入门HarmonyOS应用开发(一)
harmonyos·arkts·arkui·navigation·鸿蒙开发·鸿蒙5.0
骄傲的心别枯萎13 小时前
RV1126 NO.45:RV1126+OPENCV在视频中添加LOGO图像
人工智能·opencv·计算机视觉·音视频·rv1126