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    ; ?*.~..?

?

].紿


详细的分析就以这个样例来分析。



左右TS分析流_16进制


// 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分析流_16进制


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



左右TS分析流_16进制


// 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;


左右TS分析流_16进制


以下我们来分析,在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表的结构体:



左右TS分析流_16进制


// 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;


左右TS分析流_16进制


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



左右TS分析流_16进制


// 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;

    }

}


左右TS分析流_16进制


通过上面的分析,样例中的数据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)的结构体:



左右TS分析流_16进制


// 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;


左右TS分析流_16进制


在给出调整字段函数:



左右TS分析流_16进制


// 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分析流_16进制


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

然后就能够把数据是用调整函数填入结构中。然后得到详细节目的PID为视频0x21, 音频0x22。