From e2ff6b61ce25943f96b25755bdc641df71ea830a Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Wed, 1 Jun 2022 21:24:41 +1000 Subject: [PATCH] cccombiner: initial implementation of using CCBuffer helper Part-of: --- .../ext/closedcaption/ccutils.c | 98 ++- .../ext/closedcaption/ccutils.h | 19 +- .../ext/closedcaption/gstcccombiner.c | 586 +++++++----------- .../ext/closedcaption/gstcccombiner.h | 7 +- 4 files changed, 340 insertions(+), 370 deletions(-) diff --git a/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c b/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c index c349a20ac9..20d8880ed9 100644 --- a/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c +++ b/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.c @@ -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; +} diff --git a/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.h b/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.h index c9903bc784..c5374a41a8 100644 --- a/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.h +++ b/subprojects/gst-plugins-bad/ext/closedcaption/ccutils.h @@ -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 diff --git a/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.c b/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.c index ddb4796bdf..b484ffe2ba 100644 --- a/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.c +++ b/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.c @@ -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); } diff --git a/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.h b/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.h index 2b17e41601..8d370bcf00 100644 --- a/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.h +++ b/subprojects/gst-plugins-bad/ext/closedcaption/gstcccombiner.h @@ -25,6 +25,8 @@ #include #include +#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; };