web 的能力随着移动端的发展被扩展的越来越好,这篇记录想记录下使用 Web Audio API 进行录音分析的过程。所使用的浏览器为 Chrome 最新版本。
概念
Web Audio API 使用户可以在音频上下文(AudioContext)中进行音频操作,具有模块化路由的特点。简单来说使用 Web Audio API 就是创建一个 AudioContext 在此基础环境上进行一系列的后续操作。
AudioContext
声明一个 AudioContext:
this.audioContext = new AudioContext;
创建录音/获取录音对象
使用浏览器的录音能力,需要获取用户的权限,才能调用。
window.navigator.mediaDevices.getUserMedia({
audio: true,
}).then(stream => {
// audioInput 表示 音频源节点
// stream是通过navigator.getUserMedia获取的外部(如麦克风)stream音频输出,对于这就是输入
this.audioInput = this.audioContext.createMediaStreamSource(stream);
// createMediaStreamSource()方法用于创建一个新的 MediaStreamAudioSourceNode 对象, 需要传入一个媒体流对象(MediaStream对象), 然后来自MediaStream的音频就可以被播放和操作。
this.stream = stream;
}).catch((err) => {
console.log('发生错误');
}).then(function() {
this.audioInput.connect(this.recorder); // 音频源节点连接音频处理节点
this.recorder.connect(this.audioContext.destination); // 音频处理节点连接音频渲染设备
});
通过 mediaDevices.getUserMedia
会在此时弹出授权窗口,用户授权之后就可以开始调用 recorder
上的能力。
createScriptProcessor 方法
创建一个ScriptProcessorNode用于通过JavaScript音频处理节点,具体参数见
this.recorder = this.audioContext.createScriptProcessor(4096, 1, 1);
// onaudioprocess 方法是 在一次达到设定的创建scriptNode时缓冲区大小时就出发一次
this.recorder.onaudioprocess = (e) => {
// getChannelData返回 Float32Array 类型的 pcm 数据
const data = e.inputBuffer.getChannelData(0);
pcmData.push(data);
audioSize += data.length;
}
这样我们就能实时的获取到用户的录音的PCM内容,经过处理之后就可以播放使用。
注意
在某些项目中,需求可能需要对用户的录音内容进行语音识别,在目前的市场上的大多数语音识别接口只支持 16K 的采样率的 wav 音频。然而目前的 chrome 浏览器的采样率是只读状态且不能修改(修改无效),所以需要在每一次onaudioprocess
触发的时候对 PCM 数据进行相应的转化处理,来达到使用要求。
推荐一个转化采样率的方法: Recorder
中源码
已有音频的实时分析
实时获取录音的内容经过上面的操作已经可以实现,在笔者的需求中还需要给用户一个音频处理实例,针对一个已有的音频进行实时分析。所以就需要实时获取到音频然后播放,且获取到正在播放的PCM数据。
// 一定要把 context source 的定义写在异步方法之前,否则在 safari 浏览器无法触发 source 的一些事件
const context = new (window.AudioContext || window.webkitAudioContext)();
const source = context.createBufferSource();
axios.get('音频URL', { responseType: 'arraybuffer' }).then((res) => {
const blob = new Blob([res.data], { type: 'audio/wav' }); // 实时分析wav文件,但是这个文件经过 decodeAudioData 之后是没有 wav 头信息的,如果需要请自行加上
const read = new FileReader();
read.onloadend = () => {
context.decodeAudioData(read.result, (buffer) => {
const processor = context.createScriptProcessor(4096, 1, 1);
processor.connect(context.destination);
source.connect(processor);
source.connect(context.destination); // 资源连接设备 如果不连接则不会发出声音
// 实时回调
processor.onaudioprocess = (e) => {
// getChannelData返回 Float32Array 类型的 pcm 数据
const data = e.inputBuffer.getChannelData(0);
pcmData.push(data);
audioSize += data.length;
// 实时处理...
}
source.buffer = buffer;
// 播完断开
source.onended = () => {
processor.disconnect(context.destination);
source.disconnect(processor);
source.disconnect(context.destination);
};
}
};
read.readAsArrayBuffer(blob);
})
以上是项目中使用 Web Audio API 的记录,当然需求不仅如此,还有可视化音频的处理等,不过既然已经能拿到实时的 PCM 数据,只需要对这些数据进行实时处理加工,绘制即可。
其中的技术参考了一个开源的录音库: Recorder
附录
创建空的wav头信息
const createWavHeader = () => {
const WAV_HEAD_SIZE = 44;
const buffer = new ArrayBuffer(WAV_HEAD_SIZE);
// 需要用一个view来操控buffer
const data = new DataView(buffer);
let offset = 0;
const writeString = (str) => {
for (let i = 0; i < str.length; i += 1, offset += 1) {
data.setUint8(offset, str.charCodeAt(i));
};
};
const write16 = (v) => {
data.setUint16(offset, v, true);
offset += 2;
};
const write32 = (v) => {
data.setUint32(offset, v, true);
offset += 4;
};
/* RIFF identifier */
writeString('RIFF');
/* RIFF chunk length */
write32(36 + 0); // 0 这个参数是代表wav数据长度 可通过 pcmData 获得,此处写0是因为录音开始无法获取到wav总大小
/* RIFF type */
writeString('WAVE');
/* format chunk identifier */
writeString('fmt ');
/* format chunk length */
write32(16);
/* sample format (raw) */
write16(1);
/* channel count */
write16(1);
/* sample rate */
write32(16000);
/* byte rate (sample rate * block align) */
write32(16000 * (16 / 8));
/* block align (channel count * bytes per sample) */
write16(16 / 8);
/* bits per sample */
write16(16);
/* data chunk identifier */
writeString('data');
/* data chunk length */
write32(0);
return data;
};
export default createWavHeader;
# JavaScript