音频可视化-简单实现

4,041 阅读4分钟

离职的第一天,刷到了一个音频可视化的帖子,简单的学习了一下来跟大家分享一下我的学习成果。第一次写帖子大家望大家多指点

理解

首先音频处理需要跟多环节例如修音、混响等,每一个环节就是一个节点,每一个环节都有一个音频的源节点,例如下图的首先获取一个音频源节点,第一个环节去处理他的音色,在下一步在新引入一个音频源节点例如鼓点,节拍之类的在第二个环节添加进去最后通过输出设备播放出来就此完成了整个流程,节点的数量是可以任意数量的。 音频上下文就是所有的节点都连接起来后形成的一个环境就可以理解为音频上下文,相当于一个音频上下文为一个厂房,每个环节就是这个厂房里面运行生产设备的的每个流程节点

音频节点截图.png 在本文音频可视化中不需要太多的节点,我们只需要将音频源获取进来通过分析器在交给输出设备进行播放,播放时分析器一直分析他的波形图在讲分析完其波形数据交给canvas绘制,进行可视化显示

分析器截图.png 分析器节点的功能通过(快速傅里叶变换[FFT])将现实中的时域图变成频率图

傅里叶变换.png

实现

接下来进行实现,首先创建上下文,拿到音频源,音频源与分析器连接,分析器在与扬声器连接进行输出,这样就形成了一个音频源到扬声器的输出通路

 //创建音频上下文(从音频源到输出整个一个区间)
    const audCtx=new AudioContext()
    //创建音频源节点 audioEle为获取的audio数据
    const source=audCtx.createMediaElementSource(audioEle)
    //创建一个AnalyserNode(分析器节点),分析器节点的功能通过(快速傅里叶变换)将现实中的时域图编程频率图 用于可视化展示音频
    analyser = audCtx.createAnalyser()
      //将音频源与分析器链接
    source.connect(analyser)
     //将分析器与destination链接,这样才能形成到达扬声器的通路(音频渲染的最终目标节点,一般是音频渲染设备,例如扬声器)意思就是将分析器跟扬声器连接形成通路
    analyser.connect(audCtx.destination)

下一步就是通过对分析器进行操作,分析器节点的功能通过(快速傅里叶变换)将现实中的时域图变成频率图

//FFT窗口大小的选择应根据信号的采样率和处理要求进行(必须是从 32 到 32768 范围内的 2 的非零幂)想要的显示的柱状图越多
    analyser.fftSize=512;
    //定义一个8位无符号整型数组,他的长度为波形的一半,因为波形的前后一半和后一半是相同的,所以获取一半即可此处用的是analyser中的方法,便于理解也可以使用analyser.fftSize/2结果是一样的
    dataArray=new Uint8Array(analyser.frequencyBinCount)

isInit参数是在播放之前为false,防止初次加载报错,这里循环操作使用requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销。每次循环时第一步将画布清空,getByteFrequencyData方法是将播放时分析器分析出的数据存入dataArray中

function draw(){
    //循环操作
    requestAnimationFrame(draw)
    
    //清空画布
    const{width,height}=cvs
    ctx.clearRect(0,0,width,height);
    //初次加载的时候防止报错没播放时跳出
    if(!isInit){
        return
    }
    //让分析器节点分析出的数据到数组中
    analyser.getByteFrequencyData(dataArray)
    console.log(dataArray);
}

成功打印结果

分析数据成功.png 最后一步就是根据数据绘制图表,我这边绘制的为柱状图,如果有需要其他类型图表可以自行选择

//因为人耳能接收到的频率是有限的,所以后面有一部分频率我们是看不到,将len长度/2.5是为了舍去后面几乎听不到的数据从而实现更好的展示。下面就是循环绘制canvas了
const len=dataArray.length/2.5
    const barWidth=width/len
    ctx.fillStyle='#78c5F7'
    for(let i =0;i<len;i++){
        const data=dataArray[i]
        const barHeight=data/255*height
        const x=i*barWidth
        const y =height-barHeight
        ctx.fillRect(x,y,barWidth-2,barHeight)
    }

最终效果