[FFmpeg-devel] [PATCH 01/42] lavu/opt: add API for setting array-type option values

Anton Khirnov anton at khirnov.net
Tue Aug 27 18:04:41 EEST 2024


Previously one could only replace the entire array with a new one
deserialized from a string. The new API allows inserting, replacing, and
removing arbitrary element ranges.
---
 doc/APIchanges      |   3 +
 libavutil/opt.c     | 186 ++++++++++++++++++++++++++++++++++++++++++++
 libavutil/opt.h     |  56 +++++++++++++
 libavutil/version.h |   2 +-
 4 files changed, 246 insertions(+), 1 deletion(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index 226c6f8b10..9c3eeffff5 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-09-xx - xxxxxxxxx - lavu 59.36.100 - opt.h
+  Add av_opt_set_array() and AV_OPT_ARRAY_REPLACE.
+
 2024-08-xx - xxxxxxxxx - lavu 59.35.100 - opt.h
   Add av_opt_get_array_size() and av_opt_get_array().
 
diff --git a/libavutil/opt.c b/libavutil/opt.c
index d515e20e97..e07ec8ce0f 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -2244,6 +2244,192 @@ fail:
     return ret;
 }
 
+int av_opt_set_array(void *obj, const char *name, int search_flags,
+                     unsigned int start_elem, unsigned int nb_elems,
+                     enum AVOptionType val_type, const void *val)
+{
+    const size_t elem_size_val = opt_elem_size[TYPE_BASE(val_type)];
+
+    const AVOption *o;
+    const AVOptionArrayDef *arr;
+    void *target_obj;
+
+    void *parray;
+    void *new_elems;
+    unsigned *array_size, new_size;
+    size_t elem_size;
+
+    int ret;
+
+    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) ||
+        (val_type & AV_OPT_TYPE_FLAG_ARRAY))
+        return AVERROR(EINVAL);
+
+    arr        = o->default_val.arr;
+    parray     = (uint8_t *)target_obj + o->offset;
+    array_size = opt_array_pcount(parray);
+    elem_size  = opt_elem_size[TYPE_BASE(o->type)];
+
+    if (start_elem > *array_size)
+        return AVERROR(EINVAL);
+
+    // compute new array size
+    if (!val) {
+        if (*array_size - start_elem < nb_elems)
+            return AVERROR(EINVAL);
+
+        new_size = *array_size - nb_elems;
+    } else if (search_flags & AV_OPT_ARRAY_REPLACE) {
+        if (start_elem >= UINT_MAX - nb_elems)
+            return AVERROR(EINVAL);
+
+        new_size = FFMAX(*array_size, start_elem + nb_elems);
+    } else {
+        if (nb_elems >= UINT_MAX - *array_size)
+            return AVERROR(EINVAL);
+
+        new_size = *array_size + nb_elems;
+    }
+
+    if (arr &&
+        ((arr->size_max && new_size > arr->size_max) ||
+         (arr->size_min && new_size < arr->size_min)))
+        return AVERROR(EINVAL);
+
+    // desired operation is shrinking the array
+    if (!val) {
+        void *array = *(void**)parray;
+
+        for (unsigned i = 0; i < nb_elems; i++) {
+            opt_free_elem(o->type,
+                          opt_array_pelem(o, array, start_elem + i));
+        }
+
+        if (new_size > 0) {
+            memmove(opt_array_pelem(o, array, start_elem),
+                    opt_array_pelem(o, array, start_elem + nb_elems),
+                    elem_size * (*array_size - start_elem - nb_elems));
+
+            array = av_realloc_array(array, new_size, elem_size);
+            if (!array)
+                return AVERROR(ENOMEM);
+
+            *(void**)parray = array;
+        } else
+            av_freep(parray);
+
+        *array_size = new_size;
+
+        return 0;
+    }
+
+    // otherwise, desired operation is insert/replace;
+    // first, store new elements in a separate array to simplify
+    // rollback on failure
+    new_elems = av_calloc(nb_elems, elem_size);
+    if (!new_elems)
+        return AVERROR(ENOMEM);
+
+    // convert/validate each new element
+    for (unsigned i = 0; i < nb_elems; i++) {
+        void       *dst = opt_array_pelem(o, new_elems, i);
+        const void *src = (uint8_t*)val + i * elem_size_val;
+
+        double     num = 1.0;
+        int        den = 1;
+        int64_t intnum = 1;
+
+        if (val_type == TYPE_BASE(o->type)) {
+            ret = opt_copy_elem(obj, val_type, dst, src);
+            if (ret < 0)
+                goto fail;
+
+            // validate the range for numeric options
+            ret = read_number(o, dst, &num, &den, &intnum);
+            if (ret >= 0 && TYPE_BASE(o->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, "Cannot set array element %u for "
+                       "parameter '%s': value %f out of range [%g - %g]\n",
+                       start_elem + i, o->name, num, o->min, o->max);
+                ret = AVERROR(ERANGE);
+                goto fail;
+            }
+        } else if (val_type == AV_OPT_TYPE_STRING) {
+            ret = opt_set_elem(obj, target_obj, o, *(const char **)src, dst);
+            if (ret < 0)
+                goto fail;
+        } if (val_type == AV_OPT_TYPE_INT      ||
+              val_type == AV_OPT_TYPE_INT64    ||
+              val_type == AV_OPT_TYPE_FLOAT    ||
+              val_type == AV_OPT_TYPE_DOUBLE   ||
+              val_type == AV_OPT_TYPE_RATIONAL) {
+            int ret;
+
+            switch (val_type) {
+            case AV_OPT_TYPE_INT:       intnum = *(int*)src;                break;
+            case AV_OPT_TYPE_INT64:     intnum = *(int64_t*)src;            break;
+            case AV_OPT_TYPE_FLOAT:     num    = *(float*)src;              break;
+            case AV_OPT_TYPE_DOUBLE:    num    = *(double*)src;             break;
+            case AV_OPT_TYPE_RATIONAL:  intnum = ((AVRational*)src)->num;
+                                        den    = ((AVRational*)src)->den;   break;
+            default: av_assert0(0);
+            }
+
+            ret = write_number(obj, o, dst, num, den, intnum);
+            if (ret < 0)
+                goto fail;
+        } else {
+            ret = AVERROR(ENOSYS);
+            goto fail;
+        }
+    }
+
+    // commit new elements to the array
+    if (start_elem == 0 && nb_elems == new_size) {
+        // replacing the existing array entirely
+        opt_free_array(o, parray, array_size);
+        *(void**)parray = new_elems;
+        *array_size     = nb_elems;
+
+        new_elems = NULL;
+        nb_elems  = 0;
+    } else {
+        void *array = av_realloc_array(*(void**)parray, new_size, elem_size);
+        if (!array) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        if (search_flags & AV_OPT_ARRAY_REPLACE) {
+            // free the elements being overwritten
+            for (unsigned i = start_elem; i < FFMIN(start_elem + nb_elems, *array_size); i++)
+                opt_free_elem(o->type, opt_array_pelem(o, array, i));
+        } else {
+            // shift existing elements to the end
+            memmove(opt_array_pelem(o, array, start_elem + nb_elems),
+                    opt_array_pelem(o, array, start_elem),
+                    elem_size * (*array_size - start_elem));
+        }
+
+        memcpy((uint8_t*)array + elem_size * start_elem, new_elems, elem_size * nb_elems);
+
+        av_freep(&new_elems);
+        nb_elems = 0;
+
+        *(void**)parray = array;
+        *array_size     = new_size;
+    }
+
+fail:
+    opt_free_array(o, &new_elems, &nb_elems);
+
+    return ret;
+}
+
 int av_opt_query_ranges(AVOptionRanges **ranges_arg, void *obj, const char *key, int flags)
 {
     int ret;
diff --git a/libavutil/opt.h b/libavutil/opt.h
index cf9ebb9b12..be189f7653 100644
--- a/libavutil/opt.h
+++ b/libavutil/opt.h
@@ -618,6 +618,12 @@ const AVClass *av_opt_child_class_iterate(const AVClass *parent, void **iter);
  */
 #define AV_OPT_ALLOW_NULL (1 << 2)
 
+/**
+ * May be used with av_opt_set_array() to signal that new elements should
+ * replace the existing ones in the indicated range.
+ */
+#define AV_OPT_ARRAY_REPLACE (1 << 3)
+
 /**
  *  Allows av_opt_query_ranges and av_opt_query_ranges_default to return more than
  *  one component for certain option types.
@@ -896,6 +902,56 @@ int av_opt_set_dict_val(void *obj, const char *name, const AVDictionary *val, in
      av_opt_set_bin(obj, name, (const uint8_t *)(val), \
                     av_int_list_length(val, term) * sizeof(*(val)), flags))
 
+/**
+ * Add, replace, or remove elements for an array option. Which of these
+ * operations is performed depends on the values of val and search_flags.
+ *
+ * @param start_elem Index of the first array element to modify; must not be
+ *                   larger than array size as returned by
+ *                   av_opt_get_array_size().
+ * @param nb_elems number of array elements to modify; when val is NULL,
+ *                 start_elem+nb_elems must not be larger than array size as
+ *                 returned by av_opt_get_array_size()
+ *
+ * @param val_type Option type corresponding to the type of val, ignored when val is
+ *                 NULL.
+ *
+ *                 The effect of this function will will be as if av_opt_setX()
+ *                 was called for each element, where X is specified by type.
+ *                 E.g. AV_OPT_TYPE_STRING corresponds to av_opt_set().
+ *
+ *                 Typically this should be the same as the scalarized type of
+ *                 the AVOption being set, but certain conversions are also
+ *                 possible - the same as those done by the corresponding
+ *                 av_opt_set*() function. E.g. any option type can be set from
+ *                 a string, numeric types can be set from int64, double, or
+ *                 rational, etc.
+ *
+ * @param val Array with nb_elems elements or NULL.
+ *
+ *            When NULL, nb_elems array elements starting at start_elem are
+ *            removed from the array. Any array elements remaining at the end
+ *            are shifted by nb_elems towards the first element in order to keep
+ *            the array contiguous.
+ *
+ *            Otherwise (val is non-NULL), the type of val must match the
+ *            underlying C type as documented for val_type.
+ *
+ *            When AV_OPT_ARRAY_REPLACE is not set in search_flags, the array is
+ *            enlarged by nb_elems, and the contents of val are inserted at
+ *            start_elem. Previously existing array elements from start_elem
+ *            onwards (if present) are shifted by nb_elems away from the first
+ *            element in order to make space for the new elements.
+ *
+ *            When AV_OPT_ARRAY_REPLACE is set in search_flags, the contents
+ *            of val replace existing array elements from start_elem to
+ *            start_elem+nb_elems (if present). New array size is
+ *            max(start_elem + nb_elems, old array size).
+ */
+int av_opt_set_array(void *obj, const char *name, int search_flags,
+                     unsigned int start_elem, unsigned int nb_elems,
+                     enum AVOptionType val_type, const void *val);
+
 /**
  * @}
  * @}
diff --git a/libavutil/version.h b/libavutil/version.h
index 5ac9cc59dc..25a6f5531b 100644
--- a/libavutil/version.h
+++ b/libavutil/version.h
@@ -79,7 +79,7 @@
  */
 
 #define LIBAVUTIL_VERSION_MAJOR  59
-#define LIBAVUTIL_VERSION_MINOR  35
+#define LIBAVUTIL_VERSION_MINOR  36
 #define LIBAVUTIL_VERSION_MICRO 100
 
 #define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
-- 
2.43.0



More information about the ffmpeg-devel mailing list