获取使用Web Audio API的音频可视化效果

2020-10-30 12:08:36

几个星期以来,我一直致力于让WebRTC视频聊天在网站上运行。我终于达到了文本、视频聊天和屏幕分享都工作得很好的地步,但在我脑海中的某个地方,我一直在想着在大流行期间对“缩放疲劳”的抱怨:

霍尔现在认为,变焦疲劳是真实存在的。“Zoom让人精疲力尽,也很孤独,因为你必须比打电话时更用心、更清楚地知道发生了什么。”如果你没有关掉自己的相机,你也在看着自己说话,这可能会让人兴奋和不安。时断时续、延误和断句也造成了混乱。还需要做更多的探索,但他说,“也许这不是我们认为的问题的解决方案。”相比之下,电话要求较低。“你可以呆在你自己的空间里。你可以散步,做饭,“霍尔说。

当您花费数周时间编写/调试/测试视频聊天代码时,脑海中浮现出这样的想法是一件很有趣的事情。

所以我决定添加一个纯音频模式。如果我要这么做,我必须展示一些很酷的东西来代替视频。因此,我想当其中一个用户或两个用户都没有打开视频时,我会尝试添加音频可视化效果。使用相对较新的1Web Audio API似乎是正确的选择。

要创建音频可视化,首先需要一个AnalyserNode,您可以从BaseAudioContext的createAnalyser方法中获得它。你可以很容易地得到这两样东西,就像这样:

接下来,使用AudioContext.createMediaStreamSource从现有数据流(我分别使用来自getUserMedia或RTCPeerConnection的‘Track’事件的本地或远程数据流)创建一个MediaStreamAudioSourceNode。然后,您可以将该音频源连接到分析器对象,如下所示:

Window.requestAnimationFrame很不错。调用它,传入绘图函数,然后在该函数内部再次调用requestAnimationFrame。让您自己进行一个漂亮的小递归循环,浏览器会自动对其进行正确的计时。

在我的情况下,将运行0、1或2个可视化效果,因为任何一方都可以选择视频聊天、纯音频(…。除了在屏幕共享期间),或者仅仅是文本聊天。所以我有一个循环来画这两个。它看起来是这样的:

我为这些可视化对象创建了类,它们处理是否绘制。它们每个都包含用于可视化的分析器、源和上下文对象。

然后,当我检测到该循环不再需要运行时,我可以使用该audioCancel值取消它:

就像在这个例子中一样,如果您查看MDN文档,您会看到很多东西,我提供了两种音频可视化的选项:频率条和正弦波。下面是我如何为每种类型配置分析器:

1开关(this.type){2个案例#39;:3个this.analyser.minDecibels=-90;4个this.analyser.maxDecibels=-10;5个this.analyser.平滑TimeConstant=0.85;6个this.analyser.fftSize=256;7个this.BufferLength=this.analyser.requencyBinCount;8个this.dataArray=new Uint8Array(this.BufferLength);9个中断;10个默认值:11个this.analyser.minDecibels=-90;12个this.analyser.maxDecibels=-10;13 this.analyser.mooth ingTimeConstant=0.9;14 this.analyser.fftSize=1024;15 this.BufferLength=this.analyser.fftSize;16 this.dataArray=new Uint8Array(this.BufferLength);17 Break;18}。

我已经对这些数字做了很多调整,而且我还会继续这样做。关于fftSize和requencyBinCount:requencyBinCount的注释是在设置fftSize之后立即设置的,它通常只是fftSize值的一半。这些值是关于您想要从我接下来要讨论的主要分析器函数接收的数据量。如您所见,它们直接控制数据数组的大小,您将使用该数组在每次绘制调用时存储音频数据。

在每次绘制调用时,根据可视化的类型,使用上面创建的数组调用getByteFrequencyData或getByteTimeDomainData,它将用数据填充。然后在每个元素上运行一个简单的循环并开始绘图。这是我的正弦波代码:

1this.analyser.getByteTimeDomainData(this.dataArray);2this.ctx.lineWidth=2;3this.ctx.strokeStyle=AudioSecond daryStroke;4 5this.ctx.eginPath();6 7个字母v,y;8 for(设i=0;i<;this.BufferLength;i++){9v=this.dataArray[i]/128.0;10y=v*高度/2;11 12 if(i==0){13 this.ctx.moveTo(x,y);14}Else{15 this.ctx.lineTo(x,y);16}17 18 x+=width*1.0/this.BufferLength;19}20 21 this.ctx.lineTo(width,Height/2);22 this.ctx.strok();

所以我做了所有我刚才谈到的事情,但是有几天我无法在Safari中使用这些东西。不是因为错误或其他原因,而是因为getByteFrequencyData和getByteTimeDomainData每次都用0填充数组。不管我做了什么。我可以很好地获取Firefox中的音频数据。

所以一开始,我想它在Safari中根本不起作用,我只能等到苹果修复它。但是后来我发现了这个示例音频项目,并注意到它在Safari中工作得很好。

所以我花了一个小时研究代码,试图理解我的代码和他们的代码有什么不同。我对我的代码做了很多修改,使其更像他们正在做的事情。最大的区别之一是,它们将音频源连接到不同的音频失真节点,以实际更改音频。我只想创建一个可视化效果,所以我没有使用这些对象中的任何一个。

WaveShaperNode:使用BaseAudioContext.createWaveShaper创建非线性失真。您可以使用自定义函数更改音频数据。

这些对象中的每一个都有一个连接函数,您可以在其中传递另一个上下文、输出或过滤器。每一个都有一定数量的输入和输出。以下是示例项目中将它们全部连接起来示例:

注意:如果您只是尝试为呼叫创建可视化效果,请不要连接到音频上下文目标。用户将听到自己的谈话。

不管怎样,我试着将这些东西添加到我的代码中,看看这是否能让它在Safari中工作,但我没有运气。

我开始对试图弄清楚这件事感到非常沮丧。当我认为Safari只是坏了(因为它通常是坏的)时,我想让它过去,但因为我知道它可以在Safari中工作,所以我不能让它一个人呆着。

最后,我从该示例下载了实际的HTML和Javascript文件,并开始从他们的代码中清除垃圾,在本地运行,看看它是否正常工作。它确实做到了。所以现在我在编辑我自己的代码,和他们的代码,让它们几乎是一样的。我确实做到了。尽管如此,他们的还是起作用了,而我的不起作用。

接下来,我开始拼命地在代码中的不同位置记录每个对象,以找出他妈的是怎么回事。然后我注意到一些东西。

国家被“停职”了吗?为什么?我不知道。我在示例代码(我已经下载并在我的机器上运行)中执行了相同的日志,它正在“运行”。

调用Resume会更改状态,然后一切都会正常工作。直到今天,我仍然不知道为什么示例代码不需要该行。

就像我网站上的其他东西一样,所有这些都必须支持不同的配色方案(以及屏幕大小和移动设备)。当试图在画布上绘制SVG时,这是出人意料的困难。

我正在使用FontAwesom作为我在网站上的所有图标。我想用它们中的一个来实现这些可视化。FontAwese文件都是SVG(这很棒),但是我不知道如何在Javascript中用不同的颜色绘制图像。我决定这样做的方法是将SVG文件加载到一个Javascript Image对象中,然后在每次绘制调用时将其绘制到画布上。

这很管用,但即使在更改了填充和笔触颜色之后,它也只将其绘制为黑色。因此,在一些网络搜索之后,我读到有人决定在屏幕外画布上画出一幅图像,读取所有图像数据,如果alpha通道大于0,则手动重写每个像素的图像数据。然后,实际的可视化代码就可以将图像从屏幕外的画布复制到真实的画布上。

所以我就是这么做的。但当然也有一个特定于浏览器的问题。但不是从萨法里来的!

事实证明,将SVG文件加载到Image对象(屏幕外)实际上并不填充Firefox中该对象的宽度和高度属性。在Safari中是这样的,这也是我用3测试过的。我实际上需要宽度和高度来做画布绘制操作。

因此,作为一种解决办法,我尝试加载SVG,如果对象没有宽度,则使用Pixelmator加载从SVG生成的PNG文件。以下是加载图像并将其绘制到画布的代码:

1audioImage.onload=()=>;{2 if(!audioImage.width){3 audioImage.src=';/static/image/microphone e.png';;4 return;5}6 7 audioCanvas.width=audioImage.width;8 audioCanvas.high=audioImage.high;9 10 const ctx=audioCanvas.getContext(';2d';);11 ctx.drawImage(audioImage,0,0);12 13 const svgData=ctx.getImageData(0,0,audioImage.width,audioImage.high);14 const data=svgData.data;15 for(设i=0;i<;data.length;i+=4){16if(data[i+3]!==0){17data[i]=parseInt(audioStroke.substring(1,3),16);18 data[i+1]=parseInt(audioStroke.substring(3,5),16);19data[i+2]=parseInt(audioStroke.substring(5,7),16);20}21}22 23ctx.putImageData(svgData,0,0);24};25 26audioImage.src=';/static/image/microhone.svg';;

在本例中,我知道AudioStrok值始终采用#000000的格式,所以我只是解析颜色并将它们写入数组。

如果您已经绘制过任何画布元素(特别是当您同时具有高和低DPI监视器时),您就知道在默认情况下它看起来分辨率相当低。我绘制的任何画布都会考虑window.devicePixelRatio。

其想法是调整画布的“实际”宽度以考虑屏幕像素比率,然后CSS将其调整回原始大小。因此,在高分辨率屏幕上(就像在任何Macbook中一样),window.devicePixelRatio将为2,因此您需要将画布的大小调整为宽度和高度的两倍,然后CSS将其大小缩小到您想要的大小。

这与在Retina屏幕首次面世时创建2倍图像的概念相同,这样它们就可以缩小尺寸,看起来更清晰。

1 const dpr=window.devicePixelRatio||1;2this.canvasRect=this.canvas.get边界ClientRect();3 4this.canvas.width=this.canvasRect.width*dpr;5this.canvas.high=this.canRect.high*dpr;6this.ctx=this.canvas.getContext(';2d';);7this.ctx.scale(DPR,DPR);8 9this.canvas.style.width=this.canvasRect.width+';PX';;10 this.canvas.style.high=this.canvasRect.high+';px';;

我存储canvasRect,以便可以将宽度和高度用于所有其他绘图计算。

我真的很喜欢这件事最终的结果。有几次我认为它在一些浏览器中会完全崩溃,有那么一瞬间我认为我必须放弃我的目标,让网站上的所有东西都能对颜色方案切换做出反应,但实际上我做了我想做的一切。

现在,我只需要不断地处理这些AnalyserNode值,直到得到看起来完美的东西。4.。

看起来这个API的早期Mozilla版本从2010年就已经出现了,但是苹果公司最近一直在努力制定这个官方的Web Audio API标准。请参阅他们的Safari技术预览版发行说明的版本115(截至我撰写本文之日的当前版本)。↩︎。

我一直在使用Google的这个“Adapter.js”填充程序来消除WebRTC对象与浏览器之间的差异,它对Web Audio API也很有帮助。一些浏览器仍然将AudioContext作为webkitAudioContext的前缀,所以如果您没有使用Adapter.js之类的内容,则必须执行new(window.AudioContext||window.webkitAudioContext)()。↩︎。

有趣的是,经过这么长时间的斗争和抱怨,我仍然在使用Safari开发Safari问题(其中有很多)。在这种情况下,很大程度上是因为Firefox在我进行WebRTC测试时运行我的粉丝。↩︎。

LOL。电脑是没有“完美”的。这项工作永远不会结束。在完全替换之前,我会处理所有这些代码。↩︎。

这篇文章对你有用吗?想要支持此网站吗?了解更多。谢谢你的阅读!

代码添加代码,以便您以后可以编辑或删除此注释。对多个注释使用相同的名称和代码会将它们链接在一起。了解更多。

{##**$$}通过发布评论,即表示您同意使用条款。