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" /><…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...