1.TS格式介绍
TS:全称为MPEG2-TS。TS即"Transport Stream"的缩写。它是分包发送的,每一个包长为188字节(还有192和204个字节的包)。包的结构为,包头为4个字节(第一个字节为0x47),负载为184个字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。MPEG2-TS主要应用于实时传送的节目,比如实时广播的电视节目。MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。简单地说,将DVD上的VOB文件的前面一截cut掉(或者是数据损坏数据)就会导致整个文件无法解码,而电视节目是任何时候打开电视机都能解码(收看)的。
TS解析需要参考:ISO/IEC 13818-1的2.4 Transport Stream bitstream requirements
TS的包结构:
包头信息说明
Packet Header(包头)信息说明 | |||
1 | sync_byte | 8bits | 同步字节 |
2 | transport_error_indicator | 1bit | 错误指示信息(1:该包至少有1bits传输错误) |
3 | payload_unit_start_indicator | 1bit | 负载单元开始标志(packet不满188字节时需填充) |
4 | transport_priority | 1bit | 传输优先级标志(1:优先级高) |
5 | PID | 13bits | Packet ID号码,唯一的号码对应不同的包 |
6 | transport_scrambling_control | 2bits | 加密标志(00:未加密;其他表示已加密) |
7 | adaptation_field_control | 2bits | 附加区域控制 |
8 | continuity_counter | 4bits | 包递增计数器 |
2.TS流包含的内容
BAT解析例举:
TS-包1:
这个包比较特别,为什么说它是第一个包呢,因为 continuity_counter=0 & payload_unit_start_indicator=1。
也就是说任何SCTION段过滤器都开始于continuity_counter=0的TS包,continuity_counter有4BIT 1-15这样计算的的话,SECTION最大为184X16=2944 Byte。
那么SECTION究竟有多大的,这要取决于要解析的SECTION语法,取出有效载荷的前3 Byte也就知道了SCTION的长度,计算公式通常为:
secLen = ((uint16_t)(buf[1] & 0xf)) << 8 | buf[2]) buf为有效载荷的起始位置。详见BAT表语法结构。
概述如下:
1、包递增器为0,表示为第一个SCTION的开始,包递增器为0的TS包payload_unit_start_indicator=1,其它情况下payload_unit_start_indicator=0。
2、有效载荷的具体位置取决于 包头4+附加区域长度+负载单元起始位置。
3、每个SCTION长度为包含在有效载荷的前3Byte。
解复用器中通常使用payload_unit_start_indicator=1作为判断新包的开始。
47 40 11 10 表示无调整字段,有负载单元起始标志,那么有效载荷的起始位置为data[5]以后,有效载荷长度为183,有效载荷的前3Byte为 4a f2 ed,换算出当前的SCTION长度为secLen=749 Byte。
TS-包2
47 00 11 11 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。
TS-包3:
47 00 11 12 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。
TS-包4
47 00 11 13 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。
TS-包5
47 00 11 14 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为17。
这是当前SCTION的最后一个TS包,那么怎么判断是最后一个包呢?
首先我们解析了第一个TS包时得出secLen=749,随着包递增器的增加,我们陆续获取有效载荷,749-183-184-184-184=17,所以当解析到第5个TS包时,有效载荷为17 Bype,BAT完成数据的获取工作,BAT的语法解析如下:
========================================================================================================================
一段TS流,必须包含PAT包、PMT包、多个音频包、多个视频包、多个PCR包、以及其他信息包。
解析TS流数据的流程:查找PID为0x0的包,解析PAT,PAT包中的program_map_PID表示PMT的PID;查找PMT,PMT包中的elementary_PID表示音视频包的PID,PMT包中的PCR_PID表示PCR的PID,有的时候PCR的PID跟音频或者视频的PID相同,说明PCR会融进音视频的包,注意解析,有的时候PCR是自己单独的包;CAT、NIT、SDT、EIT的PID分别为: 0x01、0x10、0x11、0x12。
下面我们来分析,在ISO/IEC 13818-1里有说明,BAT的PID值为0x11,TS包的标识(即sync_byte)为0x47,并且为了确保这个TS包里的数据有效,所以我们一开始查找47 40 11这三组16进制数,为什么这样?具体的奥秘在TS包的结构上,前面已经说了sync_byte固定为0x47。现在往下看transport_error_indicator、payload_unit_start_indicator、transport_priority和PID这四个元素,PID为0x11,这是BAT的标识。transport_error_indicator为0,transport_priority为0。把他们看成是两组8位16进制数就是:40 11。现在看看我们的TS流片断例子,看来正好是47 40 11开头的,一个TS流的头部占据了4个字节。剩下的负载部分的内容由PID来决定,例子看来就是一个BAT表。在这里有个地方需要注意一下,payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。现在看例子中的数据47 40 11 10 00第五个字节是00,说明紧跟着00之后就是具体的负载内容,也就是说4a f2 ed后面是载荷。
到现在为止,引申出TS包有效载荷的定位问题,有效载荷定位要注意两点:
1、自适应区的大小
2、“payload_unit_start_indicator” 有效载荷的起始区域
我们以“tsBuf[]”为例,“tsBuf[]”存储了一帧TS包,tsBuf[0]=0x47,TS的同步头,那么tsBuf[4]为自适应区域的长度。
transport_packet(){
sync_byte
...
adaptation_field_control bslbf
continuity_counter uimsbf
if(adaptation_field_control == '' || adaptation_field_control == ''){
adaptation_field()
}
...
}
tsBuf[4]=adaptation_field_length
adaptation_field(){
adaptation_field_length uimsbf
if(adaptation_field_length>){
...
PCR_flag bslbf
...
if(PCR_flag == ''){
program_clock_reference_base uimsbf
Reserved bslbf
program_clock_reference_extension uimsbf
...
}
}
}
关于自适应区的ISO/IEC 13818-1语法如下:
3.TS包头解析
TS包头有4个字节
//Transport Stream header typedef struct TS_header { unsigned sync_byte :8; //同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47的 unsigned transport_error_indicator :1; //传输错误标志位,一般传输错误的话就不会处理这个包了 unsigned payload_unit_start_indicator :1; //有效负载的开始标志,根据后面有效负载的内容不同功能也不同 // payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。 unsigned transport_priority :1; //传输优先级位,1表示高优先级 unsigned PID :13; //有效负载数据的类型 unsigned transport_scrambling_control :2; //加密标志位,00表示未加密 unsigned adaption_field_control :2; //调整字段控制,。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。 unsigned continuity_counter :4; //一个4bit的计数器,范围0-15 } TS_header; //特殊参数说明: //sync_byte:0x47 //payload_unit_start_indicator:0x01表示含有PSI或者PES头 //PID:0x0表示后面负载内容为PAT,不同的PID表示不同的负载 //adaption_field_control: // 0x0: // reserved for future use by ISO/IEC // 0x1: // 无调整字段,仅含有效负载 // 0x2: // 仅含调整字段,无有效负载 // 0x3: // 调整字段后含有效负载 // Parse TS header int Parse_TS_header(unsigned char *pTSBuf, TS_header *pheader) { pheader->sync_byte = pTSBuf[0]; if (pheader->sync_byte != 0x47) return -1; pheader->transport_error_indicator = pTSBuf[1] >> 7; pheader->payload_unit_start_indicator = pTSBuf[1] >> 6 & 0x01; pheader->transport_priority = pTSBuf[1] >> 5 & 0x01; pheader->PID = (pTSBuf[1] & 0x1F) << 8 | pTSBuf[2]; pheader->transport_scrambling_control = pTSBuf[3] >> 6; pheader->adaption_field_control = pTSBuf[3] >> 4 & 0x03; pheader->continuity_counter = pTSBuf[3] & 0x0F; return 0; } |
TS包头解析需要参考:ISO/IEC 13818-1的2.4.3.2 Transport Stream packet layer
4.TS负载格式解析
4.1 PAT解析
TS_header包头中的PID值为0x0,表示当前负载为PAT(Program Association Table)。PAT数据的信息可以理解为整个TS流包含的节目信息。
// Program Association Table typedef struct PAT_Packet_tag { unsigned table_id : 8; //固定为0x00 ,标志是该表是PAT unsigned section_syntax_indicator : 1; //段语法标志位,固定为1 unsigned zero : 1; //0 unsigned reserved_1 : 2; // 保留位 unsigned section_length : 12; //表示这个字节后面有用的字节数,包括CRC32 unsigned transport_stream_id : 16; //该传输流的ID,区别于一个网络中其它多路复用的流 unsigned reserved_2 : 2; // 保留位 unsigned version_number : 5; //范围0-31,表示PAT的版本号 unsigned current_next_indicator : 1; //发送的PAT是当前有效还是下一个PAT有效 unsigned section_number : 8; //分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段 unsigned last_section_number : 8; //最后一个分段的号码 // for(i=0; i<N; i++) // { unsigned program_number : 16; unsigned reserved_3 : 3; unsigned network_PID : 16; // 或者program_map_PID unsigned CRC_32 : 32; // } } PAT_Packet; // Parse PAT int Parse_PAT(unsigned char *pTSBuf, PAT_Packet *packet) { TS_header TSheader; if (Parse_TS_packet_header(pTSBuf, &TSheader) != 0) return -1; if (TSheader.payload_unit_start_indicator == 0x01) // 表示含有PSI或者PES头 { if (TSheader.PID == 0x0) // 表示PAT { int iBeginlen = 4; int adaptation_field_length = pTSBuf[4]; switch (TSheader.adaption_field_control) { case 0x0: // reserved for future use by ISO/IEC return -1; case 0x1: // 无调整字段,仅含有效负载 iBeginlen += pTSBuf[iBeginlen] + 1; // + pointer_field break ; case 0x2: // 仅含调整字段,无有效负载 return -1; case 0x3: // 调整字段后含有效负载 if (adaptation_field_length > 0) { iBeginlen += 1; // adaptation_field_length占8位 iBeginlen += adaptation_field_length; // + adaptation_field_length } else { iBeginlen += 1; // adaptation_field_length占8位 } iBeginlen += pTSBuf[iBeginlen] + 1; // + pointer_field break ; default : break ; } unsigned char *pPAT = pTSBuf + iBeginlen; packet->table_id = pTSBuf[0]; packet->section_syntax_indicator = pTSBuf[1] >> 7; packet->zero = pTSBuf[1] >> 6 & 0x1; packet->reserved_1 = pTSBuf[1] >> 4 & 0x3; packet->section_length = (pTSBuf[1] & 0x0F) << 8 | pTSBuf[2]; packet->transport_stream_id = pTSBuf[3] << 8 | pTSBuf[4]; packet->reserved_2 = pTSBuf[5] >> 6; packet->version_number = pTSBuf[5] >> 1 & 0x1F; packet->current_next_indicator = (pTSBuf[5] << 7) >> 7; packet->section_number = pTSBuf[6]; packet->last_section_number = pTSBuf[7]; int len = 0; len = 3 + packet->section_length; packet->CRC_32 = (pTSBuf[len-4] & 0x000000FF) << 24 | (pTSBuf[len-3] & 0x000000FF) << 16 | (pTSBuf[len-2] & 0x000000FF) << 8 | (pTSBuf[len-1] & 0x000000FF); int n = 0; for ( n = 0; n < (packet->section_length - 12); n += 4 ) { packet->program_number = pTSBuf[8 + n ] << 8 | pTSBuf[9 + n ]; packet->reserved_3 = pTSBuf[10 + n ] >> 5; if ( packet->program_number == 0x00) { packet->network_PID = (pTSBuf[10 + n ] & 0x1F) << 8 | pTSBuf[11 + n ]; } else { // 有效的PMT的PID,然后通过这个PID值去查找PMT包 program_map_PID = (pTSBuf[10 + n] & 0x1F) << 8 | pTSBuf[11 + n]; } } return 0; } } return -1; } |
PAT数据解析需要参考:ISO/IEC 13818-1的2.4.4.3 Program Association Table
4.2 PMT解析
由PAT包中的program_map_PID可以确定PMT(Program Map Table)的PID。PMT数据的信息可以理解为这个节目包含的音频和视频信息。
// Program Map Table typedef struct PMT_Packet_tag { 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; // for(i=0; i<N; i++) // { 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; } PMT_Packet; // Parse PMT int Parse_PMT(unsigned char *pTSBuf, PMT_Packet *packet) { // 参考Parse_PAT()来做就行了 // ... return 0; } |
PMT数据解析需要参考:ISO/IEC 13818-1的2.4.4.8 Program Map Table
4.3 PES解析
根据文档参考PAT、PMT的解析流程就能完成PES的解析了。
需要注意的是PES中PTS的解析,一般来说在90 kHz 中,PTS/9000的值为秒单位。
unsigned long long Parse_PTS(unsigned *pBuf) { unsigned long long llpts = (((unsigned long long )(pBuf[0] & 0x0E)) << 29) | (unsigned long long )(pBuf[1] << 22) | (((unsigned long long )(pBuf[2] & 0xFE)) << 14) | (unsigned long long )(pBuf[3] << 7) | (unsigned long long )(pBuf[4] >> 1); return llpts; } |
PES数据解析需要参考:2.5.5.1 Syntax of the PES packet syntax for Program Stream directory