HarmonyOS/OpenHarmony Audio 实现音频录制及播放功能
关键词:audio、音频录制、音频播放、权限申请、文件管理
在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景,那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块,核心逻辑为利用 AudioCapturer 创建音频采集器收集音频并写入文件至沙箱,利用 AudioRenderer 播放沙箱中写入的音频文件,确定目标那么开始。
本期文章的完整demo代码已经提交至Gitee:https://gitee.com/luvi/sound-recording
1. 添加权限
需要录音,必不可少的是麦克风权限,需要在 module.json5 中添加 ohos.permission.MICROPHONE 权限。

2. 引导用户授权
在第一步添加完麦克风权限后,app开启后并不能直接使用该权限,用户需要手动确认麦克风权限的开启,在用户手动确认后,麦克风权限则开始在当前app生效。

所以,在代码中我们需要进行访问权限控制弹窗的拉起操作,在这里使用 requestPermissionsFromUser 即可。需要注意的是,若用户拒绝权限后,下次需要引导用户前往设置页手动打开该权限,此处就不做过多逻辑处理,默认用户会同意该权限。
// 此处需要导入权限控制模块
import { abilityAccessCtrl, Permissions,PermissionRequestResult } from '@kit.AbilityKit';let permissionList: Permissions[] = ["ohos.permission.MICROPHONE"]
// 获取访问控制模块对象
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let context: Context = getContext(this) as common.UIAbilityContext;
atManager.requestPermissionsFromUser(context, permissionList, (err: BusinessError, data: PermissionRequestResult) => {if (err) {console.error(`luvi > requestPermissionsFromUser fail, err->${JSON.stringify(err)}`);} else {// 权限获取成功console.info('luvi > data:' + JSON.stringify(data));}
});
3. 创建 AudioCapturer 音频采集器,准备录音
在第2部授权操作完成后才可进行 AudioCapturer 音频采集器的创建,不然没有权限是会报系统异常的错误。
// 此audioCapturer是写在struct中,自行修改位置
audioCapturer: audio.AudioCapturer | null = null...let audioCapturerOptions: audio.AudioCapturerOptions = {// 音频流信息streamInfo: {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,channels: audio.AudioChannel.CHANNEL_2,sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW},// 采集器信息capturerInfo: {source: audio.SourceType.SOURCE_TYPE_MIC,capturerFlags: 0}
}// 创建音频采集器
audio.createAudioCapturer(audioCapturerOptions, (err, data) => {if (err) {console.error(`luvi > AudioCapturer Created : Error: ${err}`);} else {console.info('luvi > AudioCapturer Created : Success.');// 音频采集器对象this.audioCapturer = data;}
});
4. 开始录音
在第3步的操作后,我们已经拿到了 audioCapturer 对象,后续需要通过该对象进行音频录制与取消。
在录音过程中,需要不断的写入声音数据到文件中,所以我们需要订阅音频数据读入回调事件 后触发 start 操作开始录音,在文件数据写入前需要增加 fs.OpenMode.READ_WRITE 权限。此处需要注意的是 MyVoice.wav 文件本身并不存在与沙箱文件中,但是我们使用文件管理的 open 方法配置 fs.OpenMode.CREATE 权限则会自动创建出该文件。
// 导入文件管理模块
import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit';...// struct中
destFile: fs.File | null = null...Button("开始采集语音").onClick(() => {let path = getContext().getApplicationContext().filesDir;let bufferSize: number = 0;let filePath = path + '/MyVoice.wav';this.destFile = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);let readDataCallback = (buffer: ArrayBuffer) => {let options: ReadOptions = {offset: bufferSize,length: buffer.byteLength}fs.writeSync(this.destFile?.fd, buffer, options);bufferSize += buffer.byteLength;}this.audioCapturer?.on('readData', readDataCallback);this.audioCapturer?.start((err: BusinessError) => {if (err) {console.error('luvi > Capturer start failed.');} else {console.info('luvi > Capturer start success.');}});
})
5. 结束录音
录音结束后关闭文件操作,避免资源占用。
Button("结束采集音频").onClick(() => {this.audioCapturer?.stop((err: BusinessError) => {if (err) {console.error('luvi > Capturer stop failed');} else {console.info('luvi > Capturer stopped.');}});fs.close(this.destFile)
})
此时录制的音频已经保存至了沙箱中。

6. 创建音频渲染器
audioRenderer 是写在 struct 中,需要保存音频渲染器对象供后续使用。
// 此audioRenderer是写在struct中,自行修改位置
audioRenderer: audio.AudioRenderer| null = null...
let audioRendererOptions: audio.AudioRendererOptions = {streamInfo: {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率channels: audio.AudioChannel.CHANNEL_2, // 通道sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式},rendererInfo: {content: audio.ContentType.CONTENT_TYPE_MUSIC, // 媒体类型usage: audio.StreamUsage.STREAM_USAGE_MEDIA, // 音频流使用类型rendererFlags: 0 // 音频渲染器标志}
}audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例if (!err) {console.info(`luvi > creating AudioRenderer success`);// 音频渲染器对象this.audioRenderer = renderer;} else {console.info(`luvi > creating AudioRenderer failed, error: ${err.message}`);}
});
7.播放音频
播放第5步保存的音频文件,需要使用音频渲染器对象,创建的渲染器本身无音频对象,所以需要在启动音频渲染器后,不断地在音频渲染器中写入音频文件的缓冲数据,从而达到播放效果,当播放完毕后关闭文件和渲染器。
Button("播放音频采集结果").onClick(async () => {if (!this.audioRenderer){return}let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];if (stateGroup.indexOf(this.audioRenderer.state) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染console.error('luvi > start failed');return;}await this.audioRenderer.start(); // 启动渲染const bufferSize = await this.audioRenderer.getBufferSize();let context = getContext(this).getApplicationContext();let path = context.filesDir;const filePath = path + '/MyVoice.wav'; // 使用沙箱路径获取文件,实际路径为/data/storage/el2/base/haps/entry/files/test.wavlet file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);let stat = await fs.stat(filePath);let buf = new ArrayBuffer(bufferSize);let len = stat.size % bufferSize === 0 ? Math.floor(stat.size / bufferSize) : Math.floor(stat.size / bufferSize + 1);for (let i = 0; i < len; i++) {let options: ReadOptions = {offset: i * bufferSize,length: bufferSize};let readsize = await fs.read(file.fd, buf, options);// buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染let writeSize: number = await new Promise((resolve, reject) => {this.audioRenderer?.write(buf, (err, writeSize) => {if (err) {reject(err);} else {resolve(writeSize);}});});if (this.audioRenderer.state === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为released,停止渲染fs.close(file);await this.audioRenderer.stop();}if (this.audioRenderer.state === audio.AudioState.STATE_RUNNING) {if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染fs.close(file);await this.audioRenderer.stop();}}}
})
此时,我们就已经完成了音频录制与播放的一整套功能,若在开发中遇到问题可连接设备点击 IDE 右下角的 Device File Browser 文件浏览器,查看音频文件写入是否正确,还有最重要的就算别忘记添加权限。
完整代码已经提交至了Gitee中,可回顶部查看。
相关文章:
HarmonyOS/OpenHarmony Audio 实现音频录制及播放功能
关键词:audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景,那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块&am…...
css 中 ~ 符号、text-indent、ellipsis、ellipsis-2、text-overflow: ellipsis、::before的使用
1、~的使用直接看代码 <script setup> </script><template><div class"container"><p><a href"javascript:;">纪检委</a><a href"javascript:;">中介为</a><a href"javascript:…...
Activiti 工作流大致了解
一、什么是 Activiti 简而言之,就是系统的流程图,如:请假审批流程、账单审批流程等。 二、mysql与pom配置 mysql要使用jdbc:mysql://localhost:3306/activiti?autoReconnecttrue pom文件要添加关键依赖 <!--activiti核心依赖--> &…...
速盾:高防 CDN,网站安全的有力保障
在当今数字化时代,网站安全已成为企业和个人关注的焦点。随着网络攻击手段的不断升级,传统的安全防护措施已经难以满足需求。而高防 CDN(Content Delivery Network,内容分发网络)的出现,为网站安全提供了有…...
宝塔搭建nextcould 30docker搭建onlyoffic8.0
宝塔搭建nextcould 宝塔搭建nextcould可以参考这两个博文 我搭建的是30版本的nextcould,服务组件用的是下面这些,步骤是一样的,只是版本不一样而已 nginx 1.24.0 建议选择nginx,apache没成功。 MySQL 8.0以上都可以 php 8.2.…...
【源码+文档+调试讲解】交通信息管理系统
摘 要 智能交通信息管理系统是一种基于计算机技术的软件系统,旨在提高交通管理的效率和服务质量。通过该系统可以实现智能交通管理的全面管理和优化。智能交通信息管理系统具备集成管理功能。它能够整合智能交通管理的各个业务环节,包括个人中心、用户管…...
小阿轩yx-案例:Ansible剧本文件实践
小阿轩yx-案例:Ansible剧本文件实践 Playbook 介绍 什么是 playbook playbook 顾名思义,即剧本,现实生活中演员按照剧本表演在 ansible 中,由被控计算机表演,进行安装,部署应用,提供对外的服…...
【ShuQiHere】深入理解微架构(Microarchitecture):LC-3 的底层实现 ️
【ShuQiHere】🖥️ 微架构(Microarchitecture) 是计算机体系结构中的重要概念,它定义了如何将 指令集架构(Instruction Set Architecture, ISA) 转化为实际硬件。通过微架构,我们可以理解计算机…...
Ubuntu24.04.1系统下VideoMamba环境配置
文章目录 前言第一步:基本的环境创建第二步:causal-conv1d和mamba_ssm库的安装第三步:安装requirements.txt 前言 VideoMamba环境的配置折磨了我三天,由于Mamba对Cuda的版本有要求,因此配置环境的时候Cuda版本以及各种…...
c++第十二章续(队列结构类模拟)
队列类 设计类,需要开发公有接口和私有实现 Queue类接口 公有接口: 默认初始化,和可以用显式初始化覆盖默认值 Queue类的实现 如何表示队列数据: 一种方法是使用new动态分配一个数组,它包含所需的元素数。不过&…...
数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall
数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall 数据集-目标检测系列-豹子 猎豹 检测数据集 leopard 数据量:5k 想要进一步了解,请联系。 DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集&#x…...
基于ESP8266—AT指令连接阿里云+MQTT透传数据(3)
MQTT_RX设备为接收(订阅)数据的Topic,使用ESP8266通过AT指令实现。 首先需要串口通信软件,如 SSCOM、PuTTY、SecureCRT 等串口调试工具,功能丰富,支持常见的串口调试功能,用于发送AT指令。 以下是ESP8266通过AT指令连接阿里云MQTT服务的步骤: 1、初始化WiFi 发送下面…...
redis的数据结构,内存处理,缓存问题
redisObject redis任意数据的key和value都会被封装为一个RedisObject,也叫redis对象: 这就redis的头信息,占有16个字节 redis中有两个热门数据结构 1.SkipList,跳表,首先是链表,和普通链表有以下差异&am…...
机器学习模型评估与选择
前言 承接上篇讲述了机器学习有哪些常见的模型算法,以及使用的场景,本篇将继续讲述如何选择模型和评估模型。几个概念了解一下: 经验误差:模型在训练集上的误差称之为经验误差;过拟合:模型在训练集上表现…...
Web认识 -- 第一课
文章目录 前言一、HTML是什么?二、了解Web1. 基本概念2.Web标准3. Web构成1.前端1. HTML2.CSS3. javaScript4.常见浏览器介绍 2.Web标签构成1.结构标准2.表现标准 -- css3. 行为标准 -- javaScript 总结 前言 这里是我们进入前端学习的开端,在本次更新之后我会陆续…...
Recaptcha2 图像识别 API 对接说明
Recaptcha2 图像识别 API 对接说明 本文将介绍一种 Recaptcha2 图像识别2 API 对接说明,它可以通过用户输入识别的内容和 Recaptcha2验证码图像,最后返回需要点击的小图像的坐标,完成验证。 接下来介绍下 Recaptcha2 图像识别 API 的对接说…...
6种MySQL高可用方案对比分析
大家好,我是 V 哥,关于 MySQL 高可用方案,在面试中频频出现,有同学在字节面试就遇到过,主要考察你在高可用项目中是如何应用的,V 哥整理了6种方案,供你参考。 MySQL的高可用方案有多种…...
FastAPI: websocket的用法及举例
1. Websocket 1.1 Websocket介绍 WebSocket 是一种在单个TCP连接上进行全双工通信的协议,允许客户端和服务器之间相互发送数据,而不需要像传统的HTTP请求-响应模型那样频繁建立和断开连接。 全双工通信(Full-Duplex Communication)是一种通信模式&#…...
JavaSE——面向对象2:方法的调用机制、传参机制、方法递归、方法重载、可变参数、作用域
目录 一、成员方法 (一)方法的快速入门 (二)方法的调用机制(重要) (三)方法的定义 (四)注意事项和使用细节 1.访问修饰符(作用是控制方法的使用范围) 2.返回的数据类型 3.方法名 4.形参列表 5.方法体 6.方法调用细节说明 (五)传参机制 1.基本数据类型的传参机制 …...
Vue+Flask
App.vue 首先要安装 npm install axios<template><div><h1>{{ message }}</h1><input v-model"name" placeholder"Enter your name" /><input v-model"age" placeholder"Enter your age" /><…...
Dramatron:AI驱动的剧本创作革命
Dramatron:AI驱动的剧本创作革命 【免费下载链接】dramatron Dramatron uses large language models to generate coherent scripts and screenplays. 项目地址: https://gitcode.com/gh_mirrors/dr/dramatron 价值定位:重新定义创意写作流程 在…...
Wan2.2-I2V-A14B部署教程:NVIDIA MPS多进程服务共享GPU资源方案
Wan2.2-I2V-A14B部署教程:NVIDIA MPS多进程服务共享GPU资源方案 1. 镜像概述与核心特性 Wan2.2-I2V-A14B是一款专为文本生成视频任务优化的私有部署镜像,特别针对RTX 4090D 24GB显存显卡进行了深度优化。这个镜像最大的特点是内置了完整的运行环境和模…...
聊聊我对CompletableFuture的理解
Java提供了许多工具来处理并发编程,而本文将重点介绍Java8中的CompletableFuture。在本文中,笔者通过查阅资料和实践经验,避免了重复已有优秀文章的内容和思路,而是用更简单明了的示例和语言来介绍CompletableFuture,并…...
【内测开启】一个 Token,让你的Agent拥有地图能力!
各位AI大佬/极客朋友们: 期待已久的 百度地图 Map Agent Plan 正式开启首批内测招募啦!✨ 我们深知独立开发者和 OpenClaw 玩家们的痛点,所以这次我们玩点不一样的: ✅ 极简集成: 告别复杂API申请流程,一个…...
断更 9 天放大招!OpenClaw 3.22 版全维度升级,龙虾这次真的变超强
各位技术圈的小伙伴,学长来给大家同步个重磅消息!火遍全网的 OpenClaw 断更 9 天之后,直接甩出王炸 ——2026.3.22-beta.1 预览版正式上线,这次可不是小修小补,而是从插件架构到安全防护、从模型配置到交互体验的底层大…...
从检测到分析:手机位置热力图生成与行为模式挖掘扩展方案
从检测到分析:手机位置热力图生成与行为模式挖掘扩展方案 1. 引言:从“看见”到“看懂” 想象一下,你在一间大型会议室里,墙上挂着十几个监控摄像头。传统的监控系统能告诉你“画面里有手机”,但仅此而已。你无法知道…...
从“单点防御”到“全局联动”:手把手教你用EDR和NDR构建企业安全闭环
从“单点防御”到“全局联动”:手把手教你用EDR和NDR构建企业安全闭环 当企业的安全团队还在疲于应对零散的端点告警和网络流量异常时,攻击者早已开始采用自动化工具进行横向移动。传统孤立的防御手段就像用多个单点摄像头监控银行金库——每个摄像头都…...
Python实战:利用SymPy与SciPy高效破解复杂非线性方程组
1. 为什么需要SymPy和SciPy解非线性方程组? 遇到工程计算或科研问题时,我们常需要解像这样的方程组:xy10且yz34。这种包含平方项、三角函数或指数函数的方程,传统手工计算不仅耗时还容易出错。我去年做机器人运动学分析时…...
新手入门:基于快马平台复现pencil设计工具基础功能学前端
最近在学前端开发,想找个能动手实践的项目练练手。朋友推荐了pencil官网的设计工具,但直接看源码有点复杂。后来发现用InsCode(快马)平台可以快速复现基础功能,特别适合新手理解画布操作和事件处理。下面分享我的学习过程: 画布搭…...
新手必看:在快马平台学习排列组合公式的代码实现
新手必看:在快马平台学习排列组合公式的代码实现 作为一个编程新手,当我第一次接触排列组合公式时,那些数学符号和递归逻辑让我一头雾水。直到在InsCode(快马)平台上找到了带详细注释的示例代码,才真正理解了Cn和An公式的实现原理…...

