一 从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个字节)
- ……
- }
- }
|