/* -*- 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; DVBSubtitleWindow display_def; }; #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); /* display/window information */ priv->display_def.version = -1; priv->display_def.window_flag = 0; priv->display_def.display_width = 720; priv->display_def.display_height = 576; } 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_display_definition_segment (DvbSub * dvb_sub, guint8 * buf, gint buf_size) { int dds_version, info_byte; DvbSubPrivate *ctx = dvb_sub->private_data; if (buf_size < 5) return -1; info_byte = *buf++; dds_version = info_byte >> 4; if (ctx->display_def.version == dds_version) return 0; /* already have this display definition version */ ctx->display_def.version = dds_version; ctx->display_def.display_width = GST_READ_UINT16_BE (buf) + 1; buf += 2; ctx->display_def.display_height = GST_READ_UINT16_BE (buf) + 1; buf += 2; ctx->display_def.window_flag = info_byte & 1 << 3; if (buf_size >= 13 && ctx->display_def.window_flag) { ctx->display_def.window_x = GST_READ_UINT16_BE (buf); buf += 2; ctx->display_def.window_y = GST_READ_UINT16_BE (buf); buf += 2; ctx->display_def.window_width = GST_READ_UINT16_BE (buf) - ctx->display_def.window_x + 1; buf += 2; ctx->display_def.window_height = GST_READ_UINT16_BE (buf) - ctx->display_def.window_y + 1; buf += 2; } return 0; } 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; /* copy subtitle display and window information */ sub->display_def = priv->display_def; 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->pts = pts; sub->page_time_out = priv->page_time_out; sub->num_rects = i; if (priv->callbacks.new_data) { priv->callbacks.new_data (dvb_sub, sub, priv->user_data); } else { /* No-one responsible to clean up memory, so do it ourselves */ /* FIXME: Just don't bother with all this palette image creation in the first place then... */ dvb_subtitles_free (sub); } return 1; /* FIXME: The caller of this function is probably supposed to do something with the return value */ } void dvb_subtitles_free (DVBSubtitles * sub) { int i; DVBSubtitleRect *rect; if (sub == NULL) return; /* 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); } /** * 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; } #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_DISPLAY_DEFINITION 0x14 #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 (this is probably a leftover TODO that is now done?) /* FIXME: Handle differing PTS values - all segments of a given display set must be with the same PTS, * FIXME: but we let it slip and just take it for granted in end_of_display_set */ 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_DISPLAY_DEFINITION: dvb_log (DVB_LOG_PACKET, G_LOG_LEVEL_DEBUG, "display definition segment at buffer pos %u\n", pos); _dvb_sub_parse_display_definition_segment (dvb_sub, data + pos, segment_len); 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; } }