深入拆解电话agent的音频延迟链路,从RTP封包、编码器延迟到CPaaS媒体服务器,逐跳量化各环节耗时,是难得的AI语音系统工程实践。
通话代理的优劣只靠一个数字来评判:来电者说完一句话,到听见回复之间的间隔。这个间隔是大约十几个环节的总和,其中大部分对你的应用代码是不可见的。本文逐一列出每个环节并求和。
先从物理路由说起,因为软件术语都依附于它。公网电话网络的用户通过运营商或 CPaaS 提供商的 SIP 中继联系到你;浏览器中的用户通过 WebRTC 联系到你。两者都到达媒体服务器,媒体服务器向你交付包含每 20 毫秒音频的 RTP 数据包。
INBOUND (caller speaks -> your endpointer notices they stopped)
h1 capture + packetisation handset mic, codec framing. One RTP
packet carries one frame; 20 ms is the
near-universal telephony choice, so the
encoder always holds a full frame before
it can send anything. Opus adds a few ms
of algorithmic look-ahead on top; G.711
adds none.
h2 access network mobile radio or fixed broadband, one way.
The most variable term in the whole path
and the one you cannot touch.
h3 carrier transit PSTN / interconnect, plus transcoding at
any gateway that changes codec.
h4 provider media path your CPaaS or SBC to your media server.
h5 jitter buffer de-jittering at your end. Adaptive, and
the largest network term you actually
control.
h6 frame handoff media server -> your process -> VAD.
--- the caller has now stopped speaking, and you know it ---
e endpoint wait min_silence. A policy, not a system.
r ASR finalisation from endpoint to final transcript.
m model time to first token
s TTS time to first audio byte
OUTBOUND (your first audio byte -> the caller hears it)
h7 packetisation your encoder frames the audio: another
20 ms held before the first packet leaves.
h8 provider media path
h9 carrier transit
h10 access network
h11 caller jitter buffer + decode + playout
在这个列表中,在任何具体数字附上前,可以得出两个结构性的事实。打包(packetisation)在两个方向上各算一次,因为两个编码器都不能发送尚未填满的数据包。jitter buffer 也要算两次——一次在你这边,一次在他们那边——而且都不是固定值,因为自适应缓冲会在网络变差时增长。
作为方程的预算
用来电者的体验来写响应间隔:他们最后一个音节离开嘴,到回复的第一个音节到达他们耳朵。
gap = D_in + e + r + m + s + D_out
D_in = h1 + h2 + h3 + h4 + h5 + h6 inbound one-way delay
D_out = h7 + h8 + h9 + h10 + h11 outbound one-way delay
e endpoint wait (min_silence)
r ASR finalisation after the endpoint fires
m model time to first token
s TTS time to first audio byte
Every term is in milliseconds. Fill each one from your own
measurements; nothing on this page supplies a value for you.
The two useful reference points to compare the result against:
ITU-T G.114 recommends a one-way mouth-to-ear delay at or below
150 ms for general conversational quality, treats 150-400 ms as
acceptable where the parties are aware of the transmission, and
regards anything above 400 ms as unacceptable for interactive
use. Note that G.114 is about D_in alone -- your gap contains
D_in AND D_out AND four processing terms.
Work on human turn-taking (Stivers et al., PNAS 2009, ten
languages) finds response offsets clustering around a couple of
hundred milliseconds. That is the standard a listener applies
without being asked to.
So the honest framing: a carrier-grade telephone path spends a
substantial part of the human turn-taking budget before your
software has done anything at all. Your four terms are competing
for what is left.
这就是为什么"我们让模型变快了"如此频繁地无法改变产品的体验。如果 m 是总和的五分之一,把它减半只能将间隔缩短十分之一。在优化任何部分之前,先测量整条路径。
一个示例填充
下面表格中的数字仅供参考。它们只是占位符,用来展示运算如何组合,而不是任何产品、网络或供应商的实测数据。在得出任何结论之前,用你自己路径上测得的数据替换每一个数字。计算结构才是真正可以迁移的部分。
ILLUSTRATIVE ONLY -- substitute your own measurements.
INBOUND
h1 packetisation 20 ms
h2 access network 25 ms
h3 carrier transit 20 ms
h4 provider media path 10 ms
h5 your jitter buffer 40 ms
h6 frame handoff 5 ms
------
D_in 120 ms
PROCESSING
e endpoint wait 600 ms
r ASR finalisation 150 ms
m model time to first token 400 ms
s TTS time to first audio 200 ms
------
1350 ms
OUTBOUND
h7 packetisation 20 ms
h8 provider media path 10 ms
h9 carrier transit 20 ms
h10 access network 25 ms
h11 caller jitter + playout 40 ms
------
D_out 115 ms
gap = 120 + 1350 + 115 = 1585 ms
Read what that says rather than the number itself:
network + telephony 235 ms (15%) mostly not yours
endpoint wait 600 ms (38%) a policy you chose
ASR + model + TTS 750 ms (47%) four sequential services
The largest single line is a parameter someone typed, and the
second largest is three services run one after another that do not
have to be.
哪些可以重叠,哪些不可以
四个处理环节中有三个可以部分相互隐藏。做到这一点,是 demo 和产品的分水岭。
e 不与任何环节重叠——而是缩短它。endpoint wait 按构造来说就是纯粹的空闲时间:它是在确认什么都没发生所花费的时间。它不能与任何环节重叠。唯一的杠杆是让决策更智能,这就是语义端点检测(semantic endpointing)的作用:当部分转写看起来已经完整时就缩短 e,当它以连词或未完成的数字结尾时就延长 e。
让 r 与 m 重叠。在 endpoint 触发的瞬间,立即用最新的部分转写发出模型请求,而不是等待 ASR 最终结果。当最终结果到达时,比较两者:如果与你发送的内容一致,你就节省了整个 r;如果有实质差异,取消并重新发出。代价是偶尔会出现一次浪费的调用。
让 m 与 s 重叠。将模型的 token 流式输入 TTS,并在第一个句子边界就开始合成,而不是在回复结束时才开始。这将 m 从"完整答案的时间"转变为"到第一个 token 的时间"加上到达子句边界的时间。这通常是软件层面最大的单次收益,也是为什么对于语音产品来说,首 token 延迟比 token 吞吐量重要得多的原因。
让 s 与 h7 重叠,通过流式输出音频。在 TTS 产生音频时就将第一批音频帧推送到媒体服务器,而不是等文件完整后再发送。如果一个 TTS 只返回完整的话语,这件事就不可能做到,并且应该因此被淘汰。
Sequential: gap = D_in + e + r + m_total + s_total + D_out
With all three overlaps:
gap ~= D_in + e + max(r, m_first_token) + s_first_audio + D_out
where m_first_token is time to FIRST token, not the whole reply,
and s_first_audio is time to the first audio byte of the first
clause. The rest of the reply is generated and synthesised while
the caller is already listening to its beginning -- which works
only if generation is faster than speech, roughly 150 words per
minute. If it is not, the caller hears gaps mid-sentence, which
is worse than one gap at the start.
最后一个约束是人们在生产环境中才发现的。要明确检查它:如果你的模型和 TTS 加在一起不能以比语音更快的速度持续输出,那么在话语中途流式传输会产生结巴而不是加速,你最好还是缓冲第一个句子。
步骤 2 和步骤 3 中的重叠将 m 从一个数字变成两个——到第一个 token 的时间和总时间——而且它们独立变化。Multigrid 在每个请求中同时报告两者,用 p50 和 p95 而不是平均值,这才是这里真正重要的区别:平均值会掩盖长尾,而在电话通话中,长尾正是那个来电者对着沉默说"喂?"的那一通电话。
编解码器决定了你的准确率上限
音频路径不仅让你付出毫秒的代价。它在声学意义上让你付出带宽,而这为 ASR 模型无法提升的识别准确率设定了天花板。
有两个值得围绕它设计的结论。首先,将 8 kHz 电话音频上采样到 ASR 模型期望的 16 kHz 是一种格式转换,而不是恢复:缺失的频段已经没了,任何重采样器都发明不出来。其次,路径中的每一次转码都是一次有损压缩的生成,经过两次转码的电话在转写难度上是可测量的。向你的提供商询问你实际接收的那一段使用的编解码器,而不是他们营销页上描述的那个。
这也是电话通话词错误率比干净音频所报告的数字更差的最主要原因,也是为什么用录音室录音测得的准确率数字对你的呼叫中心毫无意义。在你自己的音频上、用你自己的规范化器来测量。
你无法仅从应用日志中填入这个预算,因为一半的环节发生在你的代码看到任何东西之前。三个测量手段合在一起覆盖了整条路径。
软件环节,用一个单调时钟。从同一个进程中的同一个时钟,对每一轮对话标记六个事件:观察到最后一个语音帧、endpoint 触发、收到 ASR 最终结果、发出模型请求、收到第一个模型 token、发出第一个 TTS 音频字节。差值给你精确的 e、r、m 和 s,无需估算。
网络环节,用 RTP。RTP 每个数据包携带时间戳和序列号,RTCP 接收者报告携带抖动和往返时间。你的媒体服务器暴露这些数据;记录每通电话的往返时间和当前 jitter buffer 深度。往返时间的一半是每个方向上 h2 + h3 + h4 的可行估计,而 buffer 深度就是直接的 h5。
真正的端到端数字,一次,物理地。用真实网络上的真实手机打一通真正的电话。录制手机自己的音频——你说进去的和从扬声器出来的——到一个立体声文件中。一个声道上你话语的结束,到另一个声道上回复的开始,其间隔就是真实数字,包括你无法插桩的所有环节。在移动网络和固线上各做一次;它们不会一致。
所有这些都按 p50 和 p95 报告,绝不用平均值。单次冷启动或单次 jitter buffer 扩张就能把平均值拉高到实际上没有人经历过的水平,而在电话系统上,抱怨来自长尾的那些电话。
按能让你在每个阶段测量的顺序来构建它,因为一次性组装好的 pipeline 是无法调试的。
回答和回声。接听一通来电,用固定延迟回放来电者自己的音频。这验证媒体路径、编解码器、打包和缓冲,在你引入任何 AI 之前将 D_in + D_out 作为单一可测量数字。
添加 VAD 并只记录。不回复。记录每一个语音和静默边界,然后构建端点检测中描述的暂停直方图,从你自己的数据中选择 e。
添加流式 ASR。将部分结果和最终结果打印到与音频并排的控制台。确认最终结果在你期望的时间到达,并且转写在你读取之前已经可用。
添加 TTS,用固定脚本。还没有模型。测量 s,确认端到端流式传输工作正常,并确认你可以在话语中途停止播放——这是 barge-in 的前提条件。
最后添加模型。到此时其他每个环节都已测量,所以当间隔变化时,你确切地知道是哪个数字动了。
然后添加 barge-in,并预期它会出问题。中断与它之前的每个组件都会交互,这就是为什么它要放在最后并有自己的页面。
一个与延迟完全无关的操作警告:入站和出站呼叫在几乎所有地方的监管规定都不同,自动化出站呼叫尤其如此,而通话录音又有自己的规则。在第一通出站电话之前就阅读录音、同意和保留相关的规定,而不是之后。
Voice Activity Detection and Endpointing
Barge-In: Letting a Caller Interrupt
Costing a Voice Product Per Minute