首页
/ Remotion @remotion/whisper-web 完全指南:在浏览器端用 WASM 运行 Whisper.cpp 实现本地语音转写

Remotion @remotion/whisper-web 完全指南:在浏览器端用 WASM 运行 Whisper.cpp 实现本地语音转写

2026-09-07 16:56:58作者:裴锟轩Denise

@remotion/whisper-web 是 Remotion 生态中一个实验性(Unstable API)的浏览器端语音转写工具包:它借助 WebAssembly 在用户的浏览器内直接运行 Whisper.cpp,无需将音频上传到任何服务器即可完成本地转写,产出的带时间戳文本可直接喂给 Remotion 的字幕/视频合成流程。本篇基于仓库中 packages/whisper-web 的包说明与源码实现,完整讲解其安装方式、跨域隔离(Cross-Origin Isolation)这一硬性前提的 Vite 配置、各 API 函数(canUseWhisperWebdownloadWhisperModelresampleTo16KhztranscribetoCaptions 等)的参数与返回值,并深入源码剖析 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 入口下的 transcribedownloadWhisperModel 等全部导出都是空实现,调用即抛出:

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.crossOriginIsolatedfalse,整个包不可用。

源码中对这一硬性依赖有非常直白的说明,见 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-origin
  • Cross-Origin-Embedder-Policy: require-corp(或 credentialless

Vite 配置

以 Vite 为例,文档给出了两处关键改动(index.mdx):

  1. @remotion/whisper-web 加入 optimizeDeps.exclude,关闭 Vite 的依赖预构建,规避其对该包已知的优化器问题;
  2. 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 返回的 transcriptionTranscriptionItemWithTimestamp[],每项包含 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 个取值:tinytiny.enbasebase.ensmallsmall.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 风格短码(enzhdeesrukofrjapt 等)以及 englishchinesecantonese 等英文别名。transcribelanguage 参数缺省值即 '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

  1. window 不存在window-undefined(只能在浏览器环境使用);
  2. 未跨域隔离not-cross-origin-isolated(响应头缺失);
  3. IndexedDB 不可用indexed-db-unavailable(模型缓存依赖 IndexedDB);
  4. navigator.storage.estimate() 不可用navigator-storage-unavailable
  5. 配额/用量无法确定quota-undefined / usage-undefined
  6. 剩余空间不足not-enough-spacedetailedReason 会给出精确数字,例如 Required: 77704715 bytes, Available: ... bytes
  7. 估算存储时抛错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 展示了完整流程,值得逐段拆解:

  1. 参数校验model 必须在 MODELS 内,否则抛出 Invalid model name: ... Supported models: tiny, tiny.en, ...
  2. 环境复检:内部先调用一次 canUseWhisperWeb(model),不支持时直接 reject,detailedReason 会拼进错误信息;
  3. 缓存命中短路:以模型 URL 为 key 查 IndexedDB(getObject),命中则立即回调 onProgress({progress: 1}) 并返回 {alreadyDownloaded: true}——这是幂等设计,重复调用不会重复下载;
  4. 远程下载:通过 fetchRemotesrc/download-model.ts)按字节流下载,onProgress 回调携带 {downloadedBytes, totalBytes, progress} 三个字段,expectedLength 取自 SIZES 常量;
  5. 入库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.tsDB_NAME/DB_VERSION/DB_OBJECT_STORE_NAME)、get-object-from-db.tsput-object.tsdelete-object.ts。这意味着模型只下载一次,后续访问走本地缓存;对应地,包也暴露了 deleteModelgetLoadedModels 供你清理或盘点已下载模型(导出见 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 = 4MAX_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”的顺序不可颠倒;
  • 单任务互斥:从 printHandleronBusy 分支可以看到,若已有转写任务在跑,会 reject Another 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++ 侧推理过程不逐帧回报进度,源码采用 simulateProgresssrc/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 字幕渲染流程,打通“音频 → 字幕 → 视频”的最后一公里。

这两个函数连同 getAvailableModelsgetLoadedModelsdeleteModel 一起,构成了包的全部公开 API(类型导出清单见 src/index.ts 第 79-93 行)。

选型与注意事项

  • 实验性 API:官方文档首页即建议优先评估 @remotion/whisper-webgpu,它在能力检测、模型缓存、转写等方面提供对齐的 API(如 canUseWhisperWebgpuisWhisperModelCached 等,见 packages/docs/docs/whisper-webgpu 系列文档),且不要求跨域隔离。如果你的部署环境无法控制响应头,WebGPU 版是更平滑的替代;
  • 存储是硬约束small 系列模型近 500MB,且缓存常驻 IndexedDB。建议默认使用 tiny.en/base.en 起步,并在 UI 中提示 canUseWhisperWebnot-enough-space 场景;
  • 部署清单:① 两个 COOP/COEP 响应头(开发服务器与生产 CDN 都要);② Vite 用户将包加入 optimizeDeps.exclude;③ 所有 @remotion/* 包版本严格对齐;④ 仅用 ESM 方式导入。

综上,@remotion/whisper-web 用“跨域隔离 + WASM + IndexedDB 缓存”的组合,把 Whisper.cpp 的本地转写能力完整搬进了浏览器,配合 Remotion 的字幕与渲染链路,可以为“在用户设备端完成语音识别并生成带字幕视频”这类场景提供一个不上传音频的技术选项。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388