关于TS的解析

MPEG组织于1994年推出MPEG-2压缩标准,以实现视/音频服务与应用互操作的可能性,MPEG-2标准是针对标准数字电视和高清晰度电视在各种应用下的压缩方案和系统层的详细规定。对应于不同的应用,符合MPEG-2标准的码流又分为传送流和程序流,本文主要讲解了传送流有关的部分数据结构,从实际应用的传送流码流中截取了部分码流做了说明,并给出了部分解析传送流码流的实例程序。
在MPEG-II标准中,为了将一个或更多的音频、视频或其他的基本数据流合成单个或多个数据流,以适应于存储和传送,必须对其重新进行打包编码,在码流中还需插入各种时间标记、系统控制等信息,最后送到信道编码与调制器。这样可以形成两种数据流——传送流(TS)和程序流(PS),分别适用于不同的应用,图1给出了单路节目的视音频数据流的复用框图。
     传送流(Transport Stream)简称TS流,它是根据ITU-T Rec.H.222.0|ISO/IEC 13818-2 和ISO/IEC 13818-3协议而定义的一种数据流,其目的是为了在有可能发生严重错误的情况下进行一道或多道程序编码数据的传送和存储。这种错误表现为比特值错误或分组丢失。传送流由一道或多道节目组成,每道节目由一个或多个原始流和一些其他流复合在一起,包括视频流、音频流、节目特殊信息流(PSI)和其他数据包。其中PSI表有4种类型:节目关联表(PAT)、节目映射表(PMT)、网络信息表和条件访问表。传送流应用比较广泛,如视音频资料的保存、电视节目的非线性编辑系统及其网络等。在开发机顶盒以及视频设备时有时需要对码流的编码知识有比较清楚地了解,这样才能在遇到问题时做出全面的 分析。
TS流结构分析
     如图2所示,TS包的长度是固定的,为188字节。包括同步字节(sync_byte)0x47和数据包识别号PID等。PID为13位字段,指示存储于分组有效负载中数据的类型,PID值0x0000为程序关联表保留,而0x0001为条件访问表保留,0x1FFF为空分组保留。从PID可以判断其后面负载的数据类型是视频流、音频流、PSI还是其他数据包。
PSI描述说明
     在MPEG-II中定义了节目特定信息(PSI),PSI用来描述传送流的组成结构,在MPEG-II系统中担任极其重要的角色,在多路复用中尤为重要的是PAT表和PMT表。PAT表给出了一路MPEG-II码流中有多少套节目,以及它与PMT表PID之间的对应关系;PMT表给出了一套节目的具体组成情况与其视频、音频等PID对应关系。PSI提供了使接收机能够自动配置的信息,用于对复用流中的不同节目流进行解复用和解码。PSI信息由以下几种类型表组成:
◆ 节目关联表(PAT Program Association Table)
     PAT表用MPEG指定的PID(00)标明,通常用PID=0表示。它的主要作用是针对复用的每一路传输流,提供传输流中包含哪些节目、节目的编号以及对应节目的节目映射表(PMT)的位置,即PMT的TS包的包标识符(PID)的值,同时还提供网络信息表(NIT)的位置,即NIT的TS包的包标识符(PID)的值。
◆ 条件接收表(CAT Conditional Access Table)
     CAT表用MPEG指定的PID(01)标明,通常用PID=1表示。它提供了在复用流中条件接收系统的有关信息,指定CA系统与它们相应的授权管理信息(EMM))之间的联系,指定EMM的PID,以及相关的参数。
◆ 节目映射表(PMT Program Map Table)
     节目映射表指明该节目包含的内容,即该节目由哪些流组成,这些流的类型(音频、视频、数据),以及组成该节目的流的位置,即对应的TS包的PID值,每路节目的节目时钟参考(PCR)字段的位置。
◆ 网络信息表(NIT Nerwork Information Table)
     网络信息表提供关于多组传输流和传输网络相关的信息,其中包含传输流描述符、通道频率、卫星发射器号码、调制特性等信息。
◆ 传输流描述表(TSDT Transport Stream Description Table)
    传输流描述表由PID为2的TS包传送,提供传输流的一些主要参数。
◆ 专用段(private_section)
     MPEG-2还定义了一种专用段用于传送用户自己定义的专用数据。
◆ 描述符(Descripter)
     除了上述的表述之外,MPEG-2还定义了许多描述符,这些描述符提供关于视频流、音频流、语言、层次、系统时钟、码率等多方面的信息,在PSI的表中可以灵活的采用这些描述符进一步为接收机提供更多的信息。
     在解码时,接收机首先根据PID值找到PAT表,找出相应节目的PMT表的PID,再由该PID找到该PMT表,再在PMT表中找到相应的码流,然后开始解码。PSI结构和TS流的关系示例如图3所示。
PES包格式说明
     经过视音频压缩来的数码流称为ES流,ES流经过打包器输出PES流。PES包是非定长的,音频PES包不超过64K字节,视频一般一帧一个PES包。为实现解码的同步,还需插入相关的标志信息,多个打包后的数码流再经过复用器成为传送流(TS流),PES包的结构图如图4所示。
对截取的MPEG-II TS码流实例分析
对截取的包含PSI信息的码流分析
     从MPEG-II TS流片源上截取的包含PSI信息的码流如图5所示。
     搜索TS数据流从包同步字0x47开始,由于该同步头字节并不是唯一的,数据包中可能有码字也恰为其值。因此,要准确检测同步,必须首先找到输入缓冲区中第一个0x47,然后将其指针向后推187个字节的位置再检测是否为0x47,如果是,则输出包同步信号;接着每隔187字节检测一次,如是0x47,则继续输出包同步信号,如不是,则重新开始搜索0x47。
     在上段截取的包含PSI信息的码流中是一个TS文件中的起始数据截图,TS流文件中的数据Ts包头以0x47开头,在0x47后3字节是ts包头信息:0x40中的4表示此payload_unit_start_indicator是1,表示包含TS流分组的第一个有效字节包含point_field字段,占一个字节;ID为0x00,表示此TS流包含PSI信息,在此是PAT包;0x1c中的1表示仅不含附加信息,仅含有有效载荷;C表示continuity_ counter=c。在4字节的TS包头之后时一字节的point_field=0,然后是program_association_ section()字段的信息。Table_id=0x00,表示在此PSI内容是program_ association_section()字段的信息内容;0xB0表示的B是同步头以及保留位,0是section_length的一部分;section_length=0x00d(包含0x0D的前一个0);transport_ stream_ id=0x0000;0xC1包含保留位、vision_number、current_ next_indicator ,C中的11是保留位,C中的00和低4位中的前3位是vision_number部分,最低位是current_next_indicator部分。
     vision_number=0x00;current_next_indicator=1,表示所 发送的pat表当前有效;ection_number=0x00,last_ section_number=0x0000;program_number=0x0001;0xE0中的E的高三位是保留位,E的最低位和其后的4位0以及其后的8位都是program_map_PID字段内容,所以program_map_PID=0x032,即PMT的PID为0x32;CRC是0xbcf11595。其后的0xff为填充字节。
     在第二个TS流中,在0x47后3字节是ts包头信息:0x40中的4表示此payload_unit_start_indicator是1,表示包含TS流分组的第一个有效字节包含point_field字段,占一个字节。ID为0x32,表示此TS流包含PSI信息PID为0x032,是pat表中的PID,说明此TS流中包含PMT表的信息。point_field=0x00,(Ts头和point_field1字节共5字节)point_field后是0x02表示此处的pid为ts_program_ map_section()字段。0xB0表示的B是同步头以及保留位,0是section_length的一部部分Section_length=0x02d,规定此字段的字节数,包含CRC部分;program_number=0x0001;0xC1包含保留位、vision_number、current_next_indicator,C中的11是保留位,C中的00和低4位中的前3位是vision_number部分,最低位是current_next_indicator部分;version_number=0x0,current_next_indicator=1,section_number=0x00,last_section_number=0x00;0xE0中的E的高三位是保留位,E的最低位和其后的4位0以及其后的8位都是PCR_PID字段内容,PCR_PID=0x020;其后的0xF0中的高4位是保留位,低4位和其后的8位都是program_info_length字段内容,表示描述字段的字节数,program_info_length=0x000;stream_type=0x03表示其后的PID为ISO/IEC 11172音频数据的PID,其后的0xE0中的E的高三位是保留位,E的最低位和其后的4位0以及其后的8位都是element_PID字段内容,element_PID=0x021,表示TS流中如果包含音频部分,则此TS流的PID为0x21;其后的0xF0中的高4位是保留位,低4位和其后的8位都是ES_info_length字段内容,表示描述字段的字节数,ES_info_length=0x006,表示其后的6个字节为原始流的描述部分。6个字节之后的stream_type=0x02,表示其后的PID为ITU-T Rec. H.262 | ISO/IEC 13818-2视频数据的PID;其后的0xE0中的E的高三位是保留位,E的最低位和其后的4位0以及其后的8位都是element_PID字段内容,element_PID=0x20,表示TS流中如果包含视频部分,则此TS流的PID为0x20, 其后的0xF0中的高4位是保留位,低4位和其后的8位都是ES_info_length字段内容,表示描述字段的字节数,ES_info_length=0x10,表示其后的16个字节为原始流的描述部分。其后的4字节是CRC校验部分,然后是填充部分。
一般MPEG-II TS码流分析
     从MPEG-II TS流片源上截取的码流如图6所示。
     如上述截取的码流所示:Ts包头以0x47开头,在0x47后3字节是TS包头信息:ID为视频ID,是0x20,field_point字段00,其后是PES包包头:00 01 E0表示是视频PES包包头,其后的帧有关信息共5字节,2字节PES包长度是27 6A,表示此PES数据包的长度是0x276a即10090字节;2字节标准位信息是85 80,5字节中的最后一字节表示附加数据长度是0B,其后是正式视频数据:开始为00 00 01 00,是I、B、P帧的判别在9D,二进制为10 011 101中的中间3位011指名帧帧是什么样的帧,次例中为011即为B帧,当是001时为I帧,010时为P帧。
     从某种意义上来说,数字电视的发展主要取决于数字压缩技术和数字调制解调技术的发展。目前MPEG-2标准较为成熟,虽然码率压缩的办法许多,如MPEG系列(MPEG-1、MPEG-2、MPEG-4、MPEG-7)以及小波压缩技术等。但在实用化方面MPEG-2已经走在了前面,且遵循标准研制出的系统产品也已非常成熟,实际商业运营和效果也非常成功。
     总之,MPEG-II传输流在数字电视系统中得到了广泛的应用,在应用中可以通过分析码流来获得传输流的PID、PRC等信息,用来设置解码器等用处。

 

TS即是"Transport Stream"的缩写。他是分包发送的,每一个包长为188字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。他的包的结构为,包头为4个字节,负载为184个字节(这184个字节不一定都是有效数据,有一些可能为填充数据)。

工作形式:

因为在TS流里可以填入很多种东西,所以有必要有一种机制来确定怎么来标识这些数据。制定TS流标准的机构就规定了一些数据结构来定义。比如: PSI(Program Specific Information)表,所以解析起来就像这样: 先接收一个负载里为PAT的数据包,在整个数据包里找到一个PMT包的ID。然后再接收一个含有PMT的数据包,在这个数据包里找到有关填入数据类型的ID。之后就在接收到的TS包里找含有这个ID的负载内容,这个内容就是填入的信息。根据填入的数据类型的ID的不同,在TS流复合多种信息是可行的。关键就是找到标识的ID号。

现在以一个例子来说明具体的操作:

在开始之前先给出一片实际TS流例子:

0000f32ch: 47 40 00 17 00 00 B0 0D 00 01 C1 00 00 00 01 E0 ; G@....?..?...?

0000f33ch: 20 A2 C3 29 41 FF FF FF FF FF FF FF FF FF FF FF ;  ⒚)A???????????

0000f34ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f35ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f36ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f37ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f38ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f39ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f3ach: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f3bch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f3cch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; ????????????????

0000f3dch: FF FF FF FF FF FF FF FF FF FF FF FF 47 40 20 17 ; ????????????G@ .

0000f3ech: 00 02 B0 1B 00 01 C1 00 00 E0 21 F0 00 1B E0 21 ; ..?..?.??.?

0000f3fch: F0 04 2A 02 7E 1F 03 E0 22 F0 00 5D 16 BD 48    ; ?*.~..??].紿

具体的分析就以这个例子来分析。

// Adjust TS packet header

void adjust_TS_packet_header(TS_packet_header* pheader)

{

    unsigned char buf[4];

    memcpy(buf, pheader, 4);

    pheader->transport_error_indicator        = buf[1] >> 7;

    pheader->payload_unit_start_indicator    = buf[1] >> 6 & 0x01;

    pheader->transport_priority                = buf[1] >> 5 & 0x01;

    pheader->PID                            = (buf[1] & 0x1F) << 8 | buf[2];

    pheader->transport_scrambling_control    = buf[3] >> 6;

    pheader->adaption_field_control            = buf[3] >> 4 & 0x03;

    pheader->continuity_counter                = buf[3] & 0x03;

}

这是一个调整TS流数据包头的函数,这里牵扯到位段调整的问题。现在看一下TS流数据包头的结构的定义:

// Transport packet header

typedef struct TS_packet_header

{

    unsigned sync_byte                        : 8;

    unsigned transport_error_indicator        : 1;

    unsigned payload_unit_start_indicator    : 1;

    unsigned transport_priority                : 1;

    unsigned PID                            : 13;

    unsigned transport_scrambling_control    : 2;

    unsigned adaption_field_control            : 2;

    unsigned continuity_counter                : 4;

} TS_packet_header;

下面我们来分析,在ISO/IEC 13818-1里有说明,PAT(Program Association Table)的PID值为0x00,TS包的标识(即sync_byte)为0x47,并且为了确保这个TS包里的数据有效,所以我们一开始查找47 40 00这三组16进制数,为什么这样?具体的奥秘在TS包的结构上,前面已经说了sync_byte固定为0x47。现在往下看transport_error_indicator、payload_unit_start_indicator、transport_priority和PID这四个元素,PID为0x00,这是PAT的标识。transport_error_indicator为0,transport_priority为0。把他们看成是两组8位16进制数就是:40 00。现在看看我们的TS流片断例子,看来正好是47 40 00开头的,一个TS流的头部占据了4个字节。剩下的负载部分的内容由PID来决定,例子看来就是一个PAT表。在这里有个地方需要注意一下,payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。现在看例子中的数据47 40 00 17 00第五个字节是00,说明紧跟着00之后就是具体的负载内容。

下面给出PAT表的结构体:

// PAT table

// Programm Association Table

typedef struct TS_PAT

{

    unsigned table_id                        : 8;

    unsigned section_syntax_indicator        : 1;

    unsigned zero                            : 1;

    unsigned reserved_1                        : 2;

    unsigned section_length                    : 12;

    unsigned transport_stream_id            : 16;

    unsigned reserved_2                        : 2;

    unsigned version_number                    : 5;

    unsigned current_next_indicator            : 1;

    unsigned section_number                    : 8;

    unsigned last_section_number            : 8;

    unsigned program_number                    : 16;

    unsigned reserved_3                        : 3;

    unsigned network_PID                    : 13;

    unsigned program_map_PID                : 13;

    unsigned CRC_32                            : 32;

} TS_PAT;

再给出PAT表字段调整函数:

// Adjust PAT table

void adjust_PAT_table ( TS_PAT * packet, char * buffer )

{

    int n = 0, i = 0;

    int len = 0;

    packet->table_id                    = buffer[0];

    packet->section_syntax_indicator    = buffer[1] >> 7;

    packet->zero                        = buffer[1] >> 6 & 0x1;

    packet->reserved_1                    = buffer[1] >> 4 & 0x3;

    packet->section_length                = (buffer[1] & 0x0F) << 8 | buffer[2];   

    packet->transport_stream_id            = buffer[3] << 8 | buffer[4];

    packet->reserved_2                    = buffer[5] >> 6;

    packet->version_number                = buffer[5] >> 1 &  0x1F;

    packet->current_next_indicator        = (buffer[5] << 7) >> 7;

    packet->section_number                = buffer[6];

    packet->last_section_number            = buffer[7];

    // Get CRC_32

    len = 3 + packet->section_length;

    packet->CRC_32                        = (buffer[len-4] & 0x000000FF) << 24

                                          | (buffer[len-3] & 0x000000FF) << 16

                                          | (buffer[len-2] & 0x000000FF) << 8

                                          | (buffer[len-1] & 0x000000FF);

    // Parse network_PID or program_map_PID

    for ( n = 0; n < packet->section_length - 4; n ++ )

    {

        packet->program_number            = buffer[8] << 8 | buffer[9];

        packet->reserved_3                = buffer[10] >> 5;

        if ( packet->program_number == 0x0 )

            packet->network_PID = (buffer[10] << 3) << 5 | buffer[11];

        else

        {

            packet->program_map_PID = (buffer[10] << 3) << 5 | buffer[11];

        }

        n += 5;

    }

}

通过上面的分析,例子中的数据00 B0 0D 00 01 C1 00 00 00 01 E0 20 A2 C3 29 41就是具体的PAT表的内容,然后根据PAT结构体来具体分析PAT表。但是我们需要注意的是在PAT表里有program_number、network_PID的元素不只有一个,这两个元素是通过循环来确定的。循环的次数通过section_length元素的确定。在这个例子中program_map_PID为20,所以下面来PMT分析时,就是查找47 40 20的开头的TS包。

下面来分析PMT表,先给出PMT(Program Map Table)的结构体:

// PMT table

// Program Map Table

typedef struct TS_PMT

{

    unsigned table_id                        : 8;

    unsigned section_syntax_indicator        : 1;

    unsigned zero                            : 1;

    unsigned reserved_1                        : 2;

    unsigned section_length                    : 12;

    unsigned program_number                    : 16;

    unsigned reserved_2                        : 2;

    unsigned version_number                    : 5;

    unsigned current_next_indicator            : 1;

    unsigned section_number                    : 8;

    unsigned last_section_number            : 8;

    unsigned reserved_3                        : 3;

    unsigned PCR_PID                        : 13;

    unsigned reserved_4                        : 4;

    unsigned program_info_length            : 12;

   

    unsigned stream_type                    : 8;

    unsigned reserved_5                        : 3;

    unsigned elementary_PID                    : 13;

    unsigned reserved_6                        : 4;

    unsigned ES_info_length                    : 12;

    unsigned CRC_32                            : 32;

} TS_PMT;

在给出调整字段函数:

// Adjust PMT table

void adjust_PMT_table ( TS_PMT * packet, char * buffer )

{

    int pos = 12, len = 0;

    int i = 0;

    packet->table_id                            = buffer[0];

    packet->section_syntax_indicator            = buffer[1] >> 7;

    packet->zero                                = buffer[1] >> 6;

    packet->reserved_1                            = buffer[1] >> 4;

    packet->section_length                        = (buffer[1] & 0x0F) << 8 | buffer[2];   

    packet->program_number                        = buffer[3] << 8 | buffer[4];

    packet->reserved_2                            = buffer[5] >> 6;

    packet->version_number                        = buffer[5] >> 1 & 0x1F;

    packet->current_next_indicator                = (buffer[5] << 7) >> 7;

    packet->section_number                        = buffer[6];

    packet->last_section_number                    = buffer[7];

    packet->reserved_3                            = buffer[8] >> 5;

    packet->PCR_PID                                = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;

    packet->reserved_4                            = buffer[10] >> 4;

    packet->program_info_length                    = (buffer[10] & 0x0F) << 8 | buffer[11];

    // Get CRC_32

    len = packet->section_length + 3;   

    packet->CRC_32                = (buffer[len-4] & 0x000000FF) << 24

                                  | (buffer[len-3] & 0x000000FF) << 16

                                  | (buffer[len-2] & 0x000000FF) << 8

                                  | (buffer[len-1] & 0x000000FF);

    // program info descriptor

    if ( packet->program_info_length != 0 )

        pos += packet->program_info_length;   

    // Get stream type and PID   

    for ( ; pos <= (packet->section_length + 2 ) -  4; )

    {

        packet->stream_type                            = buffer[pos];

        packet->reserved_5                            = buffer[pos+1] >> 5;

        packet->elementary_PID                        = ((buffer[pos+1] << 8) | buffer[pos+2]) & 0x1FFF;

        packet->reserved_6                            = buffer[pos+3] >> 4;

        packet->ES_info_length                        = (buffer[pos+3] & 0x0F) << 8 | buffer[pos+4];

        // Store in es

        es[i].type = packet->stream_type;

        es[i].pid = packet->elementary_PID;

        if ( packet->ES_info_length != 0 )

        {

            pos = pos+5;

            pos += packet->ES_info_length;

        }

        else

        {

            pos += 5;

        }

        i++;

    }

}

TS流可以复合很多的节目的视频和音频,但是解码器是怎么来区分的呢?答案就在PMT表里,如其名节目映射表。他就是来解决这个问题的。现在看PMT结构体里的stream_type、elementary_PID这两个元素,前一个用来确定后一个作为标识PID的内容具体是什么,音频或视频等。还有要注意他们不只有一个,所以他们是通过循环读取来确保所有的值都被读取了,当然循环也是有规定的(具体看调整函数上)。从例子上来看,我们在倒数第三行找到了上面分析来的PMT表的PID为0x20的TS包。然后就可以把数据是用调整函数填入结构中。然后得到具体节目的PID为视频0x21, 音频0x22。

PS. 文章里的PID是用来判断具体TS包是什么包的。分析每个包得到的PID值,都可以复合在TS头部结构体的PID里。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值