一、系统概述
实时翻译子系统是 Vocalink 的技术核心。它将用户 A 说的中文语音,在 < 500ms 内变成用户 B 看到的英文文字(+ P1 听到的英文语音)。
核心指标:
| 指标 | 目标 | 测量方式 |
| 端到端延迟 P95 | < 500ms | 从音频采集到译文渲染的时间戳差 |
| 翻译 BLEU (中英) | > 40 | 每周批量评估集测试 |
| ASR 词错率 (CER) | < 8%(中文)/ < 5%(英文) | 内部标注集 |
| 翻译可用性 | 99.9% | 5 分钟粒度健康检查 |
| 并发翻译流 | 5000+ 路 | Kafka consumer 并行度 |
1.1 全链路数据流
🎙️
音频采集
iOS/Android
16kHz Opus0ms
→
📡
WebSocket 上行
WSS + 分帧
20ms/chunk20-50ms
→
🗣️
ASR 语音识别
Google Speech
流式150-200ms
→
🌐
MT 机器翻译
Google Translate
流式150-200ms
→
🗄️
缓存检查
Redis
TTL 24h2-5ms
→
📤
Kafka 发布
subtitle-out
topic5-10ms
→
📱
客户端渲染
字幕滑入
+ TTS(P1)50-100ms
1.2 延迟预算分解
采集+编码 50ms
网络上行 50-100ms
ASR 150-200ms
MT 150-200ms
下行+渲染 50-100ms
总预算:450-650ms → 优化目标 P95 < 500ms(中位数 ~350ms)
二、ASR 语音识别模块
2.1 模块职责
| 项目 | 规格 |
| 引擎 | Google Speech-to-Text API (中文) + Azure Speech (英文) |
| 模式 | 流式识别(Streaming Recognize),非整句阻塞 |
| 音频格式 | Linear16, 16kHz, 单声道, 每 chunk 20ms |
| 语种检测 | 前 3 秒自动检测(Google auto-detect) |
| 中间结果 | 每 200-500ms 返回 is_final=false 的增量结果 |
| 最终结果 | is_final=true(句子结束/停顿 > 800ms) |
| 置信度阈值 | > 0.6 才进入翻译;< 0.6 标记 [uncertain] |
| 超时 | 静音 3 秒强制 flush final |
2.2 接口契约
客户端将采集的 Opus 音频解码为 PCM 后,通过 WebSocket 发送。每帧 20ms(640 bytes @ 16kHz/16bit)。
{ "type": "audio_chunk", "call_id": "call_abc123", "seq": 42, "timestamp_ms": 1715324800000, "audio": "base64_encoded_pcm_chunk", "sample_rate": 16000, "encoding": "linear16", "language_hint": "zh-CN" }
{ "type": "asr_result", "call_id": "call_abc123", "seq": 42, "is_final": false, "text": "我今天想去", "confidence": 0.87, "language_detected": "zh-CN", "start_time_ms": 1200, "end_time_ms": 1680 }
2.3 语种自动检测
# 语种检测逻辑(Translate Service 内部)
def detect_language(audio_chunk, history=[]):
# 前 3 秒使用 Google auto-detect
if len(history) < 3: # 3 seconds of audio
result = google_speech.stream_recognize(
audio=audio_chunk,
config={"language_code": "auto"}
)
detected = result.detected_language()
else:
# 3 秒后锁定语种(避免中途切换)
detected = history[0].language
return detected # "zh-CN" or "en-US"
2.4 ASR 降级策略
| 场景 | 降级动作 | 用户感知 |
| Google API 超时 (>5s) | 切换 Azure Speech 重试 | 短暂延迟(1-2 秒) |
| 双引擎都失败 | 标记 [语音识别不可用] | 对方看到"暂时无法识别语音" |
| 置信度 < 0.4 | 显示 [unclear] + 原文波形 | 字幕显示灰色"听不清楚" |
| 网络丢包 > 20% | 降低采样到 8kHz + 降帧率 | 音质下降但识别仍工作 |
三、MT 机器翻译模块
3.1 模块职责
| 项目 | 规格 |
| 引擎 | Google Translate API(Phase 1 唯一引擎) |
| 模式 | 流式翻译(句子级,非逐词) |
| 语种对 | zh-CN ↔ en-US(Phase 1 仅 1 对) |
| 触发条件 | ASR 返回 is_final=true 或文本 > 15 字符 |
| 上下文窗口 | 最近 3 轮对话(约 200 token) |
| 术语库 | 中英双语文化术语表(持续迭代) |
| NER 过滤 | 人名/地名/专有名词不翻译(保留原文) |
| 口语优化 | 训练语料偏口语对话(非书面翻译) |
3.2 翻译请求接口
{ "call_id": "call_abc123", "seq": 42, "source_lang": "zh-CN", "target_lang": "en-US", "source_text": "我今天想去吃火锅", "context": ["你吃过火锅吗?", "没有,我来自英国"], "glossary": ["火锅→hotpot", "英国→UK"], "priority": "realtime" }
{ "call_id": "call_abc123", "seq": 42, "translated_text": "I want to eat hotpot today", "confidence": 0.92, "latency_ms": 187, "engine": "google_translate", "cached": false, "source_text": "我今天想去吃火锅" }
3.3 缓存策略
# Redis 缓存设计
import hashlib, json
def get_cache_key(source_text, src_lang, dst_lang):
key = f"{src_lang}:{dst_lang}:" + hashlib.md5(source_text.encode()).hexdigest()[:16]
return key
def translate_with_cache(text, src, dst):
cache_key = get_cache_key(text, src, dst)
# L1: Redis 缓存(命中率 ~30%)
cached = redis.get(cache_key)
if cached:
return json.loads(cached), True
# L2: 调用 Google Translate
result = google_translate.translate(text, src=src, dst=dst)
# 写入缓存(TTL 24h)
redis.setex(cache_key, 86400, json.dumps(result))
return result, False
| 缓存层 | 命中率 | 延迟 | TTL |
| L1 · 进程内 LRU | ~10% | < 1ms | 5 分钟 |
| L2 · Redis Cluster | ~30% | 2-5ms | 24 小时 |
| L3 · Google Translate | — | 150-200ms | — |
| 综合命中率 | ~35-40% | — | — |
3.4 降级策略
| 场景 | 降级动作 | 用户感知 |
| Google Translate 超时 | 重试 1 次(指数退避 500ms) | 字幕延迟 1-2 秒 |
| 重试仍失败 | 显示原文 + [翻译中…] | 对方看到原文(不懂) |
| 连续 5 次失败 | 切换备用引擎(DeepL/微软) | 短暂中断后恢复 |
| 全部引擎失败 | 降级为纯语音模式 | 无字幕,仅语音 |
四、TTS 语音合成模块(P1)
📌 Phase 1 范围:TTS 在 Phase 1 后期(M5-M6)上线,属于 P1 功能。Phase 1 前 4 个月仅提供文字字幕。
4.1 模块职责
| 项目 | 规格 |
| 引擎 | Azure TTS(Neural Voices) |
| 语音选择 | zh-CN → "en-US-JennyMultilingualNeural" en-US → "zh-CN-XiaoxiaoNeural" |
| 延迟目标 | < 300ms(从文本到音频输出) |
| 音频格式 | MP3, 24kHz, 输出到客户端播放 |
| 触发 | 仅 VIP 用户(免费用户仅文字) |
| 缓存 | 同 MT 缓存键,TTL 7 天 |
4.2 TTS 接口
{ "call_id": "call_abc123", "seq": 42, "text": "I want to eat hotpot today", "target_lang": "en-US", "voice": "en-US-JennyMultilingualNeural", "user_tier": "vip", "cached": false }
4.3 TTS 降级
| 场景 | 降级 |
| Azure 超时 | 跳过 TTS,仅显示文字字幕 |
| VIP 降级为免费 | 即时切换为纯文字模式 |
| 网络差 | 降低 TTS 音频码率到 16kHz |
五、字幕分发(Subtitle Pipeline)
5.1 Kafka Topic 设计
| Topic | 分区数 | 保留期 | Producer | Consumer |
| audio-stream | 32 | 1 小时 | Call Service | Translate Service |
| asr-results | 32 | 1 小时 | ASR Worker | Translate Worker |
| subtitle-out | 32 | 1 小时 | Translate Service | Call Service → 客户端 |
| translate-events | 16 | 7 天 | Translate Service | Analytics + 模型迭代 |
5.2 字幕推送接口
{ "type": "subtitle", "call_id": "call_abc123", "seq": 42, "is_final": true, "source_lang": "zh-CN", "target_lang": "en-US", "source_text": "我今天想去吃火锅", "translated_text": "I want to eat hotpot today", "tts_audio_url": "https://cdn.vocalink.sg/tts/abc123_42.mp3", "latency_ms": 342, "confidence": 0.92 }
5.3 客户端字幕渲染规则
| 规则 | 实现 |
| 中间结果 | 半透明显示(opacity 0.5),底部滑入动画 200ms |
| 最终结果 | 实色显示 + 小号译文在下方 |
| 新句子 | 旧句上移 20px 淡出(300ms),新句从底部滑入 |
| 超长文本 | > 2 行截断 + "…" + 点击展开 |
| 翻译中 | 灰色斜体 "Translating…"(不超过 1 秒) |
| 翻译失败 | 显示原文 + 红色小字 "Translation unavailable" |
| TTS 音频 | 收到后自动播放(用户可关闭),不抢通话音频通道 |
七、降级与容灾策略
7.1 降级金字塔
✅ Level 0
全功能
ASR+MT+TTS
延迟 < 500ms
↓
⚠️ Level 1
无 TTS
文字字幕正常
VIP 降级为文字
↓
⚠️ Level 2
无翻译
纯语音通话
显示原文
↓
🚫 Level 3
文字模式
语音不可用
切文字消息
↓
❌ Level 4
服务中断
显示维护页
预估恢复时间
7.2 自动降级触发条件
def check_degradation(metrics):
# Level 判定(从高到低检查)
if metrics.asr_latency_p95 > 1000 or metrics.mt_latency_p95 > 1000:
return "level_2" # 无翻译
if metrics.tts_latency_p95 > 800:
return "level_1" # 无 TTS
if metrics.kafka_lag > 50000:
return "level_2" # 降级为纯语音
if metrics.audio_rtt > 500:
return "level_3" # 文字模式
return "level_0" # 全功能
# 降级通过 Config Service 实时推送
config_service.set_flag("translate.degradation_level", level)
7.3 容灾方案
| 故障 | RTO | 方案 |
| Google API 区域故障 | < 2 分钟 | 自动切换 Azure Speech/Translate |
| Kafka 集群故障 | < 5 分钟 | 降级为直接 gRPC 调用(绕开 Kafka) |
| Translate Service 全挂 | < 1 分钟 | Config Service 推送 level_2 降级 |
| AWS ap-southeast-1 故障 | < 15 分钟 | DNS 切换到 GCP asia-southeast1 |
| Redis 全挂 | 即时 | L1 缓存失效,全部走 L3 API(慢但可用) |
八、翻译引擎演进路线
| 阶段 | ASR | MT | TTS | BLEU | 成本/千次 |
| Phase 1 (M1-M6) | Google(zh)+Azure(en) | Google Translate | Azure TTS | > 40 | ~$0.50 |
| Phase 1.5 (M5-M6) | 同上 + 微调 | 同上 + 术语库 | Azure + 缓存优化 | > 42 | ~$0.40 |
| Phase 2 (M7+) | 混合:高频自研 | 混合:长尾 API | 自研 TTS(P2.5) | > 42 | ~$0.20 |
| Phase 3 (Y2) | 自研为主 | 自研(PyTorch+ONNX) | 自研 Neural TTS | > 45 | ~$0.05 |
8.1 自研翻译模型计划
# Phase 3 自研模型训练 pipeline
class TranslationModel:
def __init__(self, src_lang, dst_lang):
self.model = load_pretrained("mbart-large-50")
self.tokenizer = load_tokenizer(src_lang, dst_lang)
def finetune(self, dataset):
# 使用 Vocalink 真实对话语料(脱敏)
for epoch in range(10):
loss = self.model.train_step(dataset)
if loss < 0.01:
break
def export_onnx(self, path):
# 导出 ONNX 用于生产推理(快 + 跨平台)
onnx.export(self.model, path, opset=17)
def benchmark(self, test_set):
bleu = compute_bleu(self.translate(test_set), test_set.references)
latency = measure_latency(self.translate, p95_target=300)
return {"bleu": bleu, "p95_latency_ms": latency}
💡 自研 ROI 分析:
| 指标 | Phase 1 (API) | Phase 3 (自研) | 节省 |
| 千次翻译成本 | $0.50 | $0.05 | 90% |
| 月翻译量 (P3) | 1 亿次 | 1 亿次 | — |
| 月成本 | $5 万 | $0.5 万 | $4.5 万/月 |
| 年节省 | — | — | $54 万/年 |
| 自研投入 | ~$30 万(2 名 NLP 工程师 × 6 个月) | 6 个月回本 |