查看: 253|回复: 3

鸿蒙AudioRenderer白噪音播放:杂音、卡顿和内存优化

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
睡眠质量不佳时,用白噪音助眠是一个典型的小工具需求。原文作者基于鸿蒙 AudioRenderer 做了一个白噪音播放器,核心不是 UI,而是音频数据的创建、写入和循环播放。AudioRenderer 是 AudioKit 中的音频播放组件,原文示例采用 44.1kHz、单声道、F32LE、RAW 编码创建渲染器,再用 write 写入 ArrayBuffer,最后 start 播放。这个链路对做环境音、提示音、持续背景声的开发者有参考价值。

一、先固定 AudioRenderer 参数

原文的基本用法是先创建 AudioRenderer,再写入音频数据并开始播放:
  1. import { audio } from '@kit.AudioKit';
  2. // 创建AudioRenderer
  3. const audioRenderer = await audio.createAudioRenderer({
  4. streamInfo: {
  5. samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
  6. channels: audio.AudioChannel.CHANNEL_1,
  7. sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_F32LE,
  8. encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
  9. },
  10. rendererInfo: {
  11. usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
  12. rendererFlags: 0
  13. }
  14. });
  15. // 写入音频数据
  16. await audioRenderer.write(buffer);
  17. // 开始播放
  18. await audioRenderer.start();
复制代码

这里的采样率、声道、采样格式和编码类型必须和后续生成的数据一致。原文踩坑之一就是采样率不匹配导致杂音。生成 Float32Array 时按 44100 计算时间 t,创建渲染器也使用 SAMPLE_RATE_44100,才能让数据解释一致。

二、用数据结构描述噪音类型

原文把噪音名称、频率参数和幅度放进 NoiseType,并用数组管理白噪音、粉红噪音、棕色噪音和雨声:
  1. interface NoiseType {
  2. name: string;
  3. frequency: number;
  4. amplitude: number;
  5. }
  6. const noiseTypes: NoiseType[] = [
  7. { name: '白噪音', frequency: 0, amplitude: 0.3 },
  8. { name: '粉红噪音', frequency: 1, amplitude: 0.4 },
  9. { name: '棕色噪音', frequency: 2, amplitude: 0.5 },
  10. { name: '雨声', frequency: 3, amplitude: 0.6 }
  11. ];
复制代码

这样播放逻辑只关心 type 索引和幅度,便于扩展列表和切换。

三、生成音频数据

根据类型生成不同的音频数据,原文示例用随机数叠加正弦、余弦调制来区分噪音:
  1. private generateNoiseData(type: number, bufferSize: number): ArrayBuffer {
  2. const buffer = new ArrayBuffer(bufferSize);
  3. const data = new Float32Array(buffer);
  4. for (let i = 0; i < data.length; i++) {
  5. const t = i / 44100;
  6. let value = 0;
  7. if (type === 0) { // 白噪音
  8. value = (Math.random() * 2 - 1) * 0.3;
  9. } else if (type === 1) { // 粉红噪音
  10. value = (Math.random() * 2 - 1) * 0.4 * Math.sin(t * 100);
  11. } else if (type === 2) { // 棕色噪音
  12. value = (Math.random() * 2 - 1) * 0.5 * Math.cos(t * 50);
  13. } else { // 雨声
  14. value = (Math.random() * 2 - 1) * 0.6 * Math.sin(t * 200);
  15. }
  16. data[i] = value;
  17. }
  18. return buffer;
  19. }
复制代码

这段代码是演示级实现:所有分支都依赖 Math.random,粉红、棕色噪音并没有采用标准滤波算法。真正上线时如果要接近声学定义,需要替换生成模型,但 AudioRenderer 的写入方式不变。

四、循环写入实现持续播放

持续环境音不是一次性播放文件,而是不断供给 PCM 数据,所以写入节奏直接影响是否卡顿。原文用 bufferSize 4096,每次生成后 await write,while 由 isPlaying 控制:
  1. private async playNoise(type: number): Promise<void> {
  2. const bufferSize = 4096;
  3. while (this.isPlaying) {
  4. const buffer = this.generateNoiseData(type, bufferSize);
  5. await this.audioRenderer.write(buffer);
  6. }
  7. }
复制代码

五、三个常见坑

第一个坑是杂音。原文发现是采样率不匹配,确保 AudioRenderer 的采样率和生成数据时的采样率一致,是排查这类问题的第一步。

第二个坑是播放不流畅。原文提到写入数据太慢会导致播放卡顿,思路是异步写入、提前缓冲数据,示例代码为 await audioRenderer.write(buffer, audio.AudioDataWriteMode.MODE_BLOCKING)。这里需要注意,MODE_BLOCKING 与“异步写入”的表述并不完全一致,实际应以当前 AudioKit 版本语义为准;实践重点是不要让生成数据阻塞播放链路,可提前准备缓冲区,再按写入能力供给。

第三个坑是内存占用高。持续生成音频数据会占用大量内存,原文用对象池复用 buffer:
  1. private bufferPool: ArrayBuffer[] = [];
复制代码

复用 buffer 能减少反复分配,但要保证复用前数据已写入完成,否则可能覆盖仍在播放的数据。

另外,原文提示如果要加定时关闭功能,可以用 setTimeout 停止播放。实现时要把 isPlaying 一类状态同步置为 false,避免循环写入继续执行。

AudioRenderer 适合持续音频播放,白噪音应用的关键是参数一致、数据供给节奏和 buffer 管理。原文示例给出了最小闭环;产品化还需要在噪音生成质量、写入模式和性能优化上继续验证。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙AudioRenderer白噪音播放:杂音、卡顿和内存优化

这篇分享挺实在的,把 AudioRenderer 做持续环境音的关键链路讲清楚了:参数对齐、数据生成、循环写入、常见坑。对做助眠白噪音这类小工具很有参考价值。 补充几点实践上的想法。第一,参数一致性确实是最容易踩的坑,采样率、声道、F32LE 格式不仅创建渲染器时要一致,生成 Float32Array 的长度和时间步长也要对得上,否则会变调或者出现杂音。第二,粉红噪音和棕色噪音那段目前主要靠随机数乘正弦余弦,听感上可能更像带调制的噪声,如果以后想更接近声学定义,可以换成标准的滤波生成方式,或者干脆预生成一段可循环的 PCM 缓存,减少实时计算压力。第三,循环写入这块,bufferSize 4096 在 44.1kHz 单声道 F32 下大约只有几十毫秒,如果生成数据或写入稍有抖动就容易卡顿,提前准备双缓冲或者环形缓冲会更稳。第四,对象池复用 buffer 时要注意“写完再复用”的边界,最好在 write 返回后再把 buffer 放回池里,避免覆盖还没播放的数据。最后,定时关闭除了 setTimeout 停渲染器,记得把循环状态置为 false 并清理池子,防止后台继续跑。整体思路很清晰,继续完善应该会挺稳定。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙AudioRenderer白噪音播放:杂音、卡顿和内存优化

这个思路挺实用的,拿 AudioRenderer 直接喂 PCM 做环境音,比塞音频文件灵活很多。我自己也折腾过类似的持续播放,几点体会: 一是采样率那坑确实最容易踩,除了 44100 要和生成数据一致,声道数也要留意,单声道数据往立体声渲染器写会直接错位,听起来像变调加杂音。建议把采样率、声道这类参数抽成常量,生成和创建渲染器都引用同一份,改起来不容易漏。 二是粉红和棕色噪音那部分,演示里用正弦余弦去乘随机数,听起来更像有节奏的调制声,不太像声学意义上的粉红和棕色。真要接近一些,粉红噪声可以用 Voss-McCartney 或者一组一阶低通串联,棕色噪声用白噪声做积分再归一化,成本不高,效果差别挺明显。雨声其实更适合随机脉冲加短衰减包络,比周期调制自然。 三是循环写入的节奏,4096 帧在 44.1kHz 下大概九十多毫秒,缓冲区偏小的话对生成速度要求就高。可以适当加大块大小,或者准备两三个 buffer 轮换,一个在写、一个在生成,避免生成阻塞写入。对象池复用 buffer 时最好配个简单的状态标记,确认写完再回收,不然真会出现数据被覆盖的怪声。 四是定时关闭,确实不能只调 stop,isPlaying 置 false 之后还要考虑正在 await write 的那次调用返回后的处理,最好在循环里每次写之前再检查一次状态,避免停止后还多写一轮。 整体方向没问题,作为助眠小工具够
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙AudioRenderer白噪音播放:杂音、卡顿和内存优化

这篇拆得挺清楚的,尤其是把“参数一致性”和“写入节奏”单独拎出来讲,确实抓住了 AudioRenderer 做持续环境音的两个关键点。补充几个自己踩过或者觉得值得注意的地方,供参考。 首先是生成代码里那句 `data = value;` 应该是 `data = value;` 的笔误,另外粉红、棕色那段确实如原文所说只是“随机数乘调制”,如果真按声学意义做,棕色噪音更接近随机游走的积分,需要做累加并加限幅防止直流漂移,粉红噪音可以用 Paul Kellet 那套多级一阶滤波近似,改动就集中在 generateNoiseData 里,外层的写入链路完全不用动,这点原文说得对。 其次是循环接缝的问题。4096 帧在 44.1kHz 下大约 93 毫秒,持续 while 写入本身问题不大,但每次切换噪音类型或者停止播放时,如果直接停,很容易出现“啪”的一声。建议在切换和 stop 之前做几十毫秒的线性淡出,写入的最后一个 buffer 把幅度拉到 0 再停,体感会好很多。 再就是对象池复用的安全边界。原文提醒“复用前数据已写入完成”很关键,实际做的时候更稳妥的方式是准备两份以上的 buffer 轮转,写一份、备一份,避免写入是异步完成的情况下被下一轮生成覆盖。另外 write 的阻塞模式那块,不同版本语义确实有差异,与其依赖 MODE_BLOCKING,不如自己用一个“待写队列 + 水位
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-30 09:02 , Processed in 0.025889 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部