博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
x265探索与研究(六):main()函数
阅读量:7065 次
发布时间:2019-06-28

本文共 22788 字,大约阅读时间需要 75 分钟。

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\n");        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\n", 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\n", 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
* pts_queue = cliopt.output->needPTS() ? new std::priority_queue
() : 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\n"); 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\n", 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\r", 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\n", 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.\n");        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\n");        api = x265_api_get(0);    }    param = api->param_alloc();    if (!param)    {        x265_log(NULL, X265_LOG_ERROR, "param alloc failed\n");        return true;    }    if (api->param_default_preset(param, preset, tune) < 0)    {        x265_log(NULL, X265_LOG_ERROR, "preset or tune unrecognized\n");        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\n", c);                    return true;                }            }            if (long_options_index < 0)            {                x265_log(NULL, X265_LOG_WARNING, "short option '%c' unrecognized\n", 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\n", 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\n", 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>\n", 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\n");        return true;    }    if (param->internalBitDepth != api->bit_depth)    {        x265_log(param, X265_LOG_ERROR, "Only bit depths of %d are supported in this build\n", 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>\n", 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\n", 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\n", 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\n");            this->recon->release();            this->recon = 0;        }        else            general_log(param, this->recon->getName(), X265_LOG_INFO,                    "reconstructed images %dx%d fps %d/%d %s\n",                    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\n", outputfn);        return true;    }    general_log(param, this->output->getName(), X265_LOG_INFO, "output file: %s\n", 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!\n");    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\n");        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\n", PFX(version_str));    x265_log(param, X265_LOG_INFO, "build info %s\n", 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)\n");        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
(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
(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
(enc); encoder->stopJobs(); encoder->printSummary(); //==========打印总结信息 encoder->destroy(); delete encoder; ATOMIC_DEC(&g_ctuSizeConfigured); }}

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

你可能感兴趣的文章
程序员如何成为架构师
查看>>
fiddler抓包之关于connect连接
查看>>
MySQL,binlog2sql回滚操作测试
查看>>
CentOS7下yum安装Jenkins
查看>>
简练软考知识点整理-确认范围管理
查看>>
不懂这几点就落后了:Android、Python工程师必读!
查看>>
Werkzeug 教程
查看>>
内核参数优化
查看>>
用户,组和权限零碎知识
查看>>
计算机
查看>>
文件修改较优方式
查看>>
oracle导入导出exp,imp
查看>>
oracle check if the display variable is set
查看>>
一键部署Openstack R版
查看>>
《JAVA——帮你解决高并发秒杀》
查看>>
国家级期刊发表要求注意事项
查看>>
C文件操作
查看>>
观察转小写的操作-字符函数
查看>>
Oracle查询访问同一表的两个以上索引(二)
查看>>
office 2016 下载地址
查看>>