业务布景性交
在B站Web投稿页中,封面、分区、标签的推选功能都需要使用到视频截帧才略。历史上咱们通过WebAssembly + FFmpeg来达成视频截帧。从客岁出手,出手引入WebCodecs进行高性能截帧,截帧性能有权臣擢升,从而给用户带来更快速的推选体验。
但现在WebCodecs只提供了用于解码的才略,并莫得提供对应解封装才略,只可自行达成。此前,咱们通过mp4box.js以及自行开发的mkv-demuxer,惩办了mp4+mkv主流视频时势的解封装问题, 达成了WebCodecs高性能封面截帧决策的落地。但仍存在近 2%的视频时势如flv、avi等,因为无法解封装,而无法体验到WebCodecs的高性能。
针对不同视频时势去作念解封装处理,需要进行数据疏导,API适配类的使命,存在一定的开发本钱。同期,相关的高质地在珍重的JS解封装库很少,假如连续针对单个时势去作念逐一处理,ROI会很低。
于是,咱们生机为WebCodecs低本钱定制一种通用的解封装决策,一次性因循尽可能多的视频时势。
决策设计
空意料之前使用WebAssembly + FFmpeg进行截帧的解说,FFmpeg因循的视频时势很等闲,淌若能复用FFmpeg的Demux才略,并谋划WebCodecs的Decode才略,应该就能达成两者的上风互补。将耗时短的Demux枢纽交给WebAssembly + FFmpeg去因循更多的视频时势,耗时长的Decode枢纽交给原生的WebCodecs去擢升解码性能。
惩办决策
中枢想路
基于上述设计,中枢方针便是将WebAssembly + FFmpeg中的Demux才略零丁出来,达成一个WASM Demuxer,主要法度如下:
C中新增得回WebCodecs解码所需数据的函数
JS胶水代码达成JS与C间的双向通讯,传递解封装后的数据
超碰在线视频截帧SDK中基于原始数据进行疏导,适配WebCodecs
全体经由如下图所示,底下讲防护先容下具体达成法度
C中得回WebCodecs解码所需数据
重要数据结构
FFmpeg包含许多library,这里咱们的方针是解封装,是以只需要要点讲理用于细腻多媒体文献流时势处理的libavformat,以及两个重要的结构体:
AVStream: 用于存储视频/音频流信息的结构体,包含编解码器参数、比特率、帧率等
AVPacket: 用于存储解码前的压缩数据包的结构体,包含数据包对应的技能戳、大小等
WebCodecs视频截帧中,主要用到VideoDecoder中的 configure 和 decode 步调,configure步调用于成就和开动化视频解码器,decode步调例用于向视频解码器提供编码视频数据,以便解码器简略处理和输出解码后的帧。
与configure与decode步调需要的入参作念对比后,不错很容易发现,configure步调所需的参数都不错在AVStream中找到,decode步调所需的参数也都不错在AVPacket中找到。
因此,需要在C中达成两个函数,分离用于得回视频文献中视频流的AVStream与视频流中指定技能点的AVPacket。
不外性交,FFmpeg中的AVStream和AVPacket都相比复杂,而在截帧场景无需用到扫数的参数。于是,咱们对AVStream和AVPacket进行编订,重界说了两个新的结构体 WebAVStream 与 WebAVPacket。
生成WebAVStream
编订疏导后的WebAVStream结构体如下,包含了编解码器参数、出手技能、时长等。
新建一个 get_av_stream 函数用于从文献中查找对应视频流的AVStream信息。最初从视频文献中查找到匹配的视频流信息,读取AVStream,对其进行编订与数据适配,生成并复返新界说的结构体WebAVStream。
如何生成codec_string
在构建WebAVStream时发现,WebCodecs VideoDecoder的configure 步调中有一个必要的 codec 参数,需要传入一个有用的 codec_string,即编解码字符串,描述用于编码或解码的特定编解码器时势。浏览器通过贯通该参数,智力知谈去调用哪一种编解码器。
codec_string参数无法径直从AVStream上得回,需要谋划AVStream中的信息去生成。社区里,这部分的府上至极少,并莫得现成可用的轮子,只可自行达成。调研后发现,生成codec_string主要需要两个法度:
从视频流中贯通出视频编解码器的成就信息
将视频编解码器的成就信息按照codec_string的圭臬进行疏导
最初,关于如何从视频流中去贯通出视频编解码器的成就,不错自行按照对应的圭臬去达成,不外关于不同的codec都需要单独达成,这么本钱就会相比高,不合适咱们低本钱的预期。背靠FFmpeg这个丰富的宝库,深信应该能找到可复用的步调,于是在libavformat中一番探索后,居然如斯找到了相关的贯通步调。
以VP9为例,与ISOM文献之间的绑定例范中(ISOM 即 ISO Base Media File Format,是一种用于存储多媒体执行的文献时势圭臬,常见的MP4便是基于这种文献时势),不错看到VP编解码成就信息如下:
在libavformat/vpcc.c中存在一个 ff_isom_write_vpcc 步调,该步调用于将 VP 编解码器成就写入到ISOM文献中(举例VP9会被写入到MP4文献的stsd/vp09/vpcC盒子中)。在写入成就前和会过 ff_isom_get_vpcc_features 步调来贯通生成成就参数。
由于 ff_ 起首的步调都是FFmpeg里面的步调,无法径直调用,只可将这部分逻辑复制出来,索取出重要部分,改写后当作生成编解码器成就的逻辑。改写后的 get_vpcc_features 如下,包含了生成codec_string所需的参数。
得胜贯通出编解码器的成就信息后,还需要将成就信息疏导成codec_string,于是再谋划VP9的Codecs Parameter String范例,达成codec_string的组装。
生成codec_string后,对生成的codec_string是否能正确被浏览器贯通也曾有所狐疑,于是去Chromium中毛糙探索下,找到贯通codec_string的步调video_codec_string_parsers源码,看下浏览器在得回到codec_string以后具体是怎样贯通的。
找到贯通vp9的函数ParseNewStyleVp9CodecID,不错发现首位的 sample entry 4CC势必是vp09,同期profile、level、bitDepth三个必要参数的贯通章程与 ff_isom_get_vpcc_features 中产出的成就信息时势简略正确对应上,扶持映证了生成逻辑无误。
另外,不错看到文档上有提到DASH时势中也有使用到codec_string,在 libavformat/dashenc.c 中不错发现存访佛的set_vp9_codec_str步调,亦然使用 ff_isom_get_vpcc_features 来达成的。
终末的生成函数如下所示:
其他h264、hevc等编码的codec_string生成逻辑亦然同理,都能在FFmpeg中找到可参考的步调,何况愈加毛糙。因为h264、hevc的视频编解码成就信息都会写入到 AVStream→codecpar→extradata 中,是以不错径直按照比特位去读取成就信息。以h264为例,参考 ff_isom_write_avcc,了解到成就信息的字段写入礼貌与每个字段所占比特位数,从extradata中反向读取对应成就字段,终末再拼接成codec_string即可。
生成WebAVPacket
编订疏导后的WebAVPacket结构体很毛糙,仅需包含重要帧、技能戳、时长、大小及数据
新建一个 get_av_packet 函数,用于得回指定技能点的AVPacket。最初与 get_av_stream 相同,查找到对应的视频流索引。把柄传入的截帧技能点与视频流索引,定位至指定技能点的帧,读取AVPacket数据,然后进行编订疏导,复返新界说的结构体WebAVPacket。
JS与C双向通讯
在完成C中的 get_av_stream 与 get_av_packet 步调后,还需要在JS胶水代码中开拓JS与C的双向通讯。底下以 get_av_packet 步调为例。
JS调用C
最初使用Emscripten提供的Module.cwrap步调,将C函数包装成JS函数,调用包装后的JS函数,将文献旅途和技能当作入参传入,扩充后的复返值为WebAVPacket结构体指针。
C调用JS
C函数扩充收场后,通过复返的WebAVPacket结构体指针从WASM的内存中读取数据。使用Emscripten提供的Module.getValue传入指针,复返内存中具体的值。终末,将扫数值组合成一个JS Object,通过postMessage传出。
截帧SDK新增WASM Demuxer
终末,因为WASM的处理逻辑都运行在Worker上,需要在截帧SDK中对postMessage进行Promise化包装,同期适配WebCodecs的参数时势(WebAVStream => VideoDecoderConfig、WebAVPacket => EncodedVideoChunk),封装成WASM Demuxer。
数据成果
WASM Demuxer上线后,使用WASM Demuxer + WebCodecs截帧对比之前使用WASM + FFMpeg截帧,封面推选耗时P90减少了约 40%,因为视频封装时势不因循导致WebCodecs截帧失败的纰缪量下落了约 72%
web-demuxer
商量到许多名目之前并莫得WebAssmbly+FFmpeg的基础,提真金不怕火了一个名为web-demuxer 的npm包,将WebAssmbly+FFmpeg中demuxer的部分单独索取编译,大大缩减了WASM的体积,因循MP4+MKV的最小版块gzip后的体积为115KB,对大大批Web名成见使用应该也曾可继承的。
通过毛糙的十几行代码就不错达成视频截帧
同期也提供以ReadableStream逐帧读取的花式,用来进行播放等更复杂的场景
但愿能让 WebCodecs 的使用变得愈加肤浅,防护的先容可见web-demuxer
写在终末
WebCodecs仓库的issue中也相关于是否因循媒体容器相关API的征询,但媒体使命组的想法是将这部单干作交给JS/WASM,通过开源库来达成。永恒看,原生解封装的才略的因循还猴年马月。
不外,借助FFmpeg这个丰富的宝库,咱们不错将更多的才略进行WASM层面的模块化封装,与WebCodecs等原生才略去谋划使用,去补皆原生的不及,在Web上达成更多音视频编订的可能性。翌日,跟着原生才略的冉冉发展,再冉冉替换擢升性能,从而达成渐进式的发展。