ext/cairo/: Port cairo textoverlay plugin to 0.9. Add 'shaded-background' property...
authorTim-Philipp Müller <tim@centricular.net>
Mon, 7 Nov 2005 15:09:54 +0000 (15:09 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Mon, 7 Nov 2005 15:09:54 +0000 (15:09 +0000)
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
ext/cairo/Makefile.am
ext/cairo/gstcairo.c
ext/cairo/gsttextoverlay.c
ext/cairo/gsttextoverlay.h

index 785bdc0..502b4a8 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,25 @@
 2005-11-07  Tim-Philipp Müller  <tim at centricular dot net>
 
+       * 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  <tim at centricular dot net>
+
        * gst/avi/gstavidemux.c: (gst_avi_demux_parse_stream),
        (gst_avi_demux_process_next_entry), (gst_avi_demux_stream_data),
        (gst_avi_demux_loop):
index ecd3e9a..1951d56 100644 (file)
@@ -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)
index 200cc97..953ec72 100644 (file)
@@ -22,7 +22,6 @@
 #include "config.h"
 #endif
 
-/*#define DEBUG_ENABLED */
 #include <gsttimeoverlay.h>
 #include <gsttextoverlay.h>
 #include <string.h>
@@ -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);
 
index 304ee5a..81d79df 100644 (file)
 #endif
 #include <string.h>
 #include <strings.h>
-#include <gst/gst.h>
+#include <gst/video/video.h>
 #include "gsttextoverlay.h"
 
+#include <cairo.h>
+
+/* 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 GstElementClass *parent_class = NULL;
-
-/*static guint gst_textoverlay_signals[LAST_SIGNAL] = { 0 }; */
-
-
-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 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)
+
+#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))
+
+GST_BOILERPLATE (GstTextOverlay, gst_text_overlay, GstElement, GST_TYPE_ELEMENT)
+
+     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));
 }
 
+static void
+gst_text_overlay_finalize (GObject * object)
+{
+  GstTextOverlay *overlay = GST_TEXT_OVERLAY (object);
+
+  gst_collectpads_stop (overlay->collect);
+  gst_object_unref (overlay->collect);
+
+  g_free (overlay->text_fill_image);
+  g_free (overlay->text_outline_image);
+
+  g_free (overlay->default_text);
+  g_free (overlay->font);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
 
-#if 0
 static void
-resize_bitmap (GstTextOverlay * overlay, int width, int height)
+gst_text_overlay_init (GstTextOverlay * overlay, GstTextOverlayClass * klass)
 {
-  FT_Bitmap *bitmap = &overlay->bitmap;
-  int pitch = (width | 3) + 1;
-  int size = pitch * height;
-
-  /* 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;
+  /* video sink */
+  overlay->video_sinkpad =
+      gst_pad_new_from_template (gst_static_pad_template_get
+      (&video_sink_template_factory), "video_sink");
+  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->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
+  overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
+  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 (DEFAULT_FONT);
+  overlay->slant = CAIRO_FONT_SLANT_NORMAL;
+  overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
+  overlay->scale = 20;
+  gst_text_overlay_font_init (overlay);
+
+  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_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 = GST_TEXT_OVERLAY (object);
+
+  GST_LOCK (overlay);
+
+  switch (prop_id) {
+    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);
+
+      if (strcasecmp (s, "baseline") == 0)
+        overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
+      else if (strcasecmp (s, "bottom") == 0)
+        overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
+      else if (strcasecmp (s, "top") == 0)
+        overlay->valign = GST_TEXT_OVERLAY_VALIGN_TOP;
+      else
+        g_warning ("Invalid 'valign' property value: %s", s);
+      break;
+    }
+    case ARG_HALIGN:{
+      const gchar *s = g_value_get_string (value);
+
+      if (strcasecmp (s, "left") == 0)
+        overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
+      else if (strcasecmp (s, "right") == 0)
+        overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
+      else if (strcasecmp (s, "center") == 0)
+        overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
+      else
+        g_warning ("Invalid 'halign' property value: %s", s);
+      break;
+    }
+    case ARG_XPAD:{
+      overlay->xpad = g_value_get_int (value);
+      break;
+    }
+    case ARG_YPAD:{
+      overlay->ypad = g_value_get_int (value);
+      break;
+    }
+    case ARG_DELTAX:{
+      overlay->deltax = g_value_get_int (value);
+      break;
+    }
+    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;
+    }
   }
-  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;
+
+  overlay->need_render = TRUE;
+
+  GST_UNLOCK (overlay);
 }
-#endif
 
 static void
-gst_textoverlay_render_text (GstTextOverlay * overlay, gchar * text,
-    int textlen)
+gst_text_overlay_render_text (GstTextOverlay * overlay, gchar * text,
+    gint textlen)
 {
   cairo_text_extents_t extents;
-  char *string;
+  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->text_fill_image)
-    g_free (overlay->text_fill_image);
+  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_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,
+      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);
 
-  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);
+  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->x0;
+      x = overlay->xpad;
       break;
     case GST_TEXT_OVERLAY_HALIGN_CENTER:
-      x = overlay->x0 - extents.width / 2;
+      x = (overlay->width - extents.width) / 2;
       break;
     case GST_TEXT_OVERLAY_HALIGN_RIGHT:
-      x = overlay->x0 - extents.width;
+      x = overlay->width - extents.width - overlay->xpad;
       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);
+  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);
+
+  /* ----------- */
 
-  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,
+      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);
 
-  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);
+  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 GstPadLinkReturn */
-/* gst_textoverlay_text_sinkconnect (GstPad *pad, GstCaps *caps) */
-/* { */
-/*     return GST_PAD_LINK_DONE; */
-/* } */
+static GstCaps *
+gst_text_overlay_getcaps (GstPad * pad)
+{
+  GstTextOverlay *overlay;
+  GstPad *otherpad;
+  GstCaps *caps;
 
+  overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
 
-static GstPadLinkReturn
-gst_textoverlay_video_sinkconnect (GstPad * pad, const GstCaps * caps)
+  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_TEXTOVERLAY (gst_pad_get_parent (pad));
+  overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad));
 
+  overlay->width = 0;
+  overlay->height = 0;
   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);
+  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 gst_pad_try_set_caps (overlay->srcpad, caps);
+  return ret;
 }
 
+static GstPadLinkReturn
+gst_text_overlay_text_pad_linked (GstPad * pad, GstPad * peer)
+{
+  GstTextOverlay *overlay;
+
+  overlay = GST_TEXT_OVERLAY (GST_PAD_PARENT (pad));
+
+  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, int val)
+    guchar * text_image, gint val, guint dest_stride)
 {
-  int i;
-  int j;
-  int x, a, y;
-  int y0 = 0;
+  gint i, j;
+  gint x, a, y;
+  gint y0 = 0;
 
   y = val;
 
-  for (i = 0; i < overlay->text_height; i++) {
+  for (i = 0; i < overlay->font_height; i++) {
     for (j = 0; j < overlay->width; j++) {
-      x = dest[(i + y0) * overlay->width + j];
+      x = dest[(i + y0) * dest_stride + j];
       a = text_image[4 * (i * overlay->width + j) + 1];
-      dest[(i + y0) * overlay->width + j] = (y * a + x * (255 - a)) / 255;
+      dest[(i + y0) * dest_stride + j] = (y * a + x * (255 - a)) / 255;
     }
   }
 }
 
-static void
+static inline void
 gst_text_overlay_blit_sub2x2 (GstTextOverlay * overlay, guchar * dest,
-    guchar * text_image, int val)
+    guchar * text_image, gint val, guint dest_stride)
 {
-  int i;
-  int j;
-  int x, a, y;
-  int y0 = 0;
+  gint i, j;
+  gint x, a, y;
+  gint y0 = 0;
 
   y = val;
 
-  for (i = 0; i < overlay->text_height; i += 2) {
+  for (i = 0; i < overlay->font_height; i += 2) {
     for (j = 0; j < overlay->width; j += 2) {
-      x = dest[(i / 2 + y0) * (overlay->width / 2) + 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) * (overlay->width / 2) + j / 2] =
-          (y * a + x * (255 - a)) / 255;
+      dest[(i / 2 + y0) * dest_stride + j / 2] = (y * a + x * (255 - a)) / 255;
     }
   }
 }
 
 
-static void
-gst_textoverlay_video_chain (GstPad * pad, GstData * _data)
+static GstFlowReturn
+gst_text_overlay_push_frame (GstTextOverlay * overlay, GstBuffer * video_frame)
 {
-  GstBuffer *buf = GST_BUFFER (_data);
-  GstTextOverlay *overlay;
-  guchar *pixbuf;
-  gint y;
+  guchar *y, *u, *v;
+  gint ypos;
 
-  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));
+  video_frame = gst_buffer_make_writable (video_frame);
 
-  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;
+      ypos = overlay->height - overlay->font_height - overlay->ypad;
       break;
     case GST_TEXT_OVERLAY_VALIGN_BASELINE:
-#define BASELINE 2
-      y -= (overlay->text_height - 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,
-      pixbuf + y * overlay->width, overlay->text_outline_image, 0);
+      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,
-      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);
+      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,
-      pixbuf + (overlay->height * overlay->width) +
-      (y / 2) * overlay->width / 2, overlay->text_fill_image, 128);
+      u + (ypos / 2) * I420_U_ROWSTRIDE (overlay->width),
+      overlay->text_fill_image, 128, I420_U_ROWSTRIDE (overlay->width));
   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);
+      v + (ypos / 2) * I420_V_ROWSTRIDE (overlay->width),
+      overlay->text_fill_image, 128, I420_V_ROWSTRIDE (overlay->width));
 
-  gst_pad_push (overlay->srcpad, GST_DATA (buf));
+  return gst_pad_push (overlay->srcpad, video_frame);
 }
 
-#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)
+gst_text_overlay_pop_video (GstTextOverlay * overlay)
 {
-  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;
-  }
+  GstBuffer *buf;
 
-  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));
+  buf = gst_collectpads_pop (overlay->collect, overlay->video_collect_data);
+  g_return_if_fail (buf != NULL);
+  gst_buffer_unref (buf);
 }
 
 static void
-gst_textoverlay_font_init (GstTextOverlay * overlay)
+gst_text_overlay_pop_text (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);
+  GstBuffer *buf;
 
-  cairo_current_font_extents (overlay->cr, &font_extents);
-  overlay->text_height = font_extents.height;
-  if (overlay->text_height & 1)
-    overlay->text_height++;
+  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;
 }
 
-static GstStateChangeReturn
-gst_textoverlay_change_state (GstElement * element, GstStateChange transition)
+/* 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_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);
+  overlay = GST_TEXT_OVERLAY (data);
 
-  return GST_STATE_CHANGE_SUCCESS;
-}
+  GST_DEBUG ("Collecting");
 
-static void
-gst_textoverlay_finalize (GObject * object)
-{
-  GstTextOverlay *overlay = GST_TEXTOVERLAY (object);
+  video_frame = gst_collectpads_peek (overlay->collect,
+      overlay->video_collect_data);
 
-  if (overlay->cr) {
-    cairo_destroy (overlay->cr);
+  /* 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;
   }
 
-  G_OBJECT_CLASS (parent_class)->finalize (object);
-}
+  if (GST_BUFFER_TIMESTAMP (video_frame) == GST_CLOCK_TIME_NONE) {
+    g_warning ("%s: video frame has invalid timestamp", G_STRLOC);
+  }
 
-static void
-gst_textoverlay_init (GstTextOverlay * overlay)
-{
-  /* 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_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
+  now = GST_BUFFER_TIMESTAMP (video_frame);
 
-  /* text sink */
-  overlay->text_sinkpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&text_sink_template_factory), "text_sink");
-  gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
+  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;
+  }
 
-  /* (video) source */
-  overlay->srcpad =
-      gst_pad_new_from_template (gst_static_pad_template_get
-      (&textoverlay_src_template_factory), "src");
-  gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
+  GST_DEBUG ("Got video frame: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (now), GST_TIME_ARGS (frame_end));
 
-  overlay->cr = cairo_create ();
+  /* 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;
+  }
 
-  overlay->halign = GST_TEXT_OVERLAY_HALIGN_CENTER;
-  overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
-  overlay->x0 = overlay->y0 = 25;
+  text_buf = gst_collectpads_peek (overlay->collect,
+      overlay->text_collect_data);
 
-  overlay->default_text = g_strdup ("");
-  overlay->need_render = TRUE;
+  /* 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;
+  }
 
-  overlay->font = g_strdup ("sans");
-  overlay->slant = CAIRO_FONT_SLANT_NORMAL;
-  overlay->weight = CAIRO_FONT_WEIGHT_NORMAL;
-  overlay->scale = 20;
-  gst_textoverlay_font_init (overlay);
+  /* 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;
+  }
 
-  gst_element_set_loop_function (GST_ELEMENT (overlay), gst_textoverlay_loop);
-}
+  txt_end = GST_BUFFER_TIMESTAMP (text_buf) + GST_BUFFER_DURATION (text_buf);
 
-static void
-gst_textoverlay_set_property (GObject * object, guint prop_id,
-    const GValue * value, GParamSpec * pspec)
-{
-  GstTextOverlay *overlay;
+  GST_DEBUG ("Got text buffer: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (text_buf)), GST_TIME_ARGS (txt_end));
 
-  g_return_if_fail (GST_IS_TEXTOVERLAY (object));
-  overlay = GST_TEXTOVERLAY (object);
+  /* 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;
+  }
 
-  switch (prop_id) {
+  /* 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;
+  }
 
-    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;
-      break;
+  /* 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;
 
-    case ARG_VALIGN:
-      if (strcasecmp (g_value_get_string (value), "baseline") == 0)
-        overlay->valign = GST_TEXT_OVERLAY_VALIGN_BASELINE;
-      else if (strcasecmp (g_value_get_string (value), "bottom") == 0)
-        overlay->valign = GST_TEXT_OVERLAY_VALIGN_BOTTOM;
-      else if (strcasecmp (g_value_get_string (value), "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;
-      break;
+done:
+  {
+    if (text_buf)
+      gst_buffer_unref (text_buf);
 
-    case ARG_HALIGN:
-      if (strcasecmp (g_value_get_string (value), "left") == 0)
-        overlay->halign = GST_TEXT_OVERLAY_HALIGN_LEFT;
-      else if (strcasecmp (g_value_get_string (value), "right") == 0)
-        overlay->halign = GST_TEXT_OVERLAY_HALIGN_RIGHT;
-      else if (strcasecmp (g_value_get_string (value), "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;
-      break;
+    if (video_frame)
+      gst_buffer_unref (video_frame);
 
-    case ARG_X0:
-      overlay->x0 = g_value_get_int (value);
-      break;
+    return ret;
+  }
+}
 
-    case ARG_Y0:
-      overlay->y0 = g_value_get_int (value);
-      break;
+static GstStateChangeReturn
+gst_text_overlay_change_state (GstElement * element, GstStateChange transition)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstTextOverlay *overlay = GST_TEXT_OVERLAY (element);
 
-    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);
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      gst_collectpads_start (overlay->collect);
       break;
-
     default:
       break;
   }
-}
 
-static void
-gst_textoverlay_get_property (GObject * object, guint prop_id, GValue * value,
-    GParamSpec * pspec)
-{
-  GstTextOverlay *overlay;
-
-  g_return_if_fail (GST_IS_TEXTOVERLAY (object));
-  overlay = GST_TEXTOVERLAY (object);
+  ret = parent_class->change_state (element, transition);
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    return ret;
 
-  switch (prop_id) {
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_collectpads_stop (overlay->collect);
+      break;
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
+
+  return ret;
 }
index 6d572c6..66cd11d 100644 (file)
@@ -1,26 +1,23 @@
 
-#ifndef __GST_TEXTOVERLAY_H__
-#define __GST_TEXTOVERLAY_H__
+#ifndef __GST_TEXT_OVERLAY_H__
+#define __GST_TEXT_OVERLAY_H__
 
 #include <gst/gst.h>
-#include <cairo.h>
+#include <gst/base/gstcollectpads.h>
 
 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 */