WebAssembly

方昊英
2023-12-01

https://www.w3.org/2020/08/29-chinese-web/wasm-intro.pdf

https://zhuanlan.zhihu.com/p/68048524

WebAssembly

  • 一个可移植、体积小、加载快并且兼容 Web 的全新二进制格式(以前只有js)

  • 能从C/C++等语言将代码编译到WebAssembly格式.并在浏览器中运行它们,也就是可以在Web端跑C/C++代码,后缀名为.wasm

  • 本质是类汇编的一种新的抽象栈式虚拟机

  • 更为接近机器码,已经高度优化过,无需浏览器进行更多的优化和编译

  • 拥有静态类型校验,无需再次优化或者去优化操作

  • 没有垃圾回收机制

  • WebAssembly执行完全依赖CPU计算,不能借助GPU硬件加速,所以需要尽量挖掘CPU执行提升程序效率的手段。目前两个主要优化手段为多线程和SIMD

编译为WebAssembly方式:

  • WAT(WebAssembly Text Format)

    • wat2wasm sum.wat -o demo.wasm

  • AssemblyScript(直接将TypeScript编译成WebAssembly)

    • https://github.com/AssemblyScript/assemblyscript
  • Golang

    • GOARCH=wasm GOOS=js go build -o main.wasm main.go

  • Emscripten (核心工具、转换c\cpp为wasm)

    • emcc hello.c -o demo.wasm

JS调用wasm

onst fetchAndInstantiate = async (url,importObject = {}) => {
	const response = await fetch(url)
	const bytes = await response.arrayBuffer()
	const results = await WebAssembly. instantiate(bytes, importobject)
	return results.instance. exports
}
(async () => {
	const { addTwo } = await fetchAndInstantiate('sum.wasm')
	console. log(addTwo(1, 2))
})()

转换C代码为wasm

安装(Emscripten)

建议clone 官网地址,然后按需求下载指定版本sdk,可灵活切换

使用

add.c

int add(int a,int b){
   return a+b;
}
emcc hello.c -s WASM=1 -o index.html #时间有点久1分钟差不多
emrun --no_browser --port 8080 . 
emcc add.c -Os -s WASM=1 -s -o add.wasm
#为了快速编译,添加 -s SIDE_MODULE=1 表示就只要这一个模块

使用Emscripten集成FFmpeg到Web

在浏览器里播放H.265需要借用Webassembly+FFmpeg,因为FFmpeg 是用 C\汇编 语言开发的所以Emscripten会很方便的配置FFmpeg

Emscripten版的ffmpeg编译:https://cloud.tencent.com/developer/article/1467813

由于我们此次主要针对 H.265 的解码进行实践,所以可以在编译时通过参数来定制 FFmpeg 只支持必要的解封装和解码器。不同于常规编译 FFmpeg 时使用的./configure,在编译给 Wasm 调用的 FFmpeg 时需要使用 Emscripten 提供的 emconfigure ./configure:

完整项目:https://github.com/goldvideo/decoder_wasm

上面的项目好像编译有点问题,我写了一个完整脚本给可以一键搭建完成环境

#!/bin/bash

ff_version="ffmpeg-4.1.10"
work_path="decoder_wasm"
ff_build_dir="ffmpeg"

main(){
    initializaiton_emsdk
    get_ffmpeg
    pull_decoder_wasm
    emscripten_build_ffmpeg
    update_video_js
    emscripten_build_c
}

pull_decoder_wasm(){
    if [ ! -d decoder_wasm ];then
        git clone https://github.com/goldvideo/decoder_wasm decoder_wasm
    fi
}

intstall_emsdk(){
    pushd emsdk
    ./emsdk install 1.38.45
    ./emsdk activate 1.38.45
    source ./emsdk_env.sh
    popd
}

initializaiton_emsdk(){
obj=emcc
if [ ! -x "$(command -v $obj)" ];then
    echo "$obj installing ....."
    if [ ! -d emsdk ];then
        git clone https://github.com/emscripten-core/emsdk.git emsdk
        intstall_emsdk
    fi
fi

local version=$(emcc -v 2>&1 | grep "1\...\..." | awk '{printf $10}')
echo -e "\033[31m$version\033[0m"
if [ "$version" != "1.38.45" ];then
    intstall_emsdk
fi

local version=$(emcc -v 2>&1 | grep "1\...\..." | awk '{printf $10}')
echo $version
if [ "$version" != "1.38.45" ];then
    echo -e "\033[31mexit env error!!!\033[0m";exit
fi
}


get_ffmpeg(){
if [ ! -d "$ff_version" ];then
    if [ ! -f "$ff_version.tar.bz2" ];then
       wget https://ffmpeg.org/releases/${ff_version}.tar.bz2
       tar -xjvf ${ff_version}.tar.bz2
    fi
fi
}

emscripten_build_ffmpeg(){
pushd $ff_version

ok="y"
local out=$(find . -name "*.o")
if [ -n "$out" ];then
    echo -e "\033[31mThis is very long time operation!!!!!\033[0m"
    echo -e "\033[31mRecompile FFmpeg???? [y/N]\033[0m"
    read ok
fi
if [ "$ok" != "y" ];then popd;return;fi

if [ -d "$ff_build_dir" ];then
    rm -r $ff_build_dir
fi

make clean

emconfigure ./configure --cc="emcc" --cxx="em++" --ar="emar" --prefix=$(pwd)/../${work_path}/${ff_build_dir} --enable-cross-compile --target-os=none --arch=x86_32 --cpu=generic \
    --enable-gpl --enable-version3 --disable-avdevice --disable-avformat --disable-swresample --disable-postproc --disable-avfilter \
    --disable-programs --disable-logging --disable-everything \
    --disable-ffplay --disable-ffprobe --disable-asm --disable-doc --disable-devices --disable-network \
    --disable-hwaccels --disable-parsers --disable-bsfs --disable-debug --disable-protocols --disable-indevs --disable-outdevs --disable-pthreads --disable-w32threads --ranlib=emranlib \
    --enable-decoder=hevc --enable-parser=hevc \
    --enable-decoder=h264  --enable-parser=h264
make -j 16
make install
popd
}



emscripten_build_c(){
types="264_265"
pushd $work_path

if [ -d dist ];then rm -rf dist;fi
mkdir dist

export TOTAL_MEMORY=67108864
export EXPORTED_FUNCTIONS="[ \
        '_openDecoder', \
        '_flushDecoder', \
        '_closeDecoder', \
    '_decodeData', \
    '_main'
]"

echo "Running Emscripten..."
emcc decode_video.c ${ff_build_dir}/lib/libavcodec.a ${ff_build_dir}/lib/libavutil.a ${ff_build_dir}/lib/libswscale.a \
    -O2 \
    -I "${ff_build_dir}/include" \
    -s WASM=1 \
    -s TOTAL_MEMORY=${TOTAL_MEMORY} \
    -s EXPORTED_FUNCTIONS="${EXPORTED_FUNCTIONS}" \
    -s EXTRA_EXPORTED_RUNTIME_METHODS="['addFunction']" \
        -s RESERVED_FUNCTION_POINTERS=20 \
        -s FORCE_FILESYSTEM=1 \
    -w -o dist/libffmpeg_${types}.js

echo "Finished Build . output file in dist: `ls dist`"

echo -e "\033[31mAre you need into $work_path execution 'npm install && npm start' ?\033[0m"
}


update_video_js(){
    pushd $work_path
    git restore test/video.js
    local flag="displayVideoFrame(obj);"
    local file="test/video.js"
    line=$(cat $file | grep -n "${flag}" | awk -F ":" '{print $1}')
    ((line++))
    echo $line
    sed -i "" "${line}d" $file
    sed -i "" "/${flag}/a\ 
        },'viiiiiiiii');
    " $file
    popd
}

main

在shell里面bash init.sh即可,过程大概需要5分钟

但是项目整体还是有很大的问题,播放的h265画质会错乱。可以接着优化

性能优化

WebAssembly执行完全依赖CPU计算,不能借助GPU硬件加速!!!

需要尽量挖掘CPU执行提升程序效率的手段。目前主要优化手段:

  • 多线程
  • SIMD

EMScripten编译SIMD指令集

WASM标准目前定义了对128bit SIMD指令集的支持规范。emscripten编译工具也支持对使用了simd能力的源代码编译成wasm(只支持通过 simd内置函数方式写的源代码)

 类似资料:

相关阅读

相关文章

相关问答