FFMPEG解析ts流

news2024/9/30 13:31:06

 三篇相关联的文章: 

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.coloradmin.cn/o/1413606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业职能部门员工忙闲不均,如何调动积极性?

案例企业背景&#xff1a; 某企业隶属于中国航天科技集团公司&#xff0c;致力于光纤陀螺系统、微机电惯性系统、光纤传感系统等高新技术产品的研发。公司具有雄厚的新型惯导和光电传感技术基础&#xff0c;多年来开创了我国光纤陀螺技术在武器、卫星和载人飞船等多个任务上的…

深入浅出 diffusion(4):pytorch 实现简单 diffusion

1. 训练和采样流程 2. 无条件实现 import torch, time, os import numpy as np import torch.nn as nn import torch.optim as optim from torchvision.datasets import MNIST from torchvision import transforms from torch.utils.data import DataLoader from torchvision.…

Flink 集成 Debezium Confluent Avro ( format=debezium-avro-confluent )

博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维…

Hadoop-MapReduce-MRAppMaster启动篇

一、源码下载 下面是hadoop官方源码下载地址&#xff0c;我下载的是hadoop-3.2.4&#xff0c;那就一起来看下吧 Index of /dist/hadoop/core 二、上下文 在上一篇<Hadoop-MapReduce-源码跟读-客户端篇>中已经将到&#xff1a;作业提交到ResourceManager&#xff0c;那…

Bitbucket第一次代码仓库创建/提交/创建新分支/合并分支/忽略ignore

1. 首先要在bitbucket上创建一个项目&#xff0c;这个我没有权限创建&#xff0c;是找的管理员创建的。 管理员创建之后&#xff0c;这个项目给了我权限&#xff0c;我就可以创建我的代码仓库了。 2. 点击这个Projects下的具体项目名字&#xff0c;就会进入这样一个页面&#…

EG-2121CA (晶体振荡器 低抖动表面声波(SAW)振荡器)

在当今高度数字化的时代&#xff0c;稳定的信号传输显得尤为重要。若要实现信号的稳定传输&#xff0c;晶体振荡器必不可少。EG-2121CA&#xff0c;它是一款低抖动表面声波&#xff08;SAW&#xff09;振荡器设计的产品&#xff0c;凭借其出色的频率范围、稳定的电源电压和可靠…

网络安全全栈培训笔记(58-服务攻防-应用协议设备KibanaZabbix远控向日葵VNCTV)

第58天 服务攻防-应用协议&设备Kibana&Zabbix&远控向日葵&VNC&TV 知识点&#xff1a; 1、远程控制第三方应用安全 2、三方应用-向日葵&VNC&TV 3、设备平台-Zabbix&Kibanai漏洞 章节内容&#xff1a; 常见版务应用的安全测试&#xff1a; 1…

甲基四嗪-PEG4-叠氮,Methyltetrazine PEG4 azide,可以作为连接各种生物分子的桥梁

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;甲基四嗪-四聚乙二醇-叠氮&#xff0c;甲基四嗪-PEG4-叠氮&#xff0c;Methyltetrazine PEG4 azide &#xff0c;Methyltetrazine PEG4 N3 一、基本信息 产品简介&#xff1a;Methyltetrazine PEG4 azide is a comp…

简单记录一下如何安装python以及pycharm(图文教程)(可供福建专升本理工类同学使用)

本教程主要给不懂计算机的或者刚刚开始学习python的同学&#xff08;福建专升本理工类&#xff09;&网友学习使用&#xff0c;基础操作&#xff0c;比较详细&#xff0c;其他问题等待补充&#xff01; 安装Python 1.进入python官网&#xff08;https://www.python.org/&a…

10.Golang中的map

目录 概述map实践map声明代码 map使用代码 结束 概述 map实践 map声明 代码 package mainimport ("fmt" )func main() {// 声明方式1var map1 map[string]stringif map1 nil {fmt.Println("map1为空")}// 没有分配空间&#xff0c;是不能使用的// map…

关于在微信小程序中使用taro + react-hook后销毁函数无法执行的问题

问题&#xff1a; 在 taro中使用navigageTo() 跳转路由后hook中useEffect 的return函数没有执行 没有执行return函数 框架版本&#xff1a; tarojs: 3.6 react: 18.0 原因&#xff1a; 使用navigateTo() 跳转路由的话并不会销毁页面和组件&#xff0c;会加入一…

携程开源 基于真实请求与数据的流量回放测试平台、自动化接口测试平台AREX

携程开源 基于真实请求与数据的流量回放测试平台、自动化接口测试平台AREX 官网文档 基于真实请求与数据的流量回放测试平台、自动化接口测试平台AREX 这篇文章稍稍水一下&#xff0c;主要讲下部署过程里踩的坑&#xff0c;因为部署的过程主要是运维同学去处理了&#xff0c;我…

【Java与网络3】Java网络编程之初体验

我们平时极少使用Java来直接写网络通信相关的程序&#xff0c;一般都使用Tomcat Web服务或者Netty等框架来帮助我们做&#xff0c;不过呢&#xff0c;要想将技术学到家&#xff0c;我们研究一下基本的网络编程还是非常必要的&#xff0c;这样可以让我们将很多内容融会贯通&…

把批量M3U8网络视频地址转为MP4视频

在数字媒体时代&#xff0c;视频格式的转换已成为一项常见的需求。尤其对于那些经常处理网络视频的用户来说&#xff0c;将M3U8格式的视频转换为更常见的MP4格式是一项必备技能。幸运的是&#xff0c;现在有了固乔剪辑助手这款强大的工具&#xff0c;这一过程变得异常简单。下面…

单片机学习笔记---矩阵键盘

目录 矩阵键盘的介绍 独立按键和矩阵按键的相同之处&#xff1a; 矩阵按键的扫描 代码演示 代码模块化移植 Keil自定义模板步骤&#xff1a; 代码编写 矩阵键盘就是开发板上右下角的这个模块 这一节的代码是基于上一节讲的LCD1602液晶显示屏驱动代码进行的 矩阵键盘的介…

大数据学习之Flink算子、了解(Source)源算子(基础篇二)

Source源算子&#xff08;基础篇二&#xff09; 目录 Source源算子&#xff08;基础篇二&#xff09; 二、源算子&#xff08;source&#xff09; 1. 准备工作 2.从集合中读取数据 可以使用代码中的fromCollection()方法直接读取列表 也可以使用代码中的fromElements()方…

用户密码网络传输、保存方案分析

大华 1、大华19年的IPC&#xff0c;登录认证接口有两次&#xff0c;第一次请求算法所需数据&#xff0c;第二次传输摘要值&#xff0c;看样子是私有算法。 2、添加用户传输用户密码等敏感数据时&#xff0c;使用"RPAC-256"算法&#xff0c;应该是大华内部的私有算法。…

【从零到一】跑通CATR(二):在并行超算云上使用Cifar-10进行测试

从零到一配环境篇 由于今年要展开大量的编程工作&#xff0c;实验室在用的云计算平台是并行超算云&#xff0c;因此打算在寒假期间先熟悉一下超算云的环境&#xff0c;并从配套的文档和网上的教程开始&#xff0c;从零到一先跑通一个用于音视频分割的模型CATR。 以blog的形式…

docker-compose部署单机ES+Kibana

记录部署的操作步骤 准备工作编写docker-compose.yml启动服务验证部署结果 本次elasticsearch和kibana版本为8.2.2 使用环境&#xff1a;centos7.9 本次记录还包括&#xff1a;安装elasticsearch中文分词插件和拼音分词插件 准备工作 1、创建目录和填写配置 mkdir /home/es/s…

Vue3中的ref和shallowRef、reactive和shallowReactive

一&#xff1a;ref、reactive简介 ref和reactive是Vue3中定义响应式数据的一种方式。ref通常用来定义基础类型数据。reactive通常用来定义复杂类型数据。 二、shallowRef、shallowReactive简介 shallowRef和shallowReactive是Vue3中定义浅层次响应式数据的方式 三、Api使用对比…