一、前言
在最近工作中接触到了视频点播和直播业务,也了解到了一些流媒体的后端技术,这段时间希望将了解到的一些知识总结下来,这篇文章主要介绍 HLS 流媒体协议 的基础知识。
二、常见流媒体协议
常用的流媒体协议主要有 HTTP 渐进下载和基于 RTSP/RTP 的实时流媒体协议,这两种协议是完全不同的实现方式。主要区别如下:
- 一种是分段渐近下载,一种是基于实时流来实现播放;
- 协议不同,HTTP 协议的渐近下载意味着可以在一台普通的 HTTP 的应用服务器上就可以直接提供视频点播和直播服务;
- 延迟有差异,HTTP 渐近下载的方式的延迟理论上会略高于实时流媒体协议的播放;
- 渐近下载会生成索引文件,所以需要考虑存储,对 I/O 要求较高。
三、HLS 协议介绍
HLS 协议是由 Apple 公司提出并推广开来的,以下是来一段维基百科的定义:
HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议。是苹果公司QuickTime X和iPhone软件系统的一部分。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的extended M3U (m3u8)playlist文件,用于寻找可用的媒体流。
HLS只请求基本的 HTTP 报文,与实时传输协议(RTP)不同,HLS可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。
苹果公司把HLS协议作为一个互联网草案(逐步提交),在第一阶段中已作为一个非正式的标准提交到IETF。但是,即使苹果偶尔地提交一些小的更新,IETF却没有关于制定此标准的有关进一步的动作。[1]
在网上已经有很多关于 HLS 的资料,本文主要按照我的理解整理下 HLS 流媒体协议基础知识。
HLS 协议格式要求:
- 视频的封装格式 TS(流媒体文件);
- 保存 TS 索引的 M3U8 文件;
- 视频的编码格式:H264 (只要 MPEG-TS 支持,基本都可以,只是有些格式不是免费的;音频类似);
- 音频的编码格式:AAC、MP3、AC-3。
HLS 协议优势:
- 使用标准 HTTP 传输数据,具有较好的网络穿透及防屏蔽性,更易于内容分发网络传输;
- HLS 协议本身是支持码率自适应的,客户端可以根据实际网络状况切换到合适的码率播放;
- HLS 内容发布服务更简单,对系统设备要求较低,更容易实现负载均衡,并且 HLS 是无状态协议的 HTTP,客户端只需要下载即可。
HLS 协议劣势:
- 延时较大,尤其是在直播的情况下,很难做到 10s 以内的延时(不排除网上各种改进版本及算法);
- 内容生成时对编码端性能要求较高。
四、HlS 系统架构
下图来自 Apple 官网:
HLS 支持直播或者点播,同时支持加密和认证。从概念上来说,HTTP通常包括三部分:服务器端、发布端、客户端。
1、HLS 服务器端
服务器端主要负责将输入的媒体数据进行编码、封装,并将封装之后的文件切片,以满足发布端的要求。其输出可以是音视频原始数据,也可以是编码之后的数据,也可以是封装好的 TS 数据。这也输入最终会通过分片工具切分成发布端需要的格式。这里涉及三部分:
多媒体编码器 (Media Encoder):多媒体编码器主要把采集自音视频设备的实时信号编码,封装。编码中必须选择客户端支持的格式,比如 h264 视频+ aac 音频。目前 HLS 支持的封装格式是 MPEG-TS 或者 MPEG 基本流(MPEG-ES,仅支持纯音频)。编码完成之后,编码器可以把封装之后的格式通过本地网络或者其他机制传递给分片工具(segmenter)。
分片工具(segmenter):按照输入源的不同,通常分为流分片器、文件分片器。顾名思义,二者主要区别在于输入的文件格式上。 流分片器输出的是从本地网络滴入的 MPEG-TS 流,而文件分片器处理的是封装好的 TS文件。它们的工作原理类似:将 MPEG-TS 切分成一系列等时长的媒体文件,但保证这些小的分片是可以无缝重建的,播放时音视频是连续的。分片工具还会创建索引文件(M3U8),其中包含指向单独媒体文件的索引信息。每当分片器完成一个新的媒体文件,它将更新索引文件。该索引用于记录媒体文件的位置及可访问性。在此过程中,分片工具可以加密每个分片,并为其创建密钥文件。
2、HLS 分发端
HLS 分发端较为简单,只要使用标准的网络服务器即可。它们负责接受客户端请求,并将处理好的多媒体文件和资源发送给客户端。如果并发量较大,可能需要边缘网络或其他内容分发网络。
分发系统是一个 web 服务器或者 web 缓存系统,它们能够通过 HTTP 向客户端发送媒体文件及索引文件。多数情况下,分发内容之前无需额外配置服务器、模块,仅需很少的配置就在 web 服务器上正常工作。
3、HLS 客户端
客户端负责选择合适的请求资源,下载器资源,然后解码显示(整成播放器的功能)。
客户端从获取索引文件开始,通常使用给定的 URL 来识别该流的信息。这个索引文件一般给出了可用媒体文件、解密密钥和其他可选流的位置。客户端选定流之后,就开始顺序下载每个可用的媒体文件。每个文件中包含特定流的连续分片。只要客户端下载到足够的数据,就可以开始解码数据并显示了。
如果需要,客户端负责读取所有解密密钥、认证或为用户提供用于认证或解密的接口。
客户端可以一直持续这个过程,直到它遇到索引文件中的 #EXT-X-ENDLIST
标签;若不存在该标签,则表示该索引文件是一个直播源,客户端需要定期更新索引文件,重复上述过程。
较为常用的 HLS 系统中,使用硬编码器将输入的音频编码为 AAC、将输入的视频编码为 h264,并将二者复用到 MPEG-TS 中,之后使用分片工具将其切分为一系列小的 TS 文件;这些文件将可以放到 web 服务器上。分片工具同时会创建并维护一个索引文件(HLS 中称为 M3U8),其中包含可用媒体文件的列表。索引文件的URL会在 web 服务器上发布。客户端可以读取该索引文件,然后顺序请求列出的媒体文件,这些分片可以无缝播放。
4、小结
左下方的 inputs 的视频源是什么格式都无所谓,他与 server 之间的通信协议也可以任意(比如RTMP),总之只要把视频数据传输到服务器上即可。这个视频在 server 服务器上被转换成 HLS 格式的视频(TS 和 M3U8 文件)文件。细拆分来看 server 里面的 Media encoder 的是一个转码模块负责将视频源中的视频数据转码到目标编码格式(H264)的视频数据,视频源的编码格式可以是任何的视频编码格式。转码成 H264 视频数据之后,在 stream segmenter 模块将视频切片,切片的结果就是 index file(m3u8)和 ts 文件了。图中的 Distribution 其实只是一个普通的 HTTP 文件服务器,然后客户端只需要访问一级 index 文件的路径就会自动播放 HLS 视频流了。
下图是一个简单概括的流媒体播放实现时序图:
简单描述 HLS 的工作原理是将整个流分成一系列小的基于 HTTP 的文件下载,每个下载将加载整个潜在的无限制传输流中的一小部分。由于片段之间的分段间隔时间非常短,所以看起来是一条完整的播放流,实现的重点是对于视频文件的分割。同时,HLS 还支持多码率的切换,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。多清晰度就是这样实现的。
为了播放视频,客户端首先需要获得播放列表文件,也就是根据 HLS 生成的片段列表,该列表中包含每个流媒体的文件,客户端以类似轮询的方式不断重复加载播放列表文件并将片段追加实现流媒体的播放。
五、示例分析
对 playlist.m3u8
的请求。这实际上是指向其他索引的指针,这些块需要作为流媒体的一部分进行下载。m3u8 文件本质说其实是采用了编码是 UTF-8 的 m3u 文件。
它只是一个纯索引文件,一个文件片段的列表,客户单打开它并不是播放它,而是根据它里面的文件片段找到视频文件的网路地址进行播放。
这里请求一个 m3u8 文件打开看一下究竟是什么:
curl http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/playlist.m3u8
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-STREAM-INF:BANDWIDTH=3128000,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=1280x720
chunklist_w1690990834_b3128000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1778000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=852x480
chunklist_w1690990834_b1778000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1048000,CODECS="avc1.4d001e,mp4a.40.2",RESOLUTION=640x360
chunklist_w1690990834_b1048000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=738000,CODECS="avc1.4d0015,mp4a.40.2",RESOLUTION=428x240
chunklist_w1690990834_b738000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=528000,CODECS="avc1.4d000d,mp4a.40.2",RESOLUTION=312x176
chunklist_w1690990834_b528000.m3u8
我们分析该 m3u8 文件:
#EXTM3U
:扩展标记 ,意思是我是 m3u 文件#EXT-X-VERSION
:版本#EXT-X-STREAM-INF
:指定一个包含多媒体信息的 media URI 作为 PlayList,一般做 M3U8 的嵌套使用,它只对紧跟后面的 URI 有效,#EXT-X-STREAM-INF
:有以下属性:BANDWIDTH
:带宽CODECS
:不是必须的。RESOLUTION
:分辨率。
第一个块是 chunklist_w1057647775_b3128000
还是个 m3u8 文件,之后可以看到每个后续块。每个块都将显示客户端要下载的媒体 URI。
同时可以观察发现,这其实是不同清晰度的 m3u8 文件,客户端根据网络或者选项去选择不同的清晰度的 m3u8 文件。
上面的 m3u8 文件为一级 m3u8 文件,这两个 m3u8 就称为二级索引文件,那么我们就顺着二级索引文件继续查看:
curl http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/chunklist_w570392994_b3128000.m3u8
#EXTM3U
#EXT-X-VERSION:5
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:6.0,
media_w570392994_b3128000_0.ts
#EXTINF:6.0,
media_w570392994_b3128000_1.ts
#EXTINF:6.0,
media_w570392994_b3128000_2.ts
#EXTINF:6.0,
media_w570392994_b3128000_3.ts
#EXTINF:6.0,
media_w570392994_b3128000_4.ts
#EXTINF:6.0,
media_w570392994_b3128000_5.ts
#EXTINF:6.0,
media_w570392994_b3128000_6.ts
#EXTINF:6.0,
media_w570392994_b3128000_7.ts
#EXTINF:6.0,
media_w570392994_b3128000_8.ts
#EXTINF:6.0,
media_w570392994_b3128000_9.ts
#EXTINF:6.0,
media_w570392994_b3128000_10.ts
#EXTINF:6.0,
media_w570392994_b3128000_11.ts
#EXTINF:6.0,
media_w570392994_b3128000_12.ts
#EXTINF:6.0,
media_w570392994_b3128000_13.ts
#EXTINF:6.0,
media_w570392994_b3128000_14.ts
#EXTINF:6.0,
media_w570392994_b3128000_15.ts
#EXTINF:6.0,
media_w570392994_b3128000_16.ts
#EXTINF:6.0,
media_w570392994_b3128000_17.ts
#EXTINF:6.0,
media_w570392994_b3128000_18.ts
#EXTINF:6.0,
media_w570392994_b3128000_19.ts
#EXTINF:6.0,
media_w570392994_b3128000_20.ts
#EXTINF:6.0,
media_w570392994_b3128000_21.ts
#EXTINF:2.66,
media_w570392994_b3128000_22.ts
#EXT-X-ENDLIST
我们分析该二级索引文件:
#EXT-X-VERSION
: 版本#EXT-X-TARGETDURATION
:指定最大的流片段时间长(秒),也就是说这些 ts 切片的时长不能大于这个值;#EXTINF
: 指定每个流片段(ts)的持续时间(秒),仅对其后面的 URI 有效,title 是下载资源的 URI;#EXT-X-ENDLIST
: 结束列表,这个标志同时也说明当前的流是一个非直播流。
这里我们看到了真正播放的流片段,即 ts 片,客户端拿到的就是这个 ts 片,然后不断下载请求到该片段并连续播放。
有些人可能要问了,那 ts 文件又到底是个什么东西呢,那就下载来看看,拿着其中的一个 ts 文件浏览器打开保存到本地
发现保存到本地的文件就可以直接打开,其实就是真正的流媒体文件,但是这个文件只是片段,大概只有 6s 的时间。
请求下该 “media_w570392994_b3128000_0.ts”
流片段:
curl -I http://wowzaec2demo.streamlock.net/vod/_definst_/smil:streaming_tutorial/streaming_tutorial.smil/media_w570392994_b528000_0.ts
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Expose-Headers: Date
Server: WowzaStreamingEngine/4.7.5.01
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: HEAD, GET, POST
Access-Control-Allow-Headers: Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control, Range
Date: Sat, 05 Dec 2020 18:05:53 GMT
Content-Type: video/MP2T
Content-Length: 426008
六、总结
播放 HLS 视频流的逻辑其实非常简单,先下载一级 Index file,它里面记录了二级索引文件(Alternate-A、Alternate-B、Alternate-C)的地址,然后客户端再去下载二级索引文件,二级索引文件中又记录了 TS 文件的下载地址,这样客户端就可以按顺序下载 TS 流媒体文件并连续播放。
参考资料:
- https://blog.csdn.net/phachon/article/details/52524596
- https://www.flood.io/blog/load-testing-hls-with-ruby-jmeter
- https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-SW1
- https://www.jianshu.com/p/426425cad08a