cccombiner: initial implementation of using CCBuffer helper
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3211>
This commit is contained in:
parent
088597b430
commit
e2ff6b61ce
@ -434,7 +434,9 @@ struct _CCBuffer
|
||||
/* used for tracking which field to write across output buffer boundaries */
|
||||
gboolean last_cea608_written_was_field1;
|
||||
|
||||
/* properties */
|
||||
GstClockTime max_buffer_time;
|
||||
gboolean output_padding;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (CCBuffer, cc_buffer, G_TYPE_OBJECT);
|
||||
@ -453,6 +455,7 @@ cc_buffer_init (CCBuffer * buf)
|
||||
buf->cc_data = g_array_new (FALSE, FALSE, sizeof (guint8));
|
||||
|
||||
buf->max_buffer_time = DEFAULT_MAX_BUFFER_TIME;
|
||||
buf->output_padding = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -588,7 +591,7 @@ push_internal (CCBuffer * buf, const guint8 * cea608_1,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
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)
|
||||
@ -633,9 +636,11 @@ cc_buffer_push_separated (CCBuffer * buf, const guint8 * cea608_1,
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void
|
||||
gboolean
|
||||
cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data,
|
||||
guint cc_data_len)
|
||||
{
|
||||
@ -655,11 +660,13 @@ cc_buffer_push_cc_data (CCBuffer * buf, const guint8 * cc_data,
|
||||
|
||||
if (ccp_offset < 0) {
|
||||
GST_WARNING_OBJECT (buf, "Failed to extract cea608 from cc_data");
|
||||
return;
|
||||
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
|
||||
@ -768,6 +775,12 @@ cc_buffer_get_out_sizes (CCBuffer * buf, const struct cdp_fps_entry *fps_entry,
|
||||
wrote_first = TRUE;
|
||||
}
|
||||
|
||||
if (!buf->output_padding && write_cea608_1_size == 0
|
||||
&& write_cea608_2_size == 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);
|
||||
@ -905,3 +918,82 @@ cc_buffer_take_cc_data (CCBuffer * buf,
|
||||
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)
|
||||
{
|
||||
buf->output_padding = output_padding;
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ void cc_buffer_get_stored_size (CCBuffer * buf,
|
||||
guint * cea608_2_len,
|
||||
guint * cc_data_len);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_push_separated (CCBuffer * buf,
|
||||
gboolean cc_buffer_push_separated (CCBuffer * buf,
|
||||
const guint8 * cea608_1,
|
||||
guint cea608_1_len,
|
||||
const guint8 * cea608_2,
|
||||
@ -93,7 +93,7 @@ void cc_buffer_push_separated (CCBuffer * buf,
|
||||
const guint8 * cc_data,
|
||||
guint cc_data_len);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_push_cc_data (CCBuffer * buf,
|
||||
gboolean cc_buffer_push_cc_data (CCBuffer * buf,
|
||||
const guint8 * cc_data,
|
||||
guint cc_data_len);
|
||||
G_GNUC_INTERNAL
|
||||
@ -111,10 +111,25 @@ void cc_buffer_take_separated (CCBuffer * buf,
|
||||
guint8 * cc_data,
|
||||
guint * cc_data_len);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_take_cea608_field1 (CCBuffer * buf,
|
||||
const struct cdp_fps_entry * fps_entry,
|
||||
guint8 * cea608_1,
|
||||
guint * cea608_1_len);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_take_cea608_field2 (CCBuffer * buf,
|
||||
const struct cdp_fps_entry * fps_entry,
|
||||
guint8 * cea608_2,
|
||||
guint * cea608_2_len);
|
||||
G_GNUC_INTERNAL
|
||||
gboolean cc_buffer_is_empty (CCBuffer * buf);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_discard (CCBuffer * buf);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_set_max_buffer_time (CCBuffer * buf,
|
||||
GstClockTime max_time);
|
||||
G_GNUC_INTERNAL
|
||||
void cc_buffer_set_output_padding (CCBuffer * buf,
|
||||
gboolean output_padding);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -88,22 +88,16 @@ caption_data_clear (CaptionData * data)
|
||||
gst_buffer_unref (data->buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
clear_scheduled (CaptionQueueItem * item)
|
||||
{
|
||||
gst_buffer_unref (item->buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_cc_combiner_finalize (GObject * object)
|
||||
{
|
||||
GstCCCombiner *self = GST_CCCOMBINER (object);
|
||||
|
||||
gst_queue_array_free (self->scheduled[0]);
|
||||
gst_queue_array_free (self->scheduled[1]);
|
||||
g_array_unref (self->current_frame_captions);
|
||||
self->current_frame_captions = NULL;
|
||||
|
||||
gst_clear_object (&self->cc_buffer);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@ -124,8 +118,9 @@ extract_cdp (GstCCCombiner * self, const guint8 * cdp, guint cdp_len,
|
||||
#define CDP_MODE (GST_CC_CDP_MODE_CC_DATA | GST_CC_CDP_MODE_TIME_CODE)
|
||||
|
||||
static GstBuffer *
|
||||
make_cdp (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len,
|
||||
const struct cdp_fps_entry *fps_entry, const GstVideoTimeCode * tc)
|
||||
make_cdp_buffer (GstCCCombiner * self, const guint8 * cc_data,
|
||||
guint cc_data_len, const struct cdp_fps_entry *fps_entry,
|
||||
const GstVideoTimeCode * tc)
|
||||
{
|
||||
guint len;
|
||||
GstBuffer *ret = gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
|
||||
@ -146,114 +141,67 @@ make_cdp (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len,
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
make_padding (GstCCCombiner * self, const GstVideoTimeCode * tc, guint field)
|
||||
make_buffer (GstCCCombiner * self, const guint8 * cc_data, guint cc_data_len)
|
||||
{
|
||||
GstBuffer *ret = NULL;
|
||||
|
||||
switch (self->caption_type) {
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
|
||||
{
|
||||
const guint8 cc_data[6] = { 0xfc, 0x80, 0x80, 0xf9, 0x80, 0x80 };
|
||||
|
||||
ret = make_cdp (self, cc_data, 6, self->cdp_fps_entry, tc);
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
|
||||
{
|
||||
GstMapInfo map;
|
||||
|
||||
ret = gst_buffer_new_allocate (NULL, 3, NULL);
|
||||
|
||||
gst_buffer_map (ret, &map, GST_MAP_WRITE);
|
||||
|
||||
map.data[0] = 0xfc | (field & 0x01);
|
||||
map.data[1] = 0x80;
|
||||
map.data[2] = 0x80;
|
||||
|
||||
gst_buffer_unmap (ret, &map);
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
|
||||
{
|
||||
GstMapInfo map;
|
||||
|
||||
ret = gst_buffer_new_allocate (NULL, 3, NULL);
|
||||
|
||||
gst_buffer_map (ret, &map, GST_MAP_WRITE);
|
||||
|
||||
map.data[0] = field == 0 ? 0x80 : 0x00;
|
||||
map.data[1] = 0x80;
|
||||
map.data[2] = 0x80;
|
||||
|
||||
gst_buffer_unmap (ret, &map);
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
|
||||
{
|
||||
GstMapInfo map;
|
||||
|
||||
ret = gst_buffer_new_allocate (NULL, 2, NULL);
|
||||
|
||||
gst_buffer_map (ret, &map, GST_MAP_WRITE);
|
||||
|
||||
map.data[0] = 0x80;
|
||||
map.data[1] = 0x80;
|
||||
|
||||
gst_buffer_unmap (ret, &map);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
GstBuffer *ret = gst_buffer_new_allocate (NULL, cc_data_len, NULL);
|
||||
gst_buffer_fill (ret, 0, cc_data, cc_data_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
queue_caption (GstCCCombiner * self, GstBuffer * scheduled, guint field)
|
||||
write_cc_data_to (GstCCCombiner * self, GstBuffer * buffer)
|
||||
{
|
||||
GstAggregatorPad *caption_pad;
|
||||
CaptionQueueItem item;
|
||||
GstMapInfo map;
|
||||
guint len;
|
||||
|
||||
if (self->progressive && field == 1) {
|
||||
gst_buffer_unref (scheduled);
|
||||
return;
|
||||
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
||||
len = map.size;
|
||||
cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, map.data, &len);
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
gst_buffer_set_size (buffer, len);
|
||||
}
|
||||
|
||||
static void
|
||||
prepend_s334_to_cea608 (guint field, guint8 * data, guint * len,
|
||||
guint alloc_len)
|
||||
{
|
||||
int i;
|
||||
|
||||
g_assert (*len / 2 * 3 <= alloc_len);
|
||||
|
||||
for (i = *len / 2; i >= 0; i--) {
|
||||
data[i * 3 + 0] = field == 0 ? 0x80 : 0x00;
|
||||
data[i * 3 + 1] = data[i * 2 + 0];
|
||||
data[i * 3 + 2] = data[i * 2 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
take_s334_both_fields (GstCCCombiner * self, GstBuffer * buffer)
|
||||
{
|
||||
GstMapInfo out = GST_MAP_INFO_INIT;
|
||||
guint s334_len, cc_data_len, i;
|
||||
|
||||
gst_buffer_map (buffer, &out, GST_MAP_READWRITE);
|
||||
|
||||
cc_data_len = out.size;
|
||||
cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, out.data,
|
||||
&cc_data_len);
|
||||
s334_len = drop_ccp_from_cc_data (out.data, cc_data_len);
|
||||
if (s334_len < 0) {
|
||||
s334_len = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
caption_pad =
|
||||
GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
|
||||
(self), "caption"));
|
||||
|
||||
g_assert (gst_queue_array_get_length (self->scheduled[field]) <=
|
||||
self->max_scheduled);
|
||||
|
||||
if (gst_queue_array_get_length (self->scheduled[field]) ==
|
||||
self->max_scheduled) {
|
||||
CaptionQueueItem *dropped =
|
||||
gst_queue_array_pop_tail_struct (self->scheduled[field]);
|
||||
|
||||
GST_WARNING_OBJECT (self,
|
||||
"scheduled queue runs too long, dropping %" GST_PTR_FORMAT, dropped);
|
||||
|
||||
gst_element_post_message (GST_ELEMENT_CAST (self),
|
||||
gst_message_new_qos (GST_OBJECT_CAST (self), FALSE,
|
||||
dropped->running_time, dropped->stream_time,
|
||||
GST_BUFFER_PTS (dropped->buffer), GST_BUFFER_DURATION (dropped)));
|
||||
|
||||
gst_buffer_unref (dropped->buffer);
|
||||
for (i = 0; i < s334_len / 3; i++) {
|
||||
guint byte = out.data[i * 3];
|
||||
/* We have to assume a line offset of 0 */
|
||||
out.data[i * 3] = (byte == 0xfc || byte == 0xf8) ? 0x80 : 0x00;
|
||||
}
|
||||
|
||||
gst_object_unref (caption_pad);
|
||||
|
||||
item.buffer = scheduled;
|
||||
item.running_time =
|
||||
gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_PTS (scheduled));
|
||||
item.stream_time =
|
||||
gst_segment_to_stream_time (&caption_pad->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_PTS (scheduled));
|
||||
|
||||
gst_queue_array_push_tail_struct (self->scheduled[field], &item);
|
||||
out:
|
||||
gst_buffer_unmap (buffer, &out);
|
||||
gst_buffer_set_size (buffer, s334_len);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -262,42 +210,10 @@ schedule_cdp (GstCCCombiner * self, const GstVideoTimeCode * tc,
|
||||
{
|
||||
guint8 cc_data[MAX_CDP_PACKET_LEN];
|
||||
guint cc_data_len;
|
||||
gboolean inject = FALSE;
|
||||
|
||||
cc_data_len = extract_cdp (self, data, len, cc_data);
|
||||
if (cc_data_len > 0) {
|
||||
guint8 i;
|
||||
|
||||
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 (!cc_valid)
|
||||
continue;
|
||||
|
||||
if (cc_type == 0x00 || cc_type == 0x01) {
|
||||
if (cc_data[i * 3 + 1] != 0x80 || cc_data[i * 3 + 2] != 0x80) {
|
||||
inject = TRUE;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
inject = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inject) {
|
||||
GstBuffer *buf =
|
||||
make_cdp (self, cc_data, cc_data_len, self->cdp_fps_entry, tc);
|
||||
|
||||
/* We only set those for QoS reporting purposes */
|
||||
GST_BUFFER_PTS (buf) = pts;
|
||||
GST_BUFFER_DURATION (buf) = duration;
|
||||
|
||||
queue_caption (self, buf, 0);
|
||||
}
|
||||
if (cc_buffer_push_cc_data (self->cc_buffer, cc_data, cc_data_len))
|
||||
self->current_scheduled++;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -319,134 +235,37 @@ schedule_cea608_s334_1a (GstCCCombiner * self, guint8 * data, guint len,
|
||||
if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
|
||||
continue;
|
||||
|
||||
field0_data[field0_len++] = data[i * 3];
|
||||
field0_data[field0_len++] = data[i * 3 + 1];
|
||||
field0_data[field0_len++] = data[i * 3 + 2];
|
||||
} else {
|
||||
if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
|
||||
continue;
|
||||
|
||||
field1_data[field1_len++] = data[i * 3];
|
||||
field1_data[field1_len++] = data[i * 3 + 1];
|
||||
field1_data[field1_len++] = data[i * 3 + 2];
|
||||
}
|
||||
}
|
||||
|
||||
if (field0_len > 0) {
|
||||
GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
|
||||
|
||||
gst_buffer_fill (buf, 0, field0_data, field0_len);
|
||||
GST_BUFFER_PTS (buf) = pts;
|
||||
GST_BUFFER_DURATION (buf) = duration;
|
||||
|
||||
queue_caption (self, buf, 0);
|
||||
}
|
||||
|
||||
if (field1_len > 0) {
|
||||
GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
|
||||
|
||||
gst_buffer_fill (buf, 0, field1_data, field1_len);
|
||||
GST_BUFFER_PTS (buf) = pts;
|
||||
GST_BUFFER_DURATION (buf) = duration;
|
||||
|
||||
queue_caption (self, buf, 1);
|
||||
}
|
||||
if (cc_buffer_push_separated (self->cc_buffer, field0_data, field0_len,
|
||||
field1_data, field1_len, NULL, 0))
|
||||
self->current_scheduled++;
|
||||
}
|
||||
|
||||
static void
|
||||
schedule_cea708_raw (GstCCCombiner * self, guint8 * data, guint len,
|
||||
GstClockTime pts, GstClockTime duration)
|
||||
{
|
||||
guint8 field0_data[MAX_CDP_PACKET_LEN], field1_data[3];
|
||||
guint field0_len = 0, field1_len = 0;
|
||||
guint i;
|
||||
gboolean started_ccp = FALSE;
|
||||
|
||||
if (len % 3 != 0) {
|
||||
GST_WARNING ("Invalid cc_data buffer size %u. Truncating to a multiple "
|
||||
"of 3", len);
|
||||
len = len - (len % 3);
|
||||
}
|
||||
|
||||
for (i = 0; i < len / 3; i++) {
|
||||
gboolean cc_valid = (data[i * 3] & 0x04) == 0x04;
|
||||
guint8 cc_type = data[i * 3] & 0x03;
|
||||
|
||||
if (!started_ccp) {
|
||||
if (cc_type == 0x00) {
|
||||
if (!cc_valid)
|
||||
continue;
|
||||
|
||||
if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
|
||||
continue;
|
||||
|
||||
field0_data[field0_len++] = data[i * 3];
|
||||
field0_data[field0_len++] = data[i * 3 + 1];
|
||||
field0_data[field0_len++] = data[i * 3 + 2];
|
||||
} else if (cc_type == 0x01) {
|
||||
if (!cc_valid)
|
||||
continue;
|
||||
|
||||
if (data[i * 3 + 1] == 0x80 && data[i * 3 + 2] == 0x80)
|
||||
continue;
|
||||
|
||||
field1_data[field1_len++] = data[i * 3];
|
||||
field1_data[field1_len++] = data[i * 3 + 1];
|
||||
field1_data[field1_len++] = data[i * 3 + 2];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cc_type & 0x10)
|
||||
started_ccp = TRUE;
|
||||
|
||||
if (!cc_valid)
|
||||
continue;
|
||||
|
||||
if (cc_type == 0x00 || cc_type == 0x01)
|
||||
continue;
|
||||
|
||||
field0_data[field0_len++] = data[i * 3];
|
||||
field0_data[field0_len++] = data[i * 3 + 1];
|
||||
field0_data[field0_len++] = data[i * 3 + 2];
|
||||
}
|
||||
|
||||
if (field0_len > 0) {
|
||||
GstBuffer *buf = gst_buffer_new_allocate (NULL, field0_len, NULL);
|
||||
|
||||
gst_buffer_fill (buf, 0, field0_data, field0_len);
|
||||
GST_BUFFER_PTS (buf) = pts;
|
||||
GST_BUFFER_DURATION (buf) = duration;
|
||||
|
||||
queue_caption (self, buf, 0);
|
||||
}
|
||||
|
||||
if (field1_len > 0) {
|
||||
GstBuffer *buf = gst_buffer_new_allocate (NULL, field1_len, NULL);
|
||||
|
||||
gst_buffer_fill (buf, 0, field1_data, field1_len);
|
||||
GST_BUFFER_PTS (buf) = pts;
|
||||
GST_BUFFER_DURATION (buf) = duration;
|
||||
|
||||
queue_caption (self, buf, 1);
|
||||
}
|
||||
if (cc_buffer_push_cc_data (self->cc_buffer, data, len))
|
||||
self->current_scheduled++;
|
||||
}
|
||||
|
||||
static void
|
||||
schedule_cea608_raw (GstCCCombiner * self, guint8 * data, guint len,
|
||||
GstBuffer * buffer)
|
||||
schedule_cea608_raw (GstCCCombiner * self, guint8 * data, guint len)
|
||||
{
|
||||
if (len < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[0] != 0x80 || data[1] != 0x80) {
|
||||
queue_caption (self, gst_buffer_ref (buffer), 0);
|
||||
}
|
||||
if (cc_buffer_push_separated (self->cc_buffer, data, len, NULL, 0, NULL, 0))
|
||||
self->current_scheduled++;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
|
||||
const GstVideoTimeCode * tc)
|
||||
@ -457,6 +276,34 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
|
||||
pts = GST_BUFFER_PTS (caption_buf);
|
||||
duration = GST_BUFFER_DURATION (caption_buf);
|
||||
|
||||
if (self->current_scheduled + 1 >= self->max_scheduled) {
|
||||
GstClockTime stream_time, running_time;
|
||||
GstAggregatorPad *caption_pad;
|
||||
|
||||
caption_pad =
|
||||
GST_AGGREGATOR_PAD_CAST (gst_element_get_static_pad (GST_ELEMENT_CAST
|
||||
(self), "caption"));
|
||||
|
||||
GST_WARNING_OBJECT (self,
|
||||
"scheduled queue runs too long, discarding stored");
|
||||
|
||||
running_time =
|
||||
gst_segment_to_running_time (&caption_pad->segment, GST_FORMAT_TIME,
|
||||
pts);
|
||||
stream_time =
|
||||
gst_segment_to_stream_time (&caption_pad->segment, GST_FORMAT_TIME,
|
||||
pts);
|
||||
|
||||
gst_element_post_message (GST_ELEMENT_CAST (self),
|
||||
gst_message_new_qos (GST_OBJECT_CAST (self), FALSE,
|
||||
running_time, stream_time, pts, duration));
|
||||
|
||||
cc_buffer_discard (self->cc_buffer);
|
||||
self->current_scheduled = 0;
|
||||
|
||||
gst_clear_object (&caption_pad);
|
||||
}
|
||||
|
||||
gst_buffer_map (caption_buf, &map, GST_MAP_READ);
|
||||
|
||||
switch (self->caption_type) {
|
||||
@ -470,7 +317,7 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
|
||||
schedule_cea608_s334_1a (self, map.data, map.size, pts, duration);
|
||||
break;
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
|
||||
schedule_cea608_raw (self, map.data, map.size, caption_buf);
|
||||
schedule_cea608_raw (self, map.data, map.size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -480,64 +327,119 @@ schedule_caption (GstCCCombiner * self, GstBuffer * caption_buf,
|
||||
}
|
||||
|
||||
static void
|
||||
dequeue_caption_one_field (GstCCCombiner * self, const GstVideoTimeCode * tc,
|
||||
guint field, gboolean drain)
|
||||
dequeue_caption (GstCCCombiner * self, GstVideoTimeCode * tc, gboolean drain)
|
||||
{
|
||||
CaptionQueueItem *scheduled;
|
||||
guint8 cea608_1[MAX_CEA608_LEN], cea608_2[MAX_CEA608_LEN];
|
||||
guint8 cc_data[MAX_CDP_PACKET_LEN];
|
||||
guint cea608_1_len = MAX_CEA608_LEN, cea608_2_len = MAX_CEA608_LEN;
|
||||
guint cc_data_len = MAX_CDP_PACKET_LEN;
|
||||
CaptionData caption_data;
|
||||
|
||||
if ((scheduled = gst_queue_array_pop_head_struct (self->scheduled[field]))) {
|
||||
caption_data.buffer = scheduled->buffer;
|
||||
caption_data.caption_type = self->caption_type;
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
} else if (!drain && self->output_padding) {
|
||||
caption_data.caption_type = self->caption_type;
|
||||
caption_data.buffer = make_padding (self, tc, field);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
}
|
||||
g_assert (self->current_frame_captions->len == 0);
|
||||
|
||||
static void
|
||||
dequeue_caption_both_fields (GstCCCombiner * self, const GstVideoTimeCode * tc,
|
||||
gboolean drain)
|
||||
{
|
||||
CaptionQueueItem *field0_scheduled, *field1_scheduled;
|
||||
GstBuffer *field0_buffer = NULL, *field1_buffer = NULL;
|
||||
CaptionData caption_data;
|
||||
|
||||
field0_scheduled = gst_queue_array_pop_head_struct (self->scheduled[0]);
|
||||
field1_scheduled = gst_queue_array_pop_head_struct (self->scheduled[1]);
|
||||
|
||||
if (drain && !field0_scheduled && !field1_scheduled) {
|
||||
if (drain && cc_buffer_is_empty (self->cc_buffer))
|
||||
return;
|
||||
}
|
||||
|
||||
if (field0_scheduled) {
|
||||
field0_buffer = field0_scheduled->buffer;
|
||||
} else if (self->output_padding) {
|
||||
field0_buffer = make_padding (self, tc, 0);
|
||||
}
|
||||
|
||||
if (field1_scheduled) {
|
||||
field1_buffer = field1_scheduled->buffer;
|
||||
} else if (self->output_padding) {
|
||||
field1_buffer = make_padding (self, tc, 1);
|
||||
}
|
||||
|
||||
if (field0_buffer || field1_buffer) {
|
||||
if (field0_buffer && field1_buffer) {
|
||||
caption_data.buffer = gst_buffer_append (field0_buffer, field1_buffer);
|
||||
} else if (field0_buffer) {
|
||||
caption_data.buffer = field0_buffer;
|
||||
} else if (field1_buffer) {
|
||||
caption_data.buffer = field1_buffer;
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
caption_data.caption_type = self->caption_type;
|
||||
switch (self->caption_type) {
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
|
||||
{
|
||||
/* Only relevant in alternate and mixed mode, no need to look at the caps */
|
||||
if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
||||
if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
|
||||
cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, cc_data,
|
||||
&cc_data_len);
|
||||
caption_data.buffer =
|
||||
make_cdp_buffer (self, cc_data, cc_data_len, self->cdp_fps_entry,
|
||||
tc);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
} else {
|
||||
cc_buffer_take_cc_data (self->cc_buffer, self->cdp_fps_entry, cc_data,
|
||||
&cc_data_len);
|
||||
caption_data.buffer =
|
||||
make_cdp_buffer (self, cc_data, cc_data_len, self->cdp_fps_entry,
|
||||
tc);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
caption_data.caption_type = self->caption_type;
|
||||
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
|
||||
{
|
||||
/* Only relevant in alternate and mixed mode, no need to look at the caps */
|
||||
if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
||||
if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
|
||||
caption_data.buffer =
|
||||
gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
|
||||
write_cc_data_to (self, caption_data.buffer);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
} else {
|
||||
caption_data.buffer =
|
||||
gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
|
||||
write_cc_data_to (self, caption_data.buffer);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
|
||||
{
|
||||
if (self->progressive) {
|
||||
cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
|
||||
cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
|
||||
&cc_data_len);
|
||||
prepend_s334_to_cea608 (0, cea608_1, &cea608_1_len, sizeof (cea608_1));
|
||||
caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
} else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED) &&
|
||||
GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_ONEFIELD)) {
|
||||
cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
|
||||
cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
|
||||
&cc_data_len);
|
||||
if (GST_VIDEO_BUFFER_IS_TOP_FIELD (self->current_video_buffer)) {
|
||||
prepend_s334_to_cea608 (0, cea608_1, &cea608_1_len,
|
||||
sizeof (cea608_1));
|
||||
caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
|
||||
} else {
|
||||
prepend_s334_to_cea608 (1, cea608_2, &cea608_2_len,
|
||||
sizeof (cea608_2));
|
||||
caption_data.buffer = make_buffer (self, cea608_2, cea608_2_len);
|
||||
}
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
} else {
|
||||
caption_data.buffer =
|
||||
gst_buffer_new_allocate (NULL, MAX_CDP_PACKET_LEN, NULL);
|
||||
take_s334_both_fields (self, caption_data.buffer);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
|
||||
{
|
||||
cc_buffer_take_separated (self->cc_buffer, self->cdp_fps_entry,
|
||||
cea608_1, &cea608_1_len, cea608_2, &cea608_2_len, cc_data,
|
||||
&cc_data_len);
|
||||
if (self->progressive) {
|
||||
caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
} else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
||||
if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
|
||||
caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
} else {
|
||||
caption_data.buffer = make_buffer (self, cea608_1, cea608_1_len);
|
||||
g_array_append_val (self->current_frame_captions, caption_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,60 +590,8 @@ gst_cc_combiner_collect_captions (GstCCCombiner * self, gboolean timeout)
|
||||
}
|
||||
} while (TRUE);
|
||||
|
||||
/* FIXME pad correctly according to fps */
|
||||
if (self->schedule) {
|
||||
g_assert (self->current_frame_captions->len == 0);
|
||||
|
||||
switch (self->caption_type) {
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_CDP:
|
||||
{
|
||||
/* Only relevant in alternate and mixed mode, no need to look at the caps */
|
||||
if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
||||
if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
}
|
||||
} else {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA708_RAW:
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A:
|
||||
{
|
||||
if (self->progressive) {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
} else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED) &&
|
||||
GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_ONEFIELD)) {
|
||||
if (GST_VIDEO_BUFFER_IS_TOP_FIELD (self->current_video_buffer)) {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
} else {
|
||||
dequeue_caption_one_field (self, tc, 1, caption_pad_is_eos);
|
||||
}
|
||||
} else {
|
||||
dequeue_caption_both_fields (self, tc, caption_pad_is_eos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW:
|
||||
{
|
||||
if (self->progressive) {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
} else if (GST_BUFFER_FLAG_IS_SET (self->current_video_buffer,
|
||||
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
||||
if (!GST_VIDEO_BUFFER_IS_BOTTOM_FIELD (self->current_video_buffer)) {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
}
|
||||
} else {
|
||||
dequeue_caption_one_field (self, tc, 0, caption_pad_is_eos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dequeue_caption (self, tc, caption_pad_is_eos);
|
||||
}
|
||||
|
||||
gst_aggregator_selected_samples (GST_AGGREGATOR_CAST (self),
|
||||
@ -755,6 +605,9 @@ gst_cc_combiner_collect_captions (GstCCCombiner * self, gboolean timeout)
|
||||
if (self->current_frame_captions->len > 0) {
|
||||
guint i;
|
||||
|
||||
if (self->schedule)
|
||||
self->current_scheduled = MAX (1, self->current_scheduled) - 1;
|
||||
|
||||
video_buf = gst_buffer_make_writable (self->current_video_buffer);
|
||||
self->current_video_buffer = NULL;
|
||||
|
||||
@ -976,6 +829,15 @@ gst_cc_combiner_sink_event (GstAggregator * aggregator,
|
||||
self->video_fps_d = fps_d;
|
||||
|
||||
self->cdp_fps_entry = cdp_fps_entry_from_fps (fps_n, fps_d);
|
||||
if (!self->cdp_fps_entry || self->cdp_fps_entry->fps_n == 0) {
|
||||
GST_WARNING_OBJECT (self, "Missing valid caption framerate in "
|
||||
"video caps");
|
||||
|
||||
GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (NULL),
|
||||
("Missing valid caption framerate in video caps"));
|
||||
|
||||
self->cdp_fps_entry = cdp_fps_entry_from_fps (60, 1);
|
||||
}
|
||||
|
||||
gst_aggregator_set_src_caps (aggregator, caps);
|
||||
}
|
||||
@ -1012,8 +874,8 @@ gst_cc_combiner_stop (GstAggregator * aggregator)
|
||||
g_array_set_size (self->current_frame_captions, 0);
|
||||
self->caption_type = GST_VIDEO_CAPTION_TYPE_UNKNOWN;
|
||||
|
||||
gst_queue_array_clear (self->scheduled[0]);
|
||||
gst_queue_array_clear (self->scheduled[1]);
|
||||
cc_buffer_discard (self->cc_buffer);
|
||||
self->current_scheduled = 0;
|
||||
self->cdp_fps_entry = &null_fps_entry;
|
||||
|
||||
return TRUE;
|
||||
@ -1035,8 +897,9 @@ gst_cc_combiner_flush (GstAggregator * aggregator)
|
||||
src_pad->segment.position = GST_CLOCK_TIME_NONE;
|
||||
|
||||
self->cdp_hdr_sequence_cntr = 0;
|
||||
gst_queue_array_clear (self->scheduled[0]);
|
||||
gst_queue_array_clear (self->scheduled[1]);
|
||||
|
||||
cc_buffer_discard (self->cc_buffer);
|
||||
self->current_scheduled = 0;
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
@ -1232,6 +1095,8 @@ gst_cc_combiner_change_state (GstElement * element, GstStateChange transition)
|
||||
self->schedule = self->prop_schedule;
|
||||
self->max_scheduled = self->prop_max_scheduled;
|
||||
self->output_padding = self->prop_output_padding;
|
||||
cc_buffer_set_max_buffer_time (self->cc_buffer, GST_CLOCK_TIME_NONE);
|
||||
cc_buffer_set_output_padding (self->cc_buffer, self->prop_output_padding);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1422,14 +1287,9 @@ gst_cc_combiner_init (GstCCCombiner * self)
|
||||
self->prop_schedule = DEFAULT_SCHEDULE;
|
||||
self->prop_max_scheduled = DEFAULT_MAX_SCHEDULED;
|
||||
self->prop_output_padding = DEFAULT_OUTPUT_PADDING;
|
||||
self->scheduled[0] =
|
||||
gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
|
||||
self->scheduled[1] =
|
||||
gst_queue_array_new_for_struct (sizeof (CaptionQueueItem), 0);
|
||||
gst_queue_array_set_clear_func (self->scheduled[0],
|
||||
(GDestroyNotify) clear_scheduled);
|
||||
gst_queue_array_set_clear_func (self->scheduled[1],
|
||||
(GDestroyNotify) clear_scheduled);
|
||||
self->cdp_hdr_sequence_cntr = 0;
|
||||
self->cdp_fps_entry = &null_fps_entry;
|
||||
|
||||
self->cc_buffer = cc_buffer_new ();
|
||||
cc_buffer_set_max_buffer_time (self->cc_buffer, GST_CLOCK_TIME_NONE);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <gst/base/base.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "ccutils.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
#define GST_TYPE_CCCOMBINER \
|
||||
(gst_cc_combiner_get_type())
|
||||
@ -61,8 +63,9 @@ struct _GstCCCombiner
|
||||
gboolean schedule;
|
||||
guint max_scheduled;
|
||||
gboolean output_padding;
|
||||
/* One queue per field */
|
||||
GstQueueArray *scheduled[2];
|
||||
guint current_scheduled;
|
||||
|
||||
CCBuffer *cc_buffer;
|
||||
guint16 cdp_hdr_sequence_cntr;
|
||||
const struct cdp_fps_entry *cdp_fps_entry;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user