cccombiner: initial implementation of using CCBuffer helper
authorMatthew Waters <matthew@centricular.com>
Wed, 1 Jun 2022 11:24:41 +0000 (21:24 +1000)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 10 Nov 2022 00:52:14 +0000 (00:52 +0000)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3211>

subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c
subprojects/gst-plugins-bad/ext/closedcaption/ccutils.h
subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.c
subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.h

index c349a20..20d8880 100644 (file)
@@ -434,7 +434,9 @@ struct _CCBuffer
   /* used for tracking which field to write across output buffer boundaries */
   gboolean last_cea608_written_was_field1;
 
+  /* properties */
   GstClockTime max_buffer_time;
+  gboolean output_padding;
 };
 
 G_DEFINE_TYPE (CCBuffer, cc_buffer, G_TYPE_OBJECT);
@@ -453,6 +455,7 @@ cc_buffer_init (CCBuffer * buf)
   buf->cc_data = g_array_new (FALSE, FALSE, sizeof (guint8));
 
   buf->max_buffer_time = DEFAULT_MAX_BUFFER_TIME;
+  buf->output_padding = TRUE;
 }
 
 static void
@@ -588,7 +591,7 @@ push_internal (CCBuffer * buf, const guint8 * cea608_1,
   }
 }
 
-void
+gboolean
 cc_buffer_push_separated (CCBuffer * buf, const guint8 * cea608_1,
     guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len,
     const guint8 * cc_data, guint cc_data_len)
@@ -633,9 +636,11 @@ cc_buffer_push_separated (CCBuffer * buf, const guint8 * cea608_1,
 
   push_internal (buf, cea608_1_copy, cea608_1_len, cea608_2_copy,
       cea608_2_len, cc_data_copy, cc_data_len);
+
+  return cea608_1_len > 0 || cea608_2_len > 0 || cc_data_len > 0;
 }
 
-void
+gboolean
 cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data,
     guint cc_data_len)
 {
@@ -655,11 +660,13 @@ cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data,
 
   if (ccp_offset < 0) {
     GST_WARNING_OBJECT (buf, "Failed to extract cea608 from cc_data");
-    return;
+    return FALSE;
   }
 
   push_internal (buf, cea608_1, cea608_1_len, cea608_2,
       cea608_2_len, &cc_data_copy[ccp_offset], cc_data_len - ccp_offset);
+
+  return cea608_1_len > 0 || cea608_2_len > 0 || cc_data_len - ccp_offset > 0;
 }
 
 void
@@ -768,6 +775,12 @@ cc_buffer_get_out_sizes (CCBuffer * buf, const struct cdp_fps_entry *fps_entry,
     wrote_first = TRUE;
   }
 
+  if (!buf->output_padding && write_cea608_1_size == 0
+      && write_cea608_2_size == 0) {
+    *field1_padding = 0;
+    *field2_padding = 0;
+  }
+
   GST_TRACE_OBJECT (buf, "allocated sizes ccp:%u, cea608-1:%u (pad:%u), "
       "cea608-2:%u (pad:%u)", write_ccp_size, write_cea608_1_size,
       *field1_padding, write_cea608_2_size, *field2_padding);
@@ -905,3 +918,82 @@ cc_buffer_take_cc_data (CCBuffer * buf,
   GST_LOG_OBJECT (buf, "bytes currently stored, cea608-1:%u, cea608-2:%u "
       "ccp:%u", buf->cea608_1->len, buf->cea608_2->len, buf->cc_data->len);
 }
+
+void
+cc_buffer_take_cea608_field1 (CCBuffer * buf,
+    const struct cdp_fps_entry *fps_entry, guint8 * cea608_1,
+    guint * cea608_1_len)
+{
+  guint write_cea608_1_size, field1_padding;
+  guint write_cea608_2_size, field2_padding;
+  guint cc_data_len;
+
+  cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size,
+      &field1_padding, &write_cea608_2_size, &field2_padding, &cc_data_len);
+
+  if (*cea608_1_len < write_cea608_1_size + field1_padding) {
+    GST_WARNING_OBJECT (buf,
+        "Not enough output space to write cea608 field 1 data");
+    *cea608_1_len = 0;
+    return;
+  }
+
+  if (write_cea608_1_size > 0) {
+    memcpy (cea608_1, buf->cea608_1->data, write_cea608_1_size);
+    g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size);
+  }
+  *cea608_1_len = write_cea608_1_size;
+  if (buf->output_padding && field1_padding > 0) {
+    memset (&cea608_1[write_cea608_1_size], 0x80, field1_padding);
+    *cea608_1_len += field1_padding;
+  }
+}
+
+void
+cc_buffer_take_cea608_field2 (CCBuffer * buf,
+    const struct cdp_fps_entry *fps_entry, guint8 * cea608_2,
+    guint * cea608_2_len)
+{
+  guint write_cea608_1_size, field1_padding;
+  guint write_cea608_2_size, field2_padding;
+  guint cc_data_len;
+
+  cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size,
+      &field1_padding, &write_cea608_2_size, &field2_padding, &cc_data_len);
+
+  if (*cea608_2_len < write_cea608_2_size + field2_padding) {
+    GST_WARNING_OBJECT (buf,
+        "Not enough output space to write cea608 field 2 data");
+    *cea608_2_len = 0;
+    return;
+  }
+
+  if (write_cea608_2_size > 0) {
+    memcpy (cea608_2, buf->cea608_2->data, write_cea608_2_size);
+    g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size);
+  }
+  *cea608_2_len = write_cea608_2_size;
+  if (buf->output_padding && field1_padding > 0) {
+    memset (&cea608_2[write_cea608_2_size], 0x80, field2_padding);
+    *cea608_2_len += field2_padding;
+  }
+}
+
+gboolean
+cc_buffer_is_empty (CCBuffer * buf)
+{
+  return buf->cea608_1->len == 0 && buf->cea608_2->len == 0
+      && buf->cc_data->len == 0;
+}
+
+void
+cc_buffer_set_max_buffer_time (CCBuffer * buf, GstClockTime max_time)
+{
+  buf->max_buffer_time = max_time;
+}
+
+void
+cc_buffer_set_output_padding (CCBuffer * buf, gboolean output_padding)
+{
+  buf->output_padding = output_padding;
+}
index c9903bc..c5374a4 100644 (file)
@@ -85,7 +85,7 @@ void            cc_buffer_get_stored_size       (CCBuffer * buf,
                                                  guint * cea608_2_len,
                                                  guint * cc_data_len);
 G_GNUC_INTERNAL
-void            cc_buffer_push_separated        (CCBuffer * buf,
+gboolean        cc_buffer_push_separated        (CCBuffer * buf,
                                                  const guint8 * cea608_1,
                                                  guint cea608_1_len,
                                                  const guint8 * cea608_2,
@@ -93,7 +93,7 @@ void            cc_buffer_push_separated        (CCBuffer * buf,
                                                  const guint8 * cc_data,
                                                  guint cc_data_len);
 G_GNUC_INTERNAL
-void            cc_buffer_push_cc_data          (CCBuffer * buf,
+gboolean        cc_buffer_push_cc_data          (CCBuffer * buf,
                                                  const guint8 * cc_data,
                                                  guint cc_data_len);
 G_GNUC_INTERNAL
@@ -111,10 +111,25 @@ void            cc_buffer_take_separated        (CCBuffer * buf,
                                                  guint8 * cc_data,
                                                  guint * cc_data_len);
 G_GNUC_INTERNAL
+void            cc_buffer_take_cea608_field1    (CCBuffer * buf,
+                                                 const struct cdp_fps_entry * fps_entry,
+                                                 guint8 * cea608_1,
+                                                 guint * cea608_1_len);
+G_GNUC_INTERNAL
+void            cc_buffer_take_cea608_field2    (CCBuffer * buf,
+                                                 const struct cdp_fps_entry * fps_entry,
+                                                 guint8 * cea608_2,
+                                                 guint * cea608_2_len);
+G_GNUC_INTERNAL
+gboolean        cc_buffer_is_empty              (CCBuffer * buf);
+G_GNUC_INTERNAL
 void            cc_buffer_discard               (CCBuffer * buf);
 G_GNUC_INTERNAL
 void            cc_buffer_set_max_buffer_time   (CCBuffer * buf,
                                                  GstClockTime max_time);
+G_GNUC_INTERNAL
+void            cc_buffer_set_output_padding    (CCBuffer * buf,
+                                                 gboolean output_padding);
 
 G_END_DECLS
 
index ddb4796..b484ffe 100644 (file)
@@ -89,21 +89,15 @@ caption_data_clear (CaptionData * data)
 }
 
 static void
-clear_scheduled (CaptionQueueItem * item)
-{
-  gst_buffer_unref (item->buffer);
-}
-
-static void
 gst_cc_combiner_finalize (GObject * object)
 {
   GstCCCombiner *self = GST_CCCOMBINER (object);
 
-  gst_queue_array_free (self->scheduled[0]);
-  gst_queue_array_free (self->scheduled[1]);
   g_array_unref (self->current_frame_captions);
   self->current_frame_captions = NULL;
 
+  gst_clear_object (&self->cc_buffer);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -124,8 +118,9 @@ extract_cdp (GstCCCombiner * self, const guint8 * cdp, guint cdp_len,
 #define CDP_MODE (GST_CC_CDP_MODE_CC_DATA | GST_CC_CDP_MODE_TIME_CODE)
 
 static GstBuffer *
-make_cdp (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len,
-    const struct cdp_fps_entry *fps_entry, const GstVideoTimeCode * tc)
+make_cdp_buffer (GstCCCombiner * self, const guint8 * cc_data,
+    guint cc_data_len, const struct cdp_fps_entry *fps_entry,
+    const GstVideoTimeCode * tc)
 {
   guint len;
   GstBuffer *ret = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
@@ -146,114 +141,67 @@ make_cdp (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len,
 }
 
 static GstBuffer *
-make_padding (GstCCCombiner * self, const GstVideoTimeCode * tc, guint field)
+make_buffer (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len)
 {
-  GstBuffer *ret = NULL;
-
-  switch (self->caption_type) {
-    case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
-    {
-      const guint8 cc_data[6] = { 0xfc, 0x80, 0x80, 0xf9, 0x80, 0x80 };
-
-      ret = make_cdp (self, cc_data, 6, self->cdp_fps_entry, tc);
-      break;
-    }
-    case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
-    {
-      GstMapInfo map;
-
-      ret = gst_buffer_new_allocate (NULL, 3, NULL);
-
-      gst_buffer_map (ret, &map, GST_MAP_WRITE);
-
-      map.data[0] = 0xfc | (field & 0x01);
-      map.data[1] = 0x80;
-      map.data[2] = 0x80;
-
-      gst_buffer_unmap (ret, &map);
-      break;
-    }
-    case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
-    {
-      GstMapInfo map;
-
-      ret = gst_buffer_new_allocate (NULL, 3, NULL);
-
-      gst_buffer_map (ret, &map, GST_MAP_WRITE);
-
-      map.data[0] = field == 0 ? 0x80 : 0x00;
-      map.data[1] = 0x80;
-      map.data[2] = 0x80;
+  GstBuffer *ret = gst_buffer_new_allocate (NULL, cc_data_len, NULL);
+  gst_buffer_fill (ret, 0, cc_data, cc_data_len);
+  return ret;
+}
 
-      gst_buffer_unmap (ret, &map);
-      break;
-    }
-    case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
-    {
-      GstMapInfo map;
+static void
+write_cc_data_to (GstCCCombiner * self, GstBuffer * buffer)
+{
+  GstMapInfo map;
+  guint len;
 
-      ret = gst_buffer_new_allocate (NULL, 2, NULL);
+  gst_buffer_map (buffer, &map, GST_MAP_WRITE);
+  len = map.size;
+  cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, map.data, &len);
+  gst_buffer_unmap (buffer, &map);
+  gst_buffer_set_size (buffer, len);
+}
 
-      gst_buffer_map (ret, &map, GST_MAP_WRITE);
+static void
+prepend_s334_to_cea608 (guint field, guint8 * data, guint * len,
+    guint alloc_len)
+{
+  int i;
 
-      map.data[0] = 0x80;
-      map.data[1] = 0x80;
+  g_assert (*len / 2 * 3 <= alloc_len);
 
-      gst_buffer_unmap (ret, &map);
-      break;
-    }
-    default:
-      break;
+  for (i = *len / 2; i >= 0; i--) {
+    data[i * 3 + 0] = field == 0 ? 0x80 : 0x00;
+    data[i * 3 + 1] = data[i * 2 + 0];
+    data[i * 3 + 2] = data[i * 2 + 1];
   }
-
-  return ret;
 }
 
 static void
-queue_caption (GstCCCombiner * self, GstBuffer * scheduled, guint field)
+take_s334_both_fields (GstCCCombiner * self, GstBuffer * buffer)
 {
-  GstAggregatorPad *caption_pad;
-  CaptionQueueItem item;
-
-  if (self->progressive && field == 1) {
-    gst_buffer_unref (scheduled);
-    return;
+  GstMapInfo out = GST_MAP_INFO_INIT;
+  guint s334_len, cc_data_len, i;
+
+  gst_buffer_map (buffer, &out, GST_MAP_READWRITE);
+
+  cc_data_len = out.size;
+  cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, out.data,
+      &cc_data_len);
+  s334_len = drop_ccp_from_cc_data (out.data, cc_data_len);
+  if (s334_len < 0) {
+    s334_len = 0;
+    goto out;
   }
 
-  caption_pad =
-      GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
-          (self), "caption"));
-
-  g_assert (gst_queue_array_get_length (self->scheduled[field]) <=
-      self->max_scheduled);
-
-  if (gst_queue_array_get_length (self->scheduled[field]) ==
-      self->max_scheduled) {
-    CaptionQueueItem *dropped =
-        gst_queue_array_pop_tail_struct (self->scheduled[field]);
-
-    GST_WARNING_OBJECT (self,
-        "scheduled queue runs too long, dropping %" GST_PTR_FORMAT, dropped);
-
-    gst_element_post_message (GST_ELEMENT_CAST (self),
-        gst_message_new_qos (GST_OBJECT_CAST (self), FALSE,
-            dropped->running_time, dropped->stream_time,
-            GST_BUFFER_PTS (dropped->buffer), GST_BUFFER_DURATION (dropped)));
-
-    gst_buffer_unref (dropped->buffer);
+  for (i = 0; i < s334_len / 3; i++) {
+    guint byte = out.data[i * 3];
+    /* We have to assume a line offset of 0 */
+    out.data[i * 3] = (byte == 0xfc || byte == 0xf8) ? 0x80 : 0x00;
   }
 
-  gst_object_unref (caption_pad);
-
-  item.buffer = scheduled;
-  item.running_time =
-      gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
-      GST_BUFFER_PTS (scheduled));
-  item.stream_time =
-      gst_segment_to_stream_time (&caption_pad->segment, GST_FORMAT_TIME,
-      GST_BUFFER_PTS (scheduled));
-
-  gst_queue_array_push_tail_struct (self->scheduled[field], &item);
+out:
+  gst_buffer_unmap (buffer, &out);
+  gst_buffer_set_size (buffer, s334_len);
 }
 
 static void
@@ -262,42 +210,10 @@ schedule_cdp (GstCCCombiner * self, const GstVideoTimeCode * tc,
 {
   guint8 cc_data[MAX_CDP_PACKET_LEN];
   guint cc_data_len;
-  gboolean inject = FALSE;
 
   cc_data_len = extract_cdp (self, data, len, cc_data);
-  if (cc_data_len > 0) {
-    guint8 i;
-
-    for (i = 0; i < cc_data_len / 3; i++) {
-      gboolean cc_valid = (cc_data[i * 3] & 0x04) == 0x04;
-      guint8 cc_type = cc_data[i * 3] & 0x03;
-
-      if (!cc_valid)
-        continue;
-
-      if (cc_type == 0x00 || cc_type == 0x01) {
-        if (cc_data[i * 3 + 1] != 0x80 || cc_data[i * 3 + 2] != 0x80) {
-          inject = TRUE;
-          break;
-        }
-        continue;
-      } else {
-        inject = TRUE;
-        break;
-      }
-    }
-  }
-
-  if (inject) {
-    GstBuffer *buf =
-        make_cdp (self, cc_data, cc_data_len, self->cdp_fps_entry, tc);
-
-    /* We only set those for QoS reporting purposes */
-    GST_BUFFER_PTS (buf) = pts;
-    GST_BUFFER_DURATION (buf) = duration;
-
-    queue_caption (self, buf, 0);
-  }
+  if (cc_buffer_push_cc_data (self->cc_buffer, cc_data, cc_data_len))
+    self->current_scheduled++;
 }
 
 static void
@@ -319,134 +235,37 @@ schedule_cea608_s334_1a (GstCCCombiner * self, guint8 * data, guint len,
       if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
         continue;
 
-      field0_data[field0_len++] = data[i * 3];
       field0_data[field0_len++] = data[i * 3 + 1];
       field0_data[field0_len++] = data[i * 3 + 2];
     } else {
       if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
         continue;
 
-      field1_data[field1_len++] = data[i * 3];
       field1_data[field1_len++] = data[i * 3 + 1];
       field1_data[field1_len++] = data[i * 3 + 2];
     }
   }
 
-  if (field0_len > 0) {
-    GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
-
-    gst_buffer_fill (buf, 0, field0_data, field0_len);
-    GST_BUFFER_PTS (buf) = pts;
-    GST_BUFFER_DURATION (buf) = duration;
-
-    queue_caption (self, buf, 0);
-  }
-
-  if (field1_len > 0) {
-    GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
-
-    gst_buffer_fill (buf, 0, field1_data, field1_len);
-    GST_BUFFER_PTS (buf) = pts;
-    GST_BUFFER_DURATION (buf) = duration;
-
-    queue_caption (self, buf, 1);
-  }
+  if (cc_buffer_push_separated (self->cc_buffer, field0_data, field0_len,
+          field1_data, field1_len, NULL, 0))
+    self->current_scheduled++;
 }
 
 static void
 schedule_cea708_raw (GstCCCombiner * self, guint8 * data, guint len,
     GstClockTime pts, GstClockTime duration)
 {
-  guint8 field0_data[MAX_CDP_PACKET_LEN], field1_data[3];
-  guint field0_len = 0, field1_len = 0;
-  guint i;
-  gboolean started_ccp = FALSE;
-
-  if (len % 3 != 0) {
-    GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
-        "of 3", len);
-    len = len - (len % 3);
-  }
-
-  for (i = 0; i < len / 3; i++) {
-    gboolean cc_valid = (data[i * 3] & 0x04) == 0x04;
-    guint8 cc_type = data[i * 3] & 0x03;
-
-    if (!started_ccp) {
-      if (cc_type == 0x00) {
-        if (!cc_valid)
-          continue;
-
-        if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
-          continue;
-
-        field0_data[field0_len++] = data[i * 3];
-        field0_data[field0_len++] = data[i * 3 + 1];
-        field0_data[field0_len++] = data[i * 3 + 2];
-      } else if (cc_type == 0x01) {
-        if (!cc_valid)
-          continue;
-
-        if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
-          continue;
-
-        field1_data[field1_len++] = data[i * 3];
-        field1_data[field1_len++] = data[i * 3 + 1];
-        field1_data[field1_len++] = data[i * 3 + 2];
-      }
-
-      continue;
-    }
-
-    if (cc_type & 0x10)
-      started_ccp = TRUE;
-
-    if (!cc_valid)
-      continue;
-
-    if (cc_type == 0x00 || cc_type == 0x01)
-      continue;
-
-    field0_data[field0_len++] = data[i * 3];
-    field0_data[field0_len++] = data[i * 3 + 1];
-    field0_data[field0_len++] = data[i * 3 + 2];
-  }
-
-  if (field0_len > 0) {
-    GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
-
-    gst_buffer_fill (buf, 0, field0_data, field0_len);
-    GST_BUFFER_PTS (buf) = pts;
-    GST_BUFFER_DURATION (buf) = duration;
-
-    queue_caption (self, buf, 0);
-  }
-
-  if (field1_len > 0) {
-    GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
-
-    gst_buffer_fill (buf, 0, field1_data, field1_len);
-    GST_BUFFER_PTS (buf) = pts;
-    GST_BUFFER_DURATION (buf) = duration;
-
-    queue_caption (self, buf, 1);
-  }
+  if (cc_buffer_push_cc_data (self->cc_buffer, data, len))
+    self->current_scheduled++;
 }
 
 static void
-schedule_cea608_raw (GstCCCombiner * self, guint8 * data, guint len,
-    GstBuffer * buffer)
+schedule_cea608_raw (GstCCCombiner * self, guint8 * data, guint len)
 {
-  if (len < 2) {
-    return;
-  }
-
-  if (data[0] != 0x80 || data[1] != 0x80) {
-    queue_caption (self, gst_buffer_ref (buffer), 0);
-  }
+  if (cc_buffer_push_separated (self->cc_buffer, data, len, NULL, 0, NULL, 0))
+    self->current_scheduled++;
 }
 
-
 static void
 schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
     const GstVideoTimeCode * tc)
@@ -457,6 +276,34 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
   pts = GST_BUFFER_PTS (caption_buf);
   duration = GST_BUFFER_DURATION (caption_buf);
 
+  if (self->current_scheduled + 1 >= self->max_scheduled) {
+    GstClockTime stream_time, running_time;
+    GstAggregatorPad *caption_pad;
+
+    caption_pad =
+        GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
+            (self), "caption"));
+
+    GST_WARNING_OBJECT (self,
+        "scheduled queue runs too long, discarding stored");
+
+    running_time =
+        gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
+        pts);
+    stream_time =
+        gst_segment_to_stream_time (&caption_pad->segment, GST_FORMAT_TIME,
+        pts);
+
+    gst_element_post_message (GST_ELEMENT_CAST (self),
+        gst_message_new_qos (GST_OBJECT_CAST (self), FALSE,
+            running_time, stream_time, pts, duration));
+
+    cc_buffer_discard (self->cc_buffer);
+    self->current_scheduled = 0;
+
+    gst_clear_object (&caption_pad);
+  }
+
   gst_buffer_map (caption_buf, &map, GST_MAP_READ);
 
   switch (self->caption_type) {
@@ -470,7 +317,7 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
       schedule_cea608_s334_1a (self, map.data, map.size, pts, duration);
       break;
     case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
-      schedule_cea608_raw (self, map.data, map.size, caption_buf);
+      schedule_cea608_raw (self, map.data, map.size);
       break;
     default:
       break;
@@ -480,64 +327,119 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
 }
 
 static void
-dequeue_caption_one_field (GstCCCombiner * self, const GstVideoTimeCode * tc,
-    guint field, gboolean drain)
-{
-  CaptionQueueItem *scheduled;
-  CaptionData caption_data;
-
-  if ((scheduled = gst_queue_array_pop_head_struct (self->scheduled[field]))) {
-    caption_data.buffer = scheduled->buffer;
-    caption_data.caption_type = self->caption_type;
-    g_array_append_val (self->current_frame_captions, caption_data);
-  } else if (!drain && self->output_padding) {
-    caption_data.caption_type = self->caption_type;
-    caption_data.buffer = make_padding (self, tc, field);
-    g_array_append_val (self->current_frame_captions, caption_data);
-  }
-}
-
-static void
-dequeue_caption_both_fields (GstCCCombiner * self, const GstVideoTimeCode * tc,
-    gboolean drain)
+dequeue_caption (GstCCCombiner * self, GstVideoTimeCode * tc, gboolean drain)
 {
-  CaptionQueueItem *field0_scheduled, *field1_scheduled;
-  GstBuffer *field0_buffer = NULL, *field1_buffer = NULL;
+  guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
+  guint8 cc_data[MAX_CDP_PACKET_LEN];
+  guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
+  guint cc_data_len = MAX_CDP_PACKET_LEN;
   CaptionData caption_data;
 
-  field0_scheduled = gst_queue_array_pop_head_struct (self->scheduled[0]);
-  field1_scheduled = gst_queue_array_pop_head_struct (self->scheduled[1]);
+  g_assert (self->current_frame_captions->len == 0);
 
-  if (drain && !field0_scheduled && !field1_scheduled) {
+  if (drain && cc_buffer_is_empty (self->cc_buffer))
     return;
-  }
 
-  if (field0_scheduled) {
-    field0_buffer = field0_scheduled->buffer;
-  } else if (self->output_padding) {
-    field0_buffer = make_padding (self, tc, 0);
-  }
-
-  if (field1_scheduled) {
-    field1_buffer = field1_scheduled->buffer;
-  } else if (self->output_padding) {
-    field1_buffer = make_padding (self, tc, 1);
-  }
-
-  if (field0_buffer || field1_buffer) {
-    if (field0_buffer && field1_buffer) {
-      caption_data.buffer = gst_buffer_append (field0_buffer, field1_buffer);
-    } else if (field0_buffer) {
-      caption_data.buffer = field0_buffer;
-    } else if (field1_buffer) {
-      caption_data.buffer = field1_buffer;
-    } else {
-      g_assert_not_reached ();
+  caption_data.caption_type = self->caption_type;
+  switch (self->caption_type) {
+    case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
+    {
+      /* Only relevant in alternate and mixed mode, no need to look at the caps */
+      if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
+              GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
+        if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
+          cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, cc_data,
+              &cc_data_len);
+          caption_data.buffer =
+              make_cdp_buffer (self, cc_data, cc_data_len, self->cdp_fps_entry,
+              tc);
+          g_array_append_val (self->current_frame_captions, caption_data);
+        }
+      } else {
+        cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, cc_data,
+            &cc_data_len);
+        caption_data.buffer =
+            make_cdp_buffer (self, cc_data, cc_data_len, self->cdp_fps_entry,
+            tc);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      }
+      break;
     }
-
-    caption_data.caption_type = self->caption_type;
-
-    g_array_append_val (self->current_frame_captions, caption_data);
+    case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
+    {
+      /* Only relevant in alternate and mixed mode, no need to look at the caps */
+      if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
+              GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
+        if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
+          caption_data.buffer =
+              gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
+          write_cc_data_to (self, caption_data.buffer);
+          g_array_append_val (self->current_frame_captions, caption_data);
+        }
+      } else {
+        caption_data.buffer =
+            gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
+        write_cc_data_to (self, caption_data.buffer);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      }
+      break;
+    }
+    case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
+    {
+      if (self->progressive) {
+        cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
+            cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
+            &cc_data_len);
+        prepend_s334_to_cea608 (0, cea608_1, &cea608_1_len, sizeof (cea608_1));
+        caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
+              GST_VIDEO_BUFFER_FLAG_INTERLACED) &&
+          GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
+              GST_VIDEO_BUFFER_FLAG_ONEFIELD)) {
+        cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
+            cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
+            &cc_data_len);
+        if (GST_VIDEO_BUFFER_IS_TOP_FIELD (self->current_video_buffer)) {
+          prepend_s334_to_cea608 (0, cea608_1, &cea608_1_len,
+              sizeof (cea608_1));
+          caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
+        } else {
+          prepend_s334_to_cea608 (1, cea608_2, &cea608_2_len,
+              sizeof (cea608_2));
+          caption_data.buffer = make_buffer (self, cea608_2, cea608_2_len);
+        }
+        g_array_append_val (self->current_frame_captions, caption_data);
+      } else {
+        caption_data.buffer =
+            gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
+        take_s334_both_fields (self, caption_data.buffer);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      }
+      break;
+    }
+    case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
+    {
+      cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
+          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
+          &cc_data_len);
+      if (self->progressive) {
+        caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
+              GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
+        if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
+          caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
+          g_array_append_val (self->current_frame_captions, caption_data);
+        }
+      } else {
+        caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
+        g_array_append_val (self->current_frame_captions, caption_data);
+      }
+      break;
+    }
+    default:
+      break;
   }
 }
 
@@ -688,60 +590,8 @@ gst_cc_combiner_collect_captions (GstCCCombiner * self, gboolean timeout)
     }
   } while (TRUE);
 
-  /* FIXME pad correctly according to fps */
   if (self->schedule) {
-    g_assert (self->current_frame_captions->len == 0);
-
-    switch (self->caption_type) {
-      case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
-      {
-        /* Only relevant in alternate and mixed mode, no need to look at the caps */
-        if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
-                GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
-          if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
-            dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-          }
-        } else {
-          dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-        }
-        break;
-      }
-      case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
-      case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
-      {
-        if (self->progressive) {
-          dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-        } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
-                GST_VIDEO_BUFFER_FLAG_INTERLACED) &&
-            GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
-                GST_VIDEO_BUFFER_FLAG_ONEFIELD)) {
-          if (GST_VIDEO_BUFFER_IS_TOP_FIELD (self->current_video_buffer)) {
-            dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-          } else {
-            dequeue_caption_one_field (self, tc, 1, caption_pad_is_eos);
-          }
-        } else {
-          dequeue_caption_both_fields (self, tc, caption_pad_is_eos);
-        }
-        break;
-      }
-      case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
-      {
-        if (self->progressive) {
-          dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-        } else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
-                GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
-          if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
-            dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-          }
-        } else {
-          dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
-        }
-        break;
-      }
-      default:
-        break;
-    }
+    dequeue_caption (self, tc, caption_pad_is_eos);
   }
 
   gst_aggregator_selected_samples (GST_AGGREGATOR_CAST (self),
@@ -755,6 +605,9 @@ gst_cc_combiner_collect_captions (GstCCCombiner * self, gboolean timeout)
   if (self->current_frame_captions->len > 0) {
     guint i;
 
+    if (self->schedule)
+      self->current_scheduled = MAX (1, self->current_scheduled) - 1;
+
     video_buf = gst_buffer_make_writable (self->current_video_buffer);
     self->current_video_buffer = NULL;
 
@@ -976,6 +829,15 @@ gst_cc_combiner_sink_event (GstAggregator * aggregator,
         self->video_fps_d = fps_d;
 
         self->cdp_fps_entry = cdp_fps_entry_from_fps (fps_n, fps_d);
+        if (!self->cdp_fps_entry || self->cdp_fps_entry->fps_n == 0) {
+          GST_WARNING_OBJECT (self, "Missing valid caption framerate in "
+              "video caps");
+
+          GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (NULL),
+              ("Missing valid caption framerate in video caps"));
+
+          self->cdp_fps_entry = cdp_fps_entry_from_fps (60, 1);
+        }
 
         gst_aggregator_set_src_caps (aggregator, caps);
       }
@@ -1012,8 +874,8 @@ gst_cc_combiner_stop (GstAggregator * aggregator)
   g_array_set_size (self->current_frame_captions, 0);
   self->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
 
-  gst_queue_array_clear (self->scheduled[0]);
-  gst_queue_array_clear (self->scheduled[1]);
+  cc_buffer_discard (self->cc_buffer);
+  self->current_scheduled = 0;
   self->cdp_fps_entry = &null_fps_entry;
 
   return TRUE;
@@ -1035,8 +897,9 @@ gst_cc_combiner_flush (GstAggregator * aggregator)
   src_pad->segment.position = GST_CLOCK_TIME_NONE;
 
   self->cdp_hdr_sequence_cntr = 0;
-  gst_queue_array_clear (self->scheduled[0]);
-  gst_queue_array_clear (self->scheduled[1]);
+
+  cc_buffer_discard (self->cc_buffer);
+  self->current_scheduled = 0;
 
   return GST_FLOW_OK;
 }
@@ -1232,6 +1095,8 @@ gst_cc_combiner_change_state (GstElement * element, GstStateChange transition)
       self->schedule = self->prop_schedule;
       self->max_scheduled = self->prop_max_scheduled;
       self->output_padding = self->prop_output_padding;
+      cc_buffer_set_max_buffer_time (self->cc_buffer, GST_CLOCK_TIME_NONE);
+      cc_buffer_set_output_padding (self->cc_buffer, self->prop_output_padding);
       break;
     default:
       break;
@@ -1422,14 +1287,9 @@ gst_cc_combiner_init (GstCCCombiner * self)
   self->prop_schedule = DEFAULT_SCHEDULE;
   self->prop_max_scheduled = DEFAULT_MAX_SCHEDULED;
   self->prop_output_padding = DEFAULT_OUTPUT_PADDING;
-  self->scheduled[0] =
-      gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
-  self->scheduled[1] =
-      gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
-  gst_queue_array_set_clear_func (self->scheduled[0],
-      (GDestroyNotify) clear_scheduled);
-  gst_queue_array_set_clear_func (self->scheduled[1],
-      (GDestroyNotify) clear_scheduled);
   self->cdp_hdr_sequence_cntr = 0;
   self->cdp_fps_entry = &null_fps_entry;
+
+  self->cc_buffer = cc_buffer_new ();
+  cc_buffer_set_max_buffer_time (self->cc_buffer, GST_CLOCK_TIME_NONE);
 }
index 2b17e41..8d370bc 100644 (file)
@@ -25,6 +25,8 @@
 #include <gst/base/base.h>
 #include <gst/video/video.h>
 
+#include "ccutils.h"
+
 G_BEGIN_DECLS
 #define GST_TYPE_CCCOMBINER \
   (gst_cc_combiner_get_type())
@@ -61,8 +63,9 @@ struct _GstCCCombiner
   gboolean schedule;
   guint max_scheduled;
   gboolean output_padding;
-  /* One queue per field */
-  GstQueueArray *scheduled[2];
+  guint current_scheduled;
+
+  CCBuffer *cc_buffer;
   guint16 cdp_hdr_sequence_cntr;
   const struct cdp_fps_entry *cdp_fps_entry;
 };