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:
Tim-Philipp Müller 2006-11-21 18:39:34 +00:00
parent 67e6bb5a92
commit 60ad667761
6 changed files with 1091 additions and 139 deletions

View file

@ -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):

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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

View 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);