diff --git a/libavutil/opt.c b/libavutil/opt.c
index d11e9d2ac5..ecbf7efe5f 100644
--- a/libavutil/opt.c
+++ b/libavutil/opt.c
@@ -2386,26 +2386,22 @@ int av_opt_is_set_to_default_by_name(void *obj, const char *name, int search_fla
     return av_opt_is_set_to_default(target, o);
 }
 
-int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
-                     const char key_val_sep, const char pairs_sep)
+static int opt_serialize(void *obj, int opt_flags, int flags, int *cnt,
+                         AVBPrint *bprint, const char key_val_sep, const char pairs_sep)
 {
     const AVOption *o = NULL;
+    void *child = NULL;
     uint8_t *buf;
-    AVBPrint bprint;
-    int ret, cnt = 0;
+    int ret;
     const char special_chars[] = {pairs_sep, key_val_sep, '\0'};
 
-    if (pairs_sep == '\0' || key_val_sep == '\0' || pairs_sep == key_val_sep ||
-        pairs_sep == '\\' || key_val_sep == '\\') {
-        av_log(obj, AV_LOG_ERROR, "Invalid separator(s) found.");
-        return AVERROR(EINVAL);
-    }
-
-    if (!obj || !buffer)
-        return AVERROR(EINVAL);
-
-    *buffer = NULL;
-    av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED);
+    if (flags & AV_OPT_SERIALIZE_SEARCH_CHILDREN)
+        while (child = av_opt_child_next(obj, child)) {
+            ret = opt_serialize(child, opt_flags, flags, cnt, bprint,
+                                key_val_sep, pairs_sep);
+            if (ret < 0)
+                return ret;
+        }
 
     while (o = av_opt_next(obj, o)) {
         if (o->type == AV_OPT_TYPE_CONST)
@@ -2417,18 +2413,45 @@ int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
         if (flags & AV_OPT_SERIALIZE_SKIP_DEFAULTS && av_opt_is_set_to_default(obj, o) > 0)
             continue;
         if ((ret = av_opt_get(obj, o->name, 0, &buf)) < 0) {
-            av_bprint_finalize(&bprint, NULL);
+            av_bprint_finalize(bprint, NULL);
             return ret;
         }
         if (buf) {
-            if (cnt++)
-                av_bprint_append_data(&bprint, &pairs_sep, 1);
-            av_bprint_escape(&bprint, o->name, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
-            av_bprint_append_data(&bprint, &key_val_sep, 1);
-            av_bprint_escape(&bprint, buf, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
+            if ((*cnt)++)
+                av_bprint_append_data(bprint, &pairs_sep, 1);
+            av_bprint_escape(bprint, o->name, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
+            av_bprint_append_data(bprint, &key_val_sep, 1);
+            av_bprint_escape(bprint, buf, special_chars, AV_ESCAPE_MODE_BACKSLASH, 0);
             av_freep(&buf);
         }
     }
+
+    return 0;
+}
+
+int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer,
+                     const char key_val_sep, const char pairs_sep)
+{
+    AVBPrint bprint;
+    int ret, cnt = 0;
+
+    if (pairs_sep == '\0' || key_val_sep == '\0' || pairs_sep == key_val_sep ||
+        pairs_sep == '\\' || key_val_sep == '\\') {
+        av_log(obj, AV_LOG_ERROR, "Invalid separator(s) found.");
+        return AVERROR(EINVAL);
+    }
+
+    if (!obj || !buffer)
+        return AVERROR(EINVAL);
+
+    *buffer = NULL;
+    av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED);
+
+    ret = opt_serialize(obj, opt_flags, flags, &cnt, &bprint,
+                        key_val_sep, pairs_sep);
+    if (ret < 0)
+        return ret;
+
     ret = av_bprint_finalize(&bprint, buffer);
     if (ret < 0)
         return ret;
diff --git a/libavutil/opt.h b/libavutil/opt.h
index e6013662f6..855e363091 100644
--- a/libavutil/opt.h
+++ b/libavutil/opt.h
@@ -929,6 +929,7 @@ int av_opt_flag_is_set(void *obj, const char *field_name, const char *flag_name)
 
 #define AV_OPT_SERIALIZE_SKIP_DEFAULTS              0x00000001  ///< Serialize options that are not set to default values only.
 #define AV_OPT_SERIALIZE_OPT_FLAGS_EXACT            0x00000002  ///< Serialize options that exactly match opt_flags only.
+#define AV_OPT_SERIALIZE_SEARCH_CHILDREN            0x00000004  ///< Serialize options in possible children of the given object.
 
 /**
  * Serialize object's options.
diff --git a/libavutil/tests/opt.c b/libavutil/tests/opt.c
index 32301ba842..53d8951bcf 100644
--- a/libavutil/tests/opt.c
+++ b/libavutil/tests/opt.c
@@ -30,6 +30,7 @@
 
 typedef struct TestContext {
     const AVClass *class;
+    struct ChildContext *child;
     int num;
     int toggle;
     char *string;
@@ -123,10 +124,46 @@ static const char *test_get_name(void *ctx)
     return "test";
 }
 
+typedef struct ChildContext {
+    const AVClass *class;
+    int64_t child_num64;
+    int child_num;
+} ChildContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(ChildContext, x)
+
+static const AVOption child_options[]= {
+    {"child_num64", "set num 64bit", OFFSET(child_num64), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, 100, 1 },
+    {"child_num",   "set child_num", OFFSET(child_num),   AV_OPT_TYPE_INT,   { .i64 = 1 }, 0, 100, 1 },
+    { NULL },
+};
+
+static const char *child_get_name(void *ctx)
+{
+    return "child";
+}
+
+static const AVClass child_class = {
+    .class_name = "ChildContext",
+    .item_name  = child_get_name,
+    .option     = child_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+static void *test_child_next(void *obj, void *prev)
+{
+    TestContext *test_ctx = obj;
+    if (!prev)
+        return test_ctx->child;
+    return NULL;
+}
+
 static const AVClass test_class = {
     .class_name = "TestContext",
     .item_name  = test_get_name,
     .option     = test_options,
+    .child_next = test_child_next,
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
@@ -277,11 +314,15 @@ int main(void)
             av_set_options_string(&test_ctx, buf, "=", ",");
             av_free(buf);
             if (av_opt_serialize(&test_ctx, 0, 0, &buf, '=', ',') >= 0) {
+                ChildContext child_ctx = { 0 };
                 printf("%s\n", buf);
                 av_free(buf);
-                if (av_opt_serialize(&test_ctx, 0, AV_OPT_SERIALIZE_SKIP_DEFAULTS, &buf, '=', ',') >= 0) {
-                    if (strlen(buf))
-                        printf("%s\n", buf);
+                test_ctx.child = &child_ctx;
+                child_ctx.class = &child_class;
+                if (av_opt_serialize(&test_ctx, 0,
+                                     AV_OPT_SERIALIZE_SKIP_DEFAULTS|AV_OPT_SERIALIZE_SEARCH_CHILDREN,
+                                     &buf, '=', ',') >= 0) {
+                    printf("%s\n", buf);
                     av_free(buf);
                 }
             }
diff --git a/tests/ref/fate/opt b/tests/ref/fate/opt
index 43bf0929a3..05d57066a1 100644
--- a/tests/ref/fate/opt
+++ b/tests/ref/fate/opt
@@ -179,7 +179,7 @@ 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
-flt=0.333333,dbl=0.333333,array_int=
+child_num=0,flt=0.333333,dbl=0.333333,array_int=
 
 Testing av_set_options_string()
 Setting options string ''
