通过实时语音识别实现会议场景话者分离

在线会议、访谈录制、庭审记录,这些常传统的实时语音识别只能输出一段连续的文字流。会后整理时,人工标注说话人身份既耗时又容易出错。腾讯云实时语音识别提供了句子模式和话者分离的组合能力,可以在实时转写的同时,以完整句子为单位输出,并自动区分说话人,天然适合会议纪要的实时上屏和事后归档。

相关产品
语音识别 产品页,含版本规格、价格与购买入口。

一、操作背景

这篇会带你从零搭建一个会议实时转写 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_2.0 话者分离专用引擎,支持中英文混合识别
speaker_diarization 1 开启话者分离
sentence_strategy 0 或 1 分句策略,决定定稿的颗粒度,详见下方说明

九、分句策略:单句 vs 段落

两种策略都会实时输出中间结果,用户都能即时看到文字。区别在于什么时候确认一段文字为最终结果:

分句策略 定稿条件 话者分离展示效果
0 :语义单句 一句话说完即定稿 每句话是一个独立气泡
1 :段落 段落超过 50 字或包含 3 个以上子句时定稿 同一人连续说的多句话合并为一个段落气泡

两种策略的实时性没有差异,都是边说边出字。差别在于阅读体验:

·单句模式适合需要快速逐句确认的场景,每句独立,定稿快。

·段落模式适合希望段落聚合展示的场景,阅读更流畅,更接近会议纪要的最终形态。

十、代码示例

以下代码为核心思路示意,为突出逻辑主线进行了精简。完整工程(含异常处理、日志、配置管理等)见附录的完整工程。

使用 腾讯云语音 Go SDK 创建识别器:

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_2.0", 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),将会议中的专业术语加入热词表

20、常见问题

为什么推荐用 SpeakerRecognizer 而不是 SpeechRecognizer?

SpeakerRecognizer 内置句子模式,每次返回全量句子列表,客户端只需遍历渲染即可,无需维护复杂的状态机。即使不开启话者分离,句子模式本身也非常适合上屏场景。SpeechRecognizer 则是逐片段流式推送,客户端需要自行处理句子的开始、更新、结束等状态。

21、相关链接

·实时语音识别 WebSocket 接口文档

·腾讯云语音识别 Go SDK

·Demo 完整工程代码

赞 (0)
上一篇 5天前
下一篇 5天前