JS如何调用WebAssembly的api

这里以我之前做的一个项目为例。项目是把ffmpeg编译成WebAssembly文件,然后在js中调用,实现纯前端代码软解码音视频数据。

在linux下编译的

一、编译ffmpeg生成静态库

build_decoder.sh

echo "Beginning Build:"
rm -r dist
mkdir -p dist
cd ffmpeg-3.3.3
make clean
emconfigure ./configure --cc="emcc" --cxx="em++" --ar="emar" --prefix=$(pwd)/../dist --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 --enable-decoder=hevc --enable-decoder=h264 \
    --disable-ffplay --disable-ffprobe --disable-ffserver --disable-asm --disable-doc --disable-devices --disable-network \
    --disable-hwaccels --disable-parsers --disable-bsfs --disable-debug --disable-protocols --disable-indevs --disable-outdevs \
make
make install
cd ..
./build_decoder_wasm.sh

二、编译静态库生成libffmpeg.wasm和libffmpeg.js

build_decoder_wasm.sh

rm libffmpeg.wasm libffmpeg.js 
export TOTAL_MEMORY=134217728 
export EXPORTED_FUNCTIONS="[ \
    '_avcodec_register_all', \
    '_avcodec_find_decoder', \
    '_avcodec_alloc_context3', \
    '_avcodec_open2', \
    '_av_free', \
    '_av_frame_alloc', \
    '_avcodec_close', \
    '_avcodec_decode_video2_js', \
    '_avcodec_get_image_width_js', \
    '_avcodec_get_image_height_js', \
    '_avcodec_get_chroma_format_js', \
    '_avcodec_get_image_plane_js', \
    '_avcodec_get_image_pitch_js', \
    '_avcodec_get_image_bit_depth_js', \
    '_avcodec_close_AVCodecContext_js', \
    '_avcodec_flush_buffers', \
    '_imgScaleChange_js', \
    '_drawRect_js', \
    '_setPrivacyMaskRect_js', \
    '_setFullRectGrids_js', \
    '_setMotionRectGrids_js'
]"

echo "Running Emscripten..."
emcc    dist/lib/libavcodec.a dist/lib/libavutil.a dist/lib/libswscale.a \
    -O3 \
    -s WASM=1 \
    -s TOTAL_MEMORY=${TOTAL_MEMORY} \
    -s EXPORTED_FUNCTIONS="${EXPORTED_FUNCTIONS}" \
    -o libffmpeg.js 

echo "Finished Build"
echo "cp libffmpeg.wasm libffmpeg.js /home/yy/nfsfile/ipc_www_both/www/libffmpeg/"
cp libffmpeg.wasm libffmpeg.js /home/yy/nfsfile/ipc_www_both/www/libffmpeg/
cp libffmpeg.wasm libffmpeg.js /home/yy/nfsfile/newWebH5player/lib/

三、在js中调用API

上述编译脚本涉及的API中函数后缀名有_js的是我自己封装的接口,其他的是ffmpeg原生接口,API一共有:

avcodec_register_all, avcodec_find_decoder, avcodec_alloc_context3, 
avcodec_open2, av_free, av_frame_alloc, avcodec_close, 
avcodec_decode_video2_js, 
avcodec_get_image_width_js, avcodec_get_image_height_js, 
avcodec_get_chroma_format_js, 
avcodec_get_image_plane_js, avcodec_get_image_pitch_js, avcodec_get_image_bit_depth_js, 
avcodec_close_AVCodecContext_js, avcodec_flush_buffers, 
imgScaleChange_js, drawRect_js, 
setPrivacyMaskRect_js, setFullRectGrids_js, setMotionRectGrids_js'

3.1 C语言中的函数声明

void avcodec_register_all(void);
AVCodec *avcodec_find_decoder(enum AVCodecID id);
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
void av_free(void *ptr);
AVFrame *av_frame_alloc(void);
int avcodec_close(AVCodecContext *avctx);
int avcodec_decode_video2_js(AVCodecContext *avctx, AVFrame *picture,
                  const uint8_t *avpkt_data,const int avpkt_size);
const int avcodec_get_image_width_js(const AVFrame *picture);
const int avcodec_get_image_height_js(const AVFrame *picture);
const int avcodec_get_chroma_format_js(const AVFrame *picture);
const uint8_t* avcodec_get_image_plane_js(const AVFrame *picture, int channel);
const int avcodec_get_image_pitch_js(const AVFrame *picture, int channel);
const int avcodec_get_image_bit_depth_js(const AVFrame *picture);
void avcodec_close_AVCodecContext_js(AVCodec *m_codec,AVCodecContext *m_context);
void avcodec_flush_buffers(AVCodecContext *avctx);
int imgScaleChange_js(AVCodecContext *pCodecCtx,AVFrame *src_pic,AVFrame *dst_pic,AVFrame *pad_pic,int nDstW ,int nDstH,int keep_scale );
int drawRect_js(int drawRectX1,int drawRectY1,int drawRectX2,int drawRectY2,unsigned long drawRectRgb);
int setPrivacyMaskRect_js(int x1,int y1,int w1,int h1,unsigned long rgb1,
                                int x2,int y2,int w2,int h2,unsigned long rgb2,
                                int x3,int y3,int w3,int h3,unsigned long rgb3,
                                int x4,int y4,int w4,int h4,unsigned long rgb4);
int setFullRectGrids_js(int gridsColumns,int gridsRows,int fullGridsRgb,int fullGridsEnable);
int setMotionRectGrids_js(int gridsColumns,int gridsRows,int motionGridsRgb,unsigned char *motionBlocks);

3.2 JS的调用

3.2.1 导入libffmpeg.js

(libffmpeg.js中会去加载libffmpeg.wasm的)

importScripts('libffmpeg.js');
3.2.2 接口封装

括号内的参数中,第一个number对应C函数的返回值,最后面的[]的成员对应C函数的参数。

参数数据类型number可以对应C语言中的int或是指针等,大部分都是number。

参数数据类型array是js中的数组,对应到C函数参数中也是指针,我使用的是unsigned char*

array与number不同的是,array传递到C函数中,可以通过这个指针地址取到传递过来的数组的数据。而number传递过来只是一个数字,即使转成指针地址也取不到数组内容的数据。

(function(){
var libffmpeg = {    
    avcodec_register_all:            Module["cwrap"]('avcodec_register_all', 'number'),
    avcodec_find_decoder:            Module["cwrap"]('avcodec_find_decoder', 'number', ['number']),
    avcodec_alloc_context3:            Module["cwrap"]('avcodec_alloc_context3', 'number', ['number']),
    avcodec_open2:                    Module["cwrap"]('avcodec_open2', 'number', ['number', 'number', 'number']),
    av_free:                        Module["cwrap"]('av_free', 'number', ['number']),
    av_frame_alloc:                    Module["cwrap"]('av_frame_alloc', 'number'),
    avcodec_close:                    Module["cwrap"]('avcodec_close', 'number', ['number']),
    avcodec_decode_video2_js:        Module["cwrap"]('avcodec_decode_video2_js', 'number', ['number', 'number', 'array', 'number']),
    avcodec_get_image_width_js:        Module["cwrap"]('avcodec_get_image_width_js', 'number', ['number']),
    avcodec_get_image_height_js:    Module["cwrap"]('avcodec_get_image_height_js', 'number', ['number']),
    avcodec_get_chroma_format_js:    Module["cwrap"]('avcodec_get_chroma_format_js', 'number', ['number']),
    avcodec_get_image_plane_js:        Module["cwrap"]('avcodec_get_image_plane_js', 'number', ['number', 'number']),
    avcodec_get_image_pitch_js:        Module["cwrap"]('avcodec_get_image_pitch_js', 'number', ['number', 'number']),
    avcodec_get_image_bit_depth_js:    Module["cwrap"]('avcodec_get_image_bit_depth_js', 'number', ['number']),
    avcodec_close_AVCodecContext_js:Module["cwrap"]('avcodec_close_AVCodecContext_js', 'number', ['number', 'number']),
    avcodec_flush_buffers:            Module["cwrap"]('avcodec_flush_buffers', 'number', ['number']),
    imgScaleChange_js:                Module["cwrap"]('imgScaleChange_js', 'number', ['number', 'number', 'number', 'number', 'number', 'number', 'number']),

    drawRect_js:                    Module["cwrap"]('drawRect_js', 'number', ['number', 'number', 'number', 'number', 'number']),
    setPrivacyMaskRect_js:            Module["cwrap"]('setPrivacyMaskRect_js', 'number', ['number', 'number', 'number', 'number', 'number',
                                                                                        'number', 'number', 'number', 'number', 'number',
                                                                                        'number', 'number', 'number', 'number', 'number',
                                                                                        'number', 'number', 'number', 'number', 'number']),
    setFullRectGrids_js:            Module["cwrap"]('setFullRectGrids_js', 'number', ['number', 'number', 'number', 'number']),
    setMotionRectGrids_js:            Module["cwrap"]('setMotionRectGrids_js', 'number', ['number', 'number', 'number', 'array']),


    AV_CODEC_ID_H264: 28,
    AV_CODEC_ID_H265: 174, // 0x48323635,

    _chroma_mono: 0,
    _chroma_420: 1,
    _chroma_422: 2,
    _chroma_444: 3,

};
3.2.3 封装好的libffmpeg js接口的使用示例
libffmpeg.avcodec_register_all();
var m_codec = libffmpeg.avcodec_find_decoder(AV_CODEC_ID);
var img = libffmpeg.avcodec_decode_video2_js(this.m_context, this.m_src_pic, data, size);
var w = libffmpeg.avcodec_get_image_width_js(m_pic);
libffmpeg.setMotionRectGrids_js(gridsColumns,gridsRows,motionGridsRgb,motionBlocks);

这里就不一一列举了,封装好的js接口可直接像平常的js函数那样使用。

至此,JS如何调用WebAssembly的api就讲完了。