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

功能介绍:

录音并保存为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.mzph.cn/news/772483.shtml

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

相关文章

super的使用细节

1、super的使用细节 2、super和this的比较

159.乐理基础-和声模板是什么?优缺点与运用要点

如果到这五线谱还没记住还不认识的话去看102.五线谱-高音谱号与103.五线谱-低音谱号这两个里&#xff0c;这里面有五线谱对应的音名&#xff0c;对比着看 如果一章没落下&#xff0c;看到这里&#xff0c;但是看不懂什么意思&#xff0c;那就强行下看&#xff0c;看着看着指不…

[leetcode]118.杨辉三角

前言&#xff1a;剑指offer刷题系列 问题&#xff1a; 给定一个非负整数 *numRows&#xff0c;*生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例&#xff1a; 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,…

CKS之镜像漏洞扫描工具:Trivy

目录 Trivy介绍 Trivy安装 Trivy使用命令 容器镜像扫描 打印指定&#xff08;高危、严重&#xff09;漏洞信息 JSON格式输出 HTML格式输出 离线扫描命令 离线更新Trivy数据库 Harbor安装Trivy Trivy介绍 Trivy是一款用于扫描容器镜像、文件系统、Git仓库等的漏洞扫描…

Matlab|基于两阶段鲁棒优化的微网电源储能容量优化配置

目录 主要内容 1.1 目标函数 1.2 约束条件 1.3 不确定变量 部分代码 结果一览 下载链接 主要内容 程序主要复现的是《考虑寿命损耗的微网电池储能容量优化配置》&#xff0c;解决微网中电源/储能容量优化配置的问题&#xff0c;即风电、光伏、储能以及燃气轮机…

LeetCode - 执行子串操作后的字典序最小字符串

题目要求经过操作后的字符串的字典序要比之前小。 在做这道题的之后陷入了一个误区&#xff0c;就是看a的位置&#xff0c;a-1之后z&#xff0c;z的字典序比a大&#xff0c;所以要尽可能的避免a变成z&#xff0c;但是字典序的比较是从前往后比较的&#xff0c;纠结于a变成z&am…

NSCaching: Simple and Efficient NegativeSampling for Knowledge Graph Embedding

摘要 知识图嵌入是数据挖掘研究中的一个基本问题&#xff0c;在现实世界中有着广泛的应用。它的目的是将图中的实体和关系编码到低维向量空间中&#xff0c;以便后续算法使用。负抽样&#xff0c;即从训练数据中未观察到的负三元组中抽取负三元组&#xff0c;是KG嵌入的重要步…

第四百二十六回

文章目录 1. 概念介绍2. 实现方法2.1 原生方式2.1 插件方式 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何修改程序的桌面图标"相关的内容&#xff0c;本章回中将介绍如何处理ListView中的事件冲突.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介…

利用vite创建vue3项目

vue3 项目推荐使用vue官方推荐的vite手脚架创建&#xff0c;vue3项目&#xff0c;使用vue-cli 会存在一些问题 1.node的版本 目前的vue3需要至少需要node18及以上&#xff0c;可以安装nvm node包管理器可以快速切换node版本&#xff0c;因为node的版本的兼容性真是一言难尽。…

第十四届蓝桥杯C++A组(A/B/C/D/E/H)

文章目录 A.幸运数B.有奖问答C.平方差D.更小的数E.颜色平衡树H.异或和之和 A.幸运数 /*纯暴力*/ #include <bits/stdc.h>using namespace std;void solve() {int sum 0;for(int i 1; i < 100000000; i ){int n i;int a[11];int j 1;for(; n ! 0; j ){a[j] n % …

C++ 友元函数

目录 如果觉得有用的话&#xff0c;给小弟点个赞吧&#xff01;哈哈哈哈&#xff0c;谢谢嘞&#xff01; 概念&#xff1a; 如何理解&#xff1f; 概念&#xff1a; 友元&#xff1a;慎用&#xff08;突破封装&#xff09; 友元函数&#xff1a;在函数前加friend的函数称为…

网页代理ip怎么设置的

众所周知&#xff0c;现在网络安全和隐私保护是我们非常关注的问题。为了更好地保护自己的隐私&#xff0c;提高上网的安全性&#xff0c;使用代理IP成为了很多人的首选。 那么&#xff0c;网页代理IP是怎么设置的呢&#xff1f;下面&#xff0c;就让我来一一为大家介绍。 一、…

CMake学习笔记(二)从PROJECT_BINARY_DIR看外部编译和内部编译

目录 外部编译 内部编译 总结 外部编译 看如下例子&#xff1a;我在EXE_OUT_PATH中建立了文件夹build、文件夹src2 和 文件CMakeLists.txt 其中EXE_OUT_PATH/CMakeLists.txt的内容如下&#xff1a; PROJECT(out_path) ADD_SUBDIRECTORY(src2 bin2) MESSAGE(STATUS "m…

(一)whatsapp 语音通话基本流程

经过了一整年的开发测试&#xff0c;终于将whatsapp 语音通话完成&#xff0c;期间主要参考webrtc的源码来实现.下面简要说一下大致的步骤 XMPP 协商 发起或者接受语音通话第一步是发起XMPP 协商&#xff0c;这个协商过程非常重要。下面是协商一个包 <call toxxxs.whatsap…

【大模型基础】什么是KV Cache?

哪里存在KV Cache&#xff1f; KV cache发生在多个token生成的步骤中&#xff0c;并且只发生在decoder中&#xff08;例如&#xff0c;decoder-only模型&#xff0c;如 GPT&#xff0c;或在encoder-decoder模型&#xff0c;如T5的decoder部分&#xff09;&#xff0c;BERT这样…

Protocol Buffers设计要点

概述 一种开源跨平台的序列化结构化数据的协议。可用于存储数据或在网络上进行数据通信。它提供了用于描述数据结构的接口描述语言&#xff08;IDL&#xff09;&#xff0c;也提供了根据 IDL 产生代码的程序工具。Protocol Buffers的设计目标是简单和性能&#xff0c;所以与 XM…

(执行上下文作用域链)前端八股文修炼Day4

一 作用域作用域链 作用域&#xff08;Scope&#xff09;是指程序中定义变量的区域&#xff0c;作用域规定了在这个区域内变量的可访问性。在 JavaScript 中&#xff0c;作用域可以分为全局作用域和局部作用域。 全局作用域&#xff1a;在代码中任何地方都可以访问的作用域&am…

基于Springboot的狱内罪犯危险性评估系统的设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的狱内罪犯危险性评估系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#…

宝塔部署项目

如何在云服务器上使用宝塔 登录到你的云服务器后&#xff0c;执行宝塔面板安装命令&#xff0c;阿里云服务器网使用的CentOS操作系统&#xff0c;命令如下 yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh …

题。。。。

O - 胜利大逃亡(续) 题目分析 bfs状态压缩&#xff08;在bfs的基础上&#xff0c;存储持有不同钥匙时&#xff0c;此点位是否走过的情况&#xff09;&#xff1b; -----状态压缩使用二进制实现&#xff0c;同时通过位运算修改是否转移至另一状态&#xff08;详情见代码及注释…