这篇文章的目的是?
H.265播放器(Videox.js)在淘宝直播落地已经近两年了。之前的架构设计主要针对的是直播的场景,播放m3u8和flv的直播流,由于直播落地的场景是B端主播中控台,使用场景是可以预览画面即可,故而对帧率要求不高。但是今年的短视频业务面向的多是C端用户,需要在Web场景下播放1080P/720P的H.265视频,那么必须满足短视频主流分辨率+码率流畅播放的要求。同时业务上还要支持多视频格式如(mp4/fmp4)的需求,所以综合评估后对原有架构进行了升级。既然有了升级自然就需要沉淀下经验。按照一贯套路我就来水一篇文章了。当然这两年内业界也有大量H.265播放器的实践落地,我写这篇文章也是借这次重构的机会分享自己的一些经验,希望能帮助各位少踩些坑。
视频演示
如下将演示新版播放器播放 1分钟1080p/25fps/H.265 MP4视频,具体视频参数如下:
-
预加载1000000帧(即整个视频),完全解码不播放的内存占用、CPU占用、解码间隔时间
因为整个解码过程没有进行播放,所以解码间隔=单帧解码耗时。
从上面视频能看出来,一个几十M的文件完全解码能达到4.6G的内存占用,CPU占用高达300以上(4核)。当然,这是完全不做限制,火力全开解码。但也能得出结论:无干扰情况下平均解码一帧1080p仅需要13ms(基于mbp2015版)。
旧版直播播放器解码720p需要26ms(基于mbp2015版),而新版播放器播1080p目前的13ms还不是极限,后续将继续探索优化空间。
-
预加载10帧并解码,后续边播边解的相关数据
演示1太过极端不符合日常使用的场景,但因为极限情况平均解码只需要13ms,而视频帧率是25(即间隔40ms),所以可以隔一段时间喂几帧到解码器,这样平衡了播放和解码的速率之后,CPU占用降到120左右、内存占用降低到了300M。同时还能流畅播放。不过播放策略有很多种,各位有更好的方案也欢迎和我交流。
架构设计
整体架构设计
上图所示为新播放器基本骨架,包含了主要模块。模块间互相独立,各自接收通用协议的参数。比如Loader传递给Demuxer的数据为ArrayBuffer,经Demuxer统一解封装成Packet格式Buffer数据(Annex-B)喂给Renderer。上图用MP4举例(HVCC为H.265码流格式之一),替换成flv、ts格式也是遵循这个流程。Renderer负责decoder调度,音画同步、音视频播放等,可以说是播放器最核心的模块。UI View则主要用来绘制播放器控件UI,如进度条等。本文不打算详细介绍每个功能,仅对decoder做细节解构,其它有关联的模块仅简单说明和实现。
DEMO架构
因为没有Demuxer,所以直接用Loader读取Annex-B码流。
-
通过Loader读取到Annex-B码流的Uint8Array数据
-
通过postMessge将数据发送给Worker线程的WASM包解码
-
WASM通过回调函数传回YUV数据给Worker再通过postMessage传给主线程Canvas
实操步骤
如何将 FFmpeg 编译成 WASM 包
接下来就进入正题了,第一步,先编译FFmpeg做精简,为啥呢?因为FFmpeg不光是个C库,还是非常庞大的C库。我们要在Web上使用它就需要移除一些无用的模块,好在FFmpeg提供了相应配置的能力,使用根目录configure文件按如下步骤操作即可。
1. 准备
-
编译前我们需要去emscripten官网[7]下载最新版emsdk
emsdk就是用来把FFmpeg编译成wasm包的工具
-
官网FFmpeg[8] 下载源码版的FFmpeg(本文基于4.1)
2. 编译FFmpeg静态库
创建 make_decoder.sh
echo "Beginning Build:"
rm -r ./ffmpeg-lite
mkdir -p ./ffmpeg-lite # dist目录
cd ../ffmpeg # src目录,ffmpeg源码
make clean
emconfigure ./configure --cc="emcc" --cxx="em++" --ar="emar" --ranlib="emranlib" --prefix=$(pwd)/../ffmpeg-wasm/ffmpeg-lite --enable-cross-compile --target-os=none --arch=x86_32 --cpu=generic \
--enable-gpl --enable-version3 \
--disable-swresample --disable-postproc --disable-logging --disable-everything \
--disable-programs --disable-asm --disable-doc --disable-netwo