解决Web性能瓶颈的答案:WebAssembly【含Web端使用wasm 软解H265裸流Demo】

299 阅读5分钟

1、背景

笔者前段时间工作用到了一个解析rosbag的开源工程,该工程可以直接把rosbag拖进web页面,然后就开始解析生成各种可视化界面。我看了一下代码竟然没找到后端的代码。当时我就奇怪,难道纯js可以做到实时解析文件并输出吗?于是我看了一下network,果然他会请求一个本地.wasm的文件。于是有了这篇文章

2.介绍

WebAssembly(Wasm)是一种低级的、接近机器码的二进制格式,它允许在Web浏览器中以接近原生代码的速度运行代码。Wasm最初是为了在Web上运行C/C++代码而设计的,但随着时间的推移,它也支持Rust、Go和其他语言。

3.优势

  • 高效执行:WebAssembly 是一种低级别的字节码格式,类似于汇编语言,专为机器高效执行设计。这意味着它在浏览器中可以接近原生速度地运行复杂的应用程序,如 3D 渲染、视频处理、游戏等。

  • 跨平台支持:WebAssembly 由主要的浏览器厂商共同支持,包括 Chrome、Firefox、Safari 和 Edge。这意味着开发者编写的 WebAssembly 代码可以在不同平台和设备上运行,而不需要做额外的兼容性处理。

  • 与 JavaScript 无缝集成:WebAssembly 可以与 JavaScript 互操作,开发者可以在同一个应用程序中同时使用 WebAssembly 和 JavaScript,利用 WebAssembly 的性能优势处理复杂计算任务,同时用 JavaScript 处理逻辑和 UI。

  • 语言无关性:虽然 WebAssembly 最初是为 Web 设计的,但它并不仅限于 Web 环境。开发者可以用 C、C++、Rust 等多种编程语言编写代码,然后编译成 WebAssembly 运行。这个特点使得许多已有的高性能库和工具可以轻松移植到 Web 平台上。

  • 安全性:WebAssembly 在设计时高度重视安全性。它在沙箱环境中运行,限制了代码的访问权限,防止了许多潜在的安全漏洞。

4.应用场景

  • 游戏开发:WebAssembly 允许开发者将复杂的 3D 游戏引擎移植到浏览器中运行,使得 Web 游戏的性能接近桌面应用。

  • 视频和音频处理:由于其高效的执行能力,WebAssembly 适合处理需要大量计算的任务,比如实时视频编码、音频处理等。

  • 数据密集型计算:WebAssembly 可以用来运行数据分析、机器学习模型推理等需要高性能计算的任务。

  • 跨平台应用:使用 WebAssembly,开发者可以将桌面应用程序移植到 Web 平台,从而减少开发成本并扩大用户群体。

5.DEMO

go程序打包成wasm 输出一个Hello Wasm

5.1go代码

package main

import (
    "syscall/js"
)

func main() {
    c := make(chan struct{}, 0)
    js.Global().Set("test", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
        return js.ValueOf("Hello, WASM!")
    }))
    <-c
}

5.2打包命令

$env:GOARCH="wasm"
$env:GOOS="js"
go build -o test.wasm test.go

5.3html

<!DOCTYPE html>
<html>
<head>
    <title>Go WASM Example</title>
</head>
<body>
<h1>Go WASM Example</h1>
<script src="wasm_exec.js"></script>
<script>
    const go = new Go();

    async function loadWasm() {
        try {
            // 加载并实例化 WASM 模块
            const wasmResponse = await fetch('test.wasm');
            const wasmBuffer = await wasmResponse.arrayBuffer();
            const result = await WebAssembly.instantiate(wasmBuffer, go.importObject);

            // 运行 Go 实例
            go.run(result.instance);

            // 调用 Go 导出的函数
            console.log(window.test());  // 这将输出 "Hello, WASM!"
        } catch (err) {
            console.error('Error loading WASM module:', err);
        }
    }

    loadWasm();
</script>
</body>
</html>

5.4结果

图片.png

6.实战:Web 端 H265 wasm 软解

相比硬解视频,虽然软解的性能上会相差挺多,但是软解兼容性好,针对一些不支持硬解的设备,还是需要软解来兜底。

6.1原理

基于 c 调用 FFmpeg 的解码能力,封装打包成 wasm 供 js 调用;通过 js 将 H265 裸流传入给 c 进行消费解码,回调出的 yuv420p 数据使用 canvas 进行绘制;

图片.png

6.2 yuv420P 介绍及 WebGL 绘制

yuv420P

8514d9da04bc4f8c909df2f56d4a8261~tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.webp

  • YUV 图像有两种存储格式

    • packed: 每个像素点的Y,U,V是连续交叉存储的
    • planar: 先连续存储所有像素点的Y,然后存储所有像素点的U,最后存储所有像素点的V
  • yuv420P 使用的是 planar 格式

  • 结合下图,假设现在已知图像的 width 及 height,那么 yLength 为 width * height; uvLength 为 (width / 2) * (height / 2)

39805ef5618a44acac63e76e7b2e4c87~tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.webp WebGL 绘制

  • initGL 方法中需要对纹理图像进行y轴反转: gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  • renderFrame 方法中, 在对 y、u、v 分量进行数据填充时, 注意 yuv420P 的特性即可
gl.y.fill(width, height, videoFrame.subarray(0, yLength));
// `width >> 1` 和 `height >> 1` 表示将宽度和高度除以 2,这是因为 U 和 V 数据通常是 Y 数据的四分之一
gl.u.fill(width >> 1, height >> 1, videoFrame.subarray(yLength, yLength + uvLength));
gl.v.fill(width >> 1, height >> 1, videoFrame.subarray(yLength + uvLength, videoFrame.length));
  • initGL 方法中定义的 YUV2RGB 矩阵用于将 YUV 颜色空间的数据转换为 RGB 颜色空间
const mat4 YUV2RGB = mat4 ( 1.0, 0.0, 1.402, -0.701, 1.0, -0.34414, -0.71414, 0.529, 1.0, 1.772, 0.0, -0.886, 0.0, 0.0, 0.0, 1.0 );

这个矩阵是一个 4x4 的变换矩阵,用于将 YUV 颜色空间的点映射到 RGB 颜色空间。矩阵的每一行对应于 RGB 颜色空间中的一个颜色通道(红、绿、蓝),而每一列对应于 YUV 颜色空间中的一个分量(Y、U、V)以及一个常数项。

矩阵的工作原理如下:

第一列:表示如何从 YUV 转换到 RGB 的红色通道
第二列:表示如何从 YUV 转换到 RGB 的绿色通道。
第三列:表示如何从 YUV 转换到 RGB 的蓝色通道。
第四列:是一个位移或偏移量,通常用于调整颜色的亮度。在这个例子中,对于 R、G、B 通道,常数项都是 0,表示不进行额外的亮度调整。最后一个值是 1.0,表示矩阵乘法后的归一化因子。

在 WebGL 的片段着色器中,这个矩阵被用于将纹理采样的 YUV 值转换为 RGB 值。转换后的 RGB 值随后被用于填充像素的颜色。

6.3效果

图片.png

源码及在线demo(需要梯子)

具体c如何待用ffmpeg打包成wasm过程不再赘述,这里直接拿打包好的wasm做演示。直接播放.265。源码见后面github地址,使用方式见README.MD 源码:github.com/heyu3913/wa… 在线演示地址:heyu3913.github.io/wasm/