二、业务场景
同声传译(Simultaneous Interpretation,SI)广泛应用于 跨国会议、跨境直播、海外客服、在线教育、出海带货 等场景。与"录完再翻"的离线翻译不同,SI 要求在演讲者开口后的 1–3 秒内 就给出译文,并随着演讲持续逐字、逐句增量更新。
三、本 Demo 能力矩阵
·多输入源:浏览器麦克风实时流 / 本地 WAV/PCM 文件
·语向覆盖(遵循接口限制):
·源语言:zh(中文)、en(英文)、zh_en(中英混)
·目标语言:zh(中文)、en(英文)、zh_en(中英混)
·双翻译模型:hunyuan-translation-lite(低时延,默认)/ hunyuan-translation(高质量)
五、环境依赖
·Go 1.21+
·浏览器(Chrome 、 Edge 、 Safari)
·腾讯云账号 → 开通「语音识别 – 实时语音翻译」
音频规格(接口硬约束):16kHz 采样率、16bit 位深、单声道 PCM/WAV。
六、编辑配置文件
先拷贝一份配置文件:
cp config.json.example config.json
编辑配置文件 config.json:
{ "port": 8080, "appid": "你的AppID", "secret_id": "你的SecretID", "secret_key": "你的SecretKey"}
七、启动项目
运行以下命令后,打开浏览器访问 http://localhost:8080 即可使用
cd speech-translate-demogo mod tidygo run .
八、使用翻译功能
·麦克风模式:选择源/目标语言 > 单击开始录音 > 授权麦克风 > 字幕实时出现。
·文件模式:切换到音频文件 > 拖拽 WAV/PCM > 开始翻译。
·悬浮字幕:单击悬浮字幕按钮,可拖拽到屏幕任意位置,适合叠加到直播/会议画面上。
十一、目录结构
speech-translate-demo/├── main.go # 入口:加载配置 + 路由注册├── handler.go # WebSocket 业务层 & 音频处理├── sdk/│ └── speechtranslate.go # 自研 SDK:签名、协议、事件├── static/│ ├── index.html # 单页 UI│ ├── app.js # 麦克风采集 + WS 通信 + 字幕渲染│ └── style.css # 会议风样式├── config.json # 密钥配置(需自行创建)└── go.mod
十四、代码示例
使用 腾讯云语音 Go SDK 创建实例:
credential := common.NewCredential(secretID, secretKey)listener := &MyListener{} // 实现 SpeechTranslateListener 接口translator := asr.NewSpeechTranslator( appID, credential, "zh", // 源语言 "en", // 目标语言 "hunyuan-translation-lite", // 翻译模型 listener,)translator.VoiceFormat = 1 // PCM 格式translator.Start() // 建立连接translator.Write(pcmChunk) // 持续写入音频translator.Stop() // 结束翻译
实现 SpeechTranslateListener 接口即可接收全部事件:
type SpeechTranslateListener interface { OnTranslateStart(*SpeechTranslateResponse) OnSentenceBegin(*SpeechTranslateResponse) OnTranslateResultChange(*SpeechTranslateResponse) OnSentenceEnd(*SpeechTranslateResponse) OnTranslateComplete(*SpeechTranslateResponse) OnFail(*SpeechTranslateResponse, error)}
注意:OnSentenceBegin 和 OnSentenceEnd 可能在同一帧触发(短句场景)。当 sentence_id 变化且 sentence_end=true 时,SDK 会先发 Begin 再发 End。
十五、响应结构
每次回调收到的数据结构如下:
{ "code": 0, "message": "success", "voice_id": "xxx-uuid", "sentence_id": "1", "result": { "source": "zh", "target": "en", "source_text": "今天天气怎么样", "target_text": "How is the weather today", "start_time": 0, "end_time": 1800, "sentence_end": true }, "final": 0}
| 字段 | 类型 | 说明 |
| code | int | 0=成功;非 0 见错误码表 |
| message | string | 错误描述 |
| voice_id | string | 会话唯一 ID(排查必备) |
| sentence_id | string | 当前句子 ID,同一句内不变 |
| result.source_text | string | 识别到的源文(到目前为止完整内容,非增量) |
| result.target_text | string | 译文(到目前为止完整内容,非增量) |
| result.start_time/end_time | uint32 | 当前句相对会话开始的 ms 时间戳 |
| result.sentence_end | bool | 当前句是否已终态 |
| final | uint32 | 1=整个会话结束 |
十六、两个关键标志
响应里有两个容易混淆的“结束”标志,粒度完全不同:
| 标志 | 粒度 | 含义 |
| result.sentence_end=true | 句子级 | 当前句结束,还会有下一句 |
| final=1(顶层) | 会话级 | 整个翻译流程结束,连接即将关闭 |
一次完整翻译会话中,多个句子的事件流如下:
┌──────────────────────────────────────────────────────────────┐│ 会话开始 (translate_start, voice_id="abc-123") ││ ││ ┌─ 句子1 (sentence_id="1") ──────────────────────────┐ ││ │ sentence_begin → result_change × N → sentence_end │ ││ └─────────────────────────────────────────────────────┘ ││ ││ ┌─ 句子2 (sentence_id="2") ──────────────────────────┐ ││ │ sentence_begin → result_change × N → sentence_end │ ││ └─────────────────────────────────────────────────────┘ ││ ││ … 更多句子 … ││ ││ 会话结束 (translate_complete, final=1) │└──────────────────────────────────────────────────────────────┘
十七、“全量覆盖”语义
注意:source_text / target_text 每次都是该句到目前为止的完整内容,并非增量片段。前端直接覆盖即可,切勿拼接。
以一个完整句子的事件流为例:
时间轴 (sentence_id="1"): t1 result_change source_text="你" target_text="You" t2 result_change source_text="你好" target_text="Hello" t3 result_change source_text="你好世界" target_text="Hello world" t4 sentence_end source_text="你好世界。" target_text="Hello, world." ^^^^^^^^^^^ 每次都是完整内容,直接覆盖即可
十八、前端事件分发与字幕合并
核心数据结构使用 sentence_id 作为 key 的 Map + 有序数组:
let sentences = {}; // { [sentence_id]: { source, target, final } }let sentenceOrder = []; // 保证渲染顺序
前端通过 handleServerMessage 的 switch-case 逐事件分发处理:
function handleServerMessage(msg) { switch (msg.type) { case 'translate_start': // 会话就绪,麦克风模式下开始采集音频 setStatus('录音中…', 'recording'); if (inputMode === 'mic') startAudioCapture(); break; case 'sentence_begin': // 新句子:以 sentence_id 为 key 创建条目、入队 const sid1 = msg.data.sentence_id; if (!sentences[sid1]) { sentences[sid1] = { source: '', target: '', final: false }; sentenceOrder.push(sid1); } renderSentences(); break; case 'result_change': // 中间结果:全量覆盖 source/target(不是增量拼接!) const sid2 = msg.data.sentence_id; if (!sentences[sid2]) { sentences[sid2] = { source: '', target: '', final: false }; sentenceOrder.push(sid2); } sentences[sid2].source = msg.data.result.source_text || ''; sentences[sid2].target = msg.data.result.target_text || ''; renderSentences(); break; case 'sentence_end': // 终态:最后一次覆盖 + 标记 final const sid3 = msg.data.sentence_id; sentences[sid3].source = msg.data.result.source_text || ''; sentences[sid3].target = msg.data.result.target_text || ''; sentences[sid3].final = true; renderSentences(); break; case 'translate_complete': // 整个会话结束:释放麦克风、AudioContext、定时器 setStatus('已完成', 'connected'); stopAudioCapture(); break; case 'error': // 错误兜底 setStatus('出错了', ''); stopAudioCapture(); break; }}
20、效果调优建议
音频质量直接影响翻译效果,为保证从音频中提取正确语义,建议你从以下维度来提升音频质量。
| 维度 | 建议 |
| 降噪 | 开启浏览器 noiseSuppression: true |
| 回声消除 | 开启 echoCancellation: true,避免扬声器回传 |
| 采样率 | 优先以 16kHz 原生采集,避免额外重采样损耗 |
| 物理环境 | 使用指向性麦克风、远离噪声源、距离 20–40cm |
21、参考资料
·实时语音翻译 WebSocket 接口文档
·腾讯云语音翻译 Go SDK
·同声传译完整工程