首页 > 编程语言 > 基于Web Audio API实现音频可视化效果
2020
10-08

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

网页音频接口最有趣的特性之一它就是可以获取频率、波形和其它来自声源的数据,这些数据可以被用作音频可视化。这篇文章将解释如何做到可视化,并提供了一些基础使用案例。
基本概念节
要从你的音频源获取数据,你需要一个 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();

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

在这里插入图片描述

源码:

<!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实现音频可视化内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧