cccombiner: implement scheduling

Prior to that, cccombiner's behaviour was essentially that of
a funnel: it strictly looked at input timestamps to associate
together video and caption buffers.

This patch instead exposes a "schedule" property, with a default
of TRUE, to control whether caption buffers should be smoothly
scheduled, in order to have exactly one per output video buffer.

This can involve rewriting input captions, for example when the
input is CDP sequence counters are rewritten, time codes are dropped
and potentially re-injected if the input video frame had a time code
meta.

Caption buffers may also get split up in order to assign captions to
the correct field when the input is interlaced.

This can also imply that the input will drift from synchronization,
when there isn't enough padding in the input stream to catch up. In
that case the element will start dropping old caption buffers once
the number of buffers in its internal queue reaches a certain limit
(configurable).

The property is exposed so that existing users of cccombiner can
revert back to the original behaviour, but should eventually be
removed, as that behaviour was simply inadequate.

This commit also disallows changing the input caption type, as
this would needlessly complicate implementation, and removes
the corresponding test.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2076>
This commit is contained in:
Mathieu Duponchelle 2021-03-09 13:22:10 +01:00 committed by GStreamer Marge Bot
parent 80792e12d4
commit 08442cc792
4 changed files with 919 additions and 114 deletions

View file

@ -3318,7 +3318,34 @@
"type": "GstAggregatorPad"
}
},
"properties": {},
"properties": {
"max-scheduled": {
"blurb": "Maximum number of buffers to queue for scheduling",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "0",
"max": "-1",
"min": "0",
"mutable": "ready",
"readable": true,
"type": "guint",
"writable": true
},
"schedule": {
"blurb": "Schedule caption buffers so that exactly one is output per video frame",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "true",
"mutable": "ready",
"readable": true,
"type": "gboolean",
"writable": true
}
},
"rank": "none"
},
"ccconverter": {

File diff suppressed because it is too large Load diff

View file

@ -40,18 +40,38 @@ G_BEGIN_DECLS
typedef struct _GstCCCombiner GstCCCombiner;
typedef struct _GstCCCombinerClass GstCCCombinerClass;
struct cdp_fps_entry
{
guint8 fps_idx;
guint fps_n, fps_d;
guint max_cc_count;
guint max_ccp_count;
guint max_cea608_count;
};
struct _GstCCCombiner
{
GstAggregator parent;
gint video_fps_n, video_fps_d;
gboolean progressive;
GstClockTime previous_video_running_time_end;
GstClockTime current_video_running_time;
GstClockTime current_video_running_time_end;
GstBuffer *current_video_buffer;
GArray *current_frame_captions;
GstVideoCaptionType current_caption_type;
GstVideoCaptionType caption_type;
gboolean prop_schedule;
guint prop_max_scheduled;
gboolean schedule;
guint max_scheduled;
/* One queue per field */
GstQueueArray *scheduled[2];
guint16 cdp_hdr_sequence_cntr;
const struct cdp_fps_entry *cdp_fps_entry;
};
struct _GstCCCombinerClass

View file

@ -31,8 +31,6 @@
static GstStaticCaps foo_bar_caps = GST_STATIC_CAPS ("foo/bar");
static GstStaticCaps cea708_cc_data_caps =
GST_STATIC_CAPS ("closedcaption/x-cea-708,format=(string) cc_data");
static GstStaticCaps cea708_cdp_caps =
GST_STATIC_CAPS ("closedcaption/x-cea-708,format=(string) cdp");
GST_START_TEST (no_captions)
{
@ -91,7 +89,6 @@ samples_selected_cb (GstAggregator * agg, GstSegment * segment,
buflist = gst_sample_get_buffer_list (captions_sample);
fail_unless_equals_int (gst_buffer_list_length (buflist), 1);
fail_unless (gst_buffer_list_get (buflist, 0) == expected_caption_buffer);
gst_sample_unref (captions_sample);
gst_object_unref (caption_pad);
@ -106,6 +103,7 @@ GST_START_TEST (captions_and_eos)
GstCaps *caps;
GstVideoCaptionMeta *meta;
GstBuffer *second_video_buf, *second_caption_buf;
const guint8 cc_data[3] = { 0x0, 0x0, 0x0 };
h = gst_harness_new_with_padnames ("cccombiner", "sink", "src");
h2 = gst_harness_new_with_element (h->element, NULL, NULL);
@ -127,7 +125,8 @@ GST_START_TEST (captions_and_eos)
expected_video_buffer = buf;
gst_harness_push (h, buf);
buf = gst_buffer_new_and_alloc (128);
buf = gst_buffer_new_and_alloc (3);
gst_buffer_fill (buf, 0, cc_data, 3);
GST_BUFFER_PTS (buf) = 0;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
expected_caption_buffer = buf;
@ -141,7 +140,8 @@ GST_START_TEST (captions_and_eos)
second_video_buf = buf;
gst_harness_push (h, buf);
buf = gst_buffer_new_and_alloc (128);
buf = gst_buffer_new_and_alloc (3);
gst_buffer_fill (buf, 0, cc_data, 3);
GST_BUFFER_PTS (buf) = 40 * GST_MSECOND;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
second_caption_buf = buf;
@ -158,7 +158,7 @@ GST_START_TEST (captions_and_eos)
fail_unless (meta != NULL);
fail_unless_equals_int (meta->caption_type,
GST_VIDEO_CAPTION_TYPE_CEA708_RAW);
fail_unless_equals_int (meta->size, 128);
fail_unless_equals_int (meta->size, 3);
gst_buffer_unref (outbuf);
@ -174,91 +174,7 @@ GST_START_TEST (captions_and_eos)
fail_unless (meta != NULL);
fail_unless_equals_int (meta->caption_type,
GST_VIDEO_CAPTION_TYPE_CEA708_RAW);
fail_unless_equals_int (meta->size, 128);
gst_buffer_unref (outbuf);
/* Caps should be equal to input caps */
caps = gst_pad_get_current_caps (h->sinkpad);
fail_unless (caps != NULL);
fail_unless (gst_caps_can_intersect (caps,
gst_static_caps_get (&foo_bar_caps)));
gst_caps_unref (caps);
gst_harness_teardown (h);
gst_harness_teardown (h2);
}
GST_END_TEST;
GST_START_TEST (captions_type_change_and_eos)
{
GstHarness *h, *h2;
GstBuffer *buf, *outbuf;
GstPad *caption_pad;
GstCaps *caps;
GstVideoCaptionMeta *meta;
h = gst_harness_new_with_padnames ("cccombiner", "sink", "src");
h2 = gst_harness_new_with_element (h->element, NULL, NULL);
caption_pad = gst_element_get_request_pad (h->element, "caption");
gst_harness_add_element_sink_pad (h2, caption_pad);
gst_object_unref (caption_pad);
gst_harness_set_src_caps_str (h, foo_bar_caps.string);
gst_harness_set_src_caps_str (h2, cea708_cc_data_caps.string);
/* Push a buffer and caption buffer */
buf = gst_buffer_new_and_alloc (128);
GST_BUFFER_PTS (buf) = 0;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
gst_harness_push (h, buf);
buf = gst_buffer_new_and_alloc (128);
GST_BUFFER_PTS (buf) = 0;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
gst_harness_push (h2, buf);
/* Change caption type */
gst_harness_set_src_caps_str (h2, cea708_cdp_caps.string);
/* And another one: the first video buffer should be retrievable
* after the second caption buffer is pushed */
buf = gst_buffer_new_and_alloc (128);
GST_BUFFER_PTS (buf) = 40 * GST_MSECOND;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
gst_harness_push (h, buf);
buf = gst_buffer_new_and_alloc (128);
GST_BUFFER_PTS (buf) = 40 * GST_MSECOND;
GST_BUFFER_DURATION (buf) = 40 * GST_MSECOND;
gst_harness_push (h2, buf);
/* Pull the first output buffer */
outbuf = gst_harness_pull (h);
fail_unless (outbuf != NULL);
meta = gst_buffer_get_video_caption_meta (outbuf);
fail_unless (meta != NULL);
fail_unless_equals_int (meta->caption_type,
GST_VIDEO_CAPTION_TYPE_CEA708_RAW);
fail_unless_equals_int (meta->size, 128);
gst_buffer_unref (outbuf);
/* Push EOS on both pads get the second output buffer, we otherwise wait
* in case there are further captions for the current video buffer */
gst_harness_push_event (h, gst_event_new_eos ());
gst_harness_push_event (h2, gst_event_new_eos ());
outbuf = gst_harness_pull (h);
fail_unless (outbuf != NULL);
meta = gst_buffer_get_video_caption_meta (outbuf);
fail_unless (meta != NULL);
fail_unless_equals_int (meta->caption_type,
GST_VIDEO_CAPTION_TYPE_CEA708_CDP);
fail_unless_equals_int (meta->size, 128);
fail_unless_equals_int (meta->size, 3);
gst_buffer_unref (outbuf);
@ -285,7 +201,6 @@ cccombiner_suite (void)
tcase_add_test (tc, no_captions);
tcase_add_test (tc, captions_and_eos);
tcase_add_test (tc, captions_type_change_and_eos);
return s;
}