睡眠质量不佳时,用白噪音助眠是一个典型的小工具需求。原文作者基于鸿蒙 AudioRenderer 做了一个白噪音播放器,核心不是 UI,而是音频数据的创建、写入和循环播放。AudioRenderer 是 AudioKit 中的音频播放组件,原文示例采用 44.1kHz、单声道、F32LE、RAW 编码创建渲染器,再用 write 写入 ArrayBuffer,最后 start 播放。这个链路对做环境音、提示音、持续背景声的开发者有参考价值。
一、先固定 AudioRenderer 参数
原文的基本用法是先创建 AudioRenderer,再写入音频数据并开始播放:
- import { audio } from '@kit.AudioKit';
- // 创建AudioRenderer
- const audioRenderer = await audio.createAudioRenderer({
- streamInfo: {
- samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
- channels: audio.AudioChannel.CHANNEL_1,
- sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_F32LE,
- encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
- },
- rendererInfo: {
- usage: audio.StreamUsage.STREAM_USAGE_MUSIC,
- rendererFlags: 0
- }
- });
- // 写入音频数据
- await audioRenderer.write(buffer);
- // 开始播放
- await audioRenderer.start();
复制代码
这里的采样率、声道、采样格式和编码类型必须和后续生成的数据一致。原文踩坑之一就是采样率不匹配导致杂音。生成 Float32Array 时按 44100 计算时间 t,创建渲染器也使用 SAMPLE_RATE_44100,才能让数据解释一致。
二、用数据结构描述噪音类型
原文把噪音名称、频率参数和幅度放进 NoiseType,并用数组管理白噪音、粉红噪音、棕色噪音和雨声:
- interface NoiseType {
- name: string;
- frequency: number;
- amplitude: number;
- }
- const noiseTypes: NoiseType[] = [
- { name: '白噪音', frequency: 0, amplitude: 0.3 },
- { name: '粉红噪音', frequency: 1, amplitude: 0.4 },
- { name: '棕色噪音', frequency: 2, amplitude: 0.5 },
- { name: '雨声', frequency: 3, amplitude: 0.6 }
- ];
复制代码
这样播放逻辑只关心 type 索引和幅度,便于扩展列表和切换。
三、生成音频数据
根据类型生成不同的音频数据,原文示例用随机数叠加正弦、余弦调制来区分噪音:
- private generateNoiseData(type: number, bufferSize: number): ArrayBuffer {
- const buffer = new ArrayBuffer(bufferSize);
- const data = new Float32Array(buffer);
- for (let i = 0; i < data.length; i++) {
- const t = i / 44100;
- let value = 0;
- if (type === 0) { // 白噪音
- value = (Math.random() * 2 - 1) * 0.3;
- } else if (type === 1) { // 粉红噪音
- value = (Math.random() * 2 - 1) * 0.4 * Math.sin(t * 100);
- } else if (type === 2) { // 棕色噪音
- value = (Math.random() * 2 - 1) * 0.5 * Math.cos(t * 50);
- } else { // 雨声
- value = (Math.random() * 2 - 1) * 0.6 * Math.sin(t * 200);
- }
- data[i] = value;
- }
- return buffer;
- }
复制代码
这段代码是演示级实现:所有分支都依赖 Math.random,粉红、棕色噪音并没有采用标准滤波算法。真正上线时如果要接近声学定义,需要替换生成模型,但 AudioRenderer 的写入方式不变。
四、循环写入实现持续播放
持续环境音不是一次性播放文件,而是不断供给 PCM 数据,所以写入节奏直接影响是否卡顿。原文用 bufferSize 4096,每次生成后 await write,while 由 isPlaying 控制:
- private async playNoise(type: number): Promise<void> {
- const bufferSize = 4096;
- while (this.isPlaying) {
- const buffer = this.generateNoiseData(type, bufferSize);
- await this.audioRenderer.write(buffer);
- }
- }
复制代码
五、三个常见坑
第一个坑是杂音。原文发现是采样率不匹配,确保 AudioRenderer 的采样率和生成数据时的采样率一致,是排查这类问题的第一步。
第二个坑是播放不流畅。原文提到写入数据太慢会导致播放卡顿,思路是异步写入、提前缓冲数据,示例代码为 await audioRenderer.write(buffer, audio.AudioDataWriteMode.MODE_BLOCKING)。这里需要注意,MODE_BLOCKING 与“异步写入”的表述并不完全一致,实际应以当前 AudioKit 版本语义为准;实践重点是不要让生成数据阻塞播放链路,可提前准备缓冲区,再按写入能力供给。
第三个坑是内存占用高。持续生成音频数据会占用大量内存,原文用对象池复用 buffer:
- private bufferPool: ArrayBuffer[] = [];
复制代码
复用 buffer 能减少反复分配,但要保证复用前数据已写入完成,否则可能覆盖仍在播放的数据。
另外,原文提示如果要加定时关闭功能,可以用 setTimeout 停止播放。实现时要把 isPlaying 一类状态同步置为 false,避免循环写入继续执行。
AudioRenderer 适合持续音频播放,白噪音应用的关键是参数一致、数据供给节奏和 buffer 管理。原文示例给出了最小闭环;产品化还需要在噪音生成质量、写入模式和性能优化上继续验证。 |