前言
工具地址:tools.rotato.app/compress
最近有一个视频压缩网站忽然爆火外网,据说能够将视频压缩到原视频的10~20%,而且几乎不损失清晰度,不影响视频播放效果。
这个工具无需注册,可以免费使用;视频处理我们一般都会使用FFmpeg
,这个工具也不例外,官方说明了是基于FFmpeg
的:
但是如果是服务端调用FFmpeg
,那网站的开发者真是大善人了,查看网站的请求,发现:
该网站是使用FFmpeg.wasm
在浏览器端进行视频压缩的,这样消耗的就是客户端的计算资源,也没有上传下载文件的需求了。
FFmpeg.wasm
FFmpeg
是一个音视频处理的C语言工具库,市面上几乎所有音视频处理软件都是基于FFmpeg
实现的;而FFmpeg.wasm
是一个基于WebAssembly
技术实现的,可以在浏览器中运行 FFmpeg 命令行工具,实现音视频处理功能的库。
在浏览器端使用
使用FFmpeg.wasm
其实比较简单,首先安装相关类库:
npm install @ffmpeg/ffmpeg @ffmpeg/util
然后使用官方的示例代码
// import { FFmpeg } from '@ffmpeg/ffmpeg';
// import { fetchFile, toBlobURL } from '@ffmpeg/util';
function() {
const [loaded, setLoaded] = useState(false);
const ffmpegRef = useRef(new FFmpeg());
const videoRef = useRef(null);
const messageRef = useRef(null);
const load = async () => {
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd'
const ffmpeg = ffmpegRef.current;
ffmpeg.on('log', ({ message }) => {
messageRef.current.innerHTML = message;
console.log(message);
});
// toBlobURL is used to bypass CORS issue, urls with the same
// domain can be used directly.
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
setLoaded(true);
}
const transcode = async () => {
const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile('input.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm'));
await ffmpeg.exec(['-i', 'input.webm', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4');
videoRef.current.src =
URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'}));
}
return (loaded
? (
<>
<video ref={videoRef} controls></video><br/>
<button onClick={transcode}>Transcode webm to mp4</button>
<p ref={messageRef}></p>
<p>Open Developer Tools (Ctrl+Shift+I) to View Logs</p>
</>
)
: (
<button onClick={load}>Load ffmpeg-core (~31 MB)</button>
)
);
}
官方提供的是一个React
的示例,核心流程为:
第1步,调用load方法加载相关资源:
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
第2步,写入需要处理的文件
await ffmpeg.writeFile('input.webm', await fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm'));
第3步,执行命令
await ffmpeg.exec(['-i', 'input.webm', 'output.mp4']);
第4步,获取处理完成的文件
const data = await ffmpeg.readFile('output.mp4');
需要注意:如果使用vite构建项目需要使用esm
的资源代替umd
unpkg.com/@ffmpeg/cor… => unpkg.com/@ffmpeg/cor…
多线程处理
FFmpeg.wasm
提供单线程和多线程两个版本,官方表示多线程版本会比单线程版本快2倍左右;使用多线程版本的流程如下:
首先,修改引用的资源,并且需要额外引入workerURL
:
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript'),
});
其次,多线程版本使用到SharedArrayBuffer,采用 SharedArrayBuffer,需要页面 Header 添加
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
但是,在实际使用多线程版本的过程还存在其他问题:
在Chrome浏览器中无法执行命令,并且没有错误提示
具体现象为在执行第一帧之后就卡住,同时也不报错,官方有多个相关issue,并且一直没有关闭;可能原因是 wasm 采用的线程数量过大,使得其使用的内存超过 chrome 的限制,导致无法执行;不过无法判断 wasm 是否是通过 navigator.hardwareConcurrency
(用户计算机的处理器数)来确定线程数量。
部分用户通过添加线程控制,比如 -threads 4
,就可以顺利执行多线程指令;但是有部分还是反馈无法执行。
所以:
谨慎使用多线程版本
性能对比
# | FFmpeg | core v0.12.3 | core-mt v0.12.3 |
---|---|---|---|
Avg | 5.2 sec | 128.8 sec (0.04x) | 60.4 sec (0.08x) |
Max | 5.3 sec | 130.7 sec | 63.9 sec |
Min | 5.1 sec | 126.6 sec | 59 sec |
相较于C语言版本的FFmpeg
,无论是单核还是多核版本的FFmpeg.wasm
都远远不如,存在着10-20倍的性能差距。
但是在Web端,如果使用服务端调用FFmpeg
的方式处理视频,存在消耗服务器资源、消耗带宽,这些可都是money,都是真金白银,而且如果服务器资源有限,会出现排队等待的情况。
而如果是在浏览器端执行就能避免上述问题,而且并不是所有操作都是耗时的,有些操作比如:
- 格式转换:当使用 -c copy 选项进行容器格式转换而无需重新编码时,操作会非常快。
- 视频裁剪:使用 -ss(开始时间)和 -t(持续时间)选项来裁剪视频片段时,如果视频已经被解码到内存中,这个操作可以很快完成。
- 音频提取:从视频中提取音频流(例如,使用 -vn 忽略视频并复制音频流)通常很快,尤其是当音频编码不需要重新编码时。
总结
FFmpeg.wasm
存在着很多问题,但是它的存在让前端能够具有完备的音视频处理的能力;尽管性能比较差,至少已经解决了有没有的问题,Web可以根据自己的具体的业务使用相关功能,所以:
Fabrice Bellard牛逼