ffmpeg对mpeg2-TS解析的最详细分析
博客专区 > jane1009 的博客 > 博客详情
 ffmpeg对mpeg2-TS解析的最详细分析
jane1009 发表于9个月前
 ffmpeg对mpeg2-TS解析的最详细分析
  • 发表于 9个月前
  • 阅读 16
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

 介绍: 

MPEG的系统层编码为不同的应用场景设计了两种格式: 

TS(Transport Stream) 和PS(Program Stream),

它们两者之间不具有层级关系,

在逻辑上,它们两者都是由PES(Packetized Elementary Stream)包组成的,

所以可以很方便地实现相互转换.

 

TS(Transport Stream): 

  是将具有一个或多个独立时间基的一个或多个节目(包括音频和视频)组成一个流,

  组成同一个节目的基本流(如一个视频流,多个音频流)的PES包有一个共用的时间基。

  TS的包长标准为188bytes.

 

从上面的定义可以分成三层来看TS/PS。

ES层   : 由单独的音频(如mp3),视频流(如h.264)组成基本的ES(Elementary Stream)。

PES层  : 将基本的ES按一定的规则(如H.264以AU)进行封装,并打上时间戳,组成PES。

TS/PS层: 将PES包进行切分后再封装成188bytes大小的TS包,

         同时还将一些节目信息也封装成TS包(称为section), 两者共同组成TS层。

 

从上面的总结,TS/PS总体上来说,是一种封装格式,用来承载数据。

所以FFmpeg

将TS/PS的解析文件定义在libavformat/mpegts.c文件中

将音频,视频的解码定义在libavcodec/mpeg12.c文件中

 

下面来看FFmpeg是如何进行TS的demuxer的。

1. MPEG2-TS的demuxer函数

  1. AVInputFormat ff_mpegts_demuxer = { 
  2.     "mpegts", 
  3.     NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),
  4.     sizeof(MpegTSContext),
  5.     mpegts_probe,
  6.     mpegts_read_header,
  7.     mpegts_read_packet, 
  8.     mpegts_read_close, 
  9.     read_seek,
  10.     mpegts_get_pcr,
  11.     .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT, 
  12. #ifdef USE_SYNCPOINT_SEARCH 
  13.     .read_seek2 = read_seek2, 
  14. #endif 
  15. };

 

2. 解析流中的TS格式

 

  1. /*
  2.  * 出现3种格式,主要原因是:
  3.  * TS标准是 188Bytes;
  4.  * 日本标准是192Bytes的DVH-S格式;
  5.  * 第三种的 204Bytes则是在188Bytes的基础上,加上16Bytes的FEC(前向纠错).
  6.  */
  7. #define TS_PACKET_SIZE 188
  8. #define TS_DVHS_PACKET_SIZE 192
  9. #define TS_FEC_PACKET_SIZE 204
  10.  
  11. #define TS_MAX_PACKET_SIZE 204
  12.  
  13. //< maximum score, half of that is used for file-extension-based detection
  14. #define AVPROBE_SCORE_MAX 100

 

 

  1. /*
  2.  * 函数功能:
  3.  * 分析流中是三种TS格式的哪一种
  4.  */
  5. static int mpegts_probe(AVProbeData *p)
  6. {
  7. #define CHECK_COUNT 10
  8.  
  9.   const int size= p->buf_size;
  10.   int score, fec_score, dvhs_score;
  11.   int check_count= size / TS_FEC_PACKET_SIZE;
  12.  
  13.   if (check_count < CHECK_COUNT)
  14.       return -1;
  15.  
  16.   score     = analyze(p->buf, TS_PACKET_SIZE *check_count, TS_PACKET_SIZE , NULL) 
  17.               * CHECK_COUNT / check_count;
  18.   dvhs_score= analyze(p->buf, TS_DVHS_PACKET_SIZE*check_count, TS_DVHS_PACKET_SIZE, NULL)
  19.               * CHECK_COUNT / check_count;
  20.   fec_score = analyze(p->buf, TS_FEC_PACKET_SIZE *check_count, TS_FEC_PACKET_SIZE , NULL)
  21.               * CHECK_COUNT / check_count;
  22.  
  23.   /* 
  24.    * we need a clear definition for the returned score ,
  25.    * otherwise things will become messy sooner or later
  26.    */
  27.   if (score > fec_score && score > dvhs_score && score > 6) 
  28.     return AVPROBE_SCORE_MAX + score - CHECK_COUNT;
  29.   else if(dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) 
  30.     return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT;
  31.   else if(fec_score > 6) 
  32.     return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
  33.   else 
  34.     return -1;
  35. }

 

  1. /*
  2.  * 函数功能:
  3.  * 在size大小的buf中,寻找满足特定格式,长度为packet_size的
  4.  * packet的个数;
  5.  * 显然,返回的值越大越可能是相应的格式(188/192/204)
  6.  */
  7. static int analyze(const uint8_t *buf, int size, int packet_size, int *index){
  8.   int stat[TS_MAX_PACKET_SIZE];
  9.   int i;
  10.   int x=0;
  11.   int best_score=0;
  12.  
  13.   memset(stat, 0, packet_size*sizeof(int));
  14.     
  15.   for (x=i=0; i < size-3; i++)
  16.   {
  17.     if ((buf[i] == 0x47) && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30))
  18.     {
  19.       stat[x]++;
  20.             
  21.       if (stat[x] > best_score)
  22.       {
  23.         best_score= stat[x];
  24.         if (index) 
  25.           *index= x;
  26.       }
  27.     }
  28.  
  29.     x++;
  30.     if (x == packet_size) 
  31.       x= 0; 
  32.   }
  33.     
  34.   return best_score;
  35. }

 

buf[i] == 0x47  

   其中的sync_byte固定为0x47,即上面的. 

!(buf[i+1] & 0x80)   

   由于transport_error_indicator为1的TS Packet实际有错误,

   表示携带的数据无意义, 这样的Packet显然没什么意义.

buf[i+3] & 0x30 

   对于adaptation_field_control, 如果取值为0x00,则表示为未来保留,现在不用.

 

这就是MPEG TS的侦测过程.

 

 

3. MPEG2-TS头解析

  1. #define NB_PID_MAX 8192
  2. #define MAX_SECTION_SIZE 4096
  3.         
  4. /* pids */
  5. #define PAT_PID 0x0000
  6. #define SDT_PID 0x0011
  7.         
  8. /* table ids */
  9. #define PAT_TID 0x00
  10. #define PMT_TID 0x02
  11. #define SDT_TID 0x42


 

  1. /*
  2.  * 函数功能:
  3.  * 
  4.  */
  5. int mpegts_read_header(AVFormatContext *s, AVFormatParameters *ap)
  6. {
  7.   /*
  8.    * MpegTSContext , 是为了解码不同容器格式所使用的私有数据,
  9.    * 只有在相应的诸如mpegts.c文件才可以使用的.
  10.    * 这样,增加了这个库的模块化.
  11.    */
  12.   MpegTSContext *ts = s->priv_data;
  13.   AVIOContext *pb = s->pb;
  14.   uint8_t buf[8*1024];
  15.   int len;
  16.   int64_t pos;
  17.  
  18.   /* read the first 8*1024 bytes to get packet size */
  19.   pos = avio_tell(pb);                   // 获取buf的当前位置
  20.   len = avio_read(pb, buf, sizeof(buf)); // 从pb->opaque中读取sizeof(buf)个字节到buf
  21.   if (len != sizeof(buf))
  22.     goto fail;
  23.  
  24.   /* 
  25.    * 获得TS包的实际长度
  26.    */
  27.   ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
  28.   if (ts->raw_packet_size <= 0) 
  29.   {
  30.     av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS\n");
  31.     ts->raw_packet_size = TS_PACKET_SIZE;
  32.   }
  33.  
  34.   ts->stream = s; 
  35.   ts->auto_guess = 0;
  36.   
  37.   if (s->iformat == &ff_mpegts_demuxer) 
  38.   {
  39.     /* normal demux */
  40.     /* first do a scaning to get all the services */
  41.     if (avio_seek(pb, pos, SEEK_SET) < 0)
  42.     {
  43.       av_log(s, AV_LOG_ERROR, "Unable to seek back to the start\n");
  44.     }
  45.  
  46.     /*
  47.      * 挂载了两个Section类型的过滤器,
  48.      * 其实在TS的两种负载中,section是PES的元数据,
  49.      * 只有先解析了section,才能进一步解析PES数据,因此先挂上section的过滤器。
  50.      */
  51.     mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
  52.     mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
  53.  
  54.     /*
  55.      */ 
  56.     handle_packets(ts, s->probesize / ts->raw_packet_size);
  57.  
  58.     /* if could not find service, enable auto_guess */
  59.     ts->auto_guess = 1;
  60.     av_dlog(ts->stream, "tuning done\n");
  61.     s->ctx_flags |= AVFMTCTX_NOHEADER;
  62.   } 
  63.   else 
  64.   {
  65.     ...
  66.   }
  67.  
  68.   avio_seek(pb, pos, SEEK_SET); 
  69.   return 0;
  70.  
  71. fail:
  72.   return -1;
  73. }

 

  1. MpegTSFilter *mpegts_open_section_filter(MpegTSContext* ts, 
  2.                                          unsigned int pid,
  3.                                          SectionCallback* section_cb, 
  4.                                          void* opaque,
  5.                                          int check_crc)
  6.   MpegTSFilter *filter;
  7.   MpegTSSectionFilter *sec; 
  8.  
  9.   av_dlog(ts->stream, "Filter: pid=0x%x\n", pid);
  10.  
  11.   if (pid >= NB_PID_MAX || ts->pids[pid])
  12.     return NULL;
  13.  
  14.   filter = av_mallocz(sizeof(MpegTSFilter));
  15.   if (!filter)
  16.     return NULL;
  17.  
  18.   ts->pids[pid] = filter;
  19.   filter->type = MPEGTS_SECTION;
  20.   filter->pid = pid; 
  21.   filter->last_cc = -1;
  22.   sec = &filter->u.section_filter;
  23.   sec->section_cb = section_cb;
  24.   sec->opaque = opaque;
  25.   sec->section_buf= av_malloc(MAX_SECTION_SIZE);
  26.   sec->check_crc = check_crc;
  27.  
  28.   if (!sec->section_buf) 
  29.   {
  30.     av_free(filter);
  31.     return NULL;
  32.   }
  33.  
  34.   return filter;
  35. }

对于这部分代码,需要分析数据结构的定义:

依次为:

 

    struct MpegTSContext;

               |

               V

    struct MpegTSFilter;

               |

               V

+--------------+---------------+

|                              |

V                              V

MpegTSPESFilter        MpegTSSectionFilter

 

就是struct MpegTSContext;中有NB_PID_MAX(8192)个TS的Filter,

而每个struct MpegTSFilter

  可能是 PES    的Filter

  或者是 Section的Filter。

 

为什么NB_PID_MAX 是 8192,

需要看TS的语法结构(ISO/IEC 138138-1 page 19):

 

  1. Syntax                          No. of bits         Mnemonic
  2. transport_packet(){ 
  3.   sync_byte                        8                 bslbf
  4.   transport_error_indicator        1                 bslbf
  5.   payload_unit_start_indicator     1                 bslbf
  6.   transport_priority               1                 bslbf
  7.   PID                              13                uimsbf
  8.   transport_scrambling_control     2                 bslbf
  9.   adaptation_field_control         2                 bslbf
  10.   continuity_counter               4                 uimsbf
  11.   if (adaptation_field_control=='10' || 
  12.       adaptation_field_control=='11' )
  13.   { 
  14.         adaptation_field() 
  15.   } 
  16.        
  17.   if (adaptation_field_control=='01' || 
  18.       adaptation_field_control=='11' ) 
  19.   { 
  20.     for (i=0;i<N;i++)
  21.     { 
  22.       data_byte                     8                bslbf
  23.     } 
  24.   } 
  25. }

而8192,是2^13=8192(PID)的最大数目,

为什么会有PES和Section的区分,更详细的可以参考ISO/IEC-13818-1.

 

 

挂载上了两种section过滤器,如下:

=========================================================================

PID                |Section Name           |Callback

=========================================================================

SDT_PID(0x0011)    |ServiceDescriptionTable|sdt_cb

                   |                       |

PAT_PID(0x0000)    |ProgramAssociationTable|pat_cb

=========================================================================

设计成回调函数,是为了在后面使用。

 

4. MPEG2-TS的包处理

 

 

  1. int handle_packets(MpegTSContext *ts, int nb_packets)
  2. {
  3.   AVFormatContext *s = ts->stream;
  4.   uint8_t packet[TS_PACKET_SIZE];
  5.   int packet_num, ret;
  6.      
  7.   ts->stop_parse = 0;
  8.   packet_num = 0;
  9.  
  10.   for ( ; ; ) 
  11.   {
  12.     packet_num++;
  13.     
  14.     if (nb_packets != 0 && packet_num >= nb_packets ||
  15.         ts->stop_parse > 1) 
  16.     {
  17.       ret = AVERROR(EAGAIN);
  18.       break;
  19.     }
  20.  
  21.     if (ts->stop_parse > 0)
  22.       break;
  23.         
  24.     ret = read_packet(s, packet, ts->raw_packet_size);
  25.     if (ret != 0)
  26.       return ret;
  27.  
  28.     ret = handle_packet(ts, packet);
  29.     if (ret != 0)
  30.       return ret;
  31.   } 
  32.   
  33.   return 0; 
  34. }

 

它的代码结构很简单:

handle_packets()

    |

    +->read_packet()

    |

    +->handle_packet()

        |

        +->write_section_data()

    

read_packet(),  很简单, 就是去找sync_byte(0x47),

handle_packet(),是真正处理数据的地方.它的代码如下:

  1. /* 
  2.  * 功能: handle one TS packet 
  3.  */
  4. int handle_packet(MpegTSContext *ts, const uint8_t *packet)
  5. {
  6.   AVFormatContext *s = ts->stream;
  7.   MpegTSFilter *tss;
  8.   int len, pid, cc, expected_cc, cc_ok, afc, is_start;
  9.   const uint8_t *p, *p_end;
  10.   int64_t pos;
  11.  
  12.   /* 获取该包的PID */
  13.   pid = AV_RB16(packet + 1) & 0x1fff;
  14.   if (pid && discard_pid(ts, pid))
  15.      return 0;
  16.  
  17.   /* 
  18.    * 是否是PES或者Section的开头
  19.    * 即syntax element: payload_unit_start_indicator 
  20.    */
  21.   is_start = packet[1] & 0x40;
  22.   tss = ts->pids[pid];
  23.  
  24.   /* 
  25.    * ts->auto_guess此时为0,因此不考虑下面的代码
  26.    */
  27.   if (ts->auto_guess && tss == NULL && is_start) 
  28.   {
  29.     add_pes_stream(ts, pid, -1);
  30.     tss = ts->pids[pid];
  31.   }
  32.   if (!tss)
  33.     return 0;
  34.  
  35.   /* 
  36.    * continuity check (currently not used) 
  37.    * 虽然检查,但不利用检查的结果
  38.    */
  39.   cc = (packet[3] & 0xf);
  40.   expected_cc = (packet[3] & 0x10) ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
  41.   cc_ok = (tss->last_cc < 0) || (expected_cc == cc);
  42.   tss->last_cc = cc;
  43.  
  44.   /* 
  45.    * 解析 adaptation_field_control 语法元素
  46.    * =======================================================
  47.    * 00 | Reserved for future use by ISO/IEC
  48.    * 01 | No adaptation_field, payload only
  49.    * 10 | Adaptation_field only, no payload
  50.    * 11 | Adaptation_field follwed by payload
  51.    * =======================================================
  52.    */ 
  53.   afc = (packet[3] >> 4) & 3;
  54.   p = packet + 4;
  55.   if (afc == 0) /* reserved value */
  56.     return 0; 
  57.   if (afc == 2) /* adaptation field only */ 
  58.     return 0;
  59.   if (afc == 3) 
  60.   {
  61.     /* 
  62.      * 跳过 adapation field 
  63.      * p[0]对应的语法元素为: adaptation_field_length
  64.      */
  65.     p += p[0] + 1;
  66.   }
  67.  
  68.   /* 
  69.    * if past the end of packet, ignore 
  70.    * p已近到达TS包中的有效负载的地方
  71.    */
  72.   p_end = packet + TS_PACKET_SIZE;
  73.   if (p >= p_end)
  74.     return 0;
  75.  
  76.   pos = avio_tell(ts->stream->pb);
  77.   ts->pos47= pos % ts->raw_packet_size;
  78.  
  79.   if (tss->type == MPEGTS_SECTION) 
  80.   {
  81.     /*
  82.      * 针对Section, 第一个字节对应的语法元素为:pointer_field(见2.4.4.1),
  83.      * 它表示在当前TS包中,从pointer_field开始到第一个section的第一个字节间的字节数。
  84.      * 当TS包中有至少一个section的起始时,
  85.      *    payload_unit_start_indicator = 1 且 TS负载的第一个字节为pointer_field;
  86.      *    pointer_field = 0x00时,表示section的起始就在这个字节之后;
  87.      * 当TS包中没有section的起始时, 
  88.      *    payload_unit_start_indicator = 0 且 TS负载中没有pointer_field;
  89.      */
  90.     if (is_start) 
  91.     {
  92.       /* pointer field present */
  93.       len = *p++;
  94.       if (p + len > p_end)
  95.         return 0;
  96.  
  97.       if (len && cc_ok) 
  98.       {
  99.         /* 
  100.          * write remaining section bytes 
  101.          * TS包的负载部分由Section A的End部分和Section B的Start组成,
  102.          * 先把Section A的End部分写入
  103.          */
  104.         write_section_data(s, tss, p, len, 0);
  105.  
  106.         /* check whether filter has been closed */
  107.         if (!ts->pids[pid])
  108.           return 0;
  109.       }
  110.       p += len;
  111.  
  112.       if (p < p_end) 
  113.       { 
  114.         /*
  115.          * 再将Section B的Start部分写入
  116.          */
  117.         write_section_data(s, tss, p, p_end - p, 1);
  118.       }
  119.     } 
  120.     else 
  121.     {
  122.       /* TS包负载仅是一个Section的中间部分部分,将其写入*/
  123.       if (cc_ok) 
  124.       {
  125.         write_section_data(s, tss, p, p_end - p, 0);
  126.       }
  127.     }
  128.   } 
  129.   else 
  130.   {
  131.     int ret;
  132.  
  133.     /* 
  134.      * 如果是PES类型,直接调用其Callback,
  135.      * 但显然,只有Section部分解析完成后才可能解析PES
  136.      */
  137.     // Note: The position here points actually behind the current packet.
  138.     if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
  139.         pos - ts->raw_packet_size)) < 0)
  140.       return ret;
  141.   }
  142.  
  143.   return 0;
  144. }

write_section_data()函数:

  反复收集buffer中的数据,指导完成相关Section的重组过程,

  然后调用之前注册的两个section_cb.

 

 

5. 节目指定信息的解析

 

  1. /*
  2.  * PAT(Program Association Table) 节目相关表
  3.  * 提供了节目号与PID值的对应关系
  4.  * 见ISO/IEC 13818-1 2.4.4.3 Table 2-30
  5.  */
  6. void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
  7.  
  8. /*
  9.  * PMT(Program Map Table) 节目映射表
  10.  * 提供了节目号与组成节目的元素之间的映射关系--或者称为"节目定义"
  11.  * 见ISO/IEC 13818-1 2.4.4.8 Table 2-33
  12.  */
  13. void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
  14.  
  15.  
  16. /*
  17.  * SDT(Transport Stream Description Table) TS描述表
  18.  * 用于定义TS描述子的表
  19.  * 见ISO/IEC 13818-1 2.4.4.12 Table 2-36
  20.  */
  21. void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)

 

6. 解析PES包

  1. /* 
  2.  * 见ISO/IEC 13818-1 2.4.3.6 Table 2-21
  3.  */
  4. int mpegts_push_data(MpegTSFilter* filter,
  5.                      const uint8_t* buf, 
  6.                      int buf_size, 
  7.                      int is_start,
  8.                      int64_t pos);

 

至此,整个TS层的解析基本完成。

共有 人打赏支持
粉丝 0
博文 4
码字总数 0
×
jane1009
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: