虚拟文件系统

虚拟文件系统 (VFS) 是 ffmpeg.wasm 的关键组件,它使得在浏览器环境中进行文件操作成为可能。由于 Web 浏览器无法直接访问本地文件系统,ffmpeg.wasm 实现了一个虚拟文件系统,允许在内存中读取、写入和管理文件和目录。该系统充当 FFmpeg 基于文件的操作与浏览器沙盒环境之间的中间层。

概述

ffmpeg.wasm 中的虚拟文件系统构建于 Emscripten 的文件系统实现之上。它提供了一个完整的文件系统 API,支持在浏览器内存空间中执行创建、读取、写入和删除文件和目录等操作。这使得 FFmpeg 能够像处理常规文件系统上的文件一样处理媒体文件,而实际上完全在浏览器内运行。

数据流管道

系统遵循“写入 - 执行 - 读取”的管道流程。文件被写入 VFS,由 WebAssembly 核心处理,然后将结果读回 JavaScript。

标题:VFS 写入 - 执行 - 读取管道

VFS 写入 - 执行 - 读取管道示意图

实现细节

VFS 实现为 FFmpeg 类和 FFmpegCoreModule 之间的一层。它在 Web Worker 内部完全运行,以防止阻塞主线程。

标题:代码实体映射

代码实体映射图

主要组件

  1. JavaScript API 层:FFmpeg 类提供高级方法,封装复杂的消息传递逻辑
  2. Web Worker 处理器:worker.ts 文件包含将传入的 FFMessageType 事件转换为对 Emscripten FS 对象直接调用的逻辑
  3. Emscripten FS:由编译后的 WebAssembly 核心提供的底层文件系统实现

文件系统后端

ffmpeg.wasm 通过 FFFSType 枚举支持多种文件系统类型

文件系统类型描述使用场景
MEMFS内存文件系统。默认选项;速度快但易失且占用内存
WORKERFS只读访问浏览器的 File 和 Blob 对象。处理大文件而无需将其复制到 WASM 内存
IDBFS通过 IndexedDB 进行持久化存储。跨页面重新加载缓存文件
NODEFS直接访问 Node.js fs。当前仅浏览器构建版本不支持

WORKERFS 挂载

WORKERFS 的独特之处在于它允许直接将 Blob 或 File 对象挂载到文件系统。这避免了在调用 writeFile 之前需要将文件读取到 Uint8Array 的需求。

// WORKERFS mounting logic in worker.ts
const mount = ({ fsType, options, mountPoint }: FFMessageMountData): OK => {
 const str = fsType as keyof typeof ffmpeg.FS.filesystems;
 const fs = ffmpeg.FS.filesystems[str];
 if (!fs) return false;
 ffmpeg.FS.mount(fs, options, mountPoint);
 return true;
};

API 参考

FFmpeg 类暴露了以下与 VFS 相关的方法:

文件操作

  • writeFile(path, data):将 Uint8Array 或 string 写入 VFS
  • readFile(path, encoding):读取文件。默认返回 Uint8Array,如果指定了编码则返回 string
  • deleteFile(path):从 VFS 中解除链接文件
  • rename(oldPath, newPath):移动或重命名文件/目录

目录操作

  • createDir(path):创建新目录
  • listDir(path):返回 FSNode 对象数组(包含名称和 isDir 标志)
  • deleteDir(path):删除空目录

挂载操作

  • mount(fsType, options, mountPoint):将特定后端(例如 WORKERFS)挂载到路径
  • unmount(mountPoint):卸载已挂载的文件系统

使用模式

标准内存工作流

这是中小型文件最常见的模式。

// 1. Write input to memory
await ffmpeg.writeFile('input.mp4', data);
 
// 2. Execute command (reads from and writes to VFS)
await ffmpeg.exec(['-i', 'input.mp4', 'output.mp4']);
 
// 3. Read result from memory
const output = await ffmpeg.readFile('output.mp4');

高效大文件工作流 (WORKERFS)

为了避免在处理大于 1GB 的文件时出现 RangeError: Array buffer allocation failed 错误,请使用 WORKERFS。

await ffmpeg.mount(FFFSType.WORKERFS, {
 files: [fileHandle], // 浏览器 File 对象
}, '/mnt');

await ffmpeg.exec(['-i', '/mnt/video.mp4', 'out.gif']);

技术实现内部细节

消息序列化

当调用 readFile 时,worker 会检测数据是否为 Uint8Array。如果是,它会将底层的 ArrayBuffer 添加到 postMessage 的 transferables 列表中,以避免内存克隆。

退出与清理

每次 exec 或 ffprobe 调用后,FFmpeg 核心模块都会重置,以确保内部状态(包括文件描述符)被清除,尽管 VFS 文件会保留直到被删除或 worker 终止。