本期内容
前言
背景
分析与抽象
直播流数据是通过什么方式以最低的数据量进行网络传输的呢?
方案与实施
总结
直播就是终端将自己的音视频实时传递给互联网上所有人观看的互联网技术。由于其具有内容丰富,表现形势好,不受地域限制,交互效率高等特点,被广泛应用于 教育、体育、娱乐、电商 等领域。
我们玩物得志平台所涉及的直播就是直播技术应用及其广泛且商业化最为成功的电商领域,借助互联网直播技术,商家能够低成本且极高效地向来自世界各地的用户实时讲解商品,帮助用户足不出户就能买到心仪的商品。
直播分为三个重要部分:
而本文将结合玩物得志的电商场景着重向大家介绍一下玩物得志平台的直播播放端-直播播放管理中心的演进过程。
原有播放能力存在以下问题:
同时随着业务的增长,平台多业务均需要接入直播播放能力以保证内容丰富,以及流量的高效转化,针对播放能力主要有以下要求:
截至到目前,平台业务里已经有众多非直播间业务接入了直播播放器:
在我们开始正式讲解玩物播放中心方案之前,先简单了解下拉流过程,这能够帮助我们更好地理解直播拉流。
整个直播的数据传输过程主要由两组协议构成:
这里我们着重了解一下 H264这个标准,众所周知视频是利用人眼视觉暂留的原理,通过播放一系列的图片,使人眼产生运动的感觉。
单纯传输视频画面,视频量非常大,对现有的网络和存储来说是不可接受的。
为了能够使视频便于传输和存储,人们发现视频有大量重复的信息,如果将重复信息在发送端去掉,在接收端恢复出来,这样就大大减少了视频数据的文件,因此有了H.264视频压缩标准。
视频里边的原始图像数据会采用 H.264编码格式进行压缩,音频采样数据会采用 AAC 编码格式进行压缩。视频内容经过编码压缩后,确实有利于存储和传输。不过当要观看播放时,相应地也需要解码过程。因此编码和解码之间,显然需要约定一种编码器和解码器都可以理解的约定。就视频图像编码和解码而言,这种约定很简单:
编码器将多张图像进行编码后生产成一段一段的 GOP ( Group of Pictures ) , 解码器在播放时则是读取一段一段的 GOP 进行解码后读取画面再渲染显示。GOP ( Group of Pictures) 是一组连续的画面,由一张 I 帧和数张 B / P 帧组成,是视频图像编码器和解码器存取的基本单位,它的排列顺序将会一直重复到影像结束。I 帧是内部编码帧(也称为关键帧),P帧是前向预测帧(前向参考帧),B 帧是双向内插帧(双向参考帧)。简单地讲,I 帧是一个完整的画面,而 P 帧和 B 帧记录的是相对于 I 帧的变化。如果没有 I 帧,P 帧和 B 帧就无法解码。
在H.264压缩标准中I帧、P帧、B帧用于表示传输的视频画面。
I帧
I帧:即Intra-coded picture(帧内编码图像帧),I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)。又称为内部画面 (intra picture),I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。在MPEG编码的过程中,部分视频帧序列压缩成为I帧;部分压缩成P帧;还有部分压缩成B帧。I帧法是帧内压缩法,也称为“关键帧”压缩法。I帧法是基于离散余弦变换DCT(Discrete Cosine Transform)的压缩技术,这种算法与JPEG压缩算法类似。采用I帧压缩可达到1/6的压缩比而无明显的压缩痕迹。
它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
解码时仅用I帧的数据就可重构完整图像;
I帧描述了图像背景和运动主体的详情;
I帧不需要参考其他画面而生成;
I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
I帧不需要考虑运动矢量;
I帧所占数据的信息量比较大。
进行帧内预测,决定所采用的帧内预测模式。
像素值减去预测值,得到残差。
对残差进行变换和量化。
变长编码和算术编码。
重构图像并滤波,得到的图像作为其它帧的参考帧。
例如:在视频会议系统中,终端发送给MCU(或者MCU发送给终端)的图像,并不是每次都把完整的一幅幅图片发送到远端,而只是发送后一幅画面在前一幅画面基础上发生变化的部分。如果在网络状况不好的情况下,终端的接收远端或者发送给远程的画面就会有丢包而出现图像花屏、图像卡顿的现象,在这种情况下如果没有I帧机制来让远端重新发一幅新的完整的图像到本地(或者本地重新发一幅新的完整的图像给远端),终端的输出图像的花屏、卡顿现象会越来越严重,从而造成会议无法正常进行。
在视频画面播放过程中,若I帧丢失了,则后面的P帧也就随着解不出来,就会出现视频画面黑屏的现象;若P帧丢失了,则视频画面会出现花屏、马赛克等现象。在视频会议系统中I帧只会在会议限定的带宽内发生,不会超越会议带宽而生效。
I帧机制不仅存在于MCU中,电视墙服务器、录播服务器中也存在。就是为了解决在网络状况不好的情况下,出现的丢包而造成的如图像花屏、卡顿,而影响会议会正常进行。
P帧
P帧:即Predictive-coded Picture(前向预测编码图像帧)。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
【P帧的预测与重构】
P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
【P帧特点】
P帧是I帧后面相隔1~2帧的编码帧;
P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
由于P帧是参考帧,它可能造成解码错误的扩散;
由于是差值传送,P帧的压缩比较高。
B帧
B帧:即Bidirectionally predicted picture(双向预测编码图像帧)。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别,换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。
【B帧的预测与重构】
B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。采用运动预测的方式进行帧间双向预测编码
【B帧特点】
【为什么需要B帧】
从上面的看,我们知道I和P的解码算法比较简单,资源占用也比较少,I只要自己完成就行了,P呢,也只需要解码器把前一个画面缓存一下,遇到P时就使用之前缓存的画面就好了,如果视频流只有I和P,解码器可以不管后面的数据,边读边解码,线性前进,大家很舒服。
那么为什么还要引入B帧?
网络上的电影很多都采用了B帧,因为B帧记录的是前后帧的差别,比P帧能节约更多的空间,但这样一来,文件小了,解码器就麻烦了,因为在解码时,不仅要用之前缓存的画面,还要知道下一个I或者P的画面(也就是说要预读预解码),而且,B帧不能简单地丢掉,因为B帧其实也包含了画面信息,如果简单丢掉,并用之前的画面简单重复,就会造成画面卡(其实就是丢帧了),并且由于网络上的电影为了节约空间,往往使用相当多的B帧,B帧用的多,对不支持B帧的播放器就造成更大的困扰,画面也就越卡。
【显示和解码顺序示意图】
弄清楚了直播数据是如何从主播端到观众端的输送之后,我们发现整个拉流过程并没有想象中那么神秘,相反还非常简单,将流程抽象出来看与客户端去加载一张图片、请求一个业务接口的过程并无太大差别,都是 建立连接-读取数据-解析数据-渲染数据 的过程。
而由CDN服务商提供的拉流SDK,实际上就是对整个流程基础能力的封装,将 建立连接-读取数据-解析数据-渲染数据 这个过程中的细节丰富并封装成一个独立的包体,以供上层业务方通过一系列关键配置来控制使用。
那么作为上层业务方的我们又是怎么根据自身业务场景去定制这些三方基础能力来满足自身业务需求的呢?
接下来我们回到玩物得志的业务场景中来。
1、我们选择针对三方 SDK 进行二次接口封装,形成 Adapt 层,即基于三方基础能力与我们的业务场景定制一个属于我们自己的直播播放器;
2、全面接管直播播放器 拉流配置、流类型、缓存时间、缓存自动调整时间、重试次数、重试间隔、首帧 等关键配置,在让业务层深度使用播放能力的同时对三方基础能力完全无感;
3、为了方便我们所有的业务都能够或快捷、或深度使用使用直播播放能力,我们的播放器在满足能够简单传入播放链接就能够快捷使用的同时,也要满足更深度地感知播放器的各个细节状态以及更加细致的控制。
1、为播放器设计权重体系,高优先级拥有优先播放权,能够抢用低优先级业务的播放器,每块需要播放器的业务仅需要关注自身的播放权重即可,不需要额外处理互斥逻辑;
2、设计播放管理中心,管理播放器权重,全局统筹调配播放能力:
3、管理播放器播放与流复用逻辑;
4、管理播放器的各种回调,向代理分发并兜底处理。
至此,我们已经满足了 A 业务在播放直播的时候,B 业务不允许播放直播;A 业务停止播放直播的时候,B 业务要能够马上开始播放的要求,并且支持了全平台所有业务都能够简洁、快速地使用直播的能力。搞起看效果:
OK,效果上来讲,业务方的需求看上去已经完全实现了。
BUT经过我们自己的仔细体验之后,我们对相同流切换业务场景时的流畅性并不满意!同一路流我们明明已经在 A 业务已经成功拉流了,但是切换至 B 业务的时候,还是会重新获取播放器,然后让播放器重新拉流。
流程上来说这一步是多余的;体验上来说这个切换一点也不丝滑;技术上来说播放中心还有优化的空间。
于是,我们有了以下思考!
分析我们的播放流程,我们发现:
那么我们是不是就可以用拉流地址来唯一标识一次播放行为呢?
于是,我们有了增加了如下逻辑:
经过测试,我们发现,效果与预期中完全一致,既能够保证流复用,又不会影响播放器渲染!优化后的效果如下:
而最终我们的直播播放中心架构演化成的样子如下图所示:
各业务模块仅需要管理好自己业务的播放权重,就可以随意增加、删除、调整自己在互斥场景中的优先级了!下边给大家展示下集成播放中心后的最终效果:
与此同时,不管未来业务集群中有多少业务需要接入我们的直播播放能力,都只需要找好自己的权重就行,接入成本也仅需要两行代码即可搞定播放、互斥、复用等一系列逻辑:
直播播放管理中心自上线以来,除了支撑主站的各直播场景,还以很低的开发成本支撑了flutter小窗、flutter沉浸式播放、web小窗、TV台等业务快速接入直播能力,同时对主站逻辑未产生任何影响。
未来,我们计划在直播秒开,清晰度切换方向上做出更好的体验以支撑平台业务发展。