diff --git a/configure.ac b/configure.ac
index b1ee48d..6c05e13 100644
--- a/configure.ac
+++ b/configure.ac
@@ -362,6 +362,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)
@@ -2359,6 +2360,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..ed909bc
--- /dev/null
+++ b/gst/ccdec/Makefile.am
@@ -0,0 +1,32 @@
+plugin_LTLIBRARIES = libgstccdecbad.la
+
+libgstccdecbad_la_SOURCES = gstccfilter.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) \
+	$(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/gstccfilter.c b/gst/ccdec/gstccfilter.c
new file mode 100644
index 0000000..f8dd11d
--- /dev/null
+++ b/gst/ccdec/gstccfilter.c
@@ -0,0 +1,2121 @@
+/* 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.
+ */
+
+/**
+ * SECTION:element-ccfilter
+ *
+ * ccfilter extracts 708 closed captions from picture user data.
+ * 708 windows are simulated and Webvtt formatted cues are generated
+ * when segments of text are cleared from the screen (such as when a
+ * window is deleted or hidden).
+ *
+ * TODO: implement scrolling in the simulated 708 windows.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/base/gstbitreader.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gprintf.h>
+
+#include "gstccfilter.h"
+
+#define GST_LICENSE "LGPL"
+
+
+// If the following line is defined, WEBVTT line positions will be in percent,
+// otherwise line positions will be in integer line numbers
+#undef WEBVTT_LINE_PERCENT
+
+#define STANDALONE_ELEMENT
+
+// 708 colors are defined by 2 bits each for R,G,&B for a total of 64 color combinations
+static const gchar *colorNames[] = {
+  "black",                      // 0
+  "color1",
+  "color2",
+  "color3",
+  "color4",
+  "color5",
+  "color6",
+  "color7",
+  "color8",
+  "color9",
+
+  "color10",
+  "color11",
+  "color12",
+  "color13",
+  "color14",
+  "color15",
+  "color16",
+  "color17",
+  "color18",
+  "color19",
+
+  "color20",
+  "color21",
+  "color22",
+  "color23",
+  "color24",
+  "color25",
+  "color26",
+  "color27",
+  "color28",
+  "color29",
+
+  "color30",
+  "color31",
+  "color32",
+  "color33",
+  "color34",
+  "color35",
+  "color36",
+  "color37",
+  "color38",
+  "color39",
+
+  "color40",
+  "color41",
+  "color42",
+  "color43",
+  "color44",
+  "color45",
+  "color46",
+  "color47",
+  "color48",
+  "color49",
+
+  "color50",
+  "color51",
+  "color52",
+  "color53",
+  "color54",
+  "color55",
+  "color56",
+  "color57",
+  "color58",
+  "color59",
+
+  "color60",
+  "color61",
+  "color62",
+  "white",
+  0
+};
+
+
+/* pad templates */
+static GstStaticPadTemplate userdata_sink_template =
+GST_STATIC_PAD_TEMPLATE ("userdata_sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("subtitle/x-cea-708"));
+
+static GstStaticPadTemplate vtt_src_template =
+GST_STATIC_PAD_TEMPLATE ("vtt_src",
+    GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("text/x-raw"));
+    //GST_STATIC_CAPS ("text/vtt"));
+
+/* This element's private data */
+struct _GstCcFilter
+{
+  /* Storage for parent Element */
+  GstElement element;
+
+  /* Element pads */
+  GstPad *sinkpad;
+  GstPad *srcpad;
+
+  /* input data storage */
+  GSList *pDataList;
+
+  /* output data storage */
+  GSList *pTextList;
+
+  // simulation of 708 CC windows
+  cea708Window *ccWindows[NUM_708_WINDOWS];
+  guint8 currentWindow;
+
+  gint8 outputIgnore;           // a counter used to ignore bytes in CC text stream following commands
+
+  guint64 currentTime;          // most recent timestamp from userdata
+
+  guint8 dtvccBuffer[DTVCC_LENGTH];
+  gint16 dtvccIndex;
+
+  // desiredService selects the service that will be decoded. If desiredService = -1 (default) no
+  // filtering based on service number will occur. Service #0 is reserved, and the valid range of
+  // service numbers is 1-6 with 1 being primary caption service and 2 being the secondary language
+  // service.
+  gint8 desiredService;
+
+  // previousDesiredService is used to detect a change in the desired service. When changing service
+  // number, only change the desiredService variable
+  gint8 previousDesiredService;
+
+  /* holds the most recent segment data for a new segment event */
+  GstSegment segment;
+};
+
+struct _GstCcFilterClass
+{
+  /* Storage for parent ElementClass */
+  GstElementClass parent_class;
+};
+
+
+
+GST_DEBUG_CATEGORY_STATIC (gst_cc_filter_debug);
+#define GST_CAT_DEFAULT gst_cc_filter_debug
+
+#define gst_cc_filter_parent_class parent_class
+G_DEFINE_TYPE (GstCcFilter, gst_cc_filter, GST_TYPE_ELEMENT);
+
+static void send_text (GstObject *, GSList **, gint, GstPad *);
+static gint text_list_add (GstObject *, GSList **, gint, const gchar *, ...);
+static gint buflistCompare (gconstpointer pElementA, gconstpointer pElementB);
+static gboolean gst_cc_filter_send_new_segment (GstCcFilter * pF);
+static void gst_cc_filter_dispose (GObject * object);
+static gboolean gst_cc_filter_sink_events (GstPad * pPad,
+    GstObject * pParent, GstEvent * pEvent);
+static GstFlowReturn gst_cc_filter_chain (GstPad * pPad,
+    GstObject * pParent, GstBuffer * pBuf);
+static void initWindow (GstCcFilter * pF, guint windowId);
+static void printWindowList (GstCcFilter * pF, guint8 windowList);
+static void printCommandName (GstCcFilter * pF, guint8 c);
+static void setWindowStartTime (GstCcFilter * pFilter, guint windowId);
+static void showWindow (GstCcFilter * pFilter, guint windowId);
+static void windowAddChar (GstCcFilter * pF, guint8 c);
+static void processDtvccPacket (GstCcFilter * pF, gfloat timeStamp);
+static void timeToString (guint64 pts, gchar * buffer);
+static void setWindowStyle (GstCcFilter * pF, guint8 style_id);
+static void setPenStyle (GstCcFilter * pF, guint8 pen_style_id);
+
+
+static void
+gst_cc_filter_class_init (GstCcFilterClass * klass)
+{
+  GObjectClass *gobject_klass;
+  GstElementClass *gstelement_klass;
+
+  gobject_klass = (GObjectClass *) klass;
+  gstelement_klass = (GstElementClass *) klass;
+
+  gobject_klass->dispose = gst_cc_filter_dispose;
+
+  GST_DEBUG ("-------------- %s %zu = sizeof(_GstCcFilter)", __func__,
+      sizeof (struct _GstCcFilter));
+
+  /* Pads */
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&userdata_sink_template));
+  gst_element_class_add_pad_template (gstelement_klass,
+      gst_static_pad_template_get (&vtt_src_template));
+
+  // TODO: change author pending team decision
+  gst_element_class_set_metadata (gstelement_klass,
+      "ccFilter",
+      "Decoder", "closed caption filtering element", "<ruihri@cablelabs.com>");
+#ifndef STANDALONE_ELEMENT
+  GST_DEBUG_CATEGORY_INIT (gst_cc_filter_debug, "ccfilter", 0, "CC filter");
+#endif
+}
+
+static void
+gst_cc_filter_dispose (GObject * object)
+{
+  guint i;
+  GstCcFilter *pFilter = (GstCcFilter *) object;
+
+  // Free memory allocated in gst_cc_filter_init()
+  for (i = 0; i < NUM_708_WINDOWS; i++) {
+    if (pFilter->ccWindows[i]) {
+      g_free (pFilter->ccWindows[i]);
+      pFilter->ccWindows[i] = NULL;
+    }
+  }
+
+  if (pFilter->pDataList) {
+    g_slist_free (pFilter->pDataList);
+    pFilter->pDataList = NULL;
+  }
+
+  if (pFilter->pTextList) {
+    g_slist_free (pFilter->pTextList);
+    pFilter->pTextList = NULL;
+  }
+
+  GST_INFO_OBJECT (pFilter, "complete");
+  G_OBJECT_CLASS (gst_cc_filter_parent_class)->dispose (object);
+}
+
+static void
+gst_cc_filter_init (GstCcFilter * pFilter)
+{
+  guint i;
+  GST_INFO_OBJECT (pFilter, "called");
+  gst_element_set_name (GST_ELEMENT (pFilter), "ccfilter0");
+  pFilter->pTextList = NULL;
+  pFilter->pDataList = NULL;
+  gst_segment_init (&pFilter->segment, GST_FORMAT_UNDEFINED);
+
+  // Add sink pad
+  pFilter->sinkpad = gst_pad_new_from_static_template (&userdata_sink_template,
+      "userdata_sink");
+  gst_pad_set_event_function (pFilter->sinkpad, gst_cc_filter_sink_events);
+  gst_pad_set_chain_function (pFilter->sinkpad, gst_cc_filter_chain);
+  gst_element_add_pad (GST_ELEMENT (pFilter), pFilter->sinkpad);
+  GST_INFO_OBJECT (pFilter, "sink pad added to filter");
+  pFilter->srcpad = NULL;
+
+  // Initialize 708 variables
+  for (i = 0; i < NUM_708_WINDOWS; i++) {
+    pFilter->ccWindows[i] = g_malloc (sizeof (cea708Window));
+    initWindow (pFilter, i);
+  }
+
+  pFilter->currentWindow = 0;
+  pFilter->dtvccIndex = 0;
+  pFilter->desiredService = -1;
+  pFilter->previousDesiredService = -1;
+  pFilter->outputIgnore = 0;
+  pFilter->currentTime = 0;
+}
+
+static gboolean
+gst_cc_filter_sink_events (GstPad * pPad, GstObject * pParent,
+    GstEvent * pEvent)
+{
+  gboolean retVal = FALSE;
+  GstCcFilter *pFilter = GST_CC_FILTER (pParent);
+  GstTagList *p_tags = NULL;
+  gchar *p_tags_str = NULL;
+
+  switch (GST_EVENT_TYPE (pEvent)) {
+    case GST_EVENT_SEGMENT:
+      GST_INFO_OBJECT (pFilter, "Received SEGMENT event on %s pad",
+          gst_pad_get_name (pPad));
+      gst_event_copy_segment (pEvent, &pFilter->segment);
+      gst_event_unref (pEvent);
+      retVal = TRUE;
+      break;
+
+    case GST_EVENT_TAG:
+      gst_event_parse_tag (pEvent, &p_tags);
+      if (NULL != (p_tags_str = gst_tag_list_to_string (p_tags))) {
+        GST_LOG_OBJECT (pFilter, "Received TAGS (%s)", p_tags_str);
+        g_free (p_tags_str);
+      }
+      break;
+
+    default:
+      GST_INFO_OBJECT (pFilter, "Received %s (0x%X) event on %s pad",
+          gst_event_type_get_name (GST_EVENT_TYPE (pEvent)),
+          GST_EVENT_TYPE (pEvent), gst_pad_get_name (pPad));
+      break;
+  }
+
+  retVal = gst_pad_event_default (pPad, pParent, pEvent);
+  return retVal;
+}
+
+static gboolean
+gst_cc_filter_send_new_segment (GstCcFilter * pF)
+{
+  GstEvent *pEvent;
+
+  if (pF->segment.format == GST_FORMAT_UNDEFINED) {
+    GST_INFO_OBJECT (pF, "No new segment received before first buffer!");
+  }
+
+  GST_INFO_OBJECT (pF, "Sending segment %" GST_SEGMENT_FORMAT, &pF->segment);
+  pEvent = gst_event_new_segment (&pF->segment);
+  return gst_pad_push_event (pF->srcpad, pEvent);
+}
+
+static gboolean
+add_text_vtt_srcpad (GstCcFilter * pF)
+{
+  gchar *pSID = NULL;
+
+  pF->srcpad = gst_pad_new_from_static_template (&vtt_src_template, "vtt_src");
+  gst_pad_set_active (pF->srcpad, TRUE);
+  gst_pad_use_fixed_caps (pF->srcpad);
+  pSID = gst_pad_create_stream_id_printf (pF->srcpad,
+      GST_ELEMENT (pF), "vtt_src");
+  gst_pad_push_event (pF->srcpad, gst_event_new_stream_start (pSID));
+  g_free (pSID);
+  gst_element_add_pad (GST_ELEMENT (pF), pF->srcpad);
+  gst_element_no_more_pads (GST_ELEMENT (pF));
+  return gst_cc_filter_send_new_segment (pF);
+}
+
+static void
+clearWindowText (GstCcFilter * pF, guint windowId)
+{
+  guint row, col;
+
+  for (row = 0; row < WINDOW_MAX_ROWS; row++) {
+    for (col = 0; col < WINDOW_MAX_COLS; col++) {
+      pF->ccWindows[windowId]->text[row][col].c = ' ';
+      pF->ccWindows[windowId]->text[row][col].justifyMode =
+          pF->ccWindows[windowId]->justifyMode;
+      pF->ccWindows[windowId]->text[row][col].pen_attributes =
+          pF->ccWindows[windowId]->pen_attributes;
+      pF->ccWindows[windowId]->text[row][col].pen_color =
+          pF->ccWindows[windowId]->pen_color;
+    }
+  }
+}
+
+
+static void
+scrollWindowUp (GstCcFilter * pF, guint windowId)
+{
+  guint row, col;
+
+  // This function should be called to scroll the window up if bottom to top scrolling is enabled
+  // and a carraige-return is encountered, or word-wrapping
+
+  for (row = 1; row < WINDOW_MAX_ROWS; row++) {
+    for (col = 0; col < WINDOW_MAX_COLS; col++) {
+      pF->ccWindows[windowId]->text[row - 1][col] =
+          pF->ccWindows[windowId]->text[row][col];
+    }
+  }
+
+  // Clear the bottom row:
+  row = WINDOW_MAX_ROWS - 1;
+  for (col = 0; col < WINDOW_MAX_COLS; col++) {
+    pF->ccWindows[windowId]->text[row][col].c = ' ';
+    pF->ccWindows[windowId]->text[row][col].justifyMode =
+        pF->ccWindows[windowId]->justifyMode;
+    pF->ccWindows[windowId]->text[row][col].pen_attributes =
+        pF->ccWindows[windowId]->pen_attributes;
+    pF->ccWindows[windowId]->text[row][col].pen_color =
+        pF->ccWindows[windowId]->pen_color;
+  }
+}
+
+
+
+static void
+initWindow (GstCcFilter * pF, guint windowId)
+{
+  if (windowId >= NUM_708_WINDOWS)
+    return;
+
+  pF->ccWindows[windowId]->startTime = 0;
+  pF->ccWindows[windowId]->priority = 0;
+  pF->ccWindows[windowId]->anchor_point = 0;
+  pF->ccWindows[windowId]->relative_position = 0;
+  pF->ccWindows[windowId]->anchor_vertical = 0;
+  pF->ccWindows[windowId]->anchor_horizontal = 0;
+  pF->ccWindows[windowId]->screen_vertical = 0;
+  pF->ccWindows[windowId]->screen_horizontal = 0;
+
+  pF->ccWindows[windowId]->row_count = WINDOW_MAX_ROWS;
+  pF->ccWindows[windowId]->column_count = WINDOW_MAX_COLS;
+  pF->ccWindows[windowId]->row_lock = 0;
+  pF->ccWindows[windowId]->column_lock = 0;
+  pF->ccWindows[windowId]->visible = FALSE;
+  pF->ccWindows[windowId]->styleId = 0;
+  pF->ccWindows[windowId]->penStyleId = 0;
+  pF->ccWindows[windowId]->deleted = TRUE;
+
+  pF->ccWindows[windowId]->pen_color.fg_color = COLOR_WHITE;
+  pF->ccWindows[windowId]->pen_color.fg_opacity = SOLID;
+  pF->ccWindows[windowId]->pen_color.bg_color = COLOR_BLACK;
+  pF->ccWindows[windowId]->pen_color.bg_opacity = SOLID;
+  pF->ccWindows[windowId]->pen_color.edge_color = COLOR_BLACK;
+
+  pF->ccWindows[windowId]->pen_attributes.pen_size = PEN_SIZE_STANDARD;
+  pF->ccWindows[windowId]->pen_attributes.font_style = FONT_STYLE_DEFAULT;
+  pF->ccWindows[windowId]->pen_attributes.offset = PEN_OFFSET_NORMAL;
+  pF->ccWindows[windowId]->pen_attributes.italics = FALSE;
+  pF->ccWindows[windowId]->pen_attributes.text_tag = TAG_DIALOG;
+  pF->ccWindows[windowId]->pen_attributes.underline = FALSE;
+  pF->ccWindows[windowId]->pen_attributes.edge_type = EDGE_TYPE_NONE;
+
+  // Init pen position
+  pF->ccWindows[windowId]->pen_row = 0;
+  pF->ccWindows[windowId]->pen_col = 0;
+
+  // Initialize text array to all spaces. When sending window text, only
+  // send if there are non-blank rows
+  clearWindowText (pF, windowId);
+
+  // window attributes
+  pF->ccWindows[windowId]->justifyMode = JUSTIFY_LEFT;
+  pF->ccWindows[windowId]->printDirection = PRINT_DIR_LEFT_TO_RIGHT;
+  pF->ccWindows[windowId]->scrollDirection = SCROLL_DIR_BOTTOM_TO_TOP;
+  pF->ccWindows[windowId]->wordWrap = FALSE;
+  pF->ccWindows[windowId]->displayEffect = DISPLAY_EFFECT_SNAP;
+  pF->ccWindows[windowId]->effectDirection = EFFECT_DIR_LEFT_TO_RIGHT;
+  pF->ccWindows[windowId]->effectSpeed = 0;
+  pF->ccWindows[windowId]->fillColor = COLOR_BLACK;
+  pF->ccWindows[windowId]->fillOpacity = TRANSPARENT;
+  pF->ccWindows[windowId]->borderType = BORDER_TYPE_NONE;
+  pF->ccWindows[windowId]->borderColor = COLOR_BLACK;
+
+  pF->ccWindows[windowId]->vOffset = 0;
+  pF->ccWindows[windowId]->hOffset = 0;
+
+  // Clear original definition bytes
+  memset (pF->ccWindows[windowId]->originalDef, 0, WIN_DEF_SIZE);
+}
+
+
+static void
+setPenStyle (GstCcFilter * pF, guint8 pen_style_id)
+{
+  if ((pen_style_id < 1) || (pen_style_id > 7))
+    return;
+
+  pF->ccWindows[pF->currentWindow]->pen_attributes.pen_size = PEN_SIZE_STANDARD;
+  pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+      FONT_STYLE_DEFAULT;
+  pF->ccWindows[pF->currentWindow]->pen_attributes.offset = PEN_OFFSET_NORMAL;
+  pF->ccWindows[pF->currentWindow]->pen_attributes.italics = FALSE;
+  pF->ccWindows[pF->currentWindow]->pen_attributes.underline = FALSE;
+  pF->ccWindows[pF->currentWindow]->pen_attributes.edge_type = EDGE_TYPE_NONE;
+
+  pF->ccWindows[pF->currentWindow]->pen_color.fg_color = COLOR_WHITE;
+  pF->ccWindows[pF->currentWindow]->pen_color.fg_opacity = SOLID;
+  pF->ccWindows[pF->currentWindow]->pen_color.bg_color = COLOR_BLACK;
+  pF->ccWindows[pF->currentWindow]->pen_color.bg_opacity = SOLID;
+  pF->ccWindows[pF->currentWindow]->pen_color.edge_color = COLOR_BLACK;
+
+
+  // CEA-708 predefined pen style ids
+  switch (pen_style_id) {
+    case 2:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_MONO_SERIF;
+      break;
+
+    case 3:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_PROP_SERIF;
+      break;
+
+    case 4:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_MONO_SANS;
+      break;
+
+    case 5:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_PROP_SANS;
+      break;
+
+    case 6:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_MONO_SANS;
+      pF->ccWindows[pF->currentWindow]->pen_color.bg_opacity = TRANSPARENT;
+      break;
+
+    case 7:
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          FONT_STYLE_PROP_SANS;
+      pF->ccWindows[pF->currentWindow]->pen_color.bg_opacity = TRANSPARENT;
+      break;
+  }
+}
+
+
+static void
+setWindowStyle (GstCcFilter * pF, guint8 style_id)
+{
+  // CEA-708 predefined window style ids
+  switch (style_id) {
+    case 1:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_LEFT;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = FALSE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = SOLID;
+      break;
+
+    case 2:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_LEFT;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = FALSE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = TRANSPARENT;
+      break;
+
+    case 3:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_CENTER;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = FALSE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = SOLID;
+      break;
+
+    case 4:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_LEFT;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = TRUE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = SOLID;
+      break;
+
+    case 5:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_LEFT;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = TRUE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = TRANSPARENT;
+      break;
+
+    case 6:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_CENTER;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_BOTTOM_TO_TOP;
+      pF->ccWindows[pF->currentWindow]->wordWrap = FALSE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = SOLID;
+      break;
+
+    case 7:
+      pF->ccWindows[pF->currentWindow]->justifyMode = JUSTIFY_LEFT;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          PRINT_DIR_TOP_TO_BOTTOM;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          SCROLL_DIR_RIGHT_TO_LEFT;
+      pF->ccWindows[pF->currentWindow]->wordWrap = FALSE;
+      pF->ccWindows[pF->currentWindow]->displayEffect = DISPLAY_EFFECT_SNAP;
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          EFFECT_DIR_LEFT_TO_RIGHT;
+      pF->ccWindows[pF->currentWindow]->effectSpeed = 0;
+      pF->ccWindows[pF->currentWindow]->fillColor = COLOR_BLACK;
+      pF->ccWindows[pF->currentWindow]->fillOpacity = SOLID;
+      break;
+  }
+}
+
+
+// for debug, print the windows specified by a 708 command mask
+static void
+printWindowList (GstCcFilter * pF, guint8 windowList)
+{
+  guint i;
+
+  for (i = 0; i < NUM_708_WINDOWS; i++) {
+    GST_DEBUG_OBJECT (pF, "windowList[%d]:%d %s vOffset=%d hOffset=%d",
+        i,
+        windowList & 0x1,
+        (pF->ccWindows[i]->visible) ? "visible" : "hidden",
+        pF->ccWindows[i]->vOffset, pF->ccWindows[i]->hOffset);
+
+    windowList >>= 1;
+  }
+}
+
+// For debug, print name of 708 command
+static void
+printCommandName (GstCcFilter * pF, guint8 c)
+{
+  gchar buffer[100];
+  const gchar *command;
+
+  switch (c) {
+    case CC_COMMAND_ETX:
+      command = (const gchar *) "End of text";
+      break;
+
+    case CC_COMMAND_CW0:
+    case CC_COMMAND_CW1:
+    case CC_COMMAND_CW2:
+    case CC_COMMAND_CW3:
+    case CC_COMMAND_CW4:
+    case CC_COMMAND_CW5:
+    case CC_COMMAND_CW6:
+    case CC_COMMAND_CW7:
+      // Set current window, no parameters
+      g_sprintf (buffer, "Set current window %d", c & 0x3);
+      command = buffer;
+      break;
+
+    case CC_COMMAND_CLW:
+      command = (const gchar *) "Clear windows";
+      break;
+
+    case CC_COMMAND_DSW:
+      command = (const gchar *) "Display windows";
+      break;
+
+    case CC_COMMAND_HDW:
+      command = (const gchar *) "Hide windows";
+      break;
+
+    case CC_COMMAND_TGW:
+      command = (const gchar *) "Toggle windows";
+      break;
+
+    case CC_COMMAND_DLW:
+      command = (const gchar *) "Delete windows";
+      break;
+
+    case CC_COMMAND_DLY:
+      command = (const gchar *) "Delay";
+      break;
+
+    case CC_COMMAND_DLC:
+      command = (const gchar *) "Delay cancel";
+      break;
+
+    case CC_COMMAND_RST:
+      command = (const gchar *) "Reset";
+      break;
+
+    case CC_COMMAND_SPA:
+      command = (const gchar *) "Set pen attributes";
+      break;
+
+    case CC_COMMAND_SPC:
+      command = (const gchar *) "Set pen color";
+      break;
+
+    case CC_COMMAND_SPL:
+      command = (const gchar *) "Set pen location";
+      break;
+
+    case CC_COMMAND_SWA:
+      command = (const gchar *) "Set window attributes";
+      break;
+
+    case CC_COMMAND_DF0:
+    case CC_COMMAND_DF1:
+    case CC_COMMAND_DF2:
+    case CC_COMMAND_DF3:
+    case CC_COMMAND_DF4:
+    case CC_COMMAND_DF5:
+    case CC_COMMAND_DF6:
+    case CC_COMMAND_DF7:
+      command = (const gchar *) "Define window";
+      break;
+
+    default:
+      command = (const gchar *) "Unknown";
+      break;
+  }                             // switch
+
+  GST_DEBUG_OBJECT (pF, "  ");  // make commands easier to see in debug
+  GST_DEBUG_OBJECT (pF, "--------------------------------------");      // output.
+  GST_DEBUG_OBJECT (pF, "Process 708 command: %s", command);
+}
+
+static void
+bufcat (gpointer data, gpointer user_data)
+{
+  GstMapInfo *map = user_data;
+  g_strlcat ((gchar *) map->data, data, map->size);
+  //GST_DEBUG ("%s added %s", __func__, (gchar*)data);
+  g_free (data);
+}
+
+static void
+send_text (GstObject * pF, GSList ** pTextList, gint length, GstPad * pPad)
+{
+  GstBuffer *outbuf = NULL;
+  GstMapInfo map = { 0 };
+  GST_DEBUG_OBJECT (pF, "%s(%p, %d)", __func__, pF, length);
+
+  if (0 < length) {
+    outbuf = gst_buffer_new_and_alloc (length);
+
+    if (outbuf) {
+      gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
+      map.data[0] = 0;
+      g_slist_foreach (*pTextList, bufcat, &map);
+      GST_INFO_OBJECT (pF, "sent \n%s\n", map.data);
+      gst_buffer_unmap (outbuf, &map);
+      gst_pad_push (pPad, outbuf);
+      g_slist_free (*pTextList);
+      *pTextList = NULL;
+    } else {
+      GST_ERROR_OBJECT (pF, "ERROR allocating %d", length);
+    }
+  }
+}
+
+static gint
+text_list_add (GstObject * pF, GSList ** pTextList, gint len,
+    const gchar * format, ...)
+{
+  va_list args;
+  gchar *pStr = NULL;
+
+  va_start (args, format);
+
+  if (NULL != (pStr = g_malloc0 (len))) {
+    len = vsnprintf (pStr, len, format, args);
+    *pTextList = g_slist_append (*pTextList, pStr);
+    GST_DEBUG_OBJECT (pF, "added %p pStr[%d]: %s", pStr, len, pStr);
+  } else {
+    len = 0;
+    GST_ERROR_OBJECT (pF, "failure to add the text!?");
+  }
+
+  va_end (args);
+  return len;
+}
+
+
+#ifdef WEBVTT_LINE_PERCENT
+#define WEBVTT_FORMAT "%s --> %s %s line:%d%% position:%d%%\n"
+#else
+#define WEBVTT_FORMAT "%s --> %s %s line:%d position:%d%%\n"
+#endif
+
+#define text_list_add_webvtt(filter, ...) text_list_add(GST_OBJECT(filter), \
+    &filter->pTextList, 64 + sizeof(WEBVTT_FORMAT), WEBVTT_FORMAT, ## __VA_ARGS__)
+
+#define TEXT_CUE_FORMAT                       \
+    "<c.%s.%s.bg_%s>%s</c>\n\n\n"
+
+#define text_list_add_cue(filter, len, ...) \
+    text_list_add(GST_OBJECT(filter), &filter->pTextList, \
+    len + sizeof(TEXT_CUE_FORMAT), TEXT_CUE_FORMAT, ## __VA_ARGS__)
+
+#define MINS(x)  ((x) * 90000 * 60)
+#define SECS(x)  ((x) * 90000)
+#define MSEC(x)  ((x) * 90)
+static void
+timeToString (guint64 pts, gchar * buffer)
+{
+  guint hours = 0;
+  guint mins = 0;
+  guint secs = 0;
+  guint msec = 0;
+
+  // The TS is in units of 1/90000 of a second....
+  // Convert to hours, minutes, seconds & msec
+  hours = pts / (90000 * 60 * 60);
+  pts %= (90000 * 60 * 60);
+  mins = pts / (90000 * 60);
+  pts %= (90000 * 60);
+  secs = pts / 90000;
+  pts %= 90000;
+  msec = pts / 90;
+
+  g_sprintf (buffer, "%.2d:%.2d:%.2d.%.3d", hours, mins, secs, msec);
+}
+
+static void
+setWindowStartTime (GstCcFilter * pFilter, guint windowId)
+{
+  pFilter->ccWindows[windowId]->startTime = pFilter->currentTime + MSEC (100);
+}
+
+static void
+showWindow (GstCcFilter * pFilter, guint windowId)
+{
+  guint16 row, col;
+  gboolean display = FALSE;     // = TRUE when text lines should be written
+  guint8 lineBuffer[LINEBUFFER_SIZE];
+  gchar startTime[100];
+  gchar endTime[100];
+  guint16 i;
+  guint16 rightIndex;           // within a single line of window text, the index of the rightmost
+  // non-blank character
+  guint32 percent;
+  guint16 index;
+  guint len = 0;
+  gboolean underline;
+  gboolean italics;
+  const gchar *align = NULL;
+  const gchar *font = NULL;
+  const gchar *foregroundColor = NULL;
+  const gchar *backgroundColor = NULL;
+  gint bytes = 0;
+
+  GST_LOG_OBJECT (pFilter, "*** %s[%d] ***  row_count=%d col_count=%d",
+      __func__, windowId,
+      pFilter->ccWindows[windowId]->row_count,
+      pFilter->ccWindows[windowId]->column_count);
+
+  // This function called when a window loses visibility, typically
+  // - The window is being deleted
+  // - visbility was toggled to false
+  // Create a text cue from the text in the text array
+
+  if (pFilter->ccWindows[windowId]->startTime == 0) {
+    GST_LOG_OBJECT (pFilter, "*** %s: window startTime was 0", __func__);
+    pFilter->ccWindows[windowId]->startTime = pFilter->currentTime;
+  }
+
+  for (row = 0; row < pFilter->ccWindows[windowId]->row_count; row++) {
+    for (col = 0; col < pFilter->ccWindows[windowId]->column_count; col++) {
+      if (pFilter->ccWindows[windowId]->text[row][col].c != ' ') {
+        // If there is a non-blank line, then display from there
+        display = TRUE;
+        bytes++;
+      }
+    }
+  }
+
+  // Each line of the window that has text will be a separate webvtt text cue.
+  if (display) {
+    guint64 length = 0;
+    timeToString (pFilter->ccWindows[windowId]->startTime, startTime);
+    // Guess at the duration (since we're converting real-time CC data into
+    // static start and end times and we only know the start...
+    length = MAX ((bytes * MSEC (65)), SECS (1));
+    timeToString (pFilter->ccWindows[windowId]->startTime + length, endTime);
+
+    for (row = 0; row < pFilter->ccWindows[windowId]->row_count; row++) {
+      for (col = 0; col < pFilter->ccWindows[windowId]->column_count; col++) {
+        if (pFilter->ccWindows[windowId]->text[row][col].c != ' ') {
+          // This line is non-blank
+
+          // Convert column to percent for WEBVTT using a number for width based on the
+          // maximum size of a 708 screen
+          percent = (pFilter->ccWindows[windowId]->hOffset + col) * 100;
+          percent /= SCREEN_WIDTH_708;
+
+          // Determine alignment
+          switch (pFilter->ccWindows[windowId]->text[row][col].justifyMode) {
+            case JUSTIFY_LEFT:
+              align = "align:start";
+              break;
+
+            case JUSTIFY_RIGHT:
+              align = "align:end";
+              break;
+
+            default:
+              align = "align:middle";
+              break;
+          }
+
+          // Determine font name
+          switch (pFilter->ccWindows[windowId]->text[row][col].pen_attributes.
+              font_style) {
+            case 1:
+              font = "mono_serif";
+              break;
+
+            case 2:
+              font = "prop_serif";
+              break;
+
+            case 3:
+              font = "mono_sans";
+              break;
+
+            case 4:
+              font = "prop_sans";
+              break;
+
+            case 5:
+              font = "casual";
+              break;
+
+            case 6:
+              font = "cursive";
+              break;
+
+            case 7:
+              font = "small_caps";
+              break;
+
+            case 0:
+            default:
+              font = "sans-serif";
+              break;
+          }
+
+          // Look up color name for foreground/background
+          // TODO: add opacity
+          foregroundColor =
+              colorNames[(pFilter->ccWindows[windowId]->text[row][col].
+                  pen_color.fg_color) & 0x3F];
+          backgroundColor =
+              colorNames[(pFilter->ccWindows[windowId]->text[row][col].
+                  pen_color.bg_color) & 0x3F];
+
+          // Write the time, position, color, etc
+          len += text_list_add_webvtt (pFilter, startTime, endTime, align,
+#ifdef WEBVTT_LINE_PERCENT
+              // Vertical position (line) as percent
+              (row + pFilter->ccWindows[windowId]->vOffset) * 100 /
+              SCREEN_HEIGHT_708,
+#else
+              // Add 1 so the line numbers are 1-based
+              row + pFilter->ccWindows[windowId]->vOffset + 1,
+#endif
+              percent);
+
+          GST_LOG_OBJECT (pFilter, "*** %s: adding line row=%d", __func__, row);
+
+          // Find the rightmost non-blank character on this line:
+          for (i = WINDOW_MAX_COLS - 1, rightIndex = WINDOW_MAX_COLS - 1;
+              i >= col; i--) {
+            if (pFilter->ccWindows[windowId]->text[row][i].c != ' ') {
+              rightIndex = i;
+              break;
+            }
+          }
+
+          // Copy all of the characters in this row, from the current position to the right edge
+          underline =
+              pFilter->ccWindows[windowId]->text[row][col].pen_attributes.
+              underline;
+          italics =
+              pFilter->ccWindows[windowId]->text[row][col].pen_attributes.
+              italics;
+
+          for (i = col, index = 0;
+              (i <= rightIndex) && (index < LINEBUFFER_SIZE - 15); i++) {
+            GST_LOG_OBJECT (pFilter, "******* Adding row=%d i=%d c=%c %d", row,
+                i, pFilter->ccWindows[windowId]->text[row][i].c,
+                pFilter->ccWindows[windowId]->text[row][i].c);
+
+            // Check for transitions to/from underline:
+            if ((pFilter->ccWindows[windowId]->text[row][i].pen_attributes.
+                    underline) && (underline == FALSE)) {
+              // beginning of underline mode
+              lineBuffer[index++] = '<';
+              lineBuffer[index++] = 'u';
+              lineBuffer[index++] = '>';
+            } else if ((pFilter->ccWindows[windowId]->text[row][i].
+                    pen_attributes.underline == FALSE)
+                && (underline == TRUE)) {
+              // underline mode ends
+              lineBuffer[index++] = '<';
+              lineBuffer[index++] = '/';
+              lineBuffer[index++] = 'u';
+              lineBuffer[index++] = '>';
+            }
+            underline =
+                pFilter->ccWindows[windowId]->text[row][i].pen_attributes.
+                underline;
+
+            // Check for transitions to/from italics:
+            if ((pFilter->ccWindows[windowId]->text[row][i].pen_attributes.
+                    italics) && (italics == FALSE)) {
+              // beginning of italics mode
+              lineBuffer[index++] = '<';
+              lineBuffer[index++] = 'i';
+              lineBuffer[index++] = '>';
+            } else if ((pFilter->ccWindows[windowId]->text[row][i].
+                    pen_attributes.italics == FALSE)
+                && (italics == TRUE)) {
+              // italics mode ends
+              lineBuffer[index++] = '<';
+              lineBuffer[index++] = '/';
+              lineBuffer[index++] = 'i';
+              lineBuffer[index++] = '>';
+            }
+            italics =
+                pFilter->ccWindows[windowId]->text[row][i].pen_attributes.
+                italics;
+
+            lineBuffer[index++] = pFilter->ccWindows[windowId]->text[row][i].c;
+          }
+
+          lineBuffer[index++] = 0;
+          len += text_list_add_cue (pFilter,
+              strlen ((const char *) font) +
+              strlen ((const char *) foregroundColor) +
+              strlen ((const char *) backgroundColor) +
+              strlen ((const char *) lineBuffer), font, foregroundColor,
+              backgroundColor, lineBuffer);
+          break;
+        }
+      }
+    }
+  }
+
+  if (len == 0) {
+    GST_DEBUG_OBJECT (pFilter, "*** %s: window %d had no text", __func__,
+        windowId);
+  } else {
+    // send text to output pad
+    send_text (GST_OBJECT (pFilter), &pFilter->pTextList, len, pFilter->srcpad);
+  }
+}
+
+static void
+windowAddChar (GstCcFilter * pF, guint8 c)
+{
+  guint16 pen_row;
+  guint16 pen_col;
+
+  // Add one character to the current window, using current pen location.
+  // Wrap pen location if necessary
+
+  if (c == 0)
+    return;
+
+  if (c == '\r') {
+    pF->ccWindows[pF->currentWindow]->pen_col = 0;
+  } else if (c == '\n') {
+    pF->ccWindows[pF->currentWindow]->pen_col = 0;
+    pF->ccWindows[pF->currentWindow]->pen_row++;
+
+    if ((pF->ccWindows[pF->currentWindow]->wordWrap == TRUE) &&
+        (pF->ccWindows[pF->currentWindow]->scrollDirection ==
+            SCROLL_DIR_BOTTOM_TO_TOP)) {
+      scrollWindowUp (pF, pF->currentWindow);
+    }
+  }
+  // Make sure row/col limits are not too large
+  if (pF->ccWindows[pF->currentWindow]->row_count > WINDOW_MAX_ROWS) {
+    pF->ccWindows[pF->currentWindow]->row_count = WINDOW_MAX_ROWS;
+  }
+
+  if (pF->ccWindows[pF->currentWindow]->column_count > WINDOW_MAX_COLS) {
+    pF->ccWindows[pF->currentWindow]->column_count = WINDOW_MAX_COLS;
+  }
+
+  if (pF->ccWindows[pF->currentWindow]->pen_col >=
+      pF->ccWindows[pF->currentWindow]->column_count) {
+    pF->ccWindows[pF->currentWindow]->pen_col = 0;
+    pF->ccWindows[pF->currentWindow]->pen_row++;
+
+    if ((pF->ccWindows[pF->currentWindow]->wordWrap == TRUE) &&
+        (pF->ccWindows[pF->currentWindow]->scrollDirection ==
+            SCROLL_DIR_BOTTOM_TO_TOP)) {
+      scrollWindowUp (pF, pF->currentWindow);
+    }
+  }
+  // Wrap row position if too large
+  if (pF->ccWindows[pF->currentWindow]->pen_row >=
+      pF->ccWindows[pF->currentWindow]->row_count) {
+    pF->ccWindows[pF->currentWindow]->pen_row = 0;
+  }
+
+  if ((c != '\r') && (c != '\n')) {
+    pen_row = pF->ccWindows[pF->currentWindow]->pen_row;
+    pen_col = pF->ccWindows[pF->currentWindow]->pen_col;
+
+    GST_DEBUG_OBJECT (pF, "[text x=%d y=%d fgcolor=%d win=%d vis=%d] %c %d",
+        pen_col,
+        pen_row,
+        pF->ccWindows[pF->currentWindow]->pen_color.fg_color,
+        pF->currentWindow, pF->ccWindows[pF->currentWindow]->visible, c, c);
+
+    // Each cell in the window should get the current pen color and attributes as it is written
+    pF->ccWindows[pF->currentWindow]->text[pen_row][pen_col].c = c;
+
+    pF->ccWindows[pF->currentWindow]->text[pen_row][pen_col].justifyMode =
+        pF->ccWindows[pF->currentWindow]->justifyMode;
+
+    pF->ccWindows[pF->currentWindow]->text[pen_row][pen_col].pen_color =
+        pF->ccWindows[pF->currentWindow]->pen_color;
+
+    pF->ccWindows[pF->currentWindow]->text[pen_row][pen_col].pen_attributes =
+        pF->ccWindows[pF->currentWindow]->pen_attributes;
+
+    switch (pF->ccWindows[pF->currentWindow]->printDirection) {
+      case PRINT_DIR_LEFT_TO_RIGHT:
+        pF->ccWindows[pF->currentWindow]->pen_col++;
+        break;
+
+      case PRINT_DIR_RIGHT_TO_LEFT:
+        if (pF->ccWindows[pF->currentWindow]->pen_col) {
+          pF->ccWindows[pF->currentWindow]->pen_col--;
+        }
+        break;
+
+      case PRINT_DIR_TOP_TO_BOTTOM:
+        pF->ccWindows[pF->currentWindow]->pen_row++;
+        break;
+
+      case PRINT_DIR_BOTTOM_TO_TOP:
+        if (pF->ccWindows[pF->currentWindow]->pen_row) {
+          pF->ccWindows[pF->currentWindow]->pen_row--;
+        }
+        break;
+    }                           // switch (print_direction)
+  }
+}
+
+static void
+processDtvccByte (GstCcFilter * pF, guint8 c, int index, gfloat timeStamp)
+{
+  guint i;
+
+  // for define window: (initialize to 0 to remove warning)
+  guint8 priority = 0;
+  guint8 anchor_point = 0;
+  guint8 relative_position = 0;
+  guint8 anchor_vertical = 0;
+  guint8 anchor_horizontal = 0;
+  guint8 row_count = 0;
+  guint8 column_count = 0;
+  guint8 row_lock = 0;
+  guint8 column_lock = 0;
+  gboolean visible = FALSE;
+  guint8 style_id = 0;
+  guint8 pen_style_id = 0;
+
+  guint8 windowList;
+  guint newWindow;
+
+  guint vAnchor = 0;
+  guint hAnchor = 0;
+
+
+  if (pF->outputIgnore) {
+    // Ignore characters/parameters after a command.
+    GST_LOG_OBJECT (pF, "---------- [%d] ignore %d", pF->outputIgnore, c);
+    pF->outputIgnore--;
+    return;
+  }
+
+  if (c == 0)
+    return;                     // neither a command, nor text
+
+  // Process command codes and text:
+  switch (c) {
+      //----- End of text ------------------------------------------------------------
+    case CC_COMMAND_ETX:
+      printCommandName (pF, c);
+      return;
+
+      //----- Set current window -----------------------------------------------------
+    case CC_COMMAND_CW0:
+    case CC_COMMAND_CW1:
+    case CC_COMMAND_CW2:
+    case CC_COMMAND_CW3:
+    case CC_COMMAND_CW4:
+    case CC_COMMAND_CW5:
+    case CC_COMMAND_CW6:
+    case CC_COMMAND_CW7:
+      pF->currentWindow = c & 0x03;
+      printCommandName (pF, c);
+      GST_DEBUG_OBJECT (pF, "Current window=%d", pF->currentWindow);
+      return;
+
+      //----- Clear windows -----------------------------------------------------------
+    case CC_COMMAND_CLW:
+      pF->outputIgnore = 1;     // 1 byte parameter = windowmap
+      printCommandName (pF, c);
+      windowList = pF->dtvccBuffer[index];
+      printWindowList (pF, windowList);
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        if (windowList & 0x1) {
+          // Clear window data
+          clearWindowText (pF, i);
+        }
+
+        windowList >>= 1;
+      }
+      return;
+
+      //----- Display windows ---------------------------------------------------------
+    case CC_COMMAND_DSW:
+      pF->outputIgnore = 1;     // 1 byte parameter = windowmap
+      printCommandName (pF, c);
+      windowList = pF->dtvccBuffer[index];
+      printWindowList (pF, windowList);
+
+      // Make windows visible:
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        if (windowList & 0x1) {
+          if (pF->ccWindows[i]->visible == FALSE) {
+            pF->ccWindows[i]->visible = TRUE;
+            showWindow (pF, i);
+          }
+        }
+        windowList >>= 1;
+      }
+      return;
+
+      //----- Hide windows -----------------------------------------------------------
+    case CC_COMMAND_HDW:
+      pF->outputIgnore = 1;     // 1 byte parameter = windowmap
+      printCommandName (pF, c);
+      windowList = pF->dtvccBuffer[index];
+      printWindowList (pF, windowList);
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        if (windowList & 0x1) {
+          if (pF->ccWindows[i]->visible == TRUE) {
+            pF->ccWindows[i]->visible = FALSE;
+          }
+        }
+        windowList >>= 1;
+      }
+      return;
+
+      //----- Toggle windows -----------------------------------------------------------
+    case CC_COMMAND_TGW:
+      // Toggle windows - hide displayed windows, display hidden windows
+      pF->outputIgnore = 1;     // 1 byte parameter = windowmap
+      printCommandName (pF, c);
+      windowList = pF->dtvccBuffer[index];
+      printWindowList (pF, windowList);
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        if (windowList & 0x1) {
+          if (pF->ccWindows[i]->visible) {
+            pF->ccWindows[i]->visible = FALSE;
+          } else {
+            pF->ccWindows[i]->visible = TRUE;
+            setWindowStartTime (pF, i);
+            showWindow (pF, i);
+          }
+        }
+        windowList >>= 1;
+      }
+      return;
+
+      //----- Delete windows -----------------------------------------------------------
+    case CC_COMMAND_DLW:
+      pF->outputIgnore = 1;     // 1 byte parameter = windowmap
+      printCommandName (pF, c);
+      windowList = pF->dtvccBuffer[index];
+      printWindowList (pF, windowList);
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        if (windowList & 0x1) {
+          // Clear window data
+          initWindow (pF, i);
+        }
+        windowList >>= 1;
+      }
+      return;
+
+      //----- Delay ------------------------------------------------------------------
+    case CC_COMMAND_DLY:
+      pF->outputIgnore = 1;     // 1 byte parameter = delay in tenths of a second
+      printCommandName (pF, c);
+      // TODO: - process this command.
+      return;
+
+      //----- Delay cancel -----------------------------------------------------------
+    case CC_COMMAND_DLC:
+      printCommandName (pF, c);
+      // TODO: - process this command.
+      return;
+
+      //----- Reset ------------------------------------------------------------------
+    case CC_COMMAND_RST:
+      // Reset - cancel any delay, delete all windows
+      printCommandName (pF, c);
+
+      for (i = 0; i < NUM_708_WINDOWS; i++) {
+        // Clear window data
+        initWindow (pF, i);
+      }
+      return;
+
+      //----- Set pen attributes ---------------------------------------------------
+    case CC_COMMAND_SPA:
+      pF->outputIgnore = 2;     // 2 byte parameter = pen size, font, etc
+      printCommandName (pF, c);
+
+      // format
+      //tt3 tt2 tt1 tt0 o1 o0 s1 s0
+      //i u et2 et1 et0 fs2 fs1 fs0
+      pF->ccWindows[pF->currentWindow]->pen_attributes.pen_size =
+          pF->dtvccBuffer[index] & 0x3;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.font_style =
+          pF->dtvccBuffer[index + 1] & 0x3;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.text_tag =
+          (pF->dtvccBuffer[index] & 0xF0) >> 4;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.offset =
+          (pF->dtvccBuffer[index] & 0xC0) >> 2;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.italics =
+          ((pF->dtvccBuffer[index + 1] & 0x80) >> 7) ? TRUE : FALSE;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.underline =
+          ((pF->dtvccBuffer[index + 1] & 0x40) >> 6) ? TRUE : FALSE;
+      pF->ccWindows[pF->currentWindow]->pen_attributes.edge_type =
+          (pF->dtvccBuffer[index + 1] & 0x38) >> 3;
+
+      GST_DEBUG_OBJECT (pF, "pen_size=%d font=%d text_tag=%d offset=%d",
+          pF->ccWindows[pF->currentWindow]->pen_attributes.pen_size,
+          pF->ccWindows[pF->currentWindow]->pen_attributes.font_style,
+          pF->ccWindows[pF->currentWindow]->pen_attributes.text_tag,
+          pF->ccWindows[pF->currentWindow]->pen_attributes.offset);
+
+      GST_DEBUG_OBJECT (pF, "italics=%d underline=%d edge_type=%d",
+          pF->ccWindows[pF->currentWindow]->pen_attributes.italics,
+          pF->ccWindows[pF->currentWindow]->pen_attributes.underline,
+          pF->ccWindows[pF->currentWindow]->pen_attributes.edge_type);
+      return;
+
+      //----- Set pen color --------------------------------------------------------
+    case CC_COMMAND_SPC:
+      pF->outputIgnore = 3;     // 3 byte parameter = foreground/background color, opacity
+      printCommandName (pF, c);
+
+      // format
+      // fo1 fo0 fr1 fr0 fg1 fg0 fb1 fb0
+      // bo1 bo0 br1 br0 bg1 bg0 bb1 bb0
+      // 0 0 er1 er0 eg1 eg0 eb1 eb0
+      pF->ccWindows[pF->currentWindow]->pen_color.fg_color =
+          pF->dtvccBuffer[index] & 0x3F;
+      pF->ccWindows[pF->currentWindow]->pen_color.fg_opacity =
+          (pF->dtvccBuffer[index] & 0xC0) >> 6;
+      pF->ccWindows[pF->currentWindow]->pen_color.bg_color =
+          pF->dtvccBuffer[index + 1] & 0x3F;
+      pF->ccWindows[pF->currentWindow]->pen_color.bg_opacity =
+          (pF->dtvccBuffer[index + 1] & 0xC0) >> 6;
+      pF->ccWindows[pF->currentWindow]->pen_color.edge_color =
+          pF->dtvccBuffer[index + 2] & 0x3F;
+
+      GST_DEBUG_OBJECT (pF,
+          "pen_color fg=0x%x fg_op=0x%x bg=0x%x bg_op=0x%x edge=0x%x",
+          pF->ccWindows[pF->currentWindow]->pen_color.fg_color,
+          pF->ccWindows[pF->currentWindow]->pen_color.fg_opacity,
+          pF->ccWindows[pF->currentWindow]->pen_color.bg_color,
+          pF->ccWindows[pF->currentWindow]->pen_color.bg_opacity,
+          pF->ccWindows[pF->currentWindow]->pen_color.edge_color);
+      return;
+
+      //----- Set pen location -----------------------------------------------------
+    case CC_COMMAND_SPL:
+      // Set pen location - row, column address within the current window
+      pF->outputIgnore = 2;     // 2 byte parameter = row, col
+      printCommandName (pF, c);
+      pF->ccWindows[pF->currentWindow]->pen_row = pF->dtvccBuffer[index] & 0xF;
+      pF->ccWindows[pF->currentWindow]->pen_col =
+          pF->dtvccBuffer[index + 1] & 0x3F;
+      GST_DEBUG_OBJECT (pF, "Pen location: row=%d col=%d",
+          pF->ccWindows[pF->currentWindow]->pen_row,
+          pF->ccWindows[pF->currentWindow]->pen_col);
+      return;
+
+      //----- Set window attributes ------------------------------------------------
+    case CC_COMMAND_SWA:
+      // Set window attributes - color, word wrap, border, scroll effect, etc
+      pF->outputIgnore = 4;     // 4 byte parameter
+      printCommandName (pF, c);
+
+      // format
+      // fo1 fo0 fr1 fr0 fg1 fg0 fb1 fb0
+      // bt1 bt0 br1 br0 bg1 bg0 bb1 bb0
+      // bt2 ww  pd1 pd0 sd1 sd0 j1  j0
+      // es3 es2 es1 es0 ed1 ed0 de1 de0
+      pF->ccWindows[pF->currentWindow]->fillColor =
+          pF->dtvccBuffer[index] & 0x3F;
+      pF->ccWindows[pF->currentWindow]->fillOpacity =
+          (pF->dtvccBuffer[index] & 0xC0) >> 6;
+      pF->ccWindows[pF->currentWindow]->borderColor =
+          pF->dtvccBuffer[index + 1] & 0x3F;
+      pF->ccWindows[pF->currentWindow]->borderType =
+          ((pF->dtvccBuffer[index + 1] & 0xC0) >> 6) | ((pF->dtvccBuffer[index +
+                  2] & 0x80) >> 5);
+      pF->ccWindows[pF->currentWindow]->wordWrap =
+          (pF->dtvccBuffer[index + 2] & 0x40) ? TRUE : FALSE;
+      pF->ccWindows[pF->currentWindow]->justifyMode =
+          pF->dtvccBuffer[index + 2] & 0x3;
+      pF->ccWindows[pF->currentWindow]->scrollDirection =
+          (pF->dtvccBuffer[index + 2] & 0xC) >> 2;
+      pF->ccWindows[pF->currentWindow]->printDirection =
+          (pF->dtvccBuffer[index + 2] & 0x30) >> 2;
+      pF->ccWindows[pF->currentWindow]->displayEffect =
+          (pF->dtvccBuffer[index + 3] & 0x3);
+      pF->ccWindows[pF->currentWindow]->effectDirection =
+          (pF->dtvccBuffer[index + 3] & 0xC);
+      pF->ccWindows[pF->currentWindow]->effectSpeed =
+          (pF->dtvccBuffer[index + 3] & 0xF0) >> 4;
+
+      GST_DEBUG_OBJECT (pF, "Print direction = %d",
+          pF->ccWindows[pF->currentWindow]->printDirection);
+      return;
+
+      //----- Define window --------------------------------------------------------
+    case CC_COMMAND_DF0:
+    case CC_COMMAND_DF1:
+    case CC_COMMAND_DF2:
+    case CC_COMMAND_DF3:
+    case CC_COMMAND_DF4:
+    case CC_COMMAND_DF5:
+    case CC_COMMAND_DF6:
+    case CC_COMMAND_DF7:
+    {
+      // Define window - window size, window style, pen style, anchor position, etc
+      pF->outputIgnore = 6;     // 6 byte parameter
+
+      printCommandName (pF, c);
+
+      pF->currentWindow = c & 0x3;
+
+      GST_DEBUG_OBJECT (pF, "currentWindow=%d", pF->currentWindow);
+
+      // Determine if this is a repeat of a previous definition for this window
+      newWindow =
+          memcmp (pF->ccWindows[pF->currentWindow]->originalDef,
+          &(pF->dtvccBuffer[index]), WIN_DEF_SIZE);
+
+      GST_DEBUG_OBJECT (pF, "originalDef %x %x %x %x %x %x",
+          pF->ccWindows[pF->currentWindow]->originalDef[0],
+          pF->ccWindows[pF->currentWindow]->originalDef[1],
+          pF->ccWindows[pF->currentWindow]->originalDef[2],
+          pF->ccWindows[pF->currentWindow]->originalDef[3],
+          pF->ccWindows[pF->currentWindow]->originalDef[4],
+          pF->ccWindows[pF->currentWindow]->originalDef[5]);
+
+      GST_DEBUG_OBJECT (pF, "dtvccBuffer %x %x %x %x %x %x",
+          pF->dtvccBuffer[index],
+          pF->dtvccBuffer[index + 1],
+          pF->dtvccBuffer[index + 2],
+          pF->dtvccBuffer[index + 3],
+          pF->dtvccBuffer[index + 4], pF->dtvccBuffer[index + 5]);
+
+      // Initialize window structure
+      if (newWindow) {
+        GST_DEBUG_OBJECT (pF, "*** Define Window: new definition decoded");
+        // Reset the window text to prevent carry-over of previous captions
+        clearWindowText (pF, pF->currentWindow);
+
+        pF->ccWindows[pF->currentWindow]->originalDef[0] =
+            pF->dtvccBuffer[index];
+        pF->ccWindows[pF->currentWindow]->originalDef[1] =
+            pF->dtvccBuffer[index + 1];
+        pF->ccWindows[pF->currentWindow]->originalDef[2] =
+            pF->dtvccBuffer[index + 2];
+        pF->ccWindows[pF->currentWindow]->originalDef[3] =
+            pF->dtvccBuffer[index + 3];
+        pF->ccWindows[pF->currentWindow]->originalDef[4] =
+            pF->dtvccBuffer[index + 4];
+        pF->ccWindows[pF->currentWindow]->originalDef[5] =
+            pF->dtvccBuffer[index + 5];
+
+        if (pF->ccWindows[pF->currentWindow]->deleted) {
+          // Spec says on window create (but not re-definition) the pen position
+          // must be reset to 0
+          // TODO: also set all text positions to the fill color
+          pF->ccWindows[pF->currentWindow]->deleted = FALSE;
+          pF->ccWindows[pF->currentWindow]->pen_row = 0;
+          pF->ccWindows[pF->currentWindow]->pen_col = 0;
+        }
+        // format of parameters:
+        //0 0 v rl cl p2 p1 p0
+        //rp av7 av6 av5 av4 av3 av1 av0
+        //ah7 ah6 ah5 ah4 ah3 ah2 ah1 ah0
+        //ap3 ap2 ap1 ap0 rc3 rc2 rc1 rc0
+        //0 0 cc5 cc4 cc3 cc2 cc1 cc0
+        //0 0 ws2 ws1 ws0 ps2 ps1 ps0
+
+        priority = pF->dtvccBuffer[index] & 0x7;
+        anchor_point = (pF->dtvccBuffer[index + 3] & 0xF0) >> 4;
+        relative_position = (pF->dtvccBuffer[index + 1] & 0x80) >> 7;
+        anchor_vertical = pF->dtvccBuffer[index + 1] & 0x7F;
+        anchor_horizontal = pF->dtvccBuffer[index + 2];
+        row_count = (pF->dtvccBuffer[index + 3] & 0xF) + 1;
+        column_count = (pF->dtvccBuffer[index + 4] & 0x3F) + 1;
+        row_lock = (pF->dtvccBuffer[index] & 0x10) >> 4;
+        column_lock = (pF->dtvccBuffer[index] & 0x8) >> 3;
+        visible = ((pF->dtvccBuffer[index] & 0x20) >> 5) ? TRUE : FALSE;
+        style_id = (pF->dtvccBuffer[index + 5] & 0x38) >> 3;
+        pen_style_id = pF->dtvccBuffer[index + 5] & 0x3;
+
+        pF->ccWindows[pF->currentWindow]->screen_vertical = anchor_vertical;
+        pF->ccWindows[pF->currentWindow]->screen_horizontal = anchor_horizontal;
+
+        if (relative_position == 0) {
+          // If position is in absolute coords, convert to percent
+          pF->ccWindows[pF->currentWindow]->screen_vertical /= SCREEN_HEIGHT;
+          pF->ccWindows[pF->currentWindow]->screen_horizontal /= SCREEN_WIDTH;
+          pF->ccWindows[pF->currentWindow]->screen_vertical *= 100;
+          pF->ccWindows[pF->currentWindow]->screen_horizontal *= 100;
+        }
+
+        pF->ccWindows[pF->currentWindow]->vOffset = 0;
+        pF->ccWindows[pF->currentWindow]->hOffset = 0;
+
+        // Calculate the position of the anchor point of the window in character units
+        // within a hypothetical 708 screen
+        vAnchor =
+            pF->ccWindows[pF->currentWindow]->screen_vertical *
+            SCREEN_HEIGHT_708 / 100;
+        hAnchor =
+            pF->ccWindows[pF->currentWindow]->screen_horizontal *
+            SCREEN_WIDTH_708 / 100;
+
+
+        // Adjust position based on anchor point
+        switch (anchor_point) {
+          case 0:
+            // Top left:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor;
+            break;
+
+          case 1:
+            // Top center:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor;
+            pF->ccWindows[pF->currentWindow]->hOffset =
+                hAnchor - column_count / 2;
+            break;
+
+          case 2:
+            // Top right:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor - column_count;
+            break;
+
+
+          case 3:
+            // Middle left:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count / 2;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor;
+            break;
+
+          case 4:
+            // Screen center:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count / 2;
+            pF->ccWindows[pF->currentWindow]->hOffset =
+                hAnchor - column_count / 2;
+            break;
+
+          case 5:
+            // Middle right:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count / 2;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor - column_count;
+            break;
+
+
+          case 6:
+            // Bottom left:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor;
+            break;
+
+          case 7:
+            // Bottom center:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count;
+            pF->ccWindows[pF->currentWindow]->hOffset =
+                hAnchor - column_count / 2;
+            break;
+
+          case 8:
+            // Bottom right:
+            pF->ccWindows[pF->currentWindow]->vOffset = vAnchor - row_count;
+            pF->ccWindows[pF->currentWindow]->hOffset = hAnchor - column_count;
+            break;
+        }
+
+        pF->ccWindows[pF->currentWindow]->priority = priority;
+        pF->ccWindows[pF->currentWindow]->anchor_point = anchor_point;
+        pF->ccWindows[pF->currentWindow]->relative_position = relative_position;
+        pF->ccWindows[pF->currentWindow]->anchor_vertical = anchor_vertical;
+        pF->ccWindows[pF->currentWindow]->anchor_horizontal = anchor_horizontal;
+        pF->ccWindows[pF->currentWindow]->row_count = row_count;
+        pF->ccWindows[pF->currentWindow]->column_count = column_count;
+        pF->ccWindows[pF->currentWindow]->row_lock = row_lock;
+        pF->ccWindows[pF->currentWindow]->column_lock = column_lock;
+        pF->ccWindows[pF->currentWindow]->visible = visible;
+        pF->ccWindows[pF->currentWindow]->styleId = style_id;
+        pF->ccWindows[pF->currentWindow]->penStyleId = pen_style_id;
+
+        setWindowStyle (pF, style_id);
+
+        setPenStyle (pF, pen_style_id);
+
+        if (visible) {
+          showWindow (pF, pF->currentWindow);
+        }
+#if 0
+      } else {
+        // spec says ignore duplicate window definitions, they are repeated in the stream
+        // so the decoder can initialize at any point
+        GST_INFO_OBJECT (pF, "*** Define Window: duplicate definition ignored");
+#endif
+      }
+      GST_DEBUG_OBJECT (pF,
+          "priority=%d anchor=%d relative_pos=%d anchor_v=%d anchor_h=%d",
+          pF->ccWindows[pF->currentWindow]->priority,
+          pF->ccWindows[pF->currentWindow]->anchor_point,
+          pF->ccWindows[pF->currentWindow]->relative_position,
+          pF->ccWindows[pF->currentWindow]->anchor_vertical,
+          pF->ccWindows[pF->currentWindow]->anchor_horizontal);
+
+      GST_DEBUG_OBJECT (pF,
+          "row_count=%d col_count=%d row_lock=%d col_lock=%d visible=%d",
+          pF->ccWindows[pF->currentWindow]->row_count,
+          pF->ccWindows[pF->currentWindow]->column_count,
+          pF->ccWindows[pF->currentWindow]->row_lock,
+          pF->ccWindows[pF->currentWindow]->column_lock,
+          pF->ccWindows[pF->currentWindow]->visible);
+
+      GST_DEBUG_OBJECT (pF,
+          "styleId=%d penStyleId=%d screenH=%f screenV=%f vOffset=%d hOffset=%d vAnchor=%d hAnchor=%d",
+          pF->ccWindows[pF->currentWindow]->styleId,
+          pF->ccWindows[pF->currentWindow]->penStyleId,
+          pF->ccWindows[pF->currentWindow]->screen_horizontal,
+          pF->ccWindows[pF->currentWindow]->screen_vertical,
+          pF->ccWindows[pF->currentWindow]->vOffset,
+          pF->ccWindows[pF->currentWindow]->hOffset, vAnchor, hAnchor);
+      return;
+    }
+  }
+
+  // A normal text character - add to the current string
+  windowAddChar (pF, c);
+}
+
+static void
+gst_user_data_analyze (GstCcFilter * pF, UserData * uData)
+{
+  guint index;
+  guint8 cc_count;
+  gboolean process_cc_data_flag;
+  gboolean process_em_data_flag;
+  gboolean additional_data_flag;
+  guint8 em_data;
+  guint8 marker;
+  guint i;
+  guint8 cc_type;
+  guint8 cc_valid;
+  guint8 cc_data_1;
+  guint8 cc_data_2;
+
+  GST_LOG_OBJECT (pF,
+      "****** gst_user_data_analyze: tr=%d timeStamp=%" G_GUINT64_FORMAT
+      " data[4]=%d", uData->tr, uData->timestamp, uData->data[4]);
+
+  if (uData->data[4] == 3) {
+    index = 5;
+    cc_count = uData->data[index++];
+    process_em_data_flag = (cc_count & 0x80) ? TRUE : FALSE;
+    process_cc_data_flag = (cc_count & 0x40) ? TRUE : FALSE;
+    additional_data_flag = (cc_count & 0x20) ? TRUE : FALSE;
+    cc_count &= 0x1F;
+
+    GST_LOG_OBJECT (pF,
+        "-- 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 = uData->data[index++];     // not used currently
+    (void) em_data;
+
+    if (process_cc_data_flag) {
+      for (i = 0; i < cc_count; i++) {
+        cc_type = uData->data[index++];
+        cc_data_1 = uData->data[index++];
+        cc_data_2 = uData->data[index++];
+
+        cc_valid = (cc_type & 0x04) ? 1 : 0;
+        cc_type &= 0x03;
+
+        GST_LOG_OBJECT (pF,
+            "     -- cc_data_pkt(%d): cc_valid=%d cc_type=%d cc_data_1=0x%X cc_data_2=0x%X",
+            i, cc_valid, cc_type, cc_data_1, cc_data_2);
+
+        // accumulate dvtcc packet
+        if ((cc_type == 3) && (cc_valid == 1)) {
+          // The previous packet will be complete
+          processDtvccPacket (pF, uData->timestamp);
+
+          // Start new packet
+          pF->dtvccIndex = 0;
+          pF->dtvccBuffer[pF->dtvccIndex++] = cc_data_1;
+          pF->dtvccBuffer[pF->dtvccIndex++] = cc_data_2;
+        } else if ((cc_type == 2) && (cc_valid == 1)) {
+          // Add on to the current DTVCC packet
+          if (pF->dtvccIndex < DTVCC_LENGTH - 2) {
+            pF->dtvccBuffer[pF->dtvccIndex++] = cc_data_1;
+            pF->dtvccBuffer[pF->dtvccIndex++] = cc_data_2;
+          }
+        } else if ((cc_type == 2) && (cc_valid == 0)) {
+          // This packet should be ignored, but if there is a current
+          // DTVCC packet then this is the end.
+          processDtvccPacket (pF, uData->timestamp);
+        } else if ((cc_type == 0) && (cc_valid == 1)) {
+          // TODO: NTSC/608 data
+        } else if ((cc_type == 1) && (cc_valid == 1)) {
+          // TODO: NTSC/608 data
+        }
+      }
+    }
+
+    marker = uData->data[index++];      // not used should by 0xFF
+    (void) marker;
+  }
+}
+
+static void
+processDtvccPacket (GstCcFilter * pF, gfloat timeStamp)
+{
+  guint8 packetSize;
+  guint8 ps;
+  guint8 sequenceNumber;
+  guint i;
+
+  // Service block header (see CEA-708 6.2.1)
+  guint8 blockSize;
+  guint8 serviceNumber;
+  guint8 data;
+
+  guint parseIndex = 0;
+
+  if (pF->dtvccIndex < 1) {
+    return;                     // No packet has been received yet
+  }
+
+  ps = pF->dtvccBuffer[0] & 0x3F;
+  if (ps == 0) {
+    packetSize = 127;
+  } else {
+    packetSize = ps * 2 - 1;
+  }
+  sequenceNumber = (pF->dtvccBuffer[0] & 0xC0) >> 6;
+
+  blockSize = pF->dtvccBuffer[1] & 0x1F;
+  serviceNumber = (pF->dtvccBuffer[1] & 0xE0) >> 5;
+
+  parseIndex = 2;               // first byte following serviceNumber
+  if (serviceNumber == 7) {
+    // Get extended service number
+    serviceNumber = pF->dtvccBuffer[2] & 0x3F;
+    parseIndex++;
+  }
+
+  GST_LOG_OBJECT (pF,
+      "        ******* processDtvccPacket: size=%d seq=%d blockSize=%d serviceNum=%d",
+      packetSize, sequenceNumber, blockSize, serviceNumber);
+
+  // If there has been a change in the desired service number, then clear the windows for
+  // the new service.
+  if (pF->desiredService != pF->previousDesiredService) {
+    for (i = 0; i < NUM_708_WINDOWS; i++) {
+      initWindow (pF, i);
+    }
+
+    pF->currentWindow = 0;
+
+    pF->previousDesiredService = pF->desiredService;
+  }
+  // If the desiredService has been set to a number other than -1, then filter the packets based
+  // on the that service number.
+  if ((pF->desiredService == -1) || (pF->desiredService == serviceNumber)) {
+    for (i = 0; i < blockSize; i++) {
+      data = pF->dtvccBuffer[parseIndex++];
+
+      // The Dtvcc buffer contains a stream of commands, command parameters, and characters which are
+      // the actual captions. Process commands and store captions in simulated 708 windows:
+      processDtvccByte (pF, data, parseIndex, timeStamp);
+    }
+  }
+  // Set index to 0 so this packet will not be processed twice
+  pF->dtvccIndex = 0;
+}
+
+gint
+buflistCompare (gconstpointer pElementA, gconstpointer pElementB)
+{
+  UserData *pUserDataA = NULL;
+  UserData *pUserDataB = NULL;
+  GstMapInfo eltAmap = { 0 };
+  GstMapInfo eltBmap = { 0 };
+  gint retVal = 0;
+
+  gst_buffer_map (GST_BUFFER (pElementA), &eltAmap, GST_MAP_READ);
+  pUserDataA = (UserData *) eltAmap.data;
+  gst_buffer_map (GST_BUFFER (pElementB), &eltBmap, GST_MAP_READWRITE);
+  pUserDataB = (UserData *) eltBmap.data;
+
+  if (pUserDataA->tr < pUserDataB->tr) {
+    retVal = -1;
+  } else if (pUserDataA->tr > pUserDataB->tr) {
+    retVal = 1;
+  } else {
+    g_print ("ERROR: %s - %d duplicated TR(%d) w/o GOP processing!\n",
+        __func__, __LINE__, pUserDataB->tr);
+    pUserDataB->tr += 64;
+    retVal = -1;
+  }
+
+  gst_buffer_unmap (GST_BUFFER (pElementA), &eltAmap);
+  gst_buffer_unmap (GST_BUFFER (pElementB), &eltBmap);
+  return retVal;
+}
+
+/*=============================================================================
+    gst_cc_filter_chain()
+
+    This function accepts a user data buffer.
+
+    see ATSC Digital Television Standard:
+        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’
+    }
+
+    and cc_data() is defined in CEA-708, Table 2.
+
+    ----- 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
+    1   NTSC_CC_FIELD_2
+    2   DTVCC_PACKET_DATA
+    3   DTVCC_PACKET_START
+
+    if cc_type == 0 or 1, then interpret cc_data_1 & cc_data_2 as EIA-608 captions
+    if cc_type == 3, then the current caption channel packet is complete, and a new
+                     channel packet should be started
+    if cc_type == 2, then append the cc_data_1 & cc_data_2 to the current caption
+                     channel packet
+
+
+    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_filter_chain (GstPad * pPad, GstObject * pParent, GstBuffer * pBuf)
+{
+  GstFlowReturn retVal = GST_FLOW_OK;
+  GstCcFilter *pF = GST_CC_FILTER (pParent);
+  UserData *pData = NULL;
+  GstMapInfo bufMap = { 0 };
+  GstMapInfo dataMap = { 0 };
+  GSList *pSlistElt = NULL;
+  guint8 tr0Count;
+  gint8 previousTr;
+
+  gst_buffer_map (pBuf, &bufMap, GST_MAP_READ);
+  pData = (UserData *) bufMap.data;
+
+  if (g_str_has_prefix ((const gchar *) (pData->data), "GA94") ||
+      g_str_has_prefix ((const gchar *) (pData->data), "DTG1")) {
+
+    gint len = 0;
+    // if this is the first output, initialize our output pad...
+    if (NULL == pF->srcpad) {
+      add_text_vtt_srcpad (pF);
+
+      len = text_list_add (GST_OBJECT (pF), &pF->pTextList,
+          strlen ("WEBVTT\n\n\n") + 1, "WEBVTT\n\n\n");
+      send_text (GST_OBJECT (pF), &pF->pTextList, len, pF->srcpad);
+      GST_INFO_OBJECT (pF, "vtt_src pad added to filter");
+    }
+
+    pF->pDataList = g_slist_insert_sorted (pF->pDataList, pBuf, buflistCompare);
+    GST_DEBUG_OBJECT (pF, "inserted: %c%c%c%c",
+        pData->data[0], pData->data[1], pData->data[2], pData->data[3]);
+    gst_buffer_unmap (pBuf, &bufMap);
+  } else if (g_str_has_prefix ((const gchar *) (pData->data), "GOP\0")) {
+    // New GOP header received, process existing array of user data packets
+    for (pSlistElt = pF->pDataList, tr0Count = 0, previousTr = -1;
+        NULL != pSlistElt;) {
+      gst_buffer_map (pSlistElt->data, &dataMap, GST_MAP_READ);
+      pData = (UserData *) dataMap.data;
+      if (pData->tr == 0) {
+        tr0Count++;
+      }
+      if ((pData->tr - previousTr) > 1) {
+        // Missing frames may result in missing caption characters
+        GST_WARNING_OBJECT (pF, "@@@@@ Missing video frame %d !!!!!",
+            pData->tr - 1);
+      }
+      if (pData->tr == previousTr) {
+        // Most likely caused by a missing GOP
+        GST_WARNING_OBJECT (pF, "@@@@@ Duplicate video frame %d !!!!!",
+            pData->tr);
+      }
+      previousTr = pData->tr;
+      GST_DEBUG_OBJECT (pF,
+          "============================= TR:%d ==============================",
+          pData->tr);
+      GST_DEBUG_OBJECT (pF, "LEN:%d", pData->length);
+      GST_DEBUG_OBJECT (pF, "UD:(%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X)",
+          pData->data[0], pData->data[1], pData->data[2], pData->data[3],
+          pData->data[4], pData->data[5], pData->data[6], pData->data[7]);
+      pF->currentTime = pData->timestamp;
+      gst_user_data_analyze (pF, pData);
+      gst_buffer_unmap (pSlistElt->data, &dataMap);
+      gst_buffer_unref (pSlistElt->data);
+      pSlistElt = g_slist_next (pSlistElt);
+    }
+
+    if (tr0Count > 1) {
+      // If a GOP is missed, and two GOP sets of frames are interleaved in the sorted list caption characters
+      // may be out of order
+      GST_WARNING_OBJECT (pF, "@@@@@ Missing GOP !!!!!");
+    }
+
+    g_slist_free (pF->pDataList);
+    pF->pDataList = NULL;
+    gst_buffer_unmap (pBuf, &bufMap);
+    gst_buffer_unref (pBuf);
+  } else {
+    GST_ERROR_OBJECT (pF, "process GOP unknown data, slist:%p", pF->pDataList);
+    gst_buffer_unmap (pBuf, &bufMap);
+    gst_buffer_unref (pBuf);
+  }
+
+  return retVal;
+}
+
+#ifdef STANDALONE_ELEMENT
+static gboolean
+filter_init (GstPlugin * plugin)
+{
+  gboolean res = FALSE;
+
+  GST_DEBUG ("-------------- %s called", __func__);
+  GST_DEBUG_CATEGORY_INIT (gst_cc_filter_debug, "ccfilter", 0, "CC filter");
+
+  res = gst_element_register (plugin, "ccfilter",
+      GST_RANK_PRIMARY, GST_TYPE_CC_FILTER);
+  GST_DEBUG ("-------------- %s line %d ccfilter registration %s",
+      __func__, __LINE__, res ? "succeeded" : "failed");
+  return res;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    ccdecbad,
+    "ccdec",
+    filter_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
+#endif
diff --git a/gst/ccdec/gstccfilter.h b/gst/ccdec/gstccfilter.h
new file mode 100644
index 0000000..530651e
--- /dev/null
+++ b/gst/ccdec/gstccfilter.h
@@ -0,0 +1,374 @@
+/* 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_CCFILTER_H__
+#define __GST_CCFILTER_H__
+
+#include <gst/gst.h>
+
+#define GST_TYPE_CC_FILTER            (gst_cc_filter_get_type())
+#define GST_CC_FILTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CC_FILTER,GstCcFilter))
+#define GST_CC_FILTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CC_FILTER,GstCcFilterClass))
+#define GST_IS_CC_FILTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CC_FILTER))
+#define GST_IS_CC_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CC_FILTER))
+
+
+#define DTVCC_LENGTH 128        // should never be more than 128
+
+// CEA-708-B commands
+#define CC_COMMAND_ETX 0x03     // EndOfText
+#define CC_COMMAND_CW0 0x80     // SetCurrentWindow0–7
+#define CC_COMMAND_CW1 0x81
+#define CC_COMMAND_CW2 0x82
+#define CC_COMMAND_CW3 0x83
+#define CC_COMMAND_CW4 0x84
+#define CC_COMMAND_CW5 0x85
+#define CC_COMMAND_CW6 0x86
+#define CC_COMMAND_CW7 0x87
+#define CC_COMMAND_CLW 0x88     // ClearWindows
+#define CC_COMMAND_DSW 0x89     // DisplayWindows
+#define CC_COMMAND_HDW 0x8A     // HideWindows
+#define CC_COMMAND_TGW 0x8B     // ToggleWindows
+#define CC_COMMAND_DLW 0x8C     // DeleteWindows
+#define CC_COMMAND_DLY 0x8D     // Delay
+#define CC_COMMAND_DLC 0x8E     // DelayCancel
+#define CC_COMMAND_RST 0x8F     // Reset
+#define CC_COMMAND_SPA 0x90     // SetPenAttributes
+#define CC_COMMAND_SPC 0x91     // SetPenColor
+#define CC_COMMAND_SPL 0x92     // SetPenLocation
+#define CC_COMMAND_SWA 0x97     // SetWindowAttributes
+#define CC_COMMAND_DF0 0x98     // DefineWindow0–7
+#define CC_COMMAND_DF1 0x99
+#define CC_COMMAND_DF2 0x9A
+#define CC_COMMAND_DF3 0x9B
+#define CC_COMMAND_DF4 0x9C
+#define CC_COMMAND_DF5 0x9D
+#define CC_COMMAND_DF6 0x9E
+#define CC_COMMAND_DF7 0x9F
+
+
+// Per CEA-708 spec there may be 8 CC windows
+#define NUM_708_WINDOWS  8
+
+// Each 708 window contains a grid of character positions. These are the max limits
+// defined, but each window has a row/col count which is typically smaller than
+// the limits. Note this is just one window, not the entire screen.
+#define WINDOW_MAX_ROWS 15      // height
+#define WINDOW_MAX_COLS 32      // width
+
+// The linebuffer contains text for 1 webvtt cue corresponding to 1 line of 708 text.
+// The linebuffer could be a lot larger than the window text because of required markup.
+// example <u> </u> for underline
+// The size given is an estimate, to be changed if determined that a larger buffer is needed
+#define LINEBUFFER_SIZE (WINDOW_MAX_COLS * 4)
+
+// The screen width/height defined by 708 - not character units, these are used only to
+// determine the position of the anchor on the screen.
+#define SCREEN_WIDTH    209
+#define SCREEN_HEIGHT   74
+
+#define WIN_DEF_SIZE 6          // # raw bytes of "define window" command
+
+// The maximum size of a 708 window in character units. This is used to calculate the
+// position of windows based on window anchor positions.
+#define SCREEN_HEIGHT_708  15
+#define SCREEN_WIDTH_708   32
+
+#define COLOR_BLACK 0
+#define COLOR_WHITE 0x3F
+
+#define COLOR_BLUE0  0x0
+#define COLOR_BLUE1  0x1
+#define COLOR_BLUE2  0x2
+#define COLOR_BLUE3  0x3
+
+#define COLOR_GREEN0 (COLOR_BLUE0 << 2)
+#define COLOR_GREEN1 (COLOR_BLUE1 << 2)
+#define COLOR_GREEN2 (COLOR_BLUE2 << 2)
+#define COLOR_GREEN3 (COLOR_BLUE3 << 2)
+
+#define COLOR_RED0   (COLOR_GREEN0 << 2)
+#define COLOR_RED1   (COLOR_GREEN1 << 2)
+#define COLOR_RED2   (COLOR_GREEN2 << 2)
+#define COLOR_RED3   (COLOR_GREEN3 << 2)
+
+
+typedef enum
+{
+  SOLID = 0,
+  FLASH,
+  TRANSLUCENT,
+  TRANSPARENT
+} OPACITY;
+
+typedef enum
+{
+  TAG_DIALOG = 0,
+  TAG_SPEAKER_ID,
+  TAG_ELECTRONIC_VOICE,
+  TAG_ALT_LANGUAGE_DIALOG,
+  TAG_VOICEOVER,
+  TAG_AUDIBLE_TRANSLATION,
+  TAG_SUBTITLE_TRANSLATION,
+  TAG_VOICE_QUALITY_DESCRIPTION,
+  TAG_SONG_LYRICS,
+  TAG_SOUND_EFFECT_DESCRIPTION,
+  TAG_MUSICAL_SCORE_DESCRIPTION,
+  TAG_EXPLETIVE,
+  TAG_UNDEF1,
+  TAG_UNDEF2,
+  TAG_UNDEF3,
+  TAG_NOT_DISPLAYED
+} TAG_TYPE;
+
+typedef enum
+{
+  JUSTIFY_LEFT = 0,
+  JUSTIFY_RIGHT,
+  JUSTIFY_CENTER,
+  JUSTIFY_FULL
+} JUSTIFY_MODE;
+
+typedef enum
+{
+  PRINT_DIR_LEFT_TO_RIGHT = 0,
+  PRINT_DIR_RIGHT_TO_LEFT,
+  PRINT_DIR_TOP_TO_BOTTOM,
+  PRINT_DIR_BOTTOM_TO_TOP
+} PRINT_DIRECTION;
+
+typedef enum
+{
+  SCROLL_DIR_LEFT_TO_RIGHT = 0,
+  SCROLL_DIR_RIGHT_TO_LEFT,
+  SCROLL_DIR_TOP_TO_BOTTOM,
+  SCROLL_DIR_BOTTOM_TO_TOP
+} SCROLL_DIRECTION;
+
+typedef enum
+{
+  DISPLAY_EFFECT_SNAP = 0,
+  DISPLAY_EFFECT_FADE,
+  DISPLAY_EFFECT_WIPE
+} DISPLAY_EFFECT;
+
+typedef enum
+{
+  EFFECT_DIR_LEFT_TO_RIGHT = 0,
+  EFFECT_DIR_RIGHT_TO_LEFT,
+  EFFECT_DIR_TOP_TO_BOTTOM,
+  EFFECT_DIR_BOTTOM_TO_TOP
+} EFFECT_DIRECTION;
+
+typedef enum
+{
+  BORDER_TYPE_NONE = 0,
+  BORDER_TYPE_RAISED,
+  BORDER_TYPE_DEPRESSED,
+  BORDER_TYPE_UNIFORM
+} BORDER_TYPE;
+
+typedef enum
+{
+  PEN_SIZE_SMALL = 0,
+  PEN_SIZE_STANDARD,
+  PEN_SIZE_LARGE,
+  PEN_SIZE_INVALID
+} PEN_SIZE;
+
+typedef enum
+{
+  PEN_OFFSET_SUBSCRIPT = 0,
+  PEN_OFFSET_NORMAL,
+  PEN_OFFSET_SUPERSCRIPT,
+  PEN_OFFSET_INVALID
+} PEN_OFFSET;
+
+typedef enum
+{
+  EDGE_TYPE_NONE = 0,
+  EDGE_TYPE_RAISED,
+  EDGE_TYPE_DEPRESSED,
+  EDGE_TYPE_UNIFORM,
+  EDGE_TYPE_LEFT_DROP_SHADOW,
+  EDGE_TYPE_RIGHT_DROP_SHADOW,
+  EDGE_TYPE_INVALID_1,
+  EDGE_TYPE_INVALID_2
+} EDGE_TYPE;
+
+typedef enum
+{
+  FONT_STYLE_DEFAULT = 0,
+  FONT_STYLE_MONO_SERIF,
+  FONT_STYLE_PROP_SERIF,
+  FONT_STYLE_MONO_SANS,
+  FONT_STYLE_PROP_SANS,
+  FONT_STYLE_CASUAL,
+  FONT_STYLE_CURSIVE,
+  FONT_STYLE_SMALLCAPS
+} FONT_STYLE;
+
+
+/* incoming buffer to this element */
+typedef struct
+{
+  guint64 timestamp;
+  guint16 tr;
+  guint16 length;
+  guint8 data[0];
+} UserData;
+
+
+typedef struct
+{
+  guint8 fg_color;
+  guint8 fg_opacity;
+  guint8 bg_color;
+  guint8 bg_opacity;
+  guint8 edge_color;
+} cea708PenColor;
+
+
+typedef struct
+{
+  PEN_SIZE pen_size;
+  FONT_STYLE font_style;
+  TAG_TYPE text_tag;
+  PEN_OFFSET offset;
+  gboolean italics;
+  gboolean underline;
+  EDGE_TYPE edge_type;
+} cea708PenAttributes;
+
+// The char records one cell location in the window, with the character and
+// all of its attributes
+typedef struct
+{
+  cea708PenColor pen_color;
+  cea708PenAttributes pen_attributes;
+  guint8 justifyMode;
+  guint8 c;
+} cea708char;
+
+// This struct keeps track of one cea-708 CC window. There are up to 8. As new
+// windows are created, the text they contain is visible on the screen (if the
+// window visible flag is set). When a window is deleted, all text within the
+// window is erased from the screen. Windows may be initialized and made visible
+// then hidden. Each transition should cause new text cues to be emitted as
+// text is displayed and removed from the screen.
+typedef struct
+{
+  // These are the current attributes which will be used for the next text string
+  cea708PenColor pen_color;
+  cea708PenAttributes pen_attributes;
+
+  gboolean deleted;             // true to indicate the window has not been created.
+  // set to true on delete, false on subsequent define command
+  // if true, reset pen position to 0,0 on window creation
+
+  // Text position
+  guint16 pen_row;
+  guint16 pen_col;
+
+  guint8 priority;              // window display priority
+  guint8 anchor_point;          // window position on screen 0-8
+  guint8 relative_position;     // 1 = anchor vertical/horizontal coordinates are percent
+  // 0 = physical screen coordinate, aka. rp
+
+  guint8 anchor_vertical;       // vertical position of windows anchor point 0-74 or if rp=1 then 0-99
+  guint8 anchor_horizontal;     // horz position of window anchor point 0-209(16:9) 0-159(4:3) or if rp=1 then 0-99
+
+  gfloat screen_vertical;       // vert position of upper left corner of window, in percent
+  gfloat screen_horizontal;     // horz position of upper left corner of window, in percent
+
+  guint8 row_count;             // # virtual rows of text - 1 (ex. rc=2 means there are 3 rows)
+  guint8 column_count;          // # virtual columns of text 0-41(16:9) 0-31(4:3) - 1
+
+  guint8 row_lock;              // 1 = fixes #rows of caption text
+  // 0 = more rows may be added if user selects smaller font
+
+  guint8 column_lock;           // 1 = fixes #columns of caption text
+  // 0 = more columns may be added if user selects smaller font
+
+  gboolean visible;             // TRUE = window is visible   FALSE = window not visible
+
+  guint8 styleId;               // specifies 1 of 7 static preset window attribute styles
+  // during window create, 0 = use style #1
+  // during window update, 0 = no window attributes will be changed
+
+  guint8 penStyleId;            // specifies 1 of 7 static preset pen attributes
+  // during window create, 0 = use pen style #1
+  // during window update, 0 = do not change pen attributes
+
+  guint64 startTime;            // timestamp when this window became visible
+
+
+  // window attributes
+  guint8 justifyMode;
+  guint8 printDirection;
+  guint8 scrollDirection;
+  gboolean wordWrap;
+  guint8 displayEffect;
+  guint8 effectDirection;
+  guint8 effectSpeed;
+  guint8 fillColor;
+  guint8 fillOpacity;
+  guint8 borderType;
+  guint8 borderColor;
+
+  // Character position offsets for the upper left corner of the window
+  guint vOffset;
+  guint hOffset;
+
+  guint8 originalDef[WIN_DEF_SIZE];     // original define window command bytes from DTVCC buffer
+
+  // The character array that text is written into, using the current pen position
+  cea708char text[WINDOW_MAX_ROWS][WINDOW_MAX_COLS];
+} cea708Window;
+
+
+typedef struct _GstCcFilter GstCcFilter;
+typedef struct _GstCcFilterClass GstCcFilterClass;
+
+GType gst_cc_filter_get_type (void);
+
+#endif /* __GST_CCFILTER_H__ */
diff --git a/gst/videoparsers/gstmpegvideoparse.c b/gst/videoparsers/gstmpegvideoparse.c
index 683479b..b7fe81e 100644
--- a/gst/videoparsers/gstmpegvideoparse.c
+++ b/gst/videoparsers/gstmpegvideoparse.c
@@ -27,6 +27,7 @@
 #endif
 
 #include <string.h>
+#include <gst/base/gstbitreader.h>
 #include <gst/base/gstbytereader.h>
 #include <gst/codecparsers/gstmpegvideometa.h>
 
@@ -35,6 +36,11 @@
 GST_DEBUG_CATEGORY (mpegv_parse_debug);
 #define GST_CAT_DEFAULT mpegv_parse_debug
 
+#define NS_PER_90KHZ_TICK  11111LL
+#define UD_HEADER_SIZE  7       // ATSC A/53 part 4 user data header size
+
+#undef DROP_USER_DATA
+
 static GstStaticPadTemplate src_template =
 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC,
     GST_PAD_ALWAYS,
@@ -50,6 +56,12 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
         "mpegversion = (int) [1, 2], " "systemstream = (boolean) false")
     );
 
+static GstStaticPadTemplate subtitle_src_template =
+GST_STATIC_PAD_TEMPLATE ("subtitle_src", GST_PAD_SRC,
+    GST_PAD_SOMETIMES,
+    GST_STATIC_CAPS ("subtitle/x-cea-708")
+    );
+
 /* Properties */
 #define DEFAULT_PROP_DROP       TRUE
 #define DEFAULT_PROP_GOP_SPLIT  FALSE
@@ -150,6 +162,8 @@ gst_mpegv_parse_class_init (GstMpegvParseClass * klass)
       gst_static_pad_template_get (&src_template));
   gst_element_class_add_pad_template (element_class,
       gst_static_pad_template_get (&sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&subtitle_src_template));
 
   gst_element_class_set_static_metadata (element_class,
       "MPEG video elementary stream parser",
@@ -176,6 +190,8 @@ gst_mpegv_parse_init (GstMpegvParse * parse)
   parse->config_flags = FLAG_NONE;
 
   gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (parse), FALSE);
+
+  parse->subtitle_srcpad = NULL;
 }
 
 static void
@@ -461,6 +477,259 @@ parse_packet_extension (GstMpegvParse * mpvparse, GstMapInfo * info, guint off)
   }
 }
 
+#ifndef DROP_USER_DATA
+static gboolean
+send_new_segment (GstMpegvParse * mpvparse)
+{
+  GstEvent *pEvent;
+  GstSegment segment = { 0 };
+
+  gst_segment_init (&segment, GST_FORMAT_BYTES);
+  GST_INFO_OBJECT (mpvparse, "Sending segment %" GST_SEGMENT_FORMAT, &segment);
+  pEvent = gst_event_new_segment (&segment);
+  return gst_pad_push_event (mpvparse->subtitle_srcpad, pEvent);
+}
+
+static gboolean
+add_subtitle_srcpad (GstMpegvParse * mpvparse)
+{
+  gchar *pSID = NULL;
+
+  mpvparse->subtitle_srcpad =
+      gst_pad_new_from_static_template (&subtitle_src_template, "subtitle_src");
+  gst_pad_set_active (mpvparse->subtitle_srcpad, TRUE);
+  gst_pad_use_fixed_caps (mpvparse->subtitle_srcpad);
+  pSID = gst_pad_create_stream_id_printf (mpvparse->subtitle_srcpad,
+      GST_ELEMENT (mpvparse), "subtitle_src");
+  gst_pad_push_event (mpvparse->subtitle_srcpad,
+      gst_event_new_stream_start (pSID));
+  g_free (pSID);
+
+  gst_element_add_pad (GST_ELEMENT (mpvparse), mpvparse->subtitle_srcpad);
+  //gst_element_no_more_pads (GST_ELEMENT (mpvparse));
+  GST_INFO_OBJECT (mpvparse, "subtitle_srcpad added to parser");
+  return send_new_segment (mpvparse);
+}
+#endif
+
+static GstFlowReturn
+send_user_data_packet (GstMpegvParse * mpvparse, GstBuffer * outbuf)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+#ifdef DROP_USER_DATA
+  gst_buffer_unref (outbuf);
+#else
+  if (NULL == mpvparse->subtitle_srcpad) {
+    add_subtitle_srcpad (mpvparse);
+  }
+
+  ret = gst_pad_push (mpvparse->subtitle_srcpad, outbuf);
+
+  if (ret != GST_FLOW_OK) {
+    GST_ERROR_OBJECT (mpvparse,
+        "Failed to push buffer. GstFlowReturn is %d", ret);
+  }
+#endif
+  return ret;
+}
+
+static void
+parse_packet_user_data (GstMpegvParse * mpvparse, GstMapInfo * info, guint off)
+{
+  GstMapInfo outbuf_map = { 0 };
+  GstBuffer *outbuf = NULL;
+  gboolean add_data = 0;
+  gboolean cc_data = 0;
+  gboolean em_data = 0;
+  guint64 temp64 = 0;
+  guint32 ud_ID = 0;
+  guint8 temp8 = 0;
+  gint i = 0;
+  guint16 len = 0;
+  GstBitReader gbr = { 0 };
+  gint bytes = info->size;
+
+  if (bytes >= off) {
+    gst_bit_reader_init (&gbr, info->data, info->size);
+    gst_bit_reader_skip (&gbr, 8 * off);
+    bytes -= off;
+  } else {
+    GST_ERROR_OBJECT (mpvparse, "insufficient buffer data for offset (%d < %d)",
+        bytes, off);
+    return;
+  }
+
+  gst_bit_reader_peek_bits_uint64 (&gbr, &temp64, 64);
+  ud_ID = (guint32) ((temp64 >> 32) & 0xFFFFFFFF);
+
+  // "GA94" or "DTG1" are the allowed UD identifiers for ATSC A/53 part 4
+  if ((0x47413934 == ud_ID) || (0x44544731 == ud_ID)) {
+    temp8 = (guint8) ((temp64 & 0xFF000000) >> 24);
+    em_data = ((temp64 & 0x00800000) >> 23);
+    cc_data = ((temp64 & 0x00400000) >> 22);
+    add_data = ((temp64 & 0x00200000) >> 21);
+    len = (guint8) ((temp64 & 0x001F0000) >> 16) * 3;   // triplets
+
+    if (len > bytes) {
+      GST_ERROR_OBJECT (mpvparse, "insufficient buffer data for len (%d < %d)",
+          bytes, len);
+      return;
+    }
+
+    outbuf = gst_buffer_new_allocate (NULL,
+        sizeof (UserData) + UD_HEADER_SIZE + len, NULL);
+    if (outbuf) {
+      gst_buffer_map (outbuf, &outbuf_map, GST_MAP_WRITE);
+      GST_BUFFER_DTS (outbuf) = mpvparse->frame_dts;
+      GST_BUFFER_PTS (outbuf) = mpvparse->frame_pts;
+
+      // TODO: verify we're sending the appropriate timestamp
+      ((UserData *) outbuf_map.data)->timestamp =
+          mpvparse->frame_pts / NS_PER_90KHZ_TICK;
+      ((UserData *) outbuf_map.data)->temporal_ref = mpvparse->temporal_ref;
+      ((UserData *) outbuf_map.data)->length = len + UD_HEADER_SIZE;
+
+      if (em_data) {
+        temp8 = (guint8) ((temp64 & 0x0000FF00) >> 8);
+        GST_LOG_OBJECT (mpvparse, "process EM data (%02X)", temp8);
+        // TODO: handle EM data
+      }
+
+      if (cc_data) {
+        GST_LOG_OBJECT (mpvparse, "process CC data...");
+        for (i = 0; i < len + UD_HEADER_SIZE && bytes > 0; i++) {
+          gst_bit_reader_get_bits_uint8 (&gbr, &temp8, 8);
+          ((UserData *) outbuf_map.data)->data[i] = temp8;
+          bytes -= 1;
+        }
+      }
+
+      if (add_data) {
+        guint32 terminator = 0xFFFFFFFF;
+        GST_INFO_OBJECT (mpvparse, "process additional data...");
+        for (i = len + UD_HEADER_SIZE; bytes > 0; i++) {
+          gst_bit_reader_peek_bits_uint8 (&gbr, &temp8, 8);
+          ((UserData *) outbuf_map.data)->data[i] = temp8;
+          bytes -= 1;
+          terminator <<= 8;
+          terminator |= temp8;
+
+          if (1 == (terminator & 0x00FFFFFF)) {
+            GST_INFO_OBJECT (mpvparse, "additional data complete");
+            break;
+          }
+        }
+
+        // TODO: handle additional data
+      }
+
+      GST_DEBUG_OBJECT (mpvparse, "UD: (%02X:%02X:%02X:%02X), TR:%d, LEN:%d",
+          ((UserData *) outbuf_map.data)->data[0],
+          ((UserData *) outbuf_map.data)->data[1],
+          ((UserData *) outbuf_map.data)->data[2],
+          ((UserData *) outbuf_map.data)->data[3],
+          ((UserData *) outbuf_map.data)->temporal_ref,
+          ((UserData *) outbuf_map.data)->length);
+      gst_buffer_unmap (outbuf, &outbuf_map);
+      send_user_data_packet (mpvparse, outbuf);
+    } else {
+      GST_ERROR_OBJECT (mpvparse,
+          "gst_buffer_new_allocate failure for size: %zu",
+          sizeof (UserData) + len + 8);
+    }
+  } else {
+    GST_DEBUG_OBJECT (mpvparse, "ignoring: UD ID: %08X", ud_ID);
+  }
+}
+
+static void
+parse_packet_gop (GstMpegvParse * mpvparse, GstMapInfo * info, guint off)
+{
+  guint64 gop_ts = 0;
+  guint32 timeCode = 0;
+  gint gopDrop = 0;
+  gint gopHours = 0;
+  gint gopMinutes = 0;
+  gint gopSeconds = 0;
+  gint gopPictures = 0;
+  gint gopType = 0;
+  GstBuffer *outbuf = NULL;
+  GstMapInfo out_info = { 0 };
+  GstBitReader gbr = { 0 };
+  gint bytes = info->size;
+
+  if (bytes >= off) {
+    gst_bit_reader_init (&gbr, info->data, info->size);
+    gst_bit_reader_skip (&gbr, 8 * off);
+  } else {
+    GST_ERROR_OBJECT (mpvparse, "insufficient buffer data for offset (%d < %d)",
+        bytes, off);
+    return;
+  }
+
+  gst_bit_reader_peek_bits_uint32 (&gbr, &timeCode, 32);        // get timecode
+
+  //
+  // GOP header timecode bits
+  // ----++++----++++----++++----++++
+  // dhhhhhmmmmmmRsssssspppppp0000000
+  // 33222222222211111111110000000000
+  // 10987654321098765432109876543210
+  //
+  gopDrop = (timeCode & 0x80000000) >> 31;
+  gopHours = (timeCode & 0x7C000000) >> 26;
+  gopMinutes = (timeCode & 0x03F00000) >> 20;
+  gopSeconds = (timeCode & 0x0007E000) >> 13;
+  gopPictures = (timeCode & 0x00001F80) >> 7;
+  gopType = (timeCode & 0x00000060) >> 5;
+  GST_LOG_OBJECT (mpvparse, "%sfps %s GOP timecode(%.2d:%.2d:%.2d), frame(%d)",
+      (gopDrop == 1 ? "29.97" : "30"),
+      (gopType == 2 ? "Closed" : (gopType == 1 ? "Broken" : "Open")),
+      gopHours, gopMinutes, gopSeconds, gopPictures);
+
+  gop_ts = gopHours * (90000 * 60 * 60);
+  gop_ts += gopMinutes * (90000 * 60);
+  gop_ts += gopSeconds * 90000;
+  outbuf = gst_buffer_new_and_alloc (sizeof (UserData) + sizeof ("GOP"));
+
+  if (outbuf) {
+    gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE);
+    ((UserData *) out_info.data)->timestamp = gop_ts;
+    ((UserData *) out_info.data)->temporal_ref = (gopPictures & 0xFF);
+    ((UserData *) out_info.data)->length = sizeof ("GOP");
+    ((UserData *) out_info.data)->data[0] = 'G';
+    ((UserData *) out_info.data)->data[1] = 'O';
+    ((UserData *) out_info.data)->data[2] = 'P';
+    ((UserData *) out_info.data)->data[3] = 0;
+    gst_buffer_unmap (outbuf, &out_info);
+    send_user_data_packet (mpvparse, outbuf);
+  }
+}
+
+static void
+parse_packet_picture (GstMpegvParse * mpvparse, GstMapInfo * info, guint off)
+{
+  guint8 temp8 = 0;
+  GstBitReader gbr = { 0 };
+  gint bytes = info->size;
+
+  if (bytes >= off) {
+    gst_bit_reader_init (&gbr, info->data, info->size);
+    gst_bit_reader_skip (&gbr, 8 * off);
+  } else {
+    GST_ERROR_OBJECT (mpvparse, "insufficient buffer data for offset (%d < %d)",
+        bytes, off);
+    return;
+  }
+
+  gst_bit_reader_get_bits_uint8 (&gbr, &temp8, 8);
+  mpvparse->temporal_ref = temp8;
+  gst_bit_reader_get_bits_uint8 (&gbr, &temp8, 8);
+  mpvparse->temporal_ref =
+      ((mpvparse->temporal_ref << 2) | ((temp8 & 0xC0) >> 6));
+  GST_LOG_OBJECT (mpvparse, "temporal_reference[%d]", mpvparse->temporal_ref);
+}
+
 /* caller guarantees at least start code in @buf at @off */
 /* for off == 4 initial code; returns TRUE if code starts a frame
  * otherwise returns TRUE if code terminates preceding frame */
@@ -485,6 +754,8 @@ gst_mpegv_parse_process_sc (GstMpegvParse * mpvparse,
       /* but it's a valid starting one */
       if (off == 4)
         ret = TRUE;
+
+      parse_packet_picture (mpvparse, info, off);
       break;
     case GST_MPEG_VIDEO_PACKET_SEQUENCE:
       GST_LOG_OBJECT (mpvparse, "startcode is SEQUENCE");
@@ -498,6 +769,12 @@ gst_mpegv_parse_process_sc (GstMpegvParse * mpvparse,
         ret = mpvparse->gop_split;
       else
         ret = TRUE;
+
+      parse_packet_gop (mpvparse, info, off);
+      break;
+    case GST_MPEG_VIDEO_PACKET_USER_DATA:
+      GST_LOG_OBJECT (mpvparse, "startcode is USER_DATA");
+      parse_packet_user_data (mpvparse, info, off);
       break;
     case GST_MPEG_VIDEO_PACKET_EXTENSION:
       GST_LOG_OBJECT (mpvparse, "startcode is VIDEO PACKET EXTENSION");
@@ -557,6 +834,8 @@ update_frame_parsing_status (GstMpegvParse * mpvparse,
   if (frame->flags & GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME) {
     GST_LOG_OBJECT (mpvparse, "parsing new frame");
     gst_mpegv_parse_reset_frame (mpvparse);
+    mpvparse->frame_dts = GST_BUFFER_DTS (frame->buffer);
+    mpvparse->frame_pts = GST_BUFFER_PTS (frame->buffer);
   } else {
     GST_LOG_OBJECT (mpvparse, "resuming frame parsing");
   }
diff --git a/gst/videoparsers/gstmpegvideoparse.h b/gst/videoparsers/gstmpegvideoparse.h
index 1a56076..a7068b7 100644
--- a/gst/videoparsers/gstmpegvideoparse.h
+++ b/gst/videoparsers/gstmpegvideoparse.h
@@ -46,6 +46,7 @@ G_BEGIN_DECLS
 
 typedef struct _GstMpegvParse GstMpegvParse;
 typedef struct _GstMpegvParseClass GstMpegvParseClass;
+typedef struct _UserData UserData;
 
 /* Config/sequence flags. Reset each time config is re-parsed */
 enum {
@@ -86,6 +87,9 @@ struct _GstMpegvParse {
   gboolean seqdispext_updated;
   gboolean picext_updated;
   gboolean quantmatrext_updated;
+  guint64 frame_pts;
+  guint64 frame_dts;
+  guint16 temporal_ref;
 
   /* properties */
   gboolean drop;
@@ -94,12 +98,21 @@ struct _GstMpegvParse {
   int fps_num;
   int fps_den;
   int frame_repeat_count;
+
+  GstPad* subtitle_srcpad;
 };
 
 struct _GstMpegvParseClass {
   GstBaseParseClass parent_class;
 };
 
+struct _UserData {
+  guint64 timestamp;
+  guint16 temporal_ref;
+  guint16 length;
+  guint8 data[0];
+};
+
 GType gst_mpegv_parse_get_type (void);
 
 G_END_DECLS
