360视频云Web前端HEVC播放器实践剖析

360视频云前端团队围绕HEVC前端播放及解密实现了一套基于WebAssembly、WebWorker的通用模块化Web播放器,在LiveVideoStackCon2019深圳的演讲中360奇舞团Web前端技术经理胡尊杰对其架构设计、核心原理,具体痛点问题的解决方式进行了详细剖析。

文 / 胡尊杰前端

整理 / LiveVideoStack算法

奇舞团是360集团最大的大前端团队,一样也是TC39和W3C会员,拥有Web前端、服务端、Android、iOS、设计、产品、运营等岗位人员,旗下的开源框架和技术品牌有SpriteJS、ThinkJS、MeshJS、Chimee、QiShare、声享、即视、奇字库、众成翻译、奇舞学院、奇舞周刊、泛前端分享等。浏览器

奇舞团支持的业务基本上涵盖了360大部分业务线。我我的最开始的时候也曾带队负责360核心安全平台的Web前端支持,包括你们耳熟能详的安全卫士、杀毒软件等。随着公司的业务发展,后面也负责了IoT业务前端支持,最近两年主要配合360视频云的一些Web前端支持工做。基于HEVC的播放器,实际上就是来源于咱们最近作的一个叫QHWWPlayer的播放器。HEVC并非一个新鲜事物,但对于咱们团队来讲,Web前端的HEVC播放器一直是个亟待优化的领域。虽然移动终端或PC端HEVC播放器已经遍地开花,但在Web端仍旧有不少地方须要改进。包括现存一系列智能硬件产品,也在固件采集端已经应用了HEVC的编码,不过若是想让其在Web端呈现并达到用户需求仍需加倍的努力。本次分享将从如下几个维度展开,但愿能给你们带来必定的参考价值。缓存

1. 需求背景

1.1 浏览器端HEVC的支持状况

上图展现了HEVC在浏览器端的支持状况,其中红色表明不支持的浏览器对应版本,绿色表明对HEVC具备良好的支持,青色表明没法保证浏览器能够很好地支持HEVC。整体上来讲HEVC在浏览器端并非一个获得普遍支持的靠谱方案。安全

通常状况下,PC端浏览器都给咱们提供了相应的API,若是咱们的业务场景是支持HEVC的浏览器,可尝试有效利用浏览器的原生能力。网络

基于浏览器原生video,配置source时指定解码器,告知浏览器当前视频采起的是哪种编码方案。若是浏览器自身有能力进行解码那么其天然会走入“支持HEVC”的逻辑分支当中。架构

也能够另外经过JS实现检测功能,JS也提供了相应API——canPlayType来判断当前浏览器环境是否支持HEVC解码。框架

但若是以上流程没法获得有效支持呢?这也是本次分享咱们讨论的重点。异步

1.2 Web端解码方案

浏览器端视频解码总共有以上三种方案,首先就是前文咱们提到的基于浏览器原生能力的播放,例如基于video标签拉流、解码以及渲染播放,整个过程彻底由浏览器实现。第二种方案是首先经过JS来下载视频流、对视频流进行解封装与转封装处理,最后再经过浏览器提供的相关API,交由浏览器原生video进行解码与渲染播放。如开源社区当中的HLS.JS或FLV.JS等就是基于该思路。ide

可是HEVC不能仅靠解封装与转封装来实现,由于其本质上在解码层就不支持。所以第三种方案就是:JS下载的视频流首先经由解封装(解密)处理,并在接下来进行解码,解码完成后渲染播放。若是咱们这里转成浏览器广泛支持的解码格式并让video标签进行播放,尽管理论上可行,但成本显然是很是高的,而且中间存在一个无故的浪费。所以这里一般直接采用浏览器端Canvas+WebAudio API实现视频与音频的渲染,而再也不使用浏览器原生video能力。这里若是使用纯浏览器原生的JS,因为 JS天生单线程执行的弱势,会致使整个处理的效率比较差。

近期,万维网标准化委员会正式推出了WebAssembly规范。一方面咱们能够借助WebAssembly高于JS的能力,实现更加出色的大规模数据处理与解码,另外一方面基于WebAssembly,咱们也能方便地将传统媒体处理中基于C或C++开发的一些媒体处理能力集成在浏览器端执行,而且可经过JS来调用API。对于熟悉传统Web前端开发的咱们来讲,这也是一个值得咱们坚持探索与实践的全新领域。有了WebAssembly以后,咱们就可让部门内擅长视频处理的专家级同事来配合实现更加出色的浏览器端视频播放,相对以往的开发流程来讲,不管是能力、成本控制仍是效率与灵活程度都有十分显著的提高。

1.3 浏览器端WebAssembly的支持状况

上图展示了浏览器端WebAssembly的支持状况,尽管个别低版本的浏览器有一些支持限制,但随着标准化委员会对该标准的不断推动,状况会变得愈来愈好。在包括一些混合式场景,例如APP内嵌(好比聊天工具或通信工具当中打开一个连接)等状况,是否支持也取决于WebView自己提供的能力以及WebAssembly的支持状况,整体上来讲趋于向好。

1.4 HEVC播放器需求目标

HEVC播放器的需求目标,就是基于 JavaScript 相关API,配合FFmpeg+WASM达成 HEVC 在浏览器端的解码&解密、渲染播放的需求,接下来咱们就开始研究如何落地这一目标。

2. 架构设计

整体架构设计思路如上图所示,首先咱们须要一个专门负责下载的下载器,该下载器也是基于浏览器的JS Fetch或XHR API,以实现文件获取或直播拉流等操做。成功拉取的视频流会被存储在一个数据队列当中,随后基于WebAssembly(WASM)+FFmpeg的解码器会来消费处理队列里这些流数据,解码出音视频数据,并放置在音视频帧数据队列当中,等待随后的渲染器对其进行渲染处理。渲染器基于WebGL+Canvas与WebAudio调用硬件渲染出图像与音频。

最后则是控制层用于贯穿总体流程中下载、解码、渲染等独立模块,同时实现底层一些基本功能:如以前咱们提到JS为单线程,而浏览器提供的WebWork API可拉起一个子线程。该流程中每个模块都是独立的,队列中的生产与消费过程也是异步进行的。(咱们可基于JS自己一些比较好的特性实现诸多便捷的功能。例如基于Promise能够将异步过程进行较为合理的封装,并呈现一些异步处理逻辑流程的关键环节的控制到UI层。)

除此以外,还有控制层的一些基础配置选项,包括播放器自己的一些事件或消息的管理,均可以基于控制层来实现。

3. 分解实现

3.1 下载器

下载器做为一个基本模块独立存在,具备初始配置、启动、暂停、中止、队列管理与Seek响应(用于进度条拖拽)等基本功能。上图左侧图标是在开发完成后,基于下载器的事件消息呈现的数据可视化结果。(柱状图表示单位时间下载量,这里咱们能够看到的是,下载量并不均匀,其中的变化可能取决于推流端、服务端、用户端,也可能取决于整个网络环境。)

下载器方面须要留意五个关键问题点:

线性的数据流的合并与拆分

咱们应当进行线性数据流的合并与拆分。理论上浏览器从服务端下载一个视频流的过程是线性的,但浏览器的表现实际上并不是如此,两者的差别可能会很大。

例如当一个浏览器启动并基于JSFetch API抓取流,其过程也是经过API监听数据回调来实现,每次回调可能间隔会很短、数据量也只是一个很小的一千字节左右的数据包。但有些浏览器的表现并不是如此,它们会等抓取到一个1M或2M的数据包以后才反馈给API回调。

而那些过于零碎的数据直接丢给队列或以后的流程来处理,这样势必致使更频繁的数据处理;数据包体积大的直接队列和后续流程势必增长单次处理成本。

所以对线性数据流的合理合并与拆分十分必要,整个过程也是结合初始配置来实现阈值控制。

经过阈值调节控制,咱们但愿可以作好用户端浏览器硬件资源消耗,与该业务场景下媒体播放产品服务体验之间的取舍与平衡。

内部维护管理 range 状态

除此以外,下载器实际上也须要内部维护管理range 状态。例如当用户选择点播时,咱们须要明确是从哪个字节位置到另外一个字节位置下载传输中间这一片数据。而在直播过程当中,则可能出现由网络环境形成卡顿或用户端主动暂停的现象,此时下载器须要明确知道播放或当前下载的位置。

不一样媒体类型数据获取的差别

第三点是不一样媒体类型数据获取的差别,也就是下载器针对不一样的媒体类型开发不一样的下载功能。例如一个FLV直播流能够理解为是一个连续的线性的数据获取,而点播则以包为单位获取。对于HLS流须要获取m3u8列表,完成分析以后再从中选取数据包的地址并单独下载,随后进行流的合并或拆分。总地来讲,咱们须要保证数据的最终产出尽可能均匀存储到队列中,以便于后续的一系列处理。

MOOV 前置或后置

在媒体处理中像MOOV等的索引数据有前置与后置两种状况,这里须要注意的是,咱们的播放器基于Web端。

若索引文件为后置,若是播放器直接下载了一部分数据就直接丢给FFmpeg解码器进行解码,因为FFmpeg解码器没法获取索引,固然也就没法解码成功。除非解码器等待总体媒体源下载完毕,实际上这样是不现实的。

另外因为咱们没法控制MOOV索引数据的体量,前置索引的大小没法肯定,尤为对于一些特殊状况,这种逻辑会带来不少问题。(可是这里有一个取巧的办法,就是咱们能够尝试首先抓取前面几个数据包,探测MOOV边界,并基于此获得MOOV的长度,从而判断取舍在什么时机启动后续的解码。)

慎重并折中的控制内存消耗

最后,慎重并折中控制内存消耗也相当重要。例如尽管较大的缓存能带来流畅的播放,但在Seek时就会带来很大的浪费,咱们则须要根据服务所在的应用场景、帧率码率等来实现合理的折中与取舍。

3.2 解码器

下载器以后,整个流程的核心能力就是解码器。解码器的基本功能与下载器相比大同小异,须要特别关注的是解码器并非像下载器彻底是去调用一个原生的JS Fetch API或XHR,而是在启动WebWorker以后再启动WebAssembly(这里的WebAssembly依赖中是引入了定制化的FFmpeg API,以解决解容器、解码等需求),并实现一些API的交互。上图左侧展示了音频与视频帧解码数据队列的可视化结果。

解码器方面,须要关注的关键问题主要有如下几点:

启动解码前依赖数据量控制

刚才讲到MOOV前置与后置时咱们也说起这一点,也就是在启动解码前作好数据量控制,明确其数据量是否已经达到FFmpeg的基本需求。若是索引文件的数据尚未彻底给到就直接使用命令行启动FFmpeg,那么就会出现报错的状况。咱们应当结合数据量的精准控制来对解码器的启动时机作合理的判断。

主动向下载器获取数据

解码器须要主动获取下载器生成的数据队列,这样系统即可根据数据消费效率获知当前解码器是否处于繁忙的状态。同时,主动向下载器获取数据也能在必定程度上减轻CPU的负担,并可根据CPU的负载来决定当前从下载端应该获取多少数据。例如若是CPU负载较大则数据队列天然会出现累积,咱们能够在下载器初始化时设置一个阈值,若是数据队列积累达到该阈值则下载器暂停下载,这样就可合理控制处理的总体流程并确保播放的正常。

动态解码模式控制CPU消耗

整个解码过程实际上还依赖CPU的性能,若是单帧解码的时间较长,例如一个帧率是25的视频,仅单帧解码就需耗费半秒钟甚至更长时间,此时若是咱们依然按照这样半秒钟或更久的频度解码,则解码数据生产效率彻底跟不上渲染的天然时间进度,效果确定不符合预期,播放也会断断续续。所以咱们须要针对不一样的应用场景,使用动态解码模式(主动丢帧)控制好CPU的消耗。例如在直播或安防场景下,咱们能够舍弃一些指标以保证解码与传输的时效性。

独立的音频、画面帧数据队列

如上图左侧所示,独立的音频与画面帧数据队列分别管理;好比咱们启动丢帧策略的话,会看到画面帧数据量变少,但声音没有变化。

音频从新采样

采集端编码数据的音频采样率须要结合播放端的支持状况来留意兼容问题。

浏览器是一个比较特殊的应用场景,各浏览器对音频渲染中采样率的支持程度也是不一样的。

例如安防场景对声音的要求并非很高,一般16,000的采样率便可,可是若是想在浏览器端播放视频,则部分浏览器要求至少22,050的采样率,不然浏览器端播放没法成功识别并渲染音频数据。FFmpeg自己能够进行音频从新采样,所以咱们能够在解码器端加入相应的配置项,若是用户有该需求那么就能够启动音频从新采样,从新把16,000的音频采样率重采样成符合浏览器所要求的22050采样率。有了符合要求的独立的音频与视频数据帧队列,接下来也天然就能基于浏览器实现对音视频的渲染与呈现。

3.3 渲染器

渲染器的基本功能与下载器、解码器类似,不一样之处在于如下几个关键点:

依赖解码、UI提供画布

渲染器须要浏览器提供一个独立的画布用于绘制相应的视觉画面内容。在UI模块初始化时呈现出一个画布的容器,渲染器渲染生成的画面才能表如今网页上。

除此以外,渲染器依赖解码器解码生产出的音视频帧数据才能进行音画渲染。

主动向解码器获取帧数据

这一点与解码器向下载器主动拿数据类似。

分缓存队列、渲染队列

渲染器会消费处理等待渲染的帧数据队列,只不过帧数据会被分为缓存队列与渲染队列。

而以前咱们介绍的下载器与解码器,自己只有一组数据队列。为何要这样呢?渲染器调用WebAudio API将音频数据传输给浏览器进行PCM渲染时,没法将已经经过该API传输给浏览器的数据作取回控制,所以就须要记录当前已经给了多少数据到浏览器,这就是“渲染队列”。而“缓存队列”则是从进程中获取一部分数据先存储在一个临时队列当中,从而避免频繁地向处于另外一个独立WebWorker中的解码器索取其音画帧队列数据,而带来没必要要的时间消耗。

音画同步、倍速播放、Waiting

音画同步、倍速播放以及断定是否处于等待状态相当重要。好比要追求直播的低延时,网络抖动致使数据堆积发生的时候,倍速追帧是个有效的办法。

动态码率变化

一个视频在播放的过程当中,可能随网络状态的波动出现码率的动态变化,例如为适应较差的网络情况,播放器能够主动将媒体流获取从一个较为清晰的高分辨率变化到一个比较模糊的低分辨率源。

而再渲染中,基于WebGLCanavas的渲染器,咱们首先须要对YUV着色器进行初始化操做,而YUV着色器的初始化,依赖于其所绘制的数据对应的分辨率、比例与尺寸。若是最开始的分辨率、比例和尺寸与以后要渲染的数据不同,而咱们又未对此作相应的响应适配,那么就会出现画面绘制花屏的状况。而动态码率变化就是要随时响应每一画面帧所对应的分辨率变化,对YUV着色器做动态调整,从而保证画面的实时性与稳定性。

从下载、解码到渲染,视频播放器的基本流程就此创建,播放器便有了获取媒体数据、完成解码、呈现音画效果的基本能力。

3.4 UI

基本的UI如上图左侧所示,上半部分是整个播放器在实例化以前咱们能够去作的一系列初始化配置。图中所示的仅是一小部分参数,例如媒体源的地址、是否启用了加密Key、对应的解密算法,包括渲染时为知足某些特定场景下的需求,音视频是同时进行渲染仍是在主动控制下仅渲染音频或视频——例如在安防监控业务场景,会有一些设备须要音频采集、另外一些不须要,或者干脆播放时就不想播放源流音频等等。若在这里播放器不作断定支持,则存在因为音画同步控制依赖音频帧视频帧时间戳比对,但没有音频帧数据的缘由致使没法正常播放,而播放器使用者能进行主动控制则能够避免该问题。

UI的基本功能包括实例化、用户操做触发后续流程涉及的各模块接下来要作什么,还有状态信息响应展示,也就是根据用户交互行为和播放器工做状态做出反馈与信息传递。

另外,UI也须要对相应的状态变化做出响应,例如用户控制当前播放器从正在播放切换到暂停,那么UI层面则须要针对用户操做进行相应的变化。还有快进、拖拽进度条等等。

3.5 控制层

最后的控制层相当重要,首先控制层隔离校验对外暴露的参数及方法。播放器可实现或具有的特性有不少,不可能所有暴露给用户。在播放视频时,下载与解码的数据实际上存在一个先后呼应的关系,若是咱们不考虑用户行为与需求,在网页上呈现播放器的全部特性。而用户也不对其进行科学性选择与判断,而是随意调用API,势必会带来矛盾、冲突与混乱。所以咱们须要隔离配置信息、校验对外暴露的控制参数及方法,以免可能存在的冲突。

另外根据以前的介绍咱们能够看到,不一样模块的基本功能大体相同。所以在控制层咱们须要统一各模块的生命周期,并完成用于调度各模块工做的基础类的实现。

每一个独立的模块什么时刻能够实例装载?什么时刻销毁?该模块是否支持热插拔?各模块生命周期状态的管控与事件消息的监听与调度…… 这些都由控制层进行管理。

有时咱们须要作一些取舍,例如编码器并非基于FFmpeg,而是基于咱们本身的解码解决方案,那么就能够尝试在播放器实例化时候,更换对应模块当中相对应的部分依赖为本身的解码方案;若是咱们须要调整播放器UI层界面样式,那么就可能须要定制本身的UI模块……

在这个播放器实现中,为了规避单线程一些弊端,咱们基于WebWorker API对重点模块开启子线程。

而WebWorker自己的设计存在各类不便:

首先,要求咱们必须单独打包一个JS文件,基于 new Worker(“*.js”)引入到项目中。

但咱们整个播放器做为SDK项目的构建来讲,一般只产生一个JS文件发布出去,才是合理的。若是同时产生多个JS文件,这对咱们的调试、开发或后续应用等来讲都不方便。

针对这个问题咱们结合Promise 实现了PromiseWebWorker,PromiseWebWorker 相对于原生Worker,参数再也不必须是传入一个JS引用路径,而是能够传入一个函数。

这样以来咱们就能够在项目编译时生成一个独立的JS文件,在播放器的执行过程当中将其中worker依赖的那部分函数内容生成一个虚拟的文件依赖地址,做为WebWorker执行的资源。

其次,WebWorker原生能力实现父子线程之间数据传递通信,只能经过postMessage传送数据、经过onMessage获取传送过来的数据,这对于频繁的数据交互中想保证上下文关联对应关系是比较麻烦的。PromiseWebWorker则借助了Promise的优点,对以上整个数据交换过程作严格的应答封装处理,从而实现播放器功能的健壮可靠。

上图连接http://lab.pyzy.net/qhww中是...

若对此感兴趣能够前往试用研究

要点回顾

调度控制层控制下载器、解码器、渲染器与UI&交互四大模块,若是要作某功能模块的业务定制化开发、功能加强补充,对对应独立的模块内部进行优化并作出相应的功能扩展或者调整便可。

4. 难点突破

开发过程所遇到的难点整体能够用以上三点来归纳:首先基于WebAssembly 工具链(emscripten.org),借助EMSC编译器咱们能够直接将一个C和C++编译成JS可用。这一过程自己存在诸多不便之处,主要是由于其自己对系统一些底层库的依赖或对于开发环境的要求,致使可移植性并无那么好,当须要跨机器协做时容易出现诸多问题。如今咱们内部的解决方案是本身找一台专用机器来配置作为编译发布使用。

第二点是队列管理与状态控制,只有精确实现队列管理与状态控制,咱们才能保证整个程序能合理稳定的执行。

第三点就是项目构建打包,咱们要解决前端一些构建打包的习惯以及其在逻辑需求上存在的一些冲突。

5. 将来展望

展望将来,我但愿将来浏览器能对HEVC有更加出色的支持。本次分享虽然是一个播放器,但咱们知道FFmpeg的能力不仅是解码播放,还能够作更多实用工具的发掘实现。同时我也但愿将来媒体类型百花齐放,甚至私有编解码也可以造成Web端场景更规范灵活的解决方案。WASM成熟、标准化完善、各业务领域对应解决能力的细分,也是很值得期待的一件事情;而回到播放器自己,字幕、AI、互动交互等都是能进一步提高音视频播放服务的可玩性与用户体验方向值得研究的方向。

相关文章
相关标签/搜索