资讯动态

腾讯云实时语音识别接入实战:构建会议实时转写与纪要工具

发布时间:2026/10/20 13:42:16 来源:尧图企业网站定制
开篇先说一下这个项目到底干了件什么事儿。我最近在公司内部做了一个会议辅助工具本质上是把腾讯云智能语音的实时语音识别接到了一款网页应用里让会议过程中的发言能实时转成文字同时自动抽取跟当前议题有关的关键词、待办事项以及说话密度比较高的段落。说白了就是开会的时候打开一个页面它一边听着大家说话一边在屏幕上生成逐字稿会后还能直接导出一份按议题分组的简易纪要。这个项目做完以后我们组一个周开六小时的例行评审会整理纪要的时间从原来的一小时左右压缩到了几分钟。适合谁参考呢如果你是做前端或者全栈的开发者刚好又遇到“会议纪要写不过来”“讨论过程追不到信息”“想用云厂商的AI能力但不知道怎么接到自己的业务里”这类问题这篇内容应该能帮你省掉不少弯路。1. 这个“会议小帮手”到底要解决什么以及为什么这么设计1.1 先给需求一个明确的边界动手之前我最先干的事不是申请腾讯云的接口而是把“会议助手”这个词拆成了几个能落地的子目标。市面上的会议产品本身已经很多了如果我把目标定成“做一个跟别人一样的智能会议纪要系统”那就是给自己挖坑。我把需求收敛成了三个核心场景。第一个场景是“实时转写”。开会的时候经常有人问“刚才李总说的那个数据是多少来着”这种信息倒回去找非常痛苦。所以页面必须能做到一边说话一边出文字并且把每段话标记上时间戳。第二个场景是“关键词与待办抽取”。会议废话很多大家最关心的其实是散落在各个发言里的决策项和行动项比如“这件事下周二之前要给我一个结论”或者是“回去跟某某团队对齐一下方案”。我希望系统能自动把这类话挑出来而不是让记录员逐句听录音。第三个场景是“会后快速产出纪要草稿”。原始的逐字稿不能直接发到项目群里太长了也没重点。所以我需要一个后处理逻辑把相似议题聚拢起来生成一段一段的“议题摘要”而不是简单复读原话。这三个场景确定下来以后整个项目的范围就变得非常清晰实时语音识别 文本后处理 一个简洁的前端展示交互。不是去做一个巨大的协同平台而是先把“从语音到结构化纪要”这条链路打通。1.2 为什么选择腾讯云语音识别而不是自训练模型或者其它方案选型的时候我其实先排除掉了“自己部署开源模型”这个思路。团队里没有人精通声学模型训练而且开会用的设备五花八门麦克风质量也不稳定如果端到端全自己做光是噪声环境下识别率调优就够折腾一个季度。对我来说这不是一个研究项目而是一个要快速产生业务价值的工具项目所以直接走云厂商的成熟接口是效率最高的路。在几家云厂商之间我最终用了腾讯云的实时语音识别接口。原因有几个。首先是接入成本足够低它提供了针对Web的WebSocket接口也提供了JavaScript SDK前端不需要在后端挂一层复杂的音频转发。其次腾讯云智能语音在中文普通话识别、尤其是偏口语化的长句转写上表现比较稳我测试过几个同领域的竞品在专业名词不多的情况下差距不大但如果会议里频繁出现“这个”“那个”之类的人称指代或者语速较快腾讯云的断句能力明显更自然。还有一个实际因素是我们本身在腾讯云上就有资源组和密钥管理流程走采购和申请权限比较顺不用重新走一遍供应商对接。当然这里必须说清楚云服务不是没有短板。它按调用时长计费长时间挂着不关费用是会肉眼可见涨的。另外它是通用模型如果你所在行业有大量的人名、产品代号、英文缩写识别结果里会出现不少同音错字这时候你不能干等它识别准而是要在自己的业务层加一道“纠错后处理”这个我后面会专门展开。1.3 整体架构尽量做“薄”系统架构我刻意做得比较薄没有引入复杂的微服务。整个链路就三块前端是一个单页网页负责调用麦克风采集音频、把音频流推到WebSocket通道同时实时渲染识别文本。通信层直接使用腾讯云实时语音识别提供的WebSocket接口相当于前端把音频二进制帧直接发给云端云端回传识别结果。后处理模块单独拎出来不跟实时识别混在一起。等会议结束拿到完整转写文本后再交给一个简单的关键词抽取和聚合脚本处理最终生成结构化纪要文档。这个架构最大的优点是边界清楚。实时链路只需要处理“一句话说完后返回结果”这个状态流转而纪要生成更像是一个离线批处理任务两者关注点完全不同。如果一开始就把实时识别和后处理耦合在一起出问题的时候非常难排查——你不知道到底是识别结果晚到了还是你的纪要逻辑把数据搞坏了。2. 核心环节逐层拆解接口选型、参数理解与前置准备2.1 实时语音识别接口的几个关键认知腾讯云的实时语音识别接口本质上是让客户端和云端维持一条WebSocket长连接客户端持续上传音频分片云端持续返回识别结果。这里有一个重要的术语差异它分为“实时语音识别”和“录音文件识别”两种模式前者响应延迟极低适合会议的现场转写后者适合事后上传整段录音文件费用相对更低。对这个项目来说我们只需要关注前者。接口本身有几个参数直接决定了识别效果。第一个是引擎模型参数EngSerViceType。腾讯云根据场景提供了不同的模型比如电话客服场景、音视频场景、实时会议场景。这个参数务必选对。最开始我用的是通用模型开会时发现“嗯”“啊”之类的语气词经常被莫名其妙识别成有意义的词后来切到会议专用模型之后断句和语气词过滤明显改善。有同类需求的同学先别急着调其它参数先检查这个选对了没有。第二个是VoiceFormat。前端采集到的音频通常是PCM裸流但接口要求的是特定编码格式。我用的是PCM采样率16000单声道每个采样点16 bit。这个组合是腾讯云文档里明确推荐给实时语音识别Web使用的数据帧小、解码快兼容性也最好。你如果拿到的音频是48kHz采样率建议先降采样再推流不然识别效果会有偏差。第三个是Endframe和静音检测相关的参数。实时识别有两种返回策略一种是流式返回中间结果就是半句没说完就开始出字另一种是强制等一句完整的话结束再返回最终结果。会议场景下我们当然希望看到半截文字先出来这样延迟感才低。所以前端要做的事是持续采集麦克风数据并发送而把语义边界判断交给云端。我举一个真实测试里遇到的现象如果不做任何前端静音判断把完全静音的帧也持续发出去云端会频繁触发“说话结束”的逻辑导致一句话被硬生生断成好几段转写结果非常碎。后来我的处理方式是在前端加了一个音量阈值只有当音频帧的均方根值超过一定数值才认为是有意义的声音否则不发送这样语义边界一下就准了很多。2.2 前端音视频采集踩坑最多的地方腾讯云只负责“识别”麦克风采集和音频格式处理是全前端的事。这块看似简单实际上是我整个项目里踩坑最多的地方。浏览器获取麦克风的标准API是navigator.mediaDevices.getUserMedia。这个API会弹一个权限确认框用户点击允许之后我们拿到一个MediaStream对象。如果你想在网页里实时处理这些音频下一步通常是创建一个AudioContext然后用createMediaStreamSource把MediaStream变成可被Web Audio API处理的音频源。这一步是全项目第一个坑点默认情况下浏览器拿到的原始采样率可能是48kHz而腾讯云要求16kHz所以中间必须做一次重采样。重采样的实现方式有两种。一种是把AudioContext的sampleRate参数直接设成16000后创建这样在部分浏览器上输入音频会自动完成重采样。另一种是用OfflineAudioContext或者脚本化节点手动处理。我实测的结果是前一种方式在Chrome和Edge上表现稳定Safari上偶尔会出现兼容问题所以稳妥起见我在代码里做了一次降采样处理。核心逻辑其实没那么玄奥就是收集一个缓冲块里的样本按照比例抽取对应位置的样本。你不需要理解特别高深的数字信号处理知识只要保证发送出去的帧速率均匀、不丢数据即可。另外一个必须处理的环节是PCM编码。Web Audio API给我们的音频数据是归一化后的浮点数范围是-1到1但腾讯云要求的是16 bit线性PCM。所以发送之前要把浮点样本乘以32767再转成带符号的16位整数然后按小端序排列成二进制ArrayBuffer。这块代码我封装了一个工具函数大概样子是function floatTo16BitPCM(inputArrayBuffer) { const view new DataView(inputArrayBuffer); const bytes new ArrayBuffer(view.byteLength * 2); const output new DataView(bytes); for (let i 0; i view.byteLength; i) { const s Math.max(-1, Math.min(1, view.getFloat32(i, true))); output.setInt16(i * 2, s 0 ? s * 0x8000 : s * 0x7FFF, true); } return bytes; }这个过程看起来繁琐但如果不做这一步常见的报错就是“音频数据格式非法”或者识别结果全是乱码。我自己第一次联调的时候就栽在这儿花了大半天反复检查接口参数最后才发现是数据编码问题。2.3 WebSocket状态机比想象中更重要实时语音识别的整个交互过程并不是随便 send 一下就行而是有严格的状态顺序。腾讯云实时语音识别接口的完整交互过程大致是前端首先请求一个鉴权签名然后建立WebSocket连接连接成功后发送一个包含识别参数和签名信息的请求包等云端返回“开始识别”的确认包之后才能持续发送音频帧。音频帧发完之后再发送一个特定的结束包标识等待云端返回最终的结果包然后才能关闭连接。我画不出流程图但用文字描述一下就知道这块有多容易出错。鉴权签名是腾讯云云API的通用鉴权机制本质上是把密钥ID、时间戳、某个随机数以及服务名拼接起来用HMAC-SHA256做一次摘要最后再URL编码。当时我图省事没有做签名的本地调试直接在浏览器里打印了一下鉴权URL确认能连上就去联调了但后来发现不同时间段签名的有效期只有几分钟过期后如果页面还挂着WebSocket就会认证失败、返回错误码。所以前端必须在每次建立连接前重新生成签名而不能只在页面初始化时签一次。另外关闭连接的时序也很关键。在会议结束的时候如果前端直接用socket.close()硬断开云端可能还没把最后那句完整的话处理完结果就是会议最后一句话永远缺尾巴。正确的方式是先发结束帧监听云端返回的“识别完成”消息再去做连接关闭操作。这一点对实时会议场景来说尤其重要因为会议里的最后一句话经常就是总结性结论。3. 实操过程从零构建一个能用的会议转写工具3.1 项目结构与技术栈技术栈没有刻意追新都是比较主流的东西前端用Vue 3搭配Vite做搭建因为项目量级不大不需要引入重型状态管理后端用Node.js的Express写了一个极简服务只负责两件事一是提供生成腾讯云WebSocket签名所需临时凭证的接口二是处理会后纪要生成的离线任务。为什么需要一个后端服务而不是直接在浏览器里保存密钥原因很简单腾讯云的SecretKey是不能暴露在浏览器端的。一旦被用户从开发者工具里扒出来相当于把云账号的指挥权交给了别人这是最基础的安全红线。所以正确姿势是浏览器先从我们的后端请求一个临时凭证或者签名好的WebSocket地址后端用自己环境变量里的密钥去签名签完名就把密钥忘掉返回可用的地址给前端。项目目录大致长这样meeting-assistant/ ├── client/ │ ├── src/ │ │ ├── main.js │ │ ├── App.vue │ │ ├── utils/ │ │ │ ├── audio-processor.js │ │ │ └── websocket-manager.js │ │ └── views/ │ │ └── MeetingRoom.vue │ └── package.json ├── server/ │ ├── index.js │ ├── routes/ │ │ └── auth.js │ └── services/ │ └── minutes-generator.js └── README.md3.2 后端获取临时凭证这一步是安全底线后端提供的接口设计得非常简单。前端发起一个GET请求后端接收到请求后用自己的SecretId和SecretKey生成一个带有必要参数的鉴权WebSocket地址返回给前端。这个地址里包含了鉴权签名、AppId、引擎模型类型、采样率等参数前端只需要把它当成普通的WebSocket地址去连接。核心代码可以浓缩成一段const crypto require(crypto); function buildAuthUrl() { const secretId process.env.TENCENT_SECRET_ID; const secretKey process.env.TENCENT_SECRET_KEY; const host asr.cloud.tencent.com; const path /asr/ws; const timestamp Math.floor(Date.now() / 1000); const signStr asr;${timestamp}; const sign crypto .createHmac(sha256, secretKey) .update(signStr) .digest(hex); const query new URLSearchParams({ secretid: secretId, timestamp, signature: sign, engine_model_type: 16k_zh_dialect, voice_format: 1, }); return wss://${host}${path}?${query.toString()}; }签名的细节就不全铺开了这里想强调一个容易被忽略的问题接口版本不同签名串拼接方式也不同腾讯云目前有一些历史接口在签名规则上并不完全一致。我的建议是无论你用任何云服务都先去读对应产品当前版本的文档而不是直接抄网上的旧教程。我刚开始接入时用的某个教程里的签名规则结果一直报“签名失败”换了新规则的拼接方式才通。这类文档更新得很快经验再丰富也扛不住版本差异。3.3 前端音频流封装让数据流畅地跑起来前端这一侧的节奏把控是整个项目体验的关键。一旦用户点击“开始会议”我们要依次做这些事情调用getUserMedia({ audio: true })拿到麦克风流。创建AudioContext把麦克风流接进去然后通过ScriptProcessorNode或者AudioWorklet拿到连续的音频样本。把样本重采样到16kHz转成16 bit PCM格式。将其缓存进一个队列以固定时间间隔比如每40毫秒发送一次避免一次性发太大块的数据导致网络拥塞。这里用队列而不是直接发送是因为麦克风采集回调的频率和可靠网络的传输速率未必完全匹配。我在测试的时候发现如果不加队列缓冲网络抖动一出现音频帧就会断断续续识别结果会随之出现大段的停顿和缺字。加了队列之后即使某一帧传输失败起码不会打乱后续数据的正常顺序。前端WebSocket管理器的核心逻辑大概是这样class WebSocketManager { constructor(url) { this.ws new WebSocket(url); this.ws.binaryType arraybuffer; } startRecognition() { this.ws.addEventListener(open, () this.sendStart()); this.ws.addEventListener(message, (event) this.onMessage(event)); } sendStart() { const startPacket { type: start, ... }; this.ws.send(JSON.stringify(startPacket)); } sendAudio(buffer) { this.ws.send(buffer); } sendEnd() { this.ws.send({ type: end }); } }实时识别结果回传的时候云端会返回一个包含临时结果或最终结果的JSON。页面上需要同时展示两种状态中间结果用灰色半透明文字展示最终结果用正常黑色文字展示。这样用户在会议进行中会感觉文字是“逐渐定稿”的而不是一段一段蹦出来观感好了很多。伪代码类似onMessage(event) { const data JSON.parse(event.data); if (data.final 1) { this.finalText.push(data.text); this.displayText this.finalText.join(); } else { this.tempText data.text; } }3.4 从逐字稿到结构化纪要一个不算复杂但很管用的后处理实时得到逐字稿之后我面临的下一个问题就是“如何从一大段口语文字里提取出纪要”。这部分我不依赖云端的自动纪要API因为开会的时候我们有自己的书写习惯和格式要求。最终实现了一套轻量级的后处理逻辑整体分成两阶段。第一阶段是“关键词抽取”。我维护了一个与团队业务强相关的词库包括常用产品名、项目代号、人名等然后用简单的字符串匹配方式先标注出这些词在文本中出现的位置。这一阶段的准确率非常高召回率取决于词库是否维护得勤实用性很好比你一开始就上一个复杂的命名实体识别模型更稳。第二阶段是“议题聚类”。做法是把转写文本按时间窗口切块比如每15秒算一个小块然后统计每一个小块里出现了哪些关键词再用一个最简单的文本相似度——基于关键词共现次数——把相邻的小块合并成更大的段落。当一段话里连续出现三到五个与某个项目代号相关的词且时间上连续我就认为它是同一个议题的内容。最后从每一段里挑出包含“结论”“决定”“下一步”“建议”“评估”等指令性动词的句子作为候选纪要内容。这个逻辑虽然简单但非常贴合会议场景的实际情况口语会议就是高频碎片信息夹杂少量关键结论你不需要做语义理解只要能把“一句话里同时出现项目代号和行动词”的句子筛出来就已经能覆盖大多数纪要需求了。4. 常见问题与排错速查这是我花时间最多的地方4.1 高频问题对照表我把开发过程中遇到的高频问题整理成了一张表对应排查方向非常直接现象可能原因排查与解决连接一直建立不了签名过期或者签名规则不正确检查后端生成签名的拼接格式和有效期能连上但一直不出识别文字发送的不是PCM格式检查前端音频编码是否转成16 bit PCM识别结果全是断句碎块前端没有做静音过滤增加音量阈值判断过滤静音帧一句话的最后几个字没有直接关闭了WebSocket改成发送结束帧后等云端回执再关闭识别错字很多引擎模型没选对检查是否用了会议场景专用引擎模型页面卡顿、内存上涨音频源和AudioContext未释放会议结束后主动关闭轨道并断开连接4.2 混响和噪声环境下的调优记录开会场景远比录音棚复杂。我测试时发现在一个没有做吸音处理的会议室里多人同时说话时识别结果会明显变差最典型的表现是“吞字”和人名被识别成同音字。腾讯云的接口提供了一个音频数据增强功能开关开启之后可以自动做环境降噪和回声消除。但这个功能打开之后副作用是响应延迟会略微变高。我自己的平衡方案是会议现场开增强网络环境好、信号源干净的场景比如线上参会就关掉增强效果更自然。当然最稳妥的办法还是物理侧解决比如叮嘱与会者靠近麦克风或者使用麦距更近的桌面全向麦克风。软件永远只能缓解不能彻底解决“人在远处对着空气说话”的问题。4.3 成本控制的三个技巧实时语音识别是按时长计费的这个费用在量起来之后不可小觑。我做了三个控制手段实测效果最明显的第一个是“不识别静音”。前端做了音量阈值过滤会议室里没人说话的时候就不发送音频帧。这样做既节约流量也减少有效计费时长。第二个是“会后立刻断连”。识别结束之后前端主动释放连接绝不挂在那里晾着。之前出现过测试人员开着页面忘了关一整晚没说话但WebSocket和计费时长一直算着的乌龙事件。第三个是“分批测试而不是全量跑”。联调阶段不要用一个小时的录音反复测接口可以先把音频切成30秒小片段测参数效果。确定参数可行了再上完整的会议场景。这个习惯每年能省掉不少测试费。5. 几点实操心得和可能的扩展方向最后讲一些我的个人感受。这个项目做完之后最大的体会是AI能力单独拎出来很强但嵌到实际工作流里决定成败的往往是那些看起来很“笨”的工程细节。比如音频格式对不对、静音帧有没有过滤、连接有没有优雅地关闭、关键词库维护得勤不勤。这些环节做扎实了识别准确率就算不完美整体体验仍然在可用线以上。我现在打算继续做的是再接一个“发言人分离”的能力。当前版本是整个会议共用一个麦克风没法区分是哪位同事在说话纪要到人这块暂时还是空着的。腾讯云的接口里面对音频文件识别有说话人分离的属性但实时链路怎么拿到类似的信息还需要做一些额外的特征判断或者结合声纹识别。另一条路线是在前端做音频能量方向判断配合不同的麦克风阵列来区分发言方位原理上可行但也还在实验阶段。如果你打算参考这个项目的思路我的建议是先别急着上各种高级功能把“实时转写稳定 关键词抽取准确 纪要导出顺手”这三件事做扎实。一套工具如果这三层体验做好了哪怕看起来不华丽在团队里的使用频率也会非常高。这就是这个项目给我最大的启发。

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

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

免费获取报价 →
↑