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;