网站接入服务提供商,电商怎么做的,软件设计师中级资料,学校建设网站的目的和意义Qwen3-ASR与Node.js集成#xff1a;构建实时语音转写服务 想象一下#xff0c;你正在开发一个在线会议应用#xff0c;或者一个智能客服系统。用户对着麦克风说话#xff0c;屏幕上几乎同步地出现他们说的文字。这种实时语音转写的体验#xff0c;不仅能让沟通更高效 const axios require(axios); require(dotenv).config(); // 从环境变量读取配置 const SERVER_PORT process.env.SERVER_PORT || 8080; const DASHSCOPE_API_KEY process.env.DASHSCOPE_API_KEY; const QWEN_ASR_MODEL process.env.QWEN_ASR_MODEL; const DASHSCOPE_WS_ENDPOINT process.env.DASHSCOPE_WS_ENDPOINT; // 存储活跃的客户端连接和对应的ASR会话 const clients new Map(); // key: clientWs, value: { asrClient, sessionId } // 创建WebSocket服务器 const wss new WebSocket.Server({ port: SERVER_PORT }); console.log(实时语音转写服务启动在 ws://localhost:${SERVER_PORT}); wss.on(connection, (clientWs, request) { console.log(新的客户端连接); const clientId Date.now().toString(); // 简单生成一个客户端ID // 为这个客户端创建到ASR服务的连接 const asrWsUrl ${DASHSCOPE_WS_ENDPOINT}?model${QWEN_ASR_MODEL}; const asrWs new WebSocket(asrWsUrl, { headers: { Authorization: Bearer ${DASHSCOPE_API_KEY}, OpenAI-Beta: realtimev1 } }); // 存储关联关系 clients.set(clientWs, { asrClient: asrWs, clientId }); // ... 后续代码处理消息和事件 });这段代码初始化了服务并为每一个新连接的客户端都创建了一个对应的、连接到真实Qwen3-ASR服务的WebSocket连接。我们用clients这个Map来管理这种一对一的映射关系。4.2 处理ASR服务连接与音频转发接下来我们需要处理ASR服务连接成功后的初始化以及最重要的音频数据转发。在上面的connection事件回调里我们继续补充对asrWs的事件监听和clientWs的消息处理// 监听ASR服务WebSocket的事件 asrWs.on(open, () { console.log([${clientId}] 已连接到ASR服务); // 发送会话更新事件配置识别参数 const sessionUpdateEvent { event_id: session_update_${clientId}, type: session.update, session: { modalities: [text], // 我们只关心文本输出 input_audio_format: pcm, // 假设前端发送的是PCM音频 sample_rate: 16000, // 16kHz采样率这是常见配置 input_audio_transcription: { language: zh // 指定中文识别可选。不指定则自动检测 }, turn_detection: { // 开启服务端VAD语音活动检测自动断句 type: server_vad, threshold: 0.0, silence_duration_ms: 400 // 静音400毫秒后认为一句话结束 } } }; asrWs.send(JSON.stringify(sessionUpdateEvent)); }); asrWs.on(message, (data) { // 收到来自ASR服务的消息 try { const event JSON.parse(data); // console.log([${clientId}] 收到ASR事件:, event.type); if (event.type session.finished) { // 最终转写结果 const finalTranscript event.transcript; console.log([${clientId}] 最终转写结果:, finalTranscript); // 可以发送给客户端或存入数据库等 if (clientWs.readyState WebSocket.OPEN) { clientWs.send(JSON.stringify({ type: final_transcript, data: finalTranscript })); } } else if (event.type transcript event.transcript) { // 实时增量转写结果流式输出 // console.log([${clientId}] 实时转写:, event.transcript); if (clientWs.readyState WebSocket.OPEN) { clientWs.send(JSON.stringify({ type: partial_transcript, data: event.transcript })); } } } catch (error) { console.error([${clientId}] 解析ASR消息失败:, error); } }); asrWs.on(error, (error) { console.error([${clientId}] ASR连接错误:, error); }); asrWs.on(close, () { console.log([${clientId}] ASR连接关闭); // 清理资源 cleanupClient(clientWs); }); // 监听客户端发来的消息 clientWs.on(message, (message) { const clientSession clients.get(clientWs); if (!clientSession) return; try { const msg JSON.parse(message); switch (msg.type) { case audio_data: // 前端发送来的音频数据块Base64编码的PCM数据 if (clientSession.asrClient.readyState WebSocket.OPEN) { const audioEvent { event_id: audio_${Date.now()}, type: input_audio_buffer.append, audio: msg.data // 这里应该是Base64字符串 }; clientSession.asrClient.send(JSON.stringify(audioEvent)); } break; case audio_end: // 前端通知音频发送完毕 if (clientSession.asrClient.readyState WebSocket.OPEN) { const finishEvent { event_id: finish_${clientId}, type: session.finish }; clientSession.asrClient.send(JSON.stringify(finishEvent)); } break; default: console.log([${clientId}] 未知的客户端消息类型:, msg.type); } } catch (error) { console.error([${clientId}] 处理客户端消息失败:, error); } }); // 客户端断开连接 clientWs.on(close, () { console.log([${clientId}] 客户端断开连接); cleanupClient(clientWs); }); clientWs.on(error, (error) { console.error([${clientId}] 客户端连接错误:, error); cleanupClient(clientWs); });这段代码是核心中的核心。它做了以下几件关键事配置ASR会话在asrWs.on(open)中我们发送session.update事件告诉ASR服务我们需要的音频格式PCM, 16kHz、语言并开启了服务端的VAD功能让它能自动检测一句话的开始和结束。转发音频数据在clientWs.on(message)中我们监听前端发来的audio_data消息将其重新包装成ASR服务能识别的input_audio_buffer.append事件然后转发过去。返回识别结果在asrWs.on(message)中我们监听ASR服务返回的事件。如果是transcript类型就是实时的增量识别结果我们立刻通过clientWs发回给前端。如果是session.finished就是最终完整的转写结果。处理会话结束无论是前端发来audio_end还是连接断开我们都通过cleanupClient函数来优雅地关闭ASR连接并清理资源。4.3 实现资源清理与错误处理我们还需要实现上面用到的cleanupClient函数以及服务器的基本错误处理。// 清理客户端资源的函数 function cleanupClient(clientWs) { const session clients.get(clientWs); if (session) { if (session.asrClient session.asrClient.readyState WebSocket.OPEN) { session.asrClient.close(); } clients.delete(clientWs); console.log([${session.clientId}] 资源已清理); } } // 服务器全局错误处理 wss.on(error, (error) { console.error(WebSocket服务器错误:, error); }); // 优雅关闭 process.on(SIGINT, () { console.log(正在关闭服务...); wss.close(() { console.log(WebSocket服务器已关闭); process.exit(0); }); });至此我们Node.js端的WebSocket中转服务就完成了。你可以运行node server.js来启动它。5. 前端示例采集音频并连接服务服务端准备好了我们还需要一个简单的前端来测试。这里提供一个使用浏览器Web Audio API和WebSocket的简单示例。创建一个index.html文件!DOCTYPE html html langzh-CN head meta charsetUTF-8 title实时语音转写测试/title style body { font-family: sans-serif; padding: 20px; } button { padding: 10px 20px; margin: 5px; font-size: 16px; } #status { margin: 10px 0; padding: 10px; background: #eee; } #transcript { border: 1px solid #ccc; padding: 15px; min-height: 100px; margin-top: 20px; white-space: pre-wrap; } /style /head body h1Qwen3-ASR 实时转写测试/h1 button idstartBtn开始录音/button button idstopBtn disabled停止录音/button div idstatus状态就绪/div div h3实时转写结果/h3 div idtranscript/div /div script const serverWsUrl ws://localhost:8080; // 你的Node.js服务地址 let mediaRecorder; let audioChunks []; let ws; let isRecording false; const startBtn document.getElementById(startBtn); const stopBtn document.getElementById(stopBtn); const statusDiv document.getElementById(status); const transcriptDiv document.getElementById(transcript); // 初始化WebSocket连接 function initWebSocket() { ws new WebSocket(serverWsUrl); ws.onopen () { statusDiv.textContent 状态已连接到转写服务; console.log(WebSocket连接已打开); }; ws.onmessage (event) { const data JSON.parse(event.data); if (data.type partial_transcript) { // 实时增量结果可以高亮显示最后一句 transcriptDiv.innerHTML span stylecolor:blue${data.data}/span ; } else if (data.type final_transcript) { // 一句话结束的最终结果 transcriptDiv.innerHTML strong${data.data}/strongbr; } }; ws.onerror (error) { statusDiv.textContent 状态连接错误; console.error(WebSocket错误:, error); }; ws.onclose () { statusDiv.textContent 状态连接断开; console.log(WebSocket连接关闭); }; } // 开始录音 startBtn.onclick async () { try { statusDiv.textContent 状态请求麦克风权限...; const stream await navigator.mediaDevices.getUserMedia({ audio: true }); statusDiv.textContent 状态初始化音频处理器...; const audioContext new AudioContext({ sampleRate: 16000 }); const source audioContext.createMediaStreamSource(stream); const processor audioContext.createScriptProcessor(4096, 1, 1); // 连接麦克风 - 处理器 - 目的地静音 source.connect(processor); processor.connect(audioContext.destination); // 初始化WebSocket if (!ws || ws.readyState ! WebSocket.OPEN) { initWebSocket(); } processor.onaudioprocess (e) { if (!ws || ws.readyState ! WebSocket.OPEN) return; // 获取PCM数据Float32 const inputData e.inputBuffer.getChannelData(0); // 转换为16位整数PCM (Int16Array) const pcm16 new Int16Array(inputData.length); for (let i 0; i inputData.length; i) { pcm16[i] Math.max(-32768, Math.min(32767, inputData[i] * 32768)); } // 转换为Base64 const base64String btoa(String.fromCharCode(...new Uint8Array(pcm16.buffer))); // 通过WebSocket发送音频数据块 ws.send(JSON.stringify({ type: audio_data, data: base64String })); }; isRecording true; startBtn.disabled true; stopBtn.disabled false; statusDiv.textContent 状态正在录音和转写...; transcriptDiv.innerHTML ; } catch (err) { statusDiv.textContent 状态错误 - ${err.message}; console.error(启动录音失败:, err); } }; // 停止录音 stopBtn.onclick () { if (ws ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: audio_end })); } if (mediaRecorder isRecording) { mediaRecorder.stop(); } // 关闭所有音频流和处理器 // ... (实际项目中需要更细致的资源释放) isRecording false; startBtn.disabled false; stopBtn.disabled true; statusDiv.textContent 状态已停止; }; // 页面加载时初始化 window.onload () { initWebSocket(); }; /script /body /html这个前端页面做了以下几件事提供了开始/停止录音的按钮。使用getUserMedia获取麦克风权限。使用AudioContext和ScriptProcessorNode实时处理音频流将其转换为16kHz、16位的PCM格式并分块进行Base64编码。通过WebSocket将编码后的音频数据块发送给我们刚搭建的Node.js服务。接收并显示从服务端返回的partial_transcript实时增量文字和final_transcript一句话的最终结果。注意这个前端示例为了简洁省略了详细的错误处理和资源释放代码。在生产环境中你需要更健壮地处理音频流的开启、关闭以及WebSocket的重连逻辑。6. 部署与优化建议当你本地测试通过后就可以考虑部署到生产环境了。这里有一些建议服务器选择选择一台网络状况良好、CPU性能足够的云服务器。Qwen3-ASR的推理负载主要在阿里云侧你的Node.js服务主要是网络中转所以对CPU要求不高但网络延迟和稳定性很重要。环境变量管理在生产环境使用更安全的方式管理DASHSCOPE_API_KEY比如云服务商提供的密钥管理服务如AWS KMS, 阿里云KMS。进程管理使用pm2或systemd来管理Node.js进程确保服务崩溃后能自动重启。npm install -g pm2 pm2 start server.js --name qwen-asr-service安全性为你的WebSocket服务ws://添加WSSwss://支持即WebSocket over TLS确保通信加密。这通常可以通过在Node.js服务前配置Nginx反向代理来实现。考虑添加简单的认证机制比如连接时验证Token防止服务被滥用。可扩展性如果并发用户量很大单个Node.js实例可能成为瓶颈。你可以考虑使用Node.js的集群模式cluster模块利用多核CPU。将客户端-ASR会话的映射关系存储到Redis等外部缓存中实现无状态的服务水平扩展。监控与日志接入监控系统记录服务的连接数、音频处理时长、错误率等指标。将日志输出到文件或日志服务方便排查问题。7. 总结走完这一趟我们从零搭建了一个基于Qwen3-ASR和Node.js的实时语音转写服务。这个方案把开源模型的能力与Node.js的实时特性结合了起来给了我们一个成本可控、自主性高的选择。实际用下来你会发现核心的中转逻辑并不复杂但带来的可能性却很多。你可以把这个服务集成到在线教育平台里实时生成课堂字幕可以放到视频会议工具里自动生成会议纪要甚至可以结合大语言模型做一个能实时听懂并回答问题的语音助手。当然现在这个版本还是一个起点。你可能需要根据实际业务添加更多的功能比如识别结果的后处理、多说话人分离、或者更复杂的会话状态管理。但有了这个基础框架后续的扩展都会容易很多。希望这篇文章能帮你打开思路。语音交互正在变得越来越普遍拥有一个自己能够掌控的实时转写引擎无疑会在很多项目中成为亮点。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。