从直播链路理解优化与问题

安全咨询 0 730

为什么拉不到流?

直播是一条完整的链路,链路上任何一个环节中断都会导致拉流失败的情况;

推流端-收流节点中断:单路流所有档位、所有观众将出现进房时拉不到流或直播间内卡顿;

收流节点-源站中断:一般不会出现,故障源站上的所有流将出现异常;

转码异常:某一个或几个转码档位出现异常,源流正常,观看转码档位的观众报障;

源站-拉流节点中断:从故障边缘节点上拉流的全部观众将会异常;

拉流节点-拉流端中断:单用户拉流出现故障。

前述情况主要描述的链路上某个环节中断导致的拉流失败,有时拉流失败其实是首帧耗时过长造成的,这种情况在拉流成功率指标波动分析时较为常见:首帧耗时增长导致了拉流成功率下降。


为什么首帧慢?

首帧分阶段分析

3ad768a807fd4981b1baf976dc65e1aa.png

以 HTTP-FLV 为例,从触发播放器的 play 接口到最终完成首帧渲染,需要经历以下这些过程:


DNS 解析: 决定了要从 CDN 的哪个边缘节点拉流。这一过程在拉流 SDK 以及播放器内核中都会发生,后者往往是因为 SDK 层没有解析到 IP 地址。DNS 解析耗时通常在 100ms 左右(localDNS,使用 HTTP-DNS 会进一步增加),因此 HTTP 场景下,我们可以采用 IP 直连的方式规避 DNS 耗时。

实现 IP 直连需要在拉流前获取到 IP 地址,其关键在于 DNS 缓存(缓存之前的 DNS 解析结果,拉流 SDK 层的 DNS 策略)和 DNS 预解析(在 App 启动之后一段时间,对可能用到的所有域名进行 DNS 解析并缓存结果,节点优选 SDK 的核心逻辑);

缓存的 DNS 结果会过期,尤其是 CDN 进行节点覆盖调整时,缓存的 DNS 可能会导致调整不能及时生效,因此需要定期更新 DNS 结果;

用户发生跨网时,由于运营商变化,也会导致此前缓存的 DNS 不可用,因此需要在网络状态发生变化时更新 DNS 结果。

TCP 建联: 与边缘节点建立 TCP 连接,这一过程的耗时主要是三次握手,耗时1-RTT。要优化建联耗时,可以通过 TFO(TCP Fast Open,Wiki),或使用 UDP 协议(QUIC、KCP)。

TCP 首包: HTTP 响应完成,此时开始返回音视频数据。如果此时边缘节点上有这路流的缓存,那么将直接返回缓存数据;如果没有命中缓存,TCP 首包返回之前将发生回源。要优化首包耗时,核心思路在于降低回源率,常见手段有增加边缘节点缓存保持时间,对于大型活动提前进行预热、以保证更多边缘节点上有缓存。

视频首包: 耗时主要发生在 avformat_find_stream_info 函数调用,播放器在播放前需要探测一定量的流数据,以确定流格式、编码等流信息。由于目前在各个宿主中播放的格式相对固定,因此不需要探测过多的数据即可确定流信息。所以可以通过调节探测的数据量以优化视频首包耗时。通过减小探测数据量,首包耗时下降 300ms 左右。

降低流数据探测的数据量也可能会带来不良后果。当探测的数据量过少时,可能会导致读取到的流信息不全,例如只探测到音频数据、没有探测到视频数据(尤其是在音视频数据交织不均匀的情况),最终出现有声无画或声音正常、画面卡住的问题。

视频首帧解码和视频首帧渲染: 分别完成视频首帧解码和视频首帧渲染,优化的主要方向是 decoder 及 render 实例的预初始化等。

快启buffer

为了实现秒开,各家 CDN 都在边缘节点上开启了快启 buffer 策略。快启 buffer 的核心逻辑是基于边缘节点上的缓存(GopCache),调节缓存数据下发的具体数据范围,同时保证视频数据从关键帧开始下发。快启 buffer 也存在不同的策略,例如上限优先或下限优先,后者会要求对接的 CDN 厂商按照下限优先的原则下发缓存数据,比如下限 6s ,会先在缓存数据中定位最新 6s 的数据,然后向 6s 前的旧数据查找第一个关键帧下发(解码器要从关键帧开始解码,因此一定需要从关键帧开始下发)。

为什么会卡顿?

拉流发生卡顿的直接原因是播放器中的缓存耗尽,此时播放器卡住其实是在进行 Rebuffering 。


导致 buffer 耗尽的直接原因是播放器中 buffer 消费的速度高于 buffer 接收的速度:最为常见的例子就是当前拉流端的带宽显著低于视频的码率。同时,在带宽充足的情况下,链路上前置环节的中断也有可能导致拉流端卡顿。


因此为了对抗卡顿,一个最直接的方法就是在播放器中缓存更多数据,以期在缓存数据耗尽之前渡过网络波动。最直接的做法就是增大播放器的最大缓存,使播放器能够缓存足够多的数据。


但直播场景的特殊性在于直播内容是实时产生的,在最大缓存允许的情况下,播放器中的缓存完全取决于当前链路上有多少缓存(还记得 GopCache 吗)。


在播放器数据消费速度不变、带宽充足的情况下,播放器在获得了链路上所有的缓存后,buffer长度将不再变化,也就意味着播放器只能依赖这些缓存来对抗卡顿。在能获得的缓存总量有限的情况下,进一步增强现有的缓存对抗卡顿能力的手段就是调整播放器消费数据的速度,例如在首帧之后判断当前水位( buffer 长度)以决定是否起播(对应起播 buffer 策略),或在水位较低时降低播放速度(对应网络自适应策略)。


在有限的带宽条件下,如果能够更有效的利用带宽、采用更加高效的传输协议,也可以有效地对抗弱网条件下的卡顿问题。因此 KCP、QUIC、QUICU 等相较于 TCP 更加高效的协议被应用到了音视频传输当中。除此之外,在特定的带宽情况下选择合适的码率,也是降低卡顿的有效手段之一,因此ABR也在直播中得到了应用。


实际上“卡顿”在用户反馈中涵盖的情况很多,例如:App 本身的卡顿,或者 CDN 某些操作(数据积压时丢弃视频数据造成的视频卡住、声音正常,或者在数据积压时主动使播放器报错发生重试进而导致播放器出现鬼畜),甚至视频本身帧率较低,都会导致用户出现“卡顿”的感觉。为了更加贴近用户对于卡顿的感受,我们增加了视频渲染卡顿这一指标。


为什么有延迟?

为了对抗卡顿,我们需要在直播链路中增加缓存,但是缓存的引入必然导致延迟的增加。


例如在抖音上线低延迟 FLV 之前,端到端延迟在5~8s,而这些延迟的引入主要原因就在于 CDN 边缘节点上的 GopCache --只有缓存到足够的数据才会进行下发,在这个过程中便引入了时延(当然不管是推流端采集、服务端中间链路都会引入延迟,但是相对于缓存引入的延迟影响较小)。


在客户端播放速度不变的情况下,延迟会一直保持下去,另外在发生卡顿(且未触发重试)时延迟会不断累积。


因此通过调节边缘节点的 GopCache 大小可以直接影响到链路上的端到端延迟。与此同时,Gop 的大小也会影响到不同用户之间的延迟差,在具体场景中就体现为两个观众的延迟存在差异(比如内购会别人看到主持人说了“3、2、1,开抢”,你才看到“3”),两名观众进入直播间的时间差即使很短,但是延迟差可能达到一个 Gop(以下图为例,假设快启 buffer 下限为 1.3s ,用户分别在 1.2s 和 1.4s 进入直播间,延迟分别是多少?)。


也许您对下面的内容还感兴趣:

留言0

评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。