Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Anton Khirnov <anton@khirnov.net>
To: ffmpeg-devel@ffmpeg.org
Subject: [FFmpeg-devel] [PATCH 15/38] lavu/opt: add array options
Date: Fri, 23 Feb 2024 14:58:37 +0100
Message-ID: <20240223143115.16521-16-anton@khirnov.net> (raw)
In-Reply-To: <20240223143115.16521-1-anton@khirnov.net>

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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  parent reply	other threads:[~2024-02-23 14:32 UTC|newest]

Thread overview: 63+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-23 13:58 [FFmpeg-devel] [PATCH] array AVOptions and side data preference Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 01/38] lavu/opt: cosmetics, change option flags to (1 << N) style Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 02/38] lavu/opt: cosmetics, move AV_OPT_FLAG_* out of AVOption Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 03/38] lavu/opt: document AVOption.flags Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 04/38] lavu/opt: cosmetics, group (un)init and management functions together Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 05/38] lavu/opt: cosmetics, group option setting function together Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 06/38] lavu/opt: cosmetics, group option reading " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 07/38] lavu/opt: simplify printing option type in opt_list() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 08/38] lavu/opt: factor out printing option default from opt_list() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 09/38] lavu/opt: drop useless handling of NULL return from get_bool_name() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 10/38] lavu/opt: drop an always-NULL argument to get_number() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 11/38] lavu/opt: simplify error handling in get_number() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 12/38] lavu/opt: get rid of useless read_number() calls Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 13/38] lavu/opt: factor per-type dispatch out of av_opt_get() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 14/38] lavu/opt: factor per-type dispatch out of av_opt_set() Anton Khirnov
2024-02-23 22:50   ` Michael Niedermayer
2024-02-24 22:41     ` James Almer
2024-03-05 23:08       ` Michael Niedermayer
2024-03-05 23:14         ` James Almer
2024-03-01 16:07     ` Anton Khirnov
2024-03-03 12:17       ` Anton Khirnov
2024-03-05 23:12         ` Michael Niedermayer
2024-03-05 23:21           ` James Almer
2024-02-23 13:58 ` Anton Khirnov [this message]
2024-02-23 19:05   ` [FFmpeg-devel] [PATCH 15/38] lavu/opt: add array options Marton Balint
2024-02-26 17:14     ` Anton Khirnov
2024-02-26 17:19       ` James Almer
2024-02-26 17:20       ` Andreas Rheinhardt
2024-02-26 19:38       ` Marton Balint
2024-03-03 14:55         ` Anton Khirnov
2024-03-03 15:53           ` Diederick C. Niehorster
2024-03-03 15:57             ` James Almer
2024-03-03 21:05               ` Marton Balint
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 16/38] lavc: add a decoder option for configuring side data preference Anton Khirnov
2024-02-23 17:51   ` Marton Balint
2024-02-23 17:53     ` James Almer
2024-02-23 18:34       ` James Almer
2024-02-26 17:08         ` Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 17/38] avcodec: add internal side data wrappers Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 18/38] lavc: add content light/mastering display " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 19/38] avcodec/av1dec: respect side data preference Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 20/38] avcodec/cri: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 21/38] avcodec/h264_slice: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 22/38] lavc/hevcdec: pass an actual codec context to ff_h2645_sei_to_frame() Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 23/38] avcodec/hevcdec: respect side data preference Anton Khirnov
2024-02-23 19:11   ` Marton Balint
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 24/38] avcodec/libjxldec: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 25/38] avcodec/mjpegdec: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 26/38] avcodec/mpeg12dec: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 27/38] avcodec/pngdec: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 28/38] avcodec/tiff: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 29/38] avcodec/webp: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 30/38] avcodec/libdav1d: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 31/38] avcodec/dpx: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 32/38] avcodec/mpeg12dec: use ff_frame_new_side_data Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 33/38] avcodec/h2645_sei: use ff_frame_new_side_data_from_buf Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 34/38] avcodec/snowdec: use ff_frame_new_side_data Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 35/38] avcodec/mjpegdec: " Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 36/38] avcodec/hevcdec: switch to ff_frame_new_side_data_from_buf Anton Khirnov
2024-02-23 13:58 ` [FFmpeg-devel] [PATCH 37/38] lavc/*dec: use side data preference for mastering display/content light metadata Anton Khirnov
2024-02-23 13:59 ` [FFmpeg-devel] [PATCH 38/38] tests/fate/matroska: add tests for side data preference Anton Khirnov
2024-02-23 15:02   ` Anton Khirnov
2024-02-23 15:08     ` James Almer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240223143115.16521-16-anton@khirnov.net \
    --to=anton@khirnov.net \
    --cc=ffmpeg-devel@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git