1. 程式人生 > >FFmpeg 解析命令列引數

FFmpeg 解析命令列引數

FFmpeg 命令列基礎語法:

ffmpeg [global_options] {[input_file_options] -i input_file}...{[output_file_options] output_file}...
  • global_options:全域性引數
  • input_file_options:輸入檔案相關引數
  • output_file_options:輸出檔案相關引數

如下為一個簡單的 FFmpeg 命令,將 input.avi 視訊檔案轉換為 640kbps 位元速率的 output.avi

ffmpeg -i input.avi -b:v 640k output.avi

當我們使用命令列來呼叫 FFmpeg 時,當命令列傳入 FFmpeg 時,FFmpeg內部是如何識別這些命令並進行解析和賦值的呢?

首先簡單看下流程圖

FFmpeg 解析命令列引數.png

總結起來,解析命令列的大致流程就是:

  1. 跳過 “–xx xxx” 引數
  2. “-xx xxx” 格式的預設引數存入全域性引數陣列或臨時引數陣列
  3. “-noxx xxx”格式的引數,即預設值為“0”,將值存入全域性引數陣列或臨時引數陣列
  4. 解析專屬引數,並存入專屬陣列結構體(AVDictionary)
  5. “-i xxx” 格式的輸入檔案路徑引數,將臨時引數陣列的值、輸入檔案路徑以及專屬引數存入輸入相關引數結構體,並清空臨時引數陣列
  6. “xxx” 格式的輸出檔案路徑引數,將臨時引數陣列的值、輸出檔案路徑以及專屬引數存入輸出相關引數結構體,並清空臨時引數陣列

引數解析後存放在哪?

  • 有關全域性引數、輸入引數、輸出引數都儲存到 OptionParseContext *octx

    typedef struct OptionParseContext {
        // 全域性命令分組
        OptionGroup global_opts;
    
        // 輸入和輸出的命令分組 (groups[0] 儲存與輸出檔案相關引數,groups[1] 儲存與輸入檔案相關引數)
        OptionGroupList *groups;
    int nb_groups; /* 臨時陣列,儲存輸出、輸入相關引數 */ OptionGroup cur_group; } OptionParseContext;
  • 專屬引數會先儲存到 AVDictionary

    AVDictionary *codec_opts;
    AVDictionary *format_opts;
    AVDictionary *resample_opts;
    AVDictionary *sws_dict;
    AVDictionary *swr_opts;
    

下面通過程式碼來分析 split_commandline 是如何解析命令列的:

/**
 * 解析命令列引數
 * @param octx:    用來將命令列中的引數通過分析分別存入此結構對應變數中
 * @param options: 預設引數的定義 (具體參見ffmpeg_opt.c檔案中的 const OptionDef options[] 定義)
 * @param groups:  儲存輸入檔案和輸出檔案相關引數
 * @param nb_groups: 2
 */
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
                      const OptionDef *options,
                      const OptionGroupDef *groups, int nb_groups)
{
    int optindex = 1;
    int dashdash = -2;

    /* perform system-dependent conversions for arguments list */
    prepare_app_arguments(&argc, &argv);

    /* 基本的記憶體分配,OptionGroupDef 與 OptionParseContext 建立聯絡 */
    init_parse_context(octx, groups, nb_groups, data);
    av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n");

    /* 迴圈取出引數 */
    while (optindex < argc) {
        // 取引數佇列
        const char *opt = argv[optindex++], *arg;
        const OptionDef *po;
        int ret;

        av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt);

        /* 跳過引數格式 --xx xxx */ 
        if (opt[0] == '-' && opt[1] == '-' && !opt[2]) {
            dashdash = optindex;
            continue;
        }
        /* 如果不是以 - 開頭,另外一種情況,就是定義輸出檔案 */
        if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) {
            /** 
             * 將輸出的路徑、 octx->cur_group陣列的值以及專屬引數,儲存到      			     * octx->groups[0]結構體中,並清空 octx->cur_group 陣列的值
             */
            finish_group(octx, 0, opt);
            av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name);
            continue;
        }

        /* 排除以 -- 開頭 & 輸出檔案定義,剩下的就是標準引數形式
           將引數的型別指標 +1,把 - 去掉 */
        opt++;

#define GET_ARG(arg)                                                           \
do {                                                                           \
    arg = argv[optindex++];                                                    \
    if (!arg) {                                                                \
        av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);\
        return AVERROR(EINVAL);                                                \
    }                                                                          \
} while (0)

        /* 引數格式是否為“-i xxx”,如果匹配,返回1 */
        if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) {
            // 獲取引數對應的值
            GET_ARG(arg);
            /** 
             * 將輸入的路徑、octx->cur_group陣列的值以及專屬引數,儲存到      			     * octx->groups[0]結構體中,並清空 octx->cur_group 陣列的值
             */
            finish_group(octx, ret, arg);
            av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n",
                   groups[ret].name, arg);
            continue;
        }

        // 判斷此 opt 是否為 options 中定義的引數
        po = find_option(options, opt);
        if (po->name) {
            if (po->flags & OPT_EXIT) {
                arg = argv[optindex++];
            } else if (po->flags & HAS_ARG) {
                /* 獲取引數 */
                GET_ARG(arg);
            } else {
                // 允許引數後面的值缺失,直接設定為1
                arg = "1";
            }

            /**
             * 判斷引數型別(opt->flags),存入 octx->global_opts(全域性引數) 或 
             * octx->cur_group(臨時陣列)
             */
            add_opt(octx, po, opt, arg);
            av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with "
                   "argument '%s'.\n", po->name, po->help, arg);
            continue;
        }

        if (argv[optindex]) {
            /**
             * 在 avcodec_options、avformat_options、avresample_options、   			 * swscale_options、swresample 中的 options 引數中不斷查詢,查詢專屬			  * 並存入 AVDictionary
             */
            ret = opt_default(NULL, opt, argv[optindex]);
            if (ret >= 0) {
                av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with "
                       "argument '%s'.\n", opt, argv[optindex]);
                optindex++;
                continue;
            } else if (ret != AVERROR_OPTION_NOT_FOUND) {
                // 引數格式錯誤
                av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' "
                       "with argument '%s'.\n", opt, argv[optindex]);
                return ret;
            }
        }

        /* 處理 “-noxx" 格式引數 */
        if (opt[0] == 'n' && opt[1] == 'o' &&
            (po = find_option(options, opt + 2)) &&
            po->name && po->flags & OPT_BOOL) {
            // 存入預設值 ”0“
            add_opt(octx, po, opt, "0");
            av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with "
                   "argument 0.\n", po->name, po->help);
            continue;
        }

        // 沒有匹配的引數,返回錯誤
        av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'.\n", opt);
        return AVERROR_OPTION_NOT_FOUND;
    }

    // 迴圈結束後,臨時引數及專屬引數還存在值,沒有儲存到輸入&輸出相關引數陣列中
    if (octx->cur_group.nb_opts || codec_opts || format_opts || resample_opts)
        av_log(NULL, AV_LOG_WARNING, "Trailing options were found on the "
               "commandline.\n");

    av_log(NULL, AV_LOG_DEBUG, "Finished splitting the commandline.\n");

    return 0;
}