基于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 相关文章推荐
javascript实现上传图片前的预览(TX的面试题)
Aug 20 Javascript
10个基于jQuery或JavaScript的WYSIWYG 编辑器整理
May 06 Javascript
Juqery Html(),append()等方法的Bug解决方法
Dec 13 Javascript
基于KMP算法JavaScript的实现方法分析
May 03 Javascript
利用js实现遮罩以及弹出可移动登录窗口
Jul 08 Javascript
js获取select标签的值且兼容IE与firefox
Dec 30 Javascript
深入分析js的冒泡事件
Dec 05 Javascript
jQuery Validate验证表单时多个name相同的元素只验证第一个的解决方法
Dec 24 Javascript
bootstrap table 表格中增加下拉菜单末行出现滚动条的快速解决方法
Jan 05 Javascript
BootStrap Fileinput上传插件使用实例代码
Jul 28 Javascript
详解react native页面间传递数据的几种方式
Nov 07 Javascript
详细讲解如何创建, 发布自己的 Vue UI 组件库
May 29 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
linux环境apache多端口配置虚拟主机的方法深入介绍
2013/06/09 PHP
如何使用Gitblog和Markdown建自己的博客
2015/07/31 PHP
Yii数据库缓存实例分析
2016/03/29 PHP
JavaScript网页制作特殊效果用随机数
2007/05/22 Javascript
jquery如何改变html标签的样式(两种实现方法)
2013/01/16 Javascript
原生javascript模仿win8等待提示圆圈进度条
2014/04/24 Javascript
js 弹出新页面避免被浏览器、ad拦截的一种新方法
2014/04/30 Javascript
控制文字内容的显示与隐藏示例
2014/06/11 Javascript
jquery-tips悬浮提示插件分享
2015/07/31 Javascript
nodejs加密Crypto的实例代码
2016/07/07 NodeJs
KnockoutJS 3.X API 第四章之表单textInput、hasFocus、checked绑定
2016/10/11 Javascript
javascript正则表达式模糊匹配IP地址功能示例
2017/01/06 Javascript
js制作可以延时消失的菜单
2017/01/13 Javascript
JavaScript函数节流和函数防抖之间的区别
2017/02/15 Javascript
nodejs根据ip数组在百度地图中进行定位
2017/03/06 NodeJs
从零开始做一个pagination分页组件
2017/03/15 Javascript
python中urllib模块用法实例详解
2014/11/19 Python
Python数据结构之单链表详解
2017/09/12 Python
Django实现简单分页功能的方法详解
2017/12/05 Python
Python3.5常见内置方法参数用法实例详解
2019/04/29 Python
PyTorch的深度学习入门教程之构建神经网络
2019/06/27 Python
python基于json文件实现的gearman任务自动重启代码实例
2019/08/13 Python
解决python 上传图片限制格式问题
2019/10/30 Python
Python字符串的修改方法实例
2019/12/19 Python
python让函数不返回结果的方法
2020/06/22 Python
python 爬虫如何实现百度翻译
2020/11/16 Python
CSS伪类与CSS伪元素的区别及由来具体说明
2012/12/07 HTML / CSS
HTML5新增加的功能详解
2016/09/05 HTML / CSS
资深生产主管自我评价
2013/09/22 职场文书
学前教育专业毕业生自荐信
2013/10/03 职场文书
2014政务公开实施方案
2014/02/19 职场文书
材料专业毕业生求职信
2014/02/26 职场文书
2014年人大工作总结
2014/12/10 职场文书
雨雪天气温馨提示
2015/07/15 职场文书
生鲜超市—未来中国最具有潜力零售业态
2019/08/02 职场文书
《飘》英文读后感五篇
2019/10/11 职场文书