FFMPEG解析ts流

 三篇相关联的文章: 

ffmpeg下HLS解析过程-CSDN博客
TS文件格式详解及解封装过程-CSDN博客

FFMPEG解析ts流-CSDN博客

一、简介

关于TS格式解析,可以参考《TS文件格式详解及解封装过程-CSDN博客》,本文主要代码部分解读。建议大家熟读iso13818-1,碰到问题很多情况是因为没有熟悉标准

二、主要结构体

相关结构关系

struct MpegTSContext; 
                     | 
                     V 
            struct MpegTSFilter; 
                     | 
                     V 
       +---------------+---------------+ 
       |                                    | 
       V                                   V 
       MpegTSPESFilter              MpegTSSectionFilter 测

2.1 ts文件过滤器的结构体

struct MpegTSFilter {//ts的过滤器
    int pid;
    int es_id;
    int last_cc; /* last cc code (-1 if first packet) */
    int64_t last_pcr;
    enum MpegTSFilterType type;//过滤器类型,分辨PES,PCR,SECTION
    union {
           MpegTSPESFilter pes_filter;
           MpegTSSectionFilter section_filter;
    }u;
};

2.2 section过滤器结构体

typedef struct MpegTSSectionFilter {
    int section_index;                    //section的索引
    int section_h_size;                    //头大小
    int last_ver;
    unsigned crc;
    unsigned last_crc;
    uint8_t *section_buf;                    //保存section数据
    unsigned int check_crc : 1;
    unsigned int end_of_section_reached : 1;
    SectionCallback *section_cb;                //回调函数
    void *opaque;                        //类似于类指针的东西
} MpegTSSectionFilter;

2.3 节目的结构体 

struct Program {
    unsigned int id; // program id/service id
    unsigned int nb_pids;
    unsigned int pids[MAX_PIDS_PER_PROGRAM];
 
 
    int pmt_found;        //标识pmt是否已经找到
};

 2.4 MpegTSContext

struct MpegTSContext {
    const AVClass *class;
    /* user data */
    AVFormatContext *stream;
    /** raw packet size, including FEC if present */
    int raw_packet_size;//ts格式长度
 
    int size_stat[3];//get_pcr探测ts三种格式分数用的
    int size_stat_count;//get_pcr探测ts三种格式次数
#define SIZE_STAT_THRESHOLD 10

    int64_t pos47_full;
 
    /** 如果为真, 所有的pid将会用来去寻找流 */
    int auto_guess;
 
    /** 对于每个ts包都进行精确的计算 */
    int mpeg2ts_compute_pcr;
 
    /** 修复 dvb teletext pts                                 */
    int fix_teletext_pts;

    AVPacket *pkt;
    /** to detect seek */
    int64_t last_pos;
 
    int skip_changes;
    int skip_clear;
 
    int scan_all_pmts;
 
    int resync_size;
    /******************************************/
    /* private mpegts data */
    /* scan context */
    /** structure to keep track of Program->pids mapping */
    unsigned int nb_prg;
    struct Program *prg;
 
    int8_t crc_validity[NB_PID_MAX];
    /** filters for various streams specified by PMT + for the PAT and PMT */
    MpegTSFilter *pids[NB_PID_MAX];
    int current_pid
};

二、demuxer相关对外接口

2.1 相关接口

AVInputFormat mpegtsraw_demuxer = {
    "mpegts",
    NULL_IF_CONFIG_SMALL(MPEG-TS (MPEG-2 Transport Stream)),
    sizeof(MpegTSContext),
    mpegts_probe,
    mpegts_read_header,
    mpegts_read_close,
    read_seek,
    mpegts_get_pcr,
    .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,
};

2.2 相关接口解析

2.2.1 mpegts_probe

故名思议,就是探测流是否是mpegts格式

/*

 * 函数功能:

* 分析流中是三种TS格式的哪一种

*/

static int mpegts_probe(AVProbeData *p)   
{   
#if 1   
    const int size= p->buf_size;   
    int score, fec_score, dvhs_score;   
#define CHECK_COUNT 10   
    if (size < (TS_FEC_PACKET_SIZE * CHECK_COUNT))   
        return -1;   
    score = analyze(p->buf, TS_PACKET_SIZE * CHECK_COUNT, TS_PACKET_SIZE, NULL);  
    dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE *CHECK_COUNT, TS_DVHS_PACKET_SIZE, NULL);  
    fec_score= analyze(p->buf, TS_FEC_PACKET_SIZE*CHECK_COUNT, TS_FEC_PACKET_SIZE, NULL);  

mpegts_probe被av_probe_input_format2调用,根据返回的score来判断那种格式的可能性最大。mpegts_probe调用了analyze函数

/*
 * 函数功能:
 * 在size大小的buf中,寻找满足特定格式,长度为packet_size的
 * packet的个数;
 * 显然,返回的值越大越可能是相应的格式(188/192/204)
 */
static int analyze(const uint8_t *buf, int size, int packet_size, int *index){
  int stat[TS_MAX_PACKET_SIZE];
  int i;
  int x=0;
  int best_score=0;

  memset(stat, 0, packet_size*sizeof(int));
  for (x=i=0; i < size-3; i++)
  {
    if ((buf[i] == 0x47) && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30))
    {
      stat[x]++;
            
      if (stat[x] > best_score)
      {
        best_score= stat[x];
        if (index) 
          *index= x;
      }
    }

    x++;
    if (x == packet_size) 
      x= 0; 
  }
    
  return best_score;
}

buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)是TS流同步开始的模式,

0x47是TS流同步的标志,记该模式为“TS流同步模式”

buf[i+1] & 0x80是传输错误标志

buf[i+3] & 0x30是adaptation_field_control,为0时表示为ISO/IEC未来使用保留,目前不存在这样的值。

stat数组变量存储的是“TS流同步模式”在某个位置出现的次数。返回的值越大越多是相应的格式(188/192/204)

size-3,这里为什么是-3呢?因为同步标志、传输错误标志和adaptation_field_control占了4个字节,查找的特定格式至少3 个Bytes,所以,至少最后3 个Bytes 不用查找(再找就超界了)

这就是MPEG TS的探测过程

2.2.2 mpegts_read_header

/*
 * 函数功能:
 * 
 */
int mpegts_read_header(AVFormatContext *s, AVFormatParameters *ap)
{
  /*
   * MpegTSContext , 是为了解码不同容器格式所使用的私有数据,
   * 只有在相应的诸如mpegts.c文件才可以使用的.
   * 这样,增加了这个库的模块化.
   */
  MpegTSContext *ts = s->priv_data;
  AVIOContext *pb = s->pb;
  uint8_t buf[8*1024];
  int len;
  int64_t pos;

  /* read the first 8*1024 bytes to get packet size */
  pos = avio_tell(pb);                   // 获取buf的当前位置,保存流的当前位置,便于检测操作完成后恢复到原来的位置,这样在播放的时候就不会浪费一段流
  len = avio_read(pb, buf, sizeof(buf)); // 从pb->opaque中读取8192个字节到buf
  if (len != sizeof(buf))
    goto fail;

  /* 
   * 获得TS包的实际长度,继续探测ts包的长度,这个步骤是不是重复了?

   */
  ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
  if (ts->raw_packet_size <= 0) 
  {
    av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS\n");
    ts->raw_packet_size = TS_PACKET_SIZE;
  }

  ts->stream = s; 
  ts->auto_guess = 0;
  //判断是否为MPEGTS解复用器,如果是则进行解复用

  if (s->iformat == &ff_mpegts_demuxer) 
  {
    /* normal demux */
    /* first do a scaning to get all the services */
    if (avio_seek(pb, pos, SEEK_SET) < 0)
    {
      av_log(s, AV_LOG_ERROR, "Unable to seek back to the start\n");
    }

    /*
     * 挂载了两个Section类型的过滤器,
     * 其实在TS的两种负载中,section是PES的元数据,
     * 只有先解析了section,才能进一步解析PES数据,因此先挂上section的过滤器。

     * 并设置sdt_cb,pat_cb
     */
    mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
    mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);

    /*

     *处理packets,处理的packet个数为s->probesize / ts->raw_packet_size

探测一段流,便于检测出SDT,PAT,PMT表
     */ 
    handle_packets(ts, s->probesize / ts->raw_packet_size);

    /* if could not find service, enable auto_guess */

  /*并把auto_guess置为1,auto_guess = 1, 则在handle_packet 的函数中只要发现 个PES 的pid 就 建立该PES 的stream*/

    ts->auto_guess = 1;
    av_dlog(ts->stream, "tuning done\n");

/*将 `AVFMTCTX_NOHEADER` 标志设置到 `s->ctx_flags` 中,表示header已经解析好,不需要再调用 `read_header` 函数了/
    s->ctx_flags |= AVFMTCTX_NOHEADER;
  } 
  else 
  {
    ...
  }

  avio_seek(pb, pos, SEEK_SET); //seek到pos最开始的位置
  return 0;

fail:
  return -1;
}

关于section的定义,先看结构图 

每个业务标都有section,而PMT的section包含了音视频流

 

一个表里可能有多个section,一个section可能包含多条流

SDT表当中会有节目的名子,提供商名子等等

2.2.2.1mpegts_open_section_filter函数分析

这个函数可以解释mpegts.c代码结构的精妙之处,PSI业务信息表的处理都是通过该函数挂载到MpegTSContext结构的pids字段上的。这样如果你想增加别的业务信息的表处理函数只要通过这个函数来挂载即可,体现了软件设计的著名的“开闭”原则。下面分析一下他的代码。

static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
                                         SectionCallback *section_cb, void *opaque,
                                         int check_crc)
{
    MpegTSFilter *filter;
    MpegTSSectionFilter *sec;
    dprintf(ts->stream, "Filter: pid=0x%x\n", pid);
    if (pid >= NB_PID_MAX || ts->pids[pid])
        return NULL;
    //给filter分配空间,挂载到MpegTSContext的pids上
    //就是该实例
    filter = av_mallocz(sizeof(MpegTSFilter));
    if (!filter)
        return NULL;
    //挂载filter实例
    ts->pids[pid] = filter;
    //设置filter相关的参数,因为业务信息表的分析的单位是段,
    //所以该filter的类型是MPEGTS_SECTION
    filter->type = MPEGTS_SECTION;
    
    //设置pid
    filter->pid = pid;
    filter->last_cc = -1;
    //设置filter回调处理函数
    sec = &filter->u.section_filter;
    sec->section_cb = section_cb;
    sec->opaque = opaque;
    //分配段数据处理的缓冲区,调用handle_packet函数后会调用
    //write_section_data将ts包中的业务信息表的数据存储在这儿,
    //直到一个段收集完成才交付上面注册的回调函数处理。
    sec->section_buf = av_malloc(MAX_SECTION_SIZE);
    sec->check_crc = check_crc;
    if (!sec->section_buf) {
        av_free(filter);
        return NULL;
    }
    return filter;
}
2.2.2.2handle_packets函数分析

handle_packets函数在两个地方被调用,一个是mpegts_read_header函数中,另外一个是mpegts_read_packet函数中,被mpegts_read_header函数调用是用来搜索PSI业务信息,nb_packets参数为探测的ts包的个数;在mpegts_read_packet函数中被调用用来搜索补充PSI业务信息和demux PES流,nb_packets为0,0不是表示处理的包的个数为0。

 ts->stop_parse当遇到stop_parse大于0时(解析完一个PES时此值为1),退出循环。

接下来分析最重要的地方handler_packets,简单看来

 handle_packets() 
        | 
        +->read_packet() 
        | 
        +->handle_packet() 
            | 
            +->write_section_data() 
   handle_packet是mpegts.c代码的核心,所有的其他代码都是为这个函数准备的。在调用该函数之前先调用read_packet函数获得一个ts包(通常是188bytes),然后传给该函数,packet参数就是TS包。

1、将ts包拼装为section,解析pat section
2、将ts包拼装为pes,依据video pid获取video pes

static int handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
    AVFormatContext *s = ts->stream;
    MpegTSFilter *tss;
    int len, pid, cc, cc_ok, afc, is_start;
    const uint8_t *p, *p_end;
    int64_t pos;
    //从TS包获得包的PID。
    pid = AV_RB16(packet + 1) & 0x1fff;
    if(pid && discard_pid(ts, pid))
        return 0;
    ##########################################################   
    是不是PES 或者Section 的开头(payload_unit_start_indicator)   
    ##########################################################
    is_start = packet[1] & 0x40;
    tss = ts->pids[pid];
    //ts->auto_guess在mpegts_read_header函数中被设置为0,
    //也就是说在ts检测过程中是不建立pes stream的。
    if (ts->auto_guess && tss == NULL && is_start) {
        add_pes_stream(ts, pid, -1, 0);
        tss = ts->pids[pid];
    }
    //mpegts_read_header函数调用handle_packet函数只是处理TS流的
    //业务信息(PAT,PDT等),因为并没有为对应的PES建立tss,所以tss为空,直接返回。
    //如果是pes,tss不为空,则继续
    if (!tss)
        return 0;
    /* continuity check (currently not used) */
    cc = (packet[3] & 0xf);
    cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
    tss->last_cc = cc;
  /* 
   * 解析 adaptation_field_control 语法元素
   * =======================================================
   * 00 | Reserved for future use by ISO/IEC
   * 01 | No adaptation_field, payload only
   * 10 | Adaptation_field only, no payload
   * 11 | Adaptation_field follwed by payload
   * =======================================================
   */ 
————————————————
    /* skip adaptation field */
    afc = (packet[3] >> 4) & 3;
    p = packet + 4;
    if (afc == 0) /* reserved value */
        return 0;
    if (afc == 2) /* adaptation field only */
        return 0;
    if (afc == 3) {
        /* skip adapation field p[0]对应的语法元素为: adaptation_field_length*/
        p += p[0] + 1;
    }
    ##########################################################   
    p已近 达TS 包中的有效负载的地方   
    ########################################################## 
    /* if past the end of packet, ignore */
    p_end = packet + TS_PACKET_SIZE;
    if (p >= p_end)
        return 0;
    pos = url_ftell(ts->stream->pb);
    ts->pos47= pos % ts->raw_packet_size;
    if (tss->type == MPEGTS_SECTION) {
    /*
     *
     * 针对Section, 第一个字节对应的语法元素为:pointer_field(见2.4.4.1),
     * 它表示在当前TS包中,从pointer_field开始到第一个section的第一个字节间的字节数。
     * 当TS包中有至少一个section的起始时,
     *    payload_unit_start_indicator = 1 且 TS负载的第一个字节为pointer_field;
     *    pointer_field = 0x00时,表示section的起始就在这个字节之后;
     * 当TS包中没有section的起始时, 
     *    payload_unit_start_indicator = 0 且 TS负载中没有pointer_field;
     */
        if (is_start) {
            //获取pointer field字段,
            //新的段从pointer field字段指示的位置开始
            len = *p++;
            if (p + len > p_end)
                return 0;
            if (len && cc_ok) {
                //这个时候TS的负载有两个部分构成:
                //1)从TS负载开始到pointer field字段指示的位置;
                //2)从pointer field字段指示的位置到TS包结束

                //1)位置代表的是上一个段的末尾部分。
                //2)位置代表的新的段开始的部分。
                //下面的代码是保存上一个段末尾部分数据,也就是
                //1)位置的数据。
            ########################################################   
            1).is_start == 1   
            len > 0   
            负载部分由A Section 的End 部分和B Section 的Start 组成,把A 的   
              End 部分写入   
              ########################################################  
                write_section_data(s, tss,
                                   p, len, 0);
                /* check whether filter has been closed */
                if (!ts->pids[pid])
                    return 0;
            }
            p += len;
            //保留新的段数据,也就是2)位置的数据。
            if (p < p_end) {
            ########################################################   
            2).is_start == 1   
            len > 0   
            负载部分由A Section 的End 部分和B Section 的Start 组成,把B  的   
              Start 部分写入   
              或者:   
              3).   
            is_start == 1   
            len == 0   
            负载部分仅是 个Section 的Start 部分,将其写入   
              ########################################################  
                write_section_data(s, tss,
                                   p, p_end - p, 1);
            }
        } else {
            //保存段中间的数据。
            if (cc_ok) {
        ########################################################   
        4).is_start == 0   
        负载部分仅是 个Section 的中间部分部分,将其写入  
         ######################################################## 
                write_section_data(s, tss,
                                   p, p_end - p, 0);
            }
        }
    } else {
        int ret;
           ##########################################################   
        若是是PES 类型,直接调用其Callback----mpegts_push_data,但显然,只有Section 部分   
         解析完成后才可能解析PES   
        ########################################################## 
        // Note: The position here points actually behind the current packet.
        if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
                                            pos - ts->raw_packet_size)) < 0)
            return ret;
    }
    return 0;
}

 write_section_data()函数则反复收集buffer中的数据,指导完成相关Section的重组过 
程,而后调用以前注册的两个section_cb: 
 

static void write_section_data(AVFormatContext *s, MpegTSFilter *tss1,   
                     const uint8_t *buf, int buf_size, int is_start)   
{   
    MpegTSSectionFilter *tss = &tss1->u.section_filter;   
    int len;   
    //buf 中是 个段的开始部分。   
    if (is_start) {   
        //将内容复制 tss->section_buf 中保存   
        memcpy(tss->section_buf, buf, buf_size);   
        //tss->section_index 段索引。   
        tss->section_index = buf_size;   
        //段的长度,如今还不知道,设置为-1   
        tss->section_h_size = -1;   
        //是否 达段的结尾。   
        tss->end_of_section_reached = 0;   
    } else {   
        //buf 中是段中间的数据。   
        if (tss->end_of_section_reached)   
            return;   
        len = 4096 - tss->section_index;   
        if (buf_size < len)   
            len = buf_size;  
        memcpy(tss->section_buf + tss->section_index, buf, len);   
        tss->section_index += len;   
    }   
    //若是条件知足,计算段的长度   
    if (tss->section_h_size == -1 && tss->section_index >= 3) {   
        len = (AV_RB16(tss->section_buf + 1) & 0xfff) + 3;   
        if (len > 4096)   
            return;   
        tss->section_h_size = len;   
    }   
    //判断段数据是否收集完毕,若是收集完毕,调用相应的回调函数处理该段。   
    if (tss->section_h_size  != -1 && tss->section_index >= tss->section_h_size) {   
        tss->end_of_section_reached = 1;   
        if (!tss->check_crc ||   
            av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1,   
                  tss->section_buf, tss->section_h_size) == 0)   
            tss->section_cb(tss1, tss->section_buf, tss->section_h_size);   
    }   
}   

Section_cb是一个回调函数,调用的函数有pat_cb,sdt_cb,pmt_cb,  Table_id 00代表pat表,02代表pmt表

到了这里还是一头雾水,还没看到解析PES,PTS这些在哪解析的?

处理每一个包,如果是section包,就调用 write_section_data ,这个函数里面如果一个PAT, PMT, SDT表已经构成,则会调用刚刚看到的pat_cb, pmt_cb, sdt_cb,分析到这里,已经不用再管section包了,只看pes包,所以一般会调用 tss->u.pes_filter.pes_cb ,这个函数指针到底是什么呢?在函数 add_pes_stream 里面可以看到, mpegts_open_pes_filter 函数的一个参数 mpegts_push_data 就是这里的 tss->u.pes_filter.pes_cb。

2.2.2.3 mpegts_push_data

一帧视频就是一个PES包,av_read_frame()就是从PES包队列中取出一个PES包。一个PES包是分配在连续的几个TS包中,所以如果我们要获得一帧数据,那么我们需要把连续的几个TS包里的数据全部取出来才能组合成一个PES。那我们怎么知道一个PES的开始和结尾呢?那我们还是一个个遍历每一个TS包,寻找包头里payload_unit_start_indicator为1包,这个标志位代表着是一个PES的开始,那么我从这开始,一直到下一个payload_unit_start_indicator为1,这中间的TS包组成起来就是一个PES。

/*
*函数功能
*解析PES包,得到时间戳、流索引、PES包长度等数据,并将这个PES包压入到PES包队列
*/
static int mpegts_push_data(MpegTSFilter *filter,
                            const uint8_t *buf, int buf_size, int is_start,
                            int64_t pos)
{
    PESContext *pes   = filter->u.pes_filter.opaque;
    MpegTSContext *ts = pes->ts;
    const uint8_t *p;
    int len, code;

    if (!ts->pkt)
        return 0;

    if (is_start) {
        if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) {  //当前包还没处理完,新的包到来。
            new_pes_packet(pes, ts->pkt);   //把pes的数据传给pkt(提交给上层)
            ts->stop_parse = 1; //当前包结束解析
        } else {
            reset_pes_packet_state(pes);    //新的开始
        }
        pes->state         = MPEGTS_HEADER;
        pes->ts_packet_pos = pos;
    }
    p = buf;
//4个字节的TS头,个PES的头,10为填充头
    while (buf_size > 0) {
        switch (pes->state) {
        case MPEGTS_HEADER: //解析pes包头部
if (pes->data_index == PES_START_SIZE) {

                /* we got all the PES orsection header. We can now

                 * decide */

                if (pes->header[0] == 0x00&& pes->header[1] == 0x00 &&

                    pes->header[2] == 0x01){

                    //前三个为00,00,01。PES起始位

                    /* it must be an MPEG-2 PESstream */

                    code = pes->header[3] |0x100;//stream_id

                    //得到pes长度

                    pes->total_size =AV_RB16(pes->header + 4);
                    /* 分配ES的空间 */
                    pes->buffer = av_malloc(pes->total_size+FF_INPUT_BUFFER_PADDING_SIZE);
/*****************PES解析***********************/

       /* PES packing parsing */

        case MPEGTS_PESHEADER:
        case MPEGTS_PESHEADER_FILL:
            if (pes->data_index == pes->pes_header_size) {
                const uint8_t *r;
                unsigned int flags, pes_ext, skip;

                flags = pes->header[7];//SYNTAX: PTS_DTS_flags
                r = pes->header + 9;
                pes->pts = AV_NOPTS_VALUE;
                pes->dts = AV_NOPTS_VALUE;
                //pts一共33bit,解析出pts
                if ((flags & 0xc0) == 0x80) {
                    pes->dts = pes->pts = ff_parse_pes_pts(r);
                    r += 5;
                } else if ((flags & 0xc0) == 0xc0) {
                    pes->pts = ff_parse_pes_pts(r);
                    r += 5;
                    pes->dts = ff_parse_pes_pts(r);
                    r += 5;
                }

case MPEGTS_PAYLOAD:
            if (pes->buffer) {
                if (pes->data_index > 0 &&
                    pes->data_index + buf_size > pes->total_size) { //加上新的数据超出一个pes包的长度(当前包结束)   ???
                    new_pes_packet(pes, ts->pkt);   //把pes的数据传给pkt(提交给上层)
                    pes->total_size = MAX_PES_PAYLOAD;
                    pes->buffer = av_buffer_alloc(pes->total_size +
                                                  FF_INPUT_BUFFER_PADDING_SIZE);
                    if (!pes->buffer)
                        return AVERROR(ENOMEM);
                    ts->stop_parse = 1; //当前包结束解析
                } else if (pes->data_index == 0 &&
                           buf_size > pes->total_size) {
                    // pes packet size is < ts size packet and pes data is padded with 0xff
                    // not sure if this is legal in ts but see issue #2392
                    buf_size = pes->total_size;
                }
/* 取出PES的负载数据组成TS流 */
                memcpy(pes->buffer->data + pes->data_index, p, buf_size);   //拷贝数据到pes->buffer(可能还没拷完整个pes packet的数据)
                pes->data_index += buf_size;
                /* emit complete packets with known packet size
                 * decreases demuxer delay for infrequent packets like subtitles from
                 * a couple of seconds to milliseconds for properly muxed files.
                 * total_size is the number of bytes following pes_packet_length
                 * in the pes header, i.e. not counting the first PES_START_SIZE bytes */
                if (!ts->stop_parse && pes->total_size < MAX_PES_PAYLOAD &&
                    pes->pes_header_size + pes->data_index == pes->total_size + PES_START_SIZE) {
                    ts->stop_parse = 1; //数据已经拷贝完,停止解析。
                    new_pes_packet(pes, ts->pkt);   //把pes的数据传给pkt(提交给上层)
                }
            }
            buf_size = 0;
            break;

ts->stop_parse = 1 意味着一个pes包构成了,所以上面的函数mpegts_read_packet就返回了,这样,一个pes包送上去了,再送到codec去解码,最后送去video或audio输出设置显示了。由些可以看到ts流和avi, mkv这些一样,都是一个容器,真真的数据都是包含在其中的一个一个的串流。

未完待续。。。。。

输入时间戳不边续时的处理机制
目的: 输入时间戳不连续,必须保证输出时间戳的连续。


1. 当视频时间戳连续,而音频时间戳不连续时
不强行修改时间戳,
用插入静音帧来实现重同步

三、问题

1、pat_cb/pmt_cb是在那里调用的?

答:write_section_data判断段数据是否收集完毕,若是收集完毕,调用相应的回调函数处理该段

2、mpegts_push_data是在哪调用的?

handle_packet的u.pes_filter.pes_cb对应mpegts_push_data

3、一个完整的pes包是什么时候完成解析的?

mpegts_push_data->MPEGTS_PAYLOAD->ts->stop_parse = 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/350956.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JaveWeb教程】(30)SpringBootWeb案例之《智能学习辅助系统》的详细实现步骤与代码示例(3)员工管理的实现

目录 SpringBootWeb案例033. 员工管理3.1 分页查询3.1.1 基础分页3.1.1.1 需求分析3.1.1.2 接口文档3.1.1.3 思路分析3.1.1.4 功能开发3.1.1.5 功能测试3.1.1.6 前后端联调 3.1.2 分页插件3.1.2.1 介绍3.1.2.2 代码实现3.1.2.3 测试 3.2 分页查询(带条件)3.2.1 需求3.2.2 思路分…

电流检测电路设计方案汇总

电流检测电路设计方案&#xff08;一&#xff09; 低端检流电路的检流电阻串联到地&#xff08;图1&#xff09;&#xff0c;而高端检流电路的检流电阻是串联到高电压端&#xff08;图2&#xff09;。两种方法各有特点&#xff1a;低端检流方式在地线回路中增加了额外的线绕电…

内网穿透natapp使用教程(Linux)

我的使用场景&#xff1a;在家访问学校服务器&#xff0c;由于不在一个局域网&#xff0c;所以需要使用内网穿透&#xff0c;我使用的是natapp。需要在有局域网的时候做好以下步骤。 &#xff08;natapp官网&#xff1a;https://natapp.cn/&#xff09; 1. 下载客户端 &#x…

开发微信小程序,将图片下载到相册的方法,saveImageToPhotosAlbum怎么用

在开发微信小程序的时候&#xff0c;经常能看到小程序里面有下载按钮&#xff0c;如何将小程序中的图片下载到手机相册中那&#xff0c;下面给大家说一下怎么做&#xff0c;代码如何去写。 一、到微信小程序后台开启“用户隐私保护指引” 1.进入小程序后台&#xff0c;侧拉拉到…

第16章_网络编程(网络通信要素,TCP与UDP协议,网络编程API,TCP网络编程,UDP网络编程,URL编程)

文章目录 第16章_网络编程本章专题与脉络1. 网络编程概述1.1 软件架构1.2 网络基础 2. 网络通信要素2.1 如何实现网络中的主机互相通信2.2 通信要素一&#xff1a;IP地址和域名2.2.1 IP地址2.2.2 域名 2.3 通信要素二&#xff1a;端口号2.4 通信要素三&#xff1a;网络通信协议…

[C#]winform部署yolov5实例分割模型onnx

【官方框架地址】 https://github.com/ultralytics/yolov5 【算法介绍】 YOLOv5实例分割是目标检测算法的一个变种&#xff0c;主要用于识别和分割图像中的多个物体。它是在YOLOv5的基础上&#xff0c;通过添加一个实例分割模块来实现的。 在实例分割中&#xff0c;算法不仅…

自动化软件开发常用代码分享!

随着科技的发展&#xff0c;自动化软件开发已经成为现代企业不可或缺的一部分&#xff0c;自动化软件可以帮助企业提高生产效率、降低成本、优化业务流程。 而在自动化软件开发中&#xff0c;代码是实现这些功能的基础&#xff0c;本文将分享一些常用的自动化软件开发代码&…

苍穹外卖-前端部分(持续更新中)

d 第二种&#xff1a;cmd中输入 vue ui进入图形化界面选择npm,vue2进行创建 先将创建的Vue框架导入Vsocde开发工具 然后ctrshiftp 输入npm 点击serve将项目启动 下这种写法跨域会报错&#xff1a; 解决方法&#xff1a;

虚拟机内使用 archinstall 安装 arch linux 2024.01.01

文章目录 [toc]前言碎语安装 arch linuxArchinstall languageMirrorsLocalesDisk configurationBootloaderSwapHostnameRoot passwordUser accountProfileAudioKernelsAdditional packagesNetwork configurationTimezoneAutomatic time syncOptional repositoriesInstall 进入桌…

OceanMind海睿思入选《2023大数据产业年度创新技术突破奖》,并蝉联多项图谱

近日&#xff0c;由数据猿和上海大数据联盟主办&#xff0c;上海市经济和信息化委员会、上海市科学技术委员会指导的“第六届金猿季&魔方论坛——大数据产业发展论坛”在上海成功举行&#xff0c;吸引了数百位业界精英的参与。中新赛克海睿思作为国内数字化转型优秀厂商代表…

基于springboot+vue的校园资料分享平台(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

include文件包含

include 文件包含利用日志文件什么是日志文件 之所以会burp抓包上传就可以成功的原因&#xff0c;是因为burp可以绕过url编码&#xff0c;导致写入日志文件中的代码没有进行编码&#xff0c;可以直接解析成php文件 声明&#xff1a;其中图片并非本人实操&#xff0c;而是直接截…

软件包管理:在CentOS 7中部署Tengine

目录 下载&#xff1a; 方法一&#xff1a; 方法二&#xff1a; 部署&#xff1a; 实验操作 下载&#xff1a; 方法一&#xff1a; 1、打开浏览器搜索tengine并点击官网 2、选择需要安装的版本并复制链接链接 标题栏处可以更改为中文界面 下滑选择版本单击下载 在远程连…

matlab appdesigner系列-图窗3-菜单

菜单&#xff0c;常用来作为软件界面上方的菜单栏&#xff0c;可用来集成多种功能 示例&#xff1a;创建菜单栏的file选项&#xff0c;设置open&#xff0c;save&#xff0c;exit三个选项 操作步骤&#xff1a; 1&#xff09;将菜单、图像拖拽到画布上&#xff0c;并修改对应…

【Midjourney】绘画风格关键词

1.松散素描(Loose Sketch) "Loose sketch"&#xff08;松散素描&#xff09;通常指的是一种艺术或设计中的手绘风格&#xff0c;其特点是线条和形状的表现相对宽松、自由&#xff0c;没有过多的细节和精确度。这样的素描通常用于表达创意、捕捉概念或者作为设计的初步…

《WebKit 技术内幕》学习之十五(5):Web前端的未来

5 Crosswalk项目 Crosswalk项目是由英特尔公司发起的一个开源项目&#xff0c;该项目基于WebKit&#xff08;Blink&#xff09;和Chromium等开源项目打造&#xff0c;其目的是提供一个跨不同操作系统的Web运行环境&#xff0c;包括Android、Tizen、Linux、Windows、MacOS等众多…

Docker容器(自定义镜像,Dockerfile,网桥,DockerCompose)

自定义镜像 镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。 构建步骤 镜像结构 Dockerfile 它是一个文本文件&#xff0c;包含很多指令&#xff0c;用指令来说明要执行什么操作来构建镜像。 官网&am…

滴滴开源小程序框架 Mpx 新特性:局部运行时能力增强

Mpx 是滴滴开源的一款增强型跨端小程序框架&#xff0c;自 2018 年立项开源以来如今已经进入第六个年头&#xff0c;在这六年间&#xff0c;Mpx 根植于业务&#xff0c;与业务共同成长&#xff0c;针对小程序业务开发中遇到的各类痛点问题提出了解决方案&#xff0c;并在滴滴内…

LlamaIndex和LangChain谁更胜一筹?

▼最近直播超级多&#xff0c;预约保你有收获 今晚直播&#xff1a;《LlamaIndex构建应用案例实战》 —1— LlamaIndex OR LangChain&#xff1f; LangChain 和 LlamaIndex 都是 AGI 时代新的应用程序开发框架&#xff0c;到底有什么区别&#xff1f; 第一、LangChain 是一个围…

C++ 之LeetCode刷题记录(十九)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 依旧是追求耗时0s的一天。 108. 将有序数组转换为二叉搜索树 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你…
最新文章