流媒体传输 - HLS 协议
HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议,用于实时音视频流的传输。目前 HLS 协议被广泛的应用于视频点播和直播领域。
概述
原理介绍
通过将整条流切割成一个小的可以通过 HTTP 下载的媒体文件, 然后提供一个配套的媒体列表文件, 提供给客户端, 让客户端顺序地拉取这些媒体文件播放, 来实现看上去是在播放一条流的效果. 由于传输层协议只需要标准的 HTTP 协议, HLS 可以方便的透过防火墙或者代理服务器, 而且可以很方便的利用 CDN 进行分发加速, 并且客户端实现起来也很方便.
整体架构
HLS 的架构分为三部分:Server,CDN,Client 。即服务器、分发组件和客户端。
下面是 HLS 整体架构图:
服务器用于接收媒体输入流,对它们进行编码,封装成适合于分发的格式,然后准备进行分发。
分发组件为标准的 Web 服务器。它们用于接收客户端请求,传递处理过的媒体,把资源和客户端联系起来。
客户端软件决定请求何种合适的媒体,下载这些资源,然后把它们重新组装成用户可以观看的连续流。
HLS 协议分析
HLS Playlist
其实, HLS 协议的主要内容是关于 M3U8 这个文本协议的, 其实生成与解析都非常简单. 为了更加直接地说明这一点, 我下面举两个简单的例子:
简单的 Media Playlist:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:7.975,
https://priv.example.com/fileSequence2680.ts
#EXTINF:7.941,
https://priv.example.com/fileSequence2681.ts
#EXTINF:7.975,
https://priv.example.com/fileSequence2682.ts
包含多种比特率的 Master Playlist:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2560000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
HLS通过URI(RFC3986)指向的一个Playlist来表示一个媒体流一个
Playlist可以是一个Media Playlist或者Master Playlist, 使用UTF-8编码的文本文件, 包含一些URI跟描述性的tags一个
Media Playlist包含一个Media Segments列表,当顺序播放时, 能播放整个完整的流要想播放这个
Playlist, 客户端需要首先下载他, 然后播放里面的每一个Media Segment更加复杂的情况是,
Playlist是一个Master Playlist, 包含一个Variant Stream集合, 通常每个Variant Stream里面是同一个流的多个不同版本(如: 分辨率, 码率不同)一个
Playlist文件必须通过URI(.m3u8 或 m3u)或者HTTP Content-Type来识别 (application/vnd.apple.mpegurl或audio/mpegurl)换行符可以用
\n或者\r\n以
#开头的是tag或者注释, 以#EXT开头的是tag, 其余的为注释, 在解析时应该忽略Playlist里面的URI可以用绝对地址或者相对地址, 如果使用相对地址, 那么是相对于Playlist文件的地址
HLS Media Segments
- 每一个
Media Segment通过一个URI指定, 可能包含一个byte range - 每一个
Media Segment的duration通过EXTINFtag指定 - 每一个
Media Segment有一个唯一的整数Media Segment Number - 有些媒体格式需要一个
format-specific sequence来初始化一个parser, 在Media Segment被parse之前. 这个字段叫做Media Initialization Section, 通过EXT-X-MAPtag来指定. 支持的Media Segment格式
HLS TAGS
Basic Tags: 用在Media Playlist和Master Playlist里面EXTM3U: 必须在文件的第一行, 标识是一个Extended M3U Playlist文件EXT-X-VERSION: 表示Playlist兼容的版本
Media Segment Tags: 只能出现在Media Playlist里面EXTINF: 用于指定Media Segment的durationEXT-X-BYTERANGE: 用于指定URI的sub-rangeEXT-X-DISCONTINUITY: 表示不连续EXT-X-KEY: 表示Media Segment已加密, 该值用于解密EXT-X-MAP: 用于指定Media Initialization SectionEXT-X-PROGRAM-DATE-TIME: 和Media Segment的第一个sample一起来确定时间戳EXT-X-DATERANGE: 将一个时间范围和一组属性键值对结合到一起
Media Playlist tags: 只能出现在Media Playlist里面EXT-X-TARGETDURATION: 用于指定最大的Media Segment durationEXT-X-MEDIA-SEQUENCE: 用于指定第一个Media Segment的Media Sequence NumberEXT-X-DISCONTINUITY-SEQUENCE: 用于不同Variant Stream之间同步EXT-X-ENDLIST: 表示结束EXT-X-PLAYLIST-TYPE: 可选, 指定整个Playlist的类型EXT-X-I-FRAMES-ONLY: 表示每个Media Segment描述一个单一的I-frame
Master Playlist tags: 只能出现在Master Playlist中EXT-X-MEDIA: 用于关联同一个内容的多个Media Playlist的多种renditionsEXT-X-STREAM-INF: 用于指定一个Variant StreamEXT-X-I-FRAME-STREAM-INF: 用于指定一个Media Playlist包含媒体的I-framesEXT-X-SESSION-DATA: 存放一些session数据EXT-X-SESSION-KEY: 用于解密
Media or Master Playlist Tags: 可以出现在Media Playlist或者Master Playlist中,但是如果同时出现在同一个Master Playlist和Media Playlist中时,必须为相同值EXT-X-INDEPENDENT-SEGMENTS: 表示每个Media Segment可以独立解码EXT-X-START: 标识一个优选的点来播放这个Playlist
HLS 播放
播放未加密 HLS
我们通过 VLC 播放器播放苹果官方提供的一个例子:http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8,并使用 Wirshark 对其中交互进行抓包。
GET /iphone/samples/bipbop/gear1/prog_index.m3u8 HTTP/1.1
Host: devimages.apple.com
Accept: */*
Accept-Language: zh_CN
User-Agent: VLC/3.0.8 LibVLC/3.0.8
Range: bytes=0-
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: audio/x-mpegurl
ETag: "50117c8233644c19b5ab49551b72507f:1239907352"
Last-Modified: Thu, 16 Apr 2009 18:42:32 GMT
Server: AkamaiNetStorage
Date: Sun, 22 Nov 2020 15:01:49 GMT
Content-Range: bytes 0-7018/7019
Content-Length: 7019
X-Cache: TCP_MEM_HIT from a184-26-91-45.deploy.akamaitechnologies.com (AkamaiGHost/10.2.0.2-31441410) (-)
Connection: keep-alive
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10, no desc
fileSequence0.ts
#EXTINF:10, no desc
fileSequence1.ts
#EXTINF:10, no desc
fileSequence2.ts
#EXTINF:10, no desc
......
#EXTINF:1, no desc
fileSequence180.ts
#EXT-X-ENDLIST
我们可以看到返回的 M3U8 里面他有一个一个 ts 视频片段,这个一个一个视频片段就是我们需要的播放的视频片段。
#EXTINF 表示每个 ts 切片视频文件的时长。 #EXT-X-TARGETDURATION 指定当前视频流中的切片文件的最大时长,也就是说这些 ts 切片的时长不能大于 #EXT-X-TARGETDURATION 的值。 #EXT-X-MEDIA-SEQUENCE 第一个 ts 分片的序列号 #EXT-X-ENDLIST 这个表示视频结束,有这个标志同时也说明当前的流是一个非直播流。 #EXT-X-PLAYLIST-TYPE:VOD 的意思是当前的视频流并不是一个直播流,而是点播流,换句话说就是该视频的全部的 ts 文件已经被生成好了 #EXT-X-ALLOW-CACHE 是否允许 cache
播放加密 HLS
HLS 协议总结
优点
- 客户端支持简单, 只需要支持
HTTP请求即可,HTTP协议无状态, 只需要按顺序下载媒体片段即可。 - 使用
HTTP协议网络兼容性好,HTTP数据包也可以方便地通过防火墙或者代理服务器,CDN支持良好。 Apple的全系列产品支持,不需要安装任何插件就可以原生支持播放HLS, 目前Android也加入了对HLS的支持。- 自带多码率自适应机制。
缺点
- 相比
RTMP这类长连接协议, 延时较高, 难以用到互动直播场景。 - 对于点播服务来说, 由于
TS切片通常较小, 海量碎片在文件分发, 一致性缓存, 存储等方面都有较大挑战。
改进
- 由于客户端每次请求
TS或M3U8有可能一个新的连接请求, 无法有效的标识客户端, 一旦出现问题, 基本无法有效的定位问题。 - 一般工业级的服务器都会对传统的
HLS做一些改进,常见优化是对每个M3U8文件增加Session来标识一条HLS连接。 - 不管通过哪种方式, 最终我们都能通过一个唯一的
id来标识一条流, 这样在排查问题时就可以根据这个id来定位播放过程中的问题。