实现一个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);

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

被折叠的 条评论
为什么被折叠?



