psi标准里面没说:分段也是具有结构性的,各种psi表的分段结构基本一致,参见Psi_section_st结构体。NIT和BAT表基本可以用一个函数实现的。本文用C#实现,需注意的就是ArrayList的结构体添加的元素需匹配,需要在调试的时候看见其具体元素类型,一旦不匹配直接报错退出,比较伤脑筋的。
举例一个很复杂的SDT表(一般情况是仅含当前流):
1.该表由一个当前流table_id=0x42,n个其他流组成table_id=0x46;则首先就是1个当前sdt段和n个其他sdt段构成;即n+1个段;各个sdt段的table_id和transport_stream_id是相同的(SDT多要求一个original_network_id相同);2.如果某个sdt段由6个188包装填(最大1021字节),比如 当前流的sdt段仍不能装下,那需要将该当前sdt段由 分段来填充实现:让section_number由0~m编号(m<=0xFF)即最大256个分段构成该段,即256*6个包=1k*256=256k字节的内容;同时,总的分段数量=n + m +1个,需显示这么多个分段;
谈论psi段:
1.主要的psi表结构:
(1).PAT,PMT,SDT,CAT,NIT,BAT,EIT分段都是有结构的,需总结出来。除了EIT分段section_length最大4093(24个188B包)外,其余最大1021B(6个188B包);
(2).段和分段的概念:
我的理解: 表由多个子表组成;子表至少为相同的table_id的分段构成,一般为当前传输流和其他传输流的段构成(它们的table_id就不同的了);当1021B还没装完该段时,需用分段号为0~255的section_number来装填该段,一般就能完成的了。
表和段之间:表由多个段描述来实现其整个内容,子表由1~256个分段来实现其内容;
以下是各种概念定义:
表 table
由具有相同的表标识符(table_id)的一系列子表构成。
table_id_extension:表标识符扩展,我写的程序里面全部用x_id代替;
子表 sub_table
子表是指具有相同表标识符(table_id)的段的集合,并且
对网络信息表(NIT): 具有相同的table_id_extension(network_id)和version_number;
对业务群关联表(BAT): 具有相同的table_id_extension(bouquet_id)和version_number;
对业务描述表(SDT): 具有相同的table_id_extension(transport_stream_id),相同的original_network_id 和version_number;
对事件信息表(EIT):具有相同的table_id_extension (service_id),相同的transport_stream_id 、original_network_id和version_number。
当段语法指示(section_syntax_indicator)字段置“1”时,表标识符扩展 (table_id_extension)字段等同于段的第四和第五字节。
(3).分段的结构:
各种psi表的结构大致相同,我们可以看成以下结构体:table_id,段长,段号,最后一个分段号,段内容,校验字段。见附后的段结构体;
表头部分:section_data段内容前的都是表头部分,均为8字节;只需注意reserved_future_use:PAT,PMT,CAT=0,其余为1(一般保留位均置1);
(4). 段长:这里是必须注意的, section_length指该字段的下一个字节开始的本段的字节长度(包括crc32)。而我们使用程序处理整个段的时候用到的是trueSecLen(整个段的长度),比如将整个段内容装填到6个188包中,需从段头开始装填的,每装满1个包trueSecLen减去对应长度,为trueSecLen=0则段内容打包完毕;故标准定义的这个段长真的是比较混淆的概念。
//真正的段长 = secLen+3(1字节table_id + 2字节段长) 【即该字段后的所有字节数量】
trueSecLen= section_length + 3;
(5).程序的实现:
psi表由于其含有众多的描述符,比较复杂,所以,一个良好的结构和高效通用的函数来实现是比较重要的。
段一般可以看做2层循环,loop1和loop2(各psi表在该循环时改个名称就是),参照psi表结构:
只有一层循环的表:
PAT(loop1:Programs loop):无描述符
CAT(loop1:descriptors loop):loop1下只能添加CA_descriptor
SDT(loop1:service_descriptors loop):loop1下可以添加descriptors loop
EIT(loop1:event loop):loop1下可以添加descriptors loop
两层循环的表(循环下可添加描述符):
PMT(loop1:program_info_descriptors loop; loop2:stream loop),实际loop2下还添加了loop3(stream_info_descriptors loop)
BAT和NIT结构完全可以看成一致的:
NIT(loop1:network_descriptors loop; loop2:transport_stream loop),实际loop2下还添加了loop3(transport_descriptors loop)
BAT(loop1:bouquet_descriptors loop; loop2:transport_stream loop),实际loop2下还添加了loop3(transport_descriptors loop)
目前,我是在表上存储数据的(node.Tag来存储数据),拿NIT表举例:
思路:psi表由treeview显示,根节点为PSI表,NIT表节点为L0层,以下L1,L2以此类推;这样,可能占用内容大一些,但非常直观地可以看到数据是否正确的,存取也非常方便,方便导入导出psi为ts流。用BatNodes_MenuAdd_L0的方式能明显看出现在正在操作BAT的哪一层(BAT根节点为L0层,这是L0层右键菜单的操作)
L0层存储的【表结构体】,packeted188_Set_AL为188包的集合,该集合就是需发给FPGA的PSI表的信息,FPGA改变包的计数即可;很直观地看到需发送的包的内容;section_Set_AL存的是分段的内容;可能2个段10个包的情况,也可能出现2个段2个包的情况。
L1层:显示段结构体,段的内容在此存储;
<1>.表结构
public struct PSI_Table_st //每一个表的根节点的Tag装此结构体
{
public ArrayList packeted188_Set_AL; //188包的集合
public ArrayList section_Set_AL; //Psi_section_st 段的集合
}
<2>.段结构
public struct Psi_section_st
{
public int table_id; // 8b
public int section_length; // 12b
public int section_number; // 8b
public int last_section_number; // 8b
public ArrayList section_data; // 段内容
public uint crc32; // 32b
}
<3>.各种循环描述结构,即描述子
public struct Descriptor_st //Commdes_st 通用描述符
{
public int tag;
public byte[] data;
}