1.HLS综述

HLS 就不得不谈苹果,谈苹果就不得不提乔帮主。HLS就是“HTTP Live Streaming”的缩写,它诞生自2009年,QuickTimeiPhone3GS黄金搭档下的一个标准,一个意在颠覆流媒体产业的新协议。

它的工作原理简单来说就是把一段视频流,分成一个个小的基于HTTP的文件来下载。当媒体流正在播放时,客户端可以根据当前网络环境,方便地在不同的码率流中做切换,以实现更好的观影体验。

HLS的出现是为了解决苹果原生环境中的流媒体播放,这个协议可以方便地让MaciPhone播放视频流,不依赖Adobe,更不用去管什么标准委员会。依赖自己,永远是最大力量的保障。

HLS(HTTP Live Streaming) 把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。HLS 协议由三部分组成:HTTPM3U8TS。这三部分中,HTTP 是传输协议,M3U8 是索引文件,TS 是音视频的媒体信息。

HLS 提供一个 m3u8 地址,AppleSafari 浏览器直接就能打开 m3u8 地址,譬如:

http://demo.srs.com/live/livestream.m3u8

Android 不能直接打开,需要使用 html5video 标签,然后在浏览器中打开这个页面即可,譬如:

<!-- livestream.html -->
<video width="640" height="360"
        autoplay controls autobuffer 
        src="http://demo.srs.com/live/livestream.m3u8"
        type="application/vnd.apple.mpegurl">
</video>

HLSm3u8,是一个 TS 的列表,也就是告诉浏览器可以播放这些 TS 文件,譬如:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:64
#EXT-X-TARGETDURATION:12
#EXTINF:11.550
livestream-64.TS
#EXTINF:5.250
livestream-65.TS
#EXTINF:7.700
livestream-66.TS
#EXTINF:6.850
livestream-67.TS

有几个关键的参数,这些参数在 SRS 的配置文件中都有配置项:

  • EXT-X-TARGETDURATION:所有切片的最大时长。有些 Apple 设备这个参数不正确会无法播放。SRS 会自动计算出 TS 文件的最大时长,然后更新 m3u8 时会自动更新这个值。用户不必自己配置。
  • EXTINFTS 切片的实际时长,SRS 提供配置项 hls_fragment,但实际上的 TS 时长还受 gop 影响。
  • TS 文件的数目:SRS 可配置 hls_window,指定 m3u8 中保存多少个切片,SRS 会自动清理旧的切片
  • livestream-67.TSSRS 会自动维护 TS 切片的文件名,在编码器重推之后,这个编号会继续增长,保证流的连续性。直到 SRS 重启,这个编号才重置为 0

譬如,每个 TS 切片为 10 秒,窗口为 60 秒,那么 m3u8 中会保存 6TS 切片。

每一个 .m3u8 文件,分别对应若干个 TS 文件,这些 TS 文件才是真正存放视频的数据,m3u8 文件只是存放了一些 TS 文件的配置信息和相关路径,当视频播放时,.m3u8 是动态改变的,video 标签会解析这个文件,并找到对应的 TS 文件来播放,所以一般为了加快速度,.m3u8 放在 web 服务器上,TS 文件放在 CDN 上。

.m3u8 文件,其实就是以 utf-8 编码的 m3u 文件,这个文件本身不能播放,只是存放了播放信息的文本文件。

HLS 整体框架图:Server、CDN 和 Client

ios m3u8 ios m3u8卡_ios m3u8


HLS 协议编码格式要求

  • 视频的编码格式:H264
  • 音频的编码格式:AACMP3AC-3
  • 视频的封装格式:TS
  • 保存 TS 索引的 m3u8 文件

HLS 协议优势

  • HLS 相对于 RTMP 来讲使用了标准的 HTTP 协议来传输数据,可以避免在一些特殊的网络环境下被屏蔽。
  • HLS 相比 RTMP 在服务器端做负载均衡要简单得多。因为 HLS 是基于无状态协议 HTTP 实现的,客户端只需要按照顺序使用下载存储在服务器的普通 TS 文件进行播放就可以。而 RTMP 是一种有状态协议,很难对视频服务器进行平滑扩展,因为需要为每一个播放视频流的客户端维护状态。
  • HLS 协议本身实现了码率自适应,在不同带宽情况下,设备可以自动切换到最适合自己码率的视频播放。

HLS 协议缺点

  • HLS 协议在直播的视频延迟时间很难做到 10 s 以下延时,而 RTMP 协议的延时可以降到 3s-4s 左右。

2. HLS 之 M3U8

m3u8 文件是用文件方式对媒体文件进行描述,由一些列标签组成。

m3u8 文件示例 1:单码率适配流

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:2
#EXT-X-TARGETDURATION:16
#EXTINF:14.357, no desc
livestream-2.TS
#EXTINF:15.617, no desc
livestream-3.TS
#EXTINF:14.358, no desc
livestream-4.TS
#EXTINF:15.618, no desc
livestream-5.TS
#EXTINF:11.130, no desc
livestream-6.TS

m3u8 文件只是一个简单的 Media Playlist

m3u8 文件示例 2:多码率适配流

#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

以上是包含多种比特率的 Master Playlist。该文件是一个实际使用中的顶级 m3u8 文件,该文件中又定义了

http://example.com/low.m3u8

http://example.com/mid.m3u8

http://example.com/hi.m3u8

等几个二级文件。顶级 m3u8 文件主要是做码率适配的,二级 m3u8 才是真正的切片文件,客户端会默认选择码率最高的请求,如果发现码率达不到,会请求降低码率的流。客户端拿到二级 m3u8 文件后,会继续请求里面的文件,这时就可以进行播放了。

2.1 基础概念

2.1.1 Playlist file
一个 m3uPlaylist 就是一个由多个独立行组成的文本文件,每行由回车/换行区分。每一行可以是一个 URL、空白行或是一个 以 # 号开头的字符串,并且空格只能存在于一行中不同元素间的分隔。
一个 URL表示一个媒体段或是 variant Playlist file(最多支持一层嵌套,即一个 m3u8 文件中嵌套另一个 m3u8),以 "EXT" 开头的表示一个 "tag",否则表示注释,直接忽略。

2.1.2 Tags

  • #EXTM3U: 每个 m3u8 文件第一行必须是这个 tag,如上面的两个示例。
  • #EXTINF: 指定每个媒体段(TS)的持续时间,这个仅对其后面的 URL有效,每两个媒体段 URL间被这个 tag 分隔开
    其格式为:#EXTINF:<duration>,<title>
  • duration:表示持续的时间(秒),objectivec Durations MUST be integers if the protocol version of the Playlist file is less than 3,否则可以是浮点数。
  • #EXT-X-BYTERANGE: 表示媒体段是一个媒体 URL资源中的一段,只对其后的 media URL 有效,格式为:#EXT-X-BYTERANGE:<n>[@o]
  • n:表示这个区间的大小
  • o:表示在 URL 中的 offset
  • The EXT-X-BYTERANGE tag appeared in version 4 of the protocol
  • #EXT-X-TARGETDURATION: 指定当前视频流中的单个切片(即 TS)文件的最大时长(秒)。所以 #EXTINF 中指定的时间长度必须小于或是等于这个最大值。这个 tag 在整个 Playlist 文件中只能出现一次(在嵌套的情况下,一般有真正
    TS urlm3u8 才会出现该 tag)。格式为:#EXT-X-TARGETDURATION:<s>
  • s:表示最大的秒数。
  • #EXT-X-MEDIA-SEQUENCE: 每一个 media URLPlaylist 中只有唯一的序号,相邻之间序号 +1
    格式为:#EXT-X-MEDIA-SEQUENCE:<number>。一个 media URL 并不是必须要包含的,如果没有,默认为 0.
  • #EXT-X-KEY: 表示怎么对 media segmenTS 进行解码。其作用范围是下次该 tag 出现前的所有 media URL
    格式为:#EXT-X-KEY:<attribute-list>
  • NONE 或者 AES-128。如果是 NONE,则 URL 以及 IV 属性必须不存在,如果是 AES-128(Advanced Encryption Standard),则 URL 必须存在,IV 可以不存在。
  • 对于 AES-128 的情况,keytagURL 属性共同表示了一个 key 文件,通过 URL 可以获得这个 key,如果没有 IVInitialization Vector),则使用序列号作为 IV 进行编解码,将序列号的高位赋到 16 个字节的 buffer 中,左边补 0;如果有 IV,则将该值当成 16 个字节的 16 进制数。
  • #EXT-X-PROGRAM-DATE-TIME: 将一个绝对时间或是日期和一个媒体段中的第一个 sample 相关联,只对下一个 media URL 有效,格式如下:#EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>例如:#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
  • #EXT-X-ALLOW-CACHE: 是否允许做 cache,这个可以在 Playlist 文件中任意地方出现,并且最多只出现一次,作用效果是所有的媒体段。格式如下:#EXT-X-ALLOW-CACHE:<YES|NO>
  • #EXT-X-PLAYLIST-TYPE: 提供关于 Playlist 的可变性的信息,这个对整个 Playlist 文件有效,是可选的,格式如下:#EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
  • VOD,即为点播视频,服务器不能改变 Playlist 文件,换句话说就是该视频全部的 TS 文件已经被生成好了。
  • EVENT,就是实时生成 m3u8TS 文件。服务器不能改变或是删除 Playlist 文件中的任何部分,但是可以向该文件中增加新的一行内容。它的索引文件一直处于动态变化中,播放的时候需要不断下载二级 index 文件。
  • #EXT-X-ENDLIST: 表示 m3u8 文件的结束,live m3u8 没有该 tag。它可以在 Playlist 中任意位置出现,但是只能出现一个,格式如下:#EXT-X-ENDLIST
  • #EXT-X-MEDIA: 被用来在 Playlist 中表示相同内容的不同语种/译文的版本,比如可以通过使用 3 个这种 tag 表示 3 种不同语音的音频,或者用 2 个这个 tag 表示不同角度的 video。在 Playlist 中,这个标签是独立存在的,其格式如下:#EXT-X-MEDIA:<attribute-list>
  • 该属性列表中包含:URL、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、AUTOSELECT
  • URL:如果没有,则表示这个 tag 描述的可选择版本在主 PlayListEXT-X-STREAM-INF 中存在
  • TYPEAUDIO and VIDEO
  • GROUP-ID:具有相同 IDMEDIAtag,组成一组样式
  • LANGUAGEidentifies the primary language used in the rendition
  • NAMEThe value is a quoted-string containing a human-readable description of the rendition. If the LANGUAGE attribute is present then this description SHOULD be in that language
  • DEFAULTYES 或是 NO,默认是 NO,如果是 YES,则客户端会以这种选项来播放,除非用户自己进行选择
  • AUTOSELECTYES 或是 NO,默认是 NO,如果是 YES,则客户端会根据当前播放环境来进行选择(用户没有根据自己偏好进行选择的前提下)
  • The EXT-X-MEDIA tag appeared in version 4 of the protocol
  • #EXT-X-STREAM-INF: 指定一个包含多媒体信息的 media URL 作为 Playlist,一般做 m3u8 的嵌套使用,它只对紧跟后面的 URL 有效,格式如下:#EXT-X-STREAM-INF:<attribute-list>
  • 常用的属性如下:
  • BANDWIDTH:带宽,必须有
  • PROGRAM-ID:该值是一个十进制整数,唯一地标识一个在 Playlist 文件范围内的特定的描述。一个 Playlist 文件中可能包含多个有相同 ID 的此 tag
  • CODECS:指定流的编码类型,不是必须的
  • RESOLUTION:分辨率
  • AUDIO:这个值必须和 AUDIO 类别的 EXT-X-MEDIA 标签中 GROUP-ID 属性值相匹配
  • VIDEO:同上
  • #EXT-X-DISCONTINUITY: 当遇到该 tag 的时候说明以下属性发生了变化:
  • file format
  • number and type of tracks
  • encoding parameters
  • encoding sequence
  • timestamp sequence
  • #ZEN-TOTAL-DURATION: 表示这个 m3u8 所含 TS 的总时间长度

3. HLS 之 TS

TS 文件为传输流文件,视频编码主要格式为 H264/MPEG4,音频为 AAC/MP3
TS 文件分为三层:

  • TS 层:Transport Stream,是在 pes 层的基础上加入数据流的识别和传输必须的信息。
  • pes 层: Packet Elemental Stream,是在音视频数据上加了时间戳等对数据帧的说明信息。
  • es 层:Elementary Stream,即音视频数据。

3.1 TS 层:Transport Stream

TS 包大小固定为 188 字节,TS 层分为三个部分:TS headeradaptation fieldpayloadTS header 固定 4 个字节;adaptation field可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充;payloadpes 数据。

3.1.1 TS header

ios m3u8 ios m3u8卡_xcode_02


TS 层的内容是通过 PID 值来标识的,主要内容包括:PAT 表、PMT 表、音频流、视频流。解析 TS 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到音视频流了。PAT 表的和 PMT 表需要定期插入 TS 流,因为用户随时可能加入 TS 流,这个间隔比较小,通常每隔几个视频帧就要加入 PATPMTPATPMT 表是必须的,还可以加入其它表如 SDT(业务描述表)等,不过 hls 流只要有 PATPMT 就可以播放了。

  • PAT 表:主要的作用就是指明了 PMT 表的 PID 值。
  • PMT 表:主要的作用就是指明了音视频流的 PID 值。
  • 音频流/视频流:承载音视频内容。

3.1.2 adaptation field

ios m3u8 ios m3u8卡_xcode_03


自适应区的长度要包含传输错误指示符标识的一个字节。pcr 是节目时钟参考,pcrdTSpTS 都是对同一个系统时钟的采样值,pcr 是递增的,因此可以将其设置为 dTS 值,音频数据不需要 pcr。如果没有字段,ipad 是可以播放的,但 vlc 无法播放。打包 TS 流时 PATPMT 表是没有 adaptation field 的,不够的长度直接补 0xff 即可。视频流和音频流都需要加 adaptation field,通常加在一个帧的第一个 TS 包和最后一个 TS 包里,中间的 TS 包不加。如下图所示:

ios m3u8 ios m3u8卡_xcode_04


PAT 格式如下图

ios m3u8 ios m3u8卡_xcode_05


PMT 格式如下图

ios m3u8 ios m3u8卡_xcode_06

3.2 pes 层:Packet Elemental Stream

pes 层是在每一个视频/音频帧上加入了时间戳等信息,pes 包内容很多,这里只留下最常用的。

pes 层格式如下图:

ios m3u8 ios m3u8卡_objective-c_07


pes 层内容如下图:

ios m3u8 ios m3u8卡_xcode_08


pTS 是显示时间戳、dTS 是解码时间戳,视频数据两种时间戳都需要,音频数据的 pTSdTS 相同,所以只需要 pTS。有 pTSdTS 两种时间戳是 B 帧引起的,I 帧 和 P 帧的 pTS 等于 dTS。如果一个视频没有 B 帧,则 pTS 永远和 dTS 相同。从文件中顺序读取视频帧,取出的帧顺序和 dTS 顺序相同。dTS 算法比较简单,初始值 + 增量即可,pTS 计算比较复杂,需要在 dTS 的基础上加偏移量。

音频的 pes 中只有 pTS(同 dTS),视频的 IP 帧两种时间戳都要有,视频 B 帧只要 pTS(同 dTS)。打包 pTSdTS 就需要知道视频帧类型,但是通过容器格式我们是无法判断帧类型的,必须解析 h.264 内容才可以获取帧类型。
举例说明:

.           I    P    B    B    B    P
读取顺序:   	1    2    3    4    5    6
dTS 顺序:   1    2    3    4    5    6
pTS 顺序:   1    5    3    2    4    6

点播视频 dTS 算法:
dTS = 初始值 + 90000 / video_frame_rate,初始值可以随便指定,但是最好不要取 0video_frame_rate 就是帧率,比如 2330

pTSdTS 是以 timestamp 为单位的,1s = 90000 time scale,一帧就应该是 90000/video_frame_ratetimescale

用一帧的 timescale 除以采样频率就可以转换为一帧的播放时长。

点播音频 dTS 算法:
dTS = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rateaudio_samples_per_frame 这个值与编解码相关,AAC 取值 1024mp3 取值 1158audio_sample_rate 是采样率,比如 2400041000. AAC 一般解码出来是每声道 1024sample,也就是说一帧的时长为 1024/sample_rate 秒。所以每一帧时间戳依次0,1024/sample_rate, …, 1024*n/sample_rate秒。

注:直播视频的 dTSpTS 应该直接用直播数据流中的时间,不应该按公式计算。

3.3 es 层:Elementary Stream

ES 层指的就是音视频数据。这里只介绍 h.264 视频和 AAC 音频。

3.3.1 h.264 视频

打包 h.264 数据时必须给视频数据加上一个 naluNetwork Abstraction Layer Unit),nalu 包括 nalu headernalu typenalu header 固定为 0x00000001(帧开始)或 0x000001(帧中)。h.264 的数据是由 slice 组成的,slice 的内容包括:视频spspps 等。nalu type 决定了后面的 h.264 数据内容。

0 1 2 3 4 5 6 7 
     +-+-+-+-+-+-+-+-+
     |F|NRI|  TYPE   |  
     +-+-+-+-+-+-+-+-+
  • F1bitforbidden_zero_bith.264 规定必须取 0
  • NRI2biTSnal_ref_idc,取值为 0~3,指示这个 nalu 的重要性,I 帧、spspps 通常取 3P 帧常取 2B 帧通常取 0
  • Type5biTS,取值如下表所示:
  • ios m3u8 ios m3u8卡_objective-c_09

  • 打包 es 层数据时 pes 头和 es 数据之间要加入一个 type=9nalu,关键帧 slice 前必须要加入 type=7type=8nalu,而且是紧邻的。如下图所示:
  • ios m3u8 ios m3u8卡_ios m3u8_10