[Multimedia][TS]TS流的解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

 TS即是"Transport Stream"的缩写。他是分包发送的,每一个包长为188字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。他的包的结构为,包头为4个字节,负载为184个字节(这184个字节不一定都是有效数据,有一些可能为填充数据)。


工作形式:
  因为在TS流里可以填入很多种东西,所以有必要有一种机制来确定怎么来标识这些数据。制定TS流标准的机构就规定了一些数据结构来定义。比如: PSI(Program Specific Information)表,所以解析起来就像这样: 先接收一个负载里为PAT的数据包,在整个数据包里找到一个PMT包的ID。然后再接收一个含有PMT的数据包,在这个数据包里找到有关填入数据类型的ID。之后就在接收到的TS包里找含有这个ID的负载内容,这个内容就是填入的信息。根据填入的数据类型的ID的不同,在TS流复合多种信息是可行的。关键就是找到标识的ID号。


  现在以一个例子来说明具体的操作:
  在开始之前先给出一片实际TS流例子:
0000f32ch: 47 40 00 17 00 00 B0 0D 00 01 C1 00 00 00 01 E0 ; G@....?..?...?
0000f33ch: 20 A2 C3 29 41 FF FF FF FF FF FF FF FF FF FF FF ; ⒚)A&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f34ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f35ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f36ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f37ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f38ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f39ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3ach: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3bch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3cch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;
0000f3dch: FF FF FF FF FF FF FF FF FF FF FF FF 47 40 20 17 ; &63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;&63733;G@ .
0000f3ech: 00 02 B0 1B 00 01 C1 00 00 E0 21 F0 00 1B E0 21 ; ..?..?.??.?
0000f3fch: F0 04 2A 02 7E 1F 03 E0 22 F0 00 5D 16 BD 48 ; ?*.~..??].紿


  具体的分析就以这个例子来分析。这是一个调整TS流数据包头的函数,这里牵扯到位段调整的问题。

现在看一下TS流数据包头的结构的定义:

// Adjust TS packet header
void  adjust_TS_packet_header(TS_packet_header* pheader)
{
     unsigned char  buf[4];
     memcpy (buf, pheader, 4);
     pheader->transport_error_indicator = buf[1] >> 7;
     pheader->payload_unit_start_indicator = buf[1] >> 6 & 0x01;
     pheader->transport_priority = buf[1] >> 5 & 0x01;
     pheader->PID = (buf[1] & 0x1F) << 8 | buf[2];
     pheader->transport_scrambling_control = buf[3] >> 6;
     pheader->adaption_field_control = buf[3] >> 4 & 0x03;
     pheader->continuity_counter = buf[3] & 0x03;
}
 
//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;

  下面我们来分析,在ISO/IEC 13818-1里有说明,PAT(Program Association Table)的PID值为0x00,TS包的标识(即sync_byte)为0x47,并且为了确保这个TS包里的数据有效,所以我们一开始查找47 40 00这三组16进制数,为什么这样?具体的奥秘在TS包的结构上,前面已经说了sync_byte固定为0x47。现在往下看transport_error_indicator、payload_unit_start_indicator、transport_priority和PID这四个元素,PID为0x00,这是PAT的标识。transport_error_indicator为0,transport_priority为0。把他们看成是两组8位16进制数就是:40 00。现在看看我们的TS流片断例子,看来正好是47 40 00开头的,一个TS流的头部占据了4个字节。剩下的负载部分的内容由PID来决定,例子看来就是一个PAT表。在这里有个地方需要注意一下,payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。现在看例子中的数据47 40 00 17 00第五个字节是00,说明紧跟着00之后就是具体的负载内容。


下面给出PAT表的结构体:

// PAT table
// Programm Association Table
typedef  struct  TS_PAT
{
     unsigned table_id : 8;
     unsigned section_syntax_indicator : 1;
     unsigned zero : 1;
     unsigned reserved_1 : 2;
     unsigned section_length : 12;
     unsigned transport_stream_id : 16;
     unsigned reserved_2 : 2;
     unsigned version_number : 5;
     unsigned current_next_indicator : 1;
     unsigned section_number : 8;
     unsigned last_section_number : 8;
     unsigned program_number : 16;
     unsigned reserved_3 : 3;
     unsigned network_PID : 13;
     unsigned program_map_PID : 13;
     unsigned CRC_32 : 32;
} TS_PAT;
 
再给出PAT表字段调整函数:
// Adjust PAT table
void  adjust_PAT_table ( TS_PAT * packet, char  * buffer )
{
     int  n = 0, i = 0;
     int  len = 0;
     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];
     // Get CRC_32
     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);
     // Parse network_PID or program_map_PID
     for  ( n = 0; n < packet->section_length - 4; n ++ )
     {
         packet->program_number = buffer[8] << 8 | buffer[9];
         packet->reserved_3 = buffer[10] >> 5;
         if  ( packet->program_number == 0x0 )
         packet->network_PID = (buffer[10] << 3) << 5 | buffer[11];
         else
         {
             packet->program_map_PID = (buffer[10] << 3) << 5 | buffer[11];
         }
         n += 5;
     }
}


通过上面的分析,例子中的数据00 B0 0D 00 01 C1 00 00 00 01 E0 20 A2 C3 29 41就是具体的PAT表的内容,然后根据PAT结构体来具体分析PAT表。但是我们需要注意的是在PAT表里有program_number、network_PID的元素不只有一个,这两个元素是通过循环来确定的。循环的次数通过section_length元素的确定。在这个例子中program_map_PID为20,所以下面来PMT分析时,就是查找47 40 20的开头的TS包。


下面来分析PMT表,先给出PMT(Program Map Table)的结构体:

// PMT table
// Program Map Table
typedef  struct  TS_PMT
{
     unsigned table_id : 8;
     unsigned section_syntax_indicator : 1;
     unsigned zero : 1;
     unsigned reserved_1 : 2;
     unsigned section_length : 12;
     unsigned program_number : 16;
     unsigned reserved_2 : 2;
     unsigned version_number : 5;
     unsigned current_next_indicator : 1;
     unsigned section_number : 8;
     unsigned last_section_number : 8;
     unsigned reserved_3 : 3;
     unsigned PCR_PID : 13;
     unsigned reserved_4 : 4;
     unsigned program_info_length : 12;
     unsigned stream_type : 8;
     unsigned reserved_5 : 3;
     unsigned elementary_PID : 13;
     unsigned reserved_6 : 4;
     unsigned ES_info_length : 12;
     unsigned CRC_32 : 32;
} TS_PMT;
在给出调整字段函数:
// Adjust PMT table
void  adjust_PMT_table ( TS_PMT * packet, char  * buffer )
{
     int  pos = 12, len = 0;
     int  i = 0;
     packet->table_id = buffer[0];
     packet->section_syntax_indicator = buffer[1] >> 7;
     packet->zero = buffer[1] >> 6;
     packet->reserved_1 = buffer[1] >> 4;
     packet->section_length = (buffer[1] & 0x0F) << 8 | buffer[2];
     packet->program_number = 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];
     packet->reserved_3 = buffer[8] >> 5;
     packet->PCR_PID = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;
     packet->reserved_4 = buffer[10] >> 4;
     packet->program_info_length = (buffer[10] & 0x0F) << 8 | buffer[11];
     // Get CRC_32
     len = packet->section_length + 3;
     packet->CRC_32 = (buffer[len-4] & 0x000000FF) << 24
     | (buffer[len-3] & 0x000000FF) << 16
     | (buffer[len-2] & 0x000000FF) << 8
     | (buffer[len-1] & 0x000000FF);
     // program info descriptor
     if  ( packet->program_info_length != 0 )
     pos += packet->program_info_length;
     // Get stream type and PID
     for  ( ; pos <= (packet->section_length + 2 ) - 4; )
     {
         packet->stream_type = buffer[pos];
         packet->reserved_5 = buffer[pos+1] >> 5;
         packet->elementary_PID = ((buffer[pos+1] << 8) | buffer[pos+2]) & 0x1FFF;
         packet->reserved_6 = buffer[pos+3] >> 4;
         packet->ES_info_length = (buffer[pos+3] & 0x0F) << 8 | buffer[pos+4];
         // Store in es
         es[i].type = packet->stream_type;
         es[i].pid = packet->elementary_PID;
         if  ( packet->ES_info_length != 0 )
         {
             pos = pos+5;
             pos += packet->ES_info_length;
         }
         else
         {
             pos += 5;
         }
         i++;
     }
}


TS流可以复合很多的节目的视频和音频,但是解码器是怎么来区分的呢?答案就在PMT表里,如其名节目映射表。他就是来解决这个问题的。现在看PMT结构体里的stream_type、elementary_PID这两个元素,前一个用来确定后一个作为标识PID的内容具体是什么,音频或视频等。还有要注意他们不只有一个,所以他们是通过循环读取来确保所有的值都被读取了,当然循环也是有规定的(具体看调整函数上)。从例子上来看,我们在倒数第三行找到了上面分析来的PMT表的PID为0x20的TS包。然后就可以把数据是用调整函数填入结构中。然后得到具体节目的PID为视频0x21, 音频0x22。
PS. 文章里的PID是用来判断具体TS包是什么包的。分析每个包得到的PID值,都可以复合在TS头部结构体的PID里。



本文转自静默虚空博客园博客,原文链接:http://www.cnblogs.com/jingmoxukong/articles/2118438.html,如需转载请自行联系原作者


相关文章
|
算法 决策智能 C++
干货 |【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例
干货 |【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例
840 0
关于TS流的解析
<p align="center"><strong><span style="color:#02368D;"></span></strong></p> <div style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; line-height: 25px;"> <span style="font-si
2507 0
关于TS流的解析
TS即是"TransportStream"的缩写。他是分包发送的,每一个包长为188字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。
933 0
[Multimedia][TS]TS流的解析
  TS即是"Transport Stream"的缩写。他是分包发送的,每一个包长为188字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。他的包的结构为,包头为4个字节,负载为184个字节(这184个字节不一定都是有效数据,有一些可能为填充数据)。
1011 0
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
86 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
87 0
|
3月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
68 0
|
9天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
9天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多