一、为什么要在浏览器里做转码?
说实话,我第一次听到「客户端转码」这个概念时,第一反应是——这玩意儿靠谱吗?
以前我们做视频处理,谁不是把文件扔给后端服务器?FFmpeg 装好,命令行一敲,完事。但 WebRTC 录制出来的视频,往往是原始格式,体积大得吓人。我有个项目,录了 10 分钟会议,原始 WebM 文件居然有 2 个 G。用户要下载?别想了。
后来我琢磨,能不能在浏览器里直接搞定转码?用户录完,浏览器自己压缩、转格式,再上传。这样服务器压力小,用户体验也顺滑。嗯,FFmpeg.wasm 就是干这个的。
二、FFmpeg.wasm 是什么?
FFmpeg.wasm 是 FFmpeg 的 WebAssembly 版本。说白了,就是把那个强大的命令行工具,搬到了浏览器里运行。
你想想看,以前要装 FFmpeg 到服务器,现在用户打开网页就能用。而且数据不用出浏览器,隐私安全也有保障。
核心能力:
- 视频格式转换(WebM → MP4,等等)
- 音频提取(从视频里抽出一条音轨)
- 视频压缩(调整码率、分辨率)
- 剪辑、拼接、加滤镜
我个人习惯把 FFmpeg.wasm 看作「浏览器里的瑞士军刀」。你不需要懂底层编解码,只需要知道怎么调用它的 API。
三、环境搭建与基础用法
3.1 安装
安装很简单,npm 一把梭:
npm install @ffmpeg/ffmpeg @ffmpeg/core
如果你用 CDN,也可以直接引入:
<script src="https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg/dist/ffmpeg.min.js"></script>
注意: @ffmpeg/core 是核心的 wasm 文件,大概 30MB 左右。第一次加载会比较慢,建议做预加载或者加个 loading 提示。我曾经有个项目没做预加载,用户打开页面白屏了 10 秒,差点被投诉。
3.2 初始化 FFmpeg 实例
import { FFmpeg } from '@ffmpeg/ffmpeg';
const ffmpeg = new FFmpeg();
// 加载核心
await ffmpeg.load({
coreURL: '/node_modules/@ffmpeg/core/dist/ffmpeg-core.js',
});
console.log('FFmpeg.wasm 已就绪');
这里要注意,load() 是异步的。我建议在页面初始化时就调用,别等到用户点击「转码」才开始加载。
3.3 写入文件与执行命令
FFmpeg.wasm 的工作方式,是在虚拟文件系统里操作文件。你需要先把录制好的视频「写」进去,然后执行命令,最后把结果「读」出来。
// 假设录制的视频数据是 Uint8Array
const videoData = await fetch('recording.webm').then(r => r.arrayBuffer());
// 写入虚拟文件系统
await ffmpeg.writeFile('input.webm', new Uint8Array(videoData));
// 执行转码命令:WebM → MP4
await ffmpeg.exec(['-i', 'input.webm', '-c:v', 'libx264', 'output.mp4']);
// 读取结果
const data = await ffmpeg.readFile('output.mp4');
嗯,这里有个坑。FFmpeg 的命令参数和命令行版几乎一样,但有些参数在 wasm 环境下不支持。比如硬件加速相关的 -hwaccel,就别想了。
四、实战:三种常见转码场景
4.1 视频格式转换:WebM → MP4
WebRTC 录制默认输出 WebM 格式,但很多播放器、社交平台对 MP4 支持更好。所以转码是第一需求。
async function convertToMp4(inputBlob) {
const arrayBuf = await inputBlob.arrayBuffer();
await ffmpeg.writeFile('input.webm', new Uint8Array(arrayBuf));
// 关键参数说明:
// -c:v libx264 使用 H.264 编码
// -preset fast 编码速度优先,文件稍大
// -crf 23 质量参数,18-28,越小质量越高
await ffmpeg.exec([
'-i', 'input.webm',
'-c:v', 'libx264',
'-preset', 'fast',
'-crf', '23',
'output.mp4'
]);
const outputData = await ffmpeg.readFile('output.mp4');
return new Blob([outputData.buffer], { type: 'video/mp4' });
}
我的经验: CRF 值设 23 是个不错的平衡点。如果视频要上传到平台,建议设 28,文件小很多,肉眼几乎看不出区别。如果是存档用,设 18 更保险。
4.2 提取音频:从视频中抽离音轨
有时候用户只需要音频,比如会议录音、播客素材。这时候提取音频就很有用。
async function extractAudio(videoBlob) {
const arrayBuf = await videoBlob.arrayBuffer();
await ffmpeg.writeFile('input.webm', new Uint8Array(arrayBuf));
// 提取音频,输出为 MP3
await ffmpeg.exec([
'-i', 'input.webm',
'-vn', // 不要视频流
'-acodec', 'libmp3lame',
'-ab', '128k', // 比特率 128kbps
'audio.mp3'
]);
const audioData = await ffmpeg.readFile('audio.mp3');
return new Blob([audioData.buffer], { type: 'audio/mp3' });
}
我曾经帮一个播客团队做过工具,他们录制的视频访谈,需要自动提取音频做后期剪辑。这个功能帮他们省了大量手动操作的时间。
4.3 压缩视频:减小文件体积
压缩是刚需。尤其是 WebRTC 录制的原始视频,码率往往偏高。
async function compressVideo(videoBlob, targetBitrate = '500k') {
const arrayBuf = await videoBlob.arrayBuffer();
await ffmpeg.writeFile('input.webm', new Uint8Array(arrayBuf));
// 压缩:降低码率 + 缩放分辨率
await ffmpeg.exec([
'-i', 'input.webm',
'-c:v', 'libx264',
'-b:v', targetBitrate, // 目标视频码率
'-vf', 'scale=640:-2', // 宽度缩放到 640,高度自动保持比例
'-preset', 'fast',
'-crf', '28',
'compressed.mp4'
]);
const compressedData = await ffmpeg.readFile('compressed.mp4');
return new Blob([compressedData.buffer], { type: 'video/mp4' });
}
注意: 压缩不是无损的。码率设得太低,画面会糊。我建议给用户提供「高质量」「标准」「压缩」三个选项,让用户自己权衡。
五、核心流程与知识体系
下面这张图,是我梳理的 FFmpeg.wasm 转码核心流程。你看一眼,基本就明白整个链路了。
六、性能与注意事项
| 场景 | 耗时(10秒视频) | 内存占用 | 建议 |
|---|---|---|---|
| 格式转换(WebM→MP4) | 约 3-5 秒 | 约 200MB | 使用 -preset fast 加速 |
| 提取音频 | 约 1-2 秒 | 约 100MB | 很快,几乎无感 |
| 压缩视频(降低码率) | 约 5-10 秒 | 约 300MB | 长视频建议分片处理 |
性能警告: 浏览器环境的内存是有限的。我测试过,一个 5 分钟的视频,转码过程中内存峰值能到 1GB。如果用户设备比较老,可能会卡死。建议加一个「内存不足」的兜底提示。
七、避坑指南
做 FFmpeg.wasm 开发,有几个坑我踩过,分享给你:
- 文件路径别搞错: 虚拟文件系统的路径是大小写敏感的。我一开始写
input.WebM,结果死活读不到文件。 - 输出文件要唯一: 如果多次转码,记得每次用不同的输出文件名,或者先删除旧文件。否则会读到缓存。
- 大文件分片处理: 超过 100MB 的视频,建议切成小段分别转码,再合并。不然浏览器会崩。
- Worker 模式: FFmpeg.wasm 支持在 Web Worker 里运行,避免阻塞主线程。我强烈建议开启。
我的小技巧: 转码前先调用 ffmpeg.listDir('/') 看看虚拟文件系统里有什么文件,能帮你快速定位问题。
八、总结
FFmpeg.wasm 让浏览器具备了强大的视频处理能力。格式转换、音频提取、视频压缩,这些以前必须后端做的事,现在前端也能搞定。
我个人觉得,这个技术最大的价值不是「炫技」,而是实实在在提升了用户体验。用户录完视频,立刻就能拿到处理好的文件,不用等服务器响应,也不用担心隐私泄露。
当然,性能瓶颈和内存限制是绕不开的坎。但只要你做好预加载、分片处理、错误提示,大部分场景都能流畅运行。
嗯,这一章的内容就到这里。代码你都拿到了,去试试吧。
公众号:蓝海资料掘金营,微信deep3321