网站首页 > 编程文章 正文
作者:于航
出处:https://blog.csdn.net/agora_cloud/article/details/110507988
WebAssembly 自2015年发展至今已经过去了将近5年的时间,而“Web 音视频领域”则是近些年来它的众多经典实践所涌现的地方。基于 MVP 标准提供的特性,我们已经能够对这些构建在 Web 浏览器上的应用进行一些优化,而当 Post-MVP 标准实现后,Wasm 在 Web 端的能力将会有着更进一步的提升。届时,对 Web 音视频应用的影响也将从更多方面显现。在 RTE 2020 实时互联网大会上,Paypal 资深软件工程师于航分享了 WebAssembly 在音视频领域内的一些实践。
:arrow_forward:点击「阅读原文」可观看视频回放,获取 PPT
以下为演讲实录:
Jason Yu 于航:我今天分享的这个话题是 “WebAssembly 在音视频领域内的一些精彩实践”。我先自我介绍一下,我叫于航,大家可以叫做我 Jason,我现在是在 PayPal 做软件开发,然后同时我现在也是 TC39 Member,平时会负责一些 JS 标准化的事情。我在 2018 年的时候出过一本书叫《深入浅出 WebAssembly》。算起来,我应该是 2017 年接触的 WebAssembly,到现在应该是三年左右时间,平时的工作主要还是会关注 WebAssembly 本身的标准发展,还有 WebAssembly 的一些实践。
作为布道师,我也会在国内布道 WebAssembly 这门技术,同时我在极客时间最近也刚完结,出了一个专栏叫《WebAssembly 入门课》,大家感兴趣可以看一下。
我今天的分享主要分为三个部分,前两部分是为了照顾没有 WebAssembly 这个Knowledge(知识)的一些同学。我会介绍一下 WebAssembly 是什么,它的基本应用方式是怎样的,今天我主要会分享 WebAssembly 在多媒体领域的一些实践。
在讲 WebAssembly 之前,我们还是按照惯例先来看一下 WebAssembly 它是什么东西,为什么会有这个的东西,以及它从何而来。首先我要讲一讲,在传统的现代的浏览器中,比如说我们想运行一个 JavaScript 应用,它的整体流程是怎么样子的?首先假设我们有一段 JS 代码,然后这个时候一般来说浏览器引擎或者 JS 引擎,会把这段代码转换成对应的 AST,也就是抽象语法树。这里面我们以 V8 为例。
比如在这个图中大家可以看到,整个这个紫色的圈里其实有几个部分,这个 AST 首先会送到一个叫 Interpreter 的地方,那如果说对应到 V8 里其实叫 Ignition,这样的一个解释器会快速的生成一些字节码,也就是 Bytecode,然后这些字节码会被直接执行,在执行的过程中 V8 引擎会拿到 Profiler 的一些数据,那什么叫 Profiler 数据?其实就是我在运行这段 JS 代码的时候,引擎发现的一些特征。
比如说代码的一些特征,这个时候我发现某段 JS 代码好像一直在执行,怎么办呢?我需要去优化。其实这部分就是用来帮助编译器进行优化的数据,我们一般就称为 Profiler Data,这部分数据会随着 Bytecode 一起送到一个叫 Optimizing Compiler 这样的一个组件中去进行处理,这个地方我们叫做“优化编译器”。
优化编译器会把拿到的 Bytecode 和对应的 Profiler Data 一起进行参考,然后转换成我们的 Optimized Code,也就是优化后的代码,那这个图是整个我们的 JavaScript 代码在运行时一个生命周期,你可以看到 JavaScript 从 JS 原代码到最后生成优化后的机器码,它整个流程其实是非常复杂的。这里你会发现你光看那个图,它的箭头会比较绕,其实它流程很多,那我们说其实整个紫色圈内的部分是真正使 JS 代码执行效率很难再提升的关键地方。
我们现在假设说想执行一段 JS 代码,想执行一个 “X+Y” 表达式,其实对 JS 引擎来说,当我看到这样一个表达式的时候,我去求值这个表达式时我需要怎么做呢。根据标准,一个引擎它需要进行如下这个图所示的步骤,来判断我 X+Y 这两个值到底是什么类型,因为我们知道 JS 本身是一个弱类性的编程语言,或者说它是一个动态类型的编程语言,也就是说我们每一个变量的类型,只有在真正对表达式进行求值的时候才能知道。所以这也就导致我们的 X+Y 表达式,左边可能是一个字符串,右边是一个函数,我对应就有这样一种表达式的求值规则。同理我 X+Y 也可能是简单的,两个都是数值类型,此时也有对应的,对于两个数值进行 “+” 号运算的表达式求值规则。
整个规则其实都需要从 JavaScript 的标准文档中去查,根据我这边这个图片你可以看到,它其实查的过程很复杂。不仅如此你在我右边这张图的红框里页可以看到,其实有一个向上的箭头叫 Deoptimize,这个是什么意思呢?我们知道包括 V8 在内的 JS 引擎,它们会使用到一种叫 JIT 的编译优化技术,实际上它在进行编译的时候会首先做一些假设。比如说我现在有 X+Y 这个表达式,我想去求值这个表达式,这个时候我发现,这个表达式它已经执行了一百遍,那我这个引擎想要去优化它,引擎会根据它收集的 Profiler Data,去假设 X+Y 两边可能是一个 Number,根据这样的一个假设,引擎就可以把 X+Y,JS 原代码或者 Bytecode 编译成对应的优化机器码,以更高的效率来执行它。
但是总有一些特殊情况,因为我们知道 JS 是一个动态类型的语言,举个例子,但是这个例子不一定是真实存在的。比如说我现在有一个 for 循环,我现在想循环一千次,我恰巧就在第999次的时候把 X 的值从一个 Number 变成了一个 Object。这个时候 JS 引擎在编译代码的时候,他发现我这个假设在第999次的时候它不成立了,这个时候就没有办法按照这个模式去进行优化了。
此时引擎就会执行一个叫 Deoptimize 的过程,把我优化后的机器码再原封不动的转换为对应的 Bytecode,我们知道这个过程实际上很耗费性能也是比较浪费时间的。所以我们说 JS 引擎内部,由于 JS 语言本身是一个动态类型的语言,导致它内部的实现十分复杂的,如果你看过 V8 的代码你会发现,其实很多很多地方如果没有注释的话你没法理解它为什么这么做。就算你有注释了你会发现我这句代码理解了,但是跳到下一句函数里好像发现不是这么回事。
现在包括 V8 在内的好多 JS 引擎,由于 JS 本身的动态类型导致的,在实现阶段 JS 引擎的性能提升已经跟五年和十年前比提升速没有那么快了。以前可能我每隔一个大版本,66 到 67 Chrome 版本,那我能够提升很高的一个性能,现阶段来说同样是一个版本差异,67 到 68,本身 JS 引擎这个部分的性能提升比率不是很大。
但是另一个需要注意的是,其实随着我们互联网发展或者移动互联网的发展,你会发现现在的 Web 应用体积越来越庞大,一开始的时候我们只有一个页面,有展示,有一些交互,弹出一个框让你输入一点东西就结束了。但是现在这种网页会开始调用很多很多功能。包括之前我听我朋友说,他们在做这种在线的视频剪辑 Web Application(应用),他想做一款就是完全可以在 Web 端去进行离线视频剪辑的应用,可想而知其中的实现是多么复杂,要去做分层,包括还有取视频轨道等等。所以我们说现阶段来说,其实 JS 引擎本身性能提升的速度已经赶不上 Web 应用日益发展整个体积变大的速度。
所以人们开始尝试说有没有一些办法能够帮助我们去解决性能壁垒,毕竟按照这种发展趋势,迟早有一天我们这个 JS 引擎它就负担不了比较庞大的 Web 应用了。所以早在 2014、2015 年的时候就做过一个尝试,第一个尝试是叫做 ASM.JS 这样的一个东西,它其实是一个 JS 的严格子集,它具有很高的可移植性。所谓严格子集就是只要它是 ASM.JS 它就一定是标准的 JavaScript,所以说只要在能够解释执行 JS 代码的浏览器中,我们就可以使用 ASM.JS,这个是子集的意思。而因为它是子集所以它的可移植性很高,我可以在各个浏览器运行,只要它支持 JS 代码,它就能够执行 ASM.js。
ASM.JS 通过 Annotation 的方式,来显式标注了变量的类型。看我左边的代码,“X=X|0”,“|0” 其实就是告诉浏览器 JS 引擎说,这里我想要告诉你 X 它就是 int 类型,而它在后面不会发生改变,通过类似这种方式,JS 就能够通过这些 Annotation 来直接把这些 JS 代码编译成对应的机器码,就不用再走之前 JS 的那个流程了,我们说这是一种引擎优化的方式。但是我们说如果在不支持 ASM.JS 的浏览器中,其实 X=X|0,它的值是不会变的,所以只要 ASM.JS,浏览器它支持这个特性,OK,它可以优化;它不支持,你就把它当成普通的 JS 代码去运行就 OK 了,对你应用的整体是没有影响的。
这看起来很好对吧,但是它其实还有一些问题,我们看有什么问题,首先我们说它虽然通过 Annotation 的方式进行了优化,但它本身还是一个 JS 的严格子集。 对于 V8 来说我们仍然需要对这个文本形式的代码,进行诸如“生成 AST“,“中间 IR 语言转换”,最后进行“优化编译器的编译”,这些流程是无法省略的。 这是第一点。
第二点是源代码的可读性非常的低,虽然在我现在这个 JS 的例子中你会发现还挺简单的,就是 X=X|0,但你可以去网上查一查真正运用到生产中的一些 ASM.JS 的代码文件,你会发现它们其中遍布的都是一些 “|0” 或其他符号,而对用户来说我觉得基本没有可读性,因为有的时候你简单写的一个 JS 表达式,由于我们要应用 ASM.js,那其实编译器会把这一行表达式拆分成多个子表达式,每一个表达式可能都会有 Annotation 参与其中,所以说它可能会把原先的代码结构破坏掉。
第三点就是 ASM.JS 这种东西它其实比较难以优化,因为每一个 ASM.JS 它是以一个 Module 的形式来独立存在的,每一个 Module 它其实是跟 JS 的沙箱环境隔离的,它自己本身有沙箱来与 JS 通信,因为我必须跟你的其他的 JS 代码进行通信,那我本身作为 ASM.js Module,我是要跟外面通信的。
所以它是要保持一些严格的条件,才能够保证我在执行 ASM.JS 代码的时候能够有一个安全的环境来运行,所以说它的优化条件很苛刻,如果说你这个 ASM.JS 代码不满足它的条件,那么它就会变成普通的 JS 来运行。
但是说实话它本身这个代码相对于最原始的 JS 代码的性能可能还会低一点,我说的是如果它在没有被优化的情况下,因为你看到其实这里的 “X|0” 会变成位运算,我每一个都要进行这样的运算还真是挺耗时的,所以我们说 ASM.JS 有一些问题,虽然解决了 JS 性能不够的这样一个问题,但是它本身也存在一些问题。
另一个曾经尝试过的优化叫做 PNACL,它也是一种新的技术,它使得我们可以直接把一些写好的 C/C++ 代码,在浏览器中以一个沙盒的形式来运行,我们知道直接执行 C/C++ 代码肯定是效率很高的对吧。
同时这些 C/C++ 代码还可以充分利用 CPU 的一些特性,比如说 SIMD,或者是多核心的一些处理。同时它也比较好的支持“我编译一次,可以在各个平台使用”,所以听起来其实很棒。那我直接跑了 C/C++ 代码,我是不是可以直接在浏览器中享受原生应用的这种性能了?实际上它也有很多问题,我们看一下有什么问题呢。
第一个问题就是说如果你想用 PNACL 这样的技术呢,你需要把你的 C/C++ 代码,使用 Pepper SDK 这样的开发工具包完全重写,就是你的每一行,你之前用的比如说 std::map 你需要用 Pepper SDK 提供的对应数据类型将它完全替换掉,其他的包括你在 C/C++ 当中用的 std::string,需要用 Pepper SDK中提供的特殊字符串类型替换掉。所以说你需要对你的 C/C++ 代码进行一个很大的更改,这个我们要考虑成本,如果我有 C/C++ 代码我不能用,我只能全部重写,那我觉得这个就显得太苛刻了,对于应用这门技术的人来说其实是一个很高的成本。
第二点是它的标准比较封闭,目前或者说在这个技术出现之后的这段时间里只有 Chrome 支持,但现在来看 Chrome 也准备不支持了,因为 WebAssembly 的出现导致其实谷歌自己它也不想继续维护这个技术,因为只有 Chrome 可以用,其他浏览器不能用,伴随有很多问题。所以说这个技术相较于我们前面讲的 ASM.JS,基本是处于一个不可用的状态。而上面我们讲的 ASM.JS 在某些情况下还是可以使用的,后面我会讲到什么情况下你可以使用它。
虽然这两种技术,都是在 WebAssembly 之前用来尝试解决 JS 引擎性能不够的新技术。然后在应该是2015年的时候 WebAssembly 出现了,我们看一下它是什么东西呢,这里我大致列了它的四个主要特征。
第一点它是一个新的抽象虚拟机指令集,它现在已经是 W3C 标准。
第二点是它已经被四大浏览器支持,就是四大浏览器可以运行这个 MVP 标准的 WebAssembly 对应的字节码。
第三点,WebAssembly 是一个指令集,指令集肯定有指令,一堆指令可以组成一个程序,这些程序它会保存在一个 “.wasm” 为后缀的二进制格式文件里,这个文件它有一个显著的特征,它是以一个 0X6d736100 这样一个二进制串作为开头的,我们说这串数字其实就标志着,这个 Binary 文件它是一个 WebAssembly 格式的文件。就是你可以称这个字符串是 WebAssembly 这个格式的 Magic Code。
最后一个最重要的一点,在我们现在的浏览器中,可以通过相应的 Web API 来调用这些 Wasm 指令集,比如说来运行一个由它组成的 Web 程序。
我们可以来看一下,还是以最开始的那个 JavaScript 为例,如果这个时候我把 WebAssembly 加入到 Pipeline 中它会处在一个什么位置呢,你会发现它已经处在了整个 Pipeline 的最末端,这意味着我们不需要生成 AST 这个步骤,我们不需要生成 IR、生成中间代码格式这样的步骤,我们不需要 Profiler 的步骤,我们不需要生成 Bytecode 的步骤,我们只需要的是把 WebAssembly 的 Binary 转换成对应的可能是 Machine Code 这样一种最终的执行代码格式,然后浏览器引擎或者 JS 引擎就可以使用这些优化好的代码,这个时候我也不需要 Deoptimize 那个流程。之前优化失败的时候我要做“去优化”,对 WebAssembly 来说它不需要,它只需要一个步骤就是把 WebAssembly 这个 Binary 编译成优化后的机器码来执行就可以了。
还是以上面的图为例,它整个的流程就是简化了很多,省去了很多不必要的东西,你发现只需要三个步骤或者两个步骤,因为这个地方我不需要 Profiler 这个过程,所以说它只需要两个步骤,一个是 Optimizing Compiler 和 Optimized Code。
其实从前面那个图你可以很直观的看到,相比于 JS,为什么 WebAssembly 它的性能会很高。很明显,我整个 Pipeline 完全缩短了,我只需要两个步骤就可以完成你之前六七个步骤进行的事情,那这个就是很明显的一个可以提升性能的地方。
然后我们再来看一下怎么用 Emscripten 来构建一个简单的 Wasm 应用,这里我们简单的过一下。我们会使用一个比较常用的编译工具叫做 Emscripten ,它可以把你输入给它的 C/C++ 代码编译成对应的 Wasm Code 和 JS Glue Code。这里面有人可能会问,为什么我这个工具会生成两部分,生成一个 Wasm Code 又生成了一个 JS Glue Code(如下图所示)。
其实我们想象一下,比如说我现在有 C/C++ 的代码(如下图),这个代码中我做了一个操作,我调用一个 C 标准库函数,打开了一个本地文件,这个时候我想把这些 C 代码直接编译成 Wasm 用在浏览器上。但是问题来了,我们知道浏览器其实不可能有这样的功能。我们知道浏览器本身是一个沙盒环境,它不可能让你的应用随便操作你的本地文件,但我又想编译这个东西,那怎么办呢?
其实 Emscripten 会帮助我们去构建一个在浏览器中的 Posix 沙盒环境,那什么叫 Posix 沙盒环境?你可以将它类比为 Emscripten 通过 JS 代码或者 Web API 帮我们模拟出了可以用于 C/C++ 应用程序运行的这样一个类似操作系统的环境。JS Glue Code 会帮我们处理一些对本地内存的访问过程,比方说你用 std::malloc 这种函数,它会在你的现有的内存段中分配一个空间,那 Emscripten 会帮我们把这部分主要针对 Native 环境的特性用 JS 的方式模拟出来。
举个最简单的例子,我想打开一个本地的文件,那我怎么去模拟?其实最简单的一个方式,我可不可以用 Local-Storege?我可不可以假设说我要打开的文件是已经存放在 Local-Storege 中的某一个 “key-value” 下面的一段数据呢?
其实 Emscripten 就是帮我们做这些事情,它会帮助我们生成这部分 JS Glue。Wasm Code 本身不需要知道我实际运行在一个什么样的场景中,我可以用 JS Glue 这种方式,来通过右边的 Web API 模拟出整个Wasm Code 需要的一种环境,因为我们说 Emscripten 会帮我们生成两部分内容。一个是Wasm 本身的 Binary,另外就是 JS 对应的 JS 胶水文件。
然后我们再来举个简单的例子,你可以看这里有一个新的函数,这里我们是一个叫 “add” 的函数,它可以返回输入给它的两个整数之间的和,这个地方我们有几个组成部分,我稍微介绍一下。
首先我们用 “include:的方式把 Emscripten 的头文件引入进来,这个地方我们可以看到它用了一个叫做 “EMSCRIPTEN_KEEPALIVE” 这一个宏,这个宏用来干什么呢?我们整个代码是没有写 “main” 函数,实际上我通过编译器编译的时候如果编译器发现你这里没有 main 函数,它会把你所有 main 函数没有用到的其他函数全部处理掉,它会假设这些函数是没有用的,作为优化阶段的就会把这些没有用的函数去掉。那么这里用这个宏就是让我在编译流程中保留这些函数的实现。
这个地方我们还加了 “extern C”,以保证 add 这个函数不会在编的过程中被处理,被处理之后我在外面去调的它的时候,它们名字就会被改变了,对我们的调用来说就不太方便。
我们使用 emcc,是 Emscripten 提供的一个编译器来编译这个东西,它会生成两部分文件,一个 Wasm 文件一个 JS 文件,最后我们可以通过 HTML 这样一个形式来使用生成的 Wasm 字节码,这个地方你会发现其实我用了几个 Web API,首先我们用 “fetch”,大家如果对 JS 熟悉的话,会知道它是一个可以用来获取远程资源的函数,通过它我们把位于远程位置这样一个名为 “toy.wasm” 的文件加载进来。
然后我想让这个 fetch 函数返回这个 Binary 文件对应的 Response,这些数据会存在于 “bytes” 这个数组中,这里我们调用了一个 JS 标准中专门提供的用于处理 WebAssembly 实例化的一个函数,叫 WebAssembly.instantiate,我们把这个 bytes 数组传进去。最后这个函数会返回 result,再用 result 上面的这个 result.instance.exports 就有我们导出的函数了,我们就可以调用它。然后传一个 10 和 20 后你会看到返回一个 30。
WebAssembly 在多媒体处理及相关领域的实践
前面我们大致过了一下为什么会有 WebAssembly 这样一门技术,以及 WebAssembly 出现之前业界是用什么技术来解决 JS 引擎性能不够这个问题的。我们还通过一个简单的实例来介绍了一下怎么去应用 WebAssembly。接下来我们主要看一看WebAssembly 在多媒体处理和多媒体相关领域的实践。
第一个我想介绍的,是我之前经常给人说的一个实例,Google 他们开发的基于 WebAssembly 的在线图像处理应用,它叫做 Squoosh(如上图)。Squoosh 其实它的用处很好理解,就是我可以加载一个图片到这个 Web 应用中,然后我可以离线的去进行一些对图像的处理,比如说调整它的质量,对它进行压缩、进行量化,或者其他的一些 Squoosh 提供的处理。你会发现从我这个 PPT 中的这个动图可以看到,使用 Squoosh 加载一个大小为 15.6M 的这样一个图像,然后你会发现我调整了一下 quant,它这边其实是通过一个竖线,中间这个轴划分为左右两部分,它会让你看到当我在对右侧进行处理的时候,跟左侧原始图像对比有什么区别,你会发现当我调整 quant 的时候其实整个的应用响应是非常快的,你可以看到我把它调成 0,右边基本上可能是 100—200 毫秒的样子,其实就已经对这个 15.6M 的图像的一半的象素进行了处理。如果你去看 Squoosh 本身的实现你会发现它其实依赖了很多这种现有的 C/C++ 的库,然后通过把它的编译成 WebAssembly 的这种格式来使用,这里比如说它用到了 libimagequant、MozJPEG、webp,这些都是业界存在优秀的一些 C/C++ 库,这些库可以对图片进行量化处理,对图像进行压缩,对图片的格式进行解析和处理。
所以说通过 Squoosh 这个应用我想表达的是 WebAssembly 第一个方面它对已有代码的可重用性。这意味着其实对很多这种场景来说,我们可以不用从头去开发应用,借助于 Wasm 我们可以把那些现有的已经很成熟的代码库直接拿来使用,这样可以大大降低减少我们构建应用的成本,同时也能一定程度上提高我们应用的稳定性。
因为我们知道这些库相比于你自己重新写其实会更加稳定,这个是 WebAssembly 第一个好处,就是它可以对代码进行复用,同时应用也直接体现出了 WebAssembly 本身的性能会比较好。
第二个例子是一个已经上线的例子,是 Ebay 之前在官方博客介绍过的,他们自己基于 WebAssembly 进行的一个实践,他们是如何使用 WebAssembly 来对他们的一个 Web 应用上的二维码扫描这个功能的优化。
这里同样我想给大家介绍的是,其实还是我今天讲的,就是 WebAssembly 可以对业界已有代码的重用。那 Ebay 在实现 Barcode Scanner 的时候也是选择了一个业界非常有名的这样一个基于 C 语言的二维码扫描图像库,它叫 ZBar,也就是我列出的第一个库。
他们的方案其实也是比较典型的,一个 WebAssembly 应用的实现方案。因为我们知道就目前来说不像 JS,WebAssembly 出现的时间还比较短,虽然我们说四大浏览器已经支持了它的 MVP 版本。但是毕竟还是有很多老的浏览器,所以说你在应用 WebAssembly 的时候你其实是需要兜底的,Eaby 是怎么兜底的?左边这个图你可以看到每当我这个 Web 应用想要去扫描一个图像的时候,我会创建三个线程,那每一个线程都会形成一个不同的二维码扫描实现。
比如说我第一个线程对应一个 ZBar 的实现,第二个线程是用 Ebay 自研的一个 C/C++ 库把它编译到 Wasm 的一个实现,第三个线程就是用最纯的 JS 实现。它是通过我这三个线程一起来对这个图像进行扫描,然后通过竞争的方式,也就是哪一个 Worker 先反馈给我结果,我就优先用这个结果,它是通过这种方式来保证了我的应用,当我在用低版本浏览器的时候我可以用对应 JS 的实现来告诉我结果。而当我在现代浏览器的时候我可以用 ZBar 或者我自己研发的这个 Custome Lib 对应的这个结果。所以说 Ebay 的这个例子是很常见的。我们说如果你想在生产环境中去使用 Wasm 这样一个方案,我们可以通过竞争这种方式来在保证兼容性的同时,还可以以最快的这种实现来反馈给你对应的一份结果。如下图所示,我这边放的其实是它生产环境的一个示例的截图,大家可以参考一下。
然后入下图所示,要讲的是一个基于 WebAssembly 构建的多媒体解码与播放器,它叫做 ogv.js。从这个图你可以看到这个 ogv.js 也是有多个 Worker,每一个 Worker 会在进行图像处理时做不同的工作。比如说它这边图里可以举例,有些 Worker 是专门会负责对音频进行编解码,其他的 Worker 可以对视频进行编解码。
ogv.js 它本身是支持很多种不同的格式的,包括 Ogg/WebM 等等,它在整个架构中会使用 Wasm 来对 Demuxer 进行加速,这里其实 Demuxer 可以用来处理整个媒体资源中的音视频内容。除此之外还使用到了 SIMD,SIMD 可以一次以向量为单位来对你视频中的一行像素进行同样的处理,这样相比于 CPU 本身一个个处理速度要快很多。
除此之外它还会用一些多线程,这里其实如果你看到 GitHub 它的文档里会给你提到,如果在一些很特殊的支持 Worker 特性浏览器上的话,可以有更快的图像编解码速率,包括其实我们在 Worker 中进行 Audio 或者 Video 的编解码,这个部分也是由 Wasm 进行处理的。其实 ogv.js 是一个很典型的,也是就现阶段来说 WebAssembly 在 Web 应用这个领域最多的一种实践的方向,那也就是对视频,或者音频的 Web 编解码方案。
下一个我要介绍的其实也是 WebAssembly 在 Web 端音视频解码方案中的一个比较优秀的库。我们知道传统浏览器厂商会对尤其是移动端。比如说你现在用它的浏览器打开了一个你自己的一个页面,大部分厂商会对 HTML 页面中的使用 “video” 标签播放的视频进行魔改。
比如说我单独把窗口拎出来让你可以拖动,或者说我播放完视频给你推送些广告,甚至有些浏览器在某些情况下,它本身这个浏览器不支持通过 video 标签播放视频,但是你发现你写的 try-catch 却一直无法捕获到异常,使得在这些浏览器当中你就束手无策,没有办法去实现你想要的逻辑。
而这里我要介绍的 WXlnLinePlayer 就是在这种情况下出现的一个视频播放器。它其实会通过这种字节码的方式,以软解的方式来代替 video 标签的功能,也就是说当你加载进来一个视频的时候,相比原先你使用 video 标签直接去让浏览器帮你播放,WXlnLinePlayer 会通过相应的 DRU5 等等编解码库来帮助你去通过软件的方式来解码你的视频内容。然后通过 Web GL 还有 Web Audio 的方式来把对应的图像和音频给播放出来,WXlnLinePlayer 在实现的时候也同样使用了 WASM 进行加速,同时它还用到了 ASM.JS 进行降级。所以我之前提到为什么 ASM.JS 还是可以去用一下的,因为在某一些情况下相比于使用 JS,如果说这个浏览器能够使用 ASM.JS 的话它的速度还是相对较快的,所以说很多情况下你可以用它来作为 Wasm 不能用时的一个备选方案。
其实在 WXlnLinePlayer 出现之前可能很多企业他们尝试过能不能用 FFmpeg 来直接做视频的解码,然后再通过对应的方式把这个视频或者音频播放出来,相对来说 FFmpeg 存在一些问题,它本身其实代码库体积比较大,所以会导致你在做 Web 应用的时候,你在加载 FFmpeg 库的时候它的加载成本比较高,同样你要加载代码的 Size 也会更大。
第二点是 FFmpeg 本身其实主要针对的是非 Web 端场景的优化,比如说一些针对解码之类的优化,很多特性你把它直接编译成 Wasm 其实无法使用,甚至这些代码还会拖累你 Wasm 优化部分的性能提升。所以我们说 FFmpeg 第二点问题,它本身不是针对 Web 端这个场景的,所以说你把它编译到 Web 来运行,它的性能不一定会有多少提升。
第三点就是 FFmpeg 本身因为它的体积过大,包括的功能很复杂,其实你想去优化它的成本很高,除非你有很多团队成员他们很熟悉 FFmpeg 的内部实现,然后再定制一个我们所谓的针对 Web 版的 FFmpeg,如果你有这样能力的话你可以尝试去做,但我觉得大部分企业可能都没有这样的资源。
而这里我们讲的 WXlnLinePlayer 它做的事情就只是把涉及编解码和 Remix 的部分抽出来,然后单独进行编译,其他部分还是用 JS 实现,这样的话你想使用 H264 你就单独加载 H264 的库,其他的解码部分不会被引入,所以说针对单个解码方案的优化或者实现是很容易进行的,整体也比较轻量级。
其实我觉得就是如果你想处理一些简单场景的话是完全够用的,相比于 FFmpeg 它的可操控空间是比较大的。
下面要介绍的这个也是在 WASM 多媒体领域的一个东西,它叫做 SMK,这个东西是可以让你用 C++ 编写代码,然后可以一次编译运行在多个平台,它本身可以用来实现一些 3D 或者 2D 动画,包括一些简单的音频处理,这个库本身还是处在开发当中,大家可以关注一下。如果你看源码你会发现,这个东西本身自己写的代码是比较少的,它做的东西就是帮助你把现阶段有的一些优秀的开源库,比如说我这边例的 Free type、GLEW、GLFW、GLM、 Open AL 等等这些全部都整合在一起,这些库其实是 C++ 业界非常有名的使用 C++ 编写的多媒体处理库。SMK 其实是通过把这些东西整合在一起,然后帮你提供统一的接口,你只要通过 SMK 统一接口统一来开发就可以了。
如果你想要在 Web 上来使用的话那它会通过 Emscripten 帮你把对应的 C++ 代码编译到对应的 Wasm 版本,你这边如果是 Open GL 来编写的代码,它会编译到对应的 WebGL 实现。
同理如果你用 Open AL 实现,它会帮你编译到对应的 Web Audio 接口上去,SMK 也从侧面反映出了 WASM 的一个特征,就是对代码的重用性。如果说你有这样的需求,比如说我想要用某一个 C++ 编写的多媒体处理库,想要把他应用到你的 Web 应用上,我觉得你可以尝试一下使用 WASM 把对应的一些历史库编译到 Web 平台之上。
接下来我们再看其他一些,这是我列的其他杂项,也算是 WASM 在多媒体生态的一些产出。我这边列了几个特点,你会发现其实在多媒体处理这个领域,大部分的项目都还在起步阶段。其中 Web 音视频的编解码器是最为常见的业务领域。
其他的还有诸如 2D/3D 渲染引擎,包括物理渲染引擎,算是多媒体领域的一个分支。
目前来看大部分企业使用 WASM 来做这些引擎或者编解码器并不是说他们自身使用 JS 实现的那套引擎遇到了不可逾越的一些门槛或者瓶颈,而大部分都是为了技术储备,或者说为将来的性能延伸进行考虑。所以说其实这个是 WASM 在多媒体领域的一个现状。
总结下来其实在 MVP 下 WASM 还是两个重点,这两个重点我之前一直在用,我觉得随着 2015 出现年到 2017 年发布 MVP,它的两个最大的特征是: 第一是它能够给你提供高效的计算,第二个是它可以帮助你提供对历史优秀代码库的复用 ,那这个是 MVP 两个最重要的重点,所以如果你有这样类似的需求。
如果说你有一些 Web 上想要去实现的功能,那你没有能力或者没有精力去从头实现,那你可以思考一下查一下有没有对应的 C++/Rust/Go 版本的实现,如果有的话你可以直接拿来复用,虽然这个复用并不是说我直接编译就可以,但是相较于你从头开始写这个代码库它本身的成本还是非常非常低的。
作者:于航
出处:https://blog.csdn.net/agora_cloud/article/details/110507988
猜你喜欢
- 2024-09-09 linux中docker配置nginx+php+mysql+thinkphp完整案例
- 2024-09-09 心中无码便是高清,马赛克算法 PULSE详解
- 2024-09-09 欧拉系统openEuler 成功运行.NET8(完整版)
- 2024-09-09 手把手教你搭建国产嵌入式模拟器SkyEye开发环境
- 2024-09-09 3分钟阅读 | webp画质感人,尺寸嫉妒,前后端程序员都来看
- 2024-09-09 ubuntu vscode设置 c++ opencv 非contrib版本
- 2024-09-09 HandBrake 1.7.0更新,AMD/Nvidia GPU AV1视频转码效率大大提升
- 2024-09-09 C语言基础及指针①(c语言指针基础知识)
- 2024-09-09 php函数imagettftext使用注意事项
- 2024-09-09 PHP 图像处理函数(php处理的图片格式是什么)
你 发表评论:
欢迎- 06-24一个老爸画了超级有爱的365幅画 | 父亲节献礼
- 06-24产品小白看魏则西事件——用产品思维审视百度推广
- 06-24某教程学习笔记(一):13、脚本木马原理
- 06-24十大常见web漏洞——命令执行漏洞
- 06-24初涉内网,提权那些事(内网渗透提权)
- 06-24黑客命令第16集:47种最常见的**网站方法2/2
- 06-24铭说 | 一句话木马的多种变形方式
- 06-24Java隐藏的10倍效率技巧!90%程序员不知道的魔法方法(附代码)
- 最近发表
- 标签列表
-
- spire.doc (70)
- instanceclient (62)
- solidworks (78)
- system.data.oracleclient (61)
- 按键小精灵源码提取 (66)
- pyqt5designer教程 (65)
- 联想刷bios工具 (66)
- c#源码 (64)
- graphics.h头文件 (62)
- mysqldump下载 (66)
- libmp3lame (60)
- maven3.3.9 (63)
- 二调符号库 (57)
- git.exe下载 (68)
- diskgenius_winpe (72)
- pythoncrc16 (57)
- solidworks宏文件下载 (59)
- qt帮助文档中文版 (73)
- satacontroller (66)
- hgcad (64)
- bootimg.exe (69)
- android-gif-drawable (62)
- axure9元件库免费下载 (57)
- libmysqlclient.so.18 (58)
- springbootdemo (64)
本文暂时没有评论,来添加一个吧(●'◡'●)