赛事竞猜网站开发,好的做网站公司,网站建设丷金手指专业十五,网站权限分配 数据库实现关键词#xff1a;audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景#xff0c;那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块audio、音频录制、音频播放、权限申请、文件管理 在app的开发过程中时常会遇见一些需要播放一段音频或进行语音录制的场景那么本期将介绍如何利用鸿蒙 audio 模块实现音频写入和播放的功能。本次依赖的是 ohos.multimedia.audio 音频管理模块核心逻辑为利用 AudioCapturer 创建音频采集器收集音频并写入文件至沙箱利用 AudioRenderer 播放沙箱中写入的音频文件确定目标那么开始。 本期文章的完整demo代码已经提交至Giteehttps://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中可回顶部查看。