十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FunASR × Vue:实时语音转写前端的 2pass 结果合并与分帧设计拆解

FunASR × Vue:实时语音转写前端的 2pass 结果合并与分帧设计拆解 FunASR × Vue实时语音转写前端的 2pass 结果合并与分帧设计拆解【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR把麦克风流送到 FunASR 服务端的 WebSocket 端口之后很多前端开发卡在同一件事上partial 结果刚把今天两个字渲染出来offline final 一到整段文字要么重复、要么凭空消失。这不是后端漏发了字而是 FunASR 流式服务的结果下发模型和前端只增不删的更新习惯撞在了一起。本文拆的是 web-pages/public/static/online/ 这份可直接跑的参考实现双通道结果怎么合并、音频分帧的粒度由什么决定、连接状态机怎么兜底。增量结果为什么不丢字online 与 offline 的双通道合并FunASR 的 2pass 模式在同一条 WebSocket 通道里推送两类结果online partial 边说边给VAD 判定一句话结束后再给一次 offline final。前端要自己决定这两路文本的合并策略官方 demo 的选择很克制——partial 往缓冲区尾部追加final 则整段接管if (asrmodel 2pass-offline || asrmodel offline) { offline_text offline_text handleWithTimestamp(rectxt, timestamp); rec_text offline_text; // 整段接管 } else { rec_text rec_text rectxt; // 尾部追加 }为什么 final 是整段接管而不是追加后去重因为 offline 模型对整句重新解码它给出的文本与 partial 序列并不保证字符级一致做追加再回退 diff 既脆弱又昂贵直接以 final 为准UI 上的闪一下再变准反而正是用户期望的修正反馈。handleWithTimestamp里中文按字符数推进时间戳下标、英文按单词推进这个差异来自 FunASR 时间戳的粒度约定写死成一套索引逻辑会把英文字幕切乱。partial 追加的边界条件追加只在 mode 不是 offline 分支时发生。如果你的业务允许 final 迟到超过几秒需要在rec_text上记录 last_final_time超时未修正的 partial 应打上待定视觉状态否则用户会以为识别已完成。分帧粒度怎么定帧长、采样率与服务端 chunk 的三方匹配main.js 里录音回调recProcess每次从 AudioWorklet 拿到 48k 采样帧先降到 16k 再拼进sampleBuf随后按固定帧长切片发送sampleBuf Int16Array.from([...sampleBuf, ...data_16k]); var chunk_size 960; // for asr chunk_size [5,10,5] while (sampleBuf.length chunk_size) { sendBuf sampleBuf.slice(0, chunk_size); sampleBuf sampleBuf.slice(chunk_size, sampleBuf.length); wsconnecter.wsSend(sendBuf); }960 不是随手写的16k 采样率下 960 帧正好是 60ms 的滑窗粒度与服务端流式模型的 chunk_size[5,10,5]单位 10ms匹配——首片 50ms 触发首次推理每 100ms 增量一次尾片 50ms 与下轮首片拼接形成上下文滑窗。前端负责保证喂帧节奏服务端负责推理节奏两边解耦后才能做到网络抖动不拖垮识别延迟。帧长与采样率的换算关系960 16000 × 0.06是采样率直接参与的计算结果。若未来支持 8k 电话音频而沿用 960时间窗会翻倍成 120ms首字延迟直接恶化一倍。正确做法是把目标窗长作为配置源按采样率反推帧长而不是把帧长当常量抄。断线与服务端断开时前端状态机怎么不炸wsconnecter.js 把裸 WebSocket 包成三态回调0 已连接 / 1 已关闭 / 2 出错所有发包走统一的wsSendthis.wsSend function (oneData) { if (speechSokt undefined) return; if (speechSokt.readyState 1) { // OPEN speechSokt.send(oneData); } };设计意图很直白断线期间的音频帧丢弃而不是排队重发——ASR 是时效性任务积压 10 秒旧音频再灌进去只会污染上下文。onOpen触发时先推一条 JSON 配置chunk_size、chunk_interval、itn、mode、hotwords再放开录音按钮onError时回退到可重连态并提示检查地址。文件模式下必须收到is_final true才wsStop()麦克风模式则用 3 秒延迟等尾部 offline 结果收尾——这两条退出路径是结果没拿全连接就断了这类投诉的根源所在。退出路径为什么分两条在线录音允许发完就等几秒因为用户还能再开口文件转写是单向流is_final是全句完成的唯一契约提前断开等于主动丢弃最后一段 final。如果你的产品要做断线自动重连重连后不要沿用旧rec_text直接续写——VAD 分句边界已经错位正确做法是清空缓冲区重新开一轮会话。最小验证路径与一个更硬的延伸问题不需要搭任何构建工具链本地起一个 FunASR WebSocket 服务端默认 10095 端口浏览器直接打开static/online/index.html这份 demo 即可。它是纯原生 JS 写的web-pages/src下的 Vue 工程属于产品官网模板两者分工不同——前者是协议层参考实现后者是展示层别在官网模板里找流式逻辑。协议字段细节可查 WebSocket 协议文档 与 H5 部署说明。如果把传输层从 WebSocket 换成 WebRTC 直连架构要动哪几层信令怎么协商、SRTP 的音频帧怎么映射回 PCM 分帧、2pass 会话的is_final契约跟 RTP 流走还是另开控制通道——这三问不答清楚前端那套状态机基本要重写。【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表