From 8af7381d5cac93a8af28a303533f34de7aade137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 7 Nov 2005 15:09:54 +0000 Subject: [PATCH] ext/cairo/: Port cairo textoverlay plugin to 0.9. Add 'shaded-background' property and redo position. Doesn't handle ... Original commit message from CVS: * ext/cairo/Makefile.am: * ext/cairo/gstcairo.c: (plugin_init): * ext/cairo/gsttextoverlay.c: (gst_text_overlay_base_init), (gst_text_overlay_class_init), (gst_text_overlay_finalize), (gst_text_overlay_init), (gst_text_overlay_font_init), (gst_text_overlay_set_property), (gst_text_overlay_render_text), (gst_text_overlay_getcaps), (gst_text_overlay_setcaps), (gst_text_overlay_text_pad_linked), (gst_text_overlay_text_pad_unlinked), (gst_text_overlay_shade_y), (gst_text_overlay_blit_1), (gst_text_overlay_blit_sub2x2), (gst_text_overlay_push_frame), (gst_text_overlay_pop_video), (gst_text_overlay_pop_text), (gst_text_overlay_collected), (gst_text_overlay_change_state): * ext/cairo/gsttextoverlay.h: Port cairo textoverlay plugin to 0.9. Add 'shaded-background' property and redo position. Doesn't handle upstream renegotiation yet though. --- ChangeLog | 20 + ext/cairo/Makefile.am | 10 +- ext/cairo/gstcairo.c | 5 +- ext/cairo/gsttextoverlay.c | 1232 +++++++++++++++++++++--------------- ext/cairo/gsttextoverlay.h | 67 +- 5 files changed, 776 insertions(+), 558 deletions(-) diff --git a/ChangeLog b/ChangeLog index 785bdc03b6..502b4a8415 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2005-11-07 Tim-Philipp Müller + + * ext/cairo/Makefile.am: + * ext/cairo/gstcairo.c: (plugin_init): + * ext/cairo/gsttextoverlay.c: (gst_text_overlay_base_init), + (gst_text_overlay_class_init), (gst_text_overlay_finalize), + (gst_text_overlay_init), (gst_text_overlay_font_init), + (gst_text_overlay_set_property), (gst_text_overlay_render_text), + (gst_text_overlay_getcaps), (gst_text_overlay_setcaps), + (gst_text_overlay_text_pad_linked), + (gst_text_overlay_text_pad_unlinked), (gst_text_overlay_shade_y), + (gst_text_overlay_blit_1), (gst_text_overlay_blit_sub2x2), + (gst_text_overlay_push_frame), (gst_text_overlay_pop_video), + (gst_text_overlay_pop_text), (gst_text_overlay_collected), + (gst_text_overlay_change_state): + * ext/cairo/gsttextoverlay.h: + Port cairo textoverlay plugin to 0.9. Add 'shaded-background' + property and redo position. Doesn't handle upstream renegotiation + yet though. + 2005-11-07 Tim-Philipp Müller * gst/avi/gstavidemux.c: (gst_avi_demux_parse_stream), diff --git a/ext/cairo/Makefile.am b/ext/cairo/Makefile.am index ecd3e9a9f1..1951d5627d 100644 --- a/ext/cairo/Makefile.am +++ b/ext/cairo/Makefile.am @@ -3,15 +3,17 @@ plugin_LTLIBRARIES = libgstcairo.la noinst_HEADERS = gsttimeoverlay.h gsttextoverlay.h libgstcairo_la_SOURCES = \ + gstcairo.c \ gsttimeoverlay.c \ - gstcairo.c - -# gsttextoverlay.c + gsttextoverlay.c libgstcairo_la_CFLAGS = \ -I$(top_srcdir)/gst/videofilter \ - $(GST_CFLAGS) $(CAIRO_CFLAGS) + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) $(CAIRO_CFLAGS) libgstcairo_la_LIBADD = \ $(top_builddir)/gst/videofilter/libgstvideofilter-$(GST_MAJORMINOR).la \ + $(GST_BASE_LIBS) \ $(GST_LIBS) $(CAIRO_LIBS) -lm libgstcairo_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) diff --git a/ext/cairo/gstcairo.c b/ext/cairo/gstcairo.c index 200cc97dfe..953ec72c96 100644 --- a/ext/cairo/gstcairo.c +++ b/ext/cairo/gstcairo.c @@ -22,7 +22,6 @@ #include "config.h" #endif -/*#define DEBUG_ENABLED */ #include #include #include @@ -33,10 +32,8 @@ GST_DEBUG_CATEGORY (cairo_debug); static gboolean plugin_init (GstPlugin * plugin) { -#if 0 gst_element_register (plugin, "cairotextoverlay", GST_RANK_NONE, - GST_TYPE_TEXTOVERLAY); -#endif + GST_TYPE_TEXT_OVERLAY); gst_element_register (plugin, "cairotimeoverlay", GST_RANK_NONE, GST_TYPE_TIMEOVERLAY); diff --git a/ext/cairo/gsttextoverlay.c b/ext/cairo/gsttextoverlay.c index 304ee5afcf..81d79df637 100644 --- a/ext/cairo/gsttextoverlay.c +++ b/ext/cairo/gsttextoverlay.c @@ -23,9 +23,26 @@ #endif #include #include -#include +#include #include "gsttextoverlay.h" +#include + +/* FIXME: + * - calculating the position of the shading rectangle is + * not really right (try with text "L"), to say the least. + * Seems to work at least with latin script though. + * - check final x/y position and text width/height so that + * we don't do out-of-memory access when blitting the text. + * Also, we do not want to blit over the right or left margin. + * - what about text with newline characters? Cairo doesn't deal + * with that (we'd need to fix text_height usage for that as well) + * - upstream caps renegotiation, ie. when video window gets resized + */ + +GST_DEBUG_CATEGORY_EXTERN (cairo_debug); +#define GST_CAT_DEFAULT cairo_debug + static GstElementDetails textoverlay_details = { "Text Overlay", "Filter/Editor/Video", @@ -37,30 +54,32 @@ enum { ARG_0, ARG_TEXT, + ARG_SHADING, ARG_VALIGN, ARG_HALIGN, - ARG_X0, - ARG_Y0, + ARG_XPAD, + ARG_YPAD, + ARG_DELTAX, + ARG_DELTAY, ARG_FONT_DESC }; +#define DEFAULT_YPAD 25 +#define DEFAULT_XPAD 25 +#define DEFAULT_FONT "sans" static GstStaticPadTemplate textoverlay_src_template_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-raw-yuv, " - "format = (fourcc) I420, " - "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") + GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) ); static GstStaticPadTemplate video_sink_template_factory = GST_STATIC_PAD_TEMPLATE ("video_sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-raw-yuv, " - "format = (fourcc) I420, " - "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") + GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) ); static GstStaticPadTemplate text_sink_template_factory = @@ -70,50 +89,34 @@ GST_STATIC_PAD_TEMPLATE ("text_sink", GST_STATIC_CAPS ("text/plain") ); -static void gst_textoverlay_base_init (gpointer g_class); -static void gst_textoverlay_class_init (GstTextOverlayClass * klass); -static void gst_textoverlay_init (GstTextOverlay * overlay); -static void gst_textoverlay_set_property (GObject * object, +static void gst_text_overlay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_textoverlay_get_property (GObject * object, - guint prop_id, GValue * value, GParamSpec * pspec); -static GstStateChangeReturn gst_textoverlay_change_state (GstElement * element, +static GstStateChangeReturn gst_text_overlay_change_state (GstElement * element, GstStateChange transition); -static void gst_textoverlay_finalize (GObject * object); +static GstCaps *gst_text_overlay_getcaps (GstPad * pad); +static gboolean gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps); +static GstPadLinkReturn gst_text_overlay_text_pad_linked (GstPad * pad, + GstPad * peer); +static void gst_text_overlay_text_pad_unlinked (GstPad * pad); +static GstFlowReturn gst_text_overlay_collected (GstCollectPads * pads, + gpointer data); +static void gst_text_overlay_finalize (GObject * object); +static void gst_text_overlay_font_init (GstTextOverlay * overlay); +/* These macros are adapted from videotestsrc.c */ +#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) -static GstElementClass *parent_class = NULL; +#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)) -/*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */ +#define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2)) +GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, GST_TYPE_ELEMENT) -GType -gst_textoverlay_get_type (void) -{ - static GType textoverlay_type = 0; - - if (!textoverlay_type) { - static const GTypeInfo textoverlay_info = { - sizeof (GstTextOverlayClass), - gst_textoverlay_base_init, - NULL, - (GClassInitFunc) gst_textoverlay_class_init, - NULL, - NULL, - sizeof (GstTextOverlay), - 0, - (GInstanceInitFunc) gst_textoverlay_init, - }; - - textoverlay_type = - g_type_register_static (GST_TYPE_ELEMENT, "GstTextOverlay", - &textoverlay_info, 0); - } - return textoverlay_type; -} - -static void -gst_textoverlay_base_init (gpointer g_class) + static void gst_text_overlay_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); @@ -128,7 +131,7 @@ gst_textoverlay_base_init (gpointer g_class) } static void -gst_textoverlay_class_init (GstTextOverlayClass * klass) +gst_text_overlay_class_init (GstTextOverlayClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; @@ -136,16 +139,19 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; - parent_class = g_type_class_peek_parent (klass); + gobject_class->finalize = gst_text_overlay_finalize; + gobject_class->set_property = gst_text_overlay_set_property; - gobject_class->finalize = gst_textoverlay_finalize; - gobject_class->set_property = gst_textoverlay_set_property; - gobject_class->get_property = gst_textoverlay_get_property; + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_text_overlay_change_state); - gstelement_class->change_state = gst_textoverlay_change_state; g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TEXT, g_param_spec_string ("text", "text", "Text to be display.", "", G_PARAM_WRITABLE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SHADING, + g_param_spec_boolean ("shaded-background", "shaded background", + "Whether to shade the background under the text area", FALSE, + G_PARAM_WRITABLE)); g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_VALIGN, g_param_spec_string ("valign", "vertical alignment", "Vertical alignment of the text. " @@ -156,16 +162,22 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass) "Horizontal alignment of the text. " "Can be either 'left', 'right', or 'center'", "center", G_PARAM_WRITABLE)); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_X0, - g_param_spec_int ("x0", "X position", - "Initial X position." - " Horizontal aligment takes this point" - " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE)); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_Y0, - g_param_spec_int ("y0", "Y position", - "Initial Y position." - " Vertical aligment takes this point" - " as reference.", G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_XPAD, + g_param_spec_int ("xpad", "horizontal paddding", + "Horizontal paddding when using left/right alignment", + G_MININT, G_MAXINT, DEFAULT_XPAD, G_PARAM_WRITABLE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_YPAD, + g_param_spec_int ("ypad", "vertical padding", + "Vertical padding when using top/bottom alignment", + G_MININT, G_MAXINT, DEFAULT_YPAD, G_PARAM_WRITABLE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAX, + g_param_spec_int ("deltax", "X position modifier", + "Shift X position to the left or to the right. Unit is pixels.", + G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DELTAY, + g_param_spec_int ("deltay", "Y position modifier", + "Shift Y position up or down. Unit is pixels.", + G_MININT, G_MAXINT, 0, G_PARAM_WRITABLE)); g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FONT_DESC, g_param_spec_string ("font-desc", "font description", "Pango font description of font " @@ -175,534 +187,716 @@ gst_textoverlay_class_init (GstTextOverlayClass * klass) " for syntax.", "", G_PARAM_WRITABLE)); } - -#if 0 static void -resize_bitmap (GstTextOverlay * overlay, int width, int height) +gst_text_overlay_finalize (GObject * object) { - FT_Bitmap *bitmap = &overlay->bitmap; - int pitch = (width | 3) + 1; - int size = pitch * height; + GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); - /* no need to keep reallocating; just keep the maximum size so far */ - if (size <= overlay->bitmap_buffer_size) { - bitmap->rows = height; - bitmap->width = width; - bitmap->pitch = pitch; - memset (bitmap->buffer, 0, overlay->bitmap_buffer_size); - return; - } - if (!bitmap->buffer) { - /* initialize */ - bitmap->pixel_mode = ft_pixel_mode_grays; - bitmap->num_grays = 256; - } - if (bitmap->buffer) - bitmap->buffer = g_realloc (bitmap->buffer, size); - else - bitmap->buffer = g_malloc (size); - bitmap->rows = height; - bitmap->width = width; - bitmap->pitch = pitch; - memset (bitmap->buffer, 0, size); - overlay->bitmap_buffer_size = size; -} -#endif + gst_collectpads_stop (overlay->collect); + gst_object_unref (overlay->collect); -static void -gst_textoverlay_render_text (GstTextOverlay * overlay, gchar * text, - int textlen) -{ - cairo_text_extents_t extents; - char *string; - double x, y; + g_free (overlay->text_fill_image); + g_free (overlay->text_outline_image); - string = g_strndup (text, textlen); - - if (overlay->text_fill_image) - g_free (overlay->text_fill_image); - overlay->text_fill_image = - g_malloc (4 * overlay->width * overlay->text_height); - cairo_set_target_image (overlay->cr, overlay->text_fill_image, - CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height, - overlay->width * 4); - - cairo_save (overlay->cr); - cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height); - cairo_set_rgb_color (overlay->cr, 0, 0, 0); - cairo_set_alpha (overlay->cr, 1.0); - cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC); - cairo_fill (overlay->cr); - cairo_restore (overlay->cr); - - cairo_save (overlay->cr); - cairo_text_extents (overlay->cr, string, &extents); - cairo_set_rgb_color (overlay->cr, 1, 1, 1); - cairo_set_alpha (overlay->cr, 1.0); - switch (overlay->halign) { - case GST_TEXT_OVERLAY_HALIGN_LEFT: - x = overlay->x0; - break; - case GST_TEXT_OVERLAY_HALIGN_CENTER: - x = overlay->x0 - extents.width / 2; - break; - case GST_TEXT_OVERLAY_HALIGN_RIGHT: - x = overlay->x0 - extents.width; - break; - default: - x = 0; - } - y = overlay->text_height - 2; - cairo_move_to (overlay->cr, x, y); - cairo_show_text (overlay->cr, string); - cairo_restore (overlay->cr); - - if (overlay->text_outline_image) - g_free (overlay->text_outline_image); - overlay->text_outline_image = - g_malloc (4 * overlay->width * overlay->text_height); - cairo_set_target_image (overlay->cr, overlay->text_outline_image, - CAIRO_FORMAT_ARGB32, overlay->width, overlay->text_height, - overlay->width * 4); - - cairo_save (overlay->cr); - cairo_rectangle (overlay->cr, 0, 0, overlay->width, overlay->text_height); - cairo_set_rgb_color (overlay->cr, 0, 0, 0); - cairo_set_alpha (overlay->cr, 1.0); - cairo_set_operator (overlay->cr, CAIRO_OPERATOR_SRC); - cairo_fill (overlay->cr); - cairo_restore (overlay->cr); - - cairo_save (overlay->cr); - cairo_move_to (overlay->cr, x, y); - cairo_set_rgb_color (overlay->cr, 1, 1, 1); - cairo_set_alpha (overlay->cr, 1.0); - cairo_set_line_width (overlay->cr, 1.0); - cairo_text_path (overlay->cr, string); - cairo_stroke (overlay->cr); - cairo_restore (overlay->cr); - - g_free (string); -} - -/* static GstPadLinkReturn */ -/* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */ -/* { */ -/* return GST_PAD_LINK_DONE; */ -/* } */ - - -static GstPadLinkReturn -gst_textoverlay_video_sinkconnect (GstPad * pad, const GstCaps * caps) -{ - GstTextOverlay *overlay; - GstStructure *structure; - - overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); - - structure = gst_caps_get_structure (caps, 0); - overlay->width = overlay->height = 0; - gst_structure_get_int (structure, "width", &overlay->width); - gst_structure_get_int (structure, "height", &overlay->height); - - return gst_pad_try_set_caps (overlay->srcpad, caps); -} - - -static void -gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest, - guchar * text_image, int val) -{ - int i; - int j; - int x, a, y; - int y0 = 0; - - y = val; - - for (i = 0; i < overlay->text_height; i++) { - for (j = 0; j < overlay->width; j++) { - x = dest[(i + y0) * overlay->width + j]; - a = text_image[4 * (i * overlay->width + j) + 1]; - dest[(i + y0) * overlay->width + j] = (y * a + x * (255 - a)) / 255; - } - } -} - -static void -gst_text_overlay_blit_sub2x2 (GstTextOverlay * overlay, guchar * dest, - guchar * text_image, int val) -{ - int i; - int j; - int x, a, y; - int y0 = 0; - - y = val; - - for (i = 0; i < overlay->text_height; i += 2) { - for (j = 0; j < overlay->width; j += 2) { - x = dest[(i / 2 + y0) * (overlay->width / 2) + j / 2]; - a = (text_image[4 * (i * overlay->width + j) + 1] + - text_image[4 * (i * overlay->width + j + 1) + 1] + - text_image[4 * ((i + 1) * overlay->width + j) + 1] + - text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4; - dest[(i / 2 + y0) * (overlay->width / 2) + j / 2] = - (y * a + x * (255 - a)) / 255; - } - } -} - - -static void -gst_textoverlay_video_chain (GstPad * pad, GstData * _data) -{ - GstBuffer *buf = GST_BUFFER (_data); - GstTextOverlay *overlay; - guchar *pixbuf; - gint y; - - g_return_if_fail (pad != NULL); - g_return_if_fail (GST_IS_PAD (pad)); - g_return_if_fail (buf != NULL); - overlay = GST_TEXTOVERLAY (gst_pad_get_parent (pad)); - g_return_if_fail (overlay != NULL); - g_return_if_fail (GST_IS_TEXTOVERLAY (overlay)); - - if (!GST_IS_BUFFER (_data)) - return; - - pixbuf = GST_BUFFER_DATA (buf); - - y = overlay->y0; - switch (overlay->valign) { - case GST_TEXT_OVERLAY_VALIGN_BOTTOM: - y -= overlay->text_height; - break; - case GST_TEXT_OVERLAY_VALIGN_BASELINE: -#define BASELINE 2 - y -= (overlay->text_height - BASELINE); - break; - case GST_TEXT_OVERLAY_VALIGN_TOP: - break; - } - - gst_text_overlay_blit_1 (overlay, - pixbuf + y * overlay->width, overlay->text_outline_image, 0); - gst_text_overlay_blit_sub2x2 (overlay, - pixbuf + (overlay->height * overlay->width) + - (y / 2) * overlay->width / 2, overlay->text_outline_image, 128); - gst_text_overlay_blit_sub2x2 (overlay, pixbuf + - (overlay->height * overlay->width) + - (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2, - overlay->text_outline_image, 128); - - gst_text_overlay_blit_1 (overlay, pixbuf + y * overlay->width, - overlay->text_fill_image, 255); - gst_text_overlay_blit_sub2x2 (overlay, - pixbuf + (overlay->height * overlay->width) + - (y / 2) * overlay->width / 2, overlay->text_fill_image, 128); - gst_text_overlay_blit_sub2x2 (overlay, - pixbuf + (overlay->height * overlay->width) + - (overlay->height * overlay->width) / 4 + (y / 2) * overlay->width / 2, - overlay->text_fill_image, 128); - - gst_pad_push (overlay->srcpad, GST_DATA (buf)); -} - -#define PAST_END(buffer, time) \ - (GST_BUFFER_TIMESTAMP (buffer) != GST_CLOCK_TIME_NONE && \ - GST_BUFFER_DURATION (buffer) != GST_CLOCK_TIME_NONE && \ - GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) \ - < (time)) - -static void -gst_textoverlay_loop (GstElement * element) -{ - GstTextOverlay *overlay; - GstBuffer *video_frame; - guint64 now; - - g_return_if_fail (element != NULL); - g_return_if_fail (GST_IS_TEXTOVERLAY (element)); - overlay = GST_TEXTOVERLAY (element); - - do { - GST_DEBUG ("Attempting to pull next video frame"); - video_frame = GST_BUFFER (gst_pad_pull (overlay->video_sinkpad)); - if (GST_IS_EVENT (video_frame)) { - GstEvent *event = GST_EVENT (video_frame); - GstEventType type = GST_EVENT_TYPE (event); - - gst_pad_event_default (overlay->video_sinkpad, event); - GST_DEBUG ("Received event of type %d", type); - if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT) - return; - video_frame = NULL; - } - } while (!video_frame); - now = GST_BUFFER_TIMESTAMP (video_frame); - GST_DEBUG ("Got video frame, time=%" GST_TIME_FORMAT, GST_TIME_ARGS (now)); - - /* - * This state machine has a bug that can't be resolved easily. - * (Needs a more complicated state machine.) Basically, if the - * text that came from a buffer from the sink pad is being - * displayed, and the default text is changed by set_parameter, - * we'll incorrectly display the default text. - * - * Otherwise, this is a pretty decent state machine that handles - * buffer timestamps and durations correctly. (I think) - */ - - while ((!overlay->current_buffer || - PAST_END (overlay->current_buffer, now)) && - overlay->next_buffer == NULL) { - GST_DEBUG ("attempting to pull a buffer"); - - /* read all text buffers until we get one "in the future" */ - if (!GST_PAD_IS_USABLE (overlay->text_sinkpad)) { - break; - } - do { - overlay->next_buffer = GST_BUFFER (gst_pad_pull (overlay->text_sinkpad)); - if (GST_IS_EVENT (overlay->next_buffer)) { - GstEvent *event = GST_EVENT (overlay->next_buffer); - GstEventType type = GST_EVENT_TYPE (event); - - gst_pad_event_default (overlay->text_sinkpad, event); - if (type == GST_EVENT_EOS || type == GST_EVENT_INTERRUPT) - return; - overlay->next_buffer = NULL; - } - } while (!overlay->next_buffer); - - if (PAST_END (overlay->next_buffer, now)) { - GST_DEBUG ("Received buffer is past end (%" GST_TIME_FORMAT " + %" - GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")", - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)), - GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer)), - GST_TIME_ARGS (now)); - gst_buffer_unref (overlay->next_buffer); - overlay->next_buffer = NULL; - } else { - GST_DEBUG ("Received new text buffer of time %" GST_TIME_FORMAT - "and duration %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (overlay->next_buffer)), - GST_TIME_ARGS (GST_BUFFER_DURATION (overlay->next_buffer))); - } - } - - if (overlay->next_buffer && - (GST_BUFFER_TIMESTAMP (overlay->next_buffer) <= now || - GST_BUFFER_TIMESTAMP (overlay->next_buffer) == GST_CLOCK_TIME_NONE)) { - GST_DEBUG ("using new buffer"); - - if (overlay->current_buffer) { - gst_buffer_unref (overlay->current_buffer); - } - overlay->current_buffer = overlay->next_buffer; - overlay->next_buffer = NULL; - - GST_DEBUG ("rendering '%*s'", - GST_BUFFER_SIZE (overlay->current_buffer), - GST_BUFFER_DATA (overlay->current_buffer)); - gst_textoverlay_render_text (overlay, - GST_BUFFER_DATA (overlay->current_buffer), - GST_BUFFER_SIZE (overlay->current_buffer)); - overlay->need_render = FALSE; - } - - if (overlay->current_buffer && PAST_END (overlay->current_buffer, now)) { - GST_DEBUG ("dropping old buffer"); - - gst_buffer_unref (overlay->current_buffer); - overlay->current_buffer = NULL; - - overlay->need_render = TRUE; - } - - if (overlay->need_render) { - GST_DEBUG ("rendering '%s'", overlay->default_text); - gst_textoverlay_render_text (overlay, - overlay->default_text, strlen (overlay->default_text)); - - overlay->need_render = FALSE; - } - - gst_textoverlay_video_chain (overlay->srcpad, GST_DATA (video_frame)); -} - -static void -gst_textoverlay_font_init (GstTextOverlay * overlay) -{ - cairo_font_extents_t font_extents; - - cairo_select_font (overlay->cr, overlay->font, overlay->slant, - overlay->weight); - cairo_scale_font (overlay->cr, overlay->scale); - - cairo_current_font_extents (overlay->cr, &font_extents); - overlay->text_height = font_extents.height; - if (overlay->text_height & 1) - overlay->text_height++; - - overlay->need_render = TRUE; -} - -static GstStateChangeReturn -gst_textoverlay_change_state (GstElement * element, GstStateChange transition) -{ - GstTextOverlay *overlay; - - overlay = GST_TEXTOVERLAY (element); - - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - break; - default: - break; - } - - parent_class->change_state (element, transition); - - return GST_STATE_CHANGE_SUCCESS; -} - -static void -gst_textoverlay_finalize (GObject * object) -{ - GstTextOverlay *overlay = GST_TEXTOVERLAY (object); - - if (overlay->cr) { - cairo_destroy (overlay->cr); - } + g_free (overlay->default_text); + g_free (overlay->font); G_OBJECT_CLASS (parent_class)->finalize (object); } static void -gst_textoverlay_init (GstTextOverlay * overlay) +gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass) { /* video sink */ overlay->video_sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&video_sink_template_factory), "video_sink"); - gst_pad_set_link_function (overlay->video_sinkpad, - gst_textoverlay_video_sinkconnect); + gst_pad_set_getcaps_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps)); + gst_pad_set_setcaps_function (overlay->video_sinkpad, + GST_DEBUG_FUNCPTR (gst_text_overlay_setcaps)); gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad); /* text sink */ overlay->text_sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&text_sink_template_factory), "text_sink"); + gst_pad_set_link_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_linked)); + gst_pad_set_unlink_function (overlay->text_sinkpad, + GST_DEBUG_FUNCPTR (gst_text_overlay_text_pad_unlinked)); gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad); /* (video) source */ overlay->srcpad = gst_pad_new_from_template (gst_static_pad_template_get (&textoverlay_src_template_factory), "src"); + gst_pad_set_getcaps_function (overlay->srcpad, + GST_DEBUG_FUNCPTR (gst_text_overlay_getcaps)); gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad); - overlay->cr = cairo_create (); - overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER; overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE; - overlay->x0 = overlay->y0 = 25; + overlay->xpad = DEFAULT_XPAD; + overlay->ypad = DEFAULT_YPAD; + overlay->deltax = 0; + overlay->deltay = 0; overlay->default_text = g_strdup (""); overlay->need_render = TRUE; - overlay->font = g_strdup ("sans"); + overlay->font = g_strdup (DEFAULT_FONT); overlay->slant = CAIRO_FONT_SLANT_NORMAL; overlay->weight = CAIRO_FONT_WEIGHT_NORMAL; overlay->scale = 20; - gst_textoverlay_font_init (overlay); + gst_text_overlay_font_init (overlay); - gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop); + overlay->framerate = 0.0; + + overlay->collect = gst_collectpads_new (); + + gst_collectpads_set_function (overlay->collect, + GST_DEBUG_FUNCPTR (gst_text_overlay_collected), overlay); + + overlay->video_collect_data = gst_collectpads_add_pad (overlay->collect, + overlay->video_sinkpad, sizeof (GstCollectData)); + + /* text pad will be added when it is linked */ + overlay->text_collect_data = NULL; } static void -gst_textoverlay_set_property (GObject * object, guint prop_id, +gst_text_overlay_font_init (GstTextOverlay * overlay) +{ + cairo_font_extents_t font_extents; + cairo_surface_t *surface; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256); + cr = cairo_create (surface); + + cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight); + cairo_set_font_size (cr, overlay->scale); + + cairo_font_extents (cr, &font_extents); + overlay->font_height = GST_ROUND_UP_2 ((guint) font_extents.height); + overlay->need_render = TRUE; + + cairo_destroy (cr); + cairo_surface_destroy (surface); +} + +static void +gst_text_overlay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstTextOverlay *overlay; + GstTextOverlay *overlay = GST_TEXT_OVERLAY (object); - g_return_if_fail (GST_IS_TEXTOVERLAY (object)); - overlay = GST_TEXTOVERLAY (object); + GST_LOCK (overlay); switch (prop_id) { - - case ARG_TEXT: - if (overlay->default_text) { - g_free (overlay->default_text); - } - overlay->default_text = g_strdup (g_value_get_string (value)); - overlay->need_render = TRUE; + case ARG_TEXT:{ + g_free (overlay->default_text); + overlay->default_text = g_value_dup_string (value); break; + } + case ARG_SHADING:{ + overlay->want_shading = g_value_get_boolean (value); + break; + } + case ARG_VALIGN:{ + const gchar *s = g_value_get_string (value); - case ARG_VALIGN: - if (strcasecmp (g_value_get_string (value), "baseline") == 0) + if (strcasecmp (s, "baseline") == 0) overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE; - else if (strcasecmp (g_value_get_string (value), "bottom") == 0) + else if (strcasecmp (s, "bottom") == 0) overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM; - else if (strcasecmp (g_value_get_string (value), "top") == 0) + else if (strcasecmp (s, "top") == 0) overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP; else - g_warning ("Invalid 'valign' property value: %s", - g_value_get_string (value)); - overlay->need_render = TRUE; + g_warning ("Invalid 'valign' property value: %s", s); break; + } + case ARG_HALIGN:{ + const gchar *s = g_value_get_string (value); - case ARG_HALIGN: - if (strcasecmp (g_value_get_string (value), "left") == 0) + if (strcasecmp (s, "left") == 0) overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT; - else if (strcasecmp (g_value_get_string (value), "right") == 0) + else if (strcasecmp (s, "right") == 0) overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT; - else if (strcasecmp (g_value_get_string (value), "center") == 0) + else if (strcasecmp (s, "center") == 0) overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER; else - g_warning ("Invalid 'halign' property value: %s", - g_value_get_string (value)); - overlay->need_render = TRUE; + g_warning ("Invalid 'halign' property value: %s", s); break; - - case ARG_X0: - overlay->x0 = g_value_get_int (value); + } + case ARG_XPAD:{ + overlay->xpad = g_value_get_int (value); break; - - case ARG_Y0: - overlay->y0 = g_value_get_int (value); + } + case ARG_YPAD:{ + overlay->ypad = g_value_get_int (value); break; - - case ARG_FONT_DESC: - if (overlay->font) - g_free (overlay->font); - overlay->font = g_strdup (g_value_get_string (value)); - gst_textoverlay_font_init (overlay); + } + case ARG_DELTAX:{ + overlay->deltax = g_value_get_int (value); break; - - default: + } + case ARG_DELTAY:{ + overlay->deltay = g_value_get_int (value); break; + } + case ARG_FONT_DESC:{ + g_free (overlay->font); + overlay->font = g_value_dup_string (value); + if (overlay->font == NULL) + overlay->font = g_strdup (DEFAULT_FONT); + gst_text_overlay_font_init (overlay); + break; + } + default:{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } + + overlay->need_render = TRUE; + + GST_UNLOCK (overlay); } static void -gst_textoverlay_get_property (GObject * object, guint prop_id, GValue * value, - GParamSpec * pspec) +gst_text_overlay_render_text (GstTextOverlay * overlay, gchar * text, + gint textlen) +{ + cairo_text_extents_t extents; + cairo_surface_t *surface; + cairo_t *cr; + gchar *string; + double x, y; + + if (textlen < 0) + textlen = strlen (text); + + string = g_strndup (text, textlen); + + if (overlay->need_render) { + GST_DEBUG ("Rendering text '%s' on cairo RGBA surface", string); + } else { + GST_DEBUG ("Using previously rendered text."); + g_return_if_fail (overlay->text_fill_image != NULL); + g_return_if_fail (overlay->text_outline_image != NULL); + return; + } + + overlay->text_fill_image = + g_realloc (overlay->text_fill_image, + 4 * overlay->width * overlay->font_height); + + surface = cairo_image_surface_create_for_data (overlay->text_fill_image, + CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height, + overlay->width * 4); + + cr = cairo_create (surface); + + cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight); + cairo_set_font_size (cr, overlay->scale); + + cairo_save (cr); + cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_fill (cr); + cairo_restore (cr); + + cairo_save (cr); + cairo_text_extents (cr, string, &extents); + cairo_set_source_rgba (cr, 1, 1, 1, 1.0); + + switch (overlay->halign) { + case GST_TEXT_OVERLAY_HALIGN_LEFT: + x = overlay->xpad; + break; + case GST_TEXT_OVERLAY_HALIGN_CENTER: + x = (overlay->width - extents.width) / 2; + break; + case GST_TEXT_OVERLAY_HALIGN_RIGHT: + x = overlay->width - extents.width - overlay->xpad; + break; + default: + x = 0; + } + x += overlay->deltax; + + overlay->text_x0 = x; + overlay->text_x1 = x + extents.x_advance; + + overlay->text_dy = (extents.height + extents.y_bearing); + y = overlay->font_height - overlay->text_dy; + + cairo_move_to (cr, x, y); + cairo_show_text (cr, string); + cairo_restore (cr); + + cairo_destroy (cr); + cairo_surface_destroy (surface); + + /* ----------- */ + + overlay->text_outline_image = + g_realloc (overlay->text_outline_image, + 4 * overlay->width * overlay->font_height); + + surface = cairo_image_surface_create_for_data (overlay->text_outline_image, + CAIRO_FORMAT_ARGB32, overlay->width, overlay->font_height, + overlay->width * 4); + + cr = cairo_create (surface); + + cairo_select_font_face (cr, overlay->font, overlay->slant, overlay->weight); + cairo_set_font_size (cr, overlay->scale); + + cairo_save (cr); + cairo_rectangle (cr, 0, 0, overlay->width, overlay->font_height); + cairo_set_source_rgba (cr, 0, 0, 0, 1.0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_fill (cr); + cairo_restore (cr); + + cairo_save (cr); + cairo_move_to (cr, x, y); + cairo_set_source_rgba (cr, 1, 1, 1, 1.0); + cairo_set_line_width (cr, 1.0); + cairo_text_path (cr, string); + cairo_stroke (cr); + cairo_restore (cr); + + g_free (string); + + cairo_destroy (cr); + cairo_surface_destroy (surface); + + overlay->need_render = FALSE; +} + +static GstCaps * +gst_text_overlay_getcaps (GstPad * pad) +{ + GstTextOverlay *overlay; + GstPad *otherpad; + GstCaps *caps; + + overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + if (pad == overlay->srcpad) + otherpad = overlay->video_sinkpad; + else + otherpad = overlay->srcpad; + + /* we can do what the peer can */ + caps = gst_pad_peer_get_caps (otherpad); + if (caps) { + GstCaps *temp; + const GstCaps *templ; + + GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, caps); + + /* filtered against our padtemplate */ + templ = gst_pad_get_pad_template_caps (otherpad); + GST_DEBUG_OBJECT (pad, "our template %" GST_PTR_FORMAT, templ); + temp = gst_caps_intersect (caps, templ); + GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp); + gst_caps_unref (caps); + /* this is what we can do */ + caps = temp; + } else { + /* no peer, our padtemplate is enough then */ + caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + } + + GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps); + + gst_object_unref (overlay); + + return caps; +} + +/* FIXME: upstream nego (e.g. when the video window is resized) */ + +static gboolean +gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps) +{ + GstTextOverlay *overlay; + GstStructure *structure; + gboolean ret = FALSE; + gdouble fps = 0.0; + + if (!GST_PAD_IS_SINK (pad)) + return TRUE; + + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + + overlay->width = 0; + overlay->height = 0; + structure = gst_caps_get_structure (caps, 0); + if (gst_structure_get_int (structure, "width", &overlay->width) && + gst_structure_get_int (structure, "height", &overlay->height)) { + ret = gst_pad_set_caps (overlay->srcpad, caps); + } + + (void) gst_structure_get_double (structure, "framerate", &fps); + overlay->framerate = fps; + + return ret; +} + +static GstPadLinkReturn +gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer) { GstTextOverlay *overlay; - g_return_if_fail (GST_IS_TEXTOVERLAY (object)); - overlay = GST_TEXTOVERLAY (object); + overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); - switch (prop_id) { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; + GST_DEBUG_OBJECT (overlay, "Text pad linked"); + + if (overlay->text_collect_data == NULL) { + overlay->text_collect_data = gst_collectpads_add_pad (overlay->collect, + overlay->text_sinkpad, sizeof (GstCollectData)); + } + + overlay->need_render = TRUE; + + return GST_PAD_LINK_OK; +} + +static void +gst_text_overlay_text_pad_unlinked (GstPad * pad) +{ + GstTextOverlay *overlay; + + /* don't use gst_pad_get_parent() here, will deadlock */ + overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad)); + + GST_DEBUG_OBJECT (overlay, "Text pad unlinked"); + + if (overlay->text_collect_data) { + gst_collectpads_remove_pad (overlay->collect, overlay->text_sinkpad); + overlay->text_collect_data = NULL; + } + + overlay->need_render = TRUE; +} + +#define BOX_SHADING_VAL -80 +#define BOX_XPAD 6 +#define BOX_YPAD 6 + +static inline void +gst_text_overlay_shade_y (GstTextOverlay * overlay, guchar * dest, + guint dest_stride, gint y0, gint y1) +{ + gint i, j, x0, x1; + + x0 = CLAMP (overlay->text_x0 - BOX_XPAD, 0, overlay->width); + x1 = CLAMP (overlay->text_x1 + BOX_XPAD, 0, overlay->width); + + y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height); + y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height); + + for (i = y0; i < y1; ++i) { + for (j = x0; j < x1; ++j) { + gint y = dest[(i * dest_stride) + j] + BOX_SHADING_VAL; + + dest[(i * dest_stride) + j] = CLAMP (y, 0, 255); + } } } + +static inline void +gst_text_overlay_blit_1 (GstTextOverlay * overlay, guchar * dest, + guchar * text_image, gint val, guint dest_stride) +{ + gint i, j; + gint x, a, y; + gint y0 = 0; + + y = val; + + for (i = 0; i < overlay->font_height; i++) { + for (j = 0; j < overlay->width; j++) { + x = dest[(i + y0) * dest_stride + j]; + a = text_image[4 * (i * overlay->width + j) + 1]; + dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255; + } + } +} + +static inline void +gst_text_overlay_blit_sub2x2 (GstTextOverlay * overlay, guchar * dest, + guchar * text_image, gint val, guint dest_stride) +{ + gint i, j; + gint x, a, y; + gint y0 = 0; + + y = val; + + for (i = 0; i < overlay->font_height; i += 2) { + for (j = 0; j < overlay->width; j += 2) { + x = dest[(i / 2 + y0) * dest_stride + j / 2]; + a = (text_image[4 * (i * overlay->width + j) + 1] + + text_image[4 * (i * overlay->width + j + 1) + 1] + + text_image[4 * ((i + 1) * overlay->width + j) + 1] + + text_image[4 * ((i + 1) * overlay->width + j + 1) + 1] + 2) / 4; + dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255; + } + } +} + + +static GstFlowReturn +gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame) +{ + guchar *y, *u, *v; + gint ypos; + + video_frame = gst_buffer_make_writable (video_frame); + + switch (overlay->valign) { + case GST_TEXT_OVERLAY_VALIGN_BOTTOM: + ypos = overlay->height - overlay->font_height - overlay->ypad; + break; + case GST_TEXT_OVERLAY_VALIGN_BASELINE: + ypos = overlay->height - (overlay->font_height - overlay->text_dy) + - overlay->ypad; + break; + case GST_TEXT_OVERLAY_VALIGN_TOP: + ypos = overlay->ypad; + break; + default: + ypos = overlay->ypad; + break; + } + + ypos += overlay->deltay; + + y = GST_BUFFER_DATA (video_frame); + u = y + I420_U_OFFSET (overlay->width, overlay->height); + v = y + I420_V_OFFSET (overlay->width, overlay->height); + + /* shaded background box */ + if (overlay->want_shading) { + gst_text_overlay_shade_y (overlay, + y, I420_Y_ROWSTRIDE (overlay->width), + ypos + overlay->text_dy, ypos + overlay->font_height); + } + + /* blit outline text on video image */ + gst_text_overlay_blit_1 (overlay, + y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width), + overlay->text_outline_image, 0, I420_Y_ROWSTRIDE (overlay->width)); + gst_text_overlay_blit_sub2x2 (overlay, + u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width), + overlay->text_outline_image, 128, I420_U_ROWSTRIDE (overlay->width)); + gst_text_overlay_blit_sub2x2 (overlay, + v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width), + overlay->text_outline_image, 128, I420_V_ROWSTRIDE (overlay->width)); + + /* blit text on video image */ + gst_text_overlay_blit_1 (overlay, + y + (ypos / 1) * I420_Y_ROWSTRIDE (overlay->width), + overlay->text_fill_image, 255, I420_Y_ROWSTRIDE (overlay->width)); + gst_text_overlay_blit_sub2x2 (overlay, + u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width), + overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width)); + gst_text_overlay_blit_sub2x2 (overlay, + v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width), + overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width)); + + return gst_pad_push (overlay->srcpad, video_frame); +} + +static void +gst_text_overlay_pop_video (GstTextOverlay * overlay) +{ + GstBuffer *buf; + + buf = gst_collectpads_pop (overlay->collect, overlay->video_collect_data); + g_return_if_fail (buf != NULL); + gst_buffer_unref (buf); +} + +static void +gst_text_overlay_pop_text (GstTextOverlay * overlay) +{ + GstBuffer *buf; + + if (overlay->text_collect_data) { + buf = gst_collectpads_pop (overlay->collect, overlay->text_collect_data); + g_return_if_fail (buf != NULL); + gst_buffer_unref (buf); + } + + overlay->need_render = TRUE; +} + +/* This function is called when there is data on all pads */ +static GstFlowReturn +gst_text_overlay_collected (GstCollectPads * pads, gpointer data) +{ + GstTextOverlay *overlay; + GstFlowReturn ret = GST_FLOW_OK; + GstClockTime now, txt_end, frame_end; + GstBuffer *video_frame = NULL; + GstBuffer *text_buf = NULL; + + overlay = GST_TEXT_OVERLAY (data); + + GST_DEBUG ("Collecting"); + + video_frame = gst_collectpads_peek (overlay->collect, + overlay->video_collect_data); + + /* send EOS if video stream EOSed regardless of text stream */ + if (video_frame == NULL) { + GST_DEBUG ("Video stream at EOS"); + if (overlay->text_collect_data) { + text_buf = gst_collectpads_pop (overlay->collect, + overlay->text_collect_data); + } + gst_pad_push_event (overlay->srcpad, gst_event_new_eos ()); + ret = GST_FLOW_UNEXPECTED; + goto done; + } + + if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) { + g_warning ("%s: video frame has invalid timestamp", G_STRLOC); + } + + now = GST_BUFFER_TIMESTAMP (video_frame); + + if (GST_BUFFER_DURATION (video_frame) != GST_CLOCK_TIME_NONE) { + frame_end = now + GST_BUFFER_DURATION (video_frame); + } else if (overlay->framerate > 0.0) { + frame_end = now + (GST_SECOND / overlay->framerate); + } else { + /* magic value, does not really matter since texts + * tend to span quite a few frames in practice anyway */ + frame_end = now + GST_SECOND / 25; + } + + GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end)); + + /* text pad not linked? */ + if (overlay->text_collect_data == NULL) { + GST_DEBUG ("Text pad not linked, rendering default text: '%s'", + GST_STR_NULL (overlay->default_text)); + if (overlay->default_text && *overlay->default_text != '\0') { + gst_text_overlay_render_text (overlay, overlay->default_text, -1); + ret = gst_text_overlay_push_frame (overlay, video_frame); + } else { + ret = gst_pad_push (overlay->srcpad, video_frame); + } + gst_text_overlay_pop_video (overlay); + video_frame = NULL; + goto done; + } + + text_buf = gst_collectpads_peek (overlay->collect, + overlay->text_collect_data); + + /* just push the video frame if the text stream has EOSed */ + if (text_buf == NULL) { + GST_DEBUG ("Text pad EOSed, just pushing video frame as is"); + ret = gst_pad_push (overlay->srcpad, video_frame); + gst_text_overlay_pop_video (overlay); + video_frame = NULL; + goto done; + } + + /* 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 (text_buf) == GST_CLOCK_TIME_NONE || + GST_BUFFER_DURATION (text_buf) == GST_CLOCK_TIME_NONE) { + GST_WARNING ("Got text buffer with invalid time stamp or duration"); + gst_text_overlay_pop_text (overlay); + GST_BUFFER_TIMESTAMP (text_buf) = now; + GST_BUFFER_DURATION (text_buf) = frame_end - now; + } + + txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf); + + GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end)); + + /* if the text buffer is too old, pop it off the + * queue and return so we get a new one next time */ + if (txt_end < now) { + GST_DEBUG ("Text buffer too old, popping off the queue"); + gst_text_overlay_pop_text (overlay); + ret = GST_FLOW_OK; + goto done; + } + + /* if the video frame ends before the text even starts, + * just push it out as is and pop it off the queue */ + if (frame_end < GST_BUFFER_TIMESTAMP (text_buf)) { + GST_DEBUG ("Video buffer before text, pushing out and popping off queue"); + ret = gst_pad_push (overlay->srcpad, video_frame); + gst_text_overlay_pop_video (overlay); + video_frame = NULL; + goto done; + } + + /* text duration overlaps video frame duration */ + GST_DEBUG ("Rendering '%*s'", + GST_BUFFER_SIZE (text_buf), GST_BUFFER_DATA (text_buf)); + gst_text_overlay_pop_video (overlay); + ret = gst_pad_push (overlay->srcpad, video_frame); + video_frame = NULL; + goto done; + +done: + { + if (text_buf) + gst_buffer_unref (text_buf); + + if (video_frame) + gst_buffer_unref (video_frame); + + return ret; + } +} + +static GstStateChangeReturn +gst_text_overlay_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstTextOverlay *overlay = GST_TEXT_OVERLAY (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_collectpads_start (overlay->collect); + break; + default: + break; + } + + ret = parent_class->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_collectpads_stop (overlay->collect); + break; + default: + break; + } + + return ret; +} diff --git a/ext/cairo/gsttextoverlay.h b/ext/cairo/gsttextoverlay.h index 6d572c67ed..66cd11de87 100644 --- a/ext/cairo/gsttextoverlay.h +++ b/ext/cairo/gsttextoverlay.h @@ -1,26 +1,23 @@ -#ifndef __GST_TEXTOVERLAY_H__ -#define __GST_TEXTOVERLAY_H__ +#ifndef __GST_TEXT_OVERLAY_H__ +#define __GST_TEXT_OVERLAY_H__ #include -#include +#include G_BEGIN_DECLS -GST_DEBUG_CATEGORY_EXTERN (cairo_debug); -#define GST_CAT_DEFAULT cairo_debug - -#define GST_TYPE_TEXTOVERLAY (gst_textoverlay_get_type()) -#define GST_TEXTOVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ - GST_TYPE_TEXTOVERLAY, GstTextOverlay)) -#define GST_TEXTOVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\ +#define GST_TYPE_TEXT_OVERLAY (gst_text_overlay_get_type()) +#define GST_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GST_TYPE_TEXT_OVERLAY, GstTextOverlay)) +#define GST_TEXT_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\ GST_TYPE_ULAW, GstTextOverlay)) -#define GST_TEXTOVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ - GST_TYPE_TEXTOVERLAY, GstTextOverlayClass)) -#define GST_IS_TEXTOVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\ - GST_TYPE_TEXTOVERLAY)) -#define GST_IS_TEXTOVERLAY_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),\ - GST_TYPE_TEXTOVERLAY)) +#define GST_TEXT_OVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GST_TYPE_TEXT_OVERLAY, GstTextOverlayClass)) +#define GST_IS_TEXT_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\ + GST_TYPE_TEXT_OVERLAY)) +#define GST_IS_TEXT_OVERLAY_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),\ + GST_TYPE_TEXT_OVERLAY)) typedef struct _GstTextOverlay GstTextOverlay; typedef struct _GstTextOverlayClass GstTextOverlayClass; @@ -47,36 +44,44 @@ struct _GstTextOverlay { GstPad *video_sinkpad; GstPad *text_sinkpad; GstPad *srcpad; + + GstCollectPads *collect; + GstCollectData *video_collect_data; + GstCollectData *text_collect_data; + gint width; gint height; + gdouble framerate; GstTextOverlayVAlign valign; GstTextOverlayHAlign halign; - gint x0; - gint y0; + gint xpad; + gint ypad; + gint deltax; + gint deltay; gchar *default_text; + gboolean want_shading; - cairo_t *cr; - guchar *text_fill_image; - guchar *text_outline_image; - int text_height; + guchar *text_fill_image; + guchar *text_outline_image; + gint font_height; + gint text_x0, text_x1; /* start/end x position of text */ + gint text_dy; - GstBuffer *current_buffer; - GstBuffer *next_buffer; gboolean need_render; - gchar *font; - int slant; - int weight; - double scale; + gchar *font; + gint slant; + gint weight; + gdouble scale; }; struct _GstTextOverlayClass { - GstElementClass parent_class; + GstElementClass parent_class; }; -GType gst_textoverlay_get_type(void) G_GNUC_CONST; +GType gst_text_overlay_get_type (void); G_END_DECLS -#endif /* __GST_TEXTOVERLAY_H */ +#endif /* __GST_TEXT_OVERLAY_H */