HarmonyOS ArkTS 实战:基于 AVRecorder 实现音频录制(完整录音 + 本地文件保存)
本文基于鸿蒙AVRecorder权限先行:录音必须配置并申请MICROPHONE权限,否则功能无法使用;文件准备:通过fileIo创建应用私有目录下的文件,获取fd关联录音数据;参数合规:录音配置仅支持AAC编码 +m4a格式,参数错误会导致录音失败;资源释放:停止录音后必须释放AVRecorder并关闭文件句柄,避免内存泄露和文件占用。掌握AVRecorder的核心用法后,可进一步扩展功能(如暂
在 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(读写)” 模式打开文件,获取文件句柄fd,AVRecorder通过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改为MP3、fileFormat改为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实现了基础的音频录制功能,核心要点可总结为:
- 权限先行:录音必须配置并申请
MICROPHONE权限,否则功能无法使用; - 文件准备:通过
fileIo创建应用私有目录下的文件,获取fd关联录音数据; - 参数合规:录音配置仅支持
AAC编码 +m4a格式,参数错误会导致录音失败; - 资源释放:停止录音后必须释放
AVRecorder并关闭文件句柄,避免内存泄露和文件占用。
掌握AVRecorder的核心用法后,可进一步扩展功能(如暂停 / 继续录音、录音质量切换、音频文件上传等),满足语音笔记、语音输入等复杂业务场景的需求。
更多推荐
所有评论(0)