gstreamer/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c
Matthew Waters 91e8331b52 ccconverter: fix cdp->cea608-raw field 1 60fps conversion
There was a potential busy loop occuring because when we were taking
data from the internal ccbuffer, we were not resetting which field had
written data.  This would mean that the next time data was retrieved
from ccbuffer, it was always from field 0 and never from field 1.

This only affects usage of cc_buffer_take_separated() which is only used
by cdp->raw cea608.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6423>
2024-03-26 02:23:08 +00:00

1156 lines
36 KiB
C

/*
* GStreamer
* Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gst/base/base.h>
#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;
}