Skip to content

[spec] 长句字幕延迟:冻结契约、测量门闩、一次一刀 #15

Description

@Spc-jgs

Problem Statement

看外语访谈或长句视频时,字幕经常落后语音大约一整句。短句还跟得上;一旦句子超过大约 15 个词,译文要等上一句说完才翻到眼前。已经解决了「一句话在屏幕上改几十次」的闪烁,现在剩下的是:稳是稳了,但晚了。

不想再凭印象拧参数,也不想换模型或改 Prompt 来装成修了延迟。需要一套能对照数字、一次只动一刀、升了改写率就回滚的做法。

Solution

不换架构、不换翻译引擎。现行链路已经是方案:系统音频 process tap → 端侧识别 → audio-range ledger(已定稿原文 + 可替换的未定稿原文)→ 翻译单元与显示单元分开 → 本机模型三条通道(当前句预览、整句定稿、静音后补历史)。未定稿原文已经参与当前句预览,不是「等整句识别完才开翻」。

先把落后语音的秒数拆到阶段上,并在字幕条上看见。然后按测量结果只改调度策略,一次一刀。每一刀必须用节奏诊断的会话总表对照:一句话变几次、每产出一个字被擦掉多少、落后秒数。变差就回滚那一刀。体验是否变好由使用者用真实语音验收。

User Stories

  1. As a 听访谈的人, I want 长句译文不要落后一整句, so that 我能边听边读而不是等说完再补看
  2. As a 听访谈的人, I want 已经出现在当前行上的字不被改写, so that 我不用反复重读同一句
  3. As a 听访谈的人, I want 整句定稿后进入上一行、当前行翻到下一句, so that 我能分清「刚说完」和「正在说」
  4. As a 听访谈的人, I want 短句不要为了压延迟而开始乱跳, so that 现在已经可接受的短句体验不被牺牲
  5. As a 听访谈的人, I want 字幕条三行位置和高度保持不变, so that 画面不会因为有没有译文而跳动
  6. As a 听访谈的人, I want 当前译文最醒目、上一句降权, so that 我的眼睛停在正在说的那一句
  7. As a 听访谈的人, I want 识别重复或说反了时先修识别而不是换翻译模型, so that 问题被修在真正出错的那一层
  8. As a 听访谈的人, I want 暂停或关闭后采集和识别真正停掉, so that 空闲时不占机器
  9. As a 调节奏的人, I want 字幕条上直接看到落后语音多少秒, so that 我不用只靠「追赶中」三个字和感觉判断
  10. As a 调节奏的人, I want 设置页运行时也显示落后秒数, so that 字幕条没展开时也能看数字
  11. As a 调节奏的人, I want 打开节奏诊断后得到按阶段拆开的延迟, so that 我知道慢在等识别、排队、模型、定格还是翻页
  12. As a 调节奏的人, I want 会话总表里仍有一句话变几次和擦除比, so that 降延迟没有把闪烁换回来
  13. As a 调节奏的人, I want 诊断默认关闭、用完关掉, so that 日常使用不写盘
  14. As a 调节奏的人, I want 每一刀改动都有改前改后两份会话总表, so that 我能回滚而不是靠记忆比较
  15. As a 调节奏的人, I want 改写率或擦除比升高时自动视为失败, so that 不会为了数字好看牺牲可读性
  16. As a 本机用户, I want 语音内容仍然只发到本机模型, so that 隐私边界不变
  17. As a 本机用户, I want 不安装虚拟声卡、不出现正在共享屏幕, so that 采集方式保持现在这样
  18. As an 实现者, I want 开发契约、产品说明和源码对采集、调度、三行布局说同一套话, so that 我不会按过时设计把屏幕共享或可取消定稿加回去
  19. As an 实现者, I want 正在跑的整句定稿跑完为止, so that 取消后调用方不会永远等不到翻页
  20. As an 实现者, I want 正在跑的当前句预览也不被下一帧识别取消, so that 字幕不会卡在只有原文
  21. As an 实现者, I want 已确认的译文只写一次, so that 后续识别或模型响应不能回写历史
  22. As an 实现者, I want 过期响应按会话和修订号丢弃, so that 旧翻译不能覆盖新字幕
  23. As an 实现者, I want 翻译单元和显示单元继续分开, so that 切窗不会让当前行凭空变短
  24. As an 实现者, I want 当前句预览继续只能续写已上屏译文, so that 屏幕上的字物理上不能被模型改写
  25. As an 实现者, I want 整句定稿仍然是唯一一次能推翻措辞的机会, so that 预览和定稿的职责不被合并成一次请求
  26. As an 实现者, I want 一次只改一条调度策略, so that 失败时知道该回滚哪一刀
  27. As an 实现者, I want 调度策略的测试表达不变量而不是写死词数, so that 以后拧参数不会让用例 silently 过期
  28. As an 实现者, I want 改节奏参数时必须跑状态测试, so that 9c61419 那种词数用例变红却无人发现的事不再发生
  29. As an 验收的人, I want Agent 不代替我播放测试媒体, so that 体验验收仍然由我签字
  30. As an 验收的人, I want 实现完成时标明运行态和体验仍待我验收, so that 没有人宣称延迟已经验证改善
  31. As a 看字幕的人, I want 长句在从句边界就可以出译文, so that 不必等整个自然句定稿才看到字
  32. As a 看字幕的人, I want 翻页发生在上一句定稿进了上一行之后, so that 当前行不会因为内部切窗而缩水
  33. As a 看字幕的人, I want 页面太长时才按语义边界裁, so that 翻译还没回来时当前行不会无限变长也不会动不动被剪掉
  34. As a 看字幕的人, I want 「追赶中」和具体秒数一起出现, so that 橙色标签有数字含义
  35. As a 维护契约的人, I want 开发说明里「预览互相抢占」改成与源码一致, so that 以后的人不会按文档把不取消 in-flight 改回去

Implementation Decisions

  1. 冻结现行架构,不重画数据流。 采集只用系统音频 process tap。识别产出已定稿原文与可替换未定稿原文,用音频时间范围对账。翻译单元(语义切窗 → 整句定稿)和显示单元(分页 → 当前句预览)分开。当前句预览的源文是「当前页原文 + 未定稿原文」,不是切窗器里还没切走的待处理原文。三条翻译通道优先级:当前句预览、整句定稿、静音后补历史。过期响应只丢弃。已确认译文只写一次。

  2. 以现行契约和源码为准,8 月底两份任务设计里与此冲突的段落作废。 包括:屏幕共享流采集、可取消的整句定稿、说话时不要上一行。用户可见布局跟产品说明的固定三行;视觉层级跟开发契约(当前句最高、历史降权、纵向淡入淡出)。

  3. 正在跑的整句定稿必须跑完。 取消不会触发完成回调,调用方的待办会永远翻不了页。正在跑的当前句预览也不取消:源码已经如此,避免字幕永远只有原文。开发契约里「预览互相抢占」以源码为准改掉,不要按文档把取消加回去。

  4. 不换翻译引擎,不合并预览和定稿请求,不拧切窗词数作为第一刀。 已有会话总表显示首次出字大约 168ms,模型出字不是主因。系统翻译框架和「预览定稿共用一次请求」会动隐私叙事或取消「定稿才能推翻措辞」。切窗词数历史上拧过,测试写死词数还红过。这些列为后备,本规格第一轮不做。

  5. 测量门闩先于策略刀。 节奏诊断会话总表已有落后秒数、首次出字、采集到识别、排队、采集到可见、采集到定稿、定格次数、翻页裁剪。第一刀实现是:字幕条 hover 显示落后秒数;若总表还不能区分「定格挡住预览」和「等最后一个窗口才翻页」,补上这两段时长或计数。没有改前对照总表,不动策略。

  6. 策略刀候选(按测量排序,一次一条)。 最大嫌疑是:整句定格时长直接加在延迟上;长句多窗口时当前行要等最后一个窗口定稿才翻页;in-flight 预览占住车道导致新快照要等。每一刀只改其中一条。改前改后都必须有真实语音会话总表。一句话变几次或擦除比升高 = 该刀失败,回滚。

  7. 身份键不变。 识别侧用 span 的编号、时间范围、修订和状态。模型侧用会话、片段、修订、种类、音频范围五元组。生命周期代数只服务启动、停止和语言切换,不进请求键。

  8. 源转录问题停在识别适配层。 定稿边界跟框架的定稿时刻走,不用标点、长度或定时器冒充已定稿。重复或错序先查滚动重叠、时间范围、定稿前沿和 ledger 合并。

  9. 诊断默认关。 打开才写会话总表,用完关掉。不把写盘变成实时字幕的常驻行为。

  10. 完成边界。 Agent:实现、状态测试、Release 构建、提供验证步骤。运行态权限/真实音频和体验验收由使用者做。不得宣称延迟改善已验证。

Testing Decisions

好的测试只锁外部不变量:窗口有界且不消费前瞻、不以未闭合尾词收尾、分页不因切窗而缩水、续写才追加否则按阅读门槛 hold、整句定稿不被预览取消、过期响应不写已确认字幕。不要写死某个词数。

模块与既有先例:

  • 切窗 / 预览稳定性 / 滞后模式:沿用现有流水线状态测试(不变量断言,不是词数快照)。
  • 分页:沿用现有分页测试(切窗不让当前行缩水、上界给翻译往返留余量)。
  • 展示规则:沿用现有展示测试(追加 vs 改写 hold、定格挡住下一句预览)。
  • 诊断:沿用现有诊断日志测试;若补阶段字段,只断言总表里出现该阶段,不断言具体毫秒。
  • 新缝(仅一处): 三条通道的调度行为——整句定稿 in-flight 不可取消、当前句预览 in-flight 不可取消、补历史只在无当前活动时跑、积压有上限。现在状态测试不覆盖这条调度,改车道策略前必须先有它,否则测试全绿也说明不了调度。

运行态验收不是单元测试:打开节奏诊断,跑一段真实长句语音,对比会话总表。默认关日志。

Out of Scope

  • 系统翻译框架、Apple Intelligence、云翻译作为预览引擎
  • 当前句预览与整句定稿合并为一次模型请求
  • 再拧切窗的最少词 / 最多词 / 目标时长 / 前瞻词数作为本轮主改动
  • 换 Ollama 模型或改翻译 Prompt 来降延迟
  • 麦克风、说话人、会议 SDK、字幕默认写盘导出
  • 自定义快捷键、签名公证、Swift 6、用量看板、本机助手
  • 复活屏幕共享音频流或说话时隐藏上一行

Further Notes

本规格落实 Issue #3。2026-09-04 已有一轮真实语音:99 秒 34 句,一句话平均变 1.29 次,擦除比 1.13,落后平均 1.56 秒、p90 3.96 秒,15 词以上平均 2.54 秒。首次出字平均 168ms。本轮没有再开 App 录新语料——Agent 不能代替使用者播放测试媒体。实施时用现有诊断字段做改前对照,缺的阶段再补,不要另起一套日志格式。

历史教训必须写进实施约束:把切窗默认词数从 16 降到 12 曾让不变量用例变红且多版本无人发现;旧预览滑窗曾把改写占比做到 94%,展示层追加几乎命不中。节奏数字不会让普通测试变红。

测试缝请确认是否同意:运行态以会话总表为最高缝;代码以现有切窗/分页/展示不变量测试为最高缝;唯一新增缝是三条通道调度。 不同意就在本 Issue 里改,不要另起一套测试金字塔。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentSpec is complete; an agent can implement without more triage

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions