zoukankan      html  css  js  c++  java
  • x265探索与研究(六):main()函数

    x265探索与研究(六):main()函数

            x265源代码的入口函数是main(),本文分析main()的主要功能。

    首先给出main()函数的功能及其代码结构;其次给出main()函数源代码以及分析;最后给出main()函数中的主要功能函数的详细功能。

    1main()函数的功能及其代码结构

            main()函数的主要功能是解析參数并进行编码的一些准备工作,调用了例如以下几个重要的函数:

    1cliopt.parse()函数:解析參数

    2api->encoder_open()函数:打开编码器配置

    3api->encoder_headers()函数:设置NAL相关信息

    4api->encoder_encode()函数:进入编码函数

    5api->encoder_close()函数:结束编码并进行总结

    注:encoder_open()函数、encoder_headers()函数、encoder_encode()函数与encoder_close()函数均位于api.app中。

            相应的函数关系图例如以下图所看到的:




    2main()函数源代码以及分析

            

            main()函数的源代码分析例如以下代码中的凝视。代码例如以下:

    /*=============================================================*/
    /*
     ====== Analysed by: RuiDong Fang 
     ====== Csdn Blog:	 http://blog.csdn.net/frd2009041510 
     ====== Date:		 2016.04.10
     ====== Funtion:	 x265的入口main()函数
     */
    /*=============================================================*/
    /* CLI return codes:
     *
     * 0 - encode successful
     * 1 - unable to parse command line
     * 2 - unable to open encoder
     * 3 - unable to generate stream headers
     * 4 - encoder abort
     * 5 - unable to open csv file
     *
     */
    int main(int argc, char **argv)	//主函数入口
    {
    #if HAVE_VLD
        // This uses Microsoft's proprietary WCHAR type, but this only builds on Windows to start with
        VLDSetReportOptions(VLD_OPT_REPORT_TO_DEBUGGER | VLD_OPT_REPORT_TO_FILE, L"x265_leaks.txt");
    #endif
        PROFILE_INIT();
        THREAD_NAME("API", 0);
    
        GetConsoleTitle(orgConsoleTitle, CONSOLE_TITLE_SIZE);	//获取控制台窗体
        SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
    
        ReconPlay* reconPlay = NULL;
        CLIOptions cliopt;
    
        if (cliopt.parse(argc, argv))	//==========分析參数。对编码器的參数进行设定,打开文件
        {
            cliopt.destroy();
            if (cliopt.api)
                cliopt.api->param_free(cliopt.param);
            exit(1);
        }
    
        x265_param* param = cliopt.param;
        const x265_api* api = cliopt.api;
    
        /* This allows muxers to modify bitstream format */
        cliopt.output->setParam(param);
    
        if (cliopt.reconPlayCmd)
            reconPlay = new ReconPlay(cliopt.reconPlayCmd, *param);
    
        /* note: we could try to acquire a different libx265 API here based on
         * the profile found during option parsing, but it must be done before
         * opening an encoder */
    
        x265_encoder *encoder = api->encoder_open(param);	//==========encoder_open()函数,打印编码器配置
        if (!encoder)	//若打不开编码器配置。提示错误
        {
            x265_log(param, X265_LOG_ERROR, "failed to open encoder
    ");
            cliopt.destroy();
            api->param_free(param);
            api->cleanup();
            exit(2);
        }
    
        /* get the encoder parameters post-initialization */
        api->encoder_parameters(encoder, param);
    
        if (cliopt.csvfn)
        {
            cliopt.csvfpt = x265_csvlog_open(*api, *param, cliopt.csvfn, cliopt.csvLogLevel);
            if (!cliopt.csvfpt)
            {
                x265_log(param, X265_LOG_ERROR, "Unable to open CSV log file <%s>, aborting
    ", cliopt.csvfn);
                cliopt.destroy();
                if (cliopt.api)
                    cliopt.api->param_free(cliopt.param);
                exit(5);
            }
        }
    
        /* Control-C handler */
    	//当键入Ctrl-C的时候,当前运行程序调用指针函数sigint_handler 运行完后,再返回原来运行的地方接着往下走。

    if (signal(SIGINT, sigint_handler) == SIG_ERR) x265_log(param, X265_LOG_ERROR, "Unable to register CTRL+C handler: %s ", strerror(errno)); x265_picture pic_orig, pic_out; //定义x265的输入pic_orig和输出pic_out x265_picture *pic_in = &pic_orig; //获取x265的输入pic_orig的地址 /* Allocate recon picture if analysisMode is enabled */ std::priority_queue<int64_t>* pts_queue = cliopt.output->needPTS() ? new std::priority_queue<int64_t>() : NULL; x265_picture *pic_recon = (cliopt.recon || !!param->analysisMode || pts_queue || reconPlay || cliopt.csvLogLevel) ?

    &pic_out : NULL; uint32_t inFrameCount = 0; //输入的帧数 uint32_t outFrameCount = 0; //输出的帧数 x265_nal *p_nal; x265_stats stats; uint32_t nal; int16_t *errorBuf = NULL; int ret = 0; if (!param->bRepeatHeaders) { if (api->encoder_headers(encoder, &p_nal, &nal) < 0) //==========encoder_headers函数 { x265_log(param, X265_LOG_ERROR, "Failure generating stream headers "); ret = 3; goto fail; } else cliopt.totalbytes += cliopt.output->writeHeaders(p_nal, nal); } api->picture_init(param, pic_in); if (cliopt.bDither) { errorBuf = X265_MALLOC(int16_t, param->sourceWidth + 1); if (errorBuf) memset(errorBuf, 0, (param->sourceWidth + 1) * sizeof(int16_t)); else cliopt.bDither = false; } // main encoder loop(编码主循环) while (pic_in && !b_ctrl_c) { pic_orig.poc = inFrameCount; if (cliopt.qpfile) { if (!cliopt.parseQPFile(pic_orig)) { x265_log(NULL, X265_LOG_ERROR, "can't parse qpfile for frame %d ", pic_in->poc); fclose(cliopt.qpfile); cliopt.qpfile = NULL; } } //当输入帧将要所有编码且输入的帧数大于或等于将要编码的帧数 if (cliopt.framesToBeEncoded && inFrameCount >= cliopt.framesToBeEncoded) pic_in = NULL; else if (cliopt.input->readPicture(pic_orig)) //每读入一帧 inFrameCount++; //输入的帧数自加1 else pic_in = NULL; if (pic_in) { if (pic_in->bitDepth > param->internalBitDepth && cliopt.bDither) { x265_dither_image(*api, *pic_in, cliopt.input->getWidth(), cliopt.input->getHeight(), errorBuf, param->internalBitDepth); pic_in->bitDepth = param->internalBitDepth; } /* Overwrite PTS */ pic_in->pts = pic_in->poc; } //进行编码的入口函数,读入24帧后才開始编码 int numEncoded = api->encoder_encode(encoder, &p_nal, &nal, pic_in, pic_recon); //==========encoder_encode()函数。numEncoded是将要编码的帧数 if (numEncoded < 0) { b_ctrl_c = 1; ret = 4; break; } if (reconPlay && numEncoded) reconPlay->writePicture(*pic_recon); outFrameCount += numEncoded; if (numEncoded && pic_recon && cliopt.recon) cliopt.recon->writePicture(pic_out); if (nal) { cliopt.totalbytes += cliopt.output->writeFrame(p_nal, nal, pic_out); if (pts_queue) { pts_queue->push(-pic_out.pts); if (pts_queue->size() > 2) pts_queue->pop(); } } cliopt.printStatus(outFrameCount); //打印编码帧的详细信息 if (numEncoded && cliopt.csvLogLevel) x265_csvlog_frame(cliopt.csvfpt, *param, *pic_recon, cliopt.csvLogLevel); } /* Flush the encoder */ /*功能:前面读入24帧后才開始编码,此处事实上就是处理相应的倒数的24帧,将其存储*/ while (!b_ctrl_c) //退出上一个大循环后且没有按下Ctrl+C,代码继续运行 { //==========encoder_encode()函数 int numEncoded = api->encoder_encode(encoder, &p_nal, &nal, NULL, pic_recon); if (numEncoded < 0) { ret = 4; break; } if (reconPlay && numEncoded) reconPlay->writePicture(*pic_recon); outFrameCount += numEncoded; if (numEncoded && pic_recon && cliopt.recon) cliopt.recon->writePicture(pic_out); if (nal) { cliopt.totalbytes += cliopt.output->writeFrame(p_nal, nal, pic_out); if (pts_queue) { pts_queue->push(-pic_out.pts); if (pts_queue->size() > 2) pts_queue->pop(); } } cliopt.printStatus(outFrameCount); if (numEncoded && cliopt.csvLogLevel) x265_csvlog_frame(cliopt.csvfpt, *param, *pic_recon, cliopt.csvLogLevel); if (!numEncoded) break; } /* clear progress report */ if (cliopt.bProgress) fprintf(stderr, "%*s ", 80, " "); fail: delete reconPlay; api->encoder_get_stats(encoder, &stats, sizeof(stats)); if (cliopt.csvfpt && !b_ctrl_c) x265_csvlog_encode(cliopt.csvfpt, *api, *param, stats, cliopt.csvLogLevel, argc, argv); api->encoder_close(encoder); //==========encoder_close()函数 int64_t second_largest_pts = 0; int64_t largest_pts = 0; if (pts_queue && pts_queue->size() >= 2) { second_largest_pts = -pts_queue->top(); pts_queue->pop(); largest_pts = -pts_queue->top(); pts_queue->pop(); delete pts_queue; pts_queue = NULL; } cliopt.output->closeFile(largest_pts, second_largest_pts); if (b_ctrl_c) //按下Ctrl+C,直接退出 general_log(param, NULL, X265_LOG_INFO, "aborted at input frame %d, output frame %d ", cliopt.seek + inFrameCount, stats.encodedPictureCount); api->cleanup(); /* Free library singletons */ cliopt.destroy(); api->param_free(param); X265_FREE(errorBuf); SetConsoleTitle(orgConsoleTitle); //设置控制窗体标题 SetThreadExecutionState(ES_CONTINUOUS); #if HAVE_VLD assert(VLDReportLeaks() == 0); #endif return ret; }





    3main()函数中的部分功能函数的详细功能

    3.1GetConsoleTitle(orgConsoleTitle, CONSOLE_TITLE_SIZE);

            GetConsoleTitle的主要功能是获取控制台窗体。当中orgConsoleTitle指向一个缓冲区指针以接收包括标题的字符串。CONSOLE_TITLE_SIZE)是由orgConsoleTitle指向的缓冲区大小。假设函数成功。则返回值是以字符为单位的长度控制台窗体的标题;假设该函数失败,则返回值为零。要获取错误信息。能够调用GetLastError 。

    3.2cliopt.parse(argc, argv)

            cliopt.parse(argc, argv)的主要功能是分析參数,直接调用x265.cpp中的bool CLIOptions::parse(int argc, char **argv)函数,该函数会打印输入视频的分辨率、帧率、视频格式、所要编码的帧数目以及输出文件名等。例如以下图所看到的:

            相应的代码例如以下:

    bool CLIOptions::parse(int argc, char **argv)
    {
        bool bError = false;
        int bShowHelp = false;
        int inputBitDepth = 8;
        int outputBitDepth = 0;
        int reconFileBitDepth = 0;
        const char *inputfn = NULL;
        const char *reconfn = NULL;
        const char *outputfn = NULL;
        const char *preset = NULL;
        const char *tune = NULL;
        const char *profile = NULL;
    
        if (argc <= 1)
        {
            x265_log(NULL, X265_LOG_ERROR, "No input file. Run x265 --help for a list of options.
    ");
            return true;
        }
    
        /* Presets are applied before all other options. */
        for (optind = 0;; )
        {
            int c = getopt_long(argc, argv, short_options, long_options, NULL);
            if (c == -1)
                break;
            else if (c == 'p')
                preset = optarg;
            else if (c == 't')
                tune = optarg;
            else if (c == 'D')
                outputBitDepth = atoi(optarg);
            else if (c == 'P')
                profile = optarg;
            else if (c == '?

    ') bShowHelp = true; } if (!outputBitDepth && profile) { /* try to derive the output bit depth from the requested profile */ if (strstr(profile, "10")) outputBitDepth = 10; else if (strstr(profile, "12")) outputBitDepth = 12; else outputBitDepth = 8; } api = x265_api_get(outputBitDepth); if (!api) { x265_log(NULL, X265_LOG_WARNING, "falling back to default bit-depth "); api = x265_api_get(0); } param = api->param_alloc(); if (!param) { x265_log(NULL, X265_LOG_ERROR, "param alloc failed "); return true; } if (api->param_default_preset(param, preset, tune) < 0) { x265_log(NULL, X265_LOG_ERROR, "preset or tune unrecognized "); return true; } if (bShowHelp) { printVersion(param, api); showHelp(param); } for (optind = 0;; ) { int long_options_index = -1; int c = getopt_long(argc, argv, short_options, long_options, &long_options_index); if (c == -1) break; switch (c) { case 'h': printVersion(param, api); showHelp(param); break; case 'V': printVersion(param, api); x265_report_simd(param); exit(0); default: if (long_options_index < 0 && c > 0) { for (size_t i = 0; i < sizeof(long_options) / sizeof(long_options[0]); i++) { if (long_options[i].val == c) { long_options_index = (int)i; break; } } if (long_options_index < 0) { /* getopt_long might have already printed an error message */ if (c != 63) x265_log(NULL, X265_LOG_WARNING, "internal error: short option '%c' has no long option ", c); return true; } } if (long_options_index < 0) { x265_log(NULL, X265_LOG_WARNING, "short option '%c' unrecognized ", c); return true; } #define OPT(longname) else if (!strcmp(long_options[long_options_index].name, longname)) #define OPT2(name1, name2) else if (!strcmp(long_options[long_options_index].name, name1) || !strcmp(long_options[long_options_index].name, name2)) if (0) ; OPT2("frame-skip", "seek") this->seek = (uint32_t)x265_atoi(optarg, bError); OPT("frames") this->framesToBeEncoded = (uint32_t)x265_atoi(optarg, bError); OPT("csv") this->csvfn = optarg; OPT("csv-log-level") this->csvLogLevel = x265_atoi(optarg, bError); OPT("no-progress") this->bProgress = false; OPT("output") outputfn = optarg; OPT("input") inputfn = optarg; OPT("recon") reconfn = optarg; OPT("input-depth") inputBitDepth = (uint32_t)x265_atoi(optarg, bError); OPT("dither") this->bDither = true; OPT("recon-depth") reconFileBitDepth = (uint32_t)x265_atoi(optarg, bError); OPT("y4m") this->bForceY4m = true; OPT("profile") /* handled above */; OPT("preset") /* handled above */; OPT("tune") /* handled above */; OPT("output-depth") /* handled above */; OPT("recon-y4m-exec") reconPlayCmd = optarg; OPT("qpfile") { this->qpfile = fopen(optarg, "rb"); if (!this->qpfile) { x265_log(param, X265_LOG_ERROR, "%s qpfile not found or error in opening qp file ", optarg); return false; } } else bError |= !!api->param_parse(param, long_options[long_options_index].name, optarg); if (bError) { const char *name = long_options_index > 0 ? long_options[long_options_index].name : argv[optind - 2]; x265_log(NULL, X265_LOG_ERROR, "invalid argument: %s = %s ", name, optarg); return true; } #undef OPT } } if (optind < argc && !inputfn) inputfn = argv[optind++]; if (optind < argc && !outputfn) outputfn = argv[optind++]; if (optind < argc) { x265_log(param, X265_LOG_WARNING, "extra unused command arguments given <%s> ", argv[optind]); return true; } if (argc <= 1) { api->param_default(param); printVersion(param, api); showHelp(param); } if (!inputfn || !outputfn) { x265_log(param, X265_LOG_ERROR, "input or output file not specified, try --help for help "); return true; } if (param->internalBitDepth != api->bit_depth) { x265_log(param, X265_LOG_ERROR, "Only bit depths of %d are supported in this build ", api->bit_depth); return true; } InputFileInfo info; info.filename = inputfn; info.depth = inputBitDepth; info.csp = param->internalCsp; info.width = param->sourceWidth; info.height = param->sourceHeight; info.fpsNum = param->fpsNum; info.fpsDenom = param->fpsDenom; info.sarWidth = param->vui.sarWidth; info.sarHeight = param->vui.sarHeight; info.skipFrames = seek; info.frameCount = 0; getParamAspectRatio(param, info.sarWidth, info.sarHeight); this->input = InputFile::open(info, this->bForceY4m); if (!this->input || this->input->isFail()) { x265_log(param, X265_LOG_ERROR, "unable to open input file <%s> ", inputfn); return true; } if (info.depth < 8 || info.depth > 16) { x265_log(param, X265_LOG_ERROR, "Input bit depth (%d) must be between 8 and 16 ", inputBitDepth); return true; } /* Unconditionally accept height/width/csp from file info */ param->sourceWidth = info.width; param->sourceHeight = info.height; param->internalCsp = info.csp; /* Accept fps and sar from file info if not specified by user */ if (param->fpsDenom == 0 || param->fpsNum == 0) { param->fpsDenom = info.fpsDenom; param->fpsNum = info.fpsNum; } if (!param->vui.aspectRatioIdc && info.sarWidth && info.sarHeight) setParamAspectRatio(param, info.sarWidth, info.sarHeight); if (this->framesToBeEncoded == 0 && info.frameCount > (int)seek) this->framesToBeEncoded = info.frameCount - seek; param->totalFrames = this->framesToBeEncoded; /* Force CFR until we have support for VFR */ info.timebaseNum = param->fpsDenom; info.timebaseDenom = param->fpsNum; if (api->param_apply_profile(param, profile)) return true; if (param->logLevel >= X265_LOG_INFO) { char buf[128]; int p = sprintf(buf, "%dx%d fps %d/%d %sp%d", param->sourceWidth, param->sourceHeight, param->fpsNum, param->fpsDenom, x265_source_csp_names[param->internalCsp], info.depth); int width, height; getParamAspectRatio(param, width, height); if (width && height) p += sprintf(buf + p, " sar %d:%d", width, height); if (framesToBeEncoded <= 0 || info.frameCount <= 0) strcpy(buf + p, " unknown frame count"); else sprintf(buf + p, " frames %u - %d of %d", this->seek, this->seek + this->framesToBeEncoded - 1, info.frameCount); general_log(param, input->getName(), X265_LOG_INFO, "%s ", buf); } this->input->startReader(); if (reconfn) { if (reconFileBitDepth == 0) reconFileBitDepth = param->internalBitDepth; this->recon = ReconFile::open(reconfn, param->sourceWidth, param->sourceHeight, reconFileBitDepth, param->fpsNum, param->fpsDenom, param->internalCsp); if (this->recon->isFail()) { x265_log(param, X265_LOG_WARNING, "unable to write reconstructed outputs file "); this->recon->release(); this->recon = 0; } else general_log(param, this->recon->getName(), X265_LOG_INFO, "reconstructed images %dx%d fps %d/%d %s ", param->sourceWidth, param->sourceHeight, param->fpsNum, param->fpsDenom, x265_source_csp_names[param->internalCsp]); } this->output = OutputFile::open(outputfn, info); if (this->output->isFail()) { x265_log(param, X265_LOG_ERROR, "failed to open output file <%s> for writing ", outputfn); return true; } general_log(param, this->output->getName(), X265_LOG_INFO, "output file: %s ", outputfn); return false; //完毕后返回false }


    3.3encoder_open()函数

            encoder_open(param)的主要功能是打印编码器的配置信息,直接调用api.cpp中的x265_encoder *x265_encoder_open(x265_param *p),该函数中调用了x265_print_params(param)用以打印编码器配置信息,例如以下图所看到的:




    相应的代码例如以下:

    x265_encoder *x265_encoder_open(x265_param *p)
    {
        if (!p)
            return NULL;
    
    #if _MSC_VER
    #pragma warning(disable: 4127) // conditional expression is constant, yes I know
    #endif
    
    #if HIGH_BIT_DEPTH
        if (X265_DEPTH == 12)
            x265_log(p, X265_LOG_WARNING, "Main12 is HIGHLY experimental, do not use!
    ");
        else if (X265_DEPTH != 10 && X265_DEPTH != 12)
    #else
        if (X265_DEPTH != 8)
    #endif
        {
            x265_log(p, X265_LOG_ERROR, "Build error, internal bit depth mismatch
    ");
            return NULL;
        }
    
        Encoder* encoder = NULL;
        x265_param* param = PARAM_NS::x265_param_alloc();
        x265_param* latestParam = PARAM_NS::x265_param_alloc();
        if (!param || !latestParam)
            goto fail;
    
        memcpy(param, p, sizeof(x265_param));
        x265_log(param, X265_LOG_INFO, "HEVC encoder version %s
    ", PFX(version_str));
        x265_log(param, X265_LOG_INFO, "build info %s
    ", PFX(build_info_str));
    
        x265_setup_primitives(param);
    
        if (x265_check_params(param))
            goto fail;
    
        if (x265_set_globals(param))
            goto fail;
    
        encoder = new Encoder;
        if (!param->rc.bEnableSlowFirstPass)
            PARAM_NS::x265_param_apply_fastfirstpass(param);
    
        // may change params for auto-detect, etc
        encoder->configure(param);
        // may change rate control and CPB params
        if (!enforceLevel(*param, encoder->m_vps))
            goto fail;
    
        // will detect and set profile/tier/level in VPS
        determineLevel(*param, encoder->m_vps);
    
        if (!param->bAllowNonConformance && encoder->m_vps.ptl.profileIdc == Profile::NONE)
        {
            x265_log(param, X265_LOG_INFO, "non-conformant bitstreams not allowed (--allow-non-conformance)
    ");
            goto fail;
        }
    
        encoder->create();
        encoder->m_latestParam = latestParam;
        memcpy(latestParam, param, sizeof(x265_param));
        if (encoder->m_aborted)
            goto fail;
    
        x265_print_params(param);	//打印參数
        return encoder;
    
    fail:
        delete encoder;
        PARAM_NS::x265_param_free(param);
        PARAM_NS::x265_param_free(latestParam);
        return NULL;
    }
    


    3.4encoder_headers()函数

    int x265_encoder_headers(x265_encoder *enc, x265_nal **pp_nal, uint32_t *pi_nal)
    {
        if (pp_nal && enc)
        {
            Encoder *encoder = static_cast<Encoder*>(enc);
            Entropy sbacCoder;
            Bitstream bs;
            encoder->getStreamHeaders(encoder->m_nalList, sbacCoder, bs);	//get Stream Headers
            *pp_nal = &encoder->m_nalList.m_nal[0];
            if (pi_nal) *pi_nal = encoder->m_nalList.m_numNal;
            return encoder->m_nalList.m_occupancy;
        }
    
        return -1;
    }


    3.5encoder_encode()函数

    int x265_encoder_encode(x265_encoder *enc, x265_nal **pp_nal, uint32_t *pi_nal, x265_picture *pic_in, x265_picture *pic_out)
    {
        if (!enc)
            return -1;
    
        Encoder *encoder = static_cast<Encoder*>(enc);
        int numEncoded;
    
        // While flushing, we cannot return 0 until the entire stream is flushed
        do
        {
            numEncoded = encoder->encode(pic_in, pic_out);	//==========进入编码函数
        }
        while (numEncoded == 0 && !pic_in && encoder->m_numDelayedPic);
    
        // do not allow reuse of these buffers for more than one picture. The
        // encoder now owns these analysisData buffers.
        if (pic_in)
        {
            pic_in->analysisData.intraData = NULL;
            pic_in->analysisData.interData = NULL;
        }
    
        if (pp_nal && numEncoded > 0)
        {
            *pp_nal = &encoder->m_nalList.m_nal[0];
            if (pi_nal) *pi_nal = encoder->m_nalList.m_numNal;
        }
        else if (pi_nal)
            *pi_nal = 0;
    
        return numEncoded;
    }
    


    3.6encoder_close()函数

    void x265_encoder_close(x265_encoder *enc)
    {
        if (enc)
        {
            Encoder *encoder = static_cast<Encoder*>(enc);
    
            encoder->stopJobs();
            encoder->printSummary();	//==========打印总结信息
            encoder->destroy();
            delete encoder;
            ATOMIC_DEC(&g_ctuSizeConfigured);
        }
    }
    

    大笑到这儿。main()函数的主要功能就分析完成了。



  • 相关阅读:
    css的一些属性及其属性值
    HTML基本标签
    JQuery
    js中的Dom事件模型以及表格方面等内容
    Alpha的过程总结
    数独+GUI界面
    数独
    调研《构建之法》指导下的全国高校的历届软工实践作品、全国互联网+竞赛、物联网竞赛、华为杯研究生作品赛、全国大学生服务外包赛等各类全国性大学生信息化相关的竞赛平台的历届作品
    本学期高级软件工程课程的实践项目的自我目标
    高级软件工程第八次作业:“两只小熊队”团队作业-5
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7344738.html
Copyright © 2011-2022 走看看