From patchwork Mon Mar 4 13:06:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Anton Khirnov X-Patchwork-Id: 46752 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:a919:b0:19e:cdac:8cce with SMTP id cd25csp2788867pzb; Mon, 4 Mar 2024 05:08:03 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCWTwktIbtjrKjy8pO8wNWG1NvW2kNiJ11lCRlcvmjOlGuKjpAtiM8cKPEg92bTgN4np2PRlN6BMM15dNkpZH7HAZo5TpyLExUVG+A== X-Google-Smtp-Source: AGHT+IFAx4S4VwBeD8f1mwHeoKD+bKvmRx1zVtUbS2hR3433dYLlEu7a0CnfcKYybS/z2u+cTLZh X-Received: by 2002:a50:c943:0:b0:566:14e3:2262 with SMTP id p3-20020a50c943000000b0056614e32262mr5728602edh.36.1709557683479; Mon, 04 Mar 2024 05:08:03 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1709557683; cv=none; d=google.com; s=arc-20160816; b=QnoL5zvGZ3jNRCBzahsoAlremdgjyuAug6G3qBBqgHrN6POi8IFY76Wz58vnMz0J6O p6Fz4WcTEsQO42U5DVQ8jMDxMJGinCgH9KoFVs2lXT6mysEzn80JQMl+l4VwHwCoSC6P SRpSTfdqy4PMHrzMljERZBBWwoSn4v0BRi0H5Ix4jCVpx2aB0G9M0Yu0TIO/MBhpSfR/ 3gpk4CMQsEb7gtQby3WxNlE5+quoV64WGXwPSR3VY3MRBiC9spx43bDqW6kEQnE7gO8O 99XUc4tIWe4pCJ7Ebt/Ah6hpci4XW6kOO6N91/IcJaZIh9cOWIP/WU5t0+jeUc7XKZ/m 1+dw== 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=5MmZZSTuLVU6pQ8ngRaYAZnZKn9hrNfZmsTb1tJK7EE=; fh=YOA8vD9MJZuwZ71F/05pj6KdCjf6jQRmzLS+CATXUQk=; b=Yal/bW3XzhEPhVOosfte205uLbF25/BM0aLJdwu45yd3T1cl7ZsBJhPMX11ILz6lAC wlwaJfqsgwSgIdhi4B2feNw6W0AXTd/NPO9t0OMb/TJV7wGjEBm0LNmM8FZFa1CBiAkj TK8CW6Txo7Oqsu4RnEyCRTsury1B6gNtfez/tJwyVl1hPTo7X+ndwZHCOneSSqVTEF19 Zi9BKYaMYKvg+dyvUHv0w8eJslP5zd5EGv5EVc2YNAP/0Zkai6d1K7HCrVqXSfVh989c IzJcoP0EAkZ/74W+zG/78xoPChOJptR09bW9Y8R9PuM4uDuaR3mJ7xVm7zv/eyuw81iC le6w==; 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=PEj8CgDl; 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 u10-20020a50a40a000000b00565dd87bd1bsi4019386edb.618.2024.03.04.05.08.02; Mon, 04 Mar 2024 05:08:03 -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=PEj8CgDl; 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 DD70B68D445; Mon, 4 Mar 2024 15:07:18 +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 C7CFE68D3B7 for ; Mon, 4 Mar 2024 15:07:08 +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=PEj8CgDl; dkim-atps=neutral Received: from localhost (mail1.khirnov.net [IPv6:::1]) by mail1.khirnov.net (Postfix) with ESMTP id 793BA10B9 for ; Mon, 4 Mar 2024 14:07:08 +0100 (CET) Received: from mail1.khirnov.net ([IPv6:::1]) by localhost (mail1.khirnov.net [IPv6:::1]) (amavis, port 10024) with ESMTP id UNuDM3gZ2MEZ for ; Mon, 4 Mar 2024 14:07:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=khirnov.net; s=mail; t=1709557626; bh=1nnSBeXZB2NrORqw4Soo2vE3oc+yacZHR+w9qaT4cOE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=PEj8CgDl6lYN5m+Buw3Gt4bXJiHqcMc4L1efursR6AvCxGnMkBmDrR/aJTI/AZfrq DJkgNsK6hoE8ESbSqFey7BG4EAAgeyNi1GAdPHMJ2JrbFIM6Gf4dgjzIsDnrYizEPM shF7N2sd8cHcTn2A+BteadNf/ZlHfW8RIsMFfVvQyijW+++FpgFviPPoIjqKtLR18s yuivdsAcsF3ZDqj98pBLQUh8pBE5Sty3MleB2+kJmHzfb7ZFRRsGDX1x05mIKUTKvS MoFKbGOAFlCb6RP4RbQ0+JFraYlIvHCKNfw8qZ6fnTDGdDZMw1x5Njq/PlGfGkbAQ9 /CdDJ7zk+3BDA== 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 384A84D3E for ; Mon, 4 Mar 2024 14:07:06 +0100 (CET) Received: from libav.khirnov.net (libav.khirnov.net [IPv6:::1]) by libav.khirnov.net (Postfix) with ESMTP id 1F4163A0C79 for ; Mon, 4 Mar 2024 14:07:00 +0100 (CET) From: Anton Khirnov To: ffmpeg-devel@ffmpeg.org Date: Mon, 4 Mar 2024 14:06:21 +0100 Message-ID: <20240304130657.30631-6-anton@khirnov.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240304130657.30631-1-anton@khirnov.net> References: <20240304130657.30631-1-anton@khirnov.net> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 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: E+U6tBU8S5hg --- doc/APIchanges | 3 + libavutil/opt.c | 362 +++++++++++++++++++++++++++++++++++++----- libavutil/opt.h | 62 +++++++- libavutil/tests/opt.c | 51 ++++++ tests/ref/fate/opt | 35 +++- 5 files changed, 468 insertions(+), 45 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 7d46ebb006..3209614ed6 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_TYPE_FLAG_ARRAY and AVOptionArrayDef. + 2024-02-28 - xxxxxxxxxx - swr 4.14.100 - swresample.h swr_convert() now accepts arrays of const pointers (to input and output). diff --git a/libavutil/opt.c b/libavutil/opt.c index d18a1c63b7..6a5e3c7174 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; @@ -106,6 +108,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) { @@ -151,14 +201,16 @@ FF_ENABLE_DEPRECATION_WARNINGS 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, @@ -168,7 +220,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; @@ -287,9 +339,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) { @@ -306,7 +359,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++) @@ -362,7 +415,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; @@ -547,21 +600,22 @@ 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 && + 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 && #if FF_API_OLD_CHANNEL_LAYOUT - o->type != AV_OPT_TYPE_CHANNEL_LAYOUT && + type != AV_OPT_TYPE_CHANNEL_LAYOUT && #endif - o->type != AV_OPT_TYPE_BOOL)) + 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: @@ -640,6 +694,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; @@ -655,7 +788,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) \ @@ -683,7 +817,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; @@ -766,7 +900,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) @@ -908,7 +1043,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; @@ -1014,6 +1149,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; @@ -1029,8 +1224,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) @@ -1053,6 +1259,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; @@ -1110,7 +1318,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); } @@ -1174,7 +1382,7 @@ int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int return AVERROR_OPTION_NOT_FOUND; if (o->type != AV_OPT_TYPE_CHANNEL_LAYOUT) { 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); } @@ -1341,11 +1549,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 ", ""); } @@ -1363,6 +1576,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: @@ -1530,6 +1750,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 */ @@ -1777,23 +2012,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); } } @@ -1895,7 +2119,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; } @@ -1945,6 +2171,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; @@ -1962,7 +2222,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; } @@ -2096,6 +2358,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 e402f6a0a0..77797b3fbe 100644 --- a/libavutil/opt.h +++ b/libavutil/opt.h @@ -253,6 +253,17 @@ enum AVOptionType{ #endif 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), }; /** @@ -298,6 +309,46 @@ enum AVOptionType{ */ #define AV_OPT_FLAG_CHILD_CONSTS (1 << 18) +/** + * Must be set as default_val for AV_OPT_TYPE_FLAG_ARRAY options. + */ +typedef struct AVOptionArrayDef { + /** + * Must be set to sizeof(AVOptionArrayDef), in order to allow extending this + * struct without breaking ABI. + */ + size_t sizeof_self; + + /** + * 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. + */ + uint8_t sep; +} AVOptionArrayDef; + /** * AVOption */ @@ -320,8 +371,7 @@ typedef struct AVOption { enum AVOptionType type; /** - * Native access only. - * + * Native access only, except when documented otherwise. * the default value for scalar options */ union { @@ -330,6 +380,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..37437320cf 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,17 @@ typedef struct TestContext { #define TEST_FLAG_LAME 02 #define TEST_FLAG_MU 04 +static const AVOptionArrayDef array_str = { + .sizeof_self = sizeof(AVOptionArrayDef), + .sep = '|', + .def = "str0|str\\|1|str\\\\2", +}; + +static const AVOptionArrayDef array_dict = { + .sizeof_self = sizeof(AVOptionArrayDef), + .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 +113,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 +170,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 +212,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 +247,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 +352,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 ''