记录--关于前端的音频可视化-Web Audio

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

背景

最近听音乐的时候,看到各种动效,突然好奇这些音频数据是如何获取并展示出来的,于是花了几天功夫去研究相关的内容,这里只是给大家一些代码实例,具体要看懂、看明白,还是建议大家大家结合相关API文档来阅读这篇文章。

参考资料地址:Web Audio API - Web API 接口参考 | MDN (mozilla.org)

实现思路

首先画肯定是用canvas去画,关于音频的相关数据(如频率、波形)如何去获取,需要去获取相关audio的DOM 或通过请求处理去拿到相关的音频数据,然后通过Web Audio API 提供相关的方法来实现。(当然还要考虑要音频请求跨域的问题,留在最后。)

一个简单而典型的 web audio 流程如下(取自MDN):

  1. 创建音频上下文
  2. 在音频上下文里创建源 --- 例如 <audio>, 振荡器,流
  3. 创建效果节点,例如混响、双二阶滤波器、平移、压缩
  4. 为音频选择一个目的地,例如你的系统扬声器
  5. 连接源到效果器,对目的地进行效果输出

实现

一、频率图

实现第一种类型,首先我们需要通过fetch或xhr来获取一个线上音频的数据,这里以fetch为例;

复制代码
 //创建一个音频上下文、考虑兼容性问题
 let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
 //添加一个音频源节点
 let source = audioCtx.createBufferSource();
//res.arrayBuffer是将数据转换为arrayBuffer格式
 fetch(url).then((res) => res.arrayBuffer()).then((res) => {
        //decodeAudioData是将arrayBuffer格式数据转换为audioBuffer
        audioCtx.decodeAudioData(res).then((buffer) => {
          // decodeAudioData解码完成后,返回一个AudioBuffer对象
          // 绘制音频波形图
          draw(buffer);
          // 连接音频源
          source.buffer = buffer;
          source.connect(audioCtx.destination);
          // 音频数据处理完毕
        });
      });

需要明白的是,source.connect(audioCtx.destination)是将音频源节点链接到输出设备,否则会没声音哦。那么现在有了数据、我们只需要通过canvas将数据画出来即可。

复制代码
function draw(buffer) {
  // buffer.numberOfChannels返回音频的通道数量,1即为单声道,2代表双声道。这里我们只取一条通道的数据
  let data = [];
  let originData = buffer.getChannelData(0);
  // 存储所有的正数据
  let positives = [];
  // 存储所有的负数据
  let negatives = [];
  // 先每隔50条数据取1条
  for (let i = 0; i < originData.length; i += 50) {
    data.push(originData[i]);
  }
  // 再从data中每10条取一个最大值一个最小值
  for (let j = 0, len = data.length / 10; j < len; j++) {
    let temp = data.slice(j * 10, (j + 1) * 10);
    positives.push(Math.max(...temp));
    negatives.push(Math.min(...temp));
  }
  if (canvas.getContext) {
    let ctx = canvas.getContext("2d");
    canvas.width = positives.length;
    let x = 0;
    let y = 75;
    let offset = 0;
    var grd = ctx.createLinearGradient(0, 0, canvas.width, 0);
    // 为渐变添加颜色,参数1表示渐变开始和结束之间的位置(用0至1的占比表示),参数2位颜色
    grd.addColorStop(0, "yellow");
    grd.addColorStop(0.5, "red");
    grd.addColorStop(1, "blue");
    ctx.fillStyle = grd;
    ctx.beginPath();
    ctx.moveTo(x, y);
    // 横坐标上方绘制正数据,下方绘制负数据
    // 先从左往右绘制正数据
    // x + 0.5是为了解决canvas 1像素线条模糊的问题
    for (let k = 0; k < positives.length; k++) {
      ctx.lineTo(x + k + 0.5, y - 50 * positives[k]);
    }

    // 再从右往左绘制负数据
    for (let l = negatives.length - 1; l >= 0; l--) {
      ctx.lineTo(x + l + 0.5, y + 50 * Math.abs(negatives[l]));
    }
    // 填充图形
    ctx.fill();
  }
}

参考文章\]([Web Audio - 绘制音频图谱 - 掘金 (juejin.cn)](https://juejin.cn/post/6970558571700453390#heading-1 "https://juejin.cn/post/6970558571700453390#heading-1")) ### **二、实时频率图** 实现第二种类型,获取实时频率,用到的API与第一种有区别,但流程一直,都是通过一个音频源节点通过连接达到效果。只不过在连接的中间加入了一个分析器analyser,在将分析器连接到输出设备。 const audio =document.querySelector('audio') //解决音频跨域问题 audio.crossOrigin ='anonymous' const canvas =document.querySelector('canvas') const ctx=canvas.getContext("2d") function initCanvas(){ //初始化canvas canvas.width=window.innerWidth*devicePixelRatio canvas.height=(window.innerHeight/2)*devicePixelRatio } initCanvas() //将数据提出来 let dataArray,analyser; //播放事件 audio.onplay=function(){ //创建一个音频上下文实例 const audioCtx=new (window.AudioContext || window.webkitAudioContext)(); //添加一个音频源节点 const source=audioCtx.createMediaElementSource(audio); //分析器节点 analyser=audioCtx.createAnalyser(); //fft分析器 越大 分析越细 analyser.fftSize=512 //创建一个无符号字节的数组 dataArray=new Uint8Array( analyser.frequencyBinCount); //音频源节点 链接分析器 source.connect(analyser) //分析器链接输出设备 analyser.connect(audioCtx.destination,) } 那么接下来至于怎么把数据画出来,就凭大家的想法了。 requestAnimationFrame(draw) // const {width ,height}=canvas; ctx.clearRect(0,0,width,height) //分析器节点分析出的数据到数组中 ctx.fillStyle='#78C5F7' ctx.lineWidth = 2; ctx.beginPath(); //getByteFrequencyData,分析当前音频源的数据 装到dataArray数组中去 //获取实时数据 analyser.getByteFrequencyData(dataArray) // console.log(dataArray); const len =dataArray.length; const barWidth=width/len; let x=0; for(let i=0;i

相关推荐
小王码农记3 分钟前
vue中动态绑定ref后,获取某个具体组件实例
前端·javascript·vue.js
悲且狂31 分钟前
vue辅助工具(vue系列二)
前端·javascript·vue.js
牧码岛1 小时前
Web前端之Vue+Element实现表格动态不同列合并多行、localeCompare、forEach、table、push、sort、Map
前端·javascript·elementui·vue·web·web前端
老K(郭云开)1 小时前
如何让eDrawings html文件在Chrome浏览器上展示——allWebPlugin中间件扩展
前端·javascript·chrome·中间件·edge·html
DCTANT1 小时前
【原创】vue-element-admin-plus完成确认密码功能,并实时获取Form中表单字段中的值
前端·javascript·vue.js·elementui·typescript
汪子熙1 小时前
使用 Trae 开发一个演示勾股定理的动画演示
前端·人工智能·trae
GISer_Jing2 小时前
字符串操作&栈和队列
前端·javascript
黑土豆2 小时前
TypeScript技术系列13:深入理解配置文件tsconfig.json
前端·javascript·typescript
zheshiyangyang2 小时前
JavaScript---原型和原型链
开发语言·前端·javascript
momo_养身版3 小时前
Browser use — 利用 AI 操作浏览器 · 原理篇
前端·openai