通过 Transformers.js 与 ONNX WebAssembly,Whisper 可以直接在浏览器中运行。推理期间音频留在当前标签页,模型则作为应用依赖下载并缓存。这形成了有价值的隐私边界,但不等于零网络流量,也不等于服务端级别的流式识别。
实用封装需要缓存一份 pipeline、展示模型下载进度、处理语言选择与长音频分块、释放 Object URL、管理麦克风权限,并准确解释“实时转写”其实是浏览器周期性重新识别累计录音。
区分模型交付与音频处理
模型、tokenizer、配置和 ONNX 运行时必须先到达浏览器。当前设计允许从远程模型仓库加载模型文件并使用浏览器缓存,ONNX WASM 则由站点提供;用户音频通过本地 Object URL 交给页面中的 pipeline。
第一次使用会承担模型下载和初始化成本。后续可能复用缓存,但无痕模式、清理站点数据、缓存淘汰或模型版本变化都可能重新下载。
| 数据 | 典型路径 | 缓存方式 |
|---|---|---|
| Whisper 模型与 tokenizer | 远程模型站点到浏览器 | 浏览器缓存 |
| ONNX WASM 运行时 | 站点到浏览器 | HTTP/浏览器缓存 |
| 上传音频 | File 到本地 Object URL | 推理封装不上传 |
| 转写文本 | pipeline 结果到 React state | 只有产品明确保存时才持久化 |
创建并缓存唯一 ASR Pipeline
pipeline 初始化成本很高,因此应缓存 Promise,而不是每个文件重新创建。初始化失败时重置 Promise,才能真正重试。progress callback 可以把模型下载、模型就绪、推理与完成分开显示。
示例使用单线程 WASM,以获得更可预测的兼容性。多线程通常依赖跨源隔离,必须在目标浏览器实测后再开启。
const transformers = await import('@xenova/transformers/dist/transformers.min.js');
transformers.env.allowLocalModels = false;
transformers.env.useBrowserCache = true;
transformers.env.backends.onnx.wasm.wasmPaths = '/models/transformers/';
transformers.env.backends.onnx.wasm.numThreads = 1;
const transcriber = await transformers.pipeline(
'automatic-speech-recognition',
'Xenova/whisper-tiny',
{ progress_callback: reportModelProgress },
);
const result = await transcriber(URL.createObjectURL(file), {
chunk_length_s: 30,
stride_length_s: 5,
language: selectedLanguage === 'auto' ? undefined : selectedLanguage,
});长录音使用带重叠的分块
Whisper 模型处理的是有限音频窗口。30 秒 chunk 加 5 秒 stride 会让相邻窗口重叠,降低词语刚好落在硬边界时被截断的概率,pipeline 再整合这些上下文。
长文件仍会增加总计算量与内存压力。应在加载模型前校验格式和大小,说明模型加载到 100% 后仍需继续识别,也不要承诺低性能设备可以实时完成。
- 自动语言让模型封装自行判断语言。
- 已知录音语言时明确传入 zh 或 en,可以减少歧义。
- 无论成功或失败,都要在 finally revoke 输入 URL。
- pipeline 可以缓存,但每个文件的 URL 和预览需要释放。
麦克风转写是周期性快照推理
MediaRecorder 可以每秒产生一块编码数据。应用按照固定间隔,把当前累计 chunks 合并成 File,再调用同一套文件转写函数,用最新完整结果替换预览。
这种方案比有状态流式解码器简单,但录音越长,每次快照重复处理的旧音频越多。必须阻止多个快照任务重叠,最终停止前等待正在运行的快照,并始终停止全部 MediaStream track。
const recorder = new MediaRecorder(stream, { mimeType });
const chunks: Blob[] = [];
recorder.ondataavailable = event => {
if (event.data.size > 0) chunks.push(event.data);
};
recorder.start(1000);
const refreshId = window.setInterval(async () => {
const snapshot = new File([new Blob(chunks, { type: mimeType })], 'live.webm');
const result = await transcribeAudioFile(snapshot, { language: 'auto' });
if (result.ok) setTranscript(result.text);
}, REFRESH_INTERVAL_MS);认真管理麦克风权限和生命周期
显示功能可用前,先检查 MediaRecorder、getUserMedia 和浏览器支持的 MIME。可以请求回声消除与降噪,但它们只是浏览器约束,不是质量保证。
停止时请求最后一块数据、清理刷新 interval、停止 recorder、停止所有 track、完成最后一次快照,再让 UI 回到 idle。权限拒绝或异常时也要执行同样清理,然后展示本地化错误。
理解 Tiny 模型的能力边界
Whisper tiny 优先考虑下载体积和浏览器可运行性。噪声、口音、多人对话、技术词汇、音乐或远距离麦克风都会降低准确率,推理速度也取决于 CPU、内存与 WASM 支持。
如果必须保证准确率、说话人区分、时间戳、延迟或集中审计,应使用更大模型或受控服务。界面要允许用户编辑和复制结果,不能把模型输出当作权威文本。
总结
浏览器 Whisper 不只是一次推理调用,更是模型交付和生命周期问题。缓存一份 pipeline,区分模型流量与音频处理,使用重叠分块,串行执行麦克风快照,并清理权限与 Object URL,才能让转写过程既可信又可用。