当前位置: 首页 > news >正文

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 实现音频录制及播放功能

关键词&#xff1a;audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景&#xff0c;那么本期将介绍如何利用鸿蒙 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 简而言之&#xff0c;就是系统的流程图&#xff0c;如&#xff1a;请假审批流程、账单审批流程等。 二、mysql与pom配置 mysql要使用jdbc:mysql://localhost:3306/activiti?autoReconnecttrue pom文件要添加关键依赖 <!--activiti核心依赖--> &…...

速盾:高防 CDN,网站安全的有力保障

在当今数字化时代&#xff0c;网站安全已成为企业和个人关注的焦点。随着网络攻击手段的不断升级&#xff0c;传统的安全防护措施已经难以满足需求。而高防 CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;的出现&#xff0c;为网站安全提供了有…...

宝塔搭建nextcould 30docker搭建onlyoffic8.0

宝塔搭建nextcould 宝塔搭建nextcould可以参考这两个博文 我搭建的是30版本的nextcould&#xff0c;服务组件用的是下面这些&#xff0c;步骤是一样的&#xff0c;只是版本不一样而已 nginx 1.24.0 建议选择nginx&#xff0c;apache没成功。 MySQL 8.0以上都可以 php 8.2.…...

【源码+文档+调试讲解】交通信息管理系统

摘 要 智能交通信息管理系统是一种基于计算机技术的软件系统&#xff0c;旨在提高交通管理的效率和服务质量。通过该系统可以实现智能交通管理的全面管理和优化。智能交通信息管理系统具备集成管理功能。它能够整合智能交通管理的各个业务环节&#xff0c;包括个人中心、用户管…...

小阿轩yx-案例:Ansible剧本文件实践

小阿轩yx-案例&#xff1a;Ansible剧本文件实践 Playbook 介绍 什么是 playbook playbook 顾名思义&#xff0c;即剧本&#xff0c;现实生活中演员按照剧本表演在 ansible 中&#xff0c;由被控计算机表演&#xff0c;进行安装&#xff0c;部署应用&#xff0c;提供对外的服…...

【ShuQiHere】深入理解微架构(Microarchitecture):LC-3 的底层实现 ️

【ShuQiHere】&#x1f5a5;️ 微架构&#xff08;Microarchitecture&#xff09; 是计算机体系结构中的重要概念&#xff0c;它定义了如何将 指令集架构&#xff08;Instruction Set Architecture, ISA&#xff09; 转化为实际硬件。通过微架构&#xff0c;我们可以理解计算机…...

Ubuntu24.04.1系统下VideoMamba环境配置

文章目录 前言第一步&#xff1a;基本的环境创建第二步&#xff1a;causal-conv1d和mamba_ssm库的安装第三步&#xff1a;安装requirements.txt 前言 VideoMamba环境的配置折磨了我三天&#xff0c;由于Mamba对Cuda的版本有要求&#xff0c;因此配置环境的时候Cuda版本以及各种…...

c++第十二章续(队列结构类模拟)

队列类 设计类&#xff0c;需要开发公有接口和私有实现 Queue类接口 公有接口&#xff1a; 默认初始化&#xff0c;和可以用显式初始化覆盖默认值 Queue类的实现 如何表示队列数据&#xff1a; 一种方法是使用new动态分配一个数组&#xff0c;它包含所需的元素数。不过&…...

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall

数据集-目标检测系列-豹子 猎豹 检测数据集 leopard>> DataBall 数据集-目标检测系列-豹子 猎豹 检测数据集 leopard 数据量&#xff1a;5k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#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&#xff0c;也叫redis对象&#xff1a; 这就redis的头信息&#xff0c;占有16个字节 redis中有两个热门数据结构 1.SkipList&#xff0c;跳表&#xff0c;首先是链表&#xff0c;和普通链表有以下差异&am…...

机器学习模型评估与选择

前言 承接上篇讲述了机器学习有哪些常见的模型算法&#xff0c;以及使用的场景&#xff0c;本篇将继续讲述如何选择模型和评估模型。几个概念了解一下&#xff1a; 经验误差&#xff1a;模型在训练集上的误差称之为经验误差&#xff1b;过拟合&#xff1a;模型在训练集上表现…...

Web认识 -- 第一课

文章目录 前言一、HTML是什么&#xff1f;二、了解Web1. 基本概念2.Web标准3. Web构成1.前端1. HTML2.CSS3. javaScript4.常见浏览器介绍 2.Web标签构成1.结构标准2.表现标准 -- css3. 行为标准 -- javaScript 总结 前言 这里是我们进入前端学习的开端,在本次更新之后我会陆续…...

Recaptcha2 图像识别 API 对接说明

Recaptcha2 图像识别 API 对接说明 本文将介绍一种 Recaptcha2 图像识别2 API 对接说明&#xff0c;它可以通过用户输入识别的内容和 Recaptcha2验证码图像&#xff0c;最后返回需要点击的小图像的坐标&#xff0c;完成验证。 接下来介绍下 Recaptcha2 图像识别 API 的对接说…...

6种MySQL高可用方案对比分析

大家好&#xff0c;我是 V 哥&#xff0c;关于 MySQL 高可用方案&#xff0c;在面试中频频出现&#xff0c;有同学在字节面试就遇到过&#xff0c;主要考察你在高可用项目中是如何应用的&#xff0c;V 哥整理了6种方案&#xff0c;供你参考。 MySQL的高可用方案有多种&#xf…...

FastAPI: websocket的用法及举例

1. Websocket 1.1 Websocket介绍 WebSocket 是一种在单个TCP连接上进行全双工通信的协议&#xff0c;允许客户端和服务器之间相互发送数据&#xff0c;而不需要像传统的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" /><…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...