基于WebRTC的低延迟视频直播

2021-09-01 16:58:21 浏览数 (1)

Photo by Wendy Wei from Pexels

随着直播行业的迭代更新,传统直播方案的延迟问题越发凸显。本文来自融云联合创始人、首席架构师 李淼在LiveVideoStackCon 2019深圳站中的演讲,在其中他详细介绍了如何利用WebRTC低延迟音视频传输的特点,解决传统直播方案的延迟问题。

文 / 李淼

整理 / LiveVideoStack

融云是一家全球互联网通信云平台,主要提供即时通讯、实时音视频相关业务,今天主要向大家介绍一下我们基于WebRTC的直播解决方案。

主要内容分为以下四个部分:

  1. WebRTC直播的优势
  2. WebRTC直播的过程
  3. WebRTC直播技术细节
  4. WebRTC直播服务架构

1. WebRTC直播的优势

为什么要选择WebRTC做直播呢?

WebRTC自身最大的优势:低延时、流量更少、性能好。

  1. 低延时: 目前直播场景大多基于RTMP、HLS的方式,在不考虑网络延时的情况下都会产生秒级延时,但是WebRTC实时通讯在不考虑网络链路的情况下,延时可降到100-200毫秒左右。
  2. 流量更少: RTMP或者HLS主要基于TCP传输,WebRTC是基于UDP的传输, UDP协议的头小。TCP为了保证传输质量,因此会产生很多ACK,在网络不好的情况下会产生很多重传包,而WebRTC传输是基于RTP和RTCP,重传策略是基于NACK完成。
  3. 性能好: TCP传输会涉及到拥塞算法、会话保持等相关的逻辑,在整体性能上会比UDP稍差一些。

WebRTC做直播第二大优势:主播端与观众端的方案一致。

直播会涉及推流方面的问题,例如使用OBS推流或者WebRTC推流,而观众端为了适应推流平台,会涉及接入HLS或者RTMP的播放器,如果选用WebRTC就可以达成主播端和观众端方案的一致性。对于开发来讲,编码量会更少一些,因此对于后期整体的代码维护,甚至团队的人员构成都可以保证资源消耗最低。

WebRTC做直播的劣势是标准的直播解决方案少,即我们选择WebRTC开源服务会涉及到的lincode、mediasoup大部分都在解决p2p通讯或者多人音视频通讯,但是对于直播方案来讲,很少能提供相应的解决方案。

首先了解p2p通讯或者多人音视频通讯与直播通讯的差异是什么:

  1. 观众人数的差异。 RTC多人音视频交互参与人数少,但是直播场景可能会发生巨大规模的变化,当几千甚至到几百万观众同时观看同一场直播,传统的WebRTC解决方案并不能解决。
  2. 直播会选择MCU。 直播场景中,会涉及到多人连麦的场景,目前开源的方案大多是基于SFU解决方案,多轮分发势必会造成流量增加,为了解决流量问题直播解决方案一般会选择MCU,即服务端进行混流下发的方式节省流量。
  3. 观众端无信令。信令服务器主要是在RTC通讯时主要用于通知各个端,当有新进入直播间的主播,通知服务端重新发起协商,通知订阅,对于观众端一般都不会选择通过信令的方式通知观众,因为信令服务器下发百万级观众的通知会产生很大的延时。
  4. 如果观众端不采用信令方式一般会需要固定URL,观众只需要打开URL视频流并订阅即可实现。
  5. 频繁变更房间。 在泛娱乐直播场景下,很多观众会频繁变更房间,现在很多APP可以通过向上滑动窗口实现切换房间,据统计在泛娱乐场景下观众在一个房间平均驻留时长只有十几秒,如果对内容不感兴趣可切换到下一个房间,这样就会造成观众端对分发服务器订阅运营的压力较大。
  6. 视频秒开。 WebRTC做p2p通讯可以很快看到多人或者对方的视频,其通过PLI的方式直接让对端发送关键帧。直播与RTC其实有很大的差异,因为无法在很多不同时出现的观众端向主播端通过PLI来发送关键帧,他们会出现在各个时间中,这样很容易造成主播端实时产生关键帧,形成网络风暴。视频秒开虽然是RTC和直播中的一个共同场景,但是整体的处理方式是完全不一样的。

2. WebRTC直播的过程

WebRTC支持低延时直播,那么如何通过WebRTC来完成直播场景的构建呢?

使用WebRTC首先会涉及信令相关的交互逻辑,观众端不需要接入信令,如图便是普通直播场景完整流程的时序图。主播客户端利用业务服务器申请开通直播间,提交房间号,生成一个URL,将URL保存到业务服务器(另外一种方式是URL由业务服务器生成),当URL生成后,主播端即可向URL进行推流。观众端在打开APP或者一个场景时,通过点击链接即可获得URL,根据URL发起订阅,进行拉流。

WebRTC直播的流程是完全不一样的,WebRTC直播的中间过程没有信令的接入逻辑。

如图所示是WebRTC不使用信令的方式通过客户端进行订阅的简要整体流程。

首先是主播客户端向MediaServerA发起发布资源,其中涉及SDP信息交换,此接口是MediaServerA提供的HTTP的接口,该接口可以接收SDP信息,MediaServerA接收到SDP请求后转移到MCU混流服务器上,主播客户端通过用户ID的方式接入到MediaServerA,MediaServerA通过房间ID进行聚合,MCU完成合流之后向下推送到MediaServerB,即直播间的视频流,MediaServerA是以人的方式进行分散,MediaServerB通过房间的方式进行聚合,MediaServerB生成相应URL用于描述资源所在位置,再将该URL含有SDP的answer信息依次传递到主播客户端上,主播即可接收资源信息的描述,主播可以通过自身与业务服务器的交流接口将URL上传到业务服务器上,此时观众客户端可通过点击链接或者其他方式获取URL,最后完成最下方的订阅步骤。(整体流程图中省略了很多推流的逻辑)

关于MCU的注意事项

在海量直播的情况下,如果涉及到连麦的情景,为了降低流量就需要对服务进行合流处理,合流处理需要注意以下几点:

  1. 合流,需要保证同一个房间的所有主播可以路由到同一个MCU服务器上,例如使用哈希方式定位,以房间ID进行哈希定位到一台服务器上,即可完成多个主播路由同一个服务器的任务。但是这其中也存在很多问题,首先以房间ID的方式将合流的请求打散到多台服务器上,但如果某一个房间主播特别多,会出现同一个MCU的服务器负载太大,针对这个问题,要对房间ID进行聚合,就需要按照资源配比的方式负载,因此就要一个地方可以集中存贮每一个MCU服务器上的房间状况,根据房间的方式解决负载,但是在多人连麦混流时,是无法提前预测到负载情况的,我们正在开发通过整体的运行状况,将MCU服务器上一些资源开销比较小的进行无损迁移到资源消耗较低的服务器上,以此来降低高负载服务器的压力。
  2. 大部分情况下,一个房间只有一个主播就不会出现合流的请求,因此就不需要进行编解码,可以直接将主播流向下透传到MediaServerB。

针对于观众端订阅的子流程,如上流程图拆分为上下两部分。

第一部分是No SDPCache,即客户端没有任何SDP缓存,可理解为客户端首次与服务器进行连接,第一次链接的正常流程是一个SDP的offer通过HTTP的接口向MediaServer发起订阅,交互后SDP会在客户端进行缓存,完成缓存后就进入WebRTC的标准通讯流程,后MediaServer再向客户端进行推流。

第二部分是为了应对客户端频繁切换房间进行的优化部分。优化的点是客户端不需要频繁的进行ICE以及DTLS证书交换,即如果有SDPCache情况下,可直接从客户端生成offer,通过已经缓存的Answer,将其中的ssrc信息进行替换,在客户端上setRemote SDP,在不需要与服务器进行数据交换的情况下即可完成整体的SDP交换,后续客户端向服务器发起HTTP请求订阅某一个房间的流时,MediaServer直接向下推流即可。

既然与服务器已有订阅交换的情况为什么还要使用本地缓存SDP的方式设置SDP呢?

后续客户端已经不再与MediaServer做ICE以及URL交换,当服务器收到SDP的offer时,就已经构建了整体的订阅关系,服务器向下发送Answer的同时有可能出现该Answer还没到达客户端但服务器就已经进行推流了。使用本地缓存SDP的方式设置SDP可以避免Answer不再依赖于订阅接口,因此只要设置完成SDP,服务器向下推流使得整体切换变得非常平滑。

我们的URL是通过二进制方式进行描述,再对二进制文件进行base64下发到客户端,客户端解析后整体的数据结构大体如图所示,其中包括房间ID(用于确定观众端订阅了哪个房间)、服务器ID(用于确定房间发布的第一道流所在的服务器的位置,即MediaServerB),这些都是为了后续构建直播网络确定数据所在位置,其次是关于视频流的SSRC信息的描述,将URL进行序列化后即可发送到客户端,客户端以及服务器通过数据进行反序列化获取相关数据以及信息。

在完成上述流程之后,对于直播场景还有一些基本诉求。

首先,房间号不变的情况下,一切都不能变。其中一种场景就是主播端由于网络原因断开连接,再重新与服务器进行建联就意味着要重新交换SDP,其中的资源信息可能全部会发生变化,但是对于观众端来讲,观众一直都在线等待。由此得出结论,房间号不变的情况下,所有URL信息都是不能发生变化的。因此就要对传输流包内的RTP、RTCP包加工,如图为真实主播房间的源流服务器整体的交互流程,MCU向源流服务器进行SDP交换,要从SDP中将所有SSRC相关的信息全部提取,保存对应关系,其中对应关系的生成规则就是通过房间ID 流属性进行哈希变换,即成为转换之后的SSRC信息,通过这种方式意味着房间ID不变的情况下,则其中的SSRC信息就不会发生变化。即使主播端频繁的加入、退出,或者有新的主播产生连麦请求,都可以保证所有的SSRC信息不变。

实际中我们在SSRC、RTP、RTCP包完成替换之后,向下分发,也不一定对于观众端不产生影响,还会涉及一些其他注意事项。例如,需要保证替换的RTP包的SeqNumber不能中断,如果不连续可能会造成客户端产生大量的NACK,向服务器要求重传包,会造成整体网络的拥塞。在SeqNumber处理完成后,需要处理Timestemp,即本身RTP中的时间戳,如果时间戳处理不好就有可能造成视频的卡顿现象,或出画音不同步的情况。

根据流程设计服务器与客户端交流接口:

  • 发布/取消发布流接口,正常情况下与RTC的接口一样,对于主播来讲不需要知道观众端如何进行订阅,正常发布流即可。
  • SFU的订阅/取消订阅,针对多主播在同一个房间下的连麦情况,主播之间的交流就是RTC的交流,此过程与直播的关系不太大。
  • MCU的订阅/取消订阅,即观众端与媒体Server的交流。

3. WebRTC直播技术细节

3.1 WebRTC直播的技术难点

首先将WebRTC应用在生产环境下,要解决以下几个问题,

  1. 如何做到秒开视频? 基于RTC交流情况下,可直接通过PLI交流,向发布端索要关键帧,对于接收端立即完成视频渲染。但是对于直播场景,海量的观众向主播或MCU索要PLI是不现实的。
  2. 如何降低MCU带宽压力? 通过绕开索要PLI的方式降低MCU带宽压力
  3. 如何避免流量风暴? 在MCU服务器向MediaServer服务器发送视频包时,如果产生丢包情况,则意味着所有的观众端都会收到不连续的RTP包,则所有观众端都会向服务器索要NACK,就会产生全网的网络流量风暴。

3.2 GOP缓存结构

为了解决以上问题,服务器端需要加入Gop Cache数据结构,构建基于Gop的缓存结构。

如图中间部分,是一个有序的KV存储,Key表示SeqNumber,即RTP包的序号,Val表示包的VideoRTP,每个包都会涉及到I帧或者P帧,在缓存时要对这些包进行简单的解析,区分出是I帧还是P帧,Gop缓存的起点是以SPS,包含很多视频解析参数。

上一层是对于视频包的解析处理。下一层是对音频包的缓存处理。音频包的处理不会涉及到I帧或者P帧等一些逻辑处理,对于音频包采用的是Timestamp,利用时间戳作为Key,在真实使用中视频包的Timestamp找出对应范围内的音频包下发,另外还涉及实现其他应用的指针。

如何使用Gop缓存呢?

  1. 对于NACK的处理 NACK即客户端发生丢包向服务器索要补包的请求。一种方式是利用Gop直接通过RTP通道向观众端进行下发补包,另一种方式是当服务器本身也没有这个包时,则大量的NACK请求会发到MCU服务器上。因此就需要对第一个发送NACK请求的用户进行特殊处理,即在对应地方插入一个SeqNum,产生一个空值,后续再有NACK要求补包的情况,则不会进行下发,正常处理其他NACK请求,直到MCU服务器发送回NACK包补充完整,再根据回传请求进行下发到所有的观众端。
  2. 关于首帧处理首帧处理的逻辑是:客户端在第一次与MediaServer完成交互后,会对SPS以及I帧进行一次性下发(图中的“正常下发”表示循环无间隔进行下发),在I帧完成下发之后会涉及到P帧的下发。在低延迟直播的情况下,需要考虑在Gop下发后客户端需要能够快速追上主播端的发流,所以在观众感知不明显的情况下会对P祯和B祯就会采用1.1或1.2倍速下发,,直到所有包能够追上主播端或MCU端下推包的进程,后续在MCU合流完成后的整体时间即可同步,延迟会降到最低。

为了考虑内存的使用效率对GOP缓存做基本的控制,有以下两个控制策略。

  1. Gop缓存策略 只缓存两个Gop,在真实的应用过程中,很多Bug的发生是无法控制的,例如在前一个Gop包内存储量特别大的情况下,当新一个Gop替换掉旧Gop时,产生的被中断的Gop包无法下发到客户端,此时就会产生大量的NACK,要求重传逻辑,而且在补包重传的情况下Gop缓存是无法命中的,因此会造成客户端一直卡顿,因此缓存固定个数的Gop在方案初期即被否定掉了,因此变成缓存固定包数,例如Gop只缓存4000个包,来一个新包利用尾部指针弹出最后一个包。
  2. I帧控制策略 在包的缓存完成之后,希望Gop的缓存不要特别大,对于某些场景的视频变化幅度不大的情况,整体产生关键帧的周期会很长,因此服务器需要对Gop进行相关的逻辑控制,定期要求发布端通过产生关键帧或者SPS。

对于服务器端有两个策略,其一是单个Gop允许的最大包数是多少,其二是允许单个Gop的最长时长是多少。

对于最大包数的策略采用整体缓存数的45%作为产生PLI的阈值,利用5%来抹平时间差;另外通过最大时长控制策略是通过首帧来进行追包请求,希望追包的时间尽可能短,这样观众端和主播端进度尽可能一致。对于这两个策略,只要其中一个策略满足,即可产生PLI向发布端所求关键帧。

4. WebRTC直播服务架构

支撑海量用户服务端的架构是如何的?

单个数据端的服务架构由负载均衡、反向代理以及MediaServer组成,当单个MediaServer处理直播达到上限时就会通过扩充服务器的方式弥补不足。

完成单个数据中心的处理后要应用到实际生产环境中,还有很多额外的工作。

例如需要有音视频审核能力(要按照法律要求过滤掉不合格内容)、云端录像能力(当直播中出现点播的请求时,就要在推流的过程中进行转码录像)、视频标注能力等,在满足若干额外工作能力之后,至少可以完成一个数据中心的一个直播工作构建。

  • 在生产环境中,如果用户分布很广,还会涉及到网络相关的设计,需要客户端可以进行就近接入,以缩短绝对物理距离的方式使客户端到MediaServer之间的方式减少网络传输延时。
  • 对于跨数据中心进行级联的情况下,只保证传输一路。
  • 在构建直播平台时,尽可能利用IaaS厂商能力,可充分利用其提供的关于链路优化以及网络传输等能力。

针对于全球或者区域中心分布的简单示意图

首先主播会选择就近联结数据中心,向数据中心产生合理请求再向本数据中心进行发布,其他数据中心向该数据中心级联请求拉流,每个数据中心只有一台服务器负责拉流,到单个数据中心进行分发,本客户端即可通过服务器将流拉取走,这样可以使主数据中心的资源消耗最低,对于单数据中心的流量和网络压力也可以降到最低。

解决链路优化,就要考虑到就近接入的问题,最常用的方式就是通过SmartDNS,通过计算客户端IP的方式给观众端反馈就近节点,但由于SmartDNS本就有导致DNS分配节点不准确的问题,因此会通过使用HTTP DNS解决相关问题。当SmartDNS和HTTP DNS都不准确的情况下,优化过程中又引入了BGP Anycast链路,BGP Anycast链路已经超越了基于IP分配的方式,因为Anycast IP对于很多客户端在全网看到的是同一个IP,但是这个IP服务器地址是可以按照运营商的方式就近进行选择。即在通过SmartDNS分配的服务器质量不够满意的情况下会转入BGP Anycast链路使之节点分配更准确。

对于物理链路的优化,由于服务器与服务器之间会涉及到很多接口调用,对于接口调用会使用很多Qos进行保障,这些接口调用一般选择跨国专线的方式,使接口调用的质量得到保证。

视频流如若通过跨国专线,成本会大大增加,因此选择二级级联的方式,使音频流和视频流通过二次转发的方式提高数据传输质量。

  • 在做WebRTC直播时首先模拟URL订阅发布模型,剥离信令的发布模型
  • 保证同一个房间的资源不变,即SSRC的替换、SeqNumber的替换以及Timestamp的替换,为了保证不产生网络风暴,
  • 做到视频秒开,基于GOP的视频缓存策略。
  • 支撑海量用户或者在地域分布非常广的情况,进行级联策略和网络优化

LiveVideoStackCon 2020

上海/北京/旧金山 讲师招募

2020年LiveVideoStackCon将持续迭代,LiveVideoStackCon将分别在上海(6月13-14日),北京(9月11-12日)和旧金山(11月)举行。欢迎将你的技术实践、踩坑与填坑经历、技术与商业创业的思考分享出来,独乐不如众乐。请将个人资料和话题信息邮件到 speaker@livevideostack.com 或点击【阅读原文】了解成为LiveVideoStackCon讲师的权益与义务,我们会在48小时内回复。

0 人点赞