跳到主要内容

HLS

HLS,全称HTTP Live Streaming,是苹果公司实现基于HTTP的流媒体传输协议。它可以支持流媒体的直播和点播,主要应用在IOS系统和HTML5网页播放器。

HLS的基本原理非常简单,他是将多媒体文件或直接流文件进行切片,形成一堆的ts文件和m3u8索引文件并保存到磁盘,

当播放器获取HLS流时,他首先根据时间戳,通过HTTP服务,从m3u8索引文件获取最新的ts视频文件切片地址,然后在通过HTTP协议将它们下载并缓存起来。当播放器播放HLS流时,播放线程会从缓存区中读取数据并进行播放。

通过上面的描述可以知道,HLS协议的本质就是通过HTTP下载文件,然后将下载的切片缓存起来。由于切片文件都非常小,所以可以实现边下载边播的效果。HLS规范规定,播放器至少下载一个ts切片才能播放,所以HLS理论上至少会有一个切片的延迟。

优势:

HLS是为了解决RTMP协议中存在的一些问题而设计的,所以它自然有自己的优化。主要体现在一下几个方面:

  • RTMP协议没有使用标准的HTTP接口传输数据,在一些有访问限制的网络环境下,比如企业网防火墙,是没法访问外网的,因为企业内部一般只允许80/443端口可以访问访网。而HLS使用的是HTTP协议传输数据,所以HLS协议天然就解决了这个问题。
  • HLS协议本身实现了码率自适应,不同带宽的设备可以自动切换到最适合自己码率的视频进行播放。
  • 浏览器天然支持HLS协议,而RTMP协议需要安装Flash插件才能播放RTMP流。

不足:

HLS最主要的问题就是实时性差。由于HLS往往采用10s的切片,所以最小也要有10s的延迟,一般是20~30s的延迟,有时甚至更差。

HLS之所以能达到20~30s的延迟,主要是由于HLS的实现机制造成的。HLS使用的是HTTP短连接,且HTTP是基于TCP的,所以就意味着HLS需要不断地与服务器建立连接。TCP每次建立连接时都要进行三次握手,而断开连接时,也要进行四次挥手,基于以上复杂的原因,就造成了HLS延迟比较久的局面。

HLS直播架构

推流 -> 源节点 -> m3u8 -> .ts -> 用户

客户端采集媒体数据后,通过RTMP协议将音视频流推送给CDN网络的源节点(接入节点)。源节点收到音视频流后,再通过Convert服务器将RTMP流切割为HLS切片文件,即.ts文件。同时生成与之对应的m3u8文件,即HLS播放列表文件

切割后的HLS分片文件(.ts文件)和HLS列表文件(.m3u8文件)经CDN网络转发后,客户端就可以从离自己最近的CDN边缘节点拉取HLS媒体流了。

在拉取HLS媒体流时,客户端首先通过HLS协议将m3u8索引文件下载下来,然后按索引文件中的顺序,将.ts文件一片片下载下来,然后一边播放一边缓冲。此时,你就可以在PC、手机、平板等设备观看直播节目。

对于使用HLS协议的直播系统来说,最重要的一步就是切片。源节点服务器收到音视频流后,先要数据缓冲起来,保证到达帧的所有分片都已收到之后,才会将它们切片成ts流。

FFmpeg生成HLS切片

通过FFmpeg工具将一个MP4文件转换为HLS切片和索引文件的。

切片命令
ffmpeg -i test.mp4 -c copy -start_number 0 -hls_time 10 -hls_list_size 0 -hls_segment_filename test%03d.ts index.m3u8
该命令参数说明如下:
-i 输入文件选项,可以是磁盘文件,也可以是媒体设备
-c copy 表示只是进行封装格式的转换。不需要将多媒体文件中的音视频数据重新进行编码。
-start_number 表示 .ts 文件的起始编号,这里设置从 0 开始。当然,你也可以设置其他数字。
-hls_time 表示每个 .ts 文件的最大时长,单位是秒。这里设置的是 10s,表示每个切片文件的时长,为 10 秒。当然,由于没有进行重新编码,所以这个时长并不准确。
-hls_list_size 表示播放列表文件的长度,0 表示不对播放列表文件的大小进行限制。
-hls_segment_filename 表示指定 TS 文件的名称。
index.m3u8,表示索引文件名称。

执行完毕后,在当前路径下回生成一系列.ts文件和index.m3u8文件。下面,我们在分别分析.m3u8文件格式和.ts文件格式

m3u8格式分析

正如前面讲到,HLS必须要有一个.m3u8的索引文件。它是一个播放列表文件,文件的编码必须是UTF-8格式。这里我们将前面生成的.m3u8文件内容展示一下,一边让你有个感观的认识。内容如下

#EXTM3U
#EXT-X-VERSION:3 // 版本信息
#EXT-X-TARGETDURATION:11 //每个分片的目标时长
#EXT-X-MEDIA-SEQUENCE:0 //分片起始编号
#EXTINF:10.922578, //分片实际时长
test000.ts //分片文件
#EXTINF:9.929578, //第二个分片实际时长
test001.ts //第二个分片文件
//...

RFC8216规定,.m3u8文件内容以#字母开头的行是注释和TG,其中TAG必须是#EXT开头。

  • EXTM3U表示文件是一个扩展的m3u8文件,此TAG必须放在索引文件的第一行。
  • EXT-X-VERSION:n 表示索引文件支持的版本号,后面的数字n是版本号数字。需要注意的是,一个索引文件只能有一行版本号TAG,否则播放器会解析报错。
  • EXT-X-TARGETDURATION:s 表示.ts切片的最大时长,单位是秒
  • EXT-X-MEDIA-SEQUENCE:n 表示第一个.ts切片文件的编号。若不设置此项,就是默认从0开始的。
  • EXTINF:duration, title 表示.ts文件的时长和文件名称。文件时长不能超过#EXT-X-TARGETDURATION中设置的最大时长,并且时长的单位应该采用浮点数来提高精度。

TS格式分析

HLS协议是由PAT+PMT+TS数据流组成的。其中TS数据中的视频数据采用H264编码,而音频数据采用AAC/MP3编码。ts数据流示意图如下所示

TS数据流示意图

TS数据流由TS Header和TS Payload组成。其中TS Header占4字节,TS Payload占184字节,即TS数据流总长度是188字节。

TS Payload又由PES Header 和PES Payload 组成。其中,PES Payload是真正的音视频流,也称ES流。

  • PES(Packet Elementary Stream)是将 ES 流增加 PES Header 后形成的数据包。
  • ES(Elementary Stream),基流,是编码后的音视频数据。

TS文件格式图

上图展示了TS Header各个字段的详细说明,图中数字表示长度,如果数字后面带有bytes,单位就是bytes;否则,单位都是bit。

字段名称单位(bit)字段说明
sync_byte8固定值是0x47,算是magic字段,用它可以判断是否为TS文件
transport_error_indicator1传输错误指示标志,如果此标志为1,表示发生了不可纠正的错误
payload_unit_start_indicator1标识TS数据流的Payload域中包含的是否为PES数据。
为1:表示TS Header后面第一个字节是PES Header的内容。
为0:表示后面直接是ES数据
transport_priority1包优先级标志,设置为1表示高优先级
PID13Packet Identifier 是每个ES的唯一标识。PID的部分值是保留的,用来区分Payload的类型
transport_scrambling_control2扰码控制模式,有四种模式:00 表示不加扰码;01/10/11由用户自定义
adaptation_field_control2表示在TS Header后面是否有适配控制字段
continuity_counter4TS数据包累加计算器、对于同一个PID的TS流,此字段每次都会增加,最大值是15,溢出后,从0开始计数。
此值受adaptation_field_control字段控制,当取值00或10时,计数不累加。这也说明累加器只对包含Payload的TS数据包起作用

PES Packet 作为TS数据流的Payload,也有自己的Header,如下图所示

PES结构图

PES Header长度是6字节,字段说明如下:(可参考ISO-IES 13818-1 2.4.3.7节)

字段名称单位(bit)字段说明
packet_start_code_prefix24PES包前缀,固定值是0x000001
stream_id8节目流ID,用于标识ES流。
音频取值(0xc0-0xdf),通常为0xc0
视频取值(0xe0-0xef),通常为0x0
PES_packet_length16表示PES包长度,从PES_packet_length 字段后的第一个字节开始计算。如果此值为0,表示长度不受限制
PES_scrambling_control2PES包扰码模式
PES_priority1PES包优先级,取值1优先级更高

TS加密&解密

HLS协议采用M3U8文件来告知客户端视频文件播放列表,客户端拿到M3U8文件以后就可以直接播放视频,为了避免源站的视频文件被非授权客户端访问,需要对HLS协议使用的TS视频文件做加密,对TS视频文件做了加密以后,还需要告知客户端解密方法,这里就可以通过配置M3U8标准加密改写功能,通过#EXT-X-KEY标签来告知客户端加密算法、密钥URI和鉴权key。

#EXT-X-KEY 是否加密解析。

例如:#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/video.key?token=xxx" 加密算法是AES-128,密钥通过请求 https://example.com/video.key?token=xxx 来获取,密钥请求回来以后存储在本地,并用于解密后续下载的TS视频文件。

前端处理加密请求。如果服务端设置请求key只允许一次。那么需要设置第一次缓存的key缓存到本地 https://github.com/videojs/http-streaming#cacheencryptionkeys

参考资料

HLS:实现一对多直播系统的必备协议
HTTP Live Streaming (HLS) - 概念
配置M3U8标准加密改写
视频TS文件AES加密处理