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

一直想写一个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;
}

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

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

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

相关文章

[晓理紫]AI专属会议截稿时间订阅

AI专属会议截稿时间订阅 VX关注{晓理紫}免费,每日更新最新AI专属会议信息,如感兴趣,请转发给有需要的同学,谢谢支持!! VX关注{晓理紫}免费 IROS 2024 Deadline: Sat Mar 2nd 2024 03:59:59 PM CST (2024-03-02 15:59:59 UTC-08) date_location: October 14-18, 2024. A…

鸿蒙会成为安卓的终结者吗?

随着近期鸿蒙OS系统推送测试版的时间确定&#xff0c;关于鸿蒙系统的讨论再次升温。 作为华为自主研发的操作系统&#xff0c;鸿蒙给人的第一印象是具有颠覆性。 早在几年前&#xff0c;业内就开始流传鸿蒙可能会代替Android的传言。毕竟&#xff0c;Android作为开源系统&…

第10讲用户登录SpringSecurity查库实现

用户登录SpringSecurity查库实现 security包下新建MyUserDetailServiceImpl Service public class MyUserDetailServiceImpl implements UserDetailsService {AutowiredSysUserService sysUserService;Overridepublic UserDetails loadUserByUsername(String username) throw…

C#算法(12)—对图像像素做X/Y方向的偏移

我们在上位机开发领域有时候需要对获取的图像的像素做整体的偏移,比如所有像素在X方向上偏移几个像素,或者所有像素在Y方向上偏移几个像素,本文就是开发了像素整体偏移算法来解决这个问题。 比如有一个图像大小为3*3,像素值如下图1,如果我想实现将这个幅图像的像素整体往右…

【深蓝学院】移动机器人运动规划--第5章 最优轨迹生成--作业

0. 题目 1. 解答 — Listing1&#xff1a; void minimumJerkTrajGen(// Inputs:const int pieceNum,const Eigen::Vector3d &initialPos,const Eigen::Vector3d &initialVel,const Eigen::Vector3d &initialAcc,const Eigen::Vector3d &terminalPos,const Eig…

UML---活动图

活动图概述 活动图&#xff08;Activity Diagram&#xff09;是UML&#xff08;Unified Modeling Language&#xff0c;统一建模语言&#xff09;中的一种行为建模工具&#xff0c;主要用于描述系统或业务流程中的一系列活动或操作。活动图通常用于描述用例中的行为&#xff0c…

算法项目(5)—— 时序模型TFT数据趋势预测

本文包含什么? TFT+机器学习融合完整的在线运行的代码环境(免配置环境)代码介绍运行有问题? csdn上后台随时售后.项目说明 本文主要实现用谷歌的论文Temporal Fusion Transformers for Interpretable Multi-horizon Time Series Forecasting(TFT)来做时间序列的预测. 模型整…

C++类和对象——多态详解

目录 1.多态的基本语法 2.多态的原理剖析 示例&#xff1a;计算机类 3.纯虚函数和抽象类 示例&#xff1a;制作饮品 4.虚析构和纯虚析构 示例&#xff1a;电脑组装 1.多态的基本语法 代码示例&#xff1a; #include<bits/stdc.h> using namespace std;class mus…

1903_CoreMark白皮书阅读笔记

1903_CoreMark白皮书阅读笔记 全部学习汇总&#xff1a; g_embedded: 嵌入式通用技术学习笔记 (gitee.com) 再看ARM的内核架构介绍的时候看到了不同的内核都测试了一个CoreMark/Mhz的参数。从名称看&#xff0c;可以理解为是MCU的算力跑分。至于这部分究竟是测试了哪些功能&…

CV论文--2024.2.22

SOURCE&#xff1a;CV论文--2024.2.22 1、CounterCurate: Enhancing Physical and Semantic Visio-Linguistic Compositional Reasoning via Counterfactual Examples 中文标题&#xff1a;CounterCurate&#xff1a;通过反事实示例增强物理和语义视觉语言组合推理 简介&…

“替代云”知多少?Akamai Linode 重新定义公有云服务!

自2006年云计算概念提出以来&#xff0c;云产业已经成为数字化时代所必备的底层基础&#xff0c;但随着多元化的业务需求的增多&#xff0c;多云战略、本地部署所形成混合环境&#xff0c;都使得云复杂性&#xff0c;日渐成为了迫在眉睫的挑战。 451 Research 云价格指数 (CPI…

HarmonyOS Stage模型基本概念讲解

本文 我们来说harmonyos中的一种应用模型 Stage模型 官方提供了两种模型 一种是早期的 FA模型 另一种就是就是 harmonyos 3.1才开始的新增的一种模型 Stage模型 目前来讲 Stage 会成为现在乃至将来 长期推进的一种模型 也就是 无论是 现在的harmonyos 4.0 乃至 之后要发布的 …

pytest基本应用

文章目录 1.pytest安装2.用例运行规则3.常用参数断言运行参数用例控制setup和teardownini配置文件 4.常用插件5.pytest高阶用法用例跳过参数化 6.pytest之Fixture使用fixture使用装饰器usefixtures 7.pytest之conftest.py8.conftestfixtureyieldyield介绍前后置使用 1.pytest安…

2012及其以上系统修改服务器密码指南

修改服务器密码指南,目前介绍两种不同的方案 方法一 指令式 winR键 弹出运行框里输入 cmd 点击确认或者右下角开始程序里面的点开运行 2.在弹出框里手动输入以下一组文字&#xff1a;net user administrator 123456 框内无法粘贴 需要手动输入 其中administrator 是用…

4核8G服务器腾讯云和阿里云租用价格对比,2024更新

4核8G云服务器多少钱一年&#xff1f;阿里云ECS服务器u1价格955.58元一年&#xff0c;腾讯云轻量4核8G12M带宽价格是646元15个月&#xff0c;阿腾云atengyun.com整理4核8G云服务器价格表&#xff0c;包括一年费用和1个月收费明细&#xff1a; 云服务器4核8G配置收费价格 阿里…

Sora刷爆了,先来了解下基本情况

2月15日&#xff0c;OpenAI发布的Sora模型确实在文生视频领域取得了显著的进步&#xff0c;其特点和创新性表现在以下几个方面&#xff1a; 视频生成长度&#xff1a;Sora模型能够生成长达1分钟的视频&#xff0c;这相比之前的文生视频模型有了显著的提升。这一长度的视频已经…

后端程序员入门react笔记——react的diff算法(三)

diffing算法 虚拟dom 我们知道&#xff0c;react里面操作的都是虚拟dom&#xff0c;最后经过render渲染为真正的dom&#xff0c;那么为什么要提出虚拟dom这个概念呢&#xff1f;其实就是将逻辑和视图区分开&#xff0c;react的虚拟dom&#xff0c;就相当于mvc的c&#xff0c;…

网络安全实验(三)补充

1.假设内网用户需要通过外网的web服务器和pop3邮件服务器下载文件和邮件&#xff0c;内网的FTP服务器也需要接受外网用户上传的文件。针对该场景进行防病毒的防护。 2.我们需要针对办公区用户进行上网行为管理&#xff0c;要求进行URL过滤&#xff0c;要求在上班时间仅能访问教…

UE蓝图 入口(FunctionEntry)节点和源码

系列文章目录 UE蓝图 Get节点和源码 UE蓝图 Set节点和源码 UE蓝图 Cast节点和源码 UE蓝图 分支(Branch)节点和源码 UE蓝图 入口(FunctionEntry)节点和源码 文章目录 系列文章目录一、FunctionEntry节点功能二、入口节点用法1. 创建函数2. 命名函数3. 定义参数4. 编写函数逻辑5…

http协议工具:apache详解

目录 一、常见的http服务程序 1、 Apache HTTP Server 介绍 1.1 apache 概念 1.2 apache 功能 1.3 apache 特性 2、MPM&#xff08;multi-processing module&#xff09;工作模式 2.1 prefork 2.2 worker 2.3 event 二、Apache HTTP Server安装和相关文件 1、安装方…