| 
一 从TS流开始          最近开始学习数字电视机顶盒的开发,从MPEG-2到DVB,看着看着突然就出现了一大堆表格,什么PAT、PMT、CAT……如此多的表该怎样深入了解呢?         我们知道,数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video、Audio以及我们需要学习的PAT、PMT等信息。因此,我们首先需要了解TS流是什么,以及TS流是怎样形成、有着怎样的结构。  
(一)TS流、PS流、PES流和ES流都是什么? 
 
       ES流(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流。        PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流。        PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p)。        TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输)。  
(二) TS流是如何产生的? 
 
![]()  
 
 
 
  从上图可以看出,视频ES和音频ES通过打包器和共同或独立的系统时间基准形成一个个PES,通过TS复用器复用形成的传输流。注意这里的TS流是位流格式(分析Packet的时候会解释),也即是说TS流是可以按位读取的。 
 
 
 
(三)TS流的格式是怎样的?   TS流是基于Packet的位流格式,每个包是188个字节(或204个字节,在188个字节后加上了16字节的CRC校验数据,其他格式一样)。整个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  | 包递增计数器  
 
 |  
  
 
 
   PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的。如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video、Audio或其他业务信息)。下表给出了一些表的PID值,这些值是固定的,不允许用于更改。 
  
 
表  | PID 值  |  PAT  | 0x0000  |  CAT  | 0x0001  |  TSDT  | 0x0002  |  EIT,ST  | 0x0012  |  RST,ST  | 0x0013  |  TDT,TOT,ST  | 0x0014  |  
  
 
        下面以一个TS流的其中一个Packet中的Packet Header为例进行说明:  
 
 | 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  | 9  | 10  | 11  | 12  | 13  | 14  | 15  | 16  | 17  | 18  | 19  | 20  | 21  | 22  | 23  | 24  | 25  | 26  | 27  | 28  | 29  | 30  | 31  | 32  | …  |  Packet(十六进制)  | 4  | 7  | 0  | 7  | e  | 5  | 1  | 2  | …  |  Packet(二进制)  | 0  | 1  | 0  | 0  | 0  | 1  | 1  | 1  | 0  | 0  | 0  | 0  | 0  | 1  | 1  | 1  | 1  | 1  | 1  | 0  | 0  | 1  | 0  | 1  | 0  | 0  | 0  | 1  | 0  | 0  | 1  | 0  | …  |  Packet Header 信息  | 1 sync_byte=0x47  | 2  | 3  | 4  | 5 PID=0x07e5  | 6  | 7  | 8  | …  
 |  
 sync_byte=01000111,                        就是0x47,这是DVB TS规定的同步字节,固定是0x47. transport_error_indicator=0,             表示当前包没有发生传输错误. payload_unit_start_indicator=0,      含义参考ISO13818-1标准文档 transport_priority=0,                        表示当前包是低优先级. PID=00111 11100101即0x07e5,       Video PID transport_scrambling_control=00,  表示节目没有加密 adaptation_field_control=01           即0x01,具体含义请参考ISO13818-1 continuity_counte=0010                即0x02,表示当前传送的相同类型的包是第3个 
 
 
 
          TS流的基本内容就是这些了。         回顾一下,TS流是一种位流(当然就是数字的),它是由ES流分割成PES后复用而成的;它经过网络传输被机顶盒接收到;数字电视机顶盒接收到TS流后将解析TS流。         TS流是由一个个Packet(包)构成的,每个包都是由Packet Header(包头)和Packet Data(包数据)组成的。其中Packet Header指示了该Packet是什么属性的,并给出了该Packet Data的数据的唯一网络标识符PID。         到这里,我们对TS流已经有了一定的了解,下面将从TS流转向PAT表和PMT表的学习。 二、从TS流到PAT、PMT  
   说完了TS流的基本概念,就该开始对TS流进行更深入的研究了。首先需要想一想:TS流的本质是什么?它的确是一段码流,并且是一段由数据包(Packet)组成的码流。那么这些数据包究竟是怎样的呢?它和我们收看的电视节目之间又有什么区别?这些都是这部分需要了解的内容。         在上一节中,我们可以看到PID这个被标红的字段频繁地出现。PID是当前TS流的Packet区别于其他Packet类型的唯一识别符,通过读取每个包的Packet Header,我们可以知道这个Packet的数据属于何种类型。上一节列出了几项固定的PID值,它们用于识别存储了特殊信息的Packet。下面要谈的PAT表的PID值就是固定的0x0000。   
(一) PAT表(Program Association Table,节目关联表)   由于下面的内容比较繁杂,这里先给出一个大纲,方便查阅:        1. PAT表的描述(表格+分析)        2. PAT表的定义(代码+分析)        3. PAT表的结构(代码+分析)        4. PAT表的解析(代码+分析)        5. 通过一段TS流中一个Packet分析PAT表(表格+分析)          下面,开始正式的分析! 1、PAT的描述 
PAT表定义了当前TS流中所有的节目,其PID为0x0000,它是PSI的根节点,要查寻找节目必须从PAT表开始查找。        PAT表携带以下信息:  
TS流ID  | transport_stream_id  | 该ID标志唯一的流ID  |  节目频道号  | program_number  | 该号码标志TS流中的一个频道,该频道可以包含很多的节目(即可以包含多个Video PID和Audio PID)  |  PMT的PID  | program_map_PID  | 表示本频道使用哪个PID做为PMT的PID,因为可以有很多的频道,因此DVB规定PMT的PID可以由用户自己定义  |  
  
 
2、PAT的定义 
PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来,以后会使用到这些数据。下面将PAT表的定义给出: 
 
- <span style="font-weight: normal;">typedef struct TS_PAT_Program
 - {
 -     unsigned program_number   :  16;  //节目号
 -     unsigned program_map_PID :  13; // 节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
 - }TS_PAT_Program</span>
 
 
  
3、PAT的结构 
再将PAT表的结构体给出: 
 
- typedef struct TS_PAT
 - {
 -     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;  //最后一个分段的号码
 -     std::vector<TS_PAT_Program> program;
 -     unsigned reserved_3                    : 3; // 保留位
 -     unsigned network_PID                    : 13; //网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID
 -     unsigned CRC_32                        : 32;  //CRC32校验码
 - } TS_PAT;
 
 
  
 
4、PAT的解析 
 
- <span style="font-weight: normal;">HRESULT CTS_Stream_Parse::adjust_PAT_table( TS_PAT * packet, unsigned char * buffer)
 - {
 -     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];
 -     int len = 0;
 -     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);
 -     int n = 0;
 -     for ( n = 0; n < packet->section_length - 12; n += 4 )
 -     {
 -         unsigned  program_num = buffer[8 + n ] << 8 | buffer[9 + n ];
 -         packet->reserved_3           = buffer[10 + n ] >> 5;
 -         packet->network_PID = 0x00;
 -         if ( program_num == 0x00)
 -        {
 -             packet->network_PID = (buffer[10 + n ] & 0x1F) << 8 | buffer[11 + n ];
 -             TS_network_Pid = packet->network_PID; //记录该TS流的网络PID
 -              TRACE(" packet->network_PID %0x /n/n", packet->network_PID );
 -         }
 -         else
 -         {
 -            TS_PAT_Program PAT_program;
 -            PAT_program.program_map_PID = (buffer[10 + n] & 0x1F) << 8 | buffer[11 + n];
 -            PAT_program.program_number = program_num;
 -            packet->program.push_back( PAT_program );
 -            TS_program.push_back( PAT_program );//向全局PAT节目数组中添加PAT节目信息
 -         }
 -     }
 -     return 0;
 - }
 
 
  
 
   从for()开始,就是描述了当前流中的频道数目(N),每一个频道对应的PMT PID是什么。解复用程序需要接收所有的频道号码和对应的PMT 的PID,并把这些信息在缓冲区中保存起来。在后部的处理中需要使用到PMT的 PID。 
5、 通过一段TS流中一个Packet分析PAT表 
  这里我们分析一段TS流其中一个Packet的Packet Data部分: 
        首先给出一个数据包,其数据如下: Packet Header  | Packet Data  |  0x47 0x40 0x00 0x10  | 0000 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff…… ff ff  |  
         分析Packet Header如下表所示:  
 
 | 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  | 9  | 10  | 11  | 12  | 13  | 14  | 15  | 16  | 17  | 18  | 19  | 20  | 21  | 22  | 23  | 24  | 25  | 26  | 27  | 28  | 29  | 30  | 31  | 32  | …  |  Packet(十六进制)  | 4  | 7  | 4  | 0  | 0  | 0  | 1  | 0  | …  |  Packet(二进制)  | 0  | 1  | 0  | 0  | 0  | 1  | 1  | 1  | 0  | 1  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 1  | 0  | 0  | 0  | 0  | …  |  Packet Header Bits  | 1 sync_byte=0x47  | 2  | 3  | 4  | 5 PID=0x0000  | 6  | 7  | 8  | …  |  
  
         根据包头数据格式,我们可以知晓整个数据包的属性,列表如下:  
sync_byte  | 0x47  | 固定同步字节  |  transport_error_indicator  | “0”  | 没有传输错误  |  payload_unit_start_indicator  | “1”  | 在前4个字节后会有一个调整字节。所以实际数据应该为去除第一个字节后的数据。即上面数据中红色部分不属于有效数据包。  |  transport_priority  | “0”  | 传输优先级低  |  PID  | 0x0000  | PID=0x0000说明数据包是PAT表信息  |  transport_scrambling_control  | “00”  | 未加密  |  adaptation_field_control  | “01”  | 附加区域控制  |  continuity_counte  | “0000”  | 包递增计数器  |          如上表所示,我们可以知道,首先Packet的Packet Data是PAT信息表,因为其PID为0x0000,并且在包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")。这样,Packet Data就应该是“00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff …… ff ff”。 
       Packet Data分析  |  第n个字节  |   1    | 2   |   3    | 4  | 5  | 6  | 7  | 8  | 9  | 10   | 11   |   12   |   13   | 14   | 15   | 16  | 17  | 18  | 19  | 20  | …  |  Packet Data(除去开头的0x00)  | 00  | b0  | 11  | 00  | 01  | c1  | 00  | 00  | 00  | 00  | e0  | 1f  | 00  | 01  | e1  | 00  | 24  | ac  | 48  | 84  | …  |  字段名  | 位  | 具体值  | 次序  | 说明  |  table_id  | 8  | 0000  | 第1个字节 0000 0000B(0x00)  | PAT的table_id只能是0x00  |  section_syntax_indicator  | 1  | 1  |  
第2、3个字节 1011 0000 0001 0001B(0xb0 11)  | 段语法标志位,固定为1  |  zero  | 1  | 0  |  
 |  reserved  | 2  | 11  |  
 |  section_length  | 12  | 0000 0001 0001B=0x011=17  | 段长度为17字节  |  transport_stream_id  | 16  | 0x0001  | 第4、5个字节 0x00 0x01  |  
 |  reserved  | 2  | 11  |  
第6个字节 1100 0001B(0xc1)  |  
 |  version_number  | 5  | 00000  | 一旦PAT有变化,版本号加1  |  current_next_indicator  | 1  | 1  | 当前传送的PAT表可以使用,若为0则要等待下一个表  |  section_number  | 8  | 0x00  | 第7个字节0x00  |  
 |  last_section_number  | 8  | 0x00  | 第8个字节 0x00  |  
 |  开始循环  |  program_number  | 16  | 0x0000-第一次  | 2个字节(0x00 00)  | 节目号   |  reserved  | 3  | 111  |  
2个字节 1110 0000 0001 1111B(0xe0 1f)  |  
 |  network_id(节目号为0时) program_map_PID(节目号为其他时)  | 13  | 0 0000 0001 1111B=31 -第一次  | 节目号为0x0000时,表示这是NIT,PID=0x001f,即31 节目号为0x0001时,表示这是PMT,PID=0x100,即256  |  结束循环  |  CRC_32  | 32  | --  | 4个字节  |  
 
 
 |   由以上几个表可以分析出PAT表和PMT表有着内在的联系。也就是之前提到的。PAT表描述了当前流的NIT(Network Information Table,网络信息表)中的PID、当前流中有多少不同类型的PMT表及每个PMT表对应的频道号。而PAT表和PMT表到底有什么深层次的联系呢?在讨论完了PMT表和SDT表后再做讨论吧。 
6、过滤PAT表信息的伪代码 
 
- <span style="font-weight: normal;">int Video_PID=0x07e5,Audio_PID=0x07e6;
 - void Process_Packet(unsigned char*buff)
 - { int I; int PID=GETPID(buff);
 -   if(PID==0x0000) { Process_PAT(buff+4); }  // 如果PID为0x0000,则该Packet Data为PAT信息,因此调用处理PAT表的函数
 -   else{                                     // 这里buff+4 意味着从Packet Header之后进行解析(包头占4个字节)
 -     ……
 -   }
 - }
 
 
  
 
 |