關於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裏。

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