精解PSI-SI

精解 PSI-SI

 

Packet 的概念

 (1)TS 流是基于 Packet 的位流格式 , 每个包是 188 字节或者 204 字节 ( 一般是 188 字节 ,204 字节的格式仅仅是在 188 字节的 Packet 后部加上 16 字节的 CRC 数据 , 其他格式是一样的 ), 整个 TS 流组成如下所示 :    

  Packet 1     Packet 2    ......    Packet n

在实际使用中 , 因为 TS 流已经内部具有很强的错误处理能力 , 所以一般使用较多的是 188 字节一个包的格式 ,204 字节一个包的格式据说一般在高清节目中使用较多 .

  所有的 Packet 格式都是统一的 , 包括一个 Packet header Packet datas. 其中 Packet header 包含了同步字节 ( 该字节固定是 0x47, 表示这个包的数据开始是正确的 ), Packet 的唯一号码 ( PID) 和其他一些信息 . 格式如下 ( C 格式表示 )

typedef struct

{

 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 adaptation_field_control:2;

 unsigned continuity_counter:4;

}PACKET_HEADER;

以上结构刚好占用 32 bits, 4 个字节 , 因此一个 TS 流的 Packet 头部的 4 字节是 header 信息 , 分析该 header 信息就可以知道当前 Packet 的属性 . 剩下的 184 字节有可能是 Video 数据 , 也有可能是 Audio 数据 , 也有可能是 DVB SI 信息 ,   么区分呢 ? 其实很简单 , 就是利用 header 中的 PID 信息 . 上一章说了 PAT 是节目关联表 , 它的 PID 0x0000. 这个 PID 就是对应这里  header PID. 换句话就是说 , 如果我们发现一个 Packet PID 等于 0x0000, 那么说明这个 Packet DVB PAT 表格而不是  Video 数据或者 Audio 数据 .

  际上 , 在信号编码成 TS 码流的时候 , 不同节目的 Video,Audio 等数据都分配了不同的 PID. 例如 , 一个节目有两路 Video, 三路 Audio,   么分配 PID 的时候可能是 Video 1==0x100,Video 2==0x101,Audio 1==0x102,Audio 2==0x103, Audio 3==0x104, 这样传输的 TS 码流中的 PID 就可能有以上的 PID. 因此 , 如果我们需要在程序中过滤出第一路 Video 和第二路  Audio 就可以这样处理了 ( 伪代码描述 ):

 void Process_Packet(unsigned char*buff)

 {

  int PID=GETPID(buff);

  if(PID==0x100) 

  {

   SaveToVideoBuffer(buff+4);

  }

  else if(PID==0x103)

  {

   SaveToAudioBuffer(buff+4);

  }

  else

  {

   printf("unknown PID!"n");

  }   

 }

  在的问题是 , 编码的时候分配好的 PID, 在解码的时候是怎么知道什么 PID 对应什么数据呢 ? 这就是 DVB SI 表格的分析与处理了 , 请参考第三章 . 这里先   看一个实际的 TS 码流的例子 . 这里的数据是用 UltraEdit 16 进制格式打开 TS 码流文件得到的 . 文件是 Taiwan-551.ts.

  里仅仅截取了 3 Packet 的信息 , 请注意图中用红色标注的部分 , 这就是 TS Packet 4 个字节的头信息 . 这个 TS 流是采用每个包共 188 字节的   格式 , 因为两个头信息的间隔是 188 个字节 ( 第一个 0x47 到第二个 0x47 的间隔 ). 以后的所有的 Packet 都将是 188 字节的格式 , 这是  DVB TS 标准规定的固定大小 . 那么这三个包分别包含的是什么数据 , 下面我们可以自己分析一下 .

    看第一个包 , 头信息数据是 "0x47 0x07 0xe5 0x12", 刚才已经知道了 ,header 信息都是按位操作的 ( 这就是为什么 TS 码流也可以叫   做位流的原因 ), 特别要注意的是定义和传输的时候都是 MSB first, 也就是说 , 先出现的位是数据的最高位 . 先转化成 2 进制格式 :

 01000111 00000111 11100101 00010010

请对照上面的 PACKET_HEADER 结构 :

typedef struct

{

 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 adaptation_field_control:2;

 unsigned continuity_counter:4;

}PACKET_HEADER;

那么对照一下 , 我们可以发现 :

 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

依此类推 , 再看一下第二个包 "0x47 0x07 0xe5 0x13",2 进制是 01000111 00000111 11100101 00010011

 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=0011 0x03, 表示当前传送的相同类型的包是第 4 ( 注意到了吧 , 以上两个包的 PID 都是 0x07e5, 所以这里的 continuity_counte 就递增一次 )

第三个包是 "0x47 0x07 0xf1 0x18",2 进制是 01000111 00000111 11110001 00011000.

 sync_byte=01000111, 就是 0x47, 这是 DVB TS 规定的同步字节 , 固定是 0x47.

 transport_error_indicator=0, 表示当前包没有发生传输错误 .

 payload_unit_start_indicator=0, 含义请参考 ISO13818-1 标准文档

 transport_priority=0, 表示当前包是低优先级 .

 PID=00111 11100101 0x07f1, 这代表是什么呢 , 暂时还不知道 ( 实际上是 Audio PID, 参考下图 )

 transport_scrambling_control=00, 表示节目没有加密

 adaptation_field_control=01 0x01, 具体含义请参考 ISO13818-1

 continuity_counte=1000 0x08, 表示当前传送的相同类型的包是第 9

请看解码程序 <<Seekfor MPEG-2 decoder>> 读取该文件的结果 :

 

上图我们可以发现 ,Taiwan-551.ts 有一个节目叫 "DIMO", 它的 Video PID 0x07e5,Audio PID 0x07e6

还有一个节目叫 "Service 1", 没有 Video PID, 它的 Audio PID 0x07f1( 说明是一个广播节目而非电视节目 )

这个数据刚好和我们刚才的分析是吻合的 .

  是我想大家还有疑问 , 为什么 0x07e5 代表 Video PID,0x07e6 代表其中一个 Audio PID ? 这就是刚才提到的 , 这是 TS 流在编码的   时候就分配好了的 . 但是 , 在解码的时候是怎么知道 0x07e5 就代表的是 Video 而不是 Audio ?

 

DVB SI/PSI 分析和处理

 

SI Specific Information 的简称 ,PSI program Specific Information .该机制允许DVB传送各种各样的讯息,比如节目名称,电视台名称,各种PID,私有信息,甚至单独传送数据实现数据通信等.这些功能的实现都归功于 SI/PSI.

  DVB  标准中,定义了一个标准的 PID 用来实现 SI/PSI. 这些 PID 是系统保留的,因此DVB编码的时候并不会用这些PID做为 Video PID 或者  Audio PID 或者其他PID.在一个简单的解复用程序中,只需要提供处理 PAT,PMT 表格的程序即可实现解复用,当然如果需要更友好的界面和实现   更复杂的功能(如CA)则必须处理其他的SI表.在这里仅仅分析 PAT,PMT,SDT 表格,其他 SI 表格的分析,请参考 ISO13818-1(MPEG-2 系统层标准 ) EN300468(DVB SI 标准 ) 文档.

 DVB 定义的 SI 保留的 PID 分别是 :

  

上表格的 PID 就是DVB保留的PID,分配的其他PID一定不会占用这些PID.解复用程序需要使用到的表格只有 PAT,PMT,SDT, 而CA应用还需要使用 CAT,EPG 应用还需要使用 NIT,EIT,TDT,TOT 等表格 . 所以在需要解复用的时候,伪代码需要这样写 :

  void Process_Packet(unsigned char*buff)

  {

   int PID=GETPID(buff);

   if(PID==0x0000) 

   {

    Process_PAT(buff+4);

   }

   else if(PID==......)

   {

   }

   else

   {

    printf("Unknown PID!");

   }

  }

    有的表格都开始于 Packet 中的 184 字节的数据部分,但有的时候一个表格没有 184 字节,这时在 Packet 中就可能插入一些无效信息用来填充使整个  Packet 依然保持是 188 字节.也可能用头信息中的 payload_unit_start_indicator 标志表格有个偏移位置(当  payload_unit_start_indicator=0 表示表格数据直接从 Packet 区的第四个字节开始,否则表示有一个偏移量位置开始 , 具体   请参考 ISO13818-1, 4 字节到偏移量间的数据是系统填充的无效数据).

  下面针对解复用程序详细分析一下 PAT,PMT SDT 三类表格的格式.

PAT, Program Association Table, 节目关联表

 PAT 表携带以下信息 :

(1) TS 流ID --- transport_stream_id, 该ID标志唯一的流ID

(2)  节目频道号 -- program_number, 该号码标志TS流中的一个频道,该频道可以包含很多的节目 ( 即可以包含多个 Video PID Audio PID)

(3) PMT PID--- program_map_PID, 表示本频道使用的哪个 PID 做为 PMT 的PID,因为可以有很多的频道,因此DVB规定 PMT PID 可以由用户自己定义.

PAT 表定义如下 :

 

各字段含义如下 :

 table_id:8 bits, 标志本表格的类型,应该是 0x00

 section_syntax_indicator:1 bit, 段语法标志,应该是 '1'

 '0': 固定的 '0' ,这是为了防止和 ISO13818Video 流格式中的控制字冲突而设置的.

 Reserved: 保留的 2bits, 保留位一般都是 '0'

 section_length:12bits 的段大小,单位是 Bytes.

 transport_stream_id:16bits 的当前流ID,DVB内唯一 .( 事实上很多都是自定义的 TS ID)

 version_number:5bits 版本号码,标注当前节目的版本.这是个非常有用的参数,当检测到这个字段改变时,说明TS流中的节目已经变化了,程序必须重新搜索节目.

 current_next_indicator:1bit: 当前还是未来使用标志符 , 一般情况下为 '0'

 section_number:8bits 当前段号码

 last_section_number:8bits 最后段号码 (section_number last_section_number 的功能是当 PAT 内容 >184 字节时, PAT 表会分成多个段 (sections), 解复用程序必须在全部接收完成后再进行 PAT 的分析 )

  for() 开始,就是描述了当前流中的频道数目 (N), 每一个频道对应的 PMT PID 是什么.解复用程序需要和上图类似的循环来接收所有的频道号码和对应的 PMT PID, 并把这些信息在缓冲区中保存起来.在后部的处理中需要使用到 PMT PID.

 CRC_32: 本段的 CRC 校验值,一般是会忽略的. N 是一个变量,计算方法是 N=(section_length-9)/4.

  从以上分析我们可以发现 ,PAT 表主要包含频道号码和每一个频道对应的 PMT PID 号码 , 这些信息我们在处理 PAT 表格的时候会保存起来 , 以后会使用到这些数据 . 例如我们可以定义这样的数据结构保存这些信息 :

 typedef struct

 {

  int channel_number;

  int pmt_pid;

 }PMT_ITEM;

 PMT_ITEM pmt[64];

PMT, Program Map Table, 节目影射表

  如果一个 TS 流中含有多个频道 , 那么就会包含多个 PID 不同的 PMT . 检测是否 PMT 的伪代码如下 :

 void Process_Packet(unsigned char*buff)

 {

  int I;

  int PID=GETPID(buff);

  if(PID==0x0000)

  {

   Process_PAT(buff+4);

  }

  else if(PID==.....)

  {

  }

  else

  {

   

   for(i=0;i<64;i++)

   {

    if(PID==pmt[i].pmt_pid)

    {

     Process_PMT(buff+4);

     break;

    } 

   }

  }

 }

PMT 表中包含的数据如下 :

(1)  当前频道中包含的所有 Video 数据的 PID

(2)  当前频道中包含的所有 Audio 数据的 PID

(3)  和当前频道关联在一起的其他数据的 PID( 如数字广播 , 数据通讯等使用的 PID)

PMT 定义如下 :

 

各字段含义如下 :

 table_id:8bits ID, 应该是 0x02

 section_syntax_indicator:1bit 的段语法标志 , 应该是 '1'

 '0': 固定是 '0', 如果不是说明数据有错 .

 reserved:2bits 保留位 , 应该是 '00'

 section_length:16bits 段长度 , program_number 开始 , CRC_32( 包含 ) 的字节总数 .

 program_number:16bits 的频道号码 , 表示当前的 PMT 关联到的频道 . 换句话就是说 , 当前描述的是 program_number 频道的信息 .

 reserved:2bits 保留位 , 应该是 '00'

 version_number: 版本号码 , 如果 PMT 内容有更新 , version_number 会递增 1 通知解复用程序需要重新接收节目信息 , 否则 version_number 是固定不变的 .

 current_next_indicator: 当前未来标志符 , 一般是 0

 section_number: 当前段号码

 last_section_number: 最后段号码 , 含义和 PAT 中的对应字段相同 , 请参考 PAT 部分 .

 reserved:3bits 保留位 , 一般是 '000'.

 PCR_PID:13bits PCR PID, 具体请参考 ISO13818-1, 解复用程序不使用该参数 .

reserved:4bits 保留位 , 一般是 '0000'

program_info_length: 节目信息长度 ( 之后的是 N 个描述符结构 , 一般可以忽略掉 , 这个字段就代表描述符总的长度 , 单位是 Bytes)

紧接着就是频道内部包含的节目类型和对应的 PID 号码了 .

stream_type:8bits 流类型 , 标志是 Video 还是 Audio 还是其他数据 .

reserved:3 bits 保留位 .

elementary_PID:13bits 对应的数据 PID 号码 ( 如果 stream_type Video, 那么这个 PID 就是 Video PID, 如果 stream_type 标志是 Audio, 那么这个 PID 就是 Audio PID)

reserved:4 bits 保留位 .

ES_info_length: program_info_length 类似的信息长度 ( 其后是 N2 个描述符号 )

CRC_32:32bits 段末尾是本段的 CRC 校验值 , 一般忽略 .

从以上的分析可以看出 , 只要我们处理了 PMT, 那么我们就可以获取频道中所有的 PID 信息 , 例如当前频道包含多少个 Video, 共多少个 Audio, 和其他数据 , 还能知道每种数据对应的 PID 分别是什么 .

这样如果我们要选择其中一个 Video Audio 收看 , 那么只需要把要收看的节目的 Video PID Audio PID 保存起来 , 在处理 Packet 的时候进行过滤即可实现 .

比较全面实现解复用的伪代码如下 :

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);

 }

 else if(PID==Video_PID)

 {

  SaveToVideoBuffer(buff+4);

 }

 else if(PID==Audio_PID)

 {

  SaveToAudioBuffer(buff+4);

 }

 else

{

 for( i=0;i<64;i++)

 {

  if(PID==pmt[i].pmt_pid)

  {

   Process_PMT(buff+4);

   Break;

  }

 }

 

}

以上伪代码可以实现基本的解复用 : 检测所有的频道 , 检测所有 stream PID, 选择特定的节目进行播放 . 只要读取每个 Packet 188 字节的内容 , 然后每次都调用 Process_Packet() 即可实现简单的解复用 .

介绍到这里 , 我们就可以总结一下 DVB 搜台的原理了 .( ! 洗耳恭听 !)

     顶盒先调整高频头到一个固定的频率 ( 498MHZ), 如果此频率有数字信号 , COFDM 芯片 ( MT352) 会自动把 TS 流数据传送给 MPEG- 2 decoder. MPEG-2 decoder 先进行数据的同步 , 也就是等待完整的 Packet 的到来 . 然后循环查找是否出现 PID== 0x0000 Packet, 如果出现了 , 则马上进入分析 PAT 的处理 , 获取了所有的 PMT PID. 接着循环查找是否出现 PMT, 如果发现了 , 则自动进   PMT 分析 , 获取该频段所有的频道数据并保存 . 如果没有发现 PAT 或者没有发现 PMT, 说明该频段没有信号 , 进入下一个频率扫描 .

   从以上描述可以看出 , 机顶盒搜索频率是随机发生的 , 要使每次机顶盒都能搜索到信号 , 则要求 TS 流每隔一段时间就发送一次 PAT PMT. 事实上 DVB 传输系统就是这么做的 . 因此无论何时接入终端系统 , 系统都能马上搜索到节目并正确解复用实现播放 . 不仅仅如此 , 其他数据也都是交替传送的 . 比如第一个 Packet 可能是 PAT, 第二个 Packet 可能是 PMT, 而第三个 Packet 可能是 Video 1, 第四个 Packet 可能是 Video 2,

只要系统传输速度足够快 ( 就是称之为 " 码率 " 的东东 ), 实现实时播放是没有任何问题的 .

  到这里虽然实现了解复用 , 但可以看出 , 使用的 PID 都是枯燥的数字 , 如果调台要用户自己输入数字那可是太麻烦了 , 而且还容易输入错误 , 操作非常不直观 , 即使做成一个菜单让用户选择也是非常的呆板 . 针对这个问题 ,DVB 系统提出了一个 SDT 表格 , 该表格标志一个节目的名称 , 并且能和 PMT 中的 PID 联系起来 , 这样用户就可以通过直接选择节目名称来选择节目了 .

SDT, Service description section, 服务描述段

 SDT 可以提供的信息包括 :

(1)  该节目是否在播放中

(2)  该节目是否被加密

(3)  该节目的名称

SDT 定义如下 :

 

各字段定义如下 :

 table_id:8bits ID, 可以是 0x42, 表示描述的是当前流的信息 , 也可以是 0x46, 表示是其他流的信息 (EPG 使用此参数 )

 section_syntax_indicator: 段语法标志 , 一般是 '1'

 reserved_future_used:2bits 保留未来使用

 reserved:1bit 保留位 , 防止控制字冲突 , 一般是 '0', 也有可能是 '1'

 section_length:12bits 的段长度 , 单位是 Bytes, transport_stream_id 开始 , CRC_32 结束 ( 包含 )

 transport_stream_id:16bits 当前描述的流 ID

 reserved:2bits 保留位

 version_number:5bits 的版本号码 , 如果数据更新则此字段递增 1

 current_next_indicator: 当前未来标志 , 一般是 '0', 表示当前马上使用 .

 original_netword_id:16bits 的原始网络 ID

 reserved_future_use:8bits 保留未来使用位

  接下来是 N 个节目信息的循环 :

  service_id:16 bits 的服务器 ID, 实际上就是 PMT 段中的 program_number.

  reserved_future_used:6bits 保留未来使用位

  EIT_schedule_flag:1bit EIT 信息 ,1 表示当前流实现了该节目的 EIT 传送

  EIT_present_following_flag:1bits EIT 信息 ,1 表示当前流实现了该节目的 EIT 传送

  running_status:3bits 的运行状态信息 :1- 还未播放  2- 几分钟后马上开始 ,3- 被暂停播出 ,4- 正在播放 , 其他 --- 保留

  free_CA_mode:1bits 的加密信息 ,'1' 表示该节目被加密 .

     接着的是描述符 , 一般是 Service descriptor, 分析此描述符可以获取 servive_id 指定的节目的节目名称 . 具体格式请参考  EN300468 中的 Service descriptor 部分 . 分析完毕 , 则节目名称和节目号码已经联系起来了 . 机顶盒程序就可以用这些节目名称代替  PID 让用户选择 , 从而实现比较友好的用户界面 !

  下面参考一下 <<Seekfor MPEG2 decoder>> 中的界面和显示信息 .

 

  图是 <<Seekfor MPEG2 decoder>> 打开三个不同的码流文件 (*.ts) 形成的 PID 信息和节目名称 . 用户   可以通过切换节目名称的下拉列表框切换节目 , 也可以通过 " 视频流 " " 音频流 " 下拉列表框切换 Video Audio! 这些数据都是通过分析 PAT, PMT SDT 得到的 .

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值