diff --git a/ext/closedcaption/Makefile.am b/ext/closedcaption/Makefile.am index a2a6cd1fb0..639dac0fbe 100644 --- a/ext/closedcaption/Makefile.am +++ b/ext/closedcaption/Makefile.am @@ -21,6 +21,10 @@ libgstclosedcaption_la_SOURCES = \ $(zvbi_headers) \ gstccextractor.c \ gstccextractor.h \ + gstcea708decoder.c \ + gstcea708decoder.h \ + gstceaccoverlay.c \ + gstceaccoverlay.h \ gstline21dec.c \ gstline21dec.h \ gstclosedcaption.c @@ -28,12 +32,14 @@ libgstclosedcaption_la_SOURCES = \ libgstclosedcaption_la_CFLAGS = \ $(GST_PLUGINS_BASE_CFLAGS) \ $(GST_BASE_CFLAGS) \ - $(GST_CFLAGS) + $(GST_CFLAGS) \ + $(PANGO_CFLAGS) libgstclosedcaption_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ \ $(GST_BASE_LIBS) \ - $(GST_LIBS) + $(GST_LIBS) \ + $(PANGO_LIBS) libgstclosedcaption_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) diff --git a/ext/closedcaption/gstcea708decoder.c b/ext/closedcaption/gstcea708decoder.c new file mode 100644 index 0000000000..73a408086c --- /dev/null +++ b/ext/closedcaption/gstcea708decoder.c @@ -0,0 +1,1791 @@ +/* GStreamer + * Copyright (C) 2013 CableLabs, Louisville, CO 80027 + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * @Author: Chengjun Wang + * + * 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 +#include +#include +#include + +#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; +} diff --git a/ext/closedcaption/gstcea708decoder.h b/ext/closedcaption/gstcea708decoder.h new file mode 100644 index 0000000000..2d0609c226 --- /dev/null +++ b/ext/closedcaption/gstcea708decoder.h @@ -0,0 +1,485 @@ +/* GStreamer + * Copyright (C) 2013 CableLabs, Louisville, CO 80027 + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * @Author: Chengjun Wang + * + * 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 +#include + +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 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 "" +#define CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE " underline='single'" +#define CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC " style='italic'" +#define CEA708_PANGO_SPAN_ATTRIBUTES_FONT " font_desc=" +#define CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND " foreground=" +#define CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND " background=" +#define MINIMUM_OUTLINE_OFFSET 1.0 +#define WINDOW_IN_LIST_IS_ACTIVE(list) (list & 0x1) +typedef struct _Cea708Dec Cea708Dec; + +typedef enum +{ + COLOR_TYPE_BLACK = 0, + COLOR_TYPE_WHITE, + COLOR_TYPE_RED, + COLOR_TYPE_GREEN, + COLOR_TYPE_BLUE, + COLOR_TYPE_YELLOW, + COLOR_TYPE_MAGENTA, + COLOR_TYPE_CYAN, + COLOR_TYPE_RESEVER +} Cea708ColorType; + +typedef enum +{ + NO_CHANGE = 0, + SWITCH_TO_HIDE, + SWITCH_TO_SHOW, + TOGGLE +} VisibilityControl; + +typedef enum +{ + SOLID = 0, + FLASH, + TRANSLUCENT, + TRANSPARENT +} Opacity; + +typedef enum +{ + WIN_STYLE_NORMAL = 1, + WIN_STYLE_TRANSPARENT, + WIN_STYLE_NORMAL_CENTERED, + WIN_STYLE_NORMAL_WORD_WRAP, + WIN_STYLE_TRANSPARENT_WORD_WRAP, + WIN_STYLE_TRANSPARENT_CENTERED, + WIN_STYLE_ROTATED +} WindowStyle; + +typedef enum +{ + PEN_STYLE_DEFAULT = 1, + PEN_STYLE_MONO_SERIF, + PEN_STYLE_PROP_SERIF, + PEN_STYLE_MONO_SANS, + PEN_STYLE_PROP_SANS, + PEN_STYLE_MONO_SANS_TRANSPARENT, + PEN_STYLE_PROP_SANS_TRANSPARENT +} PenStyle; + +typedef enum +{ + ANCHOR_PT_TOP_LEFT = 0, + ANCHOR_PT_TOP_CENTER, + ANCHOR_PT_TOP_RIGHT, + ANCHOR_PT_MIDDLE_LEFT, + ANCHOR_PT_CENTER, + ANCHOR_PT_MIDDLE_RIGHT, + ANCHOR_PT_BOTTOM_LEFT, + ANCHOR_PT_BOTTOM_CENTER, + ANCHOR_PT_BOTTOM_RIGHT, +} AnchorPoint; + +typedef enum +{ + TAG_DIALOG = 0, + TAG_SPEAKER_ID, + TAG_ELECTRONIC_VOICE, + TAG_ALT_LANGUAGE_DIALOG, + TAG_VOICEOVER, + TAG_AUDIBLE_TRANSLATION, + TAG_SUBTITLE_TRANSLATION, + TAG_VOICE_QUALITY_DESCRIPTION, + TAG_SONG_LYRICS, + TAG_SOUND_EFFECT_DESCRIPTION, + TAG_MUSICAL_SCORE_DESCRIPTION, + TAG_EXPLETIVE, + TAG_UNDEF1, + TAG_UNDEF2, + TAG_UNDEF3, + TAG_NOT_DISPLAYED +} TagType; + +typedef enum +{ + JUSTIFY_LEFT = 0, + JUSTIFY_RIGHT, + JUSTIFY_CENTER, + JUSTIFY_FULL +} JUSTIFY_MODE; + +typedef enum +{ + PRINT_DIR_LEFT_TO_RIGHT = 0, + PRINT_DIR_RIGHT_TO_LEFT, + PRINT_DIR_TOP_TO_BOTTOM, + PRINT_DIR_BOTTOM_TO_TOP +} PRINT_DIRECTION; + +typedef enum +{ + SCROLL_DIR_LEFT_TO_RIGHT = 0, + SCROLL_DIR_RIGHT_TO_LEFT, + SCROLL_DIR_TOP_TO_BOTTOM, + SCROLL_DIR_BOTTOM_TO_TOP +} SCROLL_DIRECTION; + +typedef enum +{ + DISPLAY_EFFECT_SNAP = 0, + DISPLAY_EFFECT_FADE, + DISPLAY_EFFECT_WIPE +} DisplayEffect; + +typedef enum +{ + EFFECT_DIR_LEFT_TO_RIGHT = 0, + EFFECT_DIR_RIGHT_TO_LEFT, + EFFECT_DIR_TOP_TO_BOTTOM, + EFFECT_DIR_BOTTOM_TO_TOP +} EFFECT_DIRECTION; + +typedef enum +{ + BORDER_TYPE_NONE = 0, + BORDER_TYPE_RAISED, + BORDER_TYPE_DEPRESSED, + BORDER_TYPE_UNIFORM +} BORDER_TYPE; + +typedef enum +{ + PEN_SIZE_SMALL = 0, + PEN_SIZE_STANDARD, + PEN_SIZE_LARGE, + PEN_SIZE_INVALID +} PenSize; + +typedef enum +{ + PEN_OFFSET_SUBSCRIPT = 0, + PEN_OFFSET_NORMAL, + PEN_OFFSET_SUPERSCRIPT, + PEN_OFFSET_INVALID +} PenOffset; + +typedef enum +{ + EDGE_TYPE_NONE = 0, + EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED, + EDGE_TYPE_UNIFORM, + EDGE_TYPE_LEFT_DROP_SHADOW, + EDGE_TYPE_RIGHT_DROP_SHADOW, + EDGE_TYPE_INVALID_1, + EDGE_TYPE_INVALID_2 +} EdgeType; + +typedef enum +{ + FONT_STYLE_DEFAULT = 0, + FONT_STYLE_MONO_SERIF, + FONT_STYLE_PROP_SERIF, + FONT_STYLE_MONO_SANS, + FONT_STYLE_PROP_SANS, + FONT_STYLE_CASUAL, + FONT_STYLE_CURSIVE, + FONT_STYLE_SMALLCAPS +} FontStyle; + +typedef struct +{ + guint8 fg_color; + guint8 fg_opacity; + guint8 bg_color; + guint8 bg_opacity; + guint8 edge_color; +} cea708PenColor; + +typedef struct +{ + gboolean span_start_flag; + gboolean span_end_flag; + gboolean span_txt_flag; + + gboolean span_next_flag; + + gboolean underline; + gboolean italics; + + guint8 size; + guint8 fg_color; + guint8 bg_color; + FontStyle font_style; +} cea708PangoSpanControl; + +typedef struct +{ + PenSize pen_size; + FontStyle font_style; + TagType text_tag; + PenOffset offset; + gboolean italics; + gboolean underline; + EdgeType edge_type; +} cea708PenAttributes; + +/* The char records one cell location in the window, with the character and all of its attributes */ +typedef struct +{ + cea708PenColor pen_color; + cea708PenAttributes pen_attributes; + guint8 justify_mode; + gunichar c; +} cea708char; + + +/* This struct keeps track of one cea-708 CC window. There are up to 8. As new + * windows are created, the text they contain is visible on the screen (if the + * window visible flag is set). When a window is deleted, all text within the + * window is erased from the screen. Windows may be initialized and made visible + * then hidden. Each transition should cause new text cues to be emitted as + * text is displayed and removed from the screen. + */ +typedef struct +{ + /* The current attributes which will be used for the next text string */ + cea708PenColor pen_color; + cea708PenAttributes pen_attributes; + + /* true to indicate the window has not been created. + * set to true on delete, false on subsequent define command + * if true, reset pen position to 0,0 on window creation + */ + gboolean deleted; + + /* Text position */ + guint16 pen_row; + guint16 pen_col; + /* window display priority */ + guint8 priority; + /* window position on screen 0-8 */ + guint8 anchor_point; + /* 1 = anchor vertical/horizontal coordinates, 0 = physical screen coordinate, aka. rp */ + guint8 relative_position; + /* vertical position of windows anchor point, 0-74 or if rp=1 then 0-99 */ + guint8 anchor_vertical; + /* horz position of window anchor point, 0-209(16:9) 0-159(4:3) or if rp=1 then 0-99 */ + guint8 anchor_horizontal; + /* vert position of upper left corner of window */ + gfloat screen_vertical; + /* horz position of upper left corner of window */ + gfloat screen_horizontal; + /* virtual rows of text - 1, (ex. rc=2 means there are 3 rows) */ + guint8 row_count; + /* virtual columns of text, 0-41(16:9) 0-31(4:3) - 1 */ + guint8 column_count; + /* 1 = fixes #rows of caption text, 0 = more rows may be added */ + guint8 row_lock; + /* 1 = fixes #columns of caption text, 0 = more columns may be added */ + guint8 column_lock; + /* TRUE = window is visible, FALSE = window not visible */ + gboolean visible; + /* specifies 1 of 7 static preset window. attribute styles, during window create, + * 0 = use style #1, during window update, 0 = no window, attributes will be changed + */ + guint8 style_id; + /* specifies 1 of 7 static preset pen attributes, during window create, + * 0 = use pen style #1, during window update, 0 = do not change pen attributes + */ + guint8 pen_style_id; + /* timestamp when this window became visible */ + guint64 start_time; + + /* window attributes */ + guint8 justify_mode; + guint8 print_direction; + guint8 scroll_direction; + gboolean word_wrap; + guint8 display_effect; + guint8 effect_direction; + guint8 effect_speed; + guint8 fill_color; + guint8 fill_opacity; + guint8 border_type; + guint8 border_color; + + /* Character position offsets for the upper left corner of the window */ + guint v_offset; + guint h_offset; + + /* The char array that text is written into, using the current pen position */ + cea708char text[WINDOW_MAX_ROWS][WINDOW_MAX_COLS]; + + PangoLayout *layout; + gdouble shadow_offset; + gdouble outline_offset; + guchar *text_image; + gint image_width; + gint image_height; + gboolean updated; +} cea708Window; + +struct _Cea708Dec +{ + /* output data storage */ + GSList *text_list; + + /* simulation of 708 CC windows */ + cea708Window *cc_windows[MAX_708_WINDOWS]; + guint8 current_window; + gchar *default_font_desc; + PangoContext *pango_context; + + /* a counter used to ignore bytes in CC text stream following commands */ + gint8 output_ignore; + /* most recent timestamp from userdata */ + guint64 current_time; + + /* desired_service selects the service that will be decoded. If + * desired_service = -1 (default) no decoding based on service number will + * occur. Service #0 is reserved, and the valid range of service numbers + * is 1-7. with 1 being primary caption service and 2 being the secondary + * language service. If service_number is 7, then the extended_service_number is added and used instead of the service_number */ + gint8 desired_service; + + gboolean use_ARGB; + gint width; + gint height; +}; + +Cea708Dec *gst_cea708dec_create (PangoContext * pango_context); +void +gst_cea708dec_set_service_number (Cea708Dec * decoder, gint8 desired_service); +gboolean +gst_cea708dec_process_dtvcc_packet (Cea708Dec * decoder, guint8 * dtvcc_buffer, gsize dtvcc_size); +void +gst_cea708dec_set_video_width_height (Cea708Dec * decoder, gint width, gint height); +void gst_cea708_decoder_init_debug(void); + + G_END_DECLS +#endif /* __GST_CEA708_DEC_H__ */ diff --git a/ext/closedcaption/gstceaccoverlay.c b/ext/closedcaption/gstceaccoverlay.c new file mode 100644 index 0000000000..5fe051eec0 --- /dev/null +++ b/ext/closedcaption/gstceaccoverlay.c @@ -0,0 +1,1957 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * @Author: Chengjun Wang + * + * 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 +#endif + +#include +#include +#include + +#include "gstceaccoverlay.h" +#include + + +#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 "); + gst_cea708_decoder_init_debug (); + +} + +static void +gst_cea_cc_overlay_finalize (GObject * object) +{ + GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object); + + if (overlay->current_composition) { + gst_video_overlay_composition_unref (overlay->current_composition); + overlay->current_composition = NULL; + } + if (overlay->next_composition) { + gst_video_overlay_composition_unref (overlay->next_composition); + overlay->next_composition = NULL; + } + + g_mutex_clear (&overlay->lock); + g_cond_clear (&overlay->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_base_cea_cc_overlay_init (GstCeaCcOverlay * overlay, + GstCeaCcOverlayClass * klass) +{ + GstPadTemplate *template; + overlay->decoder = gst_cea708dec_create (GST_CEA_CC_OVERLAY_GET_CLASS + (overlay)->pango_context); + + /* video sink */ + template = gst_static_pad_template_get (&video_sink_template_factory); + overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink"); + gst_object_unref (template); + gst_pad_set_event_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_event)); + gst_pad_set_chain_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_chain)); + gst_pad_set_query_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_video_query)); + GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); + + template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "cc_sink"); + if (template) { + /* text sink */ + overlay->cc_sinkpad = gst_pad_new_from_template (template, "cc_sink"); + + gst_pad_set_event_function (overlay->cc_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_event)); + gst_pad_set_chain_function (overlay->cc_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_chain)); + gst_pad_set_link_function (overlay->cc_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_link)); + gst_pad_set_unlink_function (overlay->cc_sinkpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_cc_pad_unlink)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->cc_sinkpad); + } + + /* (video) source */ + template = gst_static_pad_template_get (&src_template_factory); + overlay->srcpad = gst_pad_new_from_template (template, "src"); + gst_object_unref (template); + gst_pad_set_event_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_event)); + gst_pad_set_query_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_cea_cc_overlay_src_query)); + gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); + + + overlay->silent = DEFAULT_PROP_SILENT; + overlay->need_update = TRUE; + overlay->current_composition = NULL; + overlay->next_composition = NULL; + overlay->cc_pad_linked = FALSE; + overlay->current_comp_start_time = GST_CLOCK_TIME_NONE; + overlay->next_comp_start_time = GST_CLOCK_TIME_NONE; + overlay->cea608_index[0] = 0; + overlay->cea608_index[1] = 0; + overlay->cea708_index = 0; + overlay->default_window_h_pos = DEFAULT_PROP_WINDOW_H_POS; + + g_mutex_init (&overlay->lock); + g_cond_init (&overlay->cond); + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); +} + +/* only negotiate/query video overlay composition support for now */ +static gboolean +gst_cea_cc_overlay_negotiate (GstCeaCcOverlay * overlay, GstCaps * caps) +{ + GstQuery *query; + gboolean attach = FALSE; + gboolean caps_has_meta = TRUE; + gboolean ret; + GstCapsFeatures *f; + GstCaps *original_caps; + gboolean original_has_meta = FALSE; + gboolean allocation_ret = TRUE; + + GST_DEBUG_OBJECT (overlay, "performing negotiation"); + + if (!caps) + caps = gst_pad_get_current_caps (overlay->video_sinkpad); + else + gst_caps_ref (caps); + + if (!caps || gst_caps_is_empty (caps)) + goto no_format; + + original_caps = caps; + + /* Try to use the overlay meta if possible */ + f = gst_caps_get_features (caps, 0); + + /* if the caps doesn't have the overlay meta, we query if downstream + * accepts it before trying the version without the meta + * If upstream already is using the meta then we can only use it */ + if (!f + || !gst_caps_features_contains (f, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) { + GstCaps *overlay_caps; + + /* In this case we added the meta, but we can work without it + * so preserve the original caps so we can use it as a fallback */ + overlay_caps = gst_caps_copy (caps); + + f = gst_caps_get_features (overlay_caps, 0); + gst_caps_features_add (f, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); + + ret = gst_pad_peer_query_accept_caps (overlay->srcpad, overlay_caps); + GST_DEBUG_OBJECT (overlay, "Downstream accepts the overlay meta: %d", ret); + if (ret) { + gst_caps_unref (caps); + caps = overlay_caps; + + } else { + /* fallback to the original */ + gst_caps_unref (overlay_caps); + caps_has_meta = FALSE; + } + } else { + original_has_meta = TRUE; + } + GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps); + ret = gst_pad_set_caps (overlay->srcpad, caps); + + if (ret) { + /* find supported meta */ + query = gst_query_new_allocation (caps, FALSE); + + if (!gst_pad_peer_query (overlay->srcpad, query)) { + /* no problem, we use the query defaults */ + GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed"); + allocation_ret = FALSE; + } + + if (caps_has_meta && gst_query_find_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL)) + attach = TRUE; + gst_query_unref (query); + } + + overlay->attach_compo_to_buffer = attach; + + if (!allocation_ret && overlay->video_flushing) { + ret = FALSE; + } else if (original_caps && !original_has_meta && !attach) { + if (caps_has_meta) { + /* Some elements (fakesink) claim to accept the meta on caps but won't + put it in the allocation query result, this leads below + check to fail. Prevent this by removing the meta from caps */ + gst_caps_unref (caps); + caps = gst_caps_ref (original_caps); + ret = gst_pad_set_caps (overlay->srcpad, caps); + if (ret && !gst_cea_cc_overlay_can_handle_caps (caps)) + ret = FALSE; + } + } + + if (!ret) { + GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure"); + gst_pad_mark_reconfigure (overlay->srcpad); + } + gst_caps_unref (caps); + GST_DEBUG_OBJECT (overlay, "ret=%d", ret); + + return ret; + +no_format: + { + if (caps) + gst_caps_unref (caps); + return FALSE; + } +} + +static gboolean +gst_cea_cc_overlay_can_handle_caps (GstCaps * incaps) +{ + gboolean ret; + GstCaps *caps; + static GstStaticCaps static_caps = GST_STATIC_CAPS (CC_OVERLAY_CAPS); + + caps = gst_static_caps_get (&static_caps); + ret = gst_caps_is_subset (incaps, caps); + gst_caps_unref (caps); + + return ret; +} + +static gboolean +gst_cea_cc_overlay_setcaps (GstCeaCcOverlay * overlay, GstCaps * caps) +{ + GstVideoInfo info; + gboolean ret = FALSE; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + overlay->info = info; + overlay->format = GST_VIDEO_INFO_FORMAT (&info); + overlay->width = GST_VIDEO_INFO_WIDTH (&info); + overlay->height = GST_VIDEO_INFO_HEIGHT (&info); + gst_cea708dec_set_video_width_height (overlay->decoder, overlay->width, + overlay->height); + ret = gst_cea_cc_overlay_negotiate (overlay, caps); + + GST_CEA_CC_OVERLAY_LOCK (overlay); + g_mutex_lock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock); + if (!overlay->attach_compo_to_buffer && + !gst_cea_cc_overlay_can_handle_caps (caps)) { + GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps); + ret = FALSE; + } + + g_mutex_unlock (GST_CEA_CC_OVERLAY_GET_CLASS (overlay)->pango_lock); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + + return ret; + + /* ERRORS */ +invalid_caps: + { + GST_DEBUG_OBJECT (overlay, "could not parse caps"); + return FALSE; + } +} + +static void +gst_cea_cc_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object); + Cea708Dec *decoder = overlay->decoder; + + GST_CEA_CC_OVERLAY_LOCK (overlay); + switch (prop_id) { + case PROP_SERVICE_NUMBER: + { + int desired_service = g_value_get_int (value); + gst_cea708dec_set_service_number (decoder, desired_service); + break; + } + case PROP_FONT_DESC: + { + PangoFontDescription *desc = NULL; + const gchar *fontdesc_str; + fontdesc_str = g_value_get_string (value); + + GST_LOG_OBJECT (overlay, "Got font description '%s'", fontdesc_str); + if (fontdesc_str) + desc = pango_font_description_from_string (fontdesc_str); + /* Only set if NULL or valid description */ + if (desc || !fontdesc_str) { + if (desc) { + GST_INFO_OBJECT (overlay, "Setting font description: '%s'", + fontdesc_str); + pango_font_description_free (desc); + } else + GST_INFO_OBJECT (overlay, "Resetting default font description"); + g_free (decoder->default_font_desc); + decoder->default_font_desc = g_strdup (fontdesc_str); + } + break; + } + case PROP_SILENT: + overlay->silent = g_value_get_boolean (value); + break; + case PROP_WINDOW_H_POS: + overlay->default_window_h_pos = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + overlay->need_update = TRUE; + GST_CEA_CC_OVERLAY_UNLOCK (overlay); +} + +static void +gst_cea_cc_overlay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (object); + Cea708Dec *decoder = overlay->decoder; + + GST_CEA_CC_OVERLAY_LOCK (overlay); + switch (prop_id) { + case PROP_SERVICE_NUMBER: + g_value_set_int (value, decoder->desired_service); + break; + case PROP_SILENT: + g_value_set_boolean (value, overlay->silent); + break; + case PROP_FONT_DESC: + g_value_set_string (value, decoder->default_font_desc); + break; + case PROP_WINDOW_H_POS: + g_value_set_enum (value, overlay->default_window_h_pos); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_CEA_CC_OVERLAY_UNLOCK (overlay); +} + +static gboolean +gst_cea_cc_overlay_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean ret = FALSE; + GstCeaCcOverlay *overlay; + + overlay = GST_CEA_CC_OVERLAY (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CAPS: + { + GstCaps *filter, *caps; + + gst_query_parse_caps (query, &filter); + caps = gst_cea_cc_overlay_get_src_caps (pad, overlay, filter); + gst_query_set_caps_result (query, caps); + gst_caps_unref (caps); + ret = TRUE; + break; + } + default: + ret = gst_pad_query_default (pad, parent, query); + break; + } + + return ret; +} + +static gboolean +gst_cea_cc_overlay_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + GstCeaCcOverlay *overlay; + gboolean ret; + + overlay = GST_CEA_CC_OVERLAY (parent); + + if (overlay->cc_pad_linked) { + gst_event_ref (event); + ret = gst_pad_push_event (overlay->video_sinkpad, event); + gst_pad_push_event (overlay->cc_sinkpad, event); + } else { + ret = gst_pad_push_event (overlay->video_sinkpad, event); + } + + return ret; +} + +/** + * gst_cea_cc_overlay_add_feature_and_intersect: + * + * Creates a new #GstCaps containing the (given caps + + * given caps feature) + (given caps intersected by the + * given filter). + * + * Returns: the new #GstCaps + */ +static GstCaps * +gst_cea_cc_overlay_add_feature_and_intersect (GstCaps * caps, + const gchar * feature, GstCaps * filter) +{ + int i, caps_size; + GstCaps *new_caps; + + new_caps = gst_caps_copy (caps); + + caps_size = gst_caps_get_size (new_caps); + for (i = 0; i < caps_size; i++) { + GstCapsFeatures *features = gst_caps_get_features (new_caps, i); + + if (!gst_caps_features_is_any (features)) { + gst_caps_features_add (features, feature); + } + } + + gst_caps_append (new_caps, gst_caps_intersect_full (caps, + filter, GST_CAPS_INTERSECT_FIRST)); + + return new_caps; +} + +/** + * gst_cea_cc_overlay_intersect_by_feature: + * + * Creates a new #GstCaps based on the following filtering rule. + * + * For each individual caps contained in given caps, if the + * caps uses the given caps feature, keep a version of the caps + * with the feature and an another one without. Otherwise, intersect + * the caps with the given filter. + * + * Returns: the new #GstCaps + */ +static GstCaps * +gst_cea_cc_overlay_intersect_by_feature (GstCaps * caps, + const gchar * feature, GstCaps * filter) +{ + int i, caps_size; + GstCaps *new_caps; + + new_caps = gst_caps_new_empty (); + + caps_size = gst_caps_get_size (caps); + for (i = 0; i < caps_size; i++) { + GstStructure *caps_structure = gst_caps_get_structure (caps, i); + GstCapsFeatures *caps_features = + gst_caps_features_copy (gst_caps_get_features (caps, i)); + GstCaps *filtered_caps; + GstCaps *simple_caps = + gst_caps_new_full (gst_structure_copy (caps_structure), NULL); + gst_caps_set_features (simple_caps, 0, caps_features); + + if (gst_caps_features_contains (caps_features, feature)) { + gst_caps_append (new_caps, gst_caps_copy (simple_caps)); + + gst_caps_features_remove (caps_features, feature); + filtered_caps = gst_caps_ref (simple_caps); + } else { + filtered_caps = gst_caps_intersect_full (simple_caps, filter, + GST_CAPS_INTERSECT_FIRST); + } + gst_caps_unref (simple_caps); + gst_caps_append (new_caps, filtered_caps); + } + + return new_caps; +} + +static GstCaps * +gst_cea_cc_overlay_get_videosink_caps (GstPad * pad, + GstCeaCcOverlay * overlay, GstCaps * filter) +{ + GstPad *srcpad = overlay->srcpad; + GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL; + + if (G_UNLIKELY (!overlay)) + return gst_pad_get_pad_template_caps (pad); + + if (filter) { + /* filter caps + composition feature + filter caps + * filtered by the software caps. */ + GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps); + overlay_filter = gst_cea_cc_overlay_add_feature_and_intersect (filter, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps); + gst_caps_unref (sw_caps); + + GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT, + overlay_filter); + } + + peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter); + if (overlay_filter) + gst_caps_unref (overlay_filter); + if (peer_caps) { + + GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps); + + if (gst_caps_is_any (peer_caps)) { + /* if peer returns ANY caps, return filtered src pad template caps */ + caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad)); + } else { + + /* duplicate caps which contains the composition into one version with + * the meta and one without. Filter the other caps by the software caps */ + GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps); + caps = gst_cea_cc_overlay_intersect_by_feature (peer_caps, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps); + gst_caps_unref (sw_caps); + } + + gst_caps_unref (peer_caps); + + } else { + /* no peer, our padtemplate is enough then */ + caps = gst_pad_get_pad_template_caps (pad); + } + + if (filter) { + GstCaps *intersection = gst_caps_intersect_full (filter, caps, + GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = intersection; + } + + GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps); + + return caps; +} + +static GstCaps * +gst_cea_cc_overlay_get_src_caps (GstPad * pad, GstCeaCcOverlay * overlay, + GstCaps * filter) +{ + GstPad *sinkpad = overlay->video_sinkpad; + GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL; + + if (G_UNLIKELY (!overlay)) + return gst_pad_get_pad_template_caps (pad); + + if (filter) { + /* duplicate filter caps which contains the composition into one version + * with the meta and one without. Filter the other caps by the software + * caps */ + GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps); + overlay_filter = + gst_cea_cc_overlay_intersect_by_feature (filter, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps); + gst_caps_unref (sw_caps); + } + + peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter); + + if (overlay_filter) + gst_caps_unref (overlay_filter); + + if (peer_caps) { + + GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps); + + if (gst_caps_is_any (peer_caps)) { + + /* if peer returns ANY caps, return filtered sink pad template caps */ + caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad)); + + } else { + + /* return upstream caps + composition feature + upstream caps + * filtered by the software caps. */ + GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps); + caps = gst_cea_cc_overlay_add_feature_and_intersect (peer_caps, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps); + gst_caps_unref (sw_caps); + } + + gst_caps_unref (peer_caps); + + } else { + /* no peer, our padtemplate is enough then */ + caps = gst_pad_get_pad_template_caps (pad); + } + + if (filter) { + GstCaps *intersection; + + intersection = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = intersection; + } + GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps); + + return caps; +} + +/* FIXME: should probably be relative to width/height (adjusted for PAR) */ +#define BOX_XPAD 6 +#define BOX_YPAD 6 + +static GstFlowReturn +gst_cea_cc_overlay_push_frame (GstCeaCcOverlay * overlay, + GstBuffer * video_frame) +{ + GstVideoFrame frame; + + if (overlay->current_composition == NULL) + goto done; + GST_LOG_OBJECT (overlay, "gst_cea_cc_overlay_push_frame"); + + if (gst_pad_check_reconfigure (overlay->srcpad)) + gst_cea_cc_overlay_negotiate (overlay, NULL); + + video_frame = gst_buffer_make_writable (video_frame); + + if (overlay->attach_compo_to_buffer) { + GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer"); + gst_buffer_add_video_overlay_composition_meta (video_frame, + overlay->current_composition); + goto done; + } + + if (!gst_video_frame_map (&frame, &overlay->info, video_frame, + GST_MAP_READWRITE)) + goto invalid_frame; + + gst_video_overlay_composition_blend (overlay->current_composition, &frame); + + gst_video_frame_unmap (&frame); + +done: + + return gst_pad_push (overlay->srcpad, video_frame); + + /* ERRORS */ +invalid_frame: + { + gst_buffer_unref (video_frame); + return GST_FLOW_OK; + } +} + +static GstPadLinkReturn +gst_cea_cc_overlay_cc_pad_link (GstPad * pad, GstObject * parent, GstPad * peer) +{ + GstCeaCcOverlay *overlay; + + overlay = GST_CEA_CC_OVERLAY (parent); + if (G_UNLIKELY (!overlay)) + return GST_PAD_LINK_REFUSED; + + GST_DEBUG_OBJECT (overlay, "Closed Caption pad linked"); + + overlay->cc_pad_linked = TRUE; + + return GST_PAD_LINK_OK; +} + +static void +gst_cea_cc_overlay_cc_pad_unlink (GstPad * pad, GstObject * parent) +{ + GstCeaCcOverlay *overlay; + + /* don't use gst_pad_get_parent() here, will deadlock */ + overlay = GST_CEA_CC_OVERLAY (parent); + + GST_DEBUG_OBJECT (overlay, "Closed Caption pad unlinked"); + + overlay->cc_pad_linked = FALSE; + + gst_segment_init (&overlay->cc_segment, GST_FORMAT_UNDEFINED); +} + +static gboolean +gst_cea_cc_overlay_cc_event (GstPad * pad, GstObject * parent, GstEvent * event) +{ + gboolean ret = FALSE; + GstCeaCcOverlay *overlay = NULL; + + overlay = GST_CEA_CC_OVERLAY (parent); + + GST_LOG_OBJECT (overlay, "received event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GstCaps *caps; + GstStructure *st; + const gchar *cctype; + + gst_event_parse_caps (event, &caps); + st = gst_caps_get_structure (caps, 0); + cctype = gst_structure_get_string (st, "format"); + overlay->is_cdp = !g_strcmp0 (cctype, "cdp"); + ret = TRUE; + break; + } + case GST_EVENT_SEGMENT: + { + const GstSegment *segment; + + overlay->cc_eos = FALSE; + + gst_event_parse_segment (event, &segment); + + if (segment->format == GST_FORMAT_TIME) { + GST_CEA_CC_OVERLAY_LOCK (overlay); + gst_segment_copy_into (segment, &overlay->cc_segment); + GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT, + &overlay->cc_segment); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + } else { + GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), + ("received non-TIME newsegment event on text input")); + } + + gst_event_unref (event); + ret = TRUE; + + /* wake up the video chain, it might be waiting for a text buffer or + * a text segment update */ + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + break; + } + case GST_EVENT_GAP: + { + GstClockTime start, duration; + + gst_event_parse_gap (event, &start, &duration); + if (GST_CLOCK_TIME_IS_VALID (duration)) + start += duration; + /* we do not expect another buffer until after gap, + * so that is our position now */ + overlay->cc_segment.position = start; + + /* wake up the video chain, it might be waiting for a text buffer or + * a text segment update */ + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + + gst_event_unref (event); + ret = TRUE; + break; + } + case GST_EVENT_FLUSH_STOP: + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_INFO_OBJECT (overlay, "text flush stop"); + overlay->cc_flushing = FALSE; + overlay->cc_eos = FALSE; + gst_cea_cc_overlay_pop_text (overlay); + gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + case GST_EVENT_FLUSH_START: + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_INFO_OBJECT (overlay, "text flush start"); + overlay->cc_flushing = TRUE; + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + case GST_EVENT_EOS: + GST_CEA_CC_OVERLAY_LOCK (overlay); + overlay->cc_eos = TRUE; + GST_INFO_OBJECT (overlay, "closed caption EOS"); + /* wake up the video chain, it might be waiting for a text buffer or + * a text segment update */ + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + gst_event_unref (event); + ret = TRUE; + break; + default: + ret = gst_pad_event_default (pad, parent, event); + break; + } + + return ret; +} + +static gboolean +gst_cea_cc_overlay_video_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean ret = FALSE; + GstCeaCcOverlay *overlay = NULL; + + overlay = GST_CEA_CC_OVERLAY (parent); + + GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + ret = gst_cea_cc_overlay_setcaps (overlay, caps); + gst_event_unref (event); + break; + } + case GST_EVENT_SEGMENT: + { + const GstSegment *segment; + + GST_DEBUG_OBJECT (overlay, "received new segment"); + + gst_event_parse_segment (event, &segment); + + if (segment->format == GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT, + &overlay->segment); + + gst_segment_copy_into (segment, &overlay->segment); + } else { + GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL), + ("received non-TIME newsegment event on video input")); + } + + ret = gst_pad_event_default (pad, parent, event); + break; + } + case GST_EVENT_EOS: + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video EOS"); + overlay->video_eos = TRUE; + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_pad_event_default (pad, parent, event); + break; + case GST_EVENT_FLUSH_START: + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video flush start"); + overlay->video_flushing = TRUE; + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_pad_event_default (pad, parent, event); + break; + case GST_EVENT_FLUSH_STOP: + GST_CEA_CC_OVERLAY_LOCK (overlay); + GST_INFO_OBJECT (overlay, "video flush stop"); + overlay->video_flushing = FALSE; + overlay->video_eos = FALSE; + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_pad_event_default (pad, parent, event); + break; + default: + ret = gst_pad_event_default (pad, parent, event); + break; + } + + return ret; +} + +static gboolean +gst_cea_cc_overlay_video_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean ret = FALSE; + GstCeaCcOverlay *overlay; + + overlay = GST_CEA_CC_OVERLAY (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CAPS: + { + GstCaps *filter, *caps; + + gst_query_parse_caps (query, &filter); + caps = gst_cea_cc_overlay_get_videosink_caps (pad, overlay, filter); + gst_query_set_caps_result (query, caps); + gst_caps_unref (caps); + ret = TRUE; + break; + } + default: + ret = gst_pad_query_default (pad, parent, query); + break; + } + + return ret; +} + +/* Called with lock held */ +static void +gst_cea_cc_overlay_pop_text (GstCeaCcOverlay * overlay) +{ + g_return_if_fail (GST_IS_CEA_CC_OVERLAY (overlay)); + + if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time) + && overlay->current_composition) { + GST_DEBUG_OBJECT (overlay, "releasing composition %p", + overlay->current_composition); + gst_video_overlay_composition_unref (overlay->current_composition); + overlay->current_composition = NULL; + overlay->current_comp_start_time = GST_CLOCK_TIME_NONE; + } + + /* Let the text task know we used that buffer */ + GST_CEA_CC_OVERLAY_BROADCAST (overlay); +} + +static void +gst_cea_cc_overlay_image_to_argb (guchar * pixbuf, + cea708Window * window, int stride) +{ + int i, j; + guchar *p, *bitp; + int width, height; + + width = window->image_width; + height = window->image_height; + + for (i = 0; i < height; i++) { + p = pixbuf + i * stride; + bitp = window->text_image + i * width * 4; + + for (j = 0; j < width; j++) { + p[0] = bitp[CAIRO_ARGB_A]; + p[1] = bitp[CAIRO_ARGB_R]; + p[2] = bitp[CAIRO_ARGB_G]; + p[3] = bitp[CAIRO_ARGB_B]; + + /* Cairo uses pre-multiplied ARGB, unpremultiply it */ + CAIRO_UNPREMULTIPLY (p[0], p[1], p[2], p[3]); + + bitp += 4; + p += 4; + } + } +} + +static void +gst_cea_cc_overlay_image_to_ayuv (guchar * pixbuf, + cea708Window * window, int stride) +{ + int y; /* text bitmap coordinates */ + guchar *p, *bitp; + guchar a, r, g, b; + int width, height; + + width = window->image_width; + height = window->image_height; + + for (y = 0; y < height; y++) { + int n; + p = pixbuf + y * stride; + bitp = window->text_image + y * width * 4; + + for (n = 0; n < width; n++) { + b = bitp[CAIRO_ARGB_B]; + g = bitp[CAIRO_ARGB_G]; + r = bitp[CAIRO_ARGB_R]; + a = bitp[CAIRO_ARGB_A]; + bitp += 4; + + /* Cairo uses pre-multiplied ARGB, unpremultiply it */ + CAIRO_UNPREMULTIPLY (a, r, g, b); + + *p++ = a; + *p++ = CLAMP ((int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + + ((7471 * b) >> 16)), 0, 255); + *p++ = CLAMP ((int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + + ((32768 * b) >> 16) + 128), 0, 255); + *p++ = CLAMP ((int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - + ((5329 * b) >> 16) + 128), 0, 255); + } + } +} + +static void +gst_cea_cc_overlay_create_and_push_buffer (GstCeaCcOverlay * overlay) +{ + Cea708Dec *decoder = overlay->decoder; + GstBuffer *outbuf; + GstMapInfo map; + guint8 *window_image; + gint n; + guint window_id; + cea708Window *window; + guint v_anchor = 0; + guint h_anchor = 0; + GstVideoOverlayComposition *comp = NULL; + GstVideoOverlayRectangle *rect = NULL; + GST_CEA_CC_OVERLAY_LOCK (overlay); + + for (window_id = 0; window_id < 8; window_id++) { + window = decoder->cc_windows[window_id]; + + if (!window->updated) { + continue; + } + if (!window->deleted && window->visible && window->text_image != NULL) { + GST_DEBUG_OBJECT (overlay, "Allocating buffer"); + outbuf = + gst_buffer_new_and_alloc (window->image_width * + window->image_height * 4); + gst_buffer_map (outbuf, &map, GST_MAP_WRITE); + window_image = map.data; + if (decoder->use_ARGB) { + memset (window_image, 0, + window->image_width * window->image_height * 4); + gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, window->image_width, + window->image_height); + } else { + for (n = 0; n < window->image_width * window->image_height; n++) { + window_image[n * 4] = window_image[n * 4 + 1] = 0; + window_image[n * 4 + 2] = window_image[n * 4 + 3] = 128; + } + gst_buffer_add_video_meta (outbuf, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_YUV, window->image_width, + window->image_height); + } + + v_anchor = window->screen_vertical * overlay->height / 100; + switch (overlay->default_window_h_pos) { + case GST_CEA_CC_OVERLAY_WIN_H_LEFT: + window->h_offset = 0; + break; + case GST_CEA_CC_OVERLAY_WIN_H_CENTER: + window->h_offset = (overlay->width - window->image_width) / 2; + break; + case GST_CEA_CC_OVERLAY_WIN_H_RIGHT: + window->h_offset = overlay->width - window->image_width; + break; + case GST_CEA_CC_OVERLAY_WIN_H_AUTO: + default: + switch (window->anchor_point) { + case ANCHOR_PT_TOP_LEFT: + case ANCHOR_PT_MIDDLE_LEFT: + case ANCHOR_PT_BOTTOM_LEFT: + window->h_offset = h_anchor; + break; + + case ANCHOR_PT_TOP_CENTER: + case ANCHOR_PT_CENTER: + case ANCHOR_PT_BOTTOM_CENTER: + window->h_offset = h_anchor - window->image_width / 2; + break; + + case ANCHOR_PT_TOP_RIGHT: + case ANCHOR_PT_MIDDLE_RIGHT: + case ANCHOR_PT_BOTTOM_RIGHT: + window->h_offset = h_anchor - window->image_width; + break; + default: + break; + } + break; + } + + switch (window->anchor_point) { + case ANCHOR_PT_TOP_LEFT: + case ANCHOR_PT_TOP_CENTER: + case ANCHOR_PT_TOP_RIGHT: + window->v_offset = v_anchor; + break; + + case ANCHOR_PT_MIDDLE_LEFT: + case ANCHOR_PT_CENTER: + case ANCHOR_PT_MIDDLE_RIGHT: + window->v_offset = v_anchor - window->image_height / 2; + break; + + case ANCHOR_PT_BOTTOM_LEFT: + case ANCHOR_PT_BOTTOM_CENTER: + case ANCHOR_PT_BOTTOM_RIGHT: + window->v_offset = v_anchor - window->image_height; + break; + default: + break; + } + if (decoder->use_ARGB) { + gst_cea_cc_overlay_image_to_argb (window_image, window, + window->image_width * 4); + } else { + gst_cea_cc_overlay_image_to_ayuv (window_image, window, + window->image_width * 4); + } + gst_buffer_unmap (outbuf, &map); + GST_INFO_OBJECT (overlay, + "window->anchor_point=%d,v_anchor=%d,h_anchor=%d,window->image_height=%d,window->image_width=%d, window->v_offset=%d, window->h_offset=%d,window->justify_mode=%d", + window->anchor_point, v_anchor, h_anchor, window->image_height, + window->image_width, window->v_offset, window->h_offset, + window->justify_mode); + rect = + gst_video_overlay_rectangle_new_raw (outbuf, window->h_offset, + window->v_offset, window->image_width, window->image_height, 0); + if (comp == NULL) { + comp = gst_video_overlay_composition_new (rect); + } else { + gst_video_overlay_composition_add_rectangle (comp, rect); + } + gst_video_overlay_rectangle_unref (rect); + gst_buffer_unref (outbuf); + } + } + + /* Wait for the previous buffer to go away */ + if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) { + overlay->next_composition = comp; + overlay->next_comp_start_time = decoder->current_time; + GST_DEBUG_OBJECT (overlay, + "wait for render next %p, current is %p BUFFER: next ts=%" + GST_TIME_FORMAT ",current ts=%" GST_TIME_FORMAT, + overlay->next_composition, overlay->current_composition, + GST_TIME_ARGS (overlay->next_comp_start_time), + GST_TIME_ARGS (overlay->current_comp_start_time)); + + GST_DEBUG_OBJECT (overlay, "has a closed caption buffer queued, waiting"); + GST_CEA_CC_OVERLAY_WAIT (overlay); + GST_DEBUG_OBJECT (overlay, "resuming"); + if (overlay->cc_flushing) { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + return; + } + } + + overlay->next_composition = NULL; + overlay->next_comp_start_time = GST_CLOCK_TIME_NONE; + overlay->current_composition = comp; + overlay->current_comp_start_time = decoder->current_time; + GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT, + GST_TIME_ARGS (overlay->current_comp_start_time)); + overlay->need_update = FALSE; + + /* in case the video chain is waiting for a text buffer, wake it up */ + GST_CEA_CC_OVERLAY_BROADCAST (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); +} + +static void +gst_cea_cc_overlay_process_packet (GstCeaCcOverlay * overlay, guint8 cc_type) +{ + gint16 *index = NULL; + guint8 *buffer = NULL; + guint8 *dtvcc_buffer = NULL; + gboolean need_render = FALSE; + + switch (cc_type) { + case CCTYPE_608_CC1: + case CCTYPE_608_CC2: + index = &overlay->cea608_index[cc_type]; + buffer = overlay->cea608_buffer[cc_type]; + break; + + case CCTYPE_708_ADD: + case CCTYPE_708_START: + index = &overlay->cea708_index; + buffer = overlay->cea708_buffer; + break; + default: + GST_ERROR_OBJECT (overlay, + "attempted to process packet for unknown cc_type %d", cc_type); + return; + } + + if (*index > 0) { + /*TODO: in future need add 608 decoder, currently only deal with 708 */ + if (cc_type == CCTYPE_708_ADD || cc_type == CCTYPE_708_START) { + GST_LOG_OBJECT (overlay, + "called - buf[%" G_GINT16_FORMAT "] = %02X:%02X:%02X:%02X", *index, + buffer[0], buffer[1], buffer[2], buffer[3]); + dtvcc_buffer = g_malloc0 (*index + 1); + memcpy (dtvcc_buffer, buffer, *index); + need_render = + gst_cea708dec_process_dtvcc_packet (overlay->decoder, dtvcc_buffer, + *index); + g_free (dtvcc_buffer); + if (need_render) + gst_cea_cc_overlay_create_and_push_buffer (overlay); + } + } + *index = 0; +} + + +/** + * gst_cea_cc_overlay_user_data_decode: + * @overlay: The #GstCeaCcOverlay + * @user_data: The #GstMpegVideoCCData to decode + * + * decode closed caption data and render when neccesary + * in struct GstMpegVideoCCData type's user_data's data field, 3 byte's data construct 1 cc_data_pkt + * + * A cc_data_pkt is 3 bytes as follows: + * ------------------------------------------- + * 5 bits (b7-b3) marker_bits (should be all 1's) + * 1 bit (b2) cc_valid + * 2 bits (b1-b0) cc_type (bslbf) + * 8 bits cc_data_1 (bslbf) + * 8 bits cc_data_2 (bslbf) + * + * If cc_valid != 1, then ignore this packet + * + * cc_type has these values: + * 0 NTSC_CC_FIELD_1 - CEA-608 + * 1 NTSC_CC_FIELD_2 - CEA-608 + * 2 DTVCC_PACKET_DATA - CEA-708 + * 3 DTVCC_PACKET_START - CEA-708 + * + * DTVCC packet (aka. caption channel packet) + * This is formed by accumulating cc_data_1/cc_data_2 from each cc_data_pkt + * starting with a packet where cc_type = 3, and ending with a packet + * where again cc_type = 3 (start of next buffer), or cc_valid=0 && cc_type=2 + * DTVCC packet's structure is: + * -------------------------------------------------------------------------- + * 2 bits (b6-b7) sequence_number + * 6 bits (b0-b5) packet_size + * ((packet_size*2-1)&0xFF) * 8 bits packet_data (Service Block) + */ +static void +gst_cea_cc_overlay_user_data_decode (GstCeaCcOverlay * overlay, + const guint8 * ccdata, gsize ccsize) +{ + guint8 temp; + guint8 cc_count; + guint i; + guint8 cc_type; + guint8 cc_valid; + guint8 cc_data[2]; + + cc_count = ccsize / 3; + + for (i = 0; i < cc_count; i++) { + temp = *ccdata++; + cc_data[0] = *ccdata++; + cc_data[1] = *ccdata++; + cc_valid = (temp & CCTYPE_VALID_MASK) ? TRUE : FALSE; + cc_type = (temp & CCTYPE_TYPE_MASK); + + GST_LOG_OBJECT (overlay, "cc_data_pkt(%d): cc_valid=%d cc_type=%d " + "cc_data[0]=0x%02X cc_data[1]=0x%02X", + i, cc_valid, cc_type, cc_data[0], cc_data[1]); + + /* accumulate dvtcc packet */ + switch (cc_type) { + case CCTYPE_608_CC1: + case CCTYPE_608_CC2: + if (cc_valid) { + if (overlay->cea608_index[cc_type] <= DTVCC_LENGTH - 2) { + for (size_t j = 0; j < 2; ++j) { + if ((cc_data[j] < ' ') || (cc_data[j] > '~')) { + gst_cea_cc_overlay_process_packet (overlay, cc_type); + } + overlay->cea608_buffer[cc_type][overlay-> + cea608_index[cc_type]++] = cc_data[j]; + } + } else { + GST_ERROR_OBJECT (overlay, "cea608_buffer[%d] overflow!", cc_type); + } + } + break; + + case CCTYPE_708_ADD: + case CCTYPE_708_START: + if (cc_valid) { + if (cc_type == CCTYPE_708_START) { + /* The previous packet is complete */ + gst_cea_cc_overlay_process_packet (overlay, cc_type); + } + /* Add on to the current DTVCC packet */ + if (overlay->cea708_index <= DTVCC_LENGTH - 2) { + overlay->cea708_buffer[overlay->cea708_index++] = cc_data[0]; + overlay->cea708_buffer[overlay->cea708_index++] = cc_data[1]; + } else { + GST_ERROR_OBJECT (overlay, "cea708_buffer overflow!"); + } + } else if (cc_type == CCTYPE_708_ADD) { + /* This packet should be ignored, but if there is a current */ + /* DTVCC packet then this is the end. */ + gst_cea_cc_overlay_process_packet (overlay, cc_type); + } + break; + } + } +} + +/* FIXME : Move to GstVideo ANC/CC helper library */ +static gboolean +extract_ccdata_from_cdp (const guint8 * indata, gsize insize, + const guint8 ** ccdata, gsize * ccsize) +{ + GstByteReader br; + guint8 cdp_length; + guint8 framerate_code; + guint8 flags; + guint16 seqhdr; + + GST_MEMDUMP ("CDP", indata, insize); + + gst_byte_reader_init (&br, indata, insize); + + /* The smallest valid CDP we are interested in is 7 (header) + 2 (cc + * section) + 4 (footer) bytes long */ + if (gst_byte_reader_get_remaining (&br) < 13) + return FALSE; + + /* Check header */ + if (gst_byte_reader_get_uint16_be_unchecked (&br) != 0x9669) { + GST_WARNING ("Invalid CDP header"); + return FALSE; + } + cdp_length = gst_byte_reader_get_uint8_unchecked (&br); + if (cdp_length > insize) { + GST_WARNING ("CDP too small (need %d bytes, have %" G_GSIZE_FORMAT ")", + cdp_length, insize); + return FALSE; + } + framerate_code = gst_byte_reader_get_uint8_unchecked (&br) >> 4; + flags = gst_byte_reader_get_uint8_unchecked (&br); + seqhdr = gst_byte_reader_get_uint16_be_unchecked (&br); + + GST_DEBUG + ("framerate_code : 0x%02x , flags : 0x%02x , sequencer_counter : %u", + framerate_code, flags, seqhdr); + + /* Skip timecode if present */ + if (flags & 0x80) { + GST_LOG ("Skipping timecode section"); + gst_byte_reader_skip (&br, 5); + } + + /* cc data */ + if (flags & 0x40) { + guint8 ccid, cc_count; + if (!gst_byte_reader_get_uint8 (&br, &ccid) || + !gst_byte_reader_get_uint8 (&br, &cc_count)) + return FALSE; + if (ccid != 0x72) { + GST_WARNING ("Invalid ccdata_id (expected 0x72, got 0x%02x)", ccid); + return FALSE; + } + cc_count &= 0x1f; + if (!gst_byte_reader_get_data (&br, cc_count * 3, ccdata)) { + GST_WARNING ("Not enough ccdata"); + *ccdata = NULL; + *ccsize = 0; + return FALSE; + } + *ccsize = cc_count * 3; + } + + /* FIXME : Parse/validate the rest of the CDP ! */ + + return TRUE; +} + +/* We receive text buffers here. If they are out of segment we just ignore them. + If the buffer is in our segment we keep it internally except if another one + is already waiting here, in that case we wait that it gets kicked out */ +static GstFlowReturn +gst_cea_cc_overlay_cc_chain (GstPad * pad, GstObject * parent, + GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstCeaCcOverlay *overlay = (GstCeaCcOverlay *) parent; + gboolean in_seg = FALSE; + guint64 clip_start = 0, clip_stop = 0; + + GST_CEA_CC_OVERLAY_LOCK (overlay); + + if (overlay->cc_flushing) { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = GST_FLOW_FLUSHING; + GST_LOG_OBJECT (overlay, "closed caption flushing"); + goto beach; + } + + if (overlay->cc_eos) { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = GST_FLOW_EOS; + GST_LOG_OBJECT (overlay, "closed caption EOS"); + goto beach; + } + + GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" + GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) + + GST_BUFFER_DURATION (buffer))); + + if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) { + GstClockTime stop; + + if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer))) + stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer); + else + stop = GST_CLOCK_TIME_NONE; + + in_seg = gst_segment_clip (&overlay->cc_segment, GST_FORMAT_TIME, + GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop); + GST_LOG_OBJECT (overlay, "stop:%" GST_TIME_FORMAT ", in_seg: %d", + GST_TIME_ARGS (stop), in_seg); + } else { + in_seg = TRUE; + } + + + if (in_seg) { + GstMapInfo buf_map = { 0 }; + const guint8 *ccdata = NULL; + gsize ccsize = 0; + + overlay->cc_segment.position = clip_start; + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + + gst_buffer_map (buffer, &buf_map, GST_MAP_READ); + if (overlay->is_cdp) { + extract_ccdata_from_cdp (buf_map.data, buf_map.size, &ccdata, &ccsize); + } else { + ccdata = buf_map.data; + ccsize = buf_map.size; + } + if (ccsize) { + gst_cea_cc_overlay_user_data_decode (overlay, ccdata, ccsize); + overlay->decoder->current_time = GST_BUFFER_PTS (buffer); + } + gst_buffer_unmap (buffer, &buf_map); + } + +beach: + gst_buffer_unref (buffer); + return ret; +} + +static GstFlowReturn +gst_cea_cc_overlay_video_chain (GstPad * pad, GstObject * parent, + GstBuffer * buffer) +{ + GstCeaCcOverlay *overlay; + GstFlowReturn ret = GST_FLOW_OK; + gboolean in_seg = FALSE; + guint64 start, stop, clip_start = 0, clip_stop = 0; + + overlay = GST_CEA_CC_OVERLAY (parent); + + if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) + goto missing_timestamp; + + /* ignore buffers that are outside of the current segment */ + start = GST_BUFFER_TIMESTAMP (buffer); + + if (!GST_BUFFER_DURATION_IS_VALID (buffer)) { + stop = GST_CLOCK_TIME_NONE; + } else { + stop = start + GST_BUFFER_DURATION (buffer); + } + + GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%" + GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment, + GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + + /* segment_clip() will adjust start unconditionally to segment_start if + * no stop time is provided, so handle this ourselves */ + if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start) + goto out_of_segment; + + in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop, + &clip_start, &clip_stop); + + if (!in_seg) + goto out_of_segment; + + /* if the buffer is only partially in the segment, fix up stamps */ + if (clip_start != start || (stop != -1 && clip_stop != stop)) { + GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment"); + buffer = gst_buffer_make_writable (buffer); + GST_BUFFER_TIMESTAMP (buffer) = clip_start; + if (stop != -1) + GST_BUFFER_DURATION (buffer) = clip_stop - clip_start; + } + + /* now, after we've done the clipping, fix up end time if there's no + * duration (we only use those estimated values internally though, we + * don't want to set bogus values on the buffer itself) */ + if (stop == -1) { + if (overlay->info.fps_n && overlay->info.fps_d) { + GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate"); + stop = start + gst_util_uint64_scale_int (GST_SECOND, + overlay->info.fps_d, overlay->info.fps_n); + } else { + GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration"); + stop = start + 1; /* we need to assume some interval */ + } + } + + gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer)); + +wait_for_text_buf: + + GST_CEA_CC_OVERLAY_LOCK (overlay); + + if (overlay->video_flushing) + goto flushing; + + if (overlay->video_eos) + goto have_eos; + + if (overlay->silent) { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_pad_push (overlay->srcpad, buffer); + + /* Update position */ + overlay->segment.position = clip_start; + + return ret; + } + + /* Closed Caption pad not linked, rendering video only */ + if (!overlay->cc_pad_linked) { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_pad_push (overlay->srcpad, buffer); + } else { + /* Closed Caption pad linked, check if we have a text buffer queued */ + if (GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) { + gboolean pop_text = FALSE, valid_text_time = TRUE; + + GstClockTime text_running_time = GST_CLOCK_TIME_NONE; + GstClockTime next_buffer_text_running_time = GST_CLOCK_TIME_NONE; + GstClockTime vid_running_time, vid_running_time_end; + + vid_running_time = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + start); + vid_running_time_end = + gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME, + stop); + if (GST_CLOCK_TIME_IS_VALID (overlay->next_comp_start_time)) { + next_buffer_text_running_time = + gst_segment_to_running_time (&overlay->cc_segment, GST_FORMAT_TIME, + overlay->next_comp_start_time); + + if (next_buffer_text_running_time < vid_running_time_end) { + /* text buffer should be force updated, popping */ + GST_DEBUG_OBJECT (overlay, + "T: next_buffer_text_running_time: %" GST_TIME_FORMAT + " - overlay->next_comp_start_time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (next_buffer_text_running_time), + GST_TIME_ARGS (overlay->next_comp_start_time)); + GST_DEBUG_OBJECT (overlay, + "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid_running_time), + GST_TIME_ARGS (vid_running_time_end)); + GST_LOG_OBJECT (overlay, + "text buffer should be force updated, popping"); + pop_text = FALSE; + gst_cea_cc_overlay_pop_text (overlay); + GST_CEA_CC_OVERLAY_WAIT (overlay); + GST_DEBUG_OBJECT (overlay, "resuming"); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + goto wait_for_text_buf; + } + + } + + /* if the text buffer isn't stamped right, pop it off the + * queue and display it for the current video frame only */ + if (!GST_CLOCK_TIME_IS_VALID (overlay->current_comp_start_time)) { + GST_WARNING_OBJECT (overlay, "Got text buffer with invalid timestamp"); + pop_text = TRUE; + valid_text_time = FALSE; + } + + /* If timestamp and duration are valid */ + if (valid_text_time) { + text_running_time = + gst_segment_to_running_time (&overlay->cc_segment, + GST_FORMAT_TIME, overlay->current_comp_start_time); + } + + GST_DEBUG_OBJECT (overlay, "T: %" GST_TIME_FORMAT, + GST_TIME_ARGS (text_running_time)); + GST_DEBUG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (vid_running_time), + GST_TIME_ARGS (vid_running_time_end)); + + if (valid_text_time && vid_running_time_end <= text_running_time) { + GST_LOG_OBJECT (overlay, "text in future, pushing video buf"); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + /* Push the video frame */ + ret = gst_pad_push (overlay->srcpad, buffer); + } else { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + ret = gst_cea_cc_overlay_push_frame (overlay, buffer); + } + if (pop_text) { + GST_CEA_CC_OVERLAY_LOCK (overlay); + gst_cea_cc_overlay_pop_text (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + } + } else { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + GST_LOG_OBJECT (overlay, "no need to wait for a text buffer"); + ret = gst_pad_push (overlay->srcpad, buffer); + } + } + + /* Update position */ + overlay->segment.position = clip_start; + GST_DEBUG_OBJECT (overlay, "ret=%d", ret); + + return ret; + +missing_timestamp: + { + GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding"); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } + +flushing: + { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer"); + gst_buffer_unref (buffer); + return GST_FLOW_FLUSHING; + } +have_eos: + { + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + GST_DEBUG_OBJECT (overlay, "eos, discarding buffer"); + gst_buffer_unref (buffer); + return GST_FLOW_EOS; + } +out_of_segment: + { + GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding"); + gst_buffer_unref (buffer); + return GST_FLOW_OK; + } +} + +static GstStateChangeReturn +gst_cea_cc_overlay_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCeaCcOverlay *overlay = GST_CEA_CC_OVERLAY (element); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_CEA_CC_OVERLAY_LOCK (overlay); + overlay->cc_flushing = TRUE; + overlay->video_flushing = TRUE; + /* pop_text will broadcast on the GCond and thus also make the video + * chain exit if it's waiting for a text buffer */ + gst_cea_cc_overlay_pop_text (overlay); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + break; + default: + break; + } + + ret = parent_class->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_CEA_CC_OVERLAY_LOCK (overlay); + overlay->cc_flushing = FALSE; + overlay->video_flushing = FALSE; + overlay->video_eos = FALSE; + overlay->cc_eos = FALSE; + gst_segment_init (&overlay->segment, GST_FORMAT_TIME); + gst_segment_init (&overlay->cc_segment, GST_FORMAT_TIME); + GST_CEA_CC_OVERLAY_UNLOCK (overlay); + break; + default: + break; + } + + return ret; +} diff --git a/ext/closedcaption/gstceaccoverlay.h b/ext/closedcaption/gstceaccoverlay.h new file mode 100644 index 0000000000..a50401a8b9 --- /dev/null +++ b/ext/closedcaption/gstceaccoverlay.h @@ -0,0 +1,136 @@ +/* GStreamer + * Copyright (C) 2015 Samsung Electronics Co., Ltd. + * @Author: Chengjun Wang + * + * 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 +#include +#include + +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__ */ diff --git a/ext/closedcaption/gstclosedcaption.c b/ext/closedcaption/gstclosedcaption.c index affdc21fab..1ee59c0b72 100644 --- a/ext/closedcaption/gstclosedcaption.c +++ b/ext/closedcaption/gstclosedcaption.c @@ -27,18 +27,22 @@ #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; } diff --git a/ext/closedcaption/meson.build b/ext/closedcaption/meson.build index 841fab751c..4e1ed3a378 100644 --- a/ext/closedcaption/meson.build +++ b/ext/closedcaption/meson.build @@ -9,7 +9,8 @@ zvbi_sources = [ if pangocairo_dep.found() gstclosedcaption = library('gstclosedcaption', - 'gstccextractor.c', 'gstclosedcaption.c', 'gstline21dec.c', zvbi_sources, + 'gstccextractor.c', 'gstclosedcaption.c', 'gstline21dec.c', + 'gstcea708decoder.c', 'gstceaccoverlay.c', zvbi_sources, c_args : gst_plugins_bad_args, link_args : noseh_link_args, include_directories : [configinc],