在浏览器中使用 FFmpeg

简介: FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。随着浏览器能力的不断发展,我们可以通过 WebAssembly 在浏览器运行各种语言的程序,这其中就包括了使用 c++ 写的 FFmpeg。

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。随着浏览器能力的不断发展,我们可以通过 WebAssembly 在浏览器运行各种语言的程序,这其中就包括了使用 c++ 写的 FFmpeg。

虽然 FFmpeg.wasm 在浏览器中运行的性能并不是很乐观,但是,它展示了一种在浏览器中运行 ffmpeg 的可能性,也许在不远的将来,我们可以在浏览器中解码任何封装格式的视频,又或许以后有大牛会做出一款Web OBS或者 Web PR。

1682563556(1).png

畅想了这么久未来,我们开始进入正题……


SharedArrayBuffer


额,还得再等会儿,先来了解一种 JavaScript 中的数据类型——SharedArrayBuffer。

SharedArrayBuffer 对象用来表示一个通用的、固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。

SharedArrayBuffer 的出现是为了解决大体量的数据在主线程或者工作线程中通信的性能瓶颈,但是这也引入了一个漏洞,为了安全,所有的主流浏览器于 2018 年禁用了 SharedArrayBuffer,在 2020 年通过一些安全措施SharedArrayBuffer已经可用。

我们需要在上下文中设置以下HTTP 响应头

HTTP/1.1 304 Not Modified
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
复制代码

否则会出现以下报错

1682563540(1).png

浏览器提供了检查环境的能力,我们可以通过crossOriginIsolated属性判断当前的环境是否可用 SharedArrayBuffer。

if (!crossOriginIsolated) {
  alert('当前环境不可用')
  return
}
复制代码

在开发过程中我们可以在 devServer 中设置响应头,例如在 Vite 中我们可以进行如下设置

export default defineConfig({
  plugins: [vue(), {
    name: 'configure-response-headers',
    configureServer: server => {
      server.middlewares.use('/', (_, res, next) => {
        res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
        res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
        next()
      })
    }
  }],
})
复制代码

这里通过 vite 的插件机制来完成,也可以理解为 express 的中间件。

如果是 webpack 等打包工具,同样的原理参照文档添加响应头即可。

如果实在开发环境,需要在静态资源服务器的响应中添加响应头,例如 Nginx

location / {
  root /usr/share/nginx/html/edgestack;
  index index.html;
  try_files $uri $uri/ /index.html?$query_string;
  add_header Cross-Origin-Embedder-Policy 'require-corp';
  add_header Cross-Origin-Opener-Policy 'same-origin';
}
复制代码


FFmpeg.wasm


经过一些“题外话”,终于进入了今天的正文,可以先看一下他的官网

我们需要先安装@ffmpeg/core@ffmpeg/ffmpeg两个依赖,core 是核心包,我们可以通过 CDN 的形式引入,这里安装是为了本地引入,安装完成之后我们可以从 node_modules 目录中将 core 文件复制出来。

1682563590(1).png

将这三个文件复制出来就可以通过开发工具的静态资源代理来获取了(放到你设置的静态资源目录,默认一般是 public)。

然后可以通过以下方式初始化

import { createFFmpeg } from '@ffmpeg/ffmpeg'
const ffmpeg = ref(createFFmpeg({
  corePath: '/ffmpeg/ffmpeg-core.js', // 刚才移动到的目录
  log: true
}))
复制代码

在初始化之后就可以使用fetchFile读物文件,这里读取文件并不是直接从文件系统中读取文件,而是从内存中将数据读取为 Uint8Array 格式数据。

import { fetchFile } from '@ffmpeg/ffmpeg'
const videoInput = ref<HTMLInputElement>() // file 类型的 input
async function getVideoUrl() {
  if (!crossOriginIsolated) {
    alert('当前环境不可用')
    return
  }
  if (!ffmpeg.value.isLoaded()) {
    await ffmpeg.value.load()
  }
  const fileName = videoInput.value?.files?.[0].name as string
  const video = await fetchFile(videoInput.value?.files?.[0] as File)
}
复制代码

ffmpeg 需要手动 load,可以通过 ffmpeg.isLoaded 来判断当前环境时候加载完成(这里使用了Vue3 的 ref,所以我使用.value来访问 ffmpeg)

在读取完数据之后就可以使用ffmpeg 来进行处理了,使用 run 方法来执行 ffmpeg 的命令,这里的命令和 linux 中运行时是一样的,只是需要把原本的命令按照空格拆成一个个的字符串。

const videoRef = ref<HTMLVideoElement>() // video 元素
async function getVideoUrl() {
  // 省略上一部分代码
  const outputName = fileName.split('.')[0] + '.mp4'
  ffmpeg.value.FS(
    'writeFile',
    fileName,
    video)
  await ffmpeg.value.run(
    '-i',
    fileName,
    '-acodec',
    'aac',
    '-vcodec',
    'libx264',
    '-y',
    outputName
  )
  const data = ffmpeg.value.FS('readFile', outputName)
  videoRef.value!.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4'}))
}
复制代码

ffmpeg.FS 用于文件读写, 这里的读写也不是相对于文件系统, 可以理解为应用程序相对于 ffmpeg, 使用 writeFile 将文件从内存写入 ffmpeg, readFile 将文件冲 ffmpeg 读取到内存

这样就可以进行操作视频了,我们可以加点逻辑计算一下视频处理的时间

async function getVideoUrl() {
  // 函数开始
  const startTime = new Date().getTime()
  // 函数结束
  const endTime = new Date().getTime()
  console.log('视频处理时间:', endTime - startTime, 'ms')
}
复制代码

经过测试一个 47秒钟的视频,处理事件为 68 秒

1682563613(1).png

同样的视频在 mac 命令行里处理的时间是 5s 左右

1682563625(1).png

目前看来,ffmpeg 在浏览器中的性能还是有待提升,在生产环境应用的能力还是不太够。

具体的 ffmpeg 使用方法就不在这里介绍了,毕竟本文的主题是在浏览器中引入 ffmpeg。如果有时间我会总结一下 ffmpeg 的一些常用命令。

相关文章
|
Web App开发 JSON Unix
浏览器:好用的浏览器插件,亲测好用
浏览器:好用的浏览器插件,亲测好用
208 0
|
Web App开发 Android开发 iOS开发
哪些浏览器提供WebRTC支持?
哪些浏览器提供WebRTC支持?
|
Web App开发
【浏览器】chrome 打包分享自己安装的插件
【浏览器】chrome 打包分享自己安装的插件
408 1
【浏览器】chrome 打包分享自己安装的插件
|
Web App开发 前端开发 JavaScript
炫酷浏览器调试小技巧,别跟我说你全知道?
炫酷浏览器调试小技巧,别跟我说你全知道?
|
Python
【浏览器&exe桌面应用】用cef Python打造自己的浏览器
【浏览器&exe桌面应用】用cef Python打造自己的浏览器
323 0
【浏览器&exe桌面应用】用cef Python打造自己的浏览器
|
Web App开发
有的浏览器可以直接打开rtsp,有的不能
有的浏览器可以直接打开rtsp,有的不能
167 0
|
JavaScript 流计算
360浏览器兼容问题
360浏览器兼容问题
441 0
|
JavaScript
浏览器文件下载
浏览器文件下载
124 0
|
Web App开发 JavaScript Java
推荐一个好用的浏览器记笔记工具
不知道大家平常在浏览网页的过程中,对比较重要的内容,有没有记笔记的习惯。强哥在阅读比如SpringBoot等项目的官方文档,或者是看到一些比较好的博客文章时,都比较喜欢做一些笔记。
推荐一个好用的浏览器记笔记工具
|
安全 JavaScript 编译器
QT调用IE浏览器COM插件完成网页浏览
QT调用IE浏览器COM插件完成网页浏览
456 0
QT调用IE浏览器COM插件完成网页浏览