ffmpeg命令机制分析--参数如何被设置

 http://blog.csdn.net/leixiaohua1020/article/details/44279329(结构体成员管理系统-AVOption)
http://blog.csdn.net/leixiaohua1020/article/details/44268323(结构体成员管理系统-AVClass)

需要了解option结构体的定义 --------------简化版
    typedef struct AVOption {
        const char *name;
        const char *help;
        int offset;         -----------------记录偏移量
        enum AVOptionType type; -------------参数类型,根据类型调用不同接口
         union {    -------------------一般是保存默认值
            int64_t i64;
            double dbl;
            const char *str;
            AVRational q;
        } default_val;
        double min;         ///< minimum valid value for the option
        double max;         ///< maximum valid value for the option
        int flags;
        const char *unit;      --------------标志是同一类
    } AVOption;

常见的模块里面的命令:以nvenc为例

#define OFFSET(x) offsetof(NvencContext, x)
    { "preset",   "Set the encoding preset",              OFFSET(preset),      AV_OPT_TYPE_INT,    { .i64 = PRESET_MEDIUM }, PRESET_DEFAULT, PRESET_LOSSLESS_HP, VE, "preset" },
    { "default",    "",                                   0,                   AV_OPT_TYPE_CONST,  { .i64 = PRESET_DEFAULT }, 0, 0, VE, "preset" },
    { "slow",       "hq 2 passes",                        0,                   AV_OPT_TYPE_CONST,  { .i64 = PRESET_SLOW }, 0, 0, VE, "preset" },
    ......
    { "medium",     "hq 1 pass",                          0,                   AV_OPT_TYPE_CONST,  { .i64 = PRESET_MEDIUM }, 0, 0, VE, "preset" },
      { "rc",       "Override the preset rate-control",     OFFSET(rc),          AV_OPT_TYPE_INT,    { .i64 = -1 },                   -1, INT_MAX, VE, "rc" },
    { "constqp",          "Constant QP mode",                                                            0, AV_OPT_TYPE_CONST,  { .i64 = NV_ENC_PARAMS_RC_CONSTQP },              0, 0, VE, "rc" },
    ....
{ NULL }

实际上ffmpeg的机制是模块化的,命令会下发到具体一个模块,然后模块会根据这些命令执行相应的操作。那么问题是,这些命令是如何准确下发到模块去?也就是上层是通过什么方式关联到模块的私有数据?这就是命令机制的其中一部分功能。其中利用到了一个关键的结构体AVClass来做一个承接。

总的来说,设置命令的基本思路就是:使用AVClass,关联指定的结构体,通过option数组,经过查找匹配,再利用私有数据成员,把命令值设置。命令的设置关键是通过AVClass中保存了Option,这个option中的一个成员记录的偏移量很重要。以h264_nvenc编码器为例,在上层是这样赋值给私有指针priv
(const AVClass*)s->priv_data = codec->priv_class;
其中s是结构体AVCodecContext,codec是结构体AVCodec,codec->priv_class是h264_nvenc_class,这个class关键是保存了options信息

根据偏移量,也就是记录NvencContext 每个成员的相对偏移地址,并在这个地址进行赋值操作,然后在具体的底层(插件)代码中:
NvencContext *ctx = avctx->priv_data;
这样就确定了NvencContext每个成员 相对的地址的偏移量,每个NvencContext 的数据成员值就确定了

为什么priv_data就是NvencContext想要的内容,这里面还有一个关键的技巧就是用到了(const AVClass*)s这样的指针转换。这就是为什么NvencContext结构体一个成员是AVClass。几乎很多具有类似继承关系的结构体都有这样的结构体。AVClass可以说是一个管理类型的结构体。

那么,命令的参数是如何被设置上的?这里需要了解命令参数机制,如结构体,类型等

简单地,数值类一般命令有这两种方式–gpu 3 或者–gpu ls。设置命令的接口有几种,下面简单分析av_opt_set

int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
{
    int ret = 0;
    void *dst, *target_obj;
    /*查重匹配命令:里面调用av_opt_next*/
    const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);
    if (!o || !target_obj)
        return AVERROR_OPTION_NOT_FOUND;
    if (!val && (o->type != AV_OPT_TYPE_STRING &&
                 o->type != AV_OPT_TYPE_PIXEL_FMT && o->type != AV_OPT_TYPE_SAMPLE_FMT &&
                 o->type != AV_OPT_TYPE_IMAGE_SIZE && o->type != AV_OPT_TYPE_VIDEO_RATE &&
                 o->type != AV_OPT_TYPE_DURATION && o->type != AV_OPT_TYPE_COLOR &&
                 o->type != AV_OPT_TYPE_CHANNEL_LAYOUT && o->type != AV_OPT_TYPE_BOOL))
        return AVERROR(EINVAL);

    if (o->flags & AV_OPT_FLAG_READONLY)
        return AVERROR(EINVAL);

    /*确定偏移位置,转换是为了一个一个字节偏移*/
    dst = ((uint8_t *)target_obj) + o->offset;
    /*根据不同的参数类型调用不同的参数接口*/
    switch (o->type) {
    case AV_OPT_TYPE_BOOL:
        return set_string_bool(obj, o, val, dst);
    case AV_OPT_TYPE_STRING:
        return set_string(obj, o, val, dst);
    case AV_OPT_TYPE_BINARY:
        return set_string_binary(obj, o, val, dst);
    case AV_OPT_TYPE_FLAGS:
    case AV_OPT_TYPE_INT:
    case AV_OPT_TYPE_INT64:
    case AV_OPT_TYPE_FLOAT:
    case AV_OPT_TYPE_DOUBLE:
    case AV_OPT_TYPE_RATIONAL:
        return set_string_number(obj, target_obj, o, val, dst);
    case AV_OPT_TYPE_IMAGE_SIZE:
        return set_string_image_size(obj, o, val, dst);
    case AV_OPT_TYPE_VIDEO_RATE: {
        AVRational tmp;
        ret = set_string_video_rate(obj, o, val, &tmp);
        if (ret < 0)
            return ret;
        return write_number(obj, o, dst, 1, tmp.den, tmp.num);
    }
    case AV_OPT_TYPE_PIXEL_FMT:
        return set_string_pixel_fmt(obj, o, val, dst);
    case AV_OPT_TYPE_SAMPLE_FMT:
        return set_string_sample_fmt(obj, o, val, dst);
    case AV_OPT_TYPE_DURATION:
        if (!val) {
            *(int64_t *)dst = 0;
            return 0;
        } else {
            if ((ret = av_parse_time(dst, val, 1)) < 0)
                av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as duration\n", val);
            return ret;
        }
        break;
    case AV_OPT_TYPE_COLOR:
        return set_string_color(obj, o, val, dst);
    case AV_OPT_TYPE_CHANNEL_LAYOUT:
        if (!val || !strcmp(val, "none")) {
            *(int64_t *)dst = 0;
        } else {
            int64_t cl = av_get_channel_layout(val);
            if (!cl) {
                av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as channel layout\n", val);
                ret = AVERROR(EINVAL);
            }
            *(int64_t *)dst = cl;
            return ret;
        }
        break;
    }

    av_log(obj, AV_LOG_ERROR, "Invalid option type.\n");
    return AVERROR(EINVAL);
}
const AVOption *av_opt_next(const void *obj, const AVOption *last)
{
    const AVClass *class;
    if (!obj)

    /*建立联系 ——为了拿到结构体第一个成员的地址*/
    /*分析:获取一级指针内容就是对二级指针的解引用
          指针本身是一个变量,需要一个地址储存
          结构体第一个成员是指针的话,那么这个结构体的首地址就是指向这个成          员。也就是储存这个成员的地址*/
    class = *(const AVClass**)obj;

    if (!last && class && class->option && class->option[0].name)
        return class->option;
    if (last && last[1].name)
        return ++last;
    return NULL;
}
static int set_string_number(void *obj, void *target_obj, const AVOption *o, const char *val, void *dst)
{
    int ret = 0;
    int num, den;
    char c;
    /*这里的正则表达式作用还没弄懂*/
    if (sscanf(val, "%d%*1[:/]%d%c", &num, &den, &c) == 2) {
        if ((ret = write_number(obj, o, dst, 1, den, num)) >= 0)
            return ret;
        ret = 0;
    }

    for (;;) {
        int i = 0;
        char buf[256];
        int cmd = 0;
        double d;
        int64_t intnum = 1;

        if (o->type == AV_OPT_TYPE_FLAGS) {
            if (*val == '+' || *val == '-')
                cmd = *(val++);
            for (; i < sizeof(buf) - 1 && val[i] && val[i] != '+' && val[i] != '-'; i++)
                buf[i] = val[i];
            buf[i] = 0;
        }

        {
            /*实质上就是引入了unit的处理,将value转换成name,o->unit     为值,做一个中转,继续调用av_opt_find2   */
            const AVOption *o_named = av_opt_find(target_obj, i ? buf : val, o->unit, 0, 0);
            int res;
            int ci = 0;
            double const_values[64];
            const char * const_names[64];
            if (o_named && o_named->type == AV_OPT_TYPE_CONST)
                d = DEFAULT_NUMVAL(o_named);
            else {
                if (o->unit) {
                    for (o_named = NULL; o_named = av_opt_next(target_obj, o_named); ) {
                        if (o_named->type == AV_OPT_TYPE_CONST &&
                            o_named->unit &&
                            !strcmp(o_named->unit, o->unit)) {
                            if (ci + 6 >= FF_ARRAY_ELEMS(const_values)) {
                                av_log(obj, AV_LOG_ERROR, "const_values array too small for %s\n", o->unit);
                                return AVERROR_PATCHWELCOME;
                            }
                            const_names [ci  ] = o_named->name;
                            const_values[ci++] = DEFAULT_NUMVAL(o_named);
                        }
                    }
                }
                const_names [ci  ] = "default";
                const_values[ci++] = DEFAULT_NUMVAL(o);
                const_names [ci  ] = "max";
                const_values[ci++] = o->max;
                const_names [ci  ] = "min";
                const_values[ci++] = o->min;
                const_names [ci  ] = "none";
                const_values[ci++] = 0;
                const_names [ci  ] = "all";
                const_values[ci++] = ~0;
                const_names [ci] = NULL;
                const_values[ci] = 0;

                res = av_expr_parse_and_eval(&d, i ? buf : val, const_names,
                                            const_values, NULL, NULL, NULL, NULL, NULL, 0, obj);
                if (res < 0) {
                    av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\"\n", val);
                    return res;
                }
            }
        }
        if (o->type == AV_OPT_TYPE_FLAGS) {
            read_number(o, dst, NULL, NULL, &intnum);
            if (cmd == '+')
                d = intnum | (int64_t)d;
            else if (cmd == '-')
                d = intnum &~(int64_t)d;
        }

        if ((ret = write_number(obj, o, dst, d, 1, 1)) < 0)
            return ret;
        val += i;
        if (!i || !*val)
            return 0;
    }

    return 0;
}

抽离上面的程序,简单总结下面的流程:
av_opt_set
av_opt_find2 —–查重匹配命令
av_opt_next ———会在一个循环里被调用,直到遇到NULL
如果是第一次查找就会返回第一个option
class = (const AVClass*)obj; ——–建立联系 ——为了拿到结构体第一个成员的地址

dst = ((uint8_t *)target_obj) + o->offset; ——-确定偏移位置,转换是为了一个一个字节偏移
根据type,调用相应的处理接口,如数值类
set_string_number(obj, target_obj, o, val, dst);
如果是-gpu ls这种类型,需要进一步处理,
具体处理方式:
(sscanf(val, “%d%*1[:/]%d%c”, &num, &den, &c) ——-正则表达式匹配,如果不是ls这类的,调用write_number
如果上述条件不满足
执行av_opt_find(target_obj, i ? buf : val, o->unit, 0, 0);-
–实质上就是引入了unit的处理,将value转换成name,o->unit为值,做一个中转,继续调用av_opt_find2
write_number(obj, o, dst, d, 1, 1)) ———赋值

ret = av_opt_set(s->priv_data, key, value, 0))
av_opt_set(void *obj, const char *name, const char *val, int search_flags)
好了,联系上面说的(const AVClass*)s->priv_data = codec->priv_class;
总体思路:把一个私有数据s->priv_data传进去,通过转换class = (const AVClass*)obj,指针转化类型AVClass,这个结构体含有option结构体。这个option保存具体的偏移量。通过这个option查找匹配命令。找到命令,确定偏移量,对这个地址赋值操作。也就是说私有数据保存的是偏移量和这个地址对应的值。

用一个图简单的总结一下:
(const AVClass*)s->priv_data = codec->priv_class;
NvencContext *ctx = avctx->priv_data;
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值