症状:一句话“出生时是对的,播出时是错的”
先描述一个示意的时间线,数字全部是假设,用来说明现象:
- 第0秒:数据源报告比分1比1,系统生成一句“双方战成平局,比赛仍在焦灼”;
- 第2秒:这句话进入语音合成队列;
- 第4秒:数据源报告甲队进球,比分变成2比1;
- 第6秒:旧的那句话才开始播放。
从每一环自己的角度看,都没有错:文本生成时比分确实是1比1,语音合成正常,播放正常。错在没有人问一句:这句话现在还成立吗?
所以“过期”并不是某个模型的缺陷,而是流水线的一种系统性风险:每一环都在忠实地处理它拿到的东西,而世界在同时变化。本文不重复速度与准确之间的取舍(这一点在 AI主播慢两秒和说错一句话 里已经讨论),重点是过期内容怎么产生,以及怎样发现并丢掉它。
逐环节看:等待藏在哪里
| 环节 | 过期是怎么产生的 | 容易被忽略的原因 |
|---|---|---|
| 数据接入 | 数据源推送有间隔,或客户端轮询周期较长,拿到的已经是旧状态 | 以为“实时”就等于“最新” |
| 事件确认 | 等待确认期间,后面的事件已经到达 | 事件是按顺序处理的,却没有考虑“被后来者覆盖” |
| 文本生成 | 模型生成需要时间,生成期间状态改变 | 生成时的比分被当成播出时的比分 |
| 语音合成 | 首包等待、长句合成耗时 | 队列里可能积压了多句 |
| 口型驱动 | 需要等音频,可能再排队 | 通常被认为很快,被忽略 |
| 渲染与编码 | 画面处理与编码带来固定延迟 | 与音频延迟不一致,导致不同步 |
| 播放缓冲 | 为了防卡顿预留的缓冲区 | 缓冲越大越稳,但内容越老 |
表里的每一项在单独看时都不显眼,问题在于它们叠加在一起,并且是排队的。一个常见的误解是把延迟理解为“每句话都慢固定的时间”,实际上更接近排队论:只要生成速度偶尔快于播放速度,队列里就会积压,越积越久。
哪一环最容易被忽略?
我们的判断是:最容易被忽略的是“播出之前的队列”,也就是语音合成之后、真正播放之前的那一段。
原因有三点:
- 文本生成和语音合成各自的耗时都比较容易监控,而“已经合成好、正在排队等播放”的音频,往往不被算作延迟;
- 播放队列在两种情况下会堆积:一是比赛事件密集(连续进球、连续判罚),二是主播讲话速度低于内容产生速度;
- 队列里的每一句话,产生时都是对的,所以任何“文本层面的校验”都发现不了问题,只有把“产生时刻”与“播出时刻”做比较才能发现。
也就是说,过期这件事的检测点,应该放在链路的最后一步之前,而不是只放在生成之后。
给内容打上“版本”和“保质期”
要能判断“这句话现在还成立吗”,就需要让每一句话携带足够的信息:
- 状态版本号:生成这句话时所依据的比赛状态版本,例如比分变动一次,版本加一;
- 依赖事实:这句话使用了哪些事实,比如比分、场上球员、比赛阶段;
- 保质期:不同类型的内容有效时间不同,比分类内容很短,赛前背景类内容可以很长。
播出之前,队列管理器做一次很简单的比较:这句话依赖的事实,在当前状态里有没有变化?如果没有,播;如果有,这句话作废。
这里有个细节:不是所有变化都会使一句话失效。“甲队本场控球更多”这类描述,比分从1比1变成2比1之后仍然成立,可以继续播;“双方战成平局”则必须作废。所以依赖关系应当到字段级别,而不是简单地看“状态版本变了没有”。
过期之后怎么办:丢弃、打断、追赶
发现内容过期,有三种处理方式,适用场景不同:
- 丢弃未播出的内容:队列里还没播出的过期句子,直接删除。这是最常用、也最安全的一种;
- 打断正在播放的内容:句子已经开始播放,但后半段已经不成立,需要在合适的边界停下。这要求音频和口型都能被快速中断,并且尽量在句子边界处停,避免出现半个词的突兀;
- 追赶(catch-up):丢弃之后,队列里可能累积了多个错过的事件,不需要逐个补讲,而是用一句“简要回顾”概括,例如“刚才连续出现了两个进球,目前比分是……”,然后继续当下。
其中追赶最容易做坏。过度地补讲旧事件,只会让主播继续落后;完全不讲,观众又可能不知道发生了什么。更好的做法是设定一个规则:只有比分和关键状态必须及时同步,过程性的细节可以省略。
播放缓冲:稳定性和新鲜度的交换
最后回到缓冲区。缓冲越大,网络抖动时越不容易卡顿,但每一句话到达观众耳中的时间也越晚。它本质上是稳定性与新鲜度的交换:
- 对于纯观赏类的场景,稍大的缓冲可以接受;
- 对于需要与现场画面对齐的场景,应当尽量压缩缓冲,并让系统对“落后”这件事保持敏感;
- 缓冲大小最好能够根据网络状况动态调整,而不是一个写死的值。
如果画面和音频分别走不同链路,还要考虑两者的同步偏差:声音比画面慢一点,观众不一定察觉;声音比画面快,则会出现“先听到进球,后看到进球”的剧透感。这类对齐问题的整体思路,与 体育数字人的工作流对照 中的主播链路相关:越往下游,越应该关心内容是否还新鲜。
一份定位“过期”的排查思路
如果发现主播确实在讲过期内容,可以按下面的顺序缩小范围。这是工程上的思路,并不对应某个具体的已发布功能:
- 对齐时钟:先确认各环节使用的是同一个时间基准,否则时间线本身就是错的;
- 看数据源到达时间:比较“事件发生时刻”和“系统收到时刻”,确认延迟是不是出在源头;
- 看生成耗时:同一句话从触发到文本完成用了多久,是否在事件密集时明显变长;
- 看语音队列长度:合成完毕、等待播放的句子有几句,队首那句已经等了多久;
- 看播出前是否复核:队首句子播出前有没有与最新状态比较,如果没有,这就是最直接的缺口;
- 看画面与音频偏差:确认口型、字幕、画面之间是否被不同的缓冲拉开。
多数情况下,前五步就能找到主因。口型驱动和渲染的耗时通常比较稳定,一般不是“讲上一回合”的元凶,除非它们前面还有一个不受控的队列。
边界与后续
- 以上时间线与数字都是示意,不同的数据源、网络和设备,真实的分布并不相同;
- 复核和丢弃机制不能解决数据源本身延迟的问题,它只是保证系统不会把已知过期的内容继续播出去;
- 丢弃过于激进也有代价,可能让主播频繁沉默,需要在长期回放中调整保质期的设定;
- 更细的评测,例如“过期句子占比”和“从事件发生到被正确播报的时间”,需要在真实数据上统计,这里没有给出任何结果。
总结成一句话:AI主播的延迟不是一个数字,而是一条队列。要让它不讲过期的话,最有效的办法不是把每一环都做得更快,而是在开口之前,多问一句“这句话现在还成立吗”。