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

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

功能介绍:

录音并保存为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}`);})}
}

相关文章:

  • Linux文件(系统)IO(含动静态库的链接操作)
  • 最新2024年增强现实(AR)营销指南(完整版)
  • 全国青少年软件编程(Python)等级考试一级考试真题2023年9月——持续更新.....
  • HTML块级元素和内联元素(头部和布局)
  • centos 7 安装磐维(PanWeiDB)数据库(单机)
  • pandas在循环中多次写入数据到一个excel防止锁定的方法
  • 鸿蒙ARKTS--简易的购物网站
  • 【pytest、playwright】多账号同时操作
  • 基于spark的大数据分析预测地震受灾情况的系统设计
  • 【洛谷】P9241 [蓝桥杯 2023 省 B] 飞机降落
  • OpemMP 同步结构
  • React Hooks的出现解决了什么问题?
  • 手写简易操作系统(十七)--编写键盘驱动
  • vue2 配置vue.config.js devServer 时报错
  • 设计模式深度解析:AI如何影响装饰器模式与组合模式的选择与应用
  • CentOS6 编译安装 redis-3.2.3
  • CSS中外联样式表代表的含义
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • GraphQL学习过程应该是这样的
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • JavaScript 基本功--面试宝典
  • JavaScript实现分页效果
  • Java知识点总结(JavaIO-打印流)
  • Python十分钟制作属于你自己的个性logo
  • 编写高质量JavaScript代码之并发
  • 机器学习学习笔记一
  • 机器学习中为什么要做归一化normalization
  • 类orAPI - 收藏集 - 掘金
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 前端相关框架总和
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 一个项目push到多个远程Git仓库
  • 在Docker Swarm上部署Apache Storm:第1部分
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • #pragma once
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (七)Java对象在Hibernate持久化层的状态
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • (转载)利用webkit抓取动态网页和链接
  • *1 计算机基础和操作系统基础及几大协议
  • .NET 8.0 发布到 IIS
  • .Net 高效开发之不可错过的实用工具
  • .NET开源项目介绍及资源推荐:数据持久层 (微软MVP写作)
  • .NET企业级应用架构设计系列之开场白
  • .net下的富文本编辑器FCKeditor的配置方法
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • @hook扩展分析
  • [2016.7.test1] T2 偷天换日 [codevs 1163 访问艺术馆(类似)]
  • [Android] Android ActivityManager
  • [ASP.NET MVC]如何定制Numeric属性/字段验证消息