author: hjjdebug
date: 2025年 07月 11日 星期五 10:51:23 CST
descrip: ffmpeg 中 write_option()函數詳細注釋
文章目錄
- 1. 函數原型
- 1.1 參數說明
- 1.2 SpecifierOpt 說明符選項結構
- 2. write_option 代碼注釋
- 2.1 誰調用了write_option 函數?
- 3. 小結:
write_option()不僅在ffmpeg 中, ffplay,ffprobe 都調用了該函數,
write_option是處理命令行參數解析的核心函數,
負責將參數值寫入目標地址(全局變量或結構體字段)
所以這里著重介紹一下:
1. 函數原型
static int write_option(void *optctx,
const OptionDef *opt,
const char *key,
const char *val)
1.1 參數說明
optctx:目標上下文(NULL時寫入全局變量)
opt:選項實例指針,"選項定義OptionDef"完整定義如下
typedef struct OptionDef {const char *name; //這里的name 就是 查找時的key值, 由此找到實例指針int flags; // val的解釋由flags來定義,它還有一個功能確定目標類型union { //目標位置,函數指針,偏移量共用一個union, 到底是什么由flags確定void *dst_ptr;int (*func_arg)(void *, const char *, const char *);size_t off;} u; //下面2個字符串在幫助信息中使用const char *help; //幫助的提示信息const char *argname; //參數的名稱信息 } OptionDef;
key:原始命令行鍵字符串
由key 值找到的OptionDef 實例指針 opt, 但key在這里不是多余的,因為key可能還帶有:及附加信息
val 鍵對應的值, 是一個字符串.
通過opt->flags 可以將其轉換為多種數據類型.
OPT_BOOL: 轉換為bool值
OPT_STRING:分配內存并拷貝字符串
OPT_INT64:用strtol轉換成數值
OPT_OFFSET|OPT_SPEC 決定ctx中的偏移位置
目標地址由opt->u決定,可能是全局固定地址,或者optctx中的一個偏移位置,或者全局函數
舉幾個OptionDef 實例的例子.
-f 選項,有參數,是OFFSET,可為輸入或輸出參數,偏移量OFFSET(format),幫助信息 "force format", 幫助參數名 "fmt"{ "f", HAS_ARG | OPT_STRING | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(format) }, "force format", "fmt" }-y 選項, 布爾值, 直接給全局變量file_overwrite賦值,幫助信息:"overwrite output files", 參數名稱沒有設置{ "y", OPT_BOOL, { &file_overwrite }, "overwrite output files" }-c 選項, 帶參數,是字符串, 是OPT_SPEC, 可為輸入或輸出參數,偏移位置在OFFSET(codec_names)幫助信息是 "codec name", 幫助參數是"codec"{ "c", HAS_ARG | OPT_STRING | OPT_SPEC | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(codec_names) }, "codec name", "codec" },-vcodec key,帶參數是視頻參數,輸入或輸出參數,屬于文件范圍.沒有說明值值是什么類型,目標位置是函數{ "vcodec", OPT_VIDEO | HAS_ARG | OPT_PERFILE | OPT_INPUT | OPT_OUTPUT,{ .func_arg = opt_video_codec }, "force video codec ('copy' to copy stream)", "codec" },
OPT_SPEC 的意思是說明符選項,在目標位置,存儲的是一個數組指針,可以保存很多條信息.
此例codec_name 可以保留-c:v h264 -c:a aac 等, 要把"v"對應的"h264","a"對應的"aac"這些信息都保留下來
與上面類似的還有-b:v -b:a 設置等.
其實SPEC 的使用還有很多,例如 -r frame_rate; -aspect aspect 設置; -pix_fmt pixel 設置
他們都是一個鍵對應著若干個值. 這些值可以作為候選值.
1.2 SpecifierOpt 說明符選項結構
OPT_SEC 對應的是一個SpecifierOpt 選項數組, 這里給出 SpecifierOpt的結構定義:
typedef struct SpecifierOpt {char *specifier; /**< stream/chapter/program/... specifier,一般是"v","a","s","d" 字符串 */union {uint8_t *str;int i;int64_t i64;uint64_t ui64;float f;double dbl;} u; } SpecifierOpt;
2. write_option 代碼注釋
鋪墊差不多了,把代碼copy 來標注一下:
static int write_option(void *optctx, const OptionDef *po, const char *opt, const char *arg)
{//確定目標地址,帶OPT_OFFSET或OPT_SPEC, 地址在optctx結構內部(optctx+offset),否則是全局變量地址void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?(uint8_t *)optctx + po->u.off : po->u.dst_ptr;int *dstcount;double num;int ret;if (po->flags & OPT_SPEC) { //有SPEC 標志SpecifierOpt **so = dst; //目標位置是一個SpecifierOpt數組的地址char *p = strchr(opt, ':'); //把key值用:分割char *str;dstcount = (int *)(so + 1); //數組地址下面so+1是一個整數地址,這里將要存儲數組的大小ret = grow_array((void**)so, sizeof(**so), dstcount, *dstcount + 1);//把數組擴大1個if (ret < 0)return ret;str = av_strdup(p ? p + 1 : ""); //有:復制:后面部分,無冒號不復制(復制空)if (!str)return AVERROR(ENOMEM);
//dup的字符串就是specifier,翻譯為"說明符",一般是"v""a""s""d"(*so)[*dstcount - 1].specifier = str; dst = &(*so)[*dstcount - 1].u; //調整dst 為新分配的SpecifierOpt的u位置,把值存到這里}if (po->flags & OPT_STRING) { //如果是字符串,把字符串dup,地址放到目標處char *str;str = av_strdup(arg);av_freep(dst);if (!str)return AVERROR(ENOMEM);*(char **)dst = str;} else if (po->flags & OPT_BOOL || po->flags & OPT_INT) {ret = parse_number(opt, arg, OPT_INT64, INT_MIN, INT_MAX, &num);if (ret < 0)return ret;*(int *)dst = num; //bool值或int,將參數變成數值,保存到目標} else if (po->flags & OPT_INT64) {ret = parse_number(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX, &num);if (ret < 0)return ret;*(int64_t *)dst = num; //64位,那就按64位存儲} else if (po->flags & OPT_TIME) {ret = av_parse_time(dst, arg, 1); //目標存時間if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Invalid duration for option %s: %s\n",opt, arg);return ret;}} else if (po->flags & OPT_FLOAT) {ret = parse_number(opt, arg, OPT_FLOAT, -INFINITY, INFINITY, &num);if (ret < 0)return ret;*(float *)dst = num; //目標存float值} else if (po->flags & OPT_DOUBLE) {ret = parse_number(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY, &num);if (ret < 0)return ret;*(double *)dst = num; //目標存float值} else if (po->u.func_arg) { //如果實例設置了函數指針,則執行該函數int ret = po->u.func_arg(optctx, opt, arg);if (ret < 0) {av_log(NULL, AV_LOG_ERROR,"Failed to set value '%s' for option '%s': %s\n",arg, opt, av_err2str(ret));return ret;}}if (po->flags & OPT_EXIT) //選項實例要求退出,那就返回退出碼return AVERROR_EXIT;return 0;
}
2.1 誰調用了write_option 函數?
1. ffplay ffprobe 有 parse_options -> parse_option ->write_option 調用
int parse_option(void *optctx, const char *opt, const char *arg, const OptionDef *options)
它們定義的OptionDef表options 基本上都是直接分析到全局變量.
optctx 就是空,沒有使用.
opt 是key, arg 是value
其功能是依據options 表, 根據key值 opt,將val值 arg寫入目的地址.
其再上一層parse_options() 其輸入參數直接就是命令行參數. 就是循環調用parse_option
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
void (*parse_arg_function)(void , const char))
這2個函數都比較簡單,就不多做分析了.
整體的功能是把命令行參數寫到指定的全局變量中以供使用
2. ffmpeg 中調用 parse_optgroup() -> write_option()
int parse_optgroup(void *optctx, OptionGroup *g)
這一次較為復雜,因為它使用了optctx, 不再是空了,
其2引入了OptionGroup 概念. 什么是OptionGroup? 就是選項組.
我們看看它的簡化定義,忽略暫時未使用部分.
typedef struct OptionGroup {Option *opts; //OptionGroup 就是 Option 動態數組,Option就是3個指針int nb_opts;... //忽略暫時不關注部分
} OptionGroup;
parse_optgroup()功能也很好理解, write_option 寫了一條option,
parse_optgroup就是循環調用write_option, 把group中所有的option都寫出去.
那OptionGroup 從哪里來? 誰調用了parse_optgroup? 有2處
在ffmpeg_parse_options() 函數中, 有調用分析選項組函數parse_optgroup,全局選項組為參數,
這些分析的結果都為寫到全局變量中,比較簡單
int ffmpeg_parse_options(int argc, char **argv)
{...ret = parse_optgroup(NULL, &octx.global_opts);ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);...
}
還有一處,在打開文件的時候. 實際是2次,打開輸入文件列表,打開輸出文件列表
static int open_files(OptionGroupList *l, const char inout,
int (open_file)(OptionsContext, const char))
看參數我們知道,它又引入了2個概念. 先看第一個OptionGroupList
typedef struct OptionGroupList {const OptionGroupDef *group_def; //不重要,主要是名稱,分割符等OptionGroup *groups; //選項組的數組int nb_groups;
} OptionGroupList;
既然是選項組的數組,那就循環調用parse_optgroup(),把結果存到OptionsContext 結構中 o
一個文件對應一個選項組,多個文件對應多個選項組,多個選項組組織成一個表叫選項組列表
這個函數的關鍵是調用回調函數open_file,傳遞參數OptionContext &o 和 文件名g->arg
ret = open_file(&o, g->arg);
那OptionsContext 是什么結構呢?
這個很復雜,就不用copy了,它是ffmpeg 特有的,ffplay,ffprobe 都不使用這個結構.
該context定義了ffmpeg options中所有的選項值的存儲位置, 并為后面的open_file服務.
這就是ffmpeg 為什么命令行參數多樣化的來歷.
3. 小結:
-
選項key/value的引入
本來選項用argv[i]訪問挺好的.
但是由于參數變得復雜,我們引入了key/value的概念.
只所以引入key, 就是因為這樣val不再依賴位置i來確定了,而是依賴key來確定.
例如原來是arg[3]為輸入文件名, 現在規定-i 選項后面跟文件名,
顯然后者更靈活. -
選線組的引入
一個選項只能說明一個屬性,多個選項說明多個屬性,所以選線組是很自然的. 一個文件對應一個選項組 -
選項組列表的引入
一個文件對應一個選項組,那多個文件對應多個選項組,把多個"選項組"組織成表叫選項組列表,
我們把多個文件按輸入文件,輸出文件分組. 則可以分成2個文件組,
輸入文件組對應輸入選項組列表,
輸出文件組對應輸出選項組列表,
好了,有了這些概念,您大概可以讀懂ffmpeg 的ffmpeg_parse_options 函數了.
至于它的split_commandline()及open_files() 這里就不展開了. 保持簡潔和高效!
讀懂了ffmpeg_parse_options函數,就讀懂了框架的1/2.
作為實戰,不妨分析一下下面還算簡單的命令行參數
ffmpeg -v debug -c:v h264 -i ./1.ts -f null -