--- /dev/null
+/*
+* 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 */
#include <gst/video/video.h>
#include <gst/video/videooverlay.h>
+#include <gst/video/video-color.h>
#include <gst/allocators/gstdmabuf.h>
#include <drm.h>
#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"
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)
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);
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",
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