1、导读
基于实际业务需求,介绍了自定义Wasm截帧方案的实现原理和实现方案。解决传统的基于canvas的截帧方案所存在的问题,更高效灵活的实现截帧能力。
全文10103字,预计阅读时间26分钟。
2、项目背景
在视频编辑器里常见这样的功能,在用户上传完视频后抽取关键帧 ,提供给用户以便快捷选取封面,如下图:
在本文中,我们将探讨一种使用FFmpeg和WebAssembly(Wasm)的Web端视频截帧方案,以解决传统的基于canvas的截帧方案所存在的问题。通过采用这种新方法,我们可以克服video标签的限制,实现更高效、更灵活的视频截帧功能。
首先,我们需要了解一下传统的Web截帧方案的局限性。虽然该方案在处理一些常见的视频格式(如MP4、WebM和OGG)时表现良好,但其存在以下缺陷:
-
类型有限:video标签支持的视频格式十分有限,无法处理一些其他常见的视频格式,如FLV、MKV和AVI等。
-
DOM依赖:该方案依赖于DOM,只能在主线程中完成。这意味着在处理大量截帧任务时,可能会对页面性能产生负面影响。
-
抽帧策略局限:传统方案无法精确控制抽帧策只能传递时间交给浏览器,设置currentTime时会解码寻找最接近的帧,而非关键帧。
为解决上述问题,选取FFmpeg+Wasm的方案,通过自定义编译FFmpeg,在web-worker里执行rgb24格式数据到ImageData的运算,再传递结果给主线程,实现。
3、Wasm核心原理
3.1 Wasm是什么
用官网的话说,WebAssembly(缩写为Wasm)是一种用于基于堆栈的虚拟机的二进制指令格式。
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
--- WebAssembly
Wasm 可以看作一种容器技术,它定义了一种独立的、可移植的虚拟机,可以在各种平台上执行,类比于docker,但更为轻量。WebAssembly 于2017年粉墨登场,2019年12月正式认证为Web标准之一并被推荐,拥有高性能、跨平台、安全性、多语言高可移植等优势。
业界有很多Wasm虚拟机的实现,包含解释器,单层/多层AOT、JIT模式。
3.2 chrome如何运行Wasm
浏览器内置JIT引擎,V8使用了分层编译模式(Tiered)来编译和优化 WASM 代码。分层编译模式包括两个主要的编译器:
-
基线编译器(Baseline compiler) Liftoff编译器
-
优化编译器(Optimizing compiler) TurboFun编译器
3.2.1 Liftoff 编译器
当 WASM 代码首次加载时,V8 使用 Liftoff 编译器进行快速编译。Liftoff 是一个线性时间编译器,它可以在极短的时间内为每个 WASM 指令生成机器代码。这意味着,它可以尽快地生成可执行代码,从而缩短代码加载时间。
然而,Liftoff 编译器的优化空间有限。它采用一种简单的一对一映射策略,将 WASM 指令独立地转换为机器代码,而不进行任何高级优化。这使得生成的代码性能较低。
3.2.2 TurboFan 编译器
对于那些被频繁调用的热函数(Hot Functions),V8 会使用 TurboFan 编译器进行优化编译。TurboFan 是一个更高级的编译器,能够执行各种复杂的优化技术,如内联缓存(Inline Caching)、死代码消除(Dead Code Elimination)、循环展开(Loop Unrolling)和常量折叠(Constant Folding)等,从而显著提高代码的运行效率。
V8 会监控 WASM 函数的调用频率。一旦一个函数达到特定的阈值,它就会被认为是Hot,并在后台线程中触发重新编译。在优化编译完成后,新生成的 TurboFan 代码会替换原有的 Liftoff 代码。之后对该函数的任何新调用都将使用 TurboFan 生成的新的优化代码,而不是 Liftoff 代码。
3.2.3 流式编译与代码缓存
V8 引擎支持流式编译(Streaming Compilation),这意味着 WASM 代码可以在下载的同时进行编译。这大大缩短了从加载到可执行的总时间。流式编译在基线编译阶段(Liftoff 编译器)尤为重要,因为它可以确保 WASM 代码在最短的时间内变得可运行。
为了进一步提高性能和加载速度,V8 引擎支持代码缓存(Code Caching)机制。代码缓存可以将编译后的 WASM 代码存储在缓存中,以便在将来需要时直接从缓存中加载,而无需重新编译。这大大缩短了页面加载时间,提高了用户体验。目前WebAssembly 缓存仅针对流式 API 调用, compileStreaming 和 instantiateStreaming 这两个API,使用流式API拥有更好的性能。对于缓存的工作原理:
-
当TurboFan完成编译后,如果.wasm资源足够大(128 kb),Chrome 会将编译后的代码写入 WebAssembly 代码缓存。
-
当.wasm第二次请求资源时(hot run),Chrome.wasm从资源缓存中加载资源,同时查询代码缓存。如果缓存命中,编译后的module bytes将发送到渲染器进程并传递给 V8,V8将其进行反序列化,与编译相比,反序列化速度更快,占用的 CPU 更少。
-
如果.wasm资源发生了变化或是 V8 发生了变化,缓存会失效,缓存的本地代码会从缓存中清除,编译会像步骤 1 一样继续进行。
3.2.4 编译管道(Compilation Pipeline)
频效果V8编译Wasm的流程图
V8 编译 WASM 代码的