1. 会议记录这件事,到底难在哪里
开会这件事,估计每个职场人都有一肚子话想说。尤其是那种一开就是两三个小时的跨部门对齐会、需求评审会、项目复盘会,开完之后脑子嗡嗡的,还得硬撑着整理会议纪要。我以前就吃过这个亏——会上讨论得热火朝天,各种结论、待办、责任人满天飞,结果会后凭记忆整理,漏了关键决策不说,还把两个不同同事的观点记混了,第二天被领导当面纠正,那叫一个尴尬。
后来我就琢磨,能不能让机器帮我干这活儿?市面上确实有不少会议记录工具,但要么收费贵得离谱,要么识别准确率堪忧,尤其是遇到带口音的普通话、中英文夹杂的技术讨论,那识别结果简直没法看。直到我开始研究腾讯云的AI语音识别服务,发现它提供了一套相当完整的语音转文字能力,而且支持实时流式识别和录音文件识别两种模式,正好能覆盖会议场景的核心需求。
这篇文章我想聊的,就是怎么用腾讯云AI语音识别从零搭一个“会议小帮手”。它不是那种开箱即用的商业产品,而是一个你自己能掌控、能定制、能跟现有工作流打通的轻量工具。适合谁看?有一定编程基础的后端或全栈开发者,对语音识别感兴趣想动手试试的技术爱好者,以及那些受够了手动整理会议纪要、想用技术手段解放自己的效率追求者。哪怕你之前没接触过语音识别,跟着思路走也能理解整个链路的运作逻辑。
核心关键词我先摆出来:腾讯云AI语音识别、实时语音转写、录音文件识别、会议纪要自动化、语音转文字。这几个词会贯穿全文,也是整个项目落地的技术骨架。
2. 整体方案设计与技术选型思路
2.1 为什么选腾讯云AI语音识别
做语音识别,摆在面前的路其实有好几条。开源的方案像Whisper系列,本地部署不要钱,但想要好的识别效果就得有GPU,而且实时性是个大问题;自建ASR模型就更不用想了,训练数据、算力、调参,没个团队根本搞不定。腾讯云AI语音识别吸引我的点在于几个方面:一是识别准确率在中文场景下确实能打,尤其是对专业术语和行业词汇的支持,它提供了热词表功能,可以自定义词汇权重;二是它同时提供实时流式和录音文件两种识别接口,这意味着我可以在会议进行中做实时字幕,会议结束后再做一次高精度的全量转写;三是接入成本低,SDK覆盖了主流语言,文档也算清晰,不用花太多时间在环境适配上。
还有一个很实际的考量:会议场景对延迟和稳定性的要求比较特殊。实时识别需要低延迟,不然字幕跟不上说话节奏;录音文件识别则需要高准确率,因为会后整理的纪要要尽量少出错。腾讯云这两套接口正好各司其职,实时用流式接口保证响应速度,会后用文件接口保证识别质量,这个组合拳打下来,体验就上去了。
2.2 两种识别模式的分工与配合
这里得把两种模式的区别讲清楚,不然后面实操容易混淆。
实时语音识别走的是WebSocket协议,音频流一边采集一边发送,服务端实时返回识别结果。它的优势是延迟低,适合做会议现场的实时字幕、关键词提醒。但缺点是受网络波动影响大,如果会议中途网络抖了一下,可能丢几秒的音频。而且实时识别为了追求速度,在语言模型的使用上会做一些取舍,准确率通常比文件识别略低。
录音文件识别则是把整段录音上传到对象存储,然后提交识别任务,服务端异步处理,完成后回调通知或轮询获取结果。它的优势是可以使用更复杂的声学模型和语言模型,识别准确率更高,而且支持说话人分离(也就是区分不同发言人),这对会议纪要来说太重要了——谁说的什么话,得能对上号。
我的设计思路是:会议进行时用实时识别做字幕和关键信息捕捉,同时本地录制一份高质量音频;会议结束后把录音上传,跑一遍文件识别,拿到带说话人分离的完整转写文本,再结合实时识别的结果做交叉校验。这样既保证了会中的即时性,又保证了会后的准确性。
2.3 系统架构的轻量化设计
整个“会议小帮手”的架构我尽量往轻了做,不想引入太多中间件。核心模块就四个:音频采集模块、实时识别模块、文件识别模块、结果处理模块。
音频采集用浏览器端的Web Audio API或者桌面端的音频库来实现,采集到的音频同时走两条路——一条编码成PCM流推给实时识别接口,另一条存成本地文件供会后上传。实时识别模块维护WebSocket连接,处理分片发送和结果拼接。文件识别模块负责上传录音、提交任务、轮询结果。结果处理模块把两种识别结果做对齐和合并,最终输出结构化的会议纪要。
存储方面,录音文件可以放腾讯云对象存储,识别结果先落本地数据库,方便后续检索和编辑。如果团队有现成的文档协作平台,还可以把纪要自动同步过去。整个系统不需要Kubernetes这种重装备,一台中等配置的云服务器加一个前端页面就能跑起来。
3. 核心细节解析与实操要点
3.1 音频采集与预处理的关键参数
音频质量直接决定识别效果,这一步偷懒后面全是坑。腾讯云AI语音识别对音频格式有明确要求:实时识别推荐使用16kHz采样率、16bit位深、单声道的PCM音频;录音文件识别支持wav、mp3、m4a等常见格式,但同样建议16kHz以上采样率。
为什么是16kHz?因为人声的主要频率范围在300Hz到3400Hz之间,根据奈奎斯特采样定理,采样率至少要是信号最高频率的两倍才能无失真还原。16kHz的采样率能覆盖到8kHz的频率范围,对于语音识别来说足够用了。如果用8kHz,高频部分丢失,像“四”和“十”这种容易混淆的音就更难区分。44.1kHz虽然更高,但对语音识别来说属于过度采样,只会增加数据传输量,对准确率提升微乎其微。
位深选16bit也是同理,8bit的量化精度不够,信噪比太低,识别引擎容易把背景噪声当成语音。单声道则是必须的,因为识别引擎处理的是单通道信号,立体声反而需要额外做下混处理。
实操中还有一个容易忽略的点:降噪和增益控制。会议室的空调声、键盘敲击声、远处同事的交谈声,都会被麦克风收进去。我建议在采集端就做一层轻量降噪,比如使用Web Audio API的BiquadFilterNode做高通滤波,滤掉200Hz以下的低频噪声。增益方面,把音量控制在-6dB到-3dB之间,避免削波失真。这些预处理虽然简单,但能让识别准确率提升不少。
3.2 实时识别接口的调用逻辑
实时语音识别的调用流程可以拆成几步:建立WebSocket连接、发送配置参数、分片发送音频数据、接收识别结果、结束会话。
建立连接时需要在URL里带上签名参数,这个签名用腾讯云的密钥对生成,具体算法文档里有详细说明。连接建立后,第一件事是发送一个JSON格式的配置帧,告诉服务端音频的编码格式、采样率、是否开启中间结果等。这里有个细节:voice_format字段要跟实际音频格式一致,填错了服务端解析出来的就是噪声。
音频分片发送有个讲究。不能一次性把整段音频发过去,那样就失去实时性了;也不能发得太碎,每个包太小会增加网络开销。我的经验是每200毫秒发一包,每包大约6400字节(16kHz × 16bit × 0.2s ÷ 8 = 6400字节)。这个粒度下,延迟和吞吐量比较平衡。
接收结果时要注意区分中间结果和最终结果。中间结果会随着音频的输入不断修正,比如先说“会议”,后面可能修正成“会议室”。最终结果才是确定下来的文本。做实时字幕时,中间结果可以用来快速显示,但做纪要时一定要用最终结果。
注意:实时识别接口对音频流的连续性有要求,如果超过一定时间没有发送数据,服务端可能会主动断开连接。建议在静音时段也发送静音帧,保持连接活跃。
3.3 录音文件识别与说话人分离
录音文件识别的流程相对简单,但有几个参数值得仔细配置。
上传文件到对象存储后,调用识别接口时需要指定channel_num(声道数)和speaker_diarization(是否开启说话人分离)。说话人分离这个功能强烈建议开启,它能把转写文本按发言人切分,输出类似“发言人1:这个方案我觉得可行”的格式。虽然它不能直接告诉你发言人是谁,但至少把不同人的话分开了,会后对照参会名单手动标注一下就行。
热词表是另一个提升准确率的利器。腾讯云支持自定义热词,你可以把项目名称、产品代号、技术术语、同事姓名等提前录入,识别引擎会优先匹配这些词。比如你们团队把“微服务”叫“微服”,不加热词可能识别成“威武”,加了热词就能正确识别。热词表通过控制台或API创建,然后在识别请求里带上热词表ID即可。
还有一个参数是filter_dirty和filter_modal,分别用于过滤脏词和语气词。会议记录里“嗯”“啊”“那个”这些词太多会影响阅读,开启过滤后文本会干净很多。但要注意,如果会议内容涉及需要逐字记录的场合,比如法律合规相关的讨论,就不要开过滤,保持原始文本。
3.4 结果后处理的几个实用技巧
识别出来的原始文本是一大段没有标点的文字,直接看很费劲。后处理的第一步是断句和加标点。腾讯云的识别结果里其实已经包含了一些标点预测,但不够精细。我通常会用规则加模型的方式再做一轮处理:先根据停顿时间(识别结果里会返回每个词的起止时间)在超过500毫秒的停顿处断句,再用一个轻量的标点预测模型补上逗号、句号、问号。
第二步是文本归一化。把“二零二四年”转成“2024年”,把“百分之三十”转成“30%”,把英文缩写统一大小写。这些规则看起来琐碎,但能大幅提升纪要的可读性。
第三步是关键信息抽取。会议纪要的核心无非是:谁、什么时候、要做什么、什么时候完成。我写了一个简单的规则引擎,匹配“负责”“跟进”“截止”“之前完成”这类关键词,把相关的句子抽出来,自动生成待办列表。虽然不如大模型那么智能,但胜在稳定可控,不会胡编乱造。
4. 完整实操流程与核心环节实现
4.1 环境准备与依赖安装
先把基础环境搭起来。我用的技术栈是Python做后端、Vue做前端、腾讯云Python SDK做识别调用。Python版本建议3.8以上,太老的版本有些依赖装不上。
pip install tencentcloud-sdk-python pip install websocket-client pip install pydub pip install numpytencentcloud-sdk-python是腾讯云的官方SDK,封装了签名和请求逻辑。websocket-client用于实时识别的WebSocket通信。pydub处理音频格式转换,numpy做音频数据的数组运算。
前端方面,如果要做浏览器端的音频采集,需要用到MediaRecorderAPI和AudioContext。如果只是桌面端使用,可以用pyaudio直接采集麦克风数据。
密钥管理要特别注意,腾讯云的SecretId和SecretKey绝对不能硬编码在代码里。我习惯用环境变量加载,本地开发时放在.env文件里,部署时通过容器环境变量注入。
import os from tencentcloud.common import credential cred = credential.Credential( os.environ.get("TENCENT_SECRET_ID"), os.environ.get("TENCENT_SECRET_KEY") )4.2 实时识别模块的代码实现
实时识别的核心是WebSocket连接的管理。下面这段代码展示了建立连接、发送配置、循环发送音频、接收结果的基本流程。
import websocket import json import hashlib import hmac import base64 import time import urllib.parse def generate_sign(secret_key, params): """生成腾讯云实时识别接口的签名""" sorted_params = sorted(params.items()) sign_str = "&".join([f"{k}={v}" for k, v in sorted_params]) sign_bytes = hmac.new( secret_key.encode(), sign_str.encode(), hashlib.sha1 ).digest() return base64.b64encode(sign_bytes).decode() def build_ws_url(secret_id, secret_key, engine_model): """构建带签名的WebSocket连接地址""" params = { "secretid": secret_id, "timestamp": int(time.time()), "expired": int(time.time()) + 3600, "engine_model_type": engine_model, "voice_format": 1, # 1表示PCM "voice_id": "meeting_helper_001", "needvad": 1, # 开启静音检测 } params["signature"] = generate_sign(secret_key, params) query = urllib.parse.urlencode(params) return f"wss://asr.cloud.tencent.com/asr/v2/your_appid?{query}"连接建立后,先发配置帧,再循环读取音频文件分片发送。每发一片就尝试接收一次结果,用非阻塞的方式读取,避免卡住。
def start_realtime_asr(audio_file_path, ws_url): ws = websocket.create_connection(ws_url) # 发送配置帧 config = { "type": "config", "data": { "voice_format": 1, "sample_rate": 16000, "filter_modal": 1, "filter_dirty": 1, "convert_num_mode": 1 } } ws.send(json.dumps(config)) # 分片发送音频 chunk_size = 6400 # 200ms的16kHz 16bit PCM数据 with open(audio_file_path, "rb") as f: while True: chunk = f.read(chunk_size) if not chunk: break ws.send_binary(chunk) time.sleep(0.2) # 模拟实时节奏 # 非阻塞接收结果 ws.settimeout(0.01) try: result = ws.recv() handle_result(json.loads(result)) except websocket.WebSocketTimeoutException: pass # 发送结束帧 ws.send(json.dumps({"type": "end"})) ws.close()handle_result函数负责解析识别结果,把中间结果和最终结果分别存起来。中间结果用于实时显示,最终结果用于后续处理。
4.3 录音文件识别的提交与轮询
录音文件识别走的是异步任务模式。先把文件上传到对象存储,拿到文件的URL,然后提交识别任务。
from tencentcloud.asr.v20190614 import asr_client, models def submit_file_recognition(file_url, hotword_id=None): client = asr_client.AsrClient(cred, "ap-guangzhou") req = models.CreateRecTaskRequest() req.EngineModelType = "16k_zh" req.ChannelNum = 1 req.ResTextFormat = 0 # 0表示返回带标点的文本 req.SourceType = 0 # 0表示URL方式 req.Url = file_url req.SpeakerDiarization = 1 # 开启说话人分离 if hotword_id: req.HotwordId = hotword_id resp = client.CreateRecTask(req) return resp.Data.TaskId提交后拿到TaskId,然后轮询查询任务状态。轮询间隔建议从5秒开始,逐渐增加到30秒,避免频繁请求。
def poll_task_result(task_id): client = asr_client.AsrClient(cred, "ap-guangzhou") req = models.DescribeTaskStatusRequest() req.TaskId = task_id while True: resp = client.DescribeTaskStatus(req) status = resp.Data.Status if status == 2: # 2表示识别完成 return resp.Data.Result elif status == 3: # 3表示识别失败 raise Exception(f"识别失败: {resp.Data.ErrorMsg}") time.sleep(10)识别结果里包含了每个句子的起止时间、发言人编号、识别文本。把这些信息结构化存储,后面做纪要就方便了。
4.4 结果合并与纪要生成
两种识别结果拿到后,需要做一次对齐合并。实时识别的优势是时间戳精确,文件识别的优势是文本准确。我的做法是以文件识别的文本为准,用实时识别的时间戳来校正文件识别的时间偏移。
具体来说,把文件识别的每个句子和实时识别的结果做文本相似度匹配,找到最相似的片段,然后用实时识别的时间戳替换文件识别的时间戳。这样既保证了文本质量,又保证了时间轴的准确性。
纪要生成环节,我写了一个模板引擎,把结构化数据填充进去。模板大概长这样:
## 会议纪要 **会议时间**:{meeting_time} **参会人员**:{participants} **会议时长**:{duration} ### 讨论要点 {for each topic} - **{topic_title}**:{topic_summary} {end for} ### 待办事项 | 事项 | 负责人 | 截止时间 | |------|--------|----------| {for each todo} | {content} | {owner} | {deadline} | {end for}待办事项的抽取用规则匹配加人工确认的方式。规则匹配负责初筛,人工确认负责兜底。虽然多了一步确认,但比完全手动整理还是快了很多。
5. 常见问题与排查技巧实录
5.1 识别准确率不达预期怎么办
这是被问得最多的问题。识别准确率上不去,通常不是单一原因,得逐项排查。
| 可能原因 | 排查方法 | 解决措施 |
|---|---|---|
| 音频采样率不匹配 | 检查采集参数和接口参数是否一致 | 统一设为16kHz |
| 背景噪声过大 | 录制一段静音环境对比测试 | 增加降噪预处理 |
| 专业术语识别错 | 查看错误词是否属于行业词汇 | 配置热词表 |
| 说话人语速过快 | 观察是否频繁出现漏字 | 提示参会者放慢语速 |
| 麦克风距离过远 | 检查音频音量是否偏低 | 使用近场麦克风或增加增益 |
| 网络丢包 | 查看实时识别是否有断连 | 增加重连机制和本地缓存 |
我踩过最坑的一次是采样率设成了8kHz,结果“识别”被转成了“设别”,“项目”变成了“象目”。排查了半天才发现是采集端的问题。所以第一步永远是确认音频参数。
5.2 实时识别断连的恢复策略
实时识别依赖WebSocket长连接,网络抖动、服务端限流、客户端资源不足都可能导致断连。我的处理策略是三层防护:第一层是心跳保活,每隔30秒发送一个空音频帧,让服务端知道客户端还在;第二层是断连检测,一旦发现连接断开,立即启动重连;第三层是本地缓存,断连期间的音频先存本地,重连成功后补发。
补发这个逻辑要小心,不能把断连期间的音频直接塞进去,因为时间轴会对不上。我的做法是给补发的音频打上时间戳标记,在结果合并时根据时间戳做对齐。
5.3 说话人分离结果混乱的应对
说话人分离在理想情况下效果不错,但实际会议中经常出现两个人同时说话、或者一个人说话声音忽大忽小的情况,分离结果就会乱。应对方法有几个:一是会前提醒参会者尽量轮流发言,避免抢话;二是使用指向性麦克风,减少串音;三是在后处理阶段做平滑,把持续时间过短的发言人片段合并到相邻片段。
还有一个取巧的办法:如果会议有固定的发言顺序,比如按座位轮流汇报,可以结合音频的声源定位信息辅助判断。不过这个需要额外的硬件支持,一般场景下用不上。
5.4 长会议的内存与存储管理
两三个小时的会议,音频文件动辄几百MB,识别结果文本也有几万字。如果全部堆在内存里,程序很容易崩。我的做法是流式处理:音频分片读取、分片发送,不一次性加载整个文件;识别结果边接收边写入数据库,不在内存里累积。数据库选SQLite就够了,轻量且不需要额外部署。
存储方面,原始录音文件建议设置生命周期规则,比如30天后自动转冷存储或删除。识别文本则长期保留,方便后续检索。如果团队有知识管理需求,可以把纪要文本同步到内部wiki,但要注意脱敏,别把敏感信息泄露出去。
5.5 成本控制的几个实用建议
腾讯云AI语音识别是按量计费的,会议场景下如果用量大,成本也得考虑。几个省钱技巧:实时识别和文件识别不要同时全量跑,实时识别只用于会中字幕,文件识别只用于会后纪要;音频压缩后再上传,16kHz 16bit单声道的PCM压缩成OPUS格式,体积能减少80%以上,识别效果几乎不受影响;合理设置并发数,别为了追求速度开太多并发,超出配额反而会被限流。
提示:腾讯云经常有新用户优惠和资源包活动,长期使用的话买资源包比按量付费划算不少。建议先估算月均用量,再决定买多大的包。
6. 一些实操后的个人体会
这套东西我断断续续打磨了小半年,从最开始只能跑通demo,到后来能稳定支撑团队每周的例会记录,中间踩的坑比写过的代码还多。最大的感受是:语音识别这件事,算法层面的东西普通开发者改变不了,但工程层面的优化空间非常大。音频采集的质量、网络传输的稳定性、结果后处理的精细度,每一个环节做好一点,最终效果就能好一大截。
另外就是别追求一步到位。我一开始想着全自动生成完美纪要,后来发现完全不现实。现在的做法是人机协作——机器负责转写和初筛,人负责确认和润色。这样既享受了自动化的效率,又避免了机器出错带来的风险。毕竟会议纪要是要发给领导和同事看的,准确性比速度重要得多。
如果你也想动手搭一个,我的建议是从最小的闭环开始:先跑通录音文件识别,把一段录音转成文字;再加上实时识别,做会中字幕;最后做结果合并和纪要生成。每一步都验证通过了再往下走,别一上来就搞大而全的架构,那样很容易半途而废。