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".
next prev 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