diff --git a/libavcodec/cbs_h2645.c b/libavcodec/cbs_h2645.c
index 0c591871d4..cb0005cc1b 100644
--- a/libavcodec/cbs_h2645.c
+++ b/libavcodec/cbs_h2645.c
@@ -1487,6 +1487,10 @@ const CodedBitstreamType ff_cbs_type_h264 = {
 
     .flush             = &cbs_h264_flush,
     .close             = &cbs_h264_close,
+
+    .insert_metadata   = &ff_cbs_sei_insert_metadata,
+    .remove_metadata   = &ff_cbs_sei_remove_metadata,
+    .extract_metadata  = &ff_cbs_sei_extract_metadata,
 };
 
 const CodedBitstreamType ff_cbs_type_h265 = {
@@ -1503,6 +1507,10 @@ const CodedBitstreamType ff_cbs_type_h265 = {
 
     .flush             = &cbs_h265_flush,
     .close             = &cbs_h265_close,
+
+    .insert_metadata   = &ff_cbs_sei_insert_metadata,
+    .remove_metadata   = &ff_cbs_sei_remove_metadata,
+    .extract_metadata  = &ff_cbs_sei_extract_metadata,
 };
 
 static const SEIMessageTypeDescriptor cbs_sei_common_types[] = {
diff --git a/libavcodec/cbs_sei.c b/libavcodec/cbs_sei.c
index 14f1cae506..83bb5cca55 100644
--- a/libavcodec/cbs_sei.c
+++ b/libavcodec/cbs_sei.c
@@ -21,6 +21,7 @@
 #include "cbs_h264.h"
 #include "cbs_h265.h"
 #include "cbs_sei.h"
+#include "cbs_metadata.h"
 
 static void cbs_free_user_data_registered(void *opaque, uint8_t *data)
 {
@@ -372,3 +373,96 @@ void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx,
         }
     }
 }
+
+typedef struct SEIMetadata {
+    enum CBSMetadataType cbs_type;
+    int sei_type;
+} SEIMetadata;
+
+static const SEIMetadata cbs_sei_metadata[] = {
+};
+
+static const SEIMessageTypeDescriptor *cbs_sei_find_type_from_metadata
+    (CodedBitstreamContext *ctx, enum CBSMetadataType metadata_type)
+{
+    const SEIMetadata *metadata;
+
+    metadata = NULL;
+    for (int i = 0; i < FF_ARRAY_ELEMS(cbs_sei_metadata); i++) {
+        if (metadata_type == cbs_sei_metadata[i].cbs_type) {
+            metadata = &cbs_sei_metadata[i];
+            break;
+        }
+    }
+    if (metadata)
+        return ff_cbs_sei_find_type(ctx, metadata->sei_type);
+    else
+        return NULL;
+}
+
+int ff_cbs_sei_insert_metadata(CodedBitstreamContext *ctx,
+                               CodedBitstreamFragment *au,
+                               enum CBSMetadataType metadata_type,
+                               const void *data)
+{
+    const SEIMessageTypeDescriptor *desc;
+    AVBufferRef *payload_buf;
+    int err;
+
+    desc = cbs_sei_find_type_from_metadata(ctx, metadata_type);
+    if (!desc || !desc->fill)
+        return AVERROR(EINVAL);
+
+    payload_buf = av_buffer_alloc(desc->size);
+    if (!payload_buf)
+        return AVERROR(ENOMEM);
+
+    desc->fill(payload_buf->data, data);
+
+    // All the metadata SEI messages must be unique in an access unit,
+    // so delete any existing ones of the given type.
+    ff_cbs_sei_delete_message_type(ctx, au, desc->type);
+
+    err = ff_cbs_sei_add_message(ctx, au, desc->prefix, desc->type,
+                                 payload_buf->data, payload_buf);
+    av_buffer_unref(&payload_buf);
+    return err;
+}
+
+int ff_cbs_sei_remove_metadata(CodedBitstreamContext *ctx,
+                               CodedBitstreamFragment *au,
+                               enum CBSMetadataType metadata_type)
+{
+    const SEIMessageTypeDescriptor *desc;
+
+    desc = cbs_sei_find_type_from_metadata(ctx, metadata_type);
+    if (!desc)
+        return AVERROR(EINVAL);
+
+    ff_cbs_sei_delete_message_type(ctx, au, desc->type);
+    return 0;
+}
+
+int ff_cbs_sei_extract_metadata(CodedBitstreamContext *ctx,
+                                CodedBitstreamFragment *au,
+                                enum CBSMetadataType metadata_type,
+                                void *data)
+{
+    const SEIMessageTypeDescriptor *desc;
+    SEIRawMessage *message;
+    int err;
+
+    desc = cbs_sei_find_type_from_metadata(ctx, metadata_type);
+    if (!desc || !desc->extract)
+        return AVERROR(EINVAL);
+
+    message = NULL;
+    err = ff_cbs_sei_find_message(ctx, au, desc->type, &message);
+    if (err < 0) {
+        // No message of the given type found.
+        return err;
+    }
+
+    desc->extract(data, message->payload);
+    return 0;
+}
diff --git a/libavcodec/cbs_sei.h b/libavcodec/cbs_sei.h
index c7a7a95be0..4fbcb6bfc0 100644
--- a/libavcodec/cbs_sei.h
+++ b/libavcodec/cbs_sei.h
@@ -110,6 +110,8 @@ typedef int (*SEIMessageWriteFunction)(CodedBitstreamContext *ctx,
                                        void *current,
                                        SEIMessageState *sei);
 
+typedef void (*SEIConvertFunction)(void *dst, const void *src);
+
 typedef struct SEIMessageTypeDescriptor {
     // Payload type for the message.  (-1 in this field ends a list.)
     int     type;
@@ -123,6 +125,10 @@ typedef struct SEIMessageTypeDescriptor {
     SEIMessageReadFunction  read;
     // Write bitstream from SEI message.
     SEIMessageWriteFunction write;
+    // Fill SEI structure from AV metadata.
+    SEIConvertFunction fill;
+    // Extract SEI metadata to AV structure.
+    SEIConvertFunction extract;
 } SEIMessageTypeDescriptor;
 
 // Macro for the read/write pair.  The clumsy cast is needed because the
@@ -132,6 +138,10 @@ typedef struct SEIMessageTypeDescriptor {
     .read  = (SEIMessageReadFunction) cbs_ ## codec ## _read_  ## name, \
     .write = (SEIMessageWriteFunction)cbs_ ## codec ## _write_ ## name
 
+#define SEI_MESSAGE_FE(codec, name) \
+    .fill    = (SEIConvertFunction)cbs_ ## codec ## _fill_    ## name, \
+    .extract = (SEIConvertFunction)cbs_ ## codec ## _extract_ ## name
+
 // End-of-list sentinel element.
 #define SEI_MESSAGE_TYPE_END { .type = -1 }
 
@@ -189,6 +199,8 @@ int ff_cbs_sei_find_message(CodedBitstreamContext *ctx,
                             uint32_t payload_type,
                             SEIRawMessage **message);
 
+enum CBSMetadataType;
+
 /**
  * Delete all messages with the given payload type from an access unit.
  */
@@ -196,4 +208,27 @@ void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx,
                                     CodedBitstreamFragment *au,
                                     uint32_t payload_type);
 
+/**
+ * Insert metadata into an access unit.
+ */
+int ff_cbs_sei_insert_metadata(CodedBitstreamContext *ctx,
+                               CodedBitstreamFragment *au,
+                               enum CBSMetadataType type,
+                               const void *data);
+
+/**
+ * Remove metadata from an access unit.
+ */
+int ff_cbs_sei_remove_metadata(CodedBitstreamContext *ctx,
+                               CodedBitstreamFragment *au,
+                               enum CBSMetadataType type);
+
+/**
+ * Extract metadata from an access unit.
+ */
+int ff_cbs_sei_extract_metadata(CodedBitstreamContext *ctx,
+                                CodedBitstreamFragment *au,
+                                enum CBSMetadataType type,
+                                void *data);
+
 #endif /* AVCODEC_CBS_SEI_H */
