JavaScript实时语音识别在浏览器中直接调用Qwen3-ASR-0.6B模型1. 引言想象一下你正在开发一个在线会议工具或者一个语音笔记应用。传统的做法是用户对着麦克风说话音频数据被上传到云端服务器经过复杂的语音识别模型处理再把文字结果返回给浏览器。这个过程不仅依赖网络有延迟还涉及用户隐私数据在互联网上的传输。有没有一种可能让这一切都在用户的浏览器里完成让语音识别像本地软件一样快速响应同时数据完全不出用户设备彻底保护隐私这听起来像是未来的技术但其实我们现在就能做到。本文将带你探索一个激动人心的技术组合将轻量级的Qwen3-ASR-0.6B语音识别模型通过WebAssembly或ONNX Runtime Web等技术直接部署到浏览器中运行。我们将利用JavaScript和WebRTC来捕获麦克风音频并在前端直接完成语音到文字的转换。整个过程零延迟、无需网络、绝对隐私。无论你是前端开发者还是对AI应用落地感兴趣的技术人这篇文章都将为你打开一扇新的大门。2. 为什么要在浏览器里做语音识别在深入技术细节之前我们先聊聊“为什么”。把AI模型塞进浏览器听起来很酷但真的有必要吗答案是肯定的而且好处比你想象的要多。首先是速度或者说是“即时性”。云端识别的延迟是无法避免的从音频上传、服务器排队处理、到结果返回即使网络再好也至少有几百毫秒到几秒的延迟。对于实时字幕、语音指令这类应用这种延迟是致命的。而本地识别音频采集完立刻就能开始推理几乎是“所说即所得”。其次是隐私。这是当前用户越来越关心的问题。语音数据包含了大量个人信息。将原始音频数据发送到第三方服务器始终存在数据泄露或被滥用的风险。本地识别意味着用户的语音数据从未离开他的电脑或手机从根源上杜绝了隐私泄露的可能。再者是成本和可靠性。云端服务通常按调用次数或时长收费用户量一大成本就上去了。而且一旦服务器宕机或者网络波动你的整个语音功能就瘫痪了。本地化部署则没有这些顾虑一次部署无限使用且完全不受外部服务稳定性影响。当然挑战也是显而易见的。浏览器的计算资源有限模型必须足够小、足够快。这正是Qwen3-ASR-0.6B这类轻量化模型的价值所在。它专为边缘和端侧设备设计在保持不错识别准确率的同时将模型体积和计算需求降到了浏览器可以承受的范围。3. 技术栈全景图要实现这个目标我们需要一套组合拳。别担心我会用最直白的方式解释每个部分的作用它们就像乐高积木组合起来就能搭建出我们想要的功能。核心模型Qwen3-ASR-0.6B这是我们的大脑。它是一个参数规模为6亿的自动语音识别模型由通义千问团队开源。0.6B的规模意味着它比动辄数十亿、数百亿参数的大模型要轻巧得多非常适合在资源受限的环境比如浏览器中运行。它支持中英文等多种语言的语音识别。模型运行时ONNX Runtime Web 或 纯WebAssembly这是模型的“翻译官”和“执行引擎”。我们训练好的模型通常是PyTorch或TensorFlow格式不能直接在浏览器里跑。我们需要把它转换成一种通用的中间格式——ONNX。ONNX Runtime Web就是一个专门为浏览器环境优化的库它能加载ONNX格式的模型并高效地执行推理计算。WebAssembly则提供了一种更低层次、接近原生速度的运行环境适合对性能有极致要求的场景。音频采集WebRTC API这是我们的“耳朵”。WebRTC不仅用于视频通话其getUserMediaAPI也是从用户麦克风获取音频流的标准方式。我们可以通过它拿到原始的PCM音频数据这是模型推理所需的输入。前端框架纯JavaScript / 任意现代框架这是我们的“操作台”和“展示窗”。你可以用原生JavaScript也可以使用Vue、React等任何你熟悉的前端框架来构建用户界面处理用户交互并调用上述的各个模块。整个流程可以概括为用户授权麦克风 - WebRTC获取音频流 - 前端代码对音频进行预处理重采样、分帧等 - 将处理后的数据喂给在浏览器中运行的ONNX模型 - 模型输出识别文字 - 前端展示结果。听起来步骤不少但当我们一步步拆解后你会发现每一步都很清晰。4. 第一步准备你的模型模型不能直接拿来用我们需要为它“瘦身”并“换装”让它适应浏览器这个新家。4.1 模型转换从PyTorch到ONNX通常我们从开源社区获得的模型是PyTorch的.pth文件。浏览器的ONNX Runtime不认识这个格式。所以第一步是模型转换。# 示例使用Python和torch.onnx进行模型转换 import torch from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor # 1. 加载原始的Qwen3-ASR模型和处理器 model_id Qwen/Qwen3-ASR-0.6B model AutoModelForSpeechSeq2Seq.from_pretrained(model_id, torch_dtypetorch.float32) processor AutoProcessor.from_pretrained(model_id) # 2. 将模型设置为评估模式并创建一个假的输入样例dummy input model.eval() # 假设我们的输入是批次大小为116000Hz采样率下1秒钟的音频16000个采样点 dummy_input torch.randn(1, 16000) # 3. 执行ONNX导出 # 注意实际ASR模型的输入可能更复杂如特征序列这里仅为示意。 # 你需要根据模型具体的forward方法签名来调整输入和输出名。 torch.onnx.export( model, # 要转换的模型 dummy_input, # 模型输入样例 qwen3_asr_0.6b.onnx, # 输出的ONNX文件名 input_names[input_features], # 输入节点名 output_names[logits], # 输出节点名 dynamic_axes{ # 指定动态维度如批次大小、序列长度可变 input_features: {0: batch_size, 1: sequence_length}, logits: {0: batch_size, 1: sequence_length} }, opset_version14, # ONNX算子集版本 do_constant_foldingTrue # 优化常量 ) print(模型已成功导出为 ONNX 格式。)关键点动态轴设置dynamic_axes非常重要。这告诉ONNX Runtime我们的输入音频长度是可变的模型需要能处理不同时长的语音。算子集确保opset版本与你将要使用的ONNX Runtime版本兼容。验证转换后最好用ONNX RuntimePython版加载一次用同样的假数据推理确保输出与原始PyTorch模型一致。4.2 模型优化与量化原始的FP32模型对于浏览器来说可能还是有点大。我们可以通过量化来进一步压缩它。量化是将模型权重从高精度如32位浮点数转换为低精度如8位整数的过程能显著减少模型体积和提升推理速度通常对精度影响很小。# 示例使用ONNX Runtime工具进行模型量化需要安装 onnxruntime 和 onnx from onnxruntime.quantization import quantize_dynamic, QuantType # 动态量化Post-training quantization quantize_dynamic( qwen3_asr_0.6b.onnx, # 输入FP32模型 qwen3_asr_0.6b_quantized.onnx, # 输出INT8模型 weight_typeQuantType.QUInt8 # 权重量化为8位无符号整数 ) print(模型量化完成。)经过量化的模型体积可能减少到原来的1/4推理速度也能提升不少这对浏览器环境是巨大的福音。4.3 模型部署到前端现在你有了一个.onnx文件。如何让它被网页访问呢最简单的方法是把它放在你的网站静态资源目录下比如/public/models/然后通过JavaScript去加载。对于更大的模型可以考虑使用HTTP范围请求或者像TensorFlow.js的模型分片机制但Qwen3-ASR-0.6B量化后应该不大直接一次性加载即可。5. 第二步构建前端应用有了模型我们开始搭建前端。这里我们用一个简单的HTML页面来演示核心流程。5.1 项目结构与初始化创建一个基本的项目结构your-project/ ├── index.html ├── script.js └── models/ └── qwen3_asr_0.6b_quantized.onnx在index.html中引入ONNX Runtime Web的库!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title浏览器端实时语音识别/title script srchttps://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js/script /head body h1实时语音识别演示/h1 button idstartBtn开始录音/button button idstopBtn disabled停止录音/button p状态span idstatus等待开始/span/p div h3识别结果/h3 p idresult styleborder:1px solid #ccc; padding:10px; min-height:50px;/p /div script srcscript.js/script /body /html5.2 核心JavaScript逻辑 (script.js)这是所有魔法发生的地方。我们一步步来写。// script.js let mediaRecorder; let audioChunks []; let session; // ONNX Runtime会话 const SAMPLE_RATE 16000; // Qwen3-ASR模型通常期望16kHz采样率 // 1. 加载ONNX模型 async function loadModel() { const statusEl document.getElementById(status); statusEl.textContent 正在加载语音识别模型...; try { // 创建ONNX Runtime推理会话 // 对于WebAssembly后端可以指定executionProviders session await ort.InferenceSession.create(./models/qwen3_asr_0.6b_quantized.onnx, { executionProviders: [wasm], // 使用WASM后端兼容性最好 // 也可以尝试 webgl 后端可能在某些设备上更快 }); statusEl.textContent 模型加载成功; console.log(模型加载完毕输入名称, session.inputNames, 输出名称, session.outputNames); } catch (error) { statusEl.textContent 模型加载失败 error.message; console.error(加载模型失败, error); } } // 2. 请求麦克风权限并开始录音 async function startRecording() { const startBtn document.getElementById(startBtn); const stopBtn document.getElementById(stopBtn); const statusEl document.getElementById(status); if (!session) { alert(请等待模型加载完成。); return; } try { // 获取麦克风音频流 const stream await navigator.mediaDevices.getUserMedia({ audio: true }); statusEl.textContent 录音中...; // 创建一个AudioContext来处理原始音频数据 const audioContext new AudioContext({ sampleRate: SAMPLE_RATE }); const source audioContext.createMediaStreamSource(stream); // 创建一个单声道分析节点 const processor audioContext.createScriptProcessor(4096, 1, 1); source.connect(processor); processor.connect(audioContext.destination); // 当有音频数据块可用时 processor.onaudioprocess async (event) { // 获取左声道单声道的PCM数据Float32Array const inputData event.inputBuffer.getChannelData(0); // 这里可以进行实时推理 // 注意为了实时性你可能需要将音频数据缓冲到一定长度如1秒再送入模型 // 本例为了简化我们演示一个完整的“录音-停止-识别”流程 audioChunks.push(new Float32Array(inputData)); // 存储原始数据 }; // 保存引用以便停止 mediaRecorder { stream, audioContext, processor, source }; startBtn.disabled true; stopBtn.disabled false; } catch (err) { statusEl.textContent 无法访问麦克风 err.message; console.error(麦克风错误, err); } } // 3. 停止录音并进行识别 async function stopRecordingAndRecognize() { const startBtn document.getElementById(startBtn); const stopBtn document.getElementById(stopBtn); const statusEl document.getElementById(status); const resultEl document.getElementById(result); if (!mediaRecorder) return; // 断开音频节点停止录音 mediaRecorder.processor.disconnect(); mediaRecorder.source.disconnect(); mediaRecorder.audioContext.close(); mediaRecorder.stream.getTracks().forEach(track track.stop()); statusEl.textContent 处理音频中...; startBtn.disabled false; stopBtn.disabled true; // 4. 音频预处理将收集的Float32Array数据拼接并转换为模型需要的格式 // 计算总长度 let totalLength 0; for (const chunk of audioChunks) { totalLength chunk.length; } // 拼接成一个大的Float32Array const concatenatedAudio new Float32Array(totalLength); let offset 0; for (const chunk of audioChunks) { concatenatedAudio.set(chunk, offset); offset chunk.length; } // 5. 准备模型输入 // 注意实际中Qwen3-ASR模型可能需要对数梅尔频谱图作为输入而非原始波形。 // 这里我们需要一个特征提取器如librosa的MelSpectrogram的JavaScript实现。 // 此处为简化流程假设 extractFeatures 函数能完成此转换。 // 你需要根据模型具体要求实现或引入相应的音频处理库。 const features await extractFeatures(concatenatedAudio, SAMPLE_RATE); // 将特征数据转换为Tensor (ONNX Runtime接受的格式) const tensorData new ort.Tensor(float32, features, [1, features.length, featureDim]); // 假设featureDim是特征维度 try { // 6. 执行模型推理 const feeds { [session.inputNames[0]]: tensorData }; // 根据模型输入名调整 const results await session.run(feeds); const logits results[session.outputNames[0]]; // 根据模型输出名调整 // 7. 后处理将模型输出的logits解码为文本 // 这需要一个解码器如CTC解码或序列解码可能还需要词汇表。 // 假设 decodePredictions 函数能完成此工作。 const recognizedText decodePredictions(logits.data); resultEl.textContent recognizedText; statusEl.textContent 识别完成; } catch (error) { console.error(推理失败, error); statusEl.textContent 识别过程出错; resultEl.textContent 错误 error.message; } finally { // 清理本次录音数据 audioChunks []; mediaRecorder null; } } // 占位函数音频特征提取需要你根据模型要求实现 async function extractFeatures(audioData, sampleRate) { // 这里应实现将原始PCM音频转换为模型输入特征如80维梅尔频谱图的逻辑。 // 可以使用诸如librosa.js如果存在或自己用JavaScript实现FFT和梅尔滤波器组。 console.warn(特征提取函数需要实现); // 返回一个假的特征数组 [序列长度, 特征维度] return new Float32Array(100 * 80).fill(0); // 示例100帧每帧80维 } // 占位函数解码预测结果需要你根据模型输出实现 function decodePredictions(logitsArray) { // 这里应实现从模型输出的logits解码出文本字符串的逻辑。 // 可能涉及CTC解码使用束搜索或自回归解码并需要加载模型的词汇表。 console.warn(解码函数需要实现); return 这是识别出的文本示例; } // 页面加载完成后初始化 window.onload async () { await loadModel(); document.getElementById(startBtn).onclick startRecording; document.getElementById(stopBtn).onclick stopRecordingAndRecognize; };代码要点解析模型加载使用ort.InferenceSession.create加载我们准备好的ONNX模型。wasm后端兼容性最好。音频采集使用AudioContext和ScriptProcessorNode来获取原始的、未经压缩的PCM音频数据。这比MediaRecorder录制压缩格式如WebM后再解码要高效。音频预处理这是连接前端音频和AI模型的关键桥梁。extractFeatures函数需要将时域的波形数据转换成模型认识的特征通常是频域的梅尔频谱图。这部分需要你根据Qwen3-ASR模型的具体要求来实现可能需要引入一个JavaScript的音频处理库或自己编写相关算法。模型推理将预处理后的特征数据包装成ONNX Runtime的Tensor对象然后调用session.run()进行推理。结果解码模型输出的是数字logitsdecodePredictions函数需要将这些数字映射回具体的文字。这需要用到模型的“词汇表”和相应的解码算法如CTC束搜索。6. 进阶优化与挑战上面的代码提供了一个可运行的骨架但要打造一个真正流畅可用的产品我们还需要解决几个关键问题6.1 实现真正的流式识别我们的示例是“录音-停止-识别”的模式这不够实时。真正的流式识别需要音频缓冲与分帧持续将采集到的音频数据放入一个环形缓冲区并以固定长度如300毫秒的窗口配合步长如100毫秒截取音频片段送入模型。这样就能实现低延迟的连续识别。增量解码模型每次推理后解码器不应从头开始而应能结合之前的历史信息进行增量解码保证文本输出的连贯性。6.2 性能优化Web Worker音频处理和模型推理都是计算密集型任务放在主线程会阻塞UI导致页面卡顿。务必使用Web Worker将这些任务移到后台线程。后端选择ONNX Runtime Web支持wasm、webgl、webgpu等后端。wasm兼容性广webgl在支持良好的设备上可能更快新兴的webgpu则潜力巨大。可以尝试不同的后端并进行性能测试。模型剪枝与量化我们已经做了量化还可以探索更激进的模型剪枝在精度和速度间取得最佳平衡。6.3 处理浏览器的多样性不同浏览器、不同设备对WebAssembly、Web Audio API的支持程度和性能表现差异很大。需要有降级方案和充分的错误处理。例如如果WebAssembly加载失败是否可以提示用户升级浏览器如果实时处理性能不足是否可以自动切换到“录音-停止-识别”模式7. 总结将Qwen3-ASR-0.6B这样的语音识别模型直接搬到浏览器里运行不再是遥不可及的设想。通过ONNX Runtime Web和WebRTC我们能够构建出零延迟、高隐私、低成本的前端AI应用。虽然这条路线上还有不少工程细节需要打磨比如高效的音频前端处理、流式解码的实现、以及跨浏览器的性能调优但方向已经非常清晰。对于前端开发者而言这意味着我们的能力边界被极大地扩展了。我们不再仅仅是界面的构建者也能成为智能交互的直接实现者。对于用户而言他们能获得更即时、更安全的使用体验。这或许就是下一代Web应用该有的样子更智能更敏捷也更尊重用户。如果你对这个领域感兴趣不妨就从今天这个简单的Demo开始动手试一试。从加载一个模型到处理一秒钟的音频再到实现真正的流式识别每一步的突破都会带来巨大的成就感。前端AI的浪潮才刚刚开始。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
JavaScript实时语音识别:在浏览器中直接调用Qwen3-ASR-0.6B模型
JavaScript实时语音识别在浏览器中直接调用Qwen3-ASR-0.6B模型1. 引言想象一下你正在开发一个在线会议工具或者一个语音笔记应用。传统的做法是用户对着麦克风说话音频数据被上传到云端服务器经过复杂的语音识别模型处理再把文字结果返回给浏览器。这个过程不仅依赖网络有延迟还涉及用户隐私数据在互联网上的传输。有没有一种可能让这一切都在用户的浏览器里完成让语音识别像本地软件一样快速响应同时数据完全不出用户设备彻底保护隐私这听起来像是未来的技术但其实我们现在就能做到。本文将带你探索一个激动人心的技术组合将轻量级的Qwen3-ASR-0.6B语音识别模型通过WebAssembly或ONNX Runtime Web等技术直接部署到浏览器中运行。我们将利用JavaScript和WebRTC来捕获麦克风音频并在前端直接完成语音到文字的转换。整个过程零延迟、无需网络、绝对隐私。无论你是前端开发者还是对AI应用落地感兴趣的技术人这篇文章都将为你打开一扇新的大门。2. 为什么要在浏览器里做语音识别在深入技术细节之前我们先聊聊“为什么”。把AI模型塞进浏览器听起来很酷但真的有必要吗答案是肯定的而且好处比你想象的要多。首先是速度或者说是“即时性”。云端识别的延迟是无法避免的从音频上传、服务器排队处理、到结果返回即使网络再好也至少有几百毫秒到几秒的延迟。对于实时字幕、语音指令这类应用这种延迟是致命的。而本地识别音频采集完立刻就能开始推理几乎是“所说即所得”。其次是隐私。这是当前用户越来越关心的问题。语音数据包含了大量个人信息。将原始音频数据发送到第三方服务器始终存在数据泄露或被滥用的风险。本地识别意味着用户的语音数据从未离开他的电脑或手机从根源上杜绝了隐私泄露的可能。再者是成本和可靠性。云端服务通常按调用次数或时长收费用户量一大成本就上去了。而且一旦服务器宕机或者网络波动你的整个语音功能就瘫痪了。本地化部署则没有这些顾虑一次部署无限使用且完全不受外部服务稳定性影响。当然挑战也是显而易见的。浏览器的计算资源有限模型必须足够小、足够快。这正是Qwen3-ASR-0.6B这类轻量化模型的价值所在。它专为边缘和端侧设备设计在保持不错识别准确率的同时将模型体积和计算需求降到了浏览器可以承受的范围。3. 技术栈全景图要实现这个目标我们需要一套组合拳。别担心我会用最直白的方式解释每个部分的作用它们就像乐高积木组合起来就能搭建出我们想要的功能。核心模型Qwen3-ASR-0.6B这是我们的大脑。它是一个参数规模为6亿的自动语音识别模型由通义千问团队开源。0.6B的规模意味着它比动辄数十亿、数百亿参数的大模型要轻巧得多非常适合在资源受限的环境比如浏览器中运行。它支持中英文等多种语言的语音识别。模型运行时ONNX Runtime Web 或 纯WebAssembly这是模型的“翻译官”和“执行引擎”。我们训练好的模型通常是PyTorch或TensorFlow格式不能直接在浏览器里跑。我们需要把它转换成一种通用的中间格式——ONNX。ONNX Runtime Web就是一个专门为浏览器环境优化的库它能加载ONNX格式的模型并高效地执行推理计算。WebAssembly则提供了一种更低层次、接近原生速度的运行环境适合对性能有极致要求的场景。音频采集WebRTC API这是我们的“耳朵”。WebRTC不仅用于视频通话其getUserMediaAPI也是从用户麦克风获取音频流的标准方式。我们可以通过它拿到原始的PCM音频数据这是模型推理所需的输入。前端框架纯JavaScript / 任意现代框架这是我们的“操作台”和“展示窗”。你可以用原生JavaScript也可以使用Vue、React等任何你熟悉的前端框架来构建用户界面处理用户交互并调用上述的各个模块。整个流程可以概括为用户授权麦克风 - WebRTC获取音频流 - 前端代码对音频进行预处理重采样、分帧等 - 将处理后的数据喂给在浏览器中运行的ONNX模型 - 模型输出识别文字 - 前端展示结果。听起来步骤不少但当我们一步步拆解后你会发现每一步都很清晰。4. 第一步准备你的模型模型不能直接拿来用我们需要为它“瘦身”并“换装”让它适应浏览器这个新家。4.1 模型转换从PyTorch到ONNX通常我们从开源社区获得的模型是PyTorch的.pth文件。浏览器的ONNX Runtime不认识这个格式。所以第一步是模型转换。# 示例使用Python和torch.onnx进行模型转换 import torch from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor # 1. 加载原始的Qwen3-ASR模型和处理器 model_id Qwen/Qwen3-ASR-0.6B model AutoModelForSpeechSeq2Seq.from_pretrained(model_id, torch_dtypetorch.float32) processor AutoProcessor.from_pretrained(model_id) # 2. 将模型设置为评估模式并创建一个假的输入样例dummy input model.eval() # 假设我们的输入是批次大小为116000Hz采样率下1秒钟的音频16000个采样点 dummy_input torch.randn(1, 16000) # 3. 执行ONNX导出 # 注意实际ASR模型的输入可能更复杂如特征序列这里仅为示意。 # 你需要根据模型具体的forward方法签名来调整输入和输出名。 torch.onnx.export( model, # 要转换的模型 dummy_input, # 模型输入样例 qwen3_asr_0.6b.onnx, # 输出的ONNX文件名 input_names[input_features], # 输入节点名 output_names[logits], # 输出节点名 dynamic_axes{ # 指定动态维度如批次大小、序列长度可变 input_features: {0: batch_size, 1: sequence_length}, logits: {0: batch_size, 1: sequence_length} }, opset_version14, # ONNX算子集版本 do_constant_foldingTrue # 优化常量 ) print(模型已成功导出为 ONNX 格式。)关键点动态轴设置dynamic_axes非常重要。这告诉ONNX Runtime我们的输入音频长度是可变的模型需要能处理不同时长的语音。算子集确保opset版本与你将要使用的ONNX Runtime版本兼容。验证转换后最好用ONNX RuntimePython版加载一次用同样的假数据推理确保输出与原始PyTorch模型一致。4.2 模型优化与量化原始的FP32模型对于浏览器来说可能还是有点大。我们可以通过量化来进一步压缩它。量化是将模型权重从高精度如32位浮点数转换为低精度如8位整数的过程能显著减少模型体积和提升推理速度通常对精度影响很小。# 示例使用ONNX Runtime工具进行模型量化需要安装 onnxruntime 和 onnx from onnxruntime.quantization import quantize_dynamic, QuantType # 动态量化Post-training quantization quantize_dynamic( qwen3_asr_0.6b.onnx, # 输入FP32模型 qwen3_asr_0.6b_quantized.onnx, # 输出INT8模型 weight_typeQuantType.QUInt8 # 权重量化为8位无符号整数 ) print(模型量化完成。)经过量化的模型体积可能减少到原来的1/4推理速度也能提升不少这对浏览器环境是巨大的福音。4.3 模型部署到前端现在你有了一个.onnx文件。如何让它被网页访问呢最简单的方法是把它放在你的网站静态资源目录下比如/public/models/然后通过JavaScript去加载。对于更大的模型可以考虑使用HTTP范围请求或者像TensorFlow.js的模型分片机制但Qwen3-ASR-0.6B量化后应该不大直接一次性加载即可。5. 第二步构建前端应用有了模型我们开始搭建前端。这里我们用一个简单的HTML页面来演示核心流程。5.1 项目结构与初始化创建一个基本的项目结构your-project/ ├── index.html ├── script.js └── models/ └── qwen3_asr_0.6b_quantized.onnx在index.html中引入ONNX Runtime Web的库!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title浏览器端实时语音识别/title script srchttps://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js/script /head body h1实时语音识别演示/h1 button idstartBtn开始录音/button button idstopBtn disabled停止录音/button p状态span idstatus等待开始/span/p div h3识别结果/h3 p idresult styleborder:1px solid #ccc; padding:10px; min-height:50px;/p /div script srcscript.js/script /body /html5.2 核心JavaScript逻辑 (script.js)这是所有魔法发生的地方。我们一步步来写。// script.js let mediaRecorder; let audioChunks []; let session; // ONNX Runtime会话 const SAMPLE_RATE 16000; // Qwen3-ASR模型通常期望16kHz采样率 // 1. 加载ONNX模型 async function loadModel() { const statusEl document.getElementById(status); statusEl.textContent 正在加载语音识别模型...; try { // 创建ONNX Runtime推理会话 // 对于WebAssembly后端可以指定executionProviders session await ort.InferenceSession.create(./models/qwen3_asr_0.6b_quantized.onnx, { executionProviders: [wasm], // 使用WASM后端兼容性最好 // 也可以尝试 webgl 后端可能在某些设备上更快 }); statusEl.textContent 模型加载成功; console.log(模型加载完毕输入名称, session.inputNames, 输出名称, session.outputNames); } catch (error) { statusEl.textContent 模型加载失败 error.message; console.error(加载模型失败, error); } } // 2. 请求麦克风权限并开始录音 async function startRecording() { const startBtn document.getElementById(startBtn); const stopBtn document.getElementById(stopBtn); const statusEl document.getElementById(status); if (!session) { alert(请等待模型加载完成。); return; } try { // 获取麦克风音频流 const stream await navigator.mediaDevices.getUserMedia({ audio: true }); statusEl.textContent 录音中...; // 创建一个AudioContext来处理原始音频数据 const audioContext new AudioContext({ sampleRate: SAMPLE_RATE }); const source audioContext.createMediaStreamSource(stream); // 创建一个单声道分析节点 const processor audioContext.createScriptProcessor(4096, 1, 1); source.connect(processor); processor.connect(audioContext.destination); // 当有音频数据块可用时 processor.onaudioprocess async (event) { // 获取左声道单声道的PCM数据Float32Array const inputData event.inputBuffer.getChannelData(0); // 这里可以进行实时推理 // 注意为了实时性你可能需要将音频数据缓冲到一定长度如1秒再送入模型 // 本例为了简化我们演示一个完整的“录音-停止-识别”流程 audioChunks.push(new Float32Array(inputData)); // 存储原始数据 }; // 保存引用以便停止 mediaRecorder { stream, audioContext, processor, source }; startBtn.disabled true; stopBtn.disabled false; } catch (err) { statusEl.textContent 无法访问麦克风 err.message; console.error(麦克风错误, err); } } // 3. 停止录音并进行识别 async function stopRecordingAndRecognize() { const startBtn document.getElementById(startBtn); const stopBtn document.getElementById(stopBtn); const statusEl document.getElementById(status); const resultEl document.getElementById(result); if (!mediaRecorder) return; // 断开音频节点停止录音 mediaRecorder.processor.disconnect(); mediaRecorder.source.disconnect(); mediaRecorder.audioContext.close(); mediaRecorder.stream.getTracks().forEach(track track.stop()); statusEl.textContent 处理音频中...; startBtn.disabled false; stopBtn.disabled true; // 4. 音频预处理将收集的Float32Array数据拼接并转换为模型需要的格式 // 计算总长度 let totalLength 0; for (const chunk of audioChunks) { totalLength chunk.length; } // 拼接成一个大的Float32Array const concatenatedAudio new Float32Array(totalLength); let offset 0; for (const chunk of audioChunks) { concatenatedAudio.set(chunk, offset); offset chunk.length; } // 5. 准备模型输入 // 注意实际中Qwen3-ASR模型可能需要对数梅尔频谱图作为输入而非原始波形。 // 这里我们需要一个特征提取器如librosa的MelSpectrogram的JavaScript实现。 // 此处为简化流程假设 extractFeatures 函数能完成此转换。 // 你需要根据模型具体要求实现或引入相应的音频处理库。 const features await extractFeatures(concatenatedAudio, SAMPLE_RATE); // 将特征数据转换为Tensor (ONNX Runtime接受的格式) const tensorData new ort.Tensor(float32, features, [1, features.length, featureDim]); // 假设featureDim是特征维度 try { // 6. 执行模型推理 const feeds { [session.inputNames[0]]: tensorData }; // 根据模型输入名调整 const results await session.run(feeds); const logits results[session.outputNames[0]]; // 根据模型输出名调整 // 7. 后处理将模型输出的logits解码为文本 // 这需要一个解码器如CTC解码或序列解码可能还需要词汇表。 // 假设 decodePredictions 函数能完成此工作。 const recognizedText decodePredictions(logits.data); resultEl.textContent recognizedText; statusEl.textContent 识别完成; } catch (error) { console.error(推理失败, error); statusEl.textContent 识别过程出错; resultEl.textContent 错误 error.message; } finally { // 清理本次录音数据 audioChunks []; mediaRecorder null; } } // 占位函数音频特征提取需要你根据模型要求实现 async function extractFeatures(audioData, sampleRate) { // 这里应实现将原始PCM音频转换为模型输入特征如80维梅尔频谱图的逻辑。 // 可以使用诸如librosa.js如果存在或自己用JavaScript实现FFT和梅尔滤波器组。 console.warn(特征提取函数需要实现); // 返回一个假的特征数组 [序列长度, 特征维度] return new Float32Array(100 * 80).fill(0); // 示例100帧每帧80维 } // 占位函数解码预测结果需要你根据模型输出实现 function decodePredictions(logitsArray) { // 这里应实现从模型输出的logits解码出文本字符串的逻辑。 // 可能涉及CTC解码使用束搜索或自回归解码并需要加载模型的词汇表。 console.warn(解码函数需要实现); return 这是识别出的文本示例; } // 页面加载完成后初始化 window.onload async () { await loadModel(); document.getElementById(startBtn).onclick startRecording; document.getElementById(stopBtn).onclick stopRecordingAndRecognize; };代码要点解析模型加载使用ort.InferenceSession.create加载我们准备好的ONNX模型。wasm后端兼容性最好。音频采集使用AudioContext和ScriptProcessorNode来获取原始的、未经压缩的PCM音频数据。这比MediaRecorder录制压缩格式如WebM后再解码要高效。音频预处理这是连接前端音频和AI模型的关键桥梁。extractFeatures函数需要将时域的波形数据转换成模型认识的特征通常是频域的梅尔频谱图。这部分需要你根据Qwen3-ASR模型的具体要求来实现可能需要引入一个JavaScript的音频处理库或自己编写相关算法。模型推理将预处理后的特征数据包装成ONNX Runtime的Tensor对象然后调用session.run()进行推理。结果解码模型输出的是数字logitsdecodePredictions函数需要将这些数字映射回具体的文字。这需要用到模型的“词汇表”和相应的解码算法如CTC束搜索。6. 进阶优化与挑战上面的代码提供了一个可运行的骨架但要打造一个真正流畅可用的产品我们还需要解决几个关键问题6.1 实现真正的流式识别我们的示例是“录音-停止-识别”的模式这不够实时。真正的流式识别需要音频缓冲与分帧持续将采集到的音频数据放入一个环形缓冲区并以固定长度如300毫秒的窗口配合步长如100毫秒截取音频片段送入模型。这样就能实现低延迟的连续识别。增量解码模型每次推理后解码器不应从头开始而应能结合之前的历史信息进行增量解码保证文本输出的连贯性。6.2 性能优化Web Worker音频处理和模型推理都是计算密集型任务放在主线程会阻塞UI导致页面卡顿。务必使用Web Worker将这些任务移到后台线程。后端选择ONNX Runtime Web支持wasm、webgl、webgpu等后端。wasm兼容性广webgl在支持良好的设备上可能更快新兴的webgpu则潜力巨大。可以尝试不同的后端并进行性能测试。模型剪枝与量化我们已经做了量化还可以探索更激进的模型剪枝在精度和速度间取得最佳平衡。6.3 处理浏览器的多样性不同浏览器、不同设备对WebAssembly、Web Audio API的支持程度和性能表现差异很大。需要有降级方案和充分的错误处理。例如如果WebAssembly加载失败是否可以提示用户升级浏览器如果实时处理性能不足是否可以自动切换到“录音-停止-识别”模式7. 总结将Qwen3-ASR-0.6B这样的语音识别模型直接搬到浏览器里运行不再是遥不可及的设想。通过ONNX Runtime Web和WebRTC我们能够构建出零延迟、高隐私、低成本的前端AI应用。虽然这条路线上还有不少工程细节需要打磨比如高效的音频前端处理、流式解码的实现、以及跨浏览器的性能调优但方向已经非常清晰。对于前端开发者而言这意味着我们的能力边界被极大地扩展了。我们不再仅仅是界面的构建者也能成为智能交互的直接实现者。对于用户而言他们能获得更即时、更安全的使用体验。这或许就是下一代Web应用该有的样子更智能更敏捷也更尊重用户。如果你对这个领域感兴趣不妨就从今天这个简单的Demo开始动手试一试。从加载一个模型到处理一秒钟的音频再到实现真正的流式识别每一步的突破都会带来巨大的成就感。前端AI的浪潮才刚刚开始。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。