上一篇描述了PES包头的封装格式,本篇描述一下TS包的封包格式
TS流,即传输流,是对PES包的进一步封装,基本单位为TS包,固定每包大小为188字节(或204字节,在188字节后加上16字节的CRC校验数据),由TS包头和payload组成;其组成如下图:
其中包头由4个字节的固定头部和其后的adaptation field数据构成,字节顺序依次如下:
sync_byte:同步码,其大小为固定8个bit,值为0x47;
transport_error_indicator:错误标志位,占位1bit,置为1表示此分组中至少有一个不可纠正的错误;
payload_unit_start_indicator:负载开始标志位,用来表示TS包的有效净荷带有PES包或者PSI数据的情况,占位1bit;另若此值为1,且负载为PSI数据时,则在TS头后,负载起始字节会有1个调整字节point_field;
当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包结束条件。
transport_priority:发送优先级,置1则表示此包比其他相同PID置0的包有高的优先级,占位1bit;
PID:指示有效负载中的数据类型,占位13bit;0x0000代表PAT,0x0001代表CAT,0x0002-0x000F保留,0x1FFF表示空包;
transport_scrambling_control:有效负载加密模式标志,占位2bit,00表示未加密;
adaptation_field_control:调整字段标志,表示此TS首部是否跟随调整字段还是负载数据,占位2bit,其中00位保留,01表示无调整字段,只有有效负载数据,10表示只有调整字段,无有效负载,11表示有调整字段,且其后跟有有效负载;空分组此字段应为01;
如果adaptation_field_control == 1x,表示后面跟有adaptation field字段;
如果adaptation_field_control == x1,表示后面跟有data_bytes字段;
continuity_counter:连续性计数,随每一个相同PID的TS分组增加,达到最大值后又归为0;占位4bit,如果adaptation_field_control值为00或01,此值不应增加;若调整字段的标志位discontinuity_indicator值为1,则此值也不连续;
Adaptation field:调整字段,只有当adaptation_field_control == 1x时,以下字段才会存在,其中内容如下:
adaptation_field_length:调整字段长度标示,标示此字节后面调整字段的长度,占位8bit;值为0时,表示在TS分组中插入一个调整字节,后面没有调整字段,紧跟着的是有效负载;adaptation_field_control == ‘11’时,此值在0~182之间,adaptation_field_control == ‘10’时,此值为183,若字段没这么长则填充0xFF字段;
后面的字段都是在adaptation_field_length>0的时候才会出现,顺序如下:
discontinuity_indicator:不连续状态指示符,占位1bit,置位1时表示此TS分组的不连续状态为真;
random_access_indicator:随机访问指示符,占位1bit;
elementary_stream_priority_indicator:原始流数据优先级指示符,占位1bit,置位1表示此原始流数据比相同PID的TS包中的其他原始流优先级高;
PCR_flag:PCR标志位,占位1bit,置位1表示调整字段中包含PCR字段,置位0则没有PCR字段;
OPCR_flag:OPCR标志位,占位1bit,置位1表示调整字段中包含OPCR字段,置位0则没有OPCR字段;
splicing_point_flag:splice_countdown标志位,占位1bit,置位1表示调整字段中包含splice_countdown字段,置位0则没有splice_countdown字段;
transport_private_data_flag:transport_private_data标志位,占位1bit,置位1时表示调整字段中含有1个或者多个私有数据字节,置位0则无此字节;
adaptation_field_extension_flag:调整字段扩展标志位,占位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部分等;
总之,这些字段就是一层套一层,一个字段标志位控制一个字段,标志位为1时,其标志的字段才会存在;
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的起始字节;此字段是在有效负载中的,计入有效负载的长度;
PAT主要包含了节目编号和每一个节目对应的PMT的PID号码,提供了节目编号和包含此节目定义信息的TS分组(PMT分组)的PID之间的对应关系(一般我们的TS流中只有一个节目(频道),所以PAT中一般只有一个PMT);
整个表由很多字段组成;这个表可能会被分为多个分段section进行传输,即PAT可能会被分在多个TS包进行传输;PAT的数据分段section由以下字段组成,依次顺序为:
table_id字段:标示PSI分段的内容,占位8bit,当table_id == 0x00,表示此分段是PAT分段,当table_id == 0x01,表示此分段是CAT分段,当table_id == 0x02,表示此分段是PMT分段;在PAT中,id的值为0x00;
section_syntax_indicator字段:占位1bit,固定置位’1’;
‘0’字段:占位1bit;
Reserved字段:保留字段,占位2bit;
section_length字段:分段长度,占位12bit,其中前2个bit固定为’00’,后10个bit表明了后面section字段的长度,包括CRC的长度;
transport_stream_id字段:TS流识别id,用于从网络中其他的多路复用中识别出此TS流,其值由用户定义,占位16bit;
Reserved:保留字段,占位2bit;
version_number字段:整个PAT的版本号,占位5bit;当PAT变化时,其值从0到31循环累加,当current_next_indicator == 1时,version_number为当前PAT的版本号,当current_next_indicator == 0时,其值为下一个PAT的版本号;
current_next_indicator字段:指示符,占位1bit,置位1时表示当前PAT有效,置位0时表示当前PAT无效,下一个PAT才有效;
section_number字段:此分段Section的序号,占位8bit,PAT的第一个分段Section的序号应该为0x00,将随着PAT中的每一个分段累加1;
last_section_number字段:PAT最后一个分段Section的序号,即最高序号值,占位8bit;
Loop:
program_number字段:占位16bit,指明PMT可用的节目的编号;若program_number == 0x0000,则下一个参考PID是network PID,其他情况的值由用户定义;在PAT的一个版本version_number中,这个值不能取某单个值多于一次(即一个PAT分段Section中只能有一个节目编号和其PMT的PID的对应关系);
Reserved:保留字段,占位3bit;
network_PID字段:网络PID,占位13bit,当program_number == 0x0000时,此字段才存在;指明含有网络信息表NIT的TS包的PID值;
program_map_PID字段:占位13bit,当program_number != 0x0000时此字段存在,表示program_number所指明的节目可用的PMT分段的TS包的PID值;一个program_number不应有多个program_map_PID赋值,这个program_map_PID的值是由用户定义的,不过不能取为其他目的而保留的值;
Loop end;
CRC_32字段:CRC校验值,占位32bit;
PMT提供了节目编号和组成他们的节目原始流之间的映射关系,如果一个TS流中有多个节目,那个就会有多个PID不同的PMT表,在每个PMT中,都包含了节目原始流中不同的流类型TS包所对应的PID;即在PMT中,标识了当前节目中的视频流,音频流和与此节目相关的其他数据的TS包所对应的PID值;
同PAT一样,PMT也可能被分为一个或多个Section分段进行传输,PMT由很多字段组成,其字段顺序如下所示:
table_id字段:标示PSI分段的内容,占位8bit,在PMT中,固定为0x02;
section_syntax_indicator字段:占位1bit,固定置位’1’;
‘0’字段:占位1bit;
Reserved字段:保留字段,占位2bit;
section_length字段:分段长度,占位12bit,其中前2个bit固定为’00’,后10个bit表明了后面section字段的长度,包括CRC的长度;
program_number字段:节目编号,占位16bit,规定了此PMT所对应的节目编号,一个TS的PMT分段Section中,只能带有一个节目定义;
Reserved字段:保留字段,占位2bit;
version_number字段:此PMT分段的版本号,占位5bit,随着此分段信息的改变而累加1,直到31后再回到0循环;版本号对应于单个节目的定义,也就是对应于单个分段;当current_next_indicator == 1时,number值就是当前PMT分段的版本号,当current_next_indicator == 0时,number值位下一个可用PMT分段的版本号;
current_next_indicator字段:指示符,占位1bit,置位1时表示当前PMT分段有效,置位0时表示当前PMT分段无效,下一个PMT分段才有效;
section_number字段:占位8bit,固定为0x00;
last_section_number字段:占位8bit,固定为0x00;
Reserved字段:保留字段,占位3bit;
PCR_PID字段:PCR所在TS包的PID值,占位13bit;表示由program_number所指明的节目中包含PCR字段的TS包的PID值;如果一个私有流的节目定义无PCR与之相关,则这个字段应置位0x1FFF;
Reserved字段:保留字段,占位4bit;
program_info_length字段:长度字段,占位12bit;前2bit固定为00,后10个bit指明了此字段之后的descriptor的字节数;
Descriptor字段:节目描述信息,长度由上一个字段确定;
LOOP:
stream_type字段:流类型字段,占位8bit;规定了由elementary_PID所指明的TS包的负载中的节目流的类型,即是视频流还是音频流或者其他数据;stream_type == 0x00是保留值;stream_type == 0x01和stream_type == 0x02是视频;stream_type == 0x03和stream_type == 0x04是音频;stream_type == 0x06是包含私有数据的PES包;
Reserved字段:保留字段,占位3bit;
elementary_PID字段:PID字段,占位13bit;指出携带相关原始流ES的TS包的PID值,即视频包和音频包等TS包的PID值;
Reserved字段:保留字段,占位4bit;
ES_info_length字段:长度字段,占位12bit;前2bit固定为00,后10个bit表示此字段之后相关节目原始流ES的描述字段长度;
Descriptor字段:ES流描述信息,长度由上一个字段确定;
LOOP End;
CRC_32字段:CRC校验值,占位32bit;
TS总结:TS解包流程就是现在TS包的包头解出来PAT的PID,然后根据PID找到PAT,并从PAT中解出来每个节目所对应的PMT的PID,再根据PID找到所有节目的PMT,然后从每个节目的PMT中解出来当前节目所对应的不同流类型的TS包的PID,根据这些PID来找到对应的TS包,取出原始视频流,音频流和其他数据等;打包过程则是相反的;
TS头里面的PCR字段是基准时间戳,在音视频解码显示的时候,是根据PES头里面的PTS和DTS字段与其对比,相同就说明该进行解码和显示了;PCR字段是在TS的PMT中指定的PID,只有指定的PID的TS包里面的PCR字段才有用,我们打包的时候使用的是视频的PID中的PCR,只有每帧的第一包TS头里面才会有PCR,而PES头里面的PTS和DTS就是视频和音频的相对时间戳;测试遇到了音视频不同步的问题,原因就是TS打包时,PES头里面的音视频PTS都用了视频的时间戳,而我们在TS解析时是对音频有相对延后的操作,其采用的视频时间戳相对原来是有可能延后了多个视频帧的,所以导致音频有延后;
项目上TS流在IOS系统上面播放遇到坑:
1.今天调试IOS系统播放TS流,发现了一个坑,IOS系统播放必须将视频PES包头中的Packet_Length字段设置为0,音频PES包的这个值必须不为0,否则IOS系统将无法播放TS流!!!
2.如果TS流里面是只有视频没有音频的,那么在封装PMT的时候,一定不要放进去音频的PID,否则IOS系统也播放不了- - (安卓各种顺畅,IOS是各种坑啊,限制太多了ORZ...)