MPEG-TS 封装格式详解
- MPEG-TS 封装格式详解
- 简介
- 基本概念
- TS 文件格式
- PSI(Program Specific Information)
- 节目关联表(PAT,Program Association Table)
- 节目映射表(PMT,Program Map Table)
- 实例
- TS Header
- 调整字段(adaptation field)
- PES(Packet Elemental Stream)
- pes header
- optional pes header
- ES(Elementary Stream)
- 实例:TS 封装过程
- 参考
MPEG-TS 封装格式详解
简介
MPEG-TS一种标准数据容器格式,传输与存储音视频、节目与系统信息协议数据,应用于数字广播系统,譬如DVB,ATSC与IPTV。传输流在MPEG-2第1部分系统中规定,正式称为ISO / IEC标准13818-1或ITU-T建议书。
MPEG2/DVB是一种多媒体传输、复用技术,在数字电视广播中可提供数百个节目频道。复用的含义是,可以同时传输多层节目。MPEG-TS 主要应用于实时传送的节目,比如实时广播的电视节目。MPEG-TS 格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。简单地说,如果 DVD 上的 VOB 文件数据部分损坏了,就会导致整个文件无法解码,而电视节目是任何时候打开电视机都能解码的。
注意,DVB全称为Digital Video Broadcasting,包括不同的系统,如卫星数字电视广播系统,有线数字电视广播系统,地面开路数字电视广播系统,交互式数字电视广播系统以及数字电视加扰系统。DVB系统标准是一种全球数字电视技术的标准。如何定义广播中的比特流语法与句法,以实现在比特流中复用数字音频与视频,欧洲的DVB采用数字视频压缩MPEG-2标准,该标准是定义比特流的语法与句法的一个ISO/IEC标准,即13818-1标准。DVB系统的核心技术是采用MPEG-2技术进行视频、音频的编码,使用统一的MPEG-2传输流(TS流)。
MPEG-2标准中,有两种不同的码流输出到信道,一种是节目码流(PS: Program Stream),适用于没有传输误差的场景;一种是传输流(TS:Transport Stream),适用于有信道噪声的传输场景。节目流设计用于合理可靠的媒体,如光盘(如DVD),而传输流设计用于不太可靠的传输,即地面或卫星广播。此外,传输流可以携带多个节目。
MPEG-2 system(编号13818-1)是MPEG-2标准的其中一部分,该部分描述了多个视频,音频和数据多种基本流(ES)合成传输流(TS)和节目流(PS)的方式。
基本概念
-
ES(Elementary Stream):基本码流,包含音频、视频、数据的连续码流。直接从编码器出来的数据流,可以是编码过的视频数据流(H.264、MJPEG等),音频数据流(AAC),或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。
-
PES(Packet Elementary Stream):ES形成的分组称为PES分组,是用来传递ES的一种数据结构。PES流是ES流经过PES打包器处理后形成的数据流,在这个过程中完成了将ES流分组、打包、加入包头信息(主要加入了PTS/DTS信息)等操作(对ES流的第一次打包)。PES流的基本单位是PES包,PES包由包头和payload组成。每个PES包的PID是一致的,一个PES包可能由若干个TS包组成。
-
TS(Transport Stream):传输流,固定长度188字节。TS在PES层上加入了数据流识别和传输的必要信息,是对PES的重新封装。TS含有独立的一个或者多个program,一个program又可以包含多个视频,音频和文字信息的ES流。
TS 文件格式
一个 .ts 文件实际上由多个 ts packet 组成,每个 ts packet 大小固定为 188 Bytes。可以是AAC、H264、 PAT、PMT等。每个TS都有PID。
ts 层分为三个部分:ts header、adaptation field、payload。ts header 固定 4 个字节;adaptation field 可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充;payload 是 pes 数据。188-4-adaptation field 就是payload的大小。
ts文件分为三个层次:ts层、pes层、es层。es层就是音视频数据,pes层是在音视频数据上加了时间戳等数据帧的说明信息,ts层是在pes层上加入了数据流识别和传输的必要信息。
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 值。
- 音频流/视频流:承载音视频内容。
ES流打包成PES流:
PES流打包成TS流:
PSI(Program Specific Information)
PSI(节目特定信息)表用来描述传送流的组成结构。
PSI信息由四种类型的表组成,包括节目关联表(PAT,Program Association Table),节目映射表(PMT,Program Map Table),条件接收表(CAT,Conditional Access Table),网络信息表(NIT,Network Infomation Table)。
结构名 | PID 值 | 描述 |
---|---|---|
PAT | 0x0000 | 解析 .ts 文件的第一步就是找到 PAT 表,然后获取 PMT 表的 PID 值 |
PMT | 在 PAT 中给出 | 根据 PMT 表找到 audio pes packet 和 video pes packet 的 PID 值 |
CAT | 0x0001 | 将一个或多个专用EMM流分别与唯一的PID相关联,描述了TS流的加密方式 |
NIT | PAT 中给出 | 描述整个网络,如多少个TS流,频点和调制方式等 |
其中,PAT与PMT两张表帮助我们找到该传送流中的所有节目与流。PAT告诉我们,该TS流由哪些节目组成,每个节目的节目映射表PMT的PID是什么;而PMT告诉我们,该节目由哪些流组成,每一路流的类型与PID是什么。CAT与NIT暂时不考虑。
节目关联表(PAT,Program Association Table)
PAT 层次图:
各字段解释:
字段 | 类型 | 描述 |
---|---|---|
table_id | 8b | 固定为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校验码 |
PAT 句法图:
节目映射表(PMT,Program Map Table)
PMT 层次图:
各字段解释:
字段 | 类型 | 描述 |
---|---|---|
table_id | 8b | PMT表取值随意 |
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校验码 |
PMT 句法图:
实例
举个例子,你找个一个TS包,它的PID是0,说明它的负载内容是PAT信息,解析PAT信息,你发现节目1的PMT表的PID是0x10,接着,你在比特流中寻找一个PID为0x10的TS包,它的负载内容是节目1的PMT表信息,解析该PMT信息,你可以发现第一路流是MPEG2音频流,PID号0x21,第二路流是MPEG2视频流,PID号是0x22,第三路流是DVB字幕流,PID号是0x23,解析完毕,凡是比特流中PID号为0x22的TS包,所负载的内容为MPEG2视频流,把这些包一个一个找出来,把其中的有效码流一部分一部分的拼接起来,然后送给解码器去解码。
注意,就一般的视频流而言,只要拼接成一个完整的PES包,就可以送出去给解码器,然后再继续拼接下一个PES包。
TS Header
TS 包的包头提供关于传输方面的信息,大小固定为 4 字节。
TS Header 的结构如下:
以下是各字段的详细解释:
- sync_byte(8bits):同步字节,是包中的第一个字节,固定为 0x47,表示后面是一个 TS 分组(注:后面包中的数据不会出现 0x47 的),用于建立发送端和接收端包的同步。
- transport_error_indicator(1bit):传输错误标志位,为 1 表示在相关的传输包中至少有一个不可纠正的错误位。当被置 1 后,在错误被纠正之前不能重置为 0。
- payload_unit_start_indicator(1bit):负载起始标识,针对不同的负载,有不同的含义。
- PES:置为 1,标识 TS 包的有效载荷以 PES 包的第一个字节开始,即此 TS 包为 PES 包的起始包,且此 TS 分组中有且只有一个 PES 包的起始字段;置为 0,表示 TS 包不是 PES 包的起始包,是后面的数据包。
- PSI:置为 1,表示 TS 包中带有 PSI 数据分段的第一个字节,即这个 TS 包是 PSI Section 的起始包,则此 TS 包的负载的第一个字节带有 pointer_field,用来指示 PSI 数据在 payload 中的位置;置为 0,表示 TS 包不带有 PSI Section 的第一个字节,即此 TS 包不是 PSI 的起始包,即在有效负载中没有 pointer_filed,有效负载的开始就是 PSI 的数据内容。对于空包,payload_unit_start_indicator 应该置为 0。
比如:若 TS 包载荷为 PAT,则当接收到 TS 包的 payload_unit_start_indicator 为 1 时,表明这个 TS 包包含了 PAT 头信息,从这个包里面解析出 section_length 和 continuity_counter,然后继续收集后面的 payload_unit_start_indicator = 0 的 TS 包,并判断 continuity_counter 的连续性,不断读出 TS 包中的净载荷(也就是 PAT 数据),用 section_length 作为收集 TS 包结束条件。
- transport_priority(1bit):传输优先标志,为 1 表明当前 TS 包的优先级比其他具有相同 PID,但此位没有被置 1 的 TS 包高。
- transport_priority(1bit):传输优先标志,为 1 表明当前 TS 包的优先级比其他具有相同 PID,但此位没有被置 1 的 TS 包高。
- PID(13bits):指示有效负载中数据的类型。PID 是识别 TS 包的重要参数,用来识别 TS 包所承载的数据。在 TS 码流生成时,每一类业务(视频,音频,数据)的基本码流均被赋予一个不同的识别号 PID,解码器借助于 PID 判断某一个 TS 包属于哪一类业务的基本码流。标准中定义的 PID 分配见如下表:
PID 值 | 描述 |
---|---|
0x0000 | 节目关联表(program association table, PAT) |
0x0001 | 条件访问表(conditional access table, CAT) |
0x0002 | 传送流描述表(transport stream description table, TSDT) |
0x0003~0x000F | 保留 |
0x0010~0x1FFE | 自由分配 |
0x1FFF | 空包 |
-
transport_scrambling_control(2bits):有效负载加密模式标志,00 表示未加密。如果传输包包头中包括调整字段,不应被加密。其他取值含义是用户自定义的。
-
adaption_field_control(2bits):调整字段标志,表示此 ts 首部是否跟随调整字段和负载数据。占 2bits,字段值含义如下:
- 00:保留;
- 01:表示无调整字段,只有有效负载数据;
- 10:表示只有调整字段,无有效负载数据;
- 11:表示有调整字段,且其后跟随有效负载数据;
-
continuity_counter(4bits):循环计数器,用于对传输误码进行检测。在发送端对所有的包都做 0~15 的循环计数,起始值不一定是 0。在接收终端,如发现循环计数器的值有中断,表明数据在传输中有丢失。
代码实现:
typedef struct MPEGTS_FIXED_HEADER
{
/* byte 0 */
unsigned sync_byte : 8; // 同步字节,值为 0x47
/* byte 1, 2 */
unsigned transport_error_indicator : 1; // 传输错误指示位,置 1 时,表示传送包中至少有一个不可纠正的错误位
unsigned payload_unit_start_indicator : 1; // 负载单元起始指标位,针对不同的负载,有不同的含义
unsigned transport_priority : 1; // 传输优先级,表明该包比同个 PID 的但未置位的 TS 包有更高的优先级
unsigned PID : 13; // 该 TS 包的 ID 号,如果净荷是 PAT 包,则 PID 固定为 0x00
/* byte 3 */
unsigned transport_scrambling_control : 2; // 传输加扰控制位
unsigned adaptation_field_control : 2; // 自适应调整域控制位,分别表示是否有调整字段和负载数据
unsigned continuity_counter : 4;// 连续计数器,随着具有相同 PID 的 TS 包的增加而增加,达到最大时恢复为 0
/* 如果两个连续相同 PID 的 TS 包具有相同的计数,则表明这两个包是一样的,只取一个解析即可。 */
} MPEGTS_FIXED_HEADER; // MPEG-TS 包头占 4 字节
调整字段(adaptation field)
在MPEG-TS中,为了传送打包后长度不足188B(包括包头)的不完整TS,或者为了在系统层插入节目时钟参考PCR(Program Clock Reference)字段,需要在TS包中插入可变长度的调整字段。
当 TS Header 的 adaption_field_control 字段的第一个比特位是 1 时,说明该 TS packet 有调整字段(adaptation field)。
调整字段包括对较高层次的解码功能有用的相关信息,调整字段的格式基于采用若干标志符,以表示该字段的某些特定扩展是否存在。调整字段由调整字段长度、不连续指示器、随机存取器、PCR标志符、基本数据流优先级指示器、拼接点标志符、传送专用数据标志、调整字段扩展标志以及有相应标志符的字段组成。
adaptation field 结构如下:
字段 | 大小 | 描述 |
---|---|---|
adaptation_field_length | 8 bit | 调整字段长度 |
discontinuity_indicator | 1 bit | 不连续指示器,一般为 0 |
random_access_indicator | 1 bit | 随机存取器,一般为 0 |
elementary_stream_priority_indicator | 1 bit | 基本数据流优先级指示器,一般为 0 |
PCR_flag | 1 bit | PCR标志符,携带 pcr 时置 1 |
OPCR_flag | 1 bit | OPCR标志符,一般为 0 |
splicing_point_flag | 1 bit | 拼接点标志符,一般为 0 |
transport_private_data_flag | 1 bit | 传送专用数据标志,一般为 0 |
adaptation_field_extension_flag | 1 bit | 调整字段扩展标志,一般为 0 |
PCR | 40 bit | 当 PCR_flag=1 时携带 |
stuffing_bytes | 不定 | 填充字节,取值0xff |
针对不同的 ts payload,adaptation field 的应用方式也不同:
- 针对 PAT、PMT,不足 188 Bytes 的部分直接使用 0xff 进行填充,而不会使用 adaptation field(但是也有例外,有的编码器会携带)。
- 针对 PES packet,才会使用 adaptation field 做填充。
- audio PES packet 不会在 adaptation field 中携带 PCR 字段。
- video PES packet 可以选择是否在 adaptation field 中携带 PCR 字段。一般 PES packet 被拆分时,会在第一个和最后一个拆分包添加 adaptation field。第一个拆分包的 adaptation field 才会携带 pcr 时钟,且主要作用是为了携带 pcr 时钟,而不是为了填充数据;最后一个拆分包的 adaptation field 不会携带 pcr 时钟,只做填充用。
PCR 属于编码端的时钟,其作用是如果编码端时钟源与解码端时钟源不同步,那么解码端应该采用 pcr 作为自己的时钟源,以同步编码端。
例如编码端时钟是解码端的 2 倍,解码端是正常的物理时钟,这时一个物理世界 5min 的视频,因为编码端时钟源走得太快,那么最后一个视频帧的 dts 就是 10min。播放端直接播放的话,就会播放 10min,显得播放得很慢。所以播放端需要加快播放,方法就是采用 pcr 时钟作为自己的时钟源,让自己的时钟走得跟编码端一样快,这样看起来就是正常速度播放了。
注意,adaptation field 最少占用 1 个字节,即 adaptation_field_length 字段,这样最少可以填充 1 个字节。
adaptation field 层次图:
adaptation field 句法图:
PES(Packet Elemental Stream)
每个 pes packet 都包含一个完整的视频帧或音频帧,pes packet 结构如下:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| pes header | optional pes header | pes payload |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6 Byte 3~259 Byte max 65526 Byte
PES 层次图:
PES 句法图:
pes header
6 Bytes 固定的 PES Header 结构如下:
字段 | 大小 | 描述 |
---|---|---|
packet_start_code_prefix | 3 比特 | 起始码,固定为 0x000001 |
stream_id | 1 比特 | PES 包中的负载流类型。音频取值(0xc0-0xdf),通常为0xc0;视频取值(0xe0-0xef),通常为0xe0 |
PES_packet_length | 2 比特 | PES 包长度,包括此字段后的可选包头和负载的长度。0表示长度不限制,只有视频数据长度会超过0xffff |
optional pes header
optional pes header 可选包头中的控制信息有很多,这里我们只给出与 pts、dts 有关的结构:
字段 | 大小 | 描述 |
---|---|---|
PTS_DTS_flags | 2 bit | 10 表示 PES 头部有 PTS 字段,11 表示有 PTS 和 DTS 字段,00 表示都没有,10 被禁止 |
pts | 40 bit | 实际 pts 长度占用 33 bit |
dts | 40 bit | 实际 dts 长度占用 33 bit |
PTS(显示时间戳)和 DTS(解码时间戳),是用来音视频同步的。DTS/PTS是相对SCR(系统参考)的时间戳,是以 90000 为单位,PTS/DTS 到 ms 的转换公式是 PTS/90,系统时钟频率为 90Khz,所以转换到秒为 PTS/90000。
PTS 和 DTS 字段的长度为 40 bit,实际中间有 7 个填充位,有效长度为 33 bit。
ES(Elementary Stream)
es 层指的就是音视频数据。
以视频数据为 H.264 格式,音频数据为 AAC 格式为例:
video:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| start code | nalu header | h264 data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 byte 1 byte x byte
audio:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| adts header | aac data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7 byte x byte
关于 H.264 的详细介绍:H.264 压缩与编解码原理
关于 AAC 的详细介绍:AAC 格式详解
实例: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 = 11 |<—————
| |
| 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包>
现在回头看下面这个是不是有些头绪了。
参考
- https://www.cnblogs.com/moonwalk/p/16200434.html
- https://blog.csdn.net/leek5533/article/details/104993932/
- https://www.cnblogs.com/jimodetiantang/p/9140808.html
- https://zhuanlan.zhihu.com/p/612042998
- https://blog.csdn.net/Kayson12345/article/details/81266587
- https://www.cnblogs.com/lvyunxiang/p/15792678.html
- https://blog.csdn.net/u012117034/article/details/124881444
- https://blog.csdn.net/michaeluo/article/details/75263462
- TS协议解析第四部分(adaptation field)
- https://blog.csdn.net/m0_60259116/article/details/125451635
- https://www.cnblogs.com/jimodetiantang/p/9146855.html