TS即是"Transport Stream"的縮寫。他是分包發(fā)送的,每一個包長為188字節(jié)。在TS流里可以填入很多類型的數(shù)據(jù),如視頻、音頻、自定義信息等。他的包的結(jié)構(gòu)為,包頭為4個字節(jié),負載為184個字節(jié)(這184個字節(jié)不一定都是有效數(shù)據(jù),有一些可能為填充數(shù)據(jù))。
工作形式: 因為在TS流里可以填入很多種東西,所以有必要有一種機制來確定怎么來標(biāo)識這些數(shù)據(jù)。制定TS流標(biāo)準(zhǔn)的機構(gòu)就規(guī)定了一些數(shù)據(jù)結(jié)構(gòu)來定義。比如: PSI(Program Specific Information)表,所以解析起來就像這樣: 先接收一個負載里為PAT的數(shù)據(jù)包,在整個數(shù)據(jù)包里找到一個PMT包的ID。然后再接收一個含有PMT的數(shù)據(jù)包,在這個數(shù)據(jù)包里找到有關(guān)填入數(shù)據(jù)類型的ID。之后就在接收到的TS包里找含有這個ID的負載內(nèi)容,這個內(nèi)容就是填入的信息。根據(jù)填入的數(shù)據(jù)類型的ID的不同,在TS流復(fù)合多種信息是可行的。關(guān)鍵就是找到標(biāo)識的ID號。 現(xiàn)在以一個例子來說明具體的操作: 在開始之前先給出一片實際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; } 這是一個調(diào)整TS流數(shù)據(jù)包頭的函數(shù),這里牽扯到位段調(diào)整的問題。現(xiàn)在看一下TS流數(shù)據(jù)包頭的結(jié)構(gòu)的定義: // 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包的標(biāo)識(即sync_byte)為0x47,并且為了確保這個TS包里的數(shù)據(jù)有效,所以我們一開始查找47 40 00這三組16進制數(shù),為什么這樣?具體的奧秘在TS包的結(jié)構(gòu)上,前面已經(jīng)說了sync_byte固定為0x47?,F(xiàn)在往下看transport_error_indicator、payload_unit_start_indicator、transport_priority和PID這四個元素,PID為0x00,這是PAT的標(biāo)識。transport_error_indicator為0,transport_priority為0。把他們看成是兩組8位16進制數(shù)就是:40 00?,F(xiàn)在看看我們的TS流片斷例子,看來正好是47 40 00開頭的,一個TS流的頭部占據(jù)了4個字節(jié)。剩下的負載部分的內(nèi)容由PID來決定,例子看來就是一個PAT表。在這里有個地方需要注意一下,payload_unit_start_indicator為1時,在前4個字節(jié)之后會有一個調(diào)整字節(jié),它的數(shù)值決定了負載內(nèi)容的具體開始位置。現(xiàn)在看例子中的數(shù)據(jù)47 40 00 17 00第五個字節(jié)是00,說明緊跟著00之后就是具體的負載內(nèi)容。 下面給出PAT表的結(jié)構(gòu)體: // 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表字段調(diào)整函數(shù): // 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; } } 通過上面的分析,例子中的數(shù)據(jù)00 B0 0D 00 01 C1 00 00 00 01 E0 20 A2 C3 29 41就是具體的PAT表的內(nèi)容,然后根據(jù)PAT結(jié)構(gòu)體來具體分析PAT表。但是我們需要注意的是在PAT表里有program_number、network_PID的元素不只有一個,這兩個元素是通過循環(huán)來確定的。循環(huán)的次數(shù)通過section_length元素的確定。在這個例子中program_map_PID為20,所以下面來PMT分析時,就是查找47 40 20的開頭的TS包。 下面來分析PMT表,先給出PMT(Program Map Table)的結(jié)構(gòu)體: // 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; 在給出調(diào)整字段函數(shù): // 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流可以復(fù)合很多的節(jié)目的視頻和音頻,但是解碼器是怎么來區(qū)分的呢?答案就在PMT表里,如其名節(jié)目映射表。他就是來解決這個問題的。現(xiàn)在看PMT結(jié)構(gòu)體里的stream_type、elementary_PID這兩個元素,前一個用來確定后一個作為標(biāo)識PID的內(nèi)容具體是什么,音頻或視頻等。還有要注意他們不只有一個,所以他們是通過循環(huán)讀取來確保所有的值都被讀取了,當(dāng)然循環(huán)也是有規(guī)定的(具體看調(diào)整函數(shù)上)。從例子上來看,我們在倒數(shù)第三行找到了上面分析來的PMT表的PID為0x20的TS包。然后就可以把數(shù)據(jù)是用調(diào)整函數(shù)填入結(jié)構(gòu)中。然后得到具體節(jié)目的PID為視頻0x21, 音頻0x22。 PS. 文章里的PID是用來判斷具體TS包是什么包的。分析每個包得到的PID值,都可以復(fù)合在TS頭部結(jié)構(gòu)體的PID里。 |
|