用腾讯云 AI 语音识别打造会议小帮手
说真的,开会这件事,最烦的不是会开得久,而是开完之后没人记得住说了什么。我以前每次开完部门周会,微信群里丢三份录音文件,隔两天再问谁整理纪要,大家都互相推。后来我花了半天时间,接了一下腾讯云的 AI 语音识别接口,搞了个“会议小帮手”——把录音丢进去,自动转成文字稿,再按说话人拆开,顺便把重点内容抽出来。从那以后,Group Meeting 的记录工作基本就是半小时内的事。
这篇文章就记录一下我是怎么把这个小工具从零搭起来的,包括方案选择、核心代码、参数调优,还有一堆实际用起来才会遇到的坑。如果你正好也想做“录音转文字”“会议纪要自动生成”这类东西,或者单纯想入门云服务商的人工智能接口,这篇东西可以直接照着抄。
1. 项目定位:录音转文字背后的三个关键选择
先说清楚这个“会议小帮手”到底做什么。它不是一个复杂的系统,而是一条自动处理链路:拿到会议的录音文件,交给语音识别服务,让它把音频变成带时间轴、带说话人标记的文本,最后我这边再做一点过滤和排版,形成一份能直接发出去的会议纪要。
1.1 为什么选云服务商的方案而不是自己训练模型
很多人一听到语音识别,第一反应是开源模型、本地部署、GPU 训练。但我个人的建议是,如果你的目标只是“会议纪要”这种具体场景,直接用云服务商的现成接口,性价比高得多。
原因有三条。第一,自己搭语音识别,光把声学模型调明白就够折腾一两个月,更别提还得解决标点符号、数字规整、多说话人区分这些工程细节。第二,云接口的识别准确率,尤其是对普通话和常见英文混读场景,已经做得相当成熟,超出个人微调的模型一大截。第三,会议录音往往是长时间音频,云服务按量计费,但算下来一小时音频的成本极低,比团队里人工整理纪要省太多了。
我选的是腾讯云的语音识别服务,倒不是说别的家不行,而是它的录音文件识别接口和实时语音识别接口分得很清楚,并且提供了“说话人分离”和“智能中文标点”这两个对会议场景极其友好的功能。你不需要同时接好几个服务,一个接口就能把核心需求覆盖掉。
1.2 小帮手的整体工作流程
整个系统的数据流大概是这样的:
- 会议录音文件(手机录音、会议软件导出,格式通常是 m4a、mp3、wav 都行)先上传到对象存储,拿到一个可访问的 URL。
- 调用录音文件识别接口,提交这个 URL,并指定识别参数(引擎类型、说话人分离等)。
- 服务端异步处理,我们用一个回调地址或者轮询接口拿到最终结果。
- 对返回的文本做格式化:穿插入时间戳、按说话人切分段落、过滤语气词和重复的废话。
- 输出一份 Markdown 格式的会议纪要,甚至可以直接推到团队协作工具里。
这个流程里,第 1 步和第 4 步是我们自己写的代码,第 2 和 3 步完全依赖云服务。所以工作量的大头其实在接口参数的理解和返回结果的解析上。
这里我特别想强调一个设计思路:不要把精力花在“自己造轮子”上,而要把重点放在“结果如何被更好地使用”上。语音识别只是把声音变成文字,真正有价值的是让文字变成结构化的会议结论。
2. 官方接口的底层逻辑与踩坑预备
在你写第一行代码之前,我强烈建议先花十分钟把腾讯云语音识别的产品文档翻一遍。这个环节跳过了,后面迟早要回来补课,而且大概率是在被你老板催着要纪要的时候。
2.1 实时识别 vs 录音文件识别:别选错接口
腾讯云语音识别体系里,最常用的有两个方向:一个是“实时语音识别”,一个是“录音文件识别”。听名字很好理解,但很多人真的会在这里选错。
实时语音识别适合的是“边说边出字”的场景,比如会议直播字幕、语音输入法,它要求音频流式地推送给服务端,延迟控制在几百毫秒内。另一个是“录音文件识别”,适合的是离线音频,把整个音频文件丢上去,等待结果返回,支持数小时以内的长音频。我做的是会后整理,自然选录音文件识别。
录音文件识别标准版和极速版是另外一对容易搞混的东西。标准版支持说话人分离,极速版延迟更低但功能少。做会议纪要必须开说话人分离,所以哪怕标准版返回会慢一点,我也建议选它。我自己试过,一小时的音频,标准版大概几十秒内能返回结果,体感上完全能接受。
2.2 音频格式与采样率:细节里藏着魔鬼
接口文档里有一个容易被忽略的参数,叫 EngSerViceType,它不是格式类型,而是“引擎模型类型”。这里有区分:16k_zh 表示 16kHz 采样率的普通话模型,16k_zh_en 是中英文混说模型,8k_zh 是 8kHz 电话音频模型。做会议录音,我一开始直接套了 16k_zh,结果英文产品名全被识别成拼音,后来换用 16k_zh_en 才正常。
音频格式方面,官方支持 wav、mp3、m4a、opus 等常见格式。但有一点值得注意:如果你的录音文件是 8kHz 电话会议录出来的,却选了 16k 的引擎,识别效果可能会打折扣;反过来,16kHz 的录音放在 8k_zh 下也会浪费数据。所以一个比较稳妥的做法是在丢给接口之前,先看看录音文件的采样率。
我习惯用 ffmpeg 做一道预处理:
ffprobe meeting_audio.m4a ffmpeg -i meeting_audio.m4a -ar 16000 -ac 1 meeting_audio_16k.wav这里 -ar 16000 强制将采样率转换到 16kHz,-ac 1 转成单声道。为什么转单声道?因为多声道的音频在语音识别场景里并不会带来收益,反而可能加重服务端混音处理的负担,转成单声道能减少很多不可控因素。反正会议录音本来也没有立体声需求。
2.3 回调 vs 轮询:异步接口的两种取结果姿势
录音文件识别是异步的,也就是说你提交任务之后,不能立刻拿到结果。服务端处理需要时间,而获取结果有两类方式。
第一种是提交任务时传一个 CallbackUrl,服务端处理完往这个地址 POST 一个结果通知。这是生产环境更推荐的模式,但需要你有一个公网可达的服务来接收回调,对纯本机脚本来说不够友好。我只在部署到服务器上之后才用了回调。
第二种方式是提交任务后,拿着返回的 TaskId,每几秒主动去查一次“录音文件识别结果查询”接口。这个方式简单粗暴,脚本随便跑,我日常调试用的就是它。
import time result = client.DescribeTaskStatus(request) while result.get("Status") == "WAIT" or result.get("Status") == "RUNNING": time.sleep(5) result = client.DescribeTaskStatus(request)轮询间隔不要设太短,设成 3-5 秒足够,不然除了给自己电脑增加功耗之外没别的好处。
3. 从零接入:关键代码与参数调优实录
接下来是整篇博文最硬核的部分。我会带着你一步步把核心代码写出来。语言选 Python,为什么?因为腾讯云官方提供 python-sdk,而且 Python 做文本后期处理非常顺手,代码量最少。
3.1 环境准备:安装依赖和初始化客户端
首先安装腾讯云 SDK:
pip install tencentcloud-sdk-python然后,在代码里初始化客户端。这里的坑主要是“地域”的选择。语音识别产品的域名是 asr.tencentcloudapi.com,地域我建议选 ap-guangzhou,因为语音识别服务目前在广州地域支持最全。我之前选过其他地域,结果发现接口版本和功能都有差异,后来统一固定到广州才稳定。
from tencentcloud.common import credential from tencentcloud.asr.v20190614 import asr_client, models SECRET_ID = "你的SecretId" SECRET_KEY = "你的SecretKey" cred = credential.Credential(SECRET_ID, SECRET_KEY) client = asr_client.AsrClient(cred, "ap-guangzhou")SecretId 和 SecretKey 在腾讯云控制台的“访问管理 -> 访问密钥”里创建。注意,密钥千万别硬编码在代码里传到 Git 仓库,别问我怎么知道的。安全习惯好一点的话,用系统环境变量或者本地配置文件加载就行。
3.2 上传音频:先把它放到能访问的地方
录音文件识别要求你传入音频的 URL,而不是直接上传音频文件内容。这意味着你得先把本地文件丢到对象存储 COS 上。
为了把这篇文章的重心放在语音识别上,我直接给出最简单的上传方式:用腾讯云 COS 的预签名 URL。先把文件传上去,然后生成一个带鉴权参数的有效链接,有效期 24 小时就够了。
from qcloud_cos import CosConfig, CosS3Client secret_id = SECRET_ID secret_key = SECRET_KEY region = "ap-guangzhou" bucket = "meeting-audio-1250000000" config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key) cos_client = CosS3Client(config) cos_client.put_object( Bucket=bucket, Body=open("meeting_audio_16k.wav", "rb"), Key="meeting/20240601/meeting_audio.wav", ContentType="audio/wav" )上传之后,调用 get_object_url 拿到可访问链接就行。COS 的域名和密钥权限可以单独给这个操作开一个子账号,权限只限上传和下载,别用主账号,这个属于基础安全意识。
3.3 提交识别任务:参数逐项说明
这是核心中的核心。我直接把创建任务这一段完整写出来,并且逐个参数讲清楚为什么这么设。
request = models.CreateRecTaskRequest() request.EngSerViceType = "16k_zh_en" request.SourceType = 0 request.Url = "https://meeting-audio-1250000000.cos.ap-guangzhou.myqcloud.com/meeting/20240601/meeting_audio.wav" request.DataLen = os.path.getsize("meeting_audio_16k.wav") request.ConvertNumMode = 1 request.FilterDirty = 1 request.FilterModal = 1 request.FilterPunc = 0 request.SpeakerDiarization = 1 request.SpeakerNumber = 0逐个说:
- EngSerViceType:引擎模型类型,上面已经提到,会议场景中英文夹杂的,用 16k_zh_en 就对了。纯中文的可以换成 16k_zh。
- SourceType:数据来源。0 表示 URL,1 表示本地上传(录音文件识别除了 URL 也支持直接上传文件,但 URL 方式更通用)。
- Url:音频真实可访问的地址。
- DataLen:音频文件的字节大小。这个参数会稍微让人绕一下,它不是音频时长,而是文件大小。不传或者传错,有可能导致任务提交失败。用 os.path.getsize 就能拿到。
- ConvertNumMode:是否转换阿拉伯数字。会议里说“第三季度”“增长了百分之五”,识别结果可能输出“第三季度”和“5%”。1 表示转成阿拉伯数字串,更方便阅读和后续统计。我实测下来,转成数字比中文数字更适合做纪要。
- FilterDirty:过滤脏词。这里把语气词“嗯”“啊”“呃”这类词过滤掉。1 开启,效果很干净。
- FilterModal:过滤语气词。和 FilterDirty 有点重叠,但我两个都开。
- FilterPunc:过滤标点。默认 0,也就是保留标点。会议纪要肯定要保留,设置成 0。
- SpeakerDiarization:这是“说话人分离”开关。1 开启后,识别结果中会标注出不同说话人。
- SpeakerNumber:说话人数量。0 表示自动识别。你也可以指定人数,比如“我知道这个会只有两个人”。但如果会议里有人带了口音或者声音太像,指定人数反而会分错,自动识别更省心。
提交任务之后,会返回一个 TaskId:
resp = client.CreateRecTask(request) task_id = resp.TaskId3.4 拉取结果:解析返回的 JSON
创建完任务之后,等一会儿,用 TaskId 查结果。当状态变成“SUCCESS”时,结果里会有一个 Result 字段,它是一段 URL 编码过的 JSON 字符串,对应识别文本和详细信息。
req = models.DescribeTaskStatusRequest() req.TaskId = task_id for i in range(30): time.sleep(5) resp = client.DescribeTaskStatus(req) if resp.Status == "SUCCESS": break elif resp.Status == "FAILED": raise RuntimeError(resp.ErrorMsg)拿到 SUCCESS 后,解析响应:
import json from urllib.parse import unquote data = json.loads(unquote(resp.Result)) for sentence in data["AudioDuration"]: pass # 音频时长,单位秒 for item in data["Text"]: pass # 完整文本 # 说话人分离结果,通常在 data["SpeakerLabels"] for spk in data["SpeakerLabels"]: print(spk["SpeakerId"], spk["StartTime"], spk["EndTime"], spk["Text"])这里有个体验细节:不同版本的 SDK 返回字段结构略有差异,建议把 resp.Result 原样打印一次看结构,再写解析逻辑。不要照着老博客的字段名直接抄,很容易踩坑。
4. 真实环境下的高频问题与排查锦囊
功能跑通了,只是第一步。真正花了大量时间的是处理各种边界情况。我把实际使用中遇到频率最高的问题整理成一个速查表,按症状、原因、解法三列,你遇到类似问题直接对照查找。
4.1 常见问题速查表
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 提交任务返回 InvalidParameter | DataLen 与 Url 文件大小不一致 | 用上传前本地文件的大小,不要手写估算值 |
| 识别结果是乱码或英文全变拼音 | 引擎类型选错 | 中英文混合场景换 16k_zh_en,纯中文用 16k_zh |
| 说话人分离没生效,输出只有 SpeakerId 0 | 接口版本或参数没收到 | 确认 CreateRecTask 里 SpeakerDiarization=1,且使用标准版 |
| 查询结果一直 WAIT,超过 20 分钟 | 音频时间过长或格式特殊 | 先用 ffmpeg 转成 wav/16k/单声道,重新提交 |
| 回调地址一直收不到通知 | 回调地址公网不可达或签名问题 | 检查回调 URL 是否暴露在公网,服务器是否有防火墙拦截 |
| 返回文本没有标点,一句到底 | FilterPunc 设置错误或引擎不带标点 | 确认 FilterPunc=0,并检查引擎类型 |
| 音频文件 URL 访问总是 403 | COS 权限不足或签名过期 | 用预签名 URL 并设置较长有效期,或在 COS 控制台检查桶权限 |
4.2 说话人分离效果不理想怎么办
这是所有问题里最影响体验的。有几个实际的经验可以分享。
首先,会议录音尽量保证每个人说话时离麦克风距离接近。如果某个人声音特别小,识别出来可能直接被过滤掉,这个人说的话会归到上一个说话人身上。
其次,如果 SpeakerNumber 自动识别出来的说话人数量比实际人数多,你可以手动设置 SpeakerNumber 强制指定人数。比如我只想把录音分成“领导”和“员工”两类,那就设成 2,服务端会尽量把声音聚成两类。
第三,处理结果的策略比调接口参数更有效。我在拿到 SpeakerLabels 之后,会把同一说话人的连续片段合并,去掉过短的语句(比如 1 秒内的),因为这些大概率是打断别人时说的半句话。这个策略对纪要阅读体验提升非常明显。
4.3 隐私与合规,别只看技术
最后提醒一个容易被忽视的点:会议内容往往是公司内部敏感信息,上传到云端做识别,一定要确认数据的合规边界。我自己的做法是,先在团队内部说明并征得同意,同时开启对象存储的私有读权限,只给语音识别服务生成短期预签名地址,会议纪要生成后立即删除云端音频原文件。这些步骤看起来增加了工作链,但长远来看是必要的保护措施。
技术团队如果对数据安全有更高要求,腾讯云语音识别也提供私有化部署方案,不过那就是另一个量级的投入了,普通会议场景没必要。
5. 扩展方向与体验复盘
到这里,一个可用的“会议小帮手”已经能跑起来了。但既然叫“小帮手”,我还想聊几个可以让它更好用的扩展方向。
5.1 从“能转文字”到“能出纪要”
识别结果到手之后,真正的加工才刚刚开始。会议纪要不需要逐字逐句,它需要结论、任务、负责人和时间点。
我在后期处理里加了两个简单的步骤。第一步是去掉重复信息,比如主持人开场说的那些客套话,直接按词频或常见句子匹配过滤掉。第二步是提取“待办事项”,方法很朴素:把文本按句子切分,凡是包含“接下来”“下周三之前”“由XX负责”这类关键词的句子,单独摘出来放到纪要末尾的“行动计划”里。这个规则一点都不高级,但效果出奇地好。
要更进一步的话,可以直接把文本喂给大模型做摘要,生成“本次会议决定”“遗留问题”“下次议程”三个小节。我试过之后,感觉会议纪要的可用性直接上了一个台阶。技术上就是多调一次接口的问题,但价值提升非常明显。
5.2 会议场景的实时性与批处理
如果你的需求不只是“会后记录”,而是希望会议开到一半就能看到实时字幕,那需要换成实时语音识别接口,并且把麦克风音频流式推送到服务端。这个做起来比离线版本复杂一些,涉及到音频流的分片和 WebSocket 连接管理。我在第二个版本里用这个功能实现了“大屏实时字幕”,配合会议室的投屏,体验非常像现场同传。
批量处理是另一个容易想到的扩展。每周五下班前,脚本自动拉取本周所有会议录音,批量提交、批量收集,早上 9 点之前把整理好的纪要发到团队邮箱。这个自动化流程,本质上就是把上面写的代码包进一个 crontab 或一个定时任务里,工程量不大,但能实实在在帮大家省掉整理文档的半天时间。
5.3 一点真实的使用体感
用了这个方法一段时间后,最大的感受是,语音识别技术的成熟度已经足够让它做“工具”而不是“玩具”。你不再需要是一个算法工程师才能享受 AI 的便利,只要愿意花半天时间读文档、调接口,就能把重复劳动交给机器。
踩过的坑里,印象最深的倒不是技术本身,而是“参数说明里一句话没看,结果数据差了一截”的挫败感。比如 DataLen 这个字段,看起来不起眼,填错了直接任务失败,文档里藏在角落,只有在报错后回头翻才能发现。所以我特别建议第一次接的时候,把所有参数照着文档原文检查一遍再提交任务。
我目前这个方案,所有代码加起来不到两百行,没有任何一个组件是过度设计的。对一个开发团队或者个人博主来说,这种“先把流程跑通,再考虑优化”的思路,可能是最有性价比的接入方式。如果你也想做类似的东西,不要犹豫,直接拿今天的内容去搭,遇到新的坑也别怕,语音识别这种成熟产品,大部分问题都能在文档和社区里找到答案。