在 HarmonyOS 应用开发中,音频录制是语音笔记、语音输入、实时音频采集等场景的核心能力。鸿蒙官方提供的AVRecorder(音视频录制管理类)专为音频 / 视频录制设计,支持将录制的音频数据直接写入本地文件,无需第三方库即可完成从 “开始录音” 到 “结束保存” 的全流程。本文将从核心概念、权限配置、代码实现到避坑指南,手把手教你使用AVRecorder实现基础的录音功能。

一、核心概念速览(新手必懂)

在开始编码前,先理解几个关键概念,避免后续代码 “知其然不知其所以然”:

概念 作用说明
AVRecorder 鸿蒙媒体模块的音视频录制管理类,负责录音的初始化、启动、停止、资源释放等核心操作
fd(文件句柄) 文件描述符,是系统标识文件的唯一索引,AVRecorder通过fd://${fd}将音频数据写入指定文件
录音配置 包含音频源(如麦克风)、编码格式、采样率、比特率等参数,决定录音的质量和文件格式
fileIo 鸿蒙文件操作模块,用于创建、打开、关闭本地文件,为录音提供数据存储载体

二、前置准备:配置录音必备权限

音频录制依赖麦克风权限(敏感权限),必须先在配置文件中声明,否则录音功能会直接失败。

步骤 1:配置 module.json5(权限声明)

src/main/module.json5中添加麦克风权限声明(ohos.permission.MICROPHONE),需指定reason(权限用途)和usedScene(使用场景):

json

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:permission_microphone",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

步骤 2:配置字符串资源(string.json)

src/main/resources/base/element/string.json中添加权限用途的字符串,避免硬编码:

json

{
  "string": [
    {
      "name": "permission_microphone",
      "value": "录音功能需要使用麦克风权限,仅用于音频录制,不会收集其他信息"
    }
  ]
}

三、实战:完整实现录音功能(开始 + 结束)

以下是基于AVRecorder的完整录音代码实现,包含 “创建录音文件→配置录音参数→开始录音→停止录音→释放资源” 全流程,可直接复制使用。

完整代码示例

typescript

运行

// 导入核心模块
import media from '@ohos.multimedia.media';
import fileIo from '@ohos.file.fs';
import { getContext, promptAction } from '@kit.ArkUI';

@Entry
@Component
struct AudioRecordPage {
  // 声明AVRecorder实例(音视频录制管理类)
  avRecorder?: media.AVRecorder;
  // 文件句柄(用于关联录音文件)
  fd?: number;
  // 录音文件保存路径
  filePath?: string;

  /**
   * 开始录音核心方法
   */
  async startRecord() {
    try {
      // 1. 步骤1:创建本地文件,用于接收录音数据
      const ctx = getContext(this);
      // 拼接文件路径:应用私有目录 + 时间戳 + .m4a(确保文件名唯一)
      this.filePath = `${ctx.filesDir}/${Date.now()}.m4a`;
      // 以“创建+读写”模式打开文件,获取文件句柄(fd)
      const file = fileIo.openSync(
        this.filePath,
        fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE
      );
      this.fd = file.fd;

      // 2. 步骤2:配置录音参数(关键!参数错误会导致录音失败)
      const recordConfig: media.AVRecorderConfig = {
        // 音频源:麦克风(唯一支持的音频源类型)
        audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
        // 录音质量配置
        profile: {
          audioBitrate: 100000, // 音频比特率(越大音质越好,文件越大)
          audioChannels: 1, // 声道数:1=单声道,2=立体声(单声道更省空间)
          audioCodec: media.CodecMimeType.AUDIO_AAC, // 编码格式:仅支持AAC
          audioSampleRate: 48000, // 采样率:48000Hz(常用标准)
          fileFormat: media.ContainerFormatType.CFT_MPEG_4A // 封装格式:仅支持m4a
        },
        // 关联录音文件:通过fd://协议绑定文件句柄
        url: `fd://${this.fd}`
      };

      // 3. 步骤3:初始化AVRecorder并开始录音
      this.avRecorder = await media.createAVRecorder(); // 创建AVRecorder实例
      await this.avRecorder.prepare(recordConfig); // 初始化录音配置
      await this.avRecorder.start(); // 启动录音
      promptAction.showToast({ message: '开始录音...' });
    } catch (error) {
      console.error('开始录音失败:', error);
      promptAction.showToast({ message: '录音启动失败,请检查权限' });
    }
  }

  /**
   * 结束录音核心方法
   */
  async stopRecord() {
    try {
      if (!this.avRecorder || !this.fd) {
        promptAction.showToast({ message: '未开始录音' });
        return;
      }

      // 1. 停止录音
      await this.avRecorder.stop();
      // 2. 释放AVRecorder资源(关键:避免内存泄露)
      await this.avRecorder.release();
      // 3. 关闭文件句柄(避免文件被占用)
      fileIo.closeSync(this.fd);
      
      promptAction.showToast({ message: `录音已保存:${this.filePath}` });
      console.log('录音文件路径:', this.filePath);

      // 清空实例,避免重复操作
      this.avRecorder = undefined;
      this.fd = undefined;
    } catch (error) {
      console.error('结束录音失败:', error);
      promptAction.showToast({ message: '录音停止失败' });
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('音频录制演示')
        .fontSize(20)
        .fontWeight(600);

      // 开始录音按钮
      Button('开始录音')
        .backgroundColor('#007DFF')
        .fontColor('#FFFFFF')
        .onClick(() => {
          this.startRecord();
        });

      // 结束录音按钮
      Button('结束录音')
        .backgroundColor('#FF4444')
        .fontColor('#FFFFFF')
        .onClick(() => {
          this.stopRecord();
        });
    }
    .padding(40)
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center);
  }
}

四、核心代码深度解析

1. 开始录音(startRecord)

startRecord是录音的核心,分为 “创建文件→配置参数→启动录音” 三个关键步骤:

步骤 1:创建录音文件

typescript

运行

const ctx = getContext(this);
this.filePath = `${ctx.filesDir}/${Date.now()}.m4a`;
const file = fileIo.openSync(this.filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
this.fd = file.fd;
  • ctx.filesDir:应用私有目录(沙盒目录),无需额外权限即可读写,避免文件权限问题;
  • Date.now():用时间戳命名文件,确保每次录音生成唯一文件,避免覆盖;
  • fileIo.openSync:以 “CREATE(创建)+ READ_WRITE(读写)” 模式打开文件,获取文件句柄fdAVRecorder通过fd将音频数据写入文件。
步骤 2:配置录音参数

typescript

运行

const recordConfig: media.AVRecorderConfig = {
  audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频源为麦克风
  profile: {
    audioBitrate: 100000, // 比特率:100kbps(可调整,如64000=64kbps)
    audioChannels: 1, // 单声道(语音录音足够,立体声会增加文件大小)
    audioCodec: media.CodecMimeType.AUDIO_AAC, // 仅支持AAC编码(鸿蒙限制)
    audioSampleRate: 48000, // 采样率:48kHz(主流录音标准)
    fileFormat: media.ContainerFormatType.CFT_MPEG_4A // 仅支持m4a封装格式
  },
  url: `fd://${this.fd}` // 绑定文件句柄,录音数据写入该文件
};

重要限制:鸿蒙AVRecorder目前仅支持AAC编码和m4a封装格式,修改为其他格式会导致录音失败。

步骤 3:初始化并启动录音

typescript

运行

this.avRecorder = await media.createAVRecorder();
await this.avRecorder.prepare(recordConfig);
await this.avRecorder.start();
  • createAVRecorder():异步创建AVRecorder实例,必须用await等待创建完成;
  • prepare(recordConfig):加载并验证录音配置,配置错误会在此步骤抛出异常;
  • start():启动录音,麦克风开始采集音频并写入文件。

2. 结束录音(stopRecord)

stopRecord的核心是 “停止录音→释放资源→关闭文件”,资源释放是关键,否则会导致内存泄露或文件占用

typescript

运行

await this.avRecorder.stop(); // 停止录音
await this.avRecorder.release(); // 释放AVRecorder(解码器、麦克风资源等)
fileIo.closeSync(this.fd); // 关闭文件句柄,释放文件占用
  • stop():停止音频采集,但AVRecorder资源未释放;
  • release():彻底释放AVRecorder占用的所有系统资源(麦克风、解码器等);
  • closeSync(this.fd):关闭文件句柄,否则文件会被系统锁定,无法后续读取 / 删除。

五、关键避坑指南

1. 麦克风权限未申请

  • 现象:调用startRecord后无反应,控制台报错 “permission denied”;
  • 解决方案:严格按前文配置MICROPHONE权限,且确保用户授权(可参考前文权限申请教程)。

2. 文件句柄未关闭

  • 现象:多次录音后应用卡顿,或无法删除录音文件(提示 “文件被占用”);
  • 解决方案:stopRecord中必须调用fileIo.closeSync(this.fd),且释放后清空fd

3. 录音配置参数错误

  • 常见错误:将audioCodec改为MP3fileFormat改为MP3
  • 解决方案:仅使用鸿蒙支持的参数(AAC编码 +m4a格式),其他格式暂不支持。

4. AVRecorder 资源未释放

  • 现象:多次点击 “开始 / 结束” 后,麦克风被占用,无法再次录音;
  • 解决方案:stopRecord中必须调用avRecorder.release(),并清空avRecorder实例。

5. 文件路径错误

  • 错误示例:使用绝对路径(如/sdcard/record.m4a),导致文件创建失败;
  • 解决方案:优先使用应用私有目录(ctx.filesDir),无需额外权限,兼容性最好。

六、扩展优化建议

1. 添加录音状态提示

增加录音中状态标记,避免重复点击 “开始录音”:

typescript

运行

@State isRecording: boolean = false; // 录音状态标记

async startRecord() {
  if (this.isRecording) return; // 避免重复录音
  this.isRecording = true;
  // 原有录音逻辑...
}

async stopRecord() {
  this.isRecording = false;
  // 原有停止逻辑...
}

// 按钮禁用状态
Button('开始录音')
  .disabled(this.isRecording)
  .onClick(() => {
    this.startRecord();
  });

2. 增加录音时长显示

通过定时器实时显示录音时长:

typescript

运行

@State recordDuration: number = 0; // 录音时长(秒)
timerId?: number; // 定时器ID

async startRecord() {
  // 原有逻辑...
  // 启动定时器,每秒更新时长
  this.timerId = setInterval(() => {
    this.recordDuration += 1;
  }, 1000);
}

async stopRecord() {
  // 原有逻辑...
  clearInterval(this.timerId); // 清除定时器
  this.recordDuration = 0; // 重置时长
}

// 页面中显示时长
Text(`录音时长:${this.recordDuration}秒`)
  .fontSize(16)
  .fontColor(this.isRecording ? '#FF4444' : '#666666');

3. 检查录音文件是否生成

停止录音后,验证文件是否存在,避免空文件:

typescript

运行

async stopRecord() {
  // 原有逻辑...
  // 检查文件是否存在
  const isFileExist = fileIo.accessSync(this.filePath);
  if (isFileExist) {
    const fileStat = fileIo.statSync(this.filePath);
    console.log('录音文件大小:', fileStat.size, '字节');
    promptAction.showToast({ message: `录音成功,文件大小:${Math.round(fileStat.size/1024)}KB` });
  } else {
    promptAction.showToast({ message: '录音文件生成失败' });
  }
}

七、总结

本文基于鸿蒙AVRecorder实现了基础的音频录制功能,核心要点可总结为:

  1. 权限先行:录音必须配置并申请MICROPHONE权限,否则功能无法使用;
  2. 文件准备:通过fileIo创建应用私有目录下的文件,获取fd关联录音数据;
  3. 参数合规:录音配置仅支持AAC编码 +m4a格式,参数错误会导致录音失败;
  4. 资源释放:停止录音后必须释放AVRecorder并关闭文件句柄,避免内存泄露和文件占用。

掌握AVRecorder的核心用法后,可进一步扩展功能(如暂停 / 继续录音、录音质量切换、音频文件上传等),满足语音笔记、语音输入等复杂业务场景的需求。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐