1. 程式人生 > >x265原始碼分析:main函式及CLIOptions結構體解釋

x265原始碼分析:main函式及CLIOptions結構體解釋

/** 
 * 返回碼資訊:
 * 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;
                }
            }
            ... ...
    }
    ... ...
}