[FFmpeg-devel] [PATCH 15/38] lavu/opt: add array options

Anton Khirnov anton at khirnov.net
Fri Feb 23 15:58:37 EET 2024


AVOption.array_max_size is added before AVOption.unit to avoid
increasing sizeof(AVOption).
---
 doc/APIchanges        |   3 +
 libavutil/opt.c       | 344 ++++++++++++++++++++++++++++++++++++------
 libavutil/opt.h       |  26 ++++
 libavutil/tests/opt.c |  34 +++++
 tests/ref/fate/opt    |  23 ++-
 5 files changed, 385 insertions(+), 45 deletions(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index d26110b285..371fd2f465 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2023-02-09
 
 API changes, most recent first:
 
+2024-02-xx - xxxxxxxxxx - lavu 58.xx.100 - opt.h
+  Add AV_OPT_FLAG_ARRAY and AVOption.array_max_size.
+
 2024-02-21 - xxxxxxxxxx - lavc 60.40.100 - avcodec.h
   Deprecate AV_INPUT_BUFFER_MIN_SIZE without replacement.
 
diff --git a/libavutil/opt.c b/libavutil/opt.c
index ebc8063dc6..88236660a1 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -56,6 +56,77 @@ const AVOption *av_opt_next(const void *obj, const AVOption *last)
     return NULL;
 }
 
+static const size_t opt_elem_size[] = {
+    [AV_OPT_TYPE_FLAGS]         = sizeof(unsigned),
+    [AV_OPT_TYPE_INT]           = sizeof(int),
+    [AV_OPT_TYPE_INT64]         = sizeof(int64_t),
+    [AV_OPT_TYPE_UINT64]        = sizeof(uint64_t),
+    [AV_OPT_TYPE_DOUBLE]        = sizeof(double),
+    [AV_OPT_TYPE_FLOAT]         = sizeof(float),
+    [AV_OPT_TYPE_STRING]        = sizeof(char *),
+    [AV_OPT_TYPE_RATIONAL]      = sizeof(AVRational),
+    [AV_OPT_TYPE_BINARY]        = sizeof(uint8_t *),
+    [AV_OPT_TYPE_DICT]          = sizeof(AVDictionary *),
+    [AV_OPT_TYPE_IMAGE_SIZE]    = sizeof(int[2]),
+    [AV_OPT_TYPE_VIDEO_RATE]    = sizeof(AVRational),
+    [AV_OPT_TYPE_PIXEL_FMT]     = sizeof(int),
+    [AV_OPT_TYPE_SAMPLE_FMT]    = sizeof(int),
+    [AV_OPT_TYPE_DURATION]      = sizeof(int64_t),
+    [AV_OPT_TYPE_COLOR]         = sizeof(uint8_t[4]),
+#if FF_API_OLD_CHANNEL_LAYOUT
+    [AV_OPT_TYPE_CHANNEL_LAYOUT]= sizeof(uint64_t),
+#endif
+    [AV_OPT_TYPE_CHLAYOUT]      = sizeof(AVChannelLayout),
+    [AV_OPT_TYPE_BOOL]          = sizeof(int),
+};
+
+static uint8_t opt_array_sep(const AVOption *o)
+{
+    av_assert1(o->flags & AV_OPT_FLAG_ARRAY);
+    return o->default_val.str ? o->default_val.str[0] : ',';
+}
+
+static void *opt_array_pelem(const AVOption *o, void *array, unsigned idx)
+{
+    av_assert1(o->flags & AV_OPT_FLAG_ARRAY);
+    return (uint8_t *)array + idx * opt_elem_size[o->type];
+}
+
+static unsigned *opt_array_pcount(void *parray)
+{
+    return (unsigned *)((void **)parray + 1);
+}
+
+static void opt_free_elem(const AVOption *o, void *ptr)
+{
+    switch (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) {
@@ -580,6 +651,76 @@ 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 size_t elem_size = opt_elem_size[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 (o->array_max_size && nb_elems >= o->array_max_size) {
+            av_log(obj, AV_LOG_ERROR,
+                   "Cannot assign more than %u elements to array option %s\n",
+                   o->array_max_size, 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));
+
+    *((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;
@@ -595,14 +736,16 @@ 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->flags & AV_OPT_FLAG_ARRAY) ?
+            opt_set_array : opt_set_elem)(obj, target_obj, o, val, dst);
 }
 
 #define OPT_EVAL_NUMBER(name, opttype, vartype)                         \
 int av_opt_eval_ ## name(void *obj, const AVOption *o,                  \
                          const char *val, vartype *name ## _out)        \
 {                                                                       \
-    if (!o || o->type != opttype || o->flags & AV_OPT_FLAG_READONLY)    \
+    if (!o || o->type != opttype || o->flags & AV_OPT_FLAG_READONLY ||  \
+        o->flags & AV_OPT_FLAG_ARRAY)                                   \
         return AVERROR(EINVAL);                                         \
     return set_string_number(obj, obj, o, val, name ## _out);           \
 }
@@ -623,7 +766,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 | AV_OPT_FLAG_ARRAY))
         return AVERROR(EINVAL);
 
     dst = ((uint8_t *)target_obj) + o->offset;
@@ -656,7 +799,8 @@ int av_opt_set_bin(void *obj, const char *name, const uint8_t *val, int len, int
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
 
-    if (o->type != AV_OPT_TYPE_BINARY || o->flags & AV_OPT_FLAG_READONLY)
+    if (o->type != AV_OPT_TYPE_BINARY ||
+        o->flags & (AV_OPT_FLAG_READONLY | AV_OPT_FLAG_ARRAY))
         return AVERROR(EINVAL);
 
     ptr = len ? av_malloc(len) : NULL;
@@ -682,9 +826,10 @@ int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->type != AV_OPT_TYPE_IMAGE_SIZE) {
+    if (o->type != AV_OPT_TYPE_IMAGE_SIZE ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value set by option '%s' is not an image size.\n", o->name);
+               "The value set by option '%s' is not a scalar image size.\n", o->name);
         return AVERROR(EINVAL);
     }
     if (w<0 || h<0) {
@@ -704,9 +849,11 @@ int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int searc
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->type != AV_OPT_TYPE_VIDEO_RATE) {
+    if (o->type != AV_OPT_TYPE_VIDEO_RATE ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         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 scalar video rate.\n",
+               o->name);
         return AVERROR(EINVAL);
     }
     if (val.num <= 0 || val.den <= 0)
@@ -724,9 +871,10 @@ static int set_format(void *obj, const char *name, int fmt, int search_flags,
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->type != type) {
+    if (o->type != type ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value set by option '%s' is not a %s format", name, desc);
+               "The value set by option '%s' is not a scalar %s format", name, desc);
         return AVERROR(EINVAL);
     }
 
@@ -762,9 +910,10 @@ int av_opt_set_channel_layout(void *obj, const char *name, int64_t cl, int searc
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->type != AV_OPT_TYPE_CHANNEL_LAYOUT) {
+    if (o->type != AV_OPT_TYPE_CHANNEL_LAYOUT ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value set by option '%s' is not a channel layout.\n", o->name);
+               "The value set by option '%s' is not a scalar channel layout.\n", o->name);
         return AVERROR(EINVAL);
     }
     *(int64_t *)(((uint8_t *)target_obj) + o->offset) = cl;
@@ -782,7 +931,7 @@ int av_opt_set_dict_val(void *obj, const char *name, const AVDictionary *val,
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->flags & AV_OPT_FLAG_READONLY)
+    if (o->flags & (AV_OPT_FLAG_READONLY | AV_OPT_FLAG_ARRAY))
         return AVERROR(EINVAL);
 
     dst = (AVDictionary **)(((uint8_t *)target_obj) + o->offset);
@@ -802,6 +951,8 @@ int av_opt_set_chlayout(void *obj, const char *name,
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
+    if (o->flags & AV_OPT_FLAG_ARRAY)
+        return AVERROR(EINVAL);
 
     dst = (AVChannelLayout*)((uint8_t*)target_obj + o->offset);
 
@@ -954,6 +1105,66 @@ FF_ENABLE_DEPRECATION_WARNINGS
     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;
@@ -969,8 +1180,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->flags & AV_OPT_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)
@@ -993,6 +1215,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->flags & AV_OPT_FLAG_ARRAY)
+        return AVERROR(EINVAL);
 
     dst = ((uint8_t *)target_obj) + o->offset;
 
@@ -1048,9 +1272,10 @@ int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_
     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_IMAGE_SIZE) {
+    if (o->type != AV_OPT_TYPE_IMAGE_SIZE ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         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 scalar image size.\n", name);
         return AVERROR(EINVAL);
     }
 
@@ -1083,9 +1308,10 @@ static int get_format(void *obj, const char *name, int search_flags, int *out_fm
     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 != type) {
+    if (o->type != type ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value for option '%s' is not a %s format.\n", desc, name);
+               "The value for option '%s' is not a scalar %s format.\n", desc, name);
         return AVERROR(EINVAL);
     }
 
@@ -1112,9 +1338,10 @@ int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int
     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_CHANNEL_LAYOUT) {
+    if (o->type != AV_OPT_TYPE_CHANNEL_LAYOUT ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value for option '%s' is not a channel layout.\n", name);
+               "The value for option '%s' is not a scalar channel layout.\n", name);
         return AVERROR(EINVAL);
     }
 
@@ -1131,9 +1358,10 @@ int av_opt_get_chlayout(void *obj, const char *name, int search_flags, AVChannel
     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_CHLAYOUT) {
+    if (o->type != AV_OPT_TYPE_CHLAYOUT ||
+        o->flags & AV_OPT_FLAG_ARRAY) {
         av_log(obj, AV_LOG_ERROR,
-               "The value for option '%s' is not a channel layout.\n", name);
+               "The value for option '%s' is not a scalar channel layout.\n", name);
         return AVERROR(EINVAL);
     }
 
@@ -1149,7 +1377,8 @@ int av_opt_get_dict_val(void *obj, const char *name, int search_flags, AVDiction
 
     if (!o || !target_obj)
         return AVERROR_OPTION_NOT_FOUND;
-    if (o->type != AV_OPT_TYPE_DICT)
+    if (o->type != AV_OPT_TYPE_DICT ||
+        o->flags & AV_OPT_FLAG_ARRAY)
         return AVERROR(EINVAL);
 
     src = *(AVDictionary **)(((uint8_t *)target_obj) + o->offset);
@@ -1165,7 +1394,8 @@ int av_opt_flag_is_set(void *obj, const char *field_name, const char *flag_name)
                                         field ? field->unit : NULL, 0, 0);
     int64_t res;
 
-    if (!field || !flag || flag->type != AV_OPT_TYPE_CONST ||
+    if (!field || !(field->flags & AV_OPT_FLAG_ARRAY) ||
+        !flag || flag->type != AV_OPT_TYPE_CONST ||
         av_opt_get_int(obj, field_name, 0, &res) < 0)
         return 0;
     return res & flag->default_val.i64;
@@ -1284,8 +1514,12 @@ static void log_type(void *av_log_obj, const AVOption *o,
 
     if (o->type == AV_OPT_TYPE_CONST && 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 (o->type < FF_ARRAY_ELEMS(desc) && desc[o->type]) {
+        if (o->flags & AV_OPT_FLAG_ARRAY)
+            av_log(av_log_obj, AV_LOG_INFO, "×%-11s ", desc[o->type]);
+        else
+            av_log(av_log_obj, AV_LOG_INFO, "%-12s ", desc[o->type]);
+    }
     else
         av_log(av_log_obj, AV_LOG_INFO, "%-12s ", "");
 }
@@ -1470,6 +1704,22 @@ void av_opt_set_defaults2(void *s, int mask, int flags)
         if (opt->flags & AV_OPT_FLAG_READONLY)
             continue;
 
+        if (opt->flags & AV_OPT_FLAG_ARRAY) {
+            const char *val = opt->default_val.str;
+            if (val) {
+                const char sep = *val++;
+
+                // make sure people don't forget to set the separator correctly
+                av_assert0(sep &&
+                           (sep < 'a' || sep > 'z') &&
+                           (sep < 'A' || sep > 'Z') &&
+                           (sep < '0' || sep > '9'));
+
+                opt_set_array(s, s, opt, val, dst);
+            }
+            continue;
+        }
+
         switch (opt->type) {
             case AV_OPT_TYPE_CONST:
                 /* Nothing to be done here */
@@ -1717,23 +1967,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;
+        void *pitem = (uint8_t *)obj + o->offset;
 
-        case AV_OPT_TYPE_DICT:
-            av_dict_free((AVDictionary **)(((uint8_t *)obj) + o->offset));
-            break;
-
-        case AV_OPT_TYPE_CHLAYOUT:
-            av_channel_layout_uninit((AVChannelLayout *)(((uint8_t *)obj) + o->offset));
-            break;
-
-        default:
-            break;
-        }
+        if (o->flags & AV_OPT_FLAG_ARRAY)
+            opt_free_array(o, pitem, opt_array_pcount(pitem));
+        else
+            opt_free_elem(o, pitem);
     }
 }
 
@@ -1835,7 +2074,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->flags & AV_OPT_FLAG_ARRAY))
         return NULL;
     return (uint8_t*)obj + opt->offset;
 }
@@ -2067,6 +2308,23 @@ int av_opt_is_set_to_default(void *obj, const AVOption *o)
 
     dst = ((uint8_t*)obj) + o->offset;
 
+    if (o->flags & AV_OPT_FLAG_ARRAY) {
+        uint8_t *val;
+
+        ret = opt_get_array(o, dst, &val);
+        if (ret < 0)
+            return ret;
+
+        if (!!val != !!o->default_val.str)
+            ret = 0;
+        else if (val)
+            ret = !strcmp(val, o->default_val.str + 1);
+
+        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 e34b8506f8..c5678c0296 100644
--- a/libavutil/opt.h
+++ b/libavutil/opt.h
@@ -288,6 +288,16 @@ enum AVOptionType{
  */
 #define AV_OPT_FLAG_CHILD_CONSTS    (1 << 18)
 
+/**
+ * The option is an array.
+ *
+ * When adding array options to an object, @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.
+ */
+#define AV_OPT_FLAG_ARRAY           (1 << 19)
+
 /**
  * AVOption
  */
@@ -313,6 +323,16 @@ typedef struct AVOption {
     union {
         int64_t i64;
         double dbl;
+
+        /**
+         * This member is always used for AV_OPT_FLAG_ARRAY options. When
+         * non-NULL, the first character of the string must be the separator to
+         * be used for (de)serializing lists to/from strings with av_opt_get(),
+         * av_opt_set(), and similar. The separator must not conflict with valid
+         * option names or be a backslash. When the value is null, comma is used
+         * as the separator. The rest of the string is parsed as for
+         * av_opt_set().
+         */
         const char *str;
         /* TODO those are unused now */
         AVRational q;
@@ -325,6 +345,12 @@ typedef struct AVOption {
      */
     int flags;
 
+    /**
+     * For options flagged with AV_OPT_FLAG_ARRAY, this specifies the maximum
+     * number of elements that can be added to it.
+     */
+    unsigned array_max_size;
+
     /**
      * The logical unit to which the option belongs. Non-constant
      * options and corresponding named constants share the same
diff --git a/libavutil/tests/opt.c b/libavutil/tests/opt.c
index e2582cc93d..5b218b6b0a 100644
--- a/libavutil/tests/opt.c
+++ b/libavutil/tests/opt.c
@@ -57,6 +57,12 @@ typedef struct TestContext {
     int bool3;
     AVDictionary *dict1;
     AVDictionary *dict2;
+
+    char          **array_str;
+    unsigned     nb_array_str;
+
+    AVDictionary  **array_dict;
+    unsigned     nb_array_dict;
 } TestContext;
 
 #define OFFSET(x) offsetof(TestContext, x)
@@ -93,6 +99,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_str",  "array of strings",     OFFSET(array_str),    AV_OPT_TYPE_STRING,         { .str = "|str0|str\\|1|str\\\\2" }, .flags = AV_OPT_FLAG_ARRAY },
+    // 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
+    {"array_dict", "array of dicts",       OFFSET(array_dict),   AV_OPT_TYPE_DICT,           { .str = ",k00=v\\\\\\\\00:k01=v\\,01,k10=v\\\\=1\\\\:0" }, .flags = AV_OPT_FLAG_ARRAY },
     { NULL },
 };
 
@@ -146,6 +155,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 +197,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 +232,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);
     }
diff --git a/tests/ref/fate/opt b/tests/ref/fate/opt
index 832f9cc8a9..4ed632fea8 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,8 @@ 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_str         ×<string>    ........... array of strings (default "|str0|str\|1|str\\2")
+  -array_dict        ×<dictionary> ........... 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 +82,8 @@ name:     bool2 default:0 error:
 name:     bool3 default:1 error:
 name:     dict1 default:1 error:
 name:     dict2 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 +111,8 @@ name:     bool2 default:1 error:
 name:     bool3 default:1 error:
 name:     dict1 default:1 error:
 name:     dict2 default:1 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 +139,14 @@ 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_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_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 +171,9 @@ 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_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_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 ''
-- 
2.42.0



More information about the ffmpeg-devel mailing list