diff options
author | Anton Khirnov <anton@khirnov.net> | 2024-08-23 15:38:47 +0200 |
---|---|---|
committer | Anton Khirnov <anton@khirnov.net> | 2024-09-06 13:59:04 +0200 |
commit | 450a3f58edb22d28912a5e65dc08d9e2fb805066 (patch) | |
tree | 45cf7fa0abffec3bf66434bfdc264f41ad4fb686 | |
parent | 2a6f84718b172cd0858316281cbbd967f35767f0 (diff) | |
download | ffmpeg-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/APIchanges | 3 | ||||
-rw-r--r-- | libavutil/opt.c | 186 | ||||
-rw-r--r-- | libavutil/opt.h | 56 | ||||
-rw-r--r-- | libavutil/version.h | 2 |
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, \ |