ceaccoverlay: New CEA-708 Closed Caption decoder and overlayer
authorEdward Hervey <edward@centricular.com>
Wed, 11 Mar 2015 10:00:08 +0000 (18:00 +0800)
committerEdward Hervey <bilboed@bilboed.com>
Mon, 28 May 2018 13:29:01 +0000 (15:29 +0200)
This new element allows decoding and overlaying CEA-708 Closed Caption
streams over video.

* Supports CDP and cc_data closedcaption/x-cea-708 streams
* Uses pango to render CC stream
* Support GstVideoOverlayComposition meta if downstream supports is

Tested on various test files.

Remains to be fixed/improved:
* Switch to GstByteReader (for code safety)
* Switch to GString (instead of manual pango string construction)
* Move pango/rendering code outside of main 708 decoder file (so
  that actual CC parser/decoder can be (re)used in other scenarios).

Initial patches and improvements by:
* CableLabs RUIH-RI Team <ruihri@cablelabs.com>
* Steve Maynard <steve@secondstryke.com>
* cjun.wang" <cjun.wang@samsung.com>

https://bugzilla.gnome.org/show_bug.cgi?id=704881

ext/closedcaption/Makefile.am
ext/closedcaption/gstcea708decoder.c [new file with mode: 0644]
ext/closedcaption/gstcea708decoder.h [new file with mode: 0644]
ext/closedcaption/gstceaccoverlay.c [new file with mode: 0644]
ext/closedcaption/gstceaccoverlay.h [new file with mode: 0644]
ext/closedcaption/gstclosedcaption.c
ext/closedcaption/meson.build

index a2a6cd1..639dac0 100644 (file)
@@ -21,6 +21,10 @@ libgstclosedcaption_la_SOURCES = \
        $(zvbi_headers) \
        gstccextractor.c \
        gstccextractor.h \
+       gstcea708decoder.c \
+       gstcea708decoder.h \
+       gstceaccoverlay.c \
+       gstceaccoverlay.h \
        gstline21dec.c \
        gstline21dec.h \
        gstclosedcaption.c
@@ -28,12 +32,14 @@ libgstclosedcaption_la_SOURCES = \
 libgstclosedcaption_la_CFLAGS = \
        $(GST_PLUGINS_BASE_CFLAGS) \
        $(GST_BASE_CFLAGS) \
-       $(GST_CFLAGS)
+       $(GST_CFLAGS) \
+       $(PANGO_CFLAGS)
 
 libgstclosedcaption_la_LIBADD = \
        $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ \
        $(GST_BASE_LIBS) \
-       $(GST_LIBS)
+       $(GST_LIBS) \
+       $(PANGO_LIBS)
 
 libgstclosedcaption_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
diff --git a/ext/closedcaption/gstcea708decoder.c b/ext/closedcaption/gstcea708decoder.c
new file mode 100644 (file)
index 0000000..73a4080
--- /dev/null
@@ -0,0 +1,1791 @@
+/* GStreamer
+ * Copyright (C) 2013 CableLabs, Louisville, CO 80027
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *     @Author: Chengjun Wang <cjun.wang@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <gst/gst.h>
+#include <pango/pangocairo.h>
+#include <gstcea708decoder.h>
+#include <string.h>
+
+#define GST_CAT_DEFAULT gst_cea708_decoder_debug
+GST_DEBUG_CATEGORY (gst_cea708_decoder_debug);
+
+void
+gst_cea708_decoder_init_debug (void)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_cea708_decoder_debug, "cc708decoder", 0,
+      "CEA708 Closed Caption Decoder");
+}
+
+/* 708 colors are defined by 2 bits each for R,G,&B for a total of 64 color combinations */
+static const gchar *color_names[] = {
+  "black",
+  "white",
+  "red",
+  "green",
+  "blue",
+  "yellow",
+  "magenta",
+  "cyan",
+  NULL
+};
+
+static const gchar *font_names[] = {
+  "serif",
+  "courier",
+  "times new roman",
+  "helvetica",
+  "Arial",
+  "Dom Casual",
+  "Coronet",
+  "Gothic",
+  NULL
+};
+
+static const gchar *pen_size_names[] = {
+  "30",                         /*"small" */
+  "36",                         /*"medium" */
+  "42",                         /*"large" */
+  NULL
+};
+
+/* G2 table defined in EIA/CEA-708 Spec */
+static const gunichar g2_table[CC_MAX_CODE_SET_SIZE] = {
+  ' ', 0xA0, 0, 0, 0, 0x2026, 0, 0,
+  0, 0, 0x160, 0, 0x152, 0, 0, 0,
+  0x2588, 0x2018, 0x2019, 0x201c, 0x201d, 0xB7, 0, 0,
+  0, 0x2122, 0x161, 0, 0x153, 0x2120, 0, 0x178,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0x215b, 0x215c,
+  0x215d, 0x215e, 0x2502, 0x2510, 0x2514, 0x2500, 0x2518, 0x250c,
+};
+
+static void gst_cea708dec_print_command_name (Cea708Dec * decoder, guint8 c);
+static void gst_cea708dec_render_pangocairo (cea708Window * window);
+static void
+gst_cea708dec_adjust_values_with_fontdesc (cea708Window * window,
+    PangoFontDescription * desc);
+static gint
+gst_cea708dec_text_list_add (GSList ** text_list,
+    gint len, const gchar * format, ...);
+static const PangoAlignment gst_cea708dec_get_align_mode (guint8 justify_mode);
+static const gchar *gst_cea708dec_get_color_name (guint8 color);
+static guint8 gst_cea708dec_map_minimum_color (guint8 color);
+static void
+gst_cea708dec_set_pen_color (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+static void
+gst_cea708dec_set_window_attributes (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+static void
+gst_cea708dec_set_pen_style (Cea708Dec * decoder, guint8 pen_style_id);
+static void
+gst_cea708dec_set_window_style (Cea708Dec * decoder, guint8 style_id);
+static void
+gst_cea708dec_define_window (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+static inline void
+pango_span_markup_init (cea708PangoSpanControl * span_control);
+static inline void
+pango_span_markup_start (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index);
+static inline void
+pango_span_markup_txt (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index);
+static inline void
+pango_span_markup_end (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index);
+static void
+gst_cea708dec_show_pango_window (Cea708Dec * decoder, guint window_id);
+static void
+gst_cea708dec_clear_window_text (Cea708Dec * decoder, guint window_id);
+static void
+gst_cea708dec_scroll_window_up (Cea708Dec * decoder, guint window_id);
+static void gst_cea708dec_init_window (Cea708Dec * decoder, guint window_id);
+static void
+gst_cea708dec_set_pen_attributes (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+static void
+gst_cea708dec_for_each_window (Cea708Dec * decoder,
+    guint8 window_list, VisibilityControl visibility_control,
+    const gchar * log_message, void (*function) (Cea708Dec * decoder,
+        guint window_id));
+static void
+gst_cea708dec_process_command (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+static void get_cea708dec_bufcat (gpointer data, gpointer whole_buf);
+static gboolean
+gst_cea708dec_render_text (Cea708Dec * decoder, GSList ** text_list,
+    gint length, guint window_id);
+static void gst_cea708dec_window_add_char (Cea708Dec * decoder, gunichar c);
+static void
+gst_cea708dec_process_c2 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
+    int index);
+static void
+gst_cea708dec_process_g2 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
+    int index);
+static void
+gst_cea708dec_process_c3 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
+    int index);
+static void
+gst_cea708dec_process_g3 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
+    int index);
+static void
+gst_cea708dec_process_dtvcc_byte (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index);
+
+/* For debug, print name of 708 command */
+Cea708Dec *
+gst_cea708dec_create (PangoContext * pango_context)
+{
+  int i;
+  Cea708Dec *decoder = g_malloc (sizeof (Cea708Dec));;
+  memset (decoder, 0, sizeof (Cea708Dec));
+
+  /* Initialize 708 variables */
+  for (i = 0; i < MAX_708_WINDOWS; i++) {
+    decoder->cc_windows[i] = g_malloc (sizeof (cea708Window));
+    gst_cea708dec_init_window (decoder, i);
+  }
+  decoder->desired_service = 1;
+  decoder->use_ARGB = FALSE;
+  decoder->pango_context = pango_context;
+  return decoder;
+}
+
+void
+gst_cea708dec_set_service_number (Cea708Dec * decoder, gint8 desired_service)
+{
+  int i = 0;
+  gint8 previous_desired_service;
+  previous_desired_service = decoder->desired_service;
+  decoder->desired_service = desired_service;
+  /* If there has been a change in the desired service number, then clear
+   * the windows for the new service. */
+  if (decoder->desired_service != previous_desired_service) {
+    for (i = 0; i < MAX_708_WINDOWS; i++) {
+      gst_cea708dec_init_window (decoder, i);
+    }
+    decoder->current_window = 0;
+  }
+}
+
+gboolean
+gst_cea708dec_process_dtvcc_packet (Cea708Dec * decoder, guint8 * dtvcc_buffer,
+    gsize dtvcc_size)
+{
+  guint i;
+  gboolean need_render = FALSE;
+  cea708Window *window = NULL;
+  guint window_id;
+
+  /* Service block header (see CEA-708 6.2.1) */
+  guint8 block_size;
+  guint8 service_number;
+
+  guint parse_index = 0;
+  guint8 sequence_number = (dtvcc_buffer[parse_index] & 0xC0) >> 6;
+  guint8 pkt_size = DTVCC_PKT_SIZE (dtvcc_buffer[parse_index] & 0x3F);
+  parse_index += 1;
+
+  block_size = dtvcc_buffer[parse_index] & 0x1F;
+  service_number = (dtvcc_buffer[parse_index] & 0xE0) >> 5;
+  parse_index += 1;
+
+  if (service_number == 7) {
+    /* Get extended service number */
+    service_number = dtvcc_buffer[parse_index] & 0x3F;
+    parse_index += 1;
+  }
+
+  GST_LOG ("full_size:%" G_GSIZE_FORMAT
+      " size=%d seq=%d block_size=%d service_num=%d", dtvcc_size, pkt_size,
+      sequence_number, block_size, service_number);
+
+
+  /* Process desired_service cc data */
+  if (decoder->desired_service == service_number) {
+    for (i = 0; i < block_size; i++) {
+      /* 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: */
+      gst_cea708dec_process_dtvcc_byte (decoder, dtvcc_buffer, parse_index + i);
+    }
+
+    for (window_id = 0; window_id < 8; window_id++) {
+      window = decoder->cc_windows[window_id];
+      GST_LOG ("window #%02d deleted:%d visible:%d updated:%d", window_id,
+          window->deleted, window->visible, window->updated);
+      if (!window->updated) {
+        continue;
+      }
+      need_render = TRUE;
+    }
+  }
+
+  return need_render;
+}
+
+static void
+gst_cea708dec_process_dtvcc_byte (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  guint8 c = dtvcc_buffer[index];
+
+  if (decoder->output_ignore) {
+    /* Ignore characters/parameters after a command. */
+    /* GST_TRACE ("[%d] ignore %X", decoder->output_ignore, c); */
+    decoder->output_ignore--;
+    return;
+  }
+  GST_DEBUG ("processing 0x%02X", c);
+
+  if (c >= 0x00 && c <= 0x1F) { /* C0 */
+    if (c == 0x03) {            /* ETX */
+      gst_cea708dec_process_command (decoder, dtvcc_buffer, index);
+    } else if (c == 0x00 || c == 0x08 || c == 0x0C || c == 0x0D || c == 0x0E) {
+      gst_cea708dec_window_add_char (decoder, c);
+    } else if (c == 0x10) {     /* EXT1 */
+      guint8 next_c = dtvcc_buffer[index + 1];
+      if (next_c >= 0x00 && next_c <= 0x1F) {   /* C2 */
+        gst_cea708dec_process_c2 (decoder, dtvcc_buffer, index + 1);
+      } else if (next_c >= 0x20 && next_c <= 0x7F) {    /* G2 */
+        gst_cea708dec_process_g2 (decoder, dtvcc_buffer, index + 1);
+      } else if (next_c >= 0x80 && next_c <= 0x9F) {    /* C3 */
+        gst_cea708dec_process_c3 (decoder, dtvcc_buffer, index + 1);
+      } else if (next_c >= 0xA0 && next_c <= 0xFF) {    /* G3 */
+        gst_cea708dec_process_g3 (decoder, dtvcc_buffer, index + 1);
+      }
+    } else if (c > 0x10 && c < 0x18) {
+      decoder->output_ignore = 1;
+      GST_INFO ("do not support 0x11-0x17");
+    } else if (c >= 0x18) {     /* P16 */
+      /*P16 do not support now */
+      decoder->output_ignore = 2;
+      GST_INFO ("do not support 0x18-0x1F");
+    }
+  } else if ((c >= 0x20) && (c <= 0x7F)) {      /* G0 */
+    if (c == 0x7F) {
+      gst_cea708dec_window_add_char (decoder, CC_SPECIAL_CODE_MUSIC_NOTE);
+    } else {
+      gst_cea708dec_window_add_char (decoder, c);
+    }
+  } else if ((c >= 0x80) && (c <= 0x9F)) {      /* C1 */
+    gst_cea708dec_process_command (decoder, dtvcc_buffer, index);
+  } else if ((c >= 0xA0) && (c <= 0xFF)) {      /* G1 */
+    gst_cea708dec_window_add_char (decoder, c);
+  }
+}
+
+/* For debug, print name of 708 command */
+static void
+gst_cea708dec_print_command_name (Cea708Dec * decoder, guint8 c)
+{
+  gchar buffer[32];
+  const gchar *command = NULL;
+
+  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_snprintf (buffer, sizeof (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:
+      g_snprintf (buffer, sizeof (buffer), "define window %d", c & 0x3);
+      command = buffer;
+      break;
+
+    default:
+      if ((c > 0x80) && (c < 0x9F))
+        command = (const gchar *) "Unknown";
+      break;
+  }                             /* switch */
+
+  if (NULL != command) {
+    GST_LOG ("Process 708 command (%02X): %s", c, command);
+  }
+}
+
+static void
+gst_cea708dec_render_pangocairo (cea708Window * window)
+{
+  cairo_t *crt;
+  cairo_surface_t *surf;
+  cairo_t *shadow;
+  cairo_surface_t *surf_shadow;
+  PangoRectangle ink_rec, logical_rec;
+  gint width, height;
+
+  pango_layout_get_pixel_extents (window->layout, &ink_rec, &logical_rec);
+
+  width = logical_rec.width + window->shadow_offset;
+  height = logical_rec.height + logical_rec.y + window->shadow_offset;
+
+  surf_shadow = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
+  shadow = cairo_create (surf_shadow);
+
+  /* clear shadow surface */
+  cairo_set_operator (shadow, CAIRO_OPERATOR_CLEAR);
+  cairo_paint (shadow);
+  cairo_set_operator (shadow, CAIRO_OPERATOR_OVER);
+
+  /* draw shadow text */
+  cairo_save (shadow);
+  cairo_set_source_rgba (shadow, 0.0, 0.0, 0.0, 0.5);
+  cairo_translate (shadow, window->shadow_offset, window->shadow_offset);
+  pango_cairo_show_layout (shadow, window->layout);
+  cairo_restore (shadow);
+
+  /* draw outline text */
+  cairo_save (shadow);
+  cairo_set_source_rgb (shadow, 0.0, 0.0, 0.0);
+  cairo_set_line_width (shadow, window->outline_offset);
+  pango_cairo_layout_path (shadow, window->layout);
+  cairo_stroke (shadow);
+  cairo_restore (shadow);
+
+  cairo_destroy (shadow);
+
+  window->text_image = g_realloc (window->text_image, 4 * width * height);
+
+  surf = cairo_image_surface_create_for_data (window->text_image,
+      CAIRO_FORMAT_ARGB32, width, height, width * 4);
+  crt = cairo_create (surf);
+  cairo_set_operator (crt, CAIRO_OPERATOR_CLEAR);
+  cairo_paint (crt);
+  cairo_set_operator (crt, CAIRO_OPERATOR_OVER);
+
+  /* set default color */
+  cairo_set_source_rgb (crt, 1.0, 1.0, 1.0);
+
+  cairo_save (crt);
+  /* draw text */
+  pango_cairo_show_layout (crt, window->layout);
+  cairo_restore (crt);
+
+  /* composite shadow with offset */
+  cairo_set_operator (crt, CAIRO_OPERATOR_DEST_OVER);
+  cairo_set_source_surface (crt, surf_shadow, 0.0, 0.0);
+  cairo_paint (crt);
+
+  cairo_destroy (crt);
+  cairo_surface_destroy (surf_shadow);
+  cairo_surface_destroy (surf);
+  window->image_width = width;
+  window->image_height = height;
+}
+
+static void
+gst_cea708dec_adjust_values_with_fontdesc (cea708Window * window,
+    PangoFontDescription * desc)
+{
+  gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
+
+  window->shadow_offset = (double) (font_size) / 13.0;
+  window->outline_offset = (double) (font_size) / 15.0;
+  if (window->outline_offset < MINIMUM_OUTLINE_OFFSET)
+    window->outline_offset = MINIMUM_OUTLINE_OFFSET;
+}
+
+static gint
+gst_cea708dec_text_list_add (GSList ** text_list,
+    gint len, const gchar * format, ...)
+{
+  va_list args;
+  gchar *str;
+
+  va_start (args, format);
+
+  str = g_malloc0 (len);
+  len = g_vsnprintf (str, len, format, args);
+  *text_list = g_slist_append (*text_list, str);
+  GST_LOG ("added %p str[%d]: %s", str, len, str);
+
+  va_end (args);
+  return len;
+}
+
+static const PangoAlignment
+gst_cea708dec_get_align_mode (guint8 justify_mode)
+{
+  guint align_mode = PANGO_ALIGN_LEFT;
+
+  switch (justify_mode) {
+    case JUSTIFY_LEFT:
+      align_mode = PANGO_ALIGN_LEFT;
+      break;
+    case JUSTIFY_RIGHT:
+      align_mode = PANGO_ALIGN_RIGHT;
+      break;
+    case JUSTIFY_CENTER:
+      align_mode = PANGO_ALIGN_CENTER;
+      break;
+    case JUSTIFY_FULL:
+    default:
+      align_mode = PANGO_ALIGN_LEFT;
+  }
+  return align_mode;
+}
+
+static const gchar *
+gst_cea708dec_get_color_name (guint8 color)
+{
+  guint index = 0;
+
+  switch (color) {
+    case CEA708_COLOR_BLACK:
+      index = COLOR_TYPE_BLACK;
+      break;
+    case CEA708_COLOR_WHITE:
+      index = COLOR_TYPE_WHITE;
+      break;
+    case CEA708_COLOR_RED:
+      index = COLOR_TYPE_RED;
+      break;
+    case CEA708_COLOR_GREEN:
+      index = COLOR_TYPE_GREEN;
+      break;
+    case CEA708_COLOR_BLUE:
+      index = COLOR_TYPE_BLUE;
+      break;
+    case CEA708_COLOR_YELLOW:
+      index = COLOR_TYPE_YELLOW;
+      break;
+    case CEA708_COLOR_MAGENTA:
+      index = COLOR_TYPE_MAGENTA;
+      break;
+    case CEA708_COLOR_CYAN:
+      index = COLOR_TYPE_CYAN;
+      break;
+    default:
+      break;
+  }
+
+  return color_names[index];
+}
+
+static guint8
+gst_cea708dec_map_minimum_color (guint8 color)
+{
+  /*According to spec minimum color list define */
+  /*check R */
+  switch ((color & 0x30) >> 4) {
+    case 1:
+      color &= 0xF;
+      break;
+    case 3:
+      color &= 0x2F;
+      break;
+    default:
+      break;
+  }
+  /*check G */
+  switch ((color & 0xC) >> 2) {
+    case 1:
+      color &= 0x33;
+      break;
+    case 3:
+      color &= 0x3B;
+      break;
+    default:
+      break;
+  }
+  /*check B */
+  switch (color & 0x3) {
+    case 1:
+      color &= 0x3C;
+      break;
+    case 3:
+      color &= 0x3E;
+      break;
+    default:
+      break;
+  }
+
+  return color;
+}
+
+static void
+gst_cea708dec_set_pen_color (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+
+  /* 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 */
+  window->pen_color.fg_color =
+      gst_cea708dec_map_minimum_color (dtvcc_buffer[index] & 0x3F);
+  window->pen_color.fg_opacity = (dtvcc_buffer[index] & 0xC0) >> 6;
+  window->pen_color.bg_color =
+      gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 1] & 0x3F);
+  window->pen_color.bg_opacity = (dtvcc_buffer[index + 1] & 0xC0) >> 6;
+  window->pen_color.edge_color =
+      gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 2] & 0x3F);
+  GST_LOG ("pen_color fg=0x%x fg_op=0x%x bg=0x%x bg_op=0x%x edge=0x%x",
+      window->pen_color.fg_color, window->pen_color.fg_opacity,
+      window->pen_color.bg_color, window->pen_color.bg_opacity,
+      window->pen_color.edge_color);
+}
+
+static void
+gst_cea708dec_set_window_attributes (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+
+  /* 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 */
+  window->fill_color =
+      gst_cea708dec_map_minimum_color (dtvcc_buffer[index] & 0x3F);
+  window->fill_opacity = (dtvcc_buffer[index] & 0xC0) >> 6;
+  window->border_color =
+      gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 1] & 0x3F);
+  window->border_type =
+      ((dtvcc_buffer[index + 1] & 0xC0) >> 6) | ((dtvcc_buffer[index +
+              2] & 0x80) >> 5);
+  window->word_wrap = (dtvcc_buffer[index + 2] & 0x40) ? TRUE : FALSE;
+  window->justify_mode = dtvcc_buffer[index + 2] & 0x3;
+  window->scroll_direction = (dtvcc_buffer[index + 2] & 0xC) >> 2;
+  window->print_direction = (dtvcc_buffer[index + 2] & 0x30) >> 2;
+  window->display_effect = (dtvcc_buffer[index + 3] & 0x3);
+  window->effect_direction = (dtvcc_buffer[index + 3] & 0xC);
+  window->effect_speed = (dtvcc_buffer[index + 3] & 0xF0) >> 4;
+
+  GST_LOG ("Print direction = %d", window->print_direction);
+}
+
+static void
+gst_cea708dec_set_pen_style (Cea708Dec * decoder, guint8 pen_style_id)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+
+  window->pen_attributes.pen_size = PEN_SIZE_STANDARD;
+  window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
+  window->pen_attributes.offset = PEN_OFFSET_NORMAL;
+  window->pen_attributes.italics = FALSE;
+  window->pen_attributes.underline = FALSE;
+  window->pen_attributes.edge_type = EDGE_TYPE_NONE;
+  window->pen_color.fg_color = CEA708_COLOR_WHITE;
+  window->pen_color.fg_opacity = SOLID;
+  window->pen_color.bg_color = CEA708_COLOR_BLACK;
+  window->pen_color.bg_opacity = SOLID;
+  window->pen_color.edge_color = CEA708_COLOR_BLACK;
+
+  /* CEA-708 predefined pen style ids */
+  switch (pen_style_id) {
+    default:
+    case PEN_STYLE_DEFAULT:
+      window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
+      break;
+
+    case PEN_STYLE_MONO_SERIF:
+      window->pen_attributes.font_style = FONT_STYLE_MONO_SERIF;
+      break;
+
+    case PEN_STYLE_PROP_SERIF:
+      window->pen_attributes.font_style = FONT_STYLE_PROP_SERIF;
+      break;
+
+    case PEN_STYLE_MONO_SANS:
+      window->pen_attributes.font_style = FONT_STYLE_MONO_SANS;
+      break;
+
+    case PEN_STYLE_PROP_SANS:
+      window->pen_attributes.font_style = FONT_STYLE_PROP_SANS;
+      break;
+
+    case PEN_STYLE_MONO_SANS_TRANSPARENT:
+      window->pen_attributes.font_style = FONT_STYLE_MONO_SANS;
+      window->pen_color.bg_opacity = TRANSPARENT;
+      break;
+
+    case PEN_STYLE_PROP_SANS_TRANSPARENT:
+      window->pen_attributes.font_style = FONT_STYLE_PROP_SANS;
+      window->pen_color.bg_opacity = TRANSPARENT;
+      break;
+  }
+}
+
+static void
+gst_cea708dec_set_window_style (Cea708Dec * decoder, guint8 style_id)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+
+  /* set the 'normal' styles first, then deviate in special cases below... */
+  window->justify_mode = JUSTIFY_LEFT;
+  window->print_direction = PRINT_DIR_LEFT_TO_RIGHT;
+  window->scroll_direction = SCROLL_DIR_BOTTOM_TO_TOP;
+  window->word_wrap = FALSE;
+  window->effect_direction = EFFECT_DIR_LEFT_TO_RIGHT;
+  window->display_effect = DISPLAY_EFFECT_SNAP;
+  window->effect_speed = 0;
+  window->fill_color = CEA708_COLOR_BLACK;
+  window->fill_opacity = SOLID;
+
+  /* CEA-708 predefined window style ids */
+  switch (style_id) {
+    default:
+    case WIN_STYLE_NORMAL:
+      break;
+
+    case WIN_STYLE_TRANSPARENT:
+      window->fill_opacity = TRANSPARENT;
+      break;
+
+    case WIN_STYLE_NORMAL_CENTERED:
+      window->justify_mode = JUSTIFY_CENTER;
+      break;
+
+    case WIN_STYLE_NORMAL_WORD_WRAP:
+      window->word_wrap = TRUE;
+      break;
+
+    case WIN_STYLE_TRANSPARENT_WORD_WRAP:
+      window->fill_opacity = TRANSPARENT;
+      window->word_wrap = TRUE;
+      break;
+
+    case WIN_STYLE_TRANSPARENT_CENTERED:
+      window->fill_opacity = TRANSPARENT;
+      window->justify_mode = JUSTIFY_CENTER;
+      break;
+
+    case WIN_STYLE_ROTATED:
+      window->print_direction = PRINT_DIR_TOP_TO_BOTTOM;
+      window->scroll_direction = SCROLL_DIR_RIGHT_TO_LEFT;
+      break;
+  }
+}
+
+/* Define window - window size, window style, pen style, anchor position, etc */
+static void
+gst_cea708dec_define_window (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+  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;
+  guint v_anchor = 0;
+  guint h_anchor = 0;
+
+  GST_LOG ("current_window=%d", decoder->current_window);
+  GST_LOG ("dtvcc_buffer %02x %02x %02x %02x %02x %02x",
+      dtvcc_buffer[index + 0], dtvcc_buffer[index + 1],
+      dtvcc_buffer[index + 2], dtvcc_buffer[index + 3],
+      dtvcc_buffer[index + 4], dtvcc_buffer[index + 5]);
+
+  /* Initialize window structure */
+  if (NULL != window) {
+    if (window->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 */
+      window->deleted = FALSE;
+      window->pen_row = 0;
+      window->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 */
+
+    /* parameter byte 0 */
+    priority = dtvcc_buffer[index] & 0x7;
+    column_lock = (dtvcc_buffer[index] & 0x8) ? TRUE : FALSE;
+    row_lock = (dtvcc_buffer[index] & 0x10) ? TRUE : FALSE;
+    visible = (dtvcc_buffer[index] & 0x20) ? TRUE : FALSE;
+
+    /* parameter byte 1 */
+    relative_position = (dtvcc_buffer[index + 1] & 0x80) ? TRUE : FALSE;
+    anchor_vertical = dtvcc_buffer[index + 1] & 0x7F;
+
+    /* parameter byte 2 */
+    anchor_horizontal = dtvcc_buffer[index + 2];
+
+    /* parameter byte 3 */
+    anchor_point = (dtvcc_buffer[index + 3] & 0xF0) >> 4;
+    row_count = (dtvcc_buffer[index + 3] & 0xF) + 1;
+
+    /* parameter byte 4 */
+    column_count = (dtvcc_buffer[index + 4] & 0x3F) + 1;
+
+    /* parameter byte 5 */
+    style_id = (dtvcc_buffer[index + 5] & 0x38) >> 3;
+    pen_style_id = dtvcc_buffer[index + 5] & 0x7;
+
+    window->screen_vertical = anchor_vertical;
+    window->screen_horizontal = anchor_horizontal;
+
+    if (relative_position == FALSE) {
+      /* If position is in absolute coords, convert to percent */
+      if (decoder->width == 0 || decoder->height == 0) {
+        window->screen_vertical /= 100;
+        window->screen_horizontal /= 100;
+      } else if ((decoder->width * 9) % (decoder->height * 16) == 0) {
+        window->screen_vertical /= SCREEN_HEIGHT_16_9;
+        window->screen_horizontal /= SCREEN_WIDTH_16_9;
+      } else if ((decoder->width * 3) % (decoder->height * 4) == 0) {
+        window->screen_vertical /= SCREEN_HEIGHT_4_3;
+        window->screen_horizontal /= SCREEN_WIDTH_4_3;
+      } else {
+        window->screen_vertical /= 100;
+        window->screen_horizontal /= 100;
+      }
+      window->screen_vertical *= 100;
+      window->screen_horizontal *= 100;
+    }
+
+    window->priority = priority;
+    window->anchor_point = anchor_point;
+    window->relative_position = relative_position;
+    window->anchor_vertical = anchor_vertical;
+    window->anchor_horizontal = anchor_horizontal;
+    window->row_count = row_count;
+    window->column_count = column_count;
+    window->row_lock = row_lock;
+    window->column_lock = column_lock;
+    window->visible = visible;
+
+    /* Make sure row/col limits are not too large */
+    if (window->row_count > WINDOW_MAX_ROWS) {
+      GST_WARNING ("window row count %d is too large", window->row_count);
+      window->row_count = WINDOW_MAX_ROWS;
+    }
+
+    if (window->column_count > WINDOW_MAX_COLS) {
+      GST_WARNING ("window column count %d is too large", window->column_count);
+      window->column_count = WINDOW_MAX_COLS;
+    }
+
+    if (style_id != 0) {
+      window->style_id = style_id;
+    }
+
+    if (pen_style_id != 0) {
+      window->pen_style_id = pen_style_id;
+    }
+
+    gst_cea708dec_set_window_style (decoder, window->style_id);
+    gst_cea708dec_set_pen_style (decoder, window->pen_style_id);
+  }
+
+  GST_LOG ("priority=%d anchor=%d relative_pos=%d anchor_v=%d anchor_h=%d",
+      window->priority,
+      window->anchor_point,
+      window->relative_position,
+      window->anchor_vertical, window->anchor_horizontal);
+
+  GST_LOG ("row_count=%d col_count=%d row_lock=%d col_lock=%d visible=%d",
+      window->row_count,
+      window->column_count,
+      window->row_lock, window->column_lock, window->visible);
+
+  GST_LOG ("style_id=%d pen_style_id=%d screenH=%f screenV=%f v_offset=%d "
+      "h_offset=%d v_anchor=%d h_anchor=%d",
+      window->style_id,
+      window->pen_style_id,
+      window->screen_horizontal,
+      window->screen_vertical,
+      window->v_offset, window->h_offset, v_anchor, h_anchor);
+}
+
+static inline void
+pango_span_markup_init (cea708PangoSpanControl * span_control)
+{
+  memset (span_control, 0, sizeof (cea708PangoSpanControl));
+  span_control->size = PEN_SIZE_STANDARD;
+  span_control->fg_color = CEA708_COLOR_WHITE;
+  span_control->bg_color = CEA708_COLOR_INVALID;
+  span_control->size = PEN_SIZE_STANDARD;
+  span_control->font_style = FONT_STYLE_DEFAULT;
+}
+
+static inline void
+pango_span_markup_start (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index)
+{
+  GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
+      span_control->span_start_flag, span_control->span_end_flag,
+      span_control->span_txt_flag);
+  if (!span_control->span_start_flag) {
+    g_strlcat (line_buffer, CEA708_PANGO_SPAN_MARKUP_START, LINEBUFFER_SIZE);
+    *index += strlen (CEA708_PANGO_SPAN_MARKUP_START);
+    span_control->span_start_flag = TRUE;
+    span_control->span_end_flag = FALSE;
+  } else {
+    GST_WARNING ("warning span start  !!!");
+  }
+}
+
+static inline void
+pango_span_markup_txt (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index)
+{
+  GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
+      span_control->span_start_flag, span_control->span_end_flag,
+      span_control->span_txt_flag);
+  if (span_control->span_start_flag && !span_control->span_txt_flag) {
+    line_buffer[*index] = '>';
+    *index = *index + 1;
+    span_control->span_txt_flag = TRUE;
+  } else {
+    GST_WARNING ("warning span txt  !!!");
+  }
+}
+
+static inline void
+pango_span_markup_end (cea708PangoSpanControl * span_control,
+    gchar * line_buffer, guint16 * index)
+{
+  GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
+      span_control->span_start_flag, span_control->span_end_flag,
+      span_control->span_txt_flag);
+  if (span_control->span_start_flag && !span_control->span_end_flag) {
+    g_strlcat (line_buffer, CEA708_PANGO_SPAN_MARKUP_END, LINEBUFFER_SIZE);
+    *index = *index + strlen (CEA708_PANGO_SPAN_MARKUP_END);
+    span_control->span_start_flag = FALSE;
+    span_control->span_txt_flag = FALSE;
+    span_control->span_end_flag = TRUE;
+  } else {
+    GST_WARNING ("line_buffer=%s", line_buffer);
+    GST_WARNING ("warning span end  !!!");
+  }
+}
+
+/* FIXME: Convert to GString ! */
+static void
+gst_cea708dec_show_pango_window (Cea708Dec * decoder, guint window_id)
+{
+  cea708Window *window = decoder->cc_windows[window_id];
+  guint16 row, col;
+  gboolean display = FALSE;     /* = TRUE when text lines should be written */
+  gchar line_buffer[LINEBUFFER_SIZE];
+  gchar outchar_utf8[CC_UTF8_MAX_LENGTH + 1] = { 0 };
+  guint8 utf8_char_length;
+  guint16 i, j;
+  guint16 right_index;          /* within a single line of window text, the
+                                 * index of the rightmost non-blank character */
+  guint16 index;
+  guint len = 0;
+
+  cea708PangoSpanControl span_control;
+  const gchar *fg_color = NULL;
+  const gchar *bg_color = NULL;
+  const gchar *pen_size = NULL;
+  const gchar *font = NULL;
+
+  GST_DEBUG ("window #%02d (visible:%d)", window_id, window->visible);
+
+  window->updated = TRUE;
+
+  if (!window->visible) {
+    GST_DEBUG ("Window is not visible, skipping rendering");
+    return;
+  }
+
+  for (row = 0; row < window->row_count; row++) {
+    for (col = 0; col < window->column_count; col++) {
+      GST_LOG ("window->text[%d][%d].c '%c'", row, col,
+          window->text[row][col].c);
+      if (window->text[row][col].c != ' ') {
+        /* If there is a non-blank line, then display from there */
+        display = TRUE;
+      }
+    }
+  }
+
+  if (!display) {
+    GST_DEBUG ("No visible text, skiping rendering");
+    return;
+  }
+
+  for (row = 0; row < window->row_count; row++) {
+    for (col = 0; col < window->column_count; col++) {
+      if (window->text[row][col].c != ' ') {
+
+        memset (line_buffer, '\0', LINEBUFFER_SIZE);
+        pango_span_markup_init (&span_control);
+        /* Find the rightmost non-blank character on this line: */
+        for (i = right_index = WINDOW_MAX_COLS - 1; i >= col; i--) {
+          if (window->text[row][i].c != ' ') {
+            right_index = i;
+            break;
+          }
+        }
+
+        /* Copy all of the characters in this row, from the current position
+         * to the right edge */
+        for (i = 0, index = 0;
+            (i <= right_index) && (index < LINEBUFFER_SIZE - 15); i++) {
+          cea708char *current = &window->text[row][i];
+          GST_LOG ("Adding row=%d i=%d c=%c %d", row,
+              i, current->c, current->c);
+
+          do {
+            GST_MEMDUMP ("line_buffer", (guint8 *) line_buffer, index);
+            GST_INFO
+                ("text[%d][%d] '%c' underline:%d , italics:%d , font_style:%d , pen_size : %d",
+                row, i, current->c,
+                current->pen_attributes.underline,
+                current->pen_attributes.italics,
+                current->pen_attributes.font_style,
+                current->pen_attributes.pen_size);
+            GST_INFO ("text[%d][%d] '%c' pen_color fg:0x%02X bg:0x%02x", row, i,
+                current->c, current->pen_color.fg_color,
+                current->pen_color.bg_color);
+            GST_INFO
+                ("span_control: span_next_flag = %d, underline = %d, italics = %d, font_style = %d, size = %d, fg_color = 0x%02X, bg_color = 0x%02X",
+                span_control.span_next_flag, span_control.underline,
+                span_control.italics, span_control.font_style,
+                span_control.size, span_control.fg_color,
+                span_control.bg_color);
+
+            if ((current->pen_attributes.underline != span_control.underline)
+                || (current->pen_attributes.italics != span_control.italics)
+                || (current->pen_attributes.font_style !=
+                    span_control.font_style)
+                || (current->pen_attributes.pen_size != span_control.size)
+                || (current->pen_color.fg_color != span_control.fg_color)
+                || (current->pen_color.bg_color != span_control.bg_color)
+                ) {
+              GST_LOG ("Markup changed");
+
+              /* check end span to next span start */
+              if (!span_control.span_next_flag) {
+                pango_span_markup_end (&span_control, line_buffer, &index);
+                if (span_control.span_end_flag) {
+                  pango_span_markup_init (&span_control);
+                  span_control.span_next_flag = TRUE;
+                  GST_INFO ("continue check next span !!!");
+                  continue;
+                }
+              }
+
+              pango_span_markup_start (&span_control, line_buffer, &index);
+
+              /* Check for transitions to/from underline: */
+              if (current->pen_attributes.underline) {
+                g_strlcat (line_buffer,
+                    CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE,
+                    sizeof (line_buffer));
+                index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE);
+                span_control.underline = TRUE;
+              }
+
+              /* Check for transitions to/from italics: */
+              if (current->pen_attributes.italics) {
+                g_strlcat (line_buffer,
+                    CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC,
+                    sizeof (line_buffer));
+                index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC);
+                span_control.italics = TRUE;
+              }
+
+              /* FIXME : Something is totally wrong with the way fonts
+               * are being handled. Shouldn't the font description (if
+               * specified by the user) be written for everything ? */
+              if (!decoder->default_font_desc) {
+                font = font_names[current->pen_attributes.font_style];
+
+                if (font) {
+                  g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_FONT,
+                      sizeof (line_buffer));
+                  index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_FONT);
+                  line_buffer[index++] = 0x27;  /* ' */
+                  g_strlcat (line_buffer, font, sizeof (line_buffer));
+                  index += strlen (font);
+                  span_control.font_style = current->pen_attributes.font_style;
+
+                  /* Check for transitions to/from pen size  */
+                  pen_size = pen_size_names[current->pen_attributes.pen_size];
+
+                  line_buffer[index++] = ' ';
+                  g_strlcat (line_buffer, pen_size, sizeof (line_buffer));
+                  index += strlen (pen_size);
+                  line_buffer[index++] = 0x27;  /* ' */
+                  span_control.size = current->pen_attributes.pen_size;
+                }
+              }
+              /* Regardless of the above, we want to remember the latest changes */
+              span_control.font_style = current->pen_attributes.font_style;
+              span_control.size = current->pen_attributes.pen_size;
+              ;
+              /* Check for transitions to/from foreground color  */
+              fg_color =
+                  gst_cea708dec_get_color_name (current->pen_color.fg_color);
+              if (fg_color && current->pen_color.bg_opacity != TRANSPARENT) {
+                g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND,
+                    sizeof (line_buffer));
+                index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND);
+                line_buffer[index++] = 0x27;    /* ' */
+                g_strlcat (line_buffer, fg_color, sizeof (line_buffer));
+                index += strlen (fg_color);
+                line_buffer[index++] = 0x27;    /* ' */
+                span_control.fg_color = current->pen_color.fg_color;
+                GST_DEBUG ("span_control.fg_color updated to 0x%02x",
+                    span_control.fg_color);
+              } else
+                GST_DEBUG
+                    ("span_control.fg_color was NOT updated (still 0x%02x)",
+                    span_control.fg_color);
+
+              /* Check for transitions to/from background color  */
+              bg_color =
+                  gst_cea708dec_get_color_name (current->pen_color.bg_color);
+              if (bg_color && current->pen_color.bg_opacity != TRANSPARENT) {
+                g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND,
+                    sizeof (line_buffer));
+                index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND);
+                line_buffer[index++] = 0x27;    /* ' */
+                g_strlcat (line_buffer, bg_color, sizeof (line_buffer));
+                index += strlen (bg_color);
+                line_buffer[index++] = 0x27;    /* ' */
+                span_control.bg_color = current->pen_color.bg_color;
+                GST_DEBUG ("span_control.bg_color updated to 0x%02x",
+                    span_control.bg_color);
+              } else
+                GST_DEBUG
+                    ("span_control.bg_color was NOT updated (still 0x%02x)",
+                    span_control.bg_color);
+
+
+              /*span text start */
+              pango_span_markup_txt (&span_control, line_buffer, &index);
+              GST_INFO ("span_next_flag = %d", span_control.span_next_flag);
+            }
+            span_control.span_next_flag = FALSE;
+          } while (span_control.span_next_flag);
+
+
+          /* Finally write the character */
+
+          j = 0;
+
+
+          switch (current->c) {
+            case '&':
+              g_snprintf (&(line_buffer[index]),
+                  sizeof (line_buffer) - index - 1, "&amp;");
+              index += 5;
+              break;
+
+            case '<':
+              g_snprintf (&(line_buffer[index]),
+                  sizeof (line_buffer) - index - 1, "&lt;");
+              index += 4;
+              break;
+
+            case '>':
+              g_snprintf (&(line_buffer[index]),
+                  sizeof (line_buffer) - index - 1, "&gt;");
+              index += 4;
+              break;
+
+            case '\'':
+              g_snprintf (&(line_buffer[index]),
+                  sizeof (line_buffer) - index - 1, "&apos;");
+              index += 6;
+              break;
+
+            case '"':
+              g_snprintf (&(line_buffer[index]),
+                  sizeof (line_buffer) - index - 1, "&quot;");
+              index += 6;
+              break;
+
+            default:
+              /* FIXME : Use g_string_append_unichar() when switched to GString */
+              utf8_char_length = g_unichar_to_utf8 (current->c, outchar_utf8);
+              while (utf8_char_length > 0) {
+                line_buffer[index++] = outchar_utf8[j++];
+                utf8_char_length--;
+              }
+          }
+
+        }
+
+        /* pango markup span mode ends */
+        if (span_control.underline || span_control.italics
+            || (span_control.font_style != FONT_STYLE_DEFAULT)
+            || (span_control.size != PEN_SIZE_STANDARD)
+            || (span_control.fg_color != CEA708_COLOR_WHITE)
+            || (span_control.bg_color != CEA708_COLOR_INVALID)
+            ) {
+          pango_span_markup_end (&span_control, line_buffer, &index);
+          pango_span_markup_init (&span_control);
+        }
+
+        GST_LOG ("adding row[%d]: %s\nlength:%d", row, line_buffer, index);
+
+        if (row != window->row_count - 1) {
+          line_buffer[index++] = '\n';
+        }
+
+        len +=
+            gst_cea708dec_text_list_add (&decoder->text_list, index + 1, "%s",
+            line_buffer);
+        break;
+      }
+    }
+
+    if (col == window->column_count && row != window->row_count - 1) {
+      len +=
+          gst_cea708dec_text_list_add (&decoder->text_list, strlen ("\n") + 1,
+          "\n");
+    }
+  }
+
+  if (len == 0) {
+    GST_LOG ("window %d had no text", window_id);
+  } else {
+    /* send text to output pad */
+    gst_cea708dec_render_text (decoder, &decoder->text_list, len, window_id);
+  }
+}
+
+static void
+gst_cea708dec_clear_window_text (Cea708Dec * decoder, guint window_id)
+{
+  cea708Window *window = decoder->cc_windows[window_id];
+  guint row, col;
+
+  for (row = 0; row < WINDOW_MAX_ROWS; row++) {
+    for (col = 0; col < WINDOW_MAX_COLS; col++) {
+      window->text[row][col].c = ' ';
+      window->text[row][col].justify_mode = window->justify_mode;
+      window->text[row][col].pen_attributes = window->pen_attributes;
+      window->text[row][col].pen_color = window->pen_color;
+    }
+  }
+}
+
+static void
+gst_cea708dec_scroll_window_up (Cea708Dec * decoder, guint window_id)
+{
+  cea708Window *window = decoder->cc_windows[window_id];
+  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 */
+  GST_LOG_OBJECT (decoder, "called for window: %d", window_id);
+
+  /* start at row 1 to copy into row 0 (scrolling up) */
+  for (row = 1; row < WINDOW_MAX_ROWS; row++) {
+    for (col = 0; col < WINDOW_MAX_COLS; col++) {
+      window->text[row - 1][col] = window->text[row][col];
+    }
+  }
+
+  /* Clear the bottom row: */
+  row = WINDOW_MAX_ROWS - 1;
+  for (col = 0; col < WINDOW_MAX_COLS; col++) {
+    window->text[row][col].c = ' ';
+    window->text[row][col].justify_mode = window->justify_mode;
+    window->text[row][col].pen_attributes = window->pen_attributes;
+    window->text[row][col].pen_color = window->pen_color;
+  }
+}
+
+static void
+gst_cea708dec_init_window (Cea708Dec * decoder, guint window_id)
+{
+  cea708Window *window = decoder->cc_windows[window_id];
+
+  if (window_id >= MAX_708_WINDOWS) {
+    GST_ERROR ("window_id outside of range %d", window_id);
+    return;
+  }
+
+  window->priority = 0;
+  window->anchor_point = 0;
+  window->relative_position = 0;
+  window->anchor_vertical = 0;
+  window->anchor_horizontal = 0;
+  window->screen_vertical = 0;
+  window->screen_horizontal = 0;
+
+  window->row_count = WINDOW_MAX_ROWS;
+  window->column_count = WINDOW_MAX_COLS;
+  window->row_lock = 0;
+  window->column_lock = 0;
+  window->visible = FALSE;
+  window->style_id = 0;
+  window->pen_style_id = 0;
+  window->deleted = TRUE;
+  window->pen_color.fg_color = CEA708_COLOR_WHITE;
+  window->pen_color.fg_opacity = SOLID;
+  window->pen_color.bg_color = CEA708_COLOR_BLACK;
+  window->pen_color.bg_opacity = SOLID;
+  window->pen_color.edge_color = CEA708_COLOR_BLACK;
+
+  window->pen_attributes.pen_size = PEN_SIZE_STANDARD;
+  window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
+  window->pen_attributes.offset = PEN_OFFSET_NORMAL;
+  window->pen_attributes.italics = FALSE;
+  window->pen_attributes.text_tag = TAG_DIALOG;
+  window->pen_attributes.underline = FALSE;
+  window->pen_attributes.edge_type = EDGE_TYPE_NONE;
+
+  /* Init pen position */
+  window->pen_row = 0;
+  window->pen_col = 0;
+
+  /* Initialize text array to all spaces. When sending window text, only
+   * send if there are non-blank rows */
+  gst_cea708dec_clear_window_text (decoder, window_id);
+
+  /* window attributes */
+  window->justify_mode = JUSTIFY_LEFT;
+  window->print_direction = PRINT_DIR_LEFT_TO_RIGHT;
+  window->scroll_direction = SCROLL_DIR_BOTTOM_TO_TOP;
+  window->word_wrap = FALSE;
+  window->display_effect = DISPLAY_EFFECT_SNAP;
+  window->effect_direction = EFFECT_DIR_LEFT_TO_RIGHT;
+  window->effect_speed = 0;
+  window->fill_color = CEA708_COLOR_BLACK;
+  window->fill_opacity = TRANSPARENT;
+  window->border_type = BORDER_TYPE_NONE;
+  window->border_color = CEA708_COLOR_BLACK;
+
+  window->v_offset = 0;
+  window->h_offset = 0;
+  window->layout = NULL;
+  window->shadow_offset = 0;
+  window->outline_offset = 0;
+  window->image_width = 0;
+  window->image_height = 0;
+  window->text_image = NULL;
+
+}
+
+static void
+gst_cea708dec_set_pen_attributes (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+
+  /* format
+     tt3 tt2 tt1 tt0 o1 o0 s1 s0
+     i u et2 et1 et0 fs2 fs1 fs0 */
+  window->pen_attributes.pen_size = dtvcc_buffer[index] & 0x3;
+  window->pen_attributes.font_style = dtvcc_buffer[index + 1] & 0x3;
+  window->pen_attributes.text_tag = (dtvcc_buffer[index] & 0xF0) >> 4;
+  window->pen_attributes.offset = (dtvcc_buffer[index] & 0xC0) >> 2;
+  window->pen_attributes.italics =
+      ((dtvcc_buffer[index + 1] & 0x80) >> 7) ? TRUE : FALSE;
+  window->pen_attributes.underline =
+      ((dtvcc_buffer[index + 1] & 0x40) >> 6) ? TRUE : FALSE;
+  window->pen_attributes.edge_type = (dtvcc_buffer[index + 1] & 0x38) >> 3;
+
+  GST_LOG ("pen_size=%d font=%d text_tag=%d offset=%d",
+      window->pen_attributes.pen_size,
+      window->pen_attributes.font_style,
+      window->pen_attributes.text_tag, window->pen_attributes.offset);
+
+  GST_LOG ("italics=%d underline=%d edge_type=%d",
+      window->pen_attributes.italics,
+      window->pen_attributes.underline, window->pen_attributes.edge_type);
+}
+
+static void
+gst_cea708dec_for_each_window (Cea708Dec * decoder,
+    guint8 window_list, VisibilityControl visibility_control,
+    const gchar * log_message, void (*function) (Cea708Dec * decoder,
+        guint window_id))
+{
+  guint i;
+
+  GST_LOG ("window_list: %02x", window_list);
+
+  for (i = 0; i < MAX_708_WINDOWS; i++) {
+    if (WINDOW_IN_LIST_IS_ACTIVE (window_list)) {
+      GST_LOG ("%s[%d]:%d %s v_offset=%d h_offset=%d",
+          log_message, i, WINDOW_IN_LIST_IS_ACTIVE (window_list),
+          (decoder->cc_windows[i]->visible) ? "visible" : "hidden",
+          decoder->cc_windows[i]->v_offset, decoder->cc_windows[i]->h_offset);
+      switch (visibility_control) {
+        default:
+        case NO_CHANGE:
+          break;
+
+        case SWITCH_TO_HIDE:
+          decoder->cc_windows[i]->visible = FALSE;
+          break;
+
+        case SWITCH_TO_SHOW:
+          decoder->cc_windows[i]->visible = TRUE;
+          break;
+
+        case TOGGLE:
+          decoder->cc_windows[i]->visible = !decoder->cc_windows[i]->visible;
+          break;
+      }
+
+      if (NULL != function) {
+        function (decoder, i);
+      }
+    }
+
+    window_list >>= 1;
+  }
+}
+
+static void
+gst_cea708dec_process_command (Cea708Dec * decoder,
+    guint8 * dtvcc_buffer, int index)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+  guint8 c = dtvcc_buffer[index];
+  guint8 window_list = dtvcc_buffer[index + 1]; /* always the first arg (if any) */
+
+  /* Process command codes */
+  gst_cea708dec_print_command_name (decoder, c);
+  switch (c) {
+    case CC_COMMAND_ETX:       /* End of text */
+      window->visible = TRUE;
+      gst_cea708dec_show_pango_window (decoder, decoder->current_window);
+      return;
+
+    case CC_COMMAND_CW0:       /* Set current window */
+    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:
+      decoder->current_window = c & 0x03;
+      GST_LOG ("Current window=%d", decoder->current_window);
+      return;
+
+    case CC_COMMAND_CLW:       /* Clear windows */
+      decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
+
+      /* Clear window data */
+      gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
+          "clear_window", gst_cea708dec_clear_window_text);
+      return;
+
+    case CC_COMMAND_DSW:       /* Display windows */
+      decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
+
+      /* Show window */
+      gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
+          "display_window", gst_cea708dec_show_pango_window);
+      return;
+
+    case CC_COMMAND_HDW:       /* Hide windows */
+      decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
+
+      /* Hide window */
+      gst_cea708dec_for_each_window (decoder, window_list, SWITCH_TO_HIDE,
+          "hide_window", NULL);
+      return;
+
+    case CC_COMMAND_TGW:       /* Toggle windows */
+      decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
+
+      /* Toggle windows - hide displayed windows, display hidden windows */
+      gst_cea708dec_for_each_window (decoder, window_list, TOGGLE,
+          "toggle_window", gst_cea708dec_show_pango_window);
+      return;
+
+    case CC_COMMAND_DLW:       /* Delete windows */
+      decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
+
+      /* Delete window */
+      gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
+          "delete_window", gst_cea708dec_init_window);
+      return;
+
+    case CC_COMMAND_DLY:       /* Delay */
+      decoder->output_ignore = 1;       /* 1 byte parameter = delay in 1/10 sec */
+      /* TODO: - process this command. */
+      return;
+
+    case CC_COMMAND_DLC:       /* Delay cancel */
+      /* TODO: - process this command. */
+      return;
+
+      /* Reset */
+    case CC_COMMAND_RST:
+      /* Reset - cancel any delay, delete all windows */
+      window_list = 0xFF;       /* all windows... */
+
+      /* Delete window */
+      gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
+          "reset_window", gst_cea708dec_init_window);
+      return;
+
+    case CC_COMMAND_SPA:       /* Set pen attributes */
+      decoder->output_ignore = 2;       /* 2 byte parameter = pen attributes */
+      gst_cea708dec_set_pen_attributes (decoder, dtvcc_buffer, index + 1);
+      return;
+
+    case CC_COMMAND_SPC:       /* Set pen color */
+      decoder->output_ignore = 3;       /* 3 byte parameter = color & opacity */
+      gst_cea708dec_set_pen_color (decoder, dtvcc_buffer, index + 1);
+      return;
+
+    case CC_COMMAND_SPL:       /* Set pen location */
+      /* Set pen location - row, column address within the current window */
+      decoder->output_ignore = 2;       /* 2 byte parameter = row, col */
+      window->pen_row = dtvcc_buffer[index + 1] & 0xF;
+      window->pen_col = dtvcc_buffer[index + 2] & 0x3F;
+      GST_LOG ("Pen location: row=%d col=%d", window->pen_row, window->pen_col);
+      return;
+
+    case CC_COMMAND_SWA:       /* Set window attributes */
+      /* Set window attributes - color, word wrap, border, scroll effect, etc */
+      decoder->output_ignore = 4;       /* 4 byte parameter = window attributes */
+      gst_cea708dec_set_window_attributes (decoder, dtvcc_buffer, index + 1);
+      return;
+
+    case CC_COMMAND_DF0:       /* Define window */
+    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:
+    {
+      window_list = 0xFF;       /* all windows... */
+
+      /* set window - size, style, pen style, anchor position, etc. */
+      decoder->output_ignore = 6;       /* 6 byte parameter = window definition */
+      decoder->current_window = c & 0x7;
+      gst_cea708dec_define_window (decoder, dtvcc_buffer, index + 1);
+      return;
+    }
+  }
+}
+
+static void
+get_cea708dec_bufcat (gpointer data, gpointer whole_buf)
+{
+  gchar *buf = whole_buf;
+  strcat ((gchar *) buf, data);
+  g_free (data);
+}
+
+static gboolean
+gst_cea708dec_render_text (Cea708Dec * decoder, GSList ** text_list,
+    gint length, guint window_id)
+{
+  gchar *out_str = NULL;
+  PangoAlignment align_mode;
+  PangoFontDescription *desc;
+  gchar *font_desc;
+  cea708Window *window = decoder->cc_windows[window_id];
+
+  if (length > 0) {
+    out_str = g_malloc0 (length + 1);
+    memset (out_str, 0, length + 1);
+
+    g_slist_foreach (*text_list, get_cea708dec_bufcat, out_str);
+    GST_LOG ("rendering '%s'", out_str);
+    g_slist_free (*text_list);
+    window->layout = pango_layout_new (decoder->pango_context);
+    align_mode = gst_cea708dec_get_align_mode (window->justify_mode);
+    pango_layout_set_alignment (window->layout, (PangoAlignment) align_mode);
+    pango_layout_set_markup (window->layout, out_str, length);
+    if (!decoder->default_font_desc)
+      font_desc = g_strdup_printf ("%s %s", font_names[0], pen_size_names[1]);
+    else
+      font_desc = g_strdup (decoder->default_font_desc);
+    desc = pango_font_description_from_string (font_desc);
+    if (desc) {
+      GST_INFO ("font description set: %s", font_desc);
+      pango_layout_set_font_description (window->layout, desc);
+      gst_cea708dec_adjust_values_with_fontdesc (window, desc);
+      pango_font_description_free (desc);
+      gst_cea708dec_render_pangocairo (window);
+    } else {
+      GST_ERROR ("font description parse failed: %s", font_desc);
+    }
+    g_free (font_desc);
+    g_free (out_str);
+    /* data freed in slist loop!
+     *g_slist_free_full (*text_list, g_free); */
+    *text_list = NULL;
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static void
+gst_cea708dec_window_add_char (Cea708Dec * decoder, gunichar c)
+{
+  cea708Window *window = decoder->cc_windows[decoder->current_window];
+  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)                   /* NULL */
+    return;
+
+  if (c == 0x0E) {              /* HCR,moves the pen location to the beginning of the current line and deletes its contents */
+    for (pen_col = window->pen_col; pen_col >= 0; pen_col--) {
+      window->text[window->pen_row][pen_col].c = ' ';
+    }
+    window->pen_col = 0;
+    return;
+  }
+
+  if (c == 0x08) {              /* BS */
+    switch (window->print_direction) {
+      case PRINT_DIR_LEFT_TO_RIGHT:
+        if (window->pen_col) {
+          window->pen_col--;
+        }
+        break;
+
+      case PRINT_DIR_RIGHT_TO_LEFT:
+        window->pen_col++;
+        break;
+
+      case PRINT_DIR_TOP_TO_BOTTOM:
+        if (window->pen_row) {
+          window->pen_row--;
+        }
+        break;
+
+      case PRINT_DIR_BOTTOM_TO_TOP:
+        window->pen_row++;
+        break;
+    }
+    pen_row = window->pen_row;
+    pen_col = window->pen_col;
+    window->text[pen_row][pen_col].c = ' ';
+    return;
+  }
+
+  if (c == 0x0C) {              /* FF clears the screen and moves the pen location to (0,0) */
+    window->pen_row = 0;
+    window->pen_col = 0;
+    gst_cea708dec_clear_window_text (decoder, decoder->current_window);
+    return;
+  }
+
+  if (c == 0x0D) {
+    GST_DEBUG
+        ("carriage return, window->word_wrap=%d,window->scroll_direction=%d",
+        window->word_wrap, window->scroll_direction);
+    window->pen_col = 0;
+    window->pen_row++;
+  }
+
+  if (window->pen_col >= window->column_count) {
+    window->pen_col = 0;
+    window->pen_row++;
+  }
+  /* Wrap row position if too large */
+  if (window->pen_row >= window->row_count) {
+    if (window->scroll_direction == SCROLL_DIR_BOTTOM_TO_TOP) {
+      gst_cea708dec_scroll_window_up (decoder, decoder->current_window);
+    }
+    window->pen_row = window->row_count - 1;
+    GST_WARNING ("pen row exceed window row count,scroll up");
+  }
+
+  if ((c != '\r') && (c != '\n')) {
+    pen_row = window->pen_row;
+    pen_col = window->pen_col;
+
+    GST_LOG ("[text x=%d y=%d fgcolor=%d win=%d vis=%d] '%c' 0x%02X", pen_col,
+        pen_row, window->pen_color.fg_color, decoder->current_window,
+        window->visible, c, c);
+
+    /* Each cell in the window should get the current pen color and
+     * attributes as it is written */
+    window->text[pen_row][pen_col].c = c;
+    window->text[pen_row][pen_col].justify_mode = window->justify_mode;
+    window->text[pen_row][pen_col].pen_color = window->pen_color;
+    window->text[pen_row][pen_col].pen_attributes = window->pen_attributes;
+
+    switch (window->print_direction) {
+      case PRINT_DIR_LEFT_TO_RIGHT:
+        window->pen_col++;
+        break;
+
+      case PRINT_DIR_RIGHT_TO_LEFT:
+        if (window->pen_col) {
+          window->pen_col--;
+        }
+        break;
+
+      case PRINT_DIR_TOP_TO_BOTTOM:
+        window->pen_row++;
+        break;
+
+      case PRINT_DIR_BOTTOM_TO_TOP:
+        if (window->pen_row) {
+          window->pen_row--;
+        }
+        break;
+    }                           /* switch (print_direction) */
+  }
+}
+
+static void
+gst_cea708dec_process_c2 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
+{
+  guint8 c = dtvcc_buffer[index];
+  if (c >= 0x00 && c <= 0x07) {
+    decoder->output_ignore = 1;
+  } else if (c >= 0x08 && c <= 0x0F) {
+    decoder->output_ignore = 2;
+  } else if (c >= 0x10 && c <= 0x17) {
+    decoder->output_ignore = 3;
+  } else if (c >= 0x18 && c <= 0x1F) {
+    decoder->output_ignore = 4;
+  }
+}
+
+static void
+gst_cea708dec_process_g2 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
+{
+  guint8 c = dtvcc_buffer[index];
+  gst_cea708dec_window_add_char (decoder, g2_table[c - 0x20]);
+  decoder->output_ignore = 1;
+}
+
+static void
+gst_cea708dec_process_c3 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
+{
+  guint8 c = dtvcc_buffer[index];
+  int command_length = 0;
+  if (c >= 0x80 && c <= 0x87) {
+    decoder->output_ignore = 5;
+  } else if (c >= 0x88 && c <= 0x8F) {
+    decoder->output_ignore = 6;
+  } else if (c >= 0x90 && c <= 0x9F) {
+    command_length = dtvcc_buffer[index + 1] & 0x3F;
+    decoder->output_ignore = command_length + 2;
+  }
+}
+
+static void
+gst_cea708dec_process_g3 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
+{
+  gst_cea708dec_window_add_char (decoder, 0x5F);
+  decoder->output_ignore = 1;
+}
+
+void
+gst_cea708dec_set_video_width_height (Cea708Dec * decoder, gint width,
+    gint height)
+{
+  decoder->width = width;
+  decoder->height = height;
+}
diff --git a/ext/closedcaption/gstcea708decoder.h b/ext/closedcaption/gstcea708decoder.h
new file mode 100644 (file)
index 0000000..2d0609c
--- /dev/null
@@ -0,0 +1,485 @@
+/* GStreamer
+ * Copyright (C) 2013 CableLabs, Louisville, CO 80027
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *     @Author: Chengjun Wang <cjun.wang@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_CEA708_DEC_H__
+#define __GST_CEA708_DEC_H__
+
+#include <gst/gst.h>
+#include <pango/pangocairo.h>
+
+G_BEGIN_DECLS
+/* from ATSC A/53 Part 4
+ * DTVCC packets are 128 bytes MAX, length is only 6 bits, header is 2 bytes,
+ * the last byte is flag-fill, that leaves 125 possible bytes of data to be
+ * represented in 6 bits, hence the length encoding
+ */
+/* should never be more than 128 */
+#define DTVCC_LENGTH       128
+#define DTVCC_PKT_SIZE(sz_byte)  (((sz_byte) == 0) ? 127 : ((sz_byte)  * 2) -1)
+#define CCTYPE_VALID_MASK  0x04
+#define CCTYPE_TYPE_MASK   0x03
+#define NUM_608_CCTYPES 2
+/* CEA-708-B commands */
+/* EndOfText */
+#define CC_COMMAND_ETX 0x03
+/* SetCurrentWindow0 */
+#define CC_COMMAND_CW0 0x80
+#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
+/* ClearWindows */
+#define CC_COMMAND_CLW 0x88
+/* DisplayWindows */
+#define CC_COMMAND_DSW 0x89
+/* HideWindows */
+#define CC_COMMAND_HDW 0x8A
+/* ToggleWindows */
+#define CC_COMMAND_TGW 0x8B
+/* DeleteWindows */
+#define CC_COMMAND_DLW 0x8C
+/* Delay */
+#define CC_COMMAND_DLY 0x8D
+/* DelayCancel */
+#define CC_COMMAND_DLC 0x8E
+/* Reset */
+#define CC_COMMAND_RST 0x8F
+/* SetPenAttributes */
+#define CC_COMMAND_SPA 0x90
+/* SetPenColor */
+#define CC_COMMAND_SPC 0x91
+/* SetPenLocation */
+#define CC_COMMAND_SPL 0x92
+/* SetWindowAttributes */
+#define CC_COMMAND_SWA 0x97
+/* DefineWindow0 */
+#define CC_COMMAND_DF0 0x98
+#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
+/* music note unicode */
+#define CC_SPECIAL_CODE_MUSIC_NOTE    0x266a
+#define CC_UTF8_MAX_LENGTH   6
+#define CC_MAX_CODE_SET_SIZE   96
+/* Per CEA-708 spec there may be 8 CC windows */
+#define MAX_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.
+  */
+/* max row count */
+#define WINDOW_MAX_ROWS 15
+/* max column width */
+#define WINDOW_MAX_COLS 42
+/* The linebuffer contains text for 1 line pango text 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 1024
+/* 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_16_9    209
+#define SCREEN_HEIGHT_16_9   74
+#define SCREEN_WIDTH_4_3    159
+#define SCREEN_HEIGHT_4_3    74
+
+/* raw bytes of "define window" command */
+#define WIN_DEF_SIZE 6
+/* 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
+/* cea708 minimum color list */
+#define CEA708_COLOR_INVALID   0xFF
+#define CEA708_COLOR_BLACK     0x00
+#define CEA708_COLOR_WHITE     0x2A
+#define CEA708_COLOR_RED       0x20
+#define CEA708_COLOR_GREEN     0x08
+#define CEA708_COLOR_BLUE      0x02
+#define CEA708_COLOR_YELLOW    0x28
+#define CEA708_COLOR_MAGENTA   0x22
+#define CEA708_COLOR_CYAN      0x0A
+#define CEA708_PANGO_SPAN_MARKUP_START                      "<span"
+#define CEA708_PANGO_SPAN_MARKUP_END                        "</span>"
+#define CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE       " underline='single'"
+#define CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC           " style='italic'"
+#define CEA708_PANGO_SPAN_ATTRIBUTES_FONT                   " font_desc="
+#define CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND             " foreground="
+#define CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND             " background="
+#define MINIMUM_OUTLINE_OFFSET 1.0
+#define WINDOW_IN_LIST_IS_ACTIVE(list) (list & 0x1)
+typedef struct _Cea708Dec Cea708Dec;
+
+typedef enum
+{
+  COLOR_TYPE_BLACK = 0,
+  COLOR_TYPE_WHITE,
+  COLOR_TYPE_RED,
+  COLOR_TYPE_GREEN,
+  COLOR_TYPE_BLUE,
+  COLOR_TYPE_YELLOW,
+  COLOR_TYPE_MAGENTA,
+  COLOR_TYPE_CYAN,
+  COLOR_TYPE_RESEVER
+} Cea708ColorType;
+
+typedef enum
+{
+  NO_CHANGE = 0,
+  SWITCH_TO_HIDE,
+  SWITCH_TO_SHOW,
+  TOGGLE
+} VisibilityControl;
+
+typedef enum
+{
+  SOLID = 0,
+  FLASH,
+  TRANSLUCENT,
+  TRANSPARENT
+} Opacity;
+
+typedef enum
+{
+  WIN_STYLE_NORMAL = 1,
+  WIN_STYLE_TRANSPARENT,
+  WIN_STYLE_NORMAL_CENTERED,
+  WIN_STYLE_NORMAL_WORD_WRAP,
+  WIN_STYLE_TRANSPARENT_WORD_WRAP,
+  WIN_STYLE_TRANSPARENT_CENTERED,
+  WIN_STYLE_ROTATED
+} WindowStyle;
+
+typedef enum
+{
+  PEN_STYLE_DEFAULT = 1,
+  PEN_STYLE_MONO_SERIF,
+  PEN_STYLE_PROP_SERIF,
+  PEN_STYLE_MONO_SANS,
+  PEN_STYLE_PROP_SANS,
+  PEN_STYLE_MONO_SANS_TRANSPARENT,
+  PEN_STYLE_PROP_SANS_TRANSPARENT
+} PenStyle;
+
+typedef enum
+{
+  ANCHOR_PT_TOP_LEFT = 0,
+  ANCHOR_PT_TOP_CENTER,
+  ANCHOR_PT_TOP_RIGHT,
+  ANCHOR_PT_MIDDLE_LEFT,
+  ANCHOR_PT_CENTER,
+  ANCHOR_PT_MIDDLE_RIGHT,
+  ANCHOR_PT_BOTTOM_LEFT,
+  ANCHOR_PT_BOTTOM_CENTER,
+  ANCHOR_PT_BOTTOM_RIGHT,
+} AnchorPoint;
+
+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
+} TagType;
+
+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
+} DisplayEffect;
+
+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
+} PenSize;
+
+typedef enum
+{
+  PEN_OFFSET_SUBSCRIPT = 0,
+  PEN_OFFSET_NORMAL,
+  PEN_OFFSET_SUPERSCRIPT,
+  PEN_OFFSET_INVALID
+} PenOffset;
+
+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
+} EdgeType;
+
+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
+} FontStyle;
+
+typedef struct
+{
+  guint8 fg_color;
+  guint8 fg_opacity;
+  guint8 bg_color;
+  guint8 bg_opacity;
+  guint8 edge_color;
+} cea708PenColor;
+
+typedef struct
+{
+  gboolean span_start_flag;
+  gboolean span_end_flag;
+  gboolean span_txt_flag;
+
+  gboolean span_next_flag;
+
+  gboolean underline;
+  gboolean italics;
+
+  guint8 size;
+  guint8 fg_color;
+  guint8 bg_color;
+  FontStyle font_style;
+} cea708PangoSpanControl;
+
+typedef struct
+{
+  PenSize pen_size;
+  FontStyle font_style;
+  TagType text_tag;
+  PenOffset offset;
+  gboolean italics;
+  gboolean underline;
+  EdgeType 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 justify_mode;
+  gunichar 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
+{
+  /* The current attributes which will be used for the next text string */
+  cea708PenColor pen_color;
+  cea708PenAttributes pen_attributes;
+
+  /* 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
+   */
+  gboolean deleted;
+
+  /* Text position */
+  guint16 pen_row;
+  guint16 pen_col;
+  /* window display priority */
+  guint8 priority;
+  /* window position on screen 0-8 */
+  guint8 anchor_point;
+  /* 1 = anchor vertical/horizontal coordinates, 0 = physical screen coordinate, aka. rp */
+  guint8 relative_position;
+  /* vertical position of windows anchor point, 0-74 or if rp=1 then 0-99 */
+  guint8 anchor_vertical;
+  /* horz position of window anchor point, 0-209(16:9) 0-159(4:3) or if rp=1 then 0-99 */
+  guint8 anchor_horizontal;
+  /* vert position of upper left corner of window */
+  gfloat screen_vertical;
+  /* horz position of upper left corner of window */
+  gfloat screen_horizontal;
+  /* virtual rows of text - 1, (ex. rc=2 means there are 3 rows) */
+  guint8 row_count;
+  /* virtual columns of text, 0-41(16:9) 0-31(4:3) - 1 */
+  guint8 column_count;
+  /* 1 = fixes #rows of caption text, 0 = more rows may be added */
+  guint8 row_lock;
+  /* 1 = fixes #columns of caption text, 0 = more columns may be added */
+  guint8 column_lock;
+  /* TRUE = window is visible, FALSE = window not visible */
+  gboolean visible;
+  /* 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 style_id;
+  /* 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
+   */
+  guint8 pen_style_id;
+  /* timestamp when this window became visible */
+  guint64 start_time;
+
+  /* window attributes */
+  guint8 justify_mode;
+  guint8 print_direction;
+  guint8 scroll_direction;
+  gboolean word_wrap;
+  guint8 display_effect;
+  guint8 effect_direction;
+  guint8 effect_speed;
+  guint8 fill_color;
+  guint8 fill_opacity;
+  guint8 border_type;
+  guint8 border_color;
+
+  /* Character position offsets for the upper left corner of the window */
+  guint v_offset;
+  guint h_offset;
+
+  /* The char array that text is written into, using the current pen position */
+  cea708char text[WINDOW_MAX_ROWS][WINDOW_MAX_COLS];
+
+  PangoLayout *layout;
+  gdouble shadow_offset;
+  gdouble outline_offset;
+  guchar *text_image;
+  gint image_width;
+  gint image_height;
+  gboolean updated;
+} cea708Window;
+
+struct _Cea708Dec
+{
+  /* output data storage */
+  GSList *text_list;
+
+  /* simulation of 708 CC windows */
+  cea708Window *cc_windows[MAX_708_WINDOWS];
+  guint8 current_window;
+  gchar *default_font_desc;
+  PangoContext *pango_context;
+
+  /* a counter used to ignore bytes in CC text stream following commands */
+  gint8 output_ignore;
+  /* most recent timestamp from userdata */
+  guint64 current_time;
+
+  /* desired_service selects the service that will be decoded. If
+   * desired_service = -1 (default) no decoding based on service number will
+   * occur. Service #0 is reserved, and the valid range of service numbers
+   * is 1-7. with 1 being primary caption service and 2 being the secondary
+   * language service. If service_number is 7, then the extended_service_number is added and used instead of the service_number */
+  gint8 desired_service;
+
+  gboolean use_ARGB;
+  gint width;
+  gint height;
+};
+
+Cea708Dec *gst_cea708dec_create (PangoContext * pango_context);
+void
+gst_cea708dec_set_service_number (Cea708Dec * decoder, gint8 desired_service);
+gboolean
+gst_cea708dec_process_dtvcc_packet (Cea708Dec * decoder, guint8 * dtvcc_buffer, gsize dtvcc_size);
+void
+gst_cea708dec_set_video_width_height (Cea708Dec * decoder, gint width, gint height);
+void gst_cea708_decoder_init_debug(void);
+
+  G_END_DECLS
+#endif /* __GST_CEA708_DEC_H__ */
diff --git a/ext/closedcaption/gstceaccoverlay.c b/ext/closedcaption/gstceaccoverlay.c
new file mode 100644 (file)
index 0000000..5fe051e
--- /dev/null
@@ -0,0 +1,1957 @@
+/* GStreamer
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *     @Author: Chengjun Wang <cjun.wang@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gst/video/video.h>
+#include <gst/video/gstvideometa.h>
+#include <gst/base/gstbytereader.h>
+
+#include "gstceaccoverlay.h"
+#include <string.h>
+
+
+#define GST_CAT_DEFAULT gst_cea_cc_overlay_debug
+GST_DEBUG_CATEGORY (gst_cea_cc_overlay_debug);
+
+
+#define DEFAULT_PROP_FONT_DESC ""
+#define DEFAULT_PROP_SILENT    FALSE
+#define DEFAULT_PROP_SERVICE_NUMBER 1
+#define DEFAULT_PROP_WINDOW_H_POS GST_CEA_CC_OVERLAY_WIN_H_CENTER
+
+enum
+{
+  PROP_0,
+  PROP_FONT_DESC,
+  PROP_SILENT,
+  PROP_SERVICE_NUMBER,
+  PROP_WINDOW_H_POS,
+  PROP_LAST
+};
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+# define CAIRO_ARGB_A 3
+# define CAIRO_ARGB_R 2
+# define CAIRO_ARGB_G 1
+# define CAIRO_ARGB_B 0
+#else
+# define CAIRO_ARGB_A 0
+# define CAIRO_ARGB_R 1
+# define CAIRO_ARGB_G 2
+# define CAIRO_ARGB_B 3
+#endif
+
+#define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
+  b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
+  g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
+  r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
+} G_STMT_END
+
+
+#define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
+
+#define CC_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
+
+#define CC_OVERLAY_ALL_CAPS CC_OVERLAY_CAPS ";" \
+    GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
+
+static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (CC_OVERLAY_CAPS);
+
+static GstStaticPadTemplate src_template_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (CC_OVERLAY_ALL_CAPS)
+    );
+
+static GstStaticPadTemplate video_sink_template_factory =
+GST_STATIC_PAD_TEMPLATE ("video_sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (CC_OVERLAY_ALL_CAPS)
+    );
+
+static GstStaticPadTemplate cc_sink_template_factory =
+GST_STATIC_PAD_TEMPLATE ("cc_sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS
+    ("closedcaption/x-cea-708, format={ (string) cdp, (string) cc_data }")
+    );
+
+
+#define GST_TYPE_CC_OVERLAY_WIN_H_POS (gst_cea_cc_overlay_h_pos_get_type())
+static GType
+gst_cea_cc_overlay_h_pos_get_type (void)
+{
+  static GType cc_overlay_win_h_pos_type = 0;
+  static const GEnumValue cc_overlay_win_h_pos[] = {
+    {GST_CEA_CC_OVERLAY_WIN_H_LEFT, "left", "left"},
+    {GST_CEA_CC_OVERLAY_WIN_H_CENTER, "center", "center"},
+    {GST_CEA_CC_OVERLAY_WIN_H_RIGHT, "right", "right"},
+    {GST_CEA_CC_OVERLAY_WIN_H_AUTO, "auto", "auto"},
+    {0, NULL, NULL},
+  };
+
+  if (!cc_overlay_win_h_pos_type) {
+    cc_overlay_win_h_pos_type =
+        g_enum_register_static ("GstCeaCcOverlayWinHPos", cc_overlay_win_h_pos);
+  }
+  return cc_overlay_win_h_pos_type;
+}
+
+
+#define GST_CEA_CC_OVERLAY_GET_LOCK(ov) (&GST_CEA_CC_OVERLAY (ov)->lock)
+#define GST_CEA_CC_OVERLAY_GET_COND(ov) (&GST_CEA_CC_OVERLAY (ov)->cond)
+#define GST_CEA_CC_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
+#define GST_CEA_CC_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
+#define GST_CEA_CC_OVERLAY_WAIT(ov)     (g_cond_wait (GST_CEA_CC_OVERLAY_GET_COND (ov), GST_CEA_CC_OVERLAY_GET_LOCK (ov)))
+#define GST_CEA_CC_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_CEA_CC_OVERLAY_GET_COND (ov)))
+#define GST_CEA_CC_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_CEA_CC_OVERLAY_GET_COND (ov)))
+
+static GstElementClass *parent_class = NULL;
+static void gst_base_cea_cc_overlay_base_init (gpointer g_class);
+static void gst_base_cea_cc_overlay_class_init (GstCeaCcOverlayClass * klass);
+static void gst_base_cea_cc_overlay_init (GstCeaCcOverlay * overlay,
+    GstCeaCcOverlayClass * klass);
+static GstStateChangeReturn gst_cea_cc_overlay_change_state (GstElement *
+    element, GstStateChange transition);
+static GstCaps *gst_cea_cc_overlay_get_videosink_caps (GstPad * pad,
+    GstCeaCcOverlay * overlay, GstCaps * filter);
+static GstCaps *gst_cea_cc_overlay_get_src_caps (GstPad * pad,
+    GstCeaCcOverlay * overlay, GstCaps * filter);
+static gboolean gst_cea_cc_overlay_setcaps (GstCeaCcOverlay * overlay,
+    GstCaps * caps);
+static gboolean gst_cea_cc_overlay_src_event (GstPad * pad, GstObject * parent,
+    GstEvent * event);
+static gboolean gst_cea_cc_overlay_src_query (GstPad * pad, GstObject * parent,
+    GstQuery * query);
+
+static gboolean gst_cea_cc_overlay_video_event (GstPad * pad,
+    GstObject * parent, GstEvent * event);
+static gboolean gst_cea_cc_overlay_video_query (GstPad * pad,
+    GstObject * parent, GstQuery * query);
+static GstFlowReturn gst_cea_cc_overlay_video_chain (GstPad * pad,
+    GstObject * parent, GstBuffer * buffer);
+
+static gboolean gst_cea_cc_overlay_cc_event (GstPad * pad,
+    GstObject * parent, GstEvent * event);
+static GstFlowReturn gst_cea_cc_overlay_cc_chain (GstPad * pad,
+    GstObject * parent, GstBuffer * buffer);
+static GstPadLinkReturn gst_cea_cc_overlay_cc_pad_link (GstPad * pad,
+    GstObject * parent, GstPad * peer);
+static void gst_cea_cc_overlay_cc_pad_unlink (GstPad * pad, GstObject * parent);
+static void gst_cea_cc_overlay_pop_text (GstCeaCcOverlay * overlay);
+static void gst_cea_cc_overlay_finalize (GObject * object);
+static void gst_cea_cc_overlay_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_cea_cc_overlay_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static gboolean gst_cea_cc_overlay_can_handle_caps (GstCaps * incaps);
+
+GType
+gst_cea_cc_overlay_get_type (void)
+{
+  static GType type = 0;
+
+  if (g_once_init_enter ((gsize *) & type)) {
+    static const GTypeInfo info = {
+      sizeof (GstCeaCcOverlayClass),
+      (GBaseInitFunc) gst_base_cea_cc_overlay_base_init,
+      NULL,
+      (GClassInitFunc) gst_base_cea_cc_overlay_class_init,
+      NULL,
+      NULL,
+      sizeof (GstCeaCcOverlay),
+      0,
+      (GInstanceInitFunc) gst_base_cea_cc_overlay_init,
+    };
+
+    g_once_init_leave ((gsize *) & type,
+        g_type_register_static (GST_TYPE_ELEMENT, "GstCeaCcOverlay", &info, 0));
+  }
+
+  return type;
+}
+
+static void
+gst_base_cea_cc_overlay_base_init (gpointer g_class)
+{
+  GstCeaCcOverlayClass *klass = GST_CEA_CC_OVERLAY_CLASS (g_class);
+  PangoFontMap *fontmap;
+
+  /* Only lock for the subclasses here, the base class
+   * doesn't have this mutex yet and it's not necessary
+   * here */
+  /* FIXME : Not needed anymore since pango 1.32.6 ! */
+  if (klass->pango_lock)
+    g_mutex_lock (klass->pango_lock);
+  fontmap = pango_cairo_font_map_get_default ();
+  klass->pango_context =
+      pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
+  if (klass->pango_lock)
+    g_mutex_unlock (klass->pango_lock);
+
+}
+
+static void
+gst_base_cea_cc_overlay_class_init (GstCeaCcOverlayClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = gst_cea_cc_overlay_finalize;
+  gobject_class->set_property = gst_cea_cc_overlay_set_property;
+  gobject_class->get_property = gst_cea_cc_overlay_get_property;
+
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&src_template_factory));
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&video_sink_template_factory));
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&cc_sink_template_factory));
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_change_state);
+
+  klass->pango_lock = g_slice_new (GMutex);
+  g_mutex_init (klass->pango_lock);
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SERVICE_NUMBER,
+      g_param_spec_int ("service-number", "service-number",
+          "Service number. Service 1 is designated as the Primary Caption Service,"
+          " Service 2 is the Secondary Language Service.",
+          -1, 63, DEFAULT_PROP_SERVICE_NUMBER,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_H_POS,
+      g_param_spec_enum ("window-h-pos", "window-h-pos",
+          "Window's Horizontal position", GST_TYPE_CC_OVERLAY_WIN_H_POS,
+          DEFAULT_PROP_WINDOW_H_POS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
+      g_param_spec_string ("font-desc", "font description",
+          "Pango font description of font to be used for rendering.\n"
+          "See documentation of pango_font_description_from_string for syntax.\n"
+          "this will override closed caption stream specified font style/pen size.",
+          DEFAULT_PROP_FONT_DESC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstCeaCcOverlay:silent:
+   *
+   * If set, no text is rendered. Useful to switch off text rendering
+   * temporarily without removing the textoverlay element from the pipeline.
+   */
+  /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
+  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
+      g_param_spec_boolean ("silent", "silent",
+          "Whether to render the text string",
+          DEFAULT_PROP_SILENT,
+          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "Closed Caption overlay", "Mixer/Video/Overlay/Subtitle",
+      "Decode cea608/cea708 data and overlay on proper position of a video buffer",
+      "Chengjun Wang <cjun.wang@samsung.com>");
+  gst_cea708_decoder_init_debug ();
+
+}
+
+static void
+gst_cea_cc_overlay_finalize (GObject * object)
+{
+  GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
+
+  if (overlay->current_composition) {
+    gst_video_overlay_composition_unref (overlay->current_composition);
+    overlay->current_composition = NULL;
+  }
+  if (overlay->next_composition) {
+    gst_video_overlay_composition_unref (overlay->next_composition);
+    overlay->next_composition = NULL;
+  }
+
+  g_mutex_clear (&overlay->lock);
+  g_cond_clear (&overlay->cond);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_base_cea_cc_overlay_init (GstCeaCcOverlay * overlay,
+    GstCeaCcOverlayClass * klass)
+{
+  GstPadTemplate *template;
+  overlay->decoder = gst_cea708dec_create (GST_CEA_CC_OVERLAY_GET_CLASS
+      (overlay)->pango_context);
+
+  /* video sink */
+  template = gst_static_pad_template_get (&video_sink_template_factory);
+  overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
+  gst_object_unref (template);
+  gst_pad_set_event_function (overlay->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_event));
+  gst_pad_set_chain_function (overlay->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_chain));
+  gst_pad_set_query_function (overlay->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_query));
+  GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
+  gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
+
+  template =
+      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "cc_sink");
+  if (template) {
+    /* text sink */
+    overlay->cc_sinkpad = gst_pad_new_from_template (template, "cc_sink");
+
+    gst_pad_set_event_function (overlay->cc_sinkpad,
+        GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_event));
+    gst_pad_set_chain_function (overlay->cc_sinkpad,
+        GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_chain));
+    gst_pad_set_link_function (overlay->cc_sinkpad,
+        GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_link));
+    gst_pad_set_unlink_function (overlay->cc_sinkpad,
+        GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_unlink));
+    gst_element_add_pad (GST_ELEMENT (overlay), overlay->cc_sinkpad);
+  }
+
+  /* (video) source */
+  template = gst_static_pad_template_get (&src_template_factory);
+  overlay->srcpad = gst_pad_new_from_template (template, "src");
+  gst_object_unref (template);
+  gst_pad_set_event_function (overlay->srcpad,
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_event));
+  gst_pad_set_query_function (overlay->srcpad,
+      GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_query));
+  gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
+
+
+  overlay->silent = DEFAULT_PROP_SILENT;
+  overlay->need_update = TRUE;
+  overlay->current_composition = NULL;
+  overlay->next_composition = NULL;
+  overlay->cc_pad_linked = FALSE;
+  overlay->current_comp_start_time = GST_CLOCK_TIME_NONE;
+  overlay->next_comp_start_time = GST_CLOCK_TIME_NONE;
+  overlay->cea608_index[0] = 0;
+  overlay->cea608_index[1] = 0;
+  overlay->cea708_index = 0;
+  overlay->default_window_h_pos = DEFAULT_PROP_WINDOW_H_POS;
+
+  g_mutex_init (&overlay->lock);
+  g_cond_init (&overlay->cond);
+  gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
+}
+
+/* only negotiate/query video overlay composition support for now */
+static gboolean
+gst_cea_cc_overlay_negotiate (GstCeaCcOverlay * overlay, GstCaps * caps)
+{
+  GstQuery *query;
+  gboolean attach = FALSE;
+  gboolean caps_has_meta = TRUE;
+  gboolean ret;
+  GstCapsFeatures *f;
+  GstCaps *original_caps;
+  gboolean original_has_meta = FALSE;
+  gboolean allocation_ret = TRUE;
+
+  GST_DEBUG_OBJECT (overlay, "performing negotiation");
+
+  if (!caps)
+    caps = gst_pad_get_current_caps (overlay->video_sinkpad);
+  else
+    gst_caps_ref (caps);
+
+  if (!caps || gst_caps_is_empty (caps))
+    goto no_format;
+
+  original_caps = caps;
+
+  /* Try to use the overlay meta if possible */
+  f = gst_caps_get_features (caps, 0);
+
+  /* if the caps doesn't have the overlay meta, we query if downstream
+   * accepts it before trying the version without the meta
+   * If upstream already is using the meta then we can only use it */
+  if (!f
+      || !gst_caps_features_contains (f,
+          GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) {
+    GstCaps *overlay_caps;
+
+    /* In this case we added the meta, but we can work without it
+     * so preserve the original caps so we can use it as a fallback */
+    overlay_caps = gst_caps_copy (caps);
+
+    f = gst_caps_get_features (overlay_caps, 0);
+    gst_caps_features_add (f,
+        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
+
+    ret = gst_pad_peer_query_accept_caps (overlay->srcpad, overlay_caps);
+    GST_DEBUG_OBJECT (overlay, "Downstream accepts the overlay meta: %d", ret);
+    if (ret) {
+      gst_caps_unref (caps);
+      caps = overlay_caps;
+
+    } else {
+      /* fallback to the original */
+      gst_caps_unref (overlay_caps);
+      caps_has_meta = FALSE;
+    }
+  } else {
+    original_has_meta = TRUE;
+  }
+  GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
+  ret = gst_pad_set_caps (overlay->srcpad, caps);
+
+  if (ret) {
+    /* find supported meta */
+    query = gst_query_new_allocation (caps, FALSE);
+
+    if (!gst_pad_peer_query (overlay->srcpad, query)) {
+      /* no problem, we use the query defaults */
+      GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
+      allocation_ret = FALSE;
+    }
+
+    if (caps_has_meta && gst_query_find_allocation_meta (query,
+            GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
+      attach = TRUE;
+    gst_query_unref (query);
+  }
+
+  overlay->attach_compo_to_buffer = attach;
+
+  if (!allocation_ret && overlay->video_flushing) {
+    ret = FALSE;
+  } else if (original_caps && !original_has_meta && !attach) {
+    if (caps_has_meta) {
+      /* Some elements (fakesink) claim to accept the meta on caps but won't
+         put it in the allocation query result, this leads below
+         check to fail. Prevent this by removing the meta from caps */
+      gst_caps_unref (caps);
+      caps = gst_caps_ref (original_caps);
+      ret = gst_pad_set_caps (overlay->srcpad, caps);
+      if (ret && !gst_cea_cc_overlay_can_handle_caps (caps))
+        ret = FALSE;
+    }
+  }
+
+  if (!ret) {
+    GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
+    gst_pad_mark_reconfigure (overlay->srcpad);
+  }
+  gst_caps_unref (caps);
+  GST_DEBUG_OBJECT (overlay, "ret=%d", ret);
+
+  return ret;
+
+no_format:
+  {
+    if (caps)
+      gst_caps_unref (caps);
+    return FALSE;
+  }
+}
+
+static gboolean
+gst_cea_cc_overlay_can_handle_caps (GstCaps * incaps)
+{
+  gboolean ret;
+  GstCaps *caps;
+  static GstStaticCaps static_caps = GST_STATIC_CAPS (CC_OVERLAY_CAPS);
+
+  caps = gst_static_caps_get (&static_caps);
+  ret = gst_caps_is_subset (incaps, caps);
+  gst_caps_unref (caps);
+
+  return ret;
+}
+
+static gboolean
+gst_cea_cc_overlay_setcaps (GstCeaCcOverlay * overlay, GstCaps * caps)
+{
+  GstVideoInfo info;
+  gboolean ret = FALSE;
+
+  if (!gst_video_info_from_caps (&info, caps))
+    goto invalid_caps;
+
+  overlay->info = info;
+  overlay->format = GST_VIDEO_INFO_FORMAT (&info);
+  overlay->width = GST_VIDEO_INFO_WIDTH (&info);
+  overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
+  gst_cea708dec_set_video_width_height (overlay->decoder, overlay->width,
+      overlay->height);
+  ret = gst_cea_cc_overlay_negotiate (overlay, caps);
+
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+  g_mutex_lock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock);
+  if (!overlay->attach_compo_to_buffer &&
+      !gst_cea_cc_overlay_can_handle_caps (caps)) {
+    GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
+    ret = FALSE;
+  }
+
+  g_mutex_unlock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock);
+  GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+
+  return ret;
+
+  /* ERRORS */
+invalid_caps:
+  {
+    GST_DEBUG_OBJECT (overlay, "could not parse caps");
+    return FALSE;
+  }
+}
+
+static void
+gst_cea_cc_overlay_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
+  Cea708Dec *decoder = overlay->decoder;
+
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+  switch (prop_id) {
+    case PROP_SERVICE_NUMBER:
+    {
+      int desired_service = g_value_get_int (value);
+      gst_cea708dec_set_service_number (decoder, desired_service);
+      break;
+    }
+    case PROP_FONT_DESC:
+    {
+      PangoFontDescription *desc = NULL;
+      const gchar *fontdesc_str;
+      fontdesc_str = g_value_get_string (value);
+
+      GST_LOG_OBJECT (overlay, "Got font description '%s'", fontdesc_str);
+      if (fontdesc_str)
+        desc = pango_font_description_from_string (fontdesc_str);
+      /* Only set if NULL or valid description */
+      if (desc || !fontdesc_str) {
+        if (desc) {
+          GST_INFO_OBJECT (overlay, "Setting font description: '%s'",
+              fontdesc_str);
+          pango_font_description_free (desc);
+        } else
+          GST_INFO_OBJECT (overlay, "Resetting default font description");
+        g_free (decoder->default_font_desc);
+        decoder->default_font_desc = g_strdup (fontdesc_str);
+      }
+      break;
+    }
+    case PROP_SILENT:
+      overlay->silent = g_value_get_boolean (value);
+      break;
+    case PROP_WINDOW_H_POS:
+      overlay->default_window_h_pos = g_value_get_enum (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  overlay->need_update = TRUE;
+  GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+}
+
+static void
+gst_cea_cc_overlay_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object);
+  Cea708Dec *decoder = overlay->decoder;
+
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+  switch (prop_id) {
+    case PROP_SERVICE_NUMBER:
+      g_value_set_int (value, decoder->desired_service);
+      break;
+    case PROP_SILENT:
+      g_value_set_boolean (value, overlay->silent);
+      break;
+    case PROP_FONT_DESC:
+      g_value_set_string (value, decoder->default_font_desc);
+      break;
+    case PROP_WINDOW_H_POS:
+      g_value_set_enum (value, overlay->default_window_h_pos);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+}
+
+static gboolean
+gst_cea_cc_overlay_src_query (GstPad * pad, GstObject * parent,
+    GstQuery * query)
+{
+  gboolean ret = FALSE;
+  GstCeaCcOverlay *overlay;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *filter, *caps;
+
+      gst_query_parse_caps (query, &filter);
+      caps = gst_cea_cc_overlay_get_src_caps (pad, overlay, filter);
+      gst_query_set_caps_result (query, caps);
+      gst_caps_unref (caps);
+      ret = TRUE;
+      break;
+    }
+    default:
+      ret = gst_pad_query_default (pad, parent, query);
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_cea_cc_overlay_src_event (GstPad * pad, GstObject * parent,
+    GstEvent * event)
+{
+  GstCeaCcOverlay *overlay;
+  gboolean ret;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  if (overlay->cc_pad_linked) {
+    gst_event_ref (event);
+    ret = gst_pad_push_event (overlay->video_sinkpad, event);
+    gst_pad_push_event (overlay->cc_sinkpad, event);
+  } else {
+    ret = gst_pad_push_event (overlay->video_sinkpad, event);
+  }
+
+  return ret;
+}
+
+/**
+ * gst_cea_cc_overlay_add_feature_and_intersect:
+ *
+ * Creates a new #GstCaps containing the (given caps +
+ * given caps feature) + (given caps intersected by the
+ * given filter).
+ *
+ * Returns: the new #GstCaps
+ */
+static GstCaps *
+gst_cea_cc_overlay_add_feature_and_intersect (GstCaps * caps,
+    const gchar * feature, GstCaps * filter)
+{
+  int i, caps_size;
+  GstCaps *new_caps;
+
+  new_caps = gst_caps_copy (caps);
+
+  caps_size = gst_caps_get_size (new_caps);
+  for (i = 0; i < caps_size; i++) {
+    GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
+
+    if (!gst_caps_features_is_any (features)) {
+      gst_caps_features_add (features, feature);
+    }
+  }
+
+  gst_caps_append (new_caps, gst_caps_intersect_full (caps,
+          filter, GST_CAPS_INTERSECT_FIRST));
+
+  return new_caps;
+}
+
+/**
+ * gst_cea_cc_overlay_intersect_by_feature:
+ *
+ * Creates a new #GstCaps based on the following filtering rule.
+ *
+ * For each individual caps contained in given caps, if the
+ * caps uses the given caps feature, keep a version of the caps
+ * with the feature and an another one without. Otherwise, intersect
+ * the caps with the given filter.
+ *
+ * Returns: the new #GstCaps
+ */
+static GstCaps *
+gst_cea_cc_overlay_intersect_by_feature (GstCaps * caps,
+    const gchar * feature, GstCaps * filter)
+{
+  int i, caps_size;
+  GstCaps *new_caps;
+
+  new_caps = gst_caps_new_empty ();
+
+  caps_size = gst_caps_get_size (caps);
+  for (i = 0; i < caps_size; i++) {
+    GstStructure *caps_structure = gst_caps_get_structure (caps, i);
+    GstCapsFeatures *caps_features =
+        gst_caps_features_copy (gst_caps_get_features (caps, i));
+    GstCaps *filtered_caps;
+    GstCaps *simple_caps =
+        gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
+    gst_caps_set_features (simple_caps, 0, caps_features);
+
+    if (gst_caps_features_contains (caps_features, feature)) {
+      gst_caps_append (new_caps, gst_caps_copy (simple_caps));
+
+      gst_caps_features_remove (caps_features, feature);
+      filtered_caps = gst_caps_ref (simple_caps);
+    } else {
+      filtered_caps = gst_caps_intersect_full (simple_caps, filter,
+          GST_CAPS_INTERSECT_FIRST);
+    }
+    gst_caps_unref (simple_caps);
+    gst_caps_append (new_caps, filtered_caps);
+  }
+
+  return new_caps;
+}
+
+static GstCaps *
+gst_cea_cc_overlay_get_videosink_caps (GstPad * pad,
+    GstCeaCcOverlay * overlay, GstCaps * filter)
+{
+  GstPad *srcpad = overlay->srcpad;
+  GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
+
+  if (G_UNLIKELY (!overlay))
+    return gst_pad_get_pad_template_caps (pad);
+
+  if (filter) {
+    /* filter caps + composition feature + filter caps
+     * filtered by the software caps. */
+    GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
+    overlay_filter = gst_cea_cc_overlay_add_feature_and_intersect (filter,
+        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
+    gst_caps_unref (sw_caps);
+
+    GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
+        overlay_filter);
+  }
+
+  peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
+  if (overlay_filter)
+    gst_caps_unref (overlay_filter);
+  if (peer_caps) {
+
+    GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
+
+    if (gst_caps_is_any (peer_caps)) {
+      /* if peer returns ANY caps, return filtered src pad template caps */
+      caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
+    } else {
+
+      /* duplicate caps which contains the composition into one version with
+       * the meta and one without. Filter the other caps by the software caps */
+      GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
+      caps = gst_cea_cc_overlay_intersect_by_feature (peer_caps,
+          GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
+      gst_caps_unref (sw_caps);
+    }
+
+    gst_caps_unref (peer_caps);
+
+  } else {
+    /* no peer, our padtemplate is enough then */
+    caps = gst_pad_get_pad_template_caps (pad);
+  }
+
+  if (filter) {
+    GstCaps *intersection = gst_caps_intersect_full (filter, caps,
+        GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = intersection;
+  }
+
+  GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+static GstCaps *
+gst_cea_cc_overlay_get_src_caps (GstPad * pad, GstCeaCcOverlay * overlay,
+    GstCaps * filter)
+{
+  GstPad *sinkpad = overlay->video_sinkpad;
+  GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
+
+  if (G_UNLIKELY (!overlay))
+    return gst_pad_get_pad_template_caps (pad);
+
+  if (filter) {
+    /* duplicate filter caps which contains the composition into one version
+     * with the meta and one without. Filter the other caps by the software
+     * caps */
+    GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
+    overlay_filter =
+        gst_cea_cc_overlay_intersect_by_feature (filter,
+        GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
+    gst_caps_unref (sw_caps);
+  }
+
+  peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
+
+  if (overlay_filter)
+    gst_caps_unref (overlay_filter);
+
+  if (peer_caps) {
+
+    GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
+
+    if (gst_caps_is_any (peer_caps)) {
+
+      /* if peer returns ANY caps, return filtered sink pad template caps */
+      caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
+
+    } else {
+
+      /* return upstream caps + composition feature + upstream caps
+       * filtered by the software caps. */
+      GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
+      caps = gst_cea_cc_overlay_add_feature_and_intersect (peer_caps,
+          GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
+      gst_caps_unref (sw_caps);
+    }
+
+    gst_caps_unref (peer_caps);
+
+  } else {
+    /* no peer, our padtemplate is enough then */
+    caps = gst_pad_get_pad_template_caps (pad);
+  }
+
+  if (filter) {
+    GstCaps *intersection;
+
+    intersection =
+        gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = intersection;
+  }
+  GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
+
+  return caps;
+}
+
+/* FIXME: should probably be relative to width/height (adjusted for PAR) */
+#define BOX_XPAD  6
+#define BOX_YPAD  6
+
+static GstFlowReturn
+gst_cea_cc_overlay_push_frame (GstCeaCcOverlay * overlay,
+    GstBuffer * video_frame)
+{
+  GstVideoFrame frame;
+
+  if (overlay->current_composition == NULL)
+    goto done;
+  GST_LOG_OBJECT (overlay, "gst_cea_cc_overlay_push_frame");
+
+  if (gst_pad_check_reconfigure (overlay->srcpad))
+    gst_cea_cc_overlay_negotiate (overlay, NULL);
+
+  video_frame = gst_buffer_make_writable (video_frame);
+
+  if (overlay->attach_compo_to_buffer) {
+    GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
+    gst_buffer_add_video_overlay_composition_meta (video_frame,
+        overlay->current_composition);
+    goto done;
+  }
+
+  if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
+          GST_MAP_READWRITE))
+    goto invalid_frame;
+
+  gst_video_overlay_composition_blend (overlay->current_composition, &frame);
+
+  gst_video_frame_unmap (&frame);
+
+done:
+
+  return gst_pad_push (overlay->srcpad, video_frame);
+
+  /* ERRORS */
+invalid_frame:
+  {
+    gst_buffer_unref (video_frame);
+    return GST_FLOW_OK;
+  }
+}
+
+static GstPadLinkReturn
+gst_cea_cc_overlay_cc_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
+{
+  GstCeaCcOverlay *overlay;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+  if (G_UNLIKELY (!overlay))
+    return GST_PAD_LINK_REFUSED;
+
+  GST_DEBUG_OBJECT (overlay, "Closed Caption pad linked");
+
+  overlay->cc_pad_linked = TRUE;
+
+  return GST_PAD_LINK_OK;
+}
+
+static void
+gst_cea_cc_overlay_cc_pad_unlink (GstPad * pad, GstObject * parent)
+{
+  GstCeaCcOverlay *overlay;
+
+  /* don't use gst_pad_get_parent() here, will deadlock */
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  GST_DEBUG_OBJECT (overlay, "Closed Caption pad unlinked");
+
+  overlay->cc_pad_linked = FALSE;
+
+  gst_segment_init (&overlay->cc_segment, GST_FORMAT_UNDEFINED);
+}
+
+static gboolean
+gst_cea_cc_overlay_cc_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  gboolean ret = FALSE;
+  GstCeaCcOverlay *overlay = NULL;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  GST_LOG_OBJECT (overlay, "received event %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CAPS:
+    {
+      GstCaps *caps;
+      GstStructure *st;
+      const gchar *cctype;
+
+      gst_event_parse_caps (event, &caps);
+      st = gst_caps_get_structure (caps, 0);
+      cctype = gst_structure_get_string (st, "format");
+      overlay->is_cdp = !g_strcmp0 (cctype, "cdp");
+      ret = TRUE;
+      break;
+    }
+    case GST_EVENT_SEGMENT:
+    {
+      const GstSegment *segment;
+
+      overlay->cc_eos = FALSE;
+
+      gst_event_parse_segment (event, &segment);
+
+      if (segment->format == GST_FORMAT_TIME) {
+        GST_CEA_CC_OVERLAY_LOCK (overlay);
+        gst_segment_copy_into (segment, &overlay->cc_segment);
+        GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
+            &overlay->cc_segment);
+        GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      } else {
+        GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
+            ("received non-TIME newsegment event on text input"));
+      }
+
+      gst_event_unref (event);
+      ret = TRUE;
+
+      /* wake up the video chain, it might be waiting for a text buffer or
+       * a text segment update */
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      break;
+    }
+    case GST_EVENT_GAP:
+    {
+      GstClockTime start, duration;
+
+      gst_event_parse_gap (event, &start, &duration);
+      if (GST_CLOCK_TIME_IS_VALID (duration))
+        start += duration;
+      /* we do not expect another buffer until after gap,
+       * so that is our position now */
+      overlay->cc_segment.position = start;
+
+      /* wake up the video chain, it might be waiting for a text buffer or
+       * a text segment update */
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    }
+    case GST_EVENT_FLUSH_STOP:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_INFO_OBJECT (overlay, "text flush stop");
+      overlay->cc_flushing = FALSE;
+      overlay->cc_eos = FALSE;
+      gst_cea_cc_overlay_pop_text (overlay);
+      gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    case GST_EVENT_FLUSH_START:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_INFO_OBJECT (overlay, "text flush start");
+      overlay->cc_flushing = TRUE;
+      GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    case GST_EVENT_EOS:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      overlay->cc_eos = TRUE;
+      GST_INFO_OBJECT (overlay, "closed caption EOS");
+      /* wake up the video chain, it might be waiting for a text buffer or
+       * a text segment update */
+      GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    default:
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_cea_cc_overlay_video_event (GstPad * pad, GstObject * parent,
+    GstEvent * event)
+{
+  gboolean ret = FALSE;
+  GstCeaCcOverlay *overlay = NULL;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_CAPS:
+    {
+      GstCaps *caps;
+
+      gst_event_parse_caps (event, &caps);
+      ret = gst_cea_cc_overlay_setcaps (overlay, caps);
+      gst_event_unref (event);
+      break;
+    }
+    case GST_EVENT_SEGMENT:
+    {
+      const GstSegment *segment;
+
+      GST_DEBUG_OBJECT (overlay, "received new segment");
+
+      gst_event_parse_segment (event, &segment);
+
+      if (segment->format == GST_FORMAT_TIME) {
+        GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
+            &overlay->segment);
+
+        gst_segment_copy_into (segment, &overlay->segment);
+      } else {
+        GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
+            ("received non-TIME newsegment event on video input"));
+      }
+
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+    }
+    case GST_EVENT_EOS:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_INFO_OBJECT (overlay, "video EOS");
+      overlay->video_eos = TRUE;
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+    case GST_EVENT_FLUSH_START:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_INFO_OBJECT (overlay, "video flush start");
+      overlay->video_flushing = TRUE;
+      GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+    case GST_EVENT_FLUSH_STOP:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      GST_INFO_OBJECT (overlay, "video flush stop");
+      overlay->video_flushing = FALSE;
+      overlay->video_eos = FALSE;
+      gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+    default:
+      ret = gst_pad_event_default (pad, parent, event);
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_cea_cc_overlay_video_query (GstPad * pad, GstObject * parent,
+    GstQuery * query)
+{
+  gboolean ret = FALSE;
+  GstCeaCcOverlay *overlay;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *filter, *caps;
+
+      gst_query_parse_caps (query, &filter);
+      caps = gst_cea_cc_overlay_get_videosink_caps (pad, overlay, filter);
+      gst_query_set_caps_result (query, caps);
+      gst_caps_unref (caps);
+      ret = TRUE;
+      break;
+    }
+    default:
+      ret = gst_pad_query_default (pad, parent, query);
+      break;
+  }
+
+  return ret;
+}
+
+/* Called with lock held */
+static void
+gst_cea_cc_overlay_pop_text (GstCeaCcOverlay * overlay)
+{
+  g_return_if_fail (GST_IS_CEA_CC_OVERLAY (overlay));
+
+  if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)
+      && overlay->current_composition) {
+    GST_DEBUG_OBJECT (overlay, "releasing composition %p",
+        overlay->current_composition);
+    gst_video_overlay_composition_unref (overlay->current_composition);
+    overlay->current_composition = NULL;
+    overlay->current_comp_start_time = GST_CLOCK_TIME_NONE;
+  }
+
+  /* Let the text task know we used that buffer */
+  GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+}
+
+static void
+gst_cea_cc_overlay_image_to_argb (guchar * pixbuf,
+    cea708Window * window, int stride)
+{
+  int i, j;
+  guchar *p, *bitp;
+  int width, height;
+
+  width = window->image_width;
+  height = window->image_height;
+
+  for (i = 0; i < height; i++) {
+    p = pixbuf + i * stride;
+    bitp = window->text_image + i * width * 4;
+
+    for (j = 0; j < width; j++) {
+      p[0] = bitp[CAIRO_ARGB_A];
+      p[1] = bitp[CAIRO_ARGB_R];
+      p[2] = bitp[CAIRO_ARGB_G];
+      p[3] = bitp[CAIRO_ARGB_B];
+
+      /* Cairo uses pre-multiplied ARGB, unpremultiply it */
+      CAIRO_UNPREMULTIPLY (p[0], p[1], p[2], p[3]);
+
+      bitp += 4;
+      p += 4;
+    }
+  }
+}
+
+static void
+gst_cea_cc_overlay_image_to_ayuv (guchar * pixbuf,
+    cea708Window * window, int stride)
+{
+  int y;                        /* text bitmap coordinates */
+  guchar *p, *bitp;
+  guchar a, r, g, b;
+  int width, height;
+
+  width = window->image_width;
+  height = window->image_height;
+
+  for (y = 0; y < height; y++) {
+    int n;
+    p = pixbuf + y * stride;
+    bitp = window->text_image + y * width * 4;
+
+    for (n = 0; n < width; n++) {
+      b = bitp[CAIRO_ARGB_B];
+      g = bitp[CAIRO_ARGB_G];
+      r = bitp[CAIRO_ARGB_R];
+      a = bitp[CAIRO_ARGB_A];
+      bitp += 4;
+
+      /* Cairo uses pre-multiplied ARGB, unpremultiply it */
+      CAIRO_UNPREMULTIPLY (a, r, g, b);
+
+      *p++ = a;
+      *p++ = CLAMP ((int) (((19595 * r) >> 16) + ((38470 * g) >> 16) +
+              ((7471 * b) >> 16)), 0, 255);
+      *p++ = CLAMP ((int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) +
+              ((32768 * b) >> 16) + 128), 0, 255);
+      *p++ = CLAMP ((int) (((32768 * r) >> 16) - ((27439 * g) >> 16) -
+              ((5329 * b) >> 16) + 128), 0, 255);
+    }
+  }
+}
+
+static void
+gst_cea_cc_overlay_create_and_push_buffer (GstCeaCcOverlay * overlay)
+{
+  Cea708Dec *decoder = overlay->decoder;
+  GstBuffer *outbuf;
+  GstMapInfo map;
+  guint8 *window_image;
+  gint n;
+  guint window_id;
+  cea708Window *window;
+  guint v_anchor = 0;
+  guint h_anchor = 0;
+  GstVideoOverlayComposition *comp = NULL;
+  GstVideoOverlayRectangle *rect = NULL;
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+
+  for (window_id = 0; window_id < 8; window_id++) {
+    window = decoder->cc_windows[window_id];
+
+    if (!window->updated) {
+      continue;
+    }
+    if (!window->deleted && window->visible && window->text_image != NULL) {
+      GST_DEBUG_OBJECT (overlay, "Allocating buffer");
+      outbuf =
+          gst_buffer_new_and_alloc (window->image_width *
+          window->image_height * 4);
+      gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
+      window_image = map.data;
+      if (decoder->use_ARGB) {
+        memset (window_image, 0,
+            window->image_width * window->image_height * 4);
+        gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE,
+            GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, window->image_width,
+            window->image_height);
+      } else {
+        for (n = 0; n < window->image_width * window->image_height; n++) {
+          window_image[n * 4] = window_image[n * 4 + 1] = 0;
+          window_image[n * 4 + 2] = window_image[n * 4 + 3] = 128;
+        }
+        gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE,
+            GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV, window->image_width,
+            window->image_height);
+      }
+
+      v_anchor = window->screen_vertical * overlay->height / 100;
+      switch (overlay->default_window_h_pos) {
+        case GST_CEA_CC_OVERLAY_WIN_H_LEFT:
+          window->h_offset = 0;
+          break;
+        case GST_CEA_CC_OVERLAY_WIN_H_CENTER:
+          window->h_offset = (overlay->width - window->image_width) / 2;
+          break;
+        case GST_CEA_CC_OVERLAY_WIN_H_RIGHT:
+          window->h_offset = overlay->width - window->image_width;
+          break;
+        case GST_CEA_CC_OVERLAY_WIN_H_AUTO:
+        default:
+          switch (window->anchor_point) {
+            case ANCHOR_PT_TOP_LEFT:
+            case ANCHOR_PT_MIDDLE_LEFT:
+            case ANCHOR_PT_BOTTOM_LEFT:
+              window->h_offset = h_anchor;
+              break;
+
+            case ANCHOR_PT_TOP_CENTER:
+            case ANCHOR_PT_CENTER:
+            case ANCHOR_PT_BOTTOM_CENTER:
+              window->h_offset = h_anchor - window->image_width / 2;
+              break;
+
+            case ANCHOR_PT_TOP_RIGHT:
+            case ANCHOR_PT_MIDDLE_RIGHT:
+            case ANCHOR_PT_BOTTOM_RIGHT:
+              window->h_offset = h_anchor - window->image_width;
+              break;
+            default:
+              break;
+          }
+          break;
+      }
+
+      switch (window->anchor_point) {
+        case ANCHOR_PT_TOP_LEFT:
+        case ANCHOR_PT_TOP_CENTER:
+        case ANCHOR_PT_TOP_RIGHT:
+          window->v_offset = v_anchor;
+          break;
+
+        case ANCHOR_PT_MIDDLE_LEFT:
+        case ANCHOR_PT_CENTER:
+        case ANCHOR_PT_MIDDLE_RIGHT:
+          window->v_offset = v_anchor - window->image_height / 2;
+          break;
+
+        case ANCHOR_PT_BOTTOM_LEFT:
+        case ANCHOR_PT_BOTTOM_CENTER:
+        case ANCHOR_PT_BOTTOM_RIGHT:
+          window->v_offset = v_anchor - window->image_height;
+          break;
+        default:
+          break;
+      }
+      if (decoder->use_ARGB) {
+        gst_cea_cc_overlay_image_to_argb (window_image, window,
+            window->image_width * 4);
+      } else {
+        gst_cea_cc_overlay_image_to_ayuv (window_image, window,
+            window->image_width * 4);
+      }
+      gst_buffer_unmap (outbuf, &map);
+      GST_INFO_OBJECT (overlay,
+          "window->anchor_point=%d,v_anchor=%d,h_anchor=%d,window->image_height=%d,window->image_width=%d, window->v_offset=%d, window->h_offset=%d,window->justify_mode=%d",
+          window->anchor_point, v_anchor, h_anchor, window->image_height,
+          window->image_width, window->v_offset, window->h_offset,
+          window->justify_mode);
+      rect =
+          gst_video_overlay_rectangle_new_raw (outbuf, window->h_offset,
+          window->v_offset, window->image_width, window->image_height, 0);
+      if (comp == NULL) {
+        comp = gst_video_overlay_composition_new (rect);
+      } else {
+        gst_video_overlay_composition_add_rectangle (comp, rect);
+      }
+      gst_video_overlay_rectangle_unref (rect);
+      gst_buffer_unref (outbuf);
+    }
+  }
+
+  /* Wait for the previous buffer to go away */
+  if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
+    overlay->next_composition = comp;
+    overlay->next_comp_start_time = decoder->current_time;
+    GST_DEBUG_OBJECT (overlay,
+        "wait for render next %p, current is %p BUFFER: next ts=%"
+        GST_TIME_FORMAT ",current ts=%" GST_TIME_FORMAT,
+        overlay->next_composition, overlay->current_composition,
+        GST_TIME_ARGS (overlay->next_comp_start_time),
+        GST_TIME_ARGS (overlay->current_comp_start_time));
+
+    GST_DEBUG_OBJECT (overlay, "has a closed caption buffer queued, waiting");
+    GST_CEA_CC_OVERLAY_WAIT (overlay);
+    GST_DEBUG_OBJECT (overlay, "resuming");
+    if (overlay->cc_flushing) {
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      return;
+    }
+  }
+
+  overlay->next_composition = NULL;
+  overlay->next_comp_start_time = GST_CLOCK_TIME_NONE;
+  overlay->current_composition = comp;
+  overlay->current_comp_start_time = decoder->current_time;
+  GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (overlay->current_comp_start_time));
+  overlay->need_update = FALSE;
+
+  /* in case the video chain is waiting for a text buffer, wake it up */
+  GST_CEA_CC_OVERLAY_BROADCAST (overlay);
+  GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+}
+
+static void
+gst_cea_cc_overlay_process_packet (GstCeaCcOverlay * overlay, guint8 cc_type)
+{
+  gint16 *index = NULL;
+  guint8 *buffer = NULL;
+  guint8 *dtvcc_buffer = NULL;
+  gboolean need_render = FALSE;
+
+  switch (cc_type) {
+    case CCTYPE_608_CC1:
+    case CCTYPE_608_CC2:
+      index = &overlay->cea608_index[cc_type];
+      buffer = overlay->cea608_buffer[cc_type];
+      break;
+
+    case CCTYPE_708_ADD:
+    case CCTYPE_708_START:
+      index = &overlay->cea708_index;
+      buffer = overlay->cea708_buffer;
+      break;
+    default:
+      GST_ERROR_OBJECT (overlay,
+          "attempted to process packet for unknown cc_type %d", cc_type);
+      return;
+  }
+
+  if (*index > 0) {
+    /*TODO: in future need add 608 decoder, currently only deal with 708 */
+    if (cc_type == CCTYPE_708_ADD || cc_type == CCTYPE_708_START) {
+      GST_LOG_OBJECT (overlay,
+          "called - buf[%" G_GINT16_FORMAT "] = %02X:%02X:%02X:%02X", *index,
+          buffer[0], buffer[1], buffer[2], buffer[3]);
+      dtvcc_buffer = g_malloc0 (*index + 1);
+      memcpy (dtvcc_buffer, buffer, *index);
+      need_render =
+          gst_cea708dec_process_dtvcc_packet (overlay->decoder, dtvcc_buffer,
+          *index);
+      g_free (dtvcc_buffer);
+      if (need_render)
+        gst_cea_cc_overlay_create_and_push_buffer (overlay);
+    }
+  }
+  *index = 0;
+}
+
+
+/**
+ * gst_cea_cc_overlay_user_data_decode:
+ * @overlay: The #GstCeaCcOverlay 
+ * @user_data: The #GstMpegVideoCCData to decode
+ *
+ * decode closed caption data and render when neccesary
+ * in struct GstMpegVideoCCData type's user_data's data field, 3 byte's data construct 1 cc_data_pkt
+ *
+ * A cc_data_pkt is 3 bytes as follows:
+ * -------------------------------------------
+ *   5 bits (b7-b3)  marker_bits (should be all 1's)
+ *   1 bit (b2)      cc_valid
+ *   2 bits (b1-b0)  cc_type (bslbf)
+ *   8 bits          cc_data_1 (bslbf)
+ *   8 bits          cc_data_2 (bslbf)
+ *
+ *   If cc_valid != 1, then ignore this packet
+ *
+ *   cc_type has these values:
+ *   0   NTSC_CC_FIELD_1     - CEA-608
+ *   1   NTSC_CC_FIELD_2     - CEA-608
+ *   2   DTVCC_PACKET_DATA   - CEA-708
+ *   3   DTVCC_PACKET_START  - CEA-708
+ *
+ *   DTVCC packet (aka. caption channel packet)
+ *   This is formed by accumulating cc_data_1/cc_data_2 from each cc_data_pkt
+ *   starting with a packet where cc_type = 3, and ending with a packet
+ *   where again cc_type = 3 (start of next buffer), or cc_valid=0 && cc_type=2
+ *   DTVCC packet's structure is:
+ *   --------------------------------------------------------------------------
+ *   2 bits (b6-b7)  sequence_number
+ *   6 bits (b0-b5)  packet_size
+ *   ((packet_size*2-1)&0xFF) * 8 bits packet_data (Service Block)
+ */
+static void
+gst_cea_cc_overlay_user_data_decode (GstCeaCcOverlay * overlay,
+    const guint8 * ccdata, gsize ccsize)
+{
+  guint8 temp;
+  guint8 cc_count;
+  guint i;
+  guint8 cc_type;
+  guint8 cc_valid;
+  guint8 cc_data[2];
+
+  cc_count = ccsize / 3;
+
+  for (i = 0; i < cc_count; i++) {
+    temp = *ccdata++;
+    cc_data[0] = *ccdata++;
+    cc_data[1] = *ccdata++;
+    cc_valid = (temp & CCTYPE_VALID_MASK) ? TRUE : FALSE;
+    cc_type = (temp & CCTYPE_TYPE_MASK);
+
+    GST_LOG_OBJECT (overlay, "cc_data_pkt(%d): cc_valid=%d cc_type=%d "
+        "cc_data[0]=0x%02X cc_data[1]=0x%02X",
+        i, cc_valid, cc_type, cc_data[0], cc_data[1]);
+
+    /* accumulate dvtcc packet */
+    switch (cc_type) {
+      case CCTYPE_608_CC1:
+      case CCTYPE_608_CC2:
+        if (cc_valid) {
+          if (overlay->cea608_index[cc_type] <= DTVCC_LENGTH - 2) {
+            for (size_t j = 0; j < 2; ++j) {
+              if ((cc_data[j] < ' ') || (cc_data[j] > '~')) {
+                gst_cea_cc_overlay_process_packet (overlay, cc_type);
+              }
+              overlay->cea608_buffer[cc_type][overlay->
+                  cea608_index[cc_type]++] = cc_data[j];
+            }
+          } else {
+            GST_ERROR_OBJECT (overlay, "cea608_buffer[%d] overflow!", cc_type);
+          }
+        }
+        break;
+
+      case CCTYPE_708_ADD:
+      case CCTYPE_708_START:
+        if (cc_valid) {
+          if (cc_type == CCTYPE_708_START) {
+            /* The previous packet is complete */
+            gst_cea_cc_overlay_process_packet (overlay, cc_type);
+          }
+          /* Add on to the current DTVCC packet */
+          if (overlay->cea708_index <= DTVCC_LENGTH - 2) {
+            overlay->cea708_buffer[overlay->cea708_index++] = cc_data[0];
+            overlay->cea708_buffer[overlay->cea708_index++] = cc_data[1];
+          } else {
+            GST_ERROR_OBJECT (overlay, "cea708_buffer overflow!");
+          }
+        } else if (cc_type == CCTYPE_708_ADD) {
+          /* This packet should be ignored, but if there is a current */
+          /* DTVCC packet then this is the end. */
+          gst_cea_cc_overlay_process_packet (overlay, cc_type);
+        }
+        break;
+    }
+  }
+}
+
+/* FIXME : Move to GstVideo ANC/CC helper library */
+static gboolean
+extract_ccdata_from_cdp (const guint8 * indata, gsize insize,
+    const guint8 ** ccdata, gsize * ccsize)
+{
+  GstByteReader br;
+  guint8 cdp_length;
+  guint8 framerate_code;
+  guint8 flags;
+  guint16 seqhdr;
+
+  GST_MEMDUMP ("CDP", indata, insize);
+
+  gst_byte_reader_init (&br, indata, insize);
+
+  /* The smallest valid CDP we are interested in is 7 (header) + 2 (cc
+   * section) + 4 (footer) bytes long */
+  if (gst_byte_reader_get_remaining (&br) < 13)
+    return FALSE;
+
+  /* Check header */
+  if (gst_byte_reader_get_uint16_be_unchecked (&br) != 0x9669) {
+    GST_WARNING ("Invalid CDP header");
+    return FALSE;
+  }
+  cdp_length = gst_byte_reader_get_uint8_unchecked (&br);
+  if (cdp_length > insize) {
+    GST_WARNING ("CDP too small (need %d bytes, have %" G_GSIZE_FORMAT ")",
+        cdp_length, insize);
+    return FALSE;
+  }
+  framerate_code = gst_byte_reader_get_uint8_unchecked (&br) >> 4;
+  flags = gst_byte_reader_get_uint8_unchecked (&br);
+  seqhdr = gst_byte_reader_get_uint16_be_unchecked (&br);
+
+  GST_DEBUG
+      ("framerate_code : 0x%02x , flags : 0x%02x , sequencer_counter : %u",
+      framerate_code, flags, seqhdr);
+
+  /* Skip timecode if present */
+  if (flags & 0x80) {
+    GST_LOG ("Skipping timecode section");
+    gst_byte_reader_skip (&br, 5);
+  }
+
+  /* cc data */
+  if (flags & 0x40) {
+    guint8 ccid, cc_count;
+    if (!gst_byte_reader_get_uint8 (&br, &ccid) ||
+        !gst_byte_reader_get_uint8 (&br, &cc_count))
+      return FALSE;
+    if (ccid != 0x72) {
+      GST_WARNING ("Invalid ccdata_id (expected 0x72, got 0x%02x)", ccid);
+      return FALSE;
+    }
+    cc_count &= 0x1f;
+    if (!gst_byte_reader_get_data (&br, cc_count * 3, ccdata)) {
+      GST_WARNING ("Not enough ccdata");
+      *ccdata = NULL;
+      *ccsize = 0;
+      return FALSE;
+    }
+    *ccsize = cc_count * 3;
+  }
+
+  /* FIXME : Parse/validate the rest of the CDP ! */
+
+  return TRUE;
+}
+
+/* We receive text buffers here. If they are out of segment we just ignore them.
+   If the buffer is in our segment we keep it internally except if another one
+   is already waiting here, in that case we wait that it gets kicked out */
+static GstFlowReturn
+gst_cea_cc_overlay_cc_chain (GstPad * pad, GstObject * parent,
+    GstBuffer * buffer)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  GstCeaCcOverlay *overlay = (GstCeaCcOverlay *) parent;
+  gboolean in_seg = FALSE;
+  guint64 clip_start = 0, clip_stop = 0;
+
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+
+  if (overlay->cc_flushing) {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    ret = GST_FLOW_FLUSHING;
+    GST_LOG_OBJECT (overlay, "closed caption flushing");
+    goto beach;
+  }
+
+  if (overlay->cc_eos) {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    ret = GST_FLOW_EOS;
+    GST_LOG_OBJECT (overlay, "closed caption EOS");
+    goto beach;
+  }
+
+  GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
+      GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
+          GST_BUFFER_DURATION (buffer)));
+
+  if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
+    GstClockTime stop;
+
+    if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
+      stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
+    else
+      stop = GST_CLOCK_TIME_NONE;
+
+    in_seg = gst_segment_clip (&overlay->cc_segment, GST_FORMAT_TIME,
+        GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
+    GST_LOG_OBJECT (overlay, "stop:%" GST_TIME_FORMAT ", in_seg: %d",
+        GST_TIME_ARGS (stop), in_seg);
+  } else {
+    in_seg = TRUE;
+  }
+
+
+  if (in_seg) {
+    GstMapInfo buf_map = { 0 };
+    const guint8 *ccdata = NULL;
+    gsize ccsize = 0;
+
+    overlay->cc_segment.position = clip_start;
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+
+    gst_buffer_map (buffer, &buf_map, GST_MAP_READ);
+    if (overlay->is_cdp) {
+      extract_ccdata_from_cdp (buf_map.data, buf_map.size, &ccdata, &ccsize);
+    } else {
+      ccdata = buf_map.data;
+      ccsize = buf_map.size;
+    }
+    if (ccsize) {
+      gst_cea_cc_overlay_user_data_decode (overlay, ccdata, ccsize);
+      overlay->decoder->current_time = GST_BUFFER_PTS (buffer);
+    }
+    gst_buffer_unmap (buffer, &buf_map);
+  }
+
+beach:
+  gst_buffer_unref (buffer);
+  return ret;
+}
+
+static GstFlowReturn
+gst_cea_cc_overlay_video_chain (GstPad * pad, GstObject * parent,
+    GstBuffer * buffer)
+{
+  GstCeaCcOverlay *overlay;
+  GstFlowReturn ret = GST_FLOW_OK;
+  gboolean in_seg = FALSE;
+  guint64 start, stop, clip_start = 0, clip_stop = 0;
+
+  overlay = GST_CEA_CC_OVERLAY (parent);
+
+  if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
+    goto missing_timestamp;
+
+  /* ignore buffers that are outside of the current segment */
+  start = GST_BUFFER_TIMESTAMP (buffer);
+
+  if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
+    stop = GST_CLOCK_TIME_NONE;
+  } else {
+    stop = start + GST_BUFFER_DURATION (buffer);
+  }
+
+  GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
+      GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
+      GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
+
+  /* segment_clip() will adjust start unconditionally to segment_start if
+   * no stop time is provided, so handle this ourselves */
+  if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
+    goto out_of_segment;
+
+  in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
+      &clip_start, &clip_stop);
+
+  if (!in_seg)
+    goto out_of_segment;
+
+  /* if the buffer is only partially in the segment, fix up stamps */
+  if (clip_start != start || (stop != -1 && clip_stop != stop)) {
+    GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
+    buffer = gst_buffer_make_writable (buffer);
+    GST_BUFFER_TIMESTAMP (buffer) = clip_start;
+    if (stop != -1)
+      GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
+  }
+
+  /* now, after we've done the clipping, fix up end time if there's no
+   * duration (we only use those estimated values internally though, we
+   * don't want to set bogus values on the buffer itself) */
+  if (stop == -1) {
+    if (overlay->info.fps_n && overlay->info.fps_d) {
+      GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
+      stop = start + gst_util_uint64_scale_int (GST_SECOND,
+          overlay->info.fps_d, overlay->info.fps_n);
+    } else {
+      GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
+      stop = start + 1;         /* we need to assume some interval */
+    }
+  }
+
+  gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
+
+wait_for_text_buf:
+
+  GST_CEA_CC_OVERLAY_LOCK (overlay);
+
+  if (overlay->video_flushing)
+    goto flushing;
+
+  if (overlay->video_eos)
+    goto have_eos;
+
+  if (overlay->silent) {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    ret = gst_pad_push (overlay->srcpad, buffer);
+
+    /* Update position */
+    overlay->segment.position = clip_start;
+
+    return ret;
+  }
+
+  /* Closed Caption pad not linked, rendering video only */
+  if (!overlay->cc_pad_linked) {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    ret = gst_pad_push (overlay->srcpad, buffer);
+  } else {
+    /* Closed Caption pad linked, check if we have a text buffer queued */
+    if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
+      gboolean pop_text = FALSE, valid_text_time = TRUE;
+
+      GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
+      GstClockTime next_buffer_text_running_time = GST_CLOCK_TIME_NONE;
+      GstClockTime vid_running_time, vid_running_time_end;
+
+      vid_running_time =
+          gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
+          start);
+      vid_running_time_end =
+          gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
+          stop);
+      if (GST_CLOCK_TIME_IS_VALID (overlay->next_comp_start_time)) {
+        next_buffer_text_running_time =
+            gst_segment_to_running_time (&overlay->cc_segment, GST_FORMAT_TIME,
+            overlay->next_comp_start_time);
+
+        if (next_buffer_text_running_time < vid_running_time_end) {
+          /* text buffer should be force updated, popping  */
+          GST_DEBUG_OBJECT (overlay,
+              "T: next_buffer_text_running_time: %" GST_TIME_FORMAT
+              " - overlay->next_comp_start_time: %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (next_buffer_text_running_time),
+              GST_TIME_ARGS (overlay->next_comp_start_time));
+          GST_DEBUG_OBJECT (overlay,
+              "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (vid_running_time),
+              GST_TIME_ARGS (vid_running_time_end));
+          GST_LOG_OBJECT (overlay,
+              "text buffer should be force updated, popping");
+          pop_text = FALSE;
+          gst_cea_cc_overlay_pop_text (overlay);
+          GST_CEA_CC_OVERLAY_WAIT (overlay);
+          GST_DEBUG_OBJECT (overlay, "resuming");
+          GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+          goto wait_for_text_buf;
+        }
+
+      }
+
+      /* if the text buffer isn't stamped right, pop it off the
+       * queue and display it for the current video frame only */
+      if (!GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) {
+        GST_WARNING_OBJECT (overlay, "Got text buffer with invalid timestamp");
+        pop_text = TRUE;
+        valid_text_time = FALSE;
+      }
+
+      /* If timestamp and duration are valid */
+      if (valid_text_time) {
+        text_running_time =
+            gst_segment_to_running_time (&overlay->cc_segment,
+            GST_FORMAT_TIME, overlay->current_comp_start_time);
+      }
+
+      GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (text_running_time));
+      GST_DEBUG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (vid_running_time),
+          GST_TIME_ARGS (vid_running_time_end));
+
+      if (valid_text_time && vid_running_time_end <= text_running_time) {
+        GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
+        GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+        /* Push the video frame */
+        ret = gst_pad_push (overlay->srcpad, buffer);
+      } else {
+        GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+        ret = gst_cea_cc_overlay_push_frame (overlay, buffer);
+      }
+      if (pop_text) {
+        GST_CEA_CC_OVERLAY_LOCK (overlay);
+        gst_cea_cc_overlay_pop_text (overlay);
+        GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      }
+    } else {
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
+      ret = gst_pad_push (overlay->srcpad, buffer);
+    }
+  }
+
+  /* Update position */
+  overlay->segment.position = clip_start;
+  GST_DEBUG_OBJECT (overlay, "ret=%d", ret);
+
+  return ret;
+
+missing_timestamp:
+  {
+    GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+
+flushing:
+  {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_FLUSHING;
+  }
+have_eos:
+  {
+    GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+    GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_EOS;
+  }
+out_of_segment:
+  {
+    GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+}
+
+static GstStateChangeReturn
+gst_cea_cc_overlay_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      overlay->cc_flushing = TRUE;
+      overlay->video_flushing = TRUE;
+      /* pop_text will broadcast on the GCond and thus also make the video
+       * chain exit if it's waiting for a text buffer */
+      gst_cea_cc_overlay_pop_text (overlay);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      break;
+    default:
+      break;
+  }
+
+  ret = parent_class->change_state (element, transition);
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    return ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      GST_CEA_CC_OVERLAY_LOCK (overlay);
+      overlay->cc_flushing = FALSE;
+      overlay->video_flushing = FALSE;
+      overlay->video_eos = FALSE;
+      overlay->cc_eos = FALSE;
+      gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
+      gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME);
+      GST_CEA_CC_OVERLAY_UNLOCK (overlay);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
diff --git a/ext/closedcaption/gstceaccoverlay.h b/ext/closedcaption/gstceaccoverlay.h
new file mode 100644 (file)
index 0000000..a50401a
--- /dev/null
@@ -0,0 +1,136 @@
+/* GStreamer
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ *     @Author: Chengjun Wang <cjun.wang@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __GST_CEA_CC_OVERLAY_H__
+#define __GST_CEA_CC_OVERLAY_H__
+
+#include <gst/gst.h>
+#include <pango/pangocairo.h>
+#include <gstcea708decoder.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_CEA_CC_OVERLAY \
+  (gst_cea_cc_overlay_get_type())
+#define GST_CEA_CC_OVERLAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CEA_CC_OVERLAY,GstCeaCcOverlay))
+#define GST_CEA_CC_OVERLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CEA_CC_OVERLAY,GstCeaCcOverlayClass))
+#define GST_CEA_CC_OVERLAY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+  GST_TYPE_CEA_CC_OVERLAY, GstCeaCcOverlayClass))
+#define GST_IS_CEA_CC_OVERLAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CEA_CC_OVERLAY))
+#define GST_IS_CEA_CC_OVERLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CEA_CC_OVERLAY))
+
+typedef struct _GstCeaCcOverlay GstCeaCcOverlay;
+typedef struct _GstCeaCcOverlayClass GstCeaCcOverlayClass;
+
+typedef enum
+{
+  CCTYPE_608_CC1 = 0,
+  CCTYPE_608_CC2,
+  CCTYPE_708_ADD,
+  CCTYPE_708_START,
+} DtvccType;
+
+/**
+ * GstBaseTextOverlayHAlign:
+ * @GST_CEA_CC_OVERLAY_WIN_H_LEFT: closed caption window horizontal anchor left
+ * @GST_CEA_CC_OVERLAY_WIN_H_CENTER: closed caption window horizontal anchor center
+ * @GST_CEA_CC_OVERLAY_WIN_H_RIGHT: closed caption window horizontal anchor right
+ * @GST_CEA_CC_OVERLAY_WIN_H_AUTO: closed caption window horizontal anchor auto
+ *
+ * Closed Caption Window Horizontal anchor position.
+ */
+typedef enum
+{
+  GST_CEA_CC_OVERLAY_WIN_H_LEFT,
+  GST_CEA_CC_OVERLAY_WIN_H_CENTER,
+  GST_CEA_CC_OVERLAY_WIN_H_RIGHT,
+  GST_CEA_CC_OVERLAY_WIN_H_AUTO
+} GstCeaCcOverlayWinHPos;
+
+/**
+ * GstCeaCcOverlay:
+ *
+ * Opaque ccoverlay data structure.
+ */
+struct _GstCeaCcOverlay
+{
+  GstElement parent;
+  GstPad *video_sinkpad;
+  GstPad *cc_sinkpad;
+  GstPad *srcpad;
+  /* There are two possible 608 streams encapsulated by 708 */
+  gint16 cea608_index[NUM_608_CCTYPES];
+  gint16 cea708_index;
+  guint8 cea608_buffer[NUM_608_CCTYPES][DTVCC_LENGTH];
+  guint8 cea708_buffer[DTVCC_LENGTH];
+
+  /* TRUE if input is CDP, FALSE if cc_data triplet */
+  gboolean is_cdp;
+  
+  GstSegment segment;
+  GstSegment cc_segment;
+  GstVideoOverlayComposition *current_composition;
+  guint64 current_comp_start_time;
+  GstVideoOverlayComposition *next_composition;
+  guint64 next_comp_start_time;
+  GstCeaCcOverlayWinHPos default_window_h_pos;
+  gboolean cc_pad_linked;
+  gboolean video_flushing;
+  gboolean video_eos;
+  gboolean cc_flushing;
+  gboolean cc_eos;
+
+  GMutex lock;
+  GCond cond;                   /* to signal removal of a queued text
+                                 * buffer, arrival of a text buffer,
+                                 * a text segment update, or a change
+                                 * in status (e.g. shutdown, flushing) */
+
+  GstVideoInfo info;
+  GstVideoFormat format;
+  gint width;
+  gint height;
+  gboolean silent;
+  Cea708Dec *decoder;
+  gint image_width;
+  gint image_height;
+
+  gboolean need_update;
+
+  gboolean attach_compo_to_buffer;
+};
+
+/* FIXME : Pango context and MT-safe since 1.32.6 */
+struct _GstCeaCcOverlayClass
+{
+  GstElementClass parent_class;
+
+  PangoContext *pango_context;
+  GMutex *pango_lock;
+};
+
+GType gst_cea_cc_overlay_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_CEA_CC_OVERLAY_H__ */
index affdc21..1ee59c0 100644 (file)
 
 #include "gstccextractor.h"
 #include "gstline21dec.h"
+#include "gstceaccoverlay.h"
 
 static gboolean
-closedcaption_init (GstPlugin * ccextractor)
+closedcaption_init (GstPlugin * plugin)
 {
   gboolean ret;
 
-  ret = gst_element_register (ccextractor, "ccextractor", GST_RANK_NONE,
+  ret = gst_element_register (plugin, "ccextractor", GST_RANK_NONE,
       GST_TYPE_CCEXTRACTOR);
 
-  ret &= gst_element_register (ccextractor, "line21decoder", GST_RANK_NONE,
+  ret &= gst_element_register (plugin, "line21decoder", GST_RANK_NONE,
       GST_TYPE_LINE21DECODER);
 
+  ret = gst_element_register (plugin, "cc708overlay", GST_RANK_PRIMARY,
+      GST_TYPE_CEA_CC_OVERLAY);
+
   return ret;
 }
 
index 841fab7..4e1ed3a 100644 (file)
@@ -9,7 +9,8 @@ zvbi_sources = [
 
 if pangocairo_dep.found()
   gstclosedcaption = library('gstclosedcaption',
-    'gstccextractor.c', 'gstclosedcaption.c', 'gstline21dec.c', zvbi_sources,
+    'gstccextractor.c', 'gstclosedcaption.c', 'gstline21dec.c',
+    'gstcea708decoder.c', 'gstceaccoverlay.c', zvbi_sources,
     c_args : gst_plugins_bad_args,
     link_args : noseh_link_args,
     include_directories : [configinc],