BAT表、链接描述符、文件内容与CRC32/MPEG-2校验
0. 前言
这些笔记是几年前学习TS流解析和解复用的时候写的,现在整理出来记录一下。
当时的目标有如下两个:
- 解析接受到的TS流
- 从TS流中提取BAT表等数据,并解析具体内容。
1. TS包分析
需要解析TS流,先要解析接受到的每个TS包。
1.1 TS包结构
一个TS包由包头和包数据组成,总长度一般为188字节(也可能是204字节)。
包头一般为4字节左右,包含这个TS包的各种信息,剩余的是TS包数据。在数据中,可能会存在调整字节(非有效负载)。
1.2 TS包头和数据分析
TS包头由4字节组成,结构定义如下(C):
// Transport packet header
typedef struct TS_packet_header
{
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 adaption_field_control : 2;
unsigned continuity_counter : 4;
} TS_packet_header;
解析:
数据 | 长度 | 数据含义 |
---|---|---|
sync_byte | 8bit | 同步字节,一般是0x47 |
transport_error_indicator | 1bit | 该位值为1时,表示在相关的传送包中至少有一个不可纠正的错误,只有在错误纠正之后,该位才能被重新置0。 |
payload_unit_start_indicator | 1bit | 该字段用来表示TS包的有效净荷有PES包或者PSI数据的情况。payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。例如:47 40 00 17 00第五个字节是00,说明紧跟着00之后就是具体的负载内容(有效负载)。 |
transport_priority | 1bit | 传输优先级,1为高优先级 |
PID | 12bit | Packet ID号码,唯一的号码对应不同的包 |
transport_scrambling_control | 2bit | 加密标志(00:未加密;其他表示已加密) |
adaptation_field_control | 2bit | 表示传送流包首部是否跟随有调整字段。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。 |
continuity_counter | 4bit | 范围0-15,具有相同的PID的TS分组传输时每次加1,到15后清0。不过,有些情况下是不计数的。如下:(1)TS分组无有效负载。(2)复制的TS分组和原分组这个值一样。(3)标志discontinuity_indicator为1时。 |
参考:
http://www.cnblogs.com/shakin/p/3714848.html
http://www.cnblogs.com/jiangzhaowei/p/3625944.html
TS流调整参考:http://blog.csdn.net/cabbage2008/article/details/49837143
较详细的数据结构:http://blog.csdn.net/a31898534/article/details/4399353
可用以下代码结合上文协同了解(http://blog.csdn.net/a31898534/article/details/4399353 )。
transport_packet() {
sync_byte // 8
transport_error_indicator //1
payload_unit_start_indicator //1
transport_priority // 1
PID //13
transport_scrambling_control // 2
adaptation_field_control //2
continuity_counter //4
if(adaptation_field_control=='10' || adaptation_field_control=='11') {
adaptation_field()
}
if(adaptation_field_control=='01' || adaptation_field_control=='11') {
for (i=0; i<N; i++) {
data_byte //8
}
}
}
2. BAT表语法解析
2.1 BAT表简介
BAT表的一个片段如下(包含表头,不含TS包的同步字节内容)
Position | Hex | Text |
---|---|---|
0000 | 4a f2 ed 70 11 ff 00 00 f2 e0 47 08 64 61 74 61 63 61 | J…p…G.dataca |
0012 | 73 74 4a f5 00 01 00 01 a0 01 80 02 00 90 5f 00 00 00 | stJ…_… |
0024 | 00 01 02 00 02 07 00 03 07 00 04 07 00 05 07 00 06 01 | … |
0036 | 00 07 01 00 08 01 00 09 01 00 0a 01 00 0b 08 00 0c 08 | … |
0048 | 00 0d 08 00 0e 08 00 0f 03 00 10 00 00 11 00 00 12 00 | … |
2.2 BAT表的语法和解析
BAT表语法如下:
语法表中(只说明几个重要的):
语法 | 长度(Bit) | 解析 |
---|---|---|
table_id | 8 | 用于确定表类型,0x4A代表bouquet_association_section |
section_syntax_indicator | 1 | 段语法标志,此处必须是1 |
section_length | 12 | 段长,单位Byte |
bouquet_id | 16 | Bouquet表ID,在数据广播中此处为0x7011 |
version_number | 5 | 数据版本号,从0起递增,到1f后归零 |
current_next_indicator | 1 | 置于‘1’时指示发送的有条件访问表为当前有效的。该比特设置为‘0’时,它指示发送的有条件访问表尚未有效并且下一个有条件访问表将生效 |
section_number | 8 | (分段传输表时的)本段编号,第一段表编号应为0x00 |
last_section_number | 8 | 最后一段编号 |
descriptor | - | 描述符 |
|CRC32|4|从表头到数据的最后一字节的CRC32校验,算法使用CRC32/MPEG-2
上图中从最后一个reserved_future_use到最后的CRC32前的大括号均未在此出现
按照上述表格解析例子中的BAT表,得出以下结果(descriptor部分在后文进行分析):
2.3 链接描述符linkage_descriptor
在上文给出的例子中,从0xa开始的内容是链接描述符。
链接描述符的语法如下:
其中值得注意的是:
- descriptor_tag:在这里有两种descriptor_tag。tag = 0x47是表名,不用注意此段数据。tag = 0x4A才是值得注意的数据。
- linkage_type:链接类型,在这里是0x80。
- PID:文件信息所在的PID。
- table_ext_id:实际上就是文件编号
- last_section_num:该文件编号对应的文件在传输中的总段数
2.4 解析用途
对BAT表的解析主要是为了了解一下信息:
- 文件内容所在的PID
- 文件总数和每个文件总段数。
3. CRC32/MPEG2校验
3.1 CRC32/MPEG2参数模型
该校验的参数模型如下:
其中各参数定义:
-
NAME:名称
-
WIDTH:宽度,即CRC比特数
-
POLY:生成项的简写。以16进制表示,即是0x04C11DB7。忽略了最高位的"1",即完整的生成项是0x104C11DB7。重要的一点是,这是“未颠倒”的生成项!前面说过,“颠倒的”生成项是0xEDB88320。
-
INIT:这是算法开始时寄存器的初始化预置值,十六进制表示。这个值可以直接赋值给“直驱表法”算法中的寄存器,作为寄存器的初始值!
而对于“驱动表法”算法及“直接计算法”,寄存器的初始值必须是0!前面几次循环先将待测数据移入到寄存器中,当寄存器装满后,再用这个初始化预置值去XOR寄存器,这样寄存器就被这个值初始化了!
这点很重要!!如果在“驱动表法”算法开始时,寄存器的初始值不为0,那么寄存器中的值就会相当于是待测数据了,这样算出的CRC结果就不对了!我们的目的是用预置值去初始化寄存器,而不是将预置值作为待测数据去处理! -
REFIN 这个值是真TRUE或假FALSE。
如果这个值是FALSE,表示待测数据的每个字节都不用“颠倒”,即BIT7仍是作为最高位,BIT0作为最低位。
如果这个值是TRUE,表示待测数据的每个字节都要先“颠倒”,即BIT7作为最低位,BIT0作为最高位。 -
REFOUT:这个值是真TRUE或假FALSE。
如果这个值是FALSE,表示计算结束后,寄存器中的值直接进入XOROUT处理即可。
如果这个值是TRUE,表示计算结束后,寄存器中的值要先“颠倒”,再进入XOROUT处理。注意,这是将整个寄存器的值颠倒,因为寄存器的各个字节合起来表达了一个值,如果只是对各个字节各自颠倒,那结果值就错误了。 -
XOROUT:这是W位长的16进制数值。
这个值与经REFOUT后的寄存器的值相XOR,得到的值就是最终正式的CRC值!
参考:http://www.360doc.com/content/14/0513/22/7991404_377367845.shtml
3.2 CRC32/MPEG2校验的Java实现
算法的实现使用CRC32表的方式,整体代码如下:
public class TsCRC32Utils {
static final long[] CrcTable = {
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
public static int CRC32(byte[] indata) {
int i;
int crc = 0xFFFFFFFF;
for(i = 0; i < indata.length; i++)
crc = (int) ((crc << 8) ^ CrcTable[(int) (((crc >> 24) ^ indata[i]) & 0xFF)]);
return crc;
}
}
4. ABS文件内容的获取
ABS文件内容所在的PID可通过分析BAT表来读取。
此处的PID为0x200。
4.1 语法分析
先放一段例子进行分析:
000005c0h: 90 B6 3E 0E 4F FF 00 00 0F 32 32 38 5F 32 33 34 ; 惗>.O...228_234
000005d0h: 5F 30 30 37 2E 78 6D 6C 00 00 06 21 3C 3F 78 6D ; _007.xml...!<?xm
000005e0h: 6C 20 76 65 72 73 69 6F 6E 3D 22 31 2E 30 22 20 ; l version="1.0"
table_id:此处必定是0x90。
section_syntax_indicator:必须是1。
section_length:本段长度(Byte)。
table_id_ext:文件编号(和BAT表里的保持一致)
section_number:当前段编号
last_section_number:最后一段编号,和BAT表里的信息一致
file_data_length:文件长度(注意:这里的文件长度是指本段的长度)
4.2 注意事项
- 实际使用时必须使用CRC32/MPEG-2校验接收到的数据。
- 注意判断文件是否接收完成。
- 注意内存的使用情况。