如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。

1、如何选择音频播放开发方式
在HarmonyOS系统中,多种API都提供了音频播放开发的支持,不同的API适用于不同音频数据格式、音频资源来源、音频使用场景,甚至是不同开发语言。因此,选择合适的音频播放API,有助于降低开发工作量,实现更佳的音频播放效果。
-
AVPlayer:功能较完善的音频、视频播放ArkTS/JS API,集成了流媒体和本地资源解析、媒体资源解封装、音频解码和音频输出功能。可以用于直接播放mp3、m4a等格式的音频文件,不支持直接播放PCM格式文件。
-
AudioRenderer:用于音频输出的的ArkTS/JS API,仅支持PCM格式,需要应用持续写入音频数据进行工作。应用可以在输入前添加数据预处理,如设定音频文件的采样率、位宽等,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体播放应用开发。
-
OpenSL ES:一套跨平台标准化的音频Native API,目前阶段唯一的音频类Native API,同样提供音频输出能力,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输出功能的播放应用使用。
-
在音频播放中,应用时常需要用到一些急促简短的音效,如相机快门音效、按键音效、游戏射击音效等,当前只能使用AVPlayer播放音频文件替代实现,在HarmonyOS后续版本将会推出相关接口来支持该场景。
2、使用AVPlayer开发音频播放功能
使用AVPlayer可以实现端到端播放原始媒体资源,本文将以完整地播放一首音乐作为示例,讲解AVPlayer音频播放相关功能。
播放的全流程包含:创建AVPlayer,设置播放资源,设置播放参数(音量/倍速/焦点模式),播放控制(播放/暂停/跳转/停止),重置,销毁资源。
在进行应用开发的过程中,我们可以通过AVPlayer的state属性主动获取当前状态或使用on('stateChange')方法监听状态变化。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。如下图所示:

当播放处于prepared / playing / paused / completed状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存。当客户端暂时不使用播放器时,调用reset()或release()回收内存资源,做好资源利用。
2.1、开发步骤及注意事项
-
创建实例createAVPlayer(),AVPlayer初始化idle状态。
-
设置业务需要的监听事件,搭配全流程场景使用。支持的监听事件如下:

3. 设置资源:设置属性url,AVPlayer进入initialized状态。
4. 准备播放:调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置音量。
5. 音频播控:播放play(),暂停pause(),跳转seek(),停止stop() 等操作。
6. (可选)更换资源:调用reset()重置资源,AVPlayer重新进入idle状态,允许更换资源url。
7. 退出播放:调用release()销毁实例,AVPlayer进入released状态,退出播放。
2.2、Demo
参考以下示例,完整地播放一首音乐。
import media from '@ohos.multimedia.media';import fs from '@ohos.file.fs';import common from '@ohos.app.ability.common';export class AVPlayerDemo {private avPlayer;private isSeek: boolean = true; // 用于区分模式是否支持seek操作private count: number = 0;// 注册avplayer回调函数setAVPlayerCallback() {// seek操作结果回调函数this.avPlayer.on('seekDone', (seekDoneTime) => {console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);})// error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程this.avPlayer.on('error', (err) => {console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);this.avPlayer.reset(); // 调用reset重置资源,触发idle状态})// 状态机变化回调函数this.avPlayer.on('stateChange', async (state, reason) => {switch (state) {case 'idle': // 成功调用reset接口后触发该状态机上报console.info('AVPlayer state idle called.');this.avPlayer.release(); // 调用release接口销毁实例对象break;case 'initialized': // avplayer 设置播放源后触发该状态上报console.info('AVPlayerstate initialized called.');this.avPlayer.prepare().then(() => {console.info('AVPlayer prepare succeeded.');}, (err) => {console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);});break;case 'prepared': // prepare调用成功后上报该状态机console.info('AVPlayer state prepared called.');this.avPlayer.play(); // 调用播放接口开始播放break;case 'playing': // play成功调用后触发该状态机上报console.info('AVPlayer state playing called.');if (this.count !== 0) {console.info('AVPlayer start to seek.');this.avPlayer.seek(this.avPlayer.duration); //seek到音频末尾} else {this.avPlayer.pause(); // 调用暂停接口暂停播放}this.count++;break;case 'paused': // pause成功调用后触发该状态机上报console.info('AVPlayer state paused called.');this.avPlayer.play(); // 再次播放接口开始播放break;case 'completed': // 播放结束后触发该状态机上报console.info('AVPlayer state completed called.');this.avPlayer.stop(); //调用播放结束接口break;case 'stopped': // stop接口成功调用后触发该状态机上报console.info('AVPlayer state stopped called.');this.avPlayer.reset(); // 调用reset接口初始化avplayer状态break;case 'released':console.info('AVPlayer state released called.');break;default:console.info('AVPlayer state unknown called.');break;}})}// 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过url属性进行播放示例async avPlayerUrlDemo() {// 创建avPlayer实例对象this.avPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback();let fdPath = 'fd://';// 通过UIAbilityContext获取沙箱地址filesDir,以下为Stage模型获方式,如需在FA模型上获取请参考《访问应用沙箱》获取地址let context = getContext(this) as common.UIAbilityContext;let pathDir = context.filesDir;let path = pathDir + '/01.mp3';// 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报let file = await fs.open(path);fdPath = fdPath + '' + file.fd;this.avPlayer.url = fdPath;}// 以下demo为使用资源管理接口获取打包在HAP内的媒体资源文件并通过fdSrc属性进行播放示例async avPlayerFdSrcDemo() {// 创建avPlayer实例对象this.avPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback();// 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址// 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度let context = getContext(this) as common.UIAbilityContext;let fileDescriptor = await context.resourceManager.getRawFd('01.mp3');// 为fdSrc赋值触发initialized状态机上报this.avPlayer.fdSrc = fileDescriptor;this.isSeek = false; // 不支持seek操作}}
3、使用AudioRenderer开发音频播放功能
AudioRenderer是音频渲染器,用于播放PCM(Pulse Code Modulation)音频数据,相比AVPlayer而言,可以在输入前添加数据预处理,更适合有音频开发经验的开发者,以实现更灵活的播放功能。
3.1、开发指导
使用AudioRenderer播放音频涉及到AudioRenderer实例的创建、音频渲染参数的配置、渲染的开始与停止、资源的释放等。本开发指导将以一次渲染音频数据的过程为例,向开发者讲解如何使用AudioRenderer进行音频渲染。
下图展示了AudioRenderer的状态变化,在创建实例后,调用对应的方法可以进入指定的状态实现对应的行为。需要注意的是在确定的状态执行不合适的方法可能导致AudioRenderer发生错误,建议开发者在调用状态转换的方法前进行状态检查,避免程序运行产生预期以外的结果。
为保证UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用callback函数。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()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。
3.2、开发步骤及注意事项
1. 配置音频渲染参数并创建AudioRenderer实例。
import audio from '@ohos.multimedia.audio';let audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,channels: audio.AudioChannel.CHANNEL_1,sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW};let audioRendererInfo = {content: audio.ContentType.CONTENT_TYPE_SPEECH,usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,rendererFlags: 0};let audioRendererOptions = {streamInfo: audioStreamInfo,rendererInfo: audioRendererInfo};audio.createAudioRenderer(audioRendererOptions, (err, data) => {if (err) {console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`);return;} else {console.info('Invoke createAudioRenderer succeeded.');let audioRenderer = data;}});
2. 调用start()方法进入running状态,开始渲染音频。
audioRenderer.start((err) => {if (err) {console.error(`Renderer start failed, code is ${err.code}, message is ${err.message}`);} else {console.info('Renderer start success.');}});
3. 指定待渲染文件地址,打开文件调用write()方法向缓冲区持续写入音频数据进行渲染播放。如果需要对音频数据进行处理以实现个性化的播放,在写入之前操作即可。
const bufferSize = await audioRenderer.getBufferSize();let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);let buf = new ArrayBuffer(bufferSize);let readsize = await fs.read(file.fd, buf);let writeSize = await new Promise((resolve, reject) => {audioRenderer.write(buf, (err, writeSize) => {if (err) {reject(err);} else {resolve(writeSize);}});});
4. 调用stop()方法停止渲染。
audioRenderer.stop((err) => {if (err) {console.error(`Renderer stop failed, code is ${err.code}, message is ${err.message}`);} else {console.info('Renderer stopped.');}});
5. 调用release()方法销毁实例,释放资源。
audioRenderer.release((err) => {if (err) {console.error(`Renderer release failed, code is ${err.code}, message is ${err.message}`);} else {console.info('Renderer released.');}});
3.3、Demo
下面展示了使用AudioRenderer渲染音频文件的示例代码。
import audio from '@ohos.multimedia.audio';import fs from '@ohos.file.fs';const TAG = 'AudioRendererDemo';export default class AudioRendererDemo {private renderModel = undefined;private audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率channels: audio.AudioChannel.CHANNEL_2, // 通道sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式}private audioRendererInfo = {content: audio.ContentType.CONTENT_TYPE_MUSIC, // 媒体类型usage: audio.StreamUsage.STREAM_USAGE_MEDIA, // 音频流使用类型rendererFlags: 0 // 音频渲染器标志}private audioRendererOptions = {streamInfo: this.audioStreamInfo,rendererInfo: this.audioRendererInfo}// 初始化,创建实例,设置监听事件init() {audio.createAudioRenderer(this.audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例if (!err) {console.info(`${TAG}: creating AudioRenderer success`);this.renderModel = renderer;this.renderModel.on('stateChange', (state) => { // 设置监听事件,当转换到指定的状态时触发回调if (state == 2) {console.info('audio renderer state is: STATE_RUNNING');}});this.renderModel.on('markReach', 1000, (position) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调if (position == 1000) {console.info('ON Triggered successfully');}});} else {console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);}});}// 开始一次音频渲染async start() {let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];if (stateGroup.indexOf(this.renderModel.state) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染console.error(TAG + 'start failed');return;}await this.renderModel.start(); // 启动渲染const bufferSize = await this.renderModel.getBufferSize();let context = getContext(this);let path = context.filesDir;const filePath = path + '/test.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 = {offset: i * bufferSize,length: bufferSize};let readsize = await fs.read(file.fd, buf, options);// buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染let writeSize = await new Promise((resolve, reject) => {this.renderModel.write(buf, (err, writeSize) => {if (err) {reject(err);} else {resolve(writeSize);}});});if (this.renderModel.state === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为released,停止渲染fs.close(file);await this.renderModel.stop();}if (this.renderModel.state === audio.AudioState.STATE_RUNNING) {if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染fs.close(file);await this.renderModel.stop();}}}}// 暂停渲染async pause() {// 只有渲染器状态为running的时候才能暂停if (this.renderModel.state !== audio.AudioState.STATE_RUNNING) {console.info('Renderer is not running');return;}await this.renderModel.pause(); // 暂停渲染if (this.renderModel.state === audio.AudioState.STATE_PAUSED) {console.info('Renderer is paused.');} else {console.error('Pausing renderer failed.');}}// 停止渲染async stop() {// 只有渲染器状态为running或paused的时候才可以停止if (this.renderModel.state !== audio.AudioState.STATE_RUNNING && this.renderModel.state !== audio.AudioState.STATE_PAUSED) {console.info('Renderer is not running or paused.');return;}await this.renderModel.stop(); // 停止渲染if (this.renderModel.state === audio.AudioState.STATE_STOPPED) {console.info('Renderer stopped.');} else {console.error('Stopping renderer failed.');}}// 销毁实例,释放资源async release() {// 渲染器状态不是released状态,才能releaseif (this.renderModel.state === audio.AudioState.STATE_RELEASED) {console.info('Renderer already released');return;}await this.renderModel.release(); // 释放资源if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {console.info('Renderer released');} else {console.error('Renderer release failed.');}}}
当同优先级或高优先级音频流要使用输出设备时,当前音频流会被中断,应用可以自行响应中断事件并做出处理。
4、使用OpenSL ES开发音频播放功能
OpenSL ES全称为Open Sound Library for Embedded Systems,是一个嵌入式、跨平台、免费的音频处理库。为嵌入式移动多媒体设备上的应用开发者提供标准化、高性能、低延迟的API。HarmonyOS的Native API基于Khronos Group开发的OpenSL ES 1.0.1 API 规范实现,开发者可以通过和在HarmonyOS上使用相关API。
4.1、HarmonyOS上的OpenSL ES
OpenSL ES中提供了以下的接口,HarmonyOS当前仅实现了部分OpenSL ES接口,可以实现音频播放的基础功能。
调用未实现接口后会返回SL_RESULT_FEATURE_UNSUPPORTED,当前没有相关扩展可以使用。
以下列表列举了HarmonyOS上已实现的OpenSL ES的接口,具体说明请参考OpenSL ES规范:
-
HarmonyOS上支持的Engine接口:
-
SLresult (*CreateAudioPlayer) (SLEngineItf self, SLObjectItf * pPlayer, SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)
-
SLresult (*CreateAudioRecorder) (SLEngineItf self, SLObjectItf * pRecorder, SLDataSource *pAudioSrc, SLDataSink *pAudioSnk, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)
-
SLresult (*CreateOutputMix) (SLEngineItf self, SLObjectItf * pMix, SLuint32 numInterfaces, const SLInterfaceID * pInterfaceIds, const SLboolean * pInterfaceRequired)
-
-
HarmonyOS上支持的Object接口:
-
SLresult (*Realize) (SLObjectItf self, SLboolean async)
-
SLresult (*GetState) (SLObjectItf self, SLuint32 * pState)
-
SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void * pInterface)
-
void (*Destroy) (SLObjectItf self)
-
-
HarmonyOS上支持的Playback接口:
-
SLresult (*SetPlayState) (SLPlayItf self, SLuint32 state)
-
SLresult (*GetPlayState) (SLPlayItf self, SLuint32 *pState)
-
-
HarmonyOS上支持的Volume控制接口:
-
SLresult (*SetVolumeLevel) (SLVolumeItf self, SLmillibel level)
-
SLresult (*GetVolumeLevel) (SLVolumeItf self, SLmillibel *pLevel)
-
SLresult (*GetMaxVolumeLevel) (SLVolumeItf self, SLmillibel *pMaxLevel)
-
-
HarmonyOS上支持的BufferQueue接口:
以下接口需引入<OpenSLES_OpenHarmony.h>使用。

4.2、Demo
【来自官方API介绍】
-
添加头文件。
#include "SLES/OpenSLES.h"#include "SLES/OpenSLES_OpenHarmony.h"#include "SLES/OpenSLES_Platform.h"
2. 使用slCreateEngine接口和获取engine实例。
SLObjectItf engineObject = nullptr;slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
3. 获取接口SL_IID_ENGINE的engineEngine实例。
SLEngineItf engineEngine = nullptr;(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
4. 配置播放器信息,创建AudioPlayer。
SLDataLocator_BufferQueue slBufferQueue = {SL_DATALOCATOR_BUFFERQUEUE,0};// 具体参数需要根据音频文件格式进行适配SLDataFormat_PCM pcmFormat = {SL_DATAFORMAT_PCM,2, // 通道数SL_SAMPLINGRATE_48, // 采样率SL_PCMSAMPLEFORMAT_FIXED_16, // 音频采样格式0,0,0};SLDataSource slSource = {&slBufferQueue, &pcmFormat};SLObjectItf pcmPlayerObject = nullptr;(*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slSource, nullptr, 0, nullptr, nullptr);(*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);
5. 获取接口SL_IID_OH_BUFFERQUEUE的bufferQueueItf实例。
SLOHBufferQueueItf bufferQueueItf;(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_OH_BUFFERQUEUE, &bufferQueueItf);
6. 打开音频文件,注册BufferQueueCallback回调。
static void BufferQueueCallback (SLOHBufferQueueItf bufferQueueItf, void *pContext, SLuint32 size){SLuint8 *buffer = nullptr;SLuint32 pSize;(*bufferQueueItf)->GetBuffer(bufferQueueItf, &buffer, &pSize);// 将待播放音频数据写入buffer(*bufferQueueItf)->Enqueue(bufferQueueItf, buffer, size);}void *pContext; // 可传入自定义的上下文信息,会在Callback内收到(*bufferQueueItf)->RegisterCallback(bufferQueueItf, BufferQueueCallback, pContext);
7. 获取接口SL_PLAYSTATE_PLAYING的playItf实例,开始播放。
SLPlayItf playItf = nullptr;(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &playItf);(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
8. 结束音频播放。
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);(*pcmPlayerObject)->Destroy(pcmPlayerObject);(*engineObject)->Destroy(engineObject);

















![re题(20)BUUCTF [GWCTF 2019]pyre](https://i-blog.csdnimg.cn/blog_migrate/bfc0a0cab9ff38a89faef03f17ec8332.png)

