FFmpeg的HEVC解码器源代码学习笔记-1

news2024/12/24 8:44:31

一直想写一个HEVC的码流解析工具,看了雷神264码流解析工具,本来想尝试模仿写一个相似的265码流分析工具,但是发现265的解码过程和结构体和264的不太一样,很多结构体并没有完全暴露出来,没有想到很好的方法获得量化参数,运动向量等这些信息。想着从头学习一下ffmpeg中的265解码函数,再来获取解码后的量化参数,运动向量等一系列信息,再做码流分析。

这里主要学习HEVC的解析函数代码

众所周知,解码器是标准的,因此只要按照官方给定的解码流程对码流进行解码就能正常解码。(以前不知道下图这种语法元素描述的作用,最近才知道解码器的代码完全和描述对的上,就是伪代码形式,难怪雷神说parse vps,sps,pps的代码没什么技术含量)

ffmpeg为了封装成多种编解码器,是提供了包装函数,这些函数根据提供编解码标准来选择解码器。我主要看的是HEVC解码,不过其他的解码器也相似。

参考我前面的博客,经过一堆初始化函数后,首先进行的是av_parser_parse2()函数,对码流进行解析。主要是VPS,SPS,PPS进行解码。

av_parser_parse2( )

//此代码位于libavcodec\parser.c中
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts, int64_t pos)
{
    int index, i;
    uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];

    av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);

    /* Parsers only work for the specified codec ids. */
    av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||
               avctx->codec_id == s->parser->codec_ids[1] ||
               avctx->codec_id == s->parser->codec_ids[2] ||
               avctx->codec_id == s->parser->codec_ids[3] ||
               avctx->codec_id == s->parser->codec_ids[4] ||
               avctx->codec_id == s->parser->codec_ids[5] ||
               avctx->codec_id == s->parser->codec_ids[6]);

    if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
        s->next_frame_offset =
        s->cur_offset        = pos;
        s->flags            |= PARSER_FLAG_FETCHED_OFFSET;
    }

    if (buf_size == 0) {
        /* padding is always necessary even if EOF, so we add it here */
        memset(dummy_buf, 0, sizeof(dummy_buf));
        buf = dummy_buf;
    } else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
        /* add a new packet descriptor */
        i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
        s->cur_frame_start_index = i;
        s->cur_frame_offset[i]   = s->cur_offset;
        s->cur_frame_end[i]      = s->cur_offset + buf_size;
        s->cur_frame_pts[i]      = pts;
        s->cur_frame_dts[i]      = dts;
        s->cur_frame_pos[i]      = pos;
    }

    if (s->fetch_timestamp) {
        s->fetch_timestamp = 0;
        s->last_pts        = s->pts;
        s->last_dts        = s->dts;
        s->last_pos        = s->pos;
        ff_fetch_timestamp(s, 0, 0, 0);
    }
    /* WARNING: the returned index can be negative */
    // 从这里进入码流解析,parser_parse是一个函数指针,在hevc的解码过程中会指向hevc_parse()函数,
    // 此代码位于libavcodec\hevc_parser.c中,后续其他的解析函数也均在此文件内。
    // s->parser在av_parser_init()函数中被赋值。
    // 通过av_parser_iterate()遍历来寻找对应的解析结构体,这个函数的定义位于libavcodec\parser.c中,这个文件里面的parser_list通过#include "libavcodec/parser_list.c"导入,
    // 但是没有找到原始文件里面有这个文件,后面发现这个文件是在configure后生成的,类似的生成文件还包括codec_list.c
    index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
                                    poutbuf_size, buf, buf_size);
    // 这个函数里面会调用对应编码标准的解析函数,这里会调用hevc_parse()函数。
    av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name
    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        FILL(field_order);
        FILL(coded_width);
        FILL(coded_height);
        FILL(width);
        FILL(height);
    }

    /* update the file pointer */
    if (*poutbuf_size) {
        /* fill the data for the current frame */
        s->frame_offset = s->next_frame_offset;

        /* offset of the next frame */
        s->next_frame_offset = s->cur_offset + index;
        s->fetch_timestamp   = 1;
    } else {
        /* Don't return a pointer to dummy_buf. */
        *poutbuf = NULL;
    }
    if (index < 0)
        index = 0;
    s->cur_offset += index;
    return index;
}

hevc_parse( )

hevc_parse()位于libavcodec\hevc_parser.c,这个函数主要是解析额外数据,获得完整帧数据,并进行解析。

static int hevc_parse(AVCodecParserContext *s, AVCodecContext *avctx,
                      const uint8_t **poutbuf, int *poutbuf_size,
                      const uint8_t *buf, int buf_size)
{
    int next;
    HEVCParserContext *ctx = s->priv_data;
    ParseContext *pc = &ctx->pc;
    int is_dummy_buf = !buf_size;
    const uint8_t *dummy_buf = buf;
    // 解析额外的数据,主要包含用于存储一些对于编解码过程非必需,但又是非常有用的附加信息。这些信息通常是特定于编码的,用于初始化编解码器。
    if (avctx->extradata && !ctx->parsed_extradata) {
        ff_hevc_decode_extradata(avctx->extradata, avctx->extradata_size, &ctx->ps, &ctx->sei,
                                 &ctx->is_avc, &ctx->nal_length_size, avctx->err_recognition,
                                 1, avctx);
        ctx->parsed_extradata = 1;
    }

    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
        next = buf_size;
    } else {
        next = hevc_find_frame_end(s, buf, buf_size);//寻找帧的起始标记,也即#define START_CODE 0x000001 ///< start_code_prefix_one_3bytes,可以用UltraEdit查看码流的16进制表示,这样更清晰知道解码的完整流程

		// 这里将传递的码流组成为一个完整帧数据
        if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
            *poutbuf      = NULL;
            *poutbuf_size = 0;
            return buf_size;
        }
    }

    is_dummy_buf &= (dummy_buf == buf);

    if (!is_dummy_buf)// 这里开始进行解析
        parse_nal_units(s, buf, buf_size, avctx);
    *poutbuf      = buf;
    *poutbuf_size = buf_size;
    return next;
}

parse_nal_units()

parse_nal_units()函数位于libavcodec\hevc_parser.c,里面主要根据nal的类型分别对VPS,SPS,PPS,SEI等信息进行解析

static int parse_nal_units(AVCodecParserContext *s, const uint8_t *buf,
                           int buf_size, AVCodecContext *avctx)
{
    HEVCParserContext *ctx = s->priv_data;
    HEVCParamSets *ps = &ctx->ps;
    HEVCSEI *sei = &ctx->sei;
    int ret, i;

    /* set some sane default values */
    s->pict_type         = AV_PICTURE_TYPE_I;
    s->key_frame         = 0;
    s->picture_structure = AV_PICTURE_STRUCTURE_UNKNOWN;

    ff_hevc_reset_sei(sei);

    ret = ff_h2645_packet_split(&ctx->pkt, buf, buf_size, avctx, ctx->is_avc,
                                ctx->nal_length_size, AV_CODEC_ID_HEVC, 1, 0);
    if (ret < 0)
        return ret;

    for (i = 0; i < ctx->pkt.nb_nals; i++) {
        H2645NAL *nal = &ctx->pkt.nals[i];
        GetBitContext *gb = &nal->gb;

        if (nal->nuh_layer_id > 0)
            continue;

        switch (nal->type) {
        case HEVC_NAL_VPS:
            ff_hevc_decode_nal_vps(gb, avctx, ps);
            break;
        case HEVC_NAL_SPS:
            ff_hevc_decode_nal_sps(gb, avctx, ps, 1);
            break;
        case HEVC_NAL_PPS:
            ff_hevc_decode_nal_pps(gb, avctx, ps);
            break;
        case HEVC_NAL_SEI_PREFIX:
        case HEVC_NAL_SEI_SUFFIX:
            ff_hevc_decode_nal_sei(gb, avctx, sei, ps, nal->type);
            break;
        case HEVC_NAL_TRAIL_N:
        case HEVC_NAL_TRAIL_R:
        case HEVC_NAL_TSA_N:
        case HEVC_NAL_TSA_R:
        case HEVC_NAL_STSA_N:
        case HEVC_NAL_STSA_R:
        case HEVC_NAL_BLA_W_LP:
        case HEVC_NAL_BLA_W_RADL:
        case HEVC_NAL_BLA_N_LP:
        case HEVC_NAL_IDR_W_RADL:
        case HEVC_NAL_IDR_N_LP:
        case HEVC_NAL_CRA_NUT:
        case HEVC_NAL_RADL_N:
        case HEVC_NAL_RADL_R:
        case HEVC_NAL_RASL_N:
        case HEVC_NAL_RASL_R:
            if (ctx->sei.picture_timing.picture_struct == HEVC_SEI_PIC_STRUCT_FRAME_DOUBLING) {
                s->repeat_pict = 1;
            } else if (ctx->sei.picture_timing.picture_struct == HEVC_SEI_PIC_STRUCT_FRAME_TRIPLING) {
                s->repeat_pict = 2;
            }
            ret = hevc_parse_slice_header(s, nal, avctx);
            if (ret)
                return ret;
            break;
        }
    }
    /* didn't find a picture! */
    av_log(avctx, AV_LOG_ERROR, "missing picture in access unit with size %d\n", buf_size);
    return -1;
}

这里主要看了VPS,SPS和PPS的函数
VPS,SPS和PPS的函数功能都相差不大,由于代码过长就不贴了,这些函数都位于libavcodec\hevc_ps.c中,这里以VPS的解析函数ff_hevc_decode_nal_vps()为例。
下图为VPS的语法元素描述(《新一代高效视频编码 H.265/HEVC:原理、标准与实现》 每一章都有对应的语法描述,或者去官方文件里面查看)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int ff_hevc_decode_nal_vps(GetBitContext *gb, AVCodecContext *avctx,
                           HEVCParamSets *ps)
{
    int i,j;
    int vps_id = 0;
    ptrdiff_t nal_size;
    HEVCVPS *vps = ff_refstruct_allocz(sizeof(*vps));

    if (!vps)
        return AVERROR(ENOMEM);

    av_log(avctx, AV_LOG_DEBUG, "Decoding VPS\n");

    nal_size = gb->buffer_end - gb->buffer;
    if (nal_size > sizeof(vps->data)) {
        av_log(avctx, AV_LOG_WARNING, "Truncating likely oversized VPS "
               "(%"PTRDIFF_SPECIFIER" > %"SIZE_SPECIFIER")\n",
               nal_size, sizeof(vps->data));
        vps->data_size = sizeof(vps->data);
    } else {
        vps->data_size = nal_size;
    }
    memcpy(vps->data, gb->buffer, vps->data_size);

    vps_id = vps->vps_id = get_bits(gb, 4);

    if (get_bits(gb, 2) != 3) { // vps_reserved_three_2bits
        av_log(avctx, AV_LOG_ERROR, "vps_reserved_three_2bits is not three\n");
        goto err;
    }

    vps->vps_max_layers               = get_bits(gb, 6) + 1;
    vps->vps_max_sub_layers           = get_bits(gb, 3) + 1;
    vps->vps_temporal_id_nesting_flag = get_bits1(gb);

    if (get_bits(gb, 16) != 0xffff) { // vps_reserved_ffff_16bits
        av_log(avctx, AV_LOG_ERROR, "vps_reserved_ffff_16bits is not 0xffff\n");
        goto err;
    }

    if (vps->vps_max_sub_layers > HEVC_MAX_SUB_LAYERS) {
        av_log(avctx, AV_LOG_ERROR, "vps_max_sub_layers out of range: %d\n",
               vps->vps_max_sub_layers);
        goto err;
    }

    if (parse_ptl(gb, avctx, &vps->ptl, vps->vps_max_sub_layers) < 0)
        goto err;

    vps->vps_sub_layer_ordering_info_present_flag = get_bits1(gb);

    i = vps->vps_sub_layer_ordering_info_present_flag ? 0 : vps->vps_max_sub_layers - 1;
    for (; i < vps->vps_max_sub_layers; i++) {
        vps->vps_max_dec_pic_buffering[i] = get_ue_golomb_long(gb) + 1;
        vps->vps_num_reorder_pics[i]      = get_ue_golomb_long(gb);
        vps->vps_max_latency_increase[i]  = get_ue_golomb_long(gb) - 1;

        if (vps->vps_max_dec_pic_buffering[i] > HEVC_MAX_DPB_SIZE || !vps->vps_max_dec_pic_buffering[i]) {
            av_log(avctx, AV_LOG_ERROR, "vps_max_dec_pic_buffering_minus1 out of range: %d\n",
                   vps->vps_max_dec_pic_buffering[i] - 1);
            goto err;
        }
        if (vps->vps_num_reorder_pics[i] > vps->vps_max_dec_pic_buffering[i] - 1) {
            av_log(avctx, AV_LOG_WARNING, "vps_max_num_reorder_pics out of range: %d\n",
                   vps->vps_num_reorder_pics[i]);
            if (avctx->err_recognition & AV_EF_EXPLODE)
                goto err;
        }
    }

    vps->vps_max_layer_id   = get_bits(gb, 6);
    vps->vps_num_layer_sets = get_ue_golomb_long(gb) + 1;
    if (vps->vps_num_layer_sets < 1 || vps->vps_num_layer_sets > 1024 ||
        (vps->vps_num_layer_sets - 1LL) * (vps->vps_max_layer_id + 1LL) > get_bits_left(gb)) {
        av_log(avctx, AV_LOG_ERROR, "too many layer_id_included_flags\n");
        goto err;
    }

    for (i = 1; i < vps->vps_num_layer_sets; i++)
        for (j = 0; j <= vps->vps_max_layer_id; j++)
            skip_bits(gb, 1);  // layer_id_included_flag[i][j]

    vps->vps_timing_info_present_flag = get_bits1(gb);
    if (vps->vps_timing_info_present_flag) {
        vps->vps_num_units_in_tick               = get_bits_long(gb, 32);
        vps->vps_time_scale                      = get_bits_long(gb, 32);
        vps->vps_poc_proportional_to_timing_flag = get_bits1(gb);
        if (vps->vps_poc_proportional_to_timing_flag)
            vps->vps_num_ticks_poc_diff_one = get_ue_golomb_long(gb) + 1;
        vps->vps_num_hrd_parameters = get_ue_golomb_long(gb);
        if (vps->vps_num_hrd_parameters > (unsigned)vps->vps_num_layer_sets) {
            av_log(avctx, AV_LOG_ERROR,
                   "vps_num_hrd_parameters %d is invalid\n", vps->vps_num_hrd_parameters);
            goto err;
        }
        for (i = 0; i < vps->vps_num_hrd_parameters; i++) {
            int common_inf_present = 1;

            get_ue_golomb_long(gb); // hrd_layer_set_idx
            if (i)
                common_inf_present = get_bits1(gb);
            decode_hrd(gb, common_inf_present, &vps->hdr[i],
                       vps->vps_max_sub_layers);
        }
    }
    get_bits1(gb); /* vps_extension_flag */

    if (get_bits_left(gb) < 0) {
        av_log(avctx, AV_LOG_ERROR,
               "Overread VPS by %d bits\n", -get_bits_left(gb));
        if (ps->vps_list[vps_id])
            goto err;
    }

    if (ps->vps_list[vps_id] &&
        !memcmp(ps->vps_list[vps_id], vps, sizeof(*vps))) {
        ff_refstruct_unref(&vps);
    } else {
        remove_vps(ps, vps_id);
        ps->vps_list[vps_id] = vps;
    }

    return 0;

err:
    ff_refstruct_unref(&vps);
    return AVERROR_INVALIDDATA;
}

可以看出每个语法元素都能找到对应的一行代码,且变量名都完全一样,将上述语法描述和代码对应起来看就可以明白这些解析函数代码的含义。

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

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

相关文章

挑战杯 基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习

文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xf…

小程序--引入vant组件库

一、npm初始化 在微信开发者工具中打开终端&#xff0c;输入npm init&#xff0c;对npm进行初始化 二、安装vant组件库 npm install vant/weapp 三、修改app.json 修改 app.json&#xff0c;移除全局配置 "style": "v2"&#xff0c;否则 Vant 组件的样式…

R cox回归 ggDCA报错

临床预测模型的决策曲线分析&#xff08;DCA&#xff09;&#xff1a;基于ggDCA包 决策曲线分析法&#xff08;decision curve analysis&#xff0c;DCA&#xff09;是一种评估临床预测模型、诊断试验和分子标记物的简单方法。 我们在传统的诊断试验指标如&#xff1a;敏感性&a…

Hive的UDF开发之向量化表达式(VectorizedExpressions)

1. 背景 笔者的大数据平台XSailboat的SailWorks模块包含离线分析功能。离线分析的后台实现&#xff0c;包含调度引擎、执行引擎、计算引擎和存储引擎。计算和存储引擎由Hive提供&#xff0c;调度引擎和执行引擎由我们自己实现。调度引擎根据DAG图和调度计划&#xff0c;安排执…

2024年天津高考数学备考:历年选择题真题练一练(2014~2023)

距离2024年高考还有不到四个月的时间&#xff0c;今天我们来看一下2014~2023年的天津市高考数学的选择题&#xff0c;从过去十年的真题中随机抽取5道题&#xff0c;并且提供解析。后附六分成长独家制作的在线练习集&#xff0c;科学、高效地反复刷这些真题&#xff0c;吃透真题…

K8S部署Java项目(Springboot项目)pod状态:CrashLoopBackOff

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

docker 镜像仓库实战

“面对脆弱的玩笑” 悉知Docker镜像仓库的命令后&#xff0c;我们总得将这些命令使用起来&#xff0c;在实践中深刻理解执行这些命令的实际效果。 综合实战1: 搭建一个Nginx服务 至于Nginx是什么&#xff0c;我想在这一篇中已经有过讲解: Nignx服务&#xff0c;也并非本篇要详…

基于springboot+vue的课程答疑系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

YOLOv5代码解读[02] models/yolov5l.yaml文件解析

文章目录 YOLOv5代码解读[02] models/yolov5l.yaml文件解析yolov5l.yaml文件检测头1--->耦合头检测头2--->解耦头检测头3--->ASFF检测头Model类解析parse_model函数 YOLOv5代码解读[02] models/yolov5l.yaml文件解析 yolov5l.yaml文件 # YOLOv5 &#x1f680; by Ult…

网络设备和网络软件

文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器&#xff0c;简称网卡。网…

shell基础实验(1)

1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查次磁盘剩余空间。 1.1.安装邮件服务,配置邮件服务 [rootserver ~]# yum install mailx -y[rootserver ~]# vim /etc/mail.rc set from1580540058qq.com …

抽象工厂模式 Abstract Factory

1.模式定义: 提供一个创建一系列相关或互相依赖对象的接口&#xff0c;而无需指定它们具体的类 2. 应用场景: 程序需要处理不同系列的相关产品&#xff0c;但是您不希望它依赖于这些产品的 具体类时&#xff0c; 可以使用抽象工厂 3.优点: 1.可以确信你从工厂得到的产品彼…

解决弹性布局父元素设置高自动换行,子元素均分高度问题(align-content: flex-start)

案例&#xff1a; <view class"abc"><view class"abc-item" v-for"(item,index) in 8" :key"index">看我</view> </view> <style lang"less">.abc{height: 100px;display: flex;flex-wrap: …

OD(8)之Mermaid流程图(flowcharts)使用详解

OD(8)之Mermaid流程图(flowcharts)使用详解 Author: Once Day Date: 2024年2月20日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool‍…

数据结构—图

图是在数据结构中难度比较大&#xff0c;并且比较抽象一种数据结构。 图在地图&#xff0c;社交网络这方面有应用。 图的基本概念 图是由顶点集合及顶点间的关系组成的一种数据结构&#xff1a;G&#xff08;V&#xff0c;E&#xff09;。图标的英文&#xff1a;graph。 (x,…

Rust Vs Go:从头构建一个web服务

Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而&#xff0c;最终的决定性因素是重要项目的易用性。 “Rust 与 Go”争论 Rust vs Go 是一个不断出现的话题&#xff0c;并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下…

数据分析在企业培训系统中的关键作用与优势

数据分析在企业培训系统中扮演着关键的角色&#xff0c;它不仅能够帮助企业更好地了解员工培训的需求和效果&#xff0c;还能够提供有针对性的教育方案和提高培训效果。 数据分析可以帮助企业准确把握员工培训需求。通过收集和分析员工的培训需求调查和反馈信息&#xff0c;企…

浅谈WPF之利用RichTextBox实现富文本编辑器

在实际应用中&#xff0c;富文本随处可见&#xff0c;如留言板&#xff0c;聊天软件&#xff0c;文档编辑&#xff0c;特定格式内容等&#xff0c;在WPF开发中&#xff0c;如何实现富文本编辑呢&#xff1f;本文以一个简单的小例子&#xff0c;简述如何通过RichTextBox实现富文…

JavaCV之rtmp推流(FLV和M3U8)

JavaCV与FFmpeg FFmpeg是一款开源的多媒体处理工具集&#xff0c;它包含了一系列用于处理音频、视频、字幕等多媒体数据的库和工具。 JavaCV集成了FFmpeg库&#xff0c;使得Java开发者可以使用FFmpeg的功能&#xff0c;比如视频解码、编码、格式转换等。 除了FFmpeg&#xff…

01_02_mysql07_mysql8.0新特性

1.MySQL8新特性概述 MySQL从5.7版本直接跳跃发布了8.0版本 &#xff0c;可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强&#xff0c;开发者对MySQL的源代码进行了重构&#xff0c;最突出的一点是多MySQL Optimizer优化器进行了改进。不仅在速度上…