11、音频可视化(Visualizer):FFT与波形数据获取、实时频谱绘制、采样率与刷新率平衡
音频可视化,说白了就是把声音「画」出来。你听歌时那些跳动的柱状图、流动的波形线,背后就是 Visualizer 在做功。我最早接触这块是在做一款车载音乐播放器时,客户要求中控屏上必须有实时频谱显示。当时觉得不就是画几条柱子嘛,真动手才发现坑不少。
这一章,咱们就把它彻底讲透。
11.1 Visualizer 是什么?
Visualizer 是 Android 官方提供的一个音频数据采集工具。它属于 android.media.audiofx 包,和 Equalizer、BassBoost 这些效果器是同一家族的兄弟。
它能干两件事:
- 获取波形数据(Waveform):当前音频的时域采样值
- 获取 FFT 数据:频域信息,也就是频谱
你想想看,一个播放器如果没有可视化效果,用户怎么知道声音在「动」?尤其是低频节奏感强的音乐,频谱跳动带来的视觉反馈,能极大提升沉浸感。
核心要点:Visualizer 不修改音频数据,它只是「监听」音频输出流。你拿到的数据是只读的,不能反过来影响音质。
11.2 FFT 与波形数据获取
先说说波形数据。Visualizer 通过 getWaveForm(byte[] waveform) 方法,把 PCM 采样值塞进一个 byte 数组里。每个 byte 代表一个采样点的幅值,范围是 -128 到 127。
我在项目中遇到过一个问题:波形数据直接画出来,锯齿感特别强。后来发现是因为采样点太少,而且没有做平滑处理。解决办法是加一个简单的滑动平均滤波器。
FFT 数据就更有意思了。Visualizer 内部已经帮你做了快速傅里叶变换,你拿到的 getFft(byte[] fft) 返回的是频域复数结果。格式是这样的:
fft[0] = 直流分量(实部)
fft[1] = 第1个频点的实部
fft[2] = 第1个频点的虚部
fft[3] = 第2个频点的实部
fft[4] = 第2个频点的虚部
...
说白了,每两个 byte 组成一个复数。要算幅值,得用 sqrt(实部² + 虚部²)。嗯,这里要注意:直流分量(fft[0])只有实部,没有虚部,别算错了。
我的习惯:拿到 FFT 数据后,先取对数再画图。人耳对声音的感知是对数级的,线性画出来低频挤成一团,高频几乎看不见。取 log 之后,视觉分布才符合听觉感受。
11.3 实时频谱绘制
画频谱图,我推荐用自定义 View。别用 Canvas 的 drawLine 硬画,性能扛不住。用 drawRect 配合 LinearGradient 着色器,效果会好很多。
基本流程是这样的:
- 在
onDraw()里根据 FFT 数据计算每个频段的幅值 - 把幅值映射到柱状图的高度
- 用
invalidate()触发重绘
我曾经踩过一个坑:柱状图顶部闪烁严重。原因是每次重绘时,柱子的高度变化太剧烈。后来加了一个「衰减因子」——柱子上升时快速响应,下降时缓慢回落。这个技巧在专业音频软件里叫「峰值保持 + 衰减」,用户体验会好很多。
// 伪代码:带衰减的频谱更新
float targetHeight = calculateHeight(fftData, bandIndex);
// 上升快,下降慢
if (targetHeight > currentHeight) {
currentHeight = targetHeight;
} else {
currentHeight -= (currentHeight - targetHeight) * 0.1f;
}
注意:不要在 onDraw 里做 FFT 计算!onDraw 是 UI 线程,FFT 计算会卡界面。把数据解析和幅值计算放到子线程,用 Handler 或 LiveData 把结果抛给 View。
11.4 采样率与刷新率平衡
这是整个可视化最核心的调优点。采样率决定了 FFT 的频率分辨率,刷新率决定了动画的流畅度。两者互相制约。
Visualizer 的采样率通过 setCaptureSize() 设置。常见值是 1024 或 2048。采样率越高,频率分辨率越细,但计算量也越大。
刷新率呢?一般屏幕是 60Hz,也就是 16ms 一帧。但 Visualizer 的数据回调频率可以设置,我习惯用 setDataCaptureListener() 的 rate 参数来控制。
| 采样率(CaptureSize) | 频率分辨率 | 适用场景 |
|---|---|---|
| 256 | 粗 | 低端设备、简单波形显示 |
| 1024 | 中等 | 大多数频谱可视化 |
| 2048 | 细 | 专业音频分析 |
| 4096 | 极细 | 不推荐,延迟太高 |
我个人的经验是:采样率 1024,刷新率 30fps。为什么是 30fps?因为人眼对 30fps 的柱状图跳动已经觉得很流畅了,再高反而增加 CPU 负担。而且 30fps 意味着每 33ms 更新一次,和 Visualizer 的回调频率能很好地匹配。
你想想看,如果采样率设到 4096,每次回调要处理 4096 个点的 FFT 数据,再加上 UI 绘制,中低端手机直接掉帧。用户看到的不是频谱,是幻灯片。
避坑指南:我曾经把刷新率设到 60fps,结果在某个低端平板上,频谱动画卡得不行。后来用 Systrace 一查,发现 Visualizer 回调占用了大量 CPU 时间。降到 30fps 后,CPU 占用从 25% 降到了 8%,动画反而更平滑了——因为不丢帧了。
11.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。从音频数据流到最终绘制,每一步都有讲究。
11.6 性能调优小结
最后,我把几个关键点再拎一下:
- 采样率别贪高:1024 是黄金平衡点。2048 只在专业场景用。
- 刷新率 30fps 足够:别被 60fps 忽悠了,人眼对柱状图的流畅度要求没那么高。
- 计算和绘制分离:FFT 解析放子线程,UI 只负责画。
- 加衰减因子:柱子上升快、下降慢,视觉上更专业。
- 别忘了取对数:线性频谱图在低频段会挤成一团,对数缩放后分布才合理。
嗯,这一章的内容就到这里。音频可视化看起来简单,但真正做好,需要你对数据流、渲染性能、人眼感知都有深入理解。下一章我们会聊更高级的话题,先把这些基础打牢。