1. 会议记录这件事,到底卡在哪儿了
开会这件事,但凡在职场待过几年的人都有体会。真正让人头疼的往往不是开会本身,而是会后那一堆烂摊子:谁说了什么、哪个节点要交付、哪个人领了任务、下次同步的时间点是什么。一场一小时的会,整理纪要少说也要再花四十分钟,遇到多人交叉发言、口音混杂、语速飞快的情况,回听录音简直是一种折磨。
我所在的团队每周至少有五场跨部门同步会,过去一直靠人工记录,结果就是纪要质量全看当天谁值班,运气好记得全,运气不好漏掉关键结论,后面返工扯皮的成本远高于开会本身。后来我琢磨着用语音识别把这块自动化掉,试了几家方案,最后落到腾讯云 AI 语音识别上,搭了一个内部叫“会议小帮手”的小工具。它做的事情很朴素:把会议录音丢进去,自动转成带时间戳的文字稿,再按发言人切分,最后输出一份可以直接改的纪要草稿。
这篇文章就是把这套东西从选型到落地的全过程拆开讲。适合两类人看:一类是想给自己团队做个会议记录工具的开发者,另一类是想搞清楚语音识别在实际业务里到底怎么用、有哪些坑的产品或运营同学。不需要你有很深的算法背景,但基本的接口调用和数据处理概念要有。我会把参数怎么选、代码怎么写、踩过哪些坑都摊开说,尽量让你看完能直接抄作业。
2. 为什么是语音识别,为什么是这套方案
2.1 从需求倒推:会议场景到底需要什么能力
在动手之前,我先把会议记录这件事拆成了几个刚性需求。第一是准确率,尤其是中文场景下的专业术语和数字,识别错了后面全乱。第二是说话人分离,一场会五六个人轮流发言,如果转出来是一坨不分你我的文字,那跟没转一样。第三是时间戳,方便回溯“这句话是谁在什么时候说的”,也方便把文字稿和录音对齐。第四是长音频支持,一场会动辄一小时以上,很多识别服务对单次请求时长有限制。第五是成本可控,内部工具不可能烧太多钱。
把这五条摆出来,其实选型范围就窄了很多。纯本地的开源方案比如某语音识别工具包,准确率在安静环境下还行,但会议录音有回声、有键盘声、有远场拾音,效果掉得厉害,而且说话人分离要自己搭,维护成本高。纯人工众包转写质量最好但贵且慢。云服务里,几家大厂都有语音识别产品,我最后选腾讯云,主要原因是它在中文长音频和说话人分离这两块的能力比较成熟,接口文档也清楚,按量计费对小团队友好。
2.2 一句话识别、录音文件识别、实时识别,到底选哪个
腾讯云 AI 语音识别下面有好几个子能力,新手最容易在这里懵。我简单说一下区别,都是我实际用过的。
一句话识别适合短语音,比如语音输入法那种,单次不超过60秒,会议场景基本用不上。实时语音识别是边说边转,适合直播字幕、实时会议字幕这种场景,但它对网络稳定性要求高,而且说话人分离能力弱。录音文件识别是异步的,你把整段音频传上去,它慢慢跑,跑完回调给你结果,支持长音频、支持说话人分离、支持时间戳,这才是会议记录的正解。
所以我的方案核心就是录音文件识别。流程是:会议录音先上传到对象存储,然后调录音文件识别接口,指定好引擎模型和说话人分离参数,等回调拿到结果,再做后处理生成纪要。这个链路清晰、稳定,而且异步的特性意味着我不需要一直守着,跑完通知我就行。
2.3 整体架构:三个模块串起来
整个“会议小帮手”我拆成了三个模块。采集模块负责拿到音频,可以是会议软件的录制文件,也可以是手机现场录的,统一转成接口支持的格式。识别模块负责调腾讯云接口,处理上传、轮询、回调、结果解析。后处理模块负责把原始识别结果变成人话,包括按发言人合并段落、去掉语气词、提取待办事项、生成 Markdown 纪要。
这三个模块之间用简单的文件传递就行,不需要上什么消息队列。我一开始想搞复杂点,后来发现内部工具够用就好,过度设计反而增加维护负担。下面几节我会把每个模块的关键实现和参数选择讲透。
3. 核心细节拆解:参数、格式、说话人分离
3.1 音频格式与采样率:别在这一步翻车
录音文件识别对音频格式有要求,官方支持 wav、mp3、m4a、aac 等常见格式,但有几个细节不注意就会报错。首先是采样率,推荐 16kHz,这是语音识别的黄金采样率,太高浪费带宽,太低损失高频信息影响准确率。我实测过 8kHz 的录音,识别准确率大概掉 5 到 8 个百分点,尤其是“四”和“十”这种容易混。
其次是声道,单声道最好。如果是双声道录音,接口默认可能只取一个声道,或者把两个声道混在一起,反而干扰识别。我遇到过会议软件录出来是立体声的情况,处理办法是用音频处理工具先转成单声道再上传。命令很简单,用常见的音频处理工具就能做:
# 转成 16kHz 单声道 wav,这是最稳妥的格式 音频处理工具 -i input.m4a -ar 16000 -ac 1 -c:a pcm_s16le output.wav第三是码率,mp3 的话建议 64kbps 以上,太低会有压缩伪影。这些参数看着琐碎,但它们是识别质量的地基,地基没打好后面怎么调都白搭。
3.2 引擎模型选择:不同场景用不同模型
腾讯云录音文件识别提供了多种引擎模型,选对了能明显提升准确率。我整理了一个对照表,是我实际测试下来的经验值。
| 引擎模型类型 | 适用场景 | 我的实测表现 |
|---|---|---|
| 通用模型 | 日常对话、普通会议 | 准确率稳定,适合大多数场景 |
| 电话模型 | 电话录音、客服场景 | 对窄带音频优化好,会议用不上 |
| 医疗模型 | 医疗术语场景 | 专业词准,通用会议反而一般 |
| 教育模型 | 课堂、讲座 | 对长句和学术词友好 |
会议场景我一般用通用模型,如果会议涉及大量专业术语,可以在控制台配置热词表。热词表是个好东西,把团队常用的项目代号、产品名、人名加进去,识别准确率能提升不少。我试过把二十多个内部术语加进热词,相关词汇的识别错误率从大概 15% 降到了 3% 以内。热词表通过接口创建,然后在识别请求里带上热词表 ID 就行。
3.3 说话人分离:会议记录的灵魂功能
说话人分离(也叫声纹分离)是会议场景最值钱的功能。它的原理简单说就是通过声学特征判断“这几段话是不是同一个人说的”,然后给每段话打上说话人编号。腾讯云这个功能通过参数开启,可以指定说话人数量范围。
这里有个关键经验:说话人数量不要设死。如果你明确知道是 5 个人开会,可以设成 5,准确率最高。但实际会议经常有人中途加入或离开,设死了反而会错分。我一般设一个范围,比如 2 到 8,让算法自己去判断。代价是处理时间稍微长一点,但准确率更稳。
还有一个坑:说话人编号是相对的,不是绝对的。接口返回的“说话人1”“说话人2”只是区分不同的人,并不对应真实身份。所以后处理的时候需要做一个映射,比如根据第一句话的内容或者手动标注,把“说话人1”映射成“张三”。这个映射我做成了配置文件,每次会后花一分钟标一下就行。
3.4 时间戳与分句:让文字稿能对齐录音
录音文件识别返回的结果是带时间戳的,精确到毫秒。这个信息非常有用,一方面可以生成“点击文字跳转录音”的交互,另一方面方便核对。返回结构大概是每个句子一个对象,包含开始时间、结束时间、识别文本、说话人编号。
分句的粒度也需要注意。默认分句可能偏长,一句话里塞了好几个意思。我在后处理里做了一次二次切分,按照标点和停顿时间重新断句,让每句话更短更清晰。停顿超过 800 毫秒基本可以认为是换气或者换人,可以作为切分依据。这个阈值是我试出来的,太短会把正常停顿切断,太长又会把两句话粘一起。
4. 实操过程:从录音到纪要的完整链路
4.1 环境准备与依赖安装
先说环境。我用的是 Python,版本 3.8 以上都行。需要装腾讯云 SDK 和几个音频处理库。SDK 安装很简单:
pip install tencentcloud-sdk-python pip install pydubpydub 用来做音频格式转换和时长读取,它底层依赖音频处理工具,所以系统里要先装好音频处理工具。如果你不想装音频处理工具,也可以用其他纯 Python 的音频库,但格式兼容性会差一些。我建议还是装上,一劳永逸。
密钥管理这块要特别注意。不要把密钥硬编码在代码里,我见过太多人图省事直接写在脚本里,然后不小心传到公开仓库。正确做法是用环境变量或者密钥管理服务。我是在服务器上配了环境变量,代码里读环境变量,这样即使代码泄露也不怕。
import os from tencentcloud.common import credential cred = credential.Credential( os.environ.get("TENCENT_SECRET_ID"), os.environ.get("TENCENT_SECRET_KEY") )4.2 音频上传与任务创建
录音文件识别是异步的,流程分两步:先创建识别任务,再查询结果。音频文件需要先放到对象存储上,接口通过 URL 拉取。所以第一步是把本地录音传到对象存储。
上传这块我用的是 SDK 里的对象存储客户端,分片上传大文件更稳。一场一小时的会议录音大概 50 到 100MB,普通上传也够用,但分片上传能断点续传,网络不稳的时候更省心。上传完拿到文件的 URL,就可以创建识别任务了。
创建任务的关键参数有这么几个:引擎模型类型、声道数、是否开启说话人分离、说话人数量范围、热词表 ID。我把这些封装成了一个配置字典,方便不同会议用不同配置。
params = { "EngineModelType": "16k_zh", "ChannelNum": 1, "ResTextFormat": 0, "SpeakerDiarization": 1, "SpeakerNumber": 0, "HotwordId": "你的热词表ID", "SourceType": 0, "Url": audio_url }这里SpeakerNumber设 0 表示让算法自动判断人数,ResTextFormat设 0 表示返回带时间戳的详细结果。这些参数的具体含义文档里都有,但文档不会告诉你哪个组合在实际会议里最好用,上面这套是我试了十几次之后定下来的。
4.3 轮询结果与回调处理
任务创建后会返回一个 TaskId,接下来就是等结果。有两种方式拿结果:轮询和回调。回调需要你有公网可访问的接口,内部工具不一定有,所以我用的是轮询。轮询要注意频率,太频繁浪费请求,太慢又等得久。我的做法是每 10 秒查一次,最多查 60 次,也就是十分钟还没好就报错。
import time def wait_for_result(task_id, max_retry=60, interval=10): for i in range(max_retry): result = query_task(task_id) status = result.get("Status") if status == 2: # 2 表示识别完成 return result elif status == 3: # 3 表示失败 raise Exception("识别失败: " + result.get("ErrorMsg", "")) time.sleep(interval) raise Exception("识别超时")状态码这块文档里有说明,0 是等待中,1 是执行中,2 是成功,3 是失败。我踩过的坑是没处理失败状态,结果任务卡住一直轮询到超时,后来加上失败判断就清爽了。
4.4 结果解析与纪要生成
拿到结果后是后处理的重头戏。原始返回是一个 JSON,里面有个 Result 字段,包含详细的句子列表。每个句子有开始时间、结束时间、文本、说话人编号。我要做的是把这些句子按说话人合并成段落,去掉“嗯”“啊”“那个”这类语气词,然后生成 Markdown。
合并逻辑是这样的:遍历句子列表,如果当前句子和上一句是同一个说话人,且间隔小于 1.5 秒,就合并到同一段;否则新起一段。1.5 秒这个阈值也是试出来的,太短会把正常停顿切断,太长会把两个人的话粘一起。
def merge_sentences(sentences, gap_threshold=1.5): paragraphs = [] current = None for s in sentences: speaker = s["SpeakerId"] start = s["StartTime"] text = s["Text"] if current and current["speaker"] == speaker and (start - current["end"]) < gap_threshold * 1000: current["text"] += text current["end"] = s["EndTime"] else: if current: paragraphs.append(current) current = {"speaker": speaker, "start": start, "end": s["EndTime"], "text": text} if current: paragraphs.append(current) return paragraphs语气词过滤我用了一个简单的规则表,把常见的口头禅列进去,匹配到就删。注意不要删太狠,有些词在特定语境下是有意义的,比如“这个方案”里的“这个”不能删。我的做法是只删独立成句的语气词,或者句首句尾的单个语气词。
待办事项提取是个加分项。我用了一个简单的规则:包含“负责”“跟进”“下周”“之前”“完成”这类关键词的句子,标记为待办候选,输出到纪要末尾的待办清单里。这个规则不完美,但能覆盖大部分情况,剩下的靠人工扫一眼补全。
5. 常见问题与排查技巧实录
5.1 识别准确率不达预期怎么办
这是被问得最多的问题。准确率不行,先别急着换方案,按这个顺序排查。第一看音频质量,有没有回声、噪音、远场拾音。会议室如果用的是天花板麦克风,拾音距离远,识别率天然就低,这种情况建议用桌面全向麦。第二看采样率,是不是 16kHz,是不是单声道。第三看热词表,专业术语加了吗。第四看引擎模型,选对了吗。
我遇到过一次准确率突然暴跌,排查半天发现是录音软件更新后默认改成了立体声,转单声道后立刻恢复。所以音频参数一定要在流程里做校验,不符合要求的直接拒绝,别硬着头皮识别。
5.2 说话人分离错乱怎么调
说话人分离错乱一般有三种表现:同一个人被分成多个,多个人被合成一个,编号跳来跳去。第一种通常是说话人数量设太大了,调小范围试试。第二种是说话人数量设太小了,或者两个人音色太像。第三种是音频里有长时间静音或者噪音干扰,建议先做静音切除。
还有一个隐蔽的坑:如果会议里有电话接入的参会者,电话音频是窄带的,和现场音频的声学特征差异很大,容易被单独分成一类。这种情况我一般会在后处理里手动合并,或者提前把电话音频单独处理。
5.3 长音频处理超时与费用控制
一小时的会议音频,识别时间大概在 3 到 8 分钟,取决于音频长度和是否开说话人分离。如果超过十分钟还没结果,可能是任务排队或者失败了,检查一下任务状态。费用方面,录音文件识别按音频时长计费,一小时大概几毛到一块多,具体看当时的计费标准。控制费用的办法是先做静音检测,把没人说话的片段切掉再上传,一场会通常有 20% 到 30% 的静音,切掉能省不少。
静音检测用音频处理工具就能做:
# 检测静音片段并输出时间点 音频处理工具 -i input.wav -af silencedetect=noise=-30dB:d=2 -f null -这个命令会输出所有超过 2 秒、低于 -30dB 的静音片段,你可以据此裁剪音频。阈值 -30dB 和时长 2 秒是我调出来的,太敏感会把正常停顿当静音,太迟钝又切不掉。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 接口报格式错误 | 音频格式不支持或参数不对 | 转成 16kHz 单声道 wav |
| 识别结果为空 | 音频全是静音或音量太低 | 检查音频,提高音量 |
| 说话人全是同一个 | 没开说话人分离或参数没生效 | 检查 SpeakerDiarization 参数 |
| 任务一直等待中 | 排队或音频过长 | 等待或切分音频 |
| 准确率突然下降 | 音频参数变了 | 校验采样率和声道 |
| 费用超预期 | 音频太长或重复提交 | 静音切除,加去重逻辑 |
6. 一些实操心得和后续扩展方向
这套工具跑了大半年,团队每周省下的纪要时间大概有六七个小时,更重要的是纪要质量稳定了,不再看人下菜。我最大的体会是:语音识别不是万能药,它解决的是“从音频到文字”这一段,后面的整理和提炼还是需要规则和人工配合。别指望丢进去一段录音就出来一份完美纪要,那不现实。
后续我打算加两个东西。一个是关键词自动标红,把会议里提到的项目名、人名、日期自动高亮,方便快速扫读。另一个是和任务管理工具打通,识别出的待办直接创建成任务卡片,省掉手动录入。这两个都不难,核心还是把识别结果的结构化数据用好。
最后分享一个小技巧:如果你们的会议有固定的议程模板,可以在后处理里按议程切分,比如“第一部分进度同步”“第二部分问题讨论”,这样生成的纪要天然就有结构,比一坨文字好用得多。议程关键词从会议通知里提取就行,简单匹配就能做。
这套东西的门槛其实不高,一个熟悉 Python 的开发者两三天就能搭出可用版本。真正花时间的是调参和踩坑,而这些东西文档里不会写,只能自己试。希望这篇把该踩的坑都替你踩了,你上手能顺一点。