mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 04:01:08 +00:00
ext/pango/gsttextoverlay.*: Some textoverlay fixes: for one, in the video chain function, actually wait for a text bu...
Original commit message from CVS: * ext/pango/gsttextoverlay.c: (gst_text_overlay_init), (gst_text_overlay_text_pad_unlink), (gst_text_overlay_text_event), (gst_text_overlay_video_event), (gst_text_overlay_pop_text), (gst_text_overlay_text_chain), (gst_text_overlay_video_chain), (gst_text_overlay_change_state): * ext/pango/gsttextoverlay.h: Some textoverlay fixes: for one, in the video chain function, actually wait for a text buffer to come in if there is none at the moment and there should be one; also, deal more gracefully with incoming buffers that do not have a timestamp or duration; discard text buffer when not needed any longer. Fixes #341681. * tests/check/Makefile.am: * tests/check/elements/.cvsignore: * tests/check/elements/textoverlay.c: (notgst_check_setup_src_pad2), (notgst_check_teardown_src_pad2), (setup_textoverlay), (buffer_is_all_black), (create_black_buffer), (create_text_buffer), (cleanup_textoverlay), (GST_START_TEST), (test_video_waits_for_text_send_text_newsegment_thread), (test_video_waits_for_text_shutdown_element), (test_render_continuity_push_video_buffers_thread), (textoverlay_suite): Add some unit tests for textoverlay.
This commit is contained in:
parent
67e6bb5a92
commit
60ad667761
6 changed files with 1091 additions and 139 deletions
26
ChangeLog
26
ChangeLog
|
@ -1,3 +1,29 @@
|
|||
2006-11-21 Tim-Philipp Müller <tim at centricular dot net>
|
||||
|
||||
* ext/pango/gsttextoverlay.c: (gst_text_overlay_init),
|
||||
(gst_text_overlay_text_pad_unlink), (gst_text_overlay_text_event),
|
||||
(gst_text_overlay_video_event), (gst_text_overlay_pop_text),
|
||||
(gst_text_overlay_text_chain), (gst_text_overlay_video_chain),
|
||||
(gst_text_overlay_change_state):
|
||||
* ext/pango/gsttextoverlay.h:
|
||||
Some textoverlay fixes: for one, in the video chain function,
|
||||
actually wait for a text buffer to come in if there is none at the
|
||||
moment and there should be one; also, deal more gracefully with
|
||||
incoming buffers that do not have a timestamp or duration; discard
|
||||
text buffer when not needed any longer. Fixes #341681.
|
||||
|
||||
* tests/check/Makefile.am:
|
||||
* tests/check/elements/.cvsignore:
|
||||
* tests/check/elements/textoverlay.c:
|
||||
(notgst_check_setup_src_pad2), (notgst_check_teardown_src_pad2),
|
||||
(setup_textoverlay), (buffer_is_all_black), (create_black_buffer),
|
||||
(create_text_buffer), (cleanup_textoverlay), (GST_START_TEST),
|
||||
(test_video_waits_for_text_send_text_newsegment_thread),
|
||||
(test_video_waits_for_text_shutdown_element),
|
||||
(test_render_continuity_push_video_buffers_thread),
|
||||
(textoverlay_suite):
|
||||
Add some unit tests for textoverlay.
|
||||
|
||||
2006-11-21 Tim-Philipp Müller <tim at centricular dot net>
|
||||
|
||||
* gst/typefind/gsttypefindfunctions.c: (mp3_type_find_at_offset):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2003> David Schleef <ds@schleef.org>
|
||||
* Copyright (C) <2006> Julien Moutte <julien@moutte.net>
|
||||
* Copyright (C) <2006> Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
|
@ -71,6 +72,7 @@
|
|||
* </refsect2>
|
||||
*/
|
||||
|
||||
/* FIXME: alloc segment as part of instance struct */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
|
@ -461,6 +463,7 @@ gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
|
|||
overlay->text_linked = FALSE;
|
||||
overlay->video_flushing = FALSE;
|
||||
overlay->text_flushing = FALSE;
|
||||
overlay->text_eos = FALSE;
|
||||
overlay->cond = g_cond_new ();
|
||||
overlay->segment = gst_segment_new ();
|
||||
if (overlay->segment) {
|
||||
|
@ -1053,6 +1056,8 @@ gst_text_overlay_text_pad_unlink (GstPad * pad)
|
|||
GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
|
||||
|
||||
overlay->text_linked = FALSE;
|
||||
|
||||
gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
@ -1063,14 +1068,38 @@ gst_text_overlay_text_event (GstPad * pad, GstEvent * event)
|
|||
|
||||
overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
|
||||
|
||||
GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
|
||||
GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
|
||||
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_NEWSEGMENT:
|
||||
/* We just ignore those events from the text pad */
|
||||
case GST_EVENT_NEWSEGMENT:{
|
||||
GstFormat fmt;
|
||||
gboolean update;
|
||||
gdouble rate, applied_rate;
|
||||
gint64 cur, stop, time;
|
||||
|
||||
overlay->text_eos = FALSE;
|
||||
|
||||
gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
|
||||
&fmt, &cur, &stop, &time);
|
||||
|
||||
if (fmt == GST_FORMAT_TIME) {
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
gst_segment_set_newsegment_full (&overlay->text_segment, update, rate,
|
||||
applied_rate, GST_FORMAT_TIME, cur, stop, time);
|
||||
GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
|
||||
&overlay->text_segment);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
gst_event_unref (event);
|
||||
ret = TRUE;
|
||||
|
||||
/* wake up the video chain, it might be waiting for a text buffer or
|
||||
* a text segment update */
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
GST_TEXT_OVERLAY_BROADCAST (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
break;
|
||||
}
|
||||
case GST_EVENT_FLUSH_STOP:
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
overlay->text_flushing = FALSE;
|
||||
|
@ -1089,17 +1118,18 @@ gst_text_overlay_text_event (GstPad * pad, GstEvent * event)
|
|||
break;
|
||||
case GST_EVENT_EOS:
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
/* We use flushing to make sure we return WRONG_STATE */
|
||||
/* FIXME, after EOS a _pad_push() returns _UNEXPECTED */
|
||||
overlay->text_flushing = TRUE;
|
||||
/* We don't signal anything here because we want to keep the last queued
|
||||
buffer until video pad receives EOS or discard the buffer */
|
||||
overlay->text_eos = TRUE;
|
||||
/* wake up the video chain, it might be waiting for a text buffer or
|
||||
* a text segment update */
|
||||
GST_TEXT_OVERLAY_BROADCAST (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
gst_event_unref (event);
|
||||
ret = TRUE;
|
||||
break;
|
||||
default:
|
||||
ret = gst_pad_event_default (pad, event);
|
||||
break;
|
||||
}
|
||||
|
||||
gst_object_unref (overlay);
|
||||
|
@ -1148,6 +1178,7 @@ gst_text_overlay_video_event (GstPad * pad, GstEvent * event)
|
|||
case GST_EVENT_FLUSH_START:
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
overlay->video_flushing = TRUE;
|
||||
GST_TEXT_OVERLAY_BROADCAST (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = gst_pad_event_default (pad, event);
|
||||
break;
|
||||
|
@ -1173,6 +1204,16 @@ gst_text_overlay_pop_text (GstTextOverlay * overlay)
|
|||
g_return_if_fail (GST_IS_TEXT_OVERLAY (overlay));
|
||||
|
||||
if (overlay->text_buffer) {
|
||||
/* update text_segment's last stop */
|
||||
if (overlay->text_segment.format == GST_FORMAT_TIME &&
|
||||
GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer)) {
|
||||
overlay->text_segment.last_stop =
|
||||
GST_BUFFER_TIMESTAMP (overlay->text_buffer);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
|
||||
overlay->text_segment.last_stop +=
|
||||
GST_BUFFER_DURATION (overlay->text_buffer);
|
||||
}
|
||||
}
|
||||
GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
|
||||
overlay->text_buffer);
|
||||
gst_buffer_unref (overlay->text_buffer);
|
||||
|
@ -1194,16 +1235,30 @@ gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer)
|
|||
gboolean in_seg = FALSE;
|
||||
gint64 clip_start = 0, clip_stop = 0;
|
||||
|
||||
overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
|
||||
overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
if (overlay->text_eos) {
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = GST_FLOW_UNEXPECTED;
|
||||
GST_LOG_OBJECT (overlay, "text EOS");
|
||||
goto beach;
|
||||
}
|
||||
|
||||
if (overlay->text_flushing) {
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = GST_FLOW_WRONG_STATE;
|
||||
GST_LOG_OBJECT (overlay, "text flushing");
|
||||
goto beach;
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
|
||||
GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, overlay->segment,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
|
||||
GST_BUFFER_DURATION (buffer)));
|
||||
|
||||
in_seg = gst_segment_clip (overlay->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_TIMESTAMP (buffer),
|
||||
GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer),
|
||||
|
@ -1229,12 +1284,14 @@ gst_text_overlay_text_chain (GstPad * pad, GstBuffer * buffer)
|
|||
overlay->text_buffer = buffer;
|
||||
/* That's a new text buffer we need to render */
|
||||
overlay->need_render = TRUE;
|
||||
|
||||
/* in case the video chain is waiting for a text buffer, wake it up */
|
||||
GST_TEXT_OVERLAY_BROADCAST (overlay);
|
||||
}
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
|
||||
beach:
|
||||
gst_object_unref (overlay);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1242,152 +1299,247 @@ beach:
|
|||
static GstFlowReturn
|
||||
gst_text_overlay_video_chain (GstPad * pad, GstBuffer * buffer)
|
||||
{
|
||||
GstTextOverlayClass *klass;
|
||||
GstTextOverlay *overlay;
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
GstTextOverlay *overlay = NULL;
|
||||
gboolean in_seg = FALSE;
|
||||
gint64 clip_start = 0, clip_stop = 0;
|
||||
GstTextOverlayClass *klass = NULL;
|
||||
gint64 start, stop, clip_start = 0, clip_stop = 0;
|
||||
gchar *text = NULL;
|
||||
|
||||
overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
|
||||
overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
|
||||
klass = GST_TEXT_OVERLAY_GET_CLASS (overlay);
|
||||
|
||||
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
|
||||
goto missing_timestamp;
|
||||
|
||||
/* ignore buffers that are outside of the current segment */
|
||||
start = GST_BUFFER_TIMESTAMP (buffer);
|
||||
|
||||
if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
|
||||
stop = GST_CLOCK_TIME_NONE;
|
||||
} else {
|
||||
stop = start + GST_BUFFER_DURATION (buffer);
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT " BUFFER: ts=%"
|
||||
GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, overlay->segment,
|
||||
GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
|
||||
|
||||
/* segment_clip() will adjust start unconditionally to segment_start if
|
||||
* no stop time is provided, so handle this ourselves */
|
||||
if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment->start)
|
||||
goto out_of_segment;
|
||||
|
||||
in_seg = gst_segment_clip (overlay->segment, GST_FORMAT_TIME, start, stop,
|
||||
&clip_start, &clip_stop);
|
||||
|
||||
if (!in_seg)
|
||||
goto out_of_segment;
|
||||
|
||||
/* if the buffer is only partially in the segment, fix up stamps */
|
||||
if (clip_start != start || (stop != -1 && clip_stop != stop)) {
|
||||
GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
|
||||
buffer = gst_buffer_make_metadata_writable (buffer);
|
||||
GST_BUFFER_TIMESTAMP (buffer) = clip_start;
|
||||
if (stop != -1)
|
||||
GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
|
||||
}
|
||||
|
||||
/* now, after we've done the clipping, fix up end time if there's no
|
||||
* duration (we only use those estimated values internally though, we
|
||||
* don't want to set bogus values on the buffer itself) */
|
||||
if (stop == -1) {
|
||||
GstStructure *s;
|
||||
gint fps_num, fps_denom;
|
||||
|
||||
s = gst_caps_get_structure (GST_PAD_CAPS (pad), 0);
|
||||
if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom)) {
|
||||
GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
|
||||
stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
|
||||
} else {
|
||||
GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
|
||||
stop = start + 1; /* we need to assume some interval */
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_text_buf:
|
||||
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
|
||||
if (overlay->video_flushing) {
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = GST_FLOW_WRONG_STATE;
|
||||
goto beach;
|
||||
}
|
||||
if (overlay->video_flushing)
|
||||
goto flushing;
|
||||
|
||||
in_seg = gst_segment_clip (overlay->segment, GST_FORMAT_TIME,
|
||||
GST_BUFFER_TIMESTAMP (buffer),
|
||||
GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer),
|
||||
&clip_start, &clip_stop);
|
||||
|
||||
if (in_seg) {
|
||||
gchar *text = NULL;
|
||||
|
||||
GST_BUFFER_TIMESTAMP (buffer) = clip_start;
|
||||
GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
|
||||
|
||||
/* Text pad not linked, rendering internal text */
|
||||
if (!overlay->text_linked) {
|
||||
if (klass->get_text) {
|
||||
text = klass->get_text (overlay, buffer);
|
||||
} else {
|
||||
text = g_strdup (overlay->default_text);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (overlay, "Text pad not linked, rendering default "
|
||||
"text: '%s'", GST_STR_NULL (text));
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
|
||||
if (text != NULL && *text != '\0') {
|
||||
/* Render and push */
|
||||
gst_text_overlay_render_text (overlay, text, -1);
|
||||
ret = gst_text_overlay_push_frame (overlay, buffer);
|
||||
} else {
|
||||
/* Invalid or empty string */
|
||||
ret = gst_pad_push (overlay->srcpad, buffer);
|
||||
}
|
||||
/* Text pad not linked, rendering internal text */
|
||||
if (!overlay->text_linked) {
|
||||
if (klass->get_text) {
|
||||
text = klass->get_text (overlay, buffer);
|
||||
} else {
|
||||
if (overlay->text_buffer) {
|
||||
gboolean pop_text = FALSE;
|
||||
gint64 text_end = 0;
|
||||
text = g_strdup (overlay->default_text);
|
||||
}
|
||||
|
||||
/* if the text buffer isn't stamped right, pop it off the
|
||||
* queue and display it for the current video frame only */
|
||||
if (GST_BUFFER_TIMESTAMP (overlay->text_buffer) == GST_CLOCK_TIME_NONE
|
||||
|| GST_BUFFER_DURATION (overlay->text_buffer) ==
|
||||
GST_CLOCK_TIME_NONE) {
|
||||
GST_WARNING_OBJECT (overlay,
|
||||
"Got text buffer with invalid time " "stamp or duration");
|
||||
gst_buffer_stamp (overlay->text_buffer, buffer);
|
||||
GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
|
||||
"text: '%s'", GST_STR_NULL (text));
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
|
||||
if (text != NULL && *text != '\0') {
|
||||
/* Render and push */
|
||||
gst_text_overlay_render_text (overlay, text, -1);
|
||||
ret = gst_text_overlay_push_frame (overlay, buffer);
|
||||
} else {
|
||||
/* Invalid or empty string */
|
||||
ret = gst_pad_push (overlay->srcpad, buffer);
|
||||
}
|
||||
} else {
|
||||
/* Text pad linked, check if we have a text buffer queued */
|
||||
if (overlay->text_buffer) {
|
||||
gboolean pop_text = FALSE;
|
||||
gint64 text_start, text_end;
|
||||
|
||||
/* if the text buffer isn't stamped right, pop it off the
|
||||
* queue and display it for the current video frame only */
|
||||
if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
|
||||
!GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
|
||||
GST_WARNING_OBJECT (overlay,
|
||||
"Got text buffer with invalid timestamp or duration");
|
||||
text_start = start;
|
||||
text_end = stop;
|
||||
pop_text = TRUE;
|
||||
} else {
|
||||
text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
|
||||
text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (text_start), GST_TIME_ARGS (text_end));
|
||||
GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
|
||||
|
||||
/* Text too old or in the future */
|
||||
if (text_end <= start) {
|
||||
/* text buffer too old, get rid of it and do nothing */
|
||||
GST_LOG_OBJECT (overlay, "text buffer too old, popping");
|
||||
pop_text = FALSE;
|
||||
gst_text_overlay_pop_text (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
goto wait_for_text_buf;
|
||||
} else if (stop <= text_start) {
|
||||
GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
/* Push the video frame */
|
||||
ret = gst_pad_push (overlay->srcpad, buffer);
|
||||
} else {
|
||||
gchar *in_text;
|
||||
gsize in_size;
|
||||
|
||||
in_text = (gchar *) GST_BUFFER_DATA (overlay->text_buffer);
|
||||
in_size = GST_BUFFER_SIZE (overlay->text_buffer);
|
||||
|
||||
/* g_markup_escape_text() absolutely requires valid UTF8 input, it
|
||||
* might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
|
||||
* here on purpose, this is something that needs fixing upstream */
|
||||
if (!g_utf8_validate (in_text, in_size, NULL)) {
|
||||
const gchar *end = NULL;
|
||||
|
||||
GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
|
||||
in_text = g_strndup (in_text, in_size);
|
||||
while (!g_utf8_validate (in_text, in_size, &end) && end)
|
||||
*((gchar *) end) = '*';
|
||||
}
|
||||
|
||||
/* Get the string */
|
||||
if (overlay->have_pango_markup) {
|
||||
text = g_strndup (in_text, in_size);
|
||||
} else {
|
||||
text = g_markup_escape_text (in_text, in_size);
|
||||
}
|
||||
|
||||
if (text != NULL && *text != '\0') {
|
||||
gint text_len = strlen (text);
|
||||
|
||||
while (text_len > 0 && (text[text_len - 1] == '\n' ||
|
||||
text[text_len - 1] == '\r')) {
|
||||
--text_len;
|
||||
}
|
||||
GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
|
||||
gst_text_overlay_render_text (overlay, text, text_len);
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
|
||||
gst_text_overlay_render_text (overlay, " ", 1);
|
||||
}
|
||||
|
||||
if (in_text != (gchar *) GST_BUFFER_DATA (overlay->text_buffer))
|
||||
g_free (in_text);
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = gst_text_overlay_push_frame (overlay, buffer);
|
||||
|
||||
if (text_end <= stop) {
|
||||
GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
|
||||
pop_text = TRUE;
|
||||
}
|
||||
|
||||
text_end = GST_BUFFER_TIMESTAMP (overlay->text_buffer) +
|
||||
GST_BUFFER_DURATION (overlay->text_buffer);
|
||||
|
||||
/* Text too old or in the future */
|
||||
if ((text_end < clip_start) ||
|
||||
(clip_stop < GST_BUFFER_TIMESTAMP (overlay->text_buffer))) {
|
||||
if (text_end < clip_start) {
|
||||
/* Get rid of it, if it's too old only */
|
||||
pop_text = FALSE;
|
||||
gst_text_overlay_pop_text (overlay);
|
||||
}
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
/* Push the video frame */
|
||||
ret = gst_pad_push (overlay->srcpad, buffer);
|
||||
} else {
|
||||
gchar *in_text;
|
||||
gsize in_size;
|
||||
|
||||
in_text = (gchar *) GST_BUFFER_DATA (overlay->text_buffer);
|
||||
in_size = GST_BUFFER_SIZE (overlay->text_buffer);
|
||||
|
||||
/* g_markup_escape_text() absolutely requires valid UTF8 input, it
|
||||
* might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
|
||||
* here on purpose, this is something that needs fixing upstream */
|
||||
if (!g_utf8_validate (in_text, in_size, NULL)) {
|
||||
const gchar *end = NULL;
|
||||
|
||||
GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
|
||||
in_text = g_strndup (in_text, in_size);
|
||||
while (!g_utf8_validate (in_text, in_size, &end) && end)
|
||||
*((gchar *) end) = '*';
|
||||
}
|
||||
|
||||
/* Get the string */
|
||||
if (overlay->have_pango_markup) {
|
||||
text = g_strndup (in_text, in_size);
|
||||
} else {
|
||||
text = g_markup_escape_text (in_text, in_size);
|
||||
}
|
||||
|
||||
if (text != NULL && *text != '\0') {
|
||||
gint text_len = strlen (text);
|
||||
|
||||
while (text_len > 0 && (text[text_len - 1] == '\n' ||
|
||||
text[text_len - 1] == '\r')) {
|
||||
--text_len;
|
||||
}
|
||||
GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
|
||||
gst_text_overlay_render_text (overlay, text, text_len);
|
||||
} else {
|
||||
GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
|
||||
gst_text_overlay_render_text (overlay, " ", 1);
|
||||
}
|
||||
|
||||
if (in_text != (gchar *) GST_BUFFER_DATA (overlay->text_buffer))
|
||||
g_free (in_text);
|
||||
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
ret = gst_text_overlay_push_frame (overlay, buffer);
|
||||
}
|
||||
} else {
|
||||
/* No text to overlay, push the frame as is */
|
||||
}
|
||||
if (pop_text) {
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
gst_text_overlay_pop_text (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
}
|
||||
} else {
|
||||
gboolean wait_for_text_buf = TRUE;
|
||||
|
||||
if (overlay->text_eos)
|
||||
wait_for_text_buf = FALSE;
|
||||
|
||||
/* Text pad linked, but no text buffer available - what now? */
|
||||
if (overlay->text_segment.format == GST_FORMAT_TIME) {
|
||||
if (GST_BUFFER_TIMESTAMP (buffer) < overlay->text_segment.start ||
|
||||
GST_BUFFER_TIMESTAMP (buffer) < overlay->text_segment.last_stop) {
|
||||
wait_for_text_buf = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_for_text_buf) {
|
||||
GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
|
||||
GST_TEXT_OVERLAY_WAIT (overlay);
|
||||
GST_DEBUG_OBJECT (overlay, "resuming");
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
goto wait_for_text_buf;
|
||||
} else {
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
|
||||
ret = gst_pad_push (overlay->srcpad, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (text);
|
||||
|
||||
/* Update last_stop */
|
||||
gst_segment_set_last_stop (overlay->segment, GST_FORMAT_TIME, clip_start);
|
||||
} else { /* Out of segment */
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
GST_DEBUG_OBJECT (overlay, "buffer out of segment discarding");
|
||||
gst_buffer_unref (buffer);
|
||||
}
|
||||
|
||||
beach:
|
||||
gst_object_unref (overlay);
|
||||
g_free (text);
|
||||
|
||||
/* Update last_stop */
|
||||
gst_segment_set_last_stop (overlay->segment, GST_FORMAT_TIME, clip_start);
|
||||
|
||||
return ret;
|
||||
|
||||
missing_timestamp:
|
||||
{
|
||||
GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
|
||||
gst_buffer_unref (buffer);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
flushing:
|
||||
{
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
|
||||
gst_buffer_unref (buffer);
|
||||
return GST_FLOW_WRONG_STATE;
|
||||
}
|
||||
|
||||
out_of_segment:
|
||||
{
|
||||
GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
|
||||
gst_buffer_unref (buffer);
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
|
@ -1397,11 +1549,12 @@ gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
|
|||
GstTextOverlay *overlay = GST_TEXT_OVERLAY (element);
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
break;
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
overlay->text_flushing = TRUE;
|
||||
overlay->video_flushing = TRUE;
|
||||
/* pop_text will broadcast on the GCond and thus also make the video
|
||||
* chain exit if it's waiting for a text buffer */
|
||||
gst_text_overlay_pop_text (overlay);
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
break;
|
||||
|
@ -1414,6 +1567,12 @@ gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
|
|||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
GST_OBJECT_LOCK (overlay);
|
||||
overlay->text_flushing = FALSE;
|
||||
overlay->video_flushing = FALSE;
|
||||
GST_OBJECT_UNLOCK (overlay);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -78,12 +78,17 @@ struct _GstTextOverlay {
|
|||
GstPad *srcpad;
|
||||
|
||||
GstSegment *segment;
|
||||
GstSegment text_segment;
|
||||
GstBuffer *text_buffer;
|
||||
gboolean text_linked;
|
||||
gboolean video_flushing;
|
||||
gboolean text_flushing;
|
||||
|
||||
GCond *cond; /* to signal removal of data */
|
||||
gboolean text_eos;
|
||||
|
||||
GCond *cond; /* to signal removal of a queued text
|
||||
* buffer, arrival of a text buffer,
|
||||
* a text segment update, or a change
|
||||
* in status (e.g. shutdown, flushing) */
|
||||
|
||||
gint width;
|
||||
gint height;
|
||||
|
|
|
@ -33,6 +33,12 @@ else
|
|||
check_ogg =
|
||||
endif
|
||||
|
||||
if USE_PANGO
|
||||
check_pango = elements/textoverlay
|
||||
else
|
||||
check_pango =
|
||||
endif
|
||||
|
||||
if USE_VORBIS
|
||||
check_vorbis = elements/vorbisdec pipelines/vorbisenc elements/vorbistag
|
||||
else
|
||||
|
@ -48,6 +54,7 @@ endif
|
|||
check_PROGRAMS = \
|
||||
$(check_alsa) \
|
||||
$(check_ogg) \
|
||||
$(check_pango) \
|
||||
$(check_vorbis) \
|
||||
$(check_theora) \
|
||||
elements/adder \
|
||||
|
@ -143,6 +150,9 @@ elements_playbin_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS)
|
|||
elements_subparse_LDADD = $(LDADD)
|
||||
elements_subparse_CFLAGS = $(CFLAGS) $(AM_CFLAGS)
|
||||
|
||||
elements_textoverlay_LDADD = $(GST_BASE_LIBS) $(LDADD)
|
||||
elements_textoverlay_CFLAGS = $(GST_BASE_CFLAGS) $(AM_CFLAGS)
|
||||
|
||||
elements_volume_LDADD = \
|
||||
$(GST_BASE_LIBS) \
|
||||
$(LDADD)
|
||||
|
|
3
tests/check/elements/.gitignore
vendored
3
tests/check/elements/.gitignore
vendored
|
@ -5,6 +5,7 @@ audioconvert
|
|||
audiorate
|
||||
audioresample
|
||||
audiotestsrc
|
||||
decodebin
|
||||
gdpdepay
|
||||
gdppay
|
||||
multifdsink
|
||||
|
@ -12,6 +13,8 @@ videorate
|
|||
videotestsrc
|
||||
volume
|
||||
vorbisdec
|
||||
typefindfunctions
|
||||
textoverlay
|
||||
ffmpegcolorspace
|
||||
vorbistag
|
||||
playbin
|
||||
|
|
749
tests/check/elements/textoverlay.c
Normal file
749
tests/check/elements/textoverlay.c
Normal file
|
@ -0,0 +1,749 @@
|
|||
/* GStreamer unit tests for textoverlay
|
||||
*
|
||||
* Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
|
||||
*
|
||||
* 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., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <gst/check/gstcheck.h>
|
||||
|
||||
#define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
|
||||
#define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
|
||||
#define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
|
||||
|
||||
#define I420_Y_OFFSET(w,h) (0)
|
||||
#define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
|
||||
#define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
|
||||
|
||||
#define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
|
||||
|
||||
#define WIDTH 240
|
||||
#define HEIGHT 120
|
||||
|
||||
gboolean have_eos = FALSE;
|
||||
|
||||
/* For ease of programming we use globals to keep refs for our floating
|
||||
* src and sink pads we create; otherwise we always have to do get_pad,
|
||||
* get_peer, and then remove references in every test function */
|
||||
static GstPad *myvideosrcpad, *mytextsrcpad, *mysinkpad;
|
||||
|
||||
#define VIDEO_CAPS_STRING \
|
||||
"video/x-raw-yuv, " \
|
||||
"format = (fourcc) I420, " \
|
||||
"framerate = (fraction) 1/1, " \
|
||||
"width = (int) 240, " \
|
||||
"height = (int) 120"
|
||||
|
||||
#define VIDEO_CAPS_TEMPLATE_STRING \
|
||||
"video/x-raw-yuv, " \
|
||||
"format = (fourcc) I420"
|
||||
|
||||
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (VIDEO_CAPS_TEMPLATE_STRING)
|
||||
);
|
||||
static GstStaticPadTemplate text_srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS ("text/plain")
|
||||
);
|
||||
|
||||
static GstStaticPadTemplate video_srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (VIDEO_CAPS_TEMPLATE_STRING)
|
||||
);
|
||||
|
||||
/* much like gst_check_setup_src_pad(), but with possibility to give a hint
|
||||
* which sink template of the element to use, if there are multiple ones */
|
||||
static GstPad *
|
||||
notgst_check_setup_src_pad2 (GstElement * element,
|
||||
GstStaticPadTemplate * template, GstCaps * caps,
|
||||
const gchar * sink_template_name)
|
||||
{
|
||||
GstPad *srcpad, *sinkpad;
|
||||
|
||||
if (sink_template_name == NULL)
|
||||
sink_template_name = "sink";
|
||||
|
||||
/* sending pad */
|
||||
srcpad = gst_pad_new_from_static_template (template, "src");
|
||||
GST_DEBUG_OBJECT (element, "setting up sending pad %p", srcpad);
|
||||
fail_if (srcpad == NULL, "Could not create a srcpad");
|
||||
ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1);
|
||||
|
||||
sinkpad = gst_element_get_pad (element, sink_template_name);
|
||||
fail_if (sinkpad == NULL, "Could not get sink pad from %s",
|
||||
GST_ELEMENT_NAME (element));
|
||||
ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2);
|
||||
if (caps)
|
||||
fail_unless (gst_pad_set_caps (srcpad, caps));
|
||||
fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK,
|
||||
"Could not link source and %s sink pads", GST_ELEMENT_NAME (element));
|
||||
gst_object_unref (sinkpad); /* because we got it higher up */
|
||||
ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 1);
|
||||
|
||||
return srcpad;
|
||||
}
|
||||
|
||||
static void
|
||||
notgst_check_teardown_src_pad2 (GstElement * element,
|
||||
const gchar * sink_template_name)
|
||||
{
|
||||
GstPad *srcpad, *sinkpad;
|
||||
|
||||
if (sink_template_name == NULL)
|
||||
sink_template_name = "sink";
|
||||
|
||||
/* clean up floating src pad */
|
||||
sinkpad = gst_element_get_pad (element, sink_template_name);
|
||||
ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2);
|
||||
srcpad = gst_pad_get_peer (sinkpad);
|
||||
|
||||
gst_pad_unlink (srcpad, sinkpad);
|
||||
|
||||
/* caps could have been set, make sure they get unset */
|
||||
gst_pad_set_caps (srcpad, NULL);
|
||||
|
||||
/* pad refs held by both creator and this function (through _get) */
|
||||
ASSERT_OBJECT_REFCOUNT (sinkpad, "element sinkpad", 2);
|
||||
gst_object_unref (sinkpad);
|
||||
/* one more ref is held by element itself */
|
||||
|
||||
/* pad refs held by both creator and this function (through _get_peer) */
|
||||
ASSERT_OBJECT_REFCOUNT (srcpad, "check srcpad", 2);
|
||||
gst_object_unref (srcpad);
|
||||
gst_object_unref (srcpad);
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
setup_textoverlay (gboolean video_only_no_text)
|
||||
{
|
||||
GstElement *textoverlay;
|
||||
|
||||
GST_DEBUG ("setup_textoverlay");
|
||||
textoverlay = gst_check_setup_element ("textoverlay");
|
||||
mysinkpad = gst_check_setup_sink_pad (textoverlay, &sinktemplate, NULL);
|
||||
myvideosrcpad =
|
||||
notgst_check_setup_src_pad2 (textoverlay, &video_srctemplate, NULL,
|
||||
"video_sink");
|
||||
|
||||
if (!video_only_no_text) {
|
||||
mytextsrcpad =
|
||||
notgst_check_setup_src_pad2 (textoverlay, &text_srctemplate, NULL,
|
||||
"text_sink");
|
||||
gst_pad_set_active (mytextsrcpad, TRUE);
|
||||
} else {
|
||||
mytextsrcpad = NULL;
|
||||
}
|
||||
|
||||
gst_pad_set_active (myvideosrcpad, TRUE);
|
||||
gst_pad_set_active (mysinkpad, TRUE);
|
||||
|
||||
return textoverlay;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
buffer_is_all_black (GstBuffer * buf)
|
||||
{
|
||||
GstStructure *s;
|
||||
gint x, y, w, h;
|
||||
|
||||
fail_unless (buf != NULL);
|
||||
fail_unless (GST_BUFFER_CAPS (buf) != NULL);
|
||||
s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
|
||||
fail_unless (s != NULL);
|
||||
fail_unless (gst_structure_get_int (s, "width", &w));
|
||||
fail_unless (gst_structure_get_int (s, "height", &h));
|
||||
|
||||
for (y = 0; y < h; ++y) {
|
||||
guint8 *data = GST_BUFFER_DATA (buf) + (y * GST_ROUND_UP_4 (w));
|
||||
|
||||
for (x = 0; x < w; ++x) {
|
||||
if (data[x] != 0x00) {
|
||||
GST_LOG ("non-black pixel at (x,y) %d,%d", x, y);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
create_black_buffer (const gchar * caps_string)
|
||||
{
|
||||
GstStructure *s;
|
||||
GstBuffer *buffer;
|
||||
GstCaps *caps;
|
||||
gint w, h, size;
|
||||
|
||||
fail_unless (caps_string != NULL);
|
||||
|
||||
caps = gst_caps_from_string (caps_string);
|
||||
fail_unless (caps != NULL);
|
||||
fail_unless (gst_caps_is_fixed (caps));
|
||||
|
||||
s = gst_caps_get_structure (caps, 0);
|
||||
fail_unless (gst_structure_get_int (s, "width", &w));
|
||||
fail_unless (gst_structure_get_int (s, "height", &h));
|
||||
|
||||
GST_LOG ("creating buffer (%dx%d)", w, h);
|
||||
size = I420_SIZE (w, h);
|
||||
buffer = gst_buffer_new_and_alloc (size);
|
||||
/* we're only checking the Y plane later, so just zero it all out,
|
||||
* even if it's not the blackest black there is */
|
||||
memset (GST_BUFFER_DATA (buffer), 0, size);
|
||||
|
||||
gst_buffer_set_caps (buffer, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
/* double check to make sure it's been created right */
|
||||
fail_unless (buffer_is_all_black (buffer));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static GstBuffer *
|
||||
create_text_buffer (const gchar * txt, GstClockTime ts, GstClockTime duration)
|
||||
{
|
||||
GstBuffer *buffer;
|
||||
GstCaps *caps;
|
||||
guint txt_len;
|
||||
|
||||
fail_unless (txt != NULL);
|
||||
|
||||
txt_len = strlen (txt);
|
||||
|
||||
buffer = gst_buffer_new_and_alloc (txt_len);
|
||||
memcpy (GST_BUFFER_DATA (buffer), txt, txt_len);
|
||||
|
||||
GST_BUFFER_TIMESTAMP (buffer) = ts;
|
||||
GST_BUFFER_DURATION (buffer) = duration;
|
||||
|
||||
caps = gst_caps_new_simple ("text/plain", NULL);
|
||||
gst_buffer_set_caps (buffer, caps);
|
||||
gst_caps_unref (caps);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup_textoverlay (GstElement * textoverlay)
|
||||
{
|
||||
GST_DEBUG ("cleanup_textoverlay");
|
||||
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
|
||||
gst_element_set_state (textoverlay, GST_STATE_NULL);
|
||||
gst_element_get_state (textoverlay, NULL, NULL, GST_CLOCK_TIME_NONE);
|
||||
notgst_check_teardown_src_pad2 (textoverlay, "video_sink");
|
||||
if (mytextsrcpad) {
|
||||
notgst_check_teardown_src_pad2 (textoverlay, "text_sink");
|
||||
}
|
||||
gst_check_teardown_sink_pad (textoverlay);
|
||||
gst_check_teardown_element (textoverlay);
|
||||
}
|
||||
|
||||
GST_START_TEST (test_video_passthrough)
|
||||
{
|
||||
GstElement *textoverlay;
|
||||
GstBuffer *inbuffer;
|
||||
|
||||
textoverlay = setup_textoverlay (TRUE);
|
||||
fail_unless (gst_element_set_state (textoverlay,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||
"could not set to playing");
|
||||
|
||||
inbuffer = create_black_buffer (VIDEO_CAPS_STRING);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* ========== (1) video buffer without timestamp => should be dropped ==== */
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* should have been discarded as out-of-segment since it has no timestamp */
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless_equals_int (g_list_length (buffers), 0);
|
||||
|
||||
/* ========== (2) buffer with 0 timestamp => simple passthrough ========== */
|
||||
|
||||
/* now try again, this time with timestamp (segment defaults to 0 start) */
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 0;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_CLOCK_TIME_NONE;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* text pad is not linked, timestamp is in segment, no static text to
|
||||
* render, should have gone through right away without modification */
|
||||
fail_unless_equals_int (g_list_length (buffers), 1);
|
||||
fail_unless (GST_BUFFER_CAST (buffers->data) == inbuffer);
|
||||
fail_unless (buffer_is_all_black (inbuffer));
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* and clean up */
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* ========== (3) buffer with 0 timestamp and no duration, with the
|
||||
* segment starting from 1sec => should be discarded */
|
||||
|
||||
gst_pad_push_event (myvideosrcpad,
|
||||
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 1 * GST_SECOND,
|
||||
-1, 0));
|
||||
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 0;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_CLOCK_TIME_NONE;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* should have been discarded as out-of-segment */
|
||||
fail_unless_equals_int (g_list_length (buffers), 0);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* ========== (4) buffer with 0 timestamp and small defined duration, with
|
||||
* segment starting from 1sec => should be discarded */
|
||||
|
||||
gst_pad_push_event (myvideosrcpad,
|
||||
gst_event_new_new_segment (FALSE, 1.0, 1 * GST_FORMAT_TIME, GST_SECOND,
|
||||
-1, 0));
|
||||
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND / 10;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* should have been discareded as out-of-segment since it has no timestamp */
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless_equals_int (g_list_length (buffers), 0);
|
||||
|
||||
/* ========== (5) buffer partially overlapping into the segment => should
|
||||
* be pushed through, but with adjusted stamp values */
|
||||
|
||||
gst_pad_push_event (myvideosrcpad,
|
||||
gst_event_new_new_segment (FALSE, 1.0, 1 * GST_FORMAT_TIME, GST_SECOND,
|
||||
-1, 0));
|
||||
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = GST_SECOND / 4;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* should be the parent for a new subbuffer for the stamp fix-up */
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
fail_unless_equals_int (g_list_length (buffers), 1);
|
||||
fail_unless (GST_BUFFER_CAST (buffers->data) != inbuffer);
|
||||
fail_unless (GST_BUFFER_TIMESTAMP (GST_BUFFER_CAST (buffers->data)) ==
|
||||
GST_SECOND);
|
||||
fail_unless (GST_BUFFER_DURATION (GST_BUFFER_CAST (buffers->data)) ==
|
||||
(GST_SECOND / 4));
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER_CAST (buffers->data)));
|
||||
/* and clean up */
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* cleanup */
|
||||
cleanup_textoverlay (textoverlay);
|
||||
gst_buffer_unref (inbuffer);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_video_render_static_text)
|
||||
{
|
||||
GstElement *textoverlay;
|
||||
GstBuffer *inbuffer;
|
||||
|
||||
textoverlay = setup_textoverlay (TRUE);
|
||||
|
||||
/* set static text to render */
|
||||
g_object_set (textoverlay, "text", "XLX", NULL);
|
||||
|
||||
fail_unless (gst_element_set_state (textoverlay,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||
"could not set to playing");
|
||||
|
||||
inbuffer = create_black_buffer (VIDEO_CAPS_STRING);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 0;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND / 10;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* should have been dropped in favour of a new writable buffer */
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
fail_unless_equals_int (g_list_length (buffers), 1);
|
||||
fail_unless (GST_BUFFER_CAST (buffers->data) != inbuffer);
|
||||
|
||||
/* there should be text rendered */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER_CAST (buffers->data)) == FALSE);
|
||||
|
||||
fail_unless (GST_BUFFER_TIMESTAMP (GST_BUFFER_CAST (buffers->data)) == 0);
|
||||
fail_unless (GST_BUFFER_DURATION (GST_BUFFER_CAST (buffers->data)) ==
|
||||
(GST_SECOND / 10));
|
||||
|
||||
/* and clean up */
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* cleanup */
|
||||
cleanup_textoverlay (textoverlay);
|
||||
gst_buffer_unref (inbuffer);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
static gpointer
|
||||
test_video_waits_for_text_send_text_newsegment_thread (gpointer data)
|
||||
{
|
||||
g_usleep (1 * G_USEC_PER_SEC);
|
||||
|
||||
/* send an update newsegment; the video buffer should now be pushed through
|
||||
* even though there is no text buffer queued at the moment */
|
||||
GST_INFO ("Sending newsegment update on text pad");
|
||||
gst_pad_push_event (mytextsrcpad,
|
||||
gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME,
|
||||
35 * GST_SECOND, -1, 35 * GST_SECOND));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
test_video_waits_for_text_shutdown_element (gpointer data)
|
||||
{
|
||||
g_usleep (1 * G_USEC_PER_SEC);
|
||||
|
||||
GST_INFO ("Trying to shut down textoverlay element ...");
|
||||
/* set to NULL state to make sure we can shut it down while it's
|
||||
* blocking in the video chain function waiting for a text buffer */
|
||||
gst_element_set_state (GST_ELEMENT (data), GST_STATE_NULL);
|
||||
GST_INFO ("Done.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GST_START_TEST (test_video_waits_for_text)
|
||||
{
|
||||
GstElement *textoverlay;
|
||||
GstBuffer *inbuffer, *tbuf;
|
||||
GThread *thread;
|
||||
|
||||
textoverlay = setup_textoverlay (FALSE);
|
||||
|
||||
fail_unless (gst_element_set_state (textoverlay,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||
"could not set to playing");
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 1 * GST_SECOND, 5 * GST_SECOND);
|
||||
gst_buffer_ref (tbuf);
|
||||
ASSERT_BUFFER_REFCOUNT (tbuf, "tbuf", 2);
|
||||
|
||||
GST_LOG ("pushing text buffer");
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
/* it should be stuck in textoverlay until it gets a text buffer or a
|
||||
* newsegment event that indicates it's not needed any longer */
|
||||
fail_unless_equals_int (g_list_length (buffers), 0);
|
||||
|
||||
inbuffer = create_black_buffer (VIDEO_CAPS_STRING);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 0;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND / 2;
|
||||
|
||||
/* take additional ref to keep it alive */
|
||||
gst_buffer_ref (inbuffer);
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 2);
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
GST_LOG ("pushing video buffer 1");
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* video buffer should have gone through untainted, since the text is later */
|
||||
fail_unless_equals_int (g_list_length (buffers), 1);
|
||||
|
||||
/* text should still be stuck in textoverlay */
|
||||
ASSERT_BUFFER_REFCOUNT (tbuf, "tbuf", 2);
|
||||
|
||||
/* there should be no text rendered */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER_CAST (buffers->data)));
|
||||
|
||||
/* now, another video buffer */
|
||||
inbuffer = gst_buffer_make_metadata_writable (inbuffer);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = GST_SECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND / 2;
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
GST_LOG ("pushing video buffer 2");
|
||||
gst_buffer_ref (inbuffer);
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* video buffer should have gone right away, with text rendered on it */
|
||||
fail_unless_equals_int (g_list_length (buffers), 2);
|
||||
|
||||
/* text should still be stuck in textoverlay */
|
||||
ASSERT_BUFFER_REFCOUNT (tbuf, "tbuf", 2);
|
||||
|
||||
/* there should be text rendered */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER_CAST (buffers->next->data)) ==
|
||||
FALSE);
|
||||
|
||||
/* a third video buffer */
|
||||
inbuffer = gst_buffer_make_metadata_writable (inbuffer);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 30 * GST_SECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND / 2;
|
||||
|
||||
/* video buffer #3: should not go through, it should discard the current
|
||||
* text buffer as too old and then wait for the next text buffer (or a
|
||||
* newsegment event to arrive); we spawn a background thread to send such
|
||||
* a newsegment event after a second or so so we get back control */
|
||||
thread =
|
||||
g_thread_create (test_video_waits_for_text_send_text_newsegment_thread,
|
||||
NULL, FALSE, NULL);
|
||||
fail_unless (thread != NULL);
|
||||
|
||||
GST_LOG ("pushing video buffer 3");
|
||||
gst_buffer_ref (inbuffer);
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_OK);
|
||||
|
||||
/* but the text should no longer be stuck in textoverlay */
|
||||
ASSERT_BUFFER_REFCOUNT (tbuf, "tbuf", 1);
|
||||
|
||||
/* video buffer should have gone through after newsegment event */
|
||||
fail_unless_equals_int (g_list_length (buffers), 3);
|
||||
|
||||
/* ... and there should not be any text rendered on it */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER_CAST (buffers->next->next->
|
||||
data)));
|
||||
|
||||
/* a fourth video buffer */
|
||||
inbuffer = gst_buffer_make_metadata_writable (inbuffer);
|
||||
GST_BUFFER_TIMESTAMP (inbuffer) = 35 * GST_SECOND;
|
||||
GST_BUFFER_DURATION (inbuffer) = GST_SECOND;
|
||||
|
||||
/* video buffer #4: should not go through, it should wait for the next
|
||||
* text buffer (or a newsegment event) to arrive; we spawn a background
|
||||
* thread to shut down the element while it's waiting to make sure that
|
||||
* works ok */
|
||||
thread = g_thread_create (test_video_waits_for_text_shutdown_element,
|
||||
textoverlay, FALSE, NULL);
|
||||
fail_unless (thread != NULL);
|
||||
|
||||
GST_LOG ("pushing video buffer 4");
|
||||
gst_buffer_ref (inbuffer);
|
||||
fail_unless (gst_pad_push (myvideosrcpad, inbuffer) == GST_FLOW_WRONG_STATE);
|
||||
|
||||
/* and clean up */
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
|
||||
|
||||
/* cleanup */
|
||||
cleanup_textoverlay (textoverlay);
|
||||
gst_buffer_unref (inbuffer);
|
||||
|
||||
/* give up our ref, textoverlay should've cleared its queued buffer by now */
|
||||
ASSERT_BUFFER_REFCOUNT (tbuf, "tbuf", 1);
|
||||
gst_buffer_unref (tbuf);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
static gpointer
|
||||
test_render_continuity_push_video_buffers_thread (gpointer data)
|
||||
{
|
||||
/* push video buffers at 1fps */
|
||||
guint frame_count = 0;
|
||||
|
||||
do {
|
||||
GstBuffer *vbuf;
|
||||
|
||||
vbuf = create_black_buffer (VIDEO_CAPS_STRING);
|
||||
ASSERT_BUFFER_REFCOUNT (vbuf, "vbuf", 1);
|
||||
|
||||
GST_BUFFER_TIMESTAMP (vbuf) = frame_count * GST_SECOND;
|
||||
GST_BUFFER_DURATION (vbuf) = GST_SECOND;
|
||||
|
||||
/* pushing gives away one of the two references we have ... */
|
||||
GST_LOG ("pushing video buffer %u @ %" GST_TIME_FORMAT, frame_count,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (vbuf)));
|
||||
fail_unless (gst_pad_push (myvideosrcpad, vbuf) == GST_FLOW_OK);
|
||||
|
||||
++frame_count;
|
||||
} while (frame_count < 15);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
GST_START_TEST (test_render_continuity)
|
||||
{
|
||||
GThread *thread;
|
||||
GstElement *textoverlay;
|
||||
GstBuffer *tbuf;
|
||||
|
||||
textoverlay = setup_textoverlay (FALSE);
|
||||
|
||||
fail_unless (gst_element_set_state (textoverlay,
|
||||
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
|
||||
"could not set to playing");
|
||||
|
||||
thread = g_thread_create (test_render_continuity_push_video_buffers_thread,
|
||||
NULL, FALSE, NULL);
|
||||
fail_unless (thread != NULL);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 2 * GST_SECOND, GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 3 * GST_SECOND, 2 * GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 7 * GST_SECOND, GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 8 * GST_SECOND, GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 9 * GST_SECOND, GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
tbuf = create_text_buffer ("XLX", 10 * GST_SECOND, 30 * GST_SECOND);
|
||||
GST_LOG ("pushing text buffer @ %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (tbuf)));
|
||||
fail_unless (gst_pad_push (mytextsrcpad, tbuf) == GST_FLOW_OK);
|
||||
|
||||
GST_LOG ("give the other thread some time to push through the remaining"
|
||||
"video buffers");
|
||||
g_usleep (G_USEC_PER_SEC);
|
||||
GST_LOG ("done");
|
||||
|
||||
/* we should have 15 buffers each with one second length now */
|
||||
fail_unless_equals_int (g_list_length (buffers), 15);
|
||||
|
||||
/* buffers 0 + 1 should be black */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers, 0))));
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers, 1))));
|
||||
|
||||
/* buffers 2 - 4 should have text */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
2))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
3))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
4))) == FALSE);
|
||||
|
||||
/* buffers 5 + 6 should be black */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers, 5))));
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers, 6))));
|
||||
|
||||
/* buffers 7 - last should have text */
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
7))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
8))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
9))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
10))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
11))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
12))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
13))) == FALSE);
|
||||
fail_unless (buffer_is_all_black (GST_BUFFER (g_list_nth_data (buffers,
|
||||
14))) == FALSE);
|
||||
|
||||
/* and clean up */
|
||||
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
||||
g_list_free (buffers);
|
||||
buffers = NULL;
|
||||
|
||||
/* cleanup */
|
||||
cleanup_textoverlay (textoverlay);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
static Suite *
|
||||
textoverlay_suite (void)
|
||||
{
|
||||
Suite *s = suite_create ("textoverlay");
|
||||
TCase *tc_chain = tcase_create ("general");
|
||||
|
||||
suite_add_tcase (s, tc_chain);
|
||||
|
||||
tcase_add_test (tc_chain, test_video_passthrough);
|
||||
tcase_add_test (tc_chain, test_video_render_static_text);
|
||||
tcase_add_test (tc_chain, test_render_continuity);
|
||||
tcase_add_test (tc_chain, test_video_waits_for_text);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
GST_CHECK_MAIN (textoverlay);
|
Loading…
Reference in a new issue