当前位置:   article > 正文

使用AVPlayer/AudioRenderer实现音频播放功能的差别

使用AVPlayer/AudioRenderer实现音频播放功能的差别

在ArkTS中播放音频有多种可选方案,其中AVPlayer和AudioRenderer是较为常见的两种方案。虽然都可以播放音频文件,但二者之间有着较大的差别:

差别概述:

  • AVPlayer:功能较完善的音频、视频播放ArkTS/JS API,集成了流媒体和本地资源解析、媒体资源解封装、音频解码和音频输出功能。可以用于直接播放mp3、m4a等格式的音频文件,不支持直接播放PCM格式文件。
  • AudioRenderer:用于音频输出的的ArkTS/JS API,仅支持PCM格式,需要应用持续写入音频数据进行工作。应用可以在输入前添加数据预处理,如设定音频文件的采样率、位宽等,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体播放应用开发。

从以上描述可知,二者在功能上有很大的互补性,AVPlayer更泛用,使用难度更低;而AudioRenderer支持对音频数据进行更精细的处理,功能更强大,但使用难度相对也更大一些,此外二者在播放音频文件的格式方面也有一定差别。

以下是分别用AVPlayer和AudioRenderer播放音频的示例代码

使用AVPlayer播放音频文件的实例代码:

  1. import media from '@ohos.multimedia.media';
  2. import fs from '@ohos.file.fs';
  3. import common from '@ohos.app.ability.common';
  4. export class AVPlayerDemo {
  5. private avPlayer;
  6. private isSeek: boolean = true; // 用于区分模式是否支持seek操作
  7. private count: number = 0;
  8. // 注册avplayer回调函数
  9. setAVPlayerCallback() {
  10. // seek操作结果回调函数
  11. this.avPlayer.on('seekDone', (seekDoneTime) => {
  12. console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
  13. })
  14. // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
  15. this.avPlayer.on('error', (err) => {
  16. console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
  17. this.avPlayer.reset(); // 调用reset重置资源,触发idle状态
  18. })
  19. // 状态机变化回调函数
  20. this.avPlayer.on('stateChange', async (state, reason) => {
  21. switch (state) {
  22. case 'idle': // 成功调用reset接口后触发该状态机上报
  23. console.info('AVPlayer state idle called.');
  24. this.avPlayer.release(); // 调用release接口销毁实例对象
  25. break;
  26. case 'initialized': // avplayer 设置播放源后触发该状态上报
  27. console.info('AVPlayerstate initialized called.');
  28. this.avPlayer.prepare().then(() => {
  29. console.info('AVPlayer prepare succeeded.');
  30. }, (err) => {
  31. console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);
  32. });
  33. break;
  34. case 'prepared': // prepare调用成功后上报该状态机
  35. console.info('AVPlayer state prepared called.');
  36. this.avPlayer.play(); // 调用播放接口开始播放
  37. break;
  38. case 'playing': // play成功调用后触发该状态机上报
  39. console.info('AVPlayer state playing called.');
  40. if (this.count !== 0) {
  41. console.info('AVPlayer start to seek.');
  42. this.avPlayer.seek(this.avPlayer.duration); //seek到音频末尾
  43. } else {
  44. this.avPlayer.pause(); // 调用暂停接口暂停播放
  45. }
  46. this.count++;
  47. break;
  48. case 'paused': // pause成功调用后触发该状态机上报
  49. console.info('AVPlayer state paused called.');
  50. this.avPlayer.play(); // 再次播放接口开始播放
  51. break;
  52. case 'completed': // 播放结束后触发该状态机上报
  53. console.info('AVPlayer state completed called.');
  54. this.avPlayer.stop(); //调用播放结束接口
  55. break;
  56. case 'stopped': // stop接口成功调用后触发该状态机上报
  57. console.info('AVPlayer state stopped called.');
  58. this.avPlayer.reset(); // 调用reset接口初始化avplayer状态
  59. break;
  60. case 'released':
  61. console.info('AVPlayer state released called.');
  62. break;
  63. default:
  64. console.info('AVPlayer state unknown called.');
  65. break;
  66. }
  67. })
  68. }
  69. // 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过url属性进行播放示例
  70. async avPlayerUrlDemo() {
  71. // 创建avPlayer实例对象
  72. this.avPlayer = await media.createAVPlayer();
  73. // 创建状态机变化回调函数
  74. this.setAVPlayerCallback();
  75. let fdPath = 'fd://';
  76. // 通过UIAbilityContext获取沙箱地址filesDir,以下为Stage模型获方式,如需在FA模型上获取请参考《访问应用沙箱》获取地址
  77. let context = getContext(this) as common.UIAbilityContext;
  78. let pathDir = context.filesDir;
  79. let path = pathDir + '/01.mp3';
  80. // 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报
  81. let file = await fs.open(path);
  82. fdPath = fdPath + '' + file.fd;
  83. this.avPlayer.url = fdPath;
  84. }
  85. // 以下demo为使用资源管理接口获取打包在HAP内的媒体资源文件并通过fdSrc属性进行播放示例
  86. async avPlayerFdSrcDemo() {
  87. // 创建avPlayer实例对象
  88. this.avPlayer = await media.createAVPlayer();
  89. // 创建状态机变化回调函数
  90. this.setAVPlayerCallback();
  91. // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
  92. // 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
  93. let context = getContext(this) as common.UIAbilityContext;
  94. let fileDescriptor = await context.resourceManager.getRawFd('01.mp3');
  95. // 为fdSrc赋值触发initialized状态机上报
  96. this.avPlayer.fdSrc = fileDescriptor;
  97. this.isSeek = false; // 不支持seek操作
  98. }
  99. }

使用AudioRenderer播放音频文件的实例代码:

  1. import audio from '@ohos.multimedia.audio';
  2. import fs from '@ohos.file.fs';
  3. const TAG = 'AudioRendererDemo';
  4. export default class AudioRendererDemo {
  5. private renderModel = undefined;
  6. private audioStreamInfo = {
  7. samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  8. channels: audio.AudioChannel.CHANNEL_2, // 通道
  9. sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  10. encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
  11. }
  12. private audioRendererInfo = {
  13. content: audio.ContentType.CONTENT_TYPE_MUSIC, // 媒体类型
  14. usage: audio.StreamUsage.STREAM_USAGE_MEDIA, // 音频流使用类型
  15. rendererFlags: 0 // 音频渲染器标志
  16. }
  17. private audioRendererOptions = {
  18. streamInfo: this.audioStreamInfo,
  19. rendererInfo: this.audioRendererInfo
  20. }
  21. // 初始化,创建实例,设置监听事件
  22. init() {
  23. audio.createAudioRenderer(this.audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例
  24. if (!err) {
  25. console.info(`${TAG}: creating AudioRenderer success`);
  26. this.renderModel = renderer;
  27. this.renderModel.on('stateChange', (state) => { // 设置监听事件,当转换到指定的状态时触发回调
  28. if (state == 2) {
  29. console.info('audio renderer state is: STATE_RUNNING');
  30. }
  31. });
  32. this.renderModel.on('markReach', 1000, (position) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调
  33. if (position == 1000) {
  34. console.info('ON Triggered successfully');
  35. }
  36. });
  37. } else {
  38. console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
  39. }
  40. });
  41. }
  42. // 开始一次音频渲染
  43. async start() {
  44. let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
  45. if (stateGroup.indexOf(this.renderModel.state) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染
  46. console.error(TAG + 'start failed');
  47. return;
  48. }
  49. await this.renderModel.start(); // 启动渲染
  50. const bufferSize = await this.renderModel.getBufferSize();
  51. let context = getContext(this);
  52. let path = context.filesDir;
  53. const filePath = path + '/test.wav'; // 使用沙箱路径获取文件,实际路径为/data/storage/el2/base/haps/entry/files/test.wav
  54. let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
  55. let stat = await fs.stat(filePath);
  56. let buf = new ArrayBuffer(bufferSize);
  57. let len = stat.size % bufferSize === 0 ? Math.floor(stat.size / bufferSize) : Math.floor(stat.size / bufferSize + 1);
  58. for (let i = 0; i < len; i++) {
  59. let options = {
  60. offset: i * bufferSize,
  61. length: bufferSize
  62. };
  63. let readsize = await fs.read(file.fd, buf, options);
  64. // buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染
  65. let writeSize = await new Promise((resolve, reject) => {
  66. this.renderModel.write(buf, (err, writeSize) => {
  67. if (err) {
  68. reject(err);
  69. } else {
  70. resolve(writeSize);
  71. }
  72. });
  73. });
  74. if (this.renderModel.state === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为released,停止渲染
  75. fs.close(file);
  76. await this.renderModel.stop();
  77. }
  78. if (this.renderModel.state === audio.AudioState.STATE_RUNNING) {
  79. if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染
  80. fs.close(file);
  81. await this.renderModel.stop();
  82. }
  83. }
  84. }
  85. }
  86. // 暂停渲染
  87. async pause() {
  88. // 只有渲染器状态为running的时候才能暂停
  89. if (this.renderModel.state !== audio.AudioState.STATE_RUNNING) {
  90. console.info('Renderer is not running');
  91. return;
  92. }
  93. await this.renderModel.pause(); // 暂停渲染
  94. if (this.renderModel.state === audio.AudioState.STATE_PAUSED) {
  95. console.info('Renderer is paused.');
  96. } else {
  97. console.error('Pausing renderer failed.');
  98. }
  99. }
  100. // 停止渲染
  101. async stop() {
  102. // 只有渲染器状态为running或paused的时候才可以停止
  103. if (this.renderModel.state !== audio.AudioState.STATE_RUNNING && this.renderModel.state !== audio.AudioState.STATE_PAUSED) {
  104. console.info('Renderer is not running or paused.');
  105. return;
  106. }
  107. await this.renderModel.stop(); // 停止渲染
  108. if (this.renderModel.state === audio.AudioState.STATE_STOPPED) {
  109. console.info('Renderer stopped.');
  110. } else {
  111. console.error('Stopping renderer failed.');
  112. }
  113. }
  114. // 销毁实例,释放资源
  115. async release() {
  116. // 渲染器状态不是released状态,才能release
  117. if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {
  118. console.info('Renderer already released');
  119. return;
  120. }
  121. await this.renderModel.release(); // 释放资源
  122. if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {
  123. console.info('Renderer released');
  124. } else {
  125. console.error('Renderer release failed.');
  126. }
  127. }
  128. }

简而言之,考虑到大部分音频播放的场景(如界面交互的提示音,录音播放等)无需对音频进行精细处理,所以通常更建议使用AVPlayer;但部分需要操作音频流的场景下(如变声器,智能降噪等)需要使用AudioRenderer来对这些需求提供支持。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/773973
推荐阅读
相关标签
  

闽ICP备14008679号