在绘制大音频波形的场景(目前只支持 wav 格式),急着用的同学点这里,如果有问题可以联系我,我会尽快修复。github: https://github.com/CofeeWithR…
效果图
传统的音波图渲染流程是 ajax 完整加载音频,使用 audioContext 解码完整的音频,下载解码后的数据使用 canvas 绘制,这种方式当音频较小时是没有问题,但遇到超大音频(超过 100M)时会出现从加载到渲染需要很长的时间(3~8)s。
我们是否能够从下载到绘制逐步地进行,从而做到既不会因占用大量的内存与集中的大量数据绘制造成卡顿,又有快速的响应(用户可以看到加载绘制的过程)。下面主要从下载、解码、绘制三个方面来研究其实现的可行性。
增量的数据下载
XHR 与 fetch
XHR 有 responseType 属性,支持 “” | “arraybuffer” | “blob” | “document” | “json” | “text” 6 种值,参照 MDN 若是 “arraybuffer”|”blob”,在 onprogress 回调中获取的 response 的值为 nul,这样就不能下下载过程中边下载,边绘制。
参照下面代码:
const xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onprogress = data => {console.log( 'data:',data);
console.log('onprogress response:', xhr.response && xhr.response.byteLength);
}
xhr.onloadend = data => {console.log('load end..');
}
xhr.open('get','/source/test.wav');
xhr.send();
以上是使用 xhr 获取音频文件,只有在最后一次的 onprogress 回调中,获取到完整的数据,不能增量的获取数据,达到我们增量的绘制目的。
fetch
fetch API 支持对管道的管道操作与取消. 看下面的例子:
fetch('/source/test.wav').then((val: Response) => {if(val.body){
// 从 Response 中获取可读流的 reader,用于从管道中读取数据。const reader: ReadableStreamReader = val.body.getReader();
readData(reader);
}
})
function readData(reader: ReadableStreamReader){
// 从管道中读取数据。reader.read().then(data => {if(!data.done){
// data.value 为一个 Uint8Array.
console.log(data.value.byteLength);
// 若还有数据,继续读取。readData(reader);
}
})
};
fetch 返回的 Promise 中返回的是一个 Response 对象,最终我们可以在 Response 的 body.getReader() 获取到一个 ReadableStreamReader,通过该对象我们可以读取网络管道中的数据片段,以满足我们增量渲染数据的需求。ReadableStreamReader 的 read 对象可以获取一个返回值包含 ArrayBuffer 对象的 Promise.
自此,就可以增量的从网络管道中获取数据。
音频的增量解码
在浏览器中,提供了对音频处理的 audioContext 对象。它提供了一个解码音频的 Api decodeAudioData, 但不幸的是它只支持完整音频的解码。所以解码变成了完成增量渲染的核心部分。
为简单,目前只对 wav 音频做解码支持。由于 WAV 格式的特殊性,实现的总体思想也相当简单:
1、wav 文件分为文件头部与数据部分,于是将 wav 文件的头部取出,将数据部分分割为多段。
2、当解码时,将文件头稍作修改与数据片段组合成完整音频,再使用 decodeAudioData API 进行解码。
wav 文件
为实现 wav 文件的分割与重组,先了解 wav 音频文件的组成 wav 文件说明。
在文档中,给出了详细的定义与说明,其中关键的部分做下介绍:
摘自 http://soundfile.sapp.org/doc…
由 wav 文件的结构可以看到,只需要对 data 块的 size 进行修改,就可以使用该头部与数据部分进行拼接,形成新的被切割后的文件。
绘制
解码后会得到 AudioBuffer 对象,可获取的数据为 -1~1 的浮点数。
使用 canvas 的 Context.beginPath, 即可进行对数据的绘制。