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