decklink: add support for HDR output and input

Supports PQ and HLG static metadata.

Support for HDR is queried from the device and selectively enabled when
supported.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7214>
This commit is contained in:
Matthew Waters 2024-07-22 23:55:48 +10:00 committed by GStreamer Marge Bot
parent 455b6a33b2
commit 0699dd510d
7 changed files with 821 additions and 97 deletions

File diff suppressed because one or more lines are too long

View file

@ -353,12 +353,12 @@ gst_decklink_audio_channels_get_type (void)
return (GType) id; return (GType) id;
} }
#define NTSC 10, 11, false, "bt601" #define NTSC 10, 11, false
#define PAL 12, 11, true, "bt601" #define PAL 12, 11, true
#define NTSC_WS 40, 33, false, "bt601" #define NTSC_WS 40, 33, false
#define PAL_WS 16, 11, true, "bt601" #define PAL_WS 16, 11, true
#define HD 1, 1, true, "bt709" #define HD 1, 1, true
#define UHD 1, 1, true, "bt2020" #define UHD 1, 1, true
static const GstDecklinkMode modes[] = { static const GstDecklinkMode modes[] = {
{bmdModeNTSC, 720, 486, 30000, 1001, true, NTSC}, // default is ntsc {bmdModeNTSC, 720, 486, 30000, 1001, true, NTSC}, // default is ntsc
@ -757,6 +757,25 @@ gst_decklink_video_format_from_type (BMDPixelFormat pf)
return GST_VIDEO_FORMAT_UNKNOWN; return GST_VIDEO_FORMAT_UNKNOWN;
} }
GstVideoColorRange
gst_decklink_pixel_format_to_range (BMDPixelFormat pf)
{
switch (pf) {
case bmdFormat8BitYUV:
case bmdFormat10BitYUV:
case bmdFormat8BitARGB:
case bmdFormat8BitBGRA:
case bmdFormat10BitRGB:
case bmdFormat10BitRGBXLE:
case bmdFormat10BitRGBX:
return GST_VIDEO_COLOR_RANGE_16_235;
case bmdFormat12BitRGB:
case bmdFormat12BitRGBLE:
return GST_VIDEO_COLOR_RANGE_0_255;
default:
return GST_VIDEO_COLOR_RANGE_UNKNOWN;
}
}
const BMDTimecodeFormat const BMDTimecodeFormat
gst_decklink_timecode_format_from_enum (GstDecklinkTimecodeFormat f) gst_decklink_timecode_format_from_enum (GstDecklinkTimecodeFormat f)
@ -868,7 +887,6 @@ gst_decklink_mode_get_structure (GstDecklinkModeEnum e, BMDPixelFormat f,
switch (f) { switch (f) {
case bmdFormat8BitYUV: /* '2vuy' */ case bmdFormat8BitYUV: /* '2vuy' */
gst_structure_set (s, "format", G_TYPE_STRING, "UYVY", gst_structure_set (s, "format", G_TYPE_STRING, "UYVY",
"colorimetry", G_TYPE_STRING, mode->colorimetry,
"chroma-site", G_TYPE_STRING, "mpeg2", NULL); "chroma-site", G_TYPE_STRING, "mpeg2", NULL);
break; break;
case bmdFormat10BitYUV: /* 'v210' */ case bmdFormat10BitYUV: /* 'v210' */
@ -898,30 +916,69 @@ gst_decklink_mode_get_structure (GstDecklinkModeEnum e, BMDPixelFormat f,
} }
GstCaps * GstCaps *
gst_decklink_mode_get_caps (GstDecklinkModeEnum e, BMDPixelFormat f, gst_decklink_mode_get_caps (GstDecklinkModeEnum e, BMDDisplayModeFlags mode_flags, BMDPixelFormat f,
gboolean input) BMDDynamicRange dynamic_range, gboolean input)
{ {
GstCaps *caps; GstCaps *caps;
GstStructure *generic;
const char *format;
caps = gst_caps_new_empty (); caps = gst_caps_new_empty ();
caps = generic = gst_decklink_mode_get_structure (e, f, input);
gst_caps_merge_structure (caps, gst_decklink_mode_get_structure (e, f, format = gst_structure_get_string (generic, "format");
input));
if (g_strcmp0 (format, "UYVY") == 0 || g_strcmp0 (format, "v210") == 0) {
if (mode_flags & bmdDisplayModeColorspaceRec601) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt601", NULL);
caps = gst_caps_merge_structure (caps, s);
}
if (mode_flags & bmdDisplayModeColorspaceRec709) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt709", NULL);
caps = gst_caps_merge_structure (caps, s);
}
if (mode_flags & bmdDisplayModeColorspaceRec2020) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2020", NULL);
caps = gst_caps_merge_structure (caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticPQ) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-pq", NULL);
caps = gst_caps_merge_structure (caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticHLG) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-hlg", NULL);
caps = gst_caps_merge_structure (caps, s);
}
} else {
caps = gst_caps_merge_structure (caps, generic);
}
return caps; return caps;
} }
GstCaps * GstCaps *
gst_decklink_mode_get_caps_all_formats (GstDecklinkModeEnum e, gboolean input) gst_decklink_mode_get_caps_all_formats (GstDecklinkModeEnum e,
BMDDisplayModeFlags mode_flags, BMDDynamicRange dynamic_range,
gboolean input)
{ {
GstCaps *caps; GstCaps *caps;
guint i; guint i;
caps = gst_caps_new_empty (); caps = gst_caps_new_empty ();
for (i = 1; i < G_N_ELEMENTS (formats); i++) for (i = 1; i < G_N_ELEMENTS (formats); i++) {
caps = GstCaps *format_caps =
gst_caps_merge_structure (caps, gst_decklink_mode_get_structure (e, gst_decklink_mode_get_caps (e, mode_flags, formats[i].format,
formats[i].format, input)); dynamic_range, input);
caps = gst_caps_merge (caps, format_caps);
}
return caps; return caps;
} }
@ -931,12 +988,19 @@ gst_decklink_pixel_format_get_caps (BMDPixelFormat f, gboolean input)
{ {
int i; int i;
GstCaps *caps; GstCaps *caps;
GstStructure *s; BMDDisplayModeFlags mode_flags =
bmdDisplayModeColorspaceRec601 | bmdDisplayModeColorspaceRec709 |
bmdDisplayModeColorspaceRec2020;
BMDDynamicRange dynamic_range =
(BMDDynamicRange) (bmdDynamicRangeSDR | bmdDynamicRangeHDRStaticPQ |
bmdDynamicRangeHDRStaticHLG);
caps = gst_caps_new_empty (); caps = gst_caps_new_empty ();
for (i = 1; i < (int) G_N_ELEMENTS (modes); i++) { for (i = 1; i < (int) G_N_ELEMENTS (modes); i++) {
s = gst_decklink_mode_get_structure ((GstDecklinkModeEnum) i, f, input); GstCaps *format_caps =
caps = gst_caps_merge_structure (caps, s); gst_decklink_mode_get_caps ((GstDecklinkModeEnum) i, mode_flags, f,
dynamic_range, input);
caps = gst_caps_merge (caps, format_caps);
} }
return caps; return caps;
@ -952,8 +1016,8 @@ gst_decklink_mode_get_template_caps (gboolean input)
for (i = 1; i < (int) G_N_ELEMENTS (modes); i++) for (i = 1; i < (int) G_N_ELEMENTS (modes); i++)
caps = caps =
gst_caps_merge (caps, gst_caps_merge (caps,
gst_decklink_mode_get_caps_all_formats ((GstDecklinkModeEnum) i, gst_decklink_mode_get_caps_all_formats ((GstDecklinkModeEnum) i, -1,
input)); (BMDDynamicRange) -1, input));
return caps; return caps;
} }
@ -962,6 +1026,9 @@ const GstDecklinkMode *
gst_decklink_find_mode_and_format_for_caps (GstCaps * caps, gst_decklink_find_mode_and_format_for_caps (GstCaps * caps,
BMDPixelFormat * format) BMDPixelFormat * format)
{ {
BMDDisplayModeFlags mode_flags =
bmdDisplayModeColorspaceRec601 | bmdDisplayModeColorspaceRec709 |
bmdDisplayModeColorspaceRec2020;
int i; int i;
GstCaps *mode_caps; GstCaps *mode_caps;
@ -971,7 +1038,8 @@ gst_decklink_find_mode_and_format_for_caps (GstCaps * caps,
for (i = 1; i < (int) G_N_ELEMENTS (modes); i++) { for (i = 1; i < (int) G_N_ELEMENTS (modes); i++) {
mode_caps = mode_caps =
gst_decklink_mode_get_caps ((GstDecklinkModeEnum) i, *format, FALSE); gst_decklink_mode_get_caps ((GstDecklinkModeEnum) i, mode_flags, *format,
(BMDDynamicRange) -1, FALSE);
if (gst_caps_can_intersect (caps, mode_caps)) { if (gst_caps_can_intersect (caps, mode_caps)) {
gst_caps_unref (mode_caps); gst_caps_unref (mode_caps);
return gst_decklink_get_mode ((GstDecklinkModeEnum) i); return gst_decklink_get_mode ((GstDecklinkModeEnum) i);
@ -1558,12 +1626,18 @@ gst_decklink_com_thread (gpointer data)
static GOnce devices_once = G_ONCE_INIT; static GOnce devices_once = G_ONCE_INIT;
static GPtrArray *devices; /* array of Device */ static GPtrArray *devices; /* array of Device */
enum SupportedFlags {
SUPPORT_NONE = 0,
SUPPORT_FORMAT_DETECTION = (1 << 0),
SUPPORT_HDR = (1 << 1),
SUPPORT_COLORSPACE = (1 << 2),
};
static GstDecklinkDevice * static GstDecklinkDevice *
gst_decklink_device_new (const gchar * model_name, const gchar * display_name, gst_decklink_device_new (const gchar * model_name, const gchar * display_name,
const gchar * serial_number, gint64 persistent_id, const gchar * serial_number, gint64 persistent_id,
gboolean supports_format_detection, GstCaps * video_caps, enum SupportedFlags supported, GstCaps * video_caps, guint max_channels,
guint max_channels, gboolean video, gboolean capture, guint device_number) gboolean video, gboolean capture, guint device_number)
{ {
GstDevice *ret; GstDevice *ret;
gchar *name; gchar *name;
@ -1606,7 +1680,11 @@ gst_decklink_device_new (const gchar * model_name, const gchar * display_name,
if (capture) if (capture)
gst_structure_set (properties, "supports-format-detection", G_TYPE_BOOLEAN, gst_structure_set (properties, "supports-format-detection", G_TYPE_BOOLEAN,
supports_format_detection, NULL); (supported & SUPPORT_FORMAT_DETECTION) != SUPPORT_NONE, NULL);
gst_structure_set (properties, "supports-hdr", G_TYPE_BOOLEAN,
(supported & SUPPORT_HDR) != SUPPORT_NONE, "supports-colorspace",
G_TYPE_BOOLEAN, (supported & SUPPORT_COLORSPACE) != SUPPORT_NONE, NULL);
if (serial_number) if (serial_number)
gst_structure_set (properties, "serial-number", G_TYPE_STRING, gst_structure_set (properties, "serial-number", G_TYPE_STRING,
@ -1679,7 +1757,8 @@ init_devices (gpointer data)
gchar *display_name = NULL; gchar *display_name = NULL;
gchar *serial_number = NULL; gchar *serial_number = NULL;
gint64 persistent_id = 0; gint64 persistent_id = 0;
gboolean supports_format_detection = 0; enum SupportedFlags supported = SUPPORT_NONE;
BMDDynamicRange dynamic_range = (BMDDynamicRange) 0;
gint64 max_channels = 2; gint64 max_channels = 2;
GstCaps *video_input_caps = gst_caps_new_empty (); GstCaps *video_input_caps = gst_caps_new_empty ();
GstCaps *video_output_caps = gst_caps_new_empty (); GstCaps *video_output_caps = gst_caps_new_empty ();
@ -1690,6 +1769,59 @@ init_devices (gpointer data)
g_mutex_init (&dev->output.lock); g_mutex_init (&dev->output.lock);
g_cond_init (&dev->output.cond); g_cond_init (&dev->output.cond);
ret = decklink->QueryInterface (IID_IDeckLinkProfileAttributes,
(void **) &dev->input.attributes);
dev->output.attributes = dev->input.attributes;
if (ret != S_OK) {
GST_WARNING ("selected device does not have attributes interface: "
"0x%08lx", (unsigned long) ret);
} else {
bool tmp_bool = false;
int64_t tmp_int = 2;
int64_t tmp_int_persistent_id = 0;
dev->input.attributes->GetInt (BMDDeckLinkMaximumAudioChannels, &tmp_int);
max_channels = tmp_int;
dev->input.attributes->GetFlag (BMDDeckLinkSupportsInputFormatDetection,
&tmp_bool);
GST_INFO ("device %d supports format detection %u", i, tmp_bool);
if (tmp_bool)
supported = (enum SupportedFlags) ((guint32) supported | SUPPORT_FORMAT_DETECTION);
dev->input.attributes->GetFlag (BMDDeckLinkSupportsColorspaceMetadata,
&tmp_bool);
GST_INFO ("device %d supports Colorspace Metadata %u", i, tmp_bool);
if (tmp_bool)
supported = (enum SupportedFlags) ((guint32) supported | SUPPORT_COLORSPACE);
dev->input.attributes->GetFlag (BMDDeckLinkSupportsHDRMetadata,
&tmp_bool);
GST_INFO ("device %d supports HDR %u", i, tmp_bool);
if (tmp_bool)
supported = (enum SupportedFlags) ((guint32) supported | SUPPORT_HDR);
if (supported & SUPPORT_HDR) {
ret = dev->input.attributes->GetInt (BMDDeckLinkSupportedDynamicRange,
&tmp_int);
if (ret == S_OK)
dynamic_range = (BMDDynamicRange) tmp_int;
}
ret =
dev->input.attributes->GetInt (BMDDeckLinkPersistentID,
&tmp_int_persistent_id);
if (ret == S_OK) {
persistent_id = tmp_int_persistent_id;
dev->output.persistent_id = persistent_id;
dev->input.persistent_id = persistent_id;
GST_DEBUG ("device %d has persistent id %" G_GINT64_FORMAT, i, persistent_id);
} else {
persistent_id = i;
dev->output.persistent_id = i;
dev->input.persistent_id = i;
GST_DEBUG ("device %d does not have persistent id. Value set to %d", i, i);
}
}
ret = decklink->QueryInterface (IID_IDeckLinkInput, ret = decklink->QueryInterface (IID_IDeckLinkInput,
(void **) &dev->input.input); (void **) &dev->input.input);
if (ret != S_OK) { if (ret != S_OK) {
@ -1712,10 +1844,54 @@ init_devices (gpointer data)
mode_enum = mode_enum =
gst_decklink_get_mode_enum_from_bmd (mode->GetDisplayMode ()); gst_decklink_get_mode_enum_from_bmd (mode->GetDisplayMode ());
if (mode_enum != (GstDecklinkModeEnum) - 1) if (mode_enum != (GstDecklinkModeEnum) - 1) {
video_input_caps = GstStructure *generic = gst_decklink_mode_get_generic_structure (mode_enum);
gst_caps_merge_structure (video_input_caps, BMDDisplayModeFlags flags = mode->GetFlags ();
gst_decklink_mode_get_generic_structure (mode_enum));
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec601)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt601",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec709)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt709",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec2020)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2020",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticPQ) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-pq",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticHLG) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-hlg",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
gst_clear_structure (&generic);
}
mode->GetName ((COMSTR_T *) & name); mode->GetName ((COMSTR_T *) & name);
CONVERT_COM_STRING (name); CONVERT_COM_STRING (name);
@ -1759,10 +1935,54 @@ init_devices (gpointer data)
mode_enum = mode_enum =
gst_decklink_get_mode_enum_from_bmd (mode->GetDisplayMode ()); gst_decklink_get_mode_enum_from_bmd (mode->GetDisplayMode ());
if (mode_enum != (GstDecklinkModeEnum) - 1) if (mode_enum != (GstDecklinkModeEnum) - 1) {
video_output_caps = GstStructure *generic = gst_decklink_mode_get_generic_structure (mode_enum);
gst_caps_merge_structure (video_output_caps, BMDDisplayModeFlags flags = mode->GetFlags ();
gst_decklink_mode_get_generic_structure (mode_enum));
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec601)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt601",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec601)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt709",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if ((supported & SUPPORT_COLORSPACE) ||
(flags & bmdDisplayModeColorspaceRec2020)) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2020",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticPQ) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-pq",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
if (dynamic_range & bmdDynamicRangeHDRStaticHLG) {
GstStructure *s = gst_structure_copy (generic);
gst_structure_set (s, "colorimetry", G_TYPE_STRING, "bt2100-hlg",
NULL);
video_input_caps =
gst_caps_merge_structure (video_input_caps, s);
}
gst_clear_structure (&generic);
}
mode->GetName ((COMSTR_T *) & name); mode->GetName ((COMSTR_T *) & name);
CONVERT_COM_STRING (name); CONVERT_COM_STRING (name);
@ -1800,39 +2020,6 @@ init_devices (gpointer data)
} }
} }
ret = decklink->QueryInterface (IID_IDeckLinkProfileAttributes,
(void **) &dev->input.attributes);
dev->output.attributes = dev->input.attributes;
if (ret != S_OK) {
GST_WARNING ("selected device does not have attributes interface: "
"0x%08lx", (unsigned long) ret);
} else {
bool tmp_bool = false;
int64_t tmp_int = 2;
int64_t tmp_int_persistent_id = 0;
dev->input.attributes->GetInt (BMDDeckLinkMaximumAudioChannels, &tmp_int);
dev->input.attributes->GetFlag (BMDDeckLinkSupportsInputFormatDetection,
&tmp_bool);
supports_format_detection = tmp_bool;
max_channels = tmp_int;
ret =
dev->input.attributes->GetInt (BMDDeckLinkPersistentID,
&tmp_int_persistent_id);
if (ret == S_OK) {
persistent_id = tmp_int_persistent_id;
dev->output.persistent_id = persistent_id;
dev->input.persistent_id = persistent_id;
GST_DEBUG ("device %d has persistent id %" G_GINT64_FORMAT, i, persistent_id);
} else {
persistent_id = i;
dev->output.persistent_id = i;
dev->input.persistent_id = i;
GST_DEBUG ("device %d does not have persistent id. Value set to %d", i, i);
}
}
decklink->GetModelName ((COMSTR_T *) & model_name); decklink->GetModelName ((COMSTR_T *) & model_name);
if (model_name) if (model_name)
CONVERT_COM_STRING (model_name); CONVERT_COM_STRING (model_name);
@ -1843,22 +2030,22 @@ init_devices (gpointer data)
if (capture) { if (capture) {
dev->devices[0] = dev->devices[0] =
gst_decklink_device_new (model_name, display_name, serial_number, gst_decklink_device_new (model_name, display_name, serial_number,
persistent_id, supports_format_detection, video_input_caps, persistent_id, supported, video_input_caps, max_channels, TRUE, TRUE,
max_channels, TRUE, TRUE, i); i);
dev->devices[1] = dev->devices[1] =
gst_decklink_device_new (model_name, display_name, serial_number, gst_decklink_device_new (model_name, display_name, serial_number,
persistent_id, supports_format_detection, video_input_caps, persistent_id, supported, video_input_caps, max_channels, FALSE, TRUE,
max_channels, FALSE, TRUE, i); i);
} }
if (output) { if (output) {
dev->devices[2] = dev->devices[2] =
gst_decklink_device_new (model_name, display_name, serial_number, gst_decklink_device_new (model_name, display_name, serial_number,
persistent_id, supports_format_detection, video_output_caps, persistent_id, supported, video_output_caps, max_channels, TRUE,
max_channels, TRUE, FALSE, i); FALSE, i);
dev->devices[3] = dev->devices[3] =
gst_decklink_device_new (model_name, display_name, serial_number, gst_decklink_device_new (model_name, display_name, serial_number,
persistent_id, supports_format_detection, video_output_caps, persistent_id, supported, video_output_caps, max_channels, FALSE,
max_channels, FALSE, FALSE, i); FALSE, i);
} }
if (model_name) if (model_name)

View file

@ -50,6 +50,11 @@
::MultiByteToWideChar(CP_ACP, 0, (char*)_s, -1, s, _length); \ ::MultiByteToWideChar(CP_ACP, 0, (char*)_s, -1, s, _length); \
g_free(_s); \ g_free(_s); \
} G_STMT_END } G_STMT_END
#define REFIID_FORMAT "08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
#define REFIID_ARGS(_id) \
(guint32) (_id).Data1, (_id).Data2, (_id).Data3, \
(_id).Data4[0], (_id).Data4[1], (_id).Data4[2], (_id).Data4[3], \
(_id).Data4[4], (_id).Data4[5], (_id).Data4[6], (_id).Data4[7]
#elif defined(__APPLE__) #elif defined(__APPLE__)
#include "osx/DeckLinkAPI.h" #include "osx/DeckLinkAPI.h"
@ -70,6 +75,12 @@
g_free(_s); \ g_free(_s); \
} G_STMT_END } G_STMT_END
#define WINAPI #define WINAPI
#define REFIID_FORMAT "02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X"
#define REFIID_ARGS(_id) \
(_id).byte0, (_id).byte1, (_id).byte2, (_id).byte3, \
(_id).byte4, (_id).byte5, (_id).byte6, (_id).byte7, \
(_id).byte8, (_id).byte9, (_id).byte10, (_id).byte11, \
(_id).byte12, (_id).byte13, (_id).byte14, (_id).byte15
#else /* Linux */ #else /* Linux */
#include "linux/DeckLinkAPI.h" #include "linux/DeckLinkAPI.h"
@ -79,6 +90,12 @@
/* While this is a const char*, the string still has to be freed */ /* While this is a const char*, the string still has to be freed */
#define FREE_COM_STRING(s) free(s); #define FREE_COM_STRING(s) free(s);
#define WINAPI #define WINAPI
#define REFIID_FORMAT "02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X"
#define REFIID_ARGS(_id) \
(_id).byte0, (_id).byte1, (_id).byte2, (_id).byte3, \
(_id).byte4, (_id).byte5, (_id).byte6, (_id).byte7, \
(_id).byte8, (_id).byte9, (_id).byte10, (_id).byte11, \
(_id).byte12, (_id).byte13, (_id).byte14, (_id).byte15
#endif /* G_OS_WIN32 */ #endif /* G_OS_WIN32 */
void decklink_element_init (GstPlugin * plugin); void decklink_element_init (GstPlugin * plugin);
@ -395,6 +412,7 @@ enum _BMDKeyerMode
}; };
const BMDPixelFormat gst_decklink_pixel_format_from_type (GstDecklinkVideoFormat t); const BMDPixelFormat gst_decklink_pixel_format_from_type (GstDecklinkVideoFormat t);
GstVideoColorRange gst_decklink_pixel_format_to_range (BMDPixelFormat pf);
const gint gst_decklink_bpp_from_type (GstDecklinkVideoFormat t); const gint gst_decklink_bpp_from_type (GstDecklinkVideoFormat t);
const GstDecklinkVideoFormat gst_decklink_type_from_video_format (GstVideoFormat f); const GstDecklinkVideoFormat gst_decklink_type_from_video_format (GstVideoFormat f);
GstVideoFormat gst_decklink_video_format_from_type (BMDPixelFormat pf); GstVideoFormat gst_decklink_video_format_from_type (BMDPixelFormat pf);
@ -416,13 +434,12 @@ struct _GstDecklinkMode {
int par_n; int par_n;
int par_d; int par_d;
gboolean tff; gboolean tff;
const gchar *colorimetry;
}; };
const GstDecklinkMode * gst_decklink_get_mode (GstDecklinkModeEnum e); const GstDecklinkMode * gst_decklink_get_mode (GstDecklinkModeEnum e);
const GstDecklinkModeEnum gst_decklink_get_mode_enum_from_bmd (BMDDisplayMode mode); const GstDecklinkModeEnum gst_decklink_get_mode_enum_from_bmd (BMDDisplayMode mode);
const BMDVideoConnection gst_decklink_get_connection (GstDecklinkConnectionEnum e); const BMDVideoConnection gst_decklink_get_connection (GstDecklinkConnectionEnum e);
GstCaps * gst_decklink_mode_get_caps (GstDecklinkModeEnum e, BMDPixelFormat f, gboolean input); GstCaps * gst_decklink_mode_get_caps (GstDecklinkModeEnum e, BMDDisplayModeFlags flags, BMDPixelFormat f, BMDDynamicRange dynamic_range, gboolean input);
GstCaps * gst_decklink_mode_get_template_caps (gboolean input); GstCaps * gst_decklink_mode_get_template_caps (gboolean input);
typedef struct _GstDecklinkOutput GstDecklinkOutput; typedef struct _GstDecklinkOutput GstDecklinkOutput;
@ -494,7 +511,7 @@ void gst_decklink_release_nth_input (gint n, gint64 persistent_id
const GstDecklinkMode * gst_decklink_find_mode_for_caps (GstCaps * caps); const GstDecklinkMode * gst_decklink_find_mode_for_caps (GstCaps * caps);
const GstDecklinkMode * gst_decklink_find_mode_and_format_for_caps (GstCaps * caps, BMDPixelFormat * format); const GstDecklinkMode * gst_decklink_find_mode_and_format_for_caps (GstCaps * caps, BMDPixelFormat * format);
GstCaps * gst_decklink_mode_get_caps_all_formats (GstDecklinkModeEnum e, gboolean input); GstCaps * gst_decklink_mode_get_caps_all_formats (GstDecklinkModeEnum e, BMDDisplayModeFlags flags, BMDDynamicRange dynamic_range, gboolean input);
GstCaps * gst_decklink_pixel_format_get_caps (BMDPixelFormat f, gboolean input); GstCaps * gst_decklink_pixel_format_get_caps (BMDPixelFormat f, gboolean input);
#define GST_TYPE_DECKLINK_DEVICE gst_decklink_device_get_type() #define GST_TYPE_DECKLINK_DEVICE gst_decklink_device_get_type()

View file

@ -216,7 +216,7 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID *) virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *)
{ {
return E_NOINTERFACE; return E_NOINTERFACE;
} }
@ -253,21 +253,46 @@ private:
} }
}; };
class GstDecklinkVideoFrame:public IDeckLinkVideoFrame class GstDecklinkVideoFrame:public IDeckLinkVideoFrame, public IDeckLinkVideoFrameMetadataExtensions
{ {
public: public:
GstDecklinkVideoFrame (GstVideoFrame * frame): GstDecklinkVideoFrame (GstVideoFrame * frame):
running_time(0), running_time_duration(0), sync_buffer(0), m_frame(0), running_time(0), running_time_duration(0), sync_buffer(0), m_frame(0),
m_dframe (0), m_ancillary (0), m_timecode (0), m_refcount (1) have_light_level(FALSE), have_mastering_info(FALSE), m_dframe (0),
m_ancillary (0), m_timecode (0), m_refcount (1)
{ {
m_frame = g_new0 (GstVideoFrame, 1); m_frame = g_new0 (GstVideoFrame, 1);
*m_frame = *frame; *m_frame = *frame;
memset (&light_level, 0, sizeof (light_level));
memset (&mastering_info, 0, sizeof (mastering_info));
memset (&colorimetry, 0, sizeof (colorimetry));
} }
GstDecklinkVideoFrame (IDeckLinkMutableVideoFrame * dframe): GstDecklinkVideoFrame (IDeckLinkMutableVideoFrame * dframe):
running_time(0), running_time_duration(0), sync_buffer(0), m_frame(0), running_time(0), running_time_duration(0), sync_buffer(0), m_frame(0),
m_dframe (dframe), m_ancillary (0), m_timecode (0), m_refcount (1) have_light_level(FALSE), have_mastering_info(FALSE), m_dframe (dframe),
m_ancillary (0), m_timecode (0), m_refcount (1)
{ {
memset (&light_level, 0, sizeof (light_level));
memset (&mastering_info, 0, sizeof (mastering_info));
memset (&colorimetry, 0, sizeof (colorimetry));
}
void SetColorimetry (GstVideoColorimetry *colorimetry)
{
this->colorimetry = *colorimetry;
}
void SetLightLevel (GstVideoContentLightLevel * ll)
{
light_level = *ll;
have_light_level = TRUE;
}
void SetMastringInfo (GstVideoMasteringDisplayInfo * mdi)
{
memcpy (&mastering_info, mdi, sizeof (*mdi));
have_mastering_info = TRUE;
} }
virtual long STDMETHODCALLTYPE GetWidth (void) virtual long STDMETHODCALLTYPE GetWidth (void)
@ -305,7 +330,14 @@ public:
} }
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags (void) virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags (void)
{ {
return m_dframe ? m_dframe->GetFlags () : bmdFrameFlagDefault; BMDFrameFlags flags = m_dframe ? m_dframe->GetFlags () : bmdFrameFlagDefault;
if (have_mastering_info || have_light_level ||
colorimetry.transfer == GST_VIDEO_TRANSFER_ARIB_STD_B67) {
flags |= bmdFrameContainsHDRMetadata;
}
return flags;
} }
virtual HRESULT STDMETHODCALLTYPE GetBytes (void **buffer) virtual HRESULT STDMETHODCALLTYPE GetBytes (void **buffer)
{ {
@ -363,11 +395,161 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID *) virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID * ret)
{ {
GST_LOG ("frame queryinterface: %" REFIID_FORMAT, REFIID_ARGS (iid));
if (memcmp (&iid, &IID_IDeckLinkVideoFrameMetadataExtensions, sizeof (iid))
== 0) {
AddRef ();
*ret = (LPVOID *) static_cast<IDeckLinkVideoFrameMetadataExtensions *>(this);
return S_OK;
}
return E_NOINTERFACE; return E_NOINTERFACE;
} }
virtual HRESULT STDMETHODCALLTYPE GetInt (/* in */ BMDDeckLinkFrameMetadataID metadataID, /* out */ int64_t* value)
{
GST_LOG ("frame meta get int for 0x%x", metadataID);
switch (metadataID) {
case bmdDeckLinkFrameMetadataColorspace: {
switch (colorimetry.matrix) {
case GST_VIDEO_COLOR_MATRIX_BT601:
*value = bmdColorspaceRec601;
return S_OK;
case GST_VIDEO_COLOR_MATRIX_BT709:
*value = bmdColorspaceRec709;
return S_OK;
case GST_VIDEO_COLOR_MATRIX_BT2020:
*value = bmdColorspaceRec2020;
return S_OK;
default:
GST_DEBUG ("no mapping from video color matrix 0x%x to BMD", colorimetry.matrix);
return E_INVALIDARG;
}
break;
}
case bmdDeckLinkFrameMetadataHDRElectroOpticalTransferFunc:
switch (colorimetry.transfer) {
case GST_VIDEO_TRANSFER_BT601:
case GST_VIDEO_TRANSFER_BT709:
case GST_VIDEO_TRANSFER_BT2020_10:
if (have_mastering_info && have_mastering_info)
*value = 1;
else
*value = 0;
return S_OK;
case GST_VIDEO_TRANSFER_SMPTE2084:
*value = 2;
return S_OK;
case GST_VIDEO_TRANSFER_ARIB_STD_B67:
*value = 3;
return S_OK;
default:
return E_INVALIDARG;
}
break;
default:
return E_INVALIDARG;
}
}
virtual HRESULT STDMETHODCALLTYPE GetFloat (/* in */ BMDDeckLinkFrameMetadataID metadataID, /* out */ double* value)
{
GST_LOG ("frame meta get float for 0x%x", metadataID);\
switch (metadataID) {
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedX:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[0].x / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedY:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[0].y / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenX:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[1].x / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenY:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[1].y / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueX:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[2].x / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueY:
if (have_mastering_info) {
*value = (double) mastering_info.display_primaries[2].y / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRWhitePointX:
if (have_mastering_info) {
*value = (double) mastering_info.white_point.x / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRWhitePointY:
if (have_mastering_info) {
*value = (double) mastering_info.white_point.y / 50000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRMaxDisplayMasteringLuminance:
if (have_mastering_info) {
*value = (double) mastering_info.max_display_mastering_luminance * 65535.0 / 10000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRMinDisplayMasteringLuminance:
if (have_mastering_info) {
*value = (double) mastering_info.min_display_mastering_luminance * 6.55350 / 10000.0;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRMaximumContentLightLevel:
if (have_light_level) {
*value = (double) light_level.max_content_light_level;
return S_OK;
}
return E_INVALIDARG;
case bmdDeckLinkFrameMetadataHDRMaximumFrameAverageLightLevel:
if (have_light_level) {
*value = (double) light_level.max_frame_average_light_level;
return S_OK;
}
return E_INVALIDARG;
default:
return E_INVALIDARG;
}
}
virtual HRESULT STDMETHODCALLTYPE GetFlag (/* in */ BMDDeckLinkFrameMetadataID metadataID, /* out */ bool* value)
{
GST_LOG ("frame meta get flag for 0x%x", metadataID);
return E_INVALIDARG;
}
virtual HRESULT STDMETHODCALLTYPE GetString (/* in */ BMDDeckLinkFrameMetadataID metadataID, /* out */ COMSTR_T* value)
{
GST_LOG ("frame meta get string for 0x%x", metadataID);
return E_INVALIDARG;
}
virtual HRESULT STDMETHODCALLTYPE GetBytes (/* in */ BMDDeckLinkFrameMetadataID metadataID, /* out */ void* buffer /* optional */, /* in, out */ uint32_t* bufferSize)
{
GST_LOG ("frame meta get bytes for 0x%x", metadataID);
return E_INVALIDARG;
}
virtual ULONG STDMETHODCALLTYPE AddRef (void) virtual ULONG STDMETHODCALLTYPE AddRef (void)
{ {
ULONG ret; ULONG ret;
@ -394,10 +576,15 @@ public:
GstBuffer *sync_buffer; GstBuffer *sync_buffer;
private: private:
GstVideoFrame * m_frame; GstVideoFrame * m_frame;
gboolean have_light_level;
gboolean have_mastering_info;
IDeckLinkMutableVideoFrame *m_dframe; IDeckLinkMutableVideoFrame *m_dframe;
IDeckLinkVideoFrameAncillary *m_ancillary; IDeckLinkVideoFrameAncillary *m_ancillary;
GstDecklinkTimecode *m_timecode; GstDecklinkTimecode *m_timecode;
int m_refcount; int m_refcount;
GstVideoContentLightLevel light_level;
GstVideoMasteringDisplayInfo mastering_info;
GstVideoColorimetry colorimetry;
virtual ~ GstDecklinkVideoFrame () { virtual ~ GstDecklinkVideoFrame () {
if (m_frame) { if (m_frame) {
@ -923,7 +1110,6 @@ gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
if (!gst_video_info_from_caps (&info, caps)) if (!gst_video_info_from_caps (&info, caps))
return FALSE; return FALSE;
g_mutex_lock (&self->output->lock); g_mutex_lock (&self->output->lock);
if (self->output->video_enabled) { if (self->output->video_enabled) {
if (self->info.finfo->format == info.finfo->format && if (self->info.finfo->format == info.finfo->format &&
@ -932,6 +1118,11 @@ gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
// for mode selection below in auto mode // for mode selection below in auto mode
GST_DEBUG_OBJECT (self, "Nothing relevant has changed"); GST_DEBUG_OBJECT (self, "Nothing relevant has changed");
self->info = info; self->info = info;
self->have_light_level =
gst_video_content_light_level_from_caps (&self->light_level, caps);
self->have_mastering_info =
gst_video_mastering_display_info_from_caps (&self->mastering_info,
caps);
g_mutex_unlock (&self->output->lock); g_mutex_unlock (&self->output->lock);
return TRUE; return TRUE;
} else { } else {
@ -1012,6 +1203,10 @@ gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
} }
self->info = info; self->info = info;
self->have_light_level =
gst_video_content_light_level_from_caps (&self->light_level, caps);
self->have_mastering_info =
gst_video_mastering_display_info_from_caps (&self->mastering_info, caps);
g_mutex_lock (&self->output->lock); g_mutex_lock (&self->output->lock);
self->output->mode = mode; self->output->mode = mode;
self->output->video_enabled = TRUE; self->output->video_enabled = TRUE;
@ -1028,6 +1223,52 @@ gst_decklink_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
return TRUE; return TRUE;
} }
static BMDDisplayModeFlags
display_mode_flags (GstDecklinkVideoSink * self, GstDecklinkModeEnum e)
{
BMDDisplayModeFlags display_flags =
bmdDisplayModeColorspaceRec601 | bmdDisplayModeColorspaceRec709 |
bmdDisplayModeColorspaceRec2020;
if (self->output && self->output->output) {
const GstDecklinkMode *gst_mode = gst_decklink_get_mode (e);
IDeckLinkDisplayMode *display_mode = nullptr;
bool supports_colorspace = false;
self->output->attributes->GetFlag (BMDDeckLinkSupportsColorspaceMetadata,
&supports_colorspace);
if (!supports_colorspace) {
self->output->output->GetDisplayMode (gst_mode->mode, &display_mode);
if (display_mode) {
display_flags = display_mode->GetFlags ();
display_mode->Release();
}
}
}
return display_flags;
}
static BMDDynamicRange
device_dynamic_range (GstDecklinkVideoSink * self)
{
BMDDynamicRange range =
(BMDDynamicRange) (bmdDynamicRangeSDR | bmdDynamicRangeHDRStaticPQ |
bmdDynamicRangeHDRStaticHLG);
if (self->output && self->output->attributes) {
gint64 tmp_int = 0;
HRESULT ret =
self->output->attributes->GetInt (BMDDeckLinkSupportedDynamicRange,
&tmp_int);
if (ret == S_OK)
range = (BMDDynamicRange) tmp_int;
}
return range;
}
static GstCaps * static GstCaps *
gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
{ {
@ -1038,15 +1279,21 @@ gst_decklink_video_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
&& self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO) && self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO)
mode_caps = gst_decklink_mode_get_template_caps (FALSE); mode_caps = gst_decklink_mode_get_template_caps (FALSE);
else if (self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO) else if (self->video_format == GST_DECKLINK_VIDEO_FORMAT_AUTO)
mode_caps = gst_decklink_mode_get_caps_all_formats (self->mode, FALSE); mode_caps =
gst_decklink_mode_get_caps_all_formats (self->mode,
display_mode_flags (self, self->mode), device_dynamic_range (self),
FALSE);
else if (self->mode == GST_DECKLINK_MODE_AUTO) else if (self->mode == GST_DECKLINK_MODE_AUTO)
mode_caps = mode_caps =
gst_decklink_pixel_format_get_caps (gst_decklink_pixel_format_from_type gst_decklink_pixel_format_get_caps (gst_decklink_pixel_format_from_type
(self->video_format), FALSE); (self->video_format), FALSE);
else else {
mode_caps = mode_caps =
gst_decklink_mode_get_caps (self->mode, gst_decklink_mode_get_caps (self->mode,
gst_decklink_pixel_format_from_type (self->video_format), FALSE); display_mode_flags (self, self->mode),
gst_decklink_pixel_format_from_type (self->video_format),
device_dynamic_range (self), FALSE);
}
mode_caps = gst_caps_make_writable (mode_caps); mode_caps = gst_caps_make_writable (mode_caps);
/* For output we support any framerate and only really care about timestamps */ /* For output we support any framerate and only really care about timestamps */
gst_caps_map_in_place (mode_caps, reset_framerate, NULL); gst_caps_map_in_place (mode_caps, reset_framerate, NULL);
@ -1654,6 +1901,7 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
const guint8 *indata; const guint8 *indata;
gint i, src_stride, dest_stride, stride; gint i, src_stride, dest_stride, stride;
IDeckLinkMutableVideoFrame *dframe; IDeckLinkMutableVideoFrame *dframe;
GstVideoColorimetry colorimetry;
ret = self->output->output->CreateVideoFrame (self->info.width, ret = self->output->output->CreateVideoFrame (self->info.width,
self->info.height, self->info.stride[0], format, bmdFrameFlagDefault, self->info.height, self->info.stride[0], format, bmdFrameFlagDefault,
@ -1677,13 +1925,16 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
indata += src_stride; indata += src_stride;
outdata += dest_stride; outdata += dest_stride;
} }
colorimetry = vframe.info.colorimetry;
gst_video_frame_unmap (&vframe); gst_video_frame_unmap (&vframe);
// Takes ownership of the frame // Takes ownership of the frame
frame = new GstDecklinkVideoFrame (dframe); frame = new GstDecklinkVideoFrame (dframe);
frame->SetColorimetry (&colorimetry);
} else { } else {
// Takes ownership of the frame // Takes ownership of the frame
frame = new GstDecklinkVideoFrame (&vframe); frame = new GstDecklinkVideoFrame (&vframe);
frame->SetColorimetry (&vframe.info.colorimetry);
} }
tc_meta = gst_buffer_get_video_time_code_meta (buffer); tc_meta = gst_buffer_get_video_time_code_meta (buffer);
@ -1696,6 +1947,11 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
g_free (tc_str); g_free (tc_str);
} }
if (self->have_light_level)
frame->SetLightLevel (&self->light_level);
if (self->have_mastering_info)
frame->SetMastringInfo (&self->mastering_info);
write_vbi (self, buffer, format, frame, tc_meta); write_vbi (self, buffer, format, frame, tc_meta);
frame->running_time = running_time; frame->running_time = running_time;
@ -1739,7 +1995,7 @@ gst_decklink_video_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
GST_OBJECT_UNLOCK (self); GST_OBJECT_UNLOCK (self);
frame = (GstDecklinkVideoFrame *) g_queue_peek_head (self->pending_frames); frame = (GstDecklinkVideoFrame *) g_queue_peek_head (self->pending_frames);
GST_ERROR_OBJECT (self, "attempting preroll"); GST_DEBUG_OBJECT (self, "attempting preroll");
flow_ret = flow_ret =
gst_base_sink_do_preroll (bsink, gst_base_sink_do_preroll (bsink,
GST_MINI_OBJECT_CAST (frame->sync_buffer)); GST_MINI_OBJECT_CAST (frame->sync_buffer));

View file

@ -80,6 +80,11 @@ struct _GstDecklinkVideoSink
gboolean initial_sync; gboolean initial_sync;
GQueue *pending_frames; GQueue *pending_frames;
gboolean have_light_level;
GstVideoContentLightLevel light_level;
gboolean have_mastering_info;
GstVideoMasteringDisplayInfo mastering_info;
}; };
struct _GstDecklinkVideoSinkClass struct _GstDecklinkVideoSinkClass

View file

@ -188,6 +188,11 @@ typedef struct
GstDecklinkModeEnum mode; GstDecklinkModeEnum mode;
BMDPixelFormat format; BMDPixelFormat format;
GstVideoTimeCode *tc; GstVideoTimeCode *tc;
GstVideoColorimetry colorimetry;
gboolean have_light_level;
GstVideoContentLightLevel light_level;
gboolean have_mastering_info;
GstVideoMasteringDisplayInfo mastering_info;
gboolean no_signal; gboolean no_signal;
} CaptureFrame; } CaptureFrame;
@ -828,6 +833,33 @@ gst_decklink_video_src_update_time_mapping (GstDecklinkVideoSrc * self,
} }
} }
static BMDDisplayModeFlags
display_mode_flags (GstDecklinkVideoSrc * self, const GstDecklinkMode * gst_mode,
gboolean fixed)
{
BMDDisplayModeFlags display_flags =
bmdDisplayModeColorspaceRec601 | bmdDisplayModeColorspaceRec709 |
bmdDisplayModeColorspaceRec2020;
if (self->input && self->input->input) {
IDeckLinkDisplayMode *display_mode = nullptr;
bool supports_colorspace = false;
self->input->attributes->GetFlag (BMDDeckLinkSupportsColorspaceMetadata,
&supports_colorspace);
if (!supports_colorspace || fixed) {
self->input->input->GetDisplayMode (gst_mode->mode, &display_mode);
if (display_mode) {
display_flags = display_mode->GetFlags ();
display_mode->Release();
}
}
}
return display_flags;
}
static void static void
gst_decklink_video_src_got_frame (GstElement * element, gst_decklink_video_src_got_frame (GstElement * element,
IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode, IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode,
@ -903,6 +935,8 @@ gst_decklink_video_src_got_frame (GstElement * element,
GstVideoTimeCodeFlags flags = GST_VIDEO_TIME_CODE_FLAGS_NONE; GstVideoTimeCodeFlags flags = GST_VIDEO_TIME_CODE_FLAGS_NONE;
guint field_count = 0; guint field_count = 0;
guint skipped_frames = 0; guint skipped_frames = 0;
IDeckLinkVideoFrameMetadataExtensions *frame_metadata = nullptr;
HRESULT dk_ret;
while (gst_vec_deque_get_length (self->current_frames) >= while (gst_vec_deque_get_length (self->current_frames) >=
self->buffer_size) { self->buffer_size) {
@ -948,6 +982,155 @@ gst_decklink_video_src_got_frame (GstElement * element,
f.mode = mode; f.mode = mode;
f.format = frame->GetPixelFormat (); f.format = frame->GetPixelFormat ();
f.no_signal = no_signal; f.no_signal = no_signal;
bmode = gst_decklink_get_mode (mode);
/* these are defaults for the display mode, metadata may override these */
BMDDisplayModeFlags mode_flags = display_mode_flags (self, bmode, FALSE);
if (mode_flags & bmdDisplayModeColorspaceRec601) {
gst_video_colorimetry_from_string (&f.colorimetry, "bt601");
}
if (mode_flags & bmdDisplayModeColorspaceRec709) {
gst_video_colorimetry_from_string (&f.colorimetry, "bt709");
}
if (mode_flags & bmdDisplayModeColorspaceRec2020) {
gst_video_colorimetry_from_string (&f.colorimetry, "bt2020");
}
f.colorimetry.range = gst_decklink_pixel_format_to_range (f.format);
dk_ret =
frame->QueryInterface (IID_IDeckLinkVideoFrameMetadataExtensions,
(LPVOID *) &frame_metadata);
if (frame_metadata && dk_ret == S_OK) {
gint64 colorspace;
dk_ret =
frame_metadata->GetInt (bmdDeckLinkFrameMetadataColorspace,
&colorspace);
GST_LOG_OBJECT (self, "ret %x colorspace 0x%" G_GINT64_FORMAT,
(gint) dk_ret, colorspace);
if (dk_ret == S_OK) {
switch (colorspace) {
case bmdColorspaceRec601:
f.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
f.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
f.colorimetry.transfer = GST_VIDEO_TRANSFER_BT601;
break;
case bmdColorspaceRec709:
f.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
f.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
f.colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
break;
case bmdColorspaceRec2020:
f.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
f.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
f.colorimetry.transfer = GST_VIDEO_TRANSFER_BT2020_12;
break;
default:
break;
}
}
if (frame->GetFlags () & bmdFrameContainsHDRMetadata) {
double max_cll, max_fll, x, y;
gint64 tf;
f.colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
f.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
f.colorimetry.transfer = GST_VIDEO_TRANSFER_BT2020_12;
dk_ret =
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRMaximumContentLightLevel, &max_cll);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRMaximumFrameAverageLightLevel, &max_fll);
GST_LOG_OBJECT (self, "ret %x maxcll %f maxfll %f", (gint) dk_ret, max_cll,
max_fll);
if (dk_ret == S_OK) {
f.have_light_level = TRUE;
f.light_level.max_content_light_level = (guint16) max_cll;
f.light_level.max_frame_average_light_level = (guint16) max_fll;
}
dk_ret =
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedX, &x);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedY, &y);
f.mastering_info.display_primaries[0].x = (guint16) (x * 50000.0);
f.mastering_info.display_primaries[0].y = (guint16) (y * 50000.0);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenX, &x);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenY, &y);
f.mastering_info.display_primaries[1].x = (guint16) (x * 50000.0);
f.mastering_info.display_primaries[1].y = (guint16) (y * 50000.0);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueX, &x);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueY, &y);
f.mastering_info.display_primaries[2].x = (guint16) (x * 50000.0);
f.mastering_info.display_primaries[2].y = (guint16) (y * 50000.0);
dk_ret |=
frame_metadata->GetFloat (bmdDeckLinkFrameMetadataHDRWhitePointX, &x);
dk_ret |=
frame_metadata->GetFloat (bmdDeckLinkFrameMetadataHDRWhitePointY, &y);
f.mastering_info.white_point.x = (guint16) (x * 50000.0);
f.mastering_info.white_point.y = (guint16) (y * 50000.0);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRMaxDisplayMasteringLuminance, &x);
dk_ret |=
frame_metadata->GetFloat (
bmdDeckLinkFrameMetadataHDRMinDisplayMasteringLuminance, &y);
f.mastering_info.max_display_mastering_luminance = (guint32) (x * 10000.0 / 65535.0);
f.mastering_info.min_display_mastering_luminance = (guint32) (y * 10000.0 / 6.5535);
GST_LOG_OBJECT (self, "ret 0x%x mastering_info "
"R:%u,%u G:%u,%u B:%u,%u W:%u,%u", dk_ret,
f.mastering_info.display_primaries[0].x,
f.mastering_info.display_primaries[0].y,
f.mastering_info.display_primaries[1].x,
f.mastering_info.display_primaries[1].y,
f.mastering_info.display_primaries[2].x,
f.mastering_info.display_primaries[2].y,
f.mastering_info.white_point.x, f.mastering_info.white_point.y);
if (dk_ret == S_OK)
f.have_mastering_info = TRUE;
dk_ret =
frame_metadata->GetInt (
bmdDeckLinkFrameMetadataHDRElectroOpticalTransferFunc, &tf);
GST_LOG_OBJECT (self, "ret %x transfer func 0x%" G_GINT64_FORMAT,
(gint) dk_ret, tf);
if (dk_ret == S_OK) {
/* as specified in CTA 861.3-A */
switch (tf) {
case 0x0: /* traditional gamma, SDR luminance range */
case 0x1: /* traditional gamma, HDR luminance range */
f.colorimetry.transfer = GST_VIDEO_TRANSFER_BT2020_12;
break;
case 0x2: /* PQ */
f.colorimetry.transfer = GST_VIDEO_TRANSFER_SMPTE2084;
break;
case 0x3: /* HLG */
f.colorimetry.transfer = GST_VIDEO_TRANSFER_ARIB_STD_B67;
break;
default:
f.colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
break;
}
}
}
frame_metadata->Release ();
}
frame_metadata = nullptr;
if (dtc != NULL) { if (dtc != NULL) {
uint8_t hours, minutes, seconds, frames; uint8_t hours, minutes, seconds, frames;
HRESULT res; HRESULT res;
@ -960,7 +1143,6 @@ gst_decklink_video_src_got_frame (GstElement * element,
} else { } else {
GST_DEBUG_OBJECT (self, "Got timecode %02d:%02d:%02d:%02d", GST_DEBUG_OBJECT (self, "Got timecode %02d:%02d:%02d:%02d",
hours, minutes, seconds, frames); hours, minutes, seconds, frames);
bmode = gst_decklink_get_mode (mode);
if (bmode->interlaced) if (bmode->interlaced)
flags = flags =
(GstVideoTimeCodeFlags) (flags | (GstVideoTimeCodeFlags) (flags |
@ -1406,6 +1588,37 @@ retry:
} }
} }
if (!gst_video_colorimetry_is_equal (&f.colorimetry,
&self->caps_colorimetry)) {
caps_changed = TRUE;
self->caps_colorimetry = f.colorimetry;
}
if (f.have_light_level != self->caps_have_light_level ||
!gst_video_content_light_level_is_equal (&f.light_level,
&self->caps_light_level)) {
caps_changed = TRUE;
self->caps_have_light_level = f.have_light_level;
if (f.have_light_level) {
self->caps_light_level = f.light_level;
} else {
memset (&self->caps_light_level, 0, sizeof (self->caps_light_level));
}
}
if (f.have_mastering_info != self->caps_have_light_level ||
!gst_video_mastering_display_info_is_equal (&f.mastering_info,
&self->caps_mastering_info)) {
caps_changed = TRUE;
self->caps_have_light_level = f.have_mastering_info;
if (f.have_mastering_info) {
memcpy (&self->caps_mastering_info, &f.mastering_info,
sizeof (f.mastering_info));
} else {
memset (&self->caps_mastering_info, 0, sizeof (self->caps_mastering_info));
}
}
/* 1 ns error can be just a rounding error, so that's OK. The Decklink /* 1 ns error can be just a rounding error, so that's OK. The Decklink
* drivers give us a really steady stream time, so anything above 1 ns can't * drivers give us a really steady stream time, so anything above 1 ns can't
* be a rounding error and is therefore something to worry about */ * be a rounding error and is therefore something to worry about */
@ -1433,11 +1646,27 @@ retry:
g_mutex_unlock (&self->lock); g_mutex_unlock (&self->lock);
if (caps_changed) { if (caps_changed) {
char *colorimetry;
const GstDecklinkMode *gst_mode = gst_decklink_get_mode (f.mode);
self->last_cc_vbi_line = -1; self->last_cc_vbi_line = -1;
self->last_afd_bar_vbi_line = -1; self->last_afd_bar_vbi_line = -1;
self->last_cc_vbi_line_field2 = -1; self->last_cc_vbi_line_field2 = -1;
self->last_afd_bar_vbi_line_field2 = -1; self->last_afd_bar_vbi_line_field2 = -1;
caps = gst_decklink_mode_get_caps (f.mode, f.format, TRUE); GST_LOG_OBJECT (self, "mode flags 0x%x",
display_mode_flags (self, gst_mode, TRUE));
caps = gst_decklink_mode_get_caps (f.mode,
display_mode_flags (self, gst_mode, TRUE), f.format,
bmdDynamicRangeSDR, TRUE);
colorimetry = gst_video_colorimetry_to_string (&self->caps_colorimetry);
if (colorimetry)
gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, colorimetry,
NULL);
g_free (colorimetry);
if (f.have_light_level)
gst_video_content_light_level_add_to_caps (&f.light_level, caps);
if (f.have_mastering_info)
gst_video_mastering_display_info_add_to_caps (&f.mastering_info, caps);
gst_video_info_from_caps (&self->info, caps); gst_video_info_from_caps (&self->info, caps);
gst_base_src_set_caps (GST_BASE_SRC_CAST (bsrc), caps); gst_base_src_set_caps (GST_BASE_SRC_CAST (bsrc), caps);
gst_element_post_message (GST_ELEMENT_CAST (self), gst_element_post_message (GST_ELEMENT_CAST (self),
@ -1481,6 +1710,21 @@ retry:
return flow_ret; return flow_ret;
} }
static BMDDynamicRange
device_dynamic_range (GstDecklinkVideoSrc * self)
{
BMDDynamicRange range = bmdDynamicRangeSDR;
if (self->input && self->input->attributes) {
gint64 tmp_int = 0;
HRESULT ret = self->input->attributes->GetInt (BMDDeckLinkSupportedDynamicRange, &tmp_int);
if (ret == S_OK)
range = (BMDDynamicRange) tmp_int;
}
return range;
}
static GstCaps * static GstCaps *
gst_decklink_video_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter) gst_decklink_video_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
{ {
@ -1488,10 +1732,19 @@ gst_decklink_video_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
GstCaps *caps; GstCaps *caps;
if (self->mode != GST_DECKLINK_MODE_AUTO) { if (self->mode != GST_DECKLINK_MODE_AUTO) {
caps = gst_decklink_mode_get_caps (self->mode, self->caps_format, TRUE); const GstDecklinkMode *gst_mode = gst_decklink_get_mode (self->mode);
} else if (self->caps_mode != GST_DECKLINK_MODE_AUTO) { BMDDynamicRange dynamic_range = device_dynamic_range (self);
caps = caps =
gst_decklink_mode_get_caps (self->caps_mode, self->caps_format, TRUE); gst_decklink_mode_get_caps (self->mode,
display_mode_flags (self, gst_mode, FALSE), self->caps_format,
dynamic_range, TRUE);
} else if (self->caps_mode != GST_DECKLINK_MODE_AUTO) {
const GstDecklinkMode *gst_mode = gst_decklink_get_mode (self->caps_mode);
BMDDynamicRange dynamic_range = device_dynamic_range (self);
caps =
gst_decklink_mode_get_caps (self->caps_mode,
display_mode_flags (self, gst_mode, FALSE), self->caps_format,
dynamic_range, TRUE);
} else { } else {
caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc)); caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (bsrc));
} }

View file

@ -60,6 +60,12 @@ struct _GstDecklinkVideoSrc
GstDecklinkModeEnum caps_mode; GstDecklinkModeEnum caps_mode;
gint aspect_ratio_flag; /* -1 when unknown, 0 not set, 1 set */ gint aspect_ratio_flag; /* -1 when unknown, 0 not set, 1 set */
BMDPixelFormat caps_format; BMDPixelFormat caps_format;
GstVideoColorimetry colorimetry;
GstVideoColorimetry caps_colorimetry;
gboolean caps_have_light_level;
GstVideoContentLightLevel caps_light_level;
gboolean caps_have_mastering_info;
GstVideoMasteringDisplayInfo caps_mastering_info;
GstDecklinkConnectionEnum connection; GstDecklinkConnectionEnum connection;
gint device_number; gint device_number;
gint64 persistent_id; gint64 persistent_id;