2. 音频基础

聊到前端音频延迟,咱们得先搞清楚一个最基础的东西——AudioContext。你可以把它想象成一个音频引擎的“总控室”。所有音频节点、路由、播放、处理,都得在这个上下文里完成。没有它,你连个“嘀”声都发不出来。

我个人习惯,在开始任何音频项目之前,第一件事就是先把这个上下文创建好。别小看这一步,配置不对,后面延迟调得再好也白搭。

2.1 创建 AudioContext

创建 AudioContext 其实就一行代码。但这里有个坑——浏览器策略。现在的主流浏览器,不允许页面一加载就自动创建音频上下文。你得等用户跟页面有了交互(比如点击、触摸)之后才能创建。这叫“用户激活”机制。

我刚开始做 Web 音频那会儿,就踩过这个坑。页面加载完直接 new AudioContext(),结果控制台报错,说上下文被阻止了。嗯,从那以后,我都是把创建逻辑放在按钮的点击事件里。

// 正确的做法:在用户交互后创建
let audioCtx;

document.getElementById('startBtn').addEventListener('click', () => {
    if (!audioCtx) {
        audioCtx = new AudioContext();
        console.log('AudioContext 已创建,状态:', audioCtx.state);
    }
});
小提示:创建后记得检查 audioCtx.state。正常应该是 'running'。如果是 'suspended',说明被浏览器挂起了,需要调用 audioCtx.resume() 恢复。

2.2 配置选项:latencyHint 与 sampleRate

创建 AudioContext 时,可以传一个配置对象。这里面有两个属性跟延迟直接相关:latencyHint 和 sampleRate。

2.2.1 latencyHint:延迟偏好

这个参数告诉浏览器,你对延迟的容忍度。说白了,就是“你希望音频多快响应”。

值 含义 适用场景
'interactive' 低延迟,优先响应速度 游戏、实时乐器、语音通话
'balanced' 平衡延迟与功耗 普通音乐播放、音效
'playback' 高延迟,优先省电 背景音乐、非实时播放

你想想看,如果你在做一款在线钢琴应用,用户按下一个键,结果 200 毫秒后才听到声音,那体验得多糟糕。这时候就得用 'interactive'。反过来,如果只是放个背景音乐,用 'playback' 就行,省电。

// 低延迟模式,适合实时交互
const audioCtx = new AudioContext({
    latencyHint: 'interactive'
});
注意:latencyHint 只是一个“建议”,浏览器不一定会完全按照你的要求来。最终延迟还取决于硬件、驱动和浏览器实现。

2.2.2 sampleRate:采样率

采样率决定了音频的“细腻程度”。单位是 Hz,表示每秒采集多少个样本点。常见的值有 44100(CD 音质)、48000(专业音频)、96000(高解析度)。

这里有个关键点:采样率越高,延迟理论上越低。为什么?因为缓冲区大小固定时,采样率越高,每个缓冲区包含的时间越短。举个例子,缓冲区大小是 1024 帧,采样率 44100 时,缓冲区时长是 1024 / 44100 ≈ 23.2 毫秒;采样率 96000 时,时长是 1024 / 96000 ≈ 10.7 毫秒。时间短了,延迟自然就小了。

但别高兴太早。高采样率意味着 CPU 要处理更多数据。我在一个移动端项目里试过 96000,结果低端手机直接卡成狗。所以,别盲目追求高采样率,得看目标设备的性能。

// 指定采样率为 48000
const audioCtx = new AudioContext({
    sampleRate: 48000,
    latencyHint: 'interactive'
});
核心要点:如果不指定 sampleRate,浏览器会使用默认值(通常是 44100 或 48000)。你可以在创建后通过 audioCtx.sampleRate 查看当前值。

2.3 baseLatency 属性详解

好,接下来咱们聊聊 baseLatency。这个属性,说实话,很多人容易忽略。但它恰恰是理解延迟的关键。

baseLatency 是 AudioContext 的一个只读属性,单位是秒。它表示从音频数据被提交到音频输出设备,到实际播放出来之间的固定延迟。这个延迟是由音频系统的底层实现决定的,你没法直接改。

我记得有一次,我在做一个实时音频处理的应用,发现不管怎么调参数,延迟始终降不到 10 毫秒以下。后来一查 audioCtx.baseLatency,发现它本身就占了 8 毫秒。嗯,原来瓶颈在这儿。

// 查看 baseLatency
const audioCtx = new AudioContext({ latencyHint: 'interactive' });
console.log('baseLatency:', audioCtx.baseLatency, '秒');
// 输出示例:baseLatency: 0.008 秒(即 8 毫秒)

这个值在不同浏览器、不同系统上差别很大。我测试过几个环境:

浏览器 / 系统 baseLatency(毫秒) 备注
Chrome / Windows 8 ~ 12 默认配置
Firefox / macOS 5 ~ 8 相对较低
Safari / iOS 15 ~ 20 移动端偏高
Chrome / Android 10 ~ 15 受硬件影响大

你看,Safari 在 iOS 上的 baseLatency 能到 20 毫秒。这意味着,就算你代码写得再完美,延迟也至少 20 毫秒起步。所以,做移动端音频应用时,一定要把这个因素考虑进去。

调试技巧:在开发阶段,建议把 audioCtx.baseLatency 打印到控制台,或者显示在 UI 上。这样你能直观地看到当前环境的“硬性延迟”是多少,避免在不可能达到的低延迟目标上浪费时间。

2.4 知识体系:AudioContext 的核心逻辑

下面这张图,帮你理清 AudioContext 创建与配置的核心逻辑。从创建开始,到配置选项,再到最终影响延迟的因素,一目了然。

AudioContext 创建与配置核心逻辑 AudioContext 创建与配置核心逻辑 用户交互触发 new AudioContext(config) 配置选项 latencyHint sampleRate 其他(如 sinkId) 关键属性:sampleRate / baseLatency 最终影响:音频延迟 baseLatency 是硬件 决定的固定延迟

2.5 避坑指南与最佳实践

最后,分享几个我在项目中积累的经验。这些坑,我基本都踩过一遍。

  • 别在构造函数里做耗时操作。 AudioContext 创建后,别急着创建一堆节点。先检查 state,确保它是 'running'。如果被挂起,先 resume()。
  • 采样率别乱设。 除非你有明确的硬件需求,否则用默认值。强行设一个硬件不支持的采样率,浏览器会抛异常。
  • baseLatency 是只读的。 别想着去改它。你能做的,是选择 latencyHint: 'interactive' 来让浏览器尽量优化。
  • 移动端要格外小心。 我曾在 iOS Safari 上遇到过 baseLatency 高达 25 毫秒的情况。如果你的应用对延迟敏感,建议在移动端做专项测试。
  • 创建后及时释放。 如果不再需要音频上下文,调用 audioCtx.close() 释放资源。否则会一直占用内存和音频设备。
曾经踩过的坑:有一次,我在一个单页应用里反复创建和销毁 AudioContext,结果发现浏览器内存只增不减。后来才意识到,每次创建新上下文之前,得先把旧的 close() 掉。否则,旧的上下文会一直“活着”,占用资源。

好了,关于 AudioContext 的创建与配置,以及 sampleRate 和 baseLatency 这两个关键属性,就聊到这儿。记住一句话:理解底层的固定延迟,才能更好地优化你能控制的部分。