Apple 低延迟HLS分析

HLS协议

HTTP Live Streaming(HLS)是Apple公司主导提出并实现的基于HTTP的自适应码率流媒体通信协议(RFC8216),作为其产品QuickTime,Safari,OS X和iOS的一部分,在Apple的产品生态链中占有重要地位。同时,越来越多的第三方厂商的产品,如Microsoft Edge,Firefox和 Google Chrome、安卓操作系统也都实现了对HLS的支持。而且有大量的流媒体服务器都支持HLS。

HLS类似于MPEG-DASH,通过将理论上可以无限时长的直播流分解为一系列基于HTTP的小文件下载来完成流媒体的传输,每次下载获得整个流的一小部分。M3U8播放列表里也可以包含不同码率的节目流列表。

因为HLS基于标准HTTP协议,所以它可以穿透任何允许通过标准HTTP流量的防火墙或代理服务器,这一点比基于UDP的协议(比如HLS出现之前常用的RTP)优越。这也允许它基于常规 HTTP服务器提供内容,并通过HTTP CDN进行内容分发,这一点对于流媒体厂商和CDN厂商来说是一个非常大的便利,可以复用大量现成的早就非常成熟的针对HTTP的解决方案。基于HTTP还让客户端的实现变得比较简单,复用已有代码按playlist文件的指示顺序下载流媒体片段即可。该标准还包括标准加密机制和基于HTTPS的安全密钥分发机制,它们共同提供了一个简单的DRM系统。该协议还提供快进和快退以及字幕集成等功能。

HLS的方案同时兼容了直播和点播业务,并且很好地利用了已有的基础设施,在它提出的那个时代,着实让大家眼前一亮。

Apple的HLS技术lead Roger Pantos从2009年5月1日起开始将HLS提交为IETF草案,经过7个版本24次草案修改,直到8年后的2017年8月,最终成为RFC 8216。

低延迟HLS技术草案

2019年的WWDC上,Pantos宣布了最新的HLS草案,今年的变化旨在减少实时视频流的延迟。这个消息一出,业界反响很大,几家欢乐几家愁。高兴的是终于苹果正视这个被广为诟病的问题,开始从协议层面开始提出解决方案;也有一些厂商高兴不起来,因为他们已经在HLS的基础上实施了一些自己的低延迟改进方案,苹果方案的提出,一定程度上导致他们过去的研究和投资打了水漂,苹果这次提出的新方案技术上需要比较复杂的技术栈来支持,也导致不少厂商颇有微词。

为了搞清延迟问题的来龙去脉,首先我们看看HLS的基本内容:

简单来说,HLS包含两部分,m3u8文件(playlist)和承载具体媒体内容的文件(ts、CMAF、fMP4等),客户端根据m3u8的指示下载媒体内容并定时刷新m3u8文件获得最新内容列表。

以下是一个包含多种码率的master playlist(如果没有多个码率,这个m3u8 playlist可以没有):

1#EXTM3U
2#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1.640028,mp4a.40.2"
32M/mid.m3u8
4#EXT-X-STREAM-INF:BANDWIDTH=4000000,CODECS="avc1.640028,mp4a.40.2"
54M/high.m3u8
6#EXT-X-STREAM-INF:BANDWIDTH=500000,CODECS="avc1.640028,mp4a.40.2"
70.5M/low.m3u8

以下是其中一个playlist的m3u8内容(如果只有一个码率,可以只提供这个m3u8):

 1#EXTM3U
 2#EXT-X-TARGETDURATION:4
 3#EXT-X-VERSION:3
 4#EXT-X-MEDIA-SEQUENCE:0
 5
 6#EXTINF:3.96667,  
 7fileSequence0.ts
 8#EXTINF:3.96667,  
 9fileSequence1.ts
10#EXTINF:3.96667,  
11fileSequence2.ts
12#EXTINF:3.96667,  
13fileSequence3.ts

以上出现的tag描述如下,还有很多tag,具体可以参考RFC:

EXT-X-TARGETDURATION通常就是GOP(Group Of Picture)的大小,如果EXT-X-TARGETDURATION是6秒,那么需要6秒编码完成一个片段并更新playlist,客户端采用轮询方案来获取下一版playlist, 轮询的时机在(6,12)秒区间内,都将获得仅包含第一份片段的playlist,并且playlist的请求和响应本身需要一个RTT来回传,在移动网络上,这个可能增加数百毫秒的延迟,之后才是片段的请求,拥有足够多数据以后,播放器才能开始播放(有的播放器要缓存2-3个片段才开始播放,也就是延迟可能高达18秒以上)。

CDN的缓存机制也会导致延时加长:

如下图所示,源站已经前进到第四个片段,但是CDN边缘节点还缓存着上一个版本(只包含3个片段)的playlist,必须等文件的TTL过期边缘节点才会去获取最新版本的列表,所以最差情况下又要多等待一个TTL。而这个缓存TTL也不能取消,如果每个端上的请求到达CDN边缘节点时都去找源站要最新版本,源站就可能会被流量冲垮

为了将10-30的延迟降低到2秒以下,苹果提出了5点改进

  1. 减少片段发布延迟
  2. 优化片段发现机制
  3. 消除片段请求时间
  4. m3u8采用增量升级机制
  5. 加速不同码率直播流切换速度

下面针对每个改进做一个介绍

减少片段发布延迟

为了减少发布延迟,引入了EXT-X-PART和EXT-X-PART-INF tag,示例m3u8如下所示,也就是在片段还没有最终成型的时候就将片段的part(类似CMAF的chunk)先发布出来,EXT-X-PART-INF包含了片段part的最长时长,服务器必须每隔EXT-X-PART-INF时长就发布一个片段part。同时part必须是“完整的”part才能发布,也就是说不能再阻塞客户端、占用CDN额外时间等待片段part生成。这个片段part的格式和CMAF也是兼容的。

 1#EXTM3U
 2#EXT-X-TARGETDURATION:4
 3#EXT-X-VERSION:3
 4#EXT-X-PART-INF:PART-TARGET=0.202000
 5#EXT-X-MEDIA-SEQUENCE:0
 6#EXTINF:3.96667,  
 7fileSequence0.ts
 8#EXTINF:3.96667,  
 9fileSequence1.ts
10#EXTINF:3.96667,  
11fileSequence2.ts
12#EXTINF:3.96667,  
13fileSequence3.ts
14#EXTINF:3.96667,  
15fileSequence4.ts
16#EXTINF:3.96667,  
17fileSequence5.ts
18#EXTINF:3.96667,  
19fileSequence6.ts
20#EXT-X-PART:DURATION=0.20000,URI="filePart7.1.ts"
21#EXT-X-PART:DURATION=0.20000,INDEPENDENT=YES,URI="filePart7.2.ts"
22#EXT-X-PART:DURATION=0.20000,URI="filePart7.3.ts"
23#EXT-X-PART:DURATION=0.20000,URI="filePart7.4.ts"
24#EXT-X-PART:DURATION=0.20000,URI="filePart7.5.ts"
25#EXT-X-PART:DURATION=0.20000,URI="filePart7.6.ts"
26… …
27#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,CAN-SKIP-UNTIL=24,PART-HOLD-BACK=0.610

当part足够多,能够拼成一个完整的片段时候,EXT-X-PART内容就从m3u8中消失,合并成一个传统的EXTINF项。也就是说part只用于描述直播的“最前沿”(Live edge),当part数据不再是最前沿的直播内容时就被合并删除。

优化片段发现机制

优化片段发现机制采用的方法是阻塞式m3u8加载,草案里增加了EXT-X-SERVER-CONTROL来告知客户端服务端支持的低延迟功能特性,包括支持阻塞式m3u8加载机制和后面要说的m3u8增量更新机制。每个低延迟m3u8都必须带上这个tag,并且内容应该一样。CAN-BLOCK-RELOAD=YES就是告知客户端服务器支持阻塞式m3u8加载机制。客户端看见之后就可以直接开始发起下一个片段的请求,具体过程是这样:

m3u8里面包含EXT-X-MEDIA-SEQUENCE,客户端可以根据收到的片段数和EXT-X-MEDIA-SEQUENCE基数,计算出下一个片段的序列号,然后直接找服务端请求对应的m3u8:

GET https://example.com/live.m3u8?_HLS_msn=1803

上述请求表示当直播流中出现1803的ts的时候,停止阻塞,返回m3u8内容。

结合1的内容,允许服务端下发片段part的话,则请求如下:

GET https://example.com/live.m3u8?_HLS_msn=1803&_HLS_part=0&_HLS_push=1

上述请求表示当直播流中出现1803的第一部分(_HLS_part=0)的时候就停止阻塞,返回m3u8内容。

消除片段请求时间

上述请求的最后一部分——_HLS_push比较微妙,也是这次HLS协议升级的一个很大的改变,要求服务器支持HTTP/2,请求playlist的时候就直接将片段/part的内容跟随push下来,减少一个RTT延迟。

HTTP/2是2015年由RFC7540标准化的协议,已经被大量厂商接受,比如天猫、淘宝、腾讯、新浪等。其中相比HTTP/1.1很重要的改进就是连接多路复用和push,服务端可以根据需要,不需要客户端发起请求就主动将客户端必须的内容通过同一个连接提前推送给客户端,减少重新握手、请求的几个RTT延迟。

经过上述三点改进后,可以看到相比之前的旧版HLS方案,现在可以在很低的延迟下就获得首帧数据开始解码播放,图上示例的part时长是1秒,网络传输0.5秒的话,客户端观察到的延迟可以低到1.5秒左右,part长度可以进一步缩小,比如0.2秒,以获得更低的延迟。

m3u8采用增量升级机制

因为m3u8的请求可能高达每秒钟3-4次,为了进一步减少网络传输数据大小,苹果引入了增量更新机制,

其中EXT-X-SERVER-CONTROL: CAN-SKIP-UNTIL=36.0告诉客户端,比当前直播前沿数据早36秒以上的数据会被忽略 。这个数值要求是EXT-X-TARGETDURATION的6倍以上。客户端就可以通过请求的参数_HLS_skip=YES告诉服务端下发增量更新内容。

这个功能在一些场合比较有用,有些直播流允许用户往前回看一段时间,所以它们的m3u8文件会很大,上百K都有可能。使用增量更新机制能极大减小传输量。

加速不同码率直播流切换速度

最后一个,加速不同码率直播流切换速度的实现方案是在m3u8的最后带上EXT-X-RENDITION-REPORT,告诉客户端其它码率直播流的当前进展(片段序号和part序号)和加载地址。

1#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=3
2#EXT-X-RENDITION-REPORT:URI="../4M/waitForMSN.php",LAST-MSN=273,LAST-PART=3

当客户端决定要切换到另一个直播流上的时候,它不用发起一个新的请求,只要直接在原来的连接上请求即可(不过在苹果的参考实现上并没有实现这部分内容)

以上基本上就是这次苹果对低延迟HLS提出的技术草案,苹果也提供了参考实现用于测试和演示。不过从我的测试来看,iOS13 beta版里带的AVPlayer实现并没有完整实现低延迟HLS,确实只是个“参考”。

低延迟HLS demo

为了让参考实现跑起来,需要架设一个支持http2、https、php的服务器,首先尝试了MAMP最新版带的apache,发现缺少http2模块,需要自己编译一个apache,感觉比较麻烦,好在MAMP还支持nginx,将web server切换到nginx,将nginx.conf里的HTTPS Server配置前面的注释全部删除,将”listen 443 ssl;” 一行改为”listen 443 ssl http2 default_server;”, 配置好https证书,启用php支持,web server就准备好了。按照苹果的demo说明配置好推流服务、切片服务,找个目录放置master.m3u8, lowLatencyHLS.php, 一切就准备就绪了。

使用iOS13 beta里的Safari访问master.m3u8,视频播放正常,不过延迟似乎没有达到预期的2秒以内,还是在8-15秒的范围。用Charles抓包查看协议运行情况,如下几张截图所示,发现确实是基于HTTP/2协议,并且也确实请求了EXT-X-PART里指定的片段part文件,但是并没有上述5点改进中的后4点相关的内容,对服务端的lowLatencyHLS.php的访问并没有带上必要的参数,请求频率也只有4秒一次(按照EXT-X-TARGETDURATION的设置)。之后又按照苹果的要求自己写了一个基于AVPlayer的demo app,配置好了必要的provision profile和entitlements,但结果也是一样。初步分析认为iOS13 beta里Apple还没有完全实现低延迟HLS的客户端功能。

分析总结

demo告一段落,评估一下要想应用到实际生产环境中的成本,发现还有不少注意点和难点:

  1. 源站要提供HTTP / 2支持,因为低延迟HLS依赖多个HTTP / 2特性:多流控制,H2推送和H2 Ping。服务器必须支持H2优先级控制(依赖性和权重)。建议使用TCP,苹果不承诺在第一个版本中支持QUIC。每个服务器必须在主播放列表中提供所有的码率层级,这样才可以快速进行码率切换而无需重新建立连接。
  2. 不仅源站需要HTTP/2支持,CDN也需要支持HTTP/2。
  3. 上面说过客户端会发起阻塞式请求来获取还没有生成的播放列表,而真正的部署环境显然必须有CDN,加上CDN后,就要求CDN支持将多个相同的客户端请求聚合成一个请求发送给后端源站,也就是新的请求到来的时候,判断是否有相同的请求正在请求源站,如果有就等待在同样的请求队列上而不是发起新请求,数据回来之后再即时下发给客户端。否则按照CDN的缺省行为,不存在的内容直接转去请求源站的话,源站会迅速被打垮。
  4. AVPlayer的实现发现服务端对低延迟HLS支持不好的话,会自动切换回标准的HLS,让视频继续正常播放,所以测试低延迟HLS的时候只看视频是否能播放还不行,要抓包分析,确认低延迟HLS机制正常工作。
  5. 播放列表请求必须是幂等的。服务器应支持TLS 1.3或更高版本以减少连接时间。服务器还应支持媒体播放列表和媒体段的TLS 1.3 0-RTT连接。
  6. 播放列表本身必须采用gzip格式。这能加快媒体播放列表的重新加载和播放切换速度。
  7. 不同码率的直播流必须同步更新,误差在1个part时长内。
  8. 阻塞式请求实现时要注意超过3倍片段时长后还没有片段/part数据生成的话,要报503错。
  9. 为了让TCP性能最大化,从客户端到服务器整个链路上的所有设备的TCP协议栈要支持选择性确认(SACK, RFC 2018、2883 和 3517);支持拥塞期间设置显式拥塞通知(ECN,RFC 3168),并使用TCP时间戳;支持尾部丢失探测(TLP Tail Loss Probe)和TCP RACK等高性能TCP选项。
  10. 如果采用多家CDN,则需要协调多家CDN都对上述技术细节进行一致的支持。

苹果推出的低延迟HLS整套方案从现在来看实现成本还比较高,相对比较激进,有没有相对比较廉价的技术方案呢?其实早在2017年,Periscope就提出过一个没那么激进的LHLS方案,从实测数据来看也已经比较优异。

它的方案的主要技术点是基于HTTP/1.1的分块传输编码Chunked Transfer Coding机制(RFC 2616),分块传输编码主要用于发送未知长度、客户端请求时还未完全准备好的数据,用一个HTTP 响应数据简单说明如下:

上面这个过程可以看出,分块传输编码天生适合用于传输“还未到来的”HLS片段数据。Periscope的方案对标准HLS做的核心变化是提前几个片段时长就将片段网址添加到播放列表中。举例来说,当直播流正在启动并且流的第一帧从推流端到达服务器时,服务器将立即发布包含三个(数量可配置)片段的HLS媒体播放列表。当客户端收到播放列表时,它们会请求全部三个片段。服务器使用分块传输编码来响应每个请求。对于第一段的请求将首先获得在请求到达时在该段中累积的数据,但是之后的数据(在该段的剩余持续时间内)将在真正到达时候才传输给客户端 。同时,对第二段的请求最初仅接收一些MPEG传输流(TS)段报头,然后在第一段完成前不接收任何内容,第一段完成后才开始在这个连接上实时传输数据。类似地,对第三段的请求仅接收TS报头数据,直到第二段完成才开始接收数据。在播放列表可用之前就广播片段的好处是它消除了由于客户端播放列表轮询频率和CDN高速缓存中的播放列表TTL而导致的播放列表延迟问题。由于片段在它们实际包含媒体之前几秒钟被广播,如果播放列表可以被CDN聚合请求,就不会影响延迟。客户端会提前几秒钟获知即将到来的片段并请求它们,这样就可以在服务器获得数据后立即接收数据。

从Periscope的实践来看LHLS这个机制运行得非常顺利,CDN网络对持续数秒钟的分块传输编码的HTTP请求支持得非常好,并且聚合请求也都能正常工作;有一个小特性需要在播放器端进行特殊处理,就是不连续(EXT-X-DISCONTINUITY)标记,Periscope的方案是直接让播放器忽略不连续标记,仅根据时间戳和ES流中的SPS来控制播放行为,不过理论上直播流也不太可能出现这个标记。另外一个比较大的问题是采用分块传输编码后无法很方便地推算出用户的瞬时网速,不方便在多个不同码率的直播流中进行切换,这个只能想办法通过其它方式来计算瞬时网速。

Periscope的方案确实是一个物美价廉的方案,根据他们给出的图表来看,效果确实很明显,数据传输变得非常平滑,延迟也得到明显降低。

综合来看,如果想短期内实现低延迟HLS,Periscope的LHLS方案应该是一个比较好的选择,而苹果则选择了一条相对艰难、成本高昂的道路,在苹果的方案没有完全成型之前自己去实现难度很大,但是苹果的业界影响力也不容小觑,之前就有强力推动IPV6、HTTPS的先例,相信假以时日,Apple低延迟HLS也会成为业界标配。

参考文档:

  • https://en.wikipedia.org/wiki/HTTP_Live_Streaming
  • https://segmentfault.com/a/1190000008810572
  • https://developer.apple.com/documentation/http_live_streaming/protocol_extension_for_low-latency_hls_preliminary_specification
  • https://developer.apple.com/videos/play/wwdc2019/502/
  • https://tools.ietf.org/html/rfc7323
  • https://tools.ietf.org/html/rfc2018
  • https://tools.ietf.org/html/rfc2883
  • https://tools.ietf.org/html/rfc3517
  • https://tools.ietf.org/html/rfc3168
  • https://tools.ietf.org/html/rfc8216
  • https://tools.ietf.org/html/rfc7540
  • https://tools.ietf.org/html/rfc2616
  • https://medium.com/@periscopecode/introducing-lhls-media-streaming-eb6212948bef

原文发布于微信公众号 - LiveVideoStack(livevideostack)

原文发表时间:2019-06-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券