流媒体协议HLS解析
参考资料: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流媒体协议
文章目录
- 1、综述
- 1.1 原理介绍
- 1.2 整体框架
- 1.3 HLS协议编码格式要求
- 1.4 HLS相关文章
- 2、HLS 之 M3U8
- 2.1 HLS Media Segments
- 2.1.1 支持的 Media Segment 格式
- 2.2 HLS Playlists
- 2.3 Attribute Lists
- 2.4 Basic Tags
- 2.5 Media Segment Tags
- 2.6 Media Playlist Tags
- 2.7 Master Playlist Tags
- 2.8 Media or Master Playlist Tags
- 3、HLS 之 TS
- 3.1 ts 层:Transport Stream
- 3.1.1 ts header
- 3.1.2 adaptation field
- 3.1.3 PAT(Program Associate Table)格式 节目关联表
- 3.1.4 PMT(Program Map Table)格式 节目映射表
- 3.2 pes 层:Packet Elemental Stream
- 3.3 es 层:Elementary Stream
- 3.3.1 h.264 视频
- 3.3.2 aac音频
- 3.4 ts打包流程图
- 4、HLS中的内容加密
- 4.1 HLS概述
- 4.2 加密方式
- 4.3 加密机制
- 5、HLS 播放
- 5.1 播放未加密的HLS
- 5.2 播放加密HLS
- 6、HLS 协议总结
- 6.1 优点
- 6.2 缺点
- 6.3 改进
- 6.3.1 网宿的 Variant HLS
- 6.3.2 又拍云的 HLS+
- 6.3.3 HTTP 302
- 6.4 HLS 延时分析
- 7、客户端/服务器行为
- 7.1 服务器进程
- 7.1.1介绍
- 7.1.2 滑动窗口播放列表
- 7.1.3 加密媒体文件
- 7.1.4 提供变种数据流
- 7.2 客户端进程
- 7.2.1 介绍
- 7.2.2 加载播放列表文件
- 7.2.3播放播放列表文件
- 7.2.4重新载入播放列表文件
- 7.2.5 确定下一个要加载的文件
- 7.2.6 解密经加密的媒体文件
1、综述
HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议,用于实时音视频流的传输。目前HLS协议被广泛的应用于视频点播和直播领域。
1.1 原理介绍
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 标签,然后在浏览器中打开这个页面即可,譬如:
<!-- livestream.html -->
<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 的配置文件中都有配置项:
- EXT-X-TARGETDURATION:所有切片的最大时长。有些 Apple 设备这个参数不正确会无法播放。SRS 会自动计算出 ts 文件的最大时长,然后更新 m3u8 时会自动更新这个值。用户不必自己配置。
- EXTINF:ts 切片的实际时长,SRS 提供配置项 hls_fragment,但实际上的 ts 时长还受 gop 影响。
- ts 文件的数目:SRS 可配置 hls_window,指定 m3u8 中保存多少个切片,SRS 会自动清理旧的切片。
- livestream-67.ts:SRS 会自动维护 ts 切片的文件名,在编码器重推之后,这个编号会继续增长,保证流的连续性。直到 SRS 重启,这个编号才重置为 0。
譬如,每个 ts 切片为 10 秒,窗口为 60 秒,那么 m3u8 中会保存 6 个 ts 切片。
每一个 .m3u8 文件,分别对应若干个 ts 文件,这些 ts 文件才是真正存放视频的数据,m3u8 文件只是存放了一些 ts 文件的配置信息和相关路径,当视频播放时,.m3u8 是动态改变的
,video 标签会解析这个文件,并找到对应的 ts 文件来播放,所以一般为了加快速度,.m3u8 放在 web 服务器上,ts 文件放在 cdn 上。
.m3u8 文件,其实就是以 utf-8 编码的 m3u 文件,这个文件本身不能播放,只是存放了播放信息的文本文件。
1.2 整体框架
HLS的架构分为三部分:Server,CDN,Client 。即服务器、分发组件和客户端。
下面是 HLS 整体架构图:
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 file
HLS的index文件就是m3u8的文件,先下载一级index file(master_playlist.m3u8),它里面记录了二级索引文件的地址(Alternate-A、Alternate-B、Alternate-C)的地址,然后客户端再去下载二级索引文件,二级索引文件中又记录了TS文件的下载地址,这样客户端就可以按顺序下载TS视频文件并连续播放。
1.3 HLS协议编码格式要求
- 视频的编码格式:H264
- 音频的编码格式:AAC、MP3、AC-3
- 视频的封装格式:ts
- 保存 ts 索引的 m3u8 文件
1.4 HLS相关文章
- HLS 协议详解
- Apple Developer—HTTP Live Streaming Overview
- MPEG-2 Stream Encryption Format for HTTP Live Streaming
- 流媒体协议—HLS
DEMO:
- M3U8 golang library
- HLS downloader:读取一个 m3u8 URL,下载为 TS 文件。
- https://github.com/selsta/hlsdl
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
#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 文件后,会继续请求里面的文件,这时就可以进行播放了。
- 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 里面是同一个流的多个不同版本(如: 分辨率, 码率不同)。
2.1 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 来指定。
2.1.1 支持的 Media Segment 格式
(1)MPEG-2 Transport Streams
- 即最常见的 TS 文件。
- RFC: ISO_13818。
- Media Initialization Section:
PAT
(Program Association Table) 跟PMT
(Program Map Table)。 - 每个 TS segment 必须包含一个 MPEG-2 Program。
- 每一个 TS segment 包含一个 PAT 和 PMT, 最好在 segment 的开始处,或者通过一个 EXT-X-MAP tag 来指定。
(2)Fragmented MPEG-4
- 即常提到的 fMP4。
- RFC: ISOBMFF。
- Media Initialization Section:
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
后面。 - 不像普通的 MP4 文件包含一个
moov box
(包含 sample tables)和一个mdat box
(包含对应的 samples),一个 fMP4 包含一个moof box
(包含 sample table 的子集)和一个mdat box
(包含对应的 samples)。 - 在每一个 fMP4 segment 里面,每一个
traf box
必须包含一个tfdt box
,fMP4 segment 必须使用 movie-fragment relative addressing。fMP4 segments 绝对不能使用外部的 data references。 - 每一个 fMP4 segment 必须有一个 EXT-X-MAP tag。
(3)Packed Audio
- 一个 Packed Audio Segment 包含编码的 audio samples 和 ID3 tags。简单的打包到一起,包含最小的 framing,并且没有 per-sample timestamp。
- 支持的 Packed Audio:AAC with ADTS framing [ISO_13818_7],MP3 [ISO_13818_3],AC-3 [AC_3],Enhanced AC-3 [AC_3]。
- 一个 Packed Audio Segment 没有 Media Initialization Section。
- 每一个 Packed Audio Segment 必须在它的第一个 sample 指定 timestamp 通过一个 ID3 PRIV tag。
- ID3 PRIV owner identifier 必须是
com.apple.streaming.transportStreamTimestamp
。 - ID3 payload 必须是一个 33-bit MPEG-2 Program Elementary Stream timestamp 的大端 eight-octet number,高 31 为设置为 0。
(4)WebVTT
- 一个 WebVTT Segment 是一个 WebVTT 文件的一个 section,WebVTT Segment 包含 subtitles。
- Media Initialization Section:
WebVTT header
。 - 每一个 WebVTT Segment 必须有以一个 WebVTT header 开始,或者有一个 EXT-X-MAP tag 来指定。
- 每一个 WebVTT header 应该有一个 X-TIMESTAMP-MAP 来保证音视频同步。
2.2 HLS Playlists
- Playlist 文件的格式是起源于 M3U,并且继承两个 tag:EXTM3U 和 EXTINF
- 下面的 tags 通过 BNF-style 语法来指定。
- 一个 Playlist 文件必须通过 URI(.m3u8 或 m3u) 或者 HTTP Content-Type 来识别(application/vnd.apple.mpegurl 或 audio/mpegurl)。
- 一个 m3u8 的 Playlist 就是一个由多个独立行组成的文本文件。
- 每行由回车/换行区分,换行符可以用 \n 或者 \r\n。
- 每一行可以是一个 URI、空白行或是一个 以 “#” 号开头的字符串,并且空格只能存在于一行中不同元素间的分隔。
- 以 # 开头的是 tag 或者注释,以
#EXT
开头的是 tag,其余的为注释,在解析时应该忽略。 - Playlist 里面的 URI 可以用绝对地址或者相对地址,如果使用相对地址,那么是相对于 Playlist 文件的地址。
一个 URI 表示一个媒体段或是 "variant Playlist file"(最多支持一层嵌套,即一个 m3u8 文件中嵌套另一个 m3u8)。
2.3 Attribute Lists
- 有的 tags 的值是 Attribute Lists。
- 一个 Attribute List 是一个用
逗号
分隔的 attribute/value 对列表。 - 格式为:
AttributeName=AttributeValue
。
2.4 Basic Tags
Basic Tags 可以用在 Media Playlist 和 Master Playlist 里面。
(1)#EXTM3U
每个 m3u8 文件第一行必须是这个 tag,如上面的两个示例,标识是一个 Extended M3U Playlist 文件。这个标签在媒体播放列表和主播放列表中均应该被包含。形式为:#EXTM3U
(2)#EXT-X-VERSION
本标签标明Playlist播放列表文件兼容的版本。其格式为:#EXT-X-VERSION:<n>
2.5 Media Segment Tags
每一个 Media Segment 通过一系列的 Media Segment tags 跟一个 URI 来指定。有的 Media Segment tags 只应用于下一个 segment,有的则是应用所有下面的 segments。一个 Media Segment tag 只能出现在 Media Playlist 里面。
(3)#EXTINF
指定每个媒体段(ts)的持续时间,这个仅对其后面的 URI 有效,每两个媒体段 URI 间被这个 tag 分隔开。其格式为:#EXTINF:<duration>,<title>
- duration:表示持续的时间(秒),“Durations MUST be integers if the protocol version of the Playlist file is less than 3”,否则可以是浮点数。
- title:用于对其后文件片添加一些可读的描述信息。
(4)#EXT-X-BYTERANGE
表示媒体段是一个媒体 URI 资源中的一段,只对其后的 media URI 有效,格式为:#EXT-X-BYTERANGE:<n>[@o]
- n:表示这个区间的大小
- o:表示在 URI 中的 offset。o为可选项,标明子集的起始位置,相当于从资源开始处计算的偏移量。若o未定义,则其开始位置为上一个子集的结束位置下一个字节。( 当o未定义的时候,其上一个文件资源必须是同一文件的子集,且其不能为文件列表中的第一个文件片。)
- 本标签出现在4以上版本,且不应出现在主播放列表中。
(5)#EXT-X-DISCONTINUITY
标明前后两个文件片编码方式不连续,当遇到该 tag 的时候说明以下属性发生了变化:
- file format
- number and type of tracks
- encoding parameters
- encoding sequence
- timestamp sequence
其不应出现在主播放列表中。实际中还没发现它的用处。
(6)#EXT-X-KEY
媒体文件片可能被加密,而本标签表明如何对其进行解密。它作用于其后所有的文件片,直到下一个同名标签(with the same KEYFORMAT)的出现。两个以上的EXT-X-KEY可能会将不同的KEYFORMAT属性作用于同一文件片,它们必须被解析为相同的key。格式为:#EXT-X-KEY:<attribute-list>
以下是一些属性:
① METHOD:枚举值,标明加密方法,必须。
- NONE ,未加密,当METHOD取值为NONE时,以下属性不得出现 URI、IV、KEYFORMAT、KEYFORMATVERSION
- AES-128,使用128位密钥和PKCS7补齐的AES算法。该取值下,URI属性必须出现,IV属性可选。(对于 AES-128 的情况,keytag 和 URI 属性共同表示了一个 key 文件,通过 URI 可以获得这个 key,如果没有 IV(Initialization Vector),则使用序列号作为 IV 进行编解码,将序列号的高位赋到 16 个字节的 buffer 中,左边补 0;如果有 IV,则将该值当成 16 个字节的 16 进制数。)
- SAMPLE-AES ,标明文件片中包含使用AES-128加密的媒体取样,如audio或video,这些取样的加密和封装方式与媒体文件的编码和文件片类型有关。
客户端遇到无法识别的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:<attribute-list>
选项情况如下:
- URI:”string” ,指明包含头部信息的资源的URI,必须的。
- BYTERANGE:”string”,指明URI资源的一定字节范围,可选的。不填写该属性时,指代URI所指的全部资源。
这个标签的使用情景:当播放列表文件中的第一个文件片在资源的开头部分没有和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:<YYYY-MM-DDThh:mm:ssZ>
- 例如:#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
(9)#EXT-X-DATERANGE
将一个时间范围和一组属性键值对结合到一起。
2.6 Media Playlist Tags
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:<s>
- s:表示最大的秒数。
(11)#EXT-X-MEDIA-SEQUENCE
用于指定第一个 Media Segment 的 Media Sequence Number。每一个 media URI 在 Playlist 中只有唯一的序号,相邻之间序号 +1。格式为:#EXT-X-MEDIA-SEQUENCE:<number>
。一个 media URI 并不是必须要包含的,如果没有,默认为 0。
本标签需出现在第一个文件片之前,且不能出现在主播放列表中。当媒体播放列表不包含该标签时,其首个文件片的序列号被视为0 。
文件资源定位符中可不出现序列号。
(12)#EXT-X-DISCONTINUITY-SEQUENCE
该标签允许多码流不同码流之间同步,并能够使多个流在它们的媒体播放列表文件中加入EXT-X-DISCONTINUITY标签。格式为:#EXT-X-DISCONTINUITY-SEQUENCE:<number>
其中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:<EVENT|VOD>
- VOD,即为点播视频,服务器不能改变 Playlist 文件,换句话说就是该视频全部的 ts 文件已经被生成好了
- EVENT,就是实时生成 m3u8 和 ts 文件。服务器不能改变或是删除 Playlist 文件中的任何部分,但是可以向该文件中增加新的一行内容。它的索引文件一直处于动态变化中,播放的时候需要不断下载二级 index 文件,以获得最新生成的ts文件播放视频。如果一个二级index文件的末尾没有#EXT-X-ENDLIST标志,说明它是一个Live视频流。
(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 的总时间长度
2.7 Master Playlist Tags
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:<attribute-list>
- 该属性列表中包含:URI、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、AUTOSELECT。
- URI:如果没有,则表示这个 tag 描述的可选择版本在主 PlayList 的 EXT-X-STREAM-INF 中存在;
- TYPE:AUDIO and VIDEO;
- GROUP-ID:具有相同 ID 的 MEDIAtag,组成一组样式;
- LANGUAGE:identifies the primary language used in the rendition。
- NAME:The 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。
- DEFAULT:YES 或是 NO,默认是 NO,如果是 YES,则客户端会以这种选项来播放,除非用户自己进行选择
- AUTOSELECT:YES 或是 NO,默认是 NO,如果是 YES,则客户端会根据当前播放环境来进行选择(用户没有根据自己偏好进行选择的前提下)
- The EXT-X-MEDIA tag appeared in version 4 of the protocol。
(18)#EXT-X-STREAM-INF
用于指定一个 Variant Stream。指定一个包含多媒体信息的 media URI 作为 Playlist,一般做 m3u8 的嵌套使用,它只对紧跟后面的 URI 有效。格式为:#EXT-X-STREAM-INF:<attribute-list>
常用的属性如下:
- BANDWIDTH:带宽,必须有
- PROGRAM-ID:该值是一个十进制整数,唯一地标识一个在 Playlist 文件范围内的特定的描述。一个 Playlist 文件中可能包含多个有相同 ID 的此 tag
- CODECS:指定流的编码类型,不是必须的
- RESOLUTION:分辨率
- AUDIO:这个值必须和 AUDIO 类别的 “EXT-X-MEDIA” 标签中 “GROUP-ID” 属性值相匹配
- VIDEO:同上
该标签不应出现在媒体文件播放列表中。
(19)#EXT-X-I-FRAME-STREAM-INF
本标签标明媒体播放列表文件包含多媒体内容的I-frame。本标签单独使用,不依赖于任何资源的URI。其格式为:#EXT-X-I-FRAME-STREAM-INF:<attribute-list>
本标签支持所有EXT-X-I-FRAME-STREAM-INF支持的属性,并额外支持一个URI属性。
(20)EXT-X-SESSION-DATA
存放一些 session 数据。
(21)EXT-X-SESSION-KEY
用于解密。
2.8 Media or Master Playlist Tags
这里的 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:<YES|NO>
3、HLS 之 TS
来自: hls之m3u8、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 header、adaptation field、payload。ts header 固定 4 个字节;adaptation field 可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充;payload 是 pes 数据。
3.1.1 ts header
字段 | 大小 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 就可以播放了。
- PAT 表:主要的作用就是指明了 PMT 表的 PID 值。
- PMT 表:主要的作用就是指明了音视频流的 PID 值。
- 音频流/视频流:承载音视频内容。
3.1.2 adaptation field
字段 | 大小 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包不加。
3.1.3 PAT(Program Associate Table)格式 节目关联表
字段 | 大小 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校验码 |
3.1.4 PMT(Program Map Table)格式 节目映射表
字段 | 大小 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校验码 |
3.2 pes 层:Packet Elemental Stream
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应该直接用直播数据流中的时间,不应该按公式计算。
3.3 es 层:Elementary Stream
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) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
3.3.1 h.264 视频
打包 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 | +-+-+-+-+-+-+-+-+
- F:1bit,forbidden_zero_bit 禁止位,h.264 规定必须取 0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
- NRI:2bits,nal_ref_idc,取值为 0~3,指示这个 nalu 的重要性,值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。I 帧、sps、pps 通常取 3,P 帧常取 2,B 帧通常取 0。
- Type:5bits,取值如下表所示:
打包 es 层数据时 pes 头和 es 数据之间要加入一个 type=9 的 nalu,关键帧 slice 前必须要加入 type=7 和 type=8 的 nalu,而且是紧邻的。如下图所示:
3.3.2 aac音频
打包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 |
3.4 ts打包流程图
一个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包>
4、HLS中的内容加密
4.1 HLS概述
HLS是Apple公司推出的动态码率自适应技术,HLS内容格式在HTTP Live Streaming draft-pantos-http-live-streaming-23中描述。
4.2 加密方式
HTTP Live Streaming中内容加密有两种,一种是对TS切片文件直接加密;另一种是对H.264编码文件中类型为1和5的NAL单元进行加密,其它类型的NAL单元不加密
[1]。
4.3 加密机制
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个字节不加密。)
5、HLS 播放
5.1 播放未加密的HLS
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
你可以直接用这个地址播放看看。
实现这种未加密的缓存还是比较好实现的,大概可以分为这几步。
- a、我们首先按照特定的格式去解析这m3u8文件。
- b、按照解析出来的ts文件按照我们知道的规则组拼起来,其下载这些ts文件,存放在手机的sd卡
- c、我们需要在本地搭建一个本地http服务器,我们之前本打算搭建一个https,但是由于生成的证书是自己生成导致播放器不去访问本地的服务器。
- d、本地服务器我们通过过滤特定的接口名字,来实现根据不同ts名字返回不同的视频文件(这里最好生成和原始的ts文件的名字一样)
- e、我们如何知道播放器播完一段视频呢,因为它是一段一段播放的,所以这里就需要我们在本地生成一份本地指向我们本地服务器的m3u8文件,直接播
5.2 播放加密HLS
看下加密的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视频就可以播放了。 其实看到这我们就因该有思路怎么去做,加密的缓存播放了。
实现播放加密缓存的思路:
- a、我们首先按照特定的格式去解析这m3u8文件。
- b、解析出来的ts文件按照我们知道的规则组拼起来,其下载这些ts文件,存放在手机的sd卡,这些下载下来的TS视频文件是播放不了的,再把正确的key下载下来。
- c、我们需要在本地搭建一个本地http服务器,我们之前本打算搭建一个https,但是由于生成的证书是自己生成导致播放器不去访问本地的服务器。
- d、本地服务器我们通过过滤特定的接口名字,来实现根据不同ts名字返回不同的视频文件(这里最好生成和原始的ts文件的名字一样)
- e、我们如何知道播放器播完一段视频呢,因为他是一段一段播放的,所以这里就需要我们在本地生成一份本地指向我们本地服务器的m3u8文件,直接播
6、HLS 协议总结
6.1 优点
- HLS 相对于 RTMP 来讲使用了标准的 HTTP 协议来传输数据,可以避免在一些特殊的网络环境下被屏蔽。
- HLS 相比 RTMP 在服务器端做负载均衡要简单得多。因为 HLS 是基于无状态协议 HTTP 实现的,客户端只需要按照顺序使用下载存储在服务器的普通 ts 文件进行播放就可以。而 RTMP 是一种有状态协议,很难对视频服务器进行平滑扩展,因为需要为每一个播放视频流的客户端维护状态。
- HLS 协议本身实现了码率自适应,在不同带宽情况下,设备可以自动切换到最适合自己码率的视频播放。
- Apple 的全系列产品支持,由于 HLS 是苹果提出的,所以在 Apple 的全系列产品包括 iphone,ipad,safari 都不需要安装任何插件就可以原生支持播放 HLS,现在,Android 也加入了对 HLS 的支持。
6.2 缺点
- HLS 协议在直播的视频延迟时间很难做到 10 s 以下延时,而 RTMP 协议的延时可以降到 3s-4s 左右。
- 对于点播服务来说,由于 TS 切片通常较小,海量碎片在文件分发,一致性缓存,存储等方面都有较大挑战。
6.3 改进
由于客户端每次请求 TS 或 M3U8 有可能都是一个新的连接请求,所以,我们无法有效的标识客户端,一旦出现问题,基本无法有效的定位问题。所以,一般工业级的服务器都会对传统的 HLS 做一些改进,常见优化是对每个M3U8文件增加Session来标识一条 HLS 连接。
这里主要介绍网宿的 Variant HLS 与又拍云的 HLS+.
6.3.1 网宿的 Variant 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
来标识一条播放连接。
6.3.2 又拍云的 HLS+
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 连接。
6.3.3 HTTP 302
首先,以 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 CurrentDload Upload Total Spent Left Speed0 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 来定位播放过程中的问题。
6.4 HLS 延时分析
HLS 理论延时 = 1 个切片的时长 + 0-1个 td (td 是 EXT-X-TARGETDURATION, 可简单理解为播放器取片的间隔时间) + 0-n 个启动切片(苹果官方建议是请求到 3 个片之后才开始播放) + 播放器最开始请求的片的网络延时(网络连接耗时)
为了追求低延时效果,可以将切片切的更小,取片间隔做的更小,播放器未取到 3 个片就启动播放。但是,这些优化方式都会增加 HLS 不稳定和出现错误的风险。
7、客户端/服务器行为
本章介绍服务器怎样产生播放列表和媒体文件以及客户端怎样下载并播放。
7.1 服务器进程
7.1.1介绍
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持续时间内。
如果服务器期望移除演示文稿,它必须使播放列表文件对于客户端不可用,在播放列表被清除时,它应该确保播放列表文件中的所有媒体文件对于客户端来说至少在一个播放列表文件持续时间内是可用的。
7.1.2 滑动窗口播放列表
服务器可以限制最近一段时间添加到播放列表文件中的媒体文件的可用性,为了达到这个目的,播放列表文件必须包含准确的EXT-X-MEDIA-SEQUENCE标签。标签的值是按照从播放列表中移除的媒体文件的URL递增的。
媒体文件的URL必须按照其加入的顺序移除。
当服务器从播放列表移除URL时,媒体文件在一段时间内必须保持可用,该时间等于媒体文件的时间加上包含该媒体文件的最长播放列表文件的时间。
当媒体文件通过http传输给客户端后,如果服务器打算移除该文件,那么它应该确保http响应头包含反应生存时间的过期头。
那些不包含EXT-X-ENDLIST标签的播放列表文件的持续时间必须至少三倍于targrt dutration。之所以为三倍的targrt dutration可能是因为,根据hls协议来看,每个终端的播放行为都是不一致的,对于点播的m3u8文件来说,都是从第一个文件开始播,但是对于直播的m3u8,播放器可以从任意一个文件开始向后(新的文件)追溯。不过一般的播放器都是从倒数第三个开始。
7.1.3 加密媒体文件
如果媒体文件需要被加密,那么服务器必须定义一个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标签从播放列表文件中移除。
7.1.4 提供变种数据流
服务器可以提供多个播放列表文件来支持对同一个演示文稿的不同编码。提供变种播放列表文件列出每一个变种流,从而使得客户端可以在不同编码之间动态切换。
变种播放列表文件必须为每一个变种流包含一个EXT-X-STREAM-INF标签(即嵌套的m3u8结构)。同一演示文稿的每个EXT-X-STREAM-INF都必须有相同的programid。每个演示文稿的programid在变种播放列表内必须是唯一的。
如果EXT-X-STREAM-INF标签包含CODECS属性,则属性值必须包含RFC4281定义的所有格式。
服务器在生成变种流的时候必须遵守以下规则:
- 1) 每一个变种流必须呈现相同的内容,包括流的间断性。
- 2) 每个变种播放列表文件必须有相同的target duration。
- 3) 只在个别变种播放列表文件中出现的内容必须放在列表文件的头或者尾,且不能超过target duration。
- 4) 变种流内匹配内容,必须有匹配时间戳。这可以使客户端同步流。
- 5) 基本音频流文件必须在文件中第一个样本的采样信号的时间戳前预先准备一个ID3 PRIV标签,标签的所有者标示符为“com.apple.streaming.transportStreamTimestamp”。二进制数据必须是33位的基本时间戳,用8字节的数字表示。
另外,所有的变种流都应该包含相同编码的音频二进制流。这使得客户端在不同的流之间切换时没有毛刺声音(音视频不同步导致的)。
7.2 客户端进程
7.2.1 介绍
客户端怎样获取播放列表中的URL不在本文档的范围之内,我们假设已经获取到了URL。
7.2.2 加载播放列表文件
每一次加载或者重载播放列表文件时:
客户端必须保证播放列表文件以EXTM3U标签开头,并且如果协议版本号存在,客户端必须支持该版本。否则,客户端不可以试图使用该列表文件。
客户端可以忽略它不能识别的标签和属性。
如果播放列表文件包含了EXT-X-MEDIA-SEQUENCE标签,那么客户端会假设在播放列表被加载的时间内以及播放列表的持续时间内媒体文件将变得不可用。播放列表的持续时间等于其中包含的媒体文件时长的总和。
7.2.3播放播放列表文件
当开始播放的时候,客户端首先从播放列表中选择要播放的媒体文件。如果不存在EXT-X-ENDLIST标签,并且客户端想正常播放媒体(按顺序以标准速率播放),那么客户端就不应该从播放列表文件尾部选择少于三个target duration的媒体文件。
为了达到正常播放的目的,媒体文件必须按照他们在播放列表中的顺序播放。客户端还可以用其他任何方式播放,比如顺序播放,随机播放,特效播放等。
对于存在EXT-X-DISCONTINUITY标签的媒体文件,在播放之前客户端必须准备好重置分析和解码器。
为了不间断播放,应该提前载入媒体文件,以补偿延时和吞吐量的变化。
如果播放列表文件包含了EXT-X-ALLOW-CATCH标签,并且它的值为NO,那么客户端在播放以后不可以缓存媒体文件。否则允许缓存用来以后重播。
客户端可以使用EXT-X-PROGRAM-DATE-TIME标签来为用户显示节目的起始时间。如果这个值包含了时区信息,那么客户端应该考虑到这点;如果不包含,那么客户端不可以推测时区。
客户端不能依靠EXT-X-ALLOW-CATCH标签值的正确性和一致性。
7.2.4重新载入播放列表文件
客户端必须阶段性的重新载入播放列表文件,除非文件包含了EXT-X-ENDLIST标签。然而也不能过于频繁的载入。
当客户端第一次载入播放列表文件或者已经载入但是发现文件与上次载入的时候有了变化,客户端都必须等待一段时间才可以再次载入。这段时间被称为原始最小重载延迟,它是从客户端开始载入一个播放列表文件开始计算的。
原始最小重载延迟是播放列表文件中最后一个媒体文件的持续时间。
媒体文件的持续时间由EXTINF标签来指定。
如果客户端重载了一个播放列表文件,但是发现文件并没有变化,那么它在重试之前必须等一段时间。最小延迟是target duration的倍数。第一次是0.5倍,第二次1.5倍,3倍。。。
7.2.5 确定下一个要加载的文件
当播放列表文件被载入或者重载以后,客户端必须检查播放列表来确定要载入的媒体文件。要载入的第一个文件必须是客户端要播放的第一个文件(直播时候,未必是m3u8中的第一个文件
)。
如果要播放的文件已经被载入,并且播放列表文件不包含EXT-X-MEDIA-SEQUENCE标签,那么客户端必须确认播放列表文件包含了最后一个被载入的媒体文件的URL,如果不包含,则暂停播放。要载入的下一个媒体文件必须是上一次载入的媒体文件URL之后的第一个媒体文件的URL。
如果要播放的文件已经被载入,并且播放列表文件包含EXT-X-MEDIA-SEQUENCE标签,那么要载入的下一个媒体文件就是比上一次载入的文件的序列号大的媒体文件中的序列号最小者。
7.2.6 解密经加密的媒体文件
如果播放列表文件包含了一个指定密钥文件URL的EXT-X-KEY标签,客户端必须获取密钥文件,并使用其中的密钥来解密KEY标签之后的所有媒体文件,直到遇到另一个EXT-X-KEY标签为止。
流媒体协议HLS解析相关推荐
- 流媒体协议初探(MPEG2-TS、RTSP、RTP、RTCP、SDP、RTMP、HLS、HDS、HSS、MPEG-DASH)
目录 一.综述 需求分析 协议定制 二.MPEG2-TS协议 三.RTSP协议.RTP.RTCP.SDP RTSP RTP.RTCP.SDP 四.RTMP 五.HLS.HDS.HSS HLS HDS和 ...
- RTMP协议深度解析:从原理到实践,掌握实时流媒体传输技术
目录标题 1. 引言 1.1 流媒体传输技术的重要性 1.2 为什么选择RTMP协议 1.3 RTMP协议的发展与应用 2. RTMP协议基础 2.1 RTMP协议简介 2.2 RTMP协议与其他流媒 ...
- 流媒体协议(一):HLS协议
HLS协议简介 HTTP Live Streaming(缩写是HLS)是一个由苹果公司提出的基于HTTP的流媒体网络传输协议. 工作原理 简单讲就是把整个流分成一个个小的,基于 HTTP 的文件来下载 ...
- 流媒体协议(一):HLS 协议
一.HLS 概述 HLS 全称是 HTTP Live Streaming,是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议,用于实时音视频流的传输.目前HLS协议被广泛的应用于视频点播 ...
- DASH 流媒体协议(Dynamic Adaptive Streaming over HTTP)概述——起源、MPD文件解析、相关项目
流媒体 注: 1.此篇文章立足于巨人的肩膀之上,侵必删 2.有许多内容尚未完成,包括segment的类别,HTTP协议基础等 DASH DASH的起源-- MPEG(Moving Picture Ex ...
- 网络流媒体协议的联系与区别(RTP RTCP RTSP RTMP HLS)
简结">三句话简结 RTP RTCP RTSP RTMP HLS区别与联系 RTP传输流媒体数据.RTCP对RTP进行控制,同步.RTSP发起/终止流媒体 RTP和RTCP互为姐妹关系 ...
- Live Streaming Protocol--三种主流的流媒体协议MEPG DASH,HLS,Smooth Streaming及其manifest 文件字段解释
文章目录 MPEG DASH协议 Manifest.mpd 1. Manifest.mpd简介 2. Manifest.mpd字段 HLS协议 1. manifest.m3u8字段 2. ts文件 s ...
- 基于HLS流媒体协议的视频加密方案
本文只讨论应用于浏览器环境的流媒体协议的加密. 背景 付费观看视频的模式是很多平台的核心业务,如果视频被录制并非法传播,付费业务将受到严重威胁.因此对视频服务进行加密的技术变得尤为重要. 本文所指的视 ...
- 流媒体通信协议HLS与DASH的对比
简单了解 HLS(HTTP Live Streaming)协议 是由苹果公司实现的基于HTTP的流媒体通信协议,并成为Quick TIme X和IPhone软件系统的一部分.苹果的IPad也有支持HL ...
最新文章
- 基于Python查找图像中最常见的颜色
- Qt5开发及实例学习之文件系统浏览
- mac python3.8怎样安装scrapy_Python爬虫与mac下Scrapy配置
- [持续收集]中国好注入-语句
- Python代码加密,将python文件编译成so文件
- varnish基本配置(二)
- 如何成为python 数据分析师_如何七周成为数据分析师20:了解和掌握Python的函数...
- fileUpload 文件上传
- android ble 实现自动连接,Android:自动重新连接BLE设备
- Android系统的开机画面显示过程分析
- IPv6 解说 ,与IPv4的同异
- reboot mysql fail_mysql 5.7.18,在系统重启后,为什么无法启动?
- 中国第一代程序员潘爱民的 30 年程序人生
- 数据库(SQL)面试题,基础知识(超全面)
- java web程序设计与..._Java Web程序设计
- linux测速,linux环境下使用speedtest测速
- UE5 预览版载具模板工程车不能移动的问题
- 树莓派Pico开发板扩展ESP01S无线WiFi模块通信实践
- 【有利可图网】PS教程:用滤镜打造3D立体文字效果
- JAVA接口设计篇:这些都不知道,别说你懂接口设计
热门文章
- 并行计算(二):并行模型的分类
- NVM(非易失存储介质)在索引结构中的机遇与挑战(未完,整理中...)
- 3-wireshark网络安全分析——ARP欺骗攻击
- Linux压缩库archive使用
- 强大无比!百度文库、音视频下载、商品历史价…一行命令满足你的各种需求...
- u盘容量影响计算机运行速度,U盘怎么提高电脑运行速度?电脑提速实用技巧【详解】...
- 二进制负数的补码为什么是符号位不变,其他位取反加一?
- C语言之电话信息查询
- 点讯梅花6Beta2之弟兄小方极点五笔6.1词库+圣经常用词汇版手把手制作说明书。
- openstack创建虚机的过程