搜索
 找回密码
 立即注册

简单一步 , 微信登陆

TS传输流的定义(一)

作者:halleyhuang | 时间:2016-10-11 15:48:02 | 阅读:4717| 显示全部楼层
一 从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表主要包含频道号码和每一个频道对应的PMTPID号码,这些信息我们在处理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个字节)
  •     ……
  •   }
  • }


该会员没有填写今日想说内容.
回复

使用道具 举报

大神点评1

bean.yang 发表于:2016-10-18 11:27:51
科普
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册
手机版