aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2024-08-23 15:38:47 +0200
committerAnton Khirnov <anton@khirnov.net>2024-09-06 13:59:04 +0200
commit450a3f58edb22d28912a5e65dc08d9e2fb805066 (patch)
tree45cf7fa0abffec3bf66434bfdc264f41ad4fb686
parent2a6f84718b172cd0858316281cbbd967f35767f0 (diff)
downloadffmpeg-450a3f58edb22d28912a5e65dc08d9e2fb805066.tar.gz
lavu/opt: add API for setting array-type option values
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.
-rw-r--r--doc/APIchanges3
-rw-r--r--libavutil/opt.c186
-rw-r--r--libavutil/opt.h56
-rw-r--r--libavutil/version.h2
4 files changed, 246 insertions, 1 deletions
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
@@ -619,6 +619,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.
* @see AVOptionRanges for details.
@@ -897,6 +903,56 @@ int av_opt_set_dict_val(void *obj, const char *name, const AVDictionary *val, in
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, \