Eros Live Engine 技术设计与当前实现
面向研发、算法、数据供应商、播放器和运维的内部说明。本文描述已经运行的 CPU-only SUT、接口合同、因果时间轴和故障策略;目标方案与尚未完成的能力会显式标注,不把演示结果写成生产结论。
00 · 架构结论与阅读约定
当前已经确定
- SUT 不接管原视频播放,只输出带目标 PTS 的独立解说音轨
- 视频、事件、视觉结果和音频统一使用媒体 PTS,而不是模型完成时间
- 硬事实以官方事件为准;视觉只输出画面状态和未经确认的候选
- 双路写手都是真实生成,不使用固定句库或结构化文案兜底
- 单场系统无本地 GPU,视觉、写作和 TTS 均调用外部服务
状态标记
| implemented | 当前 main 分支与线上环境已经运行 |
| partial | 数据结构存在,但消费链路或生产保障不完整 |
| required | 正式接入上游或规模化前必须补齐 |
01 · 整体架构
四个独立数据面:控制、视频、赛事事件、音频交付。视频里不夹赛事 JSON,事件 API 也不传图片。
控制面
创建比赛、准备 Match Package、arm/end。赛前资料在 arm 后冻结。
媒体面
SRT → MediaMTX → CPU PyAV → 约 1 Hz JPEG + 原始 PTS。
事实面
官方数据通过独立 HTTPS Push;支持修订、撤销、回放和幂等。
播放面
S3 WAV + HMAC Callback;下游按目标 PTS 播放并返回 receipt。
02 · 10 秒预算不是串行 13 秒
连续视觉任务并行,官方事件绕过视觉等待,两个写手并行;TTS 只使用剩余预算。
| 阶段 | 默认值 | 策略 |
|---|---|---|
| 视频抽帧 | 1,000 ms | 约每秒一帧,视觉池最多 8 个任务并行 |
| 云视觉 | 6,000 ms timeout | 默认 GPT-5.6 Luna;输出结构化低风险状态 |
| Fast Writer | 2,200 ms | Luna 实时路 |
| Quality Writer | 3,000 ms | SOL 质量路;Fast 有效时只追赶 450 ms |
| 选择截止 | 3,150 ms | 两路最终只交付一个有效版本 |
| Fish TTS | 最多 4,000 ms | 受 source PTS + 10 秒总 deadline 约束 |
03 · 媒体时钟、视频帧与现场事件对齐
系统不按 HTTP 到达顺序拼接信息。每条信号同时记录“发生在哪里”和“什么时候可用”,写作与播放都以原始画面的媒体 PTS 为主轴。
source_pts_ms
帧或事件在原视频中的位置。由解码帧 pts × time_base 得到,是写作、融合和播放的主时间。
received / available PTS
系统真正获得事件或模型结果的媒体水位。它决定这一事实能否进入当前上下文,禁止后来信息穿越回过去。
wall clock
服务器收包和模型耗时的 Unix/monotonic 时间,只用于超时、延迟和运维审计,不决定音频配哪幅画面。
四类时间关系
| 情况 | 判定 | 系统动作 | 是否改变目标画面 |
|---|---|---|---|
| 事件正常到达 | received ≤ source + 10s | 官方事件直接触发优先级 0 写作,不等待 VLM | 否 |
| 事件晚到但仍有预算 | 例如发生于 100s、102.4s 到达 | 使用剩余 7.6s 完成 Writer、TTS 和 Callback | 否,仍排到 100s |
| 事件超过播放截止 | ready > source + 10s | 即时句静默并记录 deadline miss;事实仍入赛中账本供后续统计 | 禁止平移到后面 |
| 事件早于对应视频帧 | 数据源比视频编码链路更快 | 目标设计应进入 Event Jitter Buffer,视频水位到达 source PTS 后释放 | 否,也不能提前泄漏 |
正式事件时钟接入顺序
- 优先要求上游提供与视频一致的 timecode/PTS。
- 其次使用双方 NTP 同步后的 UTC 时间建立映射。
- 只有比赛钟时,用半场开始、开球和关键事件建立分段锚点。
- 维护 offset/drift,不用单次事件直接重置全局映射。
- 按 stream_epoch 隔离媒体断点,比赛长期记忆继续沿用 match_id。
- 监控 clock_offset、event_lateness、early_queue 和 deadline_slack。
04 · SUT 内部处理逻辑
从帧到声音的每一步都保留证据、时间和可拒绝边界。
校验 match、epoch、递增 sequence、SHA-256 和 PTS;重复帧不会重复调用模型。
识别 scene、phase、控球方、区域、压力、镜头和安全 narration atoms;硬事件只能是 candidate。
保留最近 20 秒,在 5 秒窗口内按置信度和时间衰减投票;迟到观察会产生 revision。
需要稳定画面、足够置信度和安全事实;新句 7 秒、相同状态 12 秒、硬事件后安静 6.5 秒。
只决定 action / tactical / evaluation / person、优先级和允许引用的事实,不写固定句子。
按当前 source/received 水位最多补充 3 条已到达、未说过且可追溯的整场事实。
Luna 与 SOL 阅读同一封闭证据集;程序校验证据引用、硬事实、重复和姓名权限。
Fish 生成 WAV;高优先级硬事件可 cancel 低优先级背景音频,不平移解说。
PostgreSQL → S3 → HMAC Callback;只有下游 played 后才把事实记为已经说过。
05 · 关键接口与数据合同
控制、媒体、事实和播放四个数据面互不复用载荷。所有写接口都要求明确身份、版本或幂等键。
| 入口 | 调用方 → 接收方 | 职责 | 鉴权/幂等 |
|---|---|---|---|
POST /v1/matches | 管理端 → SUT | 创建比赛并保存 Match Package | Control Bearer;match_id |
POST /v1/matches/{id}/arm | 管理端 → SUT | 冻结赛前快照,返回一次性发布凭据 | Control Bearer;最多一场 ARMED/LIVE |
SRT publish | OBS/上游 → MediaMTX | 连续视频流;鉴权后创建 stream_epoch | 发布用户名、密码、path |
POST /v1/events | 事件供应商 → SUT | 写入不可变事件 revision | Event Bearer;event_id + revision + content hash |
POST callback_url | SUT → 播放端 | 推送 timed-audio upsert/cancel | HMAC;delivery_sequence + idempotency_key |
POST /v1/audio/ack | 播放端 → SUT | 确认 accepted / played / failed | 幂等键;只有 played 才消费赛中事实 |
FrameEnvelope
{
"match_id": "match-001",
"stream_epoch": "epoch-...",
"source_pts_ms": 100000,
"sequence": 100,
"codec": "jpeg",
"payload_base64": "...",
"trace_id": "media:...:100"
}MatchEvent revision
{
"event_id": "shot-4421",
"revision": 1,
"canonical_id": "attack-4419",
"source_pts_ms": 100000,
"received_pts_ms": 102400,
"event_type": "SHOT",
"status": "confirmed",
"authority": "official",
"temporal_scope": "live"
}Timed audio delivery
{
"action": "upsert",
"source_pts_ms": 100000,
"target_start_pts_ms": 100000,
"expires_pts_ms": 110000,
"priority": 0,
"codec": "wav",
"audio_url": "https://...",
"idempotency_key": "match:epoch:segment:r1"
}match_id 是整场长期业务主键;stream_epoch 是一次媒体连接。重连必须旋转 epoch,旧帧不能进入新 session,但同一场比赛的事件账本和赛中统计继续存在。06 · 赛前准备数据
Match Package 是审核后的比赛知识快照,不是随意拼起来的一段 prompt。
| 维度 | 示例 | 当前用途 |
|---|---|---|
| 比赛身份 | 赛事、轮次、日期、开球、场地 | 业务实体与语言背景 |
| 知识边界 | knowledge_cutoff、source_refs | 防未来信息泄漏与审计 |
| 初始状态 | phase、片段范围、起始比分 | 中途接入的起始世界 |
| 球队 | 稳定 ID、中文名、主客、教练 | 事件和文本统一映射 |
| 视觉身份 | 普通/门将球衣及颜色同义词 | 视觉辨别球队,不按球场方向猜 |
| 审核背景 | 历史、交锋、人物、形势、战术 | 长暂停中的知识卡 |
| 阵容上下文 | 首发变化、关系、换人 | 已存储,实时消费仍需增强 |
| 名单候选 | team、number、player ID/name | 姓名安全门,不等于视觉身份证明 |
07 · 赛中信号权威与取舍
视觉负责“看起来怎样”,官方事件负责“究竟发生了什么”。
| 场景 | 处理结果 |
|---|---|
| 官方事件与视觉冲突 | 官方事件决定硬事实与 play state;视觉只保留低风险画面关系 |
| 两帧都像进球/犯规 | 只能成为 corroborated visual candidate,仍是 confirmed=false |
| 官方事件迟到 | 按 received PTS 入场,不能让过去的 Writer 偷看未来;可解释此前候选 |
| 事件纠正或撤销 | 新 revision 重建投影;retracted 不再参与统计 |
| 回放旧进球 | temporal_scope=replay,可说“回放看一下”,不重复累计 |
| 没有事件流 | visual_only 安全解说继续;禁止硬事实和球员姓名 |
| Fast / Quality 竞争 | 有限时间内 Quality 优先;否则选择通过校验的 Fast |
| 两路都失败 | 静默和告警,不使用固定文案兜底 |
08 · 赛中数据库:不是 RAG,是可重建的世界
不可变事件账本
event_id + revision 保存完整历史;canonical_id 归并 attempt/result;重试幂等,低权威不能覆盖高权威。
因果投影
只使用当前时刻已经收到的 live/confirmed 事实;排除未来、回放、撤销和未确认数据。
已说事实账本
射门、射偏、牌、犯规和换人等形成可追溯统计;只有 played 后才算真正消费。
因此系统可以在下半场说“他上半场三次攻门都偏出”,也可以在球员第二张黄牌后理解纪律状态;每一句统计都能追溯到原始 event refs,而不是模型凭印象记忆。
09 · 解说风格、TTS 与交付
风格与语气
- Director 轮换动作、战术、评价和人物层
- 最近 8 句进入写作上下文,程序再阻止重复
- energy 映射 calm / focused / emphasis / excited
- pace 限制在 0.90–1.08,避免赶读
单音轨优先级
| 0 | 官方硬事件 |
| 1 | 即时动作 / 回放切入 |
| 2 | 普通战术与停止阶段 |
| 3 | 赛前知识背景 |
10 · 故障模型、降级与可观测性
实时系统不以“最终成功”作为唯一标准;迟到、错配和重复播放都属于失败。每个外部边界必须有超时、幂等和可审计结果。
| 故障 | 当前行为 | 用户侧表现 | 关键指标/告警 |
|---|---|---|---|
| 视频断流 | sampler 停止提交;session 标记 disconnected;重连旋转 epoch | 停止产生新句,已有比赛记忆保留 | last_frame_age、media_state、epoch rotation |
| VLM 超时/失败 | 该帧无观察;其他并发帧继续;事件通道不受影响 | 可能降低解说密度,不编造画面 | vision_ok/failed、p50/p95、inflight |
| 事件源中断 | 进入 visual_only 安全描述 | 仍能说场面,但不说进球、犯规、牌和姓名硬事实 | event_feed_age、visual_only_duration |
| 两路 Writer 均失败 | 静默,不生成固定兜底文案 | 出现空档,但不会重复机械句或制造事实 | writer_rejection、lane winner、silent count |
| Fish 超时 | 放弃该段,不让晚音频占用后续画面 | 该句缺失 | tts_ttfa、tts_total、deadline_slack |
| S3/Callback 暂时失败 | Outbox 保留并重试;下游按幂等键去重 | 预算内恢复则无感,过期则不播放 | delivery_retry、callback_late_ms、receipt_age |
| 事件纠错/撤销 | 追加新 revision,重建投影,不覆写历史 | 未来解说使用新事实;过去已播音频不可撤回 | revision_count、retracted_count |
建议的单场 SLO
- 有效输入时每 2 秒至少产生一帧
- clock offset p95 由上游合同确定
- Callback
late_ms=0比例至少 99.5% - 重复播放为 0,幂等冲突为 0
- 硬事实幻觉为 0 容忍
- 事件链路与视频链路分开计量可用性
每段音频的审计链
match_id / stream_epoch frame sequence + source PTS vision evidence refs world revision director slot + fact refs writer lane / rejection TTS latency object key + callback played receipt
11 · 当前能力边界
已经具备
- 无 GPU 的真实视频全链路
- 官方事件与视觉的因果融合
- 双路真实模型择优
- 赛中长期统计和去重
- S3、Callback、HMAC、重试和 receipt
- 断流保留比赛记忆,重连旋转 epoch
仍需建设
- 连续球衣号码 + track + roster 绑定
- 正式供应商事件与时钟合同
- Fish chunk 级流式播放
- 90+ 分钟非循环全场验收
- 正式 SSO/RBAC、HA、备份和告警
- 历史 run metrics 持久化报告
12 · 当前线上证据与结论强度
最近的 60 秒线上回归验证:无事件安全视觉不会再出现“视觉成功但写作 0”;金标 sidecar 只按精确视频 SHA 匹配;文件 EOF 后有界 drain 10 秒并自动释放活动槽位。