decklinkvideosrc: Add support for GstVideoTimeCode
The timecode will be fetched from the decklink source and attached to the
video buffer.
https://bugzilla.gnome.org/show_bug.cgi?id=766419
diff --git a/sys/decklink/gstdecklink.cpp b/sys/decklink/gstdecklink.cpp
index 203fe4e..930e98b 100644
--- a/sys/decklink/gstdecklink.cpp
+++ b/sys/decklink/gstdecklink.cpp
@@ -140,6 +140,33 @@
}
GType
+gst_decklink_timecode_format_get_type (void)
+{
+ static gsize id = 0;
+ static const GEnumValue timecodeformats[] = {
+ {GST_DECKLINK_TIMECODE_FORMAT_RP188VITC1, "bmdTimecodeRP188VITC1",
+ "rp188vitc1"},
+ {GST_DECKLINK_TIMECODE_FORMAT_RP188VITC2, "bmdTimecodeRP188VITC2",
+ "rp188vitc2"},
+ {GST_DECKLINK_TIMECODE_FORMAT_RP188LTC, "bmdTimecodeRP188LTC", "rp188ltc"},
+ {GST_DECKLINK_TIMECODE_FORMAT_RP188ANY, "bmdTimecodeRP188Any", "rp188any"},
+ {GST_DECKLINK_TIMECODE_FORMAT_VITC, "bmdTimecodeVITC", "vitc"},
+ {GST_DECKLINK_TIMECODE_FORMAT_VITCFIELD2, "bmdTimecodeVITCField2",
+ "vitcfield2"},
+ {GST_DECKLINK_TIMECODE_FORMAT_SERIAL, "bmdTimecodeSerial", "serial"},
+ {0, NULL, NULL}
+ };
+
+ if (g_once_init_enter (&id)) {
+ GType tmp =
+ g_enum_register_static ("GstDecklinkTimecodeFormat", timecodeformats);
+ g_once_init_leave (&id, tmp);
+ }
+
+ return (GType) id;
+}
+
+GType
gst_decklink_audio_connection_get_type (void)
{
static gsize id = 0;
@@ -232,6 +259,22 @@
/* *INDENT-ON* */
};
+static const struct
+{
+ BMDTimecodeFormat format;
+ GstDecklinkTimecodeFormat gstformat;
+} tcformats[] = {
+ /* *INDENT-OFF* */
+ {bmdTimecodeRP188VITC1, GST_DECKLINK_TIMECODE_FORMAT_RP188VITC1},
+ {bmdTimecodeRP188VITC2, GST_DECKLINK_TIMECODE_FORMAT_RP188VITC2},
+ {bmdTimecodeRP188LTC, GST_DECKLINK_TIMECODE_FORMAT_RP188LTC},
+ {bmdTimecodeRP188Any, GST_DECKLINK_TIMECODE_FORMAT_RP188ANY},
+ {bmdTimecodeVITC, GST_DECKLINK_TIMECODE_FORMAT_VITC},
+ {bmdTimecodeVITCField2, GST_DECKLINK_TIMECODE_FORMAT_VITCFIELD2},
+ {bmdTimecodeSerial, GST_DECKLINK_TIMECODE_FORMAT_SERIAL}
+ /* *INDENT-ON* */
+};
+
const GstDecklinkMode *
gst_decklink_get_mode (GstDecklinkModeEnum e)
{
@@ -367,6 +410,25 @@
return GST_DECKLINK_VIDEO_FORMAT_AUTO;
}
+const BMDTimecodeFormat
+gst_decklink_timecode_format_from_enum (GstDecklinkTimecodeFormat f)
+{
+ return tcformats[f].format;
+}
+
+const GstDecklinkTimecodeFormat
+gst_decklink_timecode_format_to_enum (BMDTimecodeFormat f)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tcformats); i++) {
+ if (tcformats[i].format == f)
+ return (GstDecklinkTimecodeFormat) i;
+ }
+ g_assert_not_reached ();
+ return GST_DECKLINK_TIMECODE_FORMAT_RP188ANY;
+}
+
static const BMDVideoConnection connections[] = {
0, /* auto */
bmdVideoConnectionSDI,
@@ -660,7 +722,9 @@
GstElement *videosrc = NULL, *audiosrc = NULL;
void (*got_video_frame) (GstElement * videosrc,
IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode,
- GstClockTime capture_time, GstClockTime capture_duration) = NULL;
+ GstClockTime capture_time, GstClockTime capture_duration, guint hours,
+ guint minutes, guint seconds, guint frames, BMDTimecodeFlags bflags) =
+ NULL;
void (*got_audio_packet) (GstElement * videosrc,
IDeckLinkAudioInputPacket * packet, GstClockTime capture_time,
gboolean discont) = NULL;
@@ -668,7 +732,11 @@
BMDTimeValue capture_time = GST_CLOCK_TIME_NONE, capture_duration =
GST_CLOCK_TIME_NONE;
HRESULT res;
+ IDeckLinkTimecode *dtc;
+ uint8_t hours, minutes, seconds, frames;
+ BMDTimecodeFlags bflags;
+ hours = minutes = seconds = frames = bflags = 0;
if (video_frame == NULL)
goto no_video_frame;
@@ -681,6 +749,35 @@
capture_duration = GST_CLOCK_TIME_NONE;
}
+ if (m_input->videosrc) {
+ /* FIXME: Avoid circularity between gstdecklink.cpp and
+ * gstdecklinkvideosrc.cpp */
+ videosrc = GST_ELEMENT_CAST (gst_object_ref (m_input->videosrc));
+ res =
+ video_frame->
+ GetTimecode (GST_DECKLINK_VIDEO_SRC (videosrc)->timecode_format,
+ &dtc);
+
+ if (res != S_OK) {
+ GST_DEBUG_OBJECT (videosrc, "Failed to get timecode: 0x%08x", res);
+ dtc = NULL;
+ } else {
+ res = dtc->GetComponents (&hours, &minutes, &seconds, &frames);
+ if (res != S_OK) {
+ GST_ERROR ("Could not get components for timecode %p", dtc);
+ hours = 0;
+ minutes = 0;
+ seconds = 0;
+ frames = 0;
+ bflags = 0;
+ } else {
+ GST_DEBUG_OBJECT (videosrc, "Got timecode %02d:%02d:%02d:%02d", hours,
+ minutes, seconds, frames);
+ bflags = dtc->GetFlags ();
+ }
+ }
+ }
+
g_mutex_lock (&m_input->lock);
if (capture_time > (BMDTimeValue) m_input->clock_start_time)
@@ -694,7 +791,6 @@
capture_time = 0;
if (m_input->videosrc) {
- videosrc = GST_ELEMENT_CAST (gst_object_ref (m_input->videosrc));
got_video_frame = m_input->got_video_frame;
}
mode = gst_decklink_get_mode_enum_from_bmd (m_input->mode->mode);
@@ -707,7 +803,8 @@
if (got_video_frame && videosrc) {
got_video_frame (videosrc, video_frame, mode, capture_time,
- capture_duration);
+ capture_duration, (guint8) hours, (guint8) minutes, (guint8) seconds,
+ (guint8) frames, bflags);
}
no_video_frame:
diff --git a/sys/decklink/gstdecklink.h b/sys/decklink/gstdecklink.h
index 6559206..81e9b7c 100644
--- a/sys/decklink/gstdecklink.h
+++ b/sys/decklink/gstdecklink.h
@@ -125,9 +125,23 @@
#define GST_TYPE_DECKLINK_VIDEO_FORMAT (gst_decklink_video_format_get_type ())
GType gst_decklink_video_format_get_type (void);
+typedef enum {
+ GST_DECKLINK_TIMECODE_FORMAT_RP188VITC1, /*bmdTimecodeRP188VITC1 */
+ GST_DECKLINK_TIMECODE_FORMAT_RP188VITC2, /*bmdTimecodeRP188VITC2 */
+ GST_DECKLINK_TIMECODE_FORMAT_RP188LTC, /*bmdTimecodeRP188LTC */
+ GST_DECKLINK_TIMECODE_FORMAT_RP188ANY, /*bmdTimecodeRP188Any */
+ GST_DECKLINK_TIMECODE_FORMAT_VITC, /*bmdTimecodeVITC */
+ GST_DECKLINK_TIMECODE_FORMAT_VITCFIELD2, /*bmdTimecodeVITCField2 */
+ GST_DECKLINK_TIMECODE_FORMAT_SERIAL /* bmdTimecodeSerial */
+} GstDecklinkTimecodeFormat;
+#define GST_TYPE_DECKLINK_TIMECODE_FORMAT (gst_decklink_timecode_format_get_type ())
+GType gst_decklink_timecode_format_get_type (void);
+
const BMDPixelFormat gst_decklink_pixel_format_from_type (GstDecklinkVideoFormat t);
const gint gst_decklink_bpp_from_type (GstDecklinkVideoFormat t);
const GstDecklinkVideoFormat gst_decklink_type_from_video_format (GstVideoFormat f);
+const BMDTimecodeFormat gst_decklink_timecode_format_from_enum (GstDecklinkTimecodeFormat f);
+const GstDecklinkTimecodeFormat gst_decklink_timecode_format_to_enum (BMDTimecodeFormat f);
typedef struct _GstDecklinkMode GstDecklinkMode;
struct _GstDecklinkMode {
@@ -189,7 +203,7 @@
GMutex lock;
/* Set by the video source */
- void (*got_video_frame) (GstElement *videosrc, IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode, GstClockTime capture_time, GstClockTime capture_duration);
+ void (*got_video_frame) (GstElement *videosrc, IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode, GstClockTime capture_time, GstClockTime capture_duration, guint hours, guint minutes, guint seconds, guint frames, BMDTimecodeFlags bflags);
/* Configured mode or NULL */
const GstDecklinkMode *mode;
BMDPixelFormat format;
diff --git a/sys/decklink/gstdecklinkvideosrc.cpp b/sys/decklink/gstdecklinkvideosrc.cpp
index 15ea0c8..90492ae 100644
--- a/sys/decklink/gstdecklinkvideosrc.cpp
+++ b/sys/decklink/gstdecklinkvideosrc.cpp
@@ -40,7 +40,8 @@
PROP_CONNECTION,
PROP_DEVICE_NUMBER,
PROP_BUFFER_SIZE,
- PROP_VIDEO_FORMAT
+ PROP_VIDEO_FORMAT,
+ PROP_TIMECODE_FORMAT
};
typedef struct
@@ -49,6 +50,7 @@
GstClockTime capture_time, capture_duration;
GstDecklinkModeEnum mode;
BMDPixelFormat format;
+ GstVideoTimeCode *tc;
} CaptureFrame;
static void
@@ -169,6 +171,14 @@
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT)));
+ g_object_class_install_property (gobject_class, PROP_TIMECODE_FORMAT,
+ g_param_spec_enum ("timecode-format", "Timecode format",
+ "Timecode format type to use for input",
+ GST_TYPE_DECKLINK_TIMECODE_FORMAT,
+ GST_DECKLINK_TIMECODE_FORMAT_RP188ANY,
+ (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT)));
+
templ_caps = gst_decklink_mode_get_template_caps ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, templ_caps));
@@ -192,6 +202,7 @@
self->device_number = 0;
self->buffer_size = DEFAULT_BUFFER_SIZE;
self->video_format = GST_DECKLINK_VIDEO_FORMAT_AUTO;
+ self->timecode_format = bmdTimecodeRP188Any;
gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
@@ -245,6 +256,11 @@
break;
}
break;
+ case PROP_TIMECODE_FORMAT:
+ self->timecode_format =
+ gst_decklink_timecode_format_from_enum ((GstDecklinkTimecodeFormat)
+ g_value_get_enum (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -273,6 +289,10 @@
case PROP_VIDEO_FORMAT:
g_value_set_enum (value, self->video_format);
break;
+ case PROP_TIMECODE_FORMAT:
+ g_value_set_enum (value,
+ gst_decklink_timecode_format_to_enum (self->timecode_format));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -493,7 +513,8 @@
static void
gst_decklink_video_src_got_frame (GstElement * element,
IDeckLinkVideoInputFrame * frame, GstDecklinkModeEnum mode,
- GstClockTime capture_time, GstClockTime capture_duration)
+ GstClockTime capture_time, GstClockTime capture_duration, guint hours,
+ guint minutes, guint seconds, guint frames, BMDTimecodeFlags bflags)
{
GstDecklinkVideoSrc *self = GST_DECKLINK_VIDEO_SRC_CAST (element);
@@ -509,6 +530,9 @@
g_mutex_lock (&self->lock);
if (!self->flushing) {
CaptureFrame *f;
+ const GstDecklinkMode *bmode;
+ GstVideoTimeCodeFlags flags = GST_VIDEO_TIME_CODE_FLAGS_NONE;
+ guint field_count = 0;
while (g_queue_get_length (&self->current_frames) >= self->buffer_size) {
f = (CaptureFrame *) g_queue_pop_head (&self->current_frames);
@@ -523,6 +547,24 @@
f->capture_duration = capture_duration;
f->mode = mode;
f->format = frame->GetPixelFormat ();
+ bmode = gst_decklink_get_mode (mode);
+ if (bmode->interlaced) {
+ flags =
+ (GstVideoTimeCodeFlags) (flags |
+ GST_VIDEO_TIME_CODE_FLAGS_INTERLACED);
+ if (bflags & bmdTimecodeFieldMark)
+ field_count = 2;
+ else
+ field_count = 1;
+ }
+ if (bflags & bmdTimecodeIsDropFrame)
+ flags =
+ (GstVideoTimeCodeFlags) (flags |
+ GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME);
+ f->tc =
+ gst_video_time_code_new (bmode->fps_n, bmode->fps_d, NULL, flags, hours,
+ minutes, seconds, frames, field_count);
+
frame->AddRef ();
g_queue_push_tail (&self->current_frames, f);
g_cond_signal (&self->cond);
@@ -556,7 +598,6 @@
GST_DEBUG_OBJECT (self, "Flushing");
return GST_FLOW_FLUSHING;
}
-
// If we're not flushing, we should have a valid frame from the queue
g_assert (f != NULL);
@@ -620,6 +661,7 @@
GST_BUFFER_TIMESTAMP (*buffer) = f->capture_time;
GST_BUFFER_DURATION (*buffer) = f->capture_duration;
+ gst_buffer_add_video_time_code_meta (*buffer, f->tc);
GST_DEBUG_OBJECT (self,
"Outputting buffer %p with timestamp %" GST_TIME_FORMAT " and duration %"
diff --git a/sys/decklink/gstdecklinkvideosrc.h b/sys/decklink/gstdecklinkvideosrc.h
index 1309364..b1e7351 100644
--- a/sys/decklink/gstdecklinkvideosrc.h
+++ b/sys/decklink/gstdecklinkvideosrc.h
@@ -58,6 +58,7 @@
GstVideoInfo info;
GstDecklinkVideoFormat video_format;
+ BMDTimecodeFormat timecode_format;
GstDecklinkInput *input;