Skip to content

音频可视化

上一篇我们介绍了 前端音视频录制 的基本知识,现在趁热打铁来看看音频的可视化知识

在数字时代,音频可视化已成为现代Web应用不可或缺的一部分。从音乐播放器的频谱跳动到语音分析应用的实时波形,前端音频可视化技术将无形的声波转化为引人入胜的视觉体验。这种技术不仅增强了用户体验,还为音频处理、音乐教育和医疗诊断等领域提供了强大的分析工具

小贴士

文章中涉及到的示例代码你都可以从 这里查看

AudioContext

一段音频不仅仅是被简单播放,而是经过复杂的处理流程——从解码到音效处理,再到空间定位和可视化呈现。这一切的背后,正是以 AudioContext 为核心的Web Audio API,将浏览器变成了一个强大的数字音频工作站

AudioContext 接口由链接在一起的音频模块构建的音频处理图,每个模块由 AudioNode 表示

创建上下文

音频上下文控制着节点的创建以及音频处理或解码的执行。您需要在执行其他任何操作之前创建AudioContext,因为一切都在上下文中发生

js
// 创建可选配置的音频上下文
const highLatencyContext = new AudioContext({
  latencyHint: 'interactive', // 低延迟模式
  sampleRate: 48000 // 设置采样率
});

创建音频源

创建了上下文后,需要指定具体的音频源,这样后续的处理效果就可以作用到它身上了。AudioContext 支持多种形式的音频源

源类型创建方法适用场景
文件音频createMediaElementSource()播放MP3/WAV等音频文件(Audio、Video)
音频缓冲区createBufferSource()处理解码后的音频数据
振荡器createOscillator()生成合成音效/电子音乐
流媒体createMediaStreamSource()处理麦克风/摄像头输入
音频处理节点createScriptProcessor()自定义音频处理逻辑

这里以Audio音频文件为例,快速设置音频源:

js
const audio = new Audio('/public/my-all.mp3');
const source = audioContext.createMediaElementSource(audio);

通过createMediaElementSource生成MediaElementAudioSourceNode类型的AudioNode

AnalyserNode

Web音频API涉及在音频上下文中处理音频操作,并旨在允许模块化路由。每个音频节点都执行一个基本的音频操作,并与其他一个音频节点链接在一起,以形成音频路由图。就像中间件的执行一样,可以在任意一个环节对音频做出调整

音频节点是通过其输入和输出链接的,形成以一个或多个来源开头的链条,通过一个或多个节点,然后最终到达目的地;如果您只想可视化某些音频数据,则不必提供目的地。 Web音频的简单,典型的工作流程看起来像这样:

sh
音频源 → 滤波器 → 效果器 → 分析器 → 输出

总结来说就是

AudioContext中对音频不同效果类型的处理节点有很多种,其中 AnalyserNode 节点表示可以提供实时频率和时间域分析信息的节点。即使不连接音频目的地节点,也允许获取生成的数据,对其进行处理并创建音频可视化

所以这里要告诉大家的是 AnalyserNode 只是音频处理的一种类型,并且它是不影响音频的效果的,除此之外还有很多类型的音频处理节点,如:振荡器、过滤器等等

本篇文章主要讲解使用AnalyserNode进行音频可视化,其他效果后续文章再讲

首先我们来创建AnalyserNode节点:

js
const analyser = new AnalyserNode(audioContext, opts?)

const analyser = audioContext.createAnalyser()

直接new构造器或者在AudioContext中创建都是可以的

傅里叶变换

要实现音频的可视化,当然就要拿到对应的音频数据,声音本质是看不见的波动,要了解声音数据首先读者要了解声音的基本概念

当我们听到声音时,实际上是在感知空气压力的波动。这些波动形成复杂的波形,但人耳却能神奇地区分出其中的不同频率成分如低音鼓和高音钢琴

傅里叶变换 正是模拟了人耳的这项能力,通过数学方法将时域信号(随时间变化的波形)转换为频域表示(不同频率的强度分布)

声波本身也是个深奥的学问,在这里我们暂可不必要深入声音学,简单了解时域频域傅里叶窗口即可,对于AnalyserNode使用只需要简单几步:

js
// 设置FFT参数
analyser.fftSize = 2048;

fftSize(快速傅里叶变换大小)决定了分析窗口的样本数量。它直接影响:

  • 频率分辨率:区分相近频率的能力
  • 时间分辨率:捕捉快速变化的能力
  • 性能开销:计算复杂度

频率分辨率 vs 时间分辨率

fftSize频率分辨率时间分辨率适用场景
256低 (宽频带)高 (快速响应)实时节奏可视化
1024通用音乐可视化
4096高 (精细)低 (延迟高)专业频谱分析
8192极高极低实验室级音频分析

计算公式:

  • 频率分辨率 = 采样率 / fftSize
  • 时间窗口长度 = fftSize / 采样率

获取音频数据

这段代码是音频可视化的核心引擎,负责将复杂的音频信号转换为可供可视化的频谱数据

js
// 准备数据容器
const bufferLength = analyser.frequencyBinCount; // = fftSize/2
const dataArray = new Uint8Array(bufferLength);

// 获取频率数据
analyser.getByteFrequencyData(dataArray);

frequencyBinCount表示频率分析结果中的数据点数,快速傅里叶变换(FFT)的输出具有对称性,只需一半数据即可完整表示频谱,计算方式fftSize / 2

getByteFrequencyData(dataArray)将当前音频帧的频谱数据填充到提供的数组中,0-255(0=静音,255=最大音量)

OfflineAudioContext

OfflineAudioContext 允许开发者在不播放声音的情况下高效处理音频数据,将浏览器变成了一个强大的离线音频工作站。与传统AudioContext不同,OfflineAudioContext不依赖实时播放,而是通过预先渲染生成精确的音频输出,为专业级音频应用开辟了新可能

这里是对音频可视化的一种优化机制,如果你仅仅是做音频数据的处理,而不涉及其他方面,那么这个API将会很好的提高性能,这里不做深入介绍,感兴趣的读者可以深一步了解

案例

掌握了以上知识后,我们来开始做几个可视化的例子

录音轨道

在音频可视化文章中我们学会了如何录制音视频,那么现在就可以结合音频可视化知识,实时展示录制时的音频反应,类似一个录音程序那样的效果

话不多说还是先通过用户设备授权拿到媒体流:

js
const mediaStream = navigator.mediaDevices.getUserMedia({
  audio: { deviceId },
  video: false
});

// 创建音频上下文
const audioContext = new AudioContext();
// 创建音频源
const source = audioContext.createMediaStreamSource(mediaStream);

创建分析节点并获取音频数据:

js
const analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.fftSize = 1024;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

拿到音频数据后,就可以实时渲染音频:

js
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();
}

这里通过麦克风录制播放的音乐,通过获取它的时域数据渲染最后的画面

通过上面画面可以看出,时域数据对于普通人来说并不是很好理解它的直观形象,比如一段音乐的高低,对于我们来说可能就想到声音大了、或者小了。那么我们就可以通过getByteFrequencyData获取到比较友好的数据了,来看下一个案例

音乐播放器

老规矩可视化我们就需要拿到音频数据,就需要音频源,这里我们直接播放对应的歌曲就可以

js
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)

接着我们就需要改变音频的canvas渲染图像了,这里我们将音频不同的频率画成不同高度的柱状图:

js
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);
  }
}

关于canvas的绘制这里不多讲,读者有疑惑的可以补充这方面的知识,来看看播放歌曲后实时绘制的效果:

看上去效果还可以,当然只要我们拿到了音频数据,想要展示成什么效果,取决于你如何绘制canvas,读者感兴趣可以发挥下想象

下期预告

文章到这里就接近尾声了,通过以上文章内容,读者基本上可以掌握音频可视化的大部分技能了,一定要亲自试试才能体会到它的强大

当然Web Audio赋予前端的能力不止这些,除了可视化外,诸如:混合器、滤波器、过滤器、声道、音频空间等等都可以实现,当然这些就需要了解一些音频方面更专业的知识了,不然对于普通人来说就如天书,后面尝试用音乐声道立体声等等切入,感谢关注

感谢支持

再次感谢您的支持,您的支持将鼓励我继续创作。文章通常首发公众号,可以关注我的公众号,获取最新优质文章;同时如果你有 珠宝首饰之类 的需求,也可以微信扫码光临小店,种类多多欢迎来选👏🏻
大卫talk
 
aphrodite-u

若您在阅读过程中发现一些错误:如语句不通、文字、逻辑等错误,可以在评论区指出,我会及时调整修改,感谢您的阅读。同时您觉得这篇文章对您有帮助的话,可以打赏作者一笔作为鼓励,金额不限,感谢支持🤝。
支付宝捐赠   微信支付捐赠

Released under the MIT License.