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

来源:互联网 发布:网王之数据大师 编辑:程序博客网 时间:2024/06/06 07:44
 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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

#define OFFSET(x) offsetof(NvencContext, x)
  • 1
  • 1
    { "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 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实际上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);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

抽离上面的程序,简单总结下面的流程: 
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;

这里写图片描述


原文地址:http://blog.csdn.net/sand_wiliam/article/details/53377721?locationNum=5&fps=1

原创粉丝点击