2. 语音识别基础
做语音识别前端工程化,绕不开 Web Audio API。说实话,很多人一上来就调语音识别 SDK,结果发现麦克风权限、音频格式、采样率全都不对。为什么?因为没搞懂浏览器里的音频管道是怎么工作的。
这一章,我们就把它拆开来看。音频上下文、音频源、音频节点、处理流程——这四个东西搞明白了,后面部署语音识别模型就是水到渠成的事。
2.1 音频上下文(AudioContext)——整个管道的发动机
AudioContext 是什么?你可以把它想象成一个音频引擎。它管理着所有音频节点,控制着采样率、声道数、音频时钟。在浏览器里,你只能有一个活跃的 AudioContext(严格来说可以多个,但没人这么干)。
核心要点:AudioContext 是 Web Audio API 的入口。没有它,后面的一切都不存在。
我个人习惯在页面初始化时就创建 AudioContext,而不是等到用户点击才创建。但要注意,Chrome 等浏览器要求 AudioContext 必须在用户手势(比如点击、触摸)之后才能恢复(resume)。否则它默认是 suspended 状态。
// 创建音频上下文
const audioContext = new AudioContext();
// 检查状态
console.log(audioContext.state); // 'suspended' 或 'running'
// 用户点击后恢复
document.getElementById('startBtn').addEventListener('click', async () => {
if (audioContext.state === 'suspended') {
await audioContext.resume();
}
});
我曾经踩过的坑:在页面加载时直接调用 audioContext.resume(),结果控制台报错「The AudioContext was not allowed to start」。后来才明白,浏览器安全策略不允许自动播放音频。必须绑定用户交互事件。
2.2 音频源(AudioSource)——音频数据的入口
音频源就是音频数据的生产者。Web Audio API 提供了几种常见的音频源:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| MediaStreamAudioSourceNode | 从麦克风或摄像头获取实时音频流 | 语音识别、实时通话 |
| AudioBufferSourceNode | 播放内存中的音频数据(如加载的音频文件) | 离线语音识别、音频回放 |
| OscillatorNode | 生成正弦波、方波等合成音 | 测试、音效生成 |
| MediaElementAudioSourceNode | 从 <audio> 或 <video> 元素获取音频 | 音视频同步处理 |
做语音识别,最常用的是 MediaStreamAudioSourceNode。你需要先通过 getUserMedia 拿到麦克风流,再把它喂给 AudioContext。
// 获取麦克风音频流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 创建音频源节点
const source = audioContext.createMediaStreamSource(stream);
console.log('音频源已创建,采样率:', audioContext.sampleRate);
小技巧:getUserMedia 返回的 stream 可以同时被多个节点使用。比如你既要做语音识别,又要做本地回放,只需要创建一个 source,然后分两路输出就行。
2.3 音频节点(AudioNode)——处理音频的积木块
音频节点是 Web Audio API 的核心。每个节点负责一种音频处理任务。你可以把它们像乐高积木一样拼接起来,形成一条处理链。
常用的音频节点包括:
- GainNode:控制音量大小
- AnalyserNode:获取音频频谱数据(用于可视化)
- ScriptProcessorNode(已废弃) / AudioWorkletNode:自定义音频处理
- BiquadFilterNode:低通、高通、带通滤波
- ChannelMergerNode / ChannelSplitterNode:声道合并与分离
对于语音识别前端工程化,最重要的节点是 AudioWorkletNode。它允许你在一个独立的线程中运行自定义音频处理代码,不会阻塞主线程。
// 创建一个增益节点(控制音量)
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.8; // 音量设为 80%
// 创建一个分析器节点(用于获取音频数据)
const analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 2048; // FFT 窗口大小
// 连接节点:source -> gain -> analyser -> destination
source.connect(gainNode);
gainNode.connect(analyserNode);
analyserNode.connect(audioContext.destination);
注意:ScriptProcessorNode 已经被标记为废弃,新项目请使用 AudioWorkletNode。虽然 AudioWorklet 的 API 稍微复杂一点,但性能和稳定性好得多。
2.4 音频流处理流程——数据是怎么流动的
理解了上下文、源、节点,接下来就是它们怎么配合工作。Web Audio API 的处理流程其实很简单:
- 创建 AudioContext:启动音频引擎
- 创建音频源:从麦克风、文件或合成器获取数据
- 创建处理节点:按需添加增益、滤波、分析等节点
- 连接节点:用 connect() 方法把节点串起来
- 输出到目的地:通常是扬声器(destination),也可以是 MediaStream(用于录制或传输)
下面这张图展示了典型的语音识别前端音频处理流程:
你想想看,这个流程其实跟工厂流水线很像。麦克风是原材料入口,音频源是传送带,各个节点是加工工位,最后输出是成品。每个工位只做一件事,但组合起来就能完成复杂的音频处理任务。
2.5 实战:搭建一个麦克风音频管道
光说不练假把式。我们直接写一个完整的例子,把麦克风音频流接入 AudioContext,经过增益和频谱分析,最后输出到扬声器。
// 1. 创建 AudioContext
const audioContext = new AudioContext();
// 2. 获取麦克风流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 3. 创建音频源
const source = audioContext.createMediaStreamSource(stream);
// 4. 创建增益节点
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.5;
// 5. 创建分析器节点
const analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 1024;
const bufferLength = analyserNode.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// 6. 连接节点
source.connect(gainNode);
gainNode.connect(analyserNode);
analyserNode.connect(audioContext.destination);
// 7. 获取频谱数据(用于可视化或语音检测)
function getAudioData() {
analyserNode.getByteFrequencyData(dataArray);
// dataArray 现在包含了各频段的能量值
return dataArray;
}
// 8. 用 requestAnimationFrame 持续读取
function update() {
getAudioData();
// 这里可以更新 UI 或检测语音活动
requestAnimationFrame(update);
}
update();
我个人习惯:在连接节点时,我会先画一个草图,标清楚每个节点的输入输出。尤其是当节点数量超过 5 个时,不画图很容易连错。你想想看,音频节点连接错了不会报错,但结果就是没声音或者声音不对,排查起来很头疼。
2.6 避坑指南
做 Web Audio API 开发,有几个坑我几乎每次都会遇到:
- AudioContext 被自动挂起:浏览器为了省电和隐私,不允许页面自动启动音频。必须绑定用户手势。
- 采样率不匹配:AudioContext 的采样率是固定的(通常是 44100Hz 或 48000Hz)。如果你从麦克风获取的音频采样率不同,需要做重采样。这个后面章节会讲。
- 节点连接顺序:connect() 的顺序决定了音频处理顺序。比如先滤波再增益,和先增益再滤波,效果完全不同。
- 内存泄漏:不再使用的节点要断开连接并置空,否则 AudioContext 不会释放内存。
我曾经犯过的错:在单页应用里频繁创建和销毁 AudioContext,结果浏览器内存飙升,页面越来越卡。后来我改成全局只维护一个 AudioContext,需要时暂停和恢复,问题就解决了。
2.7 小结
Web Audio API 其实不复杂。记住四个核心概念:AudioContext 是引擎,AudioSource 是入口,AudioNode 是处理单元,connect() 是连接方式。掌握了这些,你就能搭建出任意复杂的音频处理管道。