關於TS流的解析

關於TS流的解析

TS即是"TransportStream"的縮寫。他是分包發送的,每一個包長爲188字節。在TS流裏可以填入很多類型的數據,如視頻、音頻、自定義信息等。他的包的結構爲,包頭爲4個字節,負載爲184個字節(這184個字節不一定都是有效數據,有一些可能爲填充數據)。
工作形式:
因爲在TS流裏可以填入很多種東西,所以有必要有一種機制來確定怎麼來標識這些數據。制定TS流標準的機構就規定了一些數據結構來定義。比如: PSI(Program SpecificInformation)表,所以解析起來就像這樣: 先接收一個負載裏爲PAT的數據包,在整個數據包裏找到一個PMT包的ID。然後再接收一個含有PMT的數據包,在這個數據包裏找到有關填入數據類型的ID。之後就在接收到的TS包裏找含有這個ID的負載內容,這個內容就是填入的信息。根據填入的數據類型的ID的不同,在TS流複合多種信息是可行的。關鍵就是找到標識的ID號。
現在以一個例子來說明具體的操作:
在開始之前先給出一片實際TS流例子:
0000f32ch: 47 40 00 17 00 00B0 0D 00 01 C1 00 0000 01 E0 ; G@....?..?...?
0000f33ch: 20 A2 C329 41 FF FF FF FF FF FF FF FF FF FF FF ;  ⒚)A
0000f34ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f35ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f36ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f37ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f38ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f39ch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f3ach: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f3bch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f3cch: FF FF FF FF FF FFFF FF FF FF FF FF FF FF FF FF ; 
0000f3dch: FF FF FF FF FF FFFF 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 F000 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] & 0x0F;
}
這是一個調整TS流數據包頭的函數,這裏牽扯到位段調整的問題。現在看一下TS流數據包頭的結構的定義:
// Transport packet header
typedef struct TS_packet_header
{
    unsignedsync_byte                       : 8;
    unsignedtransport_error_indicator        : 1;
    unsigned payload_unit_start_indicator    : 1;
    unsignedtransport_priority               : 1;
    unsignedPID                           : 13;
    unsigned transport_scrambling_control    : 2;
    unsignedadaption_field_control           : 2;
    unsignedcontinuity_counter               : 4;
} TS_packet_header;
下面我們來分析,在ISO/IEC 13818-1裏有說明,PAT(ProgramAssociation 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
{
    unsignedtable_id                       : 8;
    unsignedsection_syntax_indicator        : 1;
    unsigned zero                           : 1;
    unsignedreserved_1                       : 2;
    unsignedsection_length                   : 12;
    unsignedtransport_stream_id           : 16;
    unsignedreserved_2                       : 2;
    unsigned version_number                   : 5;
    unsignedcurrent_next_indicator           : 1;
    unsignedsection_number                   : 8;
    unsignedlast_section_number           : 8;
    unsignedprogram_number                   : 16;
    unsignedreserved_3                       : 3;
    unsignednetwork_PID                   : 13;
    unsignedprogram_map_PID               : 13;
    unsignedCRC_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];8
    packet->section_syntax_indicator    =buffer[1] >> 7;1
   packet->zero                       = buffer[1] >> 6 & 0x1;1
   packet->reserved_1                   = buffer[1] >> 4 & 0x3; 2
   packet->section_length               = (buffer[1] & 0x0F) <<8 | buffer[2]; 12   
   packet->transport_stream_id           = buffer[3] << 8 | buffer[4]; 16
    packet->reserved_2                   = buffer[5] >> 6; 2
   packet->version_number               = buffer[5] >> 1 &  0x1F; 5
   packet->current_next_indicator        =(buffer[5] << 7) >> 7; 1
   packet->section_number               = buffer[6]; 8
    packet->last_section_number           = buffer[7]; 8
    // 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); 32
    // Parse network_PID or program_map_PID
    for ( n = 0; n < packet->section_length - 4; n ++ )
    {
       packet->program_number           = buffer[8] << 8 | buffer[9]; 16
       packet->reserved_3               = buffer[10] >> 5; 3
        if ( packet->program_number ==0x0 )
           packet->network_PID = (buffer[10] << 3) << 5 | buffer[11]; 13
        else
        {
           packet->program_map_PID = (buffer[10] << 3) << 5 | buffer[11]; 13
        }
        n += 5;
    }
}
通過上面的分析,例子中的數據00 B0 0D 00 01 C1 00 00 00 01 E0 20 A2 C329 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;
    unsignedsection_syntax_indicator        : 1;
    unsignedzero                           : 1;
    unsignedreserved_1                       : 2;
    unsignedsection_length                   : 12;
    unsigned program_number                   : 16;
    unsignedreserved_2                       : 2;
    unsignedversion_number                   : 5;
    unsignedcurrent_next_indicator           : 1;
    unsignedsection_number                   : 8;
    unsigned last_section_number           : 8;
    unsignedreserved_3                       : 3;
    unsignedPCR_PID                       : 13;
    unsignedreserved_4                       : 4;
    unsignedprogram_info_length           : 12;
   
    unsigned stream_type                   : 8;
    unsignedreserved_5                       : 3;
    unsignedelementary_PID                   : 13;
    unsignedreserved_6                       : 4;
    unsignedES_info_length                   : 12;
    unsignedCRC_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裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章