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_SECTIONfilter->type = MPEGTS_SECTION;//设置pidfilter->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 pesstatic 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_flagsr = pes->header + 9;pes->pts = AV_NOPTS_VALUE;pes->dts = AV_NOPTS_VALUE;//pts一共33bit,解析出ptsif ((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 #2392buf_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.rhkb.cn/news/247229.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache Shiro <= 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现

Apache Shiro < 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 漏洞名称 漏洞描述 在 1.2.5 之前的 Apache Shiro 中&#xff0c;当未为“记住我”功能配置密钥时&#xff0c;远程攻击者可以通过未指定…

聚醚醚酮(Polyether Ether Ketone)PEEK在粘接使用时可以使用UV胶水吗?要注意哪些事项?

一般情况下&#xff0c;聚醚醚酮&#xff08;Polyether Ether Ketone&#xff0c;PEEK&#xff09;是一种难以黏附的高性能工程塑料&#xff0c;而UV胶水通常不是与PEEK进行粘接的首选方法。PEEK表面的化学性质和高温性能使得它对常规胶水的附着性较低。然而&#xff0c;有一些…

vscode copilot怎么去掉提示代码(ghost text or incline completion)

原因&#xff1a;最近在刷题&#xff0c;被这个提示烦死了&#xff0c;记录一下怎么关掉&#xff0c;防止将来需要开启找不到了XD. 1.直接ctrlshiftp召唤设置 2.输入preferences: open usr settings找到如图第一个 3.去掉这个方框的勾选 ps直接在extension里disable不行呢 不…

DOM 型 XSS 攻击演示(附链接)

一、介绍 DOM&#xff08;Document Object Model&#xff09;型 XSS&#xff08;Cross-Site Scripting&#xff09;攻击是一种 Web 应用程序中的安全漏洞&#xff0c;其特点是攻击者成功地注入了恶意脚本&#xff0c;这些脚本在用户的浏览器中执行&#xff0c;从而导致恶意行为…

顺序表和链表【数据结构】【基于C语言实现】【一站式速通】

目录 顺序表 顺序表的优点 顺序表的实现 1.结构体的定义 2.初始化数组 3.插入数据 4.其余接口函数的实现 5.释放内存 顺序表的缺陷 单向链表 单向链表的优点 单向链表的实现 1.链表的定义 2.链表的初始化 3.其余接口函数的实现 5.释放内存 单向链表的缺陷 双…

ELK日志解决方案

ELK日志解决方案 ELK套件日志系统应该是Elasticsearch使用最广泛的场景之一了&#xff0c;Elasticsearch支持海量数据的存储和查询&#xff0c;特别适合日志搜索场景。广泛使用的ELK套件(Elasticsearch、Logstash、Kibana)是日志系统最经典的案例&#xff0c;使用Logstash和Be…

CVPR——Latex模版下载

CVPR官网 -> AuthorGuidelines 链接&#xff1a;AuthorGuidelines

怎么把几百M大小的视频做成二维码?扫码播放视频在线教程

怎么把几百M大小的视频做成一个二维码展示呢&#xff1f;通过二维码来作为视频的载体是现在很常用的一种手段&#xff0c;通过这种方式不仅成本比较低&#xff0c;而且传播速度也比较快&#xff0c;通过访问云端数据就可以播放视频。 视频二维码生成的方法一般会通过二维码生成…

微信小程序(二十)Vant组件库的配置

教程很详细&#xff0c;直接上过程 上一篇 官方文档也有&#xff0c;但是因为版本的更新&#xff0c;官方文档并没有跟着改变&#xff0c;这里我写一份最新版能用的教程 &#xff08;口头禅还是不能少的&#x1f923;&#x1f923;&#x1f923;&#xff09; 灵魂拷问&#xf…

【RT-DETR有效改进】反向残差块网络EMO | 一种轻量级的CNN架构(轻量化网络,参数量下降约700W)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文给大家带来的改进机制是反向残差块网络EMO,其的构成块iRMB在之前我已经发过了,同时进行了二次创新,本文的网络就是由iRMB组成的网络EMO,所以我们二次创新之后的iEMA也可以用于这个网络中,再次形成二次…

Kotlin快速入门5

Kotlin的继承与重写 kotlin的继承 Kotlin中所有类都继承自Any类&#xff0c;Any类是所有类的超类&#xff0c;对于没有超类型声明的类是默认超类&#xff08;Any 不是 java.lang.Object&#xff09;&#xff1a; class LearnKotlin // 默认继承自Any Any类默认提供三个函数…

Android 13.0 SystemUI下拉状态栏定制二 锁屏页面横竖屏时钟都居中功能实现二

1.前言 在13.0的系统rom定制化开发中,在关于systemui的锁屏页面功能定制中,由于在平板横屏锁屏功能中,时钟显示的很大,并且是在左旁边居中显示的, 由于需要和竖屏显示一样,所以就需要用到小时钟显示,然后同样需要居中,所以就来分析下相关的源码,来实现具体的功能 如图…

elementplus Dialog 对话框设置距离页面顶部的距离

默认为 15vh&#xff0c;当弹窗过于高的时候&#xff0c;这个距离其实是不合适的 <el-dialogv-model"dialogVisible"title"Tips"width"30%":before-close"handleClose"top"6vh"><span>This is a message</s…

KMP板子 前缀跳后缀

目录 原理&#xff1a; 板子&#xff1a; 原理&#xff1a; 出现重复【 存在部分前缀等于后缀 &#xff08;自己的前面一部分跟后面一部分一样的&#xff09; 】的时候&#xff0c;可以跳&#xff01; 来源&#xff1a;KMP算法中next数组的理解 - 知乎 (zhihu.com) &#xf…

JVM 笔记

JVM HotSpot Java二进制字节码的运行环境 好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;具有垃圾回收功能数组下标越界检查多态&#xff08;虚方法表&#xff09; JVM组成 类加载子系统&#xff08;Java代码转换为字节码&#xff09;运行时数据…

Qt6入门教程 12:QAbstractButton

目录 一.状态 二.信号 三.使用 1.自定义按钮 2.多选 3.互斥 QAbstractButton类实现了一个抽象按钮&#xff0c;并且让它的子类来指定如何处理用户的动作&#xff0c;并指定如何绘制按钮。QAbstractButton类是所有按钮控件的基类。 QAbstractButton提供…

Python笔记14-实战小游戏飞机大战(上)

文章目录 功能规划安装pygame绘制游戏窗口添加玩家飞机图像屏幕上绘制飞船代码重构驾驶飞船全屏模式射击 本示例源码地址 点击下载 功能规划 玩家控制一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船&#xff0c;还可使用空格键射击。游戏开始时&#xff…

1948-2022年金融许可信息明细数据

1948-2022年金融许可信息明细数据 1、时间&#xff1a;1948-2022年 2、来源&#xff1a;银监会&#xff08;银监会许可证发布系统&#xff09; 3、指标&#xff1a;来源表、机构编码、机构名称、所属银行、机构类型、业务范围、机构住所、地理坐标、行政区划代码、所属区县、…

Leetcode—2942. 查找包含给定字符的单词【简单】

2023每日刷题&#xff08;一零一&#xff09; Leetcode—2942. 查找包含给定字符的单词 实现代码 class Solution { public:vector<int> findWordsContaining(vector<string>& words, char x) {vector<int> ans;for(int i 0; i < words.size(); i)…

【Linux】动态库和静态库——动态库和静态库的打包和使用、gcc编译、拷贝到系统默认的路径、建立软连接

文章目录 动态库和静态库1.静态库和动态库的介绍2.静态库的打包和使用2.1生成静态库2.2使用静态库的三种方式2.2.1gcc编译2.2.2拷贝到系统默认的路径2.2.3建立软连接 3.动态库的打包和使用3.1生成动态库3.2使用动态库3.3解决加载不到动态库的方法 动态库和静态库 1.静态库和动…