【FFmpeg实战】avformat_find_stream_info() 函数源码解析

news2024/12/24 8:13:56

转载自地址:https://cloud.tencent.com/developer/article/1873836

先来看一下 avformat_find_stream_info() 的头文件里的注释对该函数的介绍,本文我们基于 FFmpeg n4.2 版本的源码分析。

/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG. This
 * function also computes the real framerate in case of MPEG-2 repeat
 * frame mode.
 * The logical file position is not changed by this function;
 * examined packets may be buffered for later processing.
 * ...
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

注释里说这个方法通过读取媒体文件中若干个 packet 来获取流信息,对于 MPEG 这种没有 header 的文件格式比较有用,也可以计算像 MPEG-2 这种支持 repeat mode 的真实帧率。(MPEG-2 支持对于大量静止的画面设置 repeat mode,重复的帧不用编码和存储,可以减少体积)

另外提到这个函数不会修改逻辑文件位置,为了探测流信息所读取到的 packet 不会丢掉,会缓存下来为后面使用。

上面提到的流信息包括音频流的采样率、通道数等,视频包括视频的宽高、pixel format、码率、帧率等信息。

avformat_find_stream_info() 函数体有 600 行左右的代码,我们拆开来看,一些不太重要的部分,这里就直接跳过了。

avformat_find_stream_info() 函数源码解析

我们从这两个循环开始:

for (i = 0; i < ic->nb_streams; i++) {
    const AVCodec *codec;
    AVDictionary *thread_opt = NULL;
    st = ic->streams[i];
    avctx = st->internal->avctx;

    if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
        st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
/*            if (!st->time_base.num)
            st->time_base = */
        if (!avctx->time_base.num)
            avctx->time_base = st->time_base;
    }
    //省略代码
}

for (i = 0; i < ic->nb_streams; i++) {
#if FF_API_R_FRAME_RATE
    ic->streams[i]->info->last_dts = AV_NOPTS_VALUE;
#endif
    ic->streams[i]->info->fps_first_dts = AV_NOPTS_VALUE;
    ic->streams[i]->info->fps_last_dts  = AV_NOPTS_VALUE;
}

这两个循环我们可以先跳过,原因是如果在 avformat_open_input() 之后第一次调用 avformat_find_stream_info(),此时还没有 stream 的信息,所以 ic->nb_streams 为 0(nb_streams 是 stream 的个数),进不去循环体,所以我们可以直接跳过,不影响理解。

接下来这个看着像’死循环’的 for-loop,就是我们重点的分析对象了,为了代码的简洁,这里省略掉一些不影响我们理解整体逻辑的代码。既然是个‘死循环’,如果想跳出来就只有 break 和 goto 语句,我们看的时候多留意一下这两种 case. 我也会在代码的注释里加上 break 的标记,同时也会把一些需要注意的地方加上了我自己的理解(中文部分)。

for (;;) {
    int analyzed_all_streams;
    //break1: 检查是否被打断(或者说取消了继续探测),如果是,直接 break 退出
    if (ff_check_interrupt(&ic->interrupt_callback)) {
        ret = AVERROR_EXIT;
        av_log(ic, AV_LOG_DEBUG, "interrupted\n");
        break;
    }

    /* check if one codec still needs to be handled */
    //这个 for-loop 里做了一些对流信息的检测,如果循环能正常结束,
    //说明流信息的探测基本完成,这时 i == ic->nb_streams;
    //如果中间被 break 了,也就是说某个流的信息还没有完全得到,
    //此时 i < ic->nb_streams 的。
    //(第一次执行的时候,因为还没有流,所以会直接跳过)
    for (i = 0; i < ic->nb_streams; i++) {
        int fps_analyze_framecount = 20;
        int count;

        st = ic->streams[i];
        //codec信息是否完整
        if (!has_codec_parameters(st, NULL))
            break;
        /* If the timebase is coarse (like the usual millisecond precision
         * of mkv), we need to analyze more frames to reliably arrive at
         * the correct fps. */
        if (av_q2d(st->time_base) > 0.0005)
            fps_analyze_framecount *= 2;
        if (!tb_unreliable(st->internal->avctx))
            fps_analyze_framecount = 0;
        if (ic->fps_probe_size >= 0)
            fps_analyze_framecount = ic->fps_probe_size;
        if (st->disposition & AV_DISPOSITION_ATTACHED_PIC)
            fps_analyze_framecount = 0;
        /* variable fps and no guess at the real fps */
        count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ?
                   st->info->codec_info_duration_fields/2 :
                   st->info->duration_count;
        if (!(st->r_frame_rate.num && st->avg_frame_rate.num) &&
            st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            if (count < fps_analyze_framecount)
                break;
        }
        // Look at the first 3 frames if there is evidence of frame delay
        // but the decoder delay is not set.
        if (st->info->frame_delay_evidence && count < 2 && st->internal->avctx->has_b_frames == 0)
            break;
        if (!st->internal->avctx->extradata &&
            (!st->internal->extract_extradata.inited ||
             st->internal->extract_extradata.bsf) &&
            extract_extradata_check(st))
            break;
        if (st->first_dts == AV_NOPTS_VALUE &&
            !(ic->iformat->flags & AVFMT_NOTIMESTAMPS) &&
            st->codec_info_nb_frames < ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) ? 1 : ic->max_ts_probe) &&
            (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
             st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
            break;
    }
    analyzed_all_streams = 0;
    //上面提到的 missing_streams 起到作用了,判断是否所有流都找到了
    if (!missing_streams || !*missing_streams)
    //上面也提到了,i == ic->nb_streams 时,说明所有的流,以及流信息都没有问题了
    if (i == ic->nb_streams) {
        analyzed_all_streams = 1;
        /* NOTE: If the format has no header, then we need to read some
         * packets to get most of the streams, so we cannot stop here. */
        //如果是有头的封装格式,直接break退出了,如果是像 MPEG 这种没有头的封装格式,需要解析更多的 packets 来探测。
        if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) {
            /* If we found the info for all the codecs, we can stop. */
            ret = count;
            av_log(ic, AV_LOG_DEBUG, "All info found\n");
            flush_codecs = 0;
            //break2: 所有的流以及流信息都没有问题,正常退出 for 循环
            break;
        }
    }

    //走到这里,说明还有些流没探测出来,或者有些流信息还没完善。

    /* We did not get all the codec info, but we read too much data. */
    //虽然流信息还没完全探测出来,如果已读取到的大小超过了设定的 probesize,也会退出
    if (read_size >= probesize) {
        ret = count;
        for (i = 0; i < ic->nb_streams; i++)
            if (!ic->streams[i]->r_frame_rate.num &&
                ic->streams[i]->info->duration_count <= 1 &&
                ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
                strcmp(ic->iformat->name, "image2"))
                av_log(ic, AV_LOG_WARNING,
                       "Stream #%d: not enough frames to estimate rate; "
                       "consider increasing probesize\n", i);
        //break3: 读取到的大小超过了设定的 probesize,退出
        break;
    }

    //接下来就需要从网络/文件中读取 packet,这个函数里面做的事情很多,
    //拿 flv 来举例子🌰,执行完 read_frame_internal() 函数,
    //正常情况下,音视频对应的 AVStream 结构体会被创建,
    //并且 ic->nb_streams,也就是流的个数也会是正常的值,
    //比如如果包含音频和视频,nb_streams 的值会是 2。

    /* NOTE: A new stream can be added there if no header in file
     * (AVFMTCTX_NOHEADER). */
    ret = read_frame_internal(ic, &pkt1);

    //...

    pkt = &pkt1;

    st = ic->streams[pkt->stream_index];
    //读完packet,增加 read_size,下一轮循环会跟 probesize 做对比
    if (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC))
        read_size += pkt->size;

    avctx = st->internal->avctx;
    if (!st->internal->avctx_inited) {
        ret = avcodec_parameters_to_context(avctx, st->codecpar);
        if (ret < 0)
            goto find_stream_info_err;
        st->internal->avctx_inited = 1;
    }

    //处理和更新 dts: st->info->fps_first_dts 和 st->info->fps_last_dts
    if (pkt->dts != AV_NOPTS_VALUE && st->codec_info_nb_frames > 1) {
        //...
        /* update stored dts values */
        if (st->info->fps_first_dts == AV_NOPTS_VALUE) {
            st->info->fps_first_dts     = pkt->dts;
            st->info->fps_first_dts_idx = st->codec_info_nb_frames;
        }
        st->info->fps_last_dts     = pkt->dts;
        st->info->fps_last_dts_idx = st->codec_info_nb_frames;
    }
    if (st->codec_info_nb_frames>1) {
        int64_t t = 0;
        int64_t limit;

        //计算已经读取到的时间长度
        //codec_info_duration:已经取到的packet的总时长
        if (st->time_base.den > 0)
            t = av_rescale_q(st->info->codec_info_duration, st->time_base, AV_TIME_BASE_Q);
        //根据已经读取到的帧数/帧率来计算
        if (st->avg_frame_rate.num > 0)
            t = FFMAX(t, av_rescale_q(st->codec_info_nb_frames, av_inv_q(st->avg_frame_rate), AV_TIME_BASE_Q));
        //根据 fps_last_dts - fps_first_dts 来计算
        if (t == 0
            && st->codec_info_nb_frames>30
            && st->info->fps_first_dts != AV_NOPTS_VALUE
            && st->info->fps_last_dts  != AV_NOPTS_VALUE)
            t = FFMAX(t, av_rescale_q(st->info->fps_last_dts - st->info->fps_first_dts, st->time_base, AV_TIME_BASE_Q));


        //如果流信息都探测完(analyzed_all_streams = 1),limit = max_analyze_duration
        if (analyzed_all_streams)
            limit = max_analyze_duration;
        else if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE)
            limit = max_subtitle_analyze_duration;
        else limit = max_stream_analyze_duration;

        //如果当前已经读取到packet的总时长 >= 上面的 max_analyze_duration,退出
        if (t >= limit) {
            av_log(ic, AV_LOG_VERBOSE, "max_analyze_duration %"PRId64" reached at %"PRId64" microseconds st:%d\n",
                   limit,
                   t, pkt->stream_index);
            if (ic->flags & AVFMT_FLAG_NOBUFFER)
                av_packet_unref(pkt);
            //break4: 读取到packet的总时间 >= max_analyze_duration
            break;
        }

        //更新已经读取到的packet的总时长
        if (pkt->duration) {
            //...
            st->info->codec_info_duration += pkt->duration;
            //...
        }
    }

    if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
#if FF_API_R_FRAME_RATE
        ff_rfps_add_frame(ic, st, pkt->dts);
#endif
        if (pkt->dts != pkt->pts && pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
            st->info->frame_delay_evidence = 1;
    }
    if (!st->internal->avctx->extradata) {
        ret = extract_extradata(st, pkt);
        if (ret < 0)
            goto find_stream_info_err;
    }

    /* If still no information, we try to open the codec and to
     * decompress the frame. We try to avoid that in most cases as
     * it takes longer and uses more memory. For MPEG-4, we need to
     * decompress for QuickTime.
     *
     * If AV_CODEC_CAP_CHANNEL_CONF is set this will force decoding of at
     * least one frame of codec data, this makes sure the codec initializes
     * the channel configuration and does not only trust the values from
     * the container. */

    //到这里,调用 try_decode_frame() 对获取的 packet 进行音视频的解码,
    //正常情况下,会得到当前流的所有的解码期信息,
    //比如视频的宽高、pixel format,音频的 sample format, 采样率、通道数等。
    try_decode_frame(ic, st, pkt,
                     (options && i < orig_nb_streams) ? &options[i] : NULL);

    if (ic->flags & AVFMT_FLAG_NOBUFFER)
        av_packet_unref(pkt);

    //已经探测的帧数+1,count总数+1
    st->codec_info_nb_frames++;
    count++;
}

我们用伪代码来简化一下上面代码的主要逻辑:

for (;;) {
    if 所有stream 满足 has_codec_parameters(st, ..)
       || probe_size > 设置值 {
        break 退出;
    } else {
        //继续读取 packet
        read_frame_internal(ic, &pkt1);
        //尝试对读取到的 packet 解码
        try_decode_frame(ic, st, pkt, ...);
    }
}

下面我们详细地来看一下这三个函数的作用。

has_codec_parameters() 、read_frame_internal()、try_decode_frame() 函数的作用

上面提到的 has_codec_parameters() 函数,是一个很重要的函数,它来检测当前的音视频流信息是否完整。

static int has_codec_parameters(AVStream *st, const char **errmsg_ptr)
{
    AVCodecContext *avctx = st->internal->avctx;
    //...
    if (   avctx->codec_id == AV_CODEC_ID_NONE
        && avctx->codec_type != AVMEDIA_TYPE_DATA)
        FAIL("unknown codec");
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        if (!avctx->frame_size && determinable_frame_size(avctx))
            FAIL("unspecified frame size");
        if (st->info->found_decoder >= 0 &&
            avctx->sample_fmt == AV_SAMPLE_FMT_NONE)
            FAIL("unspecified sample format");
        if (!avctx->sample_rate)
            FAIL("unspecified sample rate");
        if (!avctx->channels)
            FAIL("unspecified number of channels");
        if (st->info->found_decoder >= 0 && !st->nb_decoded_frames && avctx->codec_id == AV_CODEC_ID_DTS)
            FAIL("no decodable DTS frames");
        break;
    case AVMEDIA_TYPE_VIDEO:
        if (!avctx->width)
            FAIL("unspecified size");
        if (st->info->found_decoder >= 0 && avctx->pix_fmt == AV_PIX_FMT_NONE)
            FAIL("unspecified pixel format");
        if (st->codecpar->codec_id == AV_CODEC_ID_RV30 || st->codecpar->codec_id == AV_CODEC_ID_RV40)
            if (!st->sample_aspect_ratio.num && !st->codecpar->sample_aspect_ratio.num && !st->codec_info_nb_frames)
                FAIL("no frame in rv30/40 and no sar");
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        if (avctx->codec_id == AV_CODEC_ID_HDMV_PGS_SUBTITLE && !avctx->width)
            FAIL("unspecified size");
        break;
    case AVMEDIA_TYPE_DATA:
        if (avctx->codec_id == AV_CODEC_ID_NONE) return 1;
    }

    return 1;
}

可以看到音频要检测是否拿到 frame size,sample format, sample rate, channels 等重要参数,视频则会检测视频的 width, pixel format 等等。

代码中,我们可以看到,为了获取音视频流信息,涉及到了两个重要的函数调用:

  1. read_frame_internal() 函数
  2. try_decode_frame() 函数

在讨论他们的作用之前,我们首先以 FLV 封装格式为例(以音频编码为 AAC,视频编码为 H.264 为例),来解释一下为什么需要这两个函数。

FLV 封装格式的大概示意图如下(为了简洁,省略了一些信息,详细细节参考 FLV specification),

img

我们可以这么来理解 FLV 封装格式,除了 header 之外,它里面包含了一系列的 Tag,可能是 Video Tag,也可能是 Audio Tag, 这个是 FLV 文档来定义的。其中每个 Tag 里面包含了一些描述性信息,以及对应的编码后的 AAC 或者 H.264 数据。如果我们分为两层来看的话,一层是 FLV Tag,一层是编码后的音视频数据。

了解这个之后,我们再来试着理解上面提到的 read_frame_internal() 函数和 try_decode_frame() 在这里的作用。

read_frame_internal() 函数本质上就是从网络中读取音视频的 packet,对应到 FLV 格式的话,其实就是读取第一层,也就是 也就是 FLV Tag 信息,从 tag 里可以读取 tag 的一些描述性信息, 这些描述性信息包括(参考自 FLV Specification):

音频:

  1. 编码格式,是 AAC 还是 MP3,还是 Linear PCM?
  2. Sample rate, 采样率,比如 48000
  3. 通道数,单声道还是双声道?(FLV最多支持两个声道)
  4. Bit depth

视频:

  1. 帧类型,关键帧还是中间帧?
  2. 编码格式,H.264 还是 H.263,还是 VP6 等其他格式?

那 read_frame_internal() 函数能拿到的就是上面的这些信息,这个信息是否够全呢?跟上面提到的 has_codec_parameters() 检测函数里的要求相比,确实还差了一些信息。比如音频的 sample format(比如 AV_SAMPLE_FMT_FLTP),它需要打开音频解码器之后才能确定,sample rate 等信息也是解码后得到的更加准确。

视频的宽高、pixel format 信息是存放在 H.264 流信息里,所以也需要解码之后才能获取到,所以,这里才需要 try_decode_frame() 函数去做解码的工作才能拿到完整的信息。

也就是说 read_frame_internal() 函数负责从第一层 FLV Tag 里获得流信息,try_decode_frame() 函数负责解码第二层的编码数据,来获取更多的流编码信息,最终汇总为完整的流信息,所以两处函数调用都是必要的。

OK,我们这里不展开 read_frame_internal() 函数 和 try_decode_frame() 函数内部的实现,有兴趣的小伙伴可以自己读读源码。

如何合理设置 probesize 来降低播放首屏时间?

在直播场景下,我们为了提高用户的体验,减少首屏时间,希望直播流是秒开的。这个时候我们会希望 avformat_find_stream_info() 函数在可以完成流信息探测完整的情况下,尽可能的早一些返回。根据前面的分析,我们可以知道,read_frame_internal() 函数的耗时依赖网络的情况,try_decode_frame() 函数负责解码,依赖软件/硬件执行的效率,这两者可能都会比较耗时,所以我们要尽可能的减少这两个方法的调用,从而减少 avformat_find_stream_info() 函数执行的时间。

avformat_find_stream_info() 退出的条件有很多个,probesize 、max_analyze_duration 和 fps_analyze_framecount 以及是其中的三个:

  1. 已读取的数据 > probesize
  2. 已读取视频帧播放时间长度 > max_analyze_duration

我们先来看 probesize 和 max_analyze_duration,这两个判断是强制性的,就是说不管是否获取到了完整的流信息,只要达到了这两个条件,就会退出。下面这段代码摘自 avformat_find_stream_info() 函数的前一部分:

...
int64_t max_analyze_duration = ic->max_analyze_duration;
...
int64_t probesize = ic->probesize;
int *missing_streams = av_opt_ptr(ic->iformat->priv_class, ic->priv_data, "missing_streams");


if (!max_analyze_duration) {
    max_stream_analyze_duration =
    max_analyze_duration        = 5*AV_TIME_BASE;
}

这两个值用来设置函数探测流信息的最大 size,以及最大时长。probesize 默认值是 5,000,000 bytes 也就是 5MB 大小,max_analyze_duration 默认值是 5*AV_TIME_BASE, 也就是 5,000,000 micro-seconds 也就是 5秒。

PS: 另外 missing_streams 是个指向 int 的指针,*missing_streams 只要是 > 0,就说明还有某些流没探测到,这个后面的循环有个关键判断会用到。(假设我们要播放的是flv流,有兴趣的同学可以到 flvdec.c 文件搜索一下这个属性)

默认值对直播来说都蛮大的,不过他们都支持在调用 avformat_find_stream_info() 之前手动地设置。那设置多少比较合适呢?

通过我们刚才的分析,理论上获取到一帧视频和一帧音频,并对他们解码,我们就可以拿到完整的音视频信息。所以理论上我们把 probe size 设置为第一次获取完音频和视频帧时所需读取的长度即可。当然这是理想的情况,实际中有诸多意外因素。比如第一帧视频帧通常是关键帧会比较大,对于不同的码率的流,大小差异会比较大,还有些 CDN 下发的流中,前面放了很多的视频帧之后,才有一个音频帧(这种最好要求 CDN 厂商修改)。

可见 probe size 如果设置的太大会导致首帧时间比较长,设置的太小,可能一些 case 下会获取流信息失败,所以需要根据自己流信息的情况(特别是码率,因为码率会影响音频、视频帧的大小)去设置。我们目前用的一种策略是,设置 probe size 为一个针对我们目前直播稍大于上面所说的长度的一个值,应对大部分 case,对于一小部分 case,比如可能要播放外部流,我们除了支持服务器动态配置之外,还会在失败之后,对 probe size 和 max_analyze_duration 乘以一个系数之后再重试。

PS: 对于 FLV 格式,具体到理论上的最小的 probe size 的大小,大概等于 >smallest probe_size = sizeof(FLV header) + sizeof(script tag) + sizeof(audio tag of AAC sequence header) + sizeof(audio tag of AAC raw data) + sizeof(video tag of H.264 sequence header) + sizeof(video tag of H.264 NALU data)

>>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864 
>>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群: 739729163  领取

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

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

相关文章

Apikit 自学日记:创建 API 文档

Apikit 中一共有5种创建API文档的方式&#xff1a; 新建API文档 导入API文档&#xff0c;详情可查看《导入、导出API文档》 从模板添加API文档&#xff0c;详情可查看《API文档模板》 自动生成API文档&#xff0c;详情可查看《自动生成API文档》 IDEA插件注释同步API文档 …

linux 在线安装 Redis

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; &#x1f495;&#x1f495; 感兴趣的同学可以收…

生成式AI掀起产业智能化新浪潮|爱分析报告

报告摘要 大模型支撑的生成式AI&#xff0c;让人类社会有望步入通用人工智能时代&#xff0c;拥有广阔的应用前景&#xff0c;有望赋能千行百业。当前生成式AI的落地整体处于初级阶段&#xff0c;不同模态的落地时间表差异明显&#xff0c;企业需求主要集中在数字化程度高、容…

地平线旭日x3派部署yolov8

地平线旭日x3派部署yolov8 总体流程1.导出onnx模型导出YOLOV8_onnxruntime.py验证onnxutils.py 2.在开发机转为bin模型2.1准备数据图片2.2转换必备的yaml文件2.3开始转换 3.开发机验证**quantized_model.onnx4.板子运行bin模型 资源链接 总体流程 1.导出onnx模型 导出 使用y…

03 | 事务隔离:为什么你改了我还看不见?

以下出自 《MySQL 实战 45 讲》 03 | 事务隔离&#xff1a;为什么你改了我还看不见&#xff1f; 隔离性与隔离级别 当数据库上有多个事务同时执行的时候&#xff0c;就可能出现脏读&#xff08;dirty read&#xff09;、不可重复读&#xff08;non-repeatable read&#xff0…

搜索功能全流程解析

在产品中一般会分布着大大小小的搜索&#xff0c;以便提升用户的信息获取效率和信息消费的能力。本文作者全流程角度&#xff0c;对搜索功能进行了讲解&#xff0c;并从搜索流程中寻找提升体验的触点&#xff0c;一起来看一下吧。 在产品中因多功能诉求和业务复杂性等因素&…

《Pytorch深度学习和图神经网络(卷 1)》学习笔记——第三章

学习基于如下书籍&#xff0c;仅供自己学习&#xff0c;用来记录回顾&#xff0c;非教程。 <PyTorch深度学习和图神经网络&#xff08;卷 1&#xff09;——基础知识>一书配套代码&#xff1a; https://github.com/aianaconda/pytorch-GNN-1st 百度网盘链接&#xff1a;…

vite优化

1.利用 rollup-plugin-analyzer 插件进行进行代码体积分析&#xff0c;从而优化你的代码。 根据项目体积分析&#xff0c;进行接下来的优化&#xff1a; &#xff08;一&#xff09;使用unplugin-vue-components插件按需加载antd vue 组件&#xff1a; 使用步骤 1、安装插件…

6.18 、Java初级:锁

1 同步锁 1.1 前言 经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象. 上节笔记点这里-进程与线程笔记 我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件: 在多线程程序中 有共享数据 多条语句操作共享数据 多…

GPT-4 的创造力全方位持平或碾压人类 | 一项最新研究发现

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 最近&#xff0c;一项有关 GPT-4 的创造力思维测试火了。来自蒙大拿大学和 UM Western 大学的研究团队发现&#xff0c;GPT-4 在 Torrance 创造性思维…

Sharding-JDBC之RangeShardingAlgorithm(范围分片算法)

目录 一、简介二、maven依赖三、数据库3.1、创建数据库3.2、创建表 四、配置&#xff08;二选一&#xff09;4.1、properties配置4.2、yml配置 五、范围分片算法六、实现6.1、实体层6.2、持久层6.3、服务层6.4、测试类6.4.1、保存订单数据6.4.2、根据时间范围查询订单 一、简介…

还在等待本地渲染?云渲染才是真的省时省心又省钱!

可能很多设计师会觉得本地渲染效果图或动画更灵活&#xff0c;而且没有什么额外的附加费用&#xff0c;但其实不然&#xff01;当你面对多个大型或紧急的项目时&#xff0c;本地渲染就“慌”了。 接下来我将全面对比“本地渲染”和“云渲染”&#xff0c;相信还在等待本地渲染…

黑客常用cmd命令(window版)

1、ping命令 ping命令是一个常用的网络工具&#xff0c;用来测试和诊断网络连接状况。通过发送ICMP&#xff08;Internet控制消息协议&#xff09;数据包到目标主机&#xff0c;并接收回复的数据包&#xff0c;可以测量目标主机的可达性、平均响应时间等指标。 在Windows操作…

前后端实现:行为验证码---文字点选

最近接到一个新的需求&#xff0c;由于客户是内网&#xff0c;你能使用腾讯的验证码了&#xff0c;需要改为前后端实现。 具体的代码已经提交git 项目效果图&#xff1a; 使用的技术栈&#xff1a;vitevue3ts git地址&#xff1a;https://github.com/susanliy/point_captcha…

TCP/IP协议是什么?

78. TCP/IP协议是什么&#xff1f; TCP/IP协议是一组用于互联网通信的网络协议&#xff0c;它定义了数据在网络中的传输方式和规则。作为前端工程师&#xff0c;了解TCP/IP协议对于理解网络通信原理和调试网络问题非常重要。本篇文章将介绍TCP/IP协议的概念、主要组成部分和工…

《程序喵》项目跨域问题解决思路

跨域问题&#xff1a;由于浏览器的 同源策略 限制&#xff0c;当一个请求url的协议、域名、端口号三者之间有任意一个与当前的url不同即为跨域。 同源策略是一种约定&#xff0c;它是浏览器中最核心也最基本的安全功能。同源策略会阻止一个域的 Javascript 脚本和另一个域的内…

举例说明梯度下降算法与最小二乘法的区别

梯度下降算法和最小二乘法都是用于求解线性回归问题中参数的优化方法。我们可以通过一个简单的例子来说明它们之间的区别。 假设我们有以下数据点&#xff1a;(1, 2)&#xff0c;(2, 3)&#xff0c;(3, 4)&#xff0c;(4, 5)&#xff0c;我们希望找到一条最佳拟合线 y wx b&a…

Android 中Looper机制详解

版本基于&#xff1a;Android R 0. 前言 在《Android 基于Handler 剖析消息机制》一文中&#xff0c;以 Handler 类为起点详细分析了异步通信&#xff0c;分析了Java 端 Handler 与Looper、MessageQueue、Message 之前的通信关系。 框架如下&#xff1a; 在Java 端的 Looper …

2. IO 流原理及流的分类

2.1 Java IO 原理 • Java 程序中&#xff0c;对于数据的输入/输出操作以“流(stream)” 的方式进行&#xff0c;可以看做是一种数据的流动。 • I/O 流中的 I/O 是 Input/Output 的缩写&#xff0c; I/O 技术是非常实用的技术&#xff0c;用于处理设备之间的数据传输。如读/写…

浅谈 Cache

1. Cache的历史 在科研领域&#xff0c;C. J. Conti等人于1968年在描述360/85和360/91系统性能差异时最早引入了高速缓存&#xff08;cache&#xff09;一词。Alan Jay Smith于1982年的一篇论文中引入了空间局部性和时间局部性的概念。 Mark Hill在1987年发明了3C&#xff08…