From c803ce7a9c682e7ca54d4e2f28abe947453491fe Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Tue, 8 Jul 2025 10:09:39 -0400 Subject: [PATCH] waylandsink: Parse and set the HDR10 metadata Basically whenever the compositor have support for it, and the caps includes it, set the mastering display and light content level information. Part-of: --- .../ext/gtk/gstgtkwaylandsink.c | 21 ++++- .../ext/wayland/gstwaylandsink.c | 16 +++- .../ext/wayland/gstwaylandsink.h | 4 + .../gst-libs/gst/wayland/gstwlwindow.c | 77 +++++++++++++++++-- .../gst-libs/gst/wayland/gstwlwindow.h | 5 ++ 5 files changed, 115 insertions(+), 8 deletions(-) diff --git a/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c index c8f357471b..55cc4fcfb6 100644 --- a/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c +++ b/subprojects/gst-plugins-bad/ext/gtk/gstgtkwaylandsink.c @@ -105,6 +105,10 @@ typedef struct _GstGtkWaylandSinkPrivate gboolean video_info_changed; GstVideoInfo video_info; GstVideoInfoDmaDrm drm_info; + GstVideoMasteringDisplayInfo minfo; + GstVideoContentLightLevel linfo; + gboolean have_mastering_info; + gboolean have_light_info; GstCaps *caps; GMutex render_lock; @@ -931,6 +935,11 @@ gst_gtk_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) gst_video_info_dma_drm_init (&priv->drm_info); } + priv->have_mastering_info = + gst_video_mastering_display_info_from_caps (&priv->minfo, caps); + priv->have_light_info = + gst_video_content_light_level_from_caps (&priv->linfo, caps); + priv->video_info_changed = TRUE; priv->skip_dumb_buffer_copy = FALSE; @@ -1050,6 +1059,8 @@ render_last_buffer (GstGtkWaylandSink * self, gboolean redraw) gst_gtk_wayland_sink_get_instance_private (self); GstWlBuffer *wlbuffer; const GstVideoInfo *info = NULL; + const GstVideoMasteringDisplayInfo *minfo = NULL; + const GstVideoContentLightLevel *linfo = NULL; if (!priv->wl_window) return FALSE; @@ -1058,9 +1069,17 @@ render_last_buffer (GstGtkWaylandSink * self, gboolean redraw) if (G_UNLIKELY (priv->video_info_changed && !redraw)) { info = &priv->video_info; + + if (priv->have_mastering_info) + minfo = &priv->minfo; + + if (priv->have_light_info) + linfo = &priv->linfo; + priv->video_info_changed = FALSE; } - return gst_wl_window_render (priv->wl_window, wlbuffer, info); + return gst_wl_window_render_hdr (priv->wl_window, wlbuffer, info, minfo, + linfo); } static GstFlowReturn diff --git a/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.c b/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.c index c1fe8bcf45..68460f4e04 100644 --- a/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.c +++ b/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.c @@ -755,6 +755,11 @@ gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps) gst_video_info_dma_drm_init (&self->drm_info); } + self->have_mastering_info = + gst_video_mastering_display_info_from_caps (&self->minfo, caps); + self->have_light_info = + gst_video_content_light_level_from_caps (&self->linfo, caps); + self->video_info_changed = TRUE; self->skip_dumb_buffer_copy = FALSE; @@ -863,14 +868,23 @@ render_last_buffer (GstWaylandSink * self, gboolean redraw) { GstWlBuffer *wlbuffer; const GstVideoInfo *info = NULL; + const GstVideoMasteringDisplayInfo *minfo = NULL; + const GstVideoContentLightLevel *linfo = NULL; wlbuffer = gst_buffer_get_wl_buffer (self->display, self->last_buffer); if (G_UNLIKELY (self->video_info_changed && !redraw)) { info = &self->video_info; + + if (self->have_mastering_info) + minfo = &self->minfo; + + if (self->have_light_info) + linfo = &self->linfo; + self->video_info_changed = FALSE; } - return gst_wl_window_render (self->window, wlbuffer, info); + return gst_wl_window_render_hdr (self->window, wlbuffer, info, minfo, linfo); } static void diff --git a/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.h b/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.h index 9d4de78f0d..b12e0b6d26 100644 --- a/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.h +++ b/subprojects/gst-plugins-bad/ext/wayland/gstwaylandsink.h @@ -55,6 +55,10 @@ struct _GstWaylandSink gboolean video_info_changed; GstVideoInfo video_info; GstVideoInfoDmaDrm drm_info; + GstVideoMasteringDisplayInfo minfo; + GstVideoContentLightLevel linfo; + gboolean have_mastering_info; + gboolean have_light_info; gboolean fullscreen; GstCaps *caps; diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.c b/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.c index b90eae7f74..e4fd6de07c 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.c @@ -82,6 +82,8 @@ typedef struct _GstWlWindowPrivate GMutex window_lock; GstWlBuffer *next_buffer; GstVideoInfo *next_video_info; + GstVideoMasteringDisplayInfo *next_minfo; + GstVideoContentLightLevel *next_linfo; GstWlBuffer *staged_buffer; gboolean clear_window; struct wl_callback *frame_callback; @@ -111,7 +113,9 @@ static void gst_wl_window_commit_buffer (GstWlWindow * self, GstWlBuffer * buffer); static void gst_wl_window_set_colorimetry (GstWlWindow * self, - GstVideoColorimetry * colorimetry); + const GstVideoColorimetry * colorimetry, + const GstVideoMasteringDisplayInfo * minfo, + const GstVideoContentLightLevel * linfo); static void handle_xdg_toplevel_close (void *data, struct xdg_toplevel *xdg_toplevel) @@ -583,6 +587,8 @@ gst_wl_window_commit_buffer (GstWlWindow * self, GstWlBuffer * buffer) { GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); GstVideoInfo *info = priv->next_video_info; + GstVideoMasteringDisplayInfo *minfo = priv->next_minfo; + GstVideoContentLightLevel *linfo = priv->next_linfo; struct wl_callback *callback; if (G_UNLIKELY (info)) { @@ -595,7 +601,7 @@ gst_wl_window_commit_buffer (GstWlWindow * self, GstWlBuffer * buffer) gst_wl_window_resize_video_surface (self, FALSE); gst_wl_window_set_opaque (self, info); - gst_wl_window_set_colorimetry (self, &info->colorimetry); + gst_wl_window_set_colorimetry (self, &info->colorimetry, minfo, linfo); } if (G_LIKELY (buffer)) { @@ -630,6 +636,8 @@ gst_wl_window_commit_buffer (GstWlWindow * self, GstWlBuffer * buffer) wl_subsurface_set_desync (priv->video_subsurface); gst_video_info_free (priv->next_video_info); priv->next_video_info = NULL; + g_clear_pointer (&priv->next_minfo, g_free); + g_clear_pointer (&priv->next_linfo, g_free); } } @@ -661,6 +669,14 @@ static const struct wl_callback_listener commit_listener = { gboolean gst_wl_window_render (GstWlWindow * self, GstWlBuffer * buffer, const GstVideoInfo * info) +{ + return gst_wl_window_render_hdr (self, buffer, info, NULL, NULL); +} + +gboolean +gst_wl_window_render_hdr (GstWlWindow * self, GstWlBuffer * buffer, + const GstVideoInfo * info, const GstVideoMasteringDisplayInfo * minfo, + const GstVideoContentLightLevel * linfo) { GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); gboolean ret = TRUE; @@ -672,6 +688,16 @@ gst_wl_window_render (GstWlWindow * self, GstWlBuffer * buffer, if (G_UNLIKELY (info)) priv->next_video_info = gst_video_info_copy (info); + if (G_UNLIKELY (minfo)) { + g_clear_pointer (&priv->next_minfo, g_free); + priv->next_minfo = g_memdup2 (minfo, sizeof (*minfo)); + } + + if (G_UNLIKELY (linfo)) { + g_clear_pointer (&priv->next_linfo, g_free); + priv->next_linfo = g_memdup2 (linfo, sizeof (*linfo)); + } + if (priv->next_buffer && priv->staged_buffer) { GST_LOG_OBJECT (self, "buffer %p dropped (replaced)", priv->staged_buffer); gst_wl_buffer_unref_buffer (priv->staged_buffer); @@ -954,7 +980,9 @@ gst_colorimetry_range_to_wl (GstVideoColorRange range) static void gst_wl_window_set_image_description (GstWlWindow * self, - GstVideoColorimetry * colorimetry) + const GstVideoColorimetry * colorimetry, + const GstVideoMasteringDisplayInfo * minfo, + const GstVideoContentLightLevel * linfo) { GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); struct wl_display *wl_display; @@ -1010,6 +1038,41 @@ gst_wl_window_set_image_description (GstWlWindow * self, wp_image_description_creator_params_v1_set_primaries_named (params, wl_primaries); + if (gst_wl_display_is_color_mastering_display_supported (priv->display) + && minfo) { + /* first validate our luminance range */ + guint min_luminance = minfo->min_display_mastering_luminance / 10000; + guint max_luminance = + MAX (min_luminance + 1, minfo->max_display_mastering_luminance / 10000); + + /* We need to convert from 0.00002 unit to 0.000001 */ + const guint f = 20; + wp_image_description_creator_params_v1_set_mastering_display_primaries + (params, + minfo->display_primaries[0].x * f, minfo->display_primaries[0].y * f, + minfo->display_primaries[1].x * f, minfo->display_primaries[1].y * f, + minfo->display_primaries[2].x * f, minfo->display_primaries[2].y * f, + minfo->white_point.x, minfo->white_point.y); + wp_image_description_creator_params_v1_set_mastering_luminance (params, + minfo->min_display_mastering_luminance, max_luminance); + + /* + * FIXME its unclear what makes a color volume exceeds the primary volume, + * and how to verify it, ignoring this aspect for now, but may need to be + * revisited. + */ + + /* We can't set the light level if we don't know the luminance range */ + if (linfo) { + guint maxFALL = CLAMP (min_luminance + 1, + linfo->max_frame_average_light_level, max_luminance); + guint maxCLL = + CLAMP (maxFALL, linfo->max_content_light_level, max_luminance); + wp_image_description_creator_params_v1_set_max_cll (params, maxCLL); + wp_image_description_creator_params_v1_set_max_fall (params, maxFALL); + } + } + image_description = wp_image_description_creator_params_v1_create (params); wp_image_description_v1_add_listener (image_description, &description_listerer, &image_description_feedback); @@ -1040,7 +1103,7 @@ gst_wl_window_set_image_description (GstWlWindow * self, static void gst_wl_window_set_color_representation (GstWlWindow * self, - GstVideoColorimetry * colorimetry) + const GstVideoColorimetry * colorimetry) { GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); struct wp_color_representation_manager_v1 *cr_manager; @@ -1097,14 +1160,16 @@ gst_wl_window_set_color_representation (GstWlWindow * self, static void gst_wl_window_set_colorimetry (GstWlWindow * self, - GstVideoColorimetry * colorimetry) + const GstVideoColorimetry * colorimetry, + const GstVideoMasteringDisplayInfo * minfo, + const GstVideoContentLightLevel * linfo) { GST_OBJECT_LOCK (self); GST_INFO_OBJECT (self, "Trying to set colorimetry: %s", gst_video_colorimetry_to_string (colorimetry)); - gst_wl_window_set_image_description (self, colorimetry); + gst_wl_window_set_image_description (self, colorimetry, minfo, linfo); gst_wl_window_set_color_representation (self, colorimetry); GST_OBJECT_UNLOCK (self); diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.h b/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.h index 7d82e5d698..0badd4b8f6 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlwindow.h @@ -63,6 +63,11 @@ GST_WL_API gboolean gst_wl_window_render (GstWlWindow * self, GstWlBuffer * buffer, const GstVideoInfo * info); +GST_WL_API +gboolean gst_wl_window_render_hdr (GstWlWindow * self, GstWlBuffer * buffer, + const GstVideoInfo * info, const GstVideoMasteringDisplayInfo *minfo, + const GstVideoContentLightLevel *linfo); + GST_WL_API void gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y, gint w, gint h);