From patchwork Thu Mar 7 15:24:07 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Anton Khirnov X-Patchwork-Id: 46870 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c995:b0:1a1:738b:6bc0 with SMTP id gy21csp347563pzb; Thu, 7 Mar 2024 07:24:58 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCWBlA2g/TgNDR6mm2FsCH1Hq1qKZD/AoNCk1GJS1impcn0CkisQm4xBVbPLjuxMGKMkKY5kM817DDiTD3lDuYcFmEL/2wQUQkeEiQ== X-Google-Smtp-Source: AGHT+IH2oUKdJDm9VpAseKcuwJOR839D3peXatdjFahnRn0pFeqPnEfDO5YwO9HUsiPVulSiQHVp X-Received: by 2002:a17:906:594d:b0:a43:67c9:8c99 with SMTP id g13-20020a170906594d00b00a4367c98c99mr11980155ejr.40.1709825098420; Thu, 07 Mar 2024 07:24:58 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1709825098; cv=none; d=google.com; s=arc-20160816; b=GwrXlJq6Tm9k5wBqC/hnxDDpP2IAeNpH+ZnOOW/PRiJF3SO57/2ua/k+aqnawXIhxC 6NWJ8kS+ppsMiu4WgXlOnx7LqPZJZp12rX6xix9rXbrQ1AvbuswMSnxVsE9yokE+0d4s cUxMUFmS2ZQEmzZTc7vozzR6XtKOwDbmWLF1+cx/SDb50GUQyRjzcFWfuoi1YaJnqbV1 C3cQWXxLKXu19pmUdnnjaM2mb+cLiPWnMQc42obvKzvrRNDje7p32pDtz8HX/ADNMgzU cBmE/c7931Ys4kSZJH3Cnk2TgN3O06C9hpaL+jY4DYxrQET+2Vx/bgJ3lSXYXNIFeqgJ Fl7A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:dkim-signature:delivered-to; bh=gW4yeKt+LhQquBa2VgBOuRfHzfTvwUFwgqmIzVTrv24=; fh=YOA8vD9MJZuwZ71F/05pj6KdCjf6jQRmzLS+CATXUQk=; b=mJC/WVDJSwxlk1OIVUpp76BJAZs06zsYPZ6C1gOSjryOK7+8ez8COHESiAP73yrZa1 x5bvFQCoCVt2osiy1LCy/EsDolg4pJxqWW69UVy3pCLdwHYXiCxHqpm17gwGNLYX90tH /R7PCzB/ojov0byeXpIG0WN+/N0VUfs1W/xuhu36KEEpLxuh82RuVB4PdUh8HkddkHrO MxBzLhRmRLjfBovwi0YR26IREqspXq/+zouCcRjNDqhQgitQFPVslmKezVFGkKpypzkE pxkutc07veRh5QJc82zoZVEgAtciaHRypLv5QPkgwnaBxnoQW3L62+BGhadGqAy3gFjf kbAg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@khirnov.net header.s=mail header.b=cJAk9+T5; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id sh43-20020a1709076eab00b00a44d27980c5si5357057ejc.421.2024.03.07.07.24.55; Thu, 07 Mar 2024 07:24:58 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@khirnov.net header.s=mail header.b=cJAk9+T5; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id B67E568C463; Thu, 7 Mar 2024 17:24:52 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail1.khirnov.net (quelana.khirnov.net [94.230.150.81]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 62C9568C25D for ; Thu, 7 Mar 2024 17:24:46 +0200 (EET) Authentication-Results: mail1.khirnov.net; dkim=pass (2048-bit key; unprotected) header.d=khirnov.net header.i=@khirnov.net header.a=rsa-sha256 header.s=mail header.b=cJAk9+T5; dkim-atps=neutral Received: from localhost (mail1.khirnov.net [IPv6:::1]) by mail1.khirnov.net (Postfix) with ESMTP id E66504D42 for ; Thu, 7 Mar 2024 16:24:45 +0100 (CET) Received: from mail1.khirnov.net ([IPv6:::1]) by localhost (mail1.khirnov.net [IPv6:::1]) (amavis, port 10024) with ESMTP id gr9n0VdokvCV for ; Thu, 7 Mar 2024 16:24:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=khirnov.net; s=mail; t=1709825084; bh=hGYTnsYzBiXrw20+tMMxBhCVM+rkczb8jCcOsKN1lRM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=cJAk9+T5jtpRjj7fXP5SEqml8I+AldyrlwQnOOjHAclmE7WyWJHkFxUEWRNowoRje r3hNI2vZu6km7s2c+KBb/WpL8uHU58mgbFcNKjVTtFSyqD3yVdCUuXskvHx7iWioEB CLz5mvxbfnu74lapXFUPlzv+3InH0Y2gVXDgFO3U76kMeJzIj16f9qMKaPRUB3CZOs 9Ifz5+eNNUXCnRmP9YozOMmTld7Y4IKz/NNzygWDsQq0DB0k5HmJBvFvmSDpVQqqMy p0vtJBDbErBUcFr7QTwSpytZyYKpGOh/d/oy7YH9A2OKxt12V3A+DS02Zda5j4J4rW NSsWjZj3AORhA== Received: from libav.khirnov.net (libav.khirnov.net [IPv6:2a00:c500:561:201::7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (2048 bits) client-digest SHA256) (Client CN "libav.khirnov.net", Issuer "smtp.khirnov.net SMTP CA" (verified OK)) by mail1.khirnov.net (Postfix) with ESMTPS id C78A110B9 for ; Thu, 7 Mar 2024 16:24:44 +0100 (CET) Received: from libav.khirnov.net (libav.khirnov.net [IPv6:::1]) by libav.khirnov.net (Postfix) with ESMTP id AB3DD3A03D8 for ; Thu, 7 Mar 2024 16:24:44 +0100 (CET) From: Anton Khirnov To: ffmpeg-devel@ffmpeg.org Date: Thu, 7 Mar 2024 16:24:07 +0100 Message-ID: <20240307152416.5151-1-anton@khirnov.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2 06/29] lavu/opt: add array options X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: bfCaujfXCX+B --- doc/APIchanges | 3 + libavutil/opt.c | 360 +++++++++++++++++++++++++++++++++++++----- libavutil/opt.h | 56 ++++++- libavutil/tests/opt.c | 49 ++++++ tests/ref/fate/opt | 35 +++- 5 files changed, 458 insertions(+), 45 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 4f7612e449..6e2c851e31 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-02-xx - xxxxxxxxxx - lavu 58.xx.100 - opt.h + Add AV_OPT_TYPE_FLAG_ARRAY and AVOptionArrayDef. + 2024-03-06 - xxxxxxxxxx - lavf 60.25.100 - avformat.h Deprecate av_fmt_ctx_get_duration_estimation_method(). The relevant field is public and needs no getter to access. diff --git a/libavutil/opt.c b/libavutil/opt.c index 06d3056a37..685206f416 100644 --- a/libavutil/opt.c +++ b/libavutil/opt.c @@ -43,6 +43,8 @@ #include +#define TYPE_BASE(type) ((type) & ~AV_OPT_TYPE_FLAG_ARRAY) + const AVOption *av_opt_next(const void *obj, const AVOption *last) { const AVClass *class; @@ -100,6 +102,54 @@ static int opt_is_pod(enum AVOptionType type) return 0; } +static uint8_t opt_array_sep(const AVOption *o) +{ + const AVOptionArrayDef *d = o->default_val.arr; + av_assert1(o->type & AV_OPT_TYPE_FLAG_ARRAY); + return (d && d->sep) ? d->sep : ','; +} + +static void *opt_array_pelem(const AVOption *o, void *array, unsigned idx) +{ + av_assert1(o->type & AV_OPT_TYPE_FLAG_ARRAY); + return (uint8_t *)array + idx * opt_elem_size[TYPE_BASE(o->type)]; +} + +static unsigned *opt_array_pcount(const void *parray) +{ + return (unsigned *)((const void * const *)parray + 1); +} + +static void opt_free_elem(const AVOption *o, void *ptr) +{ + switch (TYPE_BASE(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) { @@ -140,14 +190,16 @@ static int read_number(const AVOption *o, const void *dst, double *num, int *den static int write_number(void *obj, const AVOption *o, void *dst, double num, int den, int64_t intnum) { - if (o->type != AV_OPT_TYPE_FLAGS && + const enum AVOptionType type = TYPE_BASE(o->type); + + if (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, "Value %f for parameter '%s' out of range [%g - %g]\n", num, o->name, o->min, o->max); return AVERROR(ERANGE); } - if (o->type == AV_OPT_TYPE_FLAGS) { + if (type == AV_OPT_TYPE_FLAGS) { double d = num*intnum/den; if (d < -1.5 || d > 0xFFFFFFFF+0.5 || (llrint(d*256) & 255)) { av_log(obj, AV_LOG_ERROR, @@ -157,7 +209,7 @@ static int write_number(void *obj, const AVOption *o, void *dst, double num, int } } - switch (o->type) { + switch (type) { case AV_OPT_TYPE_PIXEL_FMT: *(enum AVPixelFormat *)dst = llrint(num / den) * intnum; break; @@ -271,9 +323,10 @@ static int set_string(void *obj, const AVOption *o, const char *val, uint8_t **d static int set_string_number(void *obj, void *target_obj, const AVOption *o, const char *val, void *dst) { + const enum AVOptionType type = TYPE_BASE(o->type); int ret = 0; - if (o->type == AV_OPT_TYPE_RATIONAL || o->type == AV_OPT_TYPE_VIDEO_RATE) { + if (type == AV_OPT_TYPE_RATIONAL || type == AV_OPT_TYPE_VIDEO_RATE) { int num, den; char c; if (sscanf(val, "%d%*1[:/]%d%c", &num, &den, &c) == 2) { @@ -290,7 +343,7 @@ static int set_string_number(void *obj, void *target_obj, const AVOption *o, con double d; int64_t intnum = 1; - if (o->type == AV_OPT_TYPE_FLAGS) { + if (type == AV_OPT_TYPE_FLAGS) { if (*val == '+' || *val == '-') cmd = *(val++); for (; i < sizeof(buf) - 1 && val[i] && val[i] != '+' && val[i] != '-'; i++) @@ -346,7 +399,7 @@ static int set_string_number(void *obj, void *target_obj, const AVOption *o, con } } } - if (o->type == AV_OPT_TYPE_FLAGS) { + if (type == AV_OPT_TYPE_FLAGS) { intnum = *(unsigned int*)dst; if (cmd == '+') d = intnum | (int64_t)d; @@ -531,18 +584,17 @@ static int set_string_channel_layout(void *obj, const AVOption *o, static int opt_set_elem(void *obj, void *target_obj, const AVOption *o, const char *val, void *dst) { + const enum AVOptionType type = TYPE_BASE(o->type); int ret; -FF_DISABLE_DEPRECATION_WARNINGS - if (!val && (o->type != AV_OPT_TYPE_STRING && - o->type != AV_OPT_TYPE_PIXEL_FMT && o->type != AV_OPT_TYPE_SAMPLE_FMT && - o->type != AV_OPT_TYPE_IMAGE_SIZE && - o->type != AV_OPT_TYPE_DURATION && o->type != AV_OPT_TYPE_COLOR && - o->type != AV_OPT_TYPE_BOOL)) + if (!val && (type != AV_OPT_TYPE_STRING && + type != AV_OPT_TYPE_PIXEL_FMT && type != AV_OPT_TYPE_SAMPLE_FMT && + type != AV_OPT_TYPE_IMAGE_SIZE && + type != AV_OPT_TYPE_DURATION && type != AV_OPT_TYPE_COLOR && + type != AV_OPT_TYPE_BOOL)) return AVERROR(EINVAL); -FF_ENABLE_DEPRECATION_WARNINGS - switch (o->type) { + switch (type) { case AV_OPT_TYPE_BOOL: return set_string_bool(obj, o, val, dst); case AV_OPT_TYPE_STRING: @@ -604,6 +656,85 @@ 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 AVOptionArrayDef *arr = o->default_val.arr; + const size_t elem_size = opt_elem_size[TYPE_BASE(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 (arr && arr->size_max && nb_elems >= arr->size_max) { + av_log(obj, AV_LOG_ERROR, + "Cannot assign more than %u elements to array option %s\n", + arr->size_max, 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)); + + if (arr && nb_elems < arr->size_min) { + av_log(obj, AV_LOG_ERROR, + "Cannot assign fewer than %u elements to array option %s\n", + arr->size_min, o->name); + ret = AVERROR(EINVAL); + goto fail; + } + + *((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; @@ -619,7 +750,8 @@ 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->type & AV_OPT_TYPE_FLAG_ARRAY) ? + opt_set_array : opt_set_elem)(obj, target_obj, o, val, dst); } #define OPT_EVAL_NUMBER(name, opttype, vartype) \ @@ -647,7 +779,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) || (o->type & AV_OPT_TYPE_FLAG_ARRAY)) return AVERROR(EINVAL); dst = ((uint8_t *)target_obj) + o->offset; @@ -730,7 +862,8 @@ int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int searc return AVERROR_OPTION_NOT_FOUND; if (o->type != AV_OPT_TYPE_VIDEO_RATE) { 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 video rate.\n", + o->name); return AVERROR(EINVAL); } if (val.num <= 0 || val.den <= 0) @@ -852,7 +985,7 @@ static int opt_get_elem(const AVOption *o, uint8_t **pbuf, size_t buf_len, { int ret; - switch (o->type) { + switch (TYPE_BASE(o->type)) { case AV_OPT_TYPE_BOOL: ret = snprintf(*pbuf, buf_len, "%s", get_bool_name(*(int *)dst)); break; @@ -949,6 +1082,66 @@ static int opt_get_elem(const AVOption *o, uint8_t **pbuf, size_t buf_len, 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; @@ -964,8 +1157,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->type & AV_OPT_TYPE_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) @@ -988,6 +1192,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->type & AV_OPT_TYPE_FLAG_ARRAY) + return AVERROR(EINVAL); dst = ((uint8_t *)target_obj) + o->offset; @@ -1045,7 +1251,7 @@ int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_ return AVERROR_OPTION_NOT_FOUND; if (o->type != AV_OPT_TYPE_IMAGE_SIZE) { 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 image size.\n", name); return AVERROR(EINVAL); } @@ -1252,11 +1458,16 @@ static void log_type(void *av_log_obj, const AVOption *o, [AV_OPT_TYPE_CHLAYOUT] = "", [AV_OPT_TYPE_BOOL] = "", }; + const enum AVOptionType type = TYPE_BASE(o->type); - if (o->type == AV_OPT_TYPE_CONST && parent_type == AV_OPT_TYPE_INT) + if (o->type == AV_OPT_TYPE_CONST && TYPE_BASE(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 (type < FF_ARRAY_ELEMS(desc) && desc[type]) { + if (o->type & AV_OPT_TYPE_FLAG_ARRAY) + av_log(av_log_obj, AV_LOG_INFO, "[%-10s]", desc[type]); + else + av_log(av_log_obj, AV_LOG_INFO, "%-12s ", desc[type]); + } else av_log(av_log_obj, AV_LOG_INFO, "%-12s ", ""); } @@ -1274,6 +1485,13 @@ static void log_default(void *obj, void *av_log_obj, const AVOption *opt) !opt->default_val.str) return; + if (opt->type & AV_OPT_TYPE_FLAG_ARRAY) { + const AVOptionArrayDef *arr = opt->default_val.arr; + if (arr && arr->def) + av_log(av_log_obj, AV_LOG_INFO, " (default %s)", arr->def); + return; + } + av_log(av_log_obj, AV_LOG_INFO, " (default "); switch (opt->type) { case AV_OPT_TYPE_BOOL: @@ -1434,6 +1652,21 @@ void av_opt_set_defaults2(void *s, int mask, int flags) if (opt->flags & AV_OPT_FLAG_READONLY) continue; + if (opt->type & AV_OPT_TYPE_FLAG_ARRAY) { + const AVOptionArrayDef *arr = opt->default_val.arr; + const char sep = opt_array_sep(opt); + + av_assert0(sep && sep != '\\' && + (sep < 'a' || sep > 'z') && + (sep < 'A' || sep > 'Z') && + (sep < '0' || sep > '9')); + + if (arr && arr->def) + opt_set_array(s, s, opt, arr->def, dst); + + continue; + } + switch (opt->type) { case AV_OPT_TYPE_CONST: /* Nothing to be done here */ @@ -1676,23 +1909,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->type & AV_OPT_TYPE_FLAG_ARRAY) + opt_free_array(o, pitem, opt_array_pcount(pitem)); + else + opt_free_elem(o, pitem); } } @@ -1794,7 +2016,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->type & AV_OPT_TYPE_FLAG_ARRAY)) return NULL; return (uint8_t*)obj + opt->offset; } @@ -1844,6 +2068,40 @@ static int opt_copy_elem(void *logctx, enum AVOptionType type, return 0; } +static int opt_copy_array(void *logctx, const AVOption *o, + void **pdst, const void * const *psrc) +{ + unsigned nb_elems = *opt_array_pcount(psrc); + void *dst = NULL; + int ret; + + if (*pdst == *psrc) { + *pdst = NULL; + *opt_array_pcount(pdst) = 0; + } + + opt_free_array(o, pdst, opt_array_pcount(pdst)); + + dst = av_calloc(nb_elems, opt_elem_size[TYPE_BASE(o->type)]); + if (!dst) + return AVERROR(ENOMEM); + + for (unsigned i = 0; i < nb_elems; i++) { + ret = opt_copy_elem(logctx, TYPE_BASE(o->type), + opt_array_pelem(o, dst, i), + opt_array_pelem(o, *(void**)psrc, i)); + if (ret < 0) { + opt_free_array(o, &dst, &nb_elems); + return ret; + } + } + + *pdst = dst; + *opt_array_pcount(pdst) = nb_elems; + + return 0; +} + int av_opt_copy(void *dst, const void *src) { const AVOption *o = NULL; @@ -1861,7 +2119,9 @@ int av_opt_copy(void *dst, const void *src) void *field_dst = (uint8_t *)dst + o->offset; void *field_src = (uint8_t *)src + o->offset; - int err = opt_copy_elem(dst, o->type, field_dst, field_src); + int err = (o->type & AV_OPT_TYPE_FLAG_ARRAY) ? + opt_copy_array(dst, o, field_dst, field_src) : + opt_copy_elem (dst, o->type, field_dst, field_src); if (err < 0) ret = err; } @@ -1990,6 +2250,24 @@ int av_opt_is_set_to_default(void *obj, const AVOption *o) dst = ((uint8_t*)obj) + o->offset; + if (o->type & AV_OPT_TYPE_FLAG_ARRAY) { + const char *def = o->default_val.arr ? o->default_val.arr->def : NULL; + uint8_t *val; + + ret = opt_get_array(o, dst, &val); + if (ret < 0) + return ret; + + if (!!val != !!def) + ret = 0; + else if (val) + ret = !strcmp(val, def); + + 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 82bca920fb..e6013662f6 100644 --- a/libavutil/opt.h +++ b/libavutil/opt.h @@ -250,6 +250,17 @@ enum AVOptionType{ AV_OPT_TYPE_COLOR, AV_OPT_TYPE_BOOL, AV_OPT_TYPE_CHLAYOUT, + + /** + * May be combined with another regular option type to declare an array + * option. + * + * For array options, @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. + */ + AV_OPT_TYPE_FLAG_ARRAY = (1 << 16), }; /** @@ -295,6 +306,40 @@ enum AVOptionType{ */ #define AV_OPT_FLAG_CHILD_CONSTS (1 << 18) +/** + * May be set as default_val for AV_OPT_TYPE_FLAG_ARRAY options. + */ +typedef struct AVOptionArrayDef { + /** + * Native access only. + * + * Default value of the option, as would be serialized by av_opt_get() (i.e. + * using the value of sep as the separator). + */ + const char *def; + + /** + * Minimum number of elements in the array. When this field is non-zero, def + * must be non-NULL and contain at least this number of elements. + */ + unsigned size_min; + /** + * Maximum number of elements in the array, 0 when unlimited. + */ + unsigned size_max; + + /** + * Separator between array elements in string representations of this + * option, used by av_opt_set() and av_opt_get(). It must be a printable + * ASCII character, excluding alphanumeric and the backslash. A comma is + * used when sep=0. + * + * The separator and the backslash must be backslash-escaped in order to + * appear in string representations of the option value. + */ + char sep; +} AVOptionArrayDef; + /** * AVOption */ @@ -317,8 +362,7 @@ typedef struct AVOption { enum AVOptionType type; /** - * Native access only. - * + * Native access only, except when documented otherwise. * the default value for scalar options */ union { @@ -327,6 +371,14 @@ typedef struct AVOption { const char *str; /* TODO those are unused now */ AVRational q; + + /** + * Used for AV_OPT_TYPE_FLAG_ARRAY options. May be NULL. + * + * Foreign access to some members allowed, as noted in AVOptionArrayDef + * documentation. + */ + const AVOptionArrayDef *arr; } default_val; double min; ///< minimum valid value for the option double max; ///< maximum valid value for the option diff --git a/libavutil/tests/opt.c b/libavutil/tests/opt.c index e2582cc93d..04b4901579 100644 --- a/libavutil/tests/opt.c +++ b/libavutil/tests/opt.c @@ -57,6 +57,15 @@ typedef struct TestContext { int bool3; AVDictionary *dict1; AVDictionary *dict2; + + int **array_int; + unsigned nb_array_int; + + char **array_str; + unsigned nb_array_str; + + AVDictionary **array_dict; + unsigned nb_array_dict; } TestContext; #define OFFSET(x) offsetof(TestContext, x) @@ -65,6 +74,15 @@ typedef struct TestContext { #define TEST_FLAG_LAME 02 #define TEST_FLAG_MU 04 +static const AVOptionArrayDef array_str = { + .sep = '|', + .def = "str0|str\\|1|str\\\\2", +}; + +static const AVOptionArrayDef array_dict = { + .def = "k00=v\\\\\\\\00:k01=v\\,01,k10=v\\\\=1\\\\:0", +}; + static const AVOption test_options[]= { {"num", "set num", OFFSET(num), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 100, 1 }, {"toggle", "set toggle", OFFSET(toggle), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, 1 }, @@ -93,6 +111,10 @@ 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_int", "array of ints", OFFSET(array_int), AV_OPT_TYPE_INT | AV_OPT_TYPE_FLAG_ARRAY, .max = INT_MAX, .flags = AV_OPT_FLAG_RUNTIME_PARAM }, + {"array_str", "array of strings", OFFSET(array_str), AV_OPT_TYPE_STRING | AV_OPT_TYPE_FLAG_ARRAY, { .arr = &array_str }, .flags = AV_OPT_FLAG_RUNTIME_PARAM }, + // 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 | AV_OPT_TYPE_FLAG_ARRAY, { .arr = &array_dict }, .flags = AV_OPT_FLAG_RUNTIME_PARAM }, { NULL }, }; @@ -146,6 +168,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 +210,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 +245,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); } @@ -303,6 +350,8 @@ int main(void) "bool1=true", "bool2=auto", "dict1='happy=\\:-):sad=\\:-('", + "array_int=0,32,2147483647", + "array_int=2147483648", // out of range, should fail }; test_ctx.class = &test_class; diff --git a/tests/ref/fate/opt b/tests/ref/fate/opt index 832f9cc8a9..f4fce1bd49 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 E.......... set num (from 0 to 100) (default 0) -toggle E.......... set toggle (from 0 to 1) (default 1) @@ -45,6 +51,9 @@ TestContext AVOptions: -bool3 E.......... set boolean value (default false) -dict1 E.......... set dictionary value -dict2 E.......... set dictionary value (default "happy=':-)'") + -array_int [ ].........T. array of ints + -array_str [ ].........T. array of strings (default str0|str\|1|str\\2) + -array_dict [].........T. 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 +83,9 @@ name: bool2 default:0 error: name: bool3 default:1 error: name: dict1 default:1 error: name: dict2 default:0 error: +name: array_int 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 +113,9 @@ name: bool2 default:1 error: name: bool3 default:1 error: name: dict1 default:1 error: name: dict2 default:1 error: +name: array_int default:0 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 +142,15 @@ 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_int get: set: OK get: 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_int=,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 +175,10 @@ 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_int' to value '' +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_int=,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 '' @@ -378,6 +402,13 @@ OK 'bool2=auto' Setting options string 'dict1='happy=\:-):sad=\:-('' Setting entry with key 'dict1' to value 'happy=\:-):sad=\:-(' OK 'dict1='happy=\:-):sad=\:-('' +Setting options string 'array_int=0,32,2147483647' +Setting entry with key 'array_int' to value '0,32,2147483647' +OK 'array_int=0,32,2147483647' +Setting options string 'array_int=2147483648' +Setting entry with key 'array_int' to value '2147483648' +Value 2147483648.000000 for parameter 'array_int' out of range [0 - 2.14748e+09] +Error 'array_int=2147483648' Testing av_opt_set_from_string() Setting options string ''