MPEG-2是MPEG(Moving Picture Experts Group,运动图像专家组)组织制定的视频和音频有损压缩标准之一,它的正式名称为“基于数字存储媒体运动图像和语音的压缩标准”。与MPEG-1标准相比,MPEG-2标准具有更高的图像质量、更多的图像格式和传输码率的图像压缩标准。MPEG-2标准不是MPEG-1的简单升级,而是在传输和系统方面做了更加详细的规定和进一步的完善。它是针对标准数字电视和高清晰电视在各种应用下的压缩方案,编码率从3 Mbit/s~100 Mbit/s。
MPEG-2标准特别适用于广播质量的数字电视的编码和传送,被用于无线数字电视、DVB(Digital Video Broadcasting,数字视频广播)、数字卫星电视、DVD(Digital Video Disk,数字化视频光盘)等技术中。
如上图,MPEG2标准DVB数据发送、传输、接收的流程。
在MPEG-2标准中,有两种不同类型的码流输出到信道,一种是节目码流(PS:program stream),适合没有误差产生的媒体存储,比如,DVD等存储介质;另外一种是传输流(TS:Transport Stream),适用于有信道噪声产生的传输,可在网络中进行远距离的传送。这样的TS便于综合多路节目为单路节目进行复用。多个节目或者不必具有共同的时间基准。由于MPEG-2要求这些包由传送,则在MPEG-2 TS中传送包的大小定义为4*47B=188B长度。另外,这样的TS作为一个固定长度包大小,便于找到帧的起止位置,易于从包丢失中恢复,适合于有误码的环境,但是与PS相比难于生产与复用。
TS 全称是 MPEG 2 Transport Stream,即MPEG-2 标准中的传输流。于此同时,我们常规使用的PS 是MPEG-2标准中的节目流。TS广泛用于广播电视系统,比如说数字电视,以及IPTV。
通常我们开发中往往会遇到,PS、RTP、 TS、MP4这四种封装最为常见。
其中:从发布者范畴来看,MP4 规范上为MPEG-4标准,而PS、TS属于MPEG-2标准、RTP属于国际电信联盟ITU-T的RFC系列标准。
从使用范围来看, MP4主要适用于文件的存储。 TS、RTP适用于数据流的传输,不具有存储属性。PS既有存储属性又可以做实时流的传输。
从复杂度来看MP4封装最为复杂、其次PS、再次RTP和TS。
种类 |
MP4 |
PS |
TS |
RTP |
本地保存 |
支持,可以文件级别获取码流属性 |
支持,可以GOP级别获取码流属性 |
安卓、window支持直接播放。 IOS必须配合m3u8文件构成流媒体服务器方可预览 |
不支持。文件级别流属性参数不具备 |
实时传输 |
支持,但是必须是前置打包的机制。 |
支持,属性只能获取GOP级别的参数。 海康在文件头增加流媒体属性私有信息用于辅助 |
支持,配合m3u8文件构成流媒体服务器 |
支持。必须配合RTSP协议双方获取文件属性。 |
复杂度 |
高 |
高 |
简单 |
简单 |
TS文件的基本存储单位为TS包,每一个包大小固定为188个字节。TS标准中有的地方是支持204字节的,204字节就是188个字节后面增加,16个字节的RS冗余校验信息。 一般默认188个字节。
每一个TS包的内部包含4个字节的TsPacketHeader。184字节的负载数据TsPacketPayloadData。
当TsPacketPayloadData中的实际有效数据不足184的时候,需要填充adaptation field数据,则按照如下格式。其中,填充规则按照4.1.2节描述。
一共四个字节,8个标记。如下图
结构体描述如下面的表:
Ts header结构
sync_byte |
8b |
同步字节,固定为0x47, 采用固定宏TS_SYNC_0X47替代。每一次PAT和PMT和每一帧数据处理的时候都要添。 |
transport_error_indicator |
1b |
传输错误指示符,表明在ts头的adapt域后由一个无用字节,通常都为0。 |
payload_unit_start_indicator |
1b |
负载开始标志位,用来表示TS包的有效净荷带有PES包或者PSI数据的情况,占位1bit;另若此值为1,且负载为PSI数据时,则在TS头后,负载起始字节会有1个调整字节point_field; |
transport_priority |
1b |
传输优先级,0为低优先级,1为高优先级,通常取0 |
pid: |
13b |
pid值(Packet ID号码,唯一的号码对应不同的包) 指示有效负载中的数据类型,占位13bit;0x0000代表PAT,0x0001代表CAT,0x0002-0x000F保留,0x1FFF表示空包; |
transport_scrambling_control |
2b |
传输加扰控制,00表示未加密 |
adaptation_field_control |
2b |
调整字段标志,表示此TS首部是否跟随调整字段还是负载数据,占位2bit,其中00位保留,01表示无调整字段,只有有效负载数据,10表示只有调整字段,无有效负载,11表示有调整字段,且其后跟有有效负载;空分组此字段应为01; |
continuity_counter |
4b |
固定为11,递增计数器,从0-f,起始值不一定取0,但必须是连续的 |
以上4个字节是必须存在的,但还有一些特殊的。当adaptation_field_control==1x时,会出现Adaptation field字段,Adaptation field字段如下。
Adaptation field字段
adaptation_field_length |
8b |
调整字段长度标示,标示此字节后面调整字段的长度,占位8bit;值为0时,表示在TS分组中插入一个调整字节,后面没有调整字段,紧跟着的是有效负载;adaptation_field_control == ‘11’时,此值在0~182之间,adaptation_field_control == ‘10’时,此值为183,若字段没这么长则填充0xFF字段; |
以下字段都是在adaptation_field_length>0是才会出现的 |
||
discontinuity_indicator |
1b |
不连续状态指示符,占位1bit,置位1时表示此TS分组的不连续状态为真; |
random_access_indicator |
1b |
随机访问指示符,占位1bit; |
elementary_stream_priority_indicator |
1b |
原始流数据优先级指示符,占位1bit,置位1表示此原始流数据比相同PID的TS包中的其他原始流优先级高; |
PCR_flag |
1b |
PCR标志位,占位1bit,置位1表示调整字段中包含PCR字段,置位0则没有PCR字段; |
OPCR_flag |
1b |
OPCR标志位,占位1bit,置位1表示调整字段中包含OPCR字段,置位0则没有OPCR字段; |
splicing_point_flag |
1b |
splice_countdown标志位,占位1bit,置位1表示调整字段中包含splice_countdown字段,置位0则没有splice_countdown字段; |
transport_private_data_flag |
1b |
transport_private_data标志位,占位1bit,置位1时表示调整字段中含有1个或者多个私有数据字节,置位0则无此字节; |
adaptation_field_extension_flag |
1b |
调整字段扩展标志位,占位1bit,置位1表示含有调整字段扩展字段,置位0则无扩展字段; |
以上8个bit是标识符,后面是根据标识符的值来确定的字段,顺序如下:
@PCR字段:当PCR_flag == 1时,此字段才存在,占位48bit,依次顺序为:
program_clock_reference_base字段:占位33bit;
reserved字段:占位6bit;
program_clock_reference_extension字段:占位9bit;
@OPCR字段:当OPCR_flag == 1时,此字段才存在,占位48bit,依次顺序为:
original_program_clock_reference_base字段:占位33bit;
reserved字段:占位6bit;
original_program_clock_reference_extension字段:占位9bit;
@splice_countdown字段:当splicing_point_flag == 1时此字段存在,占位8bit;
@transport_private_data字段:私有数据字段,当transport_private_data_flag==1时此字段存在,占位N*8bit,字节顺序为:
transport_private_data_length:表明私有数据的字节长度,占位8bit;
private_data_byte:私有数据,长度由前面的长度字段确定;
@adaptation_field_extension字段:调整字段扩展字段,占用长度不确定,当adaptation_field_extension_flag == 1时此字段存在,字段中也有3个标志位,来确定一些字段存不存在,其具体字节顺序如下:
adaptation_field_extension_length:调整字段扩展字段的长度,占位8bit;
ltw_flag:ltw字段标志位,置位1时表示此字段存在,占位1bit;
piecewise_rate_flag:piecewise_rate字段标志位,置位1时此字段存在,占位1bit;
seamless_splice_flag:seamless_splice标志位,置位1时此字段存在,占位1bit;
Reserved:保留字段,占位5bit;
Ltw字段:当ltw_flag == 1时此字段存在,占位16bit,其由以下两个字段组成
ltw_valid_flag:占位1bit,当ltw_valid_flag == 1时,ltw_offset才有效;
ltw_offset:占位15bit;
piecewise_rate字段:当piecewise_rate_flag == 1时此字段存在,占位24bit,其字节顺序如下:
reserved字段:保留字段,占位2bit;
piecewise_rate字段:占位22bit;此字段只有在当ltw_flag == 1和ltw_valid_flag == 1时才有定义,有定义时此字段是一个正整数;
seamless_splice字段:当seamless_splice_flag == 1时此字段存在,占位40bit;字节顺序依次为:
splice_type字段:占位4bit;标识delay和rate值;
DTS_next_AU[32..30]:占位3bit;
marker_bit字段:占位1bit;
DTS_next_AU[29..15]字段:占位15bit;
marker_bit:占位1bit;
DTS_next_AU[14..0]:占位15bit;
marker_bit:占位1bit;
stuffing_byte:填充字段,固定为0xFF;
Payload_bytes:有效负载字段,字节来自PES包,PSI部分等;
注意:
当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点:置为1,标识TS包的有效净荷以PES包的第一个字节开始,即此TS包为PES包的起始包,且此TS分组中有且只有一个PES包的起始字段;置为0,表示TS包不是PES包的起始包,是后面的数据包。
当 TS包带有PSI数据时,payload_unit_start_indicator具有以下特点:置为1,表示TS包中带有PSI数据分段的第一个字节,即这个TS包是PSI Section的起始包,则此TS包的负载的第一个字节带有pointer_field;置为0,表示TS包不带有PSI Section的第一个字节,即此TS包不是PSI的起始包,即在有效负载中没有point_field,有效负载的开始就是PSI的数据内容。point_field的定义将在下面的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包结束条件。
TS包头之后,就是负载payload的内容了,里面可以是PES分组的数据,也可以是PSI信息,PSI信息主要由PAT,PMT,CAT等,在这里主要介绍PAT和PMT两种信息表;由上所描述信息可知,payload的类型是由PID来确定的,一般PID==0x0000则payload为PAT,PID== 0x0001,则payload为CAT,而PMT的PID则是在PAT中进行指定的;
PSI还有可能有一个特殊的字段:
Point_field字段:跟在包头之后,占位8bit,属于有效负载,表示从此字段开始到负载中PSI Section的第一个字节之间的字节数;当payload_unit_start_indicator == 1时,此字段才存在;若point_field == 0x00,则表示此字节后跟着的就是PSI Section的起始字节;此字段是在有效负载中的,计入有效负载的长度;
其中PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的。如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video、Audio或其他业务信息)。下表给出了一些表的PID值,这些值是固定的,不允许用于更改。
类型 |
PID 值 |
PAT |
0x0000 |
CAT |
0x0001 |
TSDT |
0x0002 |
EIT,ST |
0x0012 |
RST,ST |
0x0013 |
TDT,TOT,ST |
0x0014 |
表1 pid 类型表
下面以一个TS流的其中一个Packet中的Packet Header为例进行说明:
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
Packet(十六进制) |
4 |
7 |
0 |
7 |
e |
5 |
1 |
2 |
… |
||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
… |
Packet Header 信息 |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x07e5 |
6 |
7 |
8 |
…
|
表2 header 例程数据表
@ sync_byte=01000111, 就是0x47,这是DVB TS规定的同步字节,固定是0x47.
@ transport_error_indicator=0, 表示当前包没有发生传输错误.
@ payload_unit_start_indicator=0, 含义参考ISO13818-1标准文档
@ transport_priority=0, 表示当前包是低优先级.
@ PID=00111 11100101即0x07e5, Video PID
@ transport_scrambling_control=00, 表示节目没有加密
@ adaptation_field_control=01 即0x01,具体含义请参考ISO13818-1
@ continuity_counte=0010 即0x02,表示当前传送的相同类型的包是第3个
ts层的内容是通过PID值来标识的,主要内容包括:PAT表、PMT表、音频流、视频流。解析ts流要先找到PAT表,只要找到PAT就可以找到PMT,然后就可以找到音视频流了。PAT表的PID值固定为0。PAT表和PMT表需要定期插入ts流,因为用户随时可能加入ts流,这个间隔比较小,通常每隔几个视频帧就要加入PAT和PMT。PAT和PMT表是必须的,还可以加入其它表如SDT(业务描述表)等。
ts流最早应用于数字电视领域,其格式非常复杂包含的配置信息表多达十几个,视频格式主要是mpeg2。苹果公司发明的http live stream流媒体是基于ts文件的,不过他大大简化了传统的ts流,只需要2个最基本的配置表PAT和PMT,再加上音视频内容就可以了,hls流媒体视频编码的主要格式为h264/mpeg4,音频为aac/mp3。
adaption为自适应填充功能,主要目的为填充凑齐不足188字节的TS包。
图6 TS adaptation填充结构
@ adaptation_field_length:8bit,自适应域长度,后面的字节数
@ flag :1bit,取0x50表示包含PCR或0x40表示不包含PCR
@ PCR:1bit,Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)。
@ stuffing_bytes:填充字节,取值0xff
自适应区的长度要包含传输错误指示符标识的一个字节。pcr是节目时钟参考,pcr、dts、pts都是对同一个系统时钟的采样值,pcr是递增的,因此可以将其设置为dts值,音频数据不需要pcr。如果没有字段,ipad是可以播放的,但vlc无法播放。打包ts流时PAT和PMT表是没有adaptation field的,不够的长度直接补0xff即可。视频流和音频流都需要加adaptation field,通常加在一个帧的第一个ts包和最后一个ts包里,中间的ts包不加。
es层就是音视频裸数据了,常用的音频编码格式为AAC,和MP3 。视频编码格式为H.264和HEVC。
其数据结构可以分别参考相应的编码规范手册。
pes层是在每一个视频/音频帧上加入了时间戳等信息,pes包内容很多,我们只留下最常用的。
如图7,按照苹果协议定义规范,PES内容可以是, PMT项数据,PAT项数据, 也可以是正常的音视频数据。
PAT全称Program Association Table,节目关联表,定义了当前TS流中所有的节目,其PID为0x0000,它是PSI的根节点,当播放器对视频开始检索分析的时候,针对每个TS 包的header中pid成员进行判定,直到找到PAT表开始的地方进行有效数据起始分析。
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 PAT 例程数据表
PAT结构根据表3 PAT 例程数据表加上我们节目数量为1的时候以得知PAT的数据结构如下:
因此PAT长度为16个字节,而payloadSize为184个字节, 那么adaptFileLen填充为168个长度。
根据表3的描述,有如下的例程。
通过一段TS流中一个Packet分析PAT表,这里我们分析一段TS流其中一个Packet的Packet Data部分:
首先给出一个数据包,其数据如下:
Packet Header |
Packet Data |
0x47 0x40 0x00 0x10 |
0000 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff…… ff ff |
分析Packet Header如下表所示:
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
Packet(十六进制) |
4 |
7 |
4 |
0 |
0 |
0 |
1 |
0 |
… |
||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
… |
Packet Header Bits |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x0000 |
6 |
7 |
8 |
… |
根据包头数据格式,我们可以知晓整个数据包的属性,列表如下:
sync_byte |
0x47 |
固定同步字节 |
transport_error_indicator |
“0” |
没有传输错误 |
payload_unit_start_indicator |
“1” |
在前4个字节后会有一个调整字节。所以实际数据应该为去除第一个字节后的数据。即上面数据中红色部分不属于有效数据包。 |
transport_priority |
“0” |
传输优先级低 |
PID |
0x0000 |
PID=0x0000说明数据包是PAT表信息 |
transport_scrambling_control |
“00” |
未加密 |
adaptation_field_control |
“01” |
附加区域控制 |
continuity_counte |
“0000” |
包递增计数器 |
如上表所示,我们可以知道,首先Packet的Packet Data是PAT信息表,因为其PID为0,并且在包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")。这样,Packet Data就应该是“00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff …… ff ff”。
Packet Data分析 |
|||||||||||||||||||||||||
第n个字节 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
… |
||||
Packet Data(除去开头的0x00) |
00 |
b0 |
11 |
00 |
01 |
c1 |
00 |
00 |
00 |
00 |
e0 |
1f |
00 |
01 |
e1 |
00 |
24 |
ac |
48 |
84 |
… |
||||
字段名 |
位 |
具体值 |
次序 |
说明 |
|||||||||||||||||||||
table_id |
8 |
0000 |
第1个字节 0000 0000B(0x00) |
PAT的table_id只能是0x00 |
|||||||||||||||||||||
section_syntax_indicator |
1 |
1 |
第2、3个字节 1011 0000 0001 0001B(0xb0 11) |
段语法标志位,固定为1 |
|||||||||||||||||||||
zero |
1 |
0 |
|
||||||||||||||||||||||
reserved |
2 |
11 |
|
||||||||||||||||||||||
section_length |
12 |
0000 0001 0001B=0x011=17 |
段长度为17字节 |
||||||||||||||||||||||
transport_stream_id |
16 |
0x0001 |
第4、5个字节 0x00 0x01 |
|
|||||||||||||||||||||
reserved |
2 |
11 |
第6个字节 1100 0001B(0xc1) |
|
|||||||||||||||||||||
version_number |
5 |
00000 |
一旦PAT有变化,版本号加1 |
||||||||||||||||||||||
current_next_indicator |
1 |
1 |
当前传送的PAT表可以使用,若为0则要等待下一个表 |
||||||||||||||||||||||
section_number |
8 |
0x00 |
第7个字节0x00 |
|
|||||||||||||||||||||
last_section_number |
8 |
0x00 |
第8个字节 0x00 |
|
|||||||||||||||||||||
开始循环 |
|||||||||||||||||||||||||
program_number |
16 |
0x0000-第一次 |
2个字节(0x00 00) |
节目号 |
|||||||||||||||||||||
reserved |
3 |
111 |
2个字节 1110 0000 0001 1111B(0xe0 1f) |
|
|||||||||||||||||||||
network_id(节目号为0时) program_map_PID(节目号为其他时) |
13 |
0 0000 0001 1111B=31 -第一次 |
节目号为0x0000时,表示这是NIT,PID=0x001f,即31 节目号为0x0001时,表示这是PMT,PID=0x100,即256 |
||||||||||||||||||||||
结束循环 |
|||||||||||||||||||||||||
CRC_32 |
32 |
-- |
4个字节 |
|
由以上几个表可以分析出PAT表和PMT表有着内在的联系。也就是之前提到的。PAT表描述了当前流的NIT(Network Information Table,网络信息表)中的PID、当前流中有多少不同类型的PMT表及每个PMT表对应的频道号。
PMT 全称Program Map Table,节目映射表,PMT数据的信息可以理解为这个节目包含的音频和视频信息。海康目前用它来填充海康自己内部的私有信息。
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校验码 |
表4 PMT数据表
通过一段TS流中一个Packet分析PMT表,通过分析一段TS流的数据包Packet来学习PMT表。下面给出了一段TS流数据中的一个Packet(十六进制数)。
Packet Header |
Packet Data |
0x47 0x43 0xe8 0x12 |
00 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff…… ff ff |
首先解析Packet Header,分析如下:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
… |
|||
Packet(十六进制) |
4 |
7 |
4 |
3 |
e |
8 |
1 |
2 |
… |
||||||||||||||||||||||||||
Packet(二进制) |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
… |
||
Packet Header Bits |
1 sync_byte=0x47 |
2 |
3 |
4 |
5 PID=0x03e8 |
6 |
7 |
8 |
… |
||||||||||||||||||||||||||
Packet Header分析 |
|||||||||||||||||||||||||||||||||||
|
Packet Header:0x47 0x40 0x00 0x10 |
||||||||||||||||||||||||||||||||||
1 |
sync_byte |
0x47 |
固定同步字节 |
||||||||||||||||||||||||||||||||
2 |
transport_error_indicator |
“0” |
没有传输错误 |
||||||||||||||||||||||||||||||||
3 |
payload_unit_start_indicator |
“1” |
在前4个字节后会有一个调整字节。所以实际数据应该为去除第一个字节后的数据。 |
||||||||||||||||||||||||||||||||
4 |
transport_priority |
“0” |
传输优先级低 |
||||||||||||||||||||||||||||||||
5 |
PID |
0x03e8 |
PID=0x03e8说明数据包是PMT表信息 |
||||||||||||||||||||||||||||||||
6 |
transport_scrambling_control |
“00” |
未加密 |
||||||||||||||||||||||||||||||||
7 |
adaptation_field_control |
“01” |
附加区域控制 |
||||||||||||||||||||||||||||||||
8 |
continuity_counte |
“0010” |
包递增计数器 |
因为payload_unit_start_indicator=‘1’,在解析数据包的时候需要去除Packet Data的第一个字节。下面是对Packet Data的详细解析:
PMT表的Packet Data分析 |
|||||||||||||||||||||||||
第n个字节 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
… |
||||
Packet Data |
02 |
b0 |
12 |
00 |
01 |
c1 |
00 |
00 |
e3 |
e9 |
f0 |
00 |
1b |
e3 |
e9 |
f0 |
00 |
f0 |
1b |
e3 |
… |
||||
字段名 |
位数 |
具体值 |
次序 |
说明 |
|||||||||||||||||||||
table_id |
8 |
0x02 |
第1个字节 |
|
|||||||||||||||||||||
section_syntax_indicator |
1 |
1B |
第2、3个字节 1011 0000 0001 0010B=0xb012 |
段语法标志 |
|||||||||||||||||||||
zero |
1 |
0B |
|
||||||||||||||||||||||
reserved |
2 |
11B=0x03 |
|
||||||||||||||||||||||
section_length |
12 |
0000 0001 0010B=0x12 |
段长度,从program_number开始,到CRC_32(含)的字节总数 |
||||||||||||||||||||||
program_number |
16 |
0x0001 |
第4、5个字节 0x00 01 |
频道号码,表示当前的PMT关联到的频道 |
|||||||||||||||||||||
reserved |
2 |
11B=0x03 |
第6个字节 1100 0001B=0xc1 |
|
|||||||||||||||||||||
version_number |
5 |
00000B=0x00 |
版本号码,如果PMT内容有更新,则它会递增1通知解复用程序需要重新接收节目信息 |
||||||||||||||||||||||
current_next_indicator |
1 |
1B=0x01 |
当前未来标志符 |
||||||||||||||||||||||
section_number |
8 |
0x00 |
第7个字节0x00 |
当前段号码 |
|||||||||||||||||||||
last_section_number |
8 |
0x00 |
第8个字节 0x00 |
最后段号码,含义和PAT中的对应字段相同 |
|||||||||||||||||||||
reserved |
3 |
111B=0x07 |
第9、10个字节 1110 0011 1110 1001B=0xe3e9 |
|
|||||||||||||||||||||
PCR_PID |
13 |
000111110B=0x3e9 |
PCR(节目参考时钟)所在TS分组的PID |
||||||||||||||||||||||
reserved |
4 |
1111B=0x0f |
第11、12个字节 1111 0000 0000 0000=0xf000 |
|
|||||||||||||||||||||
program_info_length |
12 |
000000000000B=0x000 |
节目信息长度(之后的是N个描述符结构,一般可以忽略掉,这个字段就代表描述符总的长度,单位是Bytes)紧接着就是频道内部包含的节目类型和对应的PID号码了 |
||||||||||||||||||||||
stream_type |
8 |
0x1b |
第13个字节 0x1b |
流类型,标志是Video还是Audio还是其他数据 |
|||||||||||||||||||||
reserved |
3 |
111B=0x07 |
第14、15个字节 1110 0011 1110 1001B=0xe3e9 |
|
|||||||||||||||||||||
elementary_PID |
13 |
000111110 1001=0x3e9 |
该节目中包括的视频流,音频流等对应的TS分组的PID |
||||||||||||||||||||||
reserved |
4 |
1111B=0x0f |
第16、17个字节 1111 0000 0000 0000B=0xf000 |
|
|||||||||||||||||||||
es_info_length |
12 |
0000 0000 0000=0x000 |
|
||||||||||||||||||||||
CRC |
32 |
—— |
—— |
|
音视频数据是在每一个视频/音频帧上加入了时间戳等信息,pes包内容很多,我们只留下最常用的。
pes start code |
3B |
开始码,固定为0x000001 |
stream id |
1B |
音频取值(0xc0-0xdf),通常为0xc0 |
pes packet length |
2B |
后面pes数据的长度,0表示长度不限制, |
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内容才可以获取帧类型。
时钟配置,MPEG2-TS 中规定系统采用45K的基准时钟。而我们DSP进程封装功能为90K(兼容RTP、PS封装目的)。因此需要进行转换。
假如帧率为25帧每秒, 那么每一帧的系统间隔时间为40ms。按照TS封装45K的采样率基准时钟来计算, 需要1800个时钟(40 * 45)。
假如音频采样率为16K,单个点耗时1/16ms即62.5us,那么1024个点为一帧一共64ms(62.5us * 1024),,则转为45K下为2880个时钟,(0.0625*45*1024)。