鸿蒙应用开发-录音保存并播放音频

功能介绍:

录音并保存为m4a格式的音频,然后播放该音频,参考文档使用AVRecorder开发音频录制功能(ArkTS),更详细接口信息请查看接口文档:@ohos.multimedia.media (媒体服务)。

知识点:

  1. 熟悉使用AVRecorder录音并保存在本地。
  2. 熟悉使用AVPlayer播放本地音频文件。
  3. 熟悉对敏感权限的动态申请方式,本项目的敏感权限为MICROPHONE

使用环境:

  • API 9
  • DevEco Studio 4.0 Release
  • Windows 11
  • Stage模型
  • ArkTS语言

所需权限:

  1. ohos.permission.MICROPHONE

效果图:
在这里插入图片描述

核心代码:

src/main/ets/utils/Permission.ets是动态申请权限的工具:

import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {let atManager = abilityAccessCtrl.createAtManager();let grantStatus: abilityAccessCtrl.GrantStatus;// 获取应用程序的accessTokenIDlet tokenId: number;try {let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;tokenId = appInfo.accessTokenId;} catch (err) {console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);}// 校验应用是否被授予权限try {grantStatus = await atManager.checkAccessToken(tokenId, permission);} catch (err) {console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);}return grantStatus;
}export async function checkPermissions(permission: Permissions): Promise<boolean> {let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permission);if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {return true} else {return false}
}

src/main/ets/utils/Recorder.ets是录音工具类,进行录音和获取录音数据。

import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import promptAction from '@ohos.promptAction';
import audio from '@ohos.multimedia.audio';export default class AudioRecorder {private audioFile = nullprivate avRecorder: media.AVRecorder | undefined = undefined;private avProfile: media.AVRecorderProfile = {audioBitrate: 48000, // 音频比特率audioChannels: audio.AudioChannel.CHANNEL_1, // 音频声道数audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aacaudioSampleRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 音频采样率fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a};private avConfig: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风profile: this.avProfile,url: '', // 录音文件的url};// 注册audioRecorder回调函数setAudioRecorderCallback() {if (this.avRecorder != undefined) {// 错误上报回调函数this.avRecorder.on('error', (err) => {console.error(`录音器发生错误,错误码为:${err.code}, 错误信息为:${err.message}`);})}}// 开始录制async startRecord(audioPath: string) {// 1.创建录制实例this.avRecorder = await media.createAVRecorder();this.setAudioRecorderCallback();// 创建并打开录音文件this.audioFile = fs.openSync(audioPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);// 2.获取录制文件fd赋予avConfig里的urlthis.avConfig.url = `fd://${this.audioFile.fd}`// 3.配置录制参数完成准备工作await this.avRecorder.prepare(this.avConfig);// 4.开始录制await this.avRecorder.start();console.info('正在录音...')}// 暂停录制async pauseRecord() {// 仅在started状态下调用pause为合理状态切换if (this.avRecorder != undefined && this.avRecorder.state === 'started') {await this.avRecorder.pause();}}// 恢复录制async resumeRecord() {// 仅在paused状态下调用resume为合理状态切换if (this.avRecorder != undefined && this.avRecorder.state === 'paused') {await this.avRecorder.resume();}}// 停止录制async stopRecord() {if (this.avRecorder != undefined) {// 1. 停止录制// 仅在started或者paused状态下调用stop为合理状态切换if (this.avRecorder.state === 'started'|| this.avRecorder.state === 'paused') {await this.avRecorder.stop();}// 2.重置await this.avRecorder.reset();// 3.释放录制实例await this.avRecorder.release();// 4.关闭录制文件fdfs.closeSync(this.audioFile);promptAction.showToast({ message: "录音成功!" })}}
}

还需要在src/main/module.json5添加所需要的权限,注意是在module中添加,关于字段说明,也需要在各个的string.json添加:

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

页面代码如下:

import { checkPermissions } from '../utils/Permission';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
import AudioCapturer from '../utils/Recorder';
import AudioRecorder from '../utils/Recorder';
import promptAction from '@ohos.promptAction';
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';// 需要动态申请的权限
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
// 获取程序的上下文
const context = getContext(this) as common.UIAbilityContext;
// 获取项目的files目录
const filesDir = context.filesDir;
// 如果文件夹不存在就创建
fs.access(filesDir, (err, res: boolean) => {if (!res) {fs.mkdirSync(filesDir)}
});// 录音文件路径
let audioPath = filesDir + "/audio.m4a";@Entry
@Component
struct Index {@State recordBtnText: string = '按下录音'@State playBtnText: string = '播放音频'// 录音器private audioRecorder?: AudioRecorder;// 播放器private avPlayerprivate playIng: boolean = false// 页面显示时async onPageShow() {// 判断是否已经授权let promise = checkPermissions(permissions[0])promise.then((result) => {if (result) {// 初始化录音器if (this.audioRecorder == null) {this.audioRecorder = new AudioRecorder()}} else {this.reqPermissionsAndRecord(permissions)}})// 创建avPlayer实例对象this.avPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback();console.info('播放器准备完成')}build() {Row() {RelativeContainer() {// 录音按钮Button(this.recordBtnText).id('btn1').width('90%').margin({ bottom: 10 }).alignRules({bottom: { anchor: '__container__', align: VerticalAlign.Bottom },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onTouch((event) => {switch (event.type) {case TouchType.Down:console.info('按下按钮')// 判断是否有权限let promise = checkPermissions(permissions[0])promise.then((result) => {if (result) {// 开始录音this.audioRecorder.startRecord(audioPath)this.recordBtnText = '录音中...'} else {// 申请权限this.reqPermissionsAndRecord(permissions)}})breakcase TouchType.Up:console.info('松开按钮')if (this.audioRecorder != null) {// 停止录音this.audioRecorder.stopRecord()}this.recordBtnText = '按下录音'break}})// 录音按钮Button(this.playBtnText).id('btn2').width('90%').margin({ bottom: 10 }).alignRules({bottom: { anchor: 'btn1', align: VerticalAlign.Top },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {if (!this.playIng) {this.playBtnText = '播放中...'// 播放音频this.playAudio(audioPath)}else {// 停止播放this.stopPlay()}})}.width('100%').height('100%')}.width('100%').height('100%')}// 播放音频async playAudio(path: string) {this.playIng = truelet fdPath = 'fd://';let res = fs.accessSync(path);if (!res) {console.error(`音频文件不存在:${path}`);this.playIng = falsereturn}console.info(`播放音频文件:${path}`)// 打开相应的资源文件地址获取fdlet file = await fs.open(path);fdPath = fdPath + '' + file.fd;// url赋值触发initialized状态机上报this.avPlayer.url = fdPath;}// 停止播放stopPlay() {this.avPlayer.reset();}// 注册avplayer回调函数setAVPlayerCallback() {this.avPlayer.on('error', (err) => {this.playIng = falsethis.playBtnText = '播放音频'console.error(`播放器发生错误,错误码:${err.code}, 错误信息:${err.message}`);// 调用reset重置资源,触发idle状态this.avPlayer.reset();})// 状态机变化回调函数this.avPlayer.on('stateChange', async (state) => {switch (state) {case 'initialized':// 资源初始化完成,开始准备文件this.avPlayer.prepare();break;case 'prepared':// 资源准备完成,开始准备文件this.avPlayer.play();break;case 'completed':// 调用reset()重置资源,AVPlayer重新进入idle状态,允许更换资源urlthis.avPlayer.reset();break;case 'idle':this.playIng = falsethis.playBtnText = '播放音频'break;}})}// 申请权限reqPermissionsAndRecord(permissions: Array<Permissions>): void {let atManager = abilityAccessCtrl.createAtManager();// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗atManager.requestPermissionsFromUser(context, permissions).then((data) => {let grantStatus: Array<number> = data.authResults;let length: number = grantStatus.length;for (let i = 0; i < length; i++) {if (grantStatus[i] === 0) {// 用户授权,可以继续访问目标操作console.info('授权成功')if (this.audioRecorder == null) {this.audioRecorder = new AudioCapturer()}} else {promptAction.showToast({ message: '授权失败,需要授权才能录音' })return;}}}).catch((err) => {console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);})}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/287132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

007 日期类型相关工具类

推荐一篇文章 http://t.csdnimg.cn/72F7Jhttp://t.csdnimg.cn/72F7J

agent利用知识来做规划:《KnowAgent: Knowledge-Augmented Planning for LLM-Based Agents》笔记

文章目录 简介KnowAgent思路准备知识Action Knowledge的定义Planning Path Generation with Action KnowledgePlanning Path Refinement via Knowledgeable Self-LearningKnowAgent的实验结果 总结参考资料 简介 《KnowAgent: Knowledge-Augmented Planning for LLM-Based Age…

盛⽔最多的容器【双指针】

首先我们设该容器的两边为左右两边界。 这道题中的&#xff1a;盛⽔最大容量 底 * 高 左右两边界距离 * 左右两边界的较短板。 这道题如果用暴力求解&#xff0c;是个人都能想到怎么做&#xff0c;遍历所有的情况即可。 有没有更好的办法呢&#xff1f;我是搜了资料了解的。我…

Covalent Network(CQT)的以太坊时光机:在 Rollup 时代确保长期数据可用性

以太坊正在经历一场向 “Rollup 时代” 的转型之旅&#xff0c;这一转型由以太坊改进提案 EIP-4844 推动。这标志着区块链技术的一个关键转折&#xff0c;采用了一种被称为“数据块&#xff08;blobs&#xff09;”的新型数据结构。为了与以太坊的扩容努力保持一致&#xff0c;…

MATLAB 自定义生成平面点云(可指定方向,添加噪声)(48)

MATLAB 自定义生成平面点云(可指定方向,添加噪声)(48) 一、算法介绍二、算法步骤三、算法实现1.代码2.效果一、算法介绍 通过这里的平面生成方法,可以生成模拟平面的点云数据,并可以人为设置平面方向,平面大小,并添加噪声来探索不同类型的平面数据。这种方法可以用于…

mysql刨根问底

索引&#xff1a;排好序的数据结构 二叉树&#xff1a; 红黑树 hash表&#xff1a; b-tree&#xff1a; 叶子相同深度&#xff0c;叶节点指针空&#xff0c;索引元素不重复&#xff0c;从左到右递增排序 节点带data btree&#xff1a; 非叶子节点只存储索引&#xff0c;可…

Java_15 删除排序数组中的重复项

删除排序数组中的重复项 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的…

影视作品一键转成动漫,自媒体作者用DomoAI赢麻了

前言 众所周知&#xff0c;在自媒体爆火的那段时间&#xff0c;影视号是最容易起量的&#xff0c;借助高质量的影视&#xff0c;进行剪辑&#xff0c;解说&#xff0c;等二次创作&#xff0c;最终制作成高质量的作品&#xff0c;但是随着自媒体的发展&#xff0c;影视号越来越…

MyBatis是纸老虎吗?(七)

在上篇文章中&#xff0c;我们对照手动编写jdbc的开发流程&#xff0c;对MyBatis进行了梳理。通过这次梳理我们发现了一些之前文章中从未见过的新知识&#xff0c;譬如BoundSql等。本节我想继续MyBatis这个主题&#xff0c;并探索一下MyBatis中的缓存机制。在正式开始梳理前&am…

基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现(源码+LW+部署+讲解)

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…

测试小白必看:自动化测试入门基础知识

一、首先&#xff0c;什么是自动化测试&#xff1f; 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常&#xff0c;在设计了测试用例并通过评审之后&#xff0c;由测试人员根据测试用例中描述的规程一步步执行测试&#xff0c;得到实际结果与期望结果的比较…

Vuex状态、数据持久化(vue2、vue3状态数据持久化)

简介&#xff1a;Vuex是一个仓库&#xff0c;是vue的状态管理工具&#xff0c;存放公共数据&#xff0c;任何组件都可以使用vuex里的公共数据。Vuex提供了插件系统&#xff0c;允许我们使用 vuex-persistedstate插件&#xff0c;将Vuex的状态持久化到本地存储中&#xff0c;解决…

【工具篇】总结比较几种绘画软件的优缺点

目录 一、Visio二、Processon三、draw.io四、亿图图示五、wps 写在文章开头&#xff0c;感谢你的支持与关注&#xff01;小卓的主页 一、Visio Visio 是微软公司开发的一款流程图和图表绘制软件。我们可以用它来创建各种类型的图表&#xff0c;如流程图、组织结构图、网络图、平…

【python从入门到精通】-- 第二战:注释和有关量的解释

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

java的一些内部小知识,类与对象的关系

目录 1. java2. 类与对象的关系 1. java test.java ---- javac --> Test.class ---- java-----> 内存 ----> cpu 源文件 二进制代码 所有正在运行的软件都在内存中有自己的内存空间 jvm —>运行java程序的&#xff0c;java虚拟机 main(); // 内部调用run()run(i…

Fiddler抓包工具之fiddler的常用快捷键

一、常用三个快捷键 ctrlX :清空所有记录 CtrlF&#xff1a;查找 F12&#xff1a;启动或者停止抓包 使用 QuickExec Fiddler2 成了网页调试必备的工具&#xff0c;抓包看数据。Fiddler2自带命令行控制。 fiddler 命令行快捷键&#xff1a;ctrl q &#xff0c;然后 输入 help…

QTday5

头&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> //定时器事件 #include <QTime> //时间类 #include <QtTextToSpeech> //文本转语音类 #include <QMouseEvent> //鼠标事件类 QT_BEGIN_NAMESPACE …

Redis锁,乐观锁与悲观锁

锁 悲观锁 认为什么时候都会出问题&#xff0c;无论做什么都会加锁 乐观锁 很乐观&#xff0c;认为什么时候都不会出问题&#xff0c;所以不会上锁。 更新数据时去判断一下&#xff0c;在此期间&#xff0c;是否有人修改过这个数据 应用于&#xff1a;秒杀场景 **watch*…

【保姆级讲解Edge兼容性问题解决方法】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d; 希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

新人应该从哪几个方面掌握大数据测试?

什么是大数据 大数据是指无法在一定时间范围内用传统的计算机技术进行处理的海量数据集。 对于大数据的测试则需要不同的工具、技术、框架来进行处理。 大数据的体量大、多样化和高速处理所涉及的数据生成、存储、检索和分析使得大数据工程师需要掌握极其高的技术功底。 需要你…