diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 450781886d..6e12a8171d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -71,8 +71,8 @@ OBJS-$(CONFIG_BSWAPDSP)                += bswapdsp.o
 OBJS-$(CONFIG_CABAC)                   += cabac.o
 OBJS-$(CONFIG_CBS)                     += cbs.o
 OBJS-$(CONFIG_CBS_AV1)                 += cbs_av1.o
-OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o h2645_parse.o
-OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o h2645_parse.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
 OBJS-$(CONFIG_CBS_JPEG)                += cbs_jpeg.o
 OBJS-$(CONFIG_CBS_MPEG2)               += cbs_mpeg2.o
 OBJS-$(CONFIG_CBS_VP9)                 += cbs_vp9.o
diff --git a/libavcodec/cbs_h264.h b/libavcodec/cbs_h264.h
index 81113f1ad0..9eb97eae24 100644
--- a/libavcodec/cbs_h264.h
+++ b/libavcodec/cbs_h264.h
@@ -291,34 +291,9 @@ typedef struct H264RawSEIDisplayOrientation {
     uint8_t display_orientation_extension_flag;
 } H264RawSEIDisplayOrientation;
 
-typedef struct H264RawSEIPayload {
-    uint32_t payload_type;
-    uint32_t payload_size;
-    union {
-        H264RawSEIBufferingPeriod buffering_period;
-        H264RawSEIPicTiming pic_timing;
-        H264RawSEIPanScanRect pan_scan_rect;
-        // H264RawSEIFiller filler -> no fields.
-        SEIRawUserDataRegistered user_data_registered;
-        SEIRawUserDataUnregistered user_data_unregistered;
-        H264RawSEIRecoveryPoint recovery_point;
-        H264RawSEIDisplayOrientation display_orientation;
-        SEIRawMasteringDisplayColourVolume mastering_display_colour_volume;
-        SEIRawAlternativeTransferCharacteristics
-            alternative_transfer_characteristics;
-        struct {
-            uint8_t     *data;
-            AVBufferRef *data_ref;
-            size_t       data_length;
-        } other;
-    } payload;
-} H264RawSEIPayload;
-
 typedef struct H264RawSEI {
     H264RawNALUnitHeader nal_unit_header;
-
-    H264RawSEIPayload payload[H264_MAX_SEI_PAYLOADS];
-    uint8_t payload_count;
+    SEIRawMessageList    message_list;
 } H264RawSEI;
 
 typedef struct H264RawSliceHeader {
@@ -438,27 +413,4 @@ typedef struct CodedBitstreamH264Context {
     uint8_t last_slice_nal_unit_type;
 } CodedBitstreamH264Context;
 
-
-/**
- * Add an SEI message to an access unit.
- *
- * On success, the payload will be owned by a unit in access_unit;
- * on failure, the content of the payload will be freed.
- */
-int ff_cbs_h264_add_sei_message(CodedBitstreamFragment *access_unit,
-                                H264RawSEIPayload *payload);
-
-/**
- * Delete an SEI message from an access unit.
- *
- * Deletes from nal_unit, which must be an SEI NAL unit.  If this is the
- * last message in nal_unit, also deletes it from access_unit.
- *
- * Requires nal_unit to be a unit in access_unit and position to be >= 0
- * and < the payload count of the SEI nal_unit.
- */
-void ff_cbs_h264_delete_sei_message(CodedBitstreamFragment *access_unit,
-                                    CodedBitstreamUnit *nal_unit,
-                                    int position);
-
 #endif /* AVCODEC_CBS_H264_H */
diff --git a/libavcodec/cbs_h2645.c b/libavcodec/cbs_h2645.c
index ce58e47a36..a00bc27370 100644
--- a/libavcodec/cbs_h2645.c
+++ b/libavcodec/cbs_h2645.c
@@ -348,6 +348,7 @@ static int cbs_h2645_read_more_rbsp_data(GetBitContext *gbc)
 
 #define more_rbsp_data(var) ((var) = cbs_h2645_read_more_rbsp_data(rw))
 
+#define bit_position(rw)   (get_bits_count(rw))
 #define byte_alignment(rw) (get_bits_count(rw) % 8)
 
 #define allocate(name, size) do { \
@@ -379,6 +380,7 @@ static int cbs_h2645_read_more_rbsp_data(GetBitContext *gbc)
 #undef xse
 #undef infer
 #undef more_rbsp_data
+#undef bit_position
 #undef byte_alignment
 #undef allocate
 
@@ -424,6 +426,7 @@ static int cbs_h2645_read_more_rbsp_data(GetBitContext *gbc)
 
 #define more_rbsp_data(var) (var)
 
+#define bit_position(rw)   (put_bits_count(rw))
 #define byte_alignment(rw) (put_bits_count(rw) % 8)
 
 #define allocate(name, size) do { \
@@ -460,6 +463,7 @@ static int cbs_h2645_read_more_rbsp_data(GetBitContext *gbc)
 #undef se
 #undef infer
 #undef more_rbsp_data
+#undef bit_position
 #undef byte_alignment
 #undef allocate
 
@@ -1372,36 +1376,11 @@ static void cbs_h265_close(CodedBitstreamContext *ctx)
         av_buffer_unref(&h265->pps_ref[i]);
 }
 
-static void cbs_h264_free_sei_payload(H264RawSEIPayload *payload)
-{
-    switch (payload->payload_type) {
-    case H264_SEI_TYPE_BUFFERING_PERIOD:
-    case H264_SEI_TYPE_PIC_TIMING:
-    case H264_SEI_TYPE_PAN_SCAN_RECT:
-    case H264_SEI_TYPE_RECOVERY_POINT:
-    case H264_SEI_TYPE_DISPLAY_ORIENTATION:
-    case H264_SEI_TYPE_MASTERING_DISPLAY_COLOUR_VOLUME:
-    case H264_SEI_TYPE_ALTERNATIVE_TRANSFER:
-        break;
-    case H264_SEI_TYPE_USER_DATA_REGISTERED:
-        av_buffer_unref(&payload->payload.user_data_registered.data_ref);
-        break;
-    case H264_SEI_TYPE_USER_DATA_UNREGISTERED:
-        av_buffer_unref(&payload->payload.user_data_unregistered.data_ref);
-        break;
-    default:
-        av_buffer_unref(&payload->payload.other.data_ref);
-        break;
-    }
-}
-
 static void cbs_h264_free_sei(void *opaque, uint8_t *content)
 {
     H264RawSEI *sei = (H264RawSEI*)content;
-    int i;
-    for (i = 0; i < sei->payload_count; i++)
-        cbs_h264_free_sei_payload(&sei->payload[i]);
-    av_freep(&content);
+    ff_cbs_sei_free_message_list(&sei->message_list);
+    av_free(content);
 }
 
 static const CodedBitstreamUnitTypeDescriptor cbs_h264_unit_types[] = {
@@ -1433,42 +1412,11 @@ static const CodedBitstreamUnitTypeDescriptor cbs_h264_unit_types[] = {
     CBS_UNIT_TYPE_END_OF_LIST
 };
 
-static void cbs_h265_free_sei_payload(H265RawSEIPayload *payload)
-{
-    switch (payload->payload_type) {
-    case HEVC_SEI_TYPE_BUFFERING_PERIOD:
-    case HEVC_SEI_TYPE_PICTURE_TIMING:
-    case HEVC_SEI_TYPE_PAN_SCAN_RECT:
-    case HEVC_SEI_TYPE_RECOVERY_POINT:
-    case HEVC_SEI_TYPE_DISPLAY_ORIENTATION:
-    case HEVC_SEI_TYPE_ACTIVE_PARAMETER_SETS:
-    case HEVC_SEI_TYPE_DECODED_PICTURE_HASH:
-    case HEVC_SEI_TYPE_TIME_CODE:
-    case HEVC_SEI_TYPE_MASTERING_DISPLAY_INFO:
-    case HEVC_SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO:
-    case HEVC_SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS:
-    case HEVC_SEI_TYPE_ALPHA_CHANNEL_INFO:
-        break;
-    case HEVC_SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35:
-        av_buffer_unref(&payload->payload.user_data_registered.data_ref);
-        break;
-    case HEVC_SEI_TYPE_USER_DATA_UNREGISTERED:
-        av_buffer_unref(&payload->payload.user_data_unregistered.data_ref);
-        break;
-    default:
-        av_buffer_unref(&payload->payload.other.data_ref);
-        break;
-    }
-    av_buffer_unref(&payload->extension_data.data_ref);
-}
-
 static void cbs_h265_free_sei(void *opaque, uint8_t *content)
 {
     H265RawSEI *sei = (H265RawSEI*)content;
-    int i;
-    for (i = 0; i < sei->payload_count; i++)
-        cbs_h265_free_sei_payload(&sei->payload[i]);
-    av_freep(&content);
+    ff_cbs_sei_free_message_list(&sei->message_list);
+    av_free(content);
 }
 
 static const CodedBitstreamUnitTypeDescriptor cbs_h265_unit_types[] = {
@@ -1548,92 +1496,164 @@ const CodedBitstreamType ff_cbs_type_h265 = {
     .close             = &cbs_h265_close,
 };
 
-int ff_cbs_h264_add_sei_message(CodedBitstreamFragment *au,
-                                H264RawSEIPayload *payload)
-{
-    H264RawSEI *sei = NULL;
-    int err, i;
-
-    // Find an existing SEI NAL unit to add to.
-    for (i = 0; i < au->nb_units; i++) {
-        if (au->units[i].type == H264_NAL_SEI) {
-            sei = au->units[i].content;
-            if (sei->payload_count < H264_MAX_SEI_PAYLOADS)
-                break;
-
-            sei = NULL;
-        }
-    }
-
-    if (!sei) {
-        // Need to make a new SEI NAL unit.  Insert it before the first
-        // slice data NAL unit; if no slice data, add at the end.
-        AVBufferRef *sei_ref;
-
-        sei = av_mallocz(sizeof(*sei));
-        if (!sei) {
-            err = AVERROR(ENOMEM);
-            goto fail;
-        }
-
-        sei->nal_unit_header.nal_unit_type = H264_NAL_SEI;
-        sei->nal_unit_header.nal_ref_idc   = 0;
-
-        sei_ref = av_buffer_create((uint8_t*)sei, sizeof(*sei),
-                                   &cbs_h264_free_sei, NULL, 0);
-        if (!sei_ref) {
-            av_freep(&sei);
-            err = AVERROR(ENOMEM);
-            goto fail;
-        }
-
-        for (i = 0; i < au->nb_units; i++) {
-            if (au->units[i].type == H264_NAL_SLICE ||
-                au->units[i].type == H264_NAL_IDR_SLICE)
-                break;
-        }
-
-        err = ff_cbs_insert_unit_content(au, i, H264_NAL_SEI,
-                                         sei, sei_ref);
-        av_buffer_unref(&sei_ref);
-        if (err < 0)
-            goto fail;
-    }
+static const SEIMessageTypeDescriptor cbs_sei_common_types[] = {
+    {
+        SEI_TYPE_FILLER_PAYLOAD,
+        1, 1,
+        sizeof(SEIRawFillerPayload),
+        SEI_MESSAGE_RW(sei, filler_payload),
+    },
+    {
+        SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35,
+        1, 1,
+        sizeof(SEIRawUserDataRegistered),
+        SEI_MESSAGE_RW(sei, user_data_registered),
+    },
+    {
+        SEI_TYPE_USER_DATA_UNREGISTERED,
+        1, 1,
+        sizeof(SEIRawUserDataUnregistered),
+        SEI_MESSAGE_RW(sei, user_data_unregistered),
+    },
+    {
+        SEI_TYPE_MASTERING_DISPLAY_COLOUR_VOLUME,
+        1, 0,
+        sizeof(SEIRawMasteringDisplayColourVolume),
+        SEI_MESSAGE_RW(sei, mastering_display_colour_volume),
+    },
+    {
+        SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO,
+        1, 0,
+        sizeof(SEIRawContentLightLevelInfo),
+        SEI_MESSAGE_RW(sei, content_light_level_info),
+    },
+    {
+        SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS,
+        1, 0,
+        sizeof(SEIRawAlternativeTransferCharacteristics),
+        SEI_MESSAGE_RW(sei, alternative_transfer_characteristics),
+    },
+    SEI_MESSAGE_TYPE_END,
+};
 
-    memcpy(&sei->payload[sei->payload_count], payload, sizeof(*payload));
-    ++sei->payload_count;
+static const SEIMessageTypeDescriptor cbs_sei_h264_types[] = {
+    {
+        SEI_TYPE_BUFFERING_PERIOD,
+        1, 0,
+        sizeof(H264RawSEIBufferingPeriod),
+        SEI_MESSAGE_RW(h264, sei_buffering_period),
+    },
+    {
+        SEI_TYPE_PIC_TIMING,
+        1, 0,
+        sizeof(H264RawSEIPicTiming),
+        SEI_MESSAGE_RW(h264, sei_pic_timing),
+    },
+    {
+        SEI_TYPE_PAN_SCAN_RECT,
+        1, 0,
+        sizeof(H264RawSEIPanScanRect),
+        SEI_MESSAGE_RW(h264, sei_pan_scan_rect),
+    },
+    {
+        SEI_TYPE_RECOVERY_POINT,
+        1, 0,
+        sizeof(H264RawSEIRecoveryPoint),
+        SEI_MESSAGE_RW(h264, sei_recovery_point),
+    },
+    {
+        SEI_TYPE_DISPLAY_ORIENTATION,
+        1, 0,
+        sizeof(H264RawSEIDisplayOrientation),
+        SEI_MESSAGE_RW(h264, sei_display_orientation),
+    },
+    SEI_MESSAGE_TYPE_END
+};
 
-    return 0;
-fail:
-    cbs_h264_free_sei_payload(payload);
-    return err;
-}
+static const SEIMessageTypeDescriptor cbs_sei_h265_types[] = {
+    {
+        SEI_TYPE_BUFFERING_PERIOD,
+        1, 0,
+        sizeof(H265RawSEIBufferingPeriod),
+        SEI_MESSAGE_RW(h265, sei_buffering_period),
+    },
+    {
+        SEI_TYPE_PIC_TIMING,
+        1, 0,
+        sizeof(H265RawSEIPicTiming),
+        SEI_MESSAGE_RW(h265, sei_pic_timing),
+    },
+    {
+        SEI_TYPE_PAN_SCAN_RECT,
+        1, 0,
+        sizeof(H265RawSEIPanScanRect),
+        SEI_MESSAGE_RW(h265, sei_pan_scan_rect),
+    },
+    {
+        SEI_TYPE_RECOVERY_POINT,
+        1, 0,
+        sizeof(H265RawSEIRecoveryPoint),
+        SEI_MESSAGE_RW(h265, sei_recovery_point),
+    },
+    {
+        SEI_TYPE_DISPLAY_ORIENTATION,
+        1, 0,
+        sizeof(H265RawSEIDisplayOrientation),
+        SEI_MESSAGE_RW(h265, sei_display_orientation),
+    },
+    {
+        SEI_TYPE_ACTIVE_PARAMETER_SETS,
+        1, 0,
+        sizeof(H265RawSEIActiveParameterSets),
+        SEI_MESSAGE_RW(h265, sei_active_parameter_sets),
+    },
+    {
+        SEI_TYPE_DECODED_PICTURE_HASH,
+        0, 1,
+        sizeof(H265RawSEIDecodedPictureHash),
+        SEI_MESSAGE_RW(h265, sei_decoded_picture_hash),
+    },
+    {
+        SEI_TYPE_TIME_CODE,
+        1, 0,
+        sizeof(H265RawSEITimeCode),
+        SEI_MESSAGE_RW(h265, sei_time_code),
+    },
+    {
+        SEI_TYPE_ALPHA_CHANNEL_INFO,
+        1, 0,
+        sizeof(H265RawSEIAlphaChannelInfo),
+        SEI_MESSAGE_RW(h265, sei_alpha_channel_info),
+    },
+    SEI_MESSAGE_TYPE_END
+};
 
-void ff_cbs_h264_delete_sei_message(CodedBitstreamFragment *au,
-                                    CodedBitstreamUnit *nal,
-                                    int position)
+const SEIMessageTypeDescriptor *ff_cbs_sei_find_type(CodedBitstreamContext *ctx,
+                                                     int payload_type)
 {
-    H264RawSEI *sei = nal->content;
-
-    av_assert0(nal->type == H264_NAL_SEI);
-    av_assert0(position >= 0 && position < sei->payload_count);
-
-    if (position == 0 && sei->payload_count == 1) {
-        // Deleting NAL unit entirely.
-        int i;
+    const SEIMessageTypeDescriptor *codec_list;
+    int i;
 
-        for (i = 0; i < au->nb_units; i++) {
-            if (&au->units[i] == nal)
-                break;
-        }
+    for (i = 0; cbs_sei_common_types[i].type >= 0; i++) {
+        if (cbs_sei_common_types[i].type == payload_type)
+            return &cbs_sei_common_types[i];
+    }
 
-        ff_cbs_delete_unit(au, i);
-    } else {
-        cbs_h264_free_sei_payload(&sei->payload[position]);
+    switch (ctx->codec->codec_id) {
+    case AV_CODEC_ID_H264:
+        codec_list = cbs_sei_h264_types;
+        break;
+    case AV_CODEC_ID_H265:
+        codec_list = cbs_sei_h265_types;
+        break;
+    default:
+        return NULL;
+    }
 
-        --sei->payload_count;
-        memmove(sei->payload + position,
-                sei->payload + position + 1,
-                (sei->payload_count - position) * sizeof(*sei->payload));
+    for (i = 0; codec_list[i].type >= 0; i++) {
+        if (codec_list[i].type == payload_type)
+            return &codec_list[i];
     }
+
+    return NULL;
 }
diff --git a/libavcodec/cbs_h264_syntax_template.c b/libavcodec/cbs_h264_syntax_template.c
index 76ed51cc7b..9587f33985 100644
--- a/libavcodec/cbs_h264_syntax_template.c
+++ b/libavcodec/cbs_h264_syntax_template.c
@@ -511,7 +511,8 @@ static int FUNC(pps)(CodedBitstreamContext *ctx, RWContext *rw,
 }
 
 static int FUNC(sei_buffering_period)(CodedBitstreamContext *ctx, RWContext *rw,
-                                      H264RawSEIBufferingPeriod *current)
+                                      H264RawSEIBufferingPeriod *current,
+                                      SEIMessageState *sei)
 {
     CodedBitstreamH264Context *h264 = ctx->priv_data;
     const H264RawSPS *sps;
@@ -604,7 +605,8 @@ static int FUNC(sei_pic_timestamp)(CodedBitstreamContext *ctx, RWContext *rw,
 }
 
 static int FUNC(sei_pic_timing)(CodedBitstreamContext *ctx, RWContext *rw,
-                                H264RawSEIPicTiming *current)
+                                H264RawSEIPicTiming *current,
+                                SEIMessageState *sei)
 {
     CodedBitstreamH264Context *h264 = ctx->priv_data;
     const H264RawSPS *sps;
@@ -675,7 +677,8 @@ static int FUNC(sei_pic_timing)(CodedBitstreamContext *ctx, RWContext *rw,
 }
 
 static int FUNC(sei_pan_scan_rect)(CodedBitstreamContext *ctx, RWContext *rw,
-                                   H264RawSEIPanScanRect *current)
+                                   H264RawSEIPanScanRect *current,
+                                   SEIMessageState *sei)
 {
     int err, i;
 
@@ -701,7 +704,8 @@ static int FUNC(sei_pan_scan_rect)(CodedBitstreamContext *ctx, RWContext *rw,
 }
 
 static int FUNC(sei_recovery_point)(CodedBitstreamContext *ctx, RWContext *rw,
-                                    H264RawSEIRecoveryPoint *current)
+                                    H264RawSEIRecoveryPoint *current,
+                                    SEIMessageState *sei)
 {
     int err;
 
@@ -716,7 +720,8 @@ static int FUNC(sei_recovery_point)(CodedBitstreamContext *ctx, RWContext *rw,
 }
 
 static int FUNC(sei_display_orientation)(CodedBitstreamContext *ctx, RWContext *rw,
-                                         H264RawSEIDisplayOrientation *current)
+                                         H264RawSEIDisplayOrientation *current,
+                                         SEIMessageState *sei)
 {
     int err;
 
@@ -734,171 +739,17 @@ static int FUNC(sei_display_orientation)(CodedBitstreamContext *ctx, RWContext *
     return 0;
 }
 
-static int FUNC(sei_payload)(CodedBitstreamContext *ctx, RWContext *rw,
-                             H264RawSEIPayload *current)
-{
-    int err, i;
-    int start_position, end_position;
-
-#ifdef READ
-    start_position = get_bits_count(rw);
-#else
-    start_position = put_bits_count(rw);
-#endif
-
-    switch (current->payload_type) {
-    case H264_SEI_TYPE_BUFFERING_PERIOD:
-        CHECK(FUNC(sei_buffering_period)
-              (ctx, rw, &current->payload.buffering_period));
-        break;
-    case H264_SEI_TYPE_PIC_TIMING:
-        CHECK(FUNC(sei_pic_timing)
-              (ctx, rw, &current->payload.pic_timing));
-        break;
-    case H264_SEI_TYPE_PAN_SCAN_RECT:
-        CHECK(FUNC(sei_pan_scan_rect)
-              (ctx, rw, &current->payload.pan_scan_rect));
-        break;
-    case H264_SEI_TYPE_FILLER_PAYLOAD:
-        {
-            for (i = 0; i  < current->payload_size; i++)
-                fixed(8, ff_byte, 0xff);
-        }
-        break;
-    case H264_SEI_TYPE_USER_DATA_REGISTERED:
-        CHECK(FUNC_SEI(sei_user_data_registered)
-              (ctx, rw, &current->payload.user_data_registered, &current->payload_size));
-        break;
-    case H264_SEI_TYPE_USER_DATA_UNREGISTERED:
-        CHECK(FUNC_SEI(sei_user_data_unregistered)
-              (ctx, rw, &current->payload.user_data_unregistered, &current->payload_size));
-        break;
-    case H264_SEI_TYPE_RECOVERY_POINT:
-        CHECK(FUNC(sei_recovery_point)
-              (ctx, rw, &current->payload.recovery_point));
-        break;
-    case H264_SEI_TYPE_DISPLAY_ORIENTATION:
-        CHECK(FUNC(sei_display_orientation)
-              (ctx, rw, &current->payload.display_orientation));
-        break;
-    case H264_SEI_TYPE_MASTERING_DISPLAY_COLOUR_VOLUME:
-        CHECK(FUNC_SEI(sei_mastering_display_colour_volume)
-              (ctx, rw, &current->payload.mastering_display_colour_volume));
-        break;
-    case H264_SEI_TYPE_ALTERNATIVE_TRANSFER:
-        CHECK(FUNC_SEI(sei_alternative_transfer_characteristics)
-              (ctx, rw, &current->payload.alternative_transfer_characteristics));
-        break;
-    default:
-        {
-#ifdef READ
-            current->payload.other.data_length = current->payload_size;
-#endif
-            allocate(current->payload.other.data, current->payload.other.data_length);
-            for (i = 0; i < current->payload.other.data_length; i++)
-                xu(8, payload_byte[i], current->payload.other.data[i], 0, 255, 1, i);
-        }
-    }
-
-    if (byte_alignment(rw)) {
-        fixed(1, bit_equal_to_one, 1);
-        while (byte_alignment(rw))
-            fixed(1, bit_equal_to_zero, 0);
-    }
-
-#ifdef READ
-    end_position = get_bits_count(rw);
-    if (end_position < start_position + 8 * current->payload_size) {
-        av_log(ctx->log_ctx, AV_LOG_ERROR, "Incorrect SEI payload length: "
-               "header %"PRIu32" bits, actually %d bits.\n",
-               8 * current->payload_size,
-               end_position - start_position);
-        return AVERROR_INVALIDDATA;
-    }
-#else
-    end_position = put_bits_count(rw);
-    current->payload_size = (end_position - start_position) / 8;
-#endif
-
-    return 0;
-}
-
 static int FUNC(sei)(CodedBitstreamContext *ctx, RWContext *rw,
                      H264RawSEI *current)
 {
-    int err, k;
+    int err;
 
     HEADER("Supplemental Enhancement Information");
 
     CHECK(FUNC(nal_unit_header)(ctx, rw, &current->nal_unit_header,
                                 1 << H264_NAL_SEI));
 
-#ifdef READ
-    for (k = 0; k < H264_MAX_SEI_PAYLOADS; k++) {
-        uint32_t payload_type = 0;
-        uint32_t payload_size = 0;
-        uint32_t tmp;
-
-        while (show_bits(rw, 8) == 0xff) {
-            fixed(8, ff_byte, 0xff);
-            payload_type += 255;
-        }
-        xu(8, last_payload_type_byte, tmp, 0, 254, 0);
-        payload_type += tmp;
-
-        while (show_bits(rw, 8) == 0xff) {
-            fixed(8, ff_byte, 0xff);
-            payload_size += 255;
-        }
-        xu(8, last_payload_size_byte, tmp, 0, 254, 0);
-        payload_size += tmp;
-
-        current->payload[k].payload_type = payload_type;
-        current->payload[k].payload_size = payload_size;
-
-        current->payload_count++;
-        CHECK(FUNC(sei_payload)(ctx, rw, &current->payload[k]));
-
-        if (!cbs_h2645_read_more_rbsp_data(rw))
-            break;
-    }
-    if (k >= H264_MAX_SEI_PAYLOADS) {
-        av_log(ctx->log_ctx, AV_LOG_ERROR, "Too many payloads in "
-               "SEI message: found %d.\n", k);
-        return AVERROR_INVALIDDATA;
-    }
-#else
-    for (k = 0; k < current->payload_count; k++) {
-        PutBitContext start_state;
-        uint32_t tmp;
-        int need_size, i;
-
-        // Somewhat clumsy: we write the payload twice when
-        // we don't know the size in advance.  This will mess
-        // with trace output, but is otherwise harmless.
-        start_state = *rw;
-        need_size = !current->payload[k].payload_size;
-        for (i = 0; i < 1 + need_size; i++) {
-            *rw = start_state;
-
-            tmp = current->payload[k].payload_type;
-            while (tmp >= 255) {
-                fixed(8, ff_byte, 0xff);
-                tmp -= 255;
-            }
-            xu(8, last_payload_type_byte, tmp, 0, 254, 0);
-
-            tmp = current->payload[k].payload_size;
-            while (tmp >= 255) {
-                fixed(8, ff_byte, 0xff);
-                tmp -= 255;
-            }
-            xu(8, last_payload_size_byte, tmp, 0, 254, 0);
-
-            CHECK(FUNC(sei_payload)(ctx, rw, &current->payload[k]));
-        }
-    }
-#endif
+    CHECK(FUNC_SEI(message_list)(ctx, rw, &current->message_list, 1));
 
     CHECK(FUNC(rbsp_trailing_bits)(ctx, rw));
 
diff --git a/libavcodec/cbs_h265.h b/libavcodec/cbs_h265.h
index d8e93e3bb8..738cbeec2c 100644
--- a/libavcodec/cbs_h265.h
+++ b/libavcodec/cbs_h265.h
@@ -658,40 +658,9 @@ typedef struct H265RawSEIAlphaChannelInfo {
     uint8_t  alpha_channel_clip_type_flag;
 } H265RawSEIAlphaChannelInfo;
 
-typedef struct H265RawSEIPayload {
-    uint32_t payload_type;
-    uint32_t payload_size;
-    union {
-        H265RawSEIBufferingPeriod buffering_period;
-        H265RawSEIPicTiming pic_timing;
-        H265RawSEIPanScanRect pan_scan_rect;
-        SEIRawUserDataRegistered user_data_registered;
-        SEIRawUserDataUnregistered user_data_unregistered;
-        H265RawSEIRecoveryPoint recovery_point;
-        H265RawSEIDisplayOrientation display_orientation;
-        H265RawSEIActiveParameterSets active_parameter_sets;
-        H265RawSEIDecodedPictureHash decoded_picture_hash;
-        H265RawSEITimeCode time_code;
-        SEIRawMasteringDisplayColourVolume
-            mastering_display_colour_volume;
-        SEIRawContentLightLevelInfo content_light_level;
-        SEIRawAlternativeTransferCharacteristics
-            alternative_transfer_characteristics;
-        H265RawSEIAlphaChannelInfo alpha_channel_info;
-        struct {
-            uint8_t     *data;
-            AVBufferRef *data_ref;
-            size_t       data_length;
-        } other;
-    } payload;
-    H265RawExtensionData extension_data;
-} H265RawSEIPayload;
-
 typedef struct H265RawSEI {
     H265RawNALUnitHeader nal_unit_header;
-
-    H265RawSEIPayload payload[H265_MAX_SEI_PAYLOADS];
-    uint8_t payload_count;
+    SEIRawMessageList    message_list;
 } H265RawSEI;
 
 typedef struct CodedBitstreamH265Context {
diff --git a/libavcodec/cbs_h265_syntax_template.c b/libavcodec/cbs_h265_syntax_template.c
index e4cc1a9be8..d09934cfeb 100644
--- a/libavcodec/cbs_h265_syntax_template.c
+++ b/libavcodec/cbs_h265_syntax_template.c
@@ -1596,10 +1596,9 @@ static int FUNC(slice_segment_header)(CodedBitstreamContext *ctx, RWContext *rw,
     return 0;
 }
 
-static int FUNC(sei_buffering_period)(CodedBitstreamContext *ctx, RWContext *rw,
-                                      H265RawSEIBufferingPeriod *current,
-                                      uint32_t *payload_size,
-                                      int *more_data)
+static int FUNC(sei_buffering_period)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIBufferingPeriod *current, SEIMessageState *sei)
 {
     CodedBitstreamH265Context *h265 = ctx->priv_data;
     const H265RawSPS *sps;
@@ -1687,7 +1686,7 @@ static int FUNC(sei_buffering_period)(CodedBitstreamContext *ctx, RWContext *rw,
 
 #ifdef READ
     end_pos = get_bits_count(rw);
-    if (cbs_h265_payload_extension_present(rw, *payload_size,
+    if (cbs_h265_payload_extension_present(rw, sei->payload_size,
                                            end_pos - start_pos))
         flag(use_alt_cpb_params_flag);
     else
@@ -1695,20 +1694,21 @@ static int FUNC(sei_buffering_period)(CodedBitstreamContext *ctx, RWContext *rw,
 #else
     // If unknown extension data exists, then use_alt_cpb_params_flag is
     // coded in the bitstream and must be written even if it's 0.
-    if (current->use_alt_cpb_params_flag || *more_data) {
+    if (current->use_alt_cpb_params_flag || sei->extension_present) {
         flag(use_alt_cpb_params_flag);
         // Ensure this bit is not the last in the payload by making the
         // more_data_in_payload() check evaluate to true, so it may not
         // be mistaken as something else by decoders.
-        *more_data = 1;
+        sei->extension_present = 1;
     }
 #endif
 
     return 0;
 }
 
-static int FUNC(sei_pic_timing)(CodedBitstreamContext *ctx, RWContext *rw,
-                                H265RawSEIPicTiming *current)
+static int FUNC(sei_pic_timing)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIPicTiming *current, SEIMessageState *sei)
 {
     CodedBitstreamH265Context *h265 = ctx->priv_data;
     const H265RawSPS *sps;
@@ -1782,8 +1782,9 @@ static int FUNC(sei_pic_timing)(CodedBitstreamContext *ctx, RWContext *rw,
     return 0;
 }
 
-static int FUNC(sei_pan_scan_rect)(CodedBitstreamContext *ctx, RWContext *rw,
-                                   H265RawSEIPanScanRect *current)
+static int FUNC(sei_pan_scan_rect)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIPanScanRect *current, SEIMessageState *sei)
 {
     int err, i;
 
@@ -1808,8 +1809,9 @@ static int FUNC(sei_pan_scan_rect)(CodedBitstreamContext *ctx, RWContext *rw,
     return 0;
 }
 
-static int FUNC(sei_recovery_point)(CodedBitstreamContext *ctx, RWContext *rw,
-                                    H265RawSEIRecoveryPoint *current)
+static int FUNC(sei_recovery_point)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIRecoveryPoint *current, SEIMessageState *sei)
 {
     int err;
 
@@ -1823,8 +1825,9 @@ static int FUNC(sei_recovery_point)(CodedBitstreamContext *ctx, RWContext *rw,
     return 0;
 }
 
-static int FUNC(sei_display_orientation)(CodedBitstreamContext *ctx, RWContext *rw,
-                                         H265RawSEIDisplayOrientation *current)
+static int FUNC(sei_display_orientation)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIDisplayOrientation *current, SEIMessageState *sei)
 {
     int err;
 
@@ -1841,8 +1844,9 @@ static int FUNC(sei_display_orientation)(CodedBitstreamContext *ctx, RWContext *
     return 0;
 }
 
-static int FUNC(sei_active_parameter_sets)(CodedBitstreamContext *ctx, RWContext *rw,
-                                           H265RawSEIActiveParameterSets *current)
+static int FUNC(sei_active_parameter_sets)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIActiveParameterSets *current, SEIMessageState *sei)
 {
     CodedBitstreamH265Context *h265 = ctx->priv_data;
     const H265RawVPS *vps;
@@ -1877,8 +1881,9 @@ static int FUNC(sei_active_parameter_sets)(CodedBitstreamContext *ctx, RWContext
     return 0;
 }
 
-static int FUNC(sei_decoded_picture_hash)(CodedBitstreamContext *ctx, RWContext *rw,
-                                          H265RawSEIDecodedPictureHash *current)
+static int FUNC(sei_decoded_picture_hash)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIDecodedPictureHash *current, SEIMessageState *sei)
 {
     CodedBitstreamH265Context *h265 = ctx->priv_data;
     const H265RawSPS *sps = h265->active_sps;
@@ -1908,8 +1913,9 @@ static int FUNC(sei_decoded_picture_hash)(CodedBitstreamContext *ctx, RWContext
     return 0;
 }
 
-static int FUNC(sei_time_code)(CodedBitstreamContext *ctx, RWContext *rw,
-                               H265RawSEITimeCode *current)
+static int FUNC(sei_time_code)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEITimeCode *current, SEIMessageState *sei)
 {
     int err, i;
 
@@ -1958,9 +1964,9 @@ static int FUNC(sei_time_code)(CodedBitstreamContext *ctx, RWContext *rw,
     return 0;
 }
 
-static int FUNC(sei_alpha_channel_info)(CodedBitstreamContext *ctx,
-                                        RWContext *rw,
-                                        H265RawSEIAlphaChannelInfo *current)
+static int FUNC(sei_alpha_channel_info)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     H265RawSEIAlphaChannelInfo *current, SEIMessageState *sei)
 {
     int err, length;
 
@@ -1986,156 +1992,10 @@ static int FUNC(sei_alpha_channel_info)(CodedBitstreamContext *ctx,
     return 0;
 }
 
-static int FUNC(payload_extension)(CodedBitstreamContext *ctx, RWContext *rw,
-                                   H265RawExtensionData *current, uint32_t payload_size,
-                                   int cur_pos)
-{
-    int err;
-    size_t byte_length, k;
-
-#ifdef READ
-    GetBitContext tmp;
-    int bits_left, payload_zero_bits;
-
-    if (!cbs_h265_payload_extension_present(rw, payload_size, cur_pos))
-        return 0;
-
-    bits_left = 8 * payload_size - cur_pos;
-    tmp = *rw;
-    if (bits_left > 8)
-        skip_bits_long(&tmp, bits_left - 8);
-    payload_zero_bits = get_bits(&tmp, FFMIN(bits_left, 8));
-    if (!payload_zero_bits)
-        return AVERROR_INVALIDDATA;
-    payload_zero_bits = ff_ctz(payload_zero_bits);
-    current->bit_length = bits_left - payload_zero_bits - 1;
-    allocate(current->data, (current->bit_length + 7) / 8);
-#endif
-
-    byte_length = (current->bit_length + 7) / 8;
-    for (k = 0; k < byte_length; k++) {
-        int length = FFMIN(current->bit_length - k * 8, 8);
-        xu(length, reserved_payload_extension_data, current->data[k],
-           0, MAX_UINT_BITS(length), 0);
-    }
-
-    return 0;
-}
-
-static int FUNC(sei_payload)(CodedBitstreamContext *ctx, RWContext *rw,
-                             H265RawSEIPayload *current, int prefix)
-{
-    int err, i;
-    int start_position, current_position;
-    int more_data = !!current->extension_data.bit_length;
-
-#ifdef READ
-    start_position = get_bits_count(rw);
-#else
-    start_position = put_bits_count(rw);
-#endif
-
-    switch (current->payload_type) {
-#define SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid) do { \
-            if (prefix && !prefix_valid) { \
-                av_log(ctx->log_ctx, AV_LOG_ERROR, "SEI type %s invalid " \
-                       "as prefix SEI!\n", #name); \
-                return AVERROR_INVALIDDATA; \
-            } \
-            if (!prefix && !suffix_valid) { \
-                av_log(ctx->log_ctx, AV_LOG_ERROR, "SEI type %s invalid " \
-                       "as suffix SEI!\n", #name); \
-                return AVERROR_INVALIDDATA; \
-            } \
-        } while (0)
-#define SEI_TYPE_N(type, prefix_valid, suffix_valid, name) \
-    case HEVC_SEI_TYPE_ ## type: \
-        SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid); \
-        CHECK(FUNC(sei_ ## name)(ctx, rw, &current->payload.name)); \
-        break
-#define SEI_TYPE_S(type, prefix_valid, suffix_valid, name) \
-    case HEVC_SEI_TYPE_ ## type: \
-        SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid); \
-        CHECK(FUNC(sei_ ## name)(ctx, rw, &current->payload.name, \
-                                 &current->payload_size)); \
-        break
-#define SEI_TYPE_E(type, prefix_valid, suffix_valid, name) \
-    case HEVC_SEI_TYPE_ ## type: \
-        SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid); \
-        CHECK(FUNC(sei_ ## name)(ctx, rw, &current->payload.name, \
-                                 &current->payload_size, \
-                                 &more_data)); \
-        break
-
-#define SEI_TYPE_N2(type, prefix_valid, suffix_valid, name) \
-    case HEVC_SEI_TYPE_ ## type: \
-        SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid); \
-        CHECK(FUNC_SEI(sei_ ## name)(ctx, rw, &current->payload.name)); \
-        break
-#define SEI_TYPE_S2(type, prefix_valid, suffix_valid, name) \
-    case HEVC_SEI_TYPE_ ## type: \
-        SEI_TYPE_CHECK_VALID(name, prefix_valid, suffix_valid); \
-        CHECK(FUNC_SEI(sei_ ## name)(ctx, rw, &current->payload.name, \
-                                     &current->payload_size)); \
-        break
-
-        SEI_TYPE_E(BUFFERING_PERIOD,         1, 0, buffering_period);
-        SEI_TYPE_N(PICTURE_TIMING,           1, 0, pic_timing);
-        SEI_TYPE_N(PAN_SCAN_RECT,            1, 0, pan_scan_rect);
-        SEI_TYPE_S2(USER_DATA_REGISTERED_ITU_T_T35,
-                                             1, 1, user_data_registered);
-        SEI_TYPE_S2(USER_DATA_UNREGISTERED,  1, 1, user_data_unregistered);
-        SEI_TYPE_N(RECOVERY_POINT,           1, 0, recovery_point);
-        SEI_TYPE_N(DISPLAY_ORIENTATION,      1, 0, display_orientation);
-        SEI_TYPE_N(ACTIVE_PARAMETER_SETS,    1, 0, active_parameter_sets);
-        SEI_TYPE_N(DECODED_PICTURE_HASH,     0, 1, decoded_picture_hash);
-        SEI_TYPE_N(TIME_CODE,                1, 0, time_code);
-        SEI_TYPE_N2(MASTERING_DISPLAY_INFO,  1, 0, mastering_display_colour_volume);
-        SEI_TYPE_N2(CONTENT_LIGHT_LEVEL_INFO,1, 0, content_light_level);
-        SEI_TYPE_N2(ALTERNATIVE_TRANSFER_CHARACTERISTICS,
-                                             1, 0, alternative_transfer_characteristics);
-        SEI_TYPE_N(ALPHA_CHANNEL_INFO,       1, 0, alpha_channel_info);
-
-#undef SEI_TYPE
-    default:
-        {
-#ifdef READ
-            current->payload.other.data_length = current->payload_size;
-#endif
-            allocate(current->payload.other.data, current->payload.other.data_length);
-
-            for (i = 0; i < current->payload_size; i++)
-                xu(8, payload_byte[i], current->payload.other.data[i], 0, 255,
-                   1, i);
-        }
-    }
-
-    // more_data_in_payload()
-#ifdef READ
-    current_position = get_bits_count(rw) - start_position;
-    if (current_position < 8 * current->payload_size) {
-#else
-    current_position = put_bits_count(rw) - start_position;
-    if (byte_alignment(rw) || more_data) {
-#endif
-        CHECK(FUNC(payload_extension)(ctx, rw, &current->extension_data,
-                                      current->payload_size, current_position));
-        fixed(1, bit_equal_to_one, 1);
-        while (byte_alignment(rw))
-            fixed(1, bit_equal_to_zero, 0);
-    }
-
-#ifdef WRITE
-    current->payload_size = (put_bits_count(rw) - start_position) >> 3;
-#endif
-
-    return 0;
-}
-
 static int FUNC(sei)(CodedBitstreamContext *ctx, RWContext *rw,
                      H265RawSEI *current, int prefix)
 {
-    int err, k;
+    int err;
 
     if (prefix)
         HEADER("Prefix Supplemental Enhancement Information");
@@ -2146,72 +2006,7 @@ static int FUNC(sei)(CodedBitstreamContext *ctx, RWContext *rw,
                                 prefix ? HEVC_NAL_SEI_PREFIX
                                        : HEVC_NAL_SEI_SUFFIX));
 
-#ifdef READ
-    for (k = 0; k < H265_MAX_SEI_PAYLOADS; k++) {
-        uint32_t payload_type = 0;
-        uint32_t payload_size = 0;
-        uint32_t tmp;
-
-        while (show_bits(rw, 8) == 0xff) {
-            fixed(8, ff_byte, 0xff);
-            payload_type += 255;
-        }
-        xu(8, last_payload_type_byte, tmp, 0, 254, 0);
-        payload_type += tmp;
-
-        while (show_bits(rw, 8) == 0xff) {
-            fixed(8, ff_byte, 0xff);
-            payload_size += 255;
-        }
-        xu(8, last_payload_size_byte, tmp, 0, 254, 0);
-        payload_size += tmp;
-
-        current->payload[k].payload_type = payload_type;
-        current->payload[k].payload_size = payload_size;
-
-        current->payload_count++;
-        CHECK(FUNC(sei_payload)(ctx, rw, &current->payload[k], prefix));
-
-        if (!cbs_h2645_read_more_rbsp_data(rw))
-            break;
-    }
-    if (k >= H265_MAX_SEI_PAYLOADS) {
-        av_log(ctx->log_ctx, AV_LOG_ERROR, "Too many payloads in "
-               "SEI message: found %d.\n", k);
-        return AVERROR_INVALIDDATA;
-    }
-#else
-    for (k = 0; k < current->payload_count; k++) {
-        PutBitContext start_state;
-        uint32_t tmp;
-        int need_size, i;
-
-        // Somewhat clumsy: we write the payload twice when
-        // we don't know the size in advance.  This will mess
-        // with trace output, but is otherwise harmless.
-        start_state = *rw;
-        need_size = !current->payload[k].payload_size;
-        for (i = 0; i < 1 + need_size; i++) {
-            *rw = start_state;
-
-            tmp = current->payload[k].payload_type;
-            while (tmp >= 255) {
-                fixed(8, ff_byte, 0xff);
-                tmp -= 255;
-            }
-            xu(8, last_payload_type_byte, tmp, 0, 254, 0);
-
-            tmp = current->payload[k].payload_size;
-            while (tmp >= 255) {
-                fixed(8, ff_byte, 0xff);
-                tmp -= 255;
-            }
-            xu(8, last_payload_size_byte, tmp, 0, 254, 0);
-
-            CHECK(FUNC(sei_payload)(ctx, rw, &current->payload[k], prefix));
-        }
-    }
-#endif
+    CHECK(FUNC_SEI(message_list)(ctx, rw, &current->message_list, prefix));
 
     CHECK(FUNC(rbsp_trailing_bits)(ctx, rw));
 
diff --git a/libavcodec/cbs_sei.c b/libavcodec/cbs_sei.c
new file mode 100644
index 0000000000..323997b600
--- /dev/null
+++ b/libavcodec/cbs_sei.c
@@ -0,0 +1,369 @@
+/*
+ * 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 "cbs.h"
+#include "cbs_internal.h"
+#include "cbs_h264.h"
+#include "cbs_h265.h"
+#include "cbs_sei.h"
+
+static void cbs_free_user_data_registered(void *opaque, uint8_t *data)
+{
+    SEIRawUserDataRegistered *udr = (SEIRawUserDataRegistered*)data;
+    av_buffer_unref(&udr->data_ref);
+    av_free(udr);
+}
+
+static void cbs_free_user_data_unregistered(void *opaque, uint8_t *data)
+{
+    SEIRawUserDataUnregistered *udu = (SEIRawUserDataUnregistered*)data;
+    av_buffer_unref(&udu->data_ref);
+    av_free(udu);
+}
+
+int ff_cbs_sei_alloc_message_payload(SEIRawMessage *message,
+                                     const SEIMessageTypeDescriptor *desc)
+{
+    void (*free_func)(void*, uint8_t*);
+
+    av_assert0(message->payload     == NULL &&
+               message->payload_ref == NULL);
+    message->payload_type = desc->type;
+
+    if (desc->type == SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35)
+        free_func = &cbs_free_user_data_registered;
+    else if (desc->type == SEI_TYPE_USER_DATA_UNREGISTERED)
+        free_func = &cbs_free_user_data_unregistered;
+    else
+        free_func = NULL;
+
+    if (free_func) {
+        message->payload = av_mallocz(desc->size);
+        if (!message->payload)
+            return AVERROR(ENOMEM);
+        message->payload_ref =
+            av_buffer_create(message->payload, desc->size,
+                             free_func, NULL, 0);
+    } else {
+        message->payload_ref = av_buffer_alloc(desc->size);
+    }
+    if (!message->payload_ref)
+        return AVERROR(ENOMEM);
+    message->payload = message->payload_ref->data;
+
+    return 0;
+}
+
+int ff_cbs_sei_list_add(SEIRawMessageList *list)
+{
+    void *ptr;
+    int old_count = list->nb_messages_allocated;
+
+    av_assert0(list->nb_messages <= old_count);
+    if (list->nb_messages + 1 > old_count) {
+        int new_count = 2 * old_count + 1;
+
+        ptr = av_realloc_array(list->messages,
+                               new_count, sizeof(*list->messages));
+        if (!ptr)
+            return AVERROR(ENOMEM);
+
+        list->messages = ptr;
+        list->nb_messages_allocated = new_count;
+
+        // Zero the newly-added entries.
+        memset(list->messages + old_count, 0,
+               (new_count - old_count) * sizeof(*list->messages));
+    }
+    ++list->nb_messages;
+    return 0;
+}
+
+void ff_cbs_sei_free_message_list(SEIRawMessageList *list)
+{
+    for (int i = 0; i < list->nb_messages; i++) {
+        SEIRawMessage *message = &list->messages[i];
+        av_buffer_unref(&message->payload_ref);
+        av_buffer_unref(&message->extension_data_ref);
+    }
+    av_free(list->messages);
+}
+
+static int cbs_sei_get_unit(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *au,
+                            int prefix,
+                            CodedBitstreamUnit **sei_unit)
+{
+    CodedBitstreamUnit *unit;
+    int sei_type, highest_vcl_type, err, i, position;
+
+    switch (ctx->codec->codec_id) {
+    case AV_CODEC_ID_H264:
+        // (We can ignore auxiliary slices because we only have prefix
+        // SEI in H.264 and an auxiliary picture must always follow a
+        // primary picture.)
+        highest_vcl_type = H264_NAL_IDR_SLICE;
+        if (prefix)
+            sei_type = H264_NAL_SEI;
+        else
+            return AVERROR(EINVAL);
+        break;
+    case AV_CODEC_ID_H265:
+        highest_vcl_type = HEVC_NAL_RSV_VCL31;
+        if (prefix)
+            sei_type = HEVC_NAL_SEI_PREFIX;
+        else
+            sei_type = HEVC_NAL_SEI_SUFFIX;
+        break;
+    default:
+        return AVERROR(EINVAL);
+    }
+
+    // Find an existing SEI NAL unit of the right type.
+    unit = NULL;
+    for (i = 0; i < au->nb_units; i++) {
+        if (au->units[i].type == sei_type) {
+            unit = &au->units[i];
+            break;
+        }
+    }
+
+    if (unit) {
+        *sei_unit = unit;
+        return 0;
+    }
+
+    // Need to add a new SEI NAL unit ...
+    if (prefix) {
+        // ... before the first VCL NAL unit.
+        for (i = 0; i < au->nb_units; i++) {
+            if (au->units[i].type < highest_vcl_type)
+                break;
+        }
+        position = i;
+    } else {
+        // ... after the last VCL NAL unit.
+        for (i = au->nb_units; i >= 0; i--) {
+            if (au->units[i].type < highest_vcl_type)
+                break;
+        }
+        if (i < 0) {
+            // No VCL units; just put it at the end.
+            position = -1;
+        } else {
+            position = i + 1;
+        }
+    }
+
+    err = ff_cbs_insert_unit_content(au, position, sei_type,
+                                     NULL, NULL);
+    if (err < 0)
+        return err;
+    unit = &au->units[position];
+    unit->type = sei_type;
+
+    err = ff_cbs_alloc_unit_content2(ctx, unit);
+    if (err < 0)
+        return err;
+
+    switch (ctx->codec->codec_id) {
+    case AV_CODEC_ID_H264:
+        {
+            H264RawSEI sei = {
+                .nal_unit_header = {
+                    .nal_ref_idc   = 0,
+                    .nal_unit_type = sei_type,
+                },
+            };
+            memcpy(unit->content, &sei, sizeof(sei));
+        }
+        break;
+    case AV_CODEC_ID_H265:
+        {
+            H265RawSEI sei = {
+                .nal_unit_header = {
+                    .nal_unit_type         = sei_type,
+                    .nuh_layer_id          = 0,
+                    .nuh_temporal_id_plus1 = 1,
+                },
+            };
+            memcpy(unit->content, &sei, sizeof(sei));
+        }
+        break;
+    default:
+        av_assert0(0);
+    }
+
+    *sei_unit = unit;
+    return 0;
+}
+
+static int cbs_sei_get_message_list(CodedBitstreamContext *ctx,
+                                    CodedBitstreamUnit *unit,
+                                    SEIRawMessageList **list)
+{
+    switch (ctx->codec->codec_id) {
+    case AV_CODEC_ID_H264:
+        {
+            H264RawSEI *sei = unit->content;
+            if (unit->type != H264_NAL_SEI)
+                return AVERROR(EINVAL);
+            *list = &sei->message_list;
+        }
+        break;
+    case AV_CODEC_ID_H265:
+        {
+            H265RawSEI *sei = unit->content;
+            if (unit->type != HEVC_NAL_SEI_PREFIX &&
+                unit->type != HEVC_NAL_SEI_SUFFIX)
+                return AVERROR(EINVAL);
+            *list = &sei->message_list;
+        }
+        break;
+    default:
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+int ff_cbs_sei_add_message(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *au,
+                           int prefix,
+                           uint32_t     payload_type,
+                           void        *payload_data,
+                           AVBufferRef *payload_buf)
+{
+    const SEIMessageTypeDescriptor *desc;
+    CodedBitstreamUnit *unit;
+    SEIRawMessageList *list;
+    SEIRawMessage *message;
+    AVBufferRef *payload_ref;
+    int err;
+
+    desc = ff_cbs_sei_find_type(ctx, payload_type);
+    if (!desc)
+        return AVERROR(EINVAL);
+
+    if (payload_buf) {
+        payload_ref = av_buffer_ref(payload_buf);
+        if (!payload_ref)
+            return AVERROR(ENOMEM);
+    } else {
+        payload_ref = NULL;
+    }
+
+    // Find an existing SEI unit or make a new one to add to.
+    err = cbs_sei_get_unit(ctx, au, prefix, &unit);
+    if (err < 0)
+        return err;
+
+    // Find the message list inside the codec-dependent unit.
+    err = cbs_sei_get_message_list(ctx, unit, &list);
+    if (err < 0)
+        return err;
+
+    // Add a new message to the message list.
+    err = ff_cbs_sei_list_add(list);
+    if (err < 0)
+        return err;
+
+    message = &list->messages[list->nb_messages - 1];
+
+    message->payload_type = payload_type;
+    message->payload      = payload_data;
+    message->payload_ref  = payload_ref;
+
+    return 0;
+}
+
+int ff_cbs_sei_find_message(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *au,
+                            uint32_t payload_type,
+                            SEIRawMessage **iter)
+{
+    int err, i, j, found;
+
+    found = 0;
+    for (i = 0; i < au->nb_units; i++) {
+        CodedBitstreamUnit *unit = &au->units[i];
+        SEIRawMessageList *list;
+
+        err = cbs_sei_get_message_list(ctx, unit, &list);
+        if (err < 0)
+            continue;
+
+        for (j = 0; j < list->nb_messages; j++) {
+            SEIRawMessage *message = &list->messages[j];
+
+            if (message->payload_type == payload_type) {
+                if (!*iter || found) {
+                    *iter = message;
+                    return 0;
+                }
+                if (message == *iter)
+                    found = 1;
+            }
+        }
+    }
+
+    return AVERROR(ENOENT);
+}
+
+static void cbs_sei_delete_message(SEIRawMessageList *list,
+                                   int position)
+{
+    SEIRawMessage *message;
+
+    av_assert0(0 <= position && position < list->nb_messages);
+
+    message = &list->messages[position];
+    av_buffer_unref(&message->payload_ref);
+    av_buffer_unref(&message->extension_data_ref);
+
+    --list->nb_messages;
+
+    if (list->nb_messages > 0) {
+        memmove(list->messages + position,
+                list->messages + position + 1,
+                (list->nb_messages - position) * sizeof(*list->messages));
+    }
+}
+
+void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx,
+                                    CodedBitstreamFragment *au,
+                                    uint32_t payload_type)
+{
+    int err, i, j;
+
+    for (i = 0; i < au->nb_units; i++) {
+        CodedBitstreamUnit *unit = &au->units[i];
+        SEIRawMessageList *list;
+
+        err = cbs_sei_get_message_list(ctx, unit, &list);
+        if (err < 0)
+            continue;
+
+        for (j = 0; j < list->nb_messages;) {
+            if (list->messages[j].payload_type == payload_type)
+                cbs_sei_delete_message(list, j);
+            else
+                ++j;
+        }
+    }
+}
diff --git a/libavcodec/cbs_sei.h b/libavcodec/cbs_sei.h
index 95beabf4d7..5ce4ad3ccd 100644
--- a/libavcodec/cbs_sei.h
+++ b/libavcodec/cbs_sei.h
@@ -21,8 +21,132 @@
 
 #include <stddef.h>
 #include <stdint.h>
+
 #include "libavutil/buffer.h"
 
+#include "cbs.h"
+
+// SEI payload types form a common namespace between the H.264, H.265
+// and H.266 standards.  A given payload type always has the same
+// meaning, but some names have different payload types in different
+// standards (e.g. scalable-nesting is 30 in H.264 but 133 in H.265).
+// The content of the payload data depends on the standard, though
+// many generic parts have the same interpretation everywhere (such as
+// mastering-display-colour-volume and user-data-unregistered).
+enum {
+    SEI_TYPE_BUFFERING_PERIOD                            = 0,
+    SEI_TYPE_PIC_TIMING                                  = 1,
+    SEI_TYPE_PAN_SCAN_RECT                               = 2,
+    SEI_TYPE_FILLER_PAYLOAD                              = 3,
+    SEI_TYPE_USER_DATA_REGISTERED_ITU_T_T35              = 4,
+    SEI_TYPE_USER_DATA_UNREGISTERED                      = 5,
+    SEI_TYPE_RECOVERY_POINT                              = 6,
+    SEI_TYPE_DEC_REF_PIC_MARKING_REPETITION              = 7,
+    SEI_TYPE_SPARE_PIC                                   = 8,
+    SEI_TYPE_SCENE_INFO                                  = 9,
+    SEI_TYPE_SUB_SEQ_INFO                                = 10,
+    SEI_TYPE_SUB_SEQ_LAYER_CHARACTERISTICS               = 11,
+    SEI_TYPE_SUB_SEQ_CHARACTERISTICS                     = 12,
+    SEI_TYPE_FULL_FRAME_FREEZE                           = 13,
+    SEI_TYPE_FULL_FRAME_FREEZE_RELEASE                   = 14,
+    SEI_TYPE_FULL_FRAME_SNAPSHOT                         = 15,
+    SEI_TYPE_PROGRESSIVE_REFINEMENT_SEGMENT_START        = 16,
+    SEI_TYPE_PROGRESSIVE_REFINEMENT_SEGMENT_END          = 17,
+    SEI_TYPE_MOTION_CONSTRAINED_SLICE_GROUP_SET          = 18,
+    SEI_TYPE_FILM_GRAIN_CHARACTERISTICS                  = 19,
+    SEI_TYPE_DEBLOCKING_FILTER_DISPLAY_PREFERENCE        = 20,
+    SEI_TYPE_STEREO_VIDEO_INFO                           = 21,
+    SEI_TYPE_POST_FILTER_HINT                            = 22,
+    SEI_TYPE_TONE_MAPPING_INFO                           = 23,
+    SEI_TYPE_SCALABILITY_INFO                            = 24,
+    SEI_TYPE_SUB_PIC_SCALABLE_LAYER                      = 25,
+    SEI_TYPE_NON_REQUIRED_LAYER_REP                      = 26,
+    SEI_TYPE_PRIORITY_LAYER_INFO                         = 27,
+    SEI_TYPE_LAYERS_NOT_PRESENT_4                        = 28,
+    SEI_TYPE_LAYER_DEPENDENCY_CHANGE                     = 29,
+    SEI_TYPE_SCALABLE_NESTING_4                          = 30,
+    SEI_TYPE_BASE_LAYER_TEMPORAL_HRD                     = 31,
+    SEI_TYPE_QUALITY_LAYER_INTEGRITY_CHECK               = 32,
+    SEI_TYPE_REDUNDANT_PIC_PROPERTY                      = 33,
+    SEI_TYPE_TL0_DEP_REP_INDEX                           = 34,
+    SEI_TYPE_TL_SWITCHING_POINT                          = 35,
+    SEI_TYPE_PARALLEL_DECODING_INFO                      = 36,
+    SEI_TYPE_MVC_SCALABLE_NESTING                        = 37,
+    SEI_TYPE_VIEW_SCALABILITY_INFO                       = 38,
+    SEI_TYPE_MULTIVIEW_SCENE_INFO_4                      = 39,
+    SEI_TYPE_MULTIVIEW_ACQUISITION_INFO_4                = 40,
+    SEI_TYPE_NON_REQUIRED_VIEW_COMPONENT                 = 41,
+    SEI_TYPE_VIEW_DEPENDENCY_CHANGE                      = 42,
+    SEI_TYPE_OPERATION_POINTS_NOT_PRESENT                = 43,
+    SEI_TYPE_BASE_VIEW_TEMPORAL_HRD                      = 44,
+    SEI_TYPE_FRAME_PACKING_ARRANGEMENT                   = 45,
+    SEI_TYPE_MULTIVIEW_VIEW_POSITION_4                   = 46,
+    SEI_TYPE_DISPLAY_ORIENTATION                         = 47,
+    SEI_TYPE_MVCD_SCALABLE_NESTING                       = 48,
+    SEI_TYPE_MVCD_VIEW_SCALABILITY_INFO                  = 49,
+    SEI_TYPE_DEPTH_REPRESENTATION_INFO_4                 = 50,
+    SEI_TYPE_THREE_DIMENSIONAL_REFERENCE_DISPLAYS_INFO_4 = 51,
+    SEI_TYPE_DEPTH_TIMING                                = 52,
+    SEI_TYPE_DEPTH_SAMPLING_INFO                         = 53,
+    SEI_TYPE_CONSTRAINED_DEPTH_PARAMETER_SET_IDENTIFIER  = 54,
+    SEI_TYPE_GREEN_METADATA                              = 56,
+    SEI_TYPE_STRUCTURE_OF_PICTURES_INFO                  = 128,
+    SEI_TYPE_ACTIVE_PARAMETER_SETS                       = 129,
+    SEI_TYPE_DECODING_UNIT_INFO                          = 130,
+    SEI_TYPE_TEMPORAL_SUB_LAYER_ZERO_IDX                 = 131,
+    SEI_TYPE_DECODED_PICTURE_HASH                        = 132,
+    SEI_TYPE_SCALABLE_NESTING_5                          = 133,
+    SEI_TYPE_REGION_REFRESH_INFO                         = 134,
+    SEI_TYPE_NO_DISPLAY                                  = 135,
+    SEI_TYPE_TIME_CODE                                   = 136,
+    SEI_TYPE_MASTERING_DISPLAY_COLOUR_VOLUME             = 137,
+    SEI_TYPE_SEGMENTED_RECT_FRAME_PACKING_ARRANGEMENT    = 138,
+    SEI_TYPE_TEMPORAL_MOTION_CONSTRAINED_TILE_SETS       = 139,
+    SEI_TYPE_CHROMA_RESAMPLING_FILTER_HINT               = 140,
+    SEI_TYPE_KNEE_FUNCTION_INFO                          = 141,
+    SEI_TYPE_COLOUR_REMAPPING_INFO                       = 142,
+    SEI_TYPE_DEINTERLACED_FIELD_IDENTIFICATION           = 143,
+    SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO                    = 144,
+    SEI_TYPE_DEPENDENT_RAP_INDICATION                    = 145,
+    SEI_TYPE_CODED_REGION_COMPLETION                     = 146,
+    SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS        = 147,
+    SEI_TYPE_AMBIENT_VIEWING_ENVIRONMENT                 = 148,
+    SEI_TYPE_CONTENT_COLOUR_VOLUME                       = 149,
+    SEI_TYPE_EQUIRECTANGULAR_PROJECTION                  = 150,
+    SEI_TYPE_CUBEMAP_PROJECTION                          = 151,
+    SEI_TYPE_FISHEYE_VIDEO_INFO                          = 152,
+    SEI_TYPE_SPHERE_ROTATION                             = 154,
+    SEI_TYPE_REGIONWISE_PACKING                          = 155,
+    SEI_TYPE_OMNI_VIEWPORT                               = 156,
+    SEI_TYPE_REGIONAL_NESTING                            = 157,
+    SEI_TYPE_MCTS_EXTRACTION_INFO_SETS                   = 158,
+    SEI_TYPE_MCTS_EXTRACTION_INFO_NESTING                = 159,
+    SEI_TYPE_LAYERS_NOT_PRESENT_5                        = 160,
+    SEI_TYPE_INTER_LAYER_CONSTRAINED_TILE_SETS           = 161,
+    SEI_TYPE_BSP_NESTING                                 = 162,
+    SEI_TYPE_BSP_INITIAL_ARRIVAL_TIME                    = 163,
+    SEI_TYPE_SUB_BITSTREAM_PROPERTY                      = 164,
+    SEI_TYPE_ALPHA_CHANNEL_INFO                          = 165,
+    SEI_TYPE_OVERLAY_INFO                                = 166,
+    SEI_TYPE_TEMPORAL_MV_PREDICTION_CONSTRAINTS          = 167,
+    SEI_TYPE_FRAME_FIELD_INFO                            = 168,
+    SEI_TYPE_THREE_DIMENSIONAL_REFERENCE_DISPLAYS_INFO   = 176,
+    SEI_TYPE_DEPTH_REPRESENTATION_INFO_5                 = 177,
+    SEI_TYPE_MULTIVIEW_SCENE_INFO_5                      = 178,
+    SEI_TYPE_MULTIVIEW_ACQUISITION_INFO_5                = 179,
+    SEI_TYPE_MULTIVIEW_VIEW_POSITION_5                   = 180,
+    SEI_TYPE_ALTERNATIVE_DEPTH_INFO                      = 181,
+    SEI_TYPE_SEI_MANIFEST                                = 200,
+    SEI_TYPE_SEI_PREFIX_INDICATION                       = 201,
+    SEI_TYPE_ANNOTATED_REGIONS                           = 202,
+    SEI_TYPE_SUBPIC_LEVEL_INFO                           = 203,
+    SEI_TYPE_SAMPLE_ASPECT_RATIO_INFO                    = 204,
+};
+
+
+typedef struct SEIRawFillerPayload {
+    uint32_t payload_size;
+} SEIRawFillerPayload;
 
 typedef struct SEIRawUserDataRegistered {
     uint8_t      itu_t_t35_country_code;
@@ -57,4 +181,135 @@ typedef struct SEIRawAlternativeTransferCharacteristics {
     uint8_t preferred_transfer_characteristics;
 } SEIRawAlternativeTransferCharacteristics;
 
+typedef struct SEIRawMessage {
+    uint32_t     payload_type;
+    uint32_t     payload_size;
+    void        *payload;
+    AVBufferRef *payload_ref;
+    uint8_t     *extension_data;
+    AVBufferRef *extension_data_ref;
+    size_t       extension_bit_length;
+} SEIRawMessage;
+
+typedef struct SEIRawMessageList {
+    SEIRawMessage *messages;
+    int         nb_messages;
+    int         nb_messages_allocated;
+} SEIRawMessageList;
+
+
+typedef struct SEIMessageState {
+    // The type of the payload being written.
+    uint32_t payload_type;
+    // When reading, contains the size of the payload to allow finding the
+    // end of variable-length fields (such as user_data_payload_byte[]).
+    // (When writing, the size will be derived from the total number of
+    // bytes actually written.)
+    uint32_t payload_size;
+    // When writing, indicates that payload extension data is present so
+    // all extended fields must be written.  May be updated by the writer
+    // to indicate that extended fields have been written, so the extension
+    // end bits must be written too.
+    uint8_t  extension_present;
+} SEIMessageState;
+
+struct GetBitContext;
+struct PutBitContext;
+
+typedef int (*SEIMessageReadFunction)(CodedBitstreamContext *ctx,
+                                      struct GetBitContext *rw,
+                                      void *current,
+                                      SEIMessageState *sei);
+
+typedef int (*SEIMessageWriteFunction)(CodedBitstreamContext *ctx,
+                                       struct PutBitContext *rw,
+                                       void *current,
+                                       SEIMessageState *sei);
+
+typedef struct SEIMessageTypeDescriptor {
+    // Payload type for the message.  (-1 in this field ends a list.)
+    int     type;
+    // Valid in a prefix SEI NAL unit (always for H.264).
+    uint8_t prefix;
+    // Valid in a suffix SEI NAL unit (never for H.264).
+    uint8_t suffix;
+    // Size of the decomposed structure.
+    size_t  size;
+    // Read bitstream into SEI message.
+    SEIMessageReadFunction  read;
+    // Write bitstream from SEI message.
+    SEIMessageWriteFunction write;
+} SEIMessageTypeDescriptor;
+
+// Macro for the read/write pair.  The clumsy cast is needed because the
+// current pointer is typed in all of the read/write functions but has to
+// be void here to fit all cases.
+#define SEI_MESSAGE_RW(codec, name) \
+    .read  = (SEIMessageReadFunction) cbs_ ## codec ## _read_  ## name, \
+    .write = (SEIMessageWriteFunction)cbs_ ## codec ## _write_ ## name
+
+// End-of-list sentinel element.
+#define SEI_MESSAGE_TYPE_END { .type = -1 }
+
+
+/**
+ * Find the type descriptor for the given payload type.
+ *
+ * Returns NULL if the payload type is not known.
+ */
+const SEIMessageTypeDescriptor *ff_cbs_sei_find_type(CodedBitstreamContext *ctx,
+                                                     int payload_type);
+
+/**
+ * Allocate a new payload for the given SEI message.
+ */
+int ff_cbs_sei_alloc_message_payload(SEIRawMessage *message,
+                                     const SEIMessageTypeDescriptor *desc);
+
+/**
+ * Allocate a new empty SEI message in a message list.
+ *
+ * The new message is in place nb_messages - 1.
+ */
+int ff_cbs_sei_list_add(SEIRawMessageList *list);
+
+/**
+ * Free all SEI messages in a message list.
+ */
+void ff_cbs_sei_free_message_list(SEIRawMessageList *list);
+
+/**
+ * Add an SEI message to an access unit.
+ *
+ * Will add to an existing SEI NAL unit, or create a new one for the
+ * message if there is no suitable existing one.
+ *
+ * Takes a new reference to payload_buf, if set.  If payload_buf is
+ * NULL then the new message will not be reference counted.
+ */
+int ff_cbs_sei_add_message(CodedBitstreamContext *ctx,
+                           CodedBitstreamFragment *au,
+                           int prefix,
+                           uint32_t     payload_type,
+                           void        *payload_data,
+                           AVBufferRef *payload_buf);
+
+/**
+ * Iterate over messages with the given payload type in an access unit.
+ *
+ * Set message to NULL in the first call.  Returns 0 while more messages
+ * are available, AVERROR(ENOENT) when all messages have been found.
+ */
+int ff_cbs_sei_find_message(CodedBitstreamContext *ctx,
+                            CodedBitstreamFragment *au,
+                            uint32_t payload_type,
+                            SEIRawMessage **message);
+
+/**
+ * Delete all messages with the given payload type from an access unit.
+ */
+void ff_cbs_sei_delete_message_type(CodedBitstreamContext *ctx,
+                                    CodedBitstreamFragment *au,
+                                    uint32_t payload_type);
+
 #endif /* AVCODEC_CBS_SEI_H */
diff --git a/libavcodec/cbs_sei_syntax_template.c b/libavcodec/cbs_sei_syntax_template.c
index cc900830ae..1a0516acce 100644
--- a/libavcodec/cbs_sei_syntax_template.c
+++ b/libavcodec/cbs_sei_syntax_template.c
@@ -16,9 +16,27 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-static int FUNC(sei_user_data_registered)
+static int FUNC(filler_payload)
     (CodedBitstreamContext *ctx, RWContext *rw,
-     SEIRawUserDataRegistered *current, uint32_t *payload_size)
+     SEIRawFillerPayload *current, SEIMessageState *state)
+{
+    int err, i;
+
+    HEADER("Filler Payload");
+
+#ifdef READ
+    current->payload_size = state->payload_size;
+#endif
+
+    for (i = 0; i < current->payload_size; i++)
+        fixed(8, ff_byte, 0xff);
+
+    return 0;
+}
+
+static int FUNC(user_data_registered)
+    (CodedBitstreamContext *ctx, RWContext *rw,
+     SEIRawUserDataRegistered *current, SEIMessageState *state)
 {
     int err, i, j;
 
@@ -33,14 +51,12 @@ static int FUNC(sei_user_data_registered)
     }
 
 #ifdef READ
-    if (*payload_size < i) {
+    if (state->payload_size < i) {
         av_log(ctx->log_ctx, AV_LOG_ERROR,
                "Invalid SEI user data registered payload.\n");
         return AVERROR_INVALIDDATA;
     }
-    current->data_length = *payload_size - i;
-#else
-    *payload_size = i + current->data_length;
+    current->data_length = state->payload_size - i;
 #endif
 
     allocate(current->data, current->data_length);
@@ -50,23 +66,21 @@ static int FUNC(sei_user_data_registered)
     return 0;
 }
 
-static int FUNC(sei_user_data_unregistered)
+static int FUNC(user_data_unregistered)
     (CodedBitstreamContext *ctx, RWContext *rw,
-     SEIRawUserDataUnregistered *current, uint32_t *payload_size)
+     SEIRawUserDataUnregistered *current, SEIMessageState *state)
 {
     int err, i;
 
     HEADER("User Data Unregistered");
 
 #ifdef READ
-    if (*payload_size < 16) {
+    if (state->payload_size < 16) {
         av_log(ctx->log_ctx, AV_LOG_ERROR,
                "Invalid SEI user data unregistered payload.\n");
         return AVERROR_INVALIDDATA;
     }
-    current->data_length = *payload_size - 16;
-#else
-    *payload_size = 16 + current->data_length;
+    current->data_length = state->payload_size - 16;
 #endif
 
     for (i = 0; i < 16; i++)
@@ -80,9 +94,9 @@ static int FUNC(sei_user_data_unregistered)
     return 0;
 }
 
-static int FUNC(sei_mastering_display_colour_volume)
+static int FUNC(mastering_display_colour_volume)
     (CodedBitstreamContext *ctx, RWContext *rw,
-     SEIRawMasteringDisplayColourVolume *current)
+     SEIRawMasteringDisplayColourVolume *current, SEIMessageState *state)
 {
     int err, c;
 
@@ -104,13 +118,13 @@ static int FUNC(sei_mastering_display_colour_volume)
     return 0;
 }
 
-static int FUNC(sei_content_light_level)
+static int FUNC(content_light_level_info)
     (CodedBitstreamContext *ctx, RWContext *rw,
-     SEIRawContentLightLevelInfo *current)
+     SEIRawContentLightLevelInfo *current, SEIMessageState *state)
 {
     int err;
 
-    HEADER("Content Light Level");
+    HEADER("Content Light Level Information");
 
     ub(16, max_content_light_level);
     ub(16, max_pic_average_light_level);
@@ -118,9 +132,10 @@ static int FUNC(sei_content_light_level)
     return 0;
 }
 
-static int FUNC(sei_alternative_transfer_characteristics)
+static int FUNC(alternative_transfer_characteristics)
     (CodedBitstreamContext *ctx, RWContext *rw,
-     SEIRawAlternativeTransferCharacteristics *current)
+     SEIRawAlternativeTransferCharacteristics *current,
+     SEIMessageState *state)
 {
     int err;
 
@@ -130,3 +145,165 @@ static int FUNC(sei_alternative_transfer_characteristics)
 
     return 0;
 }
+
+static int FUNC(message)(CodedBitstreamContext *ctx, RWContext *rw,
+                         SEIRawMessage *current)
+{
+    const SEIMessageTypeDescriptor *desc;
+    int err, i;
+
+    desc = ff_cbs_sei_find_type(ctx, current->payload_type);
+    if (desc) {
+        SEIMessageState state = {
+            .payload_type      = current->payload_type,
+            .payload_size      = current->payload_size,
+            .extension_present = current->extension_bit_length > 0,
+        };
+        int start_position, current_position, bits_written;
+
+#ifdef READ
+        CHECK(ff_cbs_sei_alloc_message_payload(current, desc));
+#endif
+
+        start_position = bit_position(rw);
+
+        CHECK(desc->READWRITE(ctx, rw, current->payload, &state));
+
+        current_position = bit_position(rw);
+        bits_written = current_position - start_position;
+
+        if (byte_alignment(rw) || state.extension_present ||
+            bits_written < 8 * current->payload_size) {
+            size_t bits_left;
+
+#ifdef READ
+            GetBitContext tmp = *rw;
+            int trailing_bits, trailing_zero_bits;
+
+            bits_left = 8 * current->payload_size - bits_written;
+            if (bits_left > 8)
+                skip_bits_long(&tmp, bits_left - 8);
+            trailing_bits = get_bits(&tmp, FFMIN(bits_left, 8));
+            if (trailing_bits == 0) {
+                // The trailing bits must contain a bit_equal_to_one, so
+                // they can't all be zero.
+                return AVERROR_INVALIDDATA;
+            }
+            trailing_zero_bits = ff_ctz(trailing_bits);
+            current->extension_bit_length =
+                bits_left - 1 - trailing_zero_bits;
+#endif
+
+            if (current->extension_bit_length > 0) {
+                allocate(current->extension_data,
+                         (current->extension_bit_length + 7) / 8);
+
+                bits_left = current->extension_bit_length;
+                for (i = 0; bits_left > 0; i++) {
+                    int length = FFMIN(bits_left, 8);
+                    xu(length, reserved_payload_extension_data,
+                       current->extension_data[i],
+                       0, MAX_UINT_BITS(length), 0);
+                    bits_left -= length;
+                }
+            }
+
+            fixed(1, bit_equal_to_one, 1);
+            while (byte_alignment(rw))
+                fixed(1, bit_equal_to_zero, 0);
+        }
+
+#ifdef WRITE
+        current->payload_size = (put_bits_count(rw) - start_position) / 8;
+#endif
+    } else {
+        uint8_t *data;
+
+        allocate(current->payload, current->payload_size);
+        data = current->payload;
+
+        for (i = 0; i < current->payload_size; i++)
+            xu(8, payload_byte[i], data[i], 0, 255, 1, i);
+    }
+
+    return 0;
+}
+
+static int FUNC(message_list)(CodedBitstreamContext *ctx, RWContext *rw,
+                              SEIRawMessageList *current, int prefix)
+{
+    SEIRawMessage *message;
+    int err, k;
+
+#ifdef READ
+    for (k = 0;; k++) {
+        uint32_t payload_type = 0;
+        uint32_t payload_size = 0;
+        uint32_t tmp;
+
+        while (show_bits(rw, 8) == 0xff) {
+            fixed(8, ff_byte, 0xff);
+            payload_type += 255;
+        }
+        xu(8, last_payload_type_byte, tmp, 0, 254, 0);
+        payload_type += tmp;
+
+        while (show_bits(rw, 8) == 0xff) {
+            fixed(8, ff_byte, 0xff);
+            payload_size += 255;
+        }
+        xu(8, last_payload_size_byte, tmp, 0, 254, 0);
+        payload_size += tmp;
+
+        CHECK(ff_cbs_sei_list_add(current));
+        message = &current->messages[k];
+
+        message->payload_type = payload_type;
+        message->payload_size = payload_size;
+
+        CHECK(FUNC(message)(ctx, rw, message));
+
+        if (!cbs_h2645_read_more_rbsp_data(rw))
+            break;
+    }
+#else
+    for (k = 0; k < current->nb_messages; k++) {
+        PutBitContext start_state;
+        uint32_t tmp;
+        int trace, i;
+
+        message = &current->messages[k];
+
+        // We write the payload twice in order to find the size.  Trace
+        // output is switched off for the first write.
+        trace = ctx->trace_enable;
+        ctx->trace_enable = 0;
+
+        start_state = *rw;
+        for (i = 0; i < 2; i++) {
+            *rw = start_state;
+
+            tmp = message->payload_type;
+            while (tmp >= 255) {
+                fixed(8, ff_byte, 0xff);
+                tmp -= 255;
+            }
+            xu(8, last_payload_type_byte, tmp, 0, 254, 0);
+
+            tmp = message->payload_size;
+            while (tmp >= 255) {
+                fixed(8, ff_byte, 0xff);
+                tmp -= 255;
+            }
+            xu(8, last_payload_size_byte, tmp, 0, 254, 0);
+
+            err = FUNC(message)(ctx, rw, message);
+            ctx->trace_enable = trace;
+            if (err < 0)
+                return err;
+        }
+    }
+#endif
+
+    return 0;
+}
diff --git a/libavcodec/h264_metadata_bsf.c b/libavcodec/h264_metadata_bsf.c
index f39e649ac6..4ab97aee3a 100644
--- a/libavcodec/h264_metadata_bsf.c
+++ b/libavcodec/h264_metadata_bsf.c
@@ -78,13 +78,14 @@ typedef struct H264MetadataContext {
     int crop_bottom;
 
     const char *sei_user_data;
-    H264RawSEIPayload sei_user_data_payload;
+    SEIRawUserDataUnregistered sei_user_data_payload;
 
     int delete_filler;
 
     int display_orientation;
     double rotate;
     int flip;
+    H264RawSEIDisplayOrientation display_orientation_payload;
 
     int level;
 } H264MetadataContext;
@@ -414,7 +415,9 @@ static int h264_metadata_filter(AVBSFContext *bsf, AVPacket *pkt)
     // Only insert the SEI in access units containing SPSs, and also
     // unconditionally in the first access unit we ever see.
     if (ctx->sei_user_data && (has_sps || !ctx->done_first_au)) {
-        err = ff_cbs_h264_add_sei_message(au, &ctx->sei_user_data_payload);
+        err = ff_cbs_sei_add_message(ctx->output, au, 1,
+                                     H264_SEI_TYPE_USER_DATA_UNREGISTERED,
+                                     &ctx->sei_user_data_payload, NULL);
         if (err < 0) {
             av_log(bsf, AV_LOG_ERROR, "Failed to add user data SEI "
                    "message to access unit.\n");
@@ -428,74 +431,54 @@ static int h264_metadata_filter(AVBSFContext *bsf, AVPacket *pkt)
                 ff_cbs_delete_unit(au, i);
                 continue;
             }
-
-            if (au->units[i].type == H264_NAL_SEI) {
-                // Filler SEI messages.
-                H264RawSEI *sei = au->units[i].content;
-
-                for (j = sei->payload_count - 1; j >= 0; j--) {
-                    if (sei->payload[j].payload_type ==
-                        H264_SEI_TYPE_FILLER_PAYLOAD)
-                        ff_cbs_h264_delete_sei_message(au, &au->units[i], j);
-                }
-            }
         }
+
+        ff_cbs_sei_delete_message_type(ctx->output, au,
+                                       H264_SEI_TYPE_FILLER_PAYLOAD);
     }
 
     if (ctx->display_orientation != PASS) {
-        for (i = au->nb_units - 1; i >= 0; i--) {
-            H264RawSEI *sei;
-            if (au->units[i].type != H264_NAL_SEI)
-                continue;
-            sei = au->units[i].content;
-
-            for (j = sei->payload_count - 1; j >= 0; j--) {
-                H264RawSEIDisplayOrientation *disp;
-                int32_t *matrix;
-
-                if (sei->payload[j].payload_type !=
-                    H264_SEI_TYPE_DISPLAY_ORIENTATION)
-                    continue;
-                disp = &sei->payload[j].payload.display_orientation;
-
-                if (ctx->display_orientation == REMOVE ||
-                    ctx->display_orientation == INSERT) {
-                    ff_cbs_h264_delete_sei_message(au, &au->units[i], j);
-                    continue;
-                }
-
-                matrix = av_malloc(9 * sizeof(int32_t));
-                if (!matrix) {
-                    err = AVERROR(ENOMEM);
-                    goto fail;
-                }
+        SEIRawMessage *message = NULL;
+        while (ff_cbs_sei_find_message(ctx->output, au,
+                                       H264_SEI_TYPE_DISPLAY_ORIENTATION,
+                                       &message) == 0) {
+            H264RawSEIDisplayOrientation *disp = message->payload;
+            int32_t *matrix;
+
+            matrix = av_malloc(9 * sizeof(int32_t));
+            if (!matrix) {
+                err = AVERROR(ENOMEM);
+                goto fail;
+            }
 
-                av_display_rotation_set(matrix,
-                                        disp->anticlockwise_rotation *
-                                        180.0 / 65536.0);
-                av_display_matrix_flip(matrix, disp->hor_flip, disp->ver_flip);
-
-                // If there are multiple display orientation messages in an
-                // access unit, then the last one added to the packet (i.e.
-                // the first one in the access unit) will prevail.
-                err = av_packet_add_side_data(pkt, AV_PKT_DATA_DISPLAYMATRIX,
-                                              (uint8_t*)matrix,
-                                              9 * sizeof(int32_t));
-                if (err < 0) {
-                    av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted "
-                           "displaymatrix side data to packet.\n");
-                    av_free(matrix);
-                    goto fail;
-                }
+            av_display_rotation_set(matrix,
+                                    disp->anticlockwise_rotation *
+                                    180.0 / 65536.0);
+            av_display_matrix_flip(matrix, disp->hor_flip, disp->ver_flip);
+
+            // If there are multiple display orientation messages in an
+            // access unit, then the last one added to the packet (i.e.
+            // the first one in the access unit) will prevail.
+            err = av_packet_add_side_data(pkt, AV_PKT_DATA_DISPLAYMATRIX,
+                                          (uint8_t*)matrix,
+                                          9 * sizeof(int32_t));
+            if (err < 0) {
+                av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted "
+                       "displaymatrix side data to packet.\n");
+                av_free(matrix);
+                goto fail;
             }
         }
+
+        if (ctx->display_orientation == REMOVE ||
+            ctx->display_orientation == INSERT) {
+            ff_cbs_sei_delete_message_type(ctx->output, au,
+                                           H264_SEI_TYPE_DISPLAY_ORIENTATION);
+        }
     }
     if (ctx->display_orientation == INSERT) {
-        H264RawSEIPayload payload = {
-            .payload_type = H264_SEI_TYPE_DISPLAY_ORIENTATION,
-        };
         H264RawSEIDisplayOrientation *disp =
-            &payload.payload.display_orientation;
+            &ctx->display_orientation_payload;
         uint8_t *data;
         int size;
         int write = 0;
@@ -551,7 +534,9 @@ static int h264_metadata_filter(AVBSFContext *bsf, AVPacket *pkt)
         if (write) {
             disp->display_orientation_repetition_period = 1;
 
-            err = ff_cbs_h264_add_sei_message(au, &payload);
+            err = ff_cbs_sei_add_message(ctx->output, au, 1,
+                                         H264_SEI_TYPE_DISPLAY_ORIENTATION,
+                                         disp, NULL);
             if (err < 0) {
                 av_log(bsf, AV_LOG_ERROR, "Failed to add display orientation "
                        "SEI message to access unit.\n");
@@ -585,13 +570,9 @@ static int h264_metadata_init(AVBSFContext *bsf)
     int err, i;
 
     if (ctx->sei_user_data) {
-        SEIRawUserDataUnregistered *udu =
-            &ctx->sei_user_data_payload.payload.user_data_unregistered;
+        SEIRawUserDataUnregistered *udu = &ctx->sei_user_data_payload;
         int j;
 
-        ctx->sei_user_data_payload.payload_type =
-            H264_SEI_TYPE_USER_DATA_UNREGISTERED;
-
         // Parse UUID.  It must be a hex string of length 32, possibly
         // containing '-'s between hex digits (which we ignore).
         for (i = j = 0; j < 32 && i < 64 && ctx->sei_user_data[i]; i++) {
diff --git a/libavcodec/vaapi_encode_h264.c b/libavcodec/vaapi_encode_h264.c
index b577d09caf..d24462414c 100644
--- a/libavcodec/vaapi_encode_h264.c
+++ b/libavcodec/vaapi_encode_h264.c
@@ -90,7 +90,6 @@ typedef struct VAAPIEncodeH264Context {
     H264RawAUD   raw_aud;
     H264RawSPS   raw_sps;
     H264RawPPS   raw_pps;
-    H264RawSEI   raw_sei;
     H264RawSlice raw_slice;
 
     H264RawSEIBufferingPeriod      sei_buffering_period;
@@ -210,11 +209,9 @@ static int vaapi_encode_h264_write_extra_header(AVCodecContext *avctx,
 {
     VAAPIEncodeH264Context *priv = avctx->priv_data;
     CodedBitstreamFragment   *au = &priv->current_access_unit;
-    int err, i;
+    int err;
 
     if (priv->sei_needed) {
-        H264RawSEI *sei = &priv->raw_sei;
-
         if (priv->aud_needed) {
             err = vaapi_encode_h264_add_nal(avctx, au, &priv->raw_aud);
             if (err < 0)
@@ -222,41 +219,35 @@ static int vaapi_encode_h264_write_extra_header(AVCodecContext *avctx,
             priv->aud_needed = 0;
         }
 
-        *sei = (H264RawSEI) {
-            .nal_unit_header = {
-                .nal_unit_type = H264_NAL_SEI,
-            },
-        };
-
-        i = 0;
-
         if (priv->sei_needed & SEI_IDENTIFIER) {
-            sei->payload[i].payload_type = H264_SEI_TYPE_USER_DATA_UNREGISTERED;
-            sei->payload[i].payload.user_data_unregistered = priv->sei_identifier;
-            ++i;
+            err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                         H264_SEI_TYPE_USER_DATA_UNREGISTERED,
+                                         &priv->sei_identifier, NULL);
+            if (err < 0)
+                goto fail;
         }
         if (priv->sei_needed & SEI_TIMING) {
             if (pic->type == PICTURE_TYPE_IDR) {
-                sei->payload[i].payload_type = H264_SEI_TYPE_BUFFERING_PERIOD;
-                sei->payload[i].payload.buffering_period = priv->sei_buffering_period;
-                ++i;
+                err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                             H264_SEI_TYPE_BUFFERING_PERIOD,
+                                             &priv->sei_buffering_period, NULL);
+                if (err < 0)
+                    goto fail;
             }
-            sei->payload[i].payload_type = H264_SEI_TYPE_PIC_TIMING;
-            sei->payload[i].payload.pic_timing = priv->sei_pic_timing;
-            ++i;
+            err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                         H264_SEI_TYPE_PIC_TIMING,
+                                         &priv->sei_pic_timing, NULL);
+            if (err < 0)
+                goto fail;
         }
         if (priv->sei_needed & SEI_RECOVERY_POINT) {
-            sei->payload[i].payload_type = H264_SEI_TYPE_RECOVERY_POINT;
-            sei->payload[i].payload.recovery_point = priv->sei_recovery_point;
-            ++i;
+            err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                         H264_SEI_TYPE_RECOVERY_POINT,
+                                         &priv->sei_recovery_point, NULL);
+            if (err < 0)
+                goto fail;
         }
 
-        sei->payload_count = i;
-        av_assert0(sei->payload_count > 0);
-
-        err = vaapi_encode_h264_add_nal(avctx, au, sei);
-        if (err < 0)
-            goto fail;
         priv->sei_needed = 0;
 
         err = vaapi_encode_h264_write_access_unit(avctx, data, data_len, au);
diff --git a/libavcodec/vaapi_encode_h265.c b/libavcodec/vaapi_encode_h265.c
index a7af763ae4..2e8e772008 100644
--- a/libavcodec/vaapi_encode_h265.c
+++ b/libavcodec/vaapi_encode_h265.c
@@ -73,7 +73,6 @@ typedef struct VAAPIEncodeH265Context {
     H265RawVPS   raw_vps;
     H265RawSPS   raw_sps;
     H265RawPPS   raw_pps;
-    H265RawSEI   raw_sei;
     H265RawSlice raw_slice;
 
     SEIRawMasteringDisplayColourVolume sei_mastering_display;
@@ -195,11 +194,9 @@ static int vaapi_encode_h265_write_extra_header(AVCodecContext *avctx,
 {
     VAAPIEncodeH265Context *priv = avctx->priv_data;
     CodedBitstreamFragment   *au = &priv->current_access_unit;
-    int err, i;
+    int err;
 
     if (priv->sei_needed) {
-        H265RawSEI *sei = &priv->raw_sei;
-
         if (priv->aud_needed) {
             err = vaapi_encode_h265_add_nal(avctx, au, &priv->aud);
             if (err < 0)
@@ -207,35 +204,22 @@ static int vaapi_encode_h265_write_extra_header(AVCodecContext *avctx,
             priv->aud_needed = 0;
         }
 
-        *sei = (H265RawSEI) {
-            .nal_unit_header = {
-                .nal_unit_type         = HEVC_NAL_SEI_PREFIX,
-                .nuh_layer_id          = 0,
-                .nuh_temporal_id_plus1 = 1,
-            },
-        };
-
-        i = 0;
-
         if (priv->sei_needed & SEI_MASTERING_DISPLAY) {
-            sei->payload[i].payload_type = HEVC_SEI_TYPE_MASTERING_DISPLAY_INFO;
-            sei->payload[i].payload.mastering_display_colour_volume =
-                priv->sei_mastering_display;
-            ++i;
+            err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                         HEVC_SEI_TYPE_MASTERING_DISPLAY_INFO,
+                                         &priv->sei_mastering_display, NULL);
+            if (err < 0)
+                goto fail;
         }
 
         if (priv->sei_needed & SEI_CONTENT_LIGHT_LEVEL) {
-            sei->payload[i].payload_type = HEVC_SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO;
-            sei->payload[i].payload.content_light_level = priv->sei_content_light_level;
-            ++i;
+            err = ff_cbs_sei_add_message(priv->cbc, au, 1,
+                                         HEVC_SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO,
+                                         &priv->sei_content_light_level, NULL);
+            if (err < 0)
+                goto fail;
         }
 
-        sei->payload_count = i;
-        av_assert0(sei->payload_count > 0);
-
-        err = vaapi_encode_h265_add_nal(avctx, au, sei);
-        if (err < 0)
-            goto fail;
         priv->sei_needed = 0;
 
         err = vaapi_encode_h265_write_access_unit(avctx, data, data_len, au);
