导语:
下面将分为两个部分来分享这个频谱是如何通过H5来实现的
本案例是通过声音振幅绘制的频谱图,如果对频谱图有其他严格要求的,本案例并不适用
此处借鉴了网易云音乐前端技术团队在掘金分享的文章,以及MDN官网教程,在最后会分享地址链接
1.声音与声音存储
2.H5实现的思路
高中物理告诉我们,声音是通过介质的震动产生的,并且经过科学家们的总结归纳,总结出了影响声音的几大因素,分别是频率,振幅,音色。
频率是指单位时间内,物体震动的次数就叫做频率;
振幅是指物体在一个震动周期内震动过程中达到最大的值,这就是振幅;
音色是用来表示物体震动时产生独有特性的声波的这个特性,被称为音色;
以上这些因素就是决定一个声音的条件,所以如果想要将声音保存下来,只需要随着时间将以上这些因素用一种方式保存下来就可以了。所以,我们的科学研究者们就想了一个办法,利用一些设备采集声音震动时产生的声波,并根据振幅转换成一段与该声波振幅对应的强弱电信号,再将这些变化的电信号转换成编码存储起来。
不过这里需要注意一点,采样脉冲频率一般为44kHz,这个根据人所能听到的声音的频率在20kHz,同时声波要描述出波峰波谷,那么在采样时就要在20kHz两倍或者两倍以上才可以恢复原来的声音。
至于再具体一点,这里我们就没必要进行深入探索了,只要大概了解这些就够了。
想要绘制频谱图,就一定要有数据,所以第一件事情,我们要想办法,拿到描述声音振幅的数据。
声音编码数据的获取我只找到了两种方式,一种是通过FileReader来获取本地音频数据,另一种就是通过ajax请求网络音频数据。
鉴于目前网络上鲜有利用ajax请求来获取音频数据的案例,同时实际项目中又是以网络音频资源进行渲染的,所以我这里就用请求的方式来进行举例。
思路:
1、获取数据
//请求要注意跨域问题
fetch('audioURL').then(res=>{
//这里一定要设置成arraybuffer; 对应ajax中responseType配置
return res.arraybuffer();
}).then(res=>{
//注意这里的audioData并不是我们最终要获取的音频数据,还需要进行解码
const audioData = res;
}).catch(e=>{
console.log(e);
})
通过这种请求的方式,我们可以获取到音频的arraybuffer格式的数据,但是这并不是我们想要得到的最终结果,audioData是获取真正的音频数据的关键参数
如果你已经通过请求得到了一个arraybuffer的数据,那么就可以继续获取音频数据了,
我们下面是通过AudioContext来实现
const audioCtx = new AudioContext();
//创建环境
audioCtx.decodeAudioData(audioData,(buffer)=>{ //buffer就是我们要获取的数据
// 以下是音频控制的方式,并不是绘制频谱必须的,也可以使用audio标签事件来代替控制方案
const dataSource = context.createBufferSource();
dataSource.buffer = decodedBuffer;
//连接播放节点
dataSource.connect(context.destination);
//开始播放音频
dataSource.start();
})
这里解决一个疑问,
当我们获取到了buffer,一般也是不直接使用的,原因是buffer数据量庞大,为了渲染结果美观(为了还原设计稿)会利用算法进行数据简化,但是简化后的数据表现出来的频谱图的准确性也会随之降低,可以根据自己的需要调整算法;
2.简化数据
const switchData = () => {
//获取buffer长度
const { numberOfChannels, sampleRate, length} = buffer;
//这个参数可以通过获取传递进来,canvas画布的宽度
const canvasWidth = 1800;
//canvas每一个展示单元的宽度
const unitWidth = 2;
//canvas每一个展示单元间的间距
const unitSpacing = 1;
//共需要多少份
const total = ~~(canvasWidth/(unitWidth + 2 * unitSpacing));
// 每一份的点数
const sampleSize = ~~(length/total);
const first = 0;
const last = total;
const peaks = [];
// 取左声道数据
const chan = buffer.getChannelData(0);
for (let i = first; i < last; i++) {
const start = i * sampleSize;
const end = start + sampleSize;
let min = 0;
let max = 0;
for (let j = start; j < end; j ++) {
const value = chan[j];
if (value > max) max = value;
if (value < min) min = value;
}
}
// 波峰
peaks[2 * i] = max;
// 波谷
peaks[2 * i + 1] = min;
return {peaks,unitWidth,unitSpacing};
}
上面是的函数是为了获取到与频谱图需要展示的波峰波谷相对应的数据数组,取值范围为[1,-1],这个数据非常直观的描述了音频振幅的状态,下面就可以利用这个数据进行绘制频谱了
3.canvas绘制频谱图
const { peaks,unitWidth,unitSpacing } = switchData();
//用来绘制波峰
const maxCanvas = document.queryselector('.maxCanvas');
//用来绘制波谷
const minCanvas = document.queryselector('.minCanvas');
// 这里假设画布高度,值可以通过获取传入进来
const canvasHeight = 50;
const maxCtx = minCanvas.getContext('2d');
const minCtx = maxCanvas.getContext('2d');
maxCtx.fillStyle = 'rgba(255,255,255,255)';
minCtx.fillStyle = 'rgba(255,255,255,255)';
peaks.forEach((index,item)=>{
if(item>0){
const x = (spacing * 2 + unitWidth) * (index/2) + spacing;
const y = 80 * (1 - item) >= canvasHeight - 1 ?
canvasHeight - 1 :
canvasHeight * (1 - item);
const height = canvasHeight * item < 1 ? 1 : canvasHeight * item;
maxCtx.fillRect(x,y,width,height);
}
else{
const x = (unitSpacing * 2 + unitWidth) * ((index - 1)/2) + spacing;
const y = 1;
const height = -80 * item < 1 ? 1 : -80 * item;
minCtx,fillRect(x,y,width,height);
}
maxCtx.fill();
minCtx.fill();
})
此版本的频谱图,是画布宽度来展示的,当画布宽度小时,展示的频谱丢失的采样数据会很多,导致频谱不能准确反应声音变化。但是可以通过动态的缩放画布,来实时渲染不同数量的采样数据下的频谱;
以上是整个过程,修改了之前博客中出现的错误,简化了流程;
以上的全部实现均参考了网络中现有的资料实现的,以下将参考资料地址分享给大家。
关于canvas教程MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_usage
关于AudioContext内容MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/AudioContext
关于音频频谱教程:https://juejin.im/post/5e1689b0f265da5d14242c6c