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&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f34ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f35ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f36ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f37ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f38ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f39ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3ach: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3bch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3cch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3dch: FF FF FF FF FF FF FF FF FF FF FF FF 47 40 20 17 ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;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流資料標頭的函數,這裡牽扯到位段調整的問題。
現在看一下TS流資料標頭的結構的定義:
// 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;
}
//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裡。