ts文件解析_详解TS协议(2)

0.引言

由于有些朋友说,TS协议很复杂,希望能够再多讲解,所以这次在上篇文章的基础上,对TS协议的基础上,再做些补充。在阅读本文前,也可以先阅读前面的文章。

详解TS协议(包括超强EasyIce工具介绍)

d3a2d2e1e296f3b0bb42fc19ab10ddcf.png
5454e851e1a7773057b438dc0267adb7.png

1.TS流

1.1 TS流与其他流的关系

ES(Elementary Stream):原始码流,不分组的⾳频、视频或其他信息的连续码流。

PES(Packetized Elementary Stream):分组的原始码流,将原始码流ES流根据需要,分成⻓度不等的数据包,并加上包头就形成了打包的基本码流PES流。是⽤来传输ES的⼀种数据结构。

TS(Transport Stream):传输流,是由固定⻓度的包组成,含有独⽴时间基准的⼀个或多个节⽬,适⽤于误码较多的环境,并且从流的任意⼀段开始都可以独⽴解码。在MPEG-2系统中,由视频,⾳频的ES流和辅助数据复接⽣成的⽤于实际传输的标准信息流,称为MPEG-2传送流。

注意:TS流是原始的PES流(⾳视频等)中按照⼀定的频率插⼊PSI/SI⼀些标识符(辅助数据)信息,然后按固定⻓度打包形成的传输流。PSI/SI信息在TS流中并不是只发送⼀次,⽽是按照⼀定的频率插⼊码流,是重复发送的。

PS(Program Stream):节⽬流,PS流与TS流的区别在于,PS流的包结构是可变⻓度,⽽TS流的包结构是固定⻓度。

1.2 TS包

TS包的⻓度:固定为188 Bytes或204 Bytes,204 Bytes⻓度是在188Bytes后⾯增加了16 BytesCRC校验数据。结构如下图所示:

05620d2057293eafb456d9b1d7f3d6ad.png

TS包主要由ES包和section-table组成,其中ES包主要包括音频、视频、字幕等。section-table表主要包括PAT和PMT等组成。

TS包中所包含的字段如下图所示:

Sync byte是固定数值,一直都是0x47开头。相当于一个标识。下一个包的标识也是0x47。

transport_error_Indicator: 1bit,当其为1时,表示该TS包中⾄少有⼀个不可纠正的错误位,只有在错误纠正之后,该位才能重新置0。实际获取TS包之后,该位为1的包丢弃

payload_unit_start_indicator:1bit,对于PSI数据包,该位为1时,表示该TS包是某个Section的第⼀个包,并且该包含有pointer_field,该变量的值意义在于,除了调整字段之外往后pointer_field个字节开始,才是有效数据。对于空包来说,该值为0。

transport_priority:1bit,表示传输优先级,对于相同PID的TS包,该字段置1的TS包拥有更⾼的优先级。

PID:13bit,PID可以标识存储于TS包中有效净荷的数据的类型。PID⽤于TS包阶段⽤于鉴别各种PSI/SI信息表电视节⽬,区分⾳视频的PES包等,是辨别码流信息性质的关键。

transport_scrambling_control:2bit,⽤来指示传送流包Payload的加扰⽅式。加扰如下图:

21fe8df5e1dd312c9336a522fe50a9b3.png

注意:传送流包包括调整字段,则不应被加扰空包也不加扰

adaption_field_control:2bit,表示传送流包⾸部是否跟随调整字段如果全部是调整字段,不含payload。如下图所示:

5c83587f223951321b6fc31f8b75acb7.png

continuity_counter:4bit,随着具有相同PID的TS包增加⽽增加,当它达到最⼤(31)时,⼜恢复为0。

注意:如果adaption_field_control = 00/10,该连续计数器不增加,因为不含payload

TS流,在实际的项目中,需要明确以下几点:

(1)明确频点的概率,如频点800M,提供给广东电视台使用,其中就包括山东卫视,山东都市频道,山东财经频道。这就说明一个频点会有多个节目。如果一个频点对应的带宽是30M左右,每台节目是2M,那就可以容纳15个台。

(2)在实际的应用中,搜台是很常见,可以找到流中有哪些节目。这就利用到了PAT和PMT表。

PAT表(Program Association Table,节目关联表),一般它的PID是0。

首先,设置过滤条件,只获取pid=0的ts包,目的是解析出,流里面有哪些节目,意思就是有多少个节目以及通过什么pid去寻找PMT表,并进一步获取节目信息。如图所示:

9926ff39ca2a82aeb63f2015fa7e3735.png

(3)根据不同节目的PMT的pid,进一步获取信息。如获取频道号为1的节目信息,在从这些节目信息中获取相应pid的信息,如pid =11的ts包。PMT表包含的数据一般是该频道中所有Video数据的pid,所有Audio数据的pid,其它信息的pid。

注意:播放频道1这个节目,就需要找打对应的音频和视频信息。整个过程是先解封装,就是TS包拆为PES包,PES包拆为ES包,再根据ES包解码。

(4)在数字电视直播过程中,PAT和PMT这些数据是重复插到流里面,如1分钟插一次。如果在直播中,不去频繁的插入PAT和PMT,那在搜台时,可能就没办法看到想要的直播节目。PAT、PMT、AAC、H264是交替传入。如果是本地的ts文件播放,那就是在头部插入PAT和PMT即可。

2.解析TS包

2.1 获取包长

TS包的包⻓有两种,188Bytes或者204Bytes,在解析TS包之前,必须要先判断TS包包⻓,以便后续进⾏分析。

一般获取包长比较常见的方法,这里说一个,如果有其它方法,也欢迎各位朋友在评论区给出或私信我讨论。拿到第⼀个0x47数据之后,让⽂件指针往后188Bytes,如果是0x47,就让指针继续往下,如此循环10次(可以更多),结果仍旧是0x47,就判断包⻓为188 Bytes,否则⽤相同的⽅法判断204Bytes,通过则包⻓为204Bytes,都不通过就对该⽂件继续往下搜索,⽤相同的⽅法判断包⻓。经过分析码流发现,⼤部分的TS包都是以188 B为指定⻓度的

2.2 解析TS包头

在获取包⻓之后,就要对包头信息进⾏解析并获取有效数据,需要定义⼀个结构体存储数据,如下:

 /*TS包包头的结构体*/ typedef struct CSTSPacketHeader_S { BYTE ucSyncByte; //TS包的标识符 BYTE ucTransport_error_indicator; //传输错误指示器,当值为时,表示该包有误BYTE ucPayload_unit_start_indicator; //有效净荷开始标记位,当值为时,表示该包是某个section的开头,具有pointer_field 字段BYTE ucTransport_priority; //传输的优先级WORD wPID; //TS包的ID,⽤于区分不同的sectionBYTE ucTransport_scrambling_control; //指示ts传送流包有效净荷的加扰⽅式BYTE ucAdaptation_field_control; //指示是否有调整字段和有效净荷 BYTE ucContinuity_counter; //随着相同PID TS包的增加⽽增加 }TSPacketHeader;

假定包⻓为188,我们每获取⼀个TS包,就装进⼀个⻓度为188的BYTE型数组⾥,前4个BYTE就是包头的数据了,获取数据可以逻辑与,左右移,逻辑或的⽅法进⾏,具体例⼦如下:

pstTSHeader->wPID = ((pucTSBuffer[1]& 0x1f) << 8) | pucTSBuffer[2];

注意:如果要节省存储空间,要使用位域的方式定义结构体。

2.3 判断TS包的有效性

在⼀个码流中,并不全部都是有效的TS包,需要将⼀些⽆效TS包删除,直接获取下⼀个TS包。⽆效TS包的情况分为五种。

(1)该TS包往后188B不是0x47的包头标识符(TS包都是连续发送,如果出现包不连续的地⽅,说明该包数据传送时出错)

(2)TS包存在错误,即transport_error_Indicator的值为1。

(3)TS包全是调整字段或空包,即adaption_field_control的值为10(⼆进制)。

(4)TS包的调整字段属于保留的情况,即adaption_field_control的值为00(⼆进制)。

(5)TS包被加扰,即transport_scrambling_control不为00。但是,如果有做解扰,可以去掉这种情况。

2.4 确定payload的起始位置

TS包中,Payload的起始位置并不是固定的,会受到调整字段和pointer_field的影响,解析获取包头信息之后就可以确定payload的起始位置payloadPosition了。有如下判断流程:

(1)⾸先要判断是不是有调整字段,如果有payloadPosition = 5 + 调整字段⻓度

(2)其次要判断是不是有pointer_field,payload_unit_start_indicator= 1 则有,此时payloadPosition+= 1+ pointer_field。

注意:这里算出的数组的下标,从payloadPosition(包括下标)开始都是属于有效数据。TS流里所有的长度都是从长度数据的下一个Bytes开始计算,如section_length是5,就是从section_length的下一个Bytes开始计算,有5个字节的长度,所以(1)情况的时候要加上调整字段长度本身的1个字节,还有包头4个字节,一共是5个。

3.Section

3.1 Section的概念

⼀个TS数据包的最⼤净荷为184个字节,当⼀个PSI/SI表的字节⻓度⼤于184字节时,就要对这个表进⾏分割,分段机制主要是将⼀个数据表分割成多个数据段,形成段(section)来传送。在PSI/SI表到TS包的转换过程中,段起到了中介的作⽤。由于⼀个数据包只有188字节,⽽段的⻓度是可变EIT表的段限⻓4096字节,其余PSI/SI表段限⻓为1024字节。因此,⼀个段要分成⼏部分插⼊到TS包的payload中。从TS码流中可以获取到TS包,TS包要组成Section,才能提取到想要的信息,所以⾸先要懂得怎么组section

组Section之前要了解TS包在码流中发送的⼀些情况,如下面所示:

(1)TS包发送的时候PID是⽆序连续TS包PID可能都是不⼀样

(2)TS包发送的时候Section是相对有序的,也就是说,对于同⼀个PID的TS包只有发完了⼀个Section,才会发送下⼀个Section,不然⽆法区分该TS包属于哪⼀个Section,并且对于这个Section,TS包是有序发送的,否则数据会被打乱

(3)某个Section的第⼀个TS包有PSI/SI表的⼀些表头信息(table_id,section_length等信息),我称之为SectionHeader,可能后⾯的TS包就没有,所以接收某个Section必须先拿到⾸包。

3.2 TS包组Section

TS包组section,⾸先要找到该section的第⼀个TS包(下⾯简称为⾸包),⾸包含有该section的⻓度,可以⽤来判断⼀个section是不是组完了。通过判断TS包包头中的Payload Unit Start Indicator,该值为1,说明这个TS包是⾸包,可以开始组⼀个section,⾸包含有Section的头部,结构类似下图所示:

8313054f1522dd96065830176ee9f5fa.png

拿到⾸包之后,要获取section的⻓度,有效数据的第⼆个字节的后四位和第三个字节组成⼀个12bit的字段,该值就是section_length后⾯数据的⻓度,如果算上前⾯三个字节,整个section的⻓度就是section_length += 3将section_length和TS包有效⻓度进⾏对⽐,其处理数学关系如下图所示:

(1)如果section_length > TS包的有效数据,证明后⾯还有其他的TS包,将section_length减去TS包有效数据⻓度,获得剩余⻓度。这时,表示一个section还没组完,就要获取后续的TS包,后续的TS包应该是和原来相同PID才可以,并且TS包头中continuity_counter要⽐原来的⼤1(31的话要变成0),拿到包后要与剩余⻓度(剩余长度,是否大于188字节)进⾏对⽐,重复(1)、(2)步骤。

(2)如果是section_length <= TS包的有效数据,证明该section已经结束了

3.3 组多个Section

对于⼀些PSI/SI表来说,由于数据较多,有时候不⽌⼀个section,怎么针对这个表将所有的section组全?

从上图可以看到⾸包⾥有个信息是last_section_number,这个字段表明了当前⼦表最后⼀个Section_number,也就是说,当前⼦表最多有last_section_number+1 个section(section_number从0开始),在获取同⼀个⼦表的section时,可以使⽤链表的形式,将多个section链接起来

如何判全和判重呢?

每⼀个section的⾸包信息中都有⼀个version_number的字段,表明当前⼦表的version,这个字段⼀旦发⽣变化,就表明⼦表发⽣了变化旧版本的section就要被抛弃重新获取新版本的section,如果版本没有发⽣变化,那么每获取⼀个section,就要判断这个section的section_number是否之前获取过,我们可以建⽴⼀个标记数组,每获取⼀个section,就把以section_number为下标的标记数组的值置1,表明获取过该section,如果这个标记数组下标从0到last_section_number的值都为1,证明所有的section都被收全了,如果获取了⼀个新的section,⽽其标记数组值为1,证明这个section是重复的,此时应该将它丢弃

4.总结

本篇文章,补充了ts协议,更加深入的讲解了ts协议。不过对于新手来说,可以先大致了解ts协议,关于ts协议spec的相关知识,等到正真用到,再深入研究。

关于ts协议补充,还有这些博客比较好用。把链接给出,供大家学习。

https://blog.csdn.net/rell336/article/details/38109621

https://blog.csdn.net/rongdeguoqian/article/details/18214627

本篇文章就分享到这里,欢迎关注,点赞,转发,收藏,评论区讨论。

后期会有项目相关的知识更新,欢迎关注微信公众号“记录世界 from antonio”