操作背景
在线会议、访谈录制、庭审记录,这些常见场景都有一个共同的需求:会后快速生成纪要,并清楚标注“谁说了什么”。
传统的实时语音识别只能输出一段连续的文字流。会后整理时,人工标注说话人身份既耗时又容易出错。腾讯云实时语音识别提供了句子模式和话者分离的组合能力,可以在实时转写的同时,以完整句子为单位输出,并自动区分说话人,天然适合会议纪要的实时上屏和事后归档。
本文将带您从零搭建一个会议实时转写 Demo。您可以先运行体验效果,再深入了解接入细节。
快速体验
我们准备了一个开箱即用的 Demo(Go 后端 + HTML 前端),按照下面步骤可快速启动。
环境要求
Go 1.18 或以上
腾讯云账号,已开通 语音识别服务
准备好 API 密钥:AppID、SecretID、SecretKey
启动步骤
1. 进入 Demo 目录。
cd tencent-realtime-asr-demo
2. 首次使用:自动创建配置文件 config.json。
make setup
3. 在
config.json 中填入您的密钥:{"port": 8080,"appid": "您的AppID","secret_id": "您的SecretID","secret_key": "您的SecretKey"}
4. 编译并启动服务(自动打开浏览器)
make run
浏览器会自动打开
http://localhost:8080,您应该看到如下界面:
开启话者分离开关,单击开始识别,对着麦克风说话,即可看到实时转写效果。
整体架构
Demo 采用 浏览器 > 后端 > 腾讯云 的 WebSocket 双层桥接架构:

1. 浏览器采集麦克风音频(16kHz / 16bit / 单声道 PCM),通过 WebSocket 实时发送给后端。
2. 后端将音频透传至腾讯云实时语音识别服务。
3. 腾讯云返回识别结果,后端转发给浏览器渲染。
为什么需要后端桥接?因为直连腾讯云需要 SecretKey 签名,密钥不能暴露在前端。后端负责签名和转发,前端只需处理录音和展示。
接入指南
核心概念
在开始接入前,您需要先了解以下两个关键能力:
句子模式:服务端每次回调都返回当前所有句子的完整列表,包含正在识别的和已经确认的。客户端只需用最新列表覆盖渲染,无需自行拼接文字或维护句子状态。在腾讯云 Go SDK 中,使用
SpeakerRecognizer 类会自动启用句子模式。话者分离:在句子模式的基础上,为每句话标注说话人编号(
speaker_id)。服务端自动判断有几个人在说话,无需预设人数。
会议场景推荐:同时启用句子模式和话者分离。 无论是多人会议还是单人演讲,句子模式都是更好的选择,上屏逻辑简单,体验流畅。多人场景再叠加话者分离即可。
关键参数
使用
SpeakerRecognizer 时,SDK 会自动处理连接签名和句子模式设置。开发者只需关注以下参数:参数 | 推荐值 | 说明 |
engine_model_type | 16k_zh_en_speaker | 话者分离专用引擎,支持中英文混合识别 |
speaker_diarization | 1 | 开启话者分离 |
sentence_strategy | 0 或 1 | 分句策略,决定定稿的颗粒度,详见下方说明 |
分句策略:单句 vs 段落
两种策略都会实时输出中间结果,用户都能即时看到文字。区别在于什么时候确认一段文字为最终结果:
分句策略 | 定稿条件 | 话者分离展示效果 |
0 :语义单句 | 一句话说完即定稿 | 每句话是一个独立气泡 |
1 :段落 | 段落超过 50 字或包含 3 个以上子句时定稿 | 同一人连续说的多句话合并为一个段落气泡 |
两种策略的实时性没有差异,都是边说边出字。差别在于阅读体验:
单句模式适合需要快速逐句确认的场景,每句独立,定稿快。
段落模式适合希望段落聚合展示的场景,阅读更流畅,更接近会议纪要的最终形态。
代码示例
以下代码为核心思路示意,为突出逻辑主线进行了精简。完整工程(含异常处理、日志、配置管理等)见附录的完整工程。
import ("github.com/tencentcloud/tencentcloud-speech-sdk-go/asr""github.com/tencentcloud/tencentcloud-speech-sdk-go/common")credential := common.NewCredential(secretID, secretKey)listener := &MyListener{} // 实现 SpeakerRecognitionListener 接口rec := asr.NewSpeakerRecognizer(appID, credential, "16k_zh_en_speaker", listener)rec.SpeakerDiarization = 1 // 开启话者分离rec.SentenceStrategy = 0 // 0=语义单句,1=段落rec.Start() // 建立连接rec.Write(pcmData) // 持续写入音频数据rec.Stop() // 结束识别
需要实现的回调接口:
type SpeakerRecognitionListener interface {OnRecognitionStart(*SpeakerRecognitionResponse) // 连接建立OnRecognitionSentences(*SpeakerRecognitionResponse) // 句子列表更新(核心)OnSentenceEnd(*SpeakerRecognitionResponse) // 识别结束OnFail(*SpeakerRecognitionResponse, error) // 错误}
核心是
OnRecognitionSentences——每次被调用时,resp.Sentences.SentenceList 就是当前所有句子的最新快照,直接遍历渲染即可。
返回结果解析
句子列表
每次回调返回的数据结构如下:
{"code": 0,"voice_id": "abc1234-...","sentences": {"sentence_list": [{"sentence_id": 0, // 句子编号"sentence": "今天我们讨论一下项目进度", // 识别文本"sentence_type": 1, // 1=已确认"speaker_id": 0, // 说话人 0"start_time": 1200, // 开始时间(ms)"end_time": 3800 // 结束时间(ms)},{"sentence_id": 1,"sentence": "好的,我先汇报前端的情况","sentence_type": 1, // 1=已确认"speaker_id": 1, // 说话人 1(不同的人)"start_time": 4000,"end_time": 6500},{"sentence_id": 2,"sentence": "本周完成了登录模块的","sentence_type": 0, // 0=识别中,文字还会变"speaker_id": 1,"start_time": 6600,"end_time": 8000}]}}
关键字段
字段 | 含义 |
sentence_id | 句子编号。用来追踪同一句话在多次回调中的变化 |
sentence | 识别出的文字 |
sentence_type | 当前句子识别状态,0表示识别中,文字还在变化;1表示已确认,不会再改变 |
speaker_id | 说话人编号,从 0 开始。-1 表示暂时还无法判断是谁在说 |
start_time / end_time | 时间戳(毫秒) |
渲染逻辑
句子列表是一个不断增长的快照,客户端每次收到回调直接用最新列表整体渲染:
sentence_type=1 的句子已经确认,用正常样式展示sentence_type=0 的句子还在识别中,用斜体或半透明展示,提示用户"文字可能还会变"根据
speaker_id 为不同说话人分配颜色和位置
关于 speaker_id = -1
服务端需要积累足够的音频才能判断说话人身份。一句话刚开始时,
speaker_id 可能是 -1。随着音频积累,后续回调会将其更新为具体编号。
推荐处理方式:将
speaker_id=-1 的文字以灰色斜体追加到上一位已确认说话人的气泡末尾。身份确认后,自动移入正确的气泡。这样可以避免界面跳动。
上屏方案
聊天气泡
会议场景推荐的展示方式。为每位说话人分配颜色,按出场顺序左右交替:
if (!(speakerId in speakerOrderMap)) {speakerOrderMap[speakerId] = speakerOrderCounter++;}const orderIdx = speakerOrderMap[speakerId];const side = orderIdx % 2 === 0 ? 'left' : 'right';const color = COLORS[orderIdx % COLORS.length];
每次收到句子列表,按
sentence_id 创建或更新对应气泡sentence_type=0 显示为斜体(识别中),sentence_type=1 显示为正常样式(已确认)speaker_id 变化时(从 -1 变为具体值),将气泡移到正确位置
Demo 工程结构
以下为 Demo 工程的目录结构,及各文件的作用介绍。
tencent-realtime-asr-demo/├── main.go # HTTP 服务入口├── handler.go # WebSocket 桥接:音频转发 + 结果回传├── config.go # 配置读取└── static/├── index.html # 页面结构├── app.js # 录音、通信、渲染└── style.css # 样式
handler.go:根据前端参数创建
SpeakerRecognizer 实例,实现回调接口,将句子列表通过 WebSocket 转发给前端。app.js:麦克风采集、WebSocket 通信、气泡/字幕渲染。
效果调优建议
场景 | 建议 |
话者分离不够准确 | 确保不同说话人之间有明显的停顿;避免多人同时说话(重叠语音会影响分离效果) |
环境噪音较大 | 使用带降噪功能的麦克风;尽量在安静环境下使用 |
会议人数较多(>5人) | 话者分离在 3-5 人时效果最佳,人数过多可能出现误判 |
希望段落更完整 | 将 sentence_strategy 设为 1(段落模式),同一人连续发言的内容会聚合展示 |
识别结果有错别字 | 可配合热词功能( hotword_id 或 hotword_list),将会议中的专业术语加入热词表 |
常见问题
为什么推荐用 SpeakerRecognizer 而不是 SpeechRecognizer?
SpeakerRecognizer 内置句子模式,每次返回全量句子列表,客户端只需遍历渲染即可,无需维护复杂的状态机。即使不开启话者分离,句子模式本身也非常适合上屏场景。SpeechRecognizer 则是逐片段流式推送,客户端需要自行处理句子的开始、更新、结束等状态。