实现音频可视化
这里主要使用了web audio api和canvas来实现的(当然也可以使用svg,因为canvas会有失帧的情况,svg的写法如果有大佬会的话,可以滴滴我一下)
背景
最近听音乐的时候,看到各种动效,突然好奇这些音频数据是如何获取并展示出来的,于是花了几天功夫去研究相关的内容,这里只是给大家一些代码实例,具体要看懂、看明白,还是建议大家大家结合相关api来阅读这篇文章。
参考资料地址:Web Audio API – Web API 接口参考 | MDN (mozilla.org)
实现思路
首先画肯定是用canvas去画,关于音频的相关数据(如频率、波形)如何去获取,需要去获取相关audio的DOM 或通过请求处理去拿到相关的音频数据,然后通过Web Audio API 提供相关的方法来实现。(当然还要考虑要音频请求跨域的问题,留在最后。)
一个简单而典型的 web audio 流程如下(取自MDN):
1.创建音频上下文<br>2.在音频上下文里创建源 — 例如 <audio>, 振荡器,流<br>3.创建效果节点,例如混响、双二阶滤波器、平移、压缩<br>4.为音频选择一个目的地,例如你的系统扬声器<br>5.连接源到效果器,对目的地进行效果输出<br>1.创建音频上下文<br> 2.在音频上下文里创建源 — 例如 <audio>, 振荡器,流<br> 3.创建效果节点,例如混响、双二阶滤波器、平移、压缩<br> 4.为音频选择一个目的地,例如你的系统扬声器<br> 5.连接源到效果器,对目的地进行效果输出<br>1.创建音频上下文<br> 2.在音频上下文里创建源 — 例如 <audio>, 振荡器,流<br> 3.创建效果节点,例如混响、双二阶滤波器、平移、压缩<br> 4.为音频选择一个目的地,例如你的系统扬声器<br> 5.连接源到效果器,对目的地进行效果输出<br>
上代码
<!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: #000;}div {width: 100%;border-top: 1px solid #fff;padding-top: 50px;display: flex;justify-content: center;}</style></head><body><canvas></canvas><div><audio src="./1.mp3" controls></audio></div><script>// 获取音频组件const audioEle = document.querySelector("audio");// 获取canvas元素const cvs = document.querySelector("canvas");// 创建canvas上下文const ctx = cvs.getContext("2d");// 初始化canvas的尺寸function initCvs() {cvs.width = window.innerWidth;cvs.height = window.innerHeight / 2;}initCvs();// 音频的步骤 音频源 ==》分析器 ===》输出设备//是否初始化let isInit = false;let dataArray, analyser;audioEle.onplay = function () {if (isInit) return;// 初始化const audCtx = new AudioContext(); //创建一个音频上下文const source = audCtx.createMediaElementSource(audioEle); //创建音频源节点analyser = audCtx.createAnalyser(); //拿到分析器节点analyser.fftSize = 512; // 时域图变换的窗口大小(越大越细腻)默认2048// 创建一个数组,接受分析器分析回来的数据dataArray = new Uint8Array(analyser.frequencyBinCount); // 数组每一项都是一个整数 接收数组的长度,因为快速傅里叶变换是对称的,所以除以2source.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(dataArray);const len = dataArray.length / 2.5;const barWidth = width / len / 2; // 每一个的宽度ctx.fillStyle = "#78C5F7";for (let i = 0; i < len; i++) {const data = dataArray[i]; // <256const 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></body></html><!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: #000; } div { width: 100%; border-top: 1px solid #fff; padding-top: 50px; display: flex; justify-content: center; } </style> </head> <body> <canvas></canvas> <div> <audio src="./1.mp3" controls></audio> </div> <script> // 获取音频组件 const audioEle = document.querySelector("audio"); // 获取canvas元素 const cvs = document.querySelector("canvas"); // 创建canvas上下文 const ctx = cvs.getContext("2d"); // 初始化canvas的尺寸 function initCvs() { cvs.width = window.innerWidth; cvs.height = window.innerHeight / 2; } initCvs(); // 音频的步骤 音频源 ==》分析器 ===》输出设备 //是否初始化 let isInit = false; let dataArray, analyser; audioEle.onplay = function () { if (isInit) return; // 初始化 const audCtx = new AudioContext(); //创建一个音频上下文 const source = audCtx.createMediaElementSource(audioEle); //创建音频源节点 analyser = audCtx.createAnalyser(); //拿到分析器节点 analyser.fftSize = 512; // 时域图变换的窗口大小(越大越细腻)默认2048 // 创建一个数组,接受分析器分析回来的数据 dataArray = new Uint8Array(analyser.frequencyBinCount); // 数组每一项都是一个整数 接收数组的长度,因为快速傅里叶变换是对称的,所以除以2 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(dataArray); const len = dataArray.length / 2.5; const barWidth = width / len / 2; // 每一个的宽度 ctx.fillStyle = "#78C5F7"; for (let i = 0; i < len; i++) { const data = dataArray[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> </body> </html><!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: #000; } div { width: 100%; border-top: 1px solid #fff; padding-top: 50px; display: flex; justify-content: center; } </style> </head> <body> <canvas></canvas> <div> <audio src="./1.mp3" controls></audio> </div> <script> // 获取音频组件 const audioEle = document.querySelector("audio"); // 获取canvas元素 const cvs = document.querySelector("canvas"); // 创建canvas上下文 const ctx = cvs.getContext("2d"); // 初始化canvas的尺寸 function initCvs() { cvs.width = window.innerWidth; cvs.height = window.innerHeight / 2; } initCvs(); // 音频的步骤 音频源 ==》分析器 ===》输出设备 //是否初始化 let isInit = false; let dataArray, analyser; audioEle.onplay = function () { if (isInit) return; // 初始化 const audCtx = new AudioContext(); //创建一个音频上下文 const source = audCtx.createMediaElementSource(audioEle); //创建音频源节点 analyser = audCtx.createAnalyser(); //拿到分析器节点 analyser.fftSize = 512; // 时域图变换的窗口大小(越大越细腻)默认2048 // 创建一个数组,接受分析器分析回来的数据 dataArray = new Uint8Array(analyser.frequencyBinCount); // 数组每一项都是一个整数 接收数组的长度,因为快速傅里叶变换是对称的,所以除以2 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(dataArray); const len = dataArray.length / 2.5; const barWidth = width / len / 2; // 每一个的宽度 ctx.fillStyle = "#78C5F7"; for (let i = 0; i < len; i++) { const data = dataArray[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> </body> </html>
我这里只用了简单的柱状图,还有什么其他的奇思妙想,至于怎么把数据画出来,就凭大家的想法了。
关于请求音频跨域问题解决方案
1.因为我这里是简单的html,音频文件也在同一个文件夹但是如果直接用本地路径打开本地文件是会报跨域的问题,所以我这里是使用Open with Live Server
就可以了
2.给获取的audio DOM添加一条属性即可
audio.crossOrigin ='anonymous'
或者直接在 aduio标签中 加入 crossorigin="anonymous"
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END