参考资料:https://www.cnblogs.com/jimodetiantang/p/9133564.html
https://cloud.tencent.com/developer/article/1032541
https://blog.csdn.net/yuan1125/article/details/51540918
https://blog.csdn.net/max_min_go/article/details/39463675
https://blog.csdn.net/simongyley/article/details/34411577
https://blog.csdn.net/heyatzw/article/details/76165756
Android 源码分析之基于NuPlayer的HLS流媒体协议
HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议,用于实时音视频流的传输。目前HLS协议被广泛的应用于视频点播和直播领域。
HLS 跟 DASH 协议的原理非常类似。通过将整条流切割成一个小的可以通过 HTTP 下载的媒体文件,然后提供一个配套的媒体列表文件,提供给客户端,让客户端顺序地拉取这些媒体文件播放,来实现看上去是在播放一条流的效果。由于传输层协议只需要标准的 HTTP 协议,HLS 可以方便的透过防火墙或者代理服务器,而且可以很方便的利用 CDN 进行分发加速,并且客户端实现起来也很方便。
HLS 把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。HLS 协议由三部分组成:HTTP、M3U8、TS
。这三部分中,HTTP 是传输协议,M3U8 是索引文件,TS 是音视频的媒体信息。
关于 HLS 的详细介绍可参考: https://tools.ietf.org/html/draft-pantos-http-live-streaming-23
在 HTML5 页面上使用 HLS 非常简单:
直接:
<video src="example.m3u8" controls>video>
或者:
<video controls>
<source src="example.m3u8">source>
video>
HLS 是提供一个 m3u8 地址,Apple 的 Safari 浏览器直接就能打开 m3u8 地址,譬如:http://demo.srs.com/live/livestream.m3u8
Android 不能直接打开,需要使用 html5 的 video 标签,然后在浏览器中打开这个页面即可,譬如:
<video width="640" height="360"
autoplay controls autobuffer
src="http://demo.srs.com/live/livestream.m3u8"
type="application/vnd.apple.mpegurl">
video>
HLS 的 m3u8,是一个 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 的配置文件中都有配置项:
譬如,每个 ts 切片为 10 秒,窗口为 60 秒,那么 m3u8 中会保存 6 个 ts 切片。
每一个 .m3u8 文件,分别对应若干个 ts 文件,这些 ts 文件才是真正存放视频的数据,m3u8 文件只是存放了一些 ts 文件的配置信息和相关路径,当视频播放时,.m3u8 是动态改变的
,video 标签会解析这个文件,并找到对应的 ts 文件来播放,所以一般为了加快速度,.m3u8 放在 web 服务器上,ts 文件放在 cdn 上。
.m3u8 文件,其实就是以 utf-8 编码的 m3u 文件,这个文件本身不能播放,只是存放了播放信息的文本文件。
HLS的架构分为三部分:Server,CDN,Client 。即服务器、分发组件和客户端。
下面是 HLS 整体架构图:
(1) Server
服务器端将视频数据流编码、封装和切割为连续的、时长很短的MPEG-TS格式的文件,通常一个ts分片大概是10s;并提供一个配套的媒体列表文件(m3u8文件)。
视频封装格式:MPEG-TS。
编码:视频编码为H.264,音频编码为AAC, MP3, AC-3或者EC-3格式。
HLS也支持纯音频格式,通常是MPEG基本音频文件(MP4封装的AAC格式)。
(2) Distribution
由标准的网络服务器组成,接收客户端的请求和分发所有的资源包括m3u8列表文件和ts分片文件。
(3) Client
客户端先通过下载m3u8文件,再通过m3u8文件的索引地址顺序地拉取ts媒体文件播放。对于直播,它的索引文件一直处于动态变化的,你需要不断的更新索引文件 playlist 然后移除旧的索引文件。
一般为了加快速度,m3u8 放在 web 服务器上,ts 文件放在 cdn 上。
把视频文件上传到服务器上,视频会被转换成HLS格式的视频(即TS和m3u8文件)。Media encoder模块负责将视频源中的视频数据转码到目标编码格式(H264)的视频数据,然后Stream Segment模块将视频切片,切片的结果就是index file(m3u8)和ts文件了。
HLS的index文件就是m3u8的文件,先下载一级index file(master_playlist.m3u8),它里面记录了二级索引文件的地址(Alternate-A、Alternate-B、Alternate-C)的地址,然后客户端再去下载二级索引文件,二级索引文件中又记录了TS文件的下载地址,这样客户端就可以按顺序下载TS视频文件并连续播放。
DEMO:
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
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_low",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="audio_low.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_high",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,URI="audio_high.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=100000,CODECS="mp4a.40.2,avc1.4d401e",AUDIO="audio_low"
video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=200000,CODECS="mp4a.40.2,avc1.4d401e",AUDIO="audio_high"
video.m3u8
包含多种比特率的 Master Playlist。该文件是一个实际使用中的顶级 m3u8 文件,该文件中又定义了 http://example.com/low.m3u8
、http://example.com/mid.m3u8
等几个二级文件。顶级 m3u8 文件主要是做码率适配的,二级 m3u8 才是真正的切片文件,客户端会默认选择码率最高的请求,如果发现码率达不到,会请求降低码率的流。客户端拿到二级 m3u8 文件后,会继续请求里面的文件,这时就可以进行播放了。
(1)MPEG-2 Transport Streams
PAT
(Program Association Table) 跟 PMT
(Program Map Table)。(2)Fragmented MPEG-4
ftyp box
(包含一个高于 ios6 的 brand), moov box
必须紧跟在ftyp box
之后。moov box
必须包含一个 trak box
(对于每个 fMP4 segment 里面的 traf box
,包含匹配的 track_ID
)。每个 trak box
应该包含一个 sample table,但是它的 sample count 必须为 0。mvhd box
跟 tkhd
的 duration 必须为 0。mvex box
必须跟在上一个 trak box
后面。moov box
(包含 sample tables)和一个 mdat box
(包含对应的 samples),一个 fMP4 包含一个 moof box
(包含 sample table 的子集)和一个 mdat box
(包含对应的 samples)。traf box
必须包含一个 tfdt box
,fMP4 segment 必须使用 movie-fragment relative addressing。fMP4 segments 绝对不能使用外部的 data references。(3)Packed Audio
com.apple.streaming.transportStreamTimestamp
。(4)WebVTT
WebVTT header
。#EXT
开头的是 tag,其余的为注释,在解析时应该忽略。一个 URI 表示一个媒体段或是 "variant Playlist file"(最多支持一层嵌套,即一个 m3u8 文件中嵌套另一个 m3u8)。
逗号
分隔的 attribute/value 对列表。AttributeName=AttributeValue
。Basic Tags 可以用在 Media Playlist 和 Master Playlist 里面。
(1)#EXTM3U
每个 m3u8 文件第一行必须是这个 tag,如上面的两个示例,标识是一个 Extended M3U Playlist 文件。这个标签在媒体播放列表和主播放列表中均应该被包含。形式为:#EXTM3U
(2)#EXT-X-VERSION
本标签标明Playlist播放列表文件兼容的版本。其格式为:#EXT-X-VERSION:
每一个 Media Segment 通过一系列的 Media Segment tags 跟一个 URI 来指定。有的 Media Segment tags 只应用于下一个 segment,有的则是应用所有下面的 segments。一个 Media Segment tag 只能出现在 Media Playlist 里面。
(3)#EXTINF
指定每个媒体段(ts)的持续时间,这个仅对其后面的 URI 有效,每两个媒体段 URI 间被这个 tag 分隔开。其格式为:#EXTINF:
(4)#EXT-X-BYTERANGE
表示媒体段是一个媒体 URI 资源中的一段,只对其后的 media URI 有效,格式为:#EXT-X-BYTERANGE:
(5)#EXT-X-DISCONTINUITY
标明前后两个文件片编码方式不连续,当遇到该 tag 的时候说明以下属性发生了变化:
其不应出现在主播放列表中。实际中还没发现它的用处。
(6)#EXT-X-KEY
媒体文件片可能被加密,而本标签表明如何对其进行解密。它作用于其后所有的文件片,直到下一个同名标签(with the same KEYFORMAT)的出现。两个以上的EXT-X-KEY可能会将不同的KEYFORMAT属性作用于同一文件片,它们必须被解析为相同的key。格式为:#EXT-X-KEY:
以下是一些属性:
① METHOD:枚举值,标明加密方法,必须。
客户端遇到无法识别的METHOD,放弃解密。
② URI:引号包含的字符串,其URI指明获取密钥的地址,当METHOD取值为NONE时,该属性为必须。
③ IV:十六进制整数,指明密钥使用的初始向量。
④ KEYFORMAT:引号包含的字符串,指明密钥在URI中的表现形式。该属性可选,当该属性不出现时,其具有一个默认值”identity”。
⑤ KEYFORMATVERSION:引号包含的字符串,其内容为斜杠分隔的整数,如”1/3”,当有多个KEYFORMAT时,指明实例的版本,不出现表示值为1 。
(7)#EXT-X-MAP
用于指定 Media Initialization Section。本标签用于说明如何获取用于解析媒体文件片的头部信息,比如传输流 PAT/PMT 或者WebVTT头。它作用于其后出现的所有文件片,直到文件末尾或EXT-X-DISCONTINUITY出现。其格式如下:#EXT-X-MAP:
选项情况如下:
这个标签的使用情景:当播放列表文件中的第一个文件片在资源的开头部分没有和PAT/PMT紧随,且文件片带有EXT-X-I-FRAMES-ONLY标签。该标签不应出现在主列表文件中。建议EXT-X-MAP标记只用于资源不是以PAT/PMT开头的段。
(8)#EXT-X-PROGRAM-DATE-TIME
将一个绝对时间或是日期和一个媒体段中的第一个 sample 相关联一起来确定时间戳,只对下一个 media URI 有效,格式如下:#EXT-X-PROGRAM-DATE-TIME:
(9)#EXT-X-DATERANGE
将一个时间范围和一组属性键值对结合到一起。
Media Playlist tags 描述 Media Playlist 的全局参数。同样地,Media Playlist tags 只能出现在 Media Playlist 里面。
(10)#EXT-X-TARGETDURATION
指定当前视频流中的单个切片(即 ts)文件的最大时长(秒)。所以 #EXTINF 中指定的时间长度必须小于或是等于这个最大值。这个 tag 在整个 Playlist 文件中只能出现一次(在嵌套的情况下,一般有真正 ts url 的 m3u8 才会出现该 tag,它不可出现在主播放列表中)。格式为:#EXT-X-TARGETDURATION:
(11)#EXT-X-MEDIA-SEQUENCE
用于指定第一个 Media Segment 的 Media Sequence Number。每一个 media URI 在 Playlist 中只有唯一的序号,相邻之间序号 +1。格式为:#EXT-X-MEDIA-SEQUENCE:
。一个 media URI 并不是必须要包含的,如果没有,默认为 0。
本标签需出现在第一个文件片之前,且不能出现在主播放列表中。当媒体播放列表不包含该标签时,其首个文件片的序列号被视为0 。
文件资源定位符中可不出现序列号。
(12)#EXT-X-DISCONTINUITY-SEQUENCE
该标签允许多码流不同码流之间同步,并能够使多个流在它们的媒体播放列表文件中加入EXT-X-DISCONTINUITY标签。格式为:#EXT-X-DISCONTINUITY-SEQUENCE:
其中number是一个十进制整数。不连续的文件片序列号必须是递增的。
一个媒体播放列表不能包含多于一个EXT-X-DISCONTINUITY-SEQUENCE标签。如果列表文件中不包含该标签,则文件列表中第一个文件片的不连续序列号标记为0。
本标签必须出现在第一个文件片之前,且必须出现在EXT-X-DISCONTINUITY标签前,且只能出现在媒体播放文件列表中 。
如果媒体列表文件中EXT-X-PLAYLIST-TYPE的值为VOD或者EVENT,则不可使用本标签。
(13)#EXT-X-ENDLIST
表示 m3u8 文件的结束,live m3u8 没有该 tag。它可以在 Playlist 中任意位置出现,但是只能出现一个。格式为:#EXT-X-ENDLIST
(14)#EXT-X-PLAYLIST-TYPE
提供关于 Playlist 的可变性的类型信息,它对整个 Playlist 文件有效,是可选的。格式为:#EXT-X-PLAYLIST-TYPE:
(15)#EXT-X-I-FRAMES-ONLY
本选项标明播放列表文件中每个 Media Segment 都描述了一个单一的 I-frame。本标签作用于整个播放列表文件。其格式为:#EXT-X-I-FRAMES-ONLY
在拥有该标签的播放列表文件中,文件片的时长是从某一个I帧开始到另外一个I帧的出现或者文件列表末尾。
包含关键帧资源的媒体文件必须以传输流PAT/PMT开始,或者与EXT-X-MAP标签一起出现。
包含有EXT-X-BYTERANGE标签的I帧分片的字节范围不能包含PAT/PMT。本标签只应该出现在媒体文件列表中。
(16)#ZEN-TOTAL-DURATION
表示这个 m3u8 所含 ts 的总时间长度
Master Playlist tags 定义 Variant Streams,Renditions 和 其他显示的全局参数。Master Playlist tags 只能出现在 Master Playlist 中。
(17)#EXT-X-MEDIA
用于关联同一个内容的多个 Media Playlist 的多种 renditions。即被用来在 Playlist 中表示相同内容的不同语种/译文的版本,比如可以通过使用 3 个这种 tag 表示 3 种不同语音的音频,或者用 2 个这个 tag 表示不同角度的 video。在 Playlist 中,这个标签是独立存在的。其格式为:#EXT-X-MEDIA:
(18)#EXT-X-STREAM-INF
用于指定一个 Variant Stream。指定一个包含多媒体信息的 media URI 作为 Playlist,一般做 m3u8 的嵌套使用,它只对紧跟后面的 URI 有效。格式为:#EXT-X-STREAM-INF:
常用的属性如下:
该标签不应出现在媒体文件播放列表中。
(19)#EXT-X-I-FRAME-STREAM-INF
本标签标明媒体播放列表文件包含多媒体内容的I-frame。本标签单独使用,不依赖于任何资源的URI。其格式为:#EXT-X-I-FRAME-STREAM-INF:
本标签支持所有EXT-X-I-FRAME-STREAM-INF支持的属性,并额外支持一个URI属性。
(20)EXT-X-SESSION-DATA
存放一些 session 数据。
(21)EXT-X-SESSION-KEY
用于解密。
这里的 tags 可以出现在 Media Playlist 或者 Master Playlist 中。但是如果同时出现在同一个 Master Playlist 和 Media Playlist 中时,必须为相同值。
(22)#EXT-X-INDEPENDENT-SEGMENTS
本标签标明,一个分片可以独立解码而不需要其他分片的信息。本选项作用于列表文件中的所有项目。其格式为:#EXT-X-INDEPENDENT-SEGMENTS
本选项可选,但只能使用一次,当其放置在主列表文件中时,其作用于所有播放列表文件中的每一个文件片。貌似很有用啊。
(23)EXT-X-START
标识一个优选的点来播放这个 Playlist。
(24)#EXT-X-ALLOW-CACHE
是否允许做 cache,这个可以在 Playlist 文件中任意地方出现,并且最多只出现一次,作用效果是所有的媒体段。格式为:#EXT-X-ALLOW-CACHE:
来自: hls之m3u8、ts流格式详解
ts 文件为传输流文件,视频编码主要格式为 H264/MPEG4,音频为 AAC/MP3。
ts 文件分为三层:
ts 包大小固定为 188 字节,ts 层分为三个部分:ts header、adaptation field、payload。ts header 固定 4 个字节;adaptation field 可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充;payload 是 pes 数据。
字段 | 大小 n(bit) | 说明 |
---|---|---|
sync_byte | 8b | 同步字节,固定为0x47 |
transport_error_indicator | 1b | 传输错误指示符,表明在ts头的adapt域后有一个无用字节,通常都为0,这个字节算在adapt域长度内 |
payload_unit_start_indicator | 1b | 负载单元起始标示符,一个完整的数据包开始时标记为1 |
transport_priority | 1b | 传输优先级,0为低优先级,1为高优先级,通常取0 |
pid | 13b | pid值 |
transport_scrambling_control | 2b | 传输加扰控制,00表示未加密 |
adaptation_field_control | 2b | 是否包含自适应区,‘00’保留;‘01’为无自适应域,仅含有效负载;‘10’为仅含自适应域,无有效负载;‘11’为同时带有自适应域和有效负载。 |
continuity_counter | 4b | 递增计数器,从0-f,起始值不一定取0,但必须是连续的 |
ts 层的内容是通过 PID 值来标识的,主要内容包括:PAT 表、PMT 表、音频流、视频流。解析 ts 流要先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到音视频流了。PAT 表的和 PMT 表需要定期插入 ts 流,因为用户随时可能加入 ts 流,这个间隔比较小,通常每隔几个视频帧就要加入 PAT 和 PMT。PAT 和 PMT 表是必须的,还可以加入其它表如 SDT(业务描述表)等,不过 hls 流只要有 PAT 和 PMT 就可以播放了。
字段 | 大小 n(Byte) | 说明 |
---|---|---|
adaptation_field_length | 1B | 自适应域长度,后面的字节数 |
flag | 1B | 取0x50表示包含PCR或0x40表示不包含PCR |
PCR | 5B | Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)。 |
stuffing_bytes | xB | 填充字节,取值0xff |
自适应区的长度要包含传输错误指示符标识的一个字节。pcr是节目时钟参考,pcr、dts、pts都是对同一个系统时钟的采样值,pcr是递增的,因此可以将其设置为dts值,音频数据不需要pcr。如果没有该字段,ipad是可以播放的,但vlc无法播放。打包ts流时PAT和PMT表是没有adaptation field的,不够的长度直接补0xff即可。视频流和音频流都需要加adaptation field,通常加在一个帧的第一个ts包和最后一个ts包里,中间的ts包不加。
字段 | 大小 n(bit) | 说明 |
---|---|---|
table_id | 8b | PAT表固定为0x00 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
transport_stream_id | 16b | 传输流ID,固定为0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1,表示这个PAT表可以用,如果为0则要等待下一个PAT表 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
开始循环 | ||
program_number | 16b | 节目号为0x0000时表示这是NIT,节目号为0x0001时,表示这是PMT |
reserved | 3b | 固定为111 |
PID | 13b | 节目号对应内容的PID值 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
字段 | 大小 n(bit) | 说明 |
---|---|---|
table_id | 8b | PMT表取值随意,0x02 |
section_syntax_indicator | 1b | 固定为1 |
zero | 1b | 固定为0 |
reserved | 2b | 固定为11 |
section_length | 12b | 后面数据的长度 |
program_number | 16b | 频道号码,表示当前的PMT关联到的频道,取值0x0001 |
reserved | 2b | 固定为11 |
version_number | 5b | 版本号,固定为00000,如果PAT有变化则版本号加1 |
current_next_indicator | 1b | 固定为1 |
section_number | 8b | 固定为0x00 |
last_section_number | 8b | 固定为0x00 |
reserved | 3b | 固定为111 |
PCR_PID | 13b | PCR(节目参考时钟)所在TS分组的PID,指定为视频PID |
reserved | 4b | 固定为1111 |
program_info_length | 12b | 节目描述信息,指定为0x000表示没有 |
开始循环 | ||
stream_type | 8b | 流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03 |
reserved | 3b | 固定为111 |
elementary_PID | 13b | 与stream_type对应的PID |
reserved | 4b | 固定为1111 |
ES_info_length | 12b | 描述信息,指定为0x000表示没有 |
结束循环 | ||
CRC32 | 32b | 前面数据的CRC32校验码 |
pes 层是在每一个视频/音频帧上加入了时间戳等信息,pes 包内容很多,这里只留下最常用的。
pes 层格式如下图:
字段 | 大小 n(Byte) | 说明 |
---|---|---|
pes start code | 3B | 开始码,固定为0x000001 |
stream id | 1B | 音频取值(0xc0-0xdf),通常为0xc0 ;视频取值(0xe0-0xef),通常为0xe0 |
pes packet length | 2B | 后面pes数据的长度,0表示长度不限制,只有视频数据长度会超过0xffff |
flag | 1B | 通常取值0x80,表示数据不加密、无优先级、备份的数据 |
flag | 1B | 取值0x80表示只含有pts,取值0xc0表示含有pts和dts |
pes data length | 1B | 后面数据的长度,取值5或10 |
pts | 5B | 33bit值 |
dts | 5B | 33bit值 |
pts是显示时间戳、dts是解码时间戳,视频数据两种时间戳都需要,音频数据的pts和dts相同,所以只需要pts。有pts和dts两种时间戳是B帧引起的,I帧和P帧的pts等于dts。如果一个视频没有B帧,则pts永远和dts相同。从文件中顺序读取视频帧,取出的帧顺序和dts顺序相同。dts算法比较简单,初始值 + 增量即可,pts计算比较复杂,需要在dts的基础上加偏移量。
音频的pes中只有pts(同dts),视频的I、P帧两种时间戳都要有,视频B帧只要pts(同dts)。打包pts和dts就需要知道视频帧类型,但是通过容器格式我们是无法判断帧类型的,必须解析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
,初始值可以随便指定,但是最好不要取0,video_frame_rate就是帧率,比如23、30。
pts和dts是以timescale为单位的,1s = 90000 time scale , 一帧就应该是90000/video_frame_rate 个timescale。
用一帧的timescale除以采样频率就可以转换为一帧的播放时长
点播音频dts算法:
dts = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate
,audio_samples_per_frame这个值与编解码相关,aac取值1024,mp3取值1158,audio_sample_rate是采样率,比如24000、41000。AAC一帧解码出来是每声道1024个sample,也就是说一帧的时长为1024/sample_rate秒。所以每一帧时间戳依次0,1024/sample_rate,...,1024*n/sample_rate秒
。
直播视频的dts和pts应该直接用直播数据流中的时间,不应该按公式计算。
es 层指的就是音视频数据。这里只介绍 h.264 视频和 aac 音频。
video:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| start code(4 byte)| nalu header(1 byte) | h264 data(x byte) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
audio:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| adts header(7 byte) | aac data(x byte) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
打包 h.264 数据时必须给视频数据加上一个 nalu(Network Abstraction Layer Unit),nalu 包括 nalu header 和 nalu type,nalu header 固定为 0x00000001(帧开始)或 0x000001(帧中)。h.264 的数据是由 slice 组成的,slice 的内容包括:视频、sps、pps 等。nalu type 决定了后面的 h.264 数据内容。
nalu header:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| TYPE |
+-+-+-+-+-+-+-+-+
打包 es 层数据时 pes 头和 es 数据之间要加入一个 type=9 的 nalu,关键帧 slice 前必须要加入 type=7 和 type=8 的 nalu,而且是紧邻的。如下图所示:
打包aac音频必须加上一个adts(Audio Data Transport Stream)头,一般情况下ADTS的头信息共7Byte,adts包括fixed_header和variable_header两部分,各28bit。固定头信息中的数据每一帧都相同,而可变头信息则在帧与帧之间可变。
fixed_header
字段 | 大小 n(bit) | 说明 |
---|---|---|
syncword | 12bit | 固定为0xfff |
id | 1bit | 0表示MPEG-4,1表示MPEG-2 |
layer | 2bit | 固定为00 |
protection_absent | 1bit | ADTS Header的长度可能为7字节或9字节,0表示9字节,1表示7字节 |
profile | 2bit | 取值0~3,1表示aac |
sampling_frequency_index | 4bit | 表示采样率,0: 96000 Hz,1: 88200 Hz,2: 64000 Hz,3:48000 Hz,4: 44100 Hz,5: 32000 Hz,6: 24000 Hz,7: 22050 Hz,8: 16000 Hz,9: 12000 Hz,10: 11025 Hz,11: 8000 Hz,12: 7350 Hz |
private_bit | 1bit | 固定为0 |
channel_configuration | 3bit | 取值0~7,1: 1 channel: front-center,2: 2 channels: front-left, front-right,3: 3 channels: front-center, front-left, front-right,4: 4 channels: front-center, front-left, front-right, back-center |
original_copy | 1bit | 固定为0 |
home | 1bit | 固定为0 |
variable_header
字段 | 大小 n(bit) | 说明 |
---|---|---|
copyright_identification_bit | 1bit | 固定为0 |
copyright_identification_start | 1bit | 固定为0 |
aac_frame_length | 13bit | 包括adts头在内的音频数据总长度 |
adts_buffer_fullness | 11bit | 固定为0x7ff |
number_of_raw_data_blocks_in_frame | 2bit | 固定为00 |
一个PAT包含整个TS流的信息,其中里面有一张表,比较重要的两个属性 program_number和program_map_PID,可能出现多对,
每一对program_number表示一个节目,而与该program_number对应的program_map_PID则表示该节目对应的流信息应该放在一个PMT表中,
而该PMT表的PID应该与这里的program_map_PID相等。
一个PMT中描述了流的类型,其中0x0f表示AAC音频,而0x1b表示H264视频,除这两种之外还有其他流,例如字幕。
elementay_PID表示该流的数据应该存放在以该PID为表示的TS包中。
+-+-+-+-+-+-+-+-+-+-+-+
| PAT |
| |
| program_number 5 |___
| program_map_PID 10 | |
| | |
| program_number 6 |___|__
| program_map_PID 11 | | |
| | | |
| program_number 7 | | |
| program_map_PID 12 | | |
| | | |
| ... | | |
| | | |
+-+-+-+-+-+-+-+-+-+-+-+ | |
| |
+-+-+-+-+-+-+-+-+-+-+-+ | |
| PMT | | |
| TS Header PID = 10 |<—— |
| | |
| stream_type 0x0f |______|__________________0x0f表示AAC音频,下方AAC数据打包PID=20,
| elementary_PID 20 | |
| stream_type 0x1b |______|__________________0x1b表示H264视频,下方H264数据打包PID=22
| elementary_PID 22 | |
| | |
+-+-+-+-+-+-+-+-+-+-+-+ |
|
+-+-+-+-+-+-+-+-+-+-+-+ |
| PMT | |
| TS Header PID = 6 |<—————
| |
| stream_type 0x0f |
| elementary_PID 23 |
| stream_type 0x1b |
| elementary_PID 24 |
| |
+-+-+-+-+-+-+-+-+-+-+-+
裸ACC数据:
+-+-+-+-+-+-+-+-+-+-+-+
| AAC |
+-+-+-+-+-+-+-+-+-+-+-+
添加PES头的ACC数据:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| AAC PES | AAC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
添加TS头将PES分割之后的TS包,假设正好分割成2个TS包,包大小固定188字节,不够用adaptation域填充一般填充0xFF:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|TS | AAC PES | AAC 1 |TS | adaptation| AAC 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|- - - - Packet 1 - - - |- - - - - Packet 2 - - - - - |
<假设 PID = 20 的TS包>
裸H264数据,一帧:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| H264 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
添加PES头之后的H264数据,一帧表示一个PES包:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| H264 PES| H264 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
将一个PES包分割之后,分别添加TS头之后的TS包,这里假设分割成3个TS包,每个包固定大小188字节(包含TS包头在内),
最后一个包不够188字节使用adaptaion域填充0xFF:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|TS | H264 PES| H264 1|TS | H264 2 |TS | adaptation| H264 3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|- - - Packet 1 - - - |- - Packet 2 - - | - - - - Packet 3 - - - -|
<假设 PID = 22 的TS包>
HLS是Apple公司推出的动态码率自适应技术,HLS内容格式在HTTP Live Streaming draft-pantos-http-live-streaming-23中描述。
HTTP Live Streaming中内容加密有两种,一种是对TS切片文件直接加密;另一种是对H.264编码文件中类型为1和5的NAL单元进行加密,其它类型的NAL单元不加密
[1]。
HLS中媒体分块如果是加密的,其加密密钥通过M3U8文件中的#EXT-X-KEY来指定,密钥文件由客户端从服务器请求认证获得。一个播放列表可以有一个以上的#EXT-X-KEY,同一个媒体段也可以有多个不同KEYFORMAT属性值的#EXT-X-KEY。如果播放列表仅有一个#EXT-X-KEY,则密钥文件的生命期从当前#EXT-X-KEY开始到播放列表结束;如果播放列表有两个或以上的#EXT-X-KEY,则密钥文件的生命期从当前#EXT-X-KEY开始到下一个#EXT-X-KEY结束。
#EXT-X-KEY的格式如下:
#EXT-X-KEY:
属性包括METHOD、URI、IV、KEYFORMAT、KEYFORMATVERSIONS,属性说明见表8。
METHOD属性为NONE时,表示媒体内容未加密,这种情况下不允许出现URI、IV、KEYFORMAT、KEYFORMATVERSIONS等属性;
METHOD属性为AES-128时,表示媒体内容采用AES-128方式对TS切片文件直接加密,这种情况下URI属性必须出现,IV属性可以出现也可以不出现;
METHOD属性为SAMPLE-AES时,表示对媒体段的部分或全部ES流加密,ES可以为音频、视频或其他样本,每一种ES的具体加密方式依赖于媒体编码,这种情况下IV属性可以出现也可以不出现。
IV为十六进制整数,代表加密初始向量。采用AES-128方式加密时,如果IV属性存在,则必须使用IV作为初始向量实现加密;如果IV属性不存在,使用媒体段的序列码作为初始向量实现加密。
KEYFORMAT标识密钥在密钥文件中的存储方式。默认是”identity”。GY/T 277-2014中增加扩展,如果KEYFORMAT=”chinadrm”,表示URI中给出的ChinaDRM规定的获取许可证的相关信息,包括许可证服务器地址和内容标识。
(1)TS层加密
如果属性METHOD的值为 AES-128
,并且播放列表中包含#EXT-X-I-FRAMES-ONLY标签(只包含I帧流),则整个媒体段使用AES-128 CBC加密。
如果属性METHOD的值为 AES-128
,并且播放列表中不包含#EXT-X-I-FRAMES-ONLY标签,则CBC不能跨越媒体段,每个媒体段单独使用AES-128 CBC加密,从而实现整个媒体段全部内容的加密。
上述的两种加密方式,对TS切片文件直接加密,这种加密方式在切片时实现,需要在网络电视台及CDN系统编码器中集成加密功能,实施复杂度大,成本高。
(2)ES流加密
METHOD属性为 SAMPLE-AES
时,表示媒体在内容打包封装之前对ES流加密。ES层的加密对每个包含16字节整数倍的数据块以AES-128 CBC方式加密。对于视频数据,媒体段从第一个16字节数据块开始,每间隔10的整倍数的16字节的数据块加密(即1,16,26。。。),对于音频数据,所有的16字节数据块都必须加密。
SAMPLE-AES加密方式加密后的ES流不受上层封装及切片的影响,因此可以在不影响网络电视台现有系统及CDN部署的情况下实现对视频内容的加密,满足网络电视台等对视频内容保护的要求。
视频流的加密
ES类型为视频流时,H.264编码文件中类型为1和5的NAL单元必须加密,其它类型的NAL单元不加密。
加密的NAL单元需要增加预防二义性的前缀,该前缀是未加密的。NAL单元中第一个字节的NAL_unit_type和随后的31个字节是不加密的,其后是加密的数据段,数据段的长度必须是16的整数倍(因此长度小于48字节NAL单元是不被加密的),被保护的数据段采用10%跳跃加密,即每16字节的加密数据块,跟随9个16字节不加密的数据块,以此类推。加密H.264流时, 类型为1或5并且长度大于48个字节的NAL单元必须基于上述方式加密,加密完成后为这些加密单元加上前缀码;解密H.264流时,类型为1和5的并且长度大于48字节的NAL单元需要解密,首先移除前缀码,然后按上述方式定位加密数据并解密。
音频流的加密
ES类型为AAC音频帧时,包含ADTS头的音频帧为加密帧。AAC的加密帧不需要增加预防二义性的前缀,AAC帧中7-9字节的ADTS头,以及之后的首个16字节不加密,其后是加密数据段,加密数据段的长度应该为16的整数倍,余下的0-15个字节不加密。
ES类型为AC-3音频帧时,全部的音频帧都加密。AC-3的加密帧不需要增加预防二义性的前缀,AC-3帧中的首个16字节不加密,其后是加密数据段,加密数据段的长度应该为16的整数倍,余下的0-15个字节不加密。
(注意: 对于纯音频格式,MP4封装的AAC音频不需要增加预防二义性的前缀,其加密数据段的长度应该为16的整数倍,余下的0-15个字节不加密。)
HLS格式的视频,只有安卓4.0以上才支持,目前4.0以下的机子基本可以考虑,不兼容了,所以为了减少工作量,这里继续使用MediaPlayer来进行播放。
HLS格式的视频,通过一个m3u8文件,然后里面包含若干个TS文件片段,这里有个苹果的官方的一个例子:
http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8
里面的内容为:
#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
fileSequence3.ts
#EXTINF:10, no desc
fileSequence4.ts
#EXTINF:10, no desc
fileSequence5.ts
#EXTINF:10, no desc
fileSequence6.ts
#EXTINF:10, no desc
fileSequence7.ts
我们可以看到里面它有一个一个ts视频片段,这一个一个视频片段就是我们需要的播放,那么它是如何被播放器识别播放的呢。
其实上面的这些关键的字段都是约定好的,MediaPlayer会去按照规定好的字段去解析这个m3u8文件,然后拼接成最终的播放地址进行播放。那么它在播放器最终播放的地址是怎么样的呢,是这样的
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence0.ts
你可以直接用这个地址播放看看。
实现这种未加密的缓存还是比较好实现的,大概可以分为这几步。
看下加密的m3u8文件的格式:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-KEY:METHOD=AES-128,URI="http://xxxxxx:5555//test/1102/test/segments.key"
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:19
#EXTINF:13.966667,
http://xxxxxx:5555/test/1102/test/segments0.ts
#EXTINF:10.000000,
http://xxxxxx:5555/test/1102/test/segments1.ts
#EXTINF:10.000000,
http://xxxxxx:5555/test/1102/test/segments2.ts
#EXTINF:10.000000,
http://xxxxxx.cn:5555/test/1102/test/segments3.ts
#EXTINF:10.000000,
http://xxxxxxn.cn:5555/test/1102/test/segments4.ts
#EXTINF:7.033333,
http://xxxxxx:5555/test/1102/test/segments5.ts
#EXTINF:10.000000,
我们看到了多了个字段EXT-X-KEY
,这个也是m3u8给规定好的加密字段,如果包含这个字段播放器就会先去请求这个key,然后拿这个这个key去访问加密的TS视频就可以播放了。 其实看到这我们就因该有思路怎么去做,加密的缓存播放了。
实现播放加密缓存的思路:
由于客户端每次请求 TS 或 M3U8 有可能都是一个新的连接请求,所以,我们无法有效的标识客户端,一旦出现问题,基本无法有效的定位问题。所以,一般工业级的服务器都会对传统的 HLS 做一些改进,常见优化是对每个M3U8文件增加Session来标识一条 HLS 连接。
这里主要介绍网宿的 Variant HLS 与又拍云的 HLS+.
首先, 我们可以下载一条网宿的 M3U8 文件:
wget http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8
然后, 打开下载得到的 playlist 文件:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=781000
http://bililive.kksmg.com/hls/stvd6edb9a6_45b34047833af658bf4945a8/playlist.m3u8?
wsSession=0105cb4e8fe63bccab511a4a-
149017212774715&wsIPSercert=b80d38c068c9e3634a7ebb2f2bbf9b89&wsMonitor=-1
可以看出这是一个 Master Playlist,里面嵌套了一层 M3U8,同时可以看出网宿采用 wsSession
来标识一条播放连接。
Variant HLS
首先,我们可以下载一条又拍云的 M3U8 文件:
wget http://uplive.b0.upaiyun.com/live/loading.m3u8
然后, 打开下载得到的 playlist 文件:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:1
#EXTINF:0.998, no desc
http://183.158.35.12:8080/uplive.b0.upaiyun.com/live/loading-0.ts?
shp_uuid=e4989f34fcab282e21ef1fd2980284cb&shp_ts=1490172420851&shp_cid=17906&shp_pid=3
370578&shp_sip0=127.0.0.1&shp_sip1=183.158.35.12&domain=uplive.b0.upaiyun.com&shp_seqno=0
可以看出又拍云的 HLS+ 也支持这种 Variant HLS 方式来标识一条 HLS 连接,可以看出,又拍云使用 uuid 来表示一条 HLS 连接。
首先,以 HTTP 302 方式来请求播放地址。
❯ curl -v http://uplive.b0.upaiyun.com/live/loading.m3u8\?shp_identify\=302 -o playlist
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 183.158.35.59...
* TCP_NODELAY set
* Connected to uplive.b0.upaiyun.com (183.158.35.59) port 80 (#0)
> GET /live/loading.m3u8?shp_identify=302 HTTP/1.1
> Host: uplive.b0.upaiyun.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: marco/0.26
< Date: Wed, 22 Mar 2017 08:54:11 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 259
< Connection: keep-alive
< Access-Control-Allow-Methods: GET
< Access-Control-Allow-Origin: *
< Location: http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?
shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1
730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_iden
tify=302
<
{ [259 bytes data]
* Curl_http_done: called premature == 0
100 259 100 259 0 0 4813 0 --:--:-- --:--:-- --:--:-- 4886
* Connection #0 to host uplive.b0.upaiyun.com left intact
打开 playlist 内容:
Redirect to http://183.158.35.19:8080/uplive.b0.upaiyun.com/live/loading.m3u8?
shp_uuid=2862b1b817a74cf719b1cd8f554616cd&shp_ts=1490172851450&shp_cid=59553&shp_pid=1
730488&shp_sip0=127.0.0.1&shp_sip1=183.158.35.19&domain=uplive.b0.upaiyun.com&shp_identify=302
在跳转之后的地址存放真正的 playlist,同时,也能够将 uuid 加入到了连接上。
总地来说,不管通过哪种方式,最终我们都能通过一个唯一的 id 来标识一条流,这样在排查问题时就可以根据这个 id 来定位播放过程中的问题。
HLS 理论延时 = 1 个切片的时长 + 0-1个 td (td 是 EXT-X-TARGETDURATION, 可简单理解为播放器取片的间隔时间)
+ 0-n 个启动切片(苹果官方建议是请求到 3 个片之后才开始播放) + 播放器最开始请求的片的网络延时(网络连接耗时)
为了追求低延时效果,可以将切片切的更小,取片间隔做的更小,播放器未取到 3 个片就启动播放。但是,这些优化方式都会增加 HLS 不稳定和出现错误的风险。
本章介绍服务器怎样产生播放列表和媒体文件以及客户端怎样下载并播放。
MPEG-2数据流的产生超过了本文档的范围,本文档仅仅假设有一个数据流连续的源(比如rtmp直播流)。
服务器必须将数据流分割成持续时间大致相等的媒体文件,服务器应该尝试点分割流来支持对个别媒体文件的有效解码,例如包和关键帧的边界。
服务器必须为媒体文件创建URL,允许它的客户端能够获取到文件。
服务器必须创建播放列表。播放列表必须符合第二章描述的格式。服务器要提供的媒体文件的URL必须按顺序出现在播放列表中。如果URL出现在了播放列表中,那么这个媒体文件对于客户端必须是可用的。
播放列表文件必须包含一个EXT-X-TARGRTDURATION标签,它必须指明添加到播放列表中媒体文件的最大EXTINF值。整个演示文稿期间,这个值必须保持不变。典型持续时间为10s。
播放列表文件应该(但非必须)包含EXT-X-VERSION标签来说明流对于版本的兼容性。它的值应该是服务器、播放列表文件和其所关联的媒体文件都能执行的最低协议版本。
如果播放列表文件通过HTTP传输,那么服务器应该支持客户端请求使用gzip内容编码。
从客户端的角度来看,播放列表文件的变更必须是自动的。
服务器不可以改变EXT-X-ALLOW-CATCH的值。
播放列表中每个媒体文件的URL必须以EXTINF作为前缀来说明媒体文件的持续时间。
服务器可以将媒体文件和绝对的日期和时间关联起来,只要在它的URL前缀加上一个EXT-X-PROGRAM-DATE-TIME标签。 日期和时间的值提供了一个媒体时间表到挂钟时间的信息映射,该挂钟时间可以作为搜索、显示或其他目的的基准。
如果服务器提供了这个映射,那么它应该在每个EXT-X-DISCONTINUITY标签的后边加一个EXT-X-PROGRAM-DATE-TIME标签。
如果播放列表文件包含演示文稿的最后一个分片,那么应该加一个EXT-X-ENDLIST标签。
如果播放列表文件没有包含EXT-X-ENDLIST标签(hls的live流),那么服务器应该使一个新版本的播放列表文件可用,并至少包含一个媒体文件的URL。新的播放列表文件必须与前一个播放列表文件在相对的时间内有效:从上一个播放列表文件开始有效的时间算起,不早于0.5倍持续时间,不晚于1.5倍持续时间。也就是说hls的live流要时刻更新m3u8文件,而更新时间要保持在[0.5,1.5]个ts持续时间内。
如果服务器期望移除演示文稿,它必须使播放列表文件对于客户端不可用,在播放列表被清除时,它应该确保播放列表文件中的所有媒体文件对于客户端来说至少在一个播放列表文件持续时间内是可用的。
服务器可以限制最近一段时间添加到播放列表文件中的媒体文件的可用性,为了达到这个目的,播放列表文件必须包含准确的EXT-X-MEDIA-SEQUENCE标签。标签的值是按照从播放列表中移除的媒体文件的URL递增的。
媒体文件的URL必须按照其加入的顺序移除。
当服务器从播放列表移除URL时,媒体文件在一段时间内必须保持可用,该时间等于媒体文件的时间加上包含该媒体文件的最长播放列表文件的时间。
当媒体文件通过http传输给客户端后,如果服务器打算移除该文件,那么它应该确保http响应头包含反应生存时间的过期头。
那些不包含EXT-X-ENDLIST标签的播放列表文件的持续时间必须至少三倍于targrt dutration。之所以为三倍的targrt dutration可能是因为,根据hls协议来看,每个终端的播放行为都是不一致的,对于点播的m3u8文件来说,都是从第一个文件开始播,但是对于直播的m3u8,播放器可以从任意一个文件开始向后(新的文件)追溯。不过一般的播放器都是从倒数第三个开始。
如果媒体文件需要被加密,那么服务器必须定义一个URL来允许被授权的客户端获取包含解密密钥的密钥文件。
服务器可以在密钥响应中设置超时头来表名密钥可以被缓存。
如果采用AES-128加密算法,那么AES-128 CBC加密模式应该适应于每一个媒体文件。整个文件必须是加密的。密码块的连接不能用于跨媒体文件。用于解密的初始化向量必须是媒体文件的序列号或者EXT-X-KEY标签的IV属性的值。
服务器必须使用这种加密算法和其他由紧随在播放列表文件中URL后边的EXT-X-KEY标签所指定的属性来加密播放列表文件中的每一个媒体文件。EXT-X-KEY标签中方法为none或者没有EXT-X-KEY标签的媒体文件不能被加密。
如果播放列表文件包含了一个经过加密的媒体文件的URL,那么服务器不可以将EXT-X-KEY标签从播放列表文件中移除。
服务器可以提供多个播放列表文件来支持对同一个演示文稿的不同编码。提供变种播放列表文件列出每一个变种流,从而使得客户端可以在不同编码之间动态切换。
变种播放列表文件必须为每一个变种流包含一个EXT-X-STREAM-INF标签(即嵌套的m3u8结构)。同一演示文稿的每个EXT-X-STREAM-INF都必须有相同的programid。每个演示文稿的programid在变种播放列表内必须是唯一的。
如果EXT-X-STREAM-INF标签包含CODECS属性,则属性值必须包含RFC4281定义的所有格式。
服务器在生成变种流的时候必须遵守以下规则:
另外,所有的变种流都应该包含相同编码的音频二进制流。这使得客户端在不同的流之间切换时没有毛刺声音(音视频不同步导致的)。
客户端怎样获取播放列表中的URL不在本文档的范围之内,我们假设已经获取到了URL。
每一次加载或者重载播放列表文件时:
客户端必须保证播放列表文件以EXTM3U标签开头,并且如果协议版本号存在,客户端必须支持该版本。否则,客户端不可以试图使用该列表文件。
客户端可以忽略它不能识别的标签和属性。
如果播放列表文件包含了EXT-X-MEDIA-SEQUENCE标签,那么客户端会假设在播放列表被加载的时间内以及播放列表的持续时间内媒体文件将变得不可用。播放列表的持续时间等于其中包含的媒体文件时长的总和。
当开始播放的时候,客户端首先从播放列表中选择要播放的媒体文件。如果不存在EXT-X-ENDLIST标签,并且客户端想正常播放媒体(按顺序以标准速率播放),那么客户端就不应该从播放列表文件尾部选择少于三个target duration的媒体文件。
为了达到正常播放的目的,媒体文件必须按照他们在播放列表中的顺序播放。客户端还可以用其他任何方式播放,比如顺序播放,随机播放,特效播放等。
对于存在EXT-X-DISCONTINUITY标签的媒体文件,在播放之前客户端必须准备好重置分析和解码器。
为了不间断播放,应该提前载入媒体文件,以补偿延时和吞吐量的变化。
如果播放列表文件包含了EXT-X-ALLOW-CATCH标签,并且它的值为NO,那么客户端在播放以后不可以缓存媒体文件。否则允许缓存用来以后重播。
客户端可以使用EXT-X-PROGRAM-DATE-TIME标签来为用户显示节目的起始时间。如果这个值包含了时区信息,那么客户端应该考虑到这点;如果不包含,那么客户端不可以推测时区。
客户端不能依靠EXT-X-ALLOW-CATCH标签值的正确性和一致性。
客户端必须阶段性的重新载入播放列表文件,除非文件包含了EXT-X-ENDLIST标签。然而也不能过于频繁的载入。
当客户端第一次载入播放列表文件或者已经载入但是发现文件与上次载入的时候有了变化,客户端都必须等待一段时间才可以再次载入。这段时间被称为原始最小重载延迟,它是从客户端开始载入一个播放列表文件开始计算的。
原始最小重载延迟是播放列表文件中最后一个媒体文件的持续时间。
媒体文件的持续时间由EXTINF标签来指定。
如果客户端重载了一个播放列表文件,但是发现文件并没有变化,那么它在重试之前必须等一段时间。最小延迟是target duration的倍数。第一次是0.5倍,第二次1.5倍,3倍。。。
当播放列表文件被载入或者重载以后,客户端必须检查播放列表来确定要载入的媒体文件。要载入的第一个文件必须是客户端要播放的第一个文件(直播时候,未必是m3u8中的第一个文件
)。
如果要播放的文件已经被载入,并且播放列表文件不包含EXT-X-MEDIA-SEQUENCE标签,那么客户端必须确认播放列表文件包含了最后一个被载入的媒体文件的URL,如果不包含,则暂停播放。要载入的下一个媒体文件必须是上一次载入的媒体文件URL之后的第一个媒体文件的URL。
如果要播放的文件已经被载入,并且播放列表文件包含EXT-X-MEDIA-SEQUENCE标签,那么要载入的下一个媒体文件就是比上一次载入的文件的序列号大的媒体文件中的序列号最小者。
如果播放列表文件包含了一个指定密钥文件URL的EXT-X-KEY标签,客户端必须获取密钥文件,并使用其中的密钥来解密KEY标签之后的所有媒体文件,直到遇到另一个EXT-X-KEY标签为止。