http:http:需要了解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; double max; 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)
{ "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; 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; } { 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