音频可视化
上一篇我们介绍了 前端音视频录制 的基本知识,现在趁热打铁来看看音频的可视化知识
在数字时代,音频可视化已成为现代Web应用不可或缺的一部分。从音乐播放器的频谱跳动到语音分析应用的实时波形,前端音频可视化技术将无形的声波转化为引人入胜的视觉体验。这种技术不仅增强了用户体验,还为音频处理、音乐教育和医疗诊断等领域提供了强大的分析工具
小贴士
文章中涉及到的示例代码你都可以从 这里查看
AudioContext
一段音频不仅仅是被简单播放,而是经过复杂的处理流程——从解码到音效处理,再到空间定位和可视化呈现。这一切的背后,正是以 AudioContext 为核心的Web Audio API,将浏览器变成了一个强大的数字音频工作站
AudioContext
接口由链接在一起的音频模块构建的音频处理图,每个模块由 AudioNode 表示
创建上下文
音频上下文控制着节点的创建以及音频处理或解码的执行。您需要在执行其他任何操作之前创建AudioContext,因为一切都在上下文中发生
// 创建可选配置的音频上下文
const highLatencyContext = new AudioContext({
latencyHint: 'interactive', // 低延迟模式
sampleRate: 48000 // 设置采样率
});
2
3
4
5
创建音频源
创建了上下文后,需要指定具体的音频源,这样后续的处理效果就可以作用到它身上了。AudioContext
支持多种形式的音频源
源类型 | 创建方法 | 适用场景 |
---|---|---|
文件音频 | createMediaElementSource() | 播放MP3/WAV等音频文件(Audio、Video) |
音频缓冲区 | createBufferSource() | 处理解码后的音频数据 |
振荡器 | createOscillator() | 生成合成音效/电子音乐 |
流媒体 | createMediaStreamSource() | 处理麦克风/摄像头输入 |
音频处理节点 | createScriptProcessor() | 自定义音频处理逻辑 |
这里以Audio音频文件为例,快速设置音频源:
const audio = new Audio('/public/my-all.mp3');
const source = audioContext.createMediaElementSource(audio);
2
通过createMediaElementSource
生成MediaElementAudioSourceNode
类型的AudioNode
AnalyserNode
Web音频API涉及在音频上下文中处理音频操作,并旨在允许模块化路由。每个音频节点都执行一个基本的音频操作,并与其他一个音频节点链接在一起,以形成音频路由图。就像中间件
的执行一样,可以在任意一个环节对音频做出调整
音频节点是通过其输入和输出链接的,形成以一个或多个来源开头的链条,通过一个或多个节点,然后最终到达目的地;如果您只想可视化某些音频数据,则不必提供目的地。 Web音频的简单,典型的工作流程看起来像这样:
音频源 → 滤波器 → 效果器 → 分析器 → 输出
总结来说就是
AudioContext中对音频不同效果类型的处理节点有很多种,其中 AnalyserNode 节点表示可以提供实时频率和时间域分析信息的节点。即使不连接音频目的地节点,也允许获取生成的数据,对其进行处理并创建音频可视化
所以这里要告诉大家的是 AnalyserNode
只是音频处理的一种类型,并且它是不影响音频的效果的,除此之外还有很多类型的音频处理节点,如:振荡器、过滤器等等
本篇文章主要讲解使用AnalyserNode
进行音频可视化,其他效果后续文章再讲
首先我们来创建AnalyserNode
节点:
const analyser = new AnalyserNode(audioContext, opts?)
const analyser = audioContext.createAnalyser()
2
3
直接new构造器或者在AudioContext中创建都是可以的
傅里叶变换
要实现音频的可视化,当然就要拿到对应的音频数据,声音本质是看不见的波动,要了解声音数据首先读者要了解声音的基本概念
当我们听到声音时,实际上是在感知空气压力的波动。这些波动形成复杂的波形,但人耳却能神奇地区分出其中的不同频率成分如低音鼓和高音钢琴
傅里叶变换 正是模拟了人耳的这项能力,通过数学方法将时域信号(随时间变化的波形)
转换为频域表示(不同频率的强度分布)
声波本身也是个深奥的学问,在这里我们暂可不必要深入声音学,简单了解时域
、频域
、傅里叶窗口
即可,对于AnalyserNode使用只需要简单几步:
// 设置FFT参数
analyser.fftSize = 2048;
2
fftSize
(快速傅里叶变换大小)决定了分析窗口的样本数量。它直接影响:
- 频率分辨率:区分相近频率的能力
- 时间分辨率:捕捉快速变化的能力
- 性能开销:计算复杂度
频率分辨率 vs 时间分辨率
fftSize | 频率分辨率 | 时间分辨率 | 适用场景 |
---|---|---|---|
256 | 低 (宽频带) | 高 (快速响应) | 实时节奏可视化 |
1024 | 中 | 中 | 通用音乐可视化 |
4096 | 高 (精细) | 低 (延迟高) | 专业频谱分析 |
8192 | 极高 | 极低 | 实验室级音频分析 |
计算公式:
- 频率分辨率 = 采样率 / fftSize
- 时间窗口长度 = fftSize / 采样率
获取音频数据
这段代码是音频可视化的核心引擎,负责将复杂的音频信号转换为可供可视化的频谱数据
// 准备数据容器
const bufferLength = analyser.frequencyBinCount; // = fftSize/2
const dataArray = new Uint8Array(bufferLength);
// 获取频率数据
analyser.getByteFrequencyData(dataArray);
2
3
4
5
6
frequencyBinCount
表示频率分析结果中的数据点数
,快速傅里叶变换(FFT)的输出具有对称性,只需一半数据即可完整表示频谱,计算方式fftSize / 2
getByteFrequencyData(dataArray)
将当前音频帧的频谱数据
填充到提供的数组中,0-255
(0=静音,255=最大音量)
OfflineAudioContext
OfflineAudioContext 允许开发者在不播放声音的情况下高效处理音频数据,将浏览器变成了一个强大的离线音频工作站。与传统AudioContext不同,OfflineAudioContext不依赖实时播放,而是通过预先渲染生成精确的音频输出,为专业级音频应用开辟了新可能
这里是对音频可视化的一种优化机制,如果你仅仅是做音频数据的处理,而不涉及其他方面,那么这个API将会很好的提高性能,这里不做深入介绍,感兴趣的读者可以深一步了解
案例
掌握了以上知识后,我们来开始做几个可视化的例子
录音轨道
在音频可视化文章中我们学会了如何录制音视频,那么现在就可以结合音频可视化知识,实时展示录制时的音频反应,类似一个录音程序那样的效果
话不多说还是先通过用户设备授权拿到媒体流:
const mediaStream = navigator.mediaDevices.getUserMedia({
audio: { deviceId },
video: false
});
// 创建音频上下文
const audioContext = new AudioContext();
// 创建音频源
const source = audioContext.createMediaStreamSource(mediaStream);
2
3
4
5
6
7
8
9
创建分析节点并获取音频数据:
const analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.fftSize = 1024;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
2
3
4
5
拿到音频数据后,就可以实时渲染音频:
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const columns = canvasWidth / analyser.frequencyBinCount;
draw();
function draw() {
requestAnimationFrame(draw);
// 获取时域音频数据
analyser.getByteTimeDomainData(dataArray);
ctx.clearRect(0, canvasWidth, 0, canvasHeight);
ctx.beginPath();
ctx.strokeStyle = "#fff";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.lineWidth = 1;
let x = 0;
ctx.moveTo(x, canvasHeight / 2);
for (let i = 0; i < analyser.fftSize; i++) {
const v = dataArray[i] / 128;
const y = (v * canvasHeight) / 2;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
x += columns;
}
ctx.stroke();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
这里通过麦克风录制播放的音乐,通过获取它的时域数据渲染最后的画面
通过上面画面可以看出,时域数据对于普通人来说并不是很好理解它的直观形象,比如一段音乐的高低,对于我们来说可能就想到声音大了、或者小了。那么我们就可以通过getByteFrequencyData
获取到比较友好的数据了,来看下一个案例
音乐播放器
老规矩可视化我们就需要拿到音频数据,就需要音频源,这里我们直接播放对应的歌曲就可以
const audio = new Audio('/public/my-all.mp3')
audio.play()
const audioContext = new AudioContext()
const source = audioContext.createMediaElementSource(audio)
const analyser = audioContext.createAnalyser()
source.connect(analyser).connect(audioContext.destination)
analyser.fftSize = 4096
const bufferLength = analyser.frequencyBinCount
const dataArray = new Uint8Array(bufferLength)
2
3
4
5
6
7
8
9
10
接着我们就需要改变音频的canvas渲染图像了,这里我们将音频不同的频率画成不同高度的柱状图:
function renderFrame() {
requestAnimationFrame(renderFrame)
analyser.getByteFrequencyData(dataArray)
const width = canvasWidth;
const height = canvasHeight;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, width, height);
// 柱状图个数
const barCount = 30;
const barWidth = width / barCount;
// 间隔宽度
const barGap = 2;
for (let i = 0; i < barCount; i++) {
const barIndex = Math.floor(i * dataArray.length / barCount);
const amplitude = dataArray[barIndex];
const barHeight = (amplitude / 255) * height;
const gradient = ctx.createLinearGradient(0, height - barHeight, 0, height);
gradient.addColorStop(0, '#00f2fe');
gradient.addColorStop(1, '#4facfe');
ctx.fillStyle = gradient;
ctx.fillRect(i * barWidth + barGap * (i + 1), height - barHeight, barWidth - 2, barHeight);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
关于canvas的绘制这里不多讲,读者有疑惑的可以补充这方面的知识,来看看播放歌曲后实时绘制的效果:
看上去效果还可以,当然只要我们拿到了音频数据,想要展示成什么效果,取决于你如何绘制canvas,读者感兴趣可以发挥下想象
下期预告
文章到这里就接近尾声了,通过以上文章内容,读者基本上可以掌握音频可视化的大部分技能了,一定要亲自试试才能体会到它的强大
当然Web Audio赋予前端的能力不止这些,除了可视化外,诸如:混合器、滤波器、过滤器、声道、音频空间等等都可以实现,当然这些就需要了解一些音频方面更专业的知识了,不然对于普通人来说就如天书,后面尝试用音乐声道
、立体声
等等切入,感谢关注