前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >嵌入式音视频之封装格式ts实战开发

嵌入式音视频之封装格式ts实战开发

作者头像
用户6280468
发布2023-10-25 09:12:51
6470
发布2023-10-25 09:12:51
举报
文章被收录于专栏:txp玩Linux

TS格式介绍:

TS:全称为MPEG2-TS。TS即"Transport Stream"的缩写。它是分包发送的,每一个包长为188字节(还有192和204个字节的包)。包的结构为,包头为4个字节(第一个字节为0x47),负载为184个字节。在TS流里可以填入很多类型的数据,如视频、音频、自定义信息等。MPEG2-TS主要应用于实时传送的节目,比如实时广播的电视节目。MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。简单地说,将DVD上的VOB文件的前面一截cut掉(或者是数据损坏数据)就会导致整个文件无法解码,而电视节目是任何时候打开电视机都能解码(收看)的。

TS解析需要参考:ISO/IEC 13818-1的2.4 Transport Stream bitstream requirements

具体链接如下:

代码语言:javascript
复制
https://www.iso.org/obp/ui/#iso:std:iso-iec:13818:-1:ed-7:v1:en

TS的包结构:

包头信息说明:

详细如下:

- sync_byte (同步字节):固定为0100 0111 (0x47);该字节由解码器识别,使包头和有效负载可相互分离。

  • transport_error_indicator(传输错误指示):‘1’表示在相关的传输包中至少有一个不可纠正的错误位。当被置1后,在错误被纠正之前不能重置为0。
  • payload_unit_start_indicator(开始指示):为1时,在前4个字节之后会有一个调整字节,其的数值为后面调整字段的长度length。因此有效载荷开始的位置应再偏移1+[length]个字节。原来有效负载是这样来的
  • transport_priority(传输优先级):‘1’表明优先级比其他具有相同PID 但此位没有被置‘1’的分组高。
  • PID:指示存储与分组有效负载中数据的类型。PID 值 0x0000—0x000F 保留。其中0x0000为PAT保留;0x0001为CAT保留;0x1fff为分组保留,即空包。
  • transport_scrambling_control(加扰控制):表示TS流分组有效负载的加密模式。空包为‘00’,如果传输包包头中包括调整字段,不应被加密。
  • adaptation_field_control(适配域控制):表示包头是否有调整字段或有效负载。‘00’为ISO/IEC未来使用保留;‘01’仅含有效载荷,无调整字段;‘10’ 无有效载荷,仅含调整字段;‘11’ 调整字段后为有效载荷,调整字段中的前一个字节表示调整字段的长度length,有效载荷开始的位置应再偏移[length]个字节。空包应为‘10’。
  • continuity_counter(连续性计数器):随着每一个具有相同PID的TS流分组而增加,当它达到最大值后又回复到0。范围为0~15。接收端可判断是否有包丢失及包传送顺序错误。

TS流包含的内容:

BAT解析例举:

TS-包1:

这个包比较特别,为什么说它是第一个包呢,因为 continuity_counter=0 & payload_unit_start_indicator=1。

也就是说任何SCTION段过滤器都开始于continuity_counter=0的TS包,continuity_counter有4BIT 1-15这样计算的的话,SECTION最大为184X16=2944 Byte。

那么SECTION究竟有多大的,这要取决于要解析的SECTION语法,取出有效载荷的前3 Byte也就知道了SCTION的长度,计算公式通常为:

secLen = ((uint16_t)(buf[1] & 0xf)) << 8 | buf[2])  buf为有效载荷的起始位置。详见BAT表语法结构。

概述如下:

  • 1、包递增器为0,表示为第一个SCTION的开始,包递增器为0的TS包payload_unit_start_indicator=1,其它情况下payload_unit_start_indicator=0。
  • 2、有效载荷的具体位置取决于 包头4+附加区域长度+负载单元起始位置。
  • 3、每个SCTION长度为包含在有效载荷的前3Byte。
  • 解复用器中通常使用payload_unit_start_indicator=1作为判断新包的开始。

47 40 11 10 表示无调整字段,有负载单元起始标志,那么有效载荷的起始位置为data[5]以后,有效载荷长度为183,有效载荷的前3Byte为 4a f2 ed,换算出当前的SCTION长度为secLen=749 Byte。

TS-包2:

47 00 11 11 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。

TS-包3:

47 00 11 12 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。

TS-包4:

47 00 11 13 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为184。

TS-包5:

47 00 11 14 表示无调整字段,无负载单元起始标志,那么有效载荷的起始位置为data[4]以后,有效载荷长度为17。

这是当前SCTION的最后一个TS包,那么怎么判断是最后一个包呢?

首先我们解析了第一个TS包时得出secLen=749,随着包递增器的增加,我们陆续获取有效载荷,749-183-184-184-184=17,所以当解析到第5个TS包时,有效载荷为17 Bype,BAT完成数据的获取工作,BAT的语法解析如下:

一段TS流,必须包含PAT包、PMT包、多个音频包、多个视频包、多个PCR包、以及其他信息包。

解析TS流数据的流程:查找PID为0x0的包,解析PAT,PAT包中的program_map_PID表示PMT的PID;查找PMT,PMT包中的elementary_PID表示音视频包的PID,PMT包中的PCR_PID表示PCR的PID,有的时候PCR的PID跟音频或者视频的PID相同,说明PCR会融进音视频的包,注意解析,有的时候PCR是自己单独的包;CAT、NIT、SDT、EIT的PID分别为: 0x01、0x10、0x11、0x12。

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

到现在为止,引申出TS包有效载荷的定位问题,有效载荷定位要注意两点:

  • 1、自适应区的大小
  • 2、“payload_unit_start_indicator” 有效载荷的起始区域

我们以“tsBuf[]”为例,“tsBuf[]”存储了一帧TS包,tsBuf[0]=0x47,TS的同步头,那么tsBuf[4]为自适应区域的长度。

代码语言:javascript
复制
transport_packet(){
  sync_byte
    ...
  adaptation_field_control                     2               bslbf
  continuity_counter                           4               uimsbf
  if(adaptation_field_control == '10' || adaptation_field_control == '11'){
    adaptation_field()
  }
  ...
}

tsBuf[4]=adaptation_field_length

代码语言:javascript
复制
adaptation_field(){
  adaptation_field_length                      8               uimsbf
  if(adaptation_field_length>0){
    ...
    PCR_flag                                   1               bslbf
    ...
    if(PCR_flag == '1'){
      program_clock_reference_base             33              uimsbf
      Reserved                                 6               bslbf
      program_clock_reference_extension        9               uimsbf
      ...
    }
  }
}

关于自适应区的ISO/IEC 13818-1语法如下:

TS包头解析:

TS包头有4个字节:

代码语言:javascript
复制
//Transport Stream header
typedef struct TS_header
{
         unsigned sync_byte                    :8;      //同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47的
         unsigned transport_error_indicator       :1;      //传输错误标志位,一般传输错误的话就不会处理这个包了
         unsigned payload_unit_start_indicator    :1;      //有效负载的开始标志,根据后面有效负载的内容不同功能也不同
         // payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。
         unsigned transport_priority              :1;      //传输优先级位,1表示高优先级
         unsigned PID                          :13;     //有效负载数据的类型
         unsigned transport_scrambling_control     :2;      //加密标志位,00表示未加密
         unsigned adaption_field_control          :2;      //调整字段控制,。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。
         unsigned continuity_counter              :4;      //一个4bit的计数器,范围0-15
} TS_header;
    //特殊参数说明:
   //sync_byte:0x47
   //payload_unit_start_indicator:0x01表示含有PSI或者PES头
   //PID:0x0表示后面负载内容为PAT,不同的PID表示不同的负载
   //adaption_field_control:
        // 0x0: // reserved for future use by ISO/IEC
        // 0x1: // 无调整字段,仅含有效负载   
        // 0x2: // 仅含调整字段,无有效负载
        // 0x3: // 调整字段后含有效负载
 
// Parse TS header
int Parse_TS_header(unsigned char *pTSBuf, TS_header *pheader)
{
    pheader->sync_byte                                     = pTSBuf[0];
    if (pheader->sync_byte != 0x47)
        return -1;
    pheader->transport_error_indicator       = pTSBuf[1] >> 7;
    pheader->payload_unit_start_indicator    = pTSBuf[1] >> 6 & 0x01;
    pheader->transport_priority             = pTSBuf[1] >> 5 & 0x01;
    pheader->PID                         = (pTSBuf[1] & 0x1F) << 8 | pTSBuf[2];
    pheader->transport_scrambling_control   = pTSBuf[3] >> 6;
    pheader->adaption_field_control         = pTSBuf[3] >> 4 & 0x03;
    pheader->continuity_counter            = pTSBuf[3] & 0x0F;
    return 0;
}

TS包头解析需要参考:ISO/IEC 13818-1的2.4.3.2 Transport Stream packet layer

TS负载格式解析:

1、 PAT解析:

TS_header包头中的PID值为0x0,表示当前负载为PAT(Program Association Table)。PAT数据的信息可以理解为整个TS流包含的节目信息。

代码语言:javascript
复制
// Program Association Table
typedef struct PAT_Packet_tag
{
    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; //最后一个分段的号码
    // for(i=0; i<N; i++)
    // {
    unsigned program_number                  : 16;
    unsigned reserved_3                      : 3;
    unsigned network_PID                     : 16;  // 或者program_map_PID
    unsigned CRC_32                          : 32;
    // }
} PAT_Packet;
 
// Parse PAT
int Parse_PAT(unsigned char *pTSBuf, PAT_Packet *packet)
{
    TS_header TSheader;
    if (Parse_TS_packet_header(pTSBuf, &TSheader) != 0)
        return -1;
    if (TSheader.payload_unit_start_indicator == 0x01) // 表示含有PSI或者PES头
    {
        if (TSheader.PID == 0x0)  // 表示PAT
        {
             int iBeginlen = 4;
             int adaptation_field_length = pTSBuf[4];
             switch(TSheader.adaption_field_control)
             {
             case 0x0:                                    // reserved for future use by ISO/IEC
                  return -1;
             case 0x1:                                    // 无调整字段,仅含有效负载       
                  iBeginlen += pTSBuf[iBeginlen] + 1;  // + pointer_field
                  break;
             case 0x2:                                     // 仅含调整字段,无有效负载
                  return -1;
             case 0x3: // 调整字段后含有效负载
                 if (adaptation_field_length > 0) 
                 {
                      iBeginlen += 1;                   // adaptation_field_length占8位
                      iBeginlen += adaptation_field_length; // + adaptation_field_length
                 }
                 else
                 {
                      iBeginlen += 1;                       // adaptation_field_length占8位
                 }
                 iBeginlen += pTSBuf[iBeginlen] + 1;           // + pointer_field
                 break;
            default:
                 break;
            }
            unsigned char *pPAT = pTSBuf + iBeginlen;
            packet->table_id                    = pTSBuf[0];
            packet->section_syntax_indicator    = pTSBuf[1] >> 7;
            packet->zero                        = pTSBuf[1] >> 6 & 0x1;
            packet->reserved_1                  = pTSBuf[1] >> 4 & 0x3;
            packet->section_length              = (pTSBuf[1] & 0x0F) << 8 | pTSBuf[2];
            packet->transport_stream_id         = pTSBuf[3] << 8 | pTSBuf[4];
            packet->reserved_2                  = pTSBuf[5] >> 6;
            packet->version_number              = pTSBuf[5] >> 1 &  0x1F;
            packet->current_next_indicator      = (pTSBuf[5] << 7) >> 7;
            packet->section_number              = pTSBuf[6];
            packet->last_section_number         = pTSBuf[7];
            int len = 0;
            len = 3 + packet->section_length;
            packet->CRC_32                      = (pTSBuf[len-4] & 0x000000FF) << 24
                                                | (pTSBuf[len-3] & 0x000000FF) << 16
                                                | (pTSBuf[len-2] & 0x000000FF) << 8
                                                | (pTSBuf[len-1] & 0x000000FF);
 
            int n = 0;
            for ( n = 0; n < (packet->section_length - 12); n += 4 )
            {
                 packet->program_number = pTSBuf[8 + n ] << 8 | pTSBuf[9 + n ]; 
                 packet->reserved_3                = pTSBuf[10 + n ] >> 5;
                 if ( packet->program_number == 0x00)
                 { 
                     packet->network_PID = (pTSBuf[10 + n ] & 0x1F) << 8 | pTSBuf[11 + n ];
                 }
                 else
                 {
                     // 有效的PMT的PID,然后通过这个PID值去查找PMT包
                     program_map_PID = (pTSBuf[10 + n] & 0x1F) << 8 | pTSBuf[11 + n];
                 }
            }
            return 0;
         }
    }
    return -1;
}

PAT数据解析需要参考:ISO/IEC 13818-1的2.4.4.3 Program Association Table

2、 PMT解析:

由PAT包中的program_map_PID可以确定PMT(Program Map Table)的PID。PMT数据的信息可以理解为这个节目包含的音频和视频信息。

代码语言:javascript
复制
// Program Map Table
typedef struct PMT_Packet_tag
{
     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;
     // for(i=0; i<N; i++)
     // {
     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;
} PMT_Packet;
   // Parse PMT
int Parse_PMT(unsigned char *pTSBuf, PMT_Packet *packet)
{
    // 参考Parse_PAT()来做就行了
    // ...
     
    return 0;
}

PMT数据解析需要参考:ISO/IEC 13818-1的2.4.4.8 Program Map Table

3、 PES解析:

根据文档参考PAT、PMT的解析流程就能完成PES的解析了。

需要注意的是PES中PTS的解析,一般来说在90 kHz 中,PTS/9000的值为秒单位。

代码语言:javascript
复制
unsigned long long Parse_PTS(unsigned *pBuf)
{
     unsigned long long llpts = (((unsigned long long)(pBuf[0] & 0x0E)) << 29)
         | (unsigned long long)(pBuf[1] << 22)
         | (((unsigned long long)(pBuf[2] & 0xFE)) << 14)
         | (unsigned long long)(pBuf[3] << 7)
         | (unsigned long long)(pBuf[4] >> 1);
     return llpts;
}

PES数据解析需要参考:2.5.5.1 Syntax of the PES packet syntax for Program Stream directory

码流分析工具:

1、Elecard Stream Analyzer:

2、EasyICE:

文章参考:https://www.cnblogs.com/jiangzhaowei/p/4344886.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TS格式介绍:
  • TS流包含的内容:
  • TS包头解析:
  • TS负载格式解析:
    • 1、 PAT解析:
      • 2、 PMT解析:
        • 3、 PES解析:
        • 码流分析工具:
          • 1、Elecard Stream Analyzer:
            • 2、EasyICE:
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档