首页 / 资讯中心 / 文章详情

会议记录神器:基于腾讯云AI语音识别的自动转写工具搭建指南

会议记录神器:基于腾讯云AI语音识别的自动转写工具搭建指南 ★ FEATURED ARTICLE
会议开了两个小时翻聊天记录和录音文件翻了四十分钟最后发现关键结论记漏了这种事情干过的人应该不在少数。我之前也尝试过用手机自带的录音转文字效果时好时坏普通话带点方言就崩念个英文术语直接变乱码。后来干脆折腾了一套基于腾讯云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用来上传音频文件。然后去腾讯云控制台开通三个服务语音识别找到“语音识别”产品开通录音文件识别对象存储COS创建一个存储桶地域选离你最近的访问管理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 录音文件识别主流程录音文件识别的整个流程按步骤拆开是这样的音频文件上传到COS拿到URL调用录音文件识别接口提交URL和参数接口返回一个TaskId轮询查询任务状态或者等回调通知任务完成后拉取识别结果先看上传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(Regionregion, SecretIdsecret_id, SecretKeysecret_key) client CosS3Client(config) # 上传录音文件 file_path meeting_20250620_1400.wav cos_key meetings/meeting_20250620_1400.wav response client.put_object( Bucketbucket, Bodyopen(file_path, rb), Keycos_key, ContentTypeaudio/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(indent2))这里把每个关键参数说明一下因为绝大多数识别效果问题都出在参数配错上。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(indent2)) 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 把结果整理成可用的会议纪要识别结果拿到之后别急着用。虽然过滤了语气词但口语内容还是很散乱的。我实际项目里加了一个轻量级的整理逻辑按SpeakerId分组将每个说话人的发言拼接起来去掉重复的句子识别偶尔会重复输出同一句按时间片对齐输出带时间戳的文本生成一个简单的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_pathmeeting_notes.md): with open(output_path, w, encodingutf-8) as f: f.write(# 会议纪要\n\n) f.write(content)这个整理逻辑非常简单但已经能覆盖大部分日常需求了。你甚至可以在这个基础上做二次开发比如用大模型对识别结果做摘要生成、任务提取那就是另一个项目的事了。4. 常见问题与排查技巧实录4.1 识别结果乱码或大面积错字这是我碰到最多的一个问题起因无非是三个音频采样率不匹配、声道数传错、SourceType传错。排查思路按顺序来确认wav文件是16kHz采样率可以用工具查看音频信息比如ffprobe命令确认提交任务的ChannelNum和音频实际声道一致传了1但音频是双声道识别结果就会乱确认SourceType传值正确wav传0mp3传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。转写完成后生成的会议纪要文件用同样的命名规则。这样半年之后想找某次讨论的内容搜文件名就能快速定位配合时间戳片段跳转找信息特别方便。这套工具从搭好到现在已经稳定用了大半年解决了开会后最让人头疼的记录整理问题也让我有更多精力专注在会议讨论本身。如果你也有类似的困扰照着上面的思路搭一套应该很快就能上手。
阅读完成 · 觉得有帮助?
咨询建站