前端开发攻略---根据音频节奏实时绘制不断变化的波形图。深入剖析如何通过代码实现音频数据的可视化。

1、演示

2、代码分析

逐行解析 JavaScript 代码块:

const audioEle = document.querySelector('audio')
const cvs = document.querySelector('canvas')
const ctx = cvs.getContext('2d')

这几行代码首先获取了 <audio> 和 <canvas> 元素的引用,并使用 getContext('2d') 方法获取了 Canvas 2D 上下文对象,以便后续在画布上进行绘图操作。

function initCvs() {
  cvs.width = window.innerWidth * devicePixelRatio
  cvs.height = (window.innerHeight / 2) * devicePixelRatio
}
initCvs()

initCvs 函数用于初始化画布的尺寸。它将画布的宽度设置为窗口宽度的倍数,高度设置为窗口高度的一半,同时乘以设备像素比 devicePixelRatio,以确保在不同设备上显示的效果一致。

let isInit = false
let dateArray = null
let analyser = null

这几行定义了一些状态变量,用于跟踪音频分析器的初始化状态、频率数据数组、分析器对象以及音频播放状态。

audioEle.addEventListener('play', function (e) {
  if (isInit) return
  // 初始化
  const audCtx = new AudioContext() // 创建音频上下文
  const source = audCtx.createMediaElementSource(audioEle) // 创建音频源节点
  analyser = audCtx.createAnalyser()
  analyser.fftSize = 512 // 设置 FFT 大小
  dateArray = new Uint8Array(analyser.frequencyBinCount) // 创建存储频率数据的数组
  source.connect(analyser)
  analyser.connect(audCtx.destination)

  isInit = true
})

这段代码是一个事件监听器,当音频开始播放时触发。在此事件处理程序中:

  • 首先检查是否已经初始化过分析器,如果是,则直接返回。
  • 创建 AudioContext 对象 audCtx,用于处理音频。
  • 使用 createMediaElementSource 方法创建音频源节点 source,将 <audio> 元素作为输入。
  • 创建 AnalyserNode 对象 analyser,用于分析音频频率数据。
  • 设置 AnalyserNode 的 fftSize 属性为 512,表示采样点数。
  • 创建一个 Uint8Array 数组 dateArray 用于存储频率数据。
  • 将音频源节点连接到分析器节点,然后将分析器节点连接到 AudioContext 的目标节点。
  • 最后设置状态变量 isInit 为 true,表示分析器已经初始化且音频正在播放。
function draw() {
  requestAnimationFrame(draw)
  // 清空画布
  const { width, height } = cvs
  ctx.clearRect(0, 0, width, height)
  if (!isInit && !isPlay) return
  // 获取频率数据并绘制波形图
  analyser.getByteFrequencyData(dateArray)
  const len = dateArray.length / 2.5
  ctx.fillStyle = '#266fff'
  const barWidth = width / len / 2
  for (let i = 0; i < len; i++) {
    const data = dateArray[i] // < 256
    const barHeight = (data / 255) * height
    const x1 = i * barWidth + width / 2
    const x2 = width / 2 - (i + 1) * barWidth
    const y = height - barHeight
    ctx.fillRect(x1, y, barWidth - 2, barHeight)
    ctx.fillRect(x2, y, barWidth - 2, barHeight)
  }
}
draw()

draw 函数用于绘制波形图,通过 requestAnimationFrame 实现动画效果。在函数中:

  • 首先清空画布。
  • 检查分析器是否已初始化并且音频正在播放,如果不是,则直接返回。
  • 使用 analyser.getByteFrequencyData 方法获取频率数据,并存储在 dateArray 中。
  • 计算每个柱状条的宽度和高度,并根据频率数据绘制柱状图形。

3、全部代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        background-color: #ffffff;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }
      * {
        margin: 0;
        padding: 0;
      }
      canvas {
        border-bottom: 1px solid #266fff;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
    <audio src="./123.mp3" controls></audio>
  </body>
  <script>
    const audioEle = document.querySelector('audio')
    const cvs = document.querySelector('canvas')
    const ctx = cvs.getContext('2d')
    // 初始化canvas尺寸
    function initCvs() {
      cvs.width = window.innerWidth * devicePixelRatio
      cvs.height = (window.innerHeight / 2) * devicePixelRatio
    }
    initCvs()

    // 初始化,只需要做一次就可以了
    let isInit = false
    let dateArray = null
    let analyser = null

    audioEle.addEventListener('play', function (e) {
      if (isInit) return
      // 初始化
      const audCtx = new AudioContext() // 创建音频上下文
      const source = audCtx.createMediaElementSource(audioEle) // 创建音频源节点
      // 什么叫音频源节点?节点其实就是音频处理的一个环节。音频可能有很多环节 比如说修音 比如说混响 比如说把音调高调低这些都是处理环节
      // 每一个环节就是一个节点 在这些节点当中有一种叫源节点 表示我们音频数据的来源
      analyser = audCtx.createAnalyser()
      analyser.fftSize = 512 // 2的n次幂。数值越大越细腻
      // 创建数组 用于接收分析器节点的分析数据
      dateArray = new Uint8Array(analyser.frequencyBinCount) // 数组里面的每一项都是一个无符号的8位整数
      source.connect(analyser)
      analyser.connect(audCtx.destination)
      isInit = true
    })

    // 把分析出的波形绘制到canvas上
    function draw() {
      requestAnimationFrame(draw)
      // 清空画布
      const { width, height } = cvs
      ctx.clearRect(0, 0, width, height)
      if (!isInit) return
      // 让分析器节点分析出数据到数组中
      analyser.getByteFrequencyData(dateArray)
      const len = dateArray.length / 2.5
      ctx.fillStyle = '#266fff'
      const barWidth = width / len / 2
      for (let i = 0; i < len; i++) {
        const data = dateArray[i] // < 256
        const barHeight = (data / 255) * height
        const x1 = i * barWidth + width / 2
        const x2 = width / 2 - (i + 1) * barWidth
        const y = height - barHeight
        ctx.fillRect(x1, y, barWidth - 2, barHeight)
        ctx.fillRect(x2, y, barWidth - 2, barHeight)
      }
    }
    draw()
  </script>
</html>

使用jdk16编译 import java.awt.Graphics; import java.awt.GridLayout; import java.io.File; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import javax.swing.GroupLayout; import javax.swing.JFrame; /** * */ /** * @author Administrator * */ public class Musicline extends JFrame implements Runnable { private byte[] audioData = null; private int intBytes = 0; private byte[] ml = new byte[1]; private int[] drawl = null; /** Creates new form Musicline */ public Musicline() { initComponents(); Graphics g; g = this.getGraphics(); } public void paint(Graphics g) { g.clearRect(0, 0, 900, 900); // System.out.print(drawl.length); if (audioData != null) { drawl = new int[audioData.length]; for (int i = 0; i < audioData.length; i++) { ml[0] = audioData[i]; // String s=new String(ml); drawl[i] = Math.abs((int) ml[0]); } System.out.println(drawl[0]); for (int i = 0; i < drawl.length - 1; i++) { g.drawLine(i * this.getWidth() / 256, drawl[i] + 100, (i + 1) * this.getWidth() / 256, drawl[i + 1] + 100); } } } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ public void run() { // TODO Auto-generated method stub while (intBytes != -1) { try { synchronized (this) { this.wait(10); } } catch (InterruptedException ex) { ex.printStackTrace(); } repaint(); } } public void play() { try { AudioInputStream ais = AudioSystem.getAudioInputStream(new File( "F:/perl/key2.wav"));// 获得音频输入流 ais = AudioSystem.getAudioInputStream( AudioFormat.Encoding.PCM_SIGNED, ais); AudioFormat baseFormat = ais.getFormat();// 指定声音流中特定数据安排 System.out.println("baseFormat=" + baseFormat); DataLine.Info info = new DataLine.Info(SourceDataLine.class, baseFormat); System.out.println("info=" + info);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bbamx.

谢谢您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值