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內部是如何識別這些命令並進行解析和賦值的呢?
首先簡單看下流程圖:
總結起來,解析命令行的大致流程就是:
- 跳過 “--xx xxx” 參數
- “-xx xxx” 格式的默認參數存入全局參數數組或臨時參數數組
- “-noxx xxx”格式的參數,即默認值爲“0”,將值存入全局參數數組或臨時參數數組
- 解析專屬參數,並存入專屬數組結構體(AVDictionary)
- “-i xxx” 格式的輸入文件路徑參數,將臨時參數數組的值、輸入文件路徑以及專屬參數存入輸入相關參數結構體,並清空臨時參數數組
- “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;
}