前言
最近在做一个项目,需要对webRTC录制的音频进行处理,包括音频的裁剪、多音频合并,甚至要将某个音频的某一部分替换成另一个音频。
原本笔者打算将这件工作交给服务端去完成,但考虑,其实无论是前端还是后台,所做的工作是差不多的,而且交给服务端还需要再额外走一个上传、下载音频的流程,这不仅增添了服务端的压力,而且还有网络流量的开销,于是萌生出一个想法:为什么音频处理这件事不能让前端来做呢?
于是在笔者的半摸索半实践下,产生出了这篇文章。废话少说,先上仓库地址,这是一个开箱即用的前端音频剪辑sdk(点进去了不如就star一下吧)
ffmpeg
ffmpeg是实现前端音频处理的非常核心的模块,当然,不仅是前端,ffmpge作为一个提供了录制、转换以及流化音视频的业界成熟完整解决方案,它也应用在服务端、APP应用等多种场景下。关于ffmpeg的介绍,大家自行google即可,这里不说太多。
由于ffmpeg在处理过程中需要大量的计算,直接放在前端页面上去运行是不可能的,因为我们需要单独开个web worker,让它自己在worker里面运行,而不至于阻塞页面交互。
可喜的是,万能的github上已经有开发者提供了ffmpge.js,并且提供worker版本,可以拿来直接使用。
于是我们便有了大体的思路:当获取到音频文件后,将其解码后传送给worker,让其进行计算处理,并将处理结果以事件的方式返回,这样我们就可以对音频为所欲为了:)
开启美妙之旅前的必要工作
需要提前声明的是,由于笔者的项目需求,是仅需对.mp3格式进行处理的,因此下面的代码示例以及仓库地址里面所涉及的代码,也主要是针对mp3,当然,其实不管是哪种格式,思路是类似的。
创建worker
创建worker的方式非常简单,直接new之,注意的是,由于同源策略的限制,要使worker正常工作,则要与父页面同源,由于这不是重点,所以略过
function createWorker(workerPath: string) {const worker = newWorker(workerPath);returnworker;
}
postMessage转promise
仔细看ffmpeg.js文档的童鞋都会发现,它在处理音频的不同阶段都会发射事件给父页面,比如stdout,start和done等等,如果直接为这些事件添加回调函数,在回调函数里去区分、处理一个又一个音频的结果,是不大好维护的。个人更倾向于将其转成promise:
functionpmToPromise(worker, postInfo) {return new Promise((resolve, reject) =>{//成功回调
const successHandler = function(event) {switch(event.data.type) {case "stdout":
console.log("worker stdout: ", event.data.data);break;case "start":
console.log("worker receive your command and start to work:)");break;case "done":
worker.removeEventList