news 2026/10/21 1:33:03

讯飞实时语音转文字WebAPI在Vue中的实践:从录音到字幕的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
讯飞实时语音转文字WebAPI在Vue中的实践:从录音到字幕的完整链路

简介:面向Vue.js前端开发者的科大讯飞实时语音转文字WebAPI集成资源,解决在项目中快速接入语音识别能力、实现浏览器端实时音频流转写的需求。压缩包内含8个文件,由7个JavaScript脚本和1个HTML演示页面组成,脚本覆盖SDK封装、音频流处理Worker、加密辅助模块等关键部分,页面提供可运行示例,整体精简、目录清晰,便于二次开发。资源包仅16KB,代码量小但结构完整,适合具备Vue基础、想掌握讯飞WebAPI鉴权与MediaDevices录音集成的前端开发者参考,也可作为入门级语音识别项目脚手架。目前已有4430人学习下载,可从中学习到getUserMedia获取麦克风、Web Worker处理音频流、RESTful接口调用及识别结果异步渲染等完整流程,也能为会议记录、在线教育、直播互动等实时转写场景提供可直接扩展的前端方案。

1. 一个浏览器里的实时语音转文字方案:讯飞 WebAPI 与 Vue 的组合

提交了一个「把现场语音实时转成字幕」的需求,第一反应是找 SDK,但正儿八经的 SDK 大多跑在服务端;把科大讯飞实时语音转文字 vuejs 版本的能力接进 Vue 工程,让浏览器边录边出字,才是前端工程里最容易落地的姿势。这个「科大讯飞实时语音转文字 vuejs 版本」项目干的就是这件事:前端打开麦克风,把 PCM 音频通过 WebSocket 推给讯飞实时语音听写 WebAPI,服务端返回的中间结果逐句渲染到页面上。适合拿来抄作业的场景很明确:做会议纪要、直播字幕、语音输入框,或者想在三天内出一个能演示的语音交互原型。它把鉴权、录音、流式解析这三块最脏的活封装成了 Vue 服务和组件,跑通再改,比从零啃文档省事得多。

2. 技术选型与目录设计:接入讯飞实时语音听写的两个关键决策

2.1 为什么选实时听写 WebAPI,而不是录音文件转写

先解决一个问题:讯飞的语音能力有录音文件转写和实时语音听写两种,前者把完整音频上传,等异步任务完成再拉结果,延迟几十秒;后者走 WebSocket 边传边识别,服务器返回的是带中间结果的 JSON 帧,配合前端把 content 字段累加,就是字幕效果的来源。这个项目选的是后者,适合「人还在说话,文字就要跟上」的产品形态。实时听写在构建时的主要成本是鉴权签名和 WebSocket 帧协议,不算轻松,但一旦把签名服务化,后续加功能只是换音频源的问题。

实时听写的调用形态是「一次 WebSocket 会话 + 流式音频帧」。浏览器侧需要把麦克风采集的音频编码成 16kHz 单声道 PCM,按 12800 字节一帧推给服务端;识别过程中,服务端会在每个短句结束后通过 status 字段告诉你这帧是中间结果还是最终结果。这个项目把整条链路拆成了四个模块:鉴权签名工具、WebSocket 传输层、音频采集封装、Vue 组件层。模块只负责一件事,调试时定位问题比较省时间。

2.2 Vue 工程结构与模块划分

我拿到的这份工程里,代码组织方式如下(Vue 2 项目,src 目录下做了分层):

src/ ├── api/ │ └── speech/ │ ├── auth.js # 生成鉴权签名(HMAC-SHA256) │ ├── socket.js # WebSocket 连接、收发帧、状态回调 │ └── index.js # 对外暴露的开始/停止识别接口 ├── components/ │ └── SpeechRecorder.vue # 录音按钮、状态展示、识别文本渲染 ├── utils/ │ └── audio.js # getUserMedia + PCM 编码与分帧 └── views/ └── SpeechDemo.vue # 演示页:组件挂载 + 参数配置

各文件的职责边界很清楚:audio.js 只负责「拿到浏览器音频流并切成 PCM 帧」,不关心数据去了哪;socket.js 只负责「把帧发给讯飞并把返回的 JSON 解析出来」,不关心音频怎么来;SpeechRecorder.vue 把两者粘起来,并管理按钮状态和文本渲染。这样改造有一个直接收益:将来想换识别引擎,只要让 socket.js 保持同一个对外接口,内部实现整体替换,组件层不用动。

实时听写 WebAPI 的关键参数表如下:

参数取值说明
hostiat-api.xfyun.cn实时语音听写服务地址
path/v2/ietWebSocket 路径
appid / apikey / apisecret控制台申请的三元组用于签名认证
audio formatpcm / wav前端一般用 pcm,省去封装开销
sample rate16000 或 800016k 识别效果更好
frame size12800 字节音频帧大小,一帧约 200ms/16k
vad_eos默认约 1800ms静音超过该时长自动结束分句

这些参数背后有一个容易被忽略的点:vad_eos 决定了一句识别多快结束,如果演示时发现「一个字一个字地往外蹦,但整句迟迟不结束」,多半是环境噪音让 VAD 误判了边界,而不是识别接口出了问题。

2.3 状态机设计:录音识别过程中的四个状态

组件内部用四个状态驱动 UI 和 WebSocket 生命周期:idle(待机)、connecting(签名完成、连接建立中)、listening(音频采集与推流中)、done(识别结束)。每次状态切换都要同时处理三件事:按钮文案与禁用态、WebSocket 回调的开关、音频流的启停。最常写错的不是状态本身,而是状态切换顺序——比如先关闭连接再停麦克风,会导致最后半句音频被丢弃。我一般会固定成「麦克风先停,再发结束帧,最后关连接」的顺序,三条指令按这个次序执行,能少踩一半的坑。

3. 核心模块拆解:从录音到字幕的完整链路

3.1 鉴权签名生成:HMAC-SHA256 的 JavaScript 实现

讯飞 WebAPI 的认证方式是先拼出签名原串,再用 APISecret 做 HMAC-SHA256,最后把签名放进 authorization 头里。整个流程不依赖服务端,前端用 crypto-js 就能完成,代码集中在 auth.js 里。签名生成是整条链路里最像黑匣子的部分,网上大部分 10108 报错都出自这里。

import CryptoJS from 'crypto-js' // 生成 RFC1123 格式的 GMT 时间 function getDate () { const date = new Date() return date.toUTCString() } // 按讯飞规范拼接签名原串 function getSignature (host, date, APISecret) { const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iet HTTP/1.1` return CryptoJS.HmacSHA256(signatureOrigin, APISecret).toString(CryptoJS.enc.Base64) } export function genAuthUrl (appid, apiKey, apiSecret) { const host = 'iat-api.xfyun.cn' const date = getDate() const signature = getSignature(host, date, apiSecret) const authorization = btoa(`api_key="${apiKey}", algorithm="hmac-sha256"` + `, headers="host date request-line", signature="${signature}"`) return `wss://${host}/v2/iet?authorization=${encodeURIComponent(authorization)}` + `&date=${encodeURIComponent(date)}&host=${encodeURIComponent(host)}` }

逻辑说明:signatureOrigin 的格式是硬性要求,三行分别对应 host 头、date 头和请求行,顺序不能乱;加密密钥用的是 APISecret,不是 APIKey;最终拼出的 wss 地址要带上三个 query 参数,并且 authorization 整体需要 encodeURIComponent,否则浏览器会截断特殊字符。

参数说明:appid 是讯飞控制台创建应用后生成的唯一标识,apiKey 和 apiSecret 是访问密钥,注意 git 提交时不要把密钥写成字面量,工程里用 .env 文件维护这三个值,启动时通过环境变量注入。一个常见的翻车点是本机时间不对——签名里的 date 用的是当前 UTC 时间,服务端会校验偏差,系统时间快了五分钟,签名一样报 10108。

3.2 WebSocket 连接与消息帧组装

签名 URL 生成后,就到了 socket.js 的核心逻辑:建连、推帧、解析返回。讯飞实时听写协议要求客户端在连接建立后持续发送二进制音频帧,长度按 12800 字节切分,最后一帧发一个空的二进制帧表示结束。服务端返回的是 JSON 消息,其中 code 为 0 时正常,data 里带识别结果和状态码。

export class IatSocket { constructor ({ url, onText, onEnd }) { this.ws = new WebSocket(url) this.onText = onText this.onEnd = onEnd this.ws.onmessage = (event) => { if (typeof event.data !== 'string') return const payload = JSON.parse(event.data) if (payload.code !== 0) { console.error('识别错误:', payload.code, payload.message) return } const result = payload.data?.result if (!result) return if (payload.data.status === 1) { // 短句识别完成,content 是整句文本 this.onText(result.text, true) } else { // status 为 0 是中间结果,用于实时渲染 this.onText(result.text, false) } if (payload.data.status === 2) { this.ws.close() this.onEnd && this.onEnd() } } } sendPcm (chunk) { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(chunk) } } finish () { this.ws.send(new ArrayBuffer(0)) // 空帧通知服务端音频结束 } }

逻辑说明:status 是整条链路里的关键字段——0 表示中间结果(一边说话一边更新的那版文字),1 表示一个短句的最终结果,2 表示整个会话结束。前端要把中间结果和最终结果分开展示:中间结果用灰字显示在光标处,最终结果落定成正式字幕行。如果混淆两者,会出现「字越说越多但整句被覆盖」的错乱渲染。

参数说明:onmessage 里先判断 event.data 是否为字符串,是因为讯飞会混发二进制帧和文本帧,文本才是识别结果;ws.send(chunk) 传的是 ArrayBuffer 或 Blob,浏览器默认按二进发送,不要转成字符串,否则服务端解析音频会失败。

3.3 Vue 组件封装:录音 API 与渲染逻辑的粘合

组件层做的事情是把 getUserMedia 采集到的音频流交给 audio.js 分帧,再把帧喂给 IatSocket,同时把结果渲染到页面。这是整个项目里和 Vue 生命周期关系最紧密的部分。

export default { data () { return { status: 'idle', iatSocket: null, recorder: null, finalText: '', // 已落定的最终结果 interimText: '' // 当前中间结果 } }, methods: { async start () { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) const pcmStream = createPcmStream(stream, { rate: 16000 }) const url = genAuthUrl(APPID, API_KEY, API_SECRET) this.iatSocket = new IatSocket({ url, onText: (text, isFinal) => { if (isFinal) { this.finalText += text + '\n' this.interimText = '' } else { this.interimText = text } }, onEnd: () => { this.status = 'done' } }) pcmStream.on('data', (chunk) => this.iatSocket.sendPcm(chunk)) this.status = 'listening' }, async stop () { this.recorder?.stop() // 先停录音 this.iatSocket.finish() // 再发结束帧 // 最后等 onEnd 回调里关闭连接,不要在这里主动 close } } }

逻辑说明:这里刻意用了「录音先停、结束帧其次、连接最后关」的顺序,是为了让麦克风停止前积压的最后一段音频有机会被推送到服务端,否则会出现「最后几个字凭空消失」的现象。onText 里区分 final 和 interim,finalText 换成带换行的正式文本,interimText 仅做占位。

参数说明:createPcmStream 内部用 AudioWorklet 或 ScriptProcessor 把浏览器默认的 48kHz 音频重采样到 16kHz,这是前端实时语音识别最容易吃性能的地方,AudioWorklet 在移动端兼容更稳;如果工程面向老浏览器,需要降级到 ScriptProcessor,但要在文档里注明它跑在主线程,长录音时会卡顿。

4. 常见踩坑与排查:认证、断连和乱码的三类翻车现场

4.1 10108 鉴权失败

现象:WebSocket 连接在 onopen 之后立刻收到 code 10108 的错误帧,或者直接在浏览器控制台看到鉴权失败的提示。

原因:签名原串格式不符、APISecret 用错、系统时间偏差太大。这个项目里最容易犯的错是把 APISecret 填成了 APIKey,两者都是 32 位字符串,肉眼几乎分不出来。

解决:先检查 .env 里的三个值是否与控制台一致,再核对本地时间;如果时间没问题,把 signatureOrigin 拿出来按「host、date、GET /v2/iet HTTP/1.1」逐行比对,多了空格或换行符都会导致签名不一致。我在排查时会在 auth.js 里临时打印完整签名原串,肉眼核对比看报错码快。

4.2 连接建立后一直收不到识别结果

现象:按钮进入 listening 状态,麦克风权限也给了,但页面上一行字都不出,控制台也没有报错。

原因:音频帧格式不对,或者帧切分长度错了。讯飞要求 pcm 格式、16 位采样深度、单声道,如果浏览器采集的是 Float32 的 AudioBuffer,直接送出去服务端解析不出有效语音。

解决:在 audio.js 里把 Float32 转成 16 位 Int16 数组,转的时候注意字节序;帧大小按 12800 字节切,切多了会超出服务端单帧容量限制,切少了会因为缓冲不足无法触发端点检测。建议在发送前先自测:对着一句固定文本录音,抓抓 WebSocket 发送的帧大小是否稳定在 12800 附近。

4.3 识别文本出现乱码或整段标点缺失

现象:中文内容能出来,但偶发乱码,或者标点符号完全消失,句子变成一个超长字符串。

原因:文本编码或中间结果拼接逻辑问题。服务端返回的 JSON 里如果 ws 字段带标点信息,而前端只拼了 w 字段的文本,标点自然丢;乱码则通常是使用 fetch 拉取签名接口时设置了错误的响应编码。

解决:解析返回时把每个分句的 w 字段按顺序拼接,同时把 ws 包含的标点按位置插入;如果标点精度要求不高,也可以接受无标点结果,在二次开发时用本地标点恢复兜底。我一般在 onText 里打印完整 payload,确认服务端到底有没有返回标点,再决定前端是否要处理。

4.4 录音一段时间后内存占用持续上涨

现象:页面跑几分钟后,标签页内存明显上升,长时间录音体验越来越卡。

原因:AudioWorklet 的缓冲区没有及时释放,或者 Vue 组件里把每一帧音频都推给了响应式数组,导致 Vue 的响应式系统对每个二进制块做了代理开销。

解决:PCM 分帧结果不要放进 data 响应式字段,直接局部变量传递;如果必须记录,用一个非响应式的纯数组,或者在赋值后手动断开引用。这条在真实项目里经常被忽略,建议每个做语音前端的同学都处理一次。

5. 跑起来:工程配置、启动与最小链路验证

5.1 下载后先做三件事

第一,把 .env.example 复制成 .env,填入讯飞开放平台控制台里「实时语音听写」应用的 appid、apikey、apisecret。这三个值不要写死在源码里,这是这个项目唯一的必配项。第二,检查 Node 版本,工程是 Vue 2 + 较老依赖的情况下,Node 16 或 18 最稳,版本太新可能遇到 OpenSSL 兼容告警;如果启动报错,加 NODE_OPTIONS 兜底。第三,确认浏览器环境支持 getUserMedia 和 WebSocket,本地 localhost 下录麦克风不受 HTTPS 限制,但部署到内网 IP 时必须上 HTTPS,否则浏览器会直接拒绝麦克风。

配置完成后,依赖安装和启动命令如下:

npm install npm run serve

启动后浏览器打开本地地址,点页面上的录音按钮,允许麦克风权限,开始说话后 1 到 2 秒内应当看到中间结果逐字出现。整套链路是否通,用三个观察点验证:控制台没有任何 code 非 0 的错误帧;Network 面板里 WebSocket 连接在识别期间保持 OPEN;页面文本先出现灰色中间结果,停顿时变成正式文本行。

5.2 最小链路验证:三句话判断是否跑通

不需要等整个产品做完再验证,拿到工程后先用三连测判断链路的健康程度:

测试动作期望表现如果失败看哪里
说一句「你好」2 秒内出现中间结果看签名、看音频帧是否发出
说完停顿 2 秒该句落定为最终文本看 vad_eos 和结束帧逻辑
点停止按钮所有文本不再变化,按钮回到 idle看 onEnd 回调是否执行

这三项都通过,说明从录音到渲染的最小闭环已经建立,后续无论是调样式还是换识别引擎,都不会再动到核心链路。我在调试时习惯把 Network 面板里的 WebSocket 帧截图保存,一旦后续改动引入回归,对比帧内容能快速定位是推流问题还是解析问题。

5.3 联调时的常见问题检查单

如果最小链路不通,按以下顺序排查:先看控制台有没有 10108,有则回 4.1;再看 WebSocket 是否持续 OPEN,如果反复断开,多半是网络对 wss 支持不稳定,某些内网环境需要把 iat-api.xfyun.cn 加白名单;最后看 Network 面板的帧内容,如果发送帧为空,说明 audio.js 的分帧流没有正确产出数据。这三个环节一字排开,基本能覆盖九成问题。

6. 二次开发思路:三个能直接落地的改造方向

6.1 从演示页改成可复用组件

目前的 SpeechRecorder.vue 和演示页耦合在一起,二次开发的第一步是把识别能力抽成带 v-model 的组件,对外只暴露 start 和 stop,识别结果通过 value/input 事件向外传递。改造后,无论接在表单、聊天框还是字幕条上,都不用再改内部实现。

6.2 迁移到 Vue 3 + TypeScript 的注意点

工程底子是 Vue 2 options API,迁移到 Vue 3 时,把 onText、onEnd 这些回调改成组合式函数暴露的 ref 会让状态管理更干净。签名和 WebSocket 逻辑不依赖 Vue,可以原样搬;crypto-js 换成 Web Crypto API 能减少依赖体积,但 HMAC 的字节序列要保持一致,这块建议迁移后跑一遍签名单测再上线。

6.3 增加后端代理签名

如果担心把 APISecret 放在前端不安全,可以加一个 Node 中间层,前端只上传音频,后端完成签名和 WebSocket 连接。改造点集中在 auth.js 和 socket.js 的调用方式上,把 genAuthUrl 改成请求后端接口,其余逻辑不动。这个方向适合生产环境部署,也是我在真实项目里最终采用的方案——从那以后我每次接语音类功能都会先把「密钥是否经过传输链路」想清楚,再开始写代码,避免上线前才返工。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 1:32:46

VS2010遗留项目实战:配置、编译与调试避坑指南

1. 为什么VS2010至今仍有人在用先把结论摆在前面:VS2010不是一款"过时但还能凑合用"的IDE,它在特定场景下依然是刚需。我接触过不少维护老系统的团队,工控上位机、医疗设备配套软件、部分嵌入式调试工具链,这些项目的代…

作者头像 李华
网站建设 2026/10/21 1:30:45

D7005高效高压降压型DC-DC转换器

D7005:卓越性能,助力电源管理升级在当今电子设备日益复杂和多样化的时代,电源管理芯片的性能和可靠性直接关系到设备的运行效率和稳定性。D7005凭借其卓越的性能,成为市场上备受瞩目的高效高压降压型DC-DC转换器。本文将详细探讨D…

作者头像 李华
网站建设 2026/10/21 1:29:38

用表格公式离线查A股本地行情:WPS STOCK_DATA 宏教程

用表格公式离线查A股本地行情:WPS STOCK_DATA 宏教程 【免费下载链接】free-stockdb 面向 A 股日K、分钟K与ETF分钟数据的本地量化引擎,集成增量同步、本地缓存、复权、批量查询、回测与指标计算。 项目地址: https://gitcode.com/GitHub_Trending/fr/…

作者头像 李华
网站建设 2026/10/21 1:29:23

Spring Boot 2升3太痛苦?用OpenRewrite批量搞定,少走90%弯路!

前言:谁懂啊!几十上百个项目升级,手动改到崩溃… 最近部门要把所有Spring Boot 2项目升级到3,消息一出,团队里哀嚎一片—— 「我们光微服务就有30多个,每个项目里的javax换jakarta、API过时、依赖冲突…手…

作者头像 李华
网站建设 2026/10/21 1:29:05

从图片到向量:用regnety_008_tv.tv2_in1k提取图像Embedding的完整指南

【免费下载链接】regnety_008_tv.tv2_in1k 项目地址: https://ai.gitcode.com/hf_mirrors/timm/regnety_008_tv.tv2_in1k 点击查看 免费下载 如果你正在寻找一个轻量、免费、开箱即用的图像 Embedding 提取方案,那么基于 timm 的 regnety_008_tv.tv2_in…

作者头像 李华