x265源码分析:main函数及CLIOptions结构体解释

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nk_wavelet/article/details/52551069
/** 
 * 返回码信息:
 * 0 – 编码成功;
 * 1 – 命令行解释失败;
 * 2 – 编码器打开失败;
 * 3 – 生成流头部失败;
 * 4 – 编码出错;
 * 5 – 打开csv文件失败.
 */
int main(int argc, char **argv)
{
    // 获取控制台窗口的标题,保存在静态数组orgConsoleTitle中
    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);

    // 命令行解释阶段,依据参数profile的值可选择不同的 libx265 API.
    // 打开编码器
    x265_encoder *encoder = api->encoder_open(param);
    if (!encoder)
    {
        x265_log(param, X265_LOG_ERROR, "failed to open encoder\n");
        cliopt.destroy();
        api->param_free(param);
        api->cleanup();
        exit(2);
    }

    // 获取编码参数
    api->encoder_parameters(encoder, param);

    if (cliopt.csvfn)
    {
        cliopt.csvfpt = x265_csvlog_open(*api, *param, cliopt.csvfn, cliopt.csvLogLevel);
        if (!cliopt.csvfpt)
        {
            x265_log_file(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);
        }
    }

    // 当按下Ctrl-C的时候,当前执行程序调用指针函数sigint_handler 执行完后,再返回原
    // 来执行的地方接着往下走。 此处将变量b_ctrl_c设置为1,停止当前编码工作。
    if (signal(SIGINT, sigint_handler) == SIG_ERR)
        x265_log(param, X265_LOG_ERROR, "Unable to register CTRL+C handler: %s\n", strerror(errno));

    // 定义x265的输入pic_orig和输出pic_out   
    x265_picture 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;

    // 如果不需要复制sps和pps放在每个关键帧的前面,则直接编码视频流的头
    if (!param->bRepeatHeaders)
    {
        if (api->encoder_headers(encoder, &p_nal, &nal) < 0)
        {
            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;
            }
        }

        // 待编码的帧数大于0 且 输入的帧数大于或等于将要编码的帧数  
        if (cliopt.framesToBeEncoded && inFrameCount >= cliopt.framesToBeEncoded)
            pic_in = NULL;
        // 成功读取一帧,则输入帧数增加1
        else if (cliopt.input->readPicture(pic_orig))
            inFrameCount++;
        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;
        }

        // 进行编码入口函数,读入几十帧(具体多少帧依赖命令行参数的设置)之后才开始编码。
        // numEncoded是本次编码出来的帧数,大部分情况下为1,如果是负数表示编码出错。
        int numEncoded = api->encoder_encode(encoder, &p_nal, &nal, pic_in, pic_recon);
        if (numEncoded < 0)
        {
            b_ctrl_c = 1;
            ret = 4;
            break;
        }

        if (reconPlay && numEncoded)
            reconPlay->writePicture(*pic_recon);

        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 */
    // 前面读入几十帧之后才开始编码,此处其实就是处理对应的倒数的几十帧,将其存储
    while (!b_ctrl_c)
    {
        ... ...
    }

    /* clear progress report */
    if (cliopt.bProgress)
        fprintf(stderr, "%*s\r", 80, " ");
fail:
    ... ...
    return ret;
}

x265 的各种参数主要是通过CLIOptions结构体及相关函数传递给x265的各API,接下来我们分析CLIOptions结构体的构成及重要函数的功能,如下:

/* 命令行接口 */
struct CLIOptions
{
    InputFile*  input;          // 输入文件,抽象类, 定义在 \input\input.h 中
    ReconFile*  recon;          // 重构文件,抽象类,定义在 \output\output.h 中
    OutputFile* output;         // 输出文件,抽象类,定义在 \output\output.h 中
    FILE*       qpfile;         // 量化因子文件指针
    FILE*       csvfpt;         // csv日志文件指针
    const char* csvfn;          // csv日志文件名
    const char* reconPlayCmd;   
    const x265_api* api;        // 
    x265_param* param;          // x265编码器参数集
    bool bProgress;             // 是否输出编码进度和其他一些编码状态数据
    bool bForceY4m;             // 如果输入文件是Y4M格式,需要强制指定输入格式
    bool bDither;
    int csvLogLevel;            // csv日志级别,log level定义在 x265.h中
    uint32_t seek;              // 输入视频起始需要跳过的帧数
    uint32_t framesToBeEncoded; // 待编码的帧数
    uint64_t totalbytes;        // 已编码的数据字节数
    int64_t startTime;          // 起始编码时间点
    int64_t prevUpdateTime;     // 上一次编码信息输出的时间点

    /* in microseconds,相邻两次编码状态输出的最小时间间隔,单位微妙 */
    static const int UPDATE_INTERVAL = 250000;

    // 构造函数,初始化上述各成员变量
    CLIOptions()
    {
        … … … … 
    }

    void destroy();
    void printStatus(uint32_t frameNum);        // 打印编码状态信息
    bool parse(int argc, char **argv);          // 解释命令行参数
    bool parseQPFile(x265_picture &pic_org);    // 解释量化因子QP文件
};

/* 打印编码状态信息 */
void CLIOptions::printStatus(uint32_t frameNum)
{
    char buf[200];
    int64_t time = x265_mdate();

    // 是否输出编码状态信息取决于:进度开关、当前编码帧数、上次信息输出和当前时间的间隔
    if (!bProgress || !frameNum || (prevUpdateTime && time - prevUpdateTime < UPDATE_INTERVAL))
        return;

    // 计算编码帧率和码率
    int64_t elapsed = time - startTime;
    double fps = elapsed > 0 ? frameNum * 1000000. / elapsed : 0;
    float bitrate = 0.008f * totalbytes * (param->fpsNum / param->fpsDenom) / ((float)frameNum);
    if (framesToBeEncoded)
    {
        int eta = (int)(elapsed * (framesToBeEncoded - frameNum) / ((int64_t)frameNum * 1000000));
        sprintf(buf, "x265 [%.1f%%] %d/%d frames, %.2f fps, %.2f kb/s, 
            eta %d:%02d:%02d", 100. * frameNum / framesToBeEncoded, frameNum, 
            framesToBeEncoded, fps, bitrate, eta / 3600, (eta / 60) % 60, eta % 60);
    }
    else
        sprintf(buf, "x265 %d frames: %.2f fps, %.2f kb/s", frameNum, fps, bitrate);

    fprintf(stderr, "%s  \r", buf + 5);
    SetConsoleTitle(buf);
    fflush(stderr);             // needed in windows
    prevUpdateTime = time;
}

/* 解释命令行参数 */
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;
                }
            }
            ... ...
    }
    ... ...
}

猜你喜欢

转载自blog.csdn.net/nk_wavelet/article/details/52551069
今日推荐