[FFmpeg-cvslog] lavu/opt: add array options
Anton Khirnov
git at videolan.org
Fri Mar 8 09:06:17 EET 2024
ffmpeg | branch: master | Anton Khirnov <anton at khirnov.net> | Thu Feb 8 08:50:18 2024 +0100| [efe447877811f2f14f814e80ce71383e2f056f36] | committer: Anton Khirnov
lavu/opt: add array options
> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=efe447877811f2f14f814e80ce71383e2f056f36
---
doc/APIchanges | 3 +
libavutil/opt.c | 360 ++++++++++++++++++++++++++++++++++++++++++++------
libavutil/opt.h | 56 +++++++-
libavutil/tests/opt.c | 49 +++++++
libavutil/version.h | 2 +-
tests/ref/fate/opt | 35 ++++-
6 files changed, 459 insertions(+), 46 deletions(-)
diff --git a/doc/APIchanges b/doc/APIchanges
index 71cd6ece34..650c135b38 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07
API changes, most recent first:
+2024-03-08 - xxxxxxxxxx - lavu 59.1.100 - opt.h
+ Add AV_OPT_TYPE_FLAG_ARRAY and AVOptionArrayDef.
+
2024-03-08 - xxxxxxxxxx - lavc 61.0.100 - vdpau.h
Deprecate av_vdpau_alloc_context(), av_alloc_vdpaucontext(),
av_vdpau_hwaccel_get_render2() and av_vdpau_hwaccel_set_render2().
diff --git a/libavutil/opt.c b/libavutil/opt.c
index 06d3056a37..685206f416 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -43,6 +43,8 @@
#include <float.h>
+#define TYPE_BASE(type) ((type) & ~AV_OPT_TYPE_FLAG_ARRAY)
+
const AVOption *av_opt_next(const void *obj, const AVOption *last)
{
const AVClass *class;
@@ -100,6 +102,54 @@ static int opt_is_pod(enum AVOptionType type)
return 0;
}
+static uint8_t opt_array_sep(const AVOption *o)
+{
+ const AVOptionArrayDef *d = o->default_val.arr;
+ av_assert1(o->type & AV_OPT_TYPE_FLAG_ARRAY);
+ return (d && d->sep) ? d->sep : ',';
+}
+
+static void *opt_array_pelem(const AVOption *o, void *array, unsigned idx)
+{
+ av_assert1(o->type & AV_OPT_TYPE_FLAG_ARRAY);
+ return (uint8_t *)array + idx * opt_elem_size[TYPE_BASE(o->type)];
+}
+
+static unsigned *opt_array_pcount(const void *parray)
+{
+ return (unsigned *)((const void * const *)parray + 1);
+}
+
+static void opt_free_elem(const AVOption *o, void *ptr)
+{
+ switch (TYPE_BASE(o->type)) {
+ case AV_OPT_TYPE_STRING:
+ case AV_OPT_TYPE_BINARY:
+ av_freep(ptr);
+ break;
+
+ case AV_OPT_TYPE_DICT:
+ av_dict_free((AVDictionary **)ptr);
+ break;
+
+ case AV_OPT_TYPE_CHLAYOUT:
+ av_channel_layout_uninit((AVChannelLayout *)ptr);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void opt_free_array(const AVOption *o, void *parray, unsigned *count)
+{
+ for (unsigned i = 0; i < *count; i++)
+ opt_free_elem(o, opt_array_pelem(o, *(void **)parray, i));
+
+ av_freep(parray);
+ *count = 0;
+}
+
static int read_number(const AVOption *o, const void *dst, double *num, int *den, int64_t *intnum)
{
switch (o->type) {
@@ -140,14 +190,16 @@ static int read_number(const AVOption *o, const void *dst, double *num, int *den
static int write_number(void *obj, const AVOption *o, void *dst, double num, int den, int64_t intnum)
{
- if (o->type != AV_OPT_TYPE_FLAGS &&
+ const enum AVOptionType type = TYPE_BASE(o->type);
+
+ if (type != AV_OPT_TYPE_FLAGS &&
(!den || o->max * den < num * intnum || o->min * den > num * intnum)) {
num = den ? num * intnum / den : (num && intnum ? INFINITY : NAN);
av_log(obj, AV_LOG_ERROR, "Value %f for parameter '%s' out of range [%g - %g]\n",
num, o->name, o->min, o->max);
return AVERROR(ERANGE);
}
- if (o->type == AV_OPT_TYPE_FLAGS) {
+ if (type == AV_OPT_TYPE_FLAGS) {
double d = num*intnum/den;
if (d < -1.5 || d > 0xFFFFFFFF+0.5 || (llrint(d*256) & 255)) {
av_log(obj, AV_LOG_ERROR,
@@ -157,7 +209,7 @@ static int write_number(void *obj, const AVOption *o, void *dst, double num, int
}
}
- switch (o->type) {
+ switch (type) {
case AV_OPT_TYPE_PIXEL_FMT:
*(enum AVPixelFormat *)dst = llrint(num / den) * intnum;
break;
@@ -271,9 +323,10 @@ static int set_string(void *obj, const AVOption *o, const char *val, uint8_t **d
static int set_string_number(void *obj, void *target_obj, const AVOption *o, const char *val, void *dst)
{
+ const enum AVOptionType type = TYPE_BASE(o->type);
int ret = 0;
- if (o->type == AV_OPT_TYPE_RATIONAL || o->type == AV_OPT_TYPE_VIDEO_RATE) {
+ if (type == AV_OPT_TYPE_RATIONAL || type == AV_OPT_TYPE_VIDEO_RATE) {
int num, den;
char c;
if (sscanf(val, "%d%*1[:/]%d%c", &num, &den, &c) == 2) {
@@ -290,7 +343,7 @@ static int set_string_number(void *obj, void *target_obj, const AVOption *o, con
double d;
int64_t intnum = 1;
- if (o->type == AV_OPT_TYPE_FLAGS) {
+ if (type == AV_OPT_TYPE_FLAGS) {
if (*val == '+' || *val == '-')
cmd = *(val++);
for (; i < sizeof(buf) - 1 && val[i] && val[i] != '+' && val[i] != '-'; i++)
@@ -346,7 +399,7 @@ static int set_string_number(void *obj, void *target_obj, const AVOption *o, con
}
}
}
- if (o->type == AV_OPT_TYPE_FLAGS) {
+ if (type == AV_OPT_TYPE_FLAGS) {
intnum = *(unsigned int*)dst;
if (cmd == '+')
d = intnum | (int64_t)d;
@@ -531,18 +584,17 @@ static int set_string_channel_layout(void *obj, const AVOption *o,
static int opt_set_elem(void *obj, void *target_obj, const AVOption *o,
const char *val, void *dst)
{
+ const enum AVOptionType type = TYPE_BASE(o->type);
int ret;
-FF_DISABLE_DEPRECATION_WARNINGS
- 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_DURATION && o->type != AV_OPT_TYPE_COLOR &&
- o->type != AV_OPT_TYPE_BOOL))
+ if (!val && (type != AV_OPT_TYPE_STRING &&
+ type != AV_OPT_TYPE_PIXEL_FMT && type != AV_OPT_TYPE_SAMPLE_FMT &&
+ type != AV_OPT_TYPE_IMAGE_SIZE &&
+ type != AV_OPT_TYPE_DURATION && type != AV_OPT_TYPE_COLOR &&
+ type != AV_OPT_TYPE_BOOL))
return AVERROR(EINVAL);
-FF_ENABLE_DEPRECATION_WARNINGS
- switch (o->type) {
+ switch (type) {
case AV_OPT_TYPE_BOOL:
return set_string_bool(obj, o, val, dst);
case AV_OPT_TYPE_STRING:
@@ -604,6 +656,85 @@ FF_ENABLE_DEPRECATION_WARNINGS
return AVERROR(EINVAL);
}
+static int opt_set_array(void *obj, void *target_obj, const AVOption *o,
+ const char *val, void *dst)
+{
+ const AVOptionArrayDef *arr = o->default_val.arr;
+ const size_t elem_size = opt_elem_size[TYPE_BASE(o->type)];
+ const uint8_t sep = opt_array_sep(o);
+ uint8_t *str = NULL;
+
+ void *elems = NULL;
+ unsigned nb_elems = 0;
+ int ret;
+
+ if (val && *val) {
+ str = av_malloc(strlen(val) + 1);
+ if (!str)
+ return AVERROR(ENOMEM);
+ }
+
+ // split and unescape the string
+ while (val && *val) {
+ uint8_t *p = str;
+ void *tmp;
+
+ if (arr && arr->size_max && nb_elems >= arr->size_max) {
+ av_log(obj, AV_LOG_ERROR,
+ "Cannot assign more than %u elements to array option %s\n",
+ arr->size_max, o->name);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ for (; *val; val++, p++) {
+ if (*val == '\\' && val[1])
+ val++;
+ else if (*val == sep) {
+ val++;
+ break;
+ }
+ *p = *val;
+ }
+ *p = 0;
+
+ tmp = av_realloc_array(elems, nb_elems + 1, elem_size);
+ if (!tmp) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ elems = tmp;
+
+ tmp = opt_array_pelem(o, elems, nb_elems);
+ memset(tmp, 0, elem_size);
+
+ ret = opt_set_elem(obj, target_obj, o, str, tmp);
+ if (ret < 0)
+ goto fail;
+ nb_elems++;
+ }
+ av_freep(&str);
+
+ opt_free_array(o, dst, opt_array_pcount(dst));
+
+ if (arr && nb_elems < arr->size_min) {
+ av_log(obj, AV_LOG_ERROR,
+ "Cannot assign fewer than %u elements to array option %s\n",
+ arr->size_min, o->name);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ *((void **)dst) = elems;
+ *opt_array_pcount(dst) = nb_elems;
+
+ return 0;
+fail:
+ av_freep(&str);
+ opt_free_array(o, &elems, &nb_elems);
+ return ret;
+}
+
int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
{
void *dst, *target_obj;
@@ -619,7 +750,8 @@ int av_opt_set(void *obj, const char *name, const char *val, int search_flags)
dst = ((uint8_t *)target_obj) + o->offset;
- return opt_set_elem(obj, target_obj, o, val, dst);
+ return ((o->type & AV_OPT_TYPE_FLAG_ARRAY) ?
+ opt_set_array : opt_set_elem)(obj, target_obj, o, val, dst);
}
#define OPT_EVAL_NUMBER(name, opttype, vartype) \
@@ -647,7 +779,7 @@ static int set_number(void *obj, const char *name, double num, int den, int64_t
if (!o || !target_obj)
return AVERROR_OPTION_NOT_FOUND;
- if (o->flags & AV_OPT_FLAG_READONLY)
+ if ((o->flags & AV_OPT_FLAG_READONLY) || (o->type & AV_OPT_TYPE_FLAG_ARRAY))
return AVERROR(EINVAL);
dst = ((uint8_t *)target_obj) + o->offset;
@@ -730,7 +862,8 @@ int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int searc
return AVERROR_OPTION_NOT_FOUND;
if (o->type != AV_OPT_TYPE_VIDEO_RATE) {
av_log(obj, AV_LOG_ERROR,
- "The value set by option '%s' is not a video rate.\n", o->name);
+ "The value set by option '%s' is not a video rate.\n",
+ o->name);
return AVERROR(EINVAL);
}
if (val.num <= 0 || val.den <= 0)
@@ -852,7 +985,7 @@ static int opt_get_elem(const AVOption *o, uint8_t **pbuf, size_t buf_len,
{
int ret;
- switch (o->type) {
+ switch (TYPE_BASE(o->type)) {
case AV_OPT_TYPE_BOOL:
ret = snprintf(*pbuf, buf_len, "%s", get_bool_name(*(int *)dst));
break;
@@ -949,6 +1082,66 @@ static int opt_get_elem(const AVOption *o, uint8_t **pbuf, size_t buf_len,
return ret;
}
+static int opt_get_array(const AVOption *o, void *dst, uint8_t **out_val)
+{
+ const unsigned count = *opt_array_pcount(dst);
+ const uint8_t sep = opt_array_sep(o);
+
+ uint8_t *str = NULL;
+ size_t str_len = 0;
+ int ret;
+
+ *out_val = NULL;
+
+ for (unsigned i = 0; i < count; i++) {
+ uint8_t buf[128], *out = buf;
+ size_t out_len;
+
+ ret = opt_get_elem(o, &out, sizeof(buf),
+ opt_array_pelem(o, *(void **)dst, i), 0);
+ if (ret < 0)
+ goto fail;
+
+ out_len = strlen(out);
+ if (out_len > SIZE_MAX / 2 - !!i ||
+ !!i + out_len * 2 > SIZE_MAX - str_len - 1) {
+ ret = AVERROR(ERANGE);
+ goto fail;
+ }
+
+ // terminator escaping separator
+ // ↓ ↓ ↓
+ ret = av_reallocp(&str, str_len + 1 + out_len * 2 + !!i);
+ if (ret < 0)
+ goto fail;
+
+ // add separator if needed
+ if (i)
+ str[str_len++] = sep;
+
+ // escape the element
+ for (unsigned j = 0; j < out_len; j++) {
+ uint8_t val = out[j];
+ if (val == sep || val == '\\')
+ str[str_len++] = '\\';
+ str[str_len++] = val;
+ }
+ str[str_len] = 0;
+
+fail:
+ if (out != buf)
+ av_freep(&out);
+ if (ret < 0) {
+ av_freep(&str);
+ return ret;
+ }
+ }
+
+ *out_val = str;
+
+ return 0;
+}
+
int av_opt_get(void *obj, const char *name, int search_flags, uint8_t **out_val)
{
void *dst, *target_obj;
@@ -964,8 +1157,19 @@ int av_opt_get(void *obj, const char *name, int search_flags, uint8_t **out_val)
dst = (uint8_t *)target_obj + o->offset;
- buf[0] = 0;
+ if (o->type & AV_OPT_TYPE_FLAG_ARRAY) {
+ ret = opt_get_array(o, dst, out_val);
+ if (ret < 0)
+ return ret;
+ if (!*out_val && !(search_flags & AV_OPT_ALLOW_NULL)) {
+ *out_val = av_strdup("");
+ if (!*out_val)
+ return AVERROR(ENOMEM);
+ }
+ return 0;
+ }
+ buf[0] = 0;
out = buf;
ret = opt_get_elem(o, &out, sizeof(buf), dst, search_flags);
if (ret < 0)
@@ -988,6 +1192,8 @@ static int get_number(void *obj, const char *name, double *num, int *den, int64_
const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);
if (!o || !target_obj)
return AVERROR_OPTION_NOT_FOUND;
+ if (o->type & AV_OPT_TYPE_FLAG_ARRAY)
+ return AVERROR(EINVAL);
dst = ((uint8_t *)target_obj) + o->offset;
@@ -1045,7 +1251,7 @@ int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_
return AVERROR_OPTION_NOT_FOUND;
if (o->type != AV_OPT_TYPE_IMAGE_SIZE) {
av_log(obj, AV_LOG_ERROR,
- "The value for option '%s' is not an image size.\n", name);
+ "The value for option '%s' is not a image size.\n", name);
return AVERROR(EINVAL);
}
@@ -1252,11 +1458,16 @@ static void log_type(void *av_log_obj, const AVOption *o,
[AV_OPT_TYPE_CHLAYOUT] = "<channel_layout>",
[AV_OPT_TYPE_BOOL] = "<boolean>",
};
+ const enum AVOptionType type = TYPE_BASE(o->type);
- if (o->type == AV_OPT_TYPE_CONST && parent_type == AV_OPT_TYPE_INT)
+ if (o->type == AV_OPT_TYPE_CONST && TYPE_BASE(parent_type) == AV_OPT_TYPE_INT)
av_log(av_log_obj, AV_LOG_INFO, "%-12"PRId64" ", o->default_val.i64);
- else if (o->type < FF_ARRAY_ELEMS(desc) && desc[o->type])
- av_log(av_log_obj, AV_LOG_INFO, "%-12s ", desc[o->type]);
+ else if (type < FF_ARRAY_ELEMS(desc) && desc[type]) {
+ if (o->type & AV_OPT_TYPE_FLAG_ARRAY)
+ av_log(av_log_obj, AV_LOG_INFO, "[%-10s]", desc[type]);
+ else
+ av_log(av_log_obj, AV_LOG_INFO, "%-12s ", desc[type]);
+ }
else
av_log(av_log_obj, AV_LOG_INFO, "%-12s ", "");
}
@@ -1274,6 +1485,13 @@ static void log_default(void *obj, void *av_log_obj, const AVOption *opt)
!opt->default_val.str)
return;
+ if (opt->type & AV_OPT_TYPE_FLAG_ARRAY) {
+ const AVOptionArrayDef *arr = opt->default_val.arr;
+ if (arr && arr->def)
+ av_log(av_log_obj, AV_LOG_INFO, " (default %s)", arr->def);
+ return;
+ }
+
av_log(av_log_obj, AV_LOG_INFO, " (default ");
switch (opt->type) {
case AV_OPT_TYPE_BOOL:
@@ -1434,6 +1652,21 @@ void av_opt_set_defaults2(void *s, int mask, int flags)
if (opt->flags & AV_OPT_FLAG_READONLY)
continue;
+ if (opt->type & AV_OPT_TYPE_FLAG_ARRAY) {
+ const AVOptionArrayDef *arr = opt->default_val.arr;
+ const char sep = opt_array_sep(opt);
+
+ av_assert0(sep && sep != '\\' &&
+ (sep < 'a' || sep > 'z') &&
+ (sep < 'A' || sep > 'Z') &&
+ (sep < '0' || sep > '9'));
+
+ if (arr && arr->def)
+ opt_set_array(s, s, opt, arr->def, dst);
+
+ continue;
+ }
+
switch (opt->type) {
case AV_OPT_TYPE_CONST:
/* Nothing to be done here */
@@ -1676,23 +1909,12 @@ void av_opt_free(void *obj)
{
const AVOption *o = NULL;
while ((o = av_opt_next(obj, o))) {
- switch (o->type) {
- case AV_OPT_TYPE_STRING:
- case AV_OPT_TYPE_BINARY:
- av_freep((uint8_t *)obj + o->offset);
- break;
-
- case AV_OPT_TYPE_DICT:
- av_dict_free((AVDictionary **)(((uint8_t *)obj) + o->offset));
- break;
+ void *pitem = (uint8_t *)obj + o->offset;
- case AV_OPT_TYPE_CHLAYOUT:
- av_channel_layout_uninit((AVChannelLayout *)(((uint8_t *)obj) + o->offset));
- break;
-
- default:
- break;
- }
+ if (o->type & AV_OPT_TYPE_FLAG_ARRAY)
+ opt_free_array(o, pitem, opt_array_pcount(pitem));
+ else
+ opt_free_elem(o, pitem);
}
}
@@ -1794,7 +2016,9 @@ const AVClass *av_opt_child_class_iterate(const AVClass *parent, void **iter)
void *av_opt_ptr(const AVClass *class, void *obj, const char *name)
{
const AVOption *opt= av_opt_find2(&class, name, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ, NULL);
- if(!opt)
+
+ // no direct access to array-type options
+ if (!opt || (opt->type & AV_OPT_TYPE_FLAG_ARRAY))
return NULL;
return (uint8_t*)obj + opt->offset;
}
@@ -1844,6 +2068,40 @@ static int opt_copy_elem(void *logctx, enum AVOptionType type,
return 0;
}
+static int opt_copy_array(void *logctx, const AVOption *o,
+ void **pdst, const void * const *psrc)
+{
+ unsigned nb_elems = *opt_array_pcount(psrc);
+ void *dst = NULL;
+ int ret;
+
+ if (*pdst == *psrc) {
+ *pdst = NULL;
+ *opt_array_pcount(pdst) = 0;
+ }
+
+ opt_free_array(o, pdst, opt_array_pcount(pdst));
+
+ dst = av_calloc(nb_elems, opt_elem_size[TYPE_BASE(o->type)]);
+ if (!dst)
+ return AVERROR(ENOMEM);
+
+ for (unsigned i = 0; i < nb_elems; i++) {
+ ret = opt_copy_elem(logctx, TYPE_BASE(o->type),
+ opt_array_pelem(o, dst, i),
+ opt_array_pelem(o, *(void**)psrc, i));
+ if (ret < 0) {
+ opt_free_array(o, &dst, &nb_elems);
+ return ret;
+ }
+ }
+
+ *pdst = dst;
+ *opt_array_pcount(pdst) = nb_elems;
+
+ return 0;
+}
+
int av_opt_copy(void *dst, const void *src)
{
const AVOption *o = NULL;
@@ -1861,7 +2119,9 @@ int av_opt_copy(void *dst, const void *src)
void *field_dst = (uint8_t *)dst + o->offset;
void *field_src = (uint8_t *)src + o->offset;
- int err = opt_copy_elem(dst, o->type, field_dst, field_src);
+ int err = (o->type & AV_OPT_TYPE_FLAG_ARRAY) ?
+ opt_copy_array(dst, o, field_dst, field_src) :
+ opt_copy_elem (dst, o->type, field_dst, field_src);
if (err < 0)
ret = err;
}
@@ -1990,6 +2250,24 @@ int av_opt_is_set_to_default(void *obj, const AVOption *o)
dst = ((uint8_t*)obj) + o->offset;
+ if (o->type & AV_OPT_TYPE_FLAG_ARRAY) {
+ const char *def = o->default_val.arr ? o->default_val.arr->def : NULL;
+ uint8_t *val;
+
+ ret = opt_get_array(o, dst, &val);
+ if (ret < 0)
+ return ret;
+
+ if (!!val != !!def)
+ ret = 0;
+ else if (val)
+ ret = !strcmp(val, def);
+
+ av_freep(&val);
+
+ return ret;
+ }
+
switch (o->type) {
case AV_OPT_TYPE_CONST:
return 1;
diff --git a/libavutil/opt.h b/libavutil/opt.h
index 82bca920fb..e6013662f6 100644
--- a/libavutil/opt.h
+++ b/libavutil/opt.h
@@ -250,6 +250,17 @@ enum AVOptionType{
AV_OPT_TYPE_COLOR,
AV_OPT_TYPE_BOOL,
AV_OPT_TYPE_CHLAYOUT,
+
+ /**
+ * May be combined with another regular option type to declare an array
+ * option.
+ *
+ * For array options, @ref AVOption.offset should refer to a pointer
+ * corresponding to the option type. The pointer should be immediately
+ * followed by an unsigned int that will store the number of elements in the
+ * array.
+ */
+ AV_OPT_TYPE_FLAG_ARRAY = (1 << 16),
};
/**
@@ -295,6 +306,40 @@ enum AVOptionType{
*/
#define AV_OPT_FLAG_CHILD_CONSTS (1 << 18)
+/**
+ * May be set as default_val for AV_OPT_TYPE_FLAG_ARRAY options.
+ */
+typedef struct AVOptionArrayDef {
+ /**
+ * Native access only.
+ *
+ * Default value of the option, as would be serialized by av_opt_get() (i.e.
+ * using the value of sep as the separator).
+ */
+ const char *def;
+
+ /**
+ * Minimum number of elements in the array. When this field is non-zero, def
+ * must be non-NULL and contain at least this number of elements.
+ */
+ unsigned size_min;
+ /**
+ * Maximum number of elements in the array, 0 when unlimited.
+ */
+ unsigned size_max;
+
+ /**
+ * Separator between array elements in string representations of this
+ * option, used by av_opt_set() and av_opt_get(). It must be a printable
+ * ASCII character, excluding alphanumeric and the backslash. A comma is
+ * used when sep=0.
+ *
+ * The separator and the backslash must be backslash-escaped in order to
+ * appear in string representations of the option value.
+ */
+ char sep;
+} AVOptionArrayDef;
+
/**
* AVOption
*/
@@ -317,8 +362,7 @@ typedef struct AVOption {
enum AVOptionType type;
/**
- * Native access only.
- *
+ * Native access only, except when documented otherwise.
* the default value for scalar options
*/
union {
@@ -327,6 +371,14 @@ typedef struct AVOption {
const char *str;
/* TODO those are unused now */
AVRational q;
+
+ /**
+ * Used for AV_OPT_TYPE_FLAG_ARRAY options. May be NULL.
+ *
+ * Foreign access to some members allowed, as noted in AVOptionArrayDef
+ * documentation.
+ */
+ const AVOptionArrayDef *arr;
} default_val;
double min; ///< minimum valid value for the option
double max; ///< maximum valid value for the option
diff --git a/libavutil/tests/opt.c b/libavutil/tests/opt.c
index e2582cc93d..ccf3a54f96 100644
--- a/libavutil/tests/opt.c
+++ b/libavutil/tests/opt.c
@@ -57,6 +57,15 @@ typedef struct TestContext {
int bool3;
AVDictionary *dict1;
AVDictionary *dict2;
+
+ int **array_int;
+ unsigned nb_array_int;
+
+ char **array_str;
+ unsigned nb_array_str;
+
+ AVDictionary **array_dict;
+ unsigned nb_array_dict;
} TestContext;
#define OFFSET(x) offsetof(TestContext, x)
@@ -65,6 +74,16 @@ typedef struct TestContext {
#define TEST_FLAG_LAME 02
#define TEST_FLAG_MU 04
+static const AVOptionArrayDef array_str = {
+ .sep = '|',
+ .def = "str0|str\\|1|str\\\\2",
+};
+
+static const AVOptionArrayDef array_dict = {
+ // there are three levels of escaping - C string, array option, dict - so 8 backslashes are needed to get a literal one inside a dict key/val
+ .def = "k00=v\\\\\\\\00:k01=v\\,01,k10=v\\\\=1\\\\:0",
+};
+
static const AVOption test_options[]= {
{"num", "set num", OFFSET(num), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 100, 1 },
{"toggle", "set toggle", OFFSET(toggle), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, 1 },
@@ -93,6 +112,9 @@ static const AVOption test_options[]= {
{"bool3", "set boolean value", OFFSET(bool3), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, 1 },
{"dict1", "set dictionary value", OFFSET(dict1), AV_OPT_TYPE_DICT, { .str = NULL}, 0, 0, 1 },
{"dict2", "set dictionary value", OFFSET(dict2), AV_OPT_TYPE_DICT, { .str = "happy=':-)'"}, 0, 0, 1 },
+ {"array_int", "array of ints", OFFSET(array_int), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = AV_OPT_FLAG_RUNTIME_PARAM },
+ {"array_str", "array of strings", OFFSET(array_str), AV_OPT_TYPE_STRING | AV_OPT_TYPE_FLAG_ARRAY, { .arr = &array_str }, .flags = AV_OPT_FLAG_RUNTIME_PARAM },
+ {"array_dict", "array of dicts", OFFSET(array_dict), AV_OPT_TYPE_DICT | AV_OPT_TYPE_FLAG_ARRAY, { .arr = &array_dict }, .flags = AV_OPT_FLAG_RUNTIME_PARAM },
{ NULL },
};
@@ -146,6 +168,17 @@ int main(void)
printf("flt=%.6f\n", test_ctx.flt);
printf("dbl=%.6f\n", test_ctx.dbl);
+ for (unsigned i = 0; i < test_ctx.nb_array_str; i++)
+ printf("array_str[%u]=%s\n", i, test_ctx.array_str[i]);
+
+ for (unsigned i = 0; i < test_ctx.nb_array_dict; i++) {
+ AVDictionary *d = test_ctx.array_dict[i];
+ const AVDictionaryEntry *e = NULL;
+
+ while ((e = av_dict_iterate(d, e)))
+ printf("array_dict[%u]: %s\t%s\n", i, e->key, e->value);
+ }
+
av_opt_show2(&test_ctx, NULL, -1, 0);
av_opt_free(&test_ctx);
@@ -177,6 +210,9 @@ int main(void)
TestContext test_ctx = { 0 };
TestContext test2_ctx = { 0 };
const AVOption *o = NULL;
+ char *val = NULL;
+ int ret;
+
test_ctx.class = &test_class;
test2_ctx.class = &test_class;
@@ -209,6 +245,17 @@ int main(void)
av_free(value1);
av_free(value2);
}
+
+ // av_opt_set(NULL) with an array option resets it
+ ret = av_opt_set(&test_ctx, "array_dict", NULL, 0);
+ printf("av_opt_set(\"array_dict\", NULL) -> %d\n", ret);
+ printf("array_dict=%sNULL; nb_array_dict=%u\n",
+ test_ctx.array_dict ? "non-" : "", test_ctx.nb_array_dict);
+
+ // av_opt_get() on an empty array should return a NULL string
+ ret = av_opt_get(&test_ctx, "array_dict", AV_OPT_ALLOW_NULL, (uint8_t**)&val);
+ printf("av_opt_get(\"array_dict\") -> %s\n", val ? val : "NULL");
+
av_opt_free(&test_ctx);
av_opt_free(&test2_ctx);
}
@@ -303,6 +350,8 @@ int main(void)
"bool1=true",
"bool2=auto",
"dict1='happy=\\:-):sad=\\:-('",
+ "array_int=0,32,2147483647",
+ "array_int=2147483648", // out of range, should fail
};
test_ctx.class = &test_class;
diff --git a/libavutil/version.h b/libavutil/version.h
index 1b50d6f5a0..09f8cdc292 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@
*/
#define LIBAVUTIL_VERSION_MAJOR 59
-#define LIBAVUTIL_VERSION_MINOR 0
+#define LIBAVUTIL_VERSION_MINOR 1
#define LIBAVUTIL_VERSION_MICRO 100
#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
diff --git a/tests/ref/fate/opt b/tests/ref/fate/opt
index 832f9cc8a9..f4fce1bd49 100644
--- a/tests/ref/fate/opt
+++ b/tests/ref/fate/opt
@@ -17,6 +17,12 @@ binary_size=4
num64=1
flt=0.333333
dbl=0.333333
+array_str[0]=str0
+array_str[1]=str|1
+array_str[2]=str\2
+array_dict[0]: k00 v\00
+array_dict[0]: k01 v,01
+array_dict[1]: k10 v=1:0
TestContext AVOptions:
-num <int> E.......... set num (from 0 to 100) (default 0)
-toggle <int> E.......... set toggle (from 0 to 1) (default 1)
@@ -45,6 +51,9 @@ TestContext AVOptions:
-bool3 <boolean> E.......... set boolean value (default false)
-dict1 <dictionary> E.......... set dictionary value
-dict2 <dictionary> E.......... set dictionary value (default "happy=':-)'")
+ -array_int [<int> ].........T. array of ints
+ -array_str [<string> ].........T. array of strings (default str0|str\|1|str\\2)
+ -array_dict [<dictionary>].........T. array of dicts (default k00=v\\\\00:k01=v\,01,k10=v\\=1\\:0)
Testing av_opt_is_set_to_default()
name: num default:1 error:
@@ -74,6 +83,9 @@ name: bool2 default:0 error:
name: bool3 default:1 error:
name: dict1 default:1 error:
name: dict2 default:0 error:
+name: array_int default:0 error:
+name: array_str default:0 error:
+name:array_dict default:0 error:
name: num default:1 error:
name: toggle default:1 error:
name: rational default:1 error:
@@ -101,6 +113,9 @@ name: bool2 default:1 error:
name: bool3 default:1 error:
name: dict1 default:1 error:
name: dict2 default:1 error:
+name: array_int default:0 error:
+name: array_str default:1 error:
+name:array_dict default:1 error:
Testing av_opt_get/av_opt_set()
name: num get: 0 set: OK get: 0 OK
@@ -127,9 +142,15 @@ name: bool2 get: true set: OK get: true
name: bool3 get: false set: OK get: false OK
name: dict1 get: set: OK get: OK
name: dict2 get: happy=\:-) set: OK get: happy=\:-) OK
+name: array_int get: set: OK get: OK
+name: array_str get: str0|str\|1|str\\2 set: OK get: str0|str\|1|str\\2 OK
+name: array_dict get: k00=v\\\\00:k01=v\,01,k10=v\\=1\\:0 set: OK get: k00=v\\\\00:k01=v\,01,k10=v\\=1\\:0 OK
+av_opt_set("array_dict", NULL) -> 0
+array_dict=NULL; nb_array_dict=0
+av_opt_get("array_dict") -> NULL
Test av_opt_serialize()
-num=0,toggle=1,rational=1/1,string=default,escape=\\\=\,,flags=0x00000001,size=200x300,pix_fmt=0bgr,sample_fmt=s16,video_rate=25/1,duration=0.001,color=0xffc0cbff,cl=hexagonal,bin=62696E00,bin1=,bin2=,num64=1,flt=0.333333,dbl=0.333333,bool1=auto,bool2=true,bool3=false,dict1=,dict2=happy\=\\:-)
+num=0,toggle=1,rational=1/1,string=default,escape=\\\=\,,flags=0x00000001,size=200x300,pix_fmt=0bgr,sample_fmt=s16,video_rate=25/1,duration=0.001,color=0xffc0cbff,cl=hexagonal,bin=62696E00,bin1=,bin2=,num64=1,flt=0.333333,dbl=0.333333,bool1=auto,bool2=true,bool3=false,dict1=,dict2=happy\=\\:-),array_int=,array_str=str0|str\\|1|str\\\\2,array_dict=k00\=v\\\\\\\\00:k01\=v\\\,01\,k10\=v\\\\\=1\\\\:0
Setting entry with key 'num' to value '0'
Setting entry with key 'toggle' to value '1'
Setting entry with key 'rational' to value '1/1'
@@ -154,7 +175,10 @@ Setting entry with key 'bool2' to value 'true'
Setting entry with key 'bool3' to value 'false'
Setting entry with key 'dict1' to value ''
Setting entry with key 'dict2' to value 'happy=\:-)'
-num=0,toggle=1,rational=1/1,string=default,escape=\\\=\,,flags=0x00000001,size=200x300,pix_fmt=0bgr,sample_fmt=s16,video_rate=25/1,duration=0.001,color=0xffc0cbff,cl=hexagonal,bin=62696E00,bin1=,bin2=,num64=1,flt=0.333333,dbl=0.333333,bool1=auto,bool2=true,bool3=false,dict1=,dict2=happy\=\\:-)
+Setting entry with key 'array_int' to value ''
+Setting entry with key 'array_str' to value 'str0|str\|1|str\\2'
+Setting entry with key 'array_dict' to value 'k00=v\\\\00:k01=v\,01,k10=v\\=1\\:0'
+num=0,toggle=1,rational=1/1,string=default,escape=\\\=\,,flags=0x00000001,size=200x300,pix_fmt=0bgr,sample_fmt=s16,video_rate=25/1,duration=0.001,color=0xffc0cbff,cl=hexagonal,bin=62696E00,bin1=,bin2=,num64=1,flt=0.333333,dbl=0.333333,bool1=auto,bool2=true,bool3=false,dict1=,dict2=happy\=\\:-),array_int=,array_str=str0|str\\|1|str\\\\2,array_dict=k00\=v\\\\\\\\00:k01\=v\\\,01\,k10\=v\\\\\=1\\\\:0
Testing av_set_options_string()
Setting options string ''
@@ -378,6 +402,13 @@ OK 'bool2=auto'
Setting options string 'dict1='happy=\:-):sad=\:-(''
Setting entry with key 'dict1' to value 'happy=\:-):sad=\:-('
OK 'dict1='happy=\:-):sad=\:-(''
+Setting options string 'array_int=0,32,2147483647'
+Setting entry with key 'array_int' to value '0,32,2147483647'
+OK 'array_int=0,32,2147483647'
+Setting options string 'array_int=2147483648'
+Setting entry with key 'array_int' to value '2147483648'
+Value 2147483648.000000 for parameter 'array_int' out of range [0 - 2.14748e+09]
+Error 'array_int=2147483648'
Testing av_opt_set_from_string()
Setting options string ''
More information about the ffmpeg-cvslog
mailing list