Remotion @remotion/whisper-web 完全指南:在浏览器端用 WASM 运行 Whisper.cpp 实现本地语音转写
@remotion/whisper-web 是 Remotion 生态中一个实验性(Unstable API)的浏览器端语音转写工具包:它借助 WebAssembly 在用户的浏览器内直接运行 Whisper.cpp,无需将音频上传到任何服务器即可完成本地转写,产出的带时间戳文本可直接喂给 Remotion 的字幕/视频合成流程。本篇基于仓库中 packages/whisper-web 的包说明与源码实现,完整讲解其安装方式、跨域隔离(Cross-Origin Isolation)这一硬性前提的 Vite 配置、各 API 函数(canUseWhisperWeb、downloadWhisperModel、resampleTo16Khz、transcribe、toCaptions 等)的参数与返回值,并深入源码剖析 IndexedDB 模型缓存、线程上限与进度模拟等底层机制,帮助你在自己的前端应用中稳妥落地浏览器端语音转写方案。
包定位与版本约束
官方包描述为 “Helpers for using Whisper.cpp in browser using WASM”,即把 Remotion 服务端安装 Whisper.cpp 的能力(@remotion/install-whisper-cpp)搬到了浏览器环境。文档站点中明确标注了它的定位与风险:
Unstable API: This package is experimental and might not become stable. Prefer
@remotion/whisper-webgpu, which uses WebGPU and does not require cross-origin isolation. —— packages/docs/docs/whisper-web/index.mdx
也就是说:
- 该包是实验性质,API 可能随版本调整;
- 官方更推荐基于 WebGPU 的
@remotion/whisper-webgpu,后者不依赖跨域隔离; - 选择 WASM 版的前提,是你能够控制页面响应头并满足
SharedArrayBuffer的安全要求(下一节详述)。
从 packages/whisper-web/package.json 可以看到:包依赖 @remotion/captions(用于 toCaptions 的字幕结构转换),当前版本为 4.0.521,入口导出分为 ESM(dist/esm/index.mjs)与 CJS(dist/index.js)两套。
安装
npm install @remotion/whisper-web --save-exact
packages/whisper-web/README.md 强调了一条 Remotion 生态的通用纪律:所有 remotion 与 @remotion/* 包的版本必须对齐。安装时去掉版本号前面的 ^,使用 --save-exact 锁定精确版本,避免混版引发运行时问题。
ESM-only 的运行约束
从 src/index.ts 的源码结构看,CJS 入口下的 transcribe、downloadWhisperModel 等全部导出都是空实现,调用即抛出:
Loading this module from CommonJS is not supported. Load the ESM version of @remotion/whisper-web.
因此该包只能通过 ESM 方式加载(import),这与它依赖 WASM 模块、Web Worker 等浏览器特性的事实是吻合的。
必需配置:跨域隔离与 Vite 设置
@remotion/whisper-web 的 WASM 后端需要 SharedArrayBuffer 支持(用于多线程)。现代浏览器将其作为安全要求:页面必须以“跨域隔离”(cross-origin isolated)方式提供,否则 window.crossOriginIsolated 为 false,整个包不可用。
源码中对这一硬性依赖有非常直白的说明,见 src/can-use-whisper-web.ts 第 34-41 行:
if (!window.crossOriginIsolated) {
return {
supported: false,
reason: WhisperWebUnsupportedReason.NotCrossOriginIsolated,
detailedReason:
'The document is not cross-origin isolated (window.crossOriginIsolated = false). ' +
'This prevents the usage of SharedArrayBuffer, which is required by `@remotion/whisper-web`. ...',
};
}
对应的 HTTP 响应头有两个,服务器(或开发服务器)必须下发:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp(或credentialless)
Vite 配置
以 Vite 为例,文档给出了两处关键改动(index.mdx):
- 将
@remotion/whisper-web加入optimizeDeps.exclude,关闭 Vite 的依赖预构建,规避其对该包已知的优化器问题; - 在
server.headers中补齐SharedArrayBuffer所需的安全头。
import {defineConfig} from 'vite';
export default defineConfig({
optimizeDeps: {
// turn off dependency optimization
exclude: ['@remotion/whisper-web'],
},
// required by SharedArrayBuffer
server: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
// ...
});
注意:开发阶段配置 server.headers 即可,生产环境同样要在正式部署的服务器/CDN 上配置这两个响应头,否则线上用户的环境检查会直接失败。
端到端使用示例:从音频文件到带时间戳的转写结果
文档给出的标准调用链是四步:能力检测 → 下载模型 → 重采样 → 转写(完整示例见 index.mdx):
import {transcribe, canUseWhisperWeb, resampleTo16Khz, downloadWhisperModel} from '@remotion/whisper-web';
const file = new File([], 'audio.wav');
const modelToUse = 'tiny.en';
const {supported, detailedReason} = await canUseWhisperWeb(modelToUse);
if (!supported) {
throw new Error(`Whisper Web is not supported in this environment: ${detailedReason}`);
}
console.log('Downloading model...');
await downloadWhisperModel({
model: modelToUse,
onProgress: ({progress}) => console.log(`Downloading model (${Math.round(progress * 100)}%)...`),
});
console.log('Resampling audio...');
const channelWaveform = await resampleTo16Khz({
file,
onProgress: (p) => console.log(`Resampling audio (${Math.round(p * 100)}%)...`),
});
console.log('Transcribing...');
const {transcription} = await transcribe({
channelWaveform,
model: modelToUse,
onProgress: (p) => console.log(`Transcribing (${Math.round(p * 100)}%)...`),
});
console.log(transcription.map((t) => t.text).join(' '));
transcribe 返回的 transcription 是 TranscriptionItemWithTimestamp[],每项包含 text 与时间戳信息(结构定义见 src/result.ts)。后续可将其通过 toCaptions 转成 Remotion 的字幕结构,驱动 <Captions> 等组件渲染视频字幕。
支持的模型与语言:从源码确认完整取值
模型列表、语言列表与模型体积都集中在 src/constants.ts 中定义,可作为参数取值的权威依据:
export const MODELS = [
'tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en',
] as const;
export type WhisperWebModel = (typeof MODELS)[number];
即 WhisperWebModel 只有 6 个取值:tiny、tiny.en、base、base.en、small、small.en(.en 变体为英语专用模型,精度/体积比更均衡)。
各模型体积(字节)来自同一文件的 SIZES 常量,canUseWhisperWeb 会用它们做存储空间预检:
| 模型 | 体积(字节) | 约等于 |
|---|---|---|
tiny |
77,691,713 | ~77.7 MB |
tiny.en |
77,704,715 | ~77.7 MB |
base |
147,951,465 | ~148 MB |
base.en |
147,964,211 | ~148 MB |
small |
487,601,967 | ~487.6 MB |
small.en |
487,614,201 | ~487.6 MB |
语言参数(WhisperWebLanguage)在 constants.ts 中声明了 120 种以上语言,包括 'auto'(自动检测)、ISO 风格短码(en、zh、de、es、ru、ko、fr、ja、pt 等)以及 english、chinese、cantonese 等英文别名。transcribe 的 language 参数缺省值即 'auto'(见 src/transcribe.ts 第 64 行)。
canUseWhisperWeb():环境能力检测的实现细节
canUseWhisperWeb(model) 是整条链路的守门员,返回 Promise<CanUseWhisperWebResult>:
interface CanUseWhisperWebResult {
supported: boolean;
reason?: WhisperWebUnsupportedReason;
detailedReason?: string;
}
参数 model 用于按该模型的体积预检存储配额。阅读 src/can-use-whisper-web.ts 可以看到它的检查顺序与全部失败原因枚举 WhisperWebUnsupportedReason:
window不存在 →window-undefined(只能在浏览器环境使用);- 未跨域隔离 →
not-cross-origin-isolated(响应头缺失); - IndexedDB 不可用 →
indexed-db-unavailable(模型缓存依赖 IndexedDB); navigator.storage.estimate()不可用 →navigator-storage-unavailable;- 配额/用量无法确定 →
quota-undefined/usage-undefined; - 剩余空间不足 →
not-enough-space,detailedReason会给出精确数字,例如Required: 77704715 bytes, Available: ... bytes; - 估算存储时抛错 →
error-estimating-storage,携带原始错误信息。
全部通过才返回 {supported: true}。
文档还附了一个 React 组件化的检查示例(can-use-whisper-web.mdx),在 useEffect 中调用并把结果映射到 UI 文案,适合作为页面“是否支持浏览器端转写”的入口判断:
import {canUseWhisperWeb, type WhisperWebModel} from '@remotion/whisper-web';
const modelToUse: WhisperWebModel = 'tiny.en';
const result = await canUseWhisperWeb(modelToUse);
// result.supported === false 时展示 result.detailedReason ?? result.reason
文档同时提醒:该函数只检查 API 可用性,WASM、IndexedDB、存储估算的支持在不同浏览器间仍有差异,务必在目标浏览器上实测。
downloadWhisperModel():远程拉取 + IndexedDB 缓存
downloadWhisperModel({model, onProgress}) 负责把模型文件拉取并持久化到浏览器本地。源码 src/download-whisper-model.ts 展示了完整流程,值得逐段拆解:
- 参数校验:
model必须在MODELS内,否则抛出Invalid model name: ... Supported models: tiny, tiny.en, ...; - 环境复检:内部先调用一次
canUseWhisperWeb(model),不支持时直接 reject,detailedReason会拼进错误信息; - 缓存命中短路:以模型 URL 为 key 查 IndexedDB(
getObject),命中则立即回调onProgress({progress: 1})并返回{alreadyDownloaded: true}——这是幂等设计,重复调用不会重复下载; - 远程下载:通过
fetchRemote(src/download-model.ts)按字节流下载,onProgress回调携带{downloadedBytes, totalBytes, progress}三个字段,expectedLength取自SIZES常量; - 入库:
putObject写入 IndexedDB。
await downloadWhisperModel({
model: modelToUse,
onProgress: ({progress, downloadedBytes, totalBytes}) => {
console.log(`(${Math.round(progress * 100)}%) ${downloadedBytes}/${totalBytes}`);
},
});
// => { alreadyDownloaded: false }
从源码结构看,缓存层建立在 src/db/ 目录下的一组 IndexedDB 工具之上:open-db.ts(数据库名 whisper-web,对象仓库 models,版本 1,见 src/constants.ts 的 DB_NAME/DB_VERSION/DB_OBJECT_STORE_NAME)、get-object-from-db.ts、put-object.ts、delete-object.ts。这意味着模型只下载一次,后续访问走本地缓存;对应地,包也暴露了 deleteModel 与 getLoadedModels 供你清理或盘点已下载模型(导出见 src/index.ts 的类型清单)。
transcribe():核心转写 API 与参数详解
transcribe 是整个包的落点。其完整参数类型(src/transcribe.ts 第 39-49 行):
export type TranscribeParams = {
channelWaveform: Float32Array; // 必填:16kHz 单声道波形
model: WhisperWebModel; // 必填:6 种模型之一
language?: WhisperWebLanguage; // 缺省 'auto'
onProgress?: (p: number) => void; // 0~1 的进度回调
onTranscriptionChunk?: (
transcription: TranscriptionItemWithTimestamp[],
) => void; // 每产出一段文本的增量回调
threads?: number; // 缺省 4,上限 16
logLevel?: LogLevel; // 缺省 'info'
};
结合源码可以确认几个重要的实现细节:
- 输入前置校验:
channelWaveform为空直接抛错No audio data provided or audio data is empty.; - 线程数限制:
DEFAULT_THREADS = 4、MAX_THREADS_ALLOWED = 16。传入超过 16 的线程数会被 reject(Thread limit exceeded: max 16 allowed.),并伴随logLevel控制的警告日志。线程数决定了 WASM 侧的并行 worker 数量,可在速度与浏览器线程资源间权衡; - 时长估算:
audioDurationInSeconds = channelWaveform.length / EXPECTED_SAMPLE_RATE(16kHz,与resampleTo16Khz的输出对齐); - 模型必须已下载:
transcribe会按getModelUrl(model)从 IndexedDB 取模型对象,取不到时抛出Model ${model} is not loaded. Call downloadWhisperModel() first.——再次印证“先downloadWhisperModel,后transcribe”的顺序不可颠倒; - 单任务互斥:从
printHandler的onBusy分支可以看到,若已有转写任务在跑,会 rejectAnother transcription is already in progress,即同一页面同时只能跑一个转写; - 底层调用:校验通过后,把模型以
${model}.bin写入 WASM 虚拟文件系统(FS_createDataFile),然后调用 Emscripten 导出的Mod.full_default(fileName, channelWaveform, model, language, threads, false)启动 C++ 侧的 Whisper.cpp 推理(源码emscripten.cpp即其 C++ 入口,packages/whisper-web/emscripten.cpp)。
进度回调的“模拟”机制
由于 C++ 侧推理过程不逐帧回报进度,源码采用 simulateProgress(src/simulate-progress.ts):按音频时长预估一个平滑进度基线,当 WASM 侧的 print 通道上报 0/100/中间步进时,基线再向真实值校准(startProgress / progressStepReceived / onProgressDone)。因此在长音频上你会看到持续平滑推进的百分比,而不是长时间卡在 0%——这是产品化体验上的用心之处。
增量输出
onTranscriptionChunk 在每段转写文本落定时被触发,适合做“边转写边显示”的流式 UI,无需等整段音频处理完毕。
resampleTo16Khz() 与 toCaptions():前后两个“胶水”函数
resampleTo16Khz({file, onProgress}):Whisper 模型只吃 16kHz 音频。该函数在浏览器里解码(Web Audio)并线性插值重采样,产出Float32Array单声道波形,正是transcribe要求的channelWaveform类型。onProgress报告 0~1 的解码/重采样进度。toCaptions():包依赖@remotion/captions,此函数把转写结果(TranscriptionJson)转换为 Remotion 的Caption结构,可直接用于 Remotion 字幕渲染流程,打通“音频 → 字幕 → 视频”的最后一公里。
这两个函数连同 getAvailableModels、getLoadedModels、deleteModel 一起,构成了包的全部公开 API(类型导出清单见 src/index.ts 第 79-93 行)。
选型与注意事项
- 实验性 API:官方文档首页即建议优先评估
@remotion/whisper-webgpu,它在能力检测、模型缓存、转写等方面提供对齐的 API(如canUseWhisperWebgpu、isWhisperModelCached等,见 packages/docs/docs/whisper-webgpu 系列文档),且不要求跨域隔离。如果你的部署环境无法控制响应头,WebGPU 版是更平滑的替代; - 存储是硬约束:
small系列模型近 500MB,且缓存常驻 IndexedDB。建议默认使用tiny.en/base.en起步,并在 UI 中提示canUseWhisperWeb的not-enough-space场景; - 部署清单:① 两个 COOP/COEP 响应头(开发服务器与生产 CDN 都要);② Vite 用户将包加入
optimizeDeps.exclude;③ 所有@remotion/*包版本严格对齐;④ 仅用 ESM 方式导入。
综上,@remotion/whisper-web 用“跨域隔离 + WASM + IndexedDB 缓存”的组合,把 Whisper.cpp 的本地转写能力完整搬进了浏览器,配合 Remotion 的字幕与渲染链路,可以为“在用户设备端完成语音识别并生成带字幕视频”这类场景提供一个不上传音频的技术选项。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00