深圳幻海软件技术有限公司 欢迎您!

Web Audio API 太强了,让我们一起领略音频之美

2023-02-28

​在浏览器中,我们通常使用 audio 标签来播放音频:复制<audiocontrols><sourcesrc="myAudio.mp3"type="audio/mpeg"><sourcesrc="myAudio.ogg"type="audio/og

QQvce">​在浏览器中,我们通常使用 audio 标签来播放音频:

<audio controls>
  <source src="myAudio.mp3" type="audio/mpeg">
  <source src="myAudio.ogg" type="audio/ogg">
</audio>
  • 1.
  • 2.
  • 3.
  • 4.

虽然 audio​ 标签使用起来很简单,但也存在一些局限。比如它只控制音频的播放、暂停、音量等。如果我们想进一步控制音频,比如通道合并和拆分、混响、音高和音频幅度压缩等。那么仅仅使用 audio 标签是做不到的。为了解决这个问题,我们需要使用 Web Audio API。

Web Audio API 提供了一个非常高效和通用的系统来控制 Web 上的音频,允许开发人员为音频添加特殊效果、可视化音频、添加空间效果等等。Web Audio API 让用户能够在音频上下文(AudioContext)中进行音频操作,具有模块化路由的特点。基本的音频操作是在音频节点上执行的,这些节点连接在一起形成一个音频路由图。

接下来,我将演示如何利用 AudioContext 对象来播放音频:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audio Context</title>
</head>
<body>
    <input id="audioFile" type="file" accept="audio/*"/>
    <script>
    const inputFile = document.querySelector("#audioFile");
    inputFile.onchange = function(event) {
      const file = event.target.files[0]; 
      const reader = new FileReader();
      reader.readAsArrayBuffer(file);
      reader.onload = evt {
          const encodedBuffer = evt.currentTarget.result;
          const context = new AudioContext();
          context.decodeAudioData(encodedBuffer, decodedBuffer => {
              const dataSource = context.createBufferSource();
              dataSource.buffer = decodedBuffer;
              dataSource.connect(context.destination);
              dataSource.start();
          })
      }
    }    
    </script>
</body>
</html>
  • 1.
  • 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.

在以上代码中,我们使用 FileReader​ API 来读取音频文件的数据。然后创建一个 AudioContext​ 对象并使用该对象上的 decodeAudioData​ 方法解码音频。获取到解码后的数据后,我们会继续创建一个 AudioBufferSourceNode​ 对象来存储解码后的音频数据,然后将 AudioBufferSourceNode​ 对象与 context.destination​ 对象连接起来,最后调用 start 方法播放音频。

看到这里,是不是觉得使用 AudioContext​ 播放音频文件很麻烦?实际上它非常强大。下面我将介绍如何使用 AudioContext、AnalyserNode、Canvas 来实现音频可视化的功能。

可视化音频文件主要分为以下 3 个步骤:

  • 获取音频文件数据;
  • 获取音频文件频率数据;
  • 使用 Canvas API 实现数据可视化。

1、获取音频文件数据

在以下的代码中,我们使用 FileReader​ API 来读取音频文件的数据。然后创建一个 AudioContext​ 对象并使用该对象上的 decodeAudioData 方法解码音频。当然,你也可以从网络上下载音频文件。

inputFile.onchange = function(event) {
  const file = event.target.files[0];
  const reader = new FileReader();
  reader.readAsArrayBuffer(file);
  reader.onload = evt=>{
    const encodedBuffer = evt.currentTarget.result;
    const context = new AudioContext();
    context.decodeAudioData(encodedBuffer, decodedBuffer=>{
      const dataSource = context.createBufferSource();
      dataSource.buffer = decodedBuffer;
      analyser = createAnalyser(context, dataSource);
      bufferLength = analyser.frequencyBinCount;
      frequencyData = new Uint8Array(bufferLength);
      dataSource.start();
      drawBar();
    }
 )
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

2、获取音频文件频率数据

要获取频率数据,我们需要利用 AnalyserNode 接口,该接口提供实时频率和时域分析信息。

const analyser = audioCtx.createAnalyser();
analyser.fftSize = 512;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

AnalyserNode 对象上的 getByteFrequencyData()​ 方法会将当前频率数据复制到传入的 Uint8Array 对象。

3、使用 Canvas API 实现数据可视化

获取频率数据后,我们就可以使用 Canvas API 实现数据可视化,比如使用 CanvasRenderingContext2D 接口中的 fillRect 方法,对数据进行可视化。

function drawBar() {
  requestAnimationFrame(drawBar);
  analyser.getByteFrequencyData(frequencyData);
  canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
  let barHeight, barWidth, r, g, b;
  for (let i = 0, x = 0; i < bufferLength; i++) {
    barHeight = frequencyData[i];
    barWidth = canvasWidth / bufferLength * 2;
    r = barHeight + 25 * (i / bufferLength);
    g = 250 * (i / bufferLength);
    b = 50;
    canvasContext.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
    canvasContext.fillRect(x, canvasHeight - barHeight, barWidth, barHeight);
    x += barWidth + 2;
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

分析完上面的处理流程,我们来看一下完整的代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Visualizations with Web Audio API</title>
    </head>
    <body>
        <input id="audioFile" type="file" accept="audio/*"/>
        <canvas id="canvas"></canvas>
        <script>
            const canvas = document.querySelector("#canvas");
            const inputFile = document.querySelector("#audioFile");

            const canvasWidth = window.innerWidth;
            const canvasHeight = window.innerHeight;
            const canvasContext = canvas.getContext("2d");
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;
            let frequencyData = [], bufferLength = 0, analyser;

            inputFile.onchange = function(event) {
                const file = event.target.files[0];

                const reader = new FileReader();
                reader.readAsArrayBuffer(file);
                reader.onload = evt=>{
                    const encodedBuffer = evt.currentTarget.result;
                    const context = new AudioContext();
                    context.decodeAudioData(encodedBuffer, decodedBuffer=>{
                        const dataSource = context.createBufferSource();
                        dataSource.buffer = decodedBuffer;
                        analyser = createAnalyser(context, dataSource);
                        bufferLength = analyser.frequencyBinCount;
                        frequencyData = new Uint8Array(bufferLength);
                        dataSource.start();
                        drawBar();
                    }
                    )
                }

                function createAnalyser(context, dataSource) {
                    const analyser = context.createAnalyser();
                    analyser.fftSize = 512;
                    dataSource.connect(analyser);
                    analyser.connect(context.destination);
                    return analyser;
                }

                function drawBar() {
                    requestAnimationFrame(drawBar);
                    analyser.getByteFrequencyData(frequencyData);
                    canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
                    let barHeight, barWidth, r, g, b;
                    for (let i = 0, x = 0; i < bufferLength; i++) {
                        barHeight = frequencyData[i];
                        barWidth = canvasWidth / bufferLength * 2;
                        r = barHeight + 25 * (i / bufferLength);
                        g = 250 * (i / bufferLength);
                        b = 50;
                        canvasContext.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
                        canvasContext.fillRect(x, canvasHeight - barHeight, barWidth, barHeight);
                        x += barWidth + 2;
                    }
                }
            }
        </script>
    </body>
</html>
  • 1.
  • 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.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.

浏览器打开包含上述代码的网页,然后选择一个音频文件后,你就可以看到类似的图形。

事实上,我们有了频率数据之后,我们还可以使用 Canvas API 绘制其他漂亮的图形。

以上图形是使用 Github 上的第三方库 vudio.js 生成的。

https://github.com/alex2wong/vudio.js