资讯动态

腾讯云AI语音识别实战:自动生成会议纪要的完整方案

发布时间:2026/10/20 9:07:58 来源:尧图企业网站定制
1. 会议记录这件事到底难在哪开会这件事但凡在职场待过几年的人都有体会。真正让人头疼的不是开会本身而是会后那一堆烂摊子——谁说了什么、决定了什么、谁负责跟进、截止时间是什么时候。一场一小时的会录音回放整理至少要再花一小时如果碰上跨部门对齐会或者需求评审会参与人多、语速快、话题跳来跳去整理难度直接翻倍。我之前在一个项目组里每周固定三次例会加上临时拉的各种对齐会一周下来光会议记录就能吃掉我大半天的时间。最开始用笨办法边听录音边手打后来试过找外部转写服务但要么按分钟计费成本太高要么转写质量堪忧专业术语全给你识别成谐音错别字。再后来我开始琢磨能不能用云厂商提供的语音识别能力自己搭一个轻量的会议小帮手把录音丢进去自动出文字稿再做一些后处理至少把最耗时的“听写”环节自动化掉。这篇文章就是把我这段时间折腾出来的方案完整拆一遍。核心思路是用腾讯云 AI 语音识别做底层转写引擎配合对象存储做文件中转再用一点简单的后处理逻辑把原始转写结果整理成可读的会议纪要雏形。整套东西不需要多高深的技术背景只要你会一点 Python、能看懂 API 文档就能跟着复现。适合经常需要整理会议记录的产品经理、项目经理、技术负责人也适合想找一个实际项目练手云服务调用的开发者。先说清楚这个方案能做什么、不能做什么。它能做的是把会议录音文件自动转成带时间戳的文字稿支持中文普通话和常见方言能区分不同说话人也就是常说的角色分离转写结果可以导出成文本或结构化数据。它不能做的是完全替代人的理解判断比如自动提炼出“这个需求到底做不做”这种需要上下文和业务判断的结论。所以定位很清楚——它是一个帮你省掉80%机械劳动的工具剩下20%的思考和决策还是得人来。2. 整体方案设计与技术选型思路2.1 为什么选腾讯云 AI 语音识别市面上做语音识别的方案不少有开源的 Whisper 系列、有各家云厂商的商用 API也有专门做会议转写的 SaaS 产品。我最终选腾讯云主要基于几个实际考量。第一是中文场景的识别准确率。会议场景里中英文混说、专业术语、口音都是常态我实测下来腾讯云在中文普通话和常见方言上的表现比较稳尤其是对互联网行业常见词汇的识别比如“迭代”“对齐”“闭环”这类词基本不会出错。第二是说话人分离能力腾讯云的录音文件识别支持开启说话人分离返回结果里会带上每个句子的说话人标识这对会议场景太重要了——你总不希望整理出来的纪要分不清哪句是产品说的、哪句是技术说的。第三是接入成本低SDK 覆盖主流语言文档也还算清晰从注册到跑通第一个 Demo 大概半小时就能搞定。当然也有取舍。开源方案的好处是数据不出本地、没有调用费用但部署和维护成本高识别效果调优需要大量精力。商用 SaaS 产品开箱即用但通常按席位或按时长收费长期用下来不便宜而且数据要上传到第三方平台。腾讯云的方案介于两者之间——按调用量付费用多少算多少数据走的是云服务但可控对于中小团队来说性价比比较合适。2.2 整体架构拆解整个会议小帮手的流程可以拆成四步录音上传 → 语音转写 → 结果后处理 → 输出纪要。录音上传这一步我用的是对象存储做中转。原因很简单腾讯云的语音识别 API 支持直接传入音频文件的 URL你不需要把整个文件读成二进制流再传只要把文件传到对象存储生成一个可访问的链接把这个链接丢给识别接口就行。这样做的好处是解耦——上传和识别是两个独立步骤失败了可以单独重试不用从头再来。语音转写是核心环节调用腾讯云的录音文件识别接口。这里有个关键选择用异步接口还是同步接口。同步接口适合短音频比如几十秒的语音消息但会议录音动辄半小时起步必须用异步接口。异步接口的逻辑是你提交一个任务腾讯云后台排队处理处理完了你再去查询结果。这个“提交-轮询-获取”的模式是会议场景的标准做法。结果后处理是我自己加的一层。腾讯云返回的原始结果是 JSON 格式包含每个句子的文本、开始时间、结束时间、说话人标识。直接看这个 JSON 没什么意义需要做几件事按说话人聚合、按时间顺序拼接、去掉语气词和重复、生成带时间戳的文本稿。如果要求再高一点还可以接入大模型做摘要提炼但那是另一个话题了这篇文章先把转写这条链路讲透。输出纪要这一步我做了两个版本一个是纯文本的逐字稿方便全文检索一个是结构化的 Markdown 文件按说话人分段带时间戳方便快速浏览。两个版本各有用途逐字稿适合需要精确引用的场景结构化稿适合快速回顾。2.3 关键参数选择与计算逻辑在调用识别接口时有几个参数直接影响最终效果和成本值得单独说一下。引擎模型选择。腾讯云提供多种识别引擎比如通用引擎、电话引擎、视频引擎等。会议场景我选的是通用引擎因为它对远场录音和多人对话的适配更好。如果你是在线会议直接录制的音频通用引擎足够如果是用手机放在会议桌上录的可能需要考虑远场优化。采样率。腾讯云支持 8000Hz 和 16000Hz 两种采样率。16000Hz 的识别准确率明显更高但文件体积也更大。我的建议是如果存储和带宽不是瓶颈一律用 16000Hz。会议录音通常不会特别大一小时 16000Hz 单声道的 WAV 文件大概 100MB 左右完全在可接受范围内。说话人分离。这个参数开启后会增加处理时间但会议场景强烈建议开启。腾讯云的说话人分离最多支持区分多个说话人具体数量取决于音频质量。实测下来如果录音清晰、每人说话间隔明显分离准确率能到八成以上。如果多人同时说话分离效果会打折扣这是所有语音识别方案的共同局限。费用估算。腾讯云语音识别的计费方式是按音频时长算具体单价会随活动和套餐变化。假设一场会议平均一小时一周开十场会一个月就是四十小时左右的音频。按照常见的阶梯计价这个量级的月成本大概在几十块钱这个区间相比人工整理的时间成本基本可以忽略不计。3. 核心细节解析与实操要点3.1 音频预处理别跳过这一步很多人拿到录音文件直接就往识别接口里丢结果识别效果不理想回头怪 API 不行。其实问题往往出在音频本身。会议录音常见的几个坑音量太小、背景噪音大、多人同时说话、格式不兼容。我的做法是在上传之前先做一轮预处理。如果录音是手机自带的录音功能生成的通常是 M4A 或 AAC 格式腾讯云虽然支持这些格式但转成 WAV 或 MP3 会更稳。转码工具我用的是 ffmpeg一条命令搞定ffmpeg -i input.m4a -ar 16000 -ac 1 -c:a pcm_s16le output.wav这条命令做了三件事把采样率统一成 16000Hz把声道数转成单声道把编码格式转成 PCM。单声道对会议录音来说足够了还能减小文件体积。如果你的录音音量偏小可以加一个音量归一化的参数ffmpeg -i input.m4a -ar 16000 -ac 1 -af volume2.0 -c:a pcm_s16le output.wavvolume2.0表示音量放大两倍具体数值根据实际情况调整别调太大否则会爆音。注意预处理不是必须的但做了之后识别准确率通常会有可感知的提升。尤其是音量归一化对远场录音效果明显。3.2 对象存储的配置与文件上传腾讯云的对象存储用来做文件中转你需要先创建一个存储桶。创建时有几个配置要注意地域选择离你最近的这样上传下载速度最快访问权限设置为私有读写因为会议录音涉及内部信息不能公开跨域设置如果只是服务端调用不需要配。上传文件的代码用 Python SDK 大概长这样from qcloud_cos import CosConfig, CosS3Client secret_id 你的SecretId secret_key 你的SecretKey region ap-guangzhou config CosConfig(Regionregion, SecretIdsecret_id, SecretKeysecret_key) client CosS3Client(config) bucket your-bucket-name key meetings/2024-01-15-weekly.wav with open(output.wav, rb) as f: client.put_object(Bucketbucket, Bodyf, Keykey) url client.get_presigned_url( MethodGET, Bucketbucket, Keykey, Expired3600 ) print(url)这里用get_presigned_url生成一个带签名的临时链接有效期设为一小时。这个链接可以直接传给语音识别接口不需要把文件再下载一遍。临时链接的好处是安全——链接过期后就失效了不会造成长期暴露。实操心得存储桶建议按项目或按月份分目录比如meetings/2024-01/时间长了文件多了好管理。另外记得设置生命周期规则比如三个月前的录音自动删除省存储费用。3.3 调用语音识别接口的完整流程腾讯云的录音文件识别是异步接口整个调用流程分三步创建任务、查询任务状态、获取识别结果。创建任务的代码示例from tencentcloud.common import credential from tencentcloud.asr.v20190614 import asr_client, models cred credential.Credential(secret_id, secret_key) client asr_client.AsrClient(cred, ap-guangzhou) req models.CreateRecTaskRequest() req.EngineModelType 16k_zh req.ChannelNum 1 req.ResTextFormat 0 req.SourceType 0 req.Url url req.SpeakerDiarization 1 req.SpeakerNumber 0 resp client.CreateRecTask(req) task_id resp.Data.TaskId print(f任务已创建TaskId: {task_id})几个参数解释一下EngineModelType设为16k_zh表示用 16000Hz 中文通用引擎ChannelNum设为 1 表示单声道SpeakerDiarization设为 1 开启说话人分离SpeakerNumber设为 0 表示让系统自动判断说话人数如果你明确知道有几个人参会也可以指定具体数字这样分离准确率会更高。创建任务后需要轮询查询任务状态import time while True: req models.DescribeTaskStatusRequest() req.TaskId task_id resp client.DescribeTaskStatus(req) status resp.Data.Status if status 2: print(识别完成) result_url resp.Data.ResultUrl break elif status 3: print(识别失败) break else: print(处理中等待10秒...) time.sleep(10)状态码的含义0 是等待中1 是处理中2 是成功3 是失败。轮询间隔建议设 10 秒左右太频繁没必要太慢又浪费时间。一场一小时的会议识别时间通常在几分钟到十几分钟之间取决于排队情况。识别完成后ResultUrl是一个 JSON 文件的下载链接你需要再发一个 HTTP 请求把结果拉下来。这个 JSON 的结构大概是这样的{ Result: { SentenceList: [ { Text: 我们先过一下上周的进度, StartTime: 0, EndTime: 2500, SpeakerId: 0 }, { Text: 好的我这边上周完成了接口联调, StartTime: 2600, EndTime: 5800, SpeakerId: 1 } ] } }每个句子包含文本、开始时间毫秒、结束时间、说话人 ID。拿到这个结构之后后处理就有据可依了。3.4 后处理从原始 JSON 到可读纪要原始 JSON 直接看是没法用的需要做几层加工。第一层是按说话人聚合。把同一个说话人的连续句子合并成一段这样读起来才像对话。实现逻辑很简单遍历句子列表如果当前句子的说话人和上一句相同就追加到同一段如果不同就新起一段。第二层是时间戳格式化。原始时间是毫秒数需要转成HH:MM:SS的格式。这个用 Python 的datetime或者简单的除法取模都能做。第三层是语气词过滤。会议录音里“嗯”“啊”“那个”“就是说”这类词很多虽然不影响理解但会让文本显得啰嗦。我维护了一个语气词列表在拼接文本时做替换或删除。不过要注意有些语气词删掉后会影响语义比如“这个方案不是不行”里的“不是”不能删所以过滤要谨慎宁可少删不要误删。第四层是输出格式化。我做了两个输出一个是纯文本逐字稿每行一句带时间戳和说话人另一个是 Markdown 格式按说话人分段段落之间空行方便阅读。def format_result(sentence_list): paragraphs [] current_speaker None current_text [] for sent in sentence_list: speaker sent[SpeakerId] text sent[Text] start sent[StartTime] if speaker ! current_speaker: if current_text: paragraphs.append({ speaker: current_speaker, text: .join(current_text) }) current_speaker speaker current_text [text] else: current_text.append(text) if current_text: paragraphs.append({ speaker: current_speaker, text: .join(current_text) }) return paragraphs这段代码的输出是一个段落列表每个段落包含说话人和该说话人的连续发言。你可以把它渲染成任何你想要的格式。注意说话人分离的 ID 是系统自动分配的不一定和实际参会人对得上。比如 SpeakerId 0 可能是张三也可能是李四需要你根据发言内容人工映射一次。如果会议有固定的参会人可以在后处理时加一个映射表把 SpeakerId 转成真实姓名。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把环境搭起来。我用的 Python 版本是 3.9太老的版本可能有些库不兼容。需要安装的依赖主要有三个腾讯云 SDK、对象存储 SDK、以及音频处理工具 ffmpeg。pip install tencentcloud-sdk-python pip install cos-python-sdk-v5ffmpeg 需要单独安装不同系统命令不一样。Ubuntu 下是apt install ffmpegmacOS 下是brew install ffmpegWindows 下建议直接下载可执行文件加到环境变量里。腾讯云的 SecretId 和 SecretKey 在控制台的访问管理页面创建。建议不要用主账号的密钥而是创建一个子账号只授予语音识别和对象存储的最小权限。这样即使密钥泄露影响范围也可控。实操心得密钥不要硬编码在代码里用环境变量或者配置文件管理。我习惯用.env文件加python-dotenv库本地开发方便上线时换成环境变量注入。4.2 完整代码实现与逐段解析把前面的片段串起来完整的流程代码大概是这样import os import time import json import requests from dotenv import load_dotenv from qcloud_cos import CosConfig, CosS3Client from tencentcloud.common import credential from tencentcloud.asr.v20190614 import asr_client, models load_dotenv() SECRET_ID os.getenv(TENCENT_SECRET_ID) SECRET_KEY os.getenv(TENCENT_SECRET_KEY) REGION ap-guangzhou BUCKET your-bucket-name def upload_audio(local_path, cos_key): config CosConfig(RegionREGION, SecretIdSECRET_ID, SecretKeySECRET_KEY) client CosS3Client(config) with open(local_path, rb) as f: client.put_object(BucketBUCKET, Bodyf, Keycos_key) url client.get_presigned_url(MethodGET, BucketBUCKET, Keycos_key, Expired3600) return url def create_asr_task(audio_url): cred credential.Credential(SECRET_ID, SECRET_KEY) client asr_client.AsrClient(cred, REGION) req models.CreateRecTaskRequest() req.EngineModelType 16k_zh req.ChannelNum 1 req.ResTextFormat 0 req.SourceType 0 req.Url audio_url req.SpeakerDiarization 1 req.SpeakerNumber 0 resp client.CreateRecTask(req) return resp.Data.TaskId def wait_for_result(task_id, max_wait1800): cred credential.Credential(SECRET_ID, SECRET_KEY) client asr_client.AsrClient(cred, REGION) waited 0 while waited max_wait: req models.DescribeTaskStatusRequest() req.TaskId task_id resp client.DescribeTaskStatus(req) status resp.Data.Status if status 2: return resp.Data.ResultUrl elif status 3: raise Exception(识别失败) time.sleep(10) waited 10 raise Exception(超时) def download_result(result_url): resp requests.get(result_url) return resp.json() def format_output(result_json): sentences result_json[Result][SentenceList] paragraphs [] current_speaker None current_text [] for sent in sentences: speaker sent[SpeakerId] text sent[Text] if speaker ! current_speaker: if current_text: paragraphs.append({speaker: current_speaker, text: .join(current_text)}) current_speaker speaker current_text [text] else: current_text.append(text) if current_text: paragraphs.append({speaker: current_speaker, text: .join(current_text)}) return paragraphs def save_markdown(paragraphs, output_path): with open(output_path, w, encodingutf-8) as f: for p in paragraphs: f.write(f**说话人 {p[speaker]}**{p[text]}\n\n) if __name__ __main__: audio_path meeting.wav cos_key meetings/2024-01-15-weekly.wav print(上传音频...) url upload_audio(audio_path, cos_key) print(创建识别任务...) task_id create_asr_task(url) print(fTaskId: {task_id}) print(等待识别完成...) result_url wait_for_result(task_id) print(下载结果...) result_json download_result(result_url) print(格式化输出...) paragraphs format_output(result_json) save_markdown(paragraphs, meeting_notes.md) print(完成结果已保存到 meeting_notes.md)这段代码可以直接跑只需要把配置项换成你自己的。整个流程从上传到输出大概需要几分钟到十几分钟取决于音频长度和排队情况。4.3 效果验证与调优记录第一次跑通之后我拿一场真实的周会录音做了测试。录音时长 52 分钟参会 5 人内容涉及技术方案讨论和排期。识别结果整体准确率在 90% 以上专业术语基本正确说话人分离大体准确但在几个人抢话的地方出现了混淆。针对发现的问题我做了几轮调优。第一轮是调整音频预处理参数把音量归一化加上识别准确率有轻微提升。第二轮是调整SpeakerNumber参数从自动判断改成指定 5 人说话人分离的准确率明显改善。第三轮是后处理阶段加了自定义词典把团队内部常用的项目代号和缩写加进去避免被识别成同音词。实操心得自定义词典这个功能很实用但要注意别加太多否则会影响识别速度。我一般只加那些反复出现且容易识别错的词控制在几十个以内。5. 常见问题与排查技巧实录5.1 识别准确率不达预期怎么办这是最常见的问题。排查思路按优先级来先看音频质量再看参数配置最后看后处理逻辑。音频质量方面如果录音本身噪音大、音量小、多人同时说话任何识别引擎都救不了。这种情况只能从源头改善比如换一个好一点的麦克风或者要求参会人发言时稍微等一下再开口。参数配置方面检查采样率是否匹配、引擎模型是否选对、说话人分离是否开启。后处理方面检查是否有过度过滤导致语义丢失。下面这张表整理了我遇到过的主要问题和对应解法问题现象可能原因排查方法解决方案大量错别字音频质量差或采样率不匹配用播放器听原始音频重新录制或转码为16000Hz说话人分不清多人同时说话或未开启分离检查SpeakerDiarization参数开启分离并指定说话人数专业术语识别错引擎不熟悉领域词汇对比原文和识别结果添加自定义词典识别任务一直排队高峰期提交或音频过长查看任务状态码错峰提交或分段处理结果下载失败临时链接过期检查ResultUrl有效期重新查询任务获取新链接5.2 任务超时与失败重试机制异步接口最大的不确定性是排队时间。我遇到过高峰期提交任务后等了快半小时才出结果的情况。所以代码里一定要有超时机制和重试逻辑。我的做法是设置一个最大等待时间比如 30 分钟超过就认为失败。失败后不直接放弃而是把 TaskId 存下来过一段时间再查一次。因为有时候任务其实已经完成了只是查询的时候还没更新状态。另外如果识别失败状态码是 3可以重新提交一次任务偶尔是偶发故障。注意重试不要无脑循环建议最多重试两次间隔逐渐拉长。如果两次都失败大概率是音频本身有问题需要人工介入检查。5.3 成本控制的几个实用技巧虽然单次识别费用不高但量大起来也是一笔开支。几个控制成本的思路一是音频预处理时把静音段剪掉会议里经常有长时间沉默或休息时间剪掉后能省不少时长二是如果对实时性要求不高可以攒一批文件一起提交有些套餐批量提交有折扣三是定期清理对象存储里的旧文件存储费用虽然低但积少成多。还有一个容易被忽略的点测试阶段不要用真实会议录音反复跑可以用一小段样本音频做验证确认流程通了再跑完整文件。5.4 数据安全与隐私保护会议录音往往涉及内部信息安全这块不能马虎。我的做法是对象存储桶设为私有所有访问通过临时签名链接识别完成后及时删除存储桶里的原始音频SecretKey 定期轮换如果对数据出境有要求确保选择的地域在合规范围内。另外如果会议内容敏感度高可以考虑在本地做一层加密再上传识别完成后再解密结果。虽然腾讯云本身有数据保护机制但多一层防护总归更稳妥。6. 后续扩展方向与个人体会这套东西跑通之后我又陆续加了一些扩展功能。比如把识别结果自动推送到团队的知识库按项目打标签方便检索比如接入大模型做自动摘要把一小时的会议浓缩成几百字的要点比如做一个简单的 Web 界面让不写代码的同事也能自己上传录音、下载纪要。如果你也想动手做我的建议是先把核心链路跑通别一上来就追求大而全。先做到“上传音频→输出文字稿”这一步用起来再根据实际痛点逐步加功能。技术选型上腾讯云的语音识别对我来说够用了但如果你有特殊需求也可以对比其他方案关键是找到适合自己场景的。最后分享一个小技巧会议开始前花一分钟确认录音设备的位置和音量比事后花一小时修音频划算得多。这个道理放在很多事上都成立——前期多花一点时间准备后期能省下大量返工的成本。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑