当前 ijkplayer 的音频管线流程为:FFmpeg 解码 → AudioRendererOnWriteData 回调 → PCM 填充 → 均衡器处理 → OH_AudioRenderer 输出。PCM 数据在原生层产生和消费,ArkTS 层完全无法访问。播放界面 (LocalMusic.ets) 使用 Swiper 容器承载封面和歌词两个页面,已有 Canvas 使用经验(ScanFilePage.ets 中用于 Lottie 动画渲染)。
关键约束:
AudioRendererOnWriteData 回调中,生命周期极短unknown/any 类型,禁止解构赋值Goals:
Non-Goals:
选择:在 SDL_Aout_Opaque 结构体中新增环形缓冲区,在 AudioRendererOnWriteData 均衡器处理之后直接 memcpy 到缓冲区。
备选方案:
AudioCapturer API 从系统层采集 — 需要额外权限,延迟高,且无法获取 app 内部播放的精确 PCM理由:直接在 AudioRendererOnWriteData 末尾拷贝,代码侵入最小(仅加几行),数据是均衡器处理后的最终 PCM,与用户听到的一致。
环形缓冲区设计:
// 新增到 SDL_Aout_Opaque
#define SPECTRUM_BUFFER_SIZE 4096 // 2048 samples * 2 channels * sizeof(int16_t)
typedef struct SpectrumBuffer {
int16_t data[SPECTRUM_BUFFER_SIZE / sizeof(int16_t)];
volatile int write_pos;
volatile bool has_data;
int sample_rate;
int channels;
} SpectrumBuffer;
选择:新增 _getSpectrumData(id) NAPI 函数,ArkTS 侧按帧主动调用获取当前缓冲区快照,返回 number[] 数组。
备选方案:
理由:拉取模式让 ArkTS 侧控制采样频率,与 Canvas 绑帧渲染天然同步,实现简单。每次调用仅拷贝一个窗口大小的数据(2048 个样本),开销可控。
选择:在 ArkTS 侧实现 Cooley-Tukey 基 2 FFT 算法(1024 点),输出 512 个频率 bin 的幅值。
备选方案:
libavutil/tx.h 做 FFT — 减少一次数据传递,但增加原生层复杂度,FFT 结果传到 JS 层也需要拷贝理由:1024 点 FFT 计算量极小(约 5000 次乘加),现代处理器在 ArkTS 中也能在 1ms 内完成。放在 ArkTS 侧便于调试和迭代,避免频繁修改原生代码重新编译。
处理流程:
PCM int16[] → 取单声道 → 加窗(Hann) → FFT → 取幅值 → 对数映射 → 分频段 → 平滑
频段划分(对数分布,映射到 32-64 个可视化柱):
选择:定义 SpectrumRenderer 接口,每种特效实现该接口的 render(ctx, spectrumData, canvasWidth, canvasHeight) 方法。主组件持有 Canvas 和定时器,按当前选择的渲染策略绘制。
理由:策略模式使新增特效只需实现一个类,不影响其他特效和主逻辑。Canvas 是 ArkUI 内置能力,无需额外依赖。
四种特效实现要点:
| 特效 | 渲染方式 | 数据驱动参数 |
|---|---|---|
| 柱状频谱 | fillRect 绘制等间距矩形条 |
高度=幅值,颜色=频段映射渐变 |
| 波形频谱 | bezierCurveTo 绘制平滑曲线 |
Y轴=幅值,线宽=能量强度 |
| 圆形频谱 | 极坐标变换后 lineTo 绘制放射线 |
半径=幅值,颜色=角度映射 |
| 粒子频谱 | 粒子池 + arc 绘制圆点 |
速度/大小/透明度=幅值,颜色=频段 |
选择:在 LocalMusic.ets 的 Swiper 中,在封面和歌词之间插入频谱页面,顺序变为:封面 → 频谱 → 歌词。频谱页面上方显示小型特效切换按钮。
备选方案:
理由:独立 Swiper 页面提供最大渲染空间,不干扰现有封面和歌词体验。用户可以根据需要滑动查看。
setInterval 33ms),不追求 60fps 以省电current = current * 0.7 + previous * 0.3,避免跳变sample_rate,FFT 分析器动态适配。主流音频均为 44100/48000HzAudioRendererOnWriteData 末尾加 memcpy,不影响播放逻辑。环形缓冲区独立于播放状态,写满自动覆盖旧数据