纯 WebSocket + WebAudio 实现低延迟对讲需绕开浏览器默认音频链,端到端延迟才能压至 80–150ms;关键在采集(AudioContext + AudioWorklet 获取原始 PCM)、传输(WebSocket binaryType='arraybuffer'、极简帧结构)、播放(AudioBufferSourceNode 精确调度)三环节的确定性控制;MediaRecorder 因编码与封装引入高延迟,不适用;iOS Safari 不支持 AudioWorklet,是架构级硬伤,无法通过 polyfill 规避。
纯 websocket + webaudio 无法直接实现低延迟对讲,必须绕开浏览器默认音频处理链,端到端延迟才能压到 80–150ms;关键不在“连上 websocket”,而在采集、传输、播放三环节的确定性控制。
为什么不能用 MediaRecorder 直接发 Blob
MediaRecorder 会强制引入编码延迟(Chrome 中常超 200ms),且输出的是 WebM/Opus 封装格式,客户端收到后还得解封装、解码,彻底破坏实时性。它适合录播,不适合对讲。
- 采集路径必须走
AudioContext.createMediaStreamSource()→AudioWorklet(或退化为ScriptProcessorNode) - 目标是拿到原始
Float32ArrayPCM 帧,不是 MediaStream 或 Blob - 采样率建议固定为
48000,帧长选128或256样本(对应 ~2.67ms / ~5.33ms),便于后续时间戳对齐
WebSocket.binaryType = 'arraybuffer' 必须设,且全程禁用字符串
任何 JSON 包裹、Base64 编码、send(data.toString()) 都会引入额外序列化/解析开销,直接让延迟翻倍。WebSocket 在这里只是“管道”,不是“邮局”。
- 发送前务必设置
socket.binaryType = 'arraybuffer' - 帧结构建议极简:5 字节头(2 字节类型 + 2 字节采样率 ID + 1 字节声道数)+ 后续
Int16Array.buffer(小端序、interleaved) - 服务端不做任何解码或重组,只做透传;丢包不重传,靠客户端抖动缓冲补偿
AudioContext({latencyHint: 'interactive'}) 创建后必须立即 suspend()
不 suspend() 的 AudioContext 会在后台持续调度,耗电且干扰主线程,尤其在移动设备上极易触发系统降频或中断音频上下文。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 初始化时调用
audioCtx.suspend(),仅在开始采集/播放前调用audioCtx.resume() - 播放端不用
MediaStreamAudioDestinationNode,改用预分配的AudioBuffer+AudioBufferSourceNode - 填入新数据后,必须用
source.start(0, offset)和audioCtx.currentTime精确调度播放起始时间,不能靠 setTimeout 或 requestAnimationFrame
iOS Safari 不支持 AudioWorklet 是硬伤,别试图 hack
iOS 17 仍不支持 AudioWorklet,官方未给出明确支持时间表。强行用 ScriptProcessorNode 虽能跑,但已废弃、主线程阻塞、延迟更高,且 iOS 上 onaudioprocess 触发不稳定——这是架构级限制,不是配置问题。
如果目标用户含大量 iOS 设备,要么接受更高延迟(200–300ms),要么切回 WebRTC;想靠 polyfill 或变通方案绕过,只会浪费调试时间。










