回复
AVRecorder音频录制实战 原创
一路向北545
发布于 2024-12-10 17:30
浏览
0收藏
AVRecorder可以实现音频录制功能,本文通过音频的录制,暂停,恢复录制,停止录制来展示AVRecorder的功能。
一、页面设计
使用Image组件加载一张麦克风的图标,当未录制时,显示灰色的图标;点击麦克风图标,开始录制,图标变为蓝色且下方显示录制时间;再次点击图标,音频录制暂停,计时暂停,图标恢复灰色;再此点击,恢复录制,图标变为蓝色,计时继续。点击完成按钮,停止音频的录制,显示播放按钮。
二、开发前准备
应用可以调用麦克风录制音频,但该行为属于隐私敏感行为,在调用麦克风前,需要先向用户申请权限“ohos.permission.MICROPHONE”
在录制开始时,先检查麦克风权限是否已获得,如果已获得, 则正常进行接下来的流程,如果没有获得,那么需要申请该权限
检查某个权限是否已经被授予
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
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 (error) {
const err: BusinessError = error as BusinessError;
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
}
return grantStatus;
}
动态请求权限
async function requestPermission(context: common.UIAbilityContext) {
const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
const granStatus: Array<number> = data.authResults;
const length: number = granStatus.length;
for (let i = 0; i < length; i++) {
if (granStatus[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
} else {
return;
}
}
})
}
三、录音状态监听
录音状态分类:
idle:闲置状态。任何状态下调用reset()后都可以进入idle状态
prepared:参数设置完成。此时可以调用start()开始录制
started:正在录制
paused:录制暂停
stopped:录制停止
released:录制资源释放。
error:错误状态
@State state: media.AVRecorderState = "idle"
// 注册audioRecorder回调函数
setRecorderCallback(): void {
if (this.avRecorder !== undefined) {
// 状态机变化回调函数
this.avRecorder.on('stateChange', (state: media.AVRecorderState, _: media.StateChangeReason) => {
this.state = state
})
// 错误上报回调函数
this.avRecorder.on('error', (err: BusinessError) => {
})
}
}
四、主要代码逻辑
(1)开始录制
async startRecord() {
if (this.avRecorder !== undefined) {
await this.avRecorder.release();
this.avRecorder = undefined;
}
// 1.创建录制实例
this.avRecorder = await media.createAVRecorder();
this.setRecorderCallback() //设置录音状态监听
// 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档
const context = getContext(this);
const path = context.filesDir;
this.filePath = path + '/test.mp3';
const file = fs.openSync(this.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
const fdNumber = file.fd;
this.avConfig.url = 'fd://' + fdNumber;
// 3.配置录制参数完成准备工作
await this.avRecorder.prepare(this.avConfig);
// 4.开始录制
await this.avRecorder.start();
}
(2)暂停录制
// 暂停录制对应的流程
async pauseRecord(): Promise<void> {
if (this.avRecorder !== undefined && this.avRecorder.state === 'started') { // 仅在started状态下调用pause为合理状态切换
await this.avRecorder.pause();
}
}
(3)恢复录制
// 恢复录制对应的流程
async resumeRecord(): Promise<void> {
if (this.avRecorder !== undefined && this.avRecorder.state === 'paused') { // 仅在paused状态下调用resume为合理状态切换
await this.avRecorder.resume();
}
}
(4)停止录制
// 停止录制对应的流程
async stopRecording(): Promise<void> {
if (this.avRecorder !== undefined) {
// 1. 停止录制
if (this.avRecorder.state === 'started'
|| this.avRecorder.state === 'paused') { // 仅在started或者paused状态下调用stop为合理状态切换
await this.avRecorder.stop();
}
// 2.重置
await this.avRecorder.reset();
// 3.释放录制实例
await this.avRecorder.release();
this.avRecorder = undefined;
}
}
(5)使用AvPlayer播放已录制的音频文件
async play() {
if (this.avRecorder === undefined) {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback(this.avPlayer);
}
let fdPath = 'fd://';
// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例
const context = getContext(this) as common.UIAbilityContext;
const pathDir = context.filesDir;
const path = pathDir + '/01.mp3';
// 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报
const file = await fs.open(this.filePath);
fdPath = fdPath + '' + file.fd;
if (this.avPlayer) {
this.avPlayer.url = fdPath;
}
}
五、完整案例代码
import { media } from '@kit.MediaKit';
import fs from '@ohos.file.fs';
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { AVPlayer } from '../AVPlayer';
// 获取麦克风权限
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
const avPlayer = new AVPlayer();
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
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 (error) {
const err: BusinessError = error as BusinessError;
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (error) {
const err: BusinessError = error as BusinessError;
}
return grantStatus;
}
async function checkPermissions(context: common.UIAbilityContext): Promise<void> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
} else {
// 申请麦克风权限
requestPermission(context)
}
}
async function requestPermission(context: common.UIAbilityContext) {
const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
const granStatus: Array<number> = data.authResults;
const length: number = granStatus.length;
for (let i = 0; i < length; i++) {
if (granStatus[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
} else {
return;
}
}
})
}
@Entry
@Component
struct Index {
@State filePath: string = ""
@State state: media.AVRecorderState = "idle"
@State avRecorder: media.AVRecorder | undefined = undefined;
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
private avProfile: media.AVRecorderProfile = {
audioBitrate: 100000, // 音频比特率
audioChannels: 2, // 音频声道数
audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aac
audioSampleRate: 48000, // 音频采样率
fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a
};
private avConfig: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风
profile: this.avProfile,
url: 'fd://35', // 参考应用文件访问与管理开发示例新建并读写一个文件
};
intervalId: number = 0
@State seconds: number = 0
// 注册audioRecorder回调函数
setRecorderCallback(): void {
if (this.avRecorder !== undefined) {
// 状态机变化回调函数
this.avRecorder.on('stateChange', (state: media.AVRecorderState, _: media.StateChangeReason) => {
this.state = state
})
// 错误上报回调函数
this.avRecorder.on('error', (err: BusinessError) => {
})
}
}
public secondToTime(d: number): string {
if (d < 0) {
d = 0
}
let duration = Math.round(d)
let min = Math.floor(d / 60)
let second = Math.round(duration - min * 60)
return (min < 10 ? `0${min}` : `${min}:`) + `:` + (second < 10 ? `0${second}` : `${second}`)
}
aboutToAppear(): void {
}
build() {
Column() {
if (this.state === "released") {
//录音完成时
Image(this.playState === "playing" ? $r("app.media.pause") : $r("app.media.play"))
.width(80)
.height(80)
.onClick(() => {
if (this.playState === "idle" || this.playState === "released") {
this.play()
} else if (this.playState === "playing") {
this.avPlayer?.pause()
} else if (this.playState === "paused") {
this.avPlayer?.play()
}
})
} else {
Image(this.state === "started" ? $r("app.media.record") : $r("app.media.record_normal"))
.width(80)
.height(80)
.onClick(() => {
if (this.state === "idle" || this.avRecorder === undefined) {
this.startRecord()
} else if (this.state === "started") {
this.pauseRecord()
} else if (this.state === "paused") {
this.resumeRecord()
}
})
}
if (this.state !== "released") {
Text(this.secondToTime(this.seconds))
.margin({ top: 10 })
}
if (this.state === "started" || this.state === "paused") {
Button("完成")
.margin({ top: 10 }).onClick(() => {
this.stopRecording()
})
}
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
// 停止录制对应的流程
async stopRecording(): Promise<void> {
if (this.avRecorder !== undefined) {
// 1. 停止录制
if (this.avRecorder.state === 'started'
|| this.avRecorder.state === 'paused') { // 仅在started或者paused状态下调用stop为合理状态切换
await this.avRecorder.stop();
clearInterval(this.intervalId);
}
// 2.重置
await this.avRecorder.reset();
// 3.释放录制实例
await this.avRecorder.release();
this.avRecorder = undefined;
}
}
// 恢复录制对应的流程
async resumeRecord(): Promise<void> {
if (this.avRecorder !== undefined && this.avRecorder.state === 'paused') { // 仅在paused状态下调用resume为合理状态切换
await this.avRecorder.resume();
}
}
// 暂停录制对应的流程
async pauseRecord(): Promise<void> {
if (this.avRecorder !== undefined && this.avRecorder.state === 'started') { // 仅在started状态下调用pause为合理状态切换
await this.avRecorder.pause();
}
}
async startRecord() {
checkPermissions(this.context).then(async () => {
if (this.avRecorder !== undefined) {
await this.avRecorder.release();
this.avRecorder = undefined;
}
// 1.创建录制实例
this.avRecorder = await media.createAVRecorder();
this.setRecorderCallback() //设置录音状态监听
// 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档
const context = getContext(this);
const path = context.filesDir;
this.filePath = path + '/test.mp3';
const file = fs.openSync(this.filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
const fdNumber = file.fd;
this.avConfig.url = 'fd://' + fdNumber;
// 3.配置录制参数完成准备工作
await this.avRecorder.prepare(this.avConfig);
// 4.开始录制
await this.avRecorder.start();
//计时
this.intervalId = setInterval(() => {
if (this.state === "started") {
this.seconds++
}
}, 1000);
})
}
private avPlayer: media.AVPlayer | undefined = undefined
@State playState: string = "idle"
async play() {
if (this.avRecorder === undefined) {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback(this.avPlayer);
}
let fdPath = 'fd://';
// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例
const context = getContext(this) as common.UIAbilityContext;
const pathDir = context.filesDir;
const path = pathDir + '/01.mp3';
// 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报
const file = await fs.open(this.filePath);
fdPath = fdPath + '' + file.fd;
if (this.avPlayer) {
this.avPlayer.url = fdPath;
}
}
// 注册avplayer回调函数
setAVPlayerCallback(avPlayer: media.AVPlayer): void {
// seek操作结果回调函数
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
avPlayer.on('error', (err: BusinessError) => {
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
})
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string, _: media.StateChangeReason) => {
this.playState = state
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info('AVPlayer state idle called.');
avPlayer.release(); // 调用release接口销毁实例对象
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info('AVPlayer state initialized called.');
avPlayer.prepare();
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info('AVPlayer state prepared called.');
avPlayer.play(); // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info('AVPlayer state playing called.');
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info('AVPlayer state paused called.');
break;
case 'completed': // 播放结束后触发该状态机上报
console.info('AVPlayer state completed called.');
avPlayer.stop(); //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
}
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
赞
收藏
回复
相关推荐