关于音频的内容,我边学习,边实践也总结了一些,从最开始实现一个简单的web音乐播放器的自定义工具栏,到后来的实现简单的音频频谱图.直到今天的对音频数据进行的进一步操作,我也是一点点的在进步.虽然很多地方,并不是那么完美和准确,但是希望能和大家能共同学习进步.
一、准备工作
声音的基本原理
首先关于音频的一些原理性的内容,在之前的关于音频频谱的博客中已经简单介绍过了,这里就不详细展开来说了
前端操作内存的方法
但是如果要实现对音频PCM数据的修改,只是了解了声音的原理,恐怕还是有点不太够用.
这里还需要了解es6中提供的arrayBuffer以及其视图的相关内容,这里推荐阮一峰老师的es6入门这本书的关于arrayBuffer这个章节,进行简单的了解
web audio API
这方面的内容,也是很重要的一部分内容、但是由于web Audio API实在太多了,往往在用的时候再去看,已然是来不及了.那个时候往往是一头雾水,面对海量API无从下手,不知道从哪里入手.所以推荐利用空闲时间、从最基础的api看起.慢慢会发现,真正常用的功能所需要的,并不是特别多.
推荐MDN关于这一部分的介绍:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API
二、音频处理
如果想要快速实现一些针对音频PCM数据的处理工作,推荐使用crunker这个方法库,虽然功能有限,但是对于获取音频二进制数据、合并、拼接等工作,安装方便、使用简单.重要的是性能真的不错.
npm地址:https://www.npmjs.com/package/crunker
音频裁剪
原理:音频裁剪,其实就是对音频数据的裁剪,并拷贝出来,创建一份新的音频数据.这就是音频的裁剪.
//获取audioBuffer数据的方式之一
fetch(audioUrl)
.then(res => res.arrayBuffer())
.then(buffer => { // 获取音频二进制数据
const audioCtx = new audioContext();
audioCtx.decodeAudio(buffer)
.then(audioBuffer => { // 获取音频解码后的audioBuffer数据
// to do sth
})
});
/**
* 音频裁剪
* @param audioBuffer 待裁剪的数据
* @param duration 音频总时长
* @param startOffset 裁剪偏移时间,单位s
*/
clipAudio(audioBuffer,duration,startOffset = 0){
return new Promise((resolve,reject) => {
// 获取音频通道数量
const channels = audioBuffer,.numberOfChannels;
// 获取采样率
const rate = audioBuffer,.sampleRate;
// 计算截取后需要的采样数量
const endOffset = rate * duration;
const frameCount = endOffset - 0;
// 创建新的audioBuffer数据
const newAudioBuffer = new AudioContext().createBuffer(channels,frameCount,rate);
// 创建Float32的空间,作为copy数据的载体
const anotherArray = new Float32Array(frameCount);
// 裁剪后放置的起始位置
const offset = 0;
// 遍历通道,将每个通道的数据分别copy到对应的newAudioBuffer的通道
for(let channel = 0; channel < channels;channel++){
audioBuffer.copyFromChannel(anotherArray, channel, rate * startOffset);
newAudioBuffer.copyToChannel(anotherArray, channel, offset);
}
// 完成裁剪
resolve(newAudioBuffer);
})
}
音频音量调整
fetch(url)
.then(res => res.arrayBuffer)
.then(buffer => {
const audioCtx = new AudioContext();
audioCtx.decodeAudioData(buffer)
.then(audioBuffer => {
//获取声道数量
const channels = audioBuffer.numberOfChannels;
// 更改每个通道内的数据
for(let channel = 0; channel < channels; channel++){
const channelData = audioBuffer.getChannelData(channel);
for(let j = 0; j < channelData.length; j++){
channelData[j] = channelData[j] * volumn; // volunm [0,1]
}
}
})
})
除了音频裁剪之外的功能,可以利用Crunker这个第三方的库来实现.具体实现方式,可以参考crunker源码
安装
npm i crunker -S
Example
let audio = new Crunker();
audio.fetchAudio("/audio/url/1.m\p3","/audio/url/2.mp3")
.then(buffers => {
// => [AudioBuffer,AudioBuffer]
audio.mergeAudio(buffers); // 将两个音频进行合成(非拼接)
})
.then(merged => {
// => AudioBuffer
audio.export(merged,"audio/mp3");
})
.then(output => {
// => {blob,element,url}
audio.download(output.blob);
document.addpend(output.element);
console.log(output.url);
})
.then(error => {
// => Error Message
})
audio.notSupported(() => {
// Handle no brower support
})
API
crunker.fetchAudio(soneURL,anotherSongURL)
获取音频buffer数据
const crunker = new Crunker();
crunker.fetchAudio(url1,url2,url3,...)
.then(buffers => {
// 获取到全部音频的buffer数据,buffers为数组
})
crunker.mergeAudio(arrayOfBuffers)
合成音频为一个buffer
const resultBuffer = crunker.mergeAudio([buffer1,buffer2,buffer3,...]);
crunker.concatAudio(arrayOfBuffers);
拼接音频为一个buffer
const resultBuffer = crunker.concatAudio([buffer1,buffer2,buffer3,...]);
crunker.export(buffer,type)
导出blob和url对象
type为可选参数:‘audio/mp3’, ‘audio/wav’, ‘audio/ogg’
const blob = crunker.export(audioBuffers, "audio/mp3");
crunker.download(blob,fileName)
自动下载
crunker.play(blob)
开始播放
audio.notSupported(callback)
如果用户浏览器不支持Web audio API ,自行定义代码.