基于Web Audio API实现音频可视化效果


Posted in Javascript onJune 12, 2020

网页音频接口最有趣的特性之一它就是可以获取频率、波形和其它来自声源的数据,这些数据可以被用作音频可视化。这篇文章将解释如何做到可视化,并提供了一些基础使用案例。
基本概念节
要从你的音频源获取数据,你需要一个 AnalyserNode节点,它可以用 AudioContext.createAnalyser() 方法创建,比如:

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();

然后把这个节点(node)连接到你的声源:

source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);

// etc.
注意: 分析器节点(Analyser Node) 不一定输出到另一个节点,不输出时也可以正常使用。但前提是它必须与一个声源相连(直接或者通过其他节点间接相连都可以)。

分析器节点(Analyser Node) 将在一个特定的频率域里使用快速傅立叶变换(Fast Fourier Transform (FFT) )来捕获音频数据,这取决于你给 AnalyserNode.fftSize 属性赋的值(如果没有赋值,默认值为2048)。

注意: 你也可以为FFT数据缩放范围指定一个最小值和最大值,使用AnalyserNode.minDecibels 和AnalyserNode.maxDecibels进行设置,要获得不同数据的平均常量,使用 AnalyserNode.smoothingTimeConstant。阅读这些页面以获得更多如何使用它们的信息。

要捕获数据,你需要使用 AnalyserNode.getFloatFrequencyData()AnalyserNode.getByteFrequencyData() 方法来获取频率数据,用 AnalyserNode.getByteTimeDomainData() 或 AnalyserNode.getFloatTimeDomainData() 来获取波形数据。

这些方法把数据复制进了一个特定的数组当中,所以你在调用它们之前要先创建一个新数组。第一个方法会产生一个32位浮点数组,第二个和第三个方法会产生8位无符号整型数组,因此一个标准的JavaScript数组就不能使用 —— 你需要用一个 Float32Array 或者 Uint8Array 数组,具体需要哪个视情况而定。

那么让我们来看看例子,比如我们正在处理一个2048尺寸的FFT。我们返回 AnalyserNode.frequencyBinCount 值,它是FFT的一半,然后调用Uint8Array(),把frequencyBinCount作为它的长度参数 —— 这代表我们将对这个尺寸的FFT收集多少数据点。

analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);

要正确检索数据并把它复制到我们的数组里,就要调用我们想要的数据收集方法,把数组作为参数传递给它,例如:

analyser.getByteTimeDomainData(dataArray);

现在我们就获取了那时的音频数据,并存到了我们的数组里,而且可以把它做成我们喜欢的可视化效果了,比如把它画在一个HTML5 <canvas> 画布上。

创建一个频率条形图节
另一种小巧的可视化方法是创建频率条形图,
现在让我们来看看它是如何实现的。

首先,我们设置好解析器和空数组,之后用 clearRect() 清空画布。与之前的唯一区别是我们这次大大减小了FFT的大小,这样做的原因是为了使得每个频率条足够宽,让它们看着像“条”而不是“细杆”。

analyser.fftSize = 256;
 var bufferLength = analyser.frequencyBinCount;
 console.log(bufferLength);
 var dataArray = new Uint8Array(bufferLength);
 canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);

接下来我们写好 draw() 函数,再一次用 requestAnimationFrame() 设置一个循环,这样显示的数据就可以保持刷新,并且每一帧都清空一次画布。

function draw() {
  drawVisual = requestAnimationFrame(draw);
  analyser.getByteFrequencyData(dataArray);
  canvasCtx.fillStyle = 'rgb(0, 0, 0)';
  canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
  }

现在我们来设置一个 barWidth 变量,它等于每一个条形的宽度。理论上用花布宽度除以条的个数就可以得到它,但是在这里我们还要乘以2.5。这是因为有很多返回的频率区域中是没有声音的,我们每天听到的大多数声音也只是在一个很小的频率区域当中。在条形图中我们肯定不想看到大片的空白条,所以我们就把一些能正常显示的条形拉宽来填充这些空白区域。

我们还要设置一个条高度变量 barHeight,还有一个 x 变量来记录当前条形的位置。

var barWidth = (WIDTH / bufferLength) * 2.5;
var barHeight;
var x = 0;

像之前一样,我们进入循环来遍历 dataArray 数组中的数据。在每一次循环过程中,我们让条形的高度 barHeight 等于数组的数值,之后根据高度设置条形的填充色(条形越高,填充色越亮),然后在横坐标 x 处按照设置的宽度和高度的一半把条形画出来(我们最后决定只画高度的一半因为这样条形看起来更美观)。

需要多加解释的一点是每个条形竖直方向的位置,我们在 HEIGHT-barHeight/2 的位置画每一条,这是因为我想让每个条形从底部向上伸出,而不是从顶部向下(如果我们把竖直位置设置为0它就会这样画)。所以,我们把竖直位置设置为画布高度减去条形高度的一半,这样每个条形就会从中间向下画,直到画布最底部。

for(var i = 0; i < bufferLength; i++) {
  barHeight = dataArray[i]/2;
  canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
  canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight);
  x += barWidth + 1;
  }
 };

和刚才一样,我们在最后调用 draw() 函数来开启整个可视化过程。

draw();

这些代码会带来下面的效果:

基于Web Audio API实现音频可视化效果

源码:

<!doctype html>
<html lang="en">
<head>
 <meta charset="UTF-8"/>
 <title>可视化音乐播放器</title>
</head>
<body>
<input type="file" name="" value="" id="musicFile">
<p id="tip"></p>
<canvas id="casvased" width="500" height="500"></canvas>
</body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
//随机变颜色
function randomRgbColor() { //随机生成RGB颜色
 var r = Math.floor(Math.random() * 256); //随机生成256以内r值
 var g = Math.floor(Math.random() * 256); //随机生成256以内g值
 var b = Math.floor(Math.random() * 256); //随机生成256以内b值
 return `rgb(${r},${g},${b})`; //返回rgb(r,g,b)格式颜色
}
//随机数 0-255
function sum (m,n){
var num = Math.floor(Math.random()*(m - n) + n);
 
}
console.log(sum(0,100));
console.log(sum(100,255));
//展示音频可视化
var canvas = document.getElementById("casvased");
var canvasCtx = canvas.getContext("2d");
//首先实例化AudioContext对象 很遗憾浏览器不兼容,只能用兼容性写法;audioContext用于音频处理的接口,并且工作原理是将AudioContext创建出来的各种节点(AudioNode)相互连接,音频数据流经这些节点并作出相应处理。
//总结就一句话 AudioContext 是音频对象,就像 new Date()是一个时间对象一样
var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
if (!AudioContext) {
 alert("您的浏览器不支持audio API,请更换浏览器(chrome、firefox)再尝试,另外本人强烈建议使用谷歌浏览器!")
}
var audioContext = new AudioContext();//实例化
// 总结一下接下来的步骤
// 1 先获取音频文件(目前只支持单个上传)
// 2 读取音频文件,读取后,获得二进制类型的音频文件
// 3 对读取后的二进制文件进行解码
$('#musicFile').change(function(){
 if (this.files.length == 0) return;
 var file = $('#musicFile')[0].files[0];//通过input上传的音频文件
 var fileReader = new FileReader();//使用FileReader异步读取文件
 fileReader.readAsArrayBuffer(file);//开始读取音频文件
 fileReader.onload = function(e) {//读取文件完成的回调
 //e.target.result 即为读取的音频文件(此文件为二进制文件)
 //下面开始解码操作 解码需要一定时间,这个时间应该让用户感知到
 var count = 0;
 $('#tip').text('开始解码')
 var timer = setInterval(function(){
  count++;
  $('#tip').text('解码中,已用时'+count+'秒')
 },1000)
 //开始解码,解码成功后执行回调函数
 audioContext.decodeAudioData(e.target.result, function(buffer) {
  clearInterval(timer)
  $('#tip').text('解码成功,用时共计:'+count+'秒')
  // 创建AudioBufferSourceNode 用于播放解码出来的buffer的节点
  var audioBufferSourceNode = audioContext.createBufferSource();
  // 创建AnalyserNode 用于分析音频频谱的节点
  var analyser = audioContext.createAnalyser();
  //fftSize (Fast Fourier Transform) 是快速傅里叶变换,一般情况下是固定值2048。具体作用是什么我也不太清除,但是经过研究,这个值可以决定音频频谱的密集程度。值大了,频谱就松散,值小就密集。
  analyser.fftSize = 256;
  // 连接节点,audioContext.destination是音频要最终输出的目标,
  // 我们可以把它理解为声卡。所以所有节点中的最后一个节点应该再
  // 连接到audioContext.destination才能听到声音。
  audioBufferSourceNode.connect(analyser);
  analyser.connect(audioContext.destination);
  console.log(audioContext.destination)
  // 播放音频
  audioBufferSourceNode.buffer = buffer; //回调函数传入的参数
  audioBufferSourceNode.start(); //部分浏览器是noteOn()函数,用法相同
  //可视化 创建数据
  // var dataArray = new Uint8Array(analyser.fftSize);
  // analyser.getByteFrequencyData(dataArray)//将数据放入数组,用来进行频谱的可视化绘制
  // console.log(analyser.getByteFrequencyData)
  var bufferLength = analyser.frequencyBinCount;
  console.log(bufferLength);
  var dataArray = new Uint8Array(bufferLength);
  console.log(dataArray)
  canvasCtx.clearRect(0, 0, 500, 500);
  function draw() {
  drawVisual = requestAnimationFrame(draw);
  analyser.getByteFrequencyData(dataArray);
  canvasCtx.fillStyle = 'rgb(0, 0, 0)';
		//canvasCtx.fillStyle = ;
  canvasCtx.fillRect(0, 0, 500, 500);
  var barWidth = (500 / bufferLength) * 2.5;
  var barHeight;
  var x = 0;
  for(var i = 0; i < bufferLength; i++) {
   barHeight = dataArray[i];
		 //随机数0-255 Math.floor(Math.random()*255) 
		 // 随机数 10*Math.random()
   canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ','+Math.floor(Math.random()*(20- 120) + 120)+','+Math.floor(Math.random()*(10 - 50) + 50)+')';
   canvasCtx.fillRect(x,500-barHeight/2,barWidth,barHeight/2);
   x += barWidth + 1;
  }
  };
  draw();
 });
 }
})
</script>
</html>

 注意:

本文中的案例展现了 AnalyserNode.getByteFrequencyData() 和 AnalyserNode.getByteTimeDomainData() 的用法。如果想要查看AnalyserNode.getFloatFrequencyData() 和 AnalyserNode.getFloatTimeDomainData() 的用法,请参考我们的 Voice-change-O-matic-float-data 演示(也能看到 源代码 )——它和本文中出现的 Voice-change-O-matic 功能完全相同,唯一区别就是它使用的是浮点数作数据,而不是本文中的无符号整型数。

《参考:https://github.com/mdn/voice-change-o-matic  》
《案例一:https://mdn.github.io/voice-change-o-matic/  》
《案例二:http://margox.github.io/vudio.js/  》

总结

到此这篇关于基于Web Audio API实现音频可视化效果的文章就介绍到这了,更多相关Web Audio API实现音频可视化内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
基于JQuery的密码强度验证代码
Mar 01 Javascript
Jquery each方法跳出循环,并获取返回值(实例讲解)
Dec 12 Javascript
javascript实现动态侧边栏代码
Feb 19 Javascript
JS跨域问题详解
Nov 25 Javascript
javascript实现动态标签云
Oct 16 Javascript
jquery制做精致的倒计时特效
Jun 13 Javascript
jQuery实现简单的tab标签页效果
Sep 12 Javascript
jquery中ajax请求后台数据成功后既不执行success也不执行error的完美解决方法
Dec 24 jQuery
javascript少儿编程关于返回值的函数内容
May 27 Javascript
vue 中Virtual Dom被创建的方法
Apr 15 Javascript
Js视频播放器插件Video.js使用方法详解
Feb 04 Javascript
Vant 在vue-cli 4.x中按需加载操作
Nov 05 Javascript
js实现mp3录音通过websocket实时传送+简易波形图效果
Jun 12 #Javascript
学前端,css与javascript重难点浅析
Jun 11 #Javascript
微信小程序 scroll-view的使用案例代码详解
Jun 11 #Javascript
基于ajax及jQuery实现局部刷新过程解析
Sep 12 #jQuery
微信小程序scroll-view实现滚动到锚点左侧导航栏点餐功能(点击种类,滚动到锚点)
Jun 11 #Javascript
多页vue应用的单页面打包方法(内含打包模式的应用)
Jun 11 #Javascript
VUE页面中通过双击实现复制表格中内容的示例代码
Jun 11 #Javascript
You might like
十天学会php之第十天
2006/10/09 PHP
php下获取Discuz论坛登录用户名、用户组、用户ID等信息的实现代码
2010/12/29 PHP
php中常用的预定义变量小结
2012/05/09 PHP
PHP For循环字母A-Z当超过26个字母时输出AA,AB,AC
2020/02/16 PHP
数据结构之利用PHP实现二分搜索树
2020/10/25 PHP
jQuery 版本的文本输入框检查器Input Check
2009/07/09 Javascript
JavaScript 浏览器验证代码(来自discuz)
2010/07/17 Javascript
使用JavaScript和CSS实现文本隔行换色的方法
2015/11/04 Javascript
jQuery EasyUi 验证功能实例解析
2017/01/06 Javascript
使用Node.js搭建静态资源服务详细教程
2017/08/02 Javascript
浅谈Vue 性能优化之深挖数组
2018/12/11 Javascript
Vue 组件参数校验与非props特性的方法
2019/02/12 Javascript
BootstrapValidator验证用户名已存在(ajax)
2019/11/08 Javascript
JavaScript实现多文件下载方法解析
2020/08/07 Javascript
Vue中computed和watch有哪些区别
2020/12/19 Vue.js
[00:32]2018DOTA2亚洲邀请赛iG出场
2018/04/03 DOTA
使用python搭建Django应用程序步骤及版本冲突问题解决
2013/11/19 Python
详解python开发环境搭建
2016/12/16 Python
多版本Python共存的配置方法
2017/05/22 Python
python实现俄罗斯方块
2018/06/26 Python
python 遍历pd.Series的index和value
2019/11/26 Python
全球最大的中文旅行网站:去哪儿网
2017/11/16 全球购物
意大利婴儿产品网上商店:Mukako
2018/10/14 全球购物
美国健康和保健平台:healtop
2020/07/02 全球购物
广州某公司软件工程师面试题
2014/12/22 面试题
红旗团支部事迹材料
2014/01/27 职场文书
大学生社会实践自我鉴定
2014/03/24 职场文书
四年级学生评语大全
2014/04/21 职场文书
清正廉洁演讲稿
2014/05/22 职场文书
热门专业求职信
2014/05/24 职场文书
品质口号大全
2014/06/17 职场文书
小学竞选班干部演讲稿
2014/08/20 职场文书
冰峪沟导游词
2015/02/09 职场文书
售后服务质量承诺书
2015/04/29 职场文书
区域销售大会开幕词
2016/03/04 职场文书
Python中Numpy和Matplotlib的基本使用指南
2021/11/02 Python