关于Web Audio中Analyzer输出的频率数据分布问题

2022-08-19 / WebAudio API

随着iOS 16 beta 5的更新,细心的小伙伴可能注意到了,系统在播放音乐时,音乐控制台的右上角多了一个小小的频谱显示器。对我这种喜欢音乐的人来说,看着它随着音乐一起动起来,心情似乎也变得好起来。那么问题来了,浏览器上是否可以实现这样的功能?答案是可以,但是似乎有一点小问题。


我们先抛开顾虑,直接查MDN堆代码看看效果。

<div id="app">
<audio id="music" src="test-2.mp3"></audio>
<button @click="play">play</button>
<div style="display: flex; height: 60px; align-items: flex-end; margin-top: 20px">
<div class="item" v-for="item in fData" :style="{height: item / 255 * 100 + '%'}"></div>
</div>
</div>

<script src="vue.js"></script>
.item {
width: 6px;
min-height: 6px;
background: #333333;
align-items: flex-end;
border-radius: 6px;
margin-right: 3px;
}
new Vue({
el: '#app',
data: function () {
return {
fData: []
}
},
methods: {
play() {
let _ = this
let audio = document.getElementById('music')
audio.play()

let audioContext = new AudioContext()
let audioSrc = audioContext.createMediaElementSource(audio)
let analyzer = audioContext.createAnalyser()
analyzer.fftSize = 32

audioSrc.connect(analyzer)
analyzer.connect(audioContext.destination)

let bufferLength = analyzer.frequencyBinCount
let frequencyData = new Uint8Array(bufferLength)

setInterval(() => {
analyzer.getByteFrequencyData(frequencyData)
_.fData = _.uint8ArrayToArray(frequencyData)
}, 1000 / 24)
},
uint8ArrayToArray(uint8Array) {
let array = []

for (let i = 0; i < uint8Array.byteLength; i++) {
array[i] = uint8Array[i]
}

return array
}
}
})

结果:

这样一通操作下来,一个简单的频谱显示器就完成了(老有成就感了),大概原理也很简单:

  1. 创建一个音频 上下文Audio Context
  2. 使用上下文映射 <audio> 并赋值为 audioSrc
  3. 创建 分析仪 节点(Analyzer Node
  4. audioSrc 连接 分析仪分析仪 再连接到 ctx.destination 即用户端输出
  5. 图形化 分析仪 的数据

现在,真正的问题来了:不管我尝试什么音乐,我的频谱图永远都是这样的 ⬇️(成就感啪一下没了)

Untitled

可能会有一些小差异,但是呈现的效果基本差不多:低频看起来很足,高频几乎没有

这时候,作为一名“准专业编曲师”,我有一种直觉,Web Audio API 给出来的频谱数据,可能是线性分布的。为了验证我的猜想,我制作了一段从 30Hz 到 20000Hz 慢慢上升的正弦波音频,调高分析仪的 fftSize ,放进代码里看看输出:

再来看看同样的音频在 Ableton Live 中 Spectrum 线性模式下的输出:

这表现可以说,基本一致,同时也验证了我的猜想 —— Web Audio API 输出的频谱数据是线性分布的。线性分布是什么分布?很好理解:一个坐标系中,X轴上10到20需要走的距离,与2000到2010需要走的距离一样。然而对于人来讲,线性分布这个解决方案,并不是最优的。我们可以先看一些专业EQ插件的截图,看看他们是怎么做频谱分布的。

FabFilter Pro Q3:

Untitled

Eiosis Air EQ Premium:

Untitled

SlateDigital Inf EQ:

Untitled

Ableton Live EQ Eight

Untitled

可以看到,这些分布基本都是在类似指数分布的频率上进行分段式的对数分布,而且似乎是遵循着等响曲线做出的优化。

我们经常听到“指数级增长”这个词,举个简单的例子:10、100、1000、10000,这个数列就是指数增长。上述频谱图中,频率分布基本是按照指数来的。

关于对数我们不需要了解太多,对数分布也可以根据上面我对线性分布的理解做出延伸:一个坐标系中,X轴上10到20需要走的距离,与2000到2010,同样是10的差距,但是需要走的距离却一样。如果我们以Ableton Live更加青睐的分布规则,把频谱分析仪的可视化面板看作是一个坐标系,则 X 轴上,10 - 10000 被分成三段,分别为10 - 100、100 - 1000、1000 - 10000,每一段距离都一样且被分成9份进行对数分布。

说到等响曲线,它并不像对数那样客观,而是一个主观概念。此概念阐述的内容如果用大白话来讲就是:同样音量但不同频率的声音,给人听起来的强弱是不一样的。即:60Db 的 4000Hz 正弦波与 60Db 的 10000Hz 正弦波,前者听起来会更响一些。此概念也同样可以用来解释,在删除歌曲 10kHz 以上部分的时候(可以理解为对半切),你不会觉得整首歌的质量下降了50%,主观上听起来可能只有10%。

个人认为,频谱这样的分布模式,其实是为了弥补人耳的缺点,在视觉上模拟出与听觉类似的“缺陷”,真正做到音视一致从而提升用户体验。


关于如何在浏览器里实现,其实很简单,我们只需要模拟指数级分布就好,不用去考虑分段中的对数分布。演示地址: https://jw1.dev/frequency-test/test-2.html

现在再来看一下对比:

Untitled

Untitled

哪个体验更好,一眼看过去便知道了。

至此,一个不算完美但是看起来还不错的频谱显示器就算是完成了,我也给 webAudio 开源项目提了issue,希望未来能够添加支持,为开发者带来更佳的体验!

✌️

觉得文章不错?
使用微信扫一扫,给我一个赞赏吧!
🥺
我想用 Google Analytics 来分析您在此网站的操作,请问可以吗?