HarmonyOS 鸿蒙Next系统如何进行pcm流式播放
HarmonyOS 鸿蒙Next系统如何进行pcm流式播放 我现在需要实现这样一个音频播放功能:
应用内会从服务端下载音频数据,数据是pcm格式的,流式持续下载,现在需要把这些数据在鸿蒙设备上 直接播放出来,搜到了指导说 AudioRenderer接口跟OHAudio接口都有一个回调模式可以实现,有没有用过的,这两接口使用上区别大吗、回调模式有啥需要注意的?
AudioRenderer是ArkTS API, 集成简单,支持后台播放,可播控中心播放。
OHAudio是C/C++ API, 开发复杂些,但性能高,延迟低,会C/C++可选。
两者编码实现区别大,但逻辑一样。
推荐用AudioRenderer,简单。实现步骤和注意的地方:
-
从服务端下载的PCM数据,存储可以选择存入文件或缓存,注意若存缓存,注意控制溢出。 使用NetworkKit的http模块的requestInStream请求后,on(‘dataReceive’)接收数据存入文件流或缓存,on(‘dataEnd’)处理下载结束。参考:《ohos.net.http》。
-
AudioRenderer从下载的缓存里或文件里加载PCM数据,注意加载PCM数据延迟处理。 使用AudioRenderer的on(‘writeData’)加载PCM数据,系统会根据各种播放状态和情况调用on(‘writeData’)的回调。注意正确处理数据写入的偏移量和文件结束EOF的情况。当所有音频数据写入完毕后,应通过返回
INVALID等方式告知系统,或参考《判断播放结束》。参考:《AudioRenderer》
参考文档的示例代码基本上就可以解决你的需求了。如果有问题可以再问。
希望帮到你。如需OHAudio,可以再留言。有用给个采纳哈。😊
更多关于HarmonyOS 鸿蒙Next系统如何进行pcm流式播放的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
可以做,而且**你这个“服务端持续下载 PCM,边下边播”**的场景,AudioRenderer 和 OHAudio 都能实现,核心模式都是:
- 播放端注册“写数据回调”
- 系统需要音频帧时回调你
- 你从本地缓冲队列里取 PCM 数据填进去
- 数据够就返回有效,不够就先别让它播这次,等下次回调
官方 FAQ 也明确把 PCM 直接播放列成两条路:
- 用 AudioRenderer 直接播 PCM
- 用 OHAudio 直接播 PCM
参考这篇 FAQ:如何播放 PCM 格式的音频数据
先说选型结论
如果你的播放逻辑主要写在 ArkTS / 应用层
优先用 AudioRenderer
原因:
- 接 ArkTS 业务更直接
- 状态机、回调、生命周期都更顺手
- 对“普通流式音频播放”已经够用
- 你这个“网络下载 PCM -> 播放”场景,用它完全合理
如果你本身就有 Native/C++ 音频链路,或者很在意低时延
优先用 OHAudio
原因:
- 华为现在对 C/C++ 播放明确写的是“推荐使用 OHAudio”
- 它同时覆盖普通通路和低时延通路
- 更适合 Native 音频引擎、实时音频、游戏/K歌这类场景
- 能直接配置低时延模式、frame size、native 回调
两者使用上区别大吗
概念上不大,工程形态上挺大。
相同点
两者都适合 PCM 播放,都有“回调喂数”的方式:
AudioRenderer:on('writeData', ...)OHAudio:OH_AudioStreamBuilder_SetRendererWriteDataCallback(...)
两者都要求你:
- 事先配置 PCM 参数
- 采样率
- 声道数
- 采样格式
- 编码类型 RAW
- 在回调里快速填满系统给的 buffer
- 避免回调线程里做耗时操作
- 正确处理“数据不足”“最后一帧不满”“中断/停止/释放”
不同点
AudioRenderer
更偏 ArkTS 应用层 API:
- 上手更简单
- 和页面、Ability 生命周期衔接自然
- Promise/Callback 风格统一
- 更适合“应用业务主导”的播放器
OHAudio
更偏 Native C API:
- 要自己处理 builder、renderer、回调函数、CMake 链接
- 更适合已有 C/C++ 音频模块
- 可直接走普通模式或低时延模式
- 对回调线程约束更硬,控制更细
你的场景推荐怎么选
你说的是:
- 服务端持续下载
- PCM 原始音频
- 边下边播
- 没强调极低时延,只是希望稳定流式播放
我建议优先用 AudioRenderer。
因为这是个典型的“应用层流式播放”需求,不是游戏/K歌那种强实时音频。
除非你已经有 Native 解码/音频引擎,或者后面还要做回声消除、低时延、混音链路,否则没必要先上 OHAudio。
回调模式最需要注意什么
这个才是重点。无论你选哪套接口,真正容易踩坑的是这里。
1. 回调里不要直接等网络
千万不要在音频回调里等待网络下载。
正确做法是两线程模型:
- 下载线程:持续从服务端收 PCM,写入本地环形缓冲区/RingBuffer
- 播放回调线程:只负责从 RingBuffer 取数据并拷贝到系统 buffer
也就是:
网络生产者 -> 本地队列 -> 音频回调消费者
如果回调里直接等网络包:
- 很容易超时
- 产生爆音、卡顿、断续
2. 一定要做“预缓冲”
不要一拿到第一包就立刻 start()。
建议先缓存一小段再开播,比如:
- 100ms
- 200ms
- 300ms
这样网络有轻微抖动时,不会立刻破音。
这是流式 PCM 播放最实用的一点。
3. PCM 参数必须完全匹配
服务端下发的 PCM 格式,必须和播放器配置一致:
- 采样率:比如
16000/24000/48000 - 声道数:单声道还是双声道
- 采样格式:常见
S16LE - 编码类型:
RAW
只要有一个不匹配,就会出现:
- 播放速度不对
- 声音发尖/发闷
- 噪音
- 失真
AudioRenderer 回调特别注意
官方文档对 AudioRenderer 的 writeData 说得很明确:
- 能填满本次回调要求的长度,才返回
VALID - 没填满时不要返回
VALID - 否则容易出现杂音、卡顿
- 最后一帧不足时,要自己补静音数据把 buffer 填满
也就是说你不能“有多少写多少,然后告诉系统可以播”。
这点非常关键。
另外文档还建议:
- 不要在主线程注册回调
- 不要在回调里做耗时业务
- 不要等 UI、文件、网络操作
参考官方文档:
使用 AudioRenderer 开发音频播放功能
OHAudio 回调特别注意
OHAudio 的要求更硬一些:
- 回调里禁止做耗时操作
- 不要在回调里调用流控制接口
StartPauseStopFlushRelease
官方在低时延文档里明确写了,回调线程要和流控制逻辑分离,不然很容易出问题。
参考:
低时延音频播放
此外 OHAudio 从 API 12 开始也推荐用新的写数据回调,并且一样是:
- 数据够才返回
VALID - 不够就返回
INVALID
针对“流式下载 PCM”我建议你这样设计
方案结构
- 网络线程持续下载 PCM chunk
- 把 chunk 写入一个线程安全 RingBuffer
- 播放前先预缓冲一段数据
- 启动
AudioRenderer或OHAudio - 在回调中按系统要求长度读取数据
- 读满则返回
VALID - 读不满则:
- 中间播放阶段:返回
INVALID,等下一轮 - 流结束阶段:补静音后结束
- 中间播放阶段:返回
队列建议
至少维护这几个状态:
bufferedBytesisStartedisDownloadingisEos,服务端是否结束isUnderrun,是否发生供数不足
启播阈值
建议按时间算,不按包数算:
startThresholdBytes = sampleRate * channels * bytesPerSample * 0.2
比如 16k、单声道、16bit:
16000 * 1 * 2 * 0.2 = 6400 bytes- 也就是先攒约 200ms 再播
低时延要不要开
如果你是:
- 语音播报
- 普通音乐/音频内容播放
- TTS/实时返回音频播放
一般不建议为了这个场景强开低时延。
官方也写了:
- 游戏、K歌、直播适合低时延
- 普通音乐、视频播放不建议用低时延
因为低时延意味着:
- buffer 更小
- 回调更频繁
- 更容易因供数不及时而卡顿
你的瓶颈反而更可能是网络抖动,不是系统播放延迟。
一个更落地的建议
你现在如果是 ArkTS 工程
直接上:
audio.createAudioRenderer(...)audioRenderer.on('writeData', ...)- RingBuffer 缓冲网络数据
- 先预缓冲再
start()
你现在如果:
- PCM 来自 Native 解码器
- 已经有 C++ 网络/解码模块
- 想后续切低时延/音频引擎
直接上 OHAudio
最后给你一个简化判断
- 想快落地,业务在 ArkTS:
AudioRenderer - 想做 Native 音频引擎或低时延:
OHAudio - 你的当前需求:我更推荐
AudioRenderer + RingBuffer + 预缓冲
如果你愿意,我下一条可以直接给你一份:
- ArkTS 版
AudioRenderer流式 PCM 播放骨架代码 或 - C++ 版
OHAudio流式 PCM 播放骨架代码
三、OHAudio 回调模式的额外注意点(如果选Native方案)
-
要自己管理线程和内存
OHAudio没有ArkTS层的封装,所有的音频会话、缓冲区、回调都要自己在C/C++里处理,线程同步、内存泄漏都得自己盯,不然容易出各种奇怪的崩溃。 -
低延迟模式的坑
它支持低延迟播放,但低延迟模式下缓冲区很小,对数据喂送的及时性要求更高,稍微慢一点就会断流,适合游戏、实时语音这种场景,普通音频播放没必要折腾。 -
跨线程回调的问题
OHAudio的回调是跑在Native的系统线程里的,和你ArkTS层的线程是隔离的,如果你要把ArkTS层下载的数据传给OHAudio,得自己写NAPI桥接,处理好跨线程数据传递的问题,比纯ArkTS麻烦很多。
四、补充:AVPlayer能不能直接用?
评论里说的AVPlayer,其实更适合播放完整的音频文件(比如mp3、aac),不适合你这种边下载边播的PCM流,因为它没法直接喂裸PCM数据进去,还是得转格式或者封装,不如直接用AudioRenderer方便。
五、给你的建议
如果你的项目不是那种极致低延迟的实时语音,直接用ArkTS的AudioRenderer回调模式就行,开发快、坑少,完全能满足流式PCM播放的需求。
一、先搞懂两个接口的本质区别
-
AudioRenderer(ArkTS侧的API)
这个是鸿蒙给应用层(ArkTS/ArkUI)封装好的音频渲染接口,你在Stage模型里直接调用就行,不用碰C/C++代码。它的回调模式,就是系统音频线程会定期“喊你”,让你喂PCM数据进去,刚好适合你这种边下载边喂数据的流式场景。 -
OHAudio(Native层的API)
这个是给Native(C/C++)代码用的接口,属于NAPI开发范畴,比AudioRenderer更底层,性能上限更高,但门槛也高,得自己处理线程、内存这些。
简单说:想省事、快速实现,选AudioRenderer;追求极致低延迟、高性能,才考虑OHAudio。
二、AudioRenderer 回调模式的核心注意点(直接用的话重点看这个)
-
数据喂得够不够快,别断流
回调是系统定时触发的,比如每10ms喊你一次要数据,你必须在回调里把足够的PCM数据塞进去,不然就会出现卡顿、爆音。
流式场景下,你下载的线程要和回调线程做好缓冲,比如搞个环形队列,下载的数据先扔队列里,回调里直接从队列取,别让回调里直接等网络下载,不然必超时。 -
格式参数必须和PCM数据严格对齐
创建AudioRenderer的时候,配置的采样率、位深(比如16bit)、声道数、数据格式,必须和你服务端下的PCM数据完全一致,不然声音会变调、杂音甚至完全没声。
举个例子:你服务端是44100Hz、16bit、单声道的PCM,创建的时候就必须配一样的参数,不能瞎写。 -
回调里别做耗时操作
回调函数是跑在系统的音频线程里的,这里面只能做“取数据、塞数据”这种轻量操作,别在里面做IO、网络、复杂计算,不然会阻塞音频线程,直接爆音。 -
线程安全问题
你下载数据的线程和AudioRenderer的回调线程,都要访问同一个数据缓冲区,必须做好加锁,不然会出现数据错乱、崩溃。
主要一个napi开发(也就是Native层),一个直接arkts开发;
如果仅仅是为了播放pcm格式的音频, 用AVPlayer也可以
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
// ...
const TAG = 'AudioRendererDemo';
class Options {
public offset?: number;
public length?: number;
}
// ...
let audioRenderer: audio.AudioRenderer | undefined = undefined;
let audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。
channels: audio.AudioChannel.CHANNEL_2, // 通道。
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。
};
let audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。
rendererFlags: 0 // 音频渲染器标志。
};
let audioRendererOptions: audio.AudioRendererOptions = {
streamInfo: audioStreamInfo,
rendererInfo: audioRendererInfo
};
let writeDataCallback: audio.AudioRendererWriteDataCallback;
async function initArguments(context: common.UIAbilityContext) {
let bufferSize: number = 0;
let file = await context.resourceManager.getRawFd('32_xiyouji.pcm');
writeDataCallback = (buffer: ArrayBuffer) => {
let options: Options = {
offset: bufferSize,
length: buffer.byteLength
};
try {
let bufferLength = fs.readSync(file.fd, buffer, options);
bufferSize += buffer.byteLength;
// 如果当前回调传入的数据不足一帧,空白区域需要使用静音数据填充,否则会导致播放出现杂音。
if (bufferLength < buffer.byteLength) {
let view = new DataView(buffer);
for (let i = bufferLength; i < buffer.byteLength; i++) {
// 空白区域填充静音数据。当使用音频采样格式为SAMPLE_FORMAT_U8时0x7F为静音数据,使用其他采样格式时0为静音数据。
view.setUint8(i, 0);
}
}
// API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。
// 如果开发者不希望播放某段buffer,返回audio.AudioDataCallbackResult.INVALID即可。
return audio.AudioDataCallbackResult.VALID;
} catch (error) {
console.error('Error reading file:', error);
// ...
// API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。
return audio.AudioDataCallbackResult.INVALID;
}
};
}
// 初始化,创建实例,设置监听事件。
async function init() {
audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例。
if (!err) {
console.info(`${TAG}: creating AudioRenderer success`);
// ...
audioRenderer = renderer;
if (audioRenderer !== undefined) {
audioRenderer.on('writeData', writeDataCallback);
// ...
}
} else {
console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
// ...
}
});
}
// 开始一次音频渲染。
async function start() {
if (audioRenderer !== undefined) {
let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf(audioRenderer.state.valueOf()) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染。
console.error(TAG + 'start failed');
// ...
return;
}
// 启动渲染。
audioRenderer.start((err: BusinessError) => {
if (err) {
console.error('Renderer start failed.');
// ...
} else {
console.info('Renderer start success.');
// ...
}
});
}
}
async function pause() {
// 暂停渲染。
if (audioRenderer !== undefined) {
// 只有渲染器状态为running的时候才能暂停。
if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING) {
console.info('Renderer is not running');
// ...
return;
}
// 暂停渲染。
audioRenderer.pause((err: BusinessError) => {
if (err) {
console.error('Renderer pause failed.');
// ...
} else {
console.info('Renderer pause success.');
// ...
}
});
}
}
// 停止渲染。
async function stop() {
if (audioRenderer !== undefined) {
// 只有渲染器状态为running或paused的时候才可以停止。
if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING &&
audioRenderer.state.valueOf() !== audio.AudioState.STATE_PAUSED) {
console.info('Renderer is not running or paused.');
// ...
return;
}
// 停止渲染。
audioRenderer.stop((err: BusinessError) => {
if (err) {
console.error('Renderer stop failed.');
// ...
} else {
console.info('Renderer stop success.');
// ...
}
});
}
}
// 销毁实例,释放资源。
async function release() {
if (audioRenderer !== undefined) {
// 渲染器状态不是released状态,才能release。
if (audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) {
console.info('Renderer already released');
// ...
return;
}
// ...
// 释放资源。
audioRenderer.release((err: BusinessError) => {
if (err) {
console.error('Renderer release failed.');
// ...
} else {
// 关闭沙箱文件
console.info('Renderer release success.');
// ...
}
});
}
}
我给你推荐一个library,云享社作者写的一个pcm解码器:
[@ospark/free-pcm(V1.0.4)](https://ohpm.openharmony.cn/#/cn/detail/@ospark%2Ffree-pcm)
Free PCM 是一个高性能音频解码库,专为 OpenHarmony/HarmonyOS 设计。支持多种主流音频格式解码为 PCM,内置流式解码引擎与 10 段均衡器。流式解码完美解决你的问题。
在HarmonyOS NEXT中,使用@ohos.multimedia.audio (AudioKit) 的 AudioRenderer 实现PCM流式播放。配置音频参数(采样率、声道、位深),调用 createAudioRenderer 创建实例,然后循环调用 write() 方法写入PCM数据块。支持回调监听写入进度。也可通过 AVPlayer 的 DataSrc 接口处理内存流。
两者均支持PCM流式播放,核心区别在开发语言与性能层级:
- AudioRenderer 是 ArkTS/JS 侧接口,适合纯鸿蒙应用快速实现;回调通过
on('writeData')填入数据,编程模型更贴近前端习惯。 - OHAudio 是 Native (C/C++) 接口,适合高性能、低延迟场景或已有 Native 代码复用;回调模式通过注册
OH_AudioRenderer_Callbacks的OH_AudioRenderer_OnWriteData触发,可获得更精确的时序控制。
回调模式需注意三点:
- 非阻塞填充:回调执行在系统音频线程,必须立即填入请求的数据长度后返回,不能在其中做网络IO或长时间计算。
- 缓冲区同步:流式下载的数据先写入应用层环形缓冲,回调中从该缓冲取数据,并做好水位控制,防止缓冲区下溢(underrun)导致爆音。
- 资源与生命周期:
start后回调会持续触发,暂停或停止务必调用stop,释放时要先stop再release,避免堆积未消费数据。


