1. 课程导入

音频延迟,说白了就是「声音从发出到听见之间的时间差」。

你对着麦克风说一句话,耳机里听到自己的声音慢了半拍——这就是延迟。我刚开始做 Web Audio 时,觉得这玩意儿不就是个时间差嘛,能有多复杂?直到第一次做实时合唱应用,用户反馈「根本没法唱」,我才意识到延迟是音频应用里最隐蔽的杀手。

1.1 什么是音频延迟

音频延迟,专业点说,是音频信号从源头(麦克风、文件、合成器)经过采集、处理、传输、播放这一整条链路,最终到达人耳所消耗的时间。

在浏览器里,这条链路长这样:

  • 采集端:麦克风拾音 → 模数转换 → 进入 AudioContext
  • 处理端:AudioNode 链式处理(滤波、混音、特效)
  • 播放端:数模转换 → 扬声器发声 → 声波传到耳膜

每个环节都会引入延迟。我见过不少新手以为「只要代码跑得快就没延迟」,其实硬件和驱动层面的延迟往往比 JS 执行时间大得多。

核心认知:延迟不是 Bug,是物理定律。我们的目标不是消灭延迟,而是把它控制在人耳无法察觉的范围内。

1.2 延迟的测量单位

测量延迟,最常用的单位有两个:毫秒(ms)和采样点(samples)。

毫秒(ms)

毫秒是给人看的。人耳对延迟的感知大致分几个档:

延迟范围 用户体验
0 – 10 ms 几乎无感,像面对面说话
10 – 20 ms 专业监听可接受,普通用户基本察觉不到
20 – 50 ms 有明显「回音感」,实时演奏/通话开始受影响
50 – 100 ms 延迟明显,对话需要等待,节奏感被破坏
> 100 ms 完全无法实时交互,像对讲机

我个人习惯把 20 ms 当作红线。超过这个值,用户就会开始皱眉。

采样点(samples)

采样点是给机器看的。数字音频系统以「块」为单位处理数据,这个块的大小就叫 buffer size,单位就是采样点。

举个例子:采样率 44100 Hz,buffer size 128 samples,那么一个 buffer 的时长就是:

128 / 44100 ≈ 0.0029 秒 = 2.9 ms

为什么用采样点?因为音频引擎内部是按采样点做循环的。你写一段 ScriptProcessorNode 或 AudioWorklet 的代码,每次回调处理的就是固定数量的采样点。

换算公式:延迟(ms)= buffer size(samples)÷ 采样率(Hz)× 1000

记住这个公式,调试时能省不少时间。

1.3 延迟对用户体验的影响

延迟不是「有或没有」的问题,而是「多少算多」的问题。不同场景的容忍度天差地别。

实时通信场景

视频通话、在线K歌、远程演奏——这些场景对延迟最敏感。我记得有一次帮一个在线音乐教育平台做调试,老师弹钢琴,学生听到的伴奏慢了 40 ms。老师反馈「学生总是抢拍」,其实不是学生的问题,是延迟在捣鬼。

  • 10 ms 以内:专业级体验,适合实时合奏
  • 10 – 30 ms:普通通话可接受,但音乐演奏会难受
  • 30 – 50 ms:对话开始有「重叠感」,需要刻意停顿
  • 超过 50 ms:基本没法正常交流

录音监听场景

歌手录歌时,耳机里听到自己的声音如果延迟超过 10 ms,就会不自觉地拖拍或抢拍。这叫「延迟干扰效应」。我曾经踩过这个坑——给一个录音棚做 Web 版监听工具,没处理好延迟,歌手录了三遍都找不到节奏,最后换回本地软件了。

游戏与交互场景

游戏音效、UI 反馈音、虚拟现实——这些场景里,延迟会直接破坏沉浸感。你按下一个按钮,0.5 秒后才听到「咔嗒」声,用户会觉得这个应用「很笨重」。

注意:视觉和听觉的延迟叠加效应更可怕。如果画面延迟 30 ms,声音延迟 30 ms,用户感知到的不是 60 ms,而是「音画不同步」的撕裂感。这在视频播放和游戏中尤其致命。

1.4 延迟的构成:一张图看懂

为了让你更直观地理解延迟从哪里来,我画了一张链路图:

音频延迟链路构成 采集 模数转换 处理 数模转换 播放 每个环节都会引入延迟,但「处理」环节是我们可以优化的重点。 采集和播放的硬件延迟通常由浏览器和驱动决定,我们能控制的空间有限。 典型延迟分布(采样率 44100 Hz,buffer size 128): 采集端:1 – 5 ms | 模数转换:0.5 – 2 ms | 处理端:2 – 10 ms 数模转换:0.5 – 2 ms | 播放端:1 – 3 ms | 总延迟:约 5 – 22 ms

1.5 一个简单的延迟测试方法

说了这么多理论,不如动手测一下。在浏览器里,我们可以用 AudioContext 的 baseLatency 和 outputLatency 属性获取系统延迟:

const ctx = new AudioContext();
console.log('基础延迟:', ctx.baseLatency, '秒');
console.log('输出延迟:', ctx.outputLatency, '秒');
console.log('总延迟:', ctx.baseLatency + ctx.outputLatency, '秒');

不过要注意,这两个属性不是所有浏览器都支持。Chrome 支持得比较好,Safari 嘛……嗯,你懂的。

实测技巧:用 performance.now() 在 AudioWorklet 的 process 方法里打时间戳,可以精确测量处理延迟。我一般会在开发环境里加一个「延迟仪表盘」,实时显示当前链路的延迟值,调试起来一目了然。

1.6 小结

音频延迟不是什么玄学,它就是信号从 A 到 B 花的时间。测量单位记住两个:毫秒(给人看)和采样点(给机器看)。用户体验上,20 ms 是分水岭,超过这个值就要警惕了。

这一章是基础,但基础不牢,后面所有优化技巧都是空中楼阁。你想想看,如果你连延迟从哪里来、怎么测都不知道,又怎么能把它降下去呢?