基于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 相关文章推荐
js模拟实现Array的sort方法
Dec 11 Javascript
javascript 字符 Escape,encodeURI,encodeURIComponent
Jul 09 Javascript
javascript实现的在当前窗口中漂浮框的代码
Mar 15 Javascript
动态加载图片路径 保持JavaScript控件的相对独立性
Sep 06 Javascript
javaScript面向对象继承方法经典实现
Aug 20 Javascript
jquery.post用法关于type设置问题补充
Jan 03 Javascript
javascript制作坦克大战全纪录(2)
Nov 27 Javascript
基于bootstrap3和jquery的分页插件
Jul 31 Javascript
JQuery实现简单的图片滑动切换特效
Nov 22 Javascript
举例讲解jQuery对DOM元素的向上遍历、向下遍历和水平遍历
Jul 07 Javascript
JavaScript学习小结之使用canvas画“哆啦A梦”时钟
Jul 24 Javascript
JS中offset和匀速动画详解
Feb 06 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遍历目录与文件夹的多种方法详解
2013/11/14 PHP
CentOS6.5 编译安装lnmp环境
2014/12/21 PHP
PHP自带ZIP压缩、解压缩类ZipArchiv使用指南
2015/03/03 PHP
解析WordPress中的post_class与get_post_class函数
2016/01/04 PHP
浅析PHP数据导出知识点
2018/02/17 PHP
php让json_encode不自动转义斜杠“/”的方法
2020/04/27 PHP
javascript 嵌套的函数(作用域链)
2010/03/15 Javascript
超简单JS二级、多级联动的简单实例
2014/02/18 Javascript
使用jQuery.wechat构建微信WEB应用
2014/10/09 Javascript
jQuery实现简易的天天爱消除小游戏
2015/10/16 Javascript
Bootstrap自动适应PC、平板、手机的Bootstrap栅格系统
2016/05/27 Javascript
jQuery插件EasyUI实现Layout框架页面中弹出窗体到最顶层效果(穿越iframe)
2016/08/05 Javascript
BootStrap tooltip提示框使用小结
2016/10/26 Javascript
jQuery实现文字自动横移
2017/01/08 Javascript
jQuery层级选择器实例代码
2017/02/06 Javascript
Vue header组件开发详解
2018/01/26 Javascript
React组件中的this的具体使用
2018/02/28 Javascript
jQuery实现图片上传预览效果功能完整实例【测试可用】
2018/05/28 jQuery
layDate日期控件使用方法详解
2018/11/15 Javascript
小程序二次贝塞尔曲线实现购物车商品曲线飞入效果
2019/01/07 Javascript
js实现弹幕飞机效果
2020/08/27 Javascript
详解ES6 中的Object.assign()的用法实例代码
2021/01/11 Javascript
python使用matplotlib绘制折线图教程
2017/02/08 Python
Python爬取当当、京东、亚马逊图书信息代码实例
2017/12/09 Python
python调用java的jar包方法
2018/12/15 Python
Python 实现加密过的PDF文件转WORD格式
2020/02/04 Python
Python wordcloud库安装方法总结
2020/12/31 Python
使用Python爬取小姐姐图片(beautifulsoup法)
2021/02/11 Python
使用CSS3制作响应式导航菜单的方法
2015/07/12 HTML / CSS
可自定义箭头样式的CSS3气泡提示框
2016/03/16 HTML / CSS
有影响力的人、名人和艺术家的官方商品:Represent
2019/11/26 全球购物
大学秋游活动方案
2014/02/11 职场文书
班子群众路线教育实践个人对照检查材料思想汇报
2014/09/30 职场文书
乡镇党员群众路线教育实践活动对照检查材料思想汇报
2014/10/05 职场文书
2015年街道除四害工作总结
2015/05/15 职场文书
Mysql排查分析慢sql之explain实战案例
2022/04/19 MySQL