diff mbox series

[FFmpeg-devel,06/29] lavu/opt: add array options

Message ID 20240304130657.30631-6-anton@khirnov.net
State New
Headers show
Series [FFmpeg-devel,01/29] lavu/opt: factor per-type dispatch out of av_opt_get() | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Anton Khirnov March 4, 2024, 1:06 p.m. UTC
---
 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(-)

Comments

Andreas Rheinhardt March 4, 2024, 1:29 p.m. UTC | #1
Anton Khirnov:
> ---
>  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.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.

How about we make this actually type-safe and use an actual struct for
this instead of relying on the compiler not adding padding between a
pointer and an unsigned int?

> +     */
> +    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;

I do not really get the point of this field: It is not sufficient for
detecting whether a user used a version that set a certain field due to
trailing padding (i.e. an additional field need not increase
sizeof(AVOptionArrayDef); this is actually relevant for this structure,
because on x64 at least a new int/unsigned would fit into trailing padding).
Luckily we already have a way to know the user's lavu version, as it is
contained in the AVClass.

> +
> +    /**
> +     * 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;

If this is a printable ASCII character, then it should be a char.

> +} 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
Anton Khirnov March 4, 2024, 9 p.m. UTC | #2
Quoting Andreas Rheinhardt (2024-03-04 14:29:59)
> Anton Khirnov:
> > ---
> >  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.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.
> 
> How about we make this actually type-safe and use an actual struct for
> this instead of relying on the compiler not adding padding between a
> pointer and an unsigned int?

A struct containing what exactly?
Marton Balint March 4, 2024, 9:32 p.m. UTC | #3
On Mon, 4 Mar 2024, Anton Khirnov wrote:

> ---
> 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.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.

This contradicts with the documentation of AVOptionArrayDef above, because 
there you write that default_val MUST be set.

> +         *
> +         * Foreign access to some members allowed, as noted in AVOptionArrayDef
> +         * documentation.
> +         */
> +        const AVOptionArrayDef *arr;
>     } default_val;

Regards,
Marton
Andreas Rheinhardt March 5, 2024, 8:52 a.m. UTC | #4
Anton Khirnov:
> Quoting Andreas Rheinhardt (2024-03-04 14:29:59)
>> Anton Khirnov:
>>> ---
>>>  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.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.
>>
>> How about we make this actually type-safe and use an actual struct for
>> this instead of relying on the compiler not adding padding between a
>> pointer and an unsigned int?
> 
> A struct containing what exactly?
> 

A pointer and a size field.

- Andreas
Anton Khirnov March 5, 2024, 9:50 a.m. UTC | #5
Quoting Marton Balint (2024-03-04 22:32:21)
> > @@ -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.
> 
> This contradicts with the documentation of AVOptionArrayDef above, because 
> there you write that default_val MUST be set.

Changed 'must' to 'may' locally. It is optional, and one of the test
entries does not set it.
Andreas Rheinhardt March 7, 2024, 2:50 p.m. UTC | #6
Andreas Rheinhardt:
> Anton Khirnov:
>> +/**
>> + * 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;
> 
> I do not really get the point of this field: It is not sufficient for
> detecting whether a user used a version that set a certain field due to
> trailing padding (i.e. an additional field need not increase
> sizeof(AVOptionArrayDef); this is actually relevant for this structure,
> because on x64 at least a new int/unsigned would fit into trailing padding).
> Luckily we already have a way to know the user's lavu version, as it is
> contained in the AVClass.
> 

I do not really see a reply to the above comment.

>> +
>> +    /**
>> +     * 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;
> 
> If this is a printable ASCII character, then it should be a char.
> 
>> +} 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
> 
> _______________________________________________
> 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".
diff mbox series

Patch

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 <float.h>
 
+#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]      = "<channel_layout>",
         [AV_OPT_TYPE_BOOL]          = "<boolean>",
     };
+    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               <int>        E.......... set num (from 0 to 100) (default 0)
   -toggle            <int>        E.......... set toggle (from 0 to 1) (default 1)
@@ -45,6 +51,9 @@  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_int         [<int>     ].........T. array of ints
+  -array_str         [<string>  ].........T. array of strings (default str0|str\|1|str\\2)
+  -array_dict        [<dictionary>].........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 ''