diff --git a/libavcodec/cbs_h2645.c b/libavcodec/cbs_h2645.c
index 42b614034e..53d5b50b42 100644
--- a/libavcodec/cbs_h2645.c
+++ b/libavcodec/cbs_h2645.c
@@ -18,6 +18,7 @@
 
 #include "libavutil/attributes.h"
 #include "libavutil/avassert.h"
+#include "libavutil/mastering_display_metadata.h"
 
 #include "bytestream.h"
 #include "cbs.h"
@@ -1504,6 +1505,133 @@ const CodedBitstreamType ff_cbs_type_h265 = {
     .extract_metadata  = &ff_cbs_sei_extract_metadata,
 };
 
+static uint32_t rescale_clip(AVRational value, uint32_t scale,
+                             uint32_t min, uint32_t max)
+{
+    int64_t scaled = av_rescale(scale, value.num, value.den);
+    return av_clip64(scaled, min, max);
+}
+
+static void cbs_sei_fill_mastering_display_colour_volume
+    (SEIRawMasteringDisplayColourVolume *mdcv,
+     const AVMasteringDisplayMetadata *mdm)
+{
+    memset(mdcv, 0, sizeof(*mdcv));
+
+    if (mdm->has_primaries) {
+        // The values in the metadata structure are fractions between 0 and 1,
+        // while the SEI message contains fixed-point values with an increment
+        // of 0.00002.  So, scale up by 50000 to convert between them and clip
+        // to the allowed range ([5, 37000] for x, [5, 42000] for y).
+
+        for (int a = 0; a < 3; a++) {
+            // The metadata structure stores this in RGB order, but the SEI
+            // wants it in GBR order.
+            static const uint8_t mapping[] = { 1, 2, 0 };
+            int b = mapping[a];
+            mdcv->display_primaries_x[a] =
+                rescale_clip(mdm->display_primaries[b][0], 50000, 5, 37000);
+            mdcv->display_primaries_y[a] =
+                rescale_clip(mdm->display_primaries[b][1], 50000, 5, 42000);
+        }
+
+        mdcv->white_point_x =
+            rescale_clip(mdm->white_point[0], 50000, 5, 37000);
+        mdcv->white_point_y =
+            rescale_clip(mdm->white_point[1], 50000, 5, 42000);
+    }
+
+    if (mdm->has_luminance) {
+        // Metadata are rational values in candelas per square metre, SEI
+        // contains fixed point in units of 0.0001 candelas per square
+        // metre.  So scale up by 10000 to convert between them, and clip to
+        // the allowed ranges.
+
+        mdcv->max_display_mastering_luminance =
+            rescale_clip(mdm->max_luminance, 10000, 50000, 100000000);
+        mdcv->min_display_mastering_luminance =
+            rescale_clip(mdm->min_luminance, 10000, 1, 50000);
+
+        // The spec requires that they are not equal when in the normal
+        // range.
+        if (mdcv->min_display_mastering_luminance >=
+            mdcv->max_display_mastering_luminance) {
+            mdcv->min_display_mastering_luminance =
+                mdcv->max_display_mastering_luminance - 1;
+        }
+    } else {
+        mdcv->max_display_mastering_luminance = 0;
+        mdcv->min_display_mastering_luminance = 0;
+    }
+}
+
+static void cbs_sei_extract_mastering_display_colour_volume
+    (AVMasteringDisplayMetadata *mdm,
+     const SEIRawMasteringDisplayColourVolume *mdcv)
+{
+#define IN_RANGE(v, min, max) ((v) >= (min) && (v) <= (max))
+#define IS_VALID_COORD(x, y) (IN_RANGE(x, 5, 37000) && IN_RANGE(y, 5, 42000))
+    int valid_chromaticity = 1;
+    for (int a = 0; a < 3; a++) {
+        if (!IS_VALID_COORD(mdcv->display_primaries_x[a],
+                            mdcv->display_primaries_y[a]))
+            valid_chromaticity = 0;
+    }
+    if (!IS_VALID_COORD(mdcv->white_point_x, mdcv->white_point_y))
+        valid_chromaticity = 0;
+
+    memset(mdm, 0, sizeof(*mdm));
+
+    if (valid_chromaticity) {
+        for (int a = 0; a < 3; a++) {
+            // SEI message in GBR order, but metadata structure in RGB order.
+            static const uint8_t mapping[] = { 2, 0, 1 };
+            int b = mapping[a];
+
+            mdm->display_primaries[a][0] =
+                av_make_q(mdcv->display_primaries_x[b], 50000);
+            mdm->display_primaries[a][1] =
+                av_make_q(mdcv->display_primaries_y[b], 50000);
+        }
+
+        mdm->white_point[0] = av_make_q(mdcv->white_point_x, 50000);
+        mdm->white_point[1] = av_make_q(mdcv->white_point_y, 50000);
+
+        mdm->has_primaries = 1;
+    }
+
+    if (IN_RANGE(mdcv->min_display_mastering_luminance, 1, 50000) &&
+        IN_RANGE(mdcv->max_display_mastering_luminance, 50000, 100000000)) {
+        mdm->min_luminance = av_make_q(mdcv->min_display_mastering_luminance, 10000);
+        mdm->max_luminance = av_make_q(mdcv->max_display_mastering_luminance, 10000);
+
+        mdm->has_luminance = 1;
+    }
+#undef IN_RANGE
+#undef IS_VALID_COORD
+}
+
+static void cbs_sei_fill_content_light_level_info
+    (SEIRawContentLightLevelInfo *cll, const AVContentLightMetadata *clm)
+{
+    memset(cll, 0, sizeof(*cll));
+
+    // Both the metadata and the SEI are in units of candelas per square
+    // metre, so we only need to clip to ensure that they are in the valid
+    // range.
+
+    cll->max_content_light_level     = av_clip_uintp2(clm->MaxCLL,  16);
+    cll->max_pic_average_light_level = av_clip_uintp2(clm->MaxFALL, 16);
+}
+
+
+static void cbs_sei_extract_content_light_level_info
+    (AVContentLightMetadata *clm, const SEIRawContentLightLevelInfo *cll)
+{
+    clm->MaxCLL  = cll->max_content_light_level;
+    clm->MaxFALL = cll->max_pic_average_light_level;
+}
+
 static const SEIMessageTypeDescriptor cbs_sei_common_types[] = {
     {
         SEI_TYPE_FILLER_PAYLOAD,
@@ -1528,12 +1656,14 @@ static const SEIMessageTypeDescriptor cbs_sei_common_types[] = {
         1, 0,
         sizeof(SEIRawMasteringDisplayColourVolume),
         SEI_MESSAGE_RW(sei, mastering_display_colour_volume),
+        SEI_MESSAGE_FE(sei, mastering_display_colour_volume),
     },
     {
         SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO,
         1, 0,
         sizeof(SEIRawContentLightLevelInfo),
         SEI_MESSAGE_RW(sei, content_light_level_info),
+        SEI_MESSAGE_FE(sei, content_light_level_info),
     },
     {
         SEI_TYPE_ALTERNATIVE_TRANSFER_CHARACTERISTICS,
diff --git a/libavcodec/cbs_sei.c b/libavcodec/cbs_sei.c
index e5f9e3e403..0c05b2bdc9 100644
--- a/libavcodec/cbs_sei.c
+++ b/libavcodec/cbs_sei.c
@@ -375,6 +375,10 @@ typedef struct SEIMetadata {
 } SEIMetadata;
 
 static const SEIMetadata cbs_sei_metadata[] = {
+    { CBS_METADATA_MASTERING_DISPLAY,
+      SEI_TYPE_MASTERING_DISPLAY_COLOUR_VOLUME },
+    { CBS_METADATA_CONTENT_LIGHT_LEVEL,
+      SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO },
 };
 
 static const SEIMessageTypeDescriptor *cbs_sei_find_type_from_metadata
