by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11022 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、温故而知新
3年前有更新过JS对audio音频剪裁的实现,昨天更新了JS改变原始音频信息来实现音量的调整,今天我们再讲讲如何使用JS实现多个音频的拼接(下图1)与合并(下图2)。
拼接:
合并:
这下子,纯JS操作MP3/WAV音频资源的活儿都齐全了。
操作过程本质上大同小异。
ArrayBuffer转AudioBuffer,然后读取音频信息,对采样信息进行处理。
话不多说,来看看如何实现的。
二、多个音频的拼接
方法如下:
1
2const concatAudio = (arrBufferList) =\> {
3
4 const audioBufferList = arrBufferList;
5
6 const maxChannelNumber = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.numberOfChannels));
7
8 const totalLength = audioBufferList.map((buffer) => buffer.length).reduce((lenA, lenB) => lenA + lenB, 0);
9
10
11 const newAudioBuffer = audioContext.createBuffer(maxChannelNumber, totalLength, audioBufferList\[0\].sampleRate);
12
13 let offset = 0;
14
15 audioBufferList.forEach((audioBuffer, index) => {
16 for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
17 newAudioBuffer.getChannelData(channel).set(audioBuffer.getChannelData(channel), offset);
18 }
19
20 offset += audioBuffer.length;
21 });
22
23 return newAudioBuffer;
24}
语法:
1concatAudio(arrBufferList);
其中:
arrBufferList
指需要合并的AudioBuffer数组。
那如何获得音频的AudioBuffer数据呢?
可以使用 new AudioContext().decodeAudioData(arrayBuffer)
方法转换。
那如何得到arrayBuffer数据呢?
本地资源可以使用FileReader对象读取为ArrayBuffer,在线资源可以使用fetch请求获取。
这里演示使用Fetch API得到音频的AudioBuffer数据。
1
2const audioContext = new AudioContext();
3
4const getAudioBuffer = (src) =\> {
5 return new Promise((resolve, reject) => {
6 fetch(src).then(response => response.arrayBuffer()).then(arrayBuffer => {
7 audioContext.decodeAudioData(arrayBuffer).then(buffer => {
8 resolve(buffer);
9 });
10 })
11 })
12}
假设现在有下面两个音频地址:
1const audioSrc = \[
2 './assets/1.wav',
3 './assets/2.wav'
4\];
则使用下面几行JS就可以实现得到concat拼接后的新的音频的AudioBuffer数据了。
1const arrBufferList = await Promise.all(audioSrc.map(src => getAudioBuffer(src)));
有了AudioBuffer数据,我们就可以播放音频,或者转为可下载的音频格式。
眼见为实,您可以狠狠地点击这里:纯JS实现音频的合并或拼接demo
点击演示页面中的“拼接”按钮,如下图所示,此时,就可以看到在下面的色块区域内显示了拼接后的音频了:
三、多个音频的合并
音频的合并指的是多个音频同时播放,但是是合在一个视频中。
我也弄了个JS函数方法:
1
2const mergeAudio = (arrBufferList) =\> {
3
4 const audioBufferList = arrBufferList;
5
6 const maxDuration = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.duration));
7
8 const maxChannelNumber = Math.max(...audioBufferList.map(audioBuffer => audioBuffer.numberOfChannels));
9
10 const newAudioBuffer = audioContext.createBuffer(maxChannelNumber, audioBufferList\[0\].sampleRate \* maxDuration, audioBufferList\[0\].sampleRate);
11
12 audioBufferList.forEach((audioBuffer, index) => {
13 for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
14 const outputData = newAudioBuffer.getChannelData(channel);
15 const bufferData = audioBuffer.getChannelData(channel);
16
17 for (let i = audioBuffer.getChannelData(channel).length \- 1; i >= 0; i--) {
18 outputData\[i\] += bufferData\[i\];
19 }
20
21 newAudioBuffer.getChannelData(channel).set(outputData);
22 }
23 });
24
25 return newAudioBuffer;
26}
其中:
arrBufferList
指需要合并的AudioBuffer数组。
mergeAudio 方法的语法和具体的使用和上面的 concatAudio 方法类似,这里就不再赘述了。
demo也是同一个页面:纯JS实现音频的合并或拼接demo
点击“合并”按钮,就可以得到背景音乐和段落音频合二为一后的新的音频了(右键播放器可以下载此视频):
完整的实现代码均在demo页面上。
相信国内没有比我这里更直观更容易上手的演示代码了。
四、使用开源的项目
如果你对原生实现不怎么感兴趣,也可以使用开源项目。
其实还挺多的,之前找了一个,没记住,忘记了。
那就用这个项目吧,名字奇奇怪怪的,叫做crunker:https://github.com/jaggad/crunker
使用示意:
1let crunker = new Crunker();
2
3crunker
4 .fetchAudio('/song.mp3', '/another-song.mp3')
5 .then((buffers) => {
6
7 return crunker.mergeAudio(buffers);
8 })
9 .then((merged) => {
10
11 return crunker.export(merged, 'audio/mp3');
12 })
13 .then((output) => {
14
15 crunker.download(output.blob);
16 document.body.append(output.element);
17 console.log(output.url);
18 })
19 .catch((error) => {
20
21 });
如果你已经有了 AudioBuffer 资源,上面的 fetchAudio() 方法也是可以省略的。
另外,此JS还支持使用静音填充视频……嗯,直接剪裁不香么~
五、结束啦下节预告
好了,正文结束。
最近项目有用到,记录下,即方便以后的自己,也方便遇到类似需求的大家,毕竟国内类似的教程资源可不多见。
然后,预告下下篇文章内容,我都已经想好了。
已知 Shader、frag、vert 等资源,如何使用类似 P5.js 这样的JS库给图片资源应用对应的滤镜效果。
技术的成长就是这样子的,一点一点积累出来的。
三五年前,我看Web Audio API的时候,感觉是天书,妈呀,这么多概念,哪个是哪个啊。
但是接触多了,一个需求一个需求慢慢实现下来,反复了解其整个API体系,自然就懂得多了,遇到需求也就知道大致的实现思路。
之前玩过JS解析 3D LUT 滤镜,不过那还是2D层面,虽然效果好,但是性能不太行,所以,这次试试WebGL,之前还没做过类似的实践,好期待哦。
(本篇完)
是不是学到了很多?可以分享到微信!
有话要说?点击这里。