dvbsuboverlay: initial version, work in progress
authorMart Raudsepp <mart.raudsepp@collabora.co.uk>
Mon, 29 Nov 2010 20:06:07 +0000 (20:06 +0000)
committerEdward Hervey <edward.hervey@collabora.co.uk>
Wed, 8 Dec 2010 15:30:09 +0000 (16:30 +0100)
configure.ac
gst/dvbsuboverlay/Makefile.am [new file with mode: 0644]
gst/dvbsuboverlay/TODO [new file with mode: 0644]
gst/dvbsuboverlay/dvb-sub.c [new file with mode: 0644]
gst/dvbsuboverlay/dvb-sub.h [new file with mode: 0644]
gst/dvbsuboverlay/ffmpeg-colorspace.h [new file with mode: 0644]
gst/dvbsuboverlay/gstdvbsuboverlay.c [new file with mode: 0644]
gst/dvbsuboverlay/gstdvbsuboverlay.h [new file with mode: 0644]

index e728fcd..566de0d 100644 (file)
@@ -300,6 +300,7 @@ AG_GST_CHECK_PLUGIN(dataurisrc)
 AG_GST_CHECK_PLUGIN(dccp)
 AG_GST_CHECK_PLUGIN(debugutils)
 AG_GST_CHECK_PLUGIN(dtmf)
+AG_GST_CHECK_PLUGIN(dvbsuboverlay)
 AG_GST_CHECK_PLUGIN(dvdspu)
 AG_GST_CHECK_PLUGIN(festival)
 AG_GST_CHECK_PLUGIN(freeze)
@@ -1725,6 +1726,7 @@ gst/dataurisrc/Makefile
 gst/dccp/Makefile
 gst/debugutils/Makefile
 gst/dtmf/Makefile
+gst/dvbsuboverlay/Makefile
 gst/dvdspu/Makefile
 gst/festival/Makefile
 gst/freeze/Makefile
diff --git a/gst/dvbsuboverlay/Makefile.am b/gst/dvbsuboverlay/Makefile.am
new file mode 100644 (file)
index 0000000..9cc2f38
--- /dev/null
@@ -0,0 +1,10 @@
+plugin_LTLIBRARIES = libgstdvbsuboverlay.la
+
+libgstdvbsuboverlay_la_SOURCES = dvb-sub.c gstdvbsuboverlay.c
+
+libgstdvbsuboverlay_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
+libgstdvbsuboverlay_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) $(GST_LIBS) -lgstvideo-@GST_MAJORMINOR@
+libgstdvbsuboverlay_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstdvbsuboverlay_la_LIBTOOLFLAGS = --tag=disable-static
+
+noinst_HEADERS = gstdvbsuboverlay.h dvb-sub.h ffmpeg-colorspace.h
diff --git a/gst/dvbsuboverlay/TODO b/gst/dvbsuboverlay/TODO
new file mode 100644 (file)
index 0000000..7042734
--- /dev/null
@@ -0,0 +1,6 @@
+Check about GST_PAD_PARENT vs gst_pad_get_parent - do we need a reference - is there any danger of losing the parent in the middle or not:
+<thaytan> one uses GST_PAD_PARENT in situations where you can be sure the parent exists and will exist for the entire time you need it
+<thaytan> and gst_pad_get_parent when you need a ref because the pad might get unparented while you're using it
+
+Ask about individual segment handling on separate sink pads. Is it possible that the separate NEWSEGMENT events on the text and video pad have different start and/or stop values, as to require some code complexity present?
+
diff --git a/gst/dvbsuboverlay/dvb-sub.c b/gst/dvbsuboverlay/dvb-sub.c
new file mode 100644 (file)
index 0000000..b0b26cc
--- /dev/null
@@ -0,0 +1,1640 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * libdvbsub - DVB subtitle decoding
+ * Copyright (C) Mart Raudsepp 2009 <mart.raudsepp@artecdesign.ee>
+ * 
+ * Heavily uses code algorithms ported from ffmpeg's libavcodec/dvbsubdec.c,
+ * especially the segment parsers. The original license applies to this
+ * ported code and the whole code in this file as well.
+ *
+ * Original copyright information follows:
+ */
+/*
+ * DVB subtitle decoding for ffmpeg
+ * Copyright (c) 2005 Ian Caulfield
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "dvb-sub.h"
+#include <string.h>             /* memset */
+#include <gst/gstutils.h>       /* GST_READ_UINT16_BE */
+#include <gst/base/gstbitreader.h>      /* GstBitReader */
+#include "ffmpeg-colorspace.h" /* YUV_TO_RGB1_CCIR */   /* FIXME: Just give YUV data to gstreamer then? */
+
+/* FIXME: Convert to GST_LOG and clean up */
+void (*g_log_callback) (GLogLevelFlags log_level, const gchar * format,
+    va_list args, gpointer user_data) = NULL;
+gpointer g_log_callback_user_data = NULL;
+
+#define DEBUG
+#ifdef DEBUG
+#define dvb_log(log_type, log_level, format...) real_dvb_log(log_type, log_level, ## format)
+typedef enum
+{
+  /* dvb_log types // DVB_LOG environment variable string */
+  DVB_LOG_GENERAL,              /* GENERAL */
+  DVB_LOG_PAGE,                 /* PAGE */
+  DVB_LOG_REGION,               /* REGION */
+  DVB_LOG_CLUT,                 /* CLUT */
+  DVB_LOG_OBJECT,               /* OBJECT */
+  DVB_LOG_PIXEL,                /* PIXEL */
+  DVB_LOG_RUNLEN,               /* RUNLEN */
+  DVB_LOG_DISPLAY,              /* DISPLAY */
+  DVB_LOG_STREAM,               /* STREAM - issues in the encoded stream (TV service provider encoder problem) */
+  DVB_LOG_PACKET,               /* PACKET - messages during raw demuxer data packet handling */
+  DVB_LOG_LAST                  /* sentinel use only */
+} DvbLogTypes;
+
+static void
+real_dvb_log (const gint log_type, GLogLevelFlags log_level,
+    const gchar * format, ...)
+{
+  if (g_log_callback) {
+    va_list va;
+    va_start (va, format);
+    switch (log_type) {
+      default:
+        g_log_callback (log_level, format, va, g_log_callback_user_data);
+        break;
+      case DVB_LOG_PIXEL:
+      case DVB_LOG_RUNLEN:
+        break;
+    }
+    va_end (va);
+  }
+}
+#else
+#define dvb_log(log_type, log_level, format...)
+#endif
+
+/* FIXME: Are we waiting for an acquisition point before trying to do things? */
+/* FIXME: In the end convert some of the guint8/16 (especially stack variables) back to gint for access efficiency */
+
+/**
+ * SECTION:dvb-sub
+ * @short_description: a DVB subtitle parsing class
+ * @stability: Unstable
+ *
+ * The #DvbSub represents an object used for parsing a DVB subpicture,
+ * and signalling the API user for new bitmaps to show on screen.
+ */
+
+#define MAX_NEG_CROP 1024
+static guint8 ff_cropTbl[256 + 2 * MAX_NEG_CROP] = { 0, };
+
+#define cm (ff_cropTbl + MAX_NEG_CROP)
+
+/* FIXME: This is really ARGB... We might need this configurable for performant
+ * FIXME: use in GStreamer as well if that likes RGBA more (Qt prefers ARGB) */
+#define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
+
+typedef struct DVBSubCLUT
+{
+  int id;                       /* default_clut uses -1 for this, so guint8 isn't fine without adaptations first */
+
+  guint32 clut4[4];
+  guint32 clut16[16];
+  guint32 clut256[256];
+
+  struct DVBSubCLUT *next;
+} DVBSubCLUT;
+
+static DVBSubCLUT default_clut;
+
+typedef struct DVBSubObjectDisplay
+{
+  /* FIXME: Use more correct sizes */
+  int object_id;
+  int region_id;
+
+  int x_pos;
+  int y_pos;
+
+  int fgcolor;
+  int bgcolor;
+
+  /* FIXME: Should we use GSList? The relating interaction and pointer assigment is quite complex and perhaps unsuited for a plain GSList anyway */
+  struct DVBSubObjectDisplay *region_list_next;
+  struct DVBSubObjectDisplay *object_list_next;
+} DVBSubObjectDisplay;
+
+typedef struct DVBSubObject
+{
+  /* FIXME: Use more correct sizes */
+  int id;                       /* FIXME: Use guint8 after checking it's fine in all code using it */
+
+  int type;
+
+  /* FIXME: Should we use GSList? */
+  DVBSubObjectDisplay *display_list;
+  struct DVBSubObject *next;
+} DVBSubObject;
+
+typedef struct DVBSubRegionDisplay
+{                               /* FIXME: Figure out if this structure is only used temporarily in page_segment parser, or also more */
+  int region_id;
+
+  int x_pos;
+  int y_pos;
+
+  struct DVBSubRegionDisplay *next;
+} DVBSubRegionDisplay;
+
+typedef struct DVBSubRegion
+{
+  guint8 id;
+  guint16 width;
+  guint16 height;
+  guint8 depth;                 /* If we want to make this a guint8, then need to ensure it isn't wrap around with reserved values in region handling code */
+
+  guint8 clut;
+  guint8 bgcolor;
+
+  /* FIXME: Validate these fields existence and exact types */
+  guint8 *pbuf;
+  int buf_size;
+
+  DVBSubObjectDisplay *display_list;
+
+  struct DVBSubRegion *next;
+} DVBSubRegion;
+
+typedef struct _DvbSubPrivate DvbSubPrivate;
+struct _DvbSubPrivate
+{
+  int fd;
+  DvbSubCallbacks callbacks;
+  gpointer user_data;
+
+  guint8 page_time_out;
+  DVBSubRegion *region_list;
+  DVBSubCLUT *clut_list;
+  DVBSubObject *object_list;
+  /* FIXME... */
+  int display_list_size;
+  DVBSubRegionDisplay *display_list;
+  GString *pes_buffer;
+};
+
+#define DVB_SUB_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DVB_TYPE_SUB, DvbSubPrivate))
+
+G_DEFINE_TYPE (DvbSub, dvb_sub, G_TYPE_OBJECT);
+
+typedef enum
+{
+  TOP_FIELD = 0,
+  BOTTOM_FIELD = 1
+} DvbSubPixelDataSubBlockFieldType;
+
+/* FIXME: It might make sense to pass DvbSubPrivate for all the get_* functions, instead of public DvbSub */
+static DVBSubObject *
+get_object (DvbSub * dvb_sub, guint16 object_id)
+{
+  const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubObject *ptr = priv->object_list;
+
+  while (ptr && ptr->id != object_id) {
+    ptr = ptr->next;
+  }
+
+  return ptr;
+}
+
+static DVBSubCLUT *
+get_clut (DvbSub * dvb_sub, gint clut_id)
+{
+  const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubCLUT *ptr = priv->clut_list;
+
+  while (ptr && ptr->id != clut_id) {
+    ptr = ptr->next;
+  }
+
+  return ptr;
+}
+
+// FIXME: Just pass private_data pointer directly here and in other get_* helper functions?
+static DVBSubRegion *
+get_region (DvbSub * dvb_sub, guint8 region_id)
+{
+  const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubRegion *ptr = priv->region_list;
+
+  while (ptr && ptr->id != region_id) {
+    ptr = ptr->next;
+  }
+
+  return ptr;
+}
+
+static void
+delete_region_display_list (DvbSub * dvb_sub, DVBSubRegion * region)
+{
+  const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubObject *object, *obj2;
+  DVBSubObject **obj2_ptr;
+  DVBSubObjectDisplay *display, *obj_disp, **obj_disp_ptr;
+
+  while (region->display_list) {
+    display = region->display_list;
+
+    object = get_object (dvb_sub, display->object_id);
+
+    if (object) {
+      obj_disp_ptr = &object->display_list;
+      obj_disp = *obj_disp_ptr;
+
+      while (obj_disp && obj_disp != display) {
+        obj_disp_ptr = &obj_disp->object_list_next;
+        obj_disp = *obj_disp_ptr;
+      }
+
+      if (obj_disp) {
+        *obj_disp_ptr = obj_disp->object_list_next;
+
+        if (!object->display_list) {
+          obj2_ptr = (DVBSubObject **) & priv->object_list;     /* FIXME: Evil casting */
+          obj2 = *obj2_ptr;
+
+          while (obj2 != object) {
+            g_assert (obj2);
+            obj2_ptr = &obj2->next;
+            obj2 = *obj2_ptr;
+          }
+
+          *obj2_ptr = obj2->next;
+
+          g_slice_free (DVBSubObject, obj2);
+        }
+      }
+    }
+
+    region->display_list = display->region_list_next;
+
+    g_slice_free (DVBSubObjectDisplay, display);
+  }
+}
+
+static void
+delete_state (DvbSub * dvb_sub)
+{
+  DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubRegion *region;
+
+  while (priv->region_list) {
+    region = priv->region_list;
+
+    priv->region_list = region->next;
+
+    delete_region_display_list (dvb_sub, region);
+    if (region->pbuf)
+      g_free (region->pbuf);
+
+    g_slice_free (DVBSubRegion, region);
+  }
+
+  g_slice_free_chain (DVBSubCLUT, priv->clut_list, next);
+  priv->clut_list = NULL;
+
+  /* Should already be null */
+  if (priv->object_list)
+    g_warning ("Memory deallocation error!");
+}
+
+static void
+dvb_sub_init (DvbSub * self)
+{
+  DvbSubPrivate *priv;
+
+  self->private_data = priv = DVB_SUB_GET_PRIVATE (self);
+
+  /* TODO: Add initialization code here */
+  /* FIXME: Do we have a reason to initiate the members to zero, or are we guaranteed that anyway? */
+  priv->region_list = NULL;
+  priv->object_list = NULL;
+  priv->page_time_out = 0;      /* FIXME: Maybe 255 instead? */
+  priv->pes_buffer = g_string_new (NULL);
+}
+
+static void
+dvb_sub_finalize (GObject * object)
+{
+  DvbSub *self = DVB_SUB (object);
+  DvbSubPrivate *priv = (DvbSubPrivate *) self->private_data;
+  /* TODO: Add deinitalization code here */
+  /* FIXME: Clear up region_list contents */
+  delete_state (self);          /* close_pid should have called this, but lets be sure */
+  g_string_free (priv->pes_buffer, TRUE);
+
+  G_OBJECT_CLASS (dvb_sub_parent_class)->finalize (object);
+}
+
+/* init static data necessary for ffmpeg-colorspace conversion */
+static void
+dsputil_static_init (void)
+{
+  int i;
+
+  for (i = 0; i < 256; i++)
+    ff_cropTbl[i + MAX_NEG_CROP] = i;
+  for (i = 0; i < MAX_NEG_CROP; i++) {
+    ff_cropTbl[i] = 0;
+    ff_cropTbl[i + MAX_NEG_CROP + 256] = 255;
+  }
+}
+
+static void
+dvb_sub_class_init (DvbSubClass * klass)
+{
+  int i, r, g, b, a = 0;
+  GObjectClass *object_class = (GObjectClass *) klass;
+
+  object_class->finalize = dvb_sub_finalize;
+
+  g_type_class_add_private (klass, sizeof (DvbSubPrivate));
+
+  dsputil_static_init ();       /* Initializes ff_cropTbl table, used in YUV_TO_RGB conversion */
+
+  /* Initialize the static default_clut structure, from which other clut
+   * structures are initialized from (to start off with default CLUTs
+   * as defined in the specification). */
+  default_clut.id = -1;
+
+  default_clut.clut4[0] = RGBA (0, 0, 0, 0);
+  default_clut.clut4[1] = RGBA (255, 255, 255, 255);
+  default_clut.clut4[2] = RGBA (0, 0, 0, 255);
+  default_clut.clut4[3] = RGBA (127, 127, 127, 255);
+
+  default_clut.clut16[0] = RGBA (0, 0, 0, 0);
+  for (i = 1; i < 16; i++) {
+    if (i < 8) {
+      r = (i & 1) ? 255 : 0;
+      g = (i & 2) ? 255 : 0;
+      b = (i & 4) ? 255 : 0;
+    } else {
+      r = (i & 1) ? 127 : 0;
+      g = (i & 2) ? 127 : 0;
+      b = (i & 4) ? 127 : 0;
+    }
+    default_clut.clut16[i] = RGBA (r, g, b, 255);
+  }
+
+  default_clut.clut256[0] = RGBA (0, 0, 0, 0);
+  for (i = 1; i < 256; i++) {
+    if (i < 8) {
+      r = (i & 1) ? 255 : 0;
+      g = (i & 2) ? 255 : 0;
+      b = (i & 4) ? 255 : 0;
+      a = 63;
+    } else {
+      switch (i & 0x88) {
+        case 0x00:
+          r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
+          g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
+          b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
+          a = 255;
+          break;
+        case 0x08:
+          r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
+          g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
+          b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
+          a = 127;
+          break;
+        case 0x80:
+          r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
+          g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
+          b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
+          a = 255;
+          break;
+        case 0x88:
+          r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
+          g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
+          b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
+          a = 255;
+          break;
+      }
+    }
+    default_clut.clut256[i] = RGBA (r, g, b, a);
+  }
+}
+
+static void
+_dvb_sub_parse_page_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
+    gint buf_size)
+{                               /* FIXME: Use guint for buf_size here and in many other places? */
+  DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+  DVBSubRegionDisplay *display;
+  DVBSubRegionDisplay *tmp_display_list, **tmp_ptr;
+
+  const guint8 *buf_end = buf + buf_size;
+  guint8 region_id;
+  guint8 page_state;
+
+#ifdef DEBUG
+  static int counter = 0;
+  static const gchar *page_state_str[] = {
+    "Normal case",
+    "ACQUISITION POINT",
+    "Mode Change",
+    "RESERVED"
+  };
+#endif
+
+  if (buf_size < 1)
+    return;
+
+  priv->page_time_out = *buf++;
+  page_state = ((*buf++) >> 2) & 3;
+
+#ifdef DEBUG
+  ++counter;
+  dvb_log (DVB_LOG_PAGE, G_LOG_LEVEL_DEBUG,
+      "%d: page_id = %u, length = %d, page_time_out = %u seconds, page_state = %s",
+      counter, page_id, buf_size, priv->page_time_out,
+      page_state_str[page_state]);
+#endif
+
+  if (page_state == 2) {        /* Mode change */
+    delete_state (dvb_sub);
+  }
+
+  tmp_display_list = priv->display_list;
+  priv->display_list = NULL;
+  priv->display_list_size = 0;
+
+  while (buf + 5 < buf_end) {
+    region_id = *buf++;
+    buf += 1;
+
+    display = tmp_display_list;
+    tmp_ptr = &tmp_display_list;
+
+    while (display && display->region_id != region_id) {
+      tmp_ptr = &display->next;
+      display = display->next;
+    }
+
+    if (!display)
+      display = g_slice_new0 (DVBSubRegionDisplay);
+
+    display->region_id = region_id;
+
+    display->x_pos = GST_READ_UINT16_BE (buf);
+    buf += 2;
+    display->y_pos = GST_READ_UINT16_BE (buf);
+    buf += 2;
+
+    *tmp_ptr = display->next;
+
+    display->next = priv->display_list;
+    priv->display_list = display;
+    priv->display_list_size++;
+
+    dvb_log (DVB_LOG_PAGE, G_LOG_LEVEL_DEBUG,
+        "%d: REGION information: ID = %u, address = %ux%u",
+        counter, region_id, display->x_pos, display->y_pos);
+  }
+
+  while (tmp_display_list) {
+    display = tmp_display_list;
+
+    tmp_display_list = display->next;
+
+    g_slice_free (DVBSubRegionDisplay, display);
+  }
+}
+
+static void
+_dvb_sub_parse_region_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
+    gint buf_size)
+{
+  DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+
+  const guint8 *buf_end = buf + buf_size;
+  guint8 region_id;
+  guint16 object_id;
+  DVBSubRegion *region;
+  DVBSubObject *object;
+  DVBSubObjectDisplay *object_display;
+  gboolean fill;
+
+  if (buf_size < 10)
+    return;
+
+  region_id = *buf++;
+
+  region = get_region (dvb_sub, region_id);
+
+  if (!region) {                /* Create a new region */
+    region = g_slice_new0 (DVBSubRegion);
+    region->id = region_id;
+    region->next = priv->region_list;
+    priv->region_list = region;
+  }
+
+  fill = ((*buf++) >> 3) & 1;
+
+  region->width = GST_READ_UINT16_BE (buf);
+  buf += 2;
+  region->height = GST_READ_UINT16_BE (buf);
+  buf += 2;
+
+  if (region->width * region->height != region->buf_size) {     /* FIXME: Read closer from spec what happens when dimensions change */
+    if (region->pbuf)
+      g_free (region->pbuf);
+
+    region->buf_size = region->width * region->height;
+
+    region->pbuf = g_malloc (region->buf_size); /* TODO: We can probably use GSlice here if careful about freeing while buf_size still records the correct size */
+
+    fill = 1;                   /* FIXME: Validate from spec that fill is forced on (in the following codes context) when dimensions change */
+  }
+
+  region->depth = 1 << (((*buf++) >> 2) & 7);
+  if (region->depth < 2 || region->depth > 8) {
+    g_warning ("region depth %d is invalid\n", region->depth);
+    region->depth = 4;          /* FIXME: Check from spec this is the default? */
+  }
+
+  region->clut = *buf++;
+
+  if (region->depth == 8)
+    region->bgcolor = *buf++;
+  else {
+    buf += 1;
+
+    if (region->depth == 4)
+      region->bgcolor = (((*buf++) >> 4) & 15);
+    else
+      region->bgcolor = (((*buf++) >> 2) & 3);
+  }
+
+  dvb_log (DVB_LOG_REGION, G_LOG_LEVEL_DEBUG,
+      "id = %u, (%ux%u)@%u-bit",
+      region_id, region->width, region->height, region->depth);
+
+  if (fill) {
+    memset (region->pbuf, region->bgcolor, region->buf_size);
+    dvb_log (DVB_LOG_REGION, G_LOG_LEVEL_DEBUG,
+        "Filling region (%u) with bgcolor = %u", region->id, region->bgcolor);
+  }
+
+  delete_region_display_list (dvb_sub, region); /* Delete the region display list for current region - FIXME: why? */
+
+  while (buf + 6 <= buf_end) {
+    object_id = GST_READ_UINT16_BE (buf);
+    buf += 2;
+
+    object = get_object (dvb_sub, object_id);
+
+    if (!object) {
+      object = g_slice_new0 (DVBSubObject);
+
+      object->id = object_id;
+
+      object->next = priv->object_list;
+      priv->object_list = object;
+    }
+
+    object->type = (*buf) >> 6;
+
+    object_display = g_slice_new0 (DVBSubObjectDisplay);
+
+    object_display->object_id = object_id;
+    object_display->region_id = region_id;
+
+    object_display->x_pos = GST_READ_UINT16_BE (buf) & 0xfff;
+    buf += 2;
+    object_display->y_pos = GST_READ_UINT16_BE (buf) & 0xfff;
+    buf += 2;
+
+    if ((object->type == 1 || object->type == 2) && buf + 2 <= buf_end) {
+      object_display->fgcolor = *buf++;
+      object_display->bgcolor = *buf++;
+    }
+
+    object_display->region_list_next = region->display_list;
+    region->display_list = object_display;
+
+    object_display->object_list_next = object->display_list;
+    object->display_list = object_display;
+
+    dvb_log (DVB_LOG_REGION, G_LOG_LEVEL_DEBUG,
+        "REGION DATA: object_id = %u, region_id = %u, pos = %ux%u, obj_type = %u",
+        object->id, region->id, object_display->x_pos, object_display->y_pos,
+        object->type);
+    if (object->type == 1 || object->type == 2)
+      dvb_log (DVB_LOG_REGION, G_LOG_LEVEL_DEBUG,
+          "REGION DATA: fgcolor = %u, bgcolor = %u\n", object_display->fgcolor,
+          object_display->bgcolor);
+  }
+}
+
+static void
+_dvb_sub_parse_clut_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
+    gint buf_size)
+{
+  DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+
+  const guint8 *buf_end = buf + buf_size;
+  guint8 clut_id;
+  DVBSubCLUT *clut;
+  int entry_id, depth, full_range;
+  int y, cr, cb, alpha;
+  int r, g, b, r_add, g_add, b_add;
+
+#ifdef DEBUG_PACKET_CONTENTS
+  g_print ("DVB clut packet:\n");
+  gst_util_dump_mem (buf, buf_size);
+#endif
+
+  clut_id = *buf++;
+  buf += 1;
+
+  clut = get_clut (dvb_sub, clut_id);
+
+  if (!clut) {
+    clut = g_slice_new (DVBSubCLUT);    /* FIXME-MEMORY-LEAK: This seems to leak per valgrind */
+
+    memcpy (clut, &default_clut, sizeof (DVBSubCLUT));
+
+    clut->id = clut_id;
+
+    clut->next = priv->clut_list;
+    priv->clut_list = clut;
+  }
+
+  while (buf + 4 < buf_end) {
+    entry_id = *buf++;
+
+    depth = (*buf) & 0xe0;
+
+    if (depth == 0) {
+      g_warning ("Invalid clut depth 0x%x!", *buf);
+      return;
+    }
+
+    full_range = (*buf++) & 1;
+
+    if (full_range) {
+      y = *buf++;
+      cr = *buf++;
+      cb = *buf++;
+      alpha = *buf++;
+    } else {
+      y = buf[0] & 0xfc;
+      cr = (((buf[0] & 3) << 2) | ((buf[1] >> 6) & 3)) << 4;
+      cb = (buf[1] << 2) & 0xf0;
+      alpha = (buf[1] << 6) & 0xc0;
+
+      buf += 2;
+    }
+
+    if (y == 0)
+      alpha = 0xff;
+
+    YUV_TO_RGB1_CCIR (cb, cr);
+    YUV_TO_RGB2_CCIR (r, g, b, y);
+
+    dvb_log (DVB_LOG_CLUT, G_LOG_LEVEL_DEBUG,
+        "CLUT DEFINITION: clut %d := (%d,%d,%d,%d)", entry_id, r, g, b, alpha);
+
+    if (depth & 0x80)
+      clut->clut4[entry_id] = RGBA (r, g, b, 255 - alpha);
+    if (depth & 0x40)
+      clut->clut16[entry_id] = RGBA (r, g, b, 255 - alpha);
+    if (depth & 0x20)
+      clut->clut256[entry_id] = RGBA (r, g, b, 255 - alpha);
+  }
+}
+
+// FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
+// FFMPEG-FIXME: refactoring as done here
+static int
+_dvb_sub_read_2bit_string (guint8 * destbuf, gint dbuf_len,
+    const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
+{
+  GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
+  /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
+
+  gboolean stop_parsing = FALSE;
+  guint32 bits = 0;
+  guint32 pixels_read = 0;
+
+  static gboolean warning_shown = FALSE;
+  if (!warning_shown) {
+    g_warning
+        ("Parsing 2bit color DVB sub-picture. This is not tested at all. If you see this message, "
+        "please provide the developers with sample media with these subtitles, if possible.");
+    warning_shown = TRUE;
+  }
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "(n=2): Inside %s with dbuf_len = %d", __PRETTY_FUNCTION__, dbuf_len);
+
+  while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
+    guint run_length = 0, clut_index = 0;
+    gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
+
+    if (bits) {                 /* 2-bit_pixel-code */
+      run_length = 1;
+      clut_index = bits;
+    } else {                    /* 2-bit_zero */
+      gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
+      if (bits == 1) {          /* switch_1 == '1' */
+        gst_bit_reader_get_bits_uint32 (&gb, &run_length, 3);
+        run_length += 3;
+        gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
+      } else {                  /* switch_1 == '0' */
+        gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
+        if (bits == 1) {        /* switch_2 == '1' */
+          run_length = 1;       /* 1x pseudo-colour '00' */
+        } else {                /* switch_2 == '0' */
+          gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
+          switch (bits) {       /* switch_3 */
+            case 0x0:          /* end of 2-bit/pixel_code_string */
+              stop_parsing = TRUE;
+              break;
+            case 0x1:          /* two pixels shall be set to pseudo colour (entry) '00' */
+              run_length = 2;
+              break;
+            case 0x2:          /* the following 6 bits contain run length coded pixel data */
+              gst_bit_reader_get_bits_uint32 (&gb, &run_length, 4);
+              run_length += 12;
+              gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
+              break;
+            case 0x3:          /* the following 10 bits contain run length coded pixel data */
+              gst_bit_reader_get_bits_uint32 (&gb, &run_length, 8);
+              run_length += 29;
+              gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
+              break;
+          }
+        }
+      }
+    }
+
+    /* If run_length is zero, continue. Only case happening is when
+     * stop_parsing is TRUE too, so next cycle shouldn't run */
+    if (run_length == 0)
+      continue;
+
+    /* Trim the run_length to not go beyond the line end and consume
+     * it from remaining length of dest line */
+    run_length = MIN (run_length, dbuf_len);
+    dbuf_len -= run_length;
+
+    /* Make clut_index refer to the index into the desired bit depths
+     * CLUT definition table */
+    if (map_table)
+      clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
+
+    /* Now we can simply memset run_length count of destination bytes
+     * to clut_index, but only if not non_modifying */
+    dvb_log (DVB_LOG_RUNLEN, G_LOG_LEVEL_DEBUG,
+        "Setting %u pixels to color 0x%x in destination buffer; dbuf_len left is %d pixels",
+        run_length, clut_index, dbuf_len);
+    if (!(non_mod == 1 && bits == 1))
+      memset (destbuf, clut_index, run_length);
+
+    destbuf += run_length;
+    pixels_read += run_length;
+  }
+
+  // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
+  //gst_bit_reader_skip_to_byte (&gb);
+  *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
+
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "Returning from 2bit_string parser with %u pixels read", pixels_read);
+  // FIXME: Shouldn't need this variable if tracking things in the loop better
+  return pixels_read;
+}
+
+// FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
+// FFMPEG-FIXME: refactoring as done here, explained in commit 895296c3
+static int
+_dvb_sub_read_4bit_string (guint8 * destbuf, gint dbuf_len,
+    const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
+{
+  GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
+  /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
+  gboolean stop_parsing = FALSE;
+  guint32 bits = 0;
+  guint32 pixels_read = 0;
+
+  dvb_log (DVB_LOG_RUNLEN, G_LOG_LEVEL_DEBUG,
+      "Entering 4bit_string parser at srcbuf position %p with buf_size = %d; destination buffer size is %d @ %p",
+      *srcbuf, buf_size, dbuf_len, destbuf);
+
+  while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
+    guint run_length = 0, clut_index = 0;
+    gst_bit_reader_get_bits_uint32 (&gb, &bits, 4);
+
+    if (bits) {
+      run_length = 1;
+      clut_index = bits;
+    } else {
+      gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
+      if (bits == 0) {          /* switch_1 == '0' */
+        gst_bit_reader_get_bits_uint32 (&gb, &run_length, 3);
+        if (!run_length) {
+          stop_parsing = TRUE;
+        } else {
+          run_length += 2;
+        }
+      } else {                  /* switch_1 == '1' */
+        gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
+        if (bits == 0) {        /* switch_2 == '0' */
+          gst_bit_reader_get_bits_uint32 (&gb, &run_length, 2);
+          run_length += 4;
+          gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
+        } else {                /* switch_2 == '1' */
+          gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
+          switch (bits) {
+            case 0x0:          /* switch_3 == '00' */
+              run_length = 1;   /* 1 pixel of pseudo-color 0 */
+              break;
+            case 0x1:          /* switch_3 == '01' */
+              run_length = 2;   /* 2 pixels of pseudo-color 0 */
+              break;
+            case 0x2:          /* switch_3 == '10' */
+              gst_bit_reader_get_bits_uint32 (&gb, &run_length, 4);
+              run_length += 9;
+              gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
+              break;
+            case 0x3:          /* switch_3 == '11' */
+              gst_bit_reader_get_bits_uint32 (&gb, &run_length, 8);
+              run_length += 25;
+              gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
+              break;
+          }
+        }
+      }
+    }
+
+    /* If run_length is zero, continue. Only case happening is when
+     * stop_parsing is TRUE too, so next cycle shouldn't run */
+    if (run_length == 0)
+      continue;
+
+    /* Trim the run_length to not go beyond the line end and consume
+     * it from remaining length of dest line */
+    run_length = MIN (run_length, dbuf_len);
+    dbuf_len -= run_length;
+
+    /* Make clut_index refer to the index into the desired bit depths
+     * CLUT definition table */
+    if (map_table)
+      clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
+
+    /* Now we can simply memset run_length count of destination bytes
+     * to clut_index, but only if not non_modifying */
+    dvb_log (DVB_LOG_RUNLEN, G_LOG_LEVEL_DEBUG,
+        "Setting %u pixels to color 0x%x in destination buffer; dbuf_len left is %d pixels",
+        run_length, clut_index, dbuf_len);
+    if (!(non_mod == 1 && bits == 1))
+      memset (destbuf, clut_index, run_length);
+
+    destbuf += run_length;
+    pixels_read += run_length;
+  }
+
+  // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
+  //gst_bit_reader_skip_to_byte (&gb);
+  *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
+
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "Returning from 4bit_string parser with %u pixels read", pixels_read);
+  // FIXME: Shouldn't need this variable if tracking things in the loop better
+  return pixels_read;
+}
+
+static int
+_dvb_sub_read_8bit_string (guint8 * destbuf, gint dbuf_len,
+    const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
+{
+  GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
+  /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
+
+  gboolean stop_parsing = FALSE;
+  guint32 bits = 0;
+  guint32 pixels_read = 0;
+
+  static gboolean warning_shown = FALSE;
+  if (!warning_shown) {
+    g_warning
+        ("Parsing 8bit color DVB sub-picture. This is not tested at all. If you see this message, "
+        "please provide the developers with sample media with these subtitles, if possible.");
+    warning_shown = TRUE;
+  }
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "(n=8): Inside %s with dbuf_len = %d", __PRETTY_FUNCTION__, dbuf_len);
+
+  /* FFMPEG-FIXME: ffmpeg uses a manual byte walking algorithm, which might be more performant,
+   * FFMPEG-FIXME: but it does almost absolutely no buffer length checking, so could walk over
+   * FFMPEG-FIXME: memory boundaries. While we don't check gst_bit_reader_get_bits_uint32
+   * FFMPEG-FIXME: return values either and therefore might get some pixels corrupted, we at
+   * FFMPEG-FIXME: lest have no chance of reading memory we don't own and visual corruption
+   * FFMPEG-FIXME: is guaranteed anyway when not all bytes are present */
+  /* Rephrased - it's better to work with bytes with default value '0' instead of reading from memory we don't own. */
+  while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
+    guint run_length = 0, clut_index = 0;
+    gst_bit_reader_get_bits_uint32 (&gb, &bits, 8);
+
+    if (bits) {                 /* 8-bit_pixel-code */
+      run_length = 1;
+      clut_index = bits;
+    } else {                    /* 8-bit_zero */
+      gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
+      if (bits == 0) {          /* switch_1 == '0' */
+        /* run_length_1-127 for pseudo-colour _entry) '0x00' */
+        gst_bit_reader_get_bits_uint32 (&gb, &run_length, 7);
+        if (run_length == 0) {  /* end_of_string_signal */
+          stop_parsing = TRUE;
+        }
+      } else {                  /* switch_1 == '1' */
+        /* run_length_3-127 */
+        gst_bit_reader_get_bits_uint32 (&gb, &run_length, 7);
+        gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 8);
+#ifdef DEBUG
+        /* Emit a debugging message about stream not following specification */
+        if (run_length < 3) {
+          dvb_log (DVB_LOG_STREAM, G_LOG_LEVEL_WARNING,
+              "8-bit/pixel_code_string::run_length_3-127 value was %u, but the spec requires it must be >=3",
+              run_length);
+        }
+#endif
+      }
+    }
+
+    /* If run_length is zero, continue. Only case happening is when
+     * stop_parsing is TRUE too, so next cycle shouldn't run */
+    if (run_length == 0)
+      continue;
+
+    /* Trim the run_length to not go beyond the line end and consume
+     * it from remaining length of dest line */
+    run_length = MIN (run_length, dbuf_len);
+    dbuf_len -= run_length;
+
+    /* Make clut_index refer to the index into the desired bit depths
+     * CLUT definition table */
+    if (map_table)
+      clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
+
+    /* Now we can simply memset run_length count of destination bytes
+     * to clut_index, but only if not non_modifying */
+    dvb_log (DVB_LOG_RUNLEN, G_LOG_LEVEL_DEBUG,
+        "Setting %u pixels to color 0x%x in destination buffer; dbuf_len left is %d pixels",
+        run_length, clut_index, dbuf_len);
+    if (!(non_mod == 1 && bits == 1))
+      memset (destbuf, clut_index, run_length);
+
+    destbuf += run_length;
+    pixels_read += run_length;
+  }
+
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "Returning from 8bit_string parser with %u pixels read", pixels_read);
+  // FIXME: Shouldn't need this variable if tracking things in the loop better
+  return pixels_read;
+}
+
+static void
+_dvb_sub_parse_pixel_data_block (DvbSub * dvb_sub,
+    DVBSubObjectDisplay * display, const guint8 * buf, gint buf_size,
+    DvbSubPixelDataSubBlockFieldType top_bottom, guint8 non_mod)
+{
+  DVBSubRegion *region = get_region (dvb_sub, display->region_id);
+  const guint8 *buf_end = buf + buf_size;
+  guint8 *pbuf;
+  int x_pos, y_pos;
+  int i;
+  gboolean dest_buf_filled = FALSE;
+
+  guint8 map2to4[] = { 0x0, 0x7, 0x8, 0xf };
+  guint8 map2to8[] = { 0x00, 0x77, 0x88, 0xff };
+  guint8 map4to8[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
+  };
+  guint8 *map_table;
+
+  dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+      "(parse_block): DVB pixel block size %d, %s field:",
+      buf_size, top_bottom ? "bottom" : "top");
+
+#ifdef DEBUG_PACKET_CONTENTS
+  gst_util_dump_mem (buf, buf_size);
+#endif
+
+  if (region == NULL) {
+    g_print ("Region is NULL, returning\n");
+    return;
+  }
+
+  pbuf = region->pbuf;
+
+  x_pos = display->x_pos;
+  y_pos = display->y_pos;
+
+  if ((y_pos & 1) != top_bottom)
+    y_pos++;
+
+  while (buf < buf_end) {
+    dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+        "Iteration start, %u bytes missing from end; buf = %p, buf_end = %p;  "
+        "Region is number %u, with a dimension of %dx%d; We are at position %dx%d",
+        buf_end - buf, buf, buf_end,
+        region->id, region->width, region->height, x_pos, y_pos);
+    // FFMPEG-FIXME: ffmpeg doesn't check for equality and so can overflow destination buffer later on with bad input data
+    // FFMPEG-FIXME: However that makes it warn on end_of_object_line and map tables as well, so we add the dest_buf_filled tracking
+    // FIXME: Removed x_pos checking here, because we don't want to turn dest_buf_filled to TRUE permanently in that case
+    // FIXME: We assume that region->width - x_pos as dbuf_len to read_nbit_string will take care of that case nicely;
+    // FIXME: That is, that read_nbit_string never scribbles anything if dbuf_len passed to it is zero due to this.
+    if (y_pos >= region->height) {
+      dest_buf_filled = TRUE;
+    }
+
+    switch (*buf++) {
+      case 0x10:
+        if (dest_buf_filled) {
+          g_warning ("Invalid object location for data_type 0x%x!\n", *(buf - 1));      /* FIXME: Be more verbose */
+          g_print ("Remaining data after invalid object location:\n");
+          gst_util_dump_mem (buf, buf_end - buf);
+          return;
+        }
+
+        if (region->depth == 8)
+          map_table = map2to8;
+        else if (region->depth == 4)
+          map_table = map2to4;
+        else
+          map_table = NULL;
+
+        // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
+        // FFMPEG-FIXME: therefore potentially walk over the memory area we own
+        x_pos +=
+            _dvb_sub_read_2bit_string (pbuf + (y_pos * region->width) + x_pos,
+            region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
+        break;
+      case 0x11:
+        if (dest_buf_filled) {
+          g_warning ("Invalid object location for data_type 0x%x!\n", *(buf - 1));      /* FIXME: Be more verbose */
+          g_print ("Remaining data after invalid object location:\n");
+          gst_util_dump_mem (buf, buf_end - buf);
+          return;               // FIXME: Perhaps tell read_nbit_string that dbuf_len is zero and let it walk the bytes regardless? (Same FIXME for 2bit and 8bit)
+        }
+
+        if (region->depth < 4) {
+          g_warning ("4-bit pixel string in %d-bit region!\n", region->depth);
+          return;
+        }
+
+        if (region->depth == 8)
+          map_table = map4to8;
+        else
+          map_table = NULL;
+
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "READ_nBIT_STRING (4): String data into position %dx%d; buf before is %p\n",
+            x_pos, y_pos, buf);
+        // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
+        // FFMPEG-FIXME: therefore potentially walk over the memory area we own
+        x_pos +=
+            _dvb_sub_read_4bit_string (pbuf + (y_pos * region->width) + x_pos,
+            region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "READ_nBIT_STRING (4) finished: buf pointer now %p", buf);
+        break;
+      case 0x12:
+        if (dest_buf_filled) {
+          g_warning ("Invalid object location for data_type 0x%x!\n", *(buf - 1));      /* FIXME: Be more verbose */
+          g_print ("Remaining data after invalid object location:\n");
+          gst_util_dump_mem (buf, buf_end - buf);
+          return;
+        }
+
+        if (region->depth < 8) {
+          g_warning ("8-bit pixel string in %d-bit region!\n", region->depth);
+          return;
+        }
+        // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
+        // FFMPEG-FIXME: therefore potentially walk over the memory area we own
+        x_pos +=
+            _dvb_sub_read_8bit_string (pbuf + (y_pos * region->width) + x_pos,
+            region->width - x_pos, &buf, buf_end - buf, non_mod, NULL);
+        break;
+
+      case 0x20:
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "(parse_block): handling map2to4 table data");
+        /* FIXME: I don't see any guards about buffer size here - buf++ happens with the switch, but
+         * FIXME: buffer is walked without length checks? Same deal in other map table cases */
+        map2to4[0] = (*buf) >> 4;
+        map2to4[1] = (*buf++) & 0xf;
+        map2to4[2] = (*buf) >> 4;
+        map2to4[3] = (*buf++) & 0xf;
+        break;
+      case 0x21:
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "(parse_block): handling map2to8 table data");
+        for (i = 0; i < 4; i++)
+          map2to8[i] = *buf++;
+        break;
+      case 0x22:
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "(parse_block): handling map4to8 table data");
+        for (i = 0; i < 16; i++)
+          map4to8[i] = *buf++;
+        break;
+
+      case 0xf0:
+        dvb_log (DVB_LOG_PIXEL, G_LOG_LEVEL_DEBUG,
+            "(parse_block): end of object line code encountered");
+        x_pos = display->x_pos;
+        y_pos += 2;
+        break;
+      default:
+        /* FIXME: Do we consume word align stuffing byte that could follow top/bottom data? */
+        g_warning ("Unknown/unsupported pixel block 0x%x", *(buf - 1));
+    }
+  }
+}
+
+static void
+_dvb_sub_parse_object_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
+    gint buf_size)
+{
+  const guint8 *buf_end = buf + buf_size;
+  guint object_id;
+  DVBSubObject *object;
+
+  guint8 coding_method, non_modifying_color;
+
+  object_id = GST_READ_UINT16_BE (buf);
+  buf += 2;
+
+  object = get_object (dvb_sub, object_id);
+
+  dvb_log (DVB_LOG_OBJECT, G_LOG_LEVEL_DEBUG,
+      "parse_object_segment: A new object segment has occurred for object_id = %u",
+      object_id);
+
+  if (!object) {
+    g_warning
+        ("Nothing known about object with ID %u yet inside parse_object_segment, bailing out",
+        object_id);
+    return;
+  }
+
+  coding_method = ((*buf) >> 2) & 3;
+  non_modifying_color = ((*buf++) >> 1) & 1;
+
+  if (coding_method == 0) {
+    const guint8 *block;
+    DVBSubObjectDisplay *display;
+    guint16 top_field_len, bottom_field_len;
+
+    top_field_len = GST_READ_UINT16_BE (buf);
+    buf += 2;
+    bottom_field_len = GST_READ_UINT16_BE (buf);
+    buf += 2;
+
+    if (buf + top_field_len + bottom_field_len > buf_end) {
+      g_warning ("%s: Field data size too large\n", __PRETTY_FUNCTION__);
+      return;
+    }
+
+    /* FIXME: Potential optimization opportunity here - parse the object pixmap only once, and copy it to all the
+     * FIXME: regions that need it. One object being in multiple regions is a rare occurrence in real life, however */
+    for (display = object->display_list; display;
+        display = display->object_list_next) {
+      block = buf;
+
+      dvb_log (DVB_LOG_OBJECT, G_LOG_LEVEL_DEBUG,
+          "Parsing top and bottom part of object id %d; top_field_len = %u, bottom_field_len = %u",
+          display->object_id, top_field_len, bottom_field_len);
+      _dvb_sub_parse_pixel_data_block (dvb_sub, display, block, top_field_len,
+          TOP_FIELD, non_modifying_color);
+
+      if (bottom_field_len > 0)
+        block = buf + top_field_len;
+      else
+        bottom_field_len = top_field_len;
+
+      _dvb_sub_parse_pixel_data_block (dvb_sub, display, block,
+          bottom_field_len, BOTTOM_FIELD, non_modifying_color);
+    }
+
+  } else if (coding_method == 1) {
+    g_warning ("'a string of characters' coding method not supported (yet?)!");
+  } else {
+    g_warning ("%s: Unknown object coding 0x%x\n", __PRETTY_FUNCTION__,
+        coding_method);
+  }
+}
+
+static gint
+_dvb_sub_parse_end_of_display_set (DvbSub * dvb_sub, guint16 page_id,
+    guint8 * buf, gint buf_size, guint64 pts)
+{
+  DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
+
+  DVBSubtitles *sub = g_slice_new0 (DVBSubtitles);
+
+  DVBSubRegion *region;
+  DVBSubRegionDisplay *display;
+  DVBSubtitleRect *rect;
+  DVBSubCLUT *clut;
+  guint32 *clut_table;
+  int i;
+
+  static unsigned counter = 0;  /* DEBUG use only */
+
+  dvb_log (DVB_LOG_DISPLAY, G_LOG_LEVEL_DEBUG,
+      "END OF DISPLAY SET: page_id = %u, length = %d\n", page_id, buf_size);
+
+  sub->rects = NULL;
+#if 0                           /* FIXME: PTS stuff not figured out yet */
+  sub->start_display_time = 0;
+  sub->end_display_time = priv->page_time_out * 1000;
+  sub->format = 0;              /* 0 = graphics */
+#endif
+
+  sub->num_rects = priv->display_list_size;
+
+  if (sub->num_rects > 0) {
+    // FIXME-MEMORY-LEAK: This structure is not freed up yet
+    sub->rects = g_malloc0 (sizeof (*sub->rects) * sub->num_rects);     /* GSlice? */
+    for (i = 0; i < sub->num_rects; i++)
+      sub->rects[i] = g_malloc0 (sizeof (*sub->rects[i]));      /* GSlice? */
+  }
+
+  i = 0;
+
+  for (display = priv->display_list; display; display = display->next) {
+    region = get_region (dvb_sub, display->region_id);
+    rect = sub->rects[i];
+
+    if (!region)
+      continue;
+
+    rect->x = display->x_pos;
+    rect->y = display->y_pos;
+    rect->w = region->width;
+    rect->h = region->height;
+#if 0                           /* FIXME: Don't think we need to save the number of colors in the palette when we are saving as RGBA? */
+    rect->nb_colors = 16;
+#endif
+#if 0                           /* FIXME: Needed to be specified once we support strings of characters based subtitles */
+    rect->type = SUBTITLE_BITMAP;
+#endif
+    rect->pict.rowstride = region->width;
+    rect->pict.palette_bits_count = region->depth;
+
+    clut = get_clut (dvb_sub, region->clut);
+
+    if (!clut)
+      clut = &default_clut;
+
+    switch (region->depth) {
+      case 2:
+        clut_table = clut->clut4;
+        break;
+      case 8:
+        clut_table = clut->clut256;
+        break;
+      case 4:
+      default:
+        clut_table = clut->clut16;
+        break;
+    }
+
+    /* FIXME: Tweak this to be saved in a format most suitable for Qt and GStreamer instead.
+     * Currently kept in AVPicture for quick save_display_set testing */
+    rect->pict.palette = g_malloc ((1 << region->depth) * sizeof (guint32));    /* FIXME: Can we use GSlice here? */
+    memcpy (rect->pict.palette, clut_table,
+        (1 << region->depth) * sizeof (guint32));
+#if 0
+    g_print ("rect->pict.data.palette content:\n");
+    gst_util_dump_mem (rect->pict.palette,
+        (1 << region->depth) * sizeof (guint32));
+#endif
+
+    rect->pict.data = g_malloc (region->buf_size);      /* FIXME: Can we use GSlice here? */
+    memcpy (rect->pict.data, region->pbuf, region->buf_size);
+
+    ++counter;
+    dvb_log (DVB_LOG_DISPLAY, G_LOG_LEVEL_DEBUG,
+        "An object rect created: number %u, iteration %u, pos: %d:%d, size: %dx%d",
+        counter, i, rect->x, rect->y, rect->w, rect->h);
+#if 0
+    g_print ("rect->pict.data content:\n");
+    gst_util_dump_mem (rect->pict.data, region->buf_size);
+#endif
+
+    ++i;
+  }
+
+  sub->num_rects = i;
+
+  if (priv->callbacks.new_data)
+    priv->callbacks.new_data (dvb_sub, pts, sub, priv->page_time_out,
+        priv->user_data);
+
+  /* Now free up all the temporary memory we allocated */
+  for (i = 0; i < sub->num_rects; ++i) {
+    rect = sub->rects[i];
+
+    g_free (rect->pict.palette);
+    g_free (rect->pict.data);
+    g_free (rect);
+  }
+  g_free (sub->rects);
+  g_slice_free (DVBSubtitles, sub);
+
+  return 1;                     /* FIXME: The caller of this function is probably supposed to do something with the return value */
+}
+
+/**
+ * dvb_sub_new:
+ *
+ * Creates a new #DvbSub.
+ *
+ * Return value: a newly created #DvbSub
+ */
+DvbSub *
+dvb_sub_new (void)
+{
+  DvbSub *dvbsub = g_object_new (DVB_TYPE_SUB, NULL);
+
+  return dvbsub;
+}
+
+/**
+ * dvb_sub_feed:
+ * @dvb_sub: a #DvbSub
+ * @data: The data to feed to the parser
+ * @len: Length of the data
+ *
+ * Feeds the DvbSub parser with new PES packet data to parse.
+ * The data given must be a full PES packet, which must
+ * include a PTS field in the headers.
+ * Only one packet is handled in one call, so the available data
+ * should be fed continously until all is consumed.
+ *
+ * Return value: a negative value on errors, -4 if simply not enough data for the PES packet;
+ *               Amount of data consumed (length of handled PES packet on success)
+ */
+gint
+dvb_sub_feed (DvbSub * dvb_sub, guint8 * data, gint len)
+{
+  guint64 pts = 0;
+  unsigned int pos = 0;
+  guint16 PES_packet_len;
+  guint8 PES_packet_header_len;
+  gboolean is_subtitle_packet = TRUE;
+  gboolean pts_field_present = FALSE;
+  g_warning ("Feeding %d bytes of data to dvbsub!!!!!!!!!!!!\n", len);
+  if (len == 0)
+    return 0;
+
+  if (len <= 8) {
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_WARNING,
+        "Length %d too small for further processing", len);
+    return -1;
+  }
+
+  if (data[0] != 0x00 || data[1] != 0x00 || data[2] != 0x01) {
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_WARNING,
+        "Data fed to dvb_sub_feed is not a PES packet - does not start with a code_prefix of 0x000001");
+    return 1;                   // FIXME: Probably handle it? - we need to skip PES_packet_len from this elementary stream then and move on
+  }
+
+  if (data[3] != 0xBD) {
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_INFO,
+        "Data fed to dvb_sub_feed is not a PES packet of type private_stream_1, but rather '0x%X', so not a subtitle stream",
+        data[3]);
+    is_subtitle_packet = FALSE;
+  }
+
+  PES_packet_len = (data[4] << 8) | data[5];
+  dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+      "PES packet length is %u", PES_packet_len);
+  pos = 6;
+
+  /* FIXME: If the packet is cut, we could be feeding data more than we actually have here, which breaks everything. Probably need to buffer up and handle it,
+   * FIXME: Or push back in front to the file descriptor buffer (but we are using read, not libc buffered fread, so that idea might not be possible )*/
+  if ((len - 5) < PES_packet_len) {
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_WARNING,
+        "!!!!!!!!!!! claimed PES packet length was %d, but we only had %d bytes available, falling back and waiting more !!!!!!!!!",
+        PES_packet_len, len - 5);
+    return -4;
+  }
+  /* FIXME: Validate sizes inbetween here */
+
+  /* If this is a non-subtitle packet, still skip the data, pretending we consumed it (FIXME: Signal up) */
+  if (!is_subtitle_packet) {
+    return pos + PES_packet_len;
+  }
+
+  pos++;
+
+  if (data[pos++] & 0x80) {     /* PTS fields present (possibly also DTS). Technically this must be present per the spec */
+    pts_field_present = TRUE;
+  }
+
+  /* pos should be 8 now anyway */
+  pos = 8;
+
+  PES_packet_header_len = data[pos++];
+
+  if (pts_field_present) {
+    /* '001x', PTS[32..30], marker, PTS[29..15], marker, PTS[14..0], marker */
+    pts = ((guint64) (data[pos] & 0x0E)) << 29; /* PTS[32..30], ignore marker at rightmost bit */
+    pts |= ((guint64) (data[pos + 1])) << 22;   /* PTS[29..22], full byte */
+    pts |= ((guint64) (data[pos + 2] & 0xFE)) << 14;    /* PTS[21..15], ignore marker at rightmost bit */
+    pts |= ((guint64) (data[pos + 3])) << 7;    /* PTS[14.. 7], full byte */
+    pts |= ((guint64) (data[pos + 4] & 0xFE)) >> 1;     /* PTS[ 6.. 0], ignore marker at rightmost bit */
+  }
+
+  pos += PES_packet_header_len; /* FIXME: Currently including all header values with all but PTS ignored */
+
+  dvb_sub_feed_with_pts (dvb_sub, pts, data + pos, PES_packet_len - PES_packet_header_len - 3); /* 2 bytes between PES_packet_len and PES_packet_header_len fields, minus header_len itself */
+  pos += PES_packet_len - PES_packet_header_len - 3;
+  dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+      "Finished PES packet - consumed %u bytes of %d", pos, len);
+
+  return pos;
+}
+
+#define DVB_SUB_SEGMENT_PAGE_COMPOSITION 0x10
+#define DVB_SUB_SEGMENT_REGION_COMPOSITION 0x11
+#define DVB_SUB_SEGMENT_CLUT_DEFINITION 0x12
+#define DVB_SUB_SEGMENT_OBJECT_DATA 0x13
+#define DVB_SUB_SEGMENT_END_OF_DISPLAY_SET 0x80
+#define DVB_SUB_SEGMENT_STUFFING 0xFF
+
+#define DVB_SUB_SYNC_BYTE 0x0f
+/**
+ * dvb_sub_feed_with_pts:
+ * @dvb_sub: a #DvbSub
+ * @pts: The PTS of the data
+ * @data: The data to feed to the parser
+ * @len: Length of the data
+ *
+ * Feeds the DvbSub parser with new binary data to parse,
+ * with an associated PTS value. E.g, data left after PES
+ * packet header has been already parsed, which contains
+ * the PTS information).
+ *
+ * Return value: -1 if data was unhandled (e.g, not a subtitle packet),
+ *                              -2 if data parsing was unsuccesful (e.g, length was invalid),
+ *                               0 or positive if data was handled. If positive, then amount of data consumed on success. FIXME: List the positive return values.
+ */
+gint
+dvb_sub_feed_with_pts (DvbSub * dvb_sub, guint64 pts, guint8 * data, gint len)
+{
+  unsigned int pos = 0;
+  guint8 segment_type;
+  guint16 segment_len;
+  guint16 page_id;
+
+  dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+      "Inside dvb_sub_feed_with_pts with pts=%" G_GUINT64_FORMAT
+      " and length %d", pts, len);
+
+  g_return_val_if_fail (data != NULL, -1);
+
+  if (len <= 3) {               /* len(0x20 0x00 end_of_PES_data_field_marker) */
+    g_warning ("Data length too short");
+    return -1;
+  }
+
+  if (data[pos++] != 0x20) {
+    g_warning
+        ("Tried to handle a PES packet private data that isn't a subtitle packet (does not start with 0x20)");
+    return -1;
+  }
+
+  if (data[pos++] != 0x00) {
+    g_warning
+        ("'Subtitle stream in this PES packet' was not 0x00, so this is in theory not a DVB subtitle stream (but some other subtitle standard?); bailing out");
+    return -1;
+  }
+
+  while (data[pos++] == DVB_SUB_SYNC_BYTE) {
+    if ((len - pos) < (2 * 2 + 1)) {
+      g_warning
+          ("Data after SYNC BYTE too short, less than needed to even get to segment_length");
+      return -2;
+    }
+    segment_type = data[pos++];
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+        "=== Segment type is 0x%x", segment_type);
+    page_id = (data[pos] << 8) | data[pos + 1];
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG, "page_id is 0x%x", page_id);
+    pos += 2;
+    segment_len = (data[pos] << 8) | data[pos + 1];
+    dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+        "segment_length is %d (0x%x 0x%x)", segment_len, data[pos],
+        data[pos + 1]);
+    pos += 2;
+    if ((len - pos) < segment_len) {
+      g_warning
+          ("segment_length was told to be %u, but we only have %d bytes left",
+          segment_len, len - pos);
+      return -2;
+    }
+    // TODO: Parse the segment per type
+    switch (segment_type) {
+      case DVB_SUB_SEGMENT_PAGE_COMPOSITION:
+        dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+            "Page composition segment at buffer pos %u\n", pos);
+        _dvb_sub_parse_page_segment (dvb_sub, page_id, data + pos, segment_len);        /* FIXME: Not sure about args */
+        break;
+      case DVB_SUB_SEGMENT_REGION_COMPOSITION:
+        dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+            "Region composition segment at buffer pos %u\n", pos);
+        _dvb_sub_parse_region_segment (dvb_sub, page_id, data + pos, segment_len);      /* FIXME: Not sure about args */
+        break;
+      case DVB_SUB_SEGMENT_CLUT_DEFINITION:
+        dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+            "CLUT definition segment at buffer pos %u\n", pos);
+        _dvb_sub_parse_clut_segment (dvb_sub, page_id, data + pos, segment_len);        /* FIXME: Not sure about args */
+        break;
+      case DVB_SUB_SEGMENT_OBJECT_DATA:
+        dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+            "Object data segment at buffer pos %u\n", pos);
+        _dvb_sub_parse_object_segment (dvb_sub, page_id, data + pos, segment_len);      /* FIXME: Not sure about args */
+        break;
+      case DVB_SUB_SEGMENT_END_OF_DISPLAY_SET:
+        dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG,
+            "End of display set at buffer pos %u\n", pos);
+        _dvb_sub_parse_end_of_display_set (dvb_sub, page_id, data + pos, segment_len, pts);     /* FIXME: Not sure about args */
+        break;
+      default:
+        g_warning ("Unhandled segment type 0x%x", segment_type);
+        break;
+    }
+
+    pos += segment_len;
+
+    if (pos == len) {
+      g_warning ("Data ended without a PES data end marker");
+      return 1;
+    }
+  }
+
+  g_warning ("Processed %d bytes out of %d\n", pos, len);
+  return pos;
+}
+
+/**
+ * dvb_sub_set_callbacks:
+ * @dvb_sub: a #DvbSub
+ * @callbacks: the callbacks to install
+ * @user_data: a user_data argument for the callback
+ *
+ * Set callback which will be executed when new subpictures are available.
+ */
+void
+dvb_sub_set_callbacks (DvbSub * dvb_sub, DvbSubCallbacks * callbacks,
+    gpointer user_data)
+{
+  DvbSubPrivate *priv;
+
+  g_return_if_fail (dvb_sub != NULL);
+  g_return_if_fail (DVB_IS_SUB (dvb_sub));
+  g_return_if_fail (callbacks != NULL);
+
+  priv = (DvbSubPrivate *) dvb_sub->private_data;
+
+  priv->callbacks = *callbacks;
+  priv->user_data = user_data;
+}
+
+void
+dvb_sub_set_global_log_cb (void (*log_cb) (GLogLevelFlags log_level,
+        const gchar * format, va_list args, gpointer user_data),
+    gpointer user_data)
+{
+  if (log_cb) {
+    g_log_callback = log_cb;
+    g_log_callback_user_data = user_data;
+  }
+}
diff --git a/gst/dvbsuboverlay/dvb-sub.h b/gst/dvbsuboverlay/dvb-sub.h
new file mode 100644 (file)
index 0000000..fee8816
--- /dev/null
@@ -0,0 +1,136 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * libdvbsub - DVB subtitle decoding
+ * Copyright (C) Mart Raudsepp 2009 <mart.raudsepp@artecdesign.ee>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _DVB_SUB_H_
+#define _DVB_SUB_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DVB_TYPE_SUB             (dvb_sub_get_type ())
+#define DVB_SUB(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), DVB_TYPE_SUB, DvbSub))
+#define DVB_SUB_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), DVB_TYPE_SUB, DvbSubClass))
+#define DVB_IS_SUB(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DVB_TYPE_SUB))
+#define DVB_IS_SUB_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), DVB_TYPE_SUB))
+#define DVB_SUB_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), DVB_TYPE_SUB, DvbSubClass))
+
+typedef struct _DvbSubClass DvbSubClass;
+typedef struct _DvbSub DvbSub;
+
+struct _DvbSubClass
+{
+       GObjectClass parent_class;
+};
+
+/**
+ * DvbSub:
+ *
+ * The #DvbSub struct contains only private fields and should not be
+ * directly accessed.
+ */
+struct _DvbSub
+{
+       GObject parent_instance;
+
+       /*< private >*/
+       gpointer private_data;
+};
+
+/**
+ * DVBSubtitlePicture:
+ * @data: the data in the form of palette indices, each byte represents one pixel
+ *   as an index into the @palette.
+ * @palette: the palette used for this subtitle rectangle, up to 256 items depending
+ *   on the depth of the subpicture; each palette item is in ARGB form, 8-bits per channel.
+ * @palette_bits_count: the amount of bits used in indeces into @palette in @data.
+ * @rowstride: the number of bytes between the start of a row and the start of the next row.
+ *
+ * A structure representing the contents of a subtitle rectangle.
+ *
+ * FIXME: Expose the depth of the palette, and perhaps also the height in this struct.
+ */
+typedef struct DVBSubtitlePicture {
+       guint8 *data;
+       guint32 *palette;
+       guint8 palette_bits_count;
+       int rowstride;
+} DVBSubtitlePicture;
+
+/**
+ * DVBSubtitleRect:
+ * @x: x coordinate of top left corner
+ * @y: y coordinate of top left corner
+ * @w: the width of this subpicture rectangle
+ * @h: the height of this subpicture rectangle
+ * @pict: the content of this subpicture rectangle
+ *
+ * A structure representing one subtitle objects position, dimension and content.
+ */
+typedef struct DVBSubtitleRect {
+       int x;
+       int y;
+       int w;
+       int h;
+
+       DVBSubtitlePicture pict;
+} DVBSubtitleRect;
+
+/**
+ * DVBSubtitles:
+ * @num_rects: the number of #DVBSubtitleRect in @rects
+ * @rects: dynamic array of #DVBSubtitleRect
+ *
+ * A structure representing a set of subtitle objects.
+ */
+typedef struct DVBSubtitles {
+       unsigned int num_rects;
+       DVBSubtitleRect **rects;
+} DVBSubtitles;
+
+/**
+ * DvbSubCallbacks:
+ * @new_data: called when new subpicture data is available for display. @dvb_sub
+ *    is the #DvbSub instance this callback originates from; @subs is the set of
+ *    subtitle objects that should be display for no more than @page_time_out
+ *    seconds at @pts; @user_data is the same user_data as was passed through
+ *    dvb_sub_set_callbacks();
+ *
+ * A set of callbacks that can be installed on the #DvbSub with
+ * dvb_sub_set_callbacks().
+ */
+typedef struct {
+       void     (*new_data) (DvbSub *dvb_sub, guint64 pts, DVBSubtitles * subs, guint8 page_time_out, gpointer user_data);
+       /*< private >*/
+       gpointer _dvb_sub_reserved[3];
+} DvbSubCallbacks;
+
+GType    dvb_sub_get_type      (void) G_GNUC_CONST;
+DvbSub  *dvb_sub_new           (void);
+gint     dvb_sub_feed          (DvbSub *dvb_sub, guint8 *data, gint len);
+gint     dvb_sub_feed_with_pts (DvbSub *dvb_sub, guint64 pts, guint8 *data, gint len);
+void     dvb_sub_set_callbacks (DvbSub *dvb_sub, DvbSubCallbacks *callbacks, gpointer user_data);
+
+void dvb_sub_set_global_log_cb (void (*log_cb) (GLogLevelFlags log_level, const gchar *format, va_list args, gpointer user_data),
+                                gpointer user_data);
+
+G_END_DECLS
+
+#endif /* _DVB_SUB_H_ */
diff --git a/gst/dvbsuboverlay/ffmpeg-colorspace.h b/gst/dvbsuboverlay/ffmpeg-colorspace.h
new file mode 100644 (file)
index 0000000..18bfe55
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * This file is copied from ffmpeg's libavcodec/colorspace.h
+ * for the YUV_TO_RGB{1,2}_CCIR macros.
+ * Original copyright header and contents follows:
+ */
+/*
+ * Colorspace conversion defines
+ * Copyright (c) 2001, 2002, 2003 Fabrice Bellard
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file libavcodec/colorspace.h
+ * Various defines for YUV<->RGB conversion
+ */
+
+#ifndef AVCODEC_COLORSPACE_H
+#define AVCODEC_COLORSPACE_H
+
+#define SCALEBITS 10
+#define ONE_HALF  (1 << (SCALEBITS - 1))
+#define FIX(x)    ((int) ((x) * (1<<SCALEBITS) + 0.5))
+
+#define YUV_TO_RGB1_CCIR(cb1, cr1)\
+{\
+    cb = (cb1) - 128;\
+    cr = (cr1) - 128;\
+    r_add = FIX(1.40200*255.0/224.0) * cr + ONE_HALF;\
+    g_add = - FIX(0.34414*255.0/224.0) * cb - FIX(0.71414*255.0/224.0) * cr + \
+            ONE_HALF;\
+    b_add = FIX(1.77200*255.0/224.0) * cb + ONE_HALF;\
+}
+
+#define YUV_TO_RGB2_CCIR(r, g, b, y1)\
+{\
+    y = ((y1) - 16) * FIX(255.0/219.0);\
+    r = cm[(y + r_add) >> SCALEBITS];\
+    g = cm[(y + g_add) >> SCALEBITS];\
+    b = cm[(y + b_add) >> SCALEBITS];\
+}
+
+#define YUV_TO_RGB1(cb1, cr1)\
+{\
+    cb = (cb1) - 128;\
+    cr = (cr1) - 128;\
+    r_add = FIX(1.40200) * cr + ONE_HALF;\
+    g_add = - FIX(0.34414) * cb - FIX(0.71414) * cr + ONE_HALF;\
+    b_add = FIX(1.77200) * cb + ONE_HALF;\
+}
+
+#define YUV_TO_RGB2(r, g, b, y1)\
+{\
+    y = (y1) << SCALEBITS;\
+    r = cm[(y + r_add) >> SCALEBITS];\
+    g = cm[(y + g_add) >> SCALEBITS];\
+    b = cm[(y + b_add) >> SCALEBITS];\
+}
+
+#define Y_CCIR_TO_JPEG(y)\
+ cm[((y) * FIX(255.0/219.0) + (ONE_HALF - 16 * FIX(255.0/219.0))) >> SCALEBITS]
+
+#define Y_JPEG_TO_CCIR(y)\
+ (((y) * FIX(219.0/255.0) + (ONE_HALF + (16 << SCALEBITS))) >> SCALEBITS)
+
+#define C_CCIR_TO_JPEG(y)\
+ cm[(((y) - 128) * FIX(127.0/112.0) + (ONE_HALF + (128 << SCALEBITS))) >> SCALEBITS]
+
+/* NOTE: the clamp is really necessary! */
+static inline int C_JPEG_TO_CCIR(int y) {
+    y = (((y - 128) * FIX(112.0/127.0) + (ONE_HALF + (128 << SCALEBITS))) >> SCALEBITS);
+    if (y < 16)
+        y = 16;
+    return y;
+}
+
+
+#define RGB_TO_Y(r, g, b) \
+((FIX(0.29900) * (r) + FIX(0.58700) * (g) + \
+  FIX(0.11400) * (b) + ONE_HALF) >> SCALEBITS)
+
+#define RGB_TO_U(r1, g1, b1, shift)\
+(((- FIX(0.16874) * r1 - FIX(0.33126) * g1 +         \
+     FIX(0.50000) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
+#define RGB_TO_V(r1, g1, b1, shift)\
+(((FIX(0.50000) * r1 - FIX(0.41869) * g1 -           \
+   FIX(0.08131) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
+#define RGB_TO_Y_CCIR(r, g, b) \
+((FIX(0.29900*219.0/255.0) * (r) + FIX(0.58700*219.0/255.0) * (g) + \
+  FIX(0.11400*219.0/255.0) * (b) + (ONE_HALF + (16 << SCALEBITS))) >> SCALEBITS)
+
+#define RGB_TO_U_CCIR(r1, g1, b1, shift)\
+(((- FIX(0.16874*224.0/255.0) * r1 - FIX(0.33126*224.0/255.0) * g1 +         \
+     FIX(0.50000*224.0/255.0) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
+#define RGB_TO_V_CCIR(r1, g1, b1, shift)\
+(((FIX(0.50000*224.0/255.0) * r1 - FIX(0.41869*224.0/255.0) * g1 -           \
+   FIX(0.08131*224.0/255.0) * b1 + (ONE_HALF << shift) - 1) >> (SCALEBITS + shift)) + 128)
+
+#endif /* AVCODEC_COLORSPACE_H */
diff --git a/gst/dvbsuboverlay/gstdvbsuboverlay.c b/gst/dvbsuboverlay/gstdvbsuboverlay.c
new file mode 100644 (file)
index 0000000..a2157e9
--- /dev/null
@@ -0,0 +1,1275 @@
+/* GStreamer DVB subtitles overlay
+ * Copyright (c) 2010 Mart Raudsepp <mart.raudsepp@collabora.co.uk>
+ *
+ * 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.
+ */
+
+/**
+ * SECTION:element-dvbsuboverlay
+ *
+ * Renders DVB subtitles on top of a video stream.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[ FIXME
+ * gst-launch -v filesrc location=/path/to/ts ! mpegtsdemux name=d ! queue ! mp3parse ! mad ! audioconvert ! autoaudiosink \
+ *     d. ! queue ! mpeg2dec ! ffmpegcolorspace ! r. \
+ *     d. ! queue ! "private/x-dvbsub" ! dvbsuboverlay name=r ! ffmpegcolorspace ! autovideosink
+ * ]| This pipeline demuxes a MPEG-TS file with MPEG2 video, MP3 audio and embedded DVB subtitles and renders the subtitles on top of the video.
+ * </refsect2>
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include "gstdvbsuboverlay.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_dvbsub_overlay_debug);
+GST_DEBUG_CATEGORY_STATIC (gst_dvbsub_overlay_lib_debug);
+#define GST_CAT_DEFAULT gst_dvbsub_overlay_debug
+
+/* Filter signals and props */
+enum
+{
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_ENABLE
+};
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (
+#ifdef DVBSUB_OVERLAY_RGB_SUPPORT
+        GST_VIDEO_CAPS_RGB ";" GST_VIDEO_CAPS_BGR ";"
+        GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
+        GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx ";"
+#endif
+        GST_VIDEO_CAPS_YUV ("I420"))
+    );
+
+static GstStaticPadTemplate video_sink_factory =
+    GST_STATIC_PAD_TEMPLATE ("video_sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS (
+#ifdef DVBSUB_OVERLAY_RGB_SUPPORT
+        GST_VIDEO_CAPS_RGB ";" GST_VIDEO_CAPS_BGR ";"
+        GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR ";"
+        GST_VIDEO_CAPS_RGBx ";" GST_VIDEO_CAPS_BGRx ";"
+#endif
+        GST_VIDEO_CAPS_YUV ("I420"))
+    );
+
+static GstStaticPadTemplate text_sink_factory =
+GST_STATIC_PAD_TEMPLATE ("text_sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("private/x-dvbsub")
+    );
+
+static void gst_dvbsub_overlay_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_dvbsub_overlay_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static void gst_dvbsub_overlay_finalize (GObject * object);
+
+#define GST_DVBSUB_OVERLAY_GET_COND(ov)  (((GstDVBSubOverlay *)ov)->subtitle_cond)
+#define GST_DVBSUB_OVERLAY_WAIT(ov)      (g_cond_wait (GST_DVBSUB_OVERLAY_GET_COND (ov), GST_OBJECT_GET_LOCK (ov)))
+#define GST_DVBSUB_OVERLAY_BROADCAST(ov) (g_cond_broadcast (GST_DVBSUB_OVERLAY_GET_COND (ov)))
+
+static GstStateChangeReturn gst_dvbsub_overlay_change_state (GstElement *
+    element, GstStateChange transition);
+
+GST_BOILERPLATE (GstDVBSubOverlay, gst_dvbsub_overlay, GstElement,
+    GST_TYPE_ELEMENT);
+
+static GstCaps *gst_dvbsub_overlay_getcaps (GstPad * pad);
+
+static gboolean gst_dvbsub_overlay_setcaps_video (GstPad * pad, GstCaps * caps);
+static gboolean gst_dvbsub_overlay_setcaps_text (GstPad * pad, GstCaps * caps);
+
+static GstFlowReturn gst_dvbsub_overlay_chain_video (GstPad * pad,
+    GstBuffer * buf);
+static GstFlowReturn gst_dvbsub_overlay_chain_text (GstPad * pad,
+    GstBuffer * buf);
+
+static gboolean gst_dvbsub_overlay_event_video (GstPad * pad, GstEvent * event);
+static gboolean gst_dvbsub_overlay_event_text (GstPad * pad, GstEvent * event);
+static gboolean gst_dvbsub_overlay_event_src (GstPad * pad, GstEvent * event);
+
+static void new_dvb_subtitles_cb (DvbSub * dvb_sub, guint64 pts,
+    DVBSubtitles * subs, guint8 page_time_out, gpointer user_data);
+
+static GstFlowReturn gst_dvbsub_overlay_bufferalloc_video (GstPad * pad,
+    guint64 offset, guint size, GstCaps * caps, GstBuffer ** buffer);
+
+static gboolean gst_dvbsub_overlay_query_src (GstPad * pad, GstQuery * query);
+
+static void
+gst_dvbsub_overlay_base_init (gpointer gclass)
+{
+  GstElementClass *element_class = (GstElementClass *) gclass;
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&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 (&text_sink_factory));
+
+  gst_element_class_set_details_simple (element_class, "DVB Subtitles Overlay", "Mixer/Video/Overlay/Subtitle", "Renders DVB subtitles", "Mart Raudsepp <mart.raudsepp@collabora.co.uk>");      // FIXME: Credit assrender and textoverlay?
+}
+
+/* initialize the plugin's class */
+static void
+gst_dvbsub_overlay_class_init (GstDVBSubOverlayClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+  gobject_class->set_property = gst_dvbsub_overlay_set_property;
+  gobject_class->get_property = gst_dvbsub_overlay_get_property;
+  gobject_class->finalize = gst_dvbsub_overlay_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_ENABLE, g_param_spec_boolean ("enable", "Enable",        /* FIXME: "enable" vs "silent"? */
+          "Enable rendering of subtitles", TRUE,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gstelement_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_change_state);
+}
+
+static void
+_dvbsub_log_cb (GLogLevelFlags level, const gchar * fmt, va_list args,
+    gpointer render)
+{
+  gchar *message = g_strdup_vprintf (fmt, args);
+
+  if (level & G_LOG_LEVEL_ERROR)
+    GST_CAT_ERROR_OBJECT (gst_dvbsub_overlay_lib_debug, render, "%s", message);
+  else if (level & G_LOG_LEVEL_WARNING)
+    GST_CAT_WARNING_OBJECT (gst_dvbsub_overlay_lib_debug, render, "%s",
+        message);
+  else if (level & G_LOG_LEVEL_INFO)
+    GST_CAT_INFO_OBJECT (gst_dvbsub_overlay_lib_debug, render, "%s", message);
+  else if (level & G_LOG_LEVEL_DEBUG)
+    GST_CAT_DEBUG_OBJECT (gst_dvbsub_overlay_lib_debug, render, "%s", message);
+  else
+    GST_CAT_LOG_OBJECT (gst_dvbsub_overlay_lib_debug, render,
+        "log level %d: %s", level, message);
+
+  g_free (message);
+}
+
+static void
+gst_dvbsub_overlay_init (GstDVBSubOverlay * render,
+    GstDVBSubOverlayClass * gclass)
+{
+  GST_DEBUG_OBJECT (render, "init");
+
+  render->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
+  render->video_sinkpad =
+      gst_pad_new_from_static_template (&video_sink_factory, "video_sink");
+  render->text_sinkpad =
+      gst_pad_new_from_static_template (&text_sink_factory, "text_sink");
+
+  gst_pad_set_setcaps_function (render->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_setcaps_video));
+  gst_pad_set_setcaps_function (render->text_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_setcaps_text));
+
+  gst_pad_set_getcaps_function (render->srcpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_getcaps));
+  gst_pad_set_getcaps_function (render->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_getcaps));
+
+  gst_pad_set_chain_function (render->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_chain_video));
+  gst_pad_set_chain_function (render->text_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_chain_text));
+
+  gst_pad_set_event_function (render->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_event_video));
+  gst_pad_set_event_function (render->text_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_event_text));
+  gst_pad_set_event_function (render->srcpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_event_src));
+
+  gst_pad_set_bufferalloc_function (render->video_sinkpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_bufferalloc_video));
+
+  gst_pad_set_query_function (render->srcpad,
+      GST_DEBUG_FUNCPTR (gst_dvbsub_overlay_query_src));
+
+  gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
+  gst_element_add_pad (GST_ELEMENT (render), render->video_sinkpad);
+  gst_element_add_pad (GST_ELEMENT (render), render->text_sinkpad);
+
+  render->width = 0;
+  render->height = 0;
+
+  render->subtitle_mutex = g_mutex_new ();
+  render->subtitle_cond = g_cond_new ();
+
+  render->renderer_init_ok = FALSE;
+  render->enable = TRUE;
+
+  gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
+  gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
+
+  render->dvbsub_mutex = g_mutex_new ();
+
+  dvb_sub_set_global_log_cb (_dvbsub_log_cb, render);
+
+
+  render->dvb_sub = dvb_sub_new ();
+  if (!render->dvb_sub) {
+    GST_WARNING_OBJECT (render, "cannot create dvbsub instance");
+    g_assert_not_reached ();
+  }
+
+  {
+    DvbSubCallbacks dvbsub_callbacks = { &new_dvb_subtitles_cb, };
+    dvb_sub_set_callbacks (render->dvb_sub, &dvbsub_callbacks, render);
+  }
+
+  GST_DEBUG_OBJECT (render, "init complete");
+}
+
+static void
+gst_dvbsub_overlay_finalize (GObject * object)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (object);
+
+  if (overlay->subtitle_mutex)
+    g_mutex_free (overlay->subtitle_mutex);
+
+  if (overlay->subtitle_cond)
+    g_cond_free (overlay->subtitle_cond);
+
+  if (overlay->dvb_sub) {
+    g_object_unref (overlay->dvb_sub);
+  }
+
+  if (overlay->dvbsub_mutex)
+    g_mutex_free (overlay->dvbsub_mutex);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_dvbsub_overlay_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (object);
+
+  switch (prop_id) {
+    case PROP_ENABLE:
+      overlay->enable = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_dvbsub_overlay_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (object);
+
+  switch (prop_id) {
+    case PROP_ENABLE:
+      g_value_set_boolean (value, overlay->enable);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstStateChangeReturn
+gst_dvbsub_overlay_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (element);
+  GstStateChangeReturn ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_READY_TO_PAUSED:
+      render->subtitle_flushing = FALSE;
+      gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
+      gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
+      break;
+    case GST_STATE_CHANGE_NULL_TO_READY:
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+    default:
+      break;
+
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      g_mutex_lock (render->subtitle_mutex);
+      render->subtitle_flushing = TRUE;
+      if (render->subtitle_pending)
+        gst_buffer_unref (render->subtitle_pending);
+      render->subtitle_pending = NULL;
+      g_cond_signal (render->subtitle_cond);
+      g_mutex_unlock (render->subtitle_mutex);
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      render->renderer_init_ok = FALSE;
+      break;
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+    case GST_STATE_CHANGE_READY_TO_NULL:
+    default:
+      break;
+  }
+
+
+  return ret;
+}
+
+static gboolean
+gst_dvbsub_overlay_query_src (GstPad * pad, GstQuery * query)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  gboolean ret;
+
+  ret = gst_pad_peer_query (render->video_sinkpad, query);
+
+  gst_object_unref (render);
+  return ret;
+}
+
+static gboolean
+gst_dvbsub_overlay_event_src (GstPad * pad, GstEvent * event)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  gboolean ret = FALSE;
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_SEEK:{
+      GstSeekFlags flags;
+
+      GST_DEBUG_OBJECT (render, "seek received, driving from here");
+
+      gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
+
+      /* Flush downstream, only for flushing seek */
+      if (flags & GST_SEEK_FLAG_FLUSH)
+        gst_pad_push_event (render->srcpad, gst_event_new_flush_start ());
+
+      /* Mark subtitle as flushing, unblocks chains */
+      g_mutex_lock (render->subtitle_mutex);
+      if (render->subtitle_pending)
+        gst_buffer_unref (render->subtitle_pending);
+      render->subtitle_pending = NULL;
+      render->subtitle_flushing = TRUE;
+      g_cond_signal (render->subtitle_cond);
+      g_mutex_unlock (render->subtitle_mutex);
+
+      /* Seek on each sink pad */
+      gst_event_ref (event);
+      ret = gst_pad_push_event (render->video_sinkpad, event);
+      if (ret) {
+        ret = gst_pad_push_event (render->text_sinkpad, event);
+      } else {
+        gst_event_unref (event);
+      }
+      break;
+    }
+    default:
+      gst_event_ref (event);
+      ret = gst_pad_push_event (render->video_sinkpad, event);
+      gst_pad_push_event (render->text_sinkpad, event);
+      break;
+  }
+
+  gst_object_unref (render);
+
+  return ret;
+}
+
+static GstCaps *
+gst_dvbsub_overlay_getcaps (GstPad * pad)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  GstPad *otherpad;
+  GstCaps *caps;
+
+  if (pad == render->srcpad)
+    otherpad = render->video_sinkpad;
+  else
+    otherpad = render->srcpad;
+
+  /* we can do what the peer can */
+  caps = gst_pad_peer_get_caps (otherpad);
+  if (caps) {
+    GstCaps *temp;
+    const GstCaps *templ;
+
+    /* filtered against our padtemplate */
+    templ = gst_pad_get_pad_template_caps (otherpad);
+    temp = gst_caps_intersect (caps, templ);
+    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_object_unref (render);
+
+  return caps;
+}
+
+#ifdef DVBSUB_OVERLAY_RGB_SUPPORT
+
+#define CREATE_RGB_BLIT_FUNCTION(name,bpp,R,G,B) \
+static void \
+blit_##name (GstDVBSubOverlay * overlay, DVBSubtitles * subs, GstBuffer * buffer) \
+{ \
+  guint counter; \
+  DVBSubtitleRect *sub_region; \
+  gint alpha, r, g, b, k; \
+  guint32 color; \
+  const guint8 *src; \
+  guint8 *dst; \
+  gint x, y, w, h; \
+  gint width = overlay->width; \
+  gint height = overlay->height; \
+  gint dst_stride = GST_ROUND_UP_4 (width * bpp); \
+  gint dst_skip; \
+  gint src_stride, src_skip; \
+  \
+  for (counter = 0; counter < subs->num_rects; counter++) { \
+    sub_region = subs->rects[counter]; \
+    if (sub_region->y > height || sub_region->x > width) \
+      continue; \
+    \
+    /* blend subtitles onto the video frame */ \
+    src = sub_region->pict.data; \
+    dst = buffer->data + sub_region->y * dst_stride + sub_region->x * bpp; \
+    \
+    w = MIN (sub_region->w, width - sub_region->x); \
+    h = MIN (sub_region->h, height - sub_region->y); \
+    src_stride = sub_region->pict.rowstride; \
+    src_skip = sub_region->pict.rowstride - w; \
+    dst_skip = dst_stride - w * bpp; \
+    \
+    for (y = 0; y < h; y++) { \
+      for (x = 0; x < w; x++) { \
+        color = sub_region->pict.palette[src[0]]; \
+        alpha = 255 - (color & 0xff); /* FIXME: We get ARGB, not RGBA as assumed here */ \
+        r = (color >> 24) & 0xff; \
+        g = (color >> 16) & 0xff; \
+        b = (color >> 8) & 0xff; \
+        k = color * alpha / 255; /* FIXME */ \
+        dst[R] = (k * r + (255 - k) * dst[R]) / 255; \
+        dst[G] = (k * g + (255 - k) * dst[G]) / 255; \
+        dst[B] = (k * b + (255 - k) * dst[B]) / 255; \
+       src++; \
+       dst += bpp; \
+      } \
+      src += src_skip; \
+      dst += dst_skip; \
+    } \
+  } \
+  GST_LOG_OBJECT (overlay, "amount of rendered DVBSubtitleRect: %u", counter); \
+}
+
+CREATE_RGB_BLIT_FUNCTION (rgb, 3, 0, 1, 2);
+CREATE_RGB_BLIT_FUNCTION (bgr, 3, 2, 1, 0);
+CREATE_RGB_BLIT_FUNCTION (xrgb, 4, 1, 2, 3);
+CREATE_RGB_BLIT_FUNCTION (xbgr, 4, 3, 2, 1);
+CREATE_RGB_BLIT_FUNCTION (rgbx, 4, 0, 1, 2);
+CREATE_RGB_BLIT_FUNCTION (bgrx, 4, 2, 1, 0);
+
+#undef CREATE_RGB_BLIT_FUNCTION
+
+#endif
+
+static inline gint
+rgb_to_y (gint r, gint g, gint b)
+{
+  gint ret;
+
+  ret = (gint) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16));
+  ret = CLAMP (ret, 0, 255);
+  return ret;
+}
+
+static inline gint
+rgb_to_u (gint r, gint g, gint b)
+{
+  gint ret;
+
+  ret =
+      (gint) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) +
+      128);
+  ret = CLAMP (ret, 0, 255);
+  return ret;
+}
+
+static inline gint
+rgb_to_v (gint r, gint g, gint b)
+{
+  gint ret;
+
+  ret =
+      (gint) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) +
+      128);
+  ret = CLAMP (ret, 0, 255);
+  return ret;
+}
+
+/* FIXME: DVB-SUB actually provides us AYUV from CLUT, but libdvbsub used to convert it to ARGB */
+static void
+blit_i420 (GstDVBSubOverlay * overlay, DVBSubtitles * subs, GstBuffer * buffer)
+{
+  guint counter;
+  DVBSubtitleRect *sub_region;
+  gint alpha, r, g, b, k, k2;
+  gint Y, U, V;
+  guint32 color, color2;
+  const guint8 *src;
+  guint8 *dst_y, *dst_u, *dst_v;
+  gint x, y, w, h;
+  gint w2, h2;
+  gint width = overlay->width;
+  gint height = overlay->height;
+  gint src_stride;
+  gint y_offset, y_height, y_width, y_stride;
+  gint u_offset, u_height, u_width, u_stride;
+  gint v_offset, v_height, v_width, v_stride;
+
+  y_offset =
+      gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 0, width,
+      height);
+  u_offset =
+      gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1, width,
+      height);
+  v_offset =
+      gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2, width,
+      height);
+
+  y_height =
+      gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 0, height);
+  u_height =
+      gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 1, height);
+  v_height =
+      gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 2, height);
+
+  y_width =
+      gst_video_format_get_component_width (GST_VIDEO_FORMAT_I420, 0, width);
+  u_width =
+      gst_video_format_get_component_width (GST_VIDEO_FORMAT_I420, 1, width);
+  v_width =
+      gst_video_format_get_component_width (GST_VIDEO_FORMAT_I420, 2, width);
+
+  y_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0, width);
+  u_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1, width);
+  v_stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2, width);
+
+  for (counter = 0; counter < subs->num_rects; counter++) {
+    sub_region = subs->rects[counter];
+    if (sub_region->y > height || sub_region->x > width)
+      continue;
+
+    /* blend subtitles onto the video frame */
+    w = MIN (sub_region->w, width - sub_region->x);
+    h = MIN (sub_region->h, height - sub_region->y);
+
+    w2 = (w + 1) / 2;
+    h2 = (h + 1) / 2;
+
+    src_stride = sub_region->pict.rowstride;
+
+    src = sub_region->pict.data;
+    dst_y = buffer->data + y_offset + sub_region->y * y_stride + sub_region->x;
+    dst_u =
+        buffer->data + u_offset + ((sub_region->y + 1) / 2) * u_stride +
+        (sub_region->x + 1) / 2;
+    dst_v =
+        buffer->data + v_offset + ((sub_region->y + 1) / 2) * v_stride +
+        (sub_region->x + 1) / 2;
+
+    for (y = 0; y < h - 1; y += 2) {
+      for (x = 0; x < w - 1; x += 2) {
+        /* FIXME: Completely wrong blending code */
+        color = sub_region->pict.palette[src[0]];
+        color2 = sub_region->pict.palette[src[1]];
+        alpha = 255 - (color & 0xff);
+        r = (color >> 24) & 0xff;
+        g = (color >> 16) & 0xff;
+        b = (color >> 8) & 0xff;
+
+        Y = rgb_to_y (r, g, b);
+        U = rgb_to_u (r, g, b);
+        V = rgb_to_v (r, g, b);
+
+        k = src[0] * alpha / 255;
+        k2 = k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        k = src[1] * alpha / 255;
+        k2 += k;
+        dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
+
+        src += src_stride;
+        dst_y += y_stride;
+
+        k = src[0] * alpha / 255;
+        k2 += k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        k = src[1] * alpha / 255;
+        k2 += k;
+        dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
+
+        k2 /= 4;
+        dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
+        dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
+        dst_u++;
+        dst_v++;
+
+        src += -src_stride + 2;
+        dst_y += -y_stride + 2;
+      }
+
+      if (x < w) {
+        /* FIXME: Completely wrong blending code */
+        color = sub_region->pict.palette[src[0]];
+        color2 = sub_region->pict.palette[src[1]];
+        alpha = 255 - (color & 0xff);
+        r = (color >> 24) & 0xff;
+        g = (color >> 16) & 0xff;
+        b = (color >> 8) & 0xff;
+
+        Y = rgb_to_y (r, g, b);
+        U = rgb_to_u (r, g, b);
+        V = rgb_to_v (r, g, b);
+
+        k = src[0] * alpha / 255;
+        k2 = k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        src += src_stride;
+        dst_y += y_stride;
+
+        k = src[0] * alpha / 255;
+        k2 += k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        k2 /= 2;
+        dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
+        dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
+        dst_u++;
+        dst_v++;
+
+        src += -src_stride + 1;
+        dst_y += -y_stride + 1;
+      }
+
+      src += src_stride + (src_stride - w);
+      dst_y += y_stride + (y_stride - w);
+      dst_u += u_stride - w2;
+      dst_v += v_stride - w2;
+    }
+
+    if (y < h) {
+      for (x = 0; x < w - 1; x += 2) {
+        /* FIXME: Completely wrong blending code */
+        color = sub_region->pict.palette[src[0]];
+        color2 = sub_region->pict.palette[src[1]];
+        alpha = 255 - (color & 0xff);
+        r = (color >> 24) & 0xff;
+        g = (color >> 16) & 0xff;
+        b = (color >> 8) & 0xff;
+
+        Y = rgb_to_y (r, g, b);
+        U = rgb_to_u (r, g, b);
+        V = rgb_to_v (r, g, b);
+
+        k = src[0] * alpha / 255;
+        k2 = k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        k = src[1] * alpha / 255;
+        k2 += k;
+        dst_y[1] = (k * Y + (255 - k) * dst_y[1]) / 255;
+
+        k2 /= 2;
+        dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
+        dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
+        dst_u++;
+        dst_v++;
+
+        src += 2;
+        dst_y += 2;
+      }
+
+      if (x < w) {
+        /* FIXME: Completely wrong blending code */
+        color = sub_region->pict.palette[src[0]];
+        color2 = sub_region->pict.palette[src[1]];
+        alpha = 255 - (color & 0xff);
+        r = (color >> 24) & 0xff;
+        g = (color >> 16) & 0xff;
+        b = (color >> 8) & 0xff;
+
+        Y = rgb_to_y (r, g, b);
+        U = rgb_to_u (r, g, b);
+        V = rgb_to_v (r, g, b);
+
+        k = src[0] * alpha / 255;
+        k2 = k;
+        dst_y[0] = (k * Y + (255 - k) * dst_y[0]) / 255;
+
+        dst_u[0] = (k2 * U + (255 - k2) * dst_u[0]) / 255;
+        dst_v[0] = (k2 * V + (255 - k2) * dst_v[0]) / 255;
+      }
+    }
+  }
+
+  GST_LOG_OBJECT (overlay, "amount of rendered DVBSubtitleRect: %u", counter);
+}
+
+static gboolean
+gst_dvbsub_overlay_setcaps_video (GstPad * pad, GstCaps * caps)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  gboolean ret = FALSE;
+  gint par_n = 1, par_d = 1;
+  gdouble dar;
+
+  render->width = 0;
+  render->height = 0;
+
+  if (!gst_video_format_parse_caps (caps, &render->format, &render->width,
+          &render->height) ||
+      !gst_video_parse_caps_framerate (caps, &render->fps_n, &render->fps_d)) {
+    GST_ERROR_OBJECT (render, "Can't parse caps: %" GST_PTR_FORMAT, caps);
+    ret = FALSE;
+    goto out;
+  }
+
+  gst_video_parse_caps_pixel_aspect_ratio (caps, &par_n, &par_d);
+
+  ret = gst_pad_set_caps (render->srcpad, caps);
+  if (!ret)
+    goto out;
+
+  switch (render->format) {
+#ifdef DVBSUB_OVERLAY_RGB_SUPPORT
+    case GST_VIDEO_FORMAT_RGB:
+      render->blit = blit_rgb;
+      break;
+    case GST_VIDEO_FORMAT_BGR:
+      render->blit = blit_bgr;
+      break;
+    case GST_VIDEO_FORMAT_xRGB:
+      render->blit = blit_xrgb;
+      break;
+    case GST_VIDEO_FORMAT_xBGR:
+      render->blit = blit_xbgr;
+      break;
+    case GST_VIDEO_FORMAT_RGBx:
+      render->blit = blit_rgbx;
+      break;
+    case GST_VIDEO_FORMAT_BGRx:
+      render->blit = blit_bgrx;
+      break;
+#endif
+    case GST_VIDEO_FORMAT_I420:
+      render->blit = blit_i420;
+      break;
+    default:
+      ret = FALSE;
+      goto out;
+  }
+
+  /* FIXME: We need to handle aspect ratio ourselves */
+#if 0
+  g_mutex_lock (render->ass_mutex);
+  ass_set_frame_size (render->ass_renderer, render->width, render->height);
+#endif
+  dar = (((gdouble) par_n) * ((gdouble) render->width))
+      / (((gdouble) par_d) * ((gdouble) render->height));
+#if 0
+  ass_set_aspect_ratio (render->ass_renderer,
+      dar, ((gdouble) render->width) / ((gdouble) render->height));
+
+  g_mutex_unlock (render->ass_mutex);
+#endif
+
+  render->renderer_init_ok = TRUE;
+
+  GST_DEBUG_OBJECT (render, "ass renderer setup complete");
+
+out:
+  gst_object_unref (render);
+
+  return ret;
+}
+
+static gboolean
+gst_dvbsub_overlay_setcaps_text (GstPad * pad, GstCaps * caps)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  GstStructure *structure;
+  const GValue *value;
+#if 0                           // FIXME
+  GstBuffer *priv;
+  gchar *codec_private;
+  guint codec_private_size;
+#endif
+  gboolean ret = FALSE;
+
+  structure = gst_caps_get_structure (caps, 0);
+
+  GST_DEBUG_OBJECT (render, "text pad linked with caps:  %" GST_PTR_FORMAT,
+      caps);
+
+  value = gst_structure_get_value (structure, "codec_data");
+
+  /* FIXME: */
+#if 0
+  g_mutex_lock (render->ass_mutex);
+  if (value != NULL) {
+    priv = gst_value_get_buffer (value);
+    g_return_val_if_fail (priv != NULL, FALSE);
+
+    codec_private = (gchar *) GST_BUFFER_DATA (priv);
+    codec_private_size = GST_BUFFER_SIZE (priv);
+
+    if (!render->ass_track)
+      render->ass_track = ass_new_track (render->ass_library);
+
+    ass_process_codec_private (render->ass_track,
+        codec_private, codec_private_size);
+
+    GST_DEBUG_OBJECT (render, "ass track created");
+
+    render->track_init_ok = TRUE;
+
+    ret = TRUE;
+  } else if (!render->ass_track) {
+    render->ass_track = ass_new_track (render->ass_library);
+
+    render->track_init_ok = TRUE;
+
+    ret = TRUE;
+  }
+  g_mutex_unlock (render->ass_mutex);
+#endif
+
+  gst_object_unref (render);
+
+  // FIXME
+  ret = TRUE;
+
+  return ret;
+}
+
+#define CLOCK_BASE 9LL
+#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \
+            CLOCK_BASE, GST_MSECOND/10))
+
+static void
+gst_dvbsub_overlay_process_text (GstDVBSubOverlay * overlay, GstBuffer * buffer)
+{
+  /* FIXME: Locking in this function */
+  guint8 *data = (guint8 *) GST_BUFFER_DATA (buffer);
+  guint size = GST_BUFFER_SIZE (buffer);
+  guint64 pts = GSTTIME_TO_MPEGTIME (GST_BUFFER_TIMESTAMP (buffer));
+
+  GST_DEBUG_OBJECT (overlay,
+      "Processing subtitles with running time %" GST_TIME_FORMAT
+      " which is PTS=%" G_GUINT64_FORMAT,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), pts);
+  GST_DEBUG_OBJECT (overlay, "Feeding %u bytes to libdvbsub", size);
+  g_mutex_lock (overlay->dvbsub_mutex); /* FIXME: Use standard lock? */
+  dvb_sub_feed_with_pts (overlay->dvb_sub, pts, data, size);
+  g_mutex_unlock (overlay->dvbsub_mutex);
+  gst_buffer_unref (buffer);
+}
+
+static void
+new_dvb_subtitles_cb (DvbSub * dvb_sub, guint64 pts, DVBSubtitles * subs,
+    guint8 page_time_out, gpointer user_data)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (user_data);
+  GST_INFO_OBJECT (overlay,
+      "New DVB subtitles arrived with a page_time_out of %d and %d regions for PTS=%"
+      G_GUINT64_FORMAT "\n", page_time_out, subs->num_rects, pts);
+  //GST_OBJECT_LOCK (overlay);
+  overlay->subtitle_buffer = subs->num_rects;
+  //GST_OBJECT_UNLOCK (overlay);
+}
+
+static GstFlowReturn
+gst_dvbsub_overlay_bufferalloc_video (GstPad * pad, guint64 offset, guint size,
+    GstCaps * caps, GstBuffer ** buffer)
+{
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+  GstFlowReturn ret = GST_FLOW_WRONG_STATE;
+  GstPad *allocpad;
+
+  GST_OBJECT_LOCK (render);
+  allocpad = render->srcpad ? gst_object_ref (render->srcpad) : NULL;
+  GST_OBJECT_UNLOCK (render);
+
+  if (allocpad) {
+    ret = gst_pad_alloc_buffer (allocpad, offset, size, caps, buffer);
+    gst_object_unref (allocpad);
+  }
+
+  gst_object_unref (render);
+
+  return ret;
+}
+
+static GstFlowReturn
+gst_dvbsub_overlay_chain_text (GstPad * pad, GstBuffer * buffer)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (GST_PAD_PARENT (pad));
+  gint64 clip_start = 0, clip_stop = 0;
+  gboolean in_seg = FALSE;
+
+  GST_INFO_OBJECT (overlay, "private/x-dvbsub buffer with size %u",
+      GST_BUFFER_SIZE (buffer));
+
+  GST_OBJECT_LOCK (overlay);
+
+  if (overlay->subtitle_flushing) {
+    GST_OBJECT_UNLOCK (overlay);
+    GST_LOG_OBJECT (overlay, "text flushing");
+    return GST_FLOW_WRONG_STATE;
+  }
+
+  if (overlay->subtitle_eos) {
+    GST_OBJECT_UNLOCK (overlay);
+    GST_LOG_OBJECT (overlay, "text EOS");
+    return GST_FLOW_UNEXPECTED;
+  }
+
+  GST_LOG_OBJECT (overlay,
+      "Video segment: %" GST_SEGMENT_FORMAT " --- Subtitle segment: %"
+      GST_SEGMENT_FORMAT " --- BUFFER: ts=%" GST_TIME_FORMAT " -- PTS: %"
+      G_GUINT64_FORMAT, &overlay->video_segment, &overlay->subtitle_segment,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+      GSTTIME_TO_MPEGTIME (GST_BUFFER_TIMESTAMP (buffer)));
+
+  /* DVB subtitle packets are required to carry the PTS */
+  if (G_UNLIKELY (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
+    GST_OBJECT_UNLOCK (overlay);
+    GST_WARNING_OBJECT (overlay,
+        "Text buffer without valid timestamp, dropping");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+
+  /* FIXME: Is the faking of a zero duration buffer correct here? Probably a better way to check segment inclusion then than to clip as a side effect */
+  in_seg = gst_segment_clip (&overlay->subtitle_segment, GST_FORMAT_TIME,
+      GST_BUFFER_TIMESTAMP (buffer), GST_BUFFER_TIMESTAMP (buffer), &clip_start,
+      &clip_stop);
+
+  /* As the passed start and stop is equal, we shouldn't need to care about out of segment at all,
+   * the subtitle data for the PTS is completely out of interest to us. A given display set must
+   * carry the same PTS value. */
+  /* FIXME: Consider with larger than 64kB display sets, which would be cut into multiple packets,
+   * FIXME: does our waiting + render code work when there are more than one packets before
+   * FIXME: rendering callback will get called? */
+
+  if (!in_seg) {
+    GST_DEBUG_OBJECT (overlay,
+        "Subtitle timestamp (%" GST_TIME_FORMAT
+        ") outside of the subtitle segment (%" GST_SEGMENT_FORMAT "), dropping",
+        GST_BUFFER_TIMESTAMP (buffer), &overlay->subtitle_segment);
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+#if 0                           /* In case of DVB-SUB, we get notified of the shown subtitle going away by the next
+                                 * page composition, so we can't just wait for the buffer to go away (unless we keep
+                                 * non-rendered raw DVBSubtitleRects or DVBSubtitlePicture in there, which is
+                                 * suboptimal */
+  /* Wait for the previous buffer to go away */
+  while (overlay->subtitle_buffer > 0) {
+    GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
+        GST_DEBUG_PAD_NAME (pad));
+    GST_DVBSUB_OVERLAY_WAIT (overlay);
+    GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
+    if (overlay->subtitle_flushing) {
+      GST_OBJECT_UNLOCK (overlay);
+      /* FIXME: No need to unref buffer? */
+      return GST_FLOW_WRONG_STATE;
+    }
+  }
+#endif
+
+  /* FIXME: How is this useful? */
+  gst_segment_set_last_stop (&overlay->subtitle_segment, GST_FORMAT_TIME,
+      clip_start);
+
+  overlay->subtitle_buffer = 0; /*buffer FIXME: Need to do buffering elsewhere */
+
+  /* That's a new text buffer we need to render */
+  /*overlay->need_render = TRUE; *//* FIXME: Actually feed it to libdvbsub and set need_render on a callback */
+
+  gst_dvbsub_overlay_process_text (overlay, buffer);
+
+  /* in case the video chain is waiting for a text buffer, wake it up */
+  GST_DVBSUB_OVERLAY_BROADCAST (overlay);
+
+  GST_OBJECT_UNLOCK (overlay);
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_dvbsub_overlay_chain_video (GstPad * pad, GstBuffer * buffer)
+{
+  GstDVBSubOverlay *overlay = GST_DVBSUB_OVERLAY (GST_PAD_PARENT (pad));
+  GstFlowReturn ret = GST_FLOW_OK;
+  gint64 start, stop;
+
+  if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
+    goto missing_timestamp;
+
+  start = GST_BUFFER_TIMESTAMP (buffer);
+
+  GST_LOG_OBJECT (overlay,
+      "Video last_stop: %" GST_TIME_FORMAT " --- Subtitle last_stop: %"
+      GST_TIME_FORMAT " --- BUFFER: ts=%" GST_TIME_FORMAT " -- PTS: %"
+      G_GUINT64_FORMAT, &overlay->video_segment.last_stop,
+      &overlay->subtitle_segment.last_stop, GST_TIME_ARGS (start),
+      GSTTIME_TO_MPEGTIME (start));
+
+  /* ignore buffers that are outside of the current segment */
+  if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
+    stop = GST_CLOCK_TIME_NONE;
+  } else {
+    stop = start + GST_BUFFER_DURATION (buffer);
+  }
+
+  /* FIXME: Probably update last_stop somewhere */
+
+  /* FIXME: Segment clipping code */
+
+  if (overlay->subtitle_buffer > 0) {
+    GST_DEBUG_OBJECT (overlay, "Should be rendering %u regions",
+        overlay->subtitle_buffer);
+  }
+
+  ret = gst_pad_push (overlay->srcpad, buffer);
+
+  return ret;
+
+missing_timestamp:
+  {
+    GST_WARNING_OBJECT (overlay, "video buffer without timestamp, discarding");
+    gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+}
+
+static gboolean
+gst_dvbsub_overlay_event_video (GstPad * pad, GstEvent * event)
+{
+  gboolean ret = FALSE;
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+
+  GST_DEBUG_OBJECT (pad, "received video event %s",
+      GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:
+    {
+      GstFormat format;
+      gdouble rate;
+      gint64 start, stop, time;
+      gboolean update;
+
+      GST_DEBUG_OBJECT (render, "received new segment");
+
+      gst_event_parse_new_segment (event, &update, &rate, &format, &start,
+          &stop, &time);
+
+      if (format == GST_FORMAT_TIME) {
+        GST_DEBUG_OBJECT (render, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
+            &render->video_segment);
+
+        gst_segment_set_newsegment (&render->video_segment, update, rate,
+            format, start, stop, time);
+
+        GST_DEBUG_OBJECT (render, "VIDEO SEGMENT after: %" GST_SEGMENT_FORMAT,
+            &render->video_segment);
+        ret = gst_pad_push_event (render->srcpad, event);
+      } else {
+        GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
+            ("received non-TIME newsegment event on video input"));
+        ret = FALSE;
+        gst_event_unref (event);
+      }
+      break;
+    }
+    case GST_EVENT_FLUSH_STOP:
+      gst_segment_init (&render->video_segment, GST_FORMAT_TIME);
+    default:
+      ret = gst_pad_push_event (render->srcpad, event);
+      break;
+  }
+
+  gst_object_unref (render);
+
+  return ret;
+}
+
+static gboolean
+gst_dvbsub_overlay_event_text (GstPad * pad, GstEvent * event)
+{
+  //gint i; // FIXME
+  gboolean ret = FALSE;
+  GstDVBSubOverlay *render = GST_DVBSUB_OVERLAY (gst_pad_get_parent (pad));
+
+  GST_DEBUG_OBJECT (pad, "received text event %s", GST_EVENT_TYPE_NAME (event));
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_NEWSEGMENT:
+    {
+      GstFormat format;
+      gdouble rate;
+      gint64 start, stop, time;
+      gboolean update;
+
+      GST_DEBUG_OBJECT (render, "received new segment");
+
+      /* FIXME: overlay */ render->subtitle_eos = FALSE;
+
+      gst_event_parse_new_segment (event, &update, &rate, &format, &start,
+          &stop, &time);
+
+      if (format == GST_FORMAT_TIME) {
+        GST_DEBUG_OBJECT (render, "SUBTITLE SEGMENT now: %" GST_SEGMENT_FORMAT,
+            &render->subtitle_segment);
+
+        gst_segment_set_newsegment (&render->subtitle_segment, update, rate,
+            format, start, stop, time);
+
+        GST_DEBUG_OBJECT (render,
+            "SUBTITLE SEGMENT after: %" GST_SEGMENT_FORMAT,
+            &render->subtitle_segment);
+        ret = TRUE;
+        gst_event_unref (event);
+      } else {
+        GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
+            ("received non-TIME newsegment event on subtitle input"));
+        ret = FALSE;
+        gst_event_unref (event);
+      }
+      /* FIXME: Not fully compared with textoverlay */
+      GST_OBJECT_LOCK (render);
+      GST_DVBSUB_OVERLAY_BROADCAST (render);
+      GST_OBJECT_UNLOCK (render);
+      break;
+    }
+    case GST_EVENT_FLUSH_STOP:
+      gst_segment_init (&render->subtitle_segment, GST_FORMAT_TIME);
+      render->subtitle_flushing = FALSE;
+      /*FIXME: overlay */ render->subtitle_eos = FALSE;
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    case GST_EVENT_FLUSH_START:
+      GST_DEBUG_OBJECT (render, "begin flushing");
+#if 0                           // FIXME
+      g_mutex_lock (render->ass_mutex);
+      if (render->ass_track) {
+        /* delete any events on the ass_track */
+        for (i = 0; i < render->ass_track->n_events; i++) {
+          GST_DEBUG_OBJECT (render, "deleted event with eid %i", i);
+          ass_free_event (render->ass_track, i);
+        }
+        render->ass_track->n_events = 0;
+        GST_DEBUG_OBJECT (render, "done flushing");
+      }
+      g_mutex_unlock (render->ass_mutex);
+#endif
+      g_mutex_lock (render->subtitle_mutex);
+      if (render->subtitle_pending)
+        gst_buffer_unref (render->subtitle_pending);
+      render->subtitle_pending = NULL;
+      render->subtitle_flushing = TRUE;
+      GST_DVBSUB_OVERLAY_BROADCAST (render);
+      g_mutex_unlock (render->subtitle_mutex);
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    case GST_EVENT_EOS:
+      GST_OBJECT_LOCK (render);
+      /*FIXME: overlay */ render->subtitle_eos = TRUE;
+      GST_INFO_OBJECT (render, "text EOS");
+      /* wake up the video chain, it might be waiting for a text buffer or
+       * a text segment update */
+      GST_DVBSUB_OVERLAY_BROADCAST (render);
+      GST_OBJECT_UNLOCK (render);
+      gst_event_unref (event);
+      ret = TRUE;
+      break;
+    default:
+      ret = gst_pad_push_event (render->srcpad, event);
+      break;
+  }
+
+  gst_object_unref (render);
+
+  return ret;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_dvbsub_overlay_debug, "dvbsuboverlay",
+      0, "DVB subtitle overlay");
+  GST_DEBUG_CATEGORY_INIT (gst_dvbsub_overlay_lib_debug, "dvbsub_library",
+      0, "libdvbsub library");
+
+  return gst_element_register (plugin, "dvbsuboverlay",
+      GST_RANK_PRIMARY, GST_TYPE_DVBSUB_OVERLAY);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "dvbsuboverlay",
+    "DVB subtitle renderer",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/gst/dvbsuboverlay/gstdvbsuboverlay.h b/gst/dvbsuboverlay/gstdvbsuboverlay.h
new file mode 100644 (file)
index 0000000..bf4c978
--- /dev/null
@@ -0,0 +1,90 @@
+/* GStreamer DVB subtitles overlay
+ * Copyright (c) 2010 Mart Raudsepp <mart.raudsepp@collabora.co.uk>
+ *
+ * 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_DVBSUB_OVERLAY_H__
+#define __GST_DVBSUB_OVERLAY_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+#include "dvb-sub.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_DVBSUB_OVERLAY (gst_dvbsub_overlay_get_type())
+#define GST_DVBSUB_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DVBSUB_OVERLAY,GstDVBSubOverlay))
+#define GST_DVBSUB_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DVBSUB_OVERLAY,GstDVBSubOverlayClass))
+#define GST_IS_DVBSUB_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DVBSUB_OVERLAY))
+#define GST_IS_DVBSUB_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DVBSUB_OVERLAY))
+
+typedef struct _GstDVBSubOverlay GstDVBSubOverlay;
+typedef struct _GstDVBSubOverlayClass GstDVBSubOverlayClass;
+typedef void (*GstAssRenderBlitFunction) (GstDVBSubOverlay *overlay, DVBSubtitles *subs, GstBuffer *buffer);
+
+struct _GstDVBSubOverlay
+{
+  GstElement element;
+
+  GstPad *video_sinkpad, *text_sinkpad, *srcpad;
+
+  /* properties */
+  gboolean enable;
+
+  /* <private> */
+  GstSegment video_segment;
+  GstSegment subtitle_segment;
+
+  GstVideoFormat format;
+  gint width, height;
+  gint fps_n, fps_d;
+  GstAssRenderBlitFunction blit;
+
+  guint subtitle_buffer; /* FIXME: This should hold the pre-rendered
+                         * subtitle regions for fast blending on top
+                         * of each frame. Currently just a number of
+                         * active regions that ought to get blended,
+                         * to get all the timing code working,
+                         * leaving the actual conversion from
+                         * libdvbsub to a suitable cache format and
+                         * blending to later */
+  GMutex *subtitle_mutex;
+  GCond *subtitle_cond; /* to signal removal of a queued text
+                        * buffer, arrival of a text buffer,
+                        * a text segment update, or a change
+                        * in status (e.g. shutdown, flushing)
+                        * FIXME: Update comment for dvbsub case */
+  GstBuffer *subtitle_pending;
+  gboolean subtitle_flushing;
+  gboolean subtitle_eos;
+
+  GMutex *dvbsub_mutex; /* FIXME: Do we need a mutex lock in case of libdvbsub? Probably, but... */
+  DvbSub *dvb_sub;
+
+  gboolean renderer_init_ok;
+};
+
+struct _GstDVBSubOverlayClass
+{
+  GstElementClass parent_class;
+};
+
+GType gst_dvbsub_overlay_get_type (void);
+
+G_END_DECLS
+
+#endif