
简介这份资源围绕 ffmpeg.js 展开面向希望在前端直接完成音视频处理的 Web 开发者与 JavaScript 学习者解决传统转码必须依赖后端服务、部署成本高的问题。借助其封装好的 API只需几行代码即可在浏览器中完成视频转码、格式转换等操作适合在线剪辑、本地预览、教学演示等场景。压缩包共 122 个文件约 3.44MB以 61 个 png 截图、27 个 js 脚本、8 个 md 说明文档为主另含 html 示例页、yml 配置、json 与 ts 文件以及 avi、wav、ogg 等测试音视频素材覆盖源码、文档与演示资源。内容预览中可见转码演示、摄像头采集、图片转视频、concat 解复用等示例页面便于读者对照理解不同输入源的处理方式。目前已有 6396 人学习下载适合想快速上手浏览器端 FFmpeg、研究前端音视频处理方案的开发者参考。1. 浏览器里跑 FFmpeg为什么我最后选了 ffmpeg.js 而不是服务端转码去年做一个在线音频剪辑的小工具需求很朴素用户拖进来一个 m4a裁掉头尾、转成 mp3、再压一下码率全程不想让文件离开浏览器。第一版我老老实实写了后端接口结果上线第二天就翻车——用户传的是几十兆的录音上传排队、磁盘爆、并发一上来 CPU 直接打满运维半夜给我打电话。后来我把整条链路挪到前端用 ffmpeg.js 在浏览器里直接跑 FFmpeg文件一个字节都没出过本机服务器只发静态资源成本瞬间归零。ffmpeg.js 本质是把 FFmpeg 编译成 WebAssembly再配一层 JavaScript 胶水让你在浏览器里调用ffmpeg命令。它解决的就是「不想为一次转码养一台服务器」这件事适合做在线音视频处理、格式转换、截图抽帧、批量压缩这类工具站也适合 Electron 或纯前端项目里需要离线处理媒体的场景。代价是首次要加载十几到二十几兆的 wasm且只能跑单线程或有限多线程重活依旧吃力。这篇就按我实际踩过的路把选型、加载、调用、参数和坑一次讲清。2. ffmpeg.js 的加载与初始化wasm 从哪来、内存怎么给2.1 先搞清楚它和原生 FFmpeg 的差别原生 FFmpeg 是本地进程能开多线程、能调 GPU、能读写任意路径。ffmpeg.js 跑在浏览器的沙箱里没有文件系统没有进程所有输入输出都得走内存。它的工作模型是你把一个Uint8Array塞进去它把结果以Uint8Array吐出来。中间那些-i input.mp4 -c:v libx264 output.mp4的命令行参数基本能照抄但路径是虚拟的通常约定输入叫input、输出叫output。选型上要分清两个东西一个是ffmpeg/ffmpeg新版基于 ESMAPI 是ffmpeg.load()/ffmpeg.exec()另一个是老的ffmpeg.js单文件暴露Module全局对象。热词里常搜的「ffmpeg.js」多半指后者但新项目我建议用ffmpeg/ffmpeg因为老版本对多线程和内存增长的处理比较糙长时间跑容易崩。下面示例用新版 API逻辑对老版同样成立。2.2 加载 wasm 与创建实例import { FFmpeg } from ffmpeg/ffmpeg; import { fetchFile, toBlobURL } from ffmpeg/util; const ffmpeg new FFmpeg(); // 加载核心 wasmcoreURL 指向 ffmpeg-core.jswasmURL 指向 ffmpeg-core.wasm async function loadFFmpeg() { const baseURL /ffmpeg; // 静态资源目录需自己托管 await ffmpeg.load({ coreURL: await toBlobURL(${baseURL}/ffmpeg-core.js, text/javascript), wasmURL: await toBlobURL(${baseURL}/ffmpeg-core.wasm, application/wasm), }); console.log(ffmpeg ready); }toBlobURL的作用是把跨域资源转成 blob URL绕开部分浏览器对 wasm 的 MIME 校验。coreURL是胶水 JSwasmURL是真正的二进制两个文件必须版本一致混用会直接报RuntimeError: abort。如果你要开多线程还得额外提供workerURL并且服务器要带Cross-Origin-Opener-Policy: same-origin和Cross-Origin-Embedder-Policy: require-corp两个响应头否则SharedArrayBuffer不可用多线程直接退化甚至报错。2.3 内存与线程参数怎么给wasm 的内存是预分配的默认上限通常够跑几十兆的媒体但处理大文件时会在exec中途抛OOM。常见做法是在加载时通过Module配置调大INITIAL_MEMORY或者干脆在业务层限制单文件大小。多线程版要设-threads N但浏览器里 N 一般给 2 到 4 就够给多了反而因为调度开销变慢。判断是否真的用上了多线程看ffmpeg.on(log)里有没有pthread相关输出没有就是没生效多半是响应头没配。提示wasm 文件建议放 CDN 并开 gzip/brotli二十几兆的 wasm 压完能到七八兆首屏体验差别很大。3. 用 ffmpeg.js 做转码与抽帧命令怎么写、结果怎么取3.1 把文件喂进去、把结果拿出来async function transcode(file) { // 写入虚拟文件系统名字随意后面命令里对应上即可 await ffmpeg.writeFile(input.m4a, await fetchFile(file)); // 监听日志排查参数错误时非常关键 ffmpeg.on(log, ({ message }) console.log([ffmpeg], message)); // 转 mp3128k 码率采样率 44100 await ffmpeg.exec([ -i, input.m4a, -vn, // 丢弃视频流纯音频场景必加 -ar, 44100, // 采样率 -b:a, 128k, // 音频码率 output.mp3, ]); const data await ffmpeg.readFile(output.mp3); return new Blob([data.buffer], { type: audio/mpeg }); }writeFile把浏览器File转成 wasm 能读的字节流exec是同步阻塞式的返回 Promise但内部串行readFile拿回结果。注意readFile返回的是Uint8Array构造 Blob 时要用data.buffer直接传data在某些浏览器会得到空文件这是我早期最常翻的车。-vn在纯音频处理里一定要加否则遇到带封面的 m4a 会尝试解视频流白白耗时甚至报错。3.2 抽帧和截图async function grabFrame(file, time 00:00:01) { await ffmpeg.writeFile(video.mp4, await fetchFile(file)); await ffmpeg.exec([ -ss, time, // 定位到指定时间点放在 -i 前更快 -i, video.mp4, -frames:v, 1, // 只取一帧 -q:v, 2, // 输出质量2 已经很高 frame.jpg, ]); const img await ffmpeg.readFile(frame.jpg); return new Blob([img.buffer], { type: image/jpeg }); }-ss放在-i之前是快速定位放在之后是精确解码抽帧场景用前者足够快。-frames:v 1保证只输出一张不加的话会按帧率吐一堆图内存直接爆。-q:v是 JPEG 质量范围 2 到 31数字越小越清晰、文件越大做缩略图给 5 左右就够。3.3 参数对照与常见组合场景关键参数说明音频转码-vn -ar -b:a丢视频流控采样率和码率视频压缩-c:v libx264 -crf 28 -preset veryfastcrf 越大越糊preset 越快越糊抽帧-ss -frames:v 1 -q:v定位、单帧、质量裁剪时长-ss -t起点加持续时长提取音频-vn -acodec copy不重编码秒出-crf是 x264 的恒定质量参数18 到 28 是常用区间28 以上肉眼可见糊。-preset从ultrafast到veryslow浏览器里我一般只敢用veryfast或ultrafast再慢用户就以为页面卡死了。-acodec copy不重编码速度极快但要求容器支持m4a 提 aac 没问题mp4 提 mp3 就会失败。4. 避坑与排查那些让我加班到凌晨的 ffmpeg.js 问题4.1 报错SharedArrayBuffer is not defined现象是加载多线程版核心时直接抛异常页面白屏。原因是浏览器出于安全策略默认禁用SharedArrayBuffer需要跨域隔离响应头。解决是在服务器给 wasm 和页面都加上Cross-Origin-Opener-Policy: same-origin与Cross-Origin-Embedder-Policy: require-corp或者干脆用单线程版核心牺牲速度换稳定。4.2exec跑完但readFile拿到空文件现象是命令日志显示成功读出来却是 0 字节。原因通常是输出文件名和命令里写的不一致或者构造 Blob 时传了Uint8Array而不是它的buffer。解决是核对writeFile/readFile的文件名并统一用new Blob([data.buffer])。另外exec是串行的前一个没 await 完就发下一个虚拟文件系统会互相覆盖。4.3 大文件跑到一半 OOM现象是处理几十兆以上文件时中途崩溃日志停在某个解码步骤。原因是 wasm 内存有上限且readFile会把整个结果读进内存。解决是限制单文件大小、分片处理或者改用流式方案新版支持ffmpeg.exec配合FS分块读写。我一般在前端就拦掉超过 100MB 的文件提示用户先本地压缩。4.4 中文文件名或路径导致失败现象是带中文名的文件写入后命令找不到。原因是虚拟文件系统对非 ASCII 路径支持不稳。解决是写入前统一重命名为input、output这类纯英文名处理完再在 Blob 层还原原始文件名用户无感知。4.5 首次加载慢、用户以为卡死现象是首屏点按钮后十几秒没反应。原因是 wasm 体积大且首次要实例化。解决是提前在页面空闲时预加载核心加一个进度提示并把 wasm 放 CDN 开压缩。别等用户点了才开始下载那是体验杀手。5. 进阶把 ffmpeg.js 用稳的几个习惯真正让 ffmpeg.js 在生产里站住脚的不是会写几条命令而是把「加载、执行、回收」当成一条流水线来管。我现在的做法是页面初始化时就load()一次实例常驻所有转码任务走一个队列串行执行避免并发把内存打爆。每个任务开始前writeFile结束后主动deleteFile清掉虚拟文件系统里的临时文件否则跑几十次之后内存只增不减最后必然 OOM。验证是否真的处理成功别只看exec有没有抛错要检查输出字节数是否大于 0必要时用ffprobe部分核心带读一下时长和编码格式。我习惯在log回调里过滤error关键字一旦出现就中断任务并提示用户换参数而不是让它跑完再发现文件是坏的。还有一个容易被忽略的点-threads在单线程核心里是无效参数写了也不报错但会让你误以为开了多线程。判断方法就是看日志里有没有pthread。另外-preset和-crf的组合对耗时影响极大做在线工具时我一般给用户两档快速ultrafastcrf 30和标准veryfastcrf 24别把veryslow暴露出去那是给离线批处理用的。从那以后我每次接媒体处理需求都强制先问一句「文件能不能不出浏览器」能就上 ffmpeg.js不能才考虑服务端。这套判断帮我省了不止一台转码服务器。希望帮到你。本文还有配套的精品资源点击获取