diff --git a/configure.ac b/configure.ac
index 4167738..fb489a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -363,6 +363,7 @@ AG_GST_CHECK_PLUGIN(audiovisualizers)
 AG_GST_CHECK_PLUGIN(autoconvert)
 AG_GST_CHECK_PLUGIN(bayer)
 AG_GST_CHECK_PLUGIN(camerabin2)
+AG_GST_CHECK_PLUGIN(ccdec)
 AG_GST_CHECK_PLUGIN(cdxaparse)
 AG_GST_CHECK_PLUGIN(coloreffects)
 AG_GST_CHECK_PLUGIN(dataurisrc)
@@ -2370,6 +2372,7 @@ gst/audiovisualizers/Makefile
 gst/autoconvert/Makefile
 gst/bayer/Makefile
 gst/camerabin2/Makefile
+gst/ccdec/Makefile
 gst/cdxaparse/Makefile
 gst/coloreffects/Makefile
 gst/dataurisrc/Makefile
diff --git a/gst/ccdec/Makefile.am b/gst/ccdec/Makefile.am
new file mode 100644
index 0000000..75e6d53
--- /dev/null
+++ b/gst/ccdec/Makefile.am
@@ -0,0 +1,32 @@
+plugin_LTLIBRARIES = libgstccdecbad.la
+
+libgstccdecbad_la_SOURCES = plugin.c gstccdec.c gstcea708dec.c
+
+libgstccdecbad_la_CFLAGS = \
+	$(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \
+	-DGST_USE_UNSTABLE_API \
+	$(GST_BASE_CFLAGS) $(GST_CFLAGS)
+
+libgstccdecbad_la_LIBADD = \
+	$(GST_PLUGINS_BASE_LIBS) -lgstpbutils-$(GST_API_VERSION) -lgstvideo-$(GST_API_VERSION) \
+	-lgsttag-$(GST_API_VERSION) $(GST_BASE_LIBS) $(GST_LIBS)
+libgstccdecbad_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstccdecbad_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS =
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+	androgenizer \
+	-:PROJECT libgstccdecbad -:SHARED libgstccdecbad \
+	 -:TAGS eng debug \
+         -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+	 -:SOURCES $(libgstccdecbad_la_SOURCES) \
+	 -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstccdecbad_la_CFLAGS) \
+	 -:LDFLAGS $(libgstccdecbad_la_LDFLAGS) \
+	           $(libgstccdecbad_la_LIBADD) \
+	           -ldl \
+	 -:LIBFILTER_STATIC gstbaseparse-@GST_API_VERSION@ \
+	                    gstcodecparsers-@GST_API_VERSION@ \
+	 -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
+		       LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \
+	> $@
diff --git a/gst/ccdec/gstccdec.c b/gst/ccdec/gstccdec.c
new file mode 100644
index 0000000..5710cc1
--- /dev/null
+++ b/gst/ccdec/gstccdec.c
@@ -0,0 +1,713 @@
+/* GStreamer CC decoder
+ * Copyright (C) 2013 CableLabs, Louisville, CO 80027
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Alternatively, the contents of this file may be used under the
+ * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
+ * which case the following provisions apply instead of the ones
+ * mentioned above:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-ccdec
+ *
+ * ccdec extracts 608 and 708 closed caption data from picture user data.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gstccdec.h"
+#include "gstcea708dec.h"
+
+#define GST_LICENSE "LGPL"
+
+#undef SUPPORT_CEA608
+#undef STANDALONE_ELEMENT
+
+#define GST_CAT_DEFAULT gst_cc_decoder_debug
+#define gst_cc_decoder_parent_class parent_class
+
+#define CCTYPE_VALID_MASK  0x04
+#define CCTYPE_TYPE_MASK   0x03
+
+#define ATSC_USER_DATA_TYPE(pkt) ((pkt)[4])
+#define ATSC_USER_DATA_TYPE_CCDATA 0x03
+
+/* pad templates */
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-user-data"));
+
+static GstStaticPadTemplate cea608_src_template =
+GST_STATIC_PAD_TEMPLATE ("cea608_src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("text/x-cea608-raw"));
+
+static GstStaticPadTemplate cea708_src_template =
+GST_STATIC_PAD_TEMPLATE ("cea708_src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("text/x-cea708-raw"));
+
+typedef enum
+{
+  CCTYPE_608_CC1 = 0,
+  CCTYPE_608_CC2,
+  CCTYPE_708_ADD,
+  CCTYPE_708_START,
+} DtvccType;
+
+#define NUM_608_CCTYPES 2
+#define CCTYPE_PAD_NAME(cc_type) ((cc_type <= CCTYPE_608_CC2) ? "cea608_srcpad" : "cea708_srcpad")
+
+/* This element's private data */
+struct _GstCcDecoder
+{
+  /* Storage for parent Element */
+  GstElement element;
+
+  /* Element pads */
+  GstPad *sinkpad;
+  GstPad *cea608_srcpad;
+  GstPad *cea708_srcpad;
+  gboolean need_no_more_pads;
+
+  /* input data storage */
+  GSList *data_list;
+
+  /* Output data storage */
+  /* There are two possible 608 streams */
+  gint16 cea608_index[NUM_608_CCTYPES];
+  gint16 cea708_index;
+  guint8 cea608_buffer[NUM_608_CCTYPES][DTVCC_LENGTH];
+  guint8 cea708_buffer[DTVCC_LENGTH];
+
+  /* holds the most recent segment data for a new segment event */
+  GstSegment segment;
+};
+
+struct _GstCcDecoderClass
+{
+  /* Storage for parent ElementClass */
+  GstElementClass parent_class;
+};
+
+GST_DEBUG_CATEGORY_STATIC (gst_cc_decoder_debug);
+
+G_DEFINE_TYPE (GstCcDecoder, gst_cc_decoder, GST_TYPE_ELEMENT);
+
+static void gst_cc_decoder_dispose (GObject * object);
+static gboolean gst_cc_decoder_sink_events (GstPad * pad,
+    GstObject * parent, GstEvent * event);
+static GstFlowReturn gst_cc_decoder_chain (GstPad * pad,
+    GstObject * parent, GstBuffer * buf);
+
+
+static void
+gst_cc_decoder_class_init (GstCcDecoderClass * klass)
+{
+  GObjectClass *gobject_klass;
+  GstElementClass *gstelement_klass;
+
+  gobject_klass = G_OBJECT_CLASS (klass);
+  gstelement_klass = GST_ELEMENT_CLASS (klass);
+
+  gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_cc_decoder_dispose);
+
+  /* Pads */
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&sink_template));
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&cea608_src_template));
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&cea708_src_template));
+
+  gst_element_class_set_metadata (gstelement_klass,
+      "ccdec",
+      "Decoder", "closed caption decoding element",
+      "CableLabs RUIH-RI Team <ruihri@cablelabs.com>");
+
+#ifndef STANDALONE_ELEMENT
+  GST_DEBUG_CATEGORY_INIT (gst_cc_decoder_debug, "ccdec", 0, "CC Decoder");
+#endif
+#ifdef DEBUG_ELEMENT_SIZE
+  g_message ("%s %d = sizeof(_GstCcDecoder)\n", __func__,
+      sizeof (struct _GstCcDecoder));
+#endif
+}
+
+static void
+gst_cc_decoder_dispose (GObject * object)
+{
+  GstCcDecoder *decoder = GST_CC_DECODER (object);
+
+  while (decoder->data_list) {
+    gst_buffer_unref (decoder->data_list->data);
+    decoder->data_list = g_slist_remove (decoder->data_list,
+        decoder->data_list->data);
+  }
+
+  GST_INFO_OBJECT (decoder, "complete");
+  G_OBJECT_CLASS (gst_cc_decoder_parent_class)->dispose (object);
+}
+
+static void
+gst_cc_decoder_init (GstCcDecoder * decoder)
+{
+  GST_DEBUG_OBJECT (decoder, "called");
+  decoder->data_list = NULL;
+  gst_segment_init (&decoder->segment, GST_FORMAT_UNDEFINED);
+
+  // Add sink pad
+  decoder->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
+  gst_pad_set_event_function (decoder->sinkpad, gst_cc_decoder_sink_events);
+  gst_pad_set_chain_function (decoder->sinkpad, gst_cc_decoder_chain);
+  gst_element_add_pad (GST_ELEMENT (decoder), decoder->sinkpad);
+  GST_INFO_OBJECT (decoder, "sink pad added to decoder");
+
+  decoder->cea608_srcpad = NULL;
+  decoder->cea708_srcpad = NULL;
+  decoder->need_no_more_pads = TRUE;
+  decoder->cea708_index = 0;
+  for (size_t i = 0; i < NUM_608_CCTYPES; ++i) {
+    decoder->cea608_index[i] = 0;
+  }
+}
+
+static gboolean
+gst_cc_decoder_sink_events (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  gboolean ret_val = FALSE;
+  GstCcDecoder *decoder = GST_CC_DECODER (parent);
+
+  if (GST_EVENT_SEGMENT == GST_EVENT_TYPE (event)) {
+    GST_INFO_OBJECT (decoder, "Received SEGMENT event on %s pad",
+        gst_pad_get_name (pad));
+    gst_event_copy_segment (event, &decoder->segment);
+  }
+
+  ret_val = gst_pad_event_default (pad, parent, event);
+  return ret_val;
+}
+
+static gboolean
+gst_cc_decoder_send_new_segment (GstCcDecoder * decoder, GstPad * srcpad)
+{
+  if (NULL != decoder && NULL != srcpad) {
+    GstEvent *event;
+
+    if (decoder->segment.format == GST_FORMAT_UNDEFINED) {
+      GST_WARNING_OBJECT (decoder, "No segment received before first buffer");
+    }
+
+    GST_INFO_OBJECT (decoder, "Sending segment %" GST_SEGMENT_FORMAT,
+        &decoder->segment);
+    event = gst_event_new_segment (&decoder->segment);
+    return gst_pad_push_event (srcpad, event);
+  }
+
+  return FALSE;
+}
+
+static GstPad *
+gst_cc_decoder_get_srcpad (GstCcDecoder * decoder, DtvccType cc_type)
+{
+  gchar *start_id = NULL;
+  GstPad **pad;
+  GstStaticPadTemplate *template;
+  switch (cc_type) {
+    case CCTYPE_608_CC1:
+    case CCTYPE_608_CC2:
+      pad = &decoder->cea608_srcpad;
+      template = &cea608_src_template;
+#ifdef SUPPORT_CEA608
+      break;
+#else
+      /* no CEA-608 support, return early to prevent adding the pad */
+      return NULL;
+#endif
+    case CCTYPE_708_ADD:
+    case CCTYPE_708_START:
+      pad = &decoder->cea708_srcpad;
+      template = &cea708_src_template;
+      break;
+    default:
+      GST_ERROR_OBJECT (decoder, "attempted to get srcpad for unknown type %d",
+          cc_type);
+      return 0;
+  }
+
+  if (*pad == NULL) {
+    if (!decoder->need_no_more_pads) {
+      GST_WARNING_OBJECT (decoder, "adding %s after no_more_pads",
+          CCTYPE_PAD_NAME (cc_type));
+    }
+    *pad = gst_pad_new_from_static_template (template, NULL);
+    GST_INFO_OBJECT (decoder, "%s added to decoder", CCTYPE_PAD_NAME (cc_type));
+    gst_pad_set_active (*pad, TRUE);
+    start_id =
+        gst_pad_create_stream_id_printf (*pad, GST_ELEMENT (decoder), NULL);
+    gst_pad_push_event (*pad, gst_event_new_stream_start (start_id));
+    g_free (start_id);
+    gst_element_add_pad (GST_ELEMENT (decoder), *pad);
+    gst_cc_decoder_send_new_segment (decoder, *pad);
+  }
+  return *pad;
+}
+
+static void
+gst_cc_decoder_send_buffer (GstCcDecoder * decoder, guint8 * data, gint length,
+    GstPad * pad, UserData * user_data)
+{
+  GstBuffer *outbuf = NULL;
+
+  if (0 < length && NULL != pad) {
+    outbuf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, data,
+        length, 0, length, NULL, NULL);
+    GST_LOG_OBJECT (decoder, "%p[%d] = %02X:%02X:%02X:%02X",
+        data, length, data[0], data[1], data[2], data[3]);
+
+    if (outbuf) {
+      GST_BUFFER_PTS (outbuf) = user_data->timestamp;
+      GST_BUFFER_DTS (outbuf) = user_data->timestamp;
+      gst_pad_push (pad, outbuf);
+    } else {
+      GST_ERROR_OBJECT (decoder, "ERROR allocating %d", length);
+    }
+  }
+}
+
+static void
+gst_cc_decoder_process_packet (GstCcDecoder * decoder, UserData * user_data,
+    guint8 cc_type)
+{
+  gint16 *index = NULL;
+  guint8 *buffer = NULL;
+
+  switch (cc_type) {
+    case CCTYPE_608_CC1:
+    case CCTYPE_608_CC2:
+      index = &decoder->cea608_index[cc_type];
+      buffer = decoder->cea608_buffer[cc_type];
+      break;
+
+    case CCTYPE_708_ADD:
+    case CCTYPE_708_START:
+      index = &decoder->cea708_index;
+      buffer = decoder->cea708_buffer;
+      break;
+    default:
+      GST_ERROR_OBJECT (decoder,
+          "attempted to process packet for unknown cc_type %d", cc_type);
+      return;
+  }
+
+  if (*index > 0) {
+    GstPad *srcpad = gst_cc_decoder_get_srcpad (decoder, cc_type);
+    gst_cc_decoder_send_buffer (decoder, buffer, *index, srcpad, user_data);
+    *index = 0;
+  }
+}
+
+static void
+gst_cc_decoder_user_data_decode (GstCcDecoder * decoder, UserData * user_data)
+{
+  guint index;
+  guint8 temp;
+  guint8 cc_count;
+  gboolean process_cc_data_flag;
+  gboolean process_em_data_flag;
+  gboolean additional_data_flag;
+  guint8 em_data;
+  guint i;
+  guint8 cc_type;
+  guint8 cc_valid;
+  guint8 cc_data[2];
+
+  // we need at least 6 bytes of data to process 1 ATSC packet
+  if (user_data->length < 6) {
+    GST_ERROR ("ATSC user data packet underflow!");
+    return;
+  }
+
+  GST_LOG_OBJECT (decoder, "tr=%d timestamp=%" G_GUINT64_FORMAT " data[4]=%d",
+      user_data->tr, user_data->timestamp, user_data->data[4]);
+
+  if (ATSC_USER_DATA_TYPE (user_data->data) == ATSC_USER_DATA_TYPE_CCDATA) {
+    // skip the 4 byte UserData header and the ATSC type byte
+    index = 5;
+    temp = user_data->data[index++];
+    process_em_data_flag = (temp & 0x80) ? TRUE : FALSE;
+    process_cc_data_flag = (temp & 0x40) ? TRUE : FALSE;
+    additional_data_flag = (temp & 0x20) ? TRUE : FALSE;
+    cc_count = (temp & 0x1F);
+
+    GST_LOG_OBJECT (decoder, "ATSC user data: process_em_df=%d "
+        "process_cc_df=%d additional_df=%d cc_count=%d", process_em_data_flag,
+        process_cc_data_flag, additional_data_flag, cc_count);
+
+    em_data = user_data->data[index++]; // not used currently
+    (void) em_data;
+
+    if (process_cc_data_flag) {
+      // verify ther is enough data in the packet!
+      if (user_data->length < (cc_count * 3)) {
+        GST_ERROR ("DTVCC user data packet underflow!");
+        return;
+      }
+
+      for (i = 0; i < cc_count; i++) {
+        temp = user_data->data[index++];
+        cc_data[0] = user_data->data[index++];
+        cc_data[1] = user_data->data[index++];
+        cc_valid = (temp & CCTYPE_VALID_MASK) ? TRUE : FALSE;
+        cc_type = (temp & CCTYPE_TYPE_MASK);
+
+        GST_LOG_OBJECT (decoder, "cc_data_pkt(%d): cc_valid=%d cc_type=%d "
+            "cc_data[0]=0x%X cc_data[1]=0x%X",
+            i, cc_valid, cc_type, cc_data[0], cc_data[1]);
+
+        // accumulate dvtcc packet
+        switch (cc_type) {
+          case CCTYPE_608_CC1:
+          case CCTYPE_608_CC2:
+            if (cc_valid) {
+              if (decoder->cea608_index[cc_type] <= DTVCC_LENGTH - 2) {
+                for (size_t i = 0; i < 2; ++i) {
+                  if ((cc_data[i] < ' ') || (cc_data[i] > '~')) {
+                    gst_cc_decoder_process_packet (decoder, user_data, cc_type);
+                  }
+                  decoder->cea608_buffer[cc_type][decoder->
+                      cea608_index[cc_type]++] = cc_data[i];
+                }
+              } else {
+                GST_ERROR_OBJECT (decoder, "cea608_buffer[%d] overflow!",
+                    cc_type);
+              }
+            }
+            break;
+
+          case CCTYPE_708_ADD:
+          case CCTYPE_708_START:
+            if (cc_valid) {
+              if (cc_type == CCTYPE_708_START) {
+                // The previous packet is complete
+                gst_cc_decoder_process_packet (decoder, user_data, cc_type);
+              }
+              // Add on to the current DTVCC packet
+              if (decoder->cea708_index <= DTVCC_LENGTH - 2) {
+                decoder->cea708_buffer[decoder->cea708_index++] = cc_data[0];
+                decoder->cea708_buffer[decoder->cea708_index++] = cc_data[1];
+              } else {
+                GST_ERROR_OBJECT (decoder, "cea708_buffer overflow!");
+              }
+            } else if (cc_type == CCTYPE_708_ADD) {
+              // This packet should be ignored, but if there is a current
+              // DTVCC packet then this is the end.
+              gst_cc_decoder_process_packet (decoder, user_data, cc_type);
+            }
+            break;
+        }
+      }
+    }
+  }
+}
+
+static void
+gst_cc_decoder_prime_decode_chain (GstCcDecoder * decoder, UserData * user_data)
+{
+  guint index;
+  gboolean process_cc_data_flag;
+  DtvccType cc_type;
+
+  if (ATSC_USER_DATA_TYPE (user_data->data) == ATSC_USER_DATA_TYPE_CCDATA) {
+    // skip the 4 byte UserData header and the ATSC type byte
+    index = 5;
+    process_cc_data_flag = (user_data->data[index] & 0x40) ? TRUE : FALSE;
+
+    if (process_cc_data_flag) {
+      // skip the EM data and to the CC packets
+      index = 13;
+      cc_type = user_data->data[index] & CCTYPE_TYPE_MASK;
+
+      switch (cc_type) {
+        case CCTYPE_608_CC1:
+        case CCTYPE_608_CC2:
+          if (NULL == decoder->cea608_srcpad) {
+            // send a DTVCC NULL to complete the pipeline instantiation...
+            decoder->cea608_index[cc_type] = 0;
+            decoder->cea608_buffer[cc_type][decoder->cea608_index[cc_type]++] =
+                0x80;
+            decoder->cea608_buffer[cc_type][decoder->cea608_index[cc_type]] =
+                0x00;
+          }
+          break;
+
+        case CCTYPE_708_ADD:
+        case CCTYPE_708_START:
+          if (NULL == decoder->cea708_srcpad) {
+            // send a DTVCC NULL to complete the pipeline instantiation...
+            decoder->cea708_index = 0;
+            decoder->cea708_buffer[decoder->cea708_index++] = 0x02;
+            decoder->cea708_buffer[decoder->cea708_index++] = 0x22;
+            decoder->cea708_buffer[decoder->cea708_index++] = 0x00;
+            decoder->cea708_buffer[decoder->cea708_index] = 0x00;
+          }
+          break;
+      }
+
+      gst_cc_decoder_process_packet (decoder, user_data, cc_type);
+    }
+  }
+}
+
+static gint
+gst_cc_decoder_buflist_compare (gconstpointer element_A,
+    gconstpointer element_B)
+{
+  UserData *user_data_A = NULL;
+  UserData *user_data_B = NULL;
+  GstMapInfo elt_map_A = { 0 };
+  GstMapInfo elt_map_B = { 0 };
+  gint ret_val = 0;
+
+  gst_buffer_map (GST_BUFFER (element_A), &elt_map_A, GST_MAP_READ);
+  user_data_A = (UserData *) elt_map_A.data;
+  gst_buffer_map (GST_BUFFER (element_B), &elt_map_B, GST_MAP_READ);
+  user_data_B = (UserData *) elt_map_B.data;
+
+  if (user_data_A->tr < user_data_B->tr) {
+    ret_val = -1;
+  } else if (user_data_A->tr > user_data_B->tr) {
+    ret_val = 1;
+  } else {
+    GST_ERROR ("duplicated TR(%d) w/o GOP processing!", user_data_B->tr);
+  }
+
+  gst_buffer_unmap (GST_BUFFER (element_A), &elt_map_A);
+  gst_buffer_unmap (GST_BUFFER (element_B), &elt_map_B);
+  return ret_val;
+}
+
+/*=============================================================================
+    gst_cc_decoder_chain()
+
+    This function accepts a user data buffer.
+
+    see ATSC Digital Television Standard:
+        A/53 Part 4 – MPEG-2 Video System Characteristics
+
+    Picture user data:
+
+    user_data() {
+      user_data_start_code 32 bslbf
+      user_data_identifier 32 bslbf
+      user_structure()
+    }
+
+    user_data_start_code = 0x000001B2
+    user_data_identifier = 0x47413934    (for ATSC user data)
+
+    ATSC_user_data() {
+      user_data_type_code 8 uimsbf
+      user_data_type_structure()
+    }
+
+    user_data_type_code value   user_data_type_structure()
+    -------------------------   --------------------------
+    0x00 – 0x02                 ATSC Reserved
+    0x03                        MPEG_cc_data() (as defined in Table 6.10)
+    0x04 – 0x05                 ATSC Reserved
+    0x06                        bar_data() (as defined in Section 6.2.3.2)
+    0x07 – 0xFF                 ATSC Reserved
+
+    if (user_data_type_code == 3) then user_data_type_structure =
+
+    MPEG_cc_data() {
+      cc_data()
+      marker_bits 8 ‘1111 1111’
+    }
+
+    ----- cc_data() start ------
+    1 bit (b7)  process_em_data_flag
+    1 bit (b6)  process_cc_data_flag
+    1 bit (b5)  additional_data_flag
+    5 bits (b0-b4)  cc_count (uimsbf)
+
+    8 bits  em_data (meaning not yet defined in ATSC a/53 standard)
+
+    cc_count*3  cc_data_pkt's  (each cc_data_pkt is 3 bytes long)
+
+    8 bits  marker_bits (all 1's)
+
+    OPT: Y bits + bytes 0x0,0x0,0x1 ATSC_reserved_user_data
+    ----- cc_data() end ---------
+
+    A cc_data_pkt is 3 bytes as follows:
+    -------------------------------------------
+    5 bits (b7-b3)  marker_bits (should be all 1's)
+    1 bit (b2)      cc_valid
+    2 bits (b1-b0)  cc_type (bslbf)
+
+    8 bits          cc_data_1 (bslbf)
+
+    8 bits          cc_data_2 (bslbf)
+
+    If cc_valid != 1, then ignore this packet
+
+    cc_type has these values:
+    0   NTSC_CC_FIELD_1     - CEA-608
+    1   NTSC_CC_FIELD_2     - CEA-608
+    2   DTVCC_PACKET_DATA   - CEA-708
+    3   DTVCC_PACKET_START  - CEA-708
+
+    DTVCC packet (aka. caption channel packet)
+    This is formed by accumulating cc_data_1/cc_data_2 from each cc_data_pkt
+    starting with a packet where cc_type = 3, and ending with a packet
+    where again cc_type = 3 (start of next buffer), or cc_valid=0 && cc_type=2
+    --------------------------------------------------------------------------
+    2 bits (b6-b7)  sequence_number
+    6 bits (b0-b5)  packet_size
+    ((packet_size*2-1)&0xFF) * 8 bits packet_data (Service Block)
+=============================================================================*/
+
+static GstFlowReturn
+gst_cc_decoder_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
+{
+  GstFlowReturn ret_val = GST_FLOW_OK;
+  GstCcDecoder *decoder = GST_CC_DECODER (parent);
+  UserData *user_data = NULL;
+  GstMapInfo buf_map = { 0 };
+  GstMapInfo data_map = { 0 };
+  guint8 tr0_count;
+  gint8 previous_tr;
+
+  gst_buffer_map (buf, &buf_map, GST_MAP_READ);
+  user_data = (UserData *) buf_map.data;
+
+  if (g_str_has_prefix ((const gchar *) (user_data->data), "GA94") ||
+      g_str_has_prefix ((const gchar *) (user_data->data), "DTG1")) {
+    gst_cc_decoder_prime_decode_chain (decoder, user_data);
+    decoder->data_list =
+        g_slist_insert_sorted (decoder->data_list, buf,
+        gst_cc_decoder_buflist_compare);
+    GST_LOG_OBJECT (decoder, "inserted: %c%c%c%c", user_data->data[0],
+        user_data->data[1], user_data->data[2], user_data->data[3]);
+    gst_buffer_unmap (buf, &buf_map);
+  } else if (g_str_has_prefix ((const gchar *) (user_data->data), "GOP\0")) {
+    // New GOP header received, process existing array of user data packets
+    for (tr0_count = 0, previous_tr = -1; NULL != decoder->data_list;) {
+      gst_buffer_map (decoder->data_list->data, &data_map, GST_MAP_READ);
+      user_data = (UserData *) data_map.data;
+
+      // Validate the CC data stream, it should be in sorted (based on
+      // the temporal reference (tr)) and contiguous.
+      if (user_data->tr == 0) {
+        tr0_count++;
+
+        if (tr0_count > 1) {
+          // If a GOP is missed, and two GOP sets of frames are interleaved in
+          // the sorted list caption data will be currupt until the next cycle.
+          GST_WARNING_OBJECT (decoder, "Missed previous GOP processing!");
+        }
+      } else if ((user_data->tr - previous_tr) > 1) {
+        // Missing frames may result in missing caption characters
+        GST_WARNING_OBJECT (decoder, "Missing video frame %d!",
+            user_data->tr - 1);
+      } else if (user_data->tr == previous_tr) {
+        // Most likely caused by a missing GOP
+        GST_WARNING_OBJECT (decoder, "Duplicate video frame %d", user_data->tr);
+      }
+
+      previous_tr = user_data->tr;
+      GST_LOG_OBJECT (decoder,
+          "TR:%d, LEN:%d, UD:(%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X)",
+          user_data->tr, user_data->length,
+          user_data->data[0], user_data->data[1], user_data->data[2],
+          user_data->data[3], user_data->data[4], user_data->data[5],
+          user_data->data[6], user_data->data[7]);
+      gst_cc_decoder_user_data_decode (decoder, user_data);
+      gst_buffer_unmap (decoder->data_list->data, &data_map);
+      gst_buffer_unref (decoder->data_list->data);
+      decoder->data_list = g_slist_remove (decoder->data_list,
+          decoder->data_list->data);
+
+      // we've seen a whole GOP's worth of CC data, emit no_more_pads if needed
+      if (decoder->need_no_more_pads) {
+        decoder->need_no_more_pads = FALSE;
+        gst_element_no_more_pads (GST_ELEMENT (decoder));
+        GST_INFO_OBJECT (decoder, "no_more_pads");
+      }
+    }
+
+    gst_buffer_unmap (buf, &buf_map);
+    gst_buffer_unref (buf);
+  } else {
+    GST_ERROR_OBJECT (decoder, "unknown data, slist:%p", decoder->data_list);
+    gst_buffer_unmap (buf, &buf_map);
+    gst_buffer_unref (buf);
+  }
+
+  return ret_val;
+}
+
+#ifdef STANDALONE_ELEMENT
+static gboolean
+decoder_init (GstPlugin * plugin)
+{
+  gboolean res = FALSE;
+
+  GST_DEBUG_CATEGORY_INIT (gst_cc_decoder_debug, "ccdec", 0, "CC Decoder");
+  res = gst_element_register (plugin, "ccdec",
+      GST_RANK_PRIMARY, GST_TYPE_CC_DECODER);
+  return res;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    ccdecbad,
+    "ccdec",
+    decoder_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
+#endif
diff --git a/gst/ccdec/gstccdec.h b/gst/ccdec/gstccdec.h
new file mode 100644
index 0000000..70f9f97
--- /dev/null
+++ b/gst/ccdec/gstccdec.h
@@ -0,0 +1,76 @@
+/* GStreamer CC filter
+ * Copyright (C) 2013 CableLabs, Louisville, CO 80027
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Alternatively, the contents of this file may be used under the
+ * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
+ * which case the following provisions apply instead of the ones
+ * mentioned above:
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_CCDEC_H__
+#define __GST_CCDEC_H__
+
+#include <gst/gst.h>
+
+#define GST_TYPE_CC_DECODER            (gst_cc_decoder_get_type())
+#define GST_CC_DECODER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CC_DECODER,GstCcDecoder))
+#define GST_CC_DECODER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CC_DECODER,GstCcDecoderClass))
+#define GST_IS_CC_DECODER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CC_DECODER))
+#define GST_IS_CC_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CC_DECODER))
+
+// from ATSC A/53 Part 4
+// DTVCC packets are 128 bytes MAX, length is only 6 bits, header is 2 bytes,
+// the last byte is flag-fill, that leaves 125 possible bytes of data to be
+// represented in 6 bits, hence the length encoding
+#define DTVCC_LENGTH       128   // should never be more than 128
+#define DTVCC_PKT_SIZE(sz_byte)  (((sz_byte) == 0) ? 127 : ((sz_byte)  * 2) -1)
+
+/* incoming buffer to this element */
+typedef struct
+{
+  guint64 timestamp;
+  guint16 tr;
+  guint16 length;
+  guint8 data[0];
+} UserData;
+
+
+typedef struct _GstCcDecoder GstCcDecoder;
+typedef struct _GstCcDecoderClass GstCcDecoderClass;
+
+GType gst_cc_decoder_get_type (void);
+
+#endif /* __GST_CCDEC_H__ */
diff --git a/gst/ccdec/plugin.c b/gst/ccdec/plugin.c
new file mode 100644
index 0000000..49cc8bf
--- /dev/null
+++ b/gst/ccdec/plugin.c
@@ -0,0 +1,48 @@
+/* GStreamer video parsers
+ * Copyright (C) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
+ * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstccdec.h"
+#include "gstcea708dec.h"
+#include <gst/tag/tag.h>
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = FALSE;
+
+  gst_tag_register_musicbrainz_tags ();
+
+  ret |= gst_element_register (plugin, "ccdec",
+      GST_RANK_PRIMARY + 1, GST_TYPE_CC_DECODER);
+  ret |= gst_element_register (plugin, "cea708dec",
+      GST_RANK_PRIMARY + 1, GST_TYPE_CEA708DEC);
+
+  return ret;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    ccdecbad,
+    "ccdecoders",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
