☰
会议记录神器:基于腾讯云AI语音识别的自动转写工具搭建指南
2026/10/20 17:45:12 网站建设 项目流程

会议开了两个小时,翻聊天记录和录音文件翻了四十分钟,最后发现关键结论记漏了,这种事情干过的人应该不在少数。我之前也尝试过用手机自带的录音转文字,效果时好时坏,普通话带点方言就崩,念个英文术语直接变乱码。后来干脆折腾了一套基于腾讯云AI语音识别的会议小帮手,录音、转写、导出一条龙,实用程度远超预期。

这套方案不是用什么高深算法自己训练模型,而是完全站在腾讯云语音识别能力的肩膀上。你负责把会开好,它负责把话记下来。这篇文章就把整个搭建过程、接口选型、参数调整、踩坑记录都摊开讲,代码可以直接拿去改,适合有基础开发能力的人,也适合想了解云语音识别到底怎么落地的朋友。

1. 项目概述与核心需求拆解

1.1 会议记录场景到底难在哪

先别急着写代码,得先认清一个现实:会议记录这件事,难点根本不在“录音”,而在“转写”和“整理”。

录音太简单了,任何一个手机都有录音功能。难的是会后你拿着一个小时的录音文件,得重新听一遍,手打关键内容,遇到听不清的地方还得往回拖进度条。这个过程极其消耗精力,尤其是一天连开四五个会的时候,到下午脑子基本是糊的,再去整理上午的录音,效率和心情都很糟糕。

更重要的是,会议内容是有价值密度的。一个项目推进会,里面可能包含了时间节点、负责人、资源需求、风险预警这些关键信息,但这些东西都散落在口语化的对话里,夹杂着“那个那个”“我觉得吧”“回头我们再对齐一下”这样的废话。人耳能自动过滤,工具却得靠技术手段去识别和结构化。

我当时的核心需求很明确:

  • 开会时能自动录音,不用我手动操作
  • 会开完,能快速拿到一份还不错的文字稿
  • 说话人区分最好有,实在没有也能接受
  • 转写速度要快,不能等半天

围绕这四个需求,我开始琢磨技术方案。

1.2 为什么选腾讯云AI语音识别而不是本地方案

市面上做语音转文字的方案不少,有开源的本地识别工具,也有各大云厂商的API接口。我最终选了腾讯云,主要基于以下几点考量:

识别准确率的差距。本地部署开源识别模型,比如一些开源语音识别框架,在安静环境、标准普通话、单个说话人的情况下表现还行,但放到真实会议场景就明显不够看了。会议室有空调声、键盘声,有人说话快,有人带口音,还有中英文夹杂的技术讨论。云厂商的识别引擎经过海量真实场景数据训练,对这种复杂声学环境的鲁棒性要强得多。

部署成本不是一个量级。本地方案听着“免费”,但要达到能用的精度,显卡算力、模型调优、数据处理链路都得自己搞。腾讯云这种API服务按量付费,不用的時候不花钱。我个人作为独立开发者,没必要为了一个月几十场会议的转写需求去养一台训练服务器。

功能完整度。腾讯云AI语音识别提供实时语音识别、录音文件识别、一句话识别等多个接口,还有说话人分离、标点预测、自定义词表这些配套能力,基本覆盖了会议场景的所有需求。

其实早期我也想过另一个路线:先用本地工具录好音,再上传到某个在线转写网站。但这条路有两个问题:一是上传下载来回折腾,二是隐私不好控制。自己花钱调API,至少数据链路是清楚的。

1.3 整体架构与适用人群

这套会议小帮手,本质上是“录音采集 + 云端转写 + 结果整理”三段式架构。

技术上非常简单,核心就两条链路:

链路A(实时转写):麦克风音频流 → 腾讯云实时语音识别 → 逐句返回文字 链路B(录音文件转写):录制的音频文件 → 上传到对象存储 → 提交录音文件识别请求 → 轮询/回调拿结果

两条链路各有应用场景。链路A适合需要现场投屏看字幕的场合,链路B适合事后整理会议纪要。我实际使用中,链路B用得更多,因为会议时间不确定,开着实时转写容易积累大量无效音频,不如先录下来,会后批量转。

这个方案适用的人群很广:

  • 需要频繁开会的产品经理、项目经理
  • 经常参加访谈、头脑风暴的运营和策划
  • 需要整理课堂笔记的老师和学生
  • 对语音识别技术感兴趣的开发者,想快速上手云API

门槛不算高,懂一点Python就能跑通,更底层的部署细节完全不用关心。

2. 核心设计:接口选型与方案对比

2.1 腾讯云语音识别产品家族怎么选

腾讯云语音识别相关的产品,如果你去控制台看,会发现有好几个入口,新手很容易懵。我梳理一下实际用得上的几个:

产品接口适用场景延迟说人话解释
录音文件识别先录好音频再转写较高,取决于文件时长你把录音丢给它,它异步处理,处理完通知你
实时语音识别边说话边出字低类似字幕组直播,麦克风进,文字出
一句话识别60秒以内的短音频极低适合语音指令,不太适合会议

会议场景,主要用的是前两个。我的经验是,两者搭配着用:现场用实时识别看大概内容,会后用录音文件识别出高质量文字稿。

为什么需要搭配?因为实时识别的结果往往比较“碎”,有时候一句话会被强行断开,而且现场噪音会影响前端识别。录音文件识别因为可以整段处理,上下文信息更完整,准确率通常更高一些。而实时识别的好处是即时性,你当场就能看到讨论走到哪了,不用等会后。

如果你预算有限,我个人建议优先保障录音文件识别这条路,它产出的稿件价值更高。

2.2 录音文件识别与实时识别,到底怎么选

先说说录音文件识别这个接口。它的流程是:客户端把音频文件上传到对象存储COS,拿到文件URL后调用录音文件识别接口,传入URL地址和音频格式参数。腾讯云后台异步处理,处理完成后通过回调或主动轮询的方式返回识别结果。

这里有个关键点容易被忽略:录音文件识别接口本身不接收音频二进制数据,它只接收文件URL。所以必须先有地方存储音频文件,对象存储COS就是这个中转站。

实时识别则是建立WebSocket连接,客户端持续推送音频分片数据,服务端边接收边返回识别结果。这种方式的好处是低延迟,适合需要即时反馈的场景,但如果网络波动,可能会导致连接断开,需要重连机制。

我在项目里两条链路都实现了,但主力走的是录音文件识别。原因很朴素:会议录音是“事后整理”的属性,不是“现场直播”的属性。多等几分钟完全可接受,但准确率每提升一个点都是赚的。

假设一场1小时的会议,转写完成后是8000多字的稿子。如果准确率从90%提升到95%,意味着减少了400个错字,这省下来的校对时间远比等待转写的那几分钟宝贵。

2.3 音频格式与识别参数的取舍

录音文件识别接口对音频格式有要求,支持wav、mp3、m4a等常见格式。这里有个容易被坑的地方:pcm转码参数必须和实际音频匹配。

腾讯云的录音文件识别,如果你提交的是mp3,它要求声道数、采样率等信息通过参数传过去,比如EngSerViceType、SourceType、ChannelNum、ResTextFormat这些字段。一旦传错,轻则识别结果乱码,重则直接报错。

我建议统一走wav格式:

参数推荐值说明
采样率16000Hz语音识别最常用的采样率,人声频率范围完全覆盖
声道1(单声道)双声道会混乱,会议场景实际只需单声道
编码PCM无损格式,识别精度最高

采样率设置成16000Hz而不是更高的44100Hz,不是因为后者不行,而是识别引擎的设计是围绕16K音频优化的,更高的采样率不会带来识别率的提升,反而增加传输和处理的负担。这个跟音乐欣赏是两码事,语音识别追求的是“说话的内容”而不是“声音的质感”。

2.4 为什么需要专门设计说话人分离

开会最怕什么?最怕分不清话是谁说的。其实腾讯云录音文件识别接口有说话人分离的功能,开启之后会在返回结果里标记SpeakerId字段,表示当前这句话是哪个说话人说的。

但这个功能不是默认开启的,而且开启方式有点隐蔽。它实际上和ChannelNum参数相关,需要在提交识别任务时传SpeakerDiarization相关的参数。

实际体验中,说话人分离的效果有一定波动。如果与会人员声线差异大、麦克风距离不同,效果会比较明显。但如果几个人声线相近,或者近距离同时说话,就可能分错人。我的建议是:

  • 现场录音时尽量让每个人的麦克风距离固定
  • 如果用了全向麦克风,说话人分离效果会打折
  • 分不清的时候,至少能把“提问者”和“回答者”大体区分开

对会议纪要来说,哪怕只是简单区分为“A说话人”和“B说话人”,整理起来也轻松很多,至少你能理解对话的上下文关系。

3. 实操过程:从零搭一个会议转写工具

3.1 环境准备与账号开通

动手之前,先要把基础环境准备好。我用的是Python 3.9+,你只要是3.7以上基本都没问题。依赖库只有两个核心的:

pip install tencentcloud-sdk-python pip install cos-python-sdk-v5

第一个是腾讯云官方SDK,包含了语音识别的接口调用;第二个是对象存储COS的SDK,用来上传音频文件。

然后去腾讯云控制台开通三个服务:

  1. 语音识别:找到“语音识别”产品,开通录音文件识别
  2. 对象存储COS:创建一个存储桶,地域选离你最近的
  3. 访问管理CAM:创建API密钥,拿到SecretId和SecretKey

创建密钥的时候注意,这个密钥相当于你账号的钥匙。不要明文写在代码里,更不要推到公开的Git仓库。正确做法是用环境变量加载。我之前见过有人把SecretKey硬编码在脚本里然后传到GitHub上,几小时后账号就被盗刷了,上来就跑了一堆高价的AI接口,账单感人。

密钥建议这样存:

export TENCENT_SECRET_ID="你的SecretId" export TENCENT_SECRET_KEY="你的SecretKey"

代码里读取环境变量:

import os secret_id = os.environ.get("TENCENT_SECRET_ID") secret_key = os.environ.get("TENCENT_SECRET_KEY")

3.2 录音文件识别主流程

录音文件识别的整个流程按步骤拆开是这样的:

  1. 音频文件上传到COS,拿到URL
  2. 调用录音文件识别接口,提交URL和参数
  3. 接口返回一个TaskId
  4. 轮询查询任务状态,或者等回调通知
  5. 任务完成后,拉取识别结果

先看上传COS的代码:

from qcloud_cos import CosConfig from qcloud_cos import CosS3Client secret_id = os.environ.get("TENCENT_SECRET_ID") secret_key = os.environ.get("TENCENT_SECRET_KEY") region = "ap-guangzhou" # 换成你的地域 bucket = "meeting-audio-1250000000" # 换成你的存储桶名称 config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key) client = CosS3Client(config) # 上传录音文件 file_path = "meeting_20250620_1400.wav" cos_key = "meetings/meeting_20250620_1400.wav" response = client.put_object( Bucket=bucket, Body=open(file_path, "rb"), Key=cos_key, ContentType="audio/wav" ) print(f"上传成功,ETag: {response['ETag']}")

这里有个细节值得注意:ContentType最好设置成正确的音频类型,虽然不设置大概率也能用,但设置之后可以避免一些边缘问题。

文件URL的拼接方式是固定的:https://{bucket}.cos.{region}.myqcloud.com/{cos_key}。拿到URL后,调用录音文件识别接口。

3.3 提交录音识别任务

录音文件识别接口是CreateRecTask。一个比较容易绕晕的点是:这个接口在tencentcloudapi包的asr.v20190614模块里,但SDK的初始化方式和对象存储COS是完全独立的。

from tencentcloud.common import credential from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.asr.v20190614 import asr_client, models cred = credential.Credential(secret_id, secret_key) http_profile = HttpProfile() http_profile.endpoint = "asr.tencentcloudapi.com" client_profile = ClientProfile() client_profile.httpProfile = http_profile client = asr_client.AsrClient(cred, "ap-guangzhou", client_profile) req = models.CreateRecTaskRequest() params = { "EngineModelType": "16k_zh", # 16k中文普通话模型 "ChannelNum": 1, # 单声道 "ResTextFormat": 0, # 0: 原文转写 "SourceType": 0, # 0: 音质无损,1: 音质有损 "Url": file_url, # 音频文件URL "ConvertNumMode": 1, # 阿拉伯数字转换 "FilterDirty": 1, # 过滤脏词 "FilterModal": 1, # 过滤语气词 "SpeakerDiarization": 1, # 开启说话人分离 "SpeakerNumber": 4 # 说话人分离最大说话人数 } req.from_json_string(json.dumps(params)) resp = client.CreateRecTask(req) print(resp.to_json_string(indent=2))

这里把每个关键参数说明一下,因为绝大多数识别效果问题,都出在参数配错上。

EngineModelType:识别引擎模型,16k_zh表示16kHz采样率的中文普通话模型。这个是使用频率最高的配置。如果你的会议里英文占比较大,可以试试16k_zh_en中英文混合模型,但这个模型对纯中文场景的识别效果略逊于16k_zh,需要做取舍。

ChannelNum:声道数,必须和音频文件实际声道数一致。我上面提到推荐录音时直接用单声道,就是为了这里省心。

ResTextFormat:返回结果格式。0是原文转写,1是数字转换后的结果。这里设置为0,然后通过ConvertNumMode单独控制数字是否转成阿拉伯数字。

SourceType:这个参数很多人忽略。0表示音质无损,适用于wav这种PCM格式;1表示音质有损,适用于mp3、m4a等压缩格式。如果设置错了,比如wav文件传了1,识别准确率会下降。

FilterDirty和FilterModal:过滤脏词和语气词。开会的时候大家难免说一些“那个”“就是说”“嗯嗯啊啊”,开关打开之后,转写结果会干净很多,后续整理纪要能省不少精力。

SpeakerDiarization:说话人分离开关。这个功能输出的是SpeakerId,区分不同说话人。SpeakerNumber建议设置成实际与会人数,不要设得太大,设太大偶尔会分出多余的空说话人。

3.4 轮询查询识别结果

提交任务之后,返回体里有一个TaskId,需要用它去查结果。录音文件识别是异步的,查询接口是DescribeTaskStatus。

req = models.DescribeTaskStatusRequest() params = { "TaskId": resp.TaskId } req.from_json_string(json.dumps(params)) # 轮询,每5秒查一次 for i in range(120): resp = client.DescribeTaskStatus(req) status = resp.Status # 0: 等待中,1: 处理中,2: 成功,3: 失败 if status == 2: print("识别完成") print(resp.to_json_string(indent=2)) break elif status == 3: print("识别失败") break else: time.sleep(5)

轮询间隔5秒是合理的平衡点。太频繁没有意义,服务器处理一个1小时的录音文件通常需要几十秒到几分钟不等;太久则浪费等待时间。如果量很大,也可以用回调通知机制替代轮询,但回调需要你有一个公网可访问的HTTP服务,在小项目里性价比不高,轮询已经够用了。

成功返回的数据结构大致是这样的:

{ "Result": { "AudioDuration": 3600, "RoleNameList": [], "SentenceList": [ { "SentenceId": 1, "SpeakerId": 2, "StartTime": 0, "EndTime": 1500, "Words": [], "Text": "今天我们讨论一下项目进度" }, { "SentenceId": 2, "SpeakerId": 1, "StartTime": 1500, "EndTime": 4200, "Text": "好的,我先说下这个迭代的完成情况" } ] } }

SentenceList是分句数组,每一句都有SpeakerId和时间戳。拿到这个结构后,就可以写代码把纯文本提取出来,顺便按说话人分组。

3.5 把结果整理成可用的会议纪要

识别结果拿到之后,别急着用。虽然过滤了语气词,但口语内容还是很散乱的。

我实际项目里加了一个轻量级的整理逻辑:

  1. 按SpeakerId分组,将每个说话人的发言拼接起来
  2. 去掉重复的句子(识别偶尔会重复输出同一句)
  3. 按时间片对齐,输出带时间戳的文本
  4. 生成一个简单的Markdown文件
import json def build_meeting_notes(recognize_result): sentences = recognize_result["Result"]["SentenceList"] lines = [] for sent in sentences: speaker = sent.get("SpeakerId", 0) text = sent["Text"].strip() if not text: continue start_time = sent["StartTime"] minutes = int(start_time / 1000 / 60) seconds = int(start_time / 1000 % 60) ts = f"{minutes:02d}:{seconds:02d}" lines.append(f"[{ts}] 说话人{speaker}: {text}") return "\n".join(lines)

最后把内容写入文件:

def save_notes(content, output_path="meeting_notes.md"): with open(output_path, "w", encoding="utf-8") as f: f.write("# 会议纪要\n\n") f.write(content)

这个整理逻辑非常简单,但已经能覆盖大部分日常需求了。你甚至可以在这个基础上做二次开发,比如用大模型对识别结果做摘要生成、任务提取,那就是另一个项目的事了。

4. 常见问题与排查技巧实录

4.1 识别结果乱码或大面积错字

这是我碰到最多的一个问题,起因无非是三个:音频采样率不匹配、声道数传错、SourceType传错。

排查思路按顺序来:

  • 确认wav文件是16kHz采样率,可以用工具查看音频信息,比如ffprobe命令
  • 确认提交任务的ChannelNum和音频实际声道一致,传了1但音频是双声道,识别结果就会乱
  • 确认SourceType传值正确,wav传0,mp3传1

这里有一件事需要特别留意:语音识别引擎是固定16kHz模型,你给它44.1kHz的音频,它并不会自动降采样,而是直接造成频谱错乱。所以录音软件里最好直接设置成16kHz采样,而不是事后转码。虽然事后用FFmpeg转也能用,但前端就处理好,能少一个出错环节。

4.2 录音文件识别任务一直卡在“处理中”

TaskId提交成功,状态一直是1,等了很久也没变成2。这种情况常见于音频时长过长的任务。

录音文件识别接口对音频时长有限制,具体数值会根据计费方案有所变化。一个5小时以上的音频,处理时间可能长达十几分钟,实际是需要一点耐心的。但如果超过20分钟还是处理中,基本可以判断是异常了。

另一种可能就是文件URL不对。URL是COS生成的,如果你用浏览器能访问,说明公网可达性没问题。如果设置了私有读权限,URL就需要附带签名,否则服务端无法拉取文件。我之前犯过这个错:把存储桶权限设成了私有读写,生成的URL没有签名,识别服务一直拿不到文件,卡在“处理中”。后来把URL加上签名参数就解决了。

4.3 说话人分离效果不理想

说话人分离不是万能的金手指,它的效果受录音环境影响很大。如果整个会场只有一支全向麦克风,几个人围着坐,分离效果还行。但如果有一个人离麦克风特别远,声音小,就容易被识别成另一个人。

实际经验是:

  • 参会人数不要超过SpeakerNumber设置值太多,比如开20个人的会,SpeakerNumber别设成4
  • 不同人的声音最好差异明显些,声线相近的确实容易混
  • 如果分离效果不好,建议退一步,直接把SpeakerDiarization关掉,只用时间戳做区分

宁可不要说话人信息,也不要给错误的说话人信息。错误的归属比没有归属更误导人。

4.4 网络不稳定导致实时识别断开

实时链路对网络的依赖更强。如果你在会议室用Wi-Fi,信号不好时WebSocket连接会断开。

排查方法很直接:

  • 用有线网络或者手机热点替代Wi-Fi
  • 降低音频分片的大小,减少单次传输压力
  • 加断线重连逻辑,检测断开后自动重连并继续推送

我自己不太依赖实时链路,主要还是事后转录音文件。如果你以离线整理为主,网络问题其实不是重点。

4.5 费用控制心得

语音识别是按量计费的,用多少付多少。但不同类型的计费方式差异很大。

控制成本的方法:

  • 会议录音先本地整理,确认需要转写再提交,避免无效转写
  • 实时识别是流式计费,一秒一秒往上加,长会议成本会累积
  • 对象存储COS的存储费用很低,可以把音频文件留存一段时间再删

最省钱的方法是:能不进实时识别就不进,能合并的文件合并转写,能删的临时音频及时删。我在实际用的时候,一个月几十场会,费用完全可以接受。

5. 项目扩展与个人经验总结

5.1 还能往哪个方向进化

这套工具的初始版本已经能跑通“录音→转写→笔记”的基础链路,接下来有几个值得做的扩展方向:

接入大模型做摘要生成。识别结果拿到后,丢给大模型做结构化提取,自动整理出“结论”“待办事项”“风险点”这些板块。这个扩展能极大提升会议纪要的可用性,是我下一步计划里优先级最高的。

做成一个Web服务。目前是命令行式的一次性脚本,想用得舒服,可以加个极简的Web界面,上传录音文件、点击转写、页面展示结果。技术上也不难,用Flask或者FastAPI包一层就行。

对接日历和会议系统。把转写任务和日历事件关联起来,会议结束自动触发转写,转写完成自动归档到对应日期的文件夹里。这个自动化流程能省掉大量人工操作。

加入关键词高亮和检索。对识别文本做词频统计和关键词高亮,会后快速定位某个话题出现的片段。这个功能对需要复盘的项目会议特别有用。

5.2 踩过坑之后的几点实在经验

整套项目从想法到落地,回头看看,最值得分享的经验有三条:

第一,先明确你的主力场景,再选接口。我一开始追求实时转写,觉得“实时”听起来更酷。但实际用下来,会后整理才是刚需,录音文件识别才是主力。先花十分钟想清楚你最大的痛点是什么,能少走不少弯路。

第二,识别准确率的最大杠杆在录音环节,不在识别参数。同一套识别引擎,你用电脑自带麦克风录出来的效果,和用专业会议麦克风录出来的效果,差距是天壤之别。资金有限的话,优先投资一个靠谱的麦克风,比调什么参数都管用。我自己后来换了一个三百多块的会议全向麦,识别准确率肉眼可见地上升。

第三,转写结果不是最终交付物,整理才是。你不可能把一份纯转写稿直接扔给团队当会议纪要,口语化表达太多了。你要么花时间在工具里加整理逻辑,要么接受自己手动整理。我的经验是,至少要花十分钟把转写稿过一遍,删掉明显重复的内容,标注重点段落。工具的价值是帮你把“听录音写逐字稿”这个最痛苦的过程省掉,而不是替代你的判断力。

最后再分享一个小技巧:我每次开完会,都会在录音文件命名上加上日期和主题,比如20250620_产品评审_语音识别接入.wav。转写完成后生成的会议纪要文件用同样的命名规则。这样半年之后想找某次讨论的内容,搜文件名就能快速定位,配合时间戳片段跳转,找信息特别方便。这套工具从搭好到现在已经稳定用了大半年,解决了开会后最让人头疼的记录整理问题,也让我有更多精力专注在会议讨论本身。如果你也有类似的困扰,照着上面的思路搭一套,应该很快就能上手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询