ccconverter: ensure correct ordering of cea608 across output buffers
authorMatthew Waters <matthew@centricular.com>
Wed, 23 Mar 2022 06:31:37 +0000 (17:31 +1100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sat, 26 Mar 2022 00:00:36 +0000 (00:00 +0000)
e.g. if a 60fps output is configured, we can only produce a single field
of cea608 that must alternate between field 1 and field 2.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2019>

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 38ecc58..66913ab 100644 (file)
@@ -795,11 +795,14 @@ 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)
+    guint8 * out, guint * out_size, gboolean * is_last_cea608_field1)
 {
   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);
@@ -807,6 +810,9 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
   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
@@ -818,9 +824,6 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
   g_assert_cmpint (cea608_1_len + cea608_2_len, <=,
       out_fps_entry->max_cea608_count);
 
-  total_cea608_1_count = cea608_1_len;
-  total_cea608_2_count = cea608_2_len;
-
 #if 0
   /* FIXME: if cea608 field 2 is generated, field 1 needs to be generated. */
   if (cea608_1_len < cea608_2_len)
@@ -830,7 +833,7 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
   if (pad_cea608) {
     max_size = out_fps_entry->max_cea608_count * 3;
   } else {
-    max_size = total_cea608_1_count + total_cea608_2_count * 3;
+    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",
@@ -841,17 +844,24 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
   /* 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) {
-    for (i = total_cea608_1_count + total_cea608_2_count;
-        i < out_fps_entry->max_cea608_count; i++) {
-      /* try to pad evenly */
-      if (!cea608_2 || i > cea608_1_len / 2)
-        total_cea608_1_count++;
-      else
+    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++;
-      cea608_output_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",
@@ -859,19 +869,24 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
   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 (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++;
-    } 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++;
+    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) {
@@ -880,18 +895,24 @@ write_cea608 (GstCCConverter * self, gboolean pad_cea608,
       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;
   }
 
   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;
 }
@@ -900,7 +921,8 @@ 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)
+    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;
@@ -911,7 +933,7 @@ combine_cc_data (GstCCConverter * self, gboolean pad_cea608,
 
   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))
+            cea608_2, cea608_2_len, out, &cea608_size, last_cea608_is_field1))
       return FALSE;
   } else {
     cea608_size = 0;
@@ -946,7 +968,8 @@ 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)
+    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);
@@ -1020,6 +1043,8 @@ fit_and_scale_cc_data (GstCCConverter * self,
        * 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
@@ -1041,6 +1066,7 @@ fit_and_scale_cc_data (GstCCConverter * self,
       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) {
@@ -1048,10 +1074,11 @@ fit_and_scale_cc_data (GstCCConverter * self,
 
         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 >= 2 * out_fps_entry->max_cea608_count)
+        if (avail_1 + avail_2 + field2_padding >=
+            2 * out_fps_entry->max_cea608_count)
           break;
 
-        if (extra_cea608_1 > 0) {
+        if (wrote_first && extra_cea608_1 > 0) {
           extra_cea608_1 -= 2;
           g_assert_cmpint (extra_cea608_1, >=, 0);
           cea608_1_off += 2;
@@ -1060,7 +1087,8 @@ fit_and_scale_cc_data (GstCCConverter * self,
 
         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 >= 2 * out_fps_entry->max_cea608_count)
+        if (avail_1 + avail_2 + field2_padding >=
+            2 * out_fps_entry->max_cea608_count)
           break;
 
         if (extra_cea608_2 > 0) {
@@ -1068,9 +1096,17 @@ fit_and_scale_cc_data (GstCCConverter * self,
           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;
       }
 
+      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, "
@@ -1367,8 +1403,11 @@ convert_cea708_cdp_cea708_cc_data_internal (GstCCConverter * self,
     cc_count &= 0x1f;
 
     len = 3 * cc_count;
-    if (gst_byte_reader_get_remaining (&br) < len)
+    if (gst_byte_reader_get_remaining (&br) < len) {
+      GST_WARNING_OBJECT (self, "not enough bytes (%u) left for the number of "
+          "byte triples (%u)", gst_byte_reader_get_remaining (&br), cc_count);
       return 0;
+    }
 
     memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len);
   }
@@ -1708,11 +1747,13 @@ convert_cea608_raw_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
     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))
+          cea608_1, &cea608_1_len, NULL, 0, tc_meta ? &tc_meta->tc : NULL,
+          FALSE))
     goto drop;
 
   if (!combine_cc_data (self, TRUE, out_fps_entry, NULL, 0, cea608_1,
-          cea608_1_len, (guint8 *) 0x1, 0, cc_data, &cc_data_len))
+          cea608_1_len, NULL, 0, cc_data, &cc_data_len,
+          &self->last_cea608_written_was_field1))
     goto drop;
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
@@ -1871,12 +1912,14 @@ convert_cea608_s334_1a_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
 
   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)) {
+          tc_meta ? &tc_meta->tc : NULL,
+          self->last_cea608_written_was_field1)) {
     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)) {
+          cea608_1_len, cea608_2, cea608_2_len, cc_data, &cc_data_len,
+          &self->last_cea608_written_was_field1)) {
     goto drop;
   }
 
@@ -2027,12 +2070,12 @@ convert_cea708_cc_data_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
 
   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))
+          tc_meta ? &tc_meta->tc : NULL, self->last_cea608_written_was_field1))
     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))
+          &cc_data_len, &self->last_cea608_written_was_field1))
     goto drop;
 
   gst_buffer_map (outbuf, &out, GST_MAP_WRITE);
@@ -2074,12 +2117,12 @@ convert_cea708_cdp_cea608_raw (GstCCConverter * self, GstBuffer * inbuf,
     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)) {
+          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)) {
+            0, out.data, &out_size, NULL)) {
       gst_buffer_unmap (outbuf, &out);
       return GST_FLOW_ERROR;
     }
@@ -2126,14 +2169,16 @@ convert_cea708_cdp_cea608_s334_1a (GstCCConverter * self, GstBuffer * inbuf,
     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))
+          cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
+          self->last_cea608_written_was_field1))
     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)) {
+          cea608_1_len, cea608_2, cea608_2_len, out.data, &cc_data_len,
+          &self->last_cea608_written_was_field1)) {
     gst_buffer_unmap (outbuf, &out);
     goto drop;
   }
@@ -2184,13 +2229,15 @@ convert_cea708_cdp_cea708_cc_data (GstCCConverter * self, GstBuffer * inbuf,
     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))
+          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
+          self->last_cea608_written_was_field1))
     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)) {
+          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;
@@ -2233,12 +2280,13 @@ convert_cea708_cdp_cea708_cdp (GstCCConverter * self, GstBuffer * inbuf,
     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))
+          &ccp_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, &tc,
+          self->last_cea608_written_was_field1))
     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)) {
+          &cc_data_len, &self->last_cea608_written_was_field1)) {
     goto out;
   }
 
@@ -2450,6 +2498,7 @@ reset_counters (GstCCConverter * self)
   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;
 }
 
 static GstFlowReturn
@@ -2592,11 +2641,7 @@ gst_cc_converter_start (GstBaseTransform * base)
   /* Resetting this is not really needed but makes debugging easier */
   self->cdp_hdr_sequence_cntr = 0;
   self->current_output_timecode = (GstVideoTimeCode) GST_VIDEO_TIME_CODE_INIT;
-  self->input_frames = 0;
-  self->output_frames = 1;
-  self->scratch_ccp_len = 0;
-  self->scratch_cea608_1_len = 0;
-  self->scratch_cea608_2_len = 0;
+  reset_counters (self);
 
   return TRUE;
 }
index 1e3578b..4202550 100644 (file)
@@ -79,6 +79,9 @@ struct _GstCCConverter
   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 eeda581..127fb97 100644 (file)
@@ -458,10 +458,10 @@ GST_START_TEST (convert_cea708_cc_data_cea708_cdp)
 {
   const guint8 in[] = { 0xfc, 0x80, 0x80, 0xfe, 0x80, 0x80 };
   const guint8 out[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xea, 0xf9, 0x80, 0x80,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xea, 0xf8, 0x80, 0x80,
     0xfe, 0x80, 0x80, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6d
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6e
   };
   check_conversion (in, sizeof (in), out, sizeof (out),
       "closedcaption/x-cea-708,format=(string)cc_data,framerate=(fraction)60/1",
@@ -529,7 +529,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cc_data_too_big)
     0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
     0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80, 0x74, 0x00, 0x00, 0x8a,
   };
-  const guint8 out[] = { 0xf9, 0x80, 0x80, 0xfe, 0x80, 0x80, 0xfe, 0x80, 0x80,
+  const guint8 out[] = { 0xf8, 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
   };
@@ -545,7 +545,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_double_framerate)
   /* tests that packets are split exactly in half when doubling the framerate */
   const guint8 in1[] =
       { 0x96, 0x69, 0x49, 0x5f, 0x43, 0x00, 0x00, 0x72, 0xf4, 0xfc, 0x01, 0x02,
-    0xfc, 0x03, 0x04, 0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
+    0xfd, 0x03, 0x04, 0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
     0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12,
     0xfe, 0x13, 0x14, 0xfe, 0x15, 0x16, 0xfe, 0x17, 0x18, 0xfe, 0x19, 0x1a,
     0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e, 0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22,
@@ -563,10 +563,10 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_double_framerate)
     0x00, 0x00, 0x10
   };
   const guint8 out2[] = { 0x96, 0x69, 0x30, 0x8f, 0xc3, 0x00, 0x01, 0x71, 0xc1,
-    0x82, 0x03, 0x09, 0x72, 0xea, 0xfc, 0x03, 0x04, 0xfe, 0x17, 0x18, 0xfe,
+    0x82, 0x03, 0x09, 0x72, 0xea, 0xfd, 0x03, 0x04, 0xfe, 0x17, 0x18, 0xfe,
     0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e, 0xfe, 0x1f, 0x20, 0xfe,
     0x21, 0x22, 0xfe, 0x23, 0x24, 0xfe, 0x25, 0x26, 0xfe, 0x27, 0x28, 0x74,
-    0x00, 0x01, 0xc5
+    0x00, 0x01, 0xc4
   };
   const guint8 *out[] = { out1, out2 };
   guint out_len[] = { sizeof (out1), sizeof (out2) };
@@ -607,7 +607,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_half_framerate)
     0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0x74, 0x00, 0x00, 0x7a
   };
   const guint8 in2[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea,
-    0xfc, 0x14, 0x15, 0xfe, 0x16, 0x17, 0xfe, 0x18, 0x19, 0xfe, 0x1a, 0x1b,
+    0xfd, 0x14, 0x15, 0xfe, 0x16, 0x17, 0xfe, 0x18, 0x19, 0xfe, 0x1a, 0x1b,
     0xfe, 0x1c, 0x1d, 0xfe, 0x1e, 0x1f, 0xfe, 0x20, 0x21, 0xfe, 0x22, 0x23,
     0xfe, 0x24, 0x25, 0xfe, 0x26, 0x27, 0x74, 0x00, 0x01, 0x70
   };
@@ -618,12 +618,12 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_half_framerate)
 
   const guint8 out1[] =
       { 0x96, 0x69, 0x4e, 0x5f, 0xc3, 0x00, 0x00, 0x71, 0xc1, 0x82, 0x03, 0x04,
-    0x72, 0xf4, 0xfc, 0x01, 0x02, 0xfc, 0x14, 0x15, 0xfe, 0x03, 0x04, 0xfe,
+    0x72, 0xf4, 0xfc, 0x01, 0x02, 0xfd, 0x14, 0x15, 0xfe, 0x03, 0x04, 0xfe,
     0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a, 0xfe, 0x0b, 0x0c, 0xfe,
     0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0xfe,
     0x16, 0x17, 0xfe, 0x18, 0x19, 0xfe, 0x1a, 0x1b, 0xfe, 0x1c, 0x1d, 0xfe,
     0x1e, 0x1f, 0xfe, 0x20, 0x21, 0xfe, 0x22, 0x23, 0xfe, 0x24, 0x25, 0xfe,
-    0x26, 0x27, 0x74, 0x00, 0x00, 0x08
+    0x26, 0x27, 0x74, 0x00, 0x00, 0x07
   };
   const guint8 *out[] = { out1 };
   guint out_len[] = { sizeof (out1) };
@@ -673,23 +673,23 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_merge)
 
   const guint8 out1[] =
       { 0x96, 0x69, 0x58, 0x1f, 0x43, 0x00, 0x00, 0x72, 0xf9, 0xfc, 0x01, 0x02,
-    0xfc, 0x01, 0x02, 0xfc, 0x01, 0x02, 0xfe, 0x03, 0x04, 0xfe, 0x05, 0x06,
+    0xf9, 0x80, 0x80, 0xfc, 0x01, 0x02, 0xfe, 0x03, 0x04, 0xfe, 0x05, 0x06,
     0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a, 0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e,
     0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0xfe, 0x03, 0x04,
     0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a, 0xfe, 0x0b, 0x0c,
     0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14,
     0xfe, 0x03, 0x04, 0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
-    0x74, 0x00, 0x00, 0xc5
+    0x74, 0x00, 0x00, 0xcb
   };
   const guint8 out2[] =
-      { 0x96, 0x69, 0x58, 0x1f, 0x43, 0x00, 0x01, 0x72, 0xf9, 0xfc, 0x01, 0x02,
-    0xfc, 0x01, 0x02, 0xfc, 0x01, 0x02, 0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e,
+      { 0x96, 0x69, 0x58, 0x1f, 0x43, 0x00, 0x01, 0x72, 0xf9, 0xf9, 0x80, 0x80,
+    0xfc, 0x01, 0x02, 0xf9, 0x80, 0x80, 0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e,
     0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0xfe, 0x03, 0x04,
     0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a, 0xfe, 0x0b, 0x0c,
     0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14,
     0xfe, 0x03, 0x04, 0xfe, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
     0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12,
-    0x74, 0x00, 0x01, 0x83
+    0x74, 0x00, 0x01, 0x8f
   };
   const guint8 *out[] = { out1, out2 };
   guint out_len[] = { sizeof (out1), sizeof (out2) };
@@ -708,7 +708,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_split)
    * high framerate */
   const guint8 in1[] =
       { 0x96, 0x69, 0x58, 0x1f, 0x43, 0x00, 0x00, 0x72, 0xf9, 0xfc, 0x01, 0x02,
-    0xfc, 0x03, 0x04, 0xfc, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
+    0xfd, 0x03, 0x04, 0xfc, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
     0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12,
     0xfe, 0x13, 0x14, 0xfe, 0x15, 0x16, 0xfe, 0x17, 0x18, 0xfe, 0x19, 0x1a,
     0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e, 0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22,
@@ -725,9 +725,9 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_split)
     0xfe, 0x15, 0x16, 0xfe, 0x17, 0x18, 0x74, 0x00, 0x00, 0x30
   };
   const guint8 out2[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea,
-    0xfc, 0x03, 0x04, 0xfe, 0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e,
+    0xfd, 0x03, 0x04, 0xfe, 0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e,
     0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22, 0xfe, 0x23, 0x24, 0xfe, 0x25, 0x26,
-    0xfe, 0x27, 0x28, 0xfe, 0x29, 0x2a, 0x74, 0x00, 0x01, 0xe6
+    0xfe, 0x27, 0x28, 0xfe, 0x29, 0x2a, 0x74, 0x00, 0x01, 0xe5
   };
   const guint8 out3[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x02, 0x72, 0xea,
     0xfc, 0x05, 0x06, 0xfe, 0x2b, 0x2c, 0xfe, 0x2d, 0x2e, 0xfe, 0x2f, 0x30,
@@ -735,9 +735,9 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_split)
     0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0x74, 0x00, 0x02, 0x54
   };
   const guint8 out4[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x03, 0x72, 0xea,
-    0xfc, 0x01, 0x02, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0xfe, 0x15, 0x16,
+    0xfd, 0x03, 0x04, 0xfe, 0x11, 0x12, 0xfe, 0x13, 0x14, 0xfe, 0x15, 0x16,
     0xfe, 0x17, 0x18, 0xfe, 0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e,
-    0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22, 0x74, 0x00, 0x03, 0x76
+    0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22, 0x74, 0x00, 0x03, 0x71
   };
   const guint8 *out[] = { out1, out2, out3, out4 };
   guint out_len[] =
@@ -757,7 +757,7 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_split_eos)
    * high framerate and that an EOS will push the pending data */
   const guint8 in1[] =
       { 0x96, 0x69, 0x58, 0x1f, 0x43, 0x00, 0x00, 0x72, 0xf9, 0xfc, 0x01, 0x02,
-    0xfc, 0x03, 0x04, 0xfc, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
+    0xfd, 0x03, 0x04, 0xfc, 0x05, 0x06, 0xfe, 0x07, 0x08, 0xfe, 0x09, 0x0a,
     0xfe, 0x0b, 0x0c, 0xfe, 0x0d, 0x0e, 0xfe, 0x0f, 0x10, 0xfe, 0x11, 0x12,
     0xfe, 0x13, 0x14, 0xfe, 0x15, 0x16, 0xfe, 0x17, 0x18, 0xfe, 0x19, 0x1a,
     0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e, 0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22,
@@ -774,9 +774,9 @@ GST_START_TEST (convert_cea708_cdp_cea708_cdp_max_split_eos)
     0xfe, 0x15, 0x16, 0xfe, 0x17, 0x18, 0x74, 0x00, 0x00, 0x30
   };
   const guint8 out2[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea,
-    0xfc, 0x03, 0x04, 0xfe, 0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e,
+    0xfd, 0x03, 0x04, 0xfe, 0x19, 0x1a, 0xfe, 0x1b, 0x1c, 0xfe, 0x1d, 0x1e,
     0xfe, 0x1f, 0x20, 0xfe, 0x21, 0x22, 0xfe, 0x23, 0x24, 0xfe, 0x25, 0x26,
-    0xfe, 0x27, 0x28, 0xfe, 0x29, 0x2a, 0x74, 0x00, 0x01, 0xe6
+    0xfe, 0x27, 0x28, 0xfe, 0x29, 0x2a, 0x74, 0x00, 0x01, 0xe5
   };
   const guint8 out3[] = { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x02, 0x72, 0xea,
     0xfc, 0x05, 0x06, 0xfe, 0x2b, 0x2c, 0xfe, 0x2d, 0x2e, 0xfe, 0x2f, 0x30,
@@ -858,8 +858,8 @@ GST_END_TEST;
 
 GST_START_TEST (convert_cea708_cc_data_cea708_cdp_double_framerate)
 {
-  const guint8 in1[] = { 0xfc, 0x80, 0x81, 0xfc, 0x82, 0x83, 0xfe, 0x84, 0x85 };
-  const guint8 in2[] = { 0xfc, 0x86, 0x87, 0xfc, 0x88, 0x89, 0xfe, 0x8a, 0x8b };
+  const guint8 in1[] = { 0xfc, 0x80, 0x81, 0xfd, 0x82, 0x83, 0xfe, 0x84, 0x85 };
+  const guint8 in2[] = { 0xfc, 0x86, 0x87, 0xfd, 0x88, 0x89, 0xfe, 0x8a, 0x8b };
   const guint8 *in[] = { in1, in2 };
   guint in_len[] = { sizeof (in1), sizeof (in2) };
   const guint8 out1[] =
@@ -869,10 +869,10 @@ GST_START_TEST (convert_cea708_cc_data_cea708_cdp_double_framerate)
     0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x60
   };
   const guint8 out2[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea, 0xfc, 0x82, 0x83,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea, 0xfd, 0x82, 0x83,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x01, 0x67
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x01, 0x66
   };
   const guint8 out3[] =
       { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x02, 0x72, 0xea, 0xfc, 0x86, 0x87,
@@ -881,10 +881,10 @@ GST_START_TEST (convert_cea708_cc_data_cea708_cdp_double_framerate)
     0xfa, 0x00, 0x00, 0x74, 0x00, 0x02, 0x44
   };
   const guint8 out4[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x03, 0x72, 0xea, 0xfc, 0x88, 0x89,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x03, 0x72, 0xea, 0xfd, 0x88, 0x89,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x03, 0x57
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x03, 0x56
   };
   const guint8 *out[] = { out1, out2, out3, out4, };
   guint out_len[] =
@@ -900,8 +900,8 @@ GST_END_TEST;
 
 GST_START_TEST (convert_cea608_raw_cea708_cdp_double_framerate)
 {
-  const guint8 in1[] = { 0x80, 0x81, 0x82, 0x83 };
-  const guint8 in2[] = { 0x84, 0x85, 0x86, 0x87 };
+  const guint8 in1[] = { 0x80, 0x81 };
+  const guint8 in2[] = { 0x82, 0x83 };
   const guint8 *in[] = { in1, in2 };
   guint in_len[] = { sizeof (in1), sizeof (in2) };
   const guint8 out1[] =
@@ -911,22 +911,22 @@ GST_START_TEST (convert_cea608_raw_cea708_cdp_double_framerate)
     0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6d
   };
   const guint8 out2[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea, 0xfc, 0x82, 0x83,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x01, 0x72, 0xea, 0xf9, 0x80, 0x80,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x01, 0x67
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x01, 0x6f
   };
   const guint8 out3[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x02, 0x72, 0xea, 0xfc, 0x84, 0x85,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x02, 0x72, 0xea, 0xfc, 0x82, 0x83,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x02, 0x61
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x02, 0x65
   };
   const guint8 out4[] =
-      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x03, 0x72, 0xea, 0xfc, 0x86, 0x87,
+      { 0x96, 0x69, 0x2b, 0x8f, 0x43, 0x00, 0x03, 0x72, 0xea, 0xf9, 0x80, 0x80,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
     0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
-    0xfa, 0x00, 0x00, 0x74, 0x00, 0x03, 0x5b
+    0xfa, 0x00, 0x00, 0x74, 0x00, 0x03, 0x6b
   };
   const guint8 *out[] = { out1, out2, out3, out4, };
   guint out_len[] =
@@ -982,6 +982,41 @@ GST_START_TEST (convert_cea608_s334_1a_cea708_cdp_double_framerate)
 
 GST_END_TEST;
 
+GST_START_TEST (convert_cea708_cdp_cea708_cc_data_double_input_data)
+{
+  /* caps say 60fps, data has 30fps. Ensure data is taken alternatatively from
+   * each field even if there is too much input data */
+  const guint8 in1[] =
+      { 0x96, 0x69, 0x16, 0x5f, 0x43, 0x00, 0x00, 0x72, 0xe3, 0xfc, 0x81, 0x82,
+    0xfd, 0x83, 0x84, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0xff
+  };
+  /* padding buffer */
+  const guint8 in2[] =
+      { 0x96, 0x69, 0x16, 0x5f, 0x43, 0x00, 0x01, 0x72, 0xe3, 0xfc, 0x80, 0x80,
+    0xfd, 0x80, 0x80, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x01, 0xff
+  };
+  const guint8 in3[] =
+      { 0x96, 0x69, 0x16, 0x5f, 0x43, 0x00, 0x02, 0x72, 0xe3, 0xfc, 0x85, 0x86,
+    0xfd, 0x87, 0x88, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x02, 0xff
+  };
+  const guint8 *in[] = { in1, in2, in3, };
+  guint in_len[] = { sizeof (in1), sizeof (in2), sizeof (in3), };
+  /* two buffers from the first buffer, then the first half of the third input
+   * buffer */
+  const guint8 out1[] = { 0xfc, 0x81, 0x82, };
+  const guint8 out2[] = { 0xfd, 0x83, 0x84, };
+  const guint8 out3[] = { 0xfc, 0x85, 0x86, };
+  const guint8 *out[] = { out1, out2, out3, };
+  guint out_len[] = { sizeof (out1), sizeof (out2), sizeof (out3), };
+  check_conversion_multiple (G_N_ELEMENTS (in_len), in, in_len,
+      G_N_ELEMENTS (out_len), out, out_len,
+      "closedcaption/x-cea-708,format=(string)cdp,framerate=(fraction)60/1",
+      "closedcaption/x-cea-708,format=(string)cc_data,framerate=(fraction)60/1",
+      NULL, NULL, 0);
+}
+
+GST_END_TEST;
+
 static Suite *
 ccextractor_suite (void)
 {
@@ -1017,6 +1052,7 @@ ccextractor_suite (void)
   tcase_add_test (tc, convert_cea708_cc_data_cea708_cdp_double_framerate);
   tcase_add_test (tc, convert_cea608_raw_cea708_cdp_double_framerate);
   tcase_add_test (tc, convert_cea608_s334_1a_cea708_cdp_double_framerate);
+  tcase_add_test (tc, convert_cea708_cdp_cea708_cc_data_double_input_data);
 
   return s;
 }