FFmpeg 是视频处理最常用的开源软件,它功能强大,用途广泛,大量用于视频网站和商业软件(比如 Youtube 和 iTunes),也是许多音频和视频格式的标准编码/解码实现。

借助 WebAssembly 的能力,它现在有了一个 Web 版本:FFMPEG.WASM,让你可以在浏览器里处理视频

官网:

GitHub:

获取FFMPEG.WASM

获取FFMPEG.WASM有多种方法。

我们最终需要的ffmpeg.wasm核心文件如下:
Snipaste_2022-02-23_17-22-50.png

第一种方式:通过源码编译获取
首先到GitHub克隆源码,然后编译出FFMPEG.WASM,步骤如下:

  1. git clone https://github.com/ffmpegwasm/ffmpeg.wasm.git
  2. cd ffmpeg.wasm
  3. yarn
  4. yarn build

编译好后,可以在dist中找到 ffmpeg.dev.jsffmpeg.min.js

然后到GitHub Release下载最新的ffmpeg-core.wasmffmpeg-core.js

第二种方式:通过npm安装依赖,从node_modules中提取
安装:

  1. yarn add @ffmpeg/ffmpeg @ffmpeg/core

然后可以从node_modules中找到我们需要的文件:
Snipaste_2022-02-23_17-18-12.png

在浏览器中使用

创建示例文件:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Title</title>
  8. <script src="./js/ffmpeg.min.js"></script>
  9. </head>
  10. <body>
  11. <video id="player" controls></video>
  12. <script>
  13. const { createFFmpeg, fetchFile } = FFmpeg;
  14. const ffmpeg = createFFmpeg({
  15. corePath: "./js/ffmpeg-core.js",
  16. log: true,
  17. });
  18. (async () => {
  19. await ffmpeg.load();
  20. const dataInputVideo = await fetchFile('data/input.mp4');
  21. ffmpeg.FS('writeFile', 'input.mp4', dataInputVideo);
  22. await ffmpeg.run('-i', 'input.mp4', 'output.mp4');
  23. const data = ffmpeg.FS('readFile', 'output.mp4');
  24. const video = document.getElementById('player');
  25. video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
  26. })();
  27. </script>
  28. </body>
  29. </html>

需要本地测试的话,可以创建一个express服务server.js,运行命令node server(详见:关于SharedArrayBuffer报错的处理

运行到浏览器,可以看到控制台疯狂输出,说明在对视频做处理:
Snipaste_2022-02-23_17-28-31.png
这个处理是本地执行的,浏览器将wasm下载到本地,浏览器再执行相关操作。
Snipaste_2022-02-23_17-31-58.png
以上示例,是直接读取了项目下的 data/input.mp4文件,输出到 output.mp4,没有实际意义,只是一个演示。

通常我们会通过input框读取用户选择的文件:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <script src="./js/ffmpeg.min.js"></script>
  9. </head>
  10. <body>
  11. <video id="player" controls></video>
  12. <input type="file" id="uploader" placeholder="choose file">
  13. <script>
  14. const { createFFmpeg, fetchFile } = FFmpeg;
  15. const ffmpeg = createFFmpeg({
  16. corePath: "./js/ffmpeg-core.js",
  17. log: true
  18. });
  19. const transcode = async ({ target: { files } }) => {
  20. const { name } = files[0];
  21. await ffmpeg.load();
  22. ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
  23. await ffmpeg.run('-i', name, 'output.mp4');
  24. const data = ffmpeg.FS('readFile', 'output.mp4');
  25. const video = document.getElementById('player');
  26. video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
  27. }
  28. document.getElementById('uploader').addEventListener('change', transcode);
  29. </script>
  30. </body>
  31. </html>

在Node.js中使用

  1. const fs = require('fs');
  2. const { createFFmpeg, fetchFile } = require('@ffmpeg/ffmpeg');
  3. const ffmpeg = createFFmpeg({ log: true });
  4. (async () => {
  5. await ffmpeg.load();
  6. ffmpeg.FS('writeFile', 'input.mp4', await fetchFile('./data/input.mp4'));
  7. await ffmpeg.run('-i', 'input.mp4', 'output.mp4');
  8. await fs.promises.writeFile('./output.mp4', ffmpeg.FS('readFile', 'output.mp4'));
  9. process.exit(0);
  10. })();

FFMPEG.WASM使用方式

ffmpeg常用的参数都可以使用,参考:
📃 使用FFMPEG实现音视频处理

通过ffmpeg.run调用即可,每个参数分开传递。
示例:

  1. // 命令行: ffmpeg -i input.mp4 -s 720x480 output.mp4
  2. await ffmpeg.run('-i', name, '-s', '400x600', 'output.mp4');

不过现在支持的格式有限,并不是所有音视频格式都是支持的,支持列表参考:Proposed Encoders / Decoders Libraries

常见错误

关于SharedArrayBuffer报错的处理

如果直接打开引入FFMPEG.WASM的html页面,浏览器会报以下错误:

  1. Uncaught (in promise) ReferenceError: SharedArrayBuffer is not defined

Snipaste_2022-02-23_15-54-40.png

这是官方的解决方案:

  1. SharedArrayBuffer is only available to pages that are cross-origin isolated. So you need to host your own server with Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Opener-Policy: same-origin headers to use ffmpeg.wasm.

意思是,需要在服务端配置两个响应头:

  1. Cross-Origin-Opener-Policy: same-origin
  2. Cross-Origin-Embedder-Policy: require-corp

比如我们可以使用express开启一个静态服务:server.js

  1. const express = require('express');
  2. const app = express();
  3. app.use((_, res, next) => {
  4. res.header('Cross-Origin-Opener-Policy', 'same-origin');
  5. res.header('Cross-Origin-Embedder-Policy', 'require-corp');
  6. next();
  7. });
  8. app.use(express.static('./'));
  9. const PORT = process.env.PORT || 8080;
  10. app.listen(PORT, () => {
  11. console.log(`Server listening on port ${PORT}...`);
  12. });

将需要加载的html文件放于server.js同级目录,运行 node server.js,默认运行到8080端口下。

效果如下:
Snipaste_2022-02-23_15-53-05.png

error:0308010C:digital envelope routines::unsupported

在运行官方vue示例的时候,可能会报以下错误

  1. error:0308010C:digital envelope routines::unsupported

解决方案为降低Node.js到17以下。

如果想要在Node.js 17以上临时解决,可以临时设置环境变量:

  1. set NODE_OPTIONS=--openssl-legacy-provider # windows
  2. export NODE_OPTIONS=--openssl-legacy-provider # linux

参考资料