ffmpeg 中 write_option()函数详细注释


author: hjjdebug

date: 2025年 07月 11日 星期五 10:51:23 CST

descrip: ffmpeg 中 write_option()函数详细注释


文章目录

  • [1. 函数原型](#1. 函数原型)
    • [1.1 参数说明](#1.1 参数说明)
    • [1.2 SpecifierOpt 说明符选项结构](#1.2 SpecifierOpt 说明符选项结构)
  • [2. write_option 代码注释](#2. write_option 代码注释)
    • [2.1 谁调用了write_option 函数?](#2.1 谁调用了write_option 函数?)
  • [3. 小结:](#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"完整定义如下

cpp 复制代码
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 实例的例子.

cpp 复制代码
    -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的结构定义:

coo 复制代码
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 来标注一下:

cpp 复制代码
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? 就是选项组.

我们看看它的简化定义,忽略暂时未使用部分.

cpp 复制代码
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,全局选项组为参数,

这些分析的结果都为写到全局变量中,比较简单

cpp 复制代码
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

cpp 复制代码
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. 小结:

  1. 选项key/value的引入

    本来选项用argv[i]访问挺好的.

    但是由于参数变得复杂,我们引入了key/value的概念.

    只所以引入key, 就是因为这样val不再依赖位置i来确定了,而是依赖key来确定.

    例如原来是arg[3]为输入文件名, 现在规定-i 选项后面跟文件名,

    显然后者更灵活.

  2. 选线组的引入

    一个选项只能说明一个属性,多个选项说明多个属性,所以选线组是很自然的. 一个文件对应一个选项组

  3. 选项组列表的引入

    一个文件对应一个选项组,那多个文件对应多个选项组,把多个"选项组"组织成表叫选项组列表,

    我们把多个文件按输入文件,输出文件分组. 则可以分成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 -

相关推荐
biubiubiu070613 小时前
FFmpeg Windows安装
windows·ffmpeg
Gene_202213 小时前
[TOOL] ubuntu 使用 ffmpeg 操作 gif、mp4
linux·ubuntu·ffmpeg
xhBruce16 小时前
FFmpeg+javacpp中av_log使用
ffmpeg·ffmpeg+javacpp
DogDaoDao16 小时前
Windows下VScode配置FFmpeg开发环境保姆级教程
windows·vscode·ffmpeg·音视频·gcc
1nv1s1ble16 小时前
ffmpeg-api记录
ffmpeg
九流下半2 天前
window wsl 环境下编译openharmony,HarmonyOS 三方库 FFmpeg
windows·ffmpeg·harmonyos·编译·openharmony·三方库
aqi002 天前
FFmpeg开发笔记(七十四)Windows给FFmpeg集成二维码图像的编解码器
ffmpeg·音视频·直播·流媒体
肥or胖2 天前
Visual Studio 2022 上使用ffmpeg
ide·ffmpeg·visual studio