音频可视化
首先推荐一下 wavesurfer.js 这个非常好用的可定制化的音频可视化工具,基于 Web Audio API 和 HTML5 Canvas 实现,支持很多插件(比如生成音频频谱图),具体使用方法可以参阅官方文档。
接下来,我们也基于 Web Audio API 和 HTML5 Canvas 来实现一个简单的音频可视化播放器。点击这里可以观看演示 ~
音频处理的流程可以概括为:
- 创建音频上下文(AudioContext)
- 在音频上下文里创建源
- 创建效果节点
- 为音频选择一个目的地,例如你的系统扬声器
- 连接源到效果器,对目的地进行效果输出
音频上下文 AudioContext 接口表示由链接在一起的音频模块构建的音频处理图,每个模块由一个 AudioNode 表示。音频上下文控制它包含的节点的创建和音频处理或解码的执行。在做任何其他操作之前,您需要创建一个AudioContext对象,因为所有事情都是在上下文中发生的。建议创建一个AudioContext对象并复用它,而不是每次初始化一个新的 AudioContext 对象,并且可以对多个不同的音频源和管道同时使用一个 AudioContext 对象。
创建音频上下文
var AudioContext = window.AudioContext || window.webkitAudioContext;
var atx = new AudioContext();
在音频上下文里创建源
源可以是<audio>
(HTMLAudioElement),音频数据(Ajax 获取的 AudioBufferSourceNode),流(MediaStreamAudioSourceNode)。这里使用<audio>
作为源。
var audio = document.getElementById("audio");
var source = atx.createMediaElementSource(audio);
创建 AnalyserNode
AnalyserNode 接口表示了一个可以提供实时频域和时域分析信息的节点。它是一个不对音频流作任何改动的 AudioNode,同时允许你获取和处理它生成的数据,从而创建音频可视化。
var analyser = atx.createAnalyser();
连接源到 AnalyserNode 进行效果输出
source.connect(analyser);
analyser.connect(atx.destination);
在这一步中将 AnalyserNode 提供的音频频域或者时域数据使用 Canvas 用图形表示出来。时域和频域是音频应用中最常用的两个概念。
时域图如下:
横轴是时间,纵轴是声音强度,可知时域图是从时间维度来衡量一段音频。
频域图如下:
横轴是频率,纵轴是当前频率的能量大小,可知频域图是从频率分布维度来衡量一段声音。
频谱通过对波形的傅里叶变换,把波形中的每个频率拆开来,再在纵轴上展开,越往上频率越高。频谱是三维的,越亮表示在这个频率上越响,越暗表示越弱。
// 创建画布
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cWidth = canvas.width;
var cHeight = canvas.height;
// 设置 AnalyserNode 接口的 fftSize 属性的值,它表示信号样本的窗口大小
// fftSize 属性的值必须是从 32 到 32768 范围内的 2 的非零幂,其默认值为 2048
analyser.fftSize = 128;
function draw() {
// AnalyserNode 接口的 frequencyBinCount 属性值为 fftSize 的一半,它通常等于将要用于可视化的数据值的数量
var audioArray = new Uint8Array(analyser.frequencyBinCount);
// AnalyserNode.getByteFrequencyData() 用于将当前频域数据拷贝进 Unit8Array 数组
// AnalyserNode.getByteTimeDomainData() 用于将当前波形或者时域数据拷贝进 Unit8Array 数组
// 这两个方法的数据范围为 0-255
analyser.getByteFrequencyData(audioArray);
// 清空画布
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.fillStyle = "#fff";
// 根据音频数据和画布大小确定图形位置和大小进行绘制
for(var i = 0; i < audioArray.length; i++){
var yOffset = cHeight - audioArray[i] / 5.12;
var y = yOffset < (cHeight - 2) ? yOffset : (cHeight - 2);
ctx.fillRect(i * 3.125, y, 1.5, cHeight);
}
// 创建动画
animationTimer = requestAnimationFrame(draw);
}
draw()
最后完成的效果如下:
参考文章:
1.音频之时域和频域
2.知乎回答 - 声音的波形和频谱是什么?它们两者有什么联系?