关于TS流的解析

转自:http://blog.csdn.net/yxliu_mountainyuelu/article/details/1803978
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
0000f34ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f35ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f36ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f37ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f38ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f39ch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f3ach: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f3bch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f3cch: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ;

0000f3dch: FF FF FF FF FF FF FF FF FF FF FF FF 47 40 20 17 ;
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    ; ?*.~..??].
紿
具体的分析就以这个例子来分析。
// 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;
}
这是一个调整 TS 流数据包头的函数,这里牵扯到位段调整的问题。现在看一下 TS 流数据包头的结构的定义:
// 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 里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Jar TS解析是指将TS(Transport Stream)文件进行解析,提取出其中的音视频、字幕等媒体数据。 TS是一种常用的音视频传输格式,在数字电视广播、互联网直播等领域得到广泛应用。TS以很小的封装单元(Packet)为基本单位,每个Packet包含一定长度的数据,包括音视频帧、时间戳、量控制等信息。 进行Jar TS解析的过程主要包括以下几步: 1. 打开TS文件:通过文件读取操作,将TS文件读入内存。 2. 解析Packet:逐个解析TS文件中的Packet。解析Packet时,首先需要检验Packet的同步字节(Sync Byte)是否正确,以确保在正确的位置进行解析。然后根据Packet中的各个字段信息,如PID(Packet Identifier)、CC(Continuity Counter)等,提取出所需的音视频数据。 3. 数据提取:根据PID的值,判断Packet所属的音视频类型。对于视频,可以提取出视频帧数据,并解析出视频编码格式、分辨率等参数。对于音频,可以提取出音频帧数据,并解析出音频编码格式、采样率等参数。如果TS中还包含字幕等数据,也可以进行相应的提取解析。 4. 时间戳处理:根据Packet中的时间戳信息,进行时间戳的解析和处理。通过解析时间戳,可以实现音视频的同步播放。 5. 数据处理:将提取到的音视频数据进行处理,可以进行解码、转码、编辑等操作,以实现后续的播放、编辑等需求。 总的来说,Jar TS解析是通过解析TS文件中的Packet,提取出其中的音视频、字幕等数据,并进行相应的处理,以实现对TS的解码、播放等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值