$(zvbi_headers) \
gstccextractor.c \
gstccextractor.h \
+ gstcea708decoder.c \
+ gstcea708decoder.h \
+ gstceaccoverlay.c \
+ gstceaccoverlay.h \
gstline21dec.c \
gstline21dec.h \
gstclosedcaption.c
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)
--- /dev/null
+/* 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, "&");
+ index += 5;
+ break;
+
+ case '<':
+ g_snprintf (&(line_buffer[index]),
+ sizeof (line_buffer) - index - 1, "<");
+ index += 4;
+ break;
+
+ case '>':
+ g_snprintf (&(line_buffer[index]),
+ sizeof (line_buffer) - index - 1, ">");
+ index += 4;
+ break;
+
+ case '\'':
+ g_snprintf (&(line_buffer[index]),
+ sizeof (line_buffer) - index - 1, "'");
+ index += 6;
+ break;
+
+ case '"':
+ g_snprintf (&(line_buffer[index]),
+ sizeof (line_buffer) - index - 1, """);
+ 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
#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;
}
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],