From: Mart Raudsepp Date: Mon, 29 Nov 2010 20:06:07 +0000 (+0000) Subject: dvbsuboverlay: initial version, work in progress X-Git-Tag: 1.19.3~507^2~16062^2~447 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a8d891dc5eb7d7404f2580c2450b2a7cf577ddc6;p=platform%2Fupstream%2Fgstreamer.git dvbsuboverlay: initial version, work in progress --- diff --git a/configure.ac b/configure.ac index e728fcd..566de0d 100644 --- a/configure.ac +++ b/configure.ac @@ -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 index 0000000..9cc2f38 --- /dev/null +++ b/gst/dvbsuboverlay/Makefile.am @@ -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 index 0000000..7042734 --- /dev/null +++ b/gst/dvbsuboverlay/TODO @@ -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: + 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 + 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 index 0000000..b0b26cc --- /dev/null +++ b/gst/dvbsuboverlay/dvb-sub.c @@ -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 + * + * 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 /* memset */ +#include /* GST_READ_UINT16_BE */ +#include /* 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 index 0000000..fee8816 --- /dev/null +++ b/gst/dvbsuboverlay/dvb-sub.h @@ -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 + * + * 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 + +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 index 0000000..18bfe55 --- /dev/null +++ b/gst/dvbsuboverlay/ffmpeg-colorspace.h @@ -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];\ + 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 index 0000000..a2157e9 --- /dev/null +++ b/gst/dvbsuboverlay/gstdvbsuboverlay.c @@ -0,0 +1,1275 @@ +/* GStreamer DVB subtitles overlay + * Copyright (c) 2010 Mart Raudsepp + * + * 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. + * + * + * Example launch line + * |[ 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. + * + */ + + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gstdvbsuboverlay.h" + +#include + +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 "); // 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 index 0000000..bf4c978 --- /dev/null +++ b/gst/dvbsuboverlay/gstdvbsuboverlay.h @@ -0,0 +1,90 @@ +/* GStreamer DVB subtitles overlay + * Copyright (c) 2010 Mart Raudsepp + * + * 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 +#include + +#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; + + /* */ + 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