熊猫直播姜雨晴:直播 H5 播放器架构探索

百家 作者:QingCloud 2018-07-10 15:14:13

7 月 27 日,Cloud Insight 2018 云计算行业峰会将在北京举行,其与LiveVideoStack联合出品『音视频技术与场景探索』论坛,我们邀请了熊猫 TV 音视频技术专家姜雨晴前来分享《成本控制与用户体验保障》。




姜雨晴曾在 LiveVideoStackCon 2017 上有过不一样的分享,本文依据分享整理而成。


大家知道 HTML5 播放器曾被广泛运用于视频点播,而今天我想与大家分享的是运用在直播领域的 HTML5 播放器。现在熊猫已不再使用 FLVJS 作为播放器了,所以今天与大家探讨一下直播 HTML5 播放器的技术难点与架构探索。



我来自熊猫直播,从去年的 7 月份加入熊猫并在 11 月中旬开始开发播放器,主要致力于 HTML5 播放器的研制开发。


接下来我将从以下几个方面介绍 HTML5 播放器的相关内容:



1

HTML5 播放器产生背景


首先让我们来看看 HTML5 播放的产生背景,



通过最近的一些新闻大家可以看到,包括谷歌的 Chrome 还有 Adobe 这样的公司都在强调其产品不再专注 Flash 转而更关注 HTML5。


在这样一个后 Flash 时代,我们必须要有自己的新技术来支撑视频播放,尤其是视频直播的需求。



作为熊猫直播最重要的用户之一,熊猫直播的老板王思聪之前提出 H5 播放器的开发需求,那么 H5 播放器具有哪些优势呢?



(1) 高效性


第一点是高效性。


我们需要明确 Video 标签为浏览器带来的是什么?其实是在背后把 H264 的 Codec 打进了浏览器,无需内嵌应用而是利用浏览器 Codec 进行视频解码。


(2) 兼容性


第二点是兼容性。


之前我们遇见了很多非同寻常的案例与需求,包括将 HTML5 播放器技术运用于电视直播或游戏主机,这其实是反映了 H5 解决方案的良好兼容性。这种兼容性体现在一次开发后可以在多个不同平台应用,降低开发成本。


(3) 浏览器新技术


第三点是快速接入浏览器新技术。


例如大家或多或少听说过的流媒体加密的浏览器新接口 Encrypted  Media  Extensions,还有 WebRTC、VP9、AV1、H.265 等新技术,通过使用 HTML5 可以将这些新技术快速接入浏览器中。


例如最新版本的 Chrome 浏览器便打入了 H.265 的 Codec。相对于 Flash 播放器, HTML5 可更便捷快速地引入新技术。


当然,HTML5 播放器的开发过程并不是一帆风顺的。


2

直播领域 H5 播放器的问题



我们之前从未尝试过将 H5 播放器技术运用于视频直播领域,因此在开发初期我们遇到了很多棘手的问题。2016 年 12 月份上线的第一版便出现音画不同步、码率过高、播放器崩溃、浏览器崩溃、延迟高等问题。



我们团队曾经将这些问题集中并研究解决方案,下面我将会选其中几个比较具有代表性的问题进行详细阐述。


2.1 音画不同步



音画不同步的问题困扰了许久,很多开发者问到相关的问题,下面就是我们对于问题的定位与解决思路。


初期我们在观察来自内核的视频时会发现主播口型与声音无法准确同步,延迟可达到两三秒。这对用户而言是一场糟糕的体验,那么究竟为什么会出现音画不同步的问题呢?


1)问题定位



我们发现,户外直播是发生音画不同步问题最为频繁的版区:


  • 第一个原因是户外主播手机性能及网络问题导致上行数据掉帧频发;

  • 第二个原因是音频和视频的掉帧时间长度存在差异;

  • 第三个原因是播放端音视频实际播放时长不一致导致音画不同步。



上图为问题示意图。灰色框为视频帧组成的视频流,红色框为音频帧组成的音频流,理想状态下的视频流与音频流应当是长度一致。


其中虚线框表示帧片丢失的状态,例如现在视频流丢了 3 片,音频流丢了 1 片,此时实际传输的音视频为上图,但实际播放的音视频为下图:



但看着一小段音视频流,两三帧的差异似乎不是特别明显;一旦累计时间过长,视频流与音频流之间的时间差异越来越大,音画不同步的现象也就会越来越明显。


相信现在使用 FLVJS 做视频直播的朋友也都会遇到这样一个问题:音画不同步的现象随时间的增长越来越显著,那么如何改进技术消除这个问题呢?


2)解决方案



上图是影视动画后期制作时使用 Au 将配音员配音人声与视频画面做对接的处理过程。当出现音画不同步的现象时最常用的处理方案是调整轨道相对位置,再添加特效使得音画自然同步。



视频直播中出现音画不同步时可以运用类似方法进行处理,我们称为抽帧处理。当然抽帧后需要进行音频补帧处理。



在这里大家一定会有疑问,后期补进去的音频帧并不是原生的,那么应该补进去什么帧呢?为了让大家比较清晰地理解这个问题,也我们使用配音中的原理进行解释。



演员配音时,因为演员说每个字时发声的频率不同,声音听上去也会不同。


如果每个字的不同频率切换得比较平滑便不会出现“嘶啦”的声音也就是“过电”现象;但如果是补一个空白帧,便会出现这样的现象,此时人耳会听到短暂的电流杂音,体验很不好;尤其是当直播频繁掉帧时用户会感觉到明显的电流杂音。



所以我们取前一帧进行音频补帧,较好避免了过电现象的发生。


3)改进效果



通过上述播放器对轨与补帧处理可以在掉帧频繁时明显降低音画不同步带来的对直播视频观看的影响。


2.2 码率问题


1)问题定位


相信大家无论是使用 Flash 还是在 H5 播放器都曾遇见正在播放时突然弹框显示“页面已崩溃”的问题。


这是为什么?因为浏览器会限制网页占用运行内存。


普通的无音视频流的网页,除非代码出现严重的 Bug 否则不会占用过高的运行内存;但如果网页中有播放器的运行便很容易使浏览器处于一个高内存占用运行状态,一旦达到运行内存上限便会使得网页崩溃。



上图是蓝光直播上线第一天的反馈情况,可以看到大家反馈的信息,无论是选手毛孔还是主播妆容都是纤毫毕现。



上图是 6000kbps 的高清的直播,可以清晰看到主播面部的细节。


对熊猫来说,高清直播是一座里程碑,也是我们产品的一个卖点。我们不可能用 3000kbps 的冒充蓝光线路,所以在这种大型活动熊猫基本上都维持在一个 6000 到 8000kbps 推流码率下的高清直播。



而对于普通主播而言高码率采集同样重要。上图是根据某天下午几个 FPS 主播们的直播房间统计出来的结果,可以看到很多主播都将码率采样推到 6000 以上,对此主播们也是乐此不疲,这是为什么?



这是我自己喜欢的几位主播平时的推流规律。其中有一个最高需要推到一万四的码率,这样一个高码率对熊猫来讲可以说是非常普遍的。我们需要保证页面不崩溃的同时维持这样一个高码率的推流,可以说难度不小。



这是FPS游戏《绝地求生》的直播画面。可以看到游戏中对手距离非常远,有的时候在画面中就是一个小黑点。像这样的 FPS 游戏一旦推至很高的码率便会大大降低用户体验。


因为会带来明显的卡顿,包括主播也对这一点心知肚明。一般情况下如果出现卡顿的问题主播会给出“换线路板”、“调清晰度”等提示语。但无论如何我们需要支持主播的高码率直播需求,那么如何解决?


2)解决方案



如果你打开熊猫 HTML5 播放器并右键点击打开监控,会看到显示“正在清洗能量槽”,很多人问我什么是正在清洗能量槽?其实是正在清理缓存的意思。这个功能的实现其实只需要几行代码,但背后会遇到了什么问题呢?


a.什么时候清洗


做前端的同学应该知道这个 Setinterval。当 Setinterval 或新的 GOP 准备好时会触发清洗能量槽的功能。


b.一次清洗多少


先说 Setinterval 和新 GOP。


Setinterval 解决方式有优点与缺点,其问题在于此定时器在页面挂起的状态下并非按照设置的时间运行,而只是把这一段代码推至站并等待运行;此时如果超过时间而又在休眠状态便失去作用。而新 GOP 会过于频繁, 干扰系统正常运行,因此最后我们选择 Setinterval 解决方式。那么关于清理多少,我们暂时是确定 10 秒以前的全部清洗。


c.容易洗出什么问题


BufferUpdating 是 MSE 的 Buffer 的一个状态。在新的 GOP 准备好时这是一个写操作,此时一定会存在这样一个无法清理的状态,这也是我们没有用新 GOP 的原因。


3)改进效果



下面来看一下我们内存控制的效果,这是我们新版内核与老版内核的对比,请注意内存的变化。



在同样的测试环境下,上面的标签页是我们使用老版内核得出的占用内存值为 285736k,下面的标签页是我们使用新版内核得出的占用内存值为 75632k,大概是老板内核内存占用的 1/4。


2.3 累计延时问题



CDN 的同事应该知道累计延时也是一个困扰大家很久的问题。


上图是我自己直播间的一个界面,左半图右侧是老版内核的,左侧是新版内核,右半图是我在新版内核网站刷新出的的一个状态,最左边的和最右边我都是已经放置了一段比较长的时间。


先对比来看时间戳,老版内核页面与刚刷新完的页面相比存在大概 4 分钟的延迟,这 4 分钟的延迟可以说为观影体验带来的影响是毁灭性的。


1)问题定位



延迟问题与码率有关。当下行网速小于平均码率时便会出现这种视频卡顿的现象。浏览器的 Video 标签是针对点播设计的,出现卡顿后一定是从卡顿点开始继续播放,这种小规模无法被轻易感知的卡顿累计多了便会造成明显的延迟,那我们该如何处理呢?


2)解决方案



这一部分是我们写的一个重新拉流,处理方法为网络抖动。如果使用网络抖动而后面网络又平滑了该怎么办?


此时需要看最后一帧是否满足需求,如果不满足就重新拉流并重新计算起始时间;然后将始终时间和当天时间作差,得出实际播出的时间以及实际消耗的时间,便是累计延时的时长。若大于一定阈值便会触发重新拉流的操作,当然这个阀值可根据应用环境进行修改,这里我设定的是 15 秒。


3)改进效果



以上是在弱网环境下的测试结果。大家可以看到如果在放置比较久的情况下会产生一定的累计延迟,大概为 3 秒。但这种体验已经比之前好很多了,可以基本保证同步。


3

熊猫 HTML5 播放器内核架构



3.1 明确问题


在整个开发过程中我们遇到了以下的一些问题使得我们将内核进行重新架构。



1)不同业务


不同业务对播放器内核的需求是不一样的。虽然这是个外层问题,但当我们再去剖析时会发现,其实针对不同需求的不同业务下所需要的内核也不太一样。这个时候该怎么办呢?当然不可能将所有的业务都写在内核里,一个业务对应一个内核会带来庞大的开发体量。


2)新技术接入


大家可以看到熊猫之前有十个多月处于 Bata 阶段。为什么我们一直没有发布正式版?因为我们想在播放器当中接入一些新技术。而每次新技术的接入就需要改变包中代码,可想而知其有多么不稳定。


3)团队新人加入


我们团队会遇到的一个很正常的问题就是当有新人加入该怎么办?新人一开始不熟悉开发过程,在开发过程中有时对内核造成不必要的影响。


3.2 构架特征



我们对于新版内核的要求就是——“高度解耦”、“模块化”、“易扩展”,也就是下面我们重构的架构。



1)Modules 层


大家可以看到在 Modules 层是由 Loader 模块、Demuxer 模块、Remuxer 模块与 Build Packages 模块组成,每个模块都有一些自己的插件。


讲到这里,大家可能会想到一些以前的库,包括 HLSJS、FLAVJS 等都大概有这样的一个架构,那么我们在 Mccree Core 层做了哪些工作?


2)Mccree Core层


首先我们设置了一个消息通道 Message Channal,其作用是当有模块要完成某些任务时会通知给下一个模块,然后会把数据给到缓冲区。


这个消息通道采用广播模式,任何一个模块在得到对应的消息时会触发对应功能。


3)底层


底层的数据结构分为 Loader Buffer、Tracks 与 Remuxed Buffer,分别用来放置原始的流数据、Demuxer 后的数据与 Demuxer 前的数据,并提供给 MICE。


其中 MICE 是一个插件,其他的几个部分是我们的核心模块。可能大家刚开始看到这个构架有些复杂,接下来我会向大家介绍这些模块是如何工作的。


3.3 模块、插件与封装



注册、调用、销毁的流程会经常在工程化中被用到。那么在我们的 Mccree Core 中模块是如何被接入的?



首先初始化模块,接下来进行模块调用;这一步比较简单的是调用标准接口也就是Loader加载数据;最后在我不用的时候进行销毁。


需要注意的是这里的 Unload 也是一个标准接口, Unload 是 promise,如果有人想比着这个东西去改 FLVJS,可以把改掉,因为这个是个 promise,泛指是个 promise,其他的也都必须做成一个 promise 才能兼容这样的一个接口。



这是我们一个具体的数据传输方式。


首先是向缓存中填充数据,再通过消息通道通知下一个模块获取数据;之后会给出获取数据的长度,否则下一块模块无法确定获取数据量;接下来收到这些消息后下一模块从缓存中提取数据。大家都知道 FLV 的视频 Header 等于 13 位,就是以上的一段代码,大家可以在开源库上看到这段代码,我就不再赘述了。


3.4 工程管理



这个较为复杂的流程本身会有一些工程管理上的麻烦:


1)工程体积大,模块多


解决方案:多包管理系统。


我们现在使用 Lerna package 管理系统,我想做前端的同学应该了解这是Babel的多包管理工具。


2)参与开发复性工作多


解决方案:完整的开发套件。


当你第一次布局这套环境时会发现需要一个个装所有的库,还要做 Lerna Bootstrap 的工作,参与开发复性工作多,如何改进?我们可以做一套完整的开发套件,将包括自动检测在内的全部工作做好。


3)模块质量保证


解决方案:完整的测试用例、文档支持。


我们要求需要有一个完整的测试用例与文档支持,即使是上一个模块我们也会做 A/B  Test 和软切换。保证其模块的质量。


4

技术创新与展望



关于这一点我想与大家分享一个简单的例子:P2P 技术想必大家并不陌生。



上图是我们实际中接入一位合作方 P2P 的代码。如果需要我在外层去控制使用 P2P 该如何解决?



我们在 P2PLoader 层先写了一些如刚才提到的 Loade 还有 URLsource 这样的标准接口,再写了这一套代码;之后把 P2P 完整接入到我们的 HTML5 播放器。



我们花了半天工时写了一个模块与几行控制代码就可以将这样一个 P2P 技术完整接入到播放器中。 


4.1 WebVR



WebVR 想必大家都了解一些。但现在距离产品化还有一段探索的路,故而一直没有推向市场。


只需封装一个 WebVR 接口,也就是去做一个插件就可很快取代我们现有的纯 MSE,很快就可以上线。


4.2 服务端应用接入



这应该是前端的同学比较熟悉的 NodeJS。


由于现在的框架包括大部分的模块和浏览器是不相关的,而唯一和浏览器相关的是部分 Loader 与基于浏览器的 MSE。


我们可以使用 Node 服务端运行提供音视频服务,将来需要去自建 CDN 时可以很轻易地将我们现在所做的包括解决音画不同步在内的一切优化都转接到一个边缘节点上。


除了姜雨晴的分享之外,在即将召开的 Cloud Insight 2018 云计算行业峰会上,我们还有更多嘉宾与精彩演讲,分享国内领先的音视频技术解决方案及与最佳应用实践,让我们先睹为快。



FIN -


关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接