利用WebAssembly实现js调用c/c++的函数

前言

前段时间关注了一个腾讯的前端女工程师,从她的公众号上知道wasm在前端视频方面的用途,刚好项目上正好在做上传,关于预览那块是一块问题,支持截帧的格式过少,只支持mp4,ogg,webm的视频格式截帧, 于是入坑开始,整个过程比较艰辛,第一是因为完全不了解这一块,无从下手,二是因为这一块的文章很少,能问的人很少,期间请教过用过的大佬,奈何大佬们都不鸟我,没办法,只能自己肝了一个月

关于WebAssembly在前端的应用

WebAssembly可以给前端带来的就是部分性能上的提高,前端的业务越来越复杂,代码量也会越来越多,对内存的要求就随之变高,在一些慢一些,内存不够的电脑上启动一个前端的项目甚至要花上十多秒,所以前端的性能问题在未来会面临新一轮的挑战,WebAssembly可以理解为通过编译c/c++的一些库函数来给前端调用,从而解决前端浏览器处理带来的压力问题,比如一些大型的游戏3D页面,比如我这边要解决的图片的压缩,截帧,视频的转码等等

准备

如果你要开始学习wasm你要具备的前置知识:

  • linux基础语法
  • emsdk的配置
  • wasm的语法
    当然,我这边用的windows环境,所以是docker搭建的emscripten编译环境以及配置,所以docker的基础语法也要了解些^_^

1.编译环境的安装

我们的最终目标是将一个c/c++的代码编译成wasm版本提供给js调用,所以编译环境的安装,如果是其他系统直接参照emscripten官网安装,我之前一直看windows的安装,一直没有安装成功,(如果有大佬在windows下安装成功了就教下我~~ )请教别人无果后,毅然采用docker安装一个emscripten环境:

下载desktop docker,打开终端:

1.拉取镜像

docker pull emscripten/emsdk

2.运行一个容器启动emscripten环境:
比如我这边分别开了两个容器:

利用WebAssembly实现js调用c/c++的函数_第1张图片

验证下当前环境是否是emscripten环境:

emcc -v 看下当前版本号:
利用WebAssembly实现js调用c/c++的函数_第2张图片

因为我使用的是vscode所以在vscode中安装插件docker更加直观的看到你的容器和镜像以及当前运行的容器和共享的文件夹

利用WebAssembly实现js调用c/c++的函数_第3张图片

并且在vscode打开用终端操作可以更方便:
利用WebAssembly实现js调用c/c++的函数_第4张图片

直接可以看使用linux指令,我们编译的文件就在这里

2.编译wasm文件和asm.js文件

编译环境安装成功后我们先在官网上找个简单的helloworld案例编译成js和wasm跑一下.

使用脚本直接创建一个c++的最简单程序:

# create helloworld.cpp
cat << EOF > helloworld.cpp
#include 
int main() {
  std::cout << "Hello World!" << std::endl;
  return 0;
}
EOF

然后运行

emcc hellworld.cpp -o helloworld.js 

如图所示:成功将一个c++编译成一个asm.js文件和wasm文件
利用WebAssembly实现js调用c/c++的函数_第5张图片

使用node运行下helloworld.js

image.png

3.在vue项目中使用asm.js文件

生成的asm.js文件是可以在vue项目中使用的,以下举个例子
我们重新写一个c++函数

extern "C" {int aa(int x){
  if(x<=0)
return 0;
if(x<=2)
return 1;
return x-2;

}
}   

emscripten环境下编译语句:

emcc   -s EXPORTED_FUNCTIONS="['_aa']"   -s EXPORTED_RUNTIME_METHODS=["cwrap"]  a.cpp -o a.js -s  WASM=0

image.png

在vue项目中:

利用WebAssembly实现js调用c/c++的函数_第6张图片

在浏览器看下调用情况
利用WebAssembly实现js调用c/c++的函数_第7张图片

注意:

  1. 如果想通过在前端用js代码调用到c++的代码需要extern C 不然会报错找不到函数
    2.必须使用WASM=0的编译方式,否则将无法正确在初始化前使用函数也是报错

image.png

总结
这只是asm.js 的一个简单的例子,具体的其他用法参考上面发我们可以看到编译出来的js文件(86kb)相比wasm文件(1kb)是比较大,但是js毕竟可读性较好,也不会存在浏览器兼容的问题, 但是执行速度上来看二进制的wasm可能是js的8倍左右,这块我没有详细去学习, 具体的c/c++和j是相互调用的方法参考阮一峰的 asm.js 和 Emscripten 入门教程

4.在vue项目中使用wasm文件

在上一节中使用了emcc编译的js文件,现在我们使用wasm文件做一样的调用
在vue项目中要使用wasm需要将这个文件通过arraybuffer的方式读取编译到 WebAssembly.Module中,然后实例化WebAssembly,使用导出的方法,具体的使用和案例方法在# WebAssembly技术文档

在vue文件中直接更改刚才的测试demo:

利用WebAssembly实现js调用c/c++的函数_第8张图片

注意:importObject是必须字段,这里面是wasm导出的函数,具体到底导出了写什么可以在生成的js中查看
运行结果:

利用WebAssembly实现js调用c/c++的函数_第9张图片

5.使用视频截帧ffmpeg.wasm

刚才用了一些简单c的方法,之后要编译一个完整的ffmpeg库是比较复杂的,因为C/c++的代码中有很多都是有头文件的引用的,不只是简单的一个唯一函数,关于视频截帧已经有大佬完成了一个直接上手就能调用的js文件,他的文章中也有详细的配置,可以从github上直接拉取代码下来学习,大佬已经把所有的编译都写在脚本中,包括webpack配置 直接一键npm run build 生成一个可用的js文件!
开始学习就是从这位大佬的代码开始的,另外要看懂这个代码准备的基础知识还是要先去学习的,不然引入了也不了解流程,后期无法自行更改里面的代码,然后自己打包属于自己项目适用的截帧文件.

文章参考:# 前端视频帧提取 ffmpeg + Webassembly

大佬编译好了ffmpeg的头文件和库文件,我直接拿来进行编译一个wasm和js文件
因为我不是用的脚本这是直接使用的命令,语法如下:

export FFMPEG_PATH=/include
export TOTAL_MEMORY=33554432
emcc  -Iinclude libavformat.a libavcodec.a libswscale.a libavutil.a capture.c -O3  -s WASM=1 -s TOTAL_MEMORY=${TOTAL_MEMORY} -s EXPORTED_FUNCTIONS='["_main", "_free", "_capture"]'  -s ASSERTIONS=1  -s ALLOW_MEMORY_GROWTH=1  -o capture.js

emcc的具体配置自行查看手册,这里我们已经把wasm文件编译出来了,用法其实和之前一节说的一样,我这边不再赘述,(毕竟有大佬已经完备的搞好一整套了,再次感谢大佬的贡献~)

总结

一切是为了看懂和使用这个ffmpeg.wasm来实现视频的截帧操作开始,从环境搭建到编译运行成功我感觉步步都是坑,纸上得来终觉浅啊,照着资料和技术文档也不一定弄得出来,前期的摸索花了我很多时间,没人可问百度也没有答案,很抓狂...但是想想前面已经有人真的做出完整的一个js脚本不由得羡慕和佩服,作为前端除了不断学习还能怎么办呢,但是总算是告一段落了,之后我也能自己简单的打一个更改的c/c++的包去配合项目上的使用,菜鸡啄米,不喜勿喷!

你可能感兴趣的:(前端webassembly)