kmssink: add HDR10 infoframe support

If stream has HDR10 metadata and HDMI device EDID supports it, this patch
will set the DRM properties accordingly.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3303>
This commit is contained in:
Bill Hofmann 2022-11-18 13:32:10 -08:00 committed by Nicolas Dufresne
parent daecbd1ff0
commit afb18e0e31
5 changed files with 514 additions and 1 deletions

View file

@ -0,0 +1,111 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2017, 2018 Collabora, Ltd.
* Copyright © 2017, 2018 General Electric Company
* Copyright (c) 2018 DisplayLink (UK) Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <math.h>
#include "gstkmsedid.h"
/* from libweston/backend-drm/modes.c unaccepted merge, modified slightly to
remove non HDR stuff, return -1 if no HDR in EDID.
https://gitlab.freedesktop.org/jcline/weston/-/commit/b3fa65d19ca60a45d0cc0fc1bfa68eea970344ee
*/
#define EDID_OFFSET_EXT_COUNT 0x7E
#define EDID_EXTENSION_SIZE 0x80
// Indicates the EDID extension is a CTA extension
#define EDID_CTA_EXTENSION_TAG 0x02
// Indicates the data block uses the extended tag field
#define EDID_CTA_EXTENDED_TAG 0x07
// Value of the extended tag field for HDR static metadata blocks
#define EDID_CTA_STATIC_HDR_TAG 0x06
/* Extract the HDR static metadata from a CTA EDID extension. */
static int
gst_kms_parse_hdr_metadata (const uint8_t * cta_ext_data,
struct gst_kms_hdr_static_metadata *metadata)
{
int i, block_len;
uint8_t cta_revision = cta_ext_data[1];
uint8_t dtd_offset = cta_ext_data[2];
const uint8_t *data_blocks = cta_ext_data + 4;
if (cta_revision != 3) {
return -1;
}
// The data block collection ranges from byte 4 to the dtd_offset; each
// block begins with the block size (in bytes) in bits 0-4 of the first byte.
for (i = 0; i < dtd_offset; i += (data_blocks[i] & 0x1f) + 1) {
if ((data_blocks[i] & 0xe0) >> 5 == EDID_CTA_EXTENDED_TAG) {
block_len = data_blocks[i] & 0x1f;
if (data_blocks[i + 1] == EDID_CTA_STATIC_HDR_TAG) {
if (block_len < 2)
continue;
metadata->eotf = data_blocks[i + 2];
metadata->metadata_type = data_blocks[i + 3];
if (block_len > 3 && data_blocks[i + 4])
metadata->max_cll = 50.0 * pow (2, data_blocks[i + 4] / 32.0);
if (block_len > 4 && data_blocks[i + 5])
metadata->max_fall = 50.0 * pow (2, data_blocks[i + 5] / 32.0);
if (block_len > 5)
metadata->min_cll =
metadata->max_cll * pow (data_blocks[i + 6] / 255.0, 2) / 100.0;
return 0;
}
}
}
return -1;
}
int
gst_kms_edid_parse (struct gst_kms_hdr_static_metadata *metadata,
const uint8_t * data, size_t length)
{
int i;
const uint8_t *edid_extension;
/* check header */
if (length < 128 || length < ((size_t) data[EDID_OFFSET_EXT_COUNT] + 1) * 128)
return -1;
if (data[0] != 0x00 || data[1] != 0xff)
return -1;
edid_extension = data + 128;
for (i = 0; i < data[EDID_OFFSET_EXT_COUNT]; i++) {
switch (edid_extension[0]) {
case EDID_CTA_EXTENSION_TAG:
return gst_kms_parse_hdr_metadata (edid_extension, metadata);
}
edid_extension += 128;
}
return 0;
}
/* END from libweston/backend-drm/modes.c unaccepted merge */

View file

@ -0,0 +1,55 @@
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2017, 2018 Collabora, Ltd.
* Copyright © 2017, 2018 General Electric Company
* Copyright (c) 2018 DisplayLink (UK) Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef __GST_KMS_EDID_H__
#define __GST_KMS_EDID_H__
/* from libweston/backend-drm/modes.c unaccepted merge, modified slightly to
remove non HDR stuff, return -1 if no HDR in EDID.
https://gitlab.freedesktop.org/jcline/weston/-/commit/b3fa65d19ca60a45d0cc0fc1bfa68eea970344ee
*/
#include <stddef.h>
#include <stdint.h>
/* HDR Metadata as per 861.G spec from linux/hdmi.h, modified for stdint.h */
struct gst_kms_hdr_static_metadata
{
uint8_t eotf;
uint8_t metadata_type;
uint16_t max_cll;
uint16_t max_fall;
uint16_t min_cll;
};
int
gst_kms_edid_parse (struct gst_kms_hdr_static_metadata *metadata, const uint8_t * data,
size_t length);
#endif /* __GST_KMS_EDID_H__ */

View file

@ -49,6 +49,7 @@
#include <gst/video/video.h>
#include <gst/video/videooverlay.h>
#include <gst/video/video-color.h>
#include <gst/allocators/gstdmabuf.h>
#include <drm.h>
@ -62,6 +63,11 @@
#include "gstkmsbufferpool.h"
#include "gstkmsallocator.h"
#ifdef HAVE_DRM_HDR
#include <math.h>
#include "gstkmsedid.h"
#endif
#define GST_PLUGIN_NAME "kmssink"
#define GST_PLUGIN_DESC "Video sink using the Linux kernel mode setting API"
@ -104,6 +110,303 @@ enum
static GParamSpec *g_properties[PROP_N] = { NULL, };
#ifdef HAVE_DRM_HDR
enum hdmi_metadata_type
{
HDMI_STATIC_METADATA_TYPE1 = 0,
};
enum hdmi_eotf
{
HDMI_EOTF_TRADITIONAL_GAMMA_SDR = 0,
HDMI_EOTF_TRADITIONAL_GAMMA_HDR,
HDMI_EOTF_SMPTE_ST2084,
HDMI_EOTF_BT_2100_HLG,
};
static void
gst_kms_populate_infoframe (struct hdr_output_metadata *pinfo_frame,
GstVideoMasteringDisplayInfo * p_hdr_minfo,
GstVideoContentLightLevel * p_hdr_cll,
gchar colorimetry, gboolean clear_it_out)
{
/* From CTA-861.3:
* When a source is transmitting the Dynamic Range and Mastering InfoFrame,
* it shall signal the end of Dynamic Range... by sending a ... InfoFrame with
* the EOTF field to '0', the Static_Metadata_Descriptor_ID field set to '0',
* and the fields of the Static_Metadata_Descriptor set to unknown (0)...
*
* See also https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html
*/
if (clear_it_out) {
/* Static_Metadata_Descriptor_ID */
pinfo_frame->metadata_type = 0;
(void) memset ((void *) &pinfo_frame->hdmi_metadata_type1, 0,
sizeof (pinfo_frame->hdmi_metadata_type1));
return;
} else {
pinfo_frame->metadata_type = HDMI_STATIC_METADATA_TYPE1;
pinfo_frame->hdmi_metadata_type1.eotf = colorimetry;
pinfo_frame->hdmi_metadata_type1.metadata_type = HDMI_STATIC_METADATA_TYPE1;
}
/* For HDR Infoframe see CTA-861-G, Section 6.9.1
* SEI message is in units of 0.0001 cd/m2, HDMI is units of 1 cd/m2 - see
* x265 specs */
pinfo_frame->hdmi_metadata_type1.max_display_mastering_luminance =
round (p_hdr_minfo->max_display_mastering_luminance / 10000.0);
pinfo_frame->hdmi_metadata_type1.min_display_mastering_luminance =
p_hdr_minfo->min_display_mastering_luminance;
pinfo_frame->hdmi_metadata_type1.max_cll = p_hdr_cll->max_content_light_level;
pinfo_frame->hdmi_metadata_type1.max_fall =
p_hdr_cll->max_frame_average_light_level;
for (int i = 0; i < 3; i++) {
pinfo_frame->hdmi_metadata_type1.display_primaries[i].x =
p_hdr_minfo->display_primaries[i].x;
pinfo_frame->hdmi_metadata_type1.display_primaries[i].y =
p_hdr_minfo->display_primaries[i].y;
}
pinfo_frame->hdmi_metadata_type1.white_point.x = p_hdr_minfo->white_point.x;
pinfo_frame->hdmi_metadata_type1.white_point.y = p_hdr_minfo->white_point.y;
}
static void
gst_kms_push_hdr_infoframe (GstKMSSink * self, gboolean clear_it_out)
{
struct hdr_output_metadata info_frame;
drmModeObjectPropertiesPtr props;
uint32_t hdrBlobID;
int drm_fd = self->fd;
uint32_t conn_id = self->conn_id;
int ret = 0;
if (self->no_infoframe || !self->has_hdr_info || (!clear_it_out
&& self->has_sent_hdrif)) {
return;
}
/* Check to see if the connection has the HDR_OUTPUT_METADATA property if
* we haven't already found it */
if (self->hdrPropID == 0 || self->edidPropID == 0) {
props =
drmModeObjectGetProperties (drm_fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
GST_ERROR_OBJECT (self, "Error on drmModeObjectGetProperties %d %s",
errno, g_strerror (errno));
return;
}
struct gst_kms_hdr_static_metadata hdr_edid_info = { 0, 0, 0, 0, 0 };
for (uint32_t i = 0;
i < props->count_props && (self->hdrPropID == 0
|| self->edidPropID == 0); i++) {
drmModePropertyPtr pprop = drmModeGetProperty (drm_fd, props->props[i]);
if (pprop) {
/* 7 16 DRM_MODE_PROP_BLOB HDR_OUTPUT_METADATA */
if (!strncmp ("HDR_OUTPUT_METADATA", pprop->name,
strlen ("HDR_OUTPUT_METADATA"))) {
self->hdrPropID = pprop->prop_id;
GST_DEBUG_OBJECT (self, "HDR prop ID = %d", self->hdrPropID);
}
if (!strncmp ("EDID", pprop->name, strlen ("EDID"))) {
self->edidPropID = pprop->prop_id;
/* Check if EDID indicates device supports HDR */
drmModePropertyBlobPtr blob;
blob = drmModeGetPropertyBlob (drm_fd, props->prop_values[i]);
if (blob) {
int res =
gst_kms_edid_parse (&hdr_edid_info, blob->data, blob->length);
if (res != 0) {
hdr_edid_info.eotf = 0;
hdr_edid_info.metadata_type = 0;
}
}
drmModeFreePropertyBlob (blob);
GST_DEBUG_OBJECT (self, "EDID prop ID = %d", self->edidPropID);
/* only these two values are guaranteed to be populated for HDR */
GST_DEBUG_OBJECT (self, "EDID EOTF = %u, metadata type = %u",
hdr_edid_info.eotf, hdr_edid_info.metadata_type);
}
drmModeFreeProperty (pprop);
} else {
GST_ERROR_OBJECT (self, "Error on drmModeGetProperty(%d)", i);
}
}
drmModeFreeObjectProperties (props);
if (self->hdrPropID == 0 || self->edidPropID == 0
|| hdr_edid_info.eotf == 0) {
GST_DEBUG_OBJECT (self, "No HDR support on target display");
self->no_infoframe = TRUE;
/* FIXME: maybe not the right flag here... */
self->has_sent_hdrif = TRUE;
return;
}
}
if (clear_it_out)
GST_INFO ("Clearing HDR Infoframe on connector %d", self->conn_id);
else
GST_INFO ("Setting HDR Infoframe, if available on connector %d",
self->conn_id);
gst_kms_populate_infoframe (&info_frame, &self->hdr_minfo, &self->hdr_cll,
self->colorimetry, clear_it_out);
/* Use non-atomic property setting */
ret = drmModeCreatePropertyBlob (drm_fd, &info_frame,
sizeof (struct hdr_output_metadata), &hdrBlobID);
if (!ret) {
ret =
drmModeObjectSetProperty (drm_fd, conn_id, DRM_MODE_OBJECT_CONNECTOR,
self->hdrPropID, hdrBlobID);
if (ret) {
GST_ERROR_OBJECT (self, "drmModeObjectSetProperty result %d %d %s", ret,
errno, g_strerror (errno));
}
drmModeDestroyPropertyBlob (drm_fd, hdrBlobID);
} else {
GST_ERROR_OBJECT (self, "Failed to drmModeCreatePropertyBlob %d %s", errno,
g_strerror (errno));
}
if (!ret) {
GST_INFO ("Set HDR Infoframe on connector %d", conn_id);
self->has_sent_hdrif = TRUE; // Hooray!
}
}
/* From an HDR10 stream caps:
*
* colorimetry=(string)bt2100-pq
* content-light-level=(string)10000:166
* mastering-display-info=(string)35400:14600:8500:39850:6550:2300:15635:16450:10000000:1
*/
static void
gst_kms_sink_set_hdr10_caps (GstKMSSink * self, GstCaps * caps)
{
GstVideoMasteringDisplayInfo hdr_minfo;
GstVideoContentLightLevel hdr_cll;
GstStructure *structure;
const gchar *colorimetry_s;
GstVideoColorimetry colorimetry;
gboolean has_hdr_eotf = FALSE;
gboolean has_cll = FALSE;
structure = gst_caps_get_structure (caps, 0);
if ((colorimetry_s = gst_structure_get_string (structure,
"colorimetry")) != NULL &&
gst_video_colorimetry_from_string (&colorimetry, colorimetry_s)) {
switch (colorimetry.transfer) {
case GST_VIDEO_TRANSFER_SMPTE2084:
self->colorimetry = HDMI_EOTF_SMPTE_ST2084;
has_hdr_eotf = TRUE;
GST_DEBUG ("Got HDR transfer value GST_VIDEO_TRANSFER_SMPTE2084: %u",
self->colorimetry);
break;
case GST_VIDEO_TRANSFER_BT2020_10:
case GST_VIDEO_TRANSFER_ARIB_STD_B67:
self->colorimetry = HDMI_EOTF_BT_2100_HLG;
has_hdr_eotf = TRUE;
GST_DEBUG ("Got HDR transfer value HDMI_EOTF_BT_2100_HLG: %u",
self->colorimetry);
break;
case GST_VIDEO_TRANSFER_BT709:
self->colorimetry = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
GST_DEBUG ("Got HDR transfer value GST_VIDEO_TRANSFER_BT709, "
"not HDR: %u", self->colorimetry);
break;
default:
/* not an HDMI and/or HDR colorimetry, we will ignore */
GST_DEBUG ("Unsupported transfer function, no HDR: %u",
colorimetry.transfer);
self->no_infoframe = TRUE;
self->has_hdr_info = FALSE;
break;
}
}
if (gst_video_mastering_display_info_from_caps (&hdr_minfo, caps)) {
if (!gst_video_mastering_display_info_is_equal (&hdr_minfo,
&self->hdr_minfo)) {
self->hdr_minfo = hdr_minfo;
self->no_infoframe = FALSE;
self->has_hdr_info = TRUE;
/* to send again */
self->has_sent_hdrif = FALSE;
}
GST_DEBUG ("Got mastering info: "
"min %u max %u wp %u %u dp[0] %u %u dp[1] %u %u dp[2] %u %u",
self->hdr_minfo.min_display_mastering_luminance,
self->hdr_minfo.max_display_mastering_luminance,
self->hdr_minfo.white_point.x, self->hdr_minfo.white_point.y,
self->hdr_minfo.display_primaries[0].x,
self->hdr_minfo.display_primaries[0].y,
self->hdr_minfo.display_primaries[1].x,
self->hdr_minfo.display_primaries[1].y,
self->hdr_minfo.display_primaries[2].x,
self->hdr_minfo.display_primaries[2].y);
} else {
if (self->has_hdr_info == TRUE) {
GST_WARNING ("Missing mastering display info");
} else {
self->no_infoframe = TRUE;
self->has_hdr_info = FALSE;
}
gst_video_mastering_display_info_init (&self->hdr_minfo);
}
if (gst_video_content_light_level_from_caps (&hdr_cll, caps)) {
GST_DEBUG ("Got content light level information: Max CLL: %u Max FALL: %u",
hdr_cll.max_content_light_level, hdr_cll.max_frame_average_light_level);
if (!gst_video_content_light_level_is_equal (&hdr_cll, &self->hdr_cll)) {
self->hdr_cll = hdr_cll;
self->no_infoframe = FALSE;
self->has_hdr_info = TRUE;
/* to send again */
self->has_sent_hdrif = FALSE;
}
has_cll = TRUE;
} else {
gst_video_content_light_level_init (&self->hdr_cll);
if (self->has_hdr_info == TRUE) {
GST_WARNING ("Missing content light level info");
}
self->no_infoframe = TRUE;
self->has_hdr_info = FALSE;
}
/* need all caps set */
if ((has_hdr_eotf || has_cll) && !(has_hdr_eotf && has_cll)) {
GST_ELEMENT_WARNING (self, STREAM, FORMAT,
("Stream doesn't have all HDR components needed"),
("Check stream caps"));
self->no_infoframe = TRUE;
self->has_hdr_info = FALSE;
}
}
#endif /* HAVE_DRM_HDR */
static void
gst_kms_sink_set_render_rectangle (GstVideoOverlay * overlay,
gint x, gint y, gint width, gint height)
@ -1148,6 +1451,10 @@ gst_kms_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
if (GST_VIDEO_SINK_WIDTH (self) <= 0 || GST_VIDEO_SINK_HEIGHT (self) <= 0)
goto invalid_size;
#ifdef HAVE_DRM_HDR
gst_kms_sink_set_hdr10_caps (self, caps);
#endif
/* discard dumb buffer pool */
if (self->pool) {
gst_buffer_pool_set_active (self->pool, FALSE);
@ -1665,6 +1972,10 @@ retry_set_plane:
src.w = result.w;
src.h = result.h;
}
#ifdef HAVE_DRM_HDR
/* Send the HDR infoframes if appropriate */
gst_kms_push_hdr_infoframe (self, FALSE);
#endif
GST_TRACE_OBJECT (self,
"drmModeSetPlane at (%i,%i) %ix%i sourcing at (%i,%i) %ix%i",
@ -1981,6 +2292,17 @@ gst_kms_sink_init (GstKMSSink * sink)
sink->poll = gst_poll_new (TRUE);
gst_video_info_init (&sink->vinfo);
sink->skip_vsync = FALSE;
#ifdef HAVE_DRM_HDR
sink->no_infoframe = FALSE;
sink->has_hdr_info = FALSE;
sink->has_sent_hdrif = FALSE;
sink->edidPropID = 0;
sink->hdrPropID = 0;
sink->colorimetry = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
gst_video_mastering_display_info_init (&sink->hdr_minfo);
gst_video_content_light_level_init (&sink->hdr_cll);
#endif
}
static void

View file

@ -27,6 +27,8 @@
#define __GST_KMS_SINK_H__
#include <gst/video/gstvideosink.h>
#include <drm/drm_mode.h>
#include <gst/video/video-hdr.h>
G_BEGIN_DECLS
@ -96,6 +98,18 @@ struct _GstKMSSink {
gboolean is_internal_fd;
gboolean skip_vsync;
#ifdef HAVE_DRM_HDR
/* HDR mastering related structure */
gboolean no_infoframe;
gboolean has_hdr_info;
gboolean has_sent_hdrif;
guint32 edidPropID;
guint32 hdrPropID;
gchar colorimetry;
GstVideoMasteringDisplayInfo hdr_minfo;
GstVideoContentLightLevel hdr_cll;
#endif
};
struct _GstKMSSinkClass {

View file

@ -4,6 +4,7 @@ kmssink_sources = [
'gstkmssink.c',
'gstkmsutils.c',
]
extra_deps = []
if host_system != 'linux'
subdir_done()
@ -12,12 +13,22 @@ endif
libdrm_dep = dependency('libdrm', version : '>= 2.4.98',
required : get_option('kms'),
fallback: ['libdrm', 'ext_libdrm'])
libdrm_hdr_dep = dependency('libdrm', version : '>= 2.4.104',
required : false,
fallback: ['libdrm', 'ext_libdrm'])
mathlib = cc.find_library('m', required : false)
if libdrm_hdr_dep.found() and mathlib.found()
cdata.set('HAVE_DRM_HDR', 1)
kmssink_sources += 'gstkmsedid.c'
endif
if libdrm_dep.found()
gstkmssink = library('gstkms',
kmssink_sources,
c_args : gst_plugins_bad_args,
include_directories : [configinc],
dependencies : [gstbase_dep, gstvideo_dep, gstallocators_dep, libdrm_dep],
dependencies : [gstbase_dep, gstvideo_dep, gstallocators_dep, libdrm_dep, mathlib],
install : true,
install_dir : plugins_install_dir,
)