流媒体传输 - 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
通过EXTINF
tag
指定 - 每一个
Media Segment
有一个唯一的整数Media Segment Number
- 有些媒体格式需要一个
format-specific sequence
来初始化一个parser
, 在Media Segment
被parse
之前. 这个字段叫做Media Initialization Section
, 通过EXT-X-MAP
tag
来指定. 支持的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
的duration
EXT-X-BYTERANGE
: 用于指定URI
的sub-range
EXT-X-DISCONTINUITY
: 表示不连续EXT-X-KEY
: 表示Media Segment
已加密, 该值用于解密EXT-X-MAP
: 用于指定Media Initialization Section
EXT-X-PROGRAM-DATE-TIME
: 和Media Segment
的第一个sample
一起来确定时间戳EXT-X-DATERANGE
: 将一个时间范围和一组属性键值对结合到一起
Media Playlist tags
: 只能出现在Media Playlist
里面EXT-X-TARGETDURATION
: 用于指定最大的Media Segment duration
EXT-X-MEDIA-SEQUENCE
: 用于指定第一个Media Segment
的Media Sequence Number
EXT-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
的多种renditions
EXT-X-STREAM-INF
: 用于指定一个Variant Stream
EXT-X-I-FRAME-STREAM-INF
: 用于指定一个Media Playlist
包含媒体的I-frames
EXT-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
来定位播放过程中的问题。