MPEG2: TS、PS

        我的音视频/流媒体开源项目(github)

目录

一、TS、PS简介

二、PES格式

三、TS格式

3.1 固定字段

3.2、调整域(Adaptation field)

3.3、第一个可选域(optional fields)

3.4、第二个可选域(optional fields)

3.5、负载

3.6、PSI

3.7、TS包长度说明

四、PS格式

4.1、PS头

4.2、PS system header封装格式

4.3、 PSM封装格式


 

        我的开源库:TS、PS封装/解封装库libmpeg2core,支持H264/H265/MPEG1 audio/MP3/AAC/AAC_LATM/G711 ,纯C语言实现,API简洁,demo也比较完善,而且还支持多节目的封装和解封装,对照本篇文章看源码,上手会更快。项目地址:https://github.com/BreakingY/libmpeg2core

一、TS、PS简介

        MPEG-2 标准中,有两种不同的码流可以输出到信号,一种是节目码流(PS Program
Stream),一种是传输流(TS Transport Stream),如下表所示,对比了TS和PS格式:

属性 TS(Transport Stream) PS(Program Stream)
定义 用于实时传输音视频流的数字容器格式。 用于高质量音视频存储的数字容器格式。
标准 MPEG-2 Part 1 MPEG-2 Part 1
主要用途 实时传输、流媒体传输、多路节目传输。 音视频存储(如 DVD 和蓝光光盘)。
数据包结构 固定大小的 188 字节包。 可变大小的数据包,通常更大。
容错能力 强,适合高误码环境(如广播电视、IP 网络)。 弱,适用于可靠介质(如光盘存储)。
支持的流数量 支持多路音视频流和数据流,使用 PID 区分。 通常只支持单路或少量流。
额外信息 提供用于同步和误码检测的头部信息。 包含更多详细的节目信息,便于解码。
典型应用场景 数字电视广播(DVB)、IPTV、HLS、实时传输。 DVD、蓝光光盘、文件存储、视频编辑软件。
设计目标 传输效率与容错能力优先,适合动态网络环境。 存储效率与解码便捷优先,适合高质量数据存储。
转码/封装需求 传输过程中可能需要解封装为 PS,用于存储或后处理。 在分发或实时预览时可能封装为 TS,用于传输。

        值得注意的是TS、PS封装H264/H265的时候都会采用Annex B格式,虽然PS适合存储,但也不会像MP4、FLV那样采用AVCC(H264)/HVCC(H265)格式。关于Annex B和AVCC(H264)/HVCC(H265)格式参考我的另外一篇文章:H264/H265的两种格式Annex B、AVCC(H264)/HVCC(H265)

        这里一共涉及到四种格式,如下所示:

        ES(Elementary Stream):基本流,直接从编码器出来的数据流,可以是编码过的音频、视频或其他连续码流。
        PES(Packetized Elementary Streams):PES 流是ES 流经过PES 打包器处理后形成的数据流,在这个过程中完成了将ES 流分组、加入包头信息(PTS、DTS 等)操作。PES 流的基本单位是PES 包,PES 包由包头和payload 组成。
        PS 流(Program Stream):节目流,PS 流由PS 包组成,而一个PS 包又由若干个PES 包组成。一个PS 包由具有同一时间基准的一个或多个PES包复合合成。
        TS 流(Transport Stream):传输流,TS 流由固定长度(188 字节)的TS 包组成,TS 包是对PES 包的另一种封装方式,同样由具有同一时间基准的一个或多个PES 包复合合成。

        如下图所示,表示了TS的封装过程,PS也类似:

        对视频PES 来说,一般是一帧一个包,PES 一般一个包不超过64KB。PES 包头信息中加入了PTS、DTS 信息,用与音视频的同步。PES 包的长度通常都是远大于TS 包的长度,一个PES 包必须由整数个TS 包来传送,没装满的TS 包由填充字节填充。PES 包进行TS 复用时,往往一个PES 包会分存到多个TS 包中。而PS中,一个PES包一般就封装成一个PS包。

二、PES格式

        PES(Packetized Elementary Stream,打包的ES),在 ES 层的基础上加入了时间戳(PTS/TDS)等信息。如果ES数据包比较大,加入PES头时需将ES进行分割,只在第一个分割的ES上加PES头。

        如下图所示,为PES头格式:

        中文含义对照图:

 

        PES层中很多字段都是有它前面的flag指定,一般情况下很多字段是不存在的,最常见的是PTS/DTS。

        其详细字段解释如下:

        Packet start code prefix:包头起始码,固定为0x000001,占位24bit

        Stream id:PES包中的负载流类型,一般视频为0xe0(标准视频)/0xEF(私有视频),音频为0xc0(标准)/0xDF(私有),占位8bit

        PES packet length:PES包长度,包括此字节后的可选包头和负载的长度,占位16bit,当其为0时,需要根据下个PS包头起始码来确定PES包长度

        Optional PES Header:可选的PES头字段,解析如下:

        (1) ’10’字段:占位2bit;

        (2) PES scrambling control:加密模式,占2bit;00未加密,01或10或11由用户定义,一般默认为00;

        (3) PES priority:有效负载的优先级,占位1bit;值为1比值为0的负载优先级高;

        (4) Data alignment indicator:数据定位指示器,占位1bit,如果是分包发送,第一个为1,后续的包为0;如果单包发送取值为1;

        (5) Copyright:版权信息,1为有版权,0无版权,占位1bit,默认值为0;

        (6) Original or copy:原始或备份,1为原始,0为备份,占位1bit;默认值为0;

        (7) flags占8bit:

  • 最高两位为PTS_DTS_flags,PTS和DTS标志位,10表示首部有PTS字段,11表示有PTS和DTS字段,00表示都没有,01被禁止,不会出现此种情况,一般在分包时,第一个包为11,其他为0;
  • ESCR_flag,ESCR标志,占位1bit,1表示首部有ESCR字段,0则无此字段,默认为0;
  • S_rate_flag:ES_rate字段,占位1bit,1表示首部有此字段,0无此字段,默认为0;
  • DSM_trick_mode_flag:占位1bit,1表示有8位的DSM_trick_mode_flag字段,0无此字段,默认为0;
  • Additional_copy_info_flag:占位1bit,1表示首部有Additional copy info字段,0表示无此字段,默认为0;
  • PES_CRC_flag:占位1bit,1表示PES分组有CRC字段,0无此字段,默认为0;
  • PES_extension_flag:占位1bit,扩展标志位,置1表示有扩展字段(PES extension),0无此字段,默认为0

        注意:在PES中一般Optional fields中只有PTS、DTS,其他都没有。

        (8) PES header data length:PES首部中可选字段和填充字段的长度,占位8bit,可选字段的内容由上面7个flags来进行控制,,主要是PTS_DTS_flags控制的PTS和DTS字段,填充字节需要注意的是,当对原始流进行 PES 分组,尤其是需要将一帧信息断开分成多个 PES 分组时,从第二个分组开始不需要 PTS,PES_header_data_length 和它前面的一个字节又都为 0x00,很可能与后面断开的数据组合形成 0x00 00 01 等类似的伪起始码或关键字,令解码端在收到流不完整时产生误判,因此填充字节 stuffing_byte 至少必须加入 1 byte 以确保这种误判不会发生。同时为了入一些私有信息,目前规定 stuffing_byte 至少必须加入 2byte。但一般不会分ES分多个PES包封装,同时填充字段一般也是不存在的。

        PTS和DTS字段规则及解释如下:

         PTS/DTS字段:在原始的显示时间戳/解码时间戳,PTS/DTS都占位40bit(5bytes),PES中时间占用33个bit,PES中的PTS和DTS的内容就是在原始时间戳的40bit中取33位,解析方式方式相同;

        PTS和DTS是相对SCR(系统参考)的时间戳,不是绝对时间,而是以系统频率90000为单位的;PES头中的时间戳的结构如下:

        start_code:起始码,占位4bit;若PTS_DTS_flags == ‘10’,则说明只有PTS,起始码为0010;若PTS_DTS_flags == ‘11’,则PTS和DTS都存在,PTS的起始码为0011,DTS的起始码为0001;(PTS的起始码后2个bit与flag相同)

        PTS[32…30]:占位3bit;

        marker_bit:占位1bit;

        PTS[29…15]:占位15bit;

        marker_bit:占位1bit;

        PTS[14…0]:占位15bit;

        marker_bit:占位1bit;

        DTS规则与PTS一致,一般PES header data length是10+填充位。

        PTS、DTS格式如下,PTS、DTS实际上各占用5个字节(上图中说的33bit只是真正时间戳占用的bit个数,有点坑):

PTSDTS同时存在
| 0011 | PTS[3230] | marker_bit | PTS[2915] |marker_bit |PTS[140] | marker_bit | 0001 | DTS[3230] | marker_bit | DTS[2915] | marker_bit | DTS[140] | marker_bit |

只有PTS
| 0010 | PTS[3230] | marker_bit | PTS[2915] |marker_bit |PTS[140] | marker_bit |

        把PES包中的PTS还原到原来的时间戳,公式为:

PTS = ((PTS1 & 0x0e) << 29) | ((PTS2 & 0xfffe) << 14) | ((PTS3 & 0xfffe ) >> 1)
其中PTS1 = start_code + PTS[32…30] + marker_bit, PTS2 = PTS[29…15]+marker_bit,PTS3= PTS[14…0]+marker_bit;
DTS同理。

        PES包头之后,紧跟着就是原始的视频帧数据(ES)或者音频数据。

三、TS格式

        参考:H264解码之TS流解析_ts流解码-CSDN博客

        将PES 包内容分配到一系列固定长度的传输包(TS Packet)中。TS 流中TS 传输包头加入了PCR(节目参考时钟)与PSI/SI(节目专用信息/业务信息表),其中PCR 用于解码器的系统时钟恢复。
        PCR 时钟作用:我们知道,编码器中有一个系统时钟,用于产生指示音视频正确显示和解码的时间标签(DTS、PTS)。解码器在解码时首先利用PCR 时钟重建与编码器同步的系统时钟,再利用PES 流中的DTS、PTS 进行音视频的同步。

        PSI 主要涉及节目和流的基础管理信息,例如节目列表、流类型和 PID、加密控制等。

        SI 主要处理与服务相关的信息,例如节目的时间表、服务名称、时区、频道集合等。

        TS格式入下图所示:

        各字段含义如下:

3.1 固定字段

        sync_byte:同步码,其大小为固定8个bit,值为0x47;

        transport_error_indicator:错误标志位,占位1bit,置为1表示此分组中至少有一个不可纠正的错误;

        payload_unit_start_indicator:负载开始标志位,用来表示TS包的有效净荷带有PES包或者PSI数据的情况,占位1bit;另若此值为1,且负载为PSI数据时,则在TS头后,负载起始字节会有1个调整字节point_field;

        1、当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点:置为1,标识TS包的有效净荷以PES包的第一个字节开始,即此TS包为PES包的起始包,且此TS分组中有且只有一个PES包的起始字段;置为0,表示TS包不是PES包的起始包,是后面的数据包。

        2、当 TS包带有PSI数据时,payload_unit_start_indicator具有以下特点:置为1,表示TS包中带有PSI数据分段的第一个字节,即这个TS包是PSI Section的起始包,则此TS包的负载的第一个字节带有pointer_field;置为0,表示TS包不带有PSI Section(Section概念在下面的PSI中介绍)的第一个字节,即此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(PSI中定义的)和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,则此值也不连续;

3.2、调整域(Adaptation field)

        调整域,只有当adaptation_field_control == 1x时,以下字段才会存在,其中内容如下:

        adaptation_field_length:调整字段长度标示,标示此字节后面调整字段的长度(包括stuffing bytes,即直到负载前的所有数据长度),占位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包中的其他原始流优先级高;

        5 flags:

                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则无扩展字段;

        optional fields:下面说明

         stuffing bytes:填充字段,固定为0xFF,TS包不满足188字节,则使用填充字段凑数;

3.3、第一个可选域(optional fields)

optional fields:

        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_length+3 flags):调整字段扩展字段,占用长度不确定,当adaptation_field_extension_flag == 1时此字段存在,字段中也有3个标志位,来确定一些字段存不存在.

                adaptation_field_extension_length:调整字段扩展字段的长度,占位8bit,用于表示下一个可选域的长度(optional fields);
                3 flags:
                        ltw_flag:ltw字段标志位,置位1时表示此字段存在,占位1bit;
                        piecewise_rate_flag:piecewise_rate字段标志位,置位1此字段存在,占位1bit;
                        seamless_splice_flag:seamless_splice标志位,置位1此字段存在,占位1bit;
                        Reserved:保留字段,占位5bit;

3.4、第二个可选域(optional fields)

optional fields:
        Ltw字段(ltw_valid_flag+ltw_offset):当ltw_flag == 1时此字段存在,占位16bit,其由以下两个字段组成
                ltw_valid_flag:占位1bit,当ltw_valid_flag == 1时,ltw_offset才有效;
                ltw_offset:占位15bit;
        piecewise_rate字段(reserved+piecewise_rate):当piecewise_rate_flag == 1时此字段存在,占位24bit,其字节顺序如下:
                reserved字段:保留字段,占位2bit;
                piecewise_rate字段:占位22bit;此字段只有在当ltw_flag == 1和ltw_valid_flag == 1时才有定义,有定义时此字段是一个正整数;
        seamless_splice字段(splice_type + DTS_next_AU):当seamless_splice_flag == 1时此字段存在,占位40bit;字节顺序依次为:
                splice_type字段:占位4bit;标识delay和rate值;
                DTS_next_AU:
                        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;

3.5、负载

        Payload_bytes:有效负载字段,字节来自PES包,PSI部分等;

        总之,这些字段就是一层套一层,一个字段标志位控制一个字段,标志位为1时,其标志的字段才会存在;

3.6、PSI

        在MPEG2标准中,PSI包含4张表,PAT、PMT、CAT和NIT,为了能够对多个TS流中的节目信息进行更好的描述,SI定义了除PSI的4张表之外的9张表,分别为SDT(业务描述表)、EIT(事件信息表)、TDT(时间日期表)、TOT(时间偏移表)、BAT(业务群关联表)、RST(运行状态表)、ST(填充表)、SIT(选择信息表)、DIT(间断信息表)。

        PSI 是 必不可少的,因为它包含了必须的节目和流的管理信息。

        SI 是 可选的,用于提供附加信息,如节目表、时区、服务描述等,但不影响基本的流传输和播放。

        TS中必不可少的是PAT和PMT,TS中PID决定了负载内容的类型,主要包括:PAT表,PMT表,视频流,音频流。常用的PID值:

PAT CAT TSDT EIT,ST RST,ST TDT,TOT,ST
PID 0x0000 0x0001 0x0002 0x0012 0x0013 0x0014
  • TableId与Pid的区别:
    (1)TableId:标识TS流中的不同表
    (2)PID:标识有效负载中存储的数据类型

        从TS中解析音视频的流程如下所示:

TS 流  ->  解析 PAT  -> 获取 PMT PID
                       -> 解析 PMT  -> 获取音视频流的 PID 以及编码格式
                                       -> 提取音视频数据流
                                       -> 解码并播放

        PSI还有可能有一个特殊的字段:
        Point_field字段:跟在包头之后,占位8bit,属于有效负载,表示从此字段开始到负载中PSI Section的第一个字节之间的字节数;当payload_unit_start_indicator == 1时,此字段才存在;若point_field == 0x00,则表示此字节后跟着的就是PSI Section的起始字节;此字段是在有效负载中的,计入有效负载的长度;

1、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;

2、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 == 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头里面的PCR字段是基准时间戳,在音视频解码显示的时候,是根据PES头里面的PTS和DTS字段与其对比,相同就说明该进行解码和显示了;PCR字段是在TS的PMT中指定的PID,只有指定的PID的TS包里面的PCR字段才有用,我们打包的时候使用的是视频的PID中的PCR,只有每帧的第一包TS头里面才会有PCR,而PES头里面的PTS和DTS就是视频和音频的相对时间戳

3、PSI/SI的Section

        参考:TS码流解析(2) Section_ts section-CSDN博客

        一个TS数据包的最大净荷为184个字节,当一个PSI/SI表的字节长度大于184字节时,就要对这个表进行分割,形成段(section)来传送。分段机制主要是将一个数据表分割成多个数据段。在PSI/SI表到TS包的转换过程中,段起到了中介的作用。由于一个数据包只有188字节,而段的长度是可变的,EIT表的段限长4096字节,其余PSI/SI表的段限长为1024字节。因此,一个段要分成几部分插入到TS包的payload中。

        从TS码流中可以获取到TS包,TS包要组成Section,才能提取到想要的信息,组Section之前要了解TS包在码流中发送的一些情况:

        1、TS包发送的时候PID是无序的,连续的TS包的PID可能都是不一样的;

        2、TS包发送的时候Section是相对有序的,也就是说,对于同一个PID的TS包,只有发完了一个Section,才会发送下一个Section,不然无法区分该TS包属于哪一个Section,并且对于这个Section,TS包是有序发送的,否则数据会被打乱;

        3、某个Section的第一个TS包有PSI/SI表的一些表头信息(table_id,section_length等信息),后面的TS包就没有,所以接收某个Section必须先拿到首包。

        TS包组装section首先要找到该section的第一个TS包(下面简称为首包),首包含有该section的长度,可以用来判断一个section是不是组完了。通过判断TS包包头中的Payload Unit Start Indicator,该值为1的话,就说明这个TS包是首包,可以开始组一个section,首包的section头结构如下:

        拿到首包之后,要获取section的长度,该值就是section_length后面数据的长度,如果算上前面三个字节(包含section_length),整个section的长度就是section_length += 3。将section_length和TS包有效长度进行对比:

        1、如果section_length > TS包的有效数据,证明后面还有其他的TS包,将section_length减去TS包有效数据长度,获得剩余长度;

        2、如果是section_length <= TS包的有效数据,证明该section已经结束了。

        如果一个section还没组完,那么就要获取后续的TS包,后续的TS包应该是和原来相同PID,并且TS包头中continuity_counter要比原来的大1(31的话要变成0),拿到包后要与剩余长度进行对比,重复上面的步骤。

组多个Section和判全判重:

        对于一些PSI/SI表来说,由于数据较多,有时候不止一个section,怎么针对这个表将所有的section组全? 

        从上图可以看到首包里有个信息是last_section_number,这个字段表明了当前子表最后一个Section_number,也就是说,当前子表最多有last_section_number+1 个section(section_number从0开始),在获取同一个子表的section时,可以使用链表的形式,将多个section链接起来。

        判全和判重:每一个section的首包信息中都有一个version_number的字段,表明当前子表的version,这个字段一旦发生变化,就表明子表发生了变化,旧版本的section就要被抛弃,重新获取新版本的section,如果版本没有发生变化,那么每获取一个section,就要判断这个section的section_number是否之前获取过,我们可以建立一个标记数组,每获取一个section,就把以section_number为下标的标记数组的值置1,表明获取过该section,如果这个标记数组下标从0到last_section_number的值都为1,证明所有的section都被收全了,如果获取了一个新的section,而其标记数组值为1,证明这个section是重复的,此时应该将它丢弃。

        关于PSI/SI的实例可参考:PSI信息解析(PAT,PMT,CAT)

3.7、TS包长度说明

        TS流一般都是188格式,但偶尔会有204格式,所以在解析TS文件时,需要先判断TS文件是属于188格式还是204格式的。上面介绍的是188字节的TS包格式,在末尾加上16个字节的校验字段就是204格式。

        判断TS长度方法:        

        按照TS流标准:一个TS流文件连续5个包步进188Byte,第一个字节都是0x47同步字节开头的,那么包长188B;同理为204B。

        那么,如果文件大小 < 5*188B(约1kB)是否就不好判断了呢?

        还是有个简单方法的:直接文件的大小除以188,余数为0,则为188包;除以204,余数为0,则为204包。哪怕是几百兆的文件都应该遵循此规律,不会出现一个文件即是188也是204的倍数的。188的一个因子为47,而204的一个因子为51,故都为质数,故不可能出现都被一个文件大小整除。

        不少码流分析软件因要判断5个连续的包,小于1k的文件就不能分析的了。应该按照如下的方法:

        1、当文件< 5*188B,直接除以188或204,能整除则为188包或204包;

        2、当文件>= 5*188B,按照TS标准判断5个同步的包的长度,188或204;

        当不能判定的时候,比如188或204都不能被整除,则说明TS文件有错误,可同步后再去读取内容;连续去找到5个同步字节0x47后,再读取5个连续的包判断包长是188还是204。

四、PS格式

        参考:视频流PS打包方式详解_ps流分析-CSDN博客

4.1、PS头

        PS头是对PES的进一步封装,是将具有共同时间基准的一个或多个PES包组合而成的单一的数据流,PS流总是以0x000001BA开始,以0x000001B9结束,对于一个PS文件,有且只有一个结束码0x000001B9,不过对于网传的PS流,则应该是没有结束码的;具体PS包头:

        pack_start_code:起始码,占位32bit,标识PS包的开始,固定为0x000001BA

        ‘01’字段:占位2bit

        SCR字段:占位46bit,其中包含42bit的SCR值和4个bit的marker_bit值;其中SCR值由system_clock_reference_base和system_clock_reference_extension两部分组成,字节顺序依次是:

        system_clock_reference_base [32…30]:占位3bit

        marker_bit:占位1bit

        system_clock_reference_base [29…15]:占位15bit

        marker_bit:占位1bit

        system_clock_reference_base [14…0]:占位15bit

        marker_bit:占位1bit

        system_clock_reference_extension:占位9bit

        marker_bit:占位1bit

        program_mux_rate字段:速率值字段,占位22bit,正整数,表示P-STD接收此字段所在包的PS流的速率;这个值以每秒50字节作为单位;禁止0值;

        marker_bit:标记字段,占位1bit,固定为’1’;

        marker_bit:标记字段,占位1bit,固定为’1’;

        reserved字段:保留字段,占位5bit;

        pack_stuffing_length字段:长度字段,占位3bit;规定了此字段之后填充字段的长度;

        stuffing_byte:填充字段,固定为0xFF;长度由前一字段确定;

        接下来有两种情况:系统头(PS system header、PSM)、PES包。

4.2、PS system header封装格式

        PS system header为系统头字段,一般封装和解封装默认值即可,没多大意义,解封装时遇到系统头,我们读取系统头的头部长度,即header_length部分,然后根据系统头部的长度,跳过PS系统头,进入下一个部分,其规则如下:

        system_header_start_code字段:系统头部起始码,占位32bit,值固定为0x000001BB,标志系统首部的开始

        header_length字段:头部长度字段,占位16bit,表示此字段之后的系统首部字节长度

        marker_bit字段:占位1bit,固定值为1

        rate_bound字段:整数值,占位22bit,为一个大于或等于PS流所有PS包中的最大program_mux_rate值的整数;可以被解码器用来判断是否可以对整个流进行解码

        marker_bit字段:占位1bit,固定值为1

        audio_bound字段:占位6bit;取值范围0到32间整数;大于或等于同时进行解码处理的PS流中的音频流的最大数目;

        fixed_flag字段:标志位,占位1bit;置位1表示固定比特率操作,置位0则为可变比特率操;

        CSPS_flag字段:CSPS标志位,占位1bit;置位1表示此PS流满足标准的限制

        system_audio_lock_flag字段:标志位,占位1bit,表示音频采样率和STD的system_clock_frequency之间有一特定常数比例关系

        system_video_lock_flag字段:标志位,占位1bit,表示在系统目标解码器system_clock_frequency和视频帧速率之间存在一特定常数比例关系

        marker_bit字段:占位1bit,固定值为1

        video_bound字段:整数,占位5bit,取值范围0到16;大于或等于同时进行解码处理的PS流中的视频流的最大数目

        packet_rate_restriction_flag字段:分组速率限制标志字段,占位1bit,若CSPS_flag == 1,则此字段表示哪种限制适用于分组速率;若CSPS_flag == 0,则此字段无意义

        reserved_bits字段:保留字段,占位7bit,固定为’1111111’

        LOOP:

        当下一个bit为1时进入

                stream_id字段:占位8bit,表示其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段所涉及的流的编码和基本流的号码;若stream_id ==’1011 1000’,则其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段对应PS流中的所有音频流;若stream_id ==’1011 1001’,则其后的P-STD_buffer_bound_scale和P-STD_buffer_size_bound字段对应PS流中的所有视频流;若取其他值,则应大于’1011 1100’,且按照标准对应Stream id(详见附录1);PS流中的每个原始流都应在每个系统首部中通过这种机制精确地规定一次它的P-STD_buffer_bound_scale和P-STD_buffer_size_bound

                ‘11’字段:占位2bit

                P-STD_buffer_bound_scale字段:占位1bit,表示用来解释后面P-STD_buffer_size_bound字段的比例因子;如果之前的stream_id表示音频流,则此值应为0,若之前的stream_id表示视频流,则此值应为1,对于其他stream类型,此值可以0或1

                P-STD_buffer_size_bound字段:占位13bit,无符号整数;大于或等于所有PS流分组的P-STD输入缓冲区大小BSn的最大值;若P-STD_buffer_bound_scale == 0,则P-STD_buffer_size_bound以128字节为单位;若P-STD_buffer_bound_scale == 1,则P-STD_buffer_size_bound以1024字节为单位

4.3、 PSM封装格式

        program_stream_map(PSM)节目流映射提供了关于节目流中原始流以及它们之间相互关系的描述,紧跟在系统头部后面的,作为一个 PES 分组出现,一般出现在IDR帧之前,其封装格式如下:

        packet start code prefix字段:包头起始码,固定为0x000001,占位24bit;与后面的字段map_stream_id一起组成分组开始码,标志着分组的开始

        map_stream_id字段:类型字段,标志此分组是什么类型,占位8bit;如果此值为0xBC,则说明此PES包为PSM

        program_stream_map_length字段:长度字段,占位16bit;表示此字段之后PSM的总长度,最大值为1018(0x3FA)

        current_next_indicator字段:标识符,占位1bit;置位1表示当前PSM是可用的,置位0则表示当前PSM不可以,下一个可用

        reserved:保留字段,占位2bit

        program_stream_map_version字段:版本字段,占位5bit;表示PSM的版本号,取值范围1到32,随着PSM定义的改变循环累加;若current_next_indicator == 1,表示当前PSM的版本号,若current_next_indicator == 0,表示下一个PSM的版本号

        reserved:保留字段,占位7bit

        marker_bit:标记字段,占位1bit,固定为1

        program_stream_info_length字段:长度字段,占位16bit;表示此字段后面的descriptor字段的长度

        descriptor字段:program Stream信息描述字段,长度由前个字段确定

        elementary_stream_map_length字段:长度字段,占位16bit;表示在这个PSM中所有ES流信息的总长度;包括stream_type, elementary_stream_id, elementary_stream_info_length的长度,即 N*32bit;是不包括具体ES流描述信息descriptor的长度的

        LOOP:

                stream_type字段:类型字段,占位8bit;表示原始流ES的类型;这个类型只能标志包含在PES包中的ES流类型;值0x05是被禁止的;常见取值类型有MPEG-4 视频流:0x10;H.264 视频流:0x1B;G.711 音频流:0x90;因为PSM只有在关键帧打包的时候,才会存在,所以如果要判断PS打包的流编码类型,就根据这个字段来判断

                elementary_stream_id字段:流ID字段,占位8bit;表示此ES流所在PES分组包头中的stream_id字段的值;其中0x(C0/DF)指音频,0x(E0/EF)为视频,即通过elementary_stream_id区分出音频和视频,用stream_type区分出音频和视频的编码格式。

                elementary_stream_info_length字段:长度字段,占位16bit;表示此字段之后的,ES流描述信息的长度

                descriptor:描述信息,长度由前个字段确定;表示此类型的ES流的描述信息,这个描述信息的长度是不包含在elementary_stream_map_length字段里面的

        CRC_32:CRC字段,占位32bit,CRC校验值

 

posted @   BreakingY  阅读(20)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示