文档中心>实践教程>语音识别>通过实时语音翻译实现同声传译

通过实时语音翻译实现同声传译

最近更新时间:2026-04-22 17:39:23

我的收藏
本文基于 speech-translate-demo 项目,采用 Go + WebSocket + 原生前端,封装腾讯云「同声传译」能力,提供麦克风实时流 / 本地音频文件两种输入方式,内建悬浮字幕与会议级 UI。

操作背景

业务场景

同声传译(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-demo
go mod tidy
go 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)
}
注意:
OnSentenceBeginOnSentenceEnd 可能在同一帧触发(短句场景)。当 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;
}
}

附录

效果调优建议

音频质量直接影响翻译效果,为保证从音频中提取正确语义,建议您从以下维度来提升音频质量。
维度
建议
降噪
开启浏览器 noiseSuppression: true
回声消除
开启 echoCancellation: true,避免扬声器回传
采样率
优先以 16kHz 原生采集,避免额外重采样损耗
物理环境
使用指向性麦克风、远离噪声源、距离 20–40cm

参考资料