音视频封装格式之TS(一)

2022-03-21 17:38:25 浏览数 (1)

前言:

大家晚上,今天开始给大家分享音视频里面的各种封装格式解析,先给大家分享封装格式基本概念,后期再分析代码实现封装格式解析。

后期的ffmpeg源码解析,我也会快速把自己掌握的一部分,到时候分享给大家,也欢迎大家多多交流,一起进步,一起飞!

一、TS格式解析:

1、TS流、PS流、PES流和ES流是啥?

  • ES流:(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流。
  • PES流:( Packet Elemental Stream)把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流。
  • PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p)。
  • TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输)。视频编码主要格式为 H264/MPEG4,⾳频为 AAC/MP3;

2、TS流产生过程:

TS流产生过程

从上图可以看出,视频ES和音频ES通过打包器和共同或独立的系统时间基准形成一个个PES,通过TS复用器复用形成的传输流。注意这里的TS流是位流格式(分析Packet的时候会解释),也即是说TS流是可以按位读取的。

3、TS流格式:

TS流是基于Packet的位流格式,每个包是188个字节(或204个字节,在188个字节后加上了16字节的CRC校验数据,其他格式一样)。ts流分为三个部分:ts header、adaptation field、payload。ts header 固定 4 个字节;adaptation field 可能存在也可能不存在,主要作⽤是给不⾜ 188 字节的数据做填充;payload 是 pes数据

Packet Header(包头)信息说明:

Packet包头说明

PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的。如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video、Audio或其他业务信息)。下表给出了一些表的PID值,这些值是固定的,不允许用于更改。

PID值表

同时解析 ts 流要 先找到 PAT 表,只要找到 PAT 就可以找到 PMT,然后就可以找到⾳视频流了。PAT 表的和 PMT 表需 要定期插⼊ ts 流,因为⽤户随时可能加⼊ ts 流,这个间隔⽐较⼩,通常每隔⼏个视频帧就要加⼊ PAT 和 PMT。PAT 和 PMT 表是必须的,还可以加⼊其它表如 SDT(业务描述表)等,不过 hls 流只要有 PAT 和 PMT 就可以播放了。

  • PAT 表:主要的作⽤就是指明了
  • PMT 表的 PID 值。PMT 表:主要的作⽤就是指明了⾳视频流的 PID 值。
  • ⾳频流/视频流:承载⾳视频内容。

4、什么是PAT、PAM表:

PAT表(Program Association Table,节目关联表)

PMT表 (Program Map Table,节目映射表)

这里我们先来看PAT表,首先PAT表定义了当前TS流中所有的节目,其PID为0x0000,它是PSI的根节点,要查寻找节目必须从PAT表开始查找。

PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来,以后会使用到这些数据。下面将PAT表的定义给出:

代码语言:javascript复制
typedef struct TS_PAT_Program
{
    unsigned program_number   :  16;  //节目号
    unsigned program_map_PID :  13; // 节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
}TS_PAT_Program

下面是PAT结构体定义:

代码语言:javascript复制
typedef struct TS_PAT
{
    unsigned table_id                     : 8; //固定为0x00 ,标志是该表是PAT表
    unsigned section_syntax_indicator     : 1; //段语法标志位,固定为1
    unsigned zero                         : 1; //0
    unsigned reserved_1                   : 2; // 保留位
     unsigned section_length               : 12; //表示从下一个字段开始到CRC32(含)之间有用的字节数
    unsigned transport_stream_id          : 16; //该传输流的ID,区别于一个网络中其它多路复用的流
    unsigned reserved_2                   : 2;// 保留位
    unsigned version_number               : 5; //范围0-31,表示PAT的版本号
    unsigned current_next_indicator       : 1; //发送的PAT是当前有效还是下一个PAT有效
    unsigned section_number               : 8; //分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
    unsigned last_section_number          : 8;  //最后一个分段的号码
 
    std::vector<TS_PAT_Program> program;
    unsigned reserved_3                    : 3; // 保留位
    unsigned network_PID                    : 13; //网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID
    unsigned CRC_32                        : 32;  //CRC32校验码
} TS_PAT; 

PAI解析:

代码语言:javascript复制
HRESULT CTS_Stream_Parse::adjust_PAT_table( TS_PAT * packet, unsigned char * buffer)
{
    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]; 
 
    int len = 0;
    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); 
 
    int n = 0;
    for ( n = 0; n < packet->section_length - 12; n  = 4 )
    {
        unsigned  program_num = buffer[8   n ] << 8 | buffer[9   n ];  
        packet->reserved_3           = buffer[10   n ] >> 5; 
  
        packet->network_PID = 0x00;
        if ( program_num == 0x00)
       {  
            packet->network_PID = (buffer[10   n ] & 0x1F) << 8 | buffer[11   n ];
 
            TS_network_Pid = packet->network_PID; //记录该TS流的网络PID
 
             TRACE(" packet->network_PID %0x /n/n", packet->network_PID );
        }
        else
        {
           TS_PAT_Program PAT_program;
           PAT_program.program_map_PID = (buffer[10   n] & 0x1F) << 8 | buffer[11   n];
           PAT_program.program_number = program_num;
           packet->program.push_back( PAT_program );
           TS_program.push_back( PAT_program );//向全局PAT节目数组中添加PAT节目信息     
        }         
    }
    return 0;
}

具体工程代码后面再分享,今天暂时不分享,主要还是以概念为主。

接下来我们再来看PAM表,如果一个TS流中含有多个频道,那么就会包含多个PID不同的PMT表。

PMT表中包含的数据如下:

  • 当前频道中包含的所有Video数据的PID
  • 当前频道中包含的所有Audio数据的PID
  • 和当前频道关联在一起的其他数据的PID(如数字广播,数据通讯等使用的PID)

只要我们处理了PMT,那么我们就可以获取频道中所有的PID信息,如当前频道包含多少个Video、共多少个Audio和其他数据,还能知道每种数据对应的PID分别是什么。这样如果我们要选择其中一个Video和Audio收看,那么只需要把要收看的节目的Video PID和Audio PID保存起来,在处理Packet的时候进行过滤即可实现。

代码语言:javascript复制
typedef struct TS_PMT_Stream  
{  
 unsigned stream_type                       : 8; //指示特定PID的节目元素包的类型。该处PID由elementary PID指定  
 unsigned elementary_PID                    : 13; //该域指示TS包的PID值。这些TS包含有相关的节目元素  
 unsigned ES_info_length                    : 12; //前两位bit为00。该域指示跟随其后的描述相关节目元素的byte数  
 unsigned descriptor;  
}TS_PMT_Stream;   

PMT表的结构体定义:

代码语言:javascript复制
//PMT 表结构体
typedef struct TS_PMT
{
    unsigned table_id                        : 8; //固定为0x02, 表示PMT表
     unsigned section_syntax_indicator        : 1; //固定为0x01
    unsigned zero                            : 1; //0x01
    unsigned reserved_1                      : 2; //0x03
    unsigned section_length                  : 12;//首先两位bit置为00,它指示段的byte数,由段长度域开始,包含CRC。
     unsigned program_number                    : 16;// 指出该节目对应于可应用的Program map PID
    unsigned reserved_2                        : 2; //0x03
    unsigned version_number                    : 5; //指出TS流中Program map section的版本号
     unsigned current_next_indicator            : 1; //当该位置1时,当前传送的Program map section可用;
                                                     //当该位置0时,指示当前传送的Program map section不可用,下一个TS流的Program map section有效。
     unsigned section_number                    : 8; //固定为0x00
    unsigned last_section_number            : 8; //固定为0x00
    unsigned reserved_3                        : 3; //0x07
    unsigned PCR_PID                        : 13; //指明TS包的PID值,该TS包含有PCR域,
            //该PCR值对应于由节目号指定的对应节目。
            //如果对于私有数据流的节目定义与PCR无关,这个域的值将为0x1FFF。
     unsigned reserved_4                        : 4; //预留为0x0F
    unsigned program_info_length            : 12; //前两位bit为00。该域指出跟随其后对节目信息的描述的byte数。
    
     std::vector<TS_PMT_Stream> PMT_Stream;  //每个元素包含8位, 指示特定PID的节目元素包的类型。该处PID由elementary PID指定
     unsigned reserved_5                        : 3; //0x07
    unsigned reserved_6                        : 4; //0x0F
    unsigned CRC_32                            : 32; 
} TS_PMT;

PMT表的解析:

代码语言:javascript复制
HRESULT CTS_Stream_Parse::adjust_PMT_table ( TS_PMT * packet, unsigned char * buffer )
{ 
    packet->table_id                            = buffer[0];
    packet->section_syntax_indicator            = buffer[1] >> 7;
    packet->zero                                = buffer[1] >> 6 & 0x01; 
    packet->reserved_1                            = buffer[1] >> 4 & 0x03;
    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;
 
 PCRID = packet->PCR_PID;
 
    packet->reserved_4                            = buffer[10] >> 4;
    packet->program_info_length                    = (buffer[10] & 0x0F) << 8 | buffer[11]; 
    // Get CRC_32
 int len = 0;
    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); 
 
 int pos = 12;
    // 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; )
    {
  TS_PMT_Stream pmt_stream;
  pmt_stream.stream_type =  buffer[pos];
  packet->reserved_5  =   buffer[pos 1] >> 5;
  pmt_stream.elementary_PID =  ((buffer[pos 1] << 8) | buffer[pos 2]) & 0x1FFF;
  packet->reserved_6     =   buffer[pos 3] >> 4;
  pmt_stream.ES_info_length =   (buffer[pos 3] & 0x0F) << 8 | buffer[pos 4];
  
  pmt_stream.descriptor = 0x00;
  if (pmt_stream.ES_info_length != 0)
  {
   pmt_stream.descriptor = buffer[pos   5];
   
   for( int len = 2; len <= pmt_stream.ES_info_length; len    )
   {
    pmt_stream.descriptor = pmt_stream.descriptor<< 8 | buffer[pos   4   len];
   }
   pos  = pmt_stream.ES_info_length;
  }
  pos  = 5;
  packet->PMT_Stream.push_back( pmt_stream );
  TS_Stream_type.push_back( pmt_stream );
    }
 return 0;
}

二、总结:

今天的总结就分享到这里,我们下期见,下期分享flv封装格式的基本概念;

0 人点赞