/* * GStreamer * Copyright (C) 2022 Matthew Waters * * 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 "ccutils.h" #define GST_CAT_DEFAULT ccutils_debug_cat GST_DEBUG_CATEGORY (GST_CAT_DEFAULT); typedef struct cdp_fps_entry cdp_fps_entry; #define VAL_OR_0(v) ((v) ? (*(v)) : 0) static const struct cdp_fps_entry cdp_fps_table[] = { {0x1f, 24000, 1001, 25, 22, 3 /* FIXME: alternating max cea608 count! */ }, {0x2f, 24, 1, 25, 22, 2}, {0x3f, 25, 1, 24, 22, 2}, {0x4f, 30000, 1001, 20, 18, 2}, {0x5f, 30, 1, 20, 18, 2}, {0x6f, 50, 1, 12, 11, 1}, {0x7f, 60000, 1001, 10, 9, 1}, {0x8f, 60, 1, 10, 9, 1}, }; const struct cdp_fps_entry null_fps_entry = { 0, 0, 0, 0 }; const struct cdp_fps_entry * cdp_fps_entry_from_fps (guint fps_n, guint fps_d) { int i; for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) { if (cdp_fps_table[i].fps_n == fps_n && cdp_fps_table[i].fps_d == fps_d) return &cdp_fps_table[i]; } return &null_fps_entry; } const struct cdp_fps_entry * cdp_fps_entry_from_id (guint8 id) { int i; for (i = 0; i < G_N_ELEMENTS (cdp_fps_table); i++) { if (cdp_fps_table[i].fps_idx == id) return &cdp_fps_table[i]; } return &null_fps_entry; } /* Converts raw CEA708 cc_data and an optional timecode into CDP */ guint convert_cea708_cc_data_to_cdp (GstObject * dbg_obj, GstCCCDPMode cdp_mode, guint16 cdp_hdr_sequence_cntr, const guint8 * cc_data, guint cc_data_len, guint8 * cdp, guint cdp_len, const GstVideoTimeCode * tc, const cdp_fps_entry * fps_entry) { GstByteWriter bw; guint8 flags, checksum; guint i, len; GST_DEBUG_OBJECT (dbg_obj, "writing out cdp packet from cc_data with " "length %u", cc_data_len); gst_byte_writer_init_with_data (&bw, cdp, cdp_len, FALSE); gst_byte_writer_put_uint16_be_unchecked (&bw, 0x9669); /* Write a length of 0 for now */ gst_byte_writer_put_uint8_unchecked (&bw, 0); gst_byte_writer_put_uint8_unchecked (&bw, fps_entry->fps_idx); if (cc_data_len / 3 > fps_entry->max_cc_count) { GST_WARNING_OBJECT (dbg_obj, "Too many cc_data triplets for framerate: %u. " "Truncating to %u", cc_data_len / 3, fps_entry->max_cc_count); cc_data_len = 3 * fps_entry->max_cc_count; } /* caption_service_active */ flags = 0x02; /* ccdata_present */ if ((cdp_mode & GST_CC_CDP_MODE_CC_DATA)) flags |= 0x40; /* time_code_present */ if ((cdp_mode & GST_CC_CDP_MODE_TIME_CODE) && tc && tc->config.fps_n > 0) flags |= 0x80; /* reserved */ flags |= 0x01; gst_byte_writer_put_uint8_unchecked (&bw, flags); gst_byte_writer_put_uint16_be_unchecked (&bw, cdp_hdr_sequence_cntr); if ((cdp_mode & GST_CC_CDP_MODE_TIME_CODE) && tc && tc->config.fps_n > 0) { guint8 u8; gst_byte_writer_put_uint8_unchecked (&bw, 0x71); /* reserved 11 - 2 bits */ u8 = 0xc0; /* tens of hours - 2 bits */ u8 |= ((tc->hours / 10) & 0x3) << 4; /* units of hours - 4 bits */ u8 |= (tc->hours % 10) & 0xf; gst_byte_writer_put_uint8_unchecked (&bw, u8); /* reserved 1 - 1 bit */ u8 = 0x80; /* tens of minutes - 3 bits */ u8 |= ((tc->minutes / 10) & 0x7) << 4; /* units of minutes - 4 bits */ u8 |= (tc->minutes % 10) & 0xf; gst_byte_writer_put_uint8_unchecked (&bw, u8); /* field flag - 1 bit */ u8 = tc->field_count < 2 ? 0x00 : 0x80; /* tens of seconds - 3 bits */ u8 |= ((tc->seconds / 10) & 0x7) << 4; /* units of seconds - 4 bits */ u8 |= (tc->seconds % 10) & 0xf; gst_byte_writer_put_uint8_unchecked (&bw, u8); /* drop frame flag - 1 bit */ u8 = (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) ? 0x80 : 0x00; /* reserved0 - 1 bit */ /* tens of frames - 2 bits */ u8 |= ((tc->frames / 10) & 0x3) << 4; /* units of frames 4 bits */ u8 |= (tc->frames % 10) & 0xf; gst_byte_writer_put_uint8_unchecked (&bw, u8); } if ((cdp_mode & GST_CC_CDP_MODE_CC_DATA)) { gst_byte_writer_put_uint8_unchecked (&bw, 0x72); gst_byte_writer_put_uint8_unchecked (&bw, 0xe0 | fps_entry->max_cc_count); gst_byte_writer_put_data_unchecked (&bw, cc_data, cc_data_len); while (fps_entry->max_cc_count > cc_data_len / 3) { gst_byte_writer_put_uint8_unchecked (&bw, 0xfa); gst_byte_writer_put_uint8_unchecked (&bw, 0x00); gst_byte_writer_put_uint8_unchecked (&bw, 0x00); cc_data_len += 3; } } gst_byte_writer_put_uint8_unchecked (&bw, 0x74); gst_byte_writer_put_uint16_be_unchecked (&bw, cdp_hdr_sequence_cntr); /* We calculate the checksum afterwards */ gst_byte_writer_put_uint8_unchecked (&bw, 0); len = gst_byte_writer_get_pos (&bw); gst_byte_writer_set_pos (&bw, 2); gst_byte_writer_put_uint8_unchecked (&bw, len); checksum = 0; for (i = 0; i < len; i++) { checksum += cdp[i]; } checksum &= 0xff; checksum = 256 - checksum; cdp[len - 1] = checksum; return len; } /* Converts CDP into raw CEA708 cc_data */ guint convert_cea708_cdp_to_cc_data (GstObject * dbg_obj, const guint8 * cdp, guint cdp_len, guint8 * cc_data, GstVideoTimeCode * tc, const cdp_fps_entry ** out_fps_entry) { GstByteReader br; guint16 u16; guint8 u8; guint8 flags; guint len = 0; const struct cdp_fps_entry *fps_entry; *out_fps_entry = &null_fps_entry; memset (tc, 0, sizeof (*tc)); /* Header + footer length */ if (cdp_len < 11) { GST_WARNING_OBJECT (dbg_obj, "cdp packet too short (%u). expected at " "least %u", cdp_len, 11); return 0; } gst_byte_reader_init (&br, cdp, cdp_len); u16 = gst_byte_reader_get_uint16_be_unchecked (&br); if (u16 != 0x9669) { GST_WARNING_OBJECT (dbg_obj, "cdp packet does not have initial magic bytes " "of 0x9669"); return 0; } u8 = gst_byte_reader_get_uint8_unchecked (&br); if (u8 != cdp_len) { GST_WARNING_OBJECT (dbg_obj, "cdp packet length (%u) does not match passed " "in value (%u)", u8, cdp_len); return 0; } u8 = gst_byte_reader_get_uint8_unchecked (&br); fps_entry = cdp_fps_entry_from_id (u8); if (!fps_entry || fps_entry->fps_n == 0) { GST_WARNING_OBJECT (dbg_obj, "cdp packet does not have a valid framerate " "id (0x%02x", u8); return 0; } flags = gst_byte_reader_get_uint8_unchecked (&br); /* No cc_data? */ if ((flags & 0x40) == 0) { GST_DEBUG_OBJECT (dbg_obj, "cdp packet does have any cc_data"); return 0; } /* cdp_hdr_sequence_cntr */ gst_byte_reader_skip_unchecked (&br, 2); /* time_code_present */ if (flags & 0x80) { guint8 hours, minutes, seconds, frames, fields; gboolean drop_frame; if (gst_byte_reader_get_remaining (&br) < 5) { GST_WARNING_OBJECT (dbg_obj, "cdp packet does not have enough data to " "contain a timecode (%u). Need at least 5 bytes", gst_byte_reader_get_remaining (&br)); return 0; } u8 = gst_byte_reader_get_uint8_unchecked (&br); if (u8 != 0x71) { GST_WARNING_OBJECT (dbg_obj, "cdp packet does not have timecode start " "byte of 0x71, found 0x%02x", u8); return 0; } u8 = gst_byte_reader_get_uint8_unchecked (&br); if ((u8 & 0xc0) != 0xc0) { GST_WARNING_OBJECT (dbg_obj, "reserved bits are not 0xc0, found 0x%02x", u8); return 0; } hours = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf); u8 = gst_byte_reader_get_uint8_unchecked (&br); if ((u8 & 0x80) != 0x80) { GST_WARNING_OBJECT (dbg_obj, "reserved bit is not 0x80, found 0x%02x", u8); return 0; } minutes = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf); u8 = gst_byte_reader_get_uint8_unchecked (&br); if (u8 & 0x80) fields = 2; else fields = 1; seconds = ((u8 >> 4) & 0x7) * 10 + (u8 & 0xf); u8 = gst_byte_reader_get_uint8_unchecked (&br); if (u8 & 0x40) { GST_WARNING_OBJECT (dbg_obj, "reserved bit is not 0x0, found 0x%02x", u8); return 0; } drop_frame = !(!(u8 & 0x80)); frames = ((u8 >> 4) & 0x3) * 10 + (u8 & 0xf); gst_video_time_code_init (tc, fps_entry->fps_n, fps_entry->fps_d, NULL, drop_frame ? GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME : GST_VIDEO_TIME_CODE_FLAGS_NONE, hours, minutes, seconds, frames, fields); } /* ccdata_present */ if (flags & 0x40) { guint8 cc_count; if (gst_byte_reader_get_remaining (&br) < 2) { GST_WARNING_OBJECT (dbg_obj, "not enough data to contain valid cc_data"); return 0; } u8 = gst_byte_reader_get_uint8_unchecked (&br); if (u8 != 0x72) { GST_WARNING_OBJECT (dbg_obj, "missing cc_data start code of 0x72, " "found 0x%02x", u8); return 0; } cc_count = gst_byte_reader_get_uint8_unchecked (&br); if ((cc_count & 0xe0) != 0xe0) { GST_WARNING_OBJECT (dbg_obj, "reserved bits are not 0xe0, found 0x%02x", u8); return 0; } cc_count &= 0x1f; len = 3 * cc_count; if (gst_byte_reader_get_remaining (&br) < len) { GST_WARNING_OBJECT (dbg_obj, "not enough bytes (%u) left for the " "number of byte triples (%u)", gst_byte_reader_get_remaining (&br), cc_count); return 0; } memcpy (cc_data, gst_byte_reader_get_data_unchecked (&br, len), len); } *out_fps_entry = fps_entry; /* skip everything else we don't care about */ return len; } #define CC_DATA_EXTRACT_TOO_MANY_FIELD1 -2 #define CC_DATA_EXTRACT_TOO_MANY_FIELD2 -3 static gint cc_data_extract_cea608 (const guint8 * cc_data, guint cc_data_len, guint8 * cea608_field1, guint * cea608_field1_len, guint8 * cea608_field2, guint * cea608_field2_len, gboolean remove_cea608_padding) { guint i, field_1_len = 0, field_2_len = 0; if (cea608_field1_len) { field_1_len = *cea608_field1_len; *cea608_field1_len = 0; } if (cea608_field2_len) { field_2_len = *cea608_field2_len; *cea608_field2_len = 0; } if (cc_data_len % 3 != 0) { GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple " "of 3", cc_data_len); cc_data_len = cc_data_len - (cc_data_len % 3); } for (i = 0; i < cc_data_len / 3; i++) { guint8 byte0 = cc_data[i * 3 + 0]; guint8 byte1 = cc_data[i * 3 + 1]; guint8 byte2 = cc_data[i * 3 + 2]; gboolean cc_valid = (byte0 & 0x04) == 0x04; guint8 cc_type = byte0 & 0x03; GST_TRACE ("0x%02x 0x%02x 0x%02x, valid: %u, type: 0b%u%u", byte0, byte1, byte2, cc_valid, (cc_type & 0x2) == 0x2, (cc_type & 0x1) == 0x1); if (cc_type == 0x00) { if (!cc_valid) continue; if (cea608_field1 && cea608_field1_len) { if (*cea608_field1_len + 2 > field_1_len) { GST_WARNING ("Too many cea608 input bytes %u for field 1", *cea608_field1_len + 2); return CC_DATA_EXTRACT_TOO_MANY_FIELD1; } if (!remove_cea608_padding || byte1 != 0x80 || byte2 != 0x80) { cea608_field1[(*cea608_field1_len)++] = byte1; cea608_field1[(*cea608_field1_len)++] = byte2; } } } else if (cc_type == 0x01) { if (!cc_valid) continue; if (cea608_field2 && cea608_field2_len) { if (*cea608_field2_len + 2 > field_2_len) { GST_WARNING ("Too many cea608 input bytes %u for field 2", *cea608_field2_len + 2); return CC_DATA_EXTRACT_TOO_MANY_FIELD2; } if (!remove_cea608_padding || byte1 != 0x80 || byte2 != 0x80) { cea608_field2[(*cea608_field2_len)++] = byte1; cea608_field2[(*cea608_field2_len)++] = byte2; } } } else { /* all cea608 packets must be at the beginning of a cc_data */ break; } } g_assert_cmpint (i * 3, <=, cc_data_len); GST_LOG ("Extracted cea608-1 of length %u and cea608-2 of length %u, " "ccp_offset %i", VAL_OR_0 (cea608_field1_len), VAL_OR_0 (cea608_field2_len), i * 3); return i * 3; } gint drop_ccp_from_cc_data (guint8 * cc_data, guint cc_data_len) { return cc_data_extract_cea608 (cc_data, cc_data_len, NULL, NULL, NULL, NULL, FALSE); } GType gst_cc_buffer_cea608_padding_strategy_get_type (void) { static const GFlagsValue values[] = { {CC_BUFFER_CEA608_PADDING_STRATEGY_INPUT_REMOVE, "Remove padding from input data", "input-remove"}, {CC_BUFFER_CEA608_PADDING_STRATEGY_VALID, "Write 608 padding as valid", "valid"}, {0, NULL, NULL} }; static GType id = 0; if (g_once_init_enter ((gsize *) & id)) { GType _id; _id = g_flags_register_static ("GstCCBufferCea608PaddingStrategy", values); g_once_init_leave ((gsize *) & id, _id); } return id; } #define DEFAULT_MAX_BUFFER_TIME (100 * GST_MSECOND) #define DEFAULT_VALID_TIMEOUT GST_CLOCK_TIME_NONE struct _CCBuffer { GstObject parent; GArray *cea608_1; GArray *cea608_2; GArray *cc_data; /* used for tracking which field to write across output buffer boundaries */ gboolean last_cea608_written_was_field1; /* tracks the timeout for valid_timeout */ guint64 field1_padding_written_count; guint64 field2_padding_written_count; gboolean cea608_1_any_valid; gboolean cea608_2_any_valid; /* properties */ GstClockTime max_buffer_time; gboolean output_padding; gboolean output_ccp_padding; CCBufferCea608PaddingStrategy cea608_padding_strategy; GstClockTime valid_timeout; }; G_DEFINE_TYPE (CCBuffer, cc_buffer, G_TYPE_OBJECT); CCBuffer * cc_buffer_new (void) { return g_object_new (cc_buffer_get_type (), NULL); } static void cc_buffer_init (CCBuffer * buf) { buf->cea608_1 = g_array_new (FALSE, FALSE, sizeof (guint8)); buf->cea608_2 = g_array_new (FALSE, FALSE, sizeof (guint8)); buf->cc_data = g_array_new (FALSE, FALSE, sizeof (guint8)); buf->max_buffer_time = DEFAULT_MAX_BUFFER_TIME; buf->output_padding = TRUE; buf->output_ccp_padding = FALSE; buf->cea608_padding_strategy = 0; buf->valid_timeout = DEFAULT_VALID_TIMEOUT; buf->field1_padding_written_count = 0; buf->field2_padding_written_count = 0; buf->cea608_1_any_valid = FALSE; buf->cea608_2_any_valid = FALSE; } static void cc_buffer_finalize (GObject * object) { CCBuffer *buf = GST_CC_BUFFER (object); g_array_unref (buf->cea608_1); buf->cea608_1 = NULL; g_array_unref (buf->cea608_2); buf->cea608_2 = NULL; g_array_unref (buf->cc_data); buf->cc_data = NULL; G_OBJECT_CLASS (cc_buffer_parent_class)->finalize (object); } static void cc_buffer_class_init (CCBufferClass * buf_class) { GObjectClass *gobject_class = (GObjectClass *) buf_class; gobject_class->finalize = cc_buffer_finalize; } /* remove padding bytes from a cc_data packet. Returns the length of the new * data in @cc_data */ static guint compact_cc_data (guint8 * cc_data, guint cc_data_len) { gboolean started_ccp = FALSE; guint out_len = 0; guint i; if (cc_data_len % 3 != 0) { GST_WARNING ("Invalid cc_data buffer size"); cc_data_len = cc_data_len - (cc_data_len % 3); } for (i = 0; i < cc_data_len / 3; i++) { gboolean cc_valid = (cc_data[i * 3] & 0x04) == 0x04; guint8 cc_type = cc_data[i * 3] & 0x03; if (!started_ccp && (cc_type == 0x00 || cc_type == 0x01)) { if (cc_valid) { /* copy over valid 608 data */ cc_data[out_len++] = cc_data[i * 3]; cc_data[out_len++] = cc_data[i * 3 + 1]; cc_data[out_len++] = cc_data[i * 3 + 2]; } continue; } if (cc_type & 0x10) started_ccp = TRUE; if (!cc_valid) continue; if (cc_type == 0x00 || cc_type == 0x01) { GST_WARNING ("Invalid cc_data. cea608 bytes after cea708"); return 0; } cc_data[out_len++] = cc_data[i * 3]; cc_data[out_len++] = cc_data[i * 3 + 1]; cc_data[out_len++] = cc_data[i * 3 + 2]; } GST_LOG ("compacted cc_data from %u to %u", cc_data_len, out_len); return out_len; } static guint calculate_n_cea608_doubles_from_time_ceil (GstClockTime ns) { /* cea608 has a maximum bitrate of 60000/1001 * 2 bytes/s */ guint ret = gst_util_uint64_scale_ceil (ns, 120000, 1001 * GST_SECOND); return GST_ROUND_UP_2 (ret); } static guint calculate_n_cea708_doubles_from_time_ceil (GstClockTime ns) { /* ccp has a maximum bitrate of 9600000/1001 bits/s */ guint ret = gst_util_uint64_scale_ceil (ns, 9600000 / 8, 1001 * GST_SECOND); return GST_ROUND_UP_2 (ret); } static void push_internal (CCBuffer * buf, const guint8 * cea608_1, guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len, const guint8 * cc_data, guint cc_data_len) { guint max_cea608_bytes; GST_DEBUG_OBJECT (buf, "pushing cea608-1: %u cea608-2: %u ccp: %u", cea608_1_len, cea608_2_len, cc_data_len); max_cea608_bytes = calculate_n_cea608_doubles_from_time_ceil (buf->max_buffer_time); if (cea608_1_len > 0) { if (cea608_1_len + buf->cea608_1->len > max_cea608_bytes) { GST_WARNING_OBJECT (buf, "cea608 field 1 overflow, dropping all " "previous data, max %u, attempted to hold %u", max_cea608_bytes, cea608_1_len + buf->cea608_1->len); g_array_set_size (buf->cea608_1, 0); } g_array_append_vals (buf->cea608_1, cea608_1, cea608_1_len); } if (cea608_2_len > 0) { if (cea608_2_len + buf->cea608_2->len > max_cea608_bytes) { GST_WARNING_OBJECT (buf, "cea608 field 2 overflow, dropping all " "previous data, max %u, attempted to hold %u", max_cea608_bytes, cea608_2_len + buf->cea608_2->len); g_array_set_size (buf->cea608_2, 0); } g_array_append_vals (buf->cea608_2, cea608_2, cea608_2_len); } if (cc_data_len > 0) { guint max_cea708_bytes = calculate_n_cea708_doubles_from_time_ceil (buf->max_buffer_time); if (cc_data_len + buf->cc_data->len > max_cea708_bytes) { GST_WARNING_OBJECT (buf, "ccp data overflow, dropping all " "previous data, max %u, attempted to hold %u", max_cea708_bytes, cc_data_len + buf->cc_data->len); g_array_set_size (buf->cea608_2, 0); } g_array_append_vals (buf->cc_data, cc_data, cc_data_len); } } gboolean cc_buffer_push_separated (CCBuffer * buf, const guint8 * cea608_1, guint cea608_1_len, const guint8 * cea608_2, guint cea608_2_len, const guint8 * cc_data, guint cc_data_len) { guint8 cea608_1_copy[MAX_CEA608_LEN]; guint8 cea608_2_copy[MAX_CEA608_LEN]; guint8 cc_data_copy[MAX_CDP_PACKET_LEN]; gboolean remove_cea608_padding = (buf->cea608_padding_strategy & CC_BUFFER_CEA608_PADDING_STRATEGY_INPUT_REMOVE) != 0; guint i; if (cea608_1 && cea608_1_len > 0) { guint out_i = 0; for (i = 0; i < cea608_1_len / 2; i++) { if (!remove_cea608_padding || cea608_1[i] != 0x80 || cea608_1[i + 1] != 0x80) { cea608_1_copy[out_i++] = cea608_1[i]; cea608_1_copy[out_i++] = cea608_1[i + 1]; } } cea608_1_len = out_i; } else { cea608_1_len = 0; } if (cea608_2 && cea608_2_len > 0) { guint out_i = 0; for (i = 0; i < cea608_2_len / 2; i++) { if (!remove_cea608_padding || cea608_2[i] != 0x80 || cea608_2[i + 1] != 0x80) { cea608_2_copy[out_i++] = cea608_2[i]; cea608_2_copy[out_i++] = cea608_2[i + 1]; } } cea608_2_len = out_i; } else { cea608_2_len = 0; } if (cc_data && cc_data_len > 0) { memcpy (cc_data_copy, cc_data, cc_data_len); cc_data_len = compact_cc_data (cc_data_copy, cc_data_len); } else { cc_data_len = 0; } push_internal (buf, cea608_1_copy, cea608_1_len, cea608_2_copy, cea608_2_len, cc_data_copy, cc_data_len); return cea608_1_len > 0 || cea608_2_len > 0 || cc_data_len > 0; } gboolean cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data, guint cc_data_len) { guint8 cea608_1[MAX_CEA608_LEN]; guint8 cea608_2[MAX_CEA608_LEN]; guint8 cc_data_copy[MAX_CDP_PACKET_LEN]; guint cea608_1_len = MAX_CEA608_LEN; guint cea608_2_len = MAX_CEA608_LEN; int ccp_offset; memcpy (cc_data_copy, cc_data, cc_data_len); cc_data_len = compact_cc_data (cc_data_copy, cc_data_len); ccp_offset = cc_data_extract_cea608 (cc_data_copy, cc_data_len, cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, (buf->cea608_padding_strategy & CC_BUFFER_CEA608_PADDING_STRATEGY_INPUT_REMOVE) != 0); if (ccp_offset < 0) { GST_WARNING_OBJECT (buf, "Failed to extract cea608 from cc_data"); return FALSE; } push_internal (buf, cea608_1, cea608_1_len, cea608_2, cea608_2_len, &cc_data_copy[ccp_offset], cc_data_len - ccp_offset); return cea608_1_len > 0 || cea608_2_len > 0 || cc_data_len - ccp_offset > 0; } void cc_buffer_get_stored_size (CCBuffer * buf, guint * cea608_1_len, guint * cea608_2_len, guint * cc_data_len) { if (cea608_1_len) *cea608_1_len = buf->cea608_1->len; if (cea608_2_len) *cea608_2_len = buf->cea608_2->len; if (cc_data_len) *cc_data_len = buf->cc_data->len; } void cc_buffer_discard (CCBuffer * buf) { g_array_set_size (buf->cea608_1, 0); g_array_set_size (buf->cea608_2, 0); g_array_set_size (buf->cc_data, 0); } #if 0 void cc_buffer_peek (CCBuffer * buf, guint8 ** cea608_1, guint * cea608_1_len, guint8 ** cea608_2, guint * cea608_2_len, guint8 ** cc_data, guint * cc_data_len) { if (cea608_1_len) { if (cea608_1) { *cea608_1 = (guint8 *) buf->cea608_1->data; } *cea608_1_len = buf->cea608_1->len; } if (cea608_1_len) { if (cea608_2) { *cea608_2 = (guint8 *) buf->cea608_2->data; } *cea608_2_len = buf->cea608_2->len; } if (cc_data_len) { if (cc_data) { *cc_data = (guint8 *) buf->cc_data->data; } *cc_data_len = buf->cc_data->len; } } #endif static void cc_buffer_get_out_sizes (CCBuffer * buf, const struct cdp_fps_entry *fps_entry, guint * cea608_1_len, guint * field1_padding, guint * cea608_2_len, guint * field2_padding, guint * cc_data_len) { gint extra_ccp = 0, extra_cea608_1 = 0, extra_cea608_2 = 0; gint write_ccp_size = 0, write_cea608_1_size = 0, write_cea608_2_size = 0; gboolean wrote_first = FALSE; if (buf->cc_data->len) { extra_ccp = buf->cc_data->len - 3 * fps_entry->max_ccp_count; extra_ccp = MAX (0, extra_ccp); write_ccp_size = buf->cc_data->len - extra_ccp; } extra_cea608_1 = buf->cea608_1->len; extra_cea608_2 = buf->cea608_2->len; *field1_padding = 0; *field2_padding = 0; wrote_first = buf->last_cea608_written_was_field1; /* try to push data into the packets. Anything 'extra' will be * stored for later */ while (TRUE) { gint avail_1, avail_2; avail_1 = buf->cea608_1->len - extra_cea608_1 + *field1_padding; avail_2 = buf->cea608_2->len - extra_cea608_2 + *field2_padding; if (avail_1 + avail_2 >= 2 * fps_entry->max_cea608_count) break; if (!wrote_first) { if (extra_cea608_1 > 0) { extra_cea608_1 -= 2; g_assert_cmpint (extra_cea608_1, >=, 0); write_cea608_1_size += 2; g_assert_cmpint (write_cea608_1_size, <=, buf->cea608_1->len); } else { *field1_padding += 2; } } avail_1 = buf->cea608_1->len - extra_cea608_1 + *field1_padding; avail_2 = buf->cea608_2->len - extra_cea608_2 + *field2_padding; if (avail_1 + avail_2 >= 2 * fps_entry->max_cea608_count) break; if (extra_cea608_2 > 0) { extra_cea608_2 -= 2; g_assert_cmpint (extra_cea608_2, >=, 0); write_cea608_2_size += 2; g_assert_cmpint (write_cea608_2_size, <=, buf->cea608_2->len); } else { /* we need to insert field 2 padding if we don't have data and are * requested to start with field2 */ *field2_padding += 2; } wrote_first = FALSE; } // don't write padding if not requested if (!buf->output_padding && write_cea608_1_size == 0 && write_cea608_2_size == 0) { // however if we are producing data for a cdp that only has a single 608 field, // in order to keep processing data will still need to alternate fields and // produce the relevant padding data if (fps_entry->max_cea608_count != 1 || (extra_cea608_1 == 0 && extra_cea608_2 == 0)) { *field1_padding = 0; *field2_padding = 0; } } GST_TRACE_OBJECT (buf, "allocated sizes ccp:%u, cea608-1:%u (pad:%u), " "cea608-2:%u (pad:%u)", write_ccp_size, write_cea608_1_size, *field1_padding, write_cea608_2_size, *field2_padding); *cea608_1_len = write_cea608_1_size; *cea608_2_len = write_cea608_2_size; *cc_data_len = write_ccp_size; } void cc_buffer_take_separated (CCBuffer * buf, const struct cdp_fps_entry *fps_entry, guint8 * cea608_1, guint * cea608_1_len, guint8 * cea608_2, guint * cea608_2_len, guint8 * cc_data, guint * cc_data_len) { guint write_cea608_1_size, write_cea608_2_size, write_ccp_size; guint field1_padding, field2_padding; cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size, &field1_padding, &write_cea608_2_size, &field2_padding, &write_ccp_size); if (cea608_1_len) { if (*cea608_1_len < write_cea608_1_size + field1_padding) { GST_WARNING_OBJECT (buf, "output cea608 field 1 buffer (%u) is too " "small to hold output (%u)", *cea608_1_len, write_cea608_1_size + field1_padding); *cea608_1_len = 0; } else if (cea608_1) { memcpy (cea608_1, buf->cea608_1->data, write_cea608_1_size); memset (&cea608_1[write_cea608_1_size], 0x80, field1_padding); if (write_cea608_1_size == 0) { buf->field1_padding_written_count += field1_padding / 2; } else { buf->field1_padding_written_count = 0; } *cea608_1_len = write_cea608_1_size + field1_padding; if (*cea608_1_len > 0) buf->last_cea608_written_was_field1 = TRUE; } else { *cea608_1_len = 0; } } if (cea608_2_len) { if (*cea608_2_len < write_cea608_2_size + field2_padding) { GST_WARNING_OBJECT (buf, "output cea608 field 2 buffer (%u) is too " "small to hold output (%u)", *cea608_2_len, write_cea608_2_size); *cea608_2_len = 0; } else if (cea608_2) { memcpy (cea608_2, buf->cea608_2->data, write_cea608_2_size); memset (&cea608_2[write_cea608_2_size], 0x80, field2_padding); if (write_cea608_2_size == 0) { buf->field2_padding_written_count += field2_padding / 2; } else { buf->field2_padding_written_count = 0; } *cea608_2_len = write_cea608_2_size + field2_padding; if (*cea608_2_len > 0) buf->last_cea608_written_was_field1 = FALSE; } else { *cea608_2_len = 0; } } if (cc_data_len) { if (*cc_data_len < write_ccp_size) { GST_WARNING_OBJECT (buf, "output ccp buffer (%u) is too " "small to hold output (%u)", *cc_data_len, write_ccp_size); *cc_data_len = 0; } else if (cc_data) { guint ccp_padding = 0; memcpy (cc_data, buf->cc_data->data, write_ccp_size); if (buf->output_ccp_padding && (write_ccp_size < 3 * fps_entry->max_ccp_count)) { guint i; ccp_padding = 3 * fps_entry->max_ccp_count - write_ccp_size; GST_TRACE_OBJECT (buf, "need %u ccp padding bytes (%u - %u)", ccp_padding, fps_entry->max_ccp_count, write_ccp_size); for (i = 0; i < ccp_padding; i += 3) { cc_data[i + write_ccp_size] = 0xfa; cc_data[i + 1 + write_ccp_size] = 0x00; cc_data[i + 2 + write_ccp_size] = 0x00; } } *cc_data_len = write_ccp_size + ccp_padding; } else if (buf->output_padding) { guint i; guint padding = 3 * fps_entry->max_ccp_count; for (i = 0; i < padding; i += 3) { cc_data[i + write_ccp_size] = 0xfa; cc_data[i + 1 + write_ccp_size] = 0x00; cc_data[i + 2 + write_ccp_size] = 0x00; } GST_TRACE_OBJECT (buf, "outputting only %u padding bytes", padding); *cc_data_len = padding; } else { *cc_data_len = 0; } } g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size); g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size); g_array_remove_range (buf->cc_data, 0, write_ccp_size); GST_LOG_OBJECT (buf, "bytes currently stored, cea608-1:%u, cea608-2:%u " "ccp:%u", buf->cea608_1->len, buf->cea608_2->len, buf->cc_data->len); } void cc_buffer_take_cc_data (CCBuffer * buf, const struct cdp_fps_entry *fps_entry, guint8 * cc_data, guint * cc_data_len) { guint write_cea608_1_size, write_cea608_2_size, write_ccp_size; guint field1_padding, field2_padding; gboolean wrote_first; gboolean nul_padding = (buf->cea608_padding_strategy & CC_BUFFER_CEA608_PADDING_STRATEGY_VALID) == 0; cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size, &field1_padding, &write_cea608_2_size, &field2_padding, &write_ccp_size); { guint cea608_1_i = 0, cea608_2_i = 0; guint out_i = 0; guint8 *cea608_1 = (guint8 *) buf->cea608_1->data; guint8 *cea608_2 = (guint8 *) buf->cea608_2->data; guint cea608_output_count = write_cea608_1_size + write_cea608_2_size + field1_padding + field2_padding; guint ccp_padding = 0; wrote_first = !buf->last_cea608_written_was_field1; while (cea608_1_i + cea608_2_i < cea608_output_count) { if (wrote_first) { if (cea608_1_i < write_cea608_1_size) { cc_data[out_i++] = 0xfc; cc_data[out_i++] = cea608_1[cea608_1_i]; cc_data[out_i++] = cea608_1[cea608_1_i + 1]; cea608_1_i += 2; buf->last_cea608_written_was_field1 = TRUE; buf->field1_padding_written_count = 0; buf->cea608_1_any_valid = TRUE; } else if (cea608_1_i < write_cea608_1_size + field1_padding) { GST_TRACE_OBJECT (buf, "write field2:%u field2_i:%u, cea608-2 buf len:%u", write_cea608_2_size, cea608_2_i, buf->cea608_2->len); if (cea608_2_i < write_cea608_2_size || buf->cea608_2->len > write_cea608_2_size) { /* if we are writing field 2, then we have to write valid field 1 */ GST_TRACE_OBJECT (buf, "writing valid field1 padding because " "we need to write valid field2"); cc_data[out_i++] = 0xfc; cc_data[out_i++] = 0x80; cc_data[out_i++] = 0x80; buf->field1_padding_written_count = 0; } else { gboolean write_invalid = nul_padding || (buf->cea608_1_any_valid && calculate_n_cea608_doubles_from_time_ceil (buf->valid_timeout) / 2 < buf->field1_padding_written_count); guint8 padding_byte = write_invalid ? 0x00 : 0x80; cc_data[out_i++] = write_invalid ? 0xf8 : 0xfc; cc_data[out_i++] = padding_byte; cc_data[out_i++] = padding_byte; buf->field1_padding_written_count += 1; } cea608_1_i += 2; buf->last_cea608_written_was_field1 = TRUE; } } if (cea608_2_i < write_cea608_2_size) { cc_data[out_i++] = 0xfd; cc_data[out_i++] = cea608_2[cea608_2_i]; cc_data[out_i++] = cea608_2[cea608_2_i + 1]; cea608_2_i += 2; buf->last_cea608_written_was_field1 = FALSE; buf->field2_padding_written_count = 0; buf->cea608_2_any_valid = TRUE; } else if (cea608_2_i < write_cea608_2_size + field2_padding) { gboolean write_invalid = nul_padding || (buf->cea608_2_any_valid && calculate_n_cea608_doubles_from_time_ceil (buf->valid_timeout) / 2 < buf->field2_padding_written_count); guint8 padding_byte = write_invalid ? 0x00 : 0x80; cc_data[out_i++] = write_invalid ? 0xf9 : 0xfd; cc_data[out_i++] = padding_byte; cc_data[out_i++] = padding_byte; cea608_2_i += 2; buf->last_cea608_written_was_field1 = FALSE; buf->field2_padding_written_count += 1; } wrote_first = TRUE; } if (write_ccp_size > 0) memcpy (&cc_data[out_i], buf->cc_data->data, write_ccp_size); if (buf->output_ccp_padding && (write_ccp_size < 3 * fps_entry->max_ccp_count)) { guint i; ccp_padding = 3 * fps_entry->max_ccp_count - write_ccp_size; GST_TRACE_OBJECT (buf, "need %u ccp padding bytes (%u - %u)", ccp_padding, fps_entry->max_ccp_count, write_ccp_size); for (i = 0; i < ccp_padding; i += 3) { cc_data[i + out_i + write_ccp_size] = 0xfa; cc_data[i + 1 + out_i + write_ccp_size] = 0x00; cc_data[i + 2 + out_i + write_ccp_size] = 0x00; } } *cc_data_len = out_i + write_ccp_size + ccp_padding; GST_TRACE_OBJECT (buf, "cc_data_len is %u (%u + %u + %u)", *cc_data_len, out_i, write_ccp_size, ccp_padding); } g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size); g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size); g_array_remove_range (buf->cc_data, 0, write_ccp_size); GST_LOG_OBJECT (buf, "bytes currently stored, cea608-1:%u, cea608-2:%u " "ccp:%u", buf->cea608_1->len, buf->cea608_2->len, buf->cc_data->len); } void cc_buffer_take_cea608_field1 (CCBuffer * buf, const struct cdp_fps_entry *fps_entry, guint8 * cea608_1, guint * cea608_1_len) { guint write_cea608_1_size, field1_padding; guint write_cea608_2_size, field2_padding; guint cc_data_len; cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size, &field1_padding, &write_cea608_2_size, &field2_padding, &cc_data_len); if (*cea608_1_len < write_cea608_1_size + field1_padding) { GST_WARNING_OBJECT (buf, "Not enough output space to write cea608 field 1 data"); *cea608_1_len = 0; return; } if (write_cea608_1_size > 0) { memcpy (cea608_1, buf->cea608_1->data, write_cea608_1_size); g_array_remove_range (buf->cea608_1, 0, write_cea608_1_size); } *cea608_1_len = write_cea608_1_size; if (buf->output_padding && field1_padding > 0) { memset (&cea608_1[write_cea608_1_size], 0x80, field1_padding); *cea608_1_len += field1_padding; } } void cc_buffer_take_cea608_field2 (CCBuffer * buf, const struct cdp_fps_entry *fps_entry, guint8 * cea608_2, guint * cea608_2_len) { guint write_cea608_1_size, field1_padding; guint write_cea608_2_size, field2_padding; guint cc_data_len; cc_buffer_get_out_sizes (buf, fps_entry, &write_cea608_1_size, &field1_padding, &write_cea608_2_size, &field2_padding, &cc_data_len); if (*cea608_2_len < write_cea608_2_size + field2_padding) { GST_WARNING_OBJECT (buf, "Not enough output space to write cea608 field 2 data"); *cea608_2_len = 0; return; } if (write_cea608_2_size > 0) { memcpy (cea608_2, buf->cea608_2->data, write_cea608_2_size); g_array_remove_range (buf->cea608_2, 0, write_cea608_2_size); } *cea608_2_len = write_cea608_2_size; if (buf->output_padding && field1_padding > 0) { memset (&cea608_2[write_cea608_2_size], 0x80, field2_padding); *cea608_2_len += field2_padding; } } gboolean cc_buffer_is_empty (CCBuffer * buf) { return buf->cea608_1->len == 0 && buf->cea608_2->len == 0 && buf->cc_data->len == 0; } void cc_buffer_set_max_buffer_time (CCBuffer * buf, GstClockTime max_time) { buf->max_buffer_time = max_time; } void cc_buffer_set_output_padding (CCBuffer * buf, gboolean output_padding, gboolean output_ccp_padding) { buf->output_padding = output_padding; buf->output_ccp_padding = output_ccp_padding; } void cc_buffer_set_cea608_padding_strategy (CCBuffer * buf, CCBufferCea608PaddingStrategy padding_strategy) { buf->cea608_padding_strategy = padding_strategy; } void cc_buffer_set_cea608_valid_timeout (CCBuffer * buf, GstClockTime valid_timeout) { buf->valid_timeout = valid_timeout; }