wasm 视频解码渲染实现

本文实现了一个wasm视频解码渲染的小demo,网页端集成emcc编译的ffmpeg库实现视频解码,用WebGL实现渲染。介绍了编译过程,包括ffmpeg、客户端代码和server的编译;阐述了解码流程,如js与wasm的数据传递;还提及WebGL渲染及server端交互,包括mongoose对wasm多线程的支持。

实现一个wasm视频解码渲染的小demo,网页端集成emcc编译的ffmpeg库,实现视频解码,使用WebGL实现视频渲染。demo中包含了一个基于mongoose的微型Web服务器,用于网页的Web服务和视频流传输,基本无需额外搭建环境以及编译第三方库,可以简单地移植到嵌入式系统中用于网页视频播放视频。学习过程中主要参考了大神代码和文章

编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(2)使用Emscripten编译 - 腾讯云开发者社区-腾讯云

demo地址

wasm_websocket_player: wasm 解码渲染demo

1.编译

1.1 ffmpeg emcc版本编译

首先需要获取emcc用于编译,Mac下可以直接通过brew install来获取。下一步就是通过emcc,将ffmpeg编译对应的静态库。注意这里需要将ffmpeg中平台相关以及汇编相关的选项禁掉,毕竟这里最终都是在js虚拟机中执行,硬件加速相关的操作都需要去掉。下面是demo中编译ffmpeg使用的命令,源文件在demo的third_party文件下。

mkdir ffmpeg-emcc
cd FFmpeg_new
#make clean
emconfigure ./configure --cc="emcc" --cxx="em++" --ar="emar" \
--ranlib=emranlib --prefix=../ffmpeg-emcc/ \
--enable-cross-compile --target-os=none \
--arch=x86_32 --cpu=generic --enable-gpl \
--disable-avdevice  \
--disable-postproc --disable-avfilter \
--disable-programs \
--disable-everything --enable-avformat  \
 --enable-decoder=hevc --enable-decoder=h264 --enable-decoder=h264_qsv \
 --enable-decoder=hevc_qsv \
 --enable-decoder=aac \
 --disable-ffplay --disable-ffprobe  --disable-asm \
 --disable-doc --disable-devices --disable-network \
 --disable-hwaccels \
 --disable-debug \
 
 --enable-protocol=file --disable-indevs --disable-outdevs \
 --enable-parser=hevc --enable-parser=h264

emmake make -j4
emmake make install

1.2 客户端源代码编译

ffmpeg静态链接库生成后,下一步就可以编译demo中客户端相关的源码,包括我们自己调用ffmpeg库的代码,c层与js层交互的代码,以及ffmpeg静态链接库,最终生成一个js文件和一个.wasm库,在网页中我们通过调用生成的js文件进行解码。下面是编译命令,源文件在demo工程的client文件下的build_with_emcc.sh。

export TOTAL_MEMORY=67108864

CURR_DIR=$(pwd)
export FFMPEG_PATH=$CURR_DIR/../third_party/ffmpeg-emcc

emcc --bind ../common/video_decoder.cc ../common/h264_reader.cc ../common/frame_queue.cc main.cc\
    -std=c++11 \
    -s USE_PTHREADS=1\
    -g \
    -I "${FFMPEG_PATH}/include" \
    -L ${FFMPEG_PATH}/lib \
    -lavutil -lavformat -lavcodec \
    -s WASM=1 -Wall \
    -s EXPORTED_FUNCTIONS="['_malloc','_free']" \
    -s ASSERTIONS=0 \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s TOTAL_MEMORY=167772160 \
    -o ${PWD}/player.js

最终会生成player.js以及player.wasm文件。

1.3 demo中server的编译与demo运行

demo中提供了一个微型Web server,提供http服务以及websocket数据传输。考虑到demo主要用于嵌入式平台,这里选择了mongoose作为Web服务器,只需要在源代码中引入一个.c文件和一个.h文件即可使用,无需复杂的编译和依赖库。demo中使用了一个本地h264文件,server收到客户端请求后会读取这个本地文件,通过avformat读取每帧h264,实际使用中可以将这块的代码更换为当前设备的采集和编码。目前调试是在Mac的arm64版本上编译,直接运行server目录下cmake即可。

可以直接在server目录下运行run.sh,即可完成客户端编译,服务端编译以及相关文件的拷贝。目前写死使用8000端口。

2.解码流程实现

2.1 js传递视频流给wasm解码

wasm内存分配与释放

这里首先介绍一下js与底层wasm的交互方式。一般视频流数据数量较小,可以直接为其分配内存空间,这里我们直接通过在js层调用_malloc和_free进行分配和释放内存,这些内存可以被wasm代码所使用。这里首先分配wasm可以使用的内存,下一步就是将js的Uint8Array数据拷贝给这块内存,这样wasm中的代码就可以操作这块内存了。

js传递数据给wasm

这里可以在C++层通过EMSCRIPTEN_BINDINGS对C++函数进行封装,基本数据类型可以使用普通的C/C++数据类型,传入js所分配的内存,在C/C++层直接使用uintptr_t类型即可。下面使用我们deocder类来进行说明。
decoder类的C++类,emscripten::val lambda类型可以将一个js函数传入wasm作为回调函数。

class StreamDecoderWrapper{
public:

    StreamDecoderWrapper(){}
    ~StreamDecoderWrapper(){}

    void OpenAvcDecoder(emscripten::val lambda){
        ... ...
        decoder.OpenWithCodecID(AV_CODEC_ID_H264);
        decoder.RegisterDecodeCallback([lambda, this](AVFrame *frame)->int{

            ... ...
            auto frame_wrapper = std::make_shared<VideoFrameWrapper>()->Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
            ... ...
            lambda(frame_wrapper);
  
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值