在浏览器中使用 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 的一些常用命令。

相关文章
|
JavaScript
Vue2使用全局函数或变量的两种常用方式
这篇文章介绍了在Vue 2项目中实现全局函数或变量的两种常用方式:一种是通过挂载到Vue的`prototype`,另一种是使用Vue的全局混入(Vue.mixin)。
1237 0
Vue2使用全局函数或变量的两种常用方式
|
移动开发 开发框架 JavaScript
uniapp如何与原生应用进行混合开发?
uniapp如何与原生应用进行混合开发?
1156 0
|
前端开发
使用ffmpeg-core的时候报错,解决Uncaught (in promise) ReferenceError: SharedArrayBuffer is not defined
使用ffmpeg-core的时候报错,解决Uncaught (in promise) ReferenceError: SharedArrayBuffer is not defined
|
监控 负载均衡 网络协议
TCP重传与超时机制:解锁网络性能之秘
TCP重传与超时机制:解锁网络性能之秘
2929 0
|
7月前
|
存储 人工智能 JSON
Open-Deep-Research:开源复现版 Deep Research,支持切换多种大模型,不再依赖 OpenAI o3
Open Deep Research 是一个开源的 AI 智能体,支持多种语言模型,具备实时数据提取、多源数据整合和AI推理功能。
1694 16
|
11月前
|
存储 人工智能 算法
精通RAG架构:从0到1,基于LLM+RAG构建生产级企业知识库
为了帮助更多人掌握大模型技术,尼恩和他的团队编写了《LLM大模型学习圣经》系列文档,包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构,基于LLM+RAG构建生产级企业知识库》和《从0到1吃透大模型的顶级架构》。这些文档不仅系统地讲解了大模型的核心技术,还提供了实战案例和配套视频,帮助读者快速上手。
精通RAG架构:从0到1,基于LLM+RAG构建生产级企业知识库
|
网络协议 安全 网络安全
免费申请 HTTPS 证书的八大方法
免费申请 HTTPS 证书的八大方法
11413 0
|
JSON JavaScript 数据格式
文本-----wangEditor的使用,设置和获取内容,展示HTML无样式怎么办????console同步展示怎样写,Vue的配置在Vue3配置文件中的配置,是editor中的v-model绑定的值
文本-----wangEditor的使用,设置和获取内容,展示HTML无样式怎么办????console同步展示怎样写,Vue的配置在Vue3配置文件中的配置,是editor中的v-model绑定的值
|
编解码 前端开发
vue+ffmpeg实现前端视频转码
如何使用ffmpeg的wasm文件在浏览器实现视频转码
|
缓存 前端开发 算法
前端需要加载一个大体积的文件时,可以这么优化
前端需要加载一个大体积的文件时,可以这么优化

热门文章

最新文章