视频流TS打包方式详解

前文介绍过PS流打包详解,从中可以看到PES的详细打包方式,TS流实际上是在PES之上进行封装,加上PAT、PMT等PSI信息表组成,如果要了解PES详细打包方式,关注公众号:壹零仓,发送:ps,获取相关文章。 HLS切片文件一般是TS流封装的音视频文件,这里主要介绍下TS流封装方式。

TS流包头封装

TS流(Transport Stream),是对PES包的进一步封装,基本单位为TS包,固定包大小为188字节(或204字节,在188字节后加上16字节的CRC校验数据),由TS包头和payload组成;其组成如下图:

图片

TS包头由4bytes的固定头和其后的附加域(adaption field)组成,按照字节顺序解释如下:

  • sync_byte:同步码,其大小为固定8个bit,值为0x47
  • transport_error_indicator:错误标志位,占位1bit,置为1表示此分组中至少有一个不可纠正的错误,一般为0
  • 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是标识符,后面是根据标识符的值来确定的字段,顺序如下:
  • PRC字段:当PCR_flag == 1时,此字段才存在,占位48bit,依次顺序为:
  • program_clock_reference_base字段:占位33bit;
  • reserved字段:占位6bit;
  • program_clock_reference_extension字段:占位9bit;
  • OPRC字段:当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部分等;

PSI(程序特殊信息表)

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:节目关联表

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:节目映射表

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枚举: 0x10-MPEG-4 视频流;0x1B-H264;0x24-H265;0x80-SVAC;0x90-G711;0x92-G722.1;0x99-G.729;0x9B-SVAC;0x0f-AAC
  • 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抓包实例解析

通过从wireshark中提取的ts文件,通过Elecard StreamEye Tools软件(软解可以关注公众号:壹零仓,发送视频流分析,获取工具)进行分析如下:

图片

从上图可以看出,TS解析的过程:

  • 根据PID=0找到并解析出到PAT,根据PAT节目关联的PMT,解析出PMT的个数以及PMT对应的PID,一般默认就一个PMT
  • 根据PMT对应的PID,找到并解析出节目映射表包含的所有流、流的类型及流的PID,这里每一种流我们知道stream type和PID
  • 根据stream type含义,我们找到我们所需要解析的流类型对应的PID,根据此PID找到真正的音视频流

TS的解封装和封装可以根据此流程进行相关代码的设计。

总结

TS定义的很多,其实我们真正进行TS的封装和解封装的代码开发的时候,所需要关注的字段很少,封装和解封装很多字段都是默认值,可选字段大部分都是没有的,具体可参照ffmpeg相关代码。

作者:壹零仓

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论