Index: gst/suboverlay/gstsuboverlay.h
===================================================================
--- gst/suboverlay/gstsuboverlay.h	(revision 0)
+++ gst/suboverlay/gstsuboverlay.h	(revision 427)
@@ -0,0 +1,72 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2006> Mark Nauwelaerts <mnauw@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __GST_SUBOVERLAY_H__
+#define __GST_SUBOVERLAY_H__
+
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SUB_OVERLAY \
+  (gst_sub_overlay_get_type())
+#define GST_SUB_OVERLAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_SUB_OVERLAY, GstSubOverlay))
+#define GST_SUB_OVERLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_SUB_OVERLAY, GstSubOverlayClass))
+#define GST_SUB_OVERLAY_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_SUB_OVERLAY, GstSubOverlayClass))
+#define GST_IS_SUB_OVERLAY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_SUB_OVERLAY))
+#define GST_IS_SUB_OVERLAY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_SUB_OVERLAY))
+
+typedef struct _GstSubOverlay GstSubOverlay;
+typedef struct _GstSubOverlayClass GstSubOverlayClass;
+
+struct _GstSubOverlay {
+  GstElement element;
+
+  GstPad *srcpad;
+  GstPad *subpad;
+  GstPad *vidpad;
+  GstMuxer *muxpads;
+
+  /* caps information */
+  gint width, height;
+  gint fps_num, fps_den;
+
+  /* current subtitle to use */
+  GstBuffer *subbuffer;
+  /* keep track of lowest scanline below which subtitle is located */
+  guint startline;
+};
+
+struct _GstSubOverlayClass {
+  GstElementClass parent_class;
+};
+
+GType gst_sub_overlay_get_type(void);
+
+G_END_DECLS
+
+#endif /* __GST_SUBOVERLAY_H__ */
Index: gst/suboverlay/Makefile.am
===================================================================
--- gst/suboverlay/Makefile.am	(revision 0)
+++ gst/suboverlay/Makefile.am	(revision 423)
@@ -0,0 +1,9 @@
+
+plugin_LTLIBRARIES = libgstsuboverlay.la
+
+libgstsuboverlay_la_SOURCES = gstsuboverlay.c
+libgstsuboverlay_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS)
+libgstsuboverlay_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS)
+libgstsuboverlay_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+
+noinst_HEADERS = gstsuboverlay.h
Index: gst/suboverlay/gstsuboverlay.c
===================================================================
--- gst/suboverlay/gstsuboverlay.c	(revision 0)
+++ gst/suboverlay/gstsuboverlay.c	(revision 426)
@@ -0,0 +1,468 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2006> Mark Nauwelaerts <mnauw@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <string.h>
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/base/gstmuxer.h>
+#include "gstsuboverlay.h"
+
+static const GstElementDetails suboverlay_details =
+GST_ELEMENT_DETAILS ("Subtitle overlay",
+    "Filter/Editor/Video",
+    "Overlays a (subtitle) AYUV onto a video stream",
+    "Mark Nauwelaerts <mnauw@users.sourceforge.net>");
+
+GST_DEBUG_CATEGORY_STATIC (suboverlay_debug);
+#define GST_CAT_DEFAULT   suboverlay_debug
+
+
+/* Filter signals and args */
+enum
+{
+  /* FILL ME */
+  LAST_SIGNAL
+};
+
+enum
+{
+  ARG_0
+};
+
+static GstStaticPadTemplate suboverlay_src_factory =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{ I420 }"))
+    );
+
+static GstStaticPadTemplate video_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("video",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{ I420 }"))
+    );
+
+static GstStaticPadTemplate sub_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sub",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("video/x-raw-yuv,"
+        "format = (fourcc) AYUV,"
+        "width = (int) [ 1, max ]," "height = (int) [ 1, max ]")
+    );
+
+
+static void gst_sub_overlay_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_sub_overlay_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+static void gst_sub_overlay_reset (GstSubOverlay * overlay);
+
+static gboolean gst_sub_overlay_setcaps (GstPad * pad, GstCaps * vscaps);
+static gboolean gst_sub_overlay_event (GstMuxer * muxer, GstMuxerData * data,
+    GstEvent * event, GstSubOverlay * overlay);
+static GstFlowReturn gst_sub_overlay_muxpads (GstMuxer *muxer,
+    GstMuxerData *data, GstBuffer *buffer, GstSubOverlay *overlay);
+
+static GstStateChangeReturn gst_sub_overlay_change_state (GstElement * element,
+    GstStateChange transition);
+GST_BOILERPLATE (GstSubOverlay, gst_sub_overlay, GstElement, GST_TYPE_ELEMENT);
+
+
+static void
+gst_sub_overlay_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&suboverlay_src_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&video_sink_factory));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&sub_sink_factory));
+  gst_element_class_set_details (element_class, &suboverlay_details);
+}
+
+static void
+gst_sub_overlay_class_init (GstSubOverlayClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  GST_DEBUG_CATEGORY_INIT (suboverlay_debug, "suboverlay", 0,
+      "Subtitle Overlay");
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_sub_overlay_change_state);
+
+  gobject_class->set_property = gst_sub_overlay_set_property;
+  gobject_class->get_property = gst_sub_overlay_get_property;
+}
+
+static void
+gst_sub_overlay_init (GstSubOverlay * overlay, GstSubOverlayClass * klass)
+{
+  GstPad *pad;
+
+  overlay->muxpads = gst_muxer_new ();
+  gst_muxer_set_function (overlay->muxpads,
+      (GstMuxerFunction) GST_DEBUG_FUNCPTR (gst_sub_overlay_muxpads),
+      overlay);
+  gst_muxer_set_event_function (overlay->muxpads,
+      (GstMuxerEventFunction) GST_DEBUG_FUNCPTR (gst_sub_overlay_event),
+      overlay);
+
+  pad = overlay->vidpad =
+      gst_pad_new_from_template (gst_static_pad_template_get
+      (&video_sink_factory), "video_sink");
+  gst_element_add_pad (GST_ELEMENT (overlay), pad);
+  gst_pad_set_setcaps_function (pad,
+      GST_DEBUG_FUNCPTR (gst_sub_overlay_setcaps));
+  gst_muxer_add_pad (overlay->muxpads, pad, sizeof (GstMuxerData));
+
+  pad = overlay->subpad =
+      gst_pad_new_from_template (gst_static_pad_template_get
+      (&sub_sink_factory), "sub_sink");
+  gst_element_add_pad (GST_ELEMENT (overlay), pad);
+  gst_pad_set_setcaps_function (pad,
+      GST_DEBUG_FUNCPTR (gst_sub_overlay_setcaps));
+  gst_muxer_add_pad (overlay->muxpads, pad, sizeof (GstMuxerData));
+
+  overlay->srcpad =
+      gst_pad_new_from_template (gst_static_pad_template_get
+      (&suboverlay_src_factory), "src");
+  gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
+  gst_pad_use_fixed_caps (overlay->srcpad);
+
+  /* init properties */
+  gst_sub_overlay_reset (overlay);
+}
+
+static void
+gst_sub_overlay_reset (GstSubOverlay * overlay)
+{
+  overlay->width = overlay->height = -1;
+  overlay->fps_num = overlay->fps_den = 1;
+  if (overlay->subbuffer) {
+    gst_buffer_unref (overlay->subbuffer);
+    overlay->subbuffer = NULL;
+  }
+}
+
+/* features a prototype of dynamic pad-type recognition
+ * (though not really needed in present setup) */
+static gboolean
+gst_sub_overlay_setcaps (GstPad * pad, GstCaps * vscaps)
+{
+  GstSubOverlay *overlay;
+  GstStructure *structure;
+  gint w, h;
+  const GValue *fps = NULL;
+
+  overlay = GST_SUB_OVERLAY (GST_PAD_PARENT (pad));
+
+  structure = gst_caps_get_structure (vscaps, 0);
+
+  g_return_val_if_fail (gst_structure_get_int (structure, "width", &w), FALSE);
+  g_return_val_if_fail (gst_structure_get_int (structure, "height", &h), FALSE);
+  fps = gst_structure_get_value (structure, "framerate");
+  g_return_val_if_fail (w > 0 && h > 0, FALSE);
+
+  GST_DEBUG_OBJECT (overlay, "set_caps on pad %" GST_PTR_FORMAT, pad);
+
+  /* in any case, dimensions must match */
+
+  /* FIXME; can't renegotiate dimensions due to this,
+   * though tricky to fix, if possible ... */
+  if (overlay->width >= 0 || overlay->height >= 0) {
+    if (overlay->width != w || overlay->height != h) {
+      return FALSE;
+    }
+  }
+  overlay->width = w;
+  overlay->height = h;
+
+  if (pad == overlay->vidpad) {
+    /* want framerate if video pad */
+    if (fps == NULL)
+      return FALSE;
+    g_return_val_if_fail (GST_VALUE_HOLDS_FRACTION (fps), FALSE);
+    if (!gst_value_get_fraction_numerator (fps))
+      return FALSE;
+    overlay->fps_num = gst_value_get_fraction_numerator (fps);
+    overlay->fps_den = gst_value_get_fraction_denominator (fps);
+    /* outgoing caps should be same as incoming video */
+    return gst_pad_set_caps (overlay->srcpad, gst_caps_copy (vscaps));
+  }
+
+  return TRUE;
+}
+
+#define BLEND_NORMAL(Y1,U1,V1,Y2,U2,V2,alpha,Y,U,V)     \
+        Y = ((Y1*(255-alpha))+(Y2*alpha))>>8;           \
+        U = ((U1*(255-alpha))+(U2*alpha))>>8;           \
+        V = ((V1*(255-alpha))+(V2*alpha))>>8;
+
+#define BLEND_MODE BLEND_NORMAL
+
+#define ROUND_UP_2(x) (((x) + 1) & ~1)
+#define ROUND_UP_4(x) (((x) + 3) & ~3)
+#define ROUND_UP_8(x) (((x) + 7) & ~7)
+
+
+#define GST_I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width))
+#define GST_I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2)
+#define GST_I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2)
+
+#define GST_I420_Y_OFFSET(w,h) (0)
+#define GST_I420_U_OFFSET(w,h) (GST_I420_Y_OFFSET(w,h)+(GST_I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h)))
+#define GST_I420_V_OFFSET(w,h) (GST_I420_U_OFFSET(w,h)+(GST_I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2))
+
+/* TODO
+ * consider other colorspaces ?
+ * some more optimization ?
+ */
+static void
+gst_sub_overlay_blend_ayuv_yuv (guint8 * src, gint width, gint height,
+    gdouble src_alpha, guint8 * dest, guint * startline)
+{
+  gint alpha, b_alpha;
+  gint i, j;
+  gint src_stride, dest_stride, c_stride;
+  gint Y, U, V;
+  guint8 *dy, *du, *dv;
+  gint hits;
+
+  src_stride = ROUND_UP_2 (width) * 4;
+  dest_stride = GST_I420_Y_ROWSTRIDE (width);
+
+  b_alpha = (gint) (src_alpha * 255);
+
+  dy = dest;
+  c_stride = GST_I420_U_ROWSTRIDE (width);
+  du = dest + GST_I420_U_OFFSET (width, height);
+  dv = dest + GST_I420_V_OFFSET (width, height);
+  hits = 1;
+  /* poor man's simple start optimization should at least save some doing */
+  for (i = *startline; i < height; ++i) {
+    if (!hits && i == *startline)
+      (*startline)++;
+    hits = 0;
+    for (j = 0; j < width; ++j) {
+      alpha = (src[0] * b_alpha) >> 8;
+      if (G_UNLIKELY (alpha)) {
+        hits++;
+        BLEND_MODE (*dy, *du, *dv, src[1], src[2], src[3],
+            alpha, Y, U, V);
+        *dy = Y;
+        *du = U;
+        *dv = V;
+      }
+
+      src += 4;
+      dy++;
+      if (j & 1) {
+        du++;
+        dv++;
+      }
+    }
+    du = dest + GST_I420_U_OFFSET (width, height) + (i >> 1) * c_stride;
+    dv = du + (width * height) / 4;
+  }
+}
+
+#undef BLEND_MODE
+
+static gboolean
+gst_sub_overlay_event (GstMuxer * muxer, GstMuxerData * data, GstEvent * event,
+    GstSubOverlay *overlay)
+{
+  GstCollectData* cdata = (GstCollectData *) data;
+
+  switch GST_EVENT_TYPE (event) {
+    case GST_EVENT_FLUSH_STOP:
+      if (overlay->subbuffer) {
+        gst_buffer_unref (overlay->subbuffer);
+        overlay->subbuffer = NULL;
+      }
+      return FALSE;
+      break;
+    case GST_EVENT_NEWSEGMENT:
+      /* forward only on video stream */
+      if (cdata->pad == overlay->vidpad) {
+        gst_pad_event_default (cdata->pad, event);
+      }
+      /* report this as handled either way */
+      return TRUE;
+      break;
+    default:
+      return FALSE;
+      break;
+  }
+}
+
+static GstFlowReturn
+gst_sub_overlay_muxpads (GstMuxer * muxer, GstMuxerData * data,
+    GstBuffer * buffer, GstSubOverlay * overlay)
+{
+  GstPad *pad = ((GstCollectData *) data)->pad;
+
+  if (!buffer)
+    return gst_pad_push_event (overlay->srcpad, gst_event_new_eos ());
+
+  /* if subtitle is now first; 
+   * dump previous one and use this one for now */
+  if (pad == overlay->subpad) {
+    if (overlay->subbuffer)
+      gst_buffer_unref (overlay->subbuffer);
+    if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) &&
+        GST_BUFFER_DURATION_IS_VALID (buffer)) {
+      overlay->subbuffer = buffer;
+      overlay->startline = 0;
+    } else
+      GST_WARNING_OBJECT (overlay,
+          "Got text buffer with invalid timestamp or duration, discarding");
+    return GST_FLOW_OK;
+  }
+
+  /* otherwise; must be video */
+
+  /* must have valid time */
+  if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
+    goto missing_timestamp;
+
+  /* note; it is normally not possible that buffer < subbuffer;
+   * since buffer has been provided by muxer after subbuffer */
+  /* ... though, if seeking occurs, ... */
+  if (overlay->subbuffer &&
+      GST_BUFFER_TIMESTAMP (overlay->subbuffer) <=
+          GST_BUFFER_TIMESTAMP (buffer) &&
+      GST_BUFFER_TIMESTAMP (buffer) <
+          GST_BUFFER_TIMESTAMP (overlay->subbuffer) +
+          GST_BUFFER_DURATION (overlay->subbuffer)) {
+    buffer = gst_buffer_make_writable (buffer);
+    gst_sub_overlay_blend_ayuv_yuv (GST_BUFFER_DATA (overlay->subbuffer),
+        overlay->width, overlay->height, 1.0, GST_BUFFER_DATA (buffer),
+        &overlay->startline);
+  } else {
+    if (overlay->subbuffer) {
+      /* must have outrun this subtitle */
+      gst_buffer_unref (overlay->subbuffer);
+      overlay->subbuffer = NULL;
+    }
+  }
+
+  gst_buffer_set_caps (buffer, GST_PAD_CAPS (overlay->srcpad));
+  return gst_pad_push (overlay->srcpad, buffer);
+
+missing_timestamp:
+  {
+    GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+}
+
+
+static void
+gst_sub_overlay_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstSubOverlay *overlay;
+
+  g_return_if_fail (GST_IS_SUB_OVERLAY (object));
+  overlay = GST_SUB_OVERLAY (object);
+
+  switch (prop_id) {
+    default:
+      break;
+  }
+}
+
+static void
+gst_sub_overlay_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstSubOverlay *overlay;
+
+  g_return_if_fail (GST_IS_SUB_OVERLAY (object));
+  overlay = GST_SUB_OVERLAY (object);
+
+  switch (prop_id) {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstStateChangeReturn
+gst_sub_overlay_change_state (GstElement * element, GstStateChange transition)
+{
+  GstSubOverlay *overlay = GST_SUB_OVERLAY (element);
+  GstStateChangeReturn ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      gst_muxer_start (overlay->muxpads);
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_muxer_stop (overlay->muxpads);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state,
+      (element, transition), GST_STATE_CHANGE_SUCCESS);
+  if (ret != GST_STATE_CHANGE_SUCCESS)
+    return ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_sub_overlay_reset (overlay);
+      break;
+    default:
+      break;
+  }
+
+  return GST_STATE_CHANGE_SUCCESS;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "suboverlay", GST_RANK_NONE,
+      GST_TYPE_SUB_OVERLAY);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "suboverlay",
+    "Overlay AYUV subtitles",
+    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
Index: configure.ac
===================================================================
--- configure.ac	(revision 400)
+++ configure.ac	(working copy)
@@ -89,6 +89,7 @@
   replaygain \
   spectrum \
   speed \
+  suboverlay \
   qtdemux \
   tta \
   videocrop \
@@ -915,6 +916,7 @@
 gst/replaygain/Makefile
 gst/spectrum/Makefile
 gst/speed/Makefile
+gst/suboverlay/Makefile
 gst/qtdemux/Makefile
 gst/tta/Makefile
 gst/videocrop/Makefile
