【鸿蒙实战开发】HarmonyOS音频播放

张春  金牌会员 | 2024-8-28 11:42:27 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 570|帖子 570|积分 1710

音频播放开发概述

如何选择音频播放开发方式

系统提供了多样化的API,来帮助开发者完成音频播放的开发,不同的API适用于不同音频数据格式、音频资源来源、音频利用场景,甚至是不同开发语言。因此,选择符合的音频播放API,有助于降低开发工作量,实现更佳的音频播放效果。


  • AudioRenderer:用于音频输出的ArkTS/JS API,仅支持PCM格式,需要应用持续写入音频数据进行工作。应用可以在输入前添加数据预处置处罚,如设定音频文件的采样率、位宽等,要求开发者具备音频处置处罚的基础知识,适用于更专业、更多样化的媒体播放应用开发。
  • AudioHaptic:用于音振协同播放的ArkTS/JS API,适用于需要在播放音频时同步发起振动的场景,如来电铃声随振、键盘按键反馈、消息通知反馈等。
  • OpenSL ES:一套跨平台尺度化的音频Native API,同样提供音频输出本领,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输出功能的播放应用利用。
  • OHAudio:用于音频输出的Native API,此API在设计上实现归一,同时支持普通音频通路和低时延通路。仅支持PCM格式,适用于依赖Native层实现音频输出功能的场景。
除上述方式外,也可以通过Media Kit实现音频播放。


  • AVPlayer:用于音频播放的ArkTS/JS API,集成了流媒体和本地资源剖析、媒体资源解封装、音频解码和音频输出功能。可用于直接播放mp3、m4a等格式的音频文件,不支持直接播放PCM格式文件。
  • SoundPool:低时延的短音播放ArkTS/JS API,适用于播放急促简短的音效,如相机快门音效、按键音效、游戏射击音效等。
开发音频播放应用须知

应用如果要实现后台播放或熄屏播放,需要同时满足:

  • 利用媒领会话功能注册到系统内同一管理,否则在应用进入后台时,播放将被强制停止。具体参考AVSession Kit开发指导。
  • 申请长时任务制止进入挂起(Suspend)状态。具体参考长时任务开发指导。
当应用进入后台,播放被停止,如果被媒领会话管控,将打印日记“pause id”;如果没有该日记,则阐明被长时任务管控。
利用AudioRenderer开发音频播放功能

AudioRenderer是音频渲染器,用于播放PCM(Pulse Code Modulation)音频数据,相比AVPlayer而言,可以在输入前添加数据预处置处罚,更适合有音频开发履历的开发者,以实现更灵活的播放功能。
开发指导

利用AudioRenderer播放音频涉及到AudioRenderer实例的创建、音频渲染参数的配置、渲染的开始与停止、资源的释放等。本开发指导将以一次渲染音频数据的过程为例,向开发者讲解如何利用AudioRenderer进行音频渲染,发起搭配AudioRenderer的API阐明阅读。
下图展示了AudioRenderer的状态变化,在创建实例后,调用对应的方法可以进入指定的状态实现对应的举动。需要注意的是在确定的状态实行不符合的方法可能导致AudioRenderer发生错误,发起开发者在调用状态转换的方法前进行状态检查,制止程序运行产生预期以外的效果。
为包管UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用callback函数。
图1 AudioRenderer状态变化表示图

在进行应用开发的过程中,发起开发者通过on(‘stateChange’)方法订阅AudioRenderer的状态变更。因为针对AudioRenderer的某些操纵,仅在音频播放器在固定状态时才气实行。如果应用在音频播放器处于错误状态时实行操纵,系统可能会抛出异常或生成其他未定义的举动。


  • prepared状态: 通过调用createAudioRenderer()方法进入到该状态。
  • running状态: 正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在paused状态和stopped状态通过调用start()方法进入此状态。
  • paused状态: 在running状态可以通过调用pause()方法停息音频数据的播放并进入paused状态,停息播放之后可以通过调用start()方法继续音频数据播放。
  • stopped状态: 在paused/running状态可以通过stop()方法停止音频数据的播放。
  • released状态: 在prepared、paused、stopped等状态,用户均可通过release()方法释放掉全部占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。
开发步骤及注意事项


  • 配置音频渲染参数并创建AudioRenderer实例,音频渲染参数的具体信息可以检察AudioRendererOptions。
  1. import { audio } from '@kit.AudioKit';
  2. let audioStreamInfo: audio.AudioStreamInfo = {
  3.   samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  4.   channels: audio.AudioChannel.CHANNEL_2, // 通道
  5.   sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  6.   encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
  7. };
  8. let audioRendererInfo: audio.AudioRendererInfo = {
  9.   usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
  10.   rendererFlags: 0
  11. };
  12. let audioRendererOptions: audio.AudioRendererOptions = {
  13.   streamInfo: audioStreamInfo,
  14.   rendererInfo: audioRendererInfo
  15. };
  16. audio.createAudioRenderer(audioRendererOptions, (err, data) => {
  17.   if (err) {
  18.     console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`);
  19.     return;
  20.   } else {
  21.     console.info('Invoke createAudioRenderer succeeded.');
  22.     let audioRenderer = data;
  23.   }
  24. });
复制代码

  • 调用on(‘writeData’)方法,订阅监听音频数据写入回调。
  1. import { BusinessError } from '@kit.BasicServicesKit';
  2. import { fileIo } from '@kit.CoreFileKit';
  3. let bufferSize: number = 0;
  4. class Options {
  5.   offset?: number;
  6.   length?: number;
  7. }
  8. let path = getContext().cacheDir;
  9. //确保该路径下存在该资源
  10. let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
  11. let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
  12. let writeDataCallback = (buffer: ArrayBuffer) => {
  13.   
  14.   let options: Options = {
  15.     offset: bufferSize,
  16.     length: buffer.byteLength
  17.   }
  18.   fileIo.readSync(file.fd, buffer, options);
  19.   bufferSize += buffer.byteLength;
  20. }
  21. audioRenderer.on('writeData', writeDataCallback);
复制代码

  • 调用start()方法进入running状态,开始渲染音频。
  1. import { BusinessError } from '@kit.BasicServicesKit';
  2. audioRenderer.start((err: BusinessError) => {
  3.   if (err) {
  4.     console.error(`Renderer start failed, code is ${err.code}, message is ${err.message}`);
  5.   } else {
  6.     console.info('Renderer start success.');
  7.   }
  8. });
复制代码

  • 调用stop()方法停止渲染。
  1. import { BusinessError } from '@kit.BasicServicesKit';
  2. audioRenderer.stop((err: BusinessError) => {
  3.   if (err) {
  4.     console.error(`Renderer stop failed, code is ${err.code}, message is ${err.message}`);
  5.   } else {
  6.     console.info('Renderer stopped.');
  7.   }
  8. });
复制代码

  • 调用release()方法烧毁实例,释放资源。
  1. import { BusinessError } from '@kit.BasicServicesKit';
  2. audioRenderer.release((err: BusinessError) => {
  3.   if (err) {
  4.     console.error(`Renderer release failed, code is ${err.code}, message is ${err.message}`);
  5.   } else {
  6.     console.info('Renderer released.');
  7.   }
  8. });
复制代码
选择精确的StreamUsage

创建播放器时候,开发者需要根据应用场景指定播放器的StreamUsage,选择精确的StreamUsage可以制止用户碰到不符合预期的举动。
在音频API文档StreamUsage介绍中,列举了每一种类型推荐的应用场景。比方音乐场景推荐利用STREAM_USAGE_MUSIC,电影或者视频场景推荐利用STREAM_USAGE_MOVIE,游戏场景推荐利用STREAM_USAGE_GAME,等等。
如果开发者配置了不精确的StreamUsage,可能带来一些不符合预期的举动。比方以下场景。


  • 游戏场景错误利用STREAM_USAGE_MUSIC类型,游戏应用将无法和其他音乐应用并发播放,而游戏场景通常可以与其他音乐应用并发播放。
  • 导航场景错误利用STREAM_USAGE_MUSIC类型,导航应用播报时候会导致正在播放的音乐停止播放,而导航场景我们通常期望正在播放的音乐仅仅降低音量播放。
完整示例

下面展示了利用AudioRenderer渲染音频文件的示例代码。
  1. import { audio } from '@kit.AudioKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { fileIo } from '@kit.CoreFileKit';
  4. const TAG = 'AudioRendererDemo';
  5. class Options {
  6.   offset?: number;
  7.   length?: number;
  8. }
  9. let context = getContext(this);
  10. let bufferSize: number = 0;
  11. let renderModel: audio.AudioRenderer | undefined = undefined;
  12. let audioStreamInfo: audio.AudioStreamInfo = {
  13.   samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  14.   channels: audio.AudioChannel.CHANNEL_2, // 通道
  15.   sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  16.   encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
  17. }
  18. let audioRendererInfo: audio.AudioRendererInfo = {
  19.   usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型
  20.   rendererFlags: 0 // 音频渲染器标志
  21. }
  22. let audioRendererOptions: audio.AudioRendererOptions = {
  23.   streamInfo: audioStreamInfo,
  24.   rendererInfo: audioRendererInfo
  25. }
  26. let path = getContext().cacheDir;
  27. //确保该路径下存在该资源
  28. let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
  29. let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
  30. let writeDataCallback = (buffer: ArrayBuffer) => {
  31.   let options: Options = {
  32.     offset: bufferSize,
  33.     length: buffer.byteLength
  34.   }
  35.   fileIo.readSync(file.fd, buffer, options);
  36.    bufferSize += buffer.byteLength;
  37. }
  38. // 初始化,创建实例,设置监听事件
  39. function init() {
  40.   audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例
  41.     if (!err) {
  42.       console.info(`${TAG}: creating AudioRenderer success`);
  43.       renderModel = renderer;
  44.       if (renderModel !== undefined) {
  45.         (renderModel as audio.AudioRenderer).on('writeData', writeDataCallback);
  46.       }
  47.     } else {
  48.       console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
  49.     }
  50.   });
  51. }
  52. // 开始一次音频渲染
  53. function start() {
  54.   if (renderModel !== undefined) {
  55.     let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
  56.     if (stateGroup.indexOf((renderModel as audio.AudioRenderer).state.valueOf()) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染
  57.       console.error(TAG + 'start failed');
  58.       return;
  59.     }
  60.     // 启动渲染
  61.     (renderModel as audio.AudioRenderer).start((err: BusinessError) => {
  62.       if (err) {
  63.         console.error('Renderer start failed.');
  64.       } else {
  65.         console.info('Renderer start success.');
  66.       }
  67.     });
  68.   }
  69. }
  70. // 暂停渲染
  71. function pause() {
  72.   if (renderModel !== undefined) {
  73.     // 只有渲染器状态为running的时候才能暂停
  74.     if ((renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING) {
  75.       console.info('Renderer is not running');
  76.       return;
  77.     }
  78.     // 暂停渲染
  79.     (renderModel as audio.AudioRenderer).pause((err: BusinessError) => {
  80.       if (err) {
  81.         console.error('Renderer pause failed.');
  82.       } else {
  83.         console.info('Renderer pause success.');
  84.       }
  85.     });
  86.   }
  87. }
  88. // 停止渲染
  89. async function stop() {
  90.   if (renderModel !== undefined) {
  91.     // 只有渲染器状态为running或paused的时候才可以停止
  92.     if ((renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING && (renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_PAUSED) {
  93.       console.info('Renderer is not running or paused.');
  94.       return;
  95.     }
  96.     // 停止渲染
  97.     (renderModel as audio.AudioRenderer).stop((err: BusinessError) => {
  98.       if (err) {
  99.         console.error('Renderer stop failed.');
  100.       } else {
  101.         fileIo.close(file);
  102.         console.info('Renderer stop success.');
  103.       }
  104.     });
  105.   }
  106. }
  107. // 销毁实例,释放资源
  108. async function release() {
  109.   if (renderModel !== undefined) {
  110.     // 渲染器状态不是released状态,才能release
  111.     if (renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
  112.       console.info('Renderer already released');
  113.       return;
  114.     }
  115.     // 释放资源
  116.     (renderModel as audio.AudioRenderer).release((err: BusinessError) => {
  117.       if (err) {
  118.         console.error('Renderer release failed.');
  119.       } else {
  120.         console.info('Renderer release success.');
  121.       }
  122.     });
  123.   }
  124. }
复制代码
当同优先级或高优先级音频流要利用输出设备时,当前音频流会被停止,应用可以自行相应停止事件并做出处置处罚。具体的音频并发处置处罚方式可参考处置处罚音频焦点事件
利用OHAudio开发音频播放功能(C/C++)

OHAudio是系统在API version 10中引入的一套C API,此API在设计上实现归一,同时支持普通音频通路和低时延通路。仅支持PCM格式,适用于依赖Native层实现音频输出功能的场景。
利用入门

开发者要利用OHAudio提供的播放或者录制本领,需要添加对应的头文件。
在 CMake 脚本中链接动态库

  1. target_link_libraries(sample PUBLIC libohaudio.so)
复制代码
添加头文件

开发者通过引入<native_audiostreambuilder.h>头文件,利用音频播放相干API。
  1. #include <ohaudio/native_audiorenderer.h>
  2. #include <ohaudio/native_audiostreambuilder.h>
复制代码
音频流构造器

OHAudio提供OH_AudioStreamBuilder接口,遵循构造器设计模式,用于构建音频流。开发者需要根据业务场景,指定对应的OH_AudioStream_Type。
OH_AudioStream_Type包含两种类型:


  • AUDIOSTREAM_TYPE_RENDERER
  • AUDIOSTREAM_TYPE_CAPTURER
利用OH_AudioStreamBuilder_Create创建构造器示例:
  1. OH_AudioStreamBuilder* builder;
  2. OH_AudioStreamBuilder_Create(&builder, streamType);
复制代码
在音频业务结束之后,开发者应该实行OH_AudioStreamBuilder_Destroy接口来烧毁构造器。
  1. OH_AudioStreamBuilder_Destroy(builder);
复制代码
开发步骤及注意事项

具体的API阐明请参考OHAudio API参考。
开发者可以通过以下几个步骤来实现一个简单的播放功能。

  • 创建构造器
  1.   OH_AudioStreamBuilder* builder;
  2. OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
复制代码

  • 配置音频流参数
    创建音频播放构造器后,可以设置音频流所需要的参数,可以参考下面的案例。
  1. // 设置音频采样率
  2. OH_AudioStreamBuilder_SetSamplingRate(builder, 48000);
  3. // 设置音频声道
  4. OH_AudioStreamBuilder_SetChannelCount(builder, 2);
  5. // 设置音频采样格式
  6. OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
  7. // 设置音频流的编码类型
  8. OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
  9. // 设置输出音频流的工作场景
  10. OH_AudioStreamBuilder_SetRendererInfo(builder, AUDIOSTREAM_USAGE_MUSIC);
复制代码
  1. 注意,播放的音频数据要通过回调接口写入,开发者要实现回调接口,使用OH_AudioStreamBuilder_SetRendererCallback设置回调函数。回调函数的声明请查看[OH_AudioRenderer_Callbacks](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/_o_h_audio-V5#oh_audiorenderer_callbacks) 。
复制代码

  • 设置音频回调函数
    多音频并发处置处罚可参考文档处置处罚音频焦点事件,仅接口语言差异。
  1. // 自定义写入数据函数
  2. int32_t MyOnWriteData(
  3.     OH_AudioRenderer* renderer,
  4.     void* userData,
  5.     void* buffer,
  6.     int32_t length)
  7. {
  8.     // 将待播放的数据,按length长度写入buffer
  9.     return 0;
  10. }
  11. // 自定义音频流事件函数
  12. int32_t MyOnStreamEvent(
  13.     OH_AudioRenderer* renderer,
  14.     void* userData,
  15.     OH_AudioStream_Event event)
  16. {
  17.     // 根据event表示的音频流事件信息,更新播放器状态和界面
  18.     return 0;
  19. }
  20. // 自定义音频中断事件函数
  21. int32_t MyOnInterruptEvent(
  22.     OH_AudioRenderer* renderer,
  23.     void* userData,
  24.     OH_AudioInterrupt_ForceType type,
  25.     OH_AudioInterrupt_Hint hint)
  26. {
  27.     // 根据type和hint表示的音频中断信息,更新播放器状态和界面
  28.     return 0;
  29. }
  30. // 自定义异常回调函数
  31. int32_t MyOnError(
  32.     OH_AudioRenderer* renderer,
  33.     void* userData,
  34.     OH_AudioStream_Result error)
  35. {
  36.     // 根据error表示的音频异常信息,做出相应的处理
  37.     return 0;
  38. }
  39. OH_AudioRenderer_Callbacks callbacks;
  40. // 配置回调函数
  41. callbacks.OH_AudioRenderer_OnWriteData = MyOnWriteData;
  42. callbacks.OH_AudioRenderer_OnStreamEvent = MyOnStreamEvent;
  43. callbacks.OH_AudioRenderer_OnInterruptEvent = MyOnInterruptEvent;
  44. callbacks.OH_AudioRenderer_OnError = MyOnError;
  45. //设置输出音频流的回调
  46. OH_AudioStreamBuilder_SetRendererCallback(builder, callbacks, nullptr);
复制代码
  1. 为了避免不可预期的行为,在设置音频回调函数时,请确保OH_AudioRenderer_Callbacks的每一个回调都被**自定义的回调方法**或**空指针**初始化。
复制代码
  1. // 自定义写入数据函数
  2. int32_t MyOnWriteData(
  3.     OH_AudioRenderer* renderer,
  4.     void* userData,
  5.     void* buffer,
  6.     int32_t length)
  7. {
  8.     // 将待播放的数据,按length长度写入buffer
  9.     return 0;
  10. }
  11. // 自定义音频中断事件函数
  12. int32_t MyOnInterruptEvent(
  13.     OH_AudioRenderer* renderer,
  14.     void* userData,
  15.     OH_AudioInterrupt_ForceType type,
  16.     OH_AudioInterrupt_Hint hint)
  17. {
  18.     // 根据type和hint表示的音频中断信息,更新播放器状态和界面
  19.     return 0;
  20. }
  21. OH_AudioRenderer_Callbacks callbacks;
  22. // 配置回调函数,如果需要监听,则赋值
  23. callbacks.OH_AudioRenderer_OnWriteData = MyOnWriteData;
  24. callbacks.OH_AudioRenderer_OnInterruptEvent = MyOnInterruptEvent;
  25. // (必选)如果不需要监听,使用空指针初始化
  26. callbacks.OH_AudioRenderer_OnStreamEvent = nullptr;
  27. callbacks.OH_AudioRenderer_OnError = nullptr;
复制代码

  • 构造播放音频流
  1. OH_AudioRenderer* audioRenderer;
  2. OH_AudioStreamBuilder_GenerateRenderer(builder, &audioRenderer);
复制代码

  • 利用音频流
    音频流包含下面接口,用来实现对音频流的控制。
       接口阐明OH_AudioStream_Result OH_AudioRenderer_Start(OH_AudioRenderer* renderer)开始播放OH_AudioStream_Result OH_AudioRenderer_Pause(OH_AudioRenderer* renderer)停息播放OH_AudioStream_Result OH_AudioRenderer_Stop(OH_AudioRenderer* renderer)停止播放OH_AudioStream_Result OH_AudioRenderer_Flush(OH_AudioRenderer* renderer)释放缓存数据OH_AudioStream_Result OH_AudioRenderer_Release(OH_AudioRenderer* renderer)释放播放实例
  • 释放构造器
    构造器不再利用时,需要释放相干资源。
  1. OH_AudioStreamBuilder_Destroy(builder);
复制代码
设置低时延模式

当设备支持低时延通路时,开发者可以利用低时延模式创建播放器,获得更高质量的音频体验。
开发流程与普通播放场景同等,仅需要在创建音频流构造器时,调用OH_AudioStreamBuilder_SetLatencyMode()设置低时延模式。
开发示例
  1. OH_AudioStreamBuilder_SetChannelLayout(builder, CH_LAYOUT_STEREO);
复制代码
设置音频声道布局

播放音频文件时,可以通过设置音频的声道布局信息,指定渲染或播放时的扬声器摆位,使得渲染和播放效果更佳,获得更高质量的音频体验。
开发流程与普通播放场景同等,仅需要在创建音频流构造器时,调用OH_AudioStreamBuilder_SetChannelLayout()设置声道布局信息。
当声道布局与声道数不匹配时,创建音频流会失败。发起在设置声道布局时,确认下发的声道布局信息是精确的。
如果不知道准确的声道布局信息,或者开发者需要利用默认声道布局,可以不调用设置声道布局接口,或者下发CH_LAYOUT_UNKNOWN,以利用基于声道数的默认声道布局。
对于HOA格式的音频,想要获得精确的渲染和播放效果,必须指定声道布局信息。
开发示例
  1. OH_AudioStreamBuilder_SetChannelLayout(builder, CH_LAYOUT_STEREO);
复制代码
播放AudioVivid格式音源

播放AudioVivid格式音频文件时,需要利用与普通播放不同的数据写入回调函数,该回调可以同时写入PCM数据与元数据。
开发流程与普通播放场景同等,仅需要在创建音频流构造器时,调用OH_AudioStreamBuilder_SetWriteDataWithMetadataCallback()设置PCM数据与元数据同时写入的回调函数,同时调用OH_AudioStreamBuilder_SetEncodingType()设置编码类型为AUDIOSTREAM_ENCODING_TYPE_AUDIOVIVID。
在播放AudioVivid时,帧长是固定的,不可通过OH_AudioStreamBuilder_SetFrameSizeInCallback()设置回调帧长。同时,在设置播放声道数和声道布局时,需要将写入音源的声床数和对象数相加后进行设置。
开发示例
  1. // 自定义同时写入PCM数据和元数据函数
  2. int32_t MyOnWriteDataWithMetadata(
  3.     OH_AudioRenderer* renderer,
  4.     void* userData,
  5.     void* audioData,
  6.     int32_t audioDataSize,
  7.     void* metadata,
  8.     int32_t metadataSize)
  9. {
  10.     // 将待播放的PCM数据和元数据,分别按audioDataSize和metadataSize写入buffer
  11.     return 0;
  12. }
  13. // 设置编码类型
  14. OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_AUDIOVIVID);
  15. // 配置回调函数
  16. OH_AudioRenderer_WriteDataWithMetadataCallback metadataCallback = MyOnWriteDataWithMetadata;
  17. // 设置同时写入PCM数据和元数据的回调
  18. OH_AudioStreamBuilder_SetWriteDataWithMetadataCallback(builder, metadataCallback, nullptr);
复制代码
写在末了

有很多小同伴不知道学习哪些鸿蒙开发技能?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频仍踩坑,终极浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习黑白常有须要的。

盼望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小同伴自行领取,限时开源,先到先得~无套路领取!!
如果你是一名有履历的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行职员,可以直接领取这份资料
请点击→纯血版全套鸿蒙HarmonyOS学习文档
鸿蒙(HarmonyOS NEXT)5.0最新学习路线


路线图适合人群:

IT开发职员:想要拓展职业边界
零基础小白:鸿蒙爱好者,盼望从0到1学习,增长一项技能。
技能提拔/进阶跳槽:发展瓶颈期,提拔职场竞争力,快速掌握鸿蒙技能
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

《鸿蒙 (HarmonyOS)开发入门教学视频》


《鸿蒙生态应用开发V3.0白皮书》


《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……

《鸿蒙开发进阶》

●Stage模子入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技能
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……

获取以上完整鸿蒙HarmonyOS学习文档,请点击→纯血版全套鸿蒙HarmonyOS学习文档



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张春

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表