diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 8f50217ad4..9b3f3ecdf7 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -69,7 +69,7 @@ OBJS-$(CONFIG_AUDIODSP)                += audiodsp.o
 OBJS-$(CONFIG_BLOCKDSP)                += blockdsp.o
 OBJS-$(CONFIG_BSWAPDSP)                += bswapdsp.o
 OBJS-$(CONFIG_CABAC)                   += cabac.o
-OBJS-$(CONFIG_CBS)                     += cbs.o cbs_bsf.o
+OBJS-$(CONFIG_CBS)                     += cbs.o cbs_bsf.o cbs_metadata.o
 OBJS-$(CONFIG_CBS_AV1)                 += cbs_av1.o
 OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o cbs_sei.o h2645_parse.o
 OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o cbs_sei.o h2645_parse.o
diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h
index faa847aad3..a505080ccf 100644
--- a/libavcodec/cbs_internal.h
+++ b/libavcodec/cbs_internal.h
@@ -83,6 +83,8 @@ typedef const struct CodedBitstreamUnitTypeDescriptor {
     int  (*content_clone)(AVBufferRef **ref, CodedBitstreamUnit *unit);
 } CodedBitstreamUnitTypeDescriptor;
 
+enum CBSMetadataType;
+
 typedef struct CodedBitstreamType {
     enum AVCodecID codec_id;
 
@@ -122,6 +124,21 @@ typedef struct CodedBitstreamType {
 
     // Free the codec internal state.
     void (*close)(CodedBitstreamContext *ctx);
+
+    // Insert metadata to fragment.
+    int (*insert_metadata)(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type, const void *data);
+
+    // Remove metadata from fragment.
+    int (*remove_metadata)(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type);
+
+    // Extract metadata from fragment.
+    int (*extract_metadata)(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *frag,
+                            enum CBSMetadataType type, void *data);
 } CodedBitstreamType;
 
 
diff --git a/libavcodec/cbs_metadata.c b/libavcodec/cbs_metadata.c
new file mode 100644
index 0000000000..140f5bcd9c
--- /dev/null
+++ b/libavcodec/cbs_metadata.c
@@ -0,0 +1,107 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/film_grain_params.h"
+#include "libavutil/mastering_display_metadata.h"
+#include "libavutil/stereo3d.h"
+
+#include "cbs.h"
+#include "cbs_internal.h"
+#include "cbs_metadata.h"
+
+static const CBSMetadataTypeDescriptor cbs_metadata_types[] = {
+    {
+        CBS_METADATA_MASTERING_DISPLAY,
+        "Mastering Display",
+        1, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA,
+        1, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+    },
+    {
+        CBS_METADATA_CONTENT_LIGHT_LEVEL,
+        "Content Light Level",
+        1, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL,
+        1, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+    },
+    {
+        CBS_METADATA_DISPLAY_MATRIX,
+        "Display Matrix",
+        1, AV_FRAME_DATA_DISPLAYMATRIX,
+        1, AV_PKT_DATA_DISPLAYMATRIX,
+    },
+    {
+        CBS_METADATA_STEREO3D,
+        "Stereo 3D",
+        1, AV_FRAME_DATA_STEREO3D,
+        1, AV_PKT_DATA_STEREO3D,
+    },
+};
+
+const CBSMetadataTypeDescriptor *ff_cbs_metadata_find_type(enum CBSMetadataType type)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(cbs_metadata_types); i++) {
+        if (cbs_metadata_types[i].type == type)
+            return &cbs_metadata_types[i];
+    }
+    return NULL;
+}
+
+void *ff_cbs_alloc_metadata(enum CBSMetadataType type, size_t *size)
+{
+    switch (type) {
+    case CBS_METADATA_MASTERING_DISPLAY:
+        *size = sizeof(AVMasteringDisplayMetadata);
+        return av_mastering_display_metadata_alloc();
+    case CBS_METADATA_CONTENT_LIGHT_LEVEL:
+        return av_content_light_metadata_alloc(size);
+    case CBS_METADATA_DISPLAY_MATRIX:
+        *size = 9 * sizeof(int32_t);
+        return av_mallocz(*size);
+    case CBS_METADATA_STEREO3D:
+        *size = sizeof(AVStereo3D);
+        return av_stereo3d_alloc();
+    default:
+        return NULL;
+    }
+}
+
+int ff_cbs_insert_metadata(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type, const void *data)
+{
+    if (!ctx->codec->insert_metadata)
+        return AVERROR(ENOSYS);
+    return ctx->codec->insert_metadata(ctx, frag, type, data);
+}
+
+int ff_cbs_remove_metadata(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type)
+{
+    if (!ctx->codec->remove_metadata)
+        return AVERROR(ENOSYS);
+    return ctx->codec->remove_metadata(ctx, frag, type);
+}
+
+int ff_cbs_extract_metadata(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *frag,
+                            enum CBSMetadataType type, void *data)
+{
+    if (!ctx->codec->extract_metadata)
+        return AVERROR(ENOSYS);
+    return ctx->codec->extract_metadata(ctx, frag, type, data);
+}
diff --git a/libavcodec/cbs_metadata.h b/libavcodec/cbs_metadata.h
new file mode 100644
index 0000000000..79ba6f4e26
--- /dev/null
+++ b/libavcodec/cbs_metadata.h
@@ -0,0 +1,94 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_CBS_METADATA_H
+#define AVCODEC_CBS_METADATA_H
+
+#include "libavutil/frame.h"
+
+#include "cbs.h"
+#include "packet.h"
+
+
+enum CBSMetadataType {
+    // List sentinel element.
+    CBS_METADATA_NONE,
+    // AVMasteringDisplayMetadata.
+    CBS_METADATA_MASTERING_DISPLAY,
+    // AVContentLightMetadata.
+    CBS_METADATA_CONTENT_LIGHT_LEVEL,
+    // int32_t matrix[9].
+    CBS_METADATA_DISPLAY_MATRIX,
+    // AVStereo3D.
+    CBS_METADATA_STEREO3D,
+};
+
+typedef struct CBSMetadataTypeDescriptor {
+    enum CBSMetadataType type;
+    const char *name;
+
+    // Matching frame and packet side data types, if available.
+    int                   has_frame_side_data;
+    enum AVFrameSideDataType  frame_side_data_type;
+    int                   has_packet_side_data;
+    enum AVPacketSideDataType packet_side_data_type;
+} CBSMetadataTypeDescriptor;
+
+/**
+ * Retrieve the descriptor for the given metadata type.
+ */
+const CBSMetadataTypeDescriptor *ff_cbs_metadata_find_type(enum CBSMetadataType type);
+
+/**
+ * Allocates an object for the given metadata type.
+ *
+ * If not null, size is written with the size of the object returned.
+ */
+void *ff_cbs_alloc_metadata(enum CBSMetadataType type, size_t *size);
+
+/**
+ * Insert metadata into a fragment.
+ *
+ * type is a CBS_METADATA_* value.  data must be a pointer to a filled
+ * structure matching the specified type.
+ */
+int ff_cbs_insert_metadata(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type, const void *data);
+
+/**
+ * Remove metadata from a fragment.
+ *
+ * Remove all metadata of the given CBS_METADATA_* type.
+ */
+int ff_cbs_remove_metadata(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *frag,
+                           enum CBSMetadataType type);
+
+/**
+ * Extract metadata from a fragment.
+ *
+ * type is a CBS_METADATA_* value.  data must be a pointer to an empty
+ * structure matching the specified type, which will be filled on
+ * success.
+ */
+int ff_cbs_extract_metadata(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *frag,
+                            enum CBSMetadataType type, void *data);
+
+#endif /* AVCODEC_CBS_METADATA_H */
