closedcaption: move CC buffering to helper object
authorMatthew Waters <matthew@centricular.com>
Wed, 1 Jun 2022 07:47:55 +0000 (17:47 +1000)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 10 Nov 2022 00:52:14 +0000 (00:52 +0000)
Move most of the interesting code from ccconverter to this new helper
object.

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/gstccconverter.c
subprojects/gst-plugins-bad/ext/closedcaption/gstccconverter.h
subprojects/gst-plugins-bad/tests/check/elements/ccconverter.c

index cf1585b..c349a20 100644 (file)
@@ -31,6 +31,8 @@ GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
 
 typedef struct cdp_fps_entry cdp_fps_entry;
 
+#define VAL_OR_0(v) ((v) ? (*(v)) : 0)
+
 static const struct cdp_fps_entry cdp_fps_table[] = {
   {0x1f, 24000, 1001, 25, 22, 3 /* FIXME: alternating max cea608 count! */ },
   {0x2f, 24, 1, 25, 22, 2},
@@ -333,3 +335,573 @@ convert_cea708_cdp_to_cc_data (GstObject * dbg_obj,
   /* skip everything else we don't care about */
   return len;
 }
+
+#define CC_DATA_EXTRACT_TOO_MANY_FIELD1 -2
+#define CC_DATA_EXTRACT_TOO_MANY_FIELD2 -3
+
+static gint
+cc_data_extract_cea608 (const guint8 * cc_data, guint cc_data_len,
+    guint8 * cea608_field1, guint * cea608_field1_len,
+    guint8 * cea608_field2, guint * cea608_field2_len)
+{
+  guint i, field_1_len = 0, field_2_len = 0;
+
+  if (cea608_field1_len) {
+    field_1_len = *cea608_field1_len;
+    *cea608_field1_len = 0;
+  }
+  if (cea608_field2_len) {
+    field_2_len = *cea608_field2_len;
+    *cea608_field2_len = 0;
+  }
+
+  if (cc_data_len % 3 != 0) {
+    GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
+        "of 3", cc_data_len);
+    cc_data_len = cc_data_len - (cc_data_len % 3);
+  }
+
+  for (i = 0; i < cc_data_len / 3; i++) {
+    guint8 byte0 = cc_data[i * 3 + 0];
+    guint8 byte1 = cc_data[i * 3 + 1];
+    guint8 byte2 = cc_data[i * 3 + 2];
+    gboolean cc_valid = (byte0 & 0x04) == 0x04;
+    guint8 cc_type = byte0 & 0x03;
+
+    GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u", byte0, byte1,
+        byte2, cc_valid, (cc_type & 0x2) == 0x2, (cc_type & 0x1) == 0x1);
+
+    if (cc_type == 0x00) {
+      if (!cc_valid)
+        continue;
+
+      if (cea608_field1 && cea608_field1_len) {
+        if (*cea608_field1_len + 2 > field_1_len) {
+          GST_WARNING ("Too many cea608 input bytes %u for field 1",
+              *cea608_field1_len + 2);
+          return CC_DATA_EXTRACT_TOO_MANY_FIELD1;
+        }
+
+        if (byte1 != 0x80 || byte2 != 0x80) {
+          cea608_field1[(*cea608_field1_len)++] = byte1;
+          cea608_field1[(*cea608_field1_len)++] = byte2;
+        }
+      }
+    } else if (cc_type == 0x01) {
+      if (!cc_valid)
+        continue;
+
+      if (cea608_field2 && cea608_field2_len) {
+        if (*cea608_field2_len + 2 > field_2_len) {
+          GST_WARNING ("Too many cea608 input bytes %u for field 2",
+              *cea608_field2_len + 2);
+          return CC_DATA_EXTRACT_TOO_MANY_FIELD2;
+        }
+        if (byte1 != 0x80 || byte2 != 0x80) {
+          cea608_field2[(*cea608_field2_len)++] = byte1;
+          cea608_field2[(*cea608_field2_len)++] = byte2;
+        }
+      }
+    } else {
+      /* all cea608 packets must be at the beginning of a cc_data */
+      break;
+    }
+  }
+
+  g_assert_cmpint (i * 3, <=, cc_data_len);
+
+  GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u, "
+      "ccp_offset %i", VAL_OR_0 (cea608_field1_len),
+      VAL_OR_0 (cea608_field2_len), i * 3);
+
+  return i * 3;
+}
+
+gint
+drop_ccp_from_cc_data (guint8 * cc_data, guint cc_data_len)
+{
+  return cc_data_extract_cea608 (cc_data, cc_data_len, NULL, NULL, NULL, NULL);
+}
+
+#define DEFAULT_MAX_BUFFER_TIME (100 * GST_MSECOND)
+
+struct _CCBuffer
+{
+  GstObject parent;
+  GArray *cea608_1;
+  GArray *cea608_2;
+  GArray *cc_data;
+  /* used for tracking which field to write across output buffer boundaries */
+  gboolean last_cea608_written_was_field1;
+
+  GstClockTime max_buffer_time;
+};
+
+G_DEFINE_TYPE (CCBuffer, cc_buffer, G_TYPE_OBJECT);
+
+CCBuffer *
+cc_buffer_new (void)
+{
+  return g_object_new (cc_buffer_get_type (), NULL);
+}
+
+static void
+cc_buffer_init (CCBuffer * buf)
+{
+  buf->cea608_1 = g_array_new (FALSE, FALSE, sizeof (guint8));
+  buf->cea608_2 = g_array_new (FALSE, FALSE, sizeof (guint8));
+  buf->cc_data = g_array_new (FALSE, FALSE, sizeof (guint8));
+
+  buf->max_buffer_time = DEFAULT_MAX_BUFFER_TIME;
+}
+
+static void
+cc_buffer_finalize (GObject * object)
+{
+  CCBuffer *buf = GST_CC_BUFFER (object);
+
+  g_array_unref (buf->cea608_1);
+  buf->cea608_1 = NULL;
+  g_array_unref (buf->cea608_2);
+  buf->cea608_2 = NULL;
+  g_array_unref (buf->cc_data);
+  buf->cc_data = NULL;
+
+  G_OBJECT_CLASS (cc_buffer_parent_class)->finalize (object);
+}
+
+static void
+cc_buffer_class_init (CCBufferClass * buf_class)
+{
+  GObjectClass *gobject_class = (GObjectClass *) buf_class;
+
+  gobject_class->finalize = cc_buffer_finalize;
+}
+
+/* remove padding bytes from a cc_data packet. Returns the length of the new
+ * data in @cc_data */
+static guint
+compact_cc_data (guint8 * cc_data, guint cc_data_len)
+{
+  gboolean started_ccp = FALSE;
+  guint out_len = 0;
+  guint i;
+
+  if (cc_data_len % 3 != 0) {
+    GST_WARNING ("Invalid cc_data buffer size");
+    cc_data_len = cc_data_len - (cc_data_len % 3);
+  }
+
+  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 (!started_ccp && (cc_type == 0x00 || cc_type == 0x01)) {
+      if (cc_valid) {
+        /* copy over valid 608 data */
+        cc_data[out_len++] = cc_data[i * 3];
+        cc_data[out_len++] = cc_data[i * 3 + 1];
+        cc_data[out_len++] = cc_data[i * 3 + 2];
+      }
+      continue;
+    }
+
+    if (cc_type & 0x10)
+      started_ccp = TRUE;
+
+    if (!cc_valid)
+      continue;
+
+    if (cc_type == 0x00 || cc_type == 0x01) {
+      GST_WARNING ("Invalid cc_data.  cea608 bytes after cea708");
+      return 0;
+    }
+
+    cc_data[out_len++] = cc_data[i * 3];
+    cc_data[out_len++] = cc_data[i * 3 + 1];
+    cc_data[out_len++] = cc_data[i * 3 + 2];
+  }
+
+  GST_LOG ("compacted cc_data from %u to %u", cc_data_len, out_len);
+
+  return out_len;
+}
+
+static guint
+calculate_n_cea608_doubles_from_time_ceil (CCBuffer * buf, GstClockTime ns)
+{
+  /* cea608 has a maximum bitrate of 60000/1001 * 2 bytes/s */
+  guint ret = gst_util_uint64_scale_ceil (ns, 120000, 1001 * GST_SECOND);
+
+  return GST_ROUND_UP_2 (ret);
+}
+
+static guint
+calculate_n_cea708_doubles_from_time_ceil (CCBuffer * buf, GstClockTime ns)
+{
+  /* ccp has a maximum bitrate of 9600000/1001 bits/s */
+  guint ret = gst_util_uint64_scale_ceil (ns, 9600000 / 8, 1001 * GST_SECOND);
+
+  return GST_ROUND_UP_2 (ret);
+}
+
+static void
+push_internal (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)
+{
+  guint max_cea608_bytes;
+
+  GST_DEBUG_OBJECT (buf, "pushing cea608-1: %u cea608-2: %u ccp: %u",
+      cea608_1_len, cea608_2_len, cc_data_len);
+  max_cea608_bytes =
+      calculate_n_cea608_doubles_from_time_ceil (buf, buf->max_buffer_time);
+
+  if (cea608_1_len > 0) {
+    if (cea608_1_len + buf->cea608_1->len > max_cea608_bytes) {
+      GST_WARNING_OBJECT (buf, "cea608 field 1 overflow, dropping all "
+          "previous data, max %u, attempted to hold %u", max_cea608_bytes,
+          cea608_1_len + buf->cea608_1->len);
+      g_array_set_size (buf->cea608_1, 0);
+    }
+    g_array_append_vals (buf->cea608_1, cea608_1, cea608_1_len);
+  }
+  if (cea608_2_len > 0) {
+    if (cea608_2_len + buf->cea608_2->len > max_cea608_bytes) {
+      GST_WARNING_OBJECT (buf, "cea608 field 2 overflow, dropping all "
+          "previous data, max %u, attempted to hold %u", max_cea608_bytes,
+          cea608_2_len + buf->cea608_2->len);
+      g_array_set_size (buf->cea608_2, 0);
+    }
+    g_array_append_vals (buf->cea608_2, cea608_2, cea608_2_len);
+  }
+  if (cc_data_len > 0) {
+    guint max_cea708_bytes =
+        calculate_n_cea708_doubles_from_time_ceil (buf, buf->max_buffer_time);
+    if (cc_data_len + buf->cc_data->len > max_cea708_bytes) {
+      GST_WARNING_OBJECT (buf, "ccp data overflow, dropping all "
+          "previous data, max %u, attempted to hold %u", max_cea708_bytes,
+          cc_data_len + buf->cc_data->len);
+      g_array_set_size (buf->cea608_2, 0);
+    }
+    g_array_append_vals (buf->cc_data, cc_data, cc_data_len);
+  }
+}
+
+void
+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)
+{
+  guint8 cea608_1_copy[MAX_CEA608_LEN];
+  guint8 cea608_2_copy[MAX_CEA608_LEN];
+  guint8 cc_data_copy[MAX_CDP_PACKET_LEN];
+  guint i;
+
+  if (cea608_1 && cea608_1_len > 0) {
+    guint out_i = 0;
+    for (i = 0; i < cea608_1_len / 2; i++) {
+      if (cea608_1[i] != 0x80 || cea608_1[i + 1] != 0x80) {
+        cea608_1_copy[out_i++] = cea608_1[i];
+        cea608_1_copy[out_i++] = cea608_1[i + 1];
+      }
+    }
+    cea608_1_len = out_i;
+  } else {
+    cea608_1_len = 0;
+  }
+
+  if (cea608_2 && cea608_2_len > 0) {
+    guint out_i = 0;
+    for (i = 0; i < cea608_2_len / 2; i++) {
+      if (cea608_2[i] != 0x80 || cea608_2[i + 1] != 0x80) {
+        cea608_2_copy[out_i++] = cea608_2[i];
+        cea608_2_copy[out_i++] = cea608_2[i + 1];
+      }
+    }
+    cea608_2_len = out_i;
+  } else {
+    cea608_2_len = 0;
+  }
+
+  if (cc_data && cc_data_len > 0) {
+    memcpy (cc_data_copy, cc_data, cc_data_len);
+    cc_data_len = compact_cc_data (cc_data_copy, cc_data_len);
+  } else {
+    cc_data_len = 0;
+  }
+
+  push_internal (buf, cea608_1_copy, cea608_1_len, cea608_2_copy,
+      cea608_2_len, cc_data_copy, cc_data_len);
+}
+
+void
+cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data,
+    guint cc_data_len)
+{
+  guint8 cea608_1[MAX_CEA608_LEN];
+  guint8 cea608_2[MAX_CEA608_LEN];
+  guint8 cc_data_copy[MAX_CDP_PACKET_LEN];
+  guint cea608_1_len = MAX_CEA608_LEN;
+  guint cea608_2_len = MAX_CEA608_LEN;
+  int ccp_offset;
+
+  memcpy (cc_data_copy, cc_data, cc_data_len);
+
+  cc_data_len = compact_cc_data (cc_data_copy, cc_data_len);
+
+  ccp_offset = cc_data_extract_cea608 (cc_data_copy, cc_data_len, cea608_1,
+      &cea608_1_len, cea608_2, &cea608_2_len);
+
+  if (ccp_offset < 0) {
+    GST_WARNING_OBJECT (buf, "Failed to extract cea608 from cc_data");
+    return;
+  }
+
+  push_internal (buf, cea608_1, cea608_1_len, cea608_2,
+      cea608_2_len, &cc_data_copy[ccp_offset], cc_data_len - ccp_offset);
+}
+
+void
+cc_buffer_get_stored_size (CCBuffer * buf, guint * cea608_1_len,
+    guint * cea608_2_len, guint * cc_data_len)
+{
+  if (cea608_1_len)
+    *cea608_1_len = buf->cea608_1->len;
+  if (cea608_2_len)
+    *cea608_2_len = buf->cea608_2->len;
+  if (cc_data_len)
+    *cc_data_len = buf->cc_data->len;
+}
+
+void
+cc_buffer_discard (CCBuffer * buf)
+{
+  g_array_set_size (buf->cea608_1, 0);
+  g_array_set_size (buf->cea608_2, 0);
+  g_array_set_size (buf->cc_data, 0);
+}
+
+#if 0
+void
+cc_buffer_peek (CCBuffer * buf, guint8 ** cea608_1, guint * cea608_1_len,
+    guint8 ** cea608_2, guint * cea608_2_len, guint8 ** cc_data,
+    guint * cc_data_len)
+{
+  if (cea608_1_len) {
+    if (cea608_1) {
+      *cea608_1 = (guint8 *) buf->cea608_1->data;
+    }
+    *cea608_1_len = buf->cea608_1->len;
+  }
+  if (cea608_1_len) {
+    if (cea608_2) {
+      *cea608_2 = (guint8 *) buf->cea608_2->data;
+    }
+    *cea608_2_len = buf->cea608_2->len;
+  }
+  if (cc_data_len) {
+    if (cc_data) {
+      *cc_data = (guint8 *) buf->cc_data->data;
+    }
+    *cc_data_len = buf->cc_data->len;
+  }
+}
+#endif
+static void
+cc_buffer_get_out_sizes (CCBuffer * buf, const struct cdp_fps_entry *fps_entry,
+    guint * cea608_1_len, guint * field1_padding, guint * cea608_2_len,
+    guint * field2_padding, guint * cc_data_len)
+{
+  gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0;
+  gint write_ccp_size = 0, write_cea608_1_size = 0, write_cea608_2_size = 0;
+  gboolean wrote_first = FALSE;
+
+  if (buf->cc_data->len) {
+    extra_ccp = buf->cc_data->len - 3 * fps_entry->max_ccp_count;
+    extra_ccp = MAX (0, extra_ccp);
+    write_ccp_size = buf->cc_data->len - extra_ccp;
+  }
+
+  extra_cea608_1 = buf->cea608_1->len;
+  extra_cea608_2 = buf->cea608_2->len;
+  *field1_padding = 0;
+  *field2_padding = 0;
+
+  wrote_first = !buf->last_cea608_written_was_field1;
+  /* try to push data into the packets.  Anything 'extra' will be
+   * stored for later */
+  while (TRUE) {
+    gint avail_1, avail_2;
+
+    avail_1 = buf->cea608_1->len - extra_cea608_1 + *field1_padding;
+    avail_2 = buf->cea608_2->len - extra_cea608_2 + *field2_padding;
+    if (avail_1 + avail_2 >= 2 * fps_entry->max_cea608_count)
+      break;
+
+    if (wrote_first) {
+      if (extra_cea608_1 > 0) {
+        extra_cea608_1 -= 2;
+        g_assert_cmpint (extra_cea608_1, >=, 0);
+        write_cea608_1_size += 2;
+        g_assert_cmpint (write_cea608_1_size, <=, buf->cea608_1->len);
+      } else {
+        *field1_padding += 2;
+      }
+    }
+
+    avail_1 = buf->cea608_1->len - extra_cea608_1 + *field1_padding;
+    avail_2 = buf->cea608_2->len - extra_cea608_2 + *field2_padding;
+    if (avail_1 + avail_2 >= 2 * fps_entry->max_cea608_count)
+      break;
+
+    if (extra_cea608_2 > 0) {
+      extra_cea608_2 -= 2;
+      g_assert_cmpint (extra_cea608_2, >=, 0);
+      write_cea608_2_size += 2;
+      g_assert_cmpint (write_cea608_2_size, <=, buf->cea608_2->len);
+    } else {
+      /* we need to insert field 2 padding if we don't have data and are
+       * requested to start with field2 */
+      *field2_padding += 2;
+    }
+    wrote_first = TRUE;
+  }
+
+  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);
+
+  *cea608_1_len = write_cea608_1_size;
+  *cea608_2_len = write_cea608_2_size;
+  *cc_data_len = write_ccp_size;
+}
+
+void
+cc_buffer_take_separated (CCBuffer * buf,
+    const struct cdp_fps_entry *fps_entry, guint8 * cea608_1,
+    guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len,
+    guint8 * cc_data, guint * cc_data_len)
+{
+  guint write_cea608_1_size, write_cea608_2_size, write_ccp_size;
+  guint field1_padding, field2_padding;
+
+  cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size,
+      &field1_padding, &write_cea608_2_size, &field2_padding, &write_ccp_size);
+
+  if (cea608_1_len) {
+    if (*cea608_1_len < write_cea608_1_size + field1_padding) {
+      GST_WARNING_OBJECT (buf, "output cea608 field 1 buffer (%u) is too "
+          "small to hold output (%u)", *cea608_1_len,
+          write_cea608_1_size + field1_padding);
+      *cea608_1_len = 0;
+    } else if (cea608_1) {
+      memcpy (cea608_1, buf->cea608_1->data, write_cea608_1_size);
+      memset (&cea608_1[write_cea608_1_size], 0x80, field1_padding);
+      *cea608_1_len = write_cea608_1_size + field1_padding;
+    } else {
+      *cea608_1_len = 0;
+    }
+  }
+  if (cea608_2_len) {
+    if (*cea608_2_len < write_cea608_2_size + field2_padding) {
+      GST_WARNING_OBJECT (buf, "output cea608 field 2 buffer (%u) is too "
+          "small to hold output (%u)", *cea608_2_len, write_cea608_2_size);
+      *cea608_2_len = 0;
+    } else if (cea608_2) {
+      memcpy (cea608_2, buf->cea608_2->data, write_cea608_2_size);
+      memset (&cea608_2[write_cea608_2_size], 0x80, field2_padding);
+      *cea608_2_len = write_cea608_2_size + field2_padding;
+    } else {
+      *cea608_2_len = 0;
+    }
+  }
+  if (cc_data_len) {
+    if (*cc_data_len < write_ccp_size) {
+      GST_WARNING_OBJECT (buf, "output ccp buffer (%u) is too "
+          "small to hold output (%u)", *cc_data_len, write_ccp_size);
+      *cc_data_len = 0;
+    } else if (cc_data) {
+      memcpy (cc_data, buf->cc_data->data, write_ccp_size);
+      *cc_data_len = write_ccp_size;
+    } else {
+      *cc_data_len = 0;
+    }
+  }
+
+  g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size);
+  g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size);
+  g_array_remove_range (buf->cc_data, 0, write_ccp_size);
+
+  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_cc_data (CCBuffer * buf,
+    const struct cdp_fps_entry *fps_entry, guint8 * cc_data,
+    guint * cc_data_len)
+{
+  guint write_cea608_1_size, write_cea608_2_size, write_ccp_size;
+  guint field1_padding, field2_padding;
+  gboolean wrote_first;
+
+  cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size,
+      &field1_padding, &write_cea608_2_size, &field2_padding, &write_ccp_size);
+
+  {
+    guint cea608_1_i = 0, cea608_2_i = 0;
+    guint out_i = 0;
+    guint8 *cea608_1 = (guint8 *) buf->cea608_1->data;
+    guint8 *cea608_2 = (guint8 *) buf->cea608_2->data;
+    guint cea608_output_count =
+        write_cea608_1_size + write_cea608_2_size + field1_padding +
+        field2_padding;
+
+    wrote_first = !buf->last_cea608_written_was_field1;
+    while (cea608_1_i + cea608_2_i < cea608_output_count) {
+      if (wrote_first) {
+        if (cea608_1_i < write_cea608_1_size) {
+          cc_data[out_i++] = 0xfc;
+          cc_data[out_i++] = cea608_1[cea608_1_i];
+          cc_data[out_i++] = cea608_1[cea608_1_i + 1];
+          cea608_1_i += 2;
+          buf->last_cea608_written_was_field1 = TRUE;
+        } else if (cea608_1_i < write_cea608_1_size + field1_padding) {
+          cc_data[out_i++] = 0xf8;
+          cc_data[out_i++] = 0x80;
+          cc_data[out_i++] = 0x80;
+          cea608_1_i += 2;
+          buf->last_cea608_written_was_field1 = TRUE;
+        }
+      }
+
+      if (cea608_2_i < write_cea608_2_size) {
+        cc_data[out_i++] = 0xfd;
+        cc_data[out_i++] = cea608_2[cea608_2_i];
+        cc_data[out_i++] = cea608_2[cea608_2_i + 1];
+        cea608_2_i += 2;
+        buf->last_cea608_written_was_field1 = FALSE;
+      } else if (cea608_2_i < write_cea608_2_size + field2_padding) {
+        cc_data[out_i++] = 0xf9;
+        cc_data[out_i++] = 0x80;
+        cc_data[out_i++] = 0x80;
+        cea608_2_i += 2;
+        buf->last_cea608_written_was_field1 = FALSE;
+      }
+
+      wrote_first = TRUE;
+    }
+
+    if (write_ccp_size > 0)
+      memcpy (&cc_data[out_i], buf->cc_data->data, write_ccp_size);
+    *cc_data_len = out_i + write_ccp_size;
+  }
+
+  g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size);
+  g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size);
+  g_array_remove_range (buf->cc_data, 0, write_ccp_size);
+
+  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);
+}
index 391d12f..c9903bc 100644 (file)
@@ -50,6 +50,7 @@ typedef enum {
   GST_CC_CDP_MODE_CC_SVC_INFO = (1<<2)
 } GstCCCDPMode;
 
+G_GNUC_INTERNAL
 guint           convert_cea708_cc_data_to_cdp  (GstObject * dbg_obj,
                                                 GstCCCDPMode cdp_mode,
                                                 guint16 cdp_hdr_sequence_cntr,
@@ -60,14 +61,60 @@ guint           convert_cea708_cc_data_to_cdp  (GstObject * dbg_obj,
                                                 const GstVideoTimeCode * tc,
                                                 const struct cdp_fps_entry *fps_entry);
 
+G_GNUC_INTERNAL
 guint           convert_cea708_cdp_to_cc_data  (GstObject * dbg_obj,
                                                 const guint8 * cdp,
                                                 guint cdp_len,
                                                 guint8 *cc_data,
                                                 GstVideoTimeCode * tc,
                                                 const struct cdp_fps_entry **out_fps_entry);
+G_GNUC_INTERNAL
+gint           drop_ccp_from_cc_data           (guint8 * cc_data,
+                                                 guint cc_data_len);
 
 #define MAX_CDP_PACKET_LEN 256
+#define MAX_CEA608_LEN 32
+
+G_DECLARE_FINAL_TYPE (CCBuffer, cc_buffer, GST, CC_BUFFER, GObject);
+
+G_GNUC_INTERNAL
+CCBuffer *      cc_buffer_new                   (void);
+G_GNUC_INTERNAL
+void            cc_buffer_get_stored_size       (CCBuffer * buf,
+                                                 guint * cea608_1_len,
+                                                 guint * cea608_2_len,
+                                                 guint * cc_data_len);
+G_GNUC_INTERNAL
+void            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);
+G_GNUC_INTERNAL
+void            cc_buffer_push_cc_data          (CCBuffer * buf,
+                                                 const guint8 * cc_data,
+                                                 guint cc_data_len);
+G_GNUC_INTERNAL
+void            cc_buffer_take_cc_data          (CCBuffer * buf,
+                                                 const struct cdp_fps_entry * fps_entry,
+                                                 guint8 * cc_data,
+                                                 guint * cc_data_len);
+G_GNUC_INTERNAL
+void            cc_buffer_take_separated        (CCBuffer * buf,
+                                                 const struct cdp_fps_entry * fps_entry,
+                                                 guint8 * cea608_1,
+                                                 guint * cea608_1_len,
+                                                 guint8 * cea608_2,
+                                                 guint * cea608_2_len,
+                                                 guint8 * cc_data,
+                                                 guint * cc_data_len);
+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_END_DECLS
 
index c87aeab..14ac7c6 100644 (file)
@@ -499,7 +499,6 @@ interpolate_time_code_with_framerate (GstCCConverter * self,
   guint output_frame;
   GstVideoTimeCodeFlags flags;
 
-  g_return_val_if_fail (tc != NULL, FALSE);
   g_return_val_if_fail (out != NULL, FALSE);
   /* out_n/d can only be 0 if scale_n/d are 1/1 */
   g_return_val_if_fail ((scale_n == 1 && scale_d == 1) || (out_fps_n != 0
@@ -556,552 +555,59 @@ interpolate_time_code_with_framerate (GstCCConverter * self,
   return TRUE;
 }
 
-/* remove padding bytes from a cc_data packet. Returns the length of the new
- * data in @cc_data */
-static guint
-compact_cc_data (guint8 * cc_data, guint cc_data_len)
-{
-  gboolean started_ccp = FALSE;
-  guint out_len = 0;
-  guint i;
-
-  if (cc_data_len % 3 != 0) {
-    GST_WARNING ("Invalid cc_data buffer size");
-    cc_data_len = cc_data_len - (cc_data_len % 3);
-  }
-
-  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 (!started_ccp && (cc_type == 0x00 || cc_type == 0x01)) {
-      if (cc_valid) {
-        /* copy over valid 608 data */
-        cc_data[out_len++] = cc_data[i * 3];
-        cc_data[out_len++] = cc_data[i * 3 + 1];
-        cc_data[out_len++] = cc_data[i * 3 + 2];
-      }
-      continue;
-    }
-
-    if (cc_type & 0x10)
-      started_ccp = TRUE;
-
-    if (!cc_valid)
-      continue;
-
-    if (cc_type == 0x00 || cc_type == 0x01) {
-      GST_WARNING ("Invalid cc_data.  cea608 bytes after cea708");
-      return 0;
-    }
-
-    cc_data[out_len++] = cc_data[i * 3];
-    cc_data[out_len++] = cc_data[i * 3 + 1];
-    cc_data[out_len++] = cc_data[i * 3 + 2];
-  }
-
-  GST_LOG ("compacted cc_data from %u to %u", cc_data_len, out_len);
-
-  return out_len;
-}
-
-#define CC_DATA_EXTRACT_TOO_MANY_FIELD1 -2
-#define CC_DATA_EXTRACT_TOO_MANY_FIELD2 -3
-
-static gint
-cc_data_extract_cea608 (guint8 * cc_data, guint cc_data_len,
-    guint8 * cea608_field1, guint * cea608_field1_len,
-    guint8 * cea608_field2, guint * cea608_field2_len)
-{
-  guint i, field_1_len = 0, field_2_len = 0;
-
-  if (cea608_field1_len) {
-    field_1_len = *cea608_field1_len;
-    *cea608_field1_len = 0;
-  }
-  if (cea608_field2_len) {
-    field_2_len = *cea608_field2_len;
-    *cea608_field2_len = 0;
-  }
-
-  if (cc_data_len % 3 != 0) {
-    GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
-        "of 3", cc_data_len);
-    cc_data_len = cc_data_len - (cc_data_len % 3);
-  }
-
-  for (i = 0; i < cc_data_len / 3; i++) {
-    guint8 byte0 = cc_data[i * 3 + 0];
-    guint8 byte1 = cc_data[i * 3 + 1];
-    guint8 byte2 = cc_data[i * 3 + 2];
-    gboolean cc_valid = (byte0 & 0x04) == 0x04;
-    guint8 cc_type = byte0 & 0x03;
-
-    GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u", byte0, byte1,
-        byte2, cc_valid, (cc_type & 0x2) == 0x2, (cc_type & 0x1) == 0x1);
-
-    if (cc_type == 0x00) {
-      if (!cc_valid)
-        continue;
-
-      if (cea608_field1 && cea608_field1_len) {
-        if (*cea608_field1_len + 2 > field_1_len) {
-          GST_WARNING ("Too many cea608 input bytes %u for field 1",
-              *cea608_field1_len + 2);
-          return CC_DATA_EXTRACT_TOO_MANY_FIELD1;
-        }
-
-        if (byte1 != 0x80 || byte2 != 0x80) {
-          cea608_field1[(*cea608_field1_len)++] = byte1;
-          cea608_field1[(*cea608_field1_len)++] = byte2;
-        }
-      }
-    } else if (cc_type == 0x01) {
-      if (!cc_valid)
-        continue;
-
-      if (cea608_field2 && cea608_field2_len) {
-        if (*cea608_field2_len + 2 > field_2_len) {
-          GST_WARNING ("Too many cea608 input bytes %u for field 2",
-              *cea608_field2_len + 2);
-          return CC_DATA_EXTRACT_TOO_MANY_FIELD2;
-        }
-        if (byte1 != 0x80 || byte2 != 0x80) {
-          cea608_field2[(*cea608_field2_len)++] = byte1;
-          cea608_field2[(*cea608_field2_len)++] = byte2;
-        }
-      }
-    } else {
-      /* all cea608 packets must be at the beginning of a cc_data */
-      break;
-    }
-  }
-
-  g_assert_cmpint (i * 3, <=, cc_data_len);
-
-  GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u",
-      VAL_OR_0 (cea608_field1_len), VAL_OR_0 (cea608_field2_len));
-
-  return i * 3;
-}
-
-static void
-store_cc_data (GstCCConverter * self, const guint8 * ccp_data,
-    guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
-    const guint8 * cea608_2, guint cea608_2_len)
-{
-  GST_TRACE_OBJECT (self, "attempting to hold data of len ccp:%u, cea608 1:%u, "
-      "cea608 2:%u until next output buffer", ccp_data_len, cea608_1_len,
-      cea608_2_len);
-
-  if (ccp_data && ccp_data_len > 0) {
-    if (ccp_data_len > sizeof (self->scratch_ccp)) {
-      GST_ELEMENT_WARNING (self, STREAM, DECODE,
-          ("Closed Caption internal buffer overun. Dropping data"),
-          ("CCP scratch buffer requires space for %u bytes but only %"
-              G_GSIZE_FORMAT " bytes are available", ccp_data_len,
-              sizeof (self->scratch_ccp)));
-      self->scratch_ccp_len = 0;
-    } else {
-      memcpy (self->scratch_ccp, ccp_data, ccp_data_len);
-      self->scratch_ccp_len = ccp_data_len;
-    }
-  } else {
-    self->scratch_ccp_len = 0;
-  }
-  g_assert_cmpint (self->scratch_ccp_len, <=, sizeof (self->scratch_ccp));
-
-  if (cea608_1 && cea608_1_len > 0) {
-    if (cea608_1_len > sizeof (self->scratch_cea608_1)) {
-      GST_ELEMENT_WARNING (self, STREAM, DECODE,
-          ("Closed Caption internal buffer overun. Dropping data"),
-          ("CEA608 field 1 scratch buffer requires space for %u bytes but "
-              "only %" G_GSIZE_FORMAT " bytes are available", cea608_1_len,
-              sizeof (self->scratch_cea608_1_len)));
-      self->scratch_cea608_1_len = 0;
-    } else {
-      memcpy (self->scratch_cea608_1, cea608_1, cea608_1_len);
-      self->scratch_cea608_1_len = cea608_1_len;
-    }
-  } else {
-    self->scratch_cea608_1_len = 0;
-  }
-  g_assert_cmpint (self->scratch_cea608_1_len, <=,
-      sizeof (self->scratch_cea608_1));
-
-  if (cea608_2 && cea608_2_len > 0) {
-    if (cea608_2_len > sizeof (self->scratch_cea608_2)) {
-      GST_ELEMENT_WARNING (self, STREAM, DECODE,
-          ("Closed Caption internal buffer overun. Dropping data"),
-          ("CEA608 field 2 scratch buffer requires space for %u bytes but "
-              "only %" G_GSIZE_FORMAT " bytes are available", cea608_2_len,
-              sizeof (self->scratch_cea608_2_len)));
-      self->scratch_cea608_2_len = 0;
-    } else {
-      memcpy (self->scratch_cea608_2, cea608_2, cea608_2_len);
-      self->scratch_cea608_2_len = cea608_2_len;
-    }
-  } else {
-    self->scratch_cea608_2_len = 0;
-  }
-  g_assert_cmpint (self->scratch_cea608_2_len, <=,
-      sizeof (self->scratch_cea608_2));
-
-  GST_DEBUG_OBJECT (self, "holding data of len ccp:%u, cea608 1:%u, "
-      "cea608 2:%u until next output buffer", self->scratch_ccp_len,
-      self->scratch_cea608_1_len, self->scratch_cea608_2_len);
-}
-
 static gboolean
-write_cea608 (GstCCConverter * self, gboolean pad_cea608,
-    const struct cdp_fps_entry *out_fps_entry, const guint8 * cea608_1,
-    guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len,
-    guint8 * out, guint * out_size, gboolean * is_last_cea608_field1)
+can_take_buffer (GstCCConverter * self,
+    const struct cdp_fps_entry *in_fps_entry,
+    const struct cdp_fps_entry *out_fps_entry,
+    const GstVideoTimeCode * in_tc, GstVideoTimeCode * out_tc)
 {
-  guint i = 0, out_i = 0, max_size = 0, cea608_1_i = 0, cea608_2_i = 0;
-  guint cea608_output_count;
-  guint total_cea608_1_count, total_cea608_2_count;
-  gboolean write_cea608_field2_first = FALSE;
-  gboolean wrote_field1_last = FALSE;
-  gboolean wrote_first = FALSE;
-
-  g_assert (out);
-  g_assert (out_size);
-  g_assert (!cea608_1 || cea608_1_len % 2 == 0);
-  g_assert (!cea608_2 || cea608_2_len % 2 == 0);
-  g_assert (cea608_1 || cea608_2);
-
-  if (is_last_cea608_field1)
-    write_cea608_field2_first = *is_last_cea608_field1;
-
-  cea608_1_len /= 2;
-  cea608_2_len /= 2;
-#if 0
-  /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated,
-   * However that is not possible for 60fps (where only one cea608 field fits)
-   * without adding previous output buffer tracking */
-  g_assert_cmpint (cea608_1_len >= cea608_2_len);
-#endif
-  g_assert_cmpint (cea608_1_len + cea608_2_len, <=,
-      out_fps_entry->max_cea608_count);
+  int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
+  int output_time_cmp, scale_n, scale_d;
 
-#if 0
-  /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated. */
-  if (cea608_1_len < cea608_2_len)
-    total_cea608_1_count += cea608_2_len - cea608_1_len;
-#endif
+  /* TODO: handle input discont */
 
-  if (pad_cea608) {
-    max_size = out_fps_entry->max_cea608_count * 3;
+  if (self->in_fps_n == 0) {
+    input_frame_n = self->input_frames;
+    input_frame_d = 1;
   } else {
-    max_size = (cea608_1_len + cea608_2_len) * 3;
-  }
-  if (*out_size < max_size) {
-    GST_WARNING_OBJECT (self, "Output data too small (%u < %u) for cea608 data",
-        *out_size, max_size);
-    return FALSE;
-  }
-
-  /* FIXME: interlacing, tff, rff, ensuring cea608 field1 is generated if
-   * field2 exists even across packets */
-
-  total_cea608_1_count = cea608_1_len;
-  total_cea608_2_count = cea608_2_len;
-  cea608_output_count = cea608_1_len + cea608_2_len;
-  if (pad_cea608) {
-    guint max_cea608_count = out_fps_entry->max_cea608_count;
-
-    total_cea608_1_count = max_cea608_count / 2;
-    total_cea608_2_count = max_cea608_count / 2;
-
-    if (total_cea608_1_count + total_cea608_2_count < max_cea608_count) {
-      if (write_cea608_field2_first) {
-        total_cea608_2_count++;
-      } else {
-        total_cea608_1_count++;
-      }
-    }
-
-    cea608_output_count = total_cea608_1_count + total_cea608_2_count;
-  }
-
-  GST_LOG ("writing %u cea608-1 fields and %u cea608-2 fields",
-      total_cea608_1_count, total_cea608_2_count);
-  g_assert_cmpint (total_cea608_1_count + total_cea608_2_count, <=,
-      out_fps_entry->max_cea608_count);
-
-  wrote_first = !write_cea608_field2_first;
-  while (cea608_1_i + cea608_2_i < cea608_output_count) {
-    if (wrote_first) {
-      if (cea608_1_i < cea608_1_len) {
-        out[out_i++] = 0xfc;
-        out[out_i++] = cea608_1[cea608_1_i * 2];
-        out[out_i++] = cea608_1[cea608_1_i * 2 + 1];
-        cea608_1_i++;
-        i++;
-        wrote_field1_last = TRUE;
-      } else if (cea608_1_i < total_cea608_1_count) {
-        out[out_i++] = 0xf8;
-        out[out_i++] = 0x80;
-        out[out_i++] = 0x80;
-        cea608_1_i++;
-        i++;
-        wrote_field1_last = TRUE;
-      }
-    }
-
-    if (cea608_2_i < cea608_2_len) {
-      out[out_i++] = 0xfd;
-      out[out_i++] = cea608_2[cea608_2_i * 2];
-      out[out_i++] = cea608_2[cea608_2_i * 2 + 1];
-      cea608_2_i++;
-      i++;
-      wrote_field1_last = FALSE;
-    } else if (cea608_2_i < total_cea608_2_count) {
-      out[out_i++] = 0xf9;
-      out[out_i++] = 0x80;
-      out[out_i++] = 0x80;
-      cea608_2_i++;
-      i++;
-      wrote_field1_last = FALSE;
-    }
-
-    wrote_first = TRUE;
+    /* compute the relative frame count for each */
+    if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
+            self->input_frames, 1, &input_frame_n, &input_frame_d))
+      /* we should never overflow */
+      g_assert_not_reached ();
   }
 
-  g_assert_cmpint (out_i / 3, <=, out_fps_entry->max_cea608_count);
-
-  *out_size = out_i;
-  if (is_last_cea608_field1)
-    *is_last_cea608_field1 = wrote_field1_last;
-
-  return TRUE;
-}
-
-static gboolean
-combine_cc_data (GstCCConverter * self, gboolean pad_cea608,
-    const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
-    guint ccp_data_len, const guint8 * cea608_1, guint cea608_1_len,
-    const guint8 * cea608_2, guint cea608_2_len, guint8 * out, guint * out_size,
-    gboolean * last_cea608_is_field1)
-{
-  guint out_i = 0, max_size = 0;
-  guint cea608_size = *out_size;
-
-  g_assert (out);
-  g_assert (out_size);
-  g_assert (!ccp_data || ccp_data_len % 3 == 0);
-
-  if (cea608_1 || cea608_2) {
-    if (!write_cea608 (self, pad_cea608, out_fps_entry, cea608_1, cea608_1_len,
-            cea608_2, cea608_2_len, out, &cea608_size, last_cea608_is_field1))
-      return FALSE;
+  if (self->in_fps_n == 0) {
+    output_frame_n = self->output_frames;
+    output_frame_d = 1;
   } else {
-    cea608_size = 0;
-  }
-
-  max_size = ccp_data_len + cea608_size;
-  if (*out_size < max_size) {
-    GST_WARNING_OBJECT (self, "Output data too small (%u < %u)", *out_size,
-        max_size);
-    return FALSE;
-  }
-
-  *out_size = cea608_size;
-  out_i = cea608_size;
-  if (ccp_data) {
-    memcpy (&out[out_i], ccp_data, ccp_data_len);
-    *out_size += ccp_data_len;
-  }
-
-  g_assert_cmpint (*out_size, <, MAX_CDP_PACKET_LEN);
-
-  return TRUE;
-}
-
-/* takes cc_data cea608_1, cea608_2 and attempts to fit it into a hypothetical
- * output packet.  Any leftover data is stored for later addition.  Returns
- * whether any output can be generated. @ccp_data_len, @cea608_1_len,
- * @cea608_2_len are also updated to reflect the size of that data to add to
- * the output packet */
-static gboolean
-fit_and_scale_cc_data (GstCCConverter * self,
-    const struct cdp_fps_entry *in_fps_entry,
-    const struct cdp_fps_entry *out_fps_entry, const guint8 * ccp_data,
-    guint * ccp_data_len, const guint8 * cea608_1, guint * cea608_1_len,
-    const guint8 * cea608_2, guint * cea608_2_len, const GstVideoTimeCode * tc,
-    gboolean use_cea608_field2_first)
-{
-  if (!in_fps_entry || in_fps_entry->fps_n == 0) {
-    in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
-    if (!in_fps_entry || in_fps_entry->fps_n == 0)
+    if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
+            self->output_frames, 1, &output_frame_n, &output_frame_d))
+      /* we should never overflow */
       g_assert_not_reached ();
   }
 
-  /* This is slightly looser than checking for the exact framerate as the cdp
-   * spec allow for 0.1% difference between framerates to be considered equal */
-  {
-    int input_frame_n, input_frame_d, output_frame_n, output_frame_d;
-    int output_time_cmp, scale_n, scale_d;
-
-    /* TODO: handle input discont */
-
-    if (self->in_fps_n == 0) {
-      input_frame_n = self->input_frames;
-      input_frame_d = 1;
-    } else {
-      /* compute the relative frame count for each */
-      if (!gst_util_fraction_multiply (self->in_fps_d, self->in_fps_n,
-              self->input_frames, 1, &input_frame_n, &input_frame_d))
-        /* we should never overflow */
-        g_assert_not_reached ();
-    }
-
-    if (self->in_fps_n == 0) {
-      output_frame_n = self->output_frames;
-      output_frame_d = 1;
-    } else {
-      if (!gst_util_fraction_multiply (self->out_fps_d, self->out_fps_n,
-              self->output_frames, 1, &output_frame_n, &output_frame_d))
-        /* we should never overflow */
-        g_assert_not_reached ();
-    }
-
-    output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
-        output_frame_n, output_frame_d);
-
-    /* compute the relative rates of the two framerates */
-    get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
-
-    GST_TRACE_OBJECT (self, "performing conversion at scale %d/%d "
-        "of cc data of with sizes, ccp:%u, cea608-1:%u, cea608-2:%u", scale_n,
-        scale_d, VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
-        VAL_OR_0 (cea608_2_len));
-
-    if (output_time_cmp < 0) {
-      /* we can't generate an output yet */
-      guint cd_len = ccp_data_len ? *ccp_data_len : 0;
-      guint c1_len = cea608_1_len ? *cea608_1_len : 0;
-      guint c2_len = cea608_2_len ? *cea608_2_len : 0;
-
-      store_cc_data (self, ccp_data, cd_len, cea608_1, c1_len, cea608_2,
-          c2_len);
-      if (ccp_data_len)
-        *ccp_data_len = 0;
-      if (cea608_1_len)
-        *cea608_1_len = 0;
-      if (cea608_2_len)
-        *cea608_2_len = 0;
-      return FALSE;
-    } else {
-      /* we are changing the framerate and may overflow the max output packet
-       * size. Split them where necessary. */
-      gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0;
-      gint ccp_off = 0, cea608_1_off = 0, cea608_2_off = 0;
-      gboolean wrote_first = FALSE;
-      gint field2_padding = 0;
-
-      if (output_time_cmp == 0) {
-        /* we have completed a cycle and can reset our counters to avoid
-         * overflow. Anything that fits into the output packet will be written */
-        GST_LOG_OBJECT (self, "cycle completed, resetting frame counters");
-        self->scratch_ccp_len = 0;
-        self->scratch_cea608_1_len = 0;
-        self->scratch_cea608_2_len = 0;
-        self->input_frames = 0;
-        self->output_frames = 0;
-      }
-
-      if (ccp_data_len) {
-        extra_ccp = *ccp_data_len - 3 * out_fps_entry->max_ccp_count;
-        extra_ccp = MAX (0, extra_ccp);
-        ccp_off = *ccp_data_len - extra_ccp;
-      }
-
-      extra_cea608_1 = VAL_OR_0 (cea608_1_len);
-      extra_cea608_2 = VAL_OR_0 (cea608_2_len);
-
-      wrote_first = !use_cea608_field2_first;
-      /* try to push data into the packets.  Anything 'extra' will be
-       * stored for later */
-      while (extra_cea608_1 > 0 || extra_cea608_2 > 0) {
-        gint avail_1, avail_2;
-
-        avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
-        avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
-        if (avail_1 + avail_2 + field2_padding >=
-            2 * out_fps_entry->max_cea608_count)
-          break;
-
-        if (wrote_first && extra_cea608_1 > 0) {
-          extra_cea608_1 -= 2;
-          g_assert_cmpint (extra_cea608_1, >=, 0);
-          cea608_1_off += 2;
-          g_assert_cmpint (cea608_1_off, <=, *cea608_1_len);
-        }
+  output_time_cmp = gst_util_fraction_compare (input_frame_n, input_frame_d,
+      output_frame_n, output_frame_d);
 
-        avail_1 = VAL_OR_0 (cea608_1_len) - extra_cea608_1;
-        avail_2 = VAL_OR_0 (cea608_2_len) - extra_cea608_2;
-        if (avail_1 + avail_2 + field2_padding >=
-            2 * out_fps_entry->max_cea608_count)
-          break;
+  in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
+  if (!in_fps_entry || in_fps_entry->fps_n == 0)
+    g_assert_not_reached ();
 
-        if (extra_cea608_2 > 0) {
-          extra_cea608_2 -= 2;
-          g_assert_cmpint (extra_cea608_2, >=, 0);
-          cea608_2_off += 2;
-          g_assert_cmpint (cea608_2_off, <=, *cea608_2_len);
-        } else if (!wrote_first) {
-          /* we need to insert field 2 padding if we don't have data and are
-           * requested to start with field2 */
-          field2_padding += 2;
-        }
-        wrote_first = TRUE;
-      }
+  /* compute the relative rates of the two framerates */
+  get_framerate_output_scale (self, in_fps_entry, &scale_n, &scale_d);
 
-      GST_TRACE_OBJECT (self, "allocated sizes ccp:%u, cea608-1:%u, "
-          "cea608-2:%u", ccp_off, cea608_1_off, cea608_2_off);
-
-      if (extra_ccp > 0 || extra_cea608_1 > 0 || extra_cea608_2 > 0) {
-        /* packet would overflow, push extra bytes into the next packet */
-        GST_DEBUG_OBJECT (self, "buffer would overflow by %u ccp bytes, "
-            "%u cea608 field 1 bytes, or %u cea608 field 2 bytes", extra_ccp,
-            extra_cea608_1, extra_cea608_2);
-        store_cc_data (self, &ccp_data[ccp_off], extra_ccp,
-            &cea608_1[cea608_1_off], extra_cea608_1, &cea608_2[cea608_2_off],
-            extra_cea608_2);
-        if (ccp_data_len)
-          *ccp_data_len = MIN (*ccp_data_len, ccp_off);
-        if (cea608_1_len)
-          *cea608_1_len = MIN (*cea608_1_len, cea608_1_off);
-        if (cea608_2_len)
-          *cea608_2_len = MIN (*cea608_2_len, cea608_2_off);
-      } else {
-        GST_DEBUG_OBJECT (self, "section sizes of %u ccp bytes, "
-            "%u cea608 field 1 bytes, and %u cea608 field 2 bytes fit within "
-            "output packet", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
-            VAL_OR_0 (cea608_2_len));
-        self->scratch_ccp_len = 0;
-        self->scratch_cea608_1_len = 0;
-        self->scratch_cea608_2_len = 0;
-      }
-    }
+  GST_TRACE_OBJECT (self, "performing conversion at scale %d/%d, "
+      "time comparison %i", scale_n, scale_d, output_time_cmp);
 
-    if (tc && tc->config.fps_n != 0)
-      interpolate_time_code_with_framerate (self, tc, out_fps_entry->fps_n,
-          out_fps_entry->fps_d, scale_n, scale_d,
-          &self->current_output_timecode);
+  if (output_time_cmp < 0) {
+    /* we can't generate an output yet */
+    return FALSE;
+  } else {
+    interpolate_time_code_with_framerate (self, in_tc, out_fps_entry->fps_n,
+        out_fps_entry->fps_d, scale_n, scale_d, out_tc);
+    return TRUE;
   }
-
-  g_assert_cmpint (VAL_OR_0 (ccp_data_len) + (VAL_OR_0 (cea608_1_len) +
-          VAL_OR_0 (cea608_2_len)) / 2 * 3, <=,
-      3 * out_fps_entry->max_cc_count);
-
-  GST_DEBUG_OBJECT (self, "write out packet with lengths ccp:%u, cea608-1:%u, "
-      "cea608-2:%u", VAL_OR_0 (ccp_data_len), VAL_OR_0 (cea608_1_len),
-      VAL_OR_0 (cea608_2_len));
-
-  return TRUE;
 }
 
 static guint
@@ -1120,192 +626,8 @@ convert_cea708_cc_data_cea708_cdp_internal (GstCCConverter * self,
 }
 
 static gboolean
-copy_from_stored_data (GstCCConverter * self, guint8 * out_ccp,
-    guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
-    guint8 * cea608_2, guint * cea608_2_len)
-{
-  guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
-
-  g_assert ((out_ccp && ccp_size) || (!out_ccp && !ccp_size));
-  g_assert ((cea608_1 && cea608_1_len) || (!cea608_1 && !cea608_1_len));
-  g_assert ((cea608_2 && cea608_2_len) || (!cea608_2 && !cea608_2_len));
-
-  if (ccp_size) {
-    ccp_in_size = *ccp_size;
-    *ccp_size = 0;
-  }
-  if (cea608_1_len) {
-    cea608_1_in_size = *cea608_1_len;
-    *cea608_1_len = 0;
-  }
-  if (cea608_2_len) {
-    cea608_2_in_size = *cea608_2_len;
-    *cea608_2_len = 0;
-  }
-
-  if (out_ccp && self->scratch_ccp_len > 0) {
-    GST_DEBUG_OBJECT (self, "copying from previous scratch ccp buffer of "
-        "%u bytes", self->scratch_ccp_len);
-    if (ccp_in_size < *ccp_size + self->scratch_ccp_len) {
-      GST_WARNING_OBJECT (self, "output buffer too small %u < %u", ccp_in_size,
-          *ccp_size + self->scratch_ccp_len);
-      goto fail;
-    }
-    memcpy (&out_ccp[*ccp_size], self->scratch_ccp, self->scratch_ccp_len);
-    *ccp_size += self->scratch_ccp_len;
-  }
-
-  if (cea608_1 && self->scratch_cea608_1_len > 0) {
-    GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 1 "
-        "buffer of %u bytes", self->scratch_cea608_1_len);
-    if (cea608_1_in_size < *cea608_1_len + self->scratch_cea608_1_len) {
-      GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
-          cea608_1_in_size, *cea608_1_len + self->scratch_cea608_1_len);
-      goto fail;
-    }
-    memcpy (&cea608_1[*cea608_1_len], self->scratch_cea608_1,
-        self->scratch_cea608_1_len);
-    *cea608_1_len += self->scratch_cea608_1_len;
-  }
-
-  if (cea608_2 && self->scratch_cea608_2_len > 0) {
-    GST_DEBUG_OBJECT (self, "copying from previous scratch cea608 field 2 "
-        "buffer of %u bytes", self->scratch_cea608_2_len);
-    if (cea608_2_in_size < *cea608_2_len + self->scratch_cea608_2_len) {
-      GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
-          cea608_2_in_size, *cea608_2_len + self->scratch_cea608_2_len);
-      goto fail;
-    }
-    memcpy (&cea608_2[*cea608_2_len], self->scratch_cea608_2,
-        self->scratch_cea608_2_len);
-    *cea608_2_len += self->scratch_cea608_2_len;
-  }
-
-  return TRUE;
-
-fail:
-  if (ccp_size)
-    *ccp_size = 0;
-  if (cea608_1_len)
-    *cea608_1_len = 0;
-  if (cea608_2_len)
-    *cea608_2_len = 0;
-  return FALSE;
-}
-
-static gboolean
-cc_data_to_cea608_ccp (GstCCConverter * self, guint8 * cc_data,
-    guint cc_data_len, guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1,
-    guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len,
-    const struct cdp_fps_entry *in_fps_entry)
-{
-  guint ccp_in_size = 0, cea608_1_in_size = 0, cea608_2_in_size = 0;
-
-  g_assert (cc_data || cc_data_len == 0);
-
-  if (ccp_size)
-    ccp_in_size = *ccp_size;
-  if (cea608_1_len)
-    cea608_1_in_size = *cea608_1_len;
-  if (cea608_2_len)
-    cea608_2_in_size = *cea608_2_len;
-
-  if (!copy_from_stored_data (self, out_ccp, ccp_size, cea608_1, cea608_1_len,
-          cea608_2, cea608_2_len))
-    goto fail;
-
-  if (cc_data) {
-    gint ccp_offset = 0;
-    guint new_cea608_1_len = 0, new_cea608_2_len = 0;
-    guint8 *new_cea608_1 = cea608_1, *new_cea608_2 = cea608_2;
-
-    cc_data_len = compact_cc_data (cc_data, cc_data_len);
-
-    if (cc_data_len / 3 > in_fps_entry->max_cc_count) {
-      GST_WARNING_OBJECT (self, "Too many cc_data triples in CDP packet %u. "
-          "Truncating to %u", cc_data_len / 3, in_fps_entry->max_cc_count);
-      cc_data_len = 3 * in_fps_entry->max_cc_count;
-    }
-
-    while (TRUE) {
-      if (cea608_1_len) {
-        new_cea608_1_len = cea608_1_in_size - *cea608_1_len;
-        new_cea608_1 = &cea608_1[*cea608_1_len];
-      }
-      if (cea608_2_len) {
-        new_cea608_2_len = cea608_2_in_size - *cea608_2_len;
-        new_cea608_2 = &cea608_2[*cea608_2_len];
-      }
-
-      ccp_offset = cc_data_extract_cea608 (cc_data, cc_data_len, new_cea608_1,
-          &new_cea608_1_len, new_cea608_2, &new_cea608_2_len);
-
-      if (ccp_offset == CC_DATA_EXTRACT_TOO_MANY_FIELD1 && cea608_1_len) {
-        GST_WARNING_OBJECT (self, "cea608 field 1 overflow, dropping all "
-            "previously stored field 1 data and trying again");
-        *cea608_1_len = 0;
-      } else if (ccp_offset == CC_DATA_EXTRACT_TOO_MANY_FIELD2 && cea608_2_len) {
-        GST_WARNING_OBJECT (self, "cea608 field 2 overflow, dropping all "
-            "previously stored field 2 data and trying again");
-        *cea608_2_len = 0;
-      } else if (ccp_offset < 0) {
-        GST_WARNING_OBJECT (self, "Failed to extract cea608 from cc_data");
-        goto fail;
-      } else {
-        /* success */
-        break;
-      }
-    }
-
-    if ((new_cea608_1_len + new_cea608_2_len) / 2 >
-        in_fps_entry->max_cea608_count) {
-      GST_WARNING_OBJECT (self, "Too many cea608 triples in CDP packet %u. "
-          "Truncating to %u", (new_cea608_1_len + new_cea608_2_len) / 2,
-          in_fps_entry->max_cea608_count);
-      if ((new_cea608_1_len + new_cea608_2_len) / 2 >
-          in_fps_entry->max_cea608_count) {
-        new_cea608_1_len = 2 * in_fps_entry->max_cea608_count;
-        new_cea608_2_len = 0;
-      } else {
-        new_cea608_2_len =
-            2 * in_fps_entry->max_cea608_count - new_cea608_1_len;
-      }
-    }
-
-    if (cea608_1_len)
-      *cea608_1_len += new_cea608_1_len;
-    if (cea608_2_len)
-      *cea608_2_len += new_cea608_2_len;
-
-    if (out_ccp) {
-      if (ccp_in_size < *ccp_size + cc_data_len - ccp_offset) {
-        GST_WARNING_OBJECT (self, "output buffer too small %u < %u",
-            ccp_in_size, *ccp_size + cc_data_len - ccp_offset);
-        goto fail;
-      }
-      memcpy (&out_ccp[*ccp_size], &cc_data[ccp_offset],
-          cc_data_len - ccp_offset);
-      *ccp_size += cc_data_len - ccp_offset;
-    }
-  }
-
-  return TRUE;
-
-fail:
-  if (ccp_size)
-    *ccp_size = 0;
-  if (cea608_1_len)
-    *cea608_1_len = 0;
-  if (cea608_2_len)
-    *cea608_2_len = 0;
-  return FALSE;
-}
-
-static gboolean
-cdp_to_cea608_cc_data (GstCCConverter * self, GstBuffer * inbuf,
-    guint8 * out_ccp, guint * ccp_size, guint8 * cea608_1, guint * cea608_1_len,
-    guint8 * cea608_2, guint * cea608_2_len, GstVideoTimeCode * out_tc,
-    const struct cdp_fps_entry **in_fps_entry)
+push_cdp_buffer (GstCCConverter * self, GstBuffer * inbuf,
+    GstVideoTimeCode * out_tc, const struct cdp_fps_entry **in_fps_entry)
 {
   guint8 cc_data[MAX_CDP_PACKET_LEN];
   guint cc_data_len = 0;
@@ -1318,13 +640,13 @@ cdp_to_cea608_cc_data (GstCCConverter * self, GstBuffer * inbuf,
         convert_cea708_cdp_to_cc_data (GST_OBJECT (self), in.data, in.size,
         cc_data, out_tc, in_fps_entry);
 
+    cc_buffer_push_cc_data (self->cc_buffer, cc_data, cc_data_len);
+
     gst_buffer_unmap (inbuf, &in);
     self->input_frames++;
   }
 
-  return cc_data_to_cea608_ccp (self, inbuf ? cc_data : NULL, cc_data_len,
-      out_ccp, ccp_size, cea608_1, cea608_1_len, cea608_2, cea608_2_len,
-      inbuf ? *in_fps_entry : NULL);
+  return TRUE;
 }
 
 static GstFlowReturn
@@ -1416,18 +738,14 @@ convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo in, out;
   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
   guint cc_data_len = MAX_CDP_PACKET_LEN;
-  guint cea608_1_len = MAX_CDP_PACKET_LEN;
-  guint8 cc_data[MAX_CDP_PACKET_LEN], cea608_1[MAX_CEA608_LEN];
+  guint8 cc_data[MAX_CDP_PACKET_LEN];
 
   in_fps_entry = cdp_fps_entry_from_fps (self->in_fps_n, self->in_fps_d);
   if (!in_fps_entry || in_fps_entry->fps_n == 0)
     g_assert_not_reached ();
 
-  if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len, NULL, 0))
-    goto drop;
-
   if (inbuf) {
-    guint n = 0, i;
+    guint n = 0;
 
     n = gst_buffer_get_size (inbuf);
     if (n & 1) {
@@ -1445,14 +763,8 @@ convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
     }
 
     gst_buffer_map (inbuf, &in, GST_MAP_READ);
-    for (i = 0; i < n; i++) {
-      guint byte1 = in.data[i * 2 + 0];
-      guint byte2 = in.data[i * 2 + 1];
-      if (byte1 != 0x80 || byte2 != 0x80) {
-        cea608_1[cea608_1_len++] = byte1;
-        cea608_1[cea608_1_len++] = byte2;
-      }
-    }
+    cc_buffer_push_separated (self->cc_buffer, in.data, in.size, NULL, 0, NULL,
+        0);
     gst_buffer_unmap (inbuf, &in);
     self->input_frames++;
   }
@@ -1461,15 +773,12 @@ convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     g_assert_not_reached ();
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
-          cea608_1, &cea608_1_len, NULL, 0, tc_meta ? &tc_meta->tc : NULL,
-          FALSE))
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry,
+          tc_meta ? &tc_meta->tc : NULL, &self->current_output_timecode))
     goto drop;
 
-  if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
-          cea608_1_len, NULL, 0, cc_data, &cc_data_len,
-          &self->last_cea608_written_was_field1))
-    goto drop;
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, cc_data,
+      &cc_data_len);
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
   cc_data_len =
@@ -1572,7 +881,7 @@ convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo in, out;
   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
   guint cc_data_len = MAX_CDP_PACKET_LEN;
-  guint cea608_1_len = MAX_CDP_PACKET_LEN, cea608_2_len = MAX_CDP_PACKET_LEN;
+  guint cea608_1_len = 0, cea608_2_len = 0;
   guint8 cc_data[MAX_CDP_PACKET_LEN];
   guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
   guint i, n;
@@ -1581,10 +890,6 @@ convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   if (!in_fps_entry || in_fps_entry->fps_n == 0)
     g_assert_not_reached ();
 
-  if (!copy_from_stored_data (self, NULL, 0, cea608_1, &cea608_1_len,
-          cea608_2, &cea608_2_len))
-    goto drop;
-
   if (inbuf) {
     n = gst_buffer_get_size (inbuf);
     if (n % 3 != 0) {
@@ -1618,6 +923,9 @@ convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
       }
     }
     gst_buffer_unmap (inbuf, &in);
+
+    cc_buffer_push_separated (self->cc_buffer, cea608_1, cea608_1_len,
+        cea608_2, cea608_2_len, NULL, 0);
     self->input_frames++;
   }
 
@@ -1625,18 +933,12 @@ convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     g_assert_not_reached ();
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
-          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
-          tc_meta ? &tc_meta->tc : NULL,
-          self->last_cea608_written_was_field1)) {
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry,
+          tc_meta ? &tc_meta->tc : NULL, &self->current_output_timecode))
     goto drop;
-  }
 
-  if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
-          cea608_1_len, cea608_2, cea608_2_len, cc_data, &cc_data_len,
-          &self->last_cea608_written_was_field1)) {
-    goto drop;
-  }
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, cc_data,
+      &cc_data_len);
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
   cc_data_len =
@@ -1748,10 +1050,8 @@ convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo in, out;
   const struct cdp_fps_entry *in_fps_entry, *out_fps_entry;
   guint in_cc_data_len;
-  guint cc_data_len = MAX_CDP_PACKET_LEN, ccp_data_len = MAX_CDP_PACKET_LEN;
-  guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
-  guint8 cc_data[MAX_CDP_PACKET_LEN], ccp_data[MAX_CDP_PACKET_LEN];
-  guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
+  guint cc_data_len = MAX_CDP_PACKET_LEN;
+  guint8 cc_data[MAX_CDP_PACKET_LEN];
   guint8 *in_cc_data;
 
   if (inbuf) {
@@ -1772,26 +1072,16 @@ convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     g_assert_not_reached ();
 
-  if (!cc_data_to_cea608_ccp (self, in_cc_data, in_cc_data_len, ccp_data,
-          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
-          in_fps_entry)) {
-    if (inbuf)
-      gst_buffer_unmap (inbuf, &in);
-    goto drop;
-  }
-
+  cc_buffer_push_cc_data (self->cc_buffer, in_cc_data, in_cc_data_len);
   if (inbuf)
     gst_buffer_unmap (inbuf, &in);
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
-          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len,
-          tc_meta ? &tc_meta->tc : NULL, self->last_cea608_written_was_field1))
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry,
+          tc_meta ? &tc_meta->tc : NULL, &self->current_output_timecode))
     goto drop;
 
-  if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
-          cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
-          &cc_data_len, &self->last_cea608_written_was_field1))
-    goto drop;
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, cc_data,
+      &cc_data_len);
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
   cc_data_len =
@@ -1816,13 +1106,10 @@ convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
 {
   GstMapInfo out;
   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
-  guint8 cea608_1[MAX_CEA608_LEN];
-  guint cea608_1_len = MAX_CEA608_LEN;
+  guint cea608_1_len;
   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
 
-  gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
-  if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
-          NULL, NULL, &tc, &in_fps_entry)) {
+  if (!push_cdp_buffer (self, inbuf, &tc, &in_fps_entry)) {
     gst_buffer_set_size (outbuf, 0);
     return GST_FLOW_OK;
   }
@@ -1831,29 +1118,16 @@ convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     out_fps_entry = in_fps_entry;
 
-  if (fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
-          cea608_1, &cea608_1_len, NULL, NULL, &tc, FALSE)) {
-    guint i, out_size = (guint) out.size;
-
-    self->output_frames++;
-    if (!write_cea608 (self, TRUE, out_fps_entry, cea608_1, cea608_1_len, NULL,
-            0, out.data, &out_size, NULL)) {
-      gst_buffer_unmap (outbuf, &out);
-      return GST_FLOW_ERROR;
-    }
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry, &tc,
+          &self->current_output_timecode))
+    goto drop;
 
-    /* remove the first byte from each cea608 packet */
-    for (i = 0; i < out_size / 3; i++) {
-      out.data[i * 2 + 0] = out.data[i * 3 + 1];
-      out.data[i * 2 + 1] = out.data[i * 3 + 2];
-    }
-    cea608_1_len = out_size / 3 * 2;
-  } else {
-    cea608_1_len = 0;
-  }
+  gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
+  cea608_1_len = out.size;
+  cc_buffer_take_separated (self->cc_buffer, out_fps_entry, out.data,
+      &cea608_1_len, NULL, 0, NULL, 0);
   gst_buffer_unmap (outbuf, &out);
-
-  gst_buffer_set_size (outbuf, cea608_1_len);
+  self->output_frames++;
 
   if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
     gst_buffer_add_video_time_code_meta (outbuf,
@@ -1861,7 +1135,13 @@ convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
     gst_video_time_code_increment_frame (&self->current_output_timecode);
   }
 
+out:
+  gst_buffer_set_size (outbuf, cea608_1_len);
   return GST_FLOW_OK;
+
+drop:
+  cea608_1_len = 0;
+  goto out;
 }
 
 static GstFlowReturn
@@ -1871,34 +1151,31 @@ convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo out;
   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
-  guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
-  guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
-  guint i, cc_data_len;
+  guint cc_data_len;
+  int s334_len;
+  guint i;
 
-  if (!cdp_to_cea608_cc_data (self, inbuf, NULL, NULL, cea608_1, &cea608_1_len,
-          cea608_2, &cea608_2_len, &tc, &in_fps_entry))
+  if (!push_cdp_buffer (self, inbuf, &tc, &in_fps_entry))
     goto drop;
 
   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     out_fps_entry = in_fps_entry;
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, NULL, 0,
-          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
-          self->last_cea608_written_was_field1))
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry, &tc,
+          &self->current_output_timecode))
     goto drop;
 
-  cc_data_len = gst_buffer_get_sizes (outbuf, NULL, NULL);
-
   gst_buffer_map (outbuf, &out, GST_MAP_READWRITE);
-  if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
-          cea608_1_len, cea608_2, cea608_2_len, out.data, &cc_data_len,
-          &self->last_cea608_written_was_field1)) {
-    gst_buffer_unmap (outbuf, &out);
+
+  cc_data_len = out.size;
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, out.data,
+      &cc_data_len);
+  s334_len = drop_ccp_from_cc_data (out.data, cc_data_len);
+  if (s334_len < 0)
     goto drop;
-  }
 
-  for (i = 0; i < cc_data_len / 3; i++) {
+  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;
@@ -1907,7 +1184,7 @@ convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
   gst_buffer_unmap (outbuf, &out);
   self->output_frames++;
 
-  gst_buffer_set_size (outbuf, cc_data_len);
+  gst_buffer_set_size (outbuf, s334_len);
 
   if (self->current_output_timecode.config.fps_n != 0 && !tc_meta) {
     gst_buffer_add_video_time_code_meta (outbuf,
@@ -1929,34 +1206,22 @@ convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo out;
   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
-  guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
-  guint8 ccp_data[MAX_CDP_PACKET_LEN];
-  guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
-  guint ccp_data_len = MAX_CDP_PACKET_LEN;
   guint out_len = 0;
 
-  if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
-          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
+  if (!push_cdp_buffer (self, inbuf, &tc, &in_fps_entry))
     goto out;
 
   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     out_fps_entry = in_fps_entry;
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
-          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
-          self->last_cea608_written_was_field1))
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry, &tc,
+          &self->current_output_timecode))
     goto out;
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
   out_len = (guint) out.size;
-  if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
-          cea608_1, cea608_1_len, cea608_2, cea608_2_len, out.data, &out_len,
-          &self->last_cea608_written_was_field1)) {
-    gst_buffer_unmap (outbuf, &out);
-    out_len = 0;
-    goto out;
-  }
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, out.data, &out_len);
 
   gst_buffer_unmap (outbuf, &out);
   self->output_frames++;
@@ -1980,30 +1245,23 @@ convert_cea708_cdp_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
   GstMapInfo out;
   GstVideoTimeCode tc = GST_VIDEO_TIME_CODE_INIT;
   const struct cdp_fps_entry *in_fps_entry = NULL, *out_fps_entry;
-  guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
-  guint8 ccp_data[MAX_CDP_PACKET_LEN], cc_data[MAX_CDP_PACKET_LEN];
-  guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
-  guint ccp_data_len = MAX_CDP_PACKET_LEN, cc_data_len = MAX_CDP_PACKET_LEN;
+  guint8 cc_data[MAX_CDP_PACKET_LEN];
+  guint cc_data_len = MAX_CDP_PACKET_LEN;
   guint out_len = 0;
 
-  if (!cdp_to_cea608_cc_data (self, inbuf, ccp_data, &ccp_data_len,
-          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc, &in_fps_entry))
+  if (!push_cdp_buffer (self, inbuf, &tc, &in_fps_entry))
     goto out;
 
   out_fps_entry = cdp_fps_entry_from_fps (self->out_fps_n, self->out_fps_d);
   if (!out_fps_entry || out_fps_entry->fps_n == 0)
     out_fps_entry = in_fps_entry;
 
-  if (!fit_and_scale_cc_data (self, in_fps_entry, out_fps_entry, ccp_data,
-          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
-          self->last_cea608_written_was_field1))
+  if (!can_take_buffer (self, in_fps_entry, out_fps_entry, &tc,
+          &self->current_output_timecode))
     goto out;
 
-  if (!combine_cc_data (self, TRUE, out_fps_entry, ccp_data, ccp_data_len,
-          cea608_1, cea608_1_len, cea608_2, cea608_2_len, cc_data,
-          &cc_data_len, &self->last_cea608_written_was_field1)) {
-    goto out;
-  }
+  cc_buffer_take_cc_data (self->cc_buffer, out_fps_entry, cc_data,
+      &cc_data_len);
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
   out_len =
@@ -2206,14 +1464,11 @@ can_generate_output (GstCCConverter * self)
 static void
 reset_counters (GstCCConverter * self)
 {
-  self->scratch_ccp_len = 0;
-  self->scratch_cea608_1_len = 0;
-  self->scratch_cea608_2_len = 0;
   self->input_frames = 0;
   self->output_frames = 1;
   gst_video_time_code_clear (&self->current_output_timecode);
   gst_clear_buffer (&self->previous_buffer);
-  self->last_cea608_written_was_field1 = FALSE;
+  cc_buffer_discard (self->cc_buffer);
 }
 
 static GstFlowReturn
@@ -2222,9 +1477,13 @@ drain_input (GstCCConverter * self)
   GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (self);
   GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
   GstFlowReturn ret = GST_FLOW_OK;
+  guint cea608_1_len, cea608_2_len, ccp_len;
+
+  cc_buffer_get_stored_size (self->cc_buffer, &cea608_1_len, &cea608_2_len,
+      &ccp_len);
 
-  while (self->scratch_ccp_len > 0 || self->scratch_cea608_1_len > 0
-      || self->scratch_cea608_2_len > 0 || can_generate_output (self)) {
+  while (ccp_len > 0 || cea608_1_len > 0 || cea608_2_len > 0
+      || can_generate_output (self)) {
     GstBuffer *outbuf;
 
     if (!self->previous_buffer) {
@@ -2244,6 +1503,8 @@ drain_input (GstCCConverter * self)
     }
 
     ret = gst_cc_converter_transform (self, NULL, outbuf);
+    cc_buffer_get_stored_size (self->cc_buffer, &cea608_1_len, &cea608_2_len,
+        &ccp_len);
     if (gst_buffer_get_size (outbuf) <= 0) {
       /* try to move the output along */
       self->input_frames++;
@@ -2405,6 +1666,16 @@ gst_cc_converter_get_property (GObject * object, guint prop_id, GValue * value,
 }
 
 static void
+gst_cc_converter_finalize (GObject * object)
+{
+  GstCCConverter *self = GST_CCCONVERTER (object);
+
+  gst_clear_object (&self->cc_buffer);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
 gst_cc_converter_class_init (GstCCConverterClass * klass)
 {
   GObjectClass *gobject_class;
@@ -2417,6 +1688,7 @@ gst_cc_converter_class_init (GstCCConverterClass * klass)
 
   gobject_class->set_property = gst_cc_converter_set_property;
   gobject_class->get_property = gst_cc_converter_get_property;
+  gobject_class->finalize = gst_cc_converter_finalize;
 
   /**
    * GstCCConverter:cdp-mode
@@ -2471,4 +1743,5 @@ static void
 gst_cc_converter_init (GstCCConverter * self)
 {
   self->cdp_mode = DEFAULT_CDP_MODE;
+  self->cc_buffer = cc_buffer_new ();
 }
index 4202550..6792c76 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_CCCONVERTER \
   (gst_cc_converter_get_type())
@@ -40,9 +42,6 @@ G_BEGIN_DECLS
 typedef struct _GstCCConverter GstCCConverter;
 typedef struct _GstCCConverterClass GstCCConverterClass;
 
-#define MAX_CDP_PACKET_LEN 256
-#define MAX_CEA608_LEN 32
-
 typedef enum {
   GST_CC_CONVERTER_CDP_MODE_TIME_CODE   = (1<<0),
   GST_CC_CONVERTER_CDP_MODE_CC_DATA     = (1<<1),
@@ -67,21 +66,13 @@ struct _GstCCConverter
   /* for framerate differences, we need to keep previous/next frames in order
    * to split/merge data across multiple input or output buffers.  The data is
    * stored as cc_data */
-  guint8    scratch_cea608_1[MAX_CEA608_LEN];
-  guint     scratch_cea608_1_len;
-  guint8    scratch_cea608_2[MAX_CEA608_LEN];
-  guint     scratch_cea608_2_len;
-  guint8    scratch_ccp[MAX_CDP_PACKET_LEN];
-  guint     scratch_ccp_len;
+  CCBuffer *cc_buffer;
 
   guint     input_frames;
   guint     output_frames;
   GstVideoTimeCode current_output_timecode;
   /* previous buffer for copying metas onto */
   GstBuffer *previous_buffer;
-
-  /* used for tracking which field to write across output buffer boundaries */
-  gboolean last_cea608_written_was_field1;
 };
 
 struct _GstCCConverterClass
index a925087..4e8138e 100644 (file)
@@ -476,12 +476,12 @@ GST_START_TEST (convert_cea708_cdp_cea608_raw)
       { 0x96, 0x69, 0x13, 0x5f, 0x43, 0x00, 0x00, 0x72, 0xe2, 0xfc, 0x80, 0x80,
     0xfd, 0x80, 0x80, 0x74, 0x00, 0x00, 0x8a
   };
-  const guint8 out1[] = { 0x80, 0x80, 0x80, 0x80 };
+  const guint8 out1[] = { 0x80, 0x80, };
   const guint8 in2[] =
       { 0x96, 0x69, 0x13, 0x5f, 0x43, 0x00, 0x00, 0x72, 0xe2, 0xfc, 0x80, 0x81,
     0xfd, 0x82, 0x83, 0x74, 0x00, 0x00, 0x8a
   };
-  const guint8 out2[] = { 0x80, 0x81, 0x80, 0x80 };
+  const guint8 out2[] = { 0x80, 0x81, };
   check_conversion_tc_passthrough (in1, sizeof (in1), out1, sizeof (out1),
       "closedcaption/x-cea-708,format=(string)cdp,framerate=30/1",
       "closedcaption/x-cea-608,format=(string)raw");
@@ -514,7 +514,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cc_data)
   };
   const guint8 out[] = { 0xf8, 0x80, 0x80, 0xf9, 0x80, 0x80 };
   check_conversion_tc_passthrough (in, sizeof (in), out, sizeof (out),
-      "closedcaption/x-cea-708,format=(string)cdp",
+      "closedcaption/x-cea-708,format=(string)cdp,framerate=30/1",
       "closedcaption/x-cea-708,format=(string)cc_data");
 }
 
@@ -524,17 +524,23 @@ GST_START_TEST (convert_cea708_cdp_cea708_cc_data_too_big)
 {
   /* tests that too large input is truncated */
   const guint8 in[] =
-      { 0x96, 0x69, 0x2e, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xeb, 0xfc, 0x80, 0x80,
+      { 0x96, 0x69, 0x4c, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xf5, 0xfc, 0x80, 0x80,
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
     0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
     0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
-    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0x74, 0x00, 0x00, 0x8a,
+    0x74, 0x00, 0x00, 0x8a,
   };
-  const guint8 out[] = { 0xf8, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+  const guint8 out[] = { 0xf8, 0x80, 0x80, 0xf9, 0x80, 0x80, 0xfe, 0x80, 0x80,
     0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
-    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+    0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+    0xfe, 0x80, 0x80
   };
   check_conversion_tc_passthrough (in, sizeof (in), out, sizeof (out),
-      "closedcaption/x-cea-708,format=(string)cdp",
+      "closedcaption/x-cea-708,format=(string)cdp,framerate=30/1",
       "closedcaption/x-cea-708,format=(string)cc_data");
 }
 
@@ -1131,12 +1137,12 @@ GST_START_TEST (convert_cea708_cc_data_cea708_cdp_field1_overflow)
       gsize in_data_offset;
       /* take frames sequentially from the input */
       gsize in_idx = i / 2;
-      /* take the first 16 input frames, then skip the next 16 frames and take
-       * the next 16 frames etc.
-       * 32 is the byte size of the internal cea608 field buffers that we are
+      /* take the first 12 input frames, then skip the next 12 frames and take
+       * the next 12 frames etc.
+       * 24 is the byte size of the internal cea608 field buffers that we are
        * overflowing but every second buffer will have cea608 field 1 in it.
-       * 16 frames is 32 bytes stored and is enough to cause overflow */
-      in_idx = (in_idx / 16) * 32 + in_idx % 16;
+       * 12 frames is 24 bytes stored and is enough to cause overflow */
+      in_idx = (in_idx / 6) * 12 + in_idx % 6;
       in_data_offset = in_idx * 3;
 
       out_data[i * 43 + 9] = in_data[in_data_offset + 0];