| /* dvb-sub.c - DVB subtitle decoding |
| * Copyright (C) Mart Raudsepp 2009 <mart.raudsepp@artecdesign.ee> |
| * Copyright (C) 2010 ONELAN Ltd. |
| * |
| * 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 <string.h> /* memset */ |
| #include <gst/gstutils.h> /* GST_READ_UINT16_BE */ |
| #include <gst/base/gstbitreader.h> /* GstBitReader */ |
| |
| #include "dvb-sub.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (dvbsub_debug); |
| #define GST_CAT_DEFAULT dvbsub_debug |
| |
| static void dvb_sub_init (void); |
| |
| /* 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 AYUV(y,u,v,a) (((a) << 24) | ((y) << 16) | ((u) << 8) | (v)) |
| #define RGBA_TO_AYUV(r,g,b,a) (((a) << 24) | ((rgb_to_y(r,g,b)) << 16) | ((rgb_to_u(r,g,b)) << 8) | (rgb_to_v(r,g,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; |
| |
| struct _DvbSub |
| { |
| 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; |
| }; |
| |
| typedef enum |
| { |
| TOP_FIELD = 0, |
| BOTTOM_FIELD = 1 |
| } DvbSubPixelDataSubBlockFieldType; |
| |
| 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; |
| } |
| |
| static DVBSubObject * |
| get_object (DvbSub * dvb_sub, guint16 object_id) |
| { |
| DVBSubObject *ptr = dvb_sub->object_list; |
| |
| while (ptr && ptr->id != object_id) { |
| ptr = ptr->next; |
| } |
| |
| return ptr; |
| } |
| |
| static DVBSubCLUT * |
| get_clut (DvbSub * dvb_sub, gint clut_id) |
| { |
| DVBSubCLUT *ptr = dvb_sub->clut_list; |
| |
| while (ptr && ptr->id != clut_id) { |
| ptr = ptr->next; |
| } |
| |
| return ptr; |
| } |
| |
| static DVBSubRegion * |
| get_region (DvbSub * dvb_sub, guint8 region_id) |
| { |
| DVBSubRegion *ptr = dvb_sub->region_list; |
| |
| while (ptr && ptr->id != region_id) { |
| ptr = ptr->next; |
| } |
| |
| return ptr; |
| } |
| |
| static void |
| delete_region_display_list (DvbSub * dvb_sub, DVBSubRegion * region) |
| { |
| 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 **) & dvb_sub->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) |
| { |
| DVBSubRegion *region; |
| |
| while (dvb_sub->region_list) { |
| region = dvb_sub->region_list; |
| |
| dvb_sub->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, dvb_sub->clut_list, next); |
| dvb_sub->clut_list = NULL; |
| |
| /* Should already be NULL */ |
| g_warn_if_fail (dvb_sub->object_list == NULL); |
| } |
| |
| static void |
| dvb_sub_init (void) |
| { |
| int i, r, g, b, a = 0; |
| |
| GST_DEBUG_CATEGORY_INIT (dvbsub_debug, "dvbsub", 0, "dvbsuboverlay parser"); |
| |
| /* 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_TO_AYUV (0, 0, 0, 0); |
| default_clut.clut4[1] = RGBA_TO_AYUV (255, 255, 255, 255); |
| default_clut.clut4[2] = RGBA_TO_AYUV (0, 0, 0, 255); |
| default_clut.clut4[3] = RGBA_TO_AYUV (127, 127, 127, 255); |
| |
| default_clut.clut16[0] = RGBA_TO_AYUV (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_TO_AYUV (r, g, b, 255); |
| } |
| |
| default_clut.clut256[0] = RGBA_TO_AYUV (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_TO_AYUV (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? */ |
| DVBSubRegionDisplay *display; |
| DVBSubRegionDisplay *tmp_display_list, **tmp_ptr; |
| |
| const guint8 *buf_end = buf + buf_size; |
| guint8 region_id; |
| guint8 page_state; |
| |
| if (buf_size < 1) |
| return; |
| |
| dvb_sub->page_time_out = *buf++; |
| page_state = ((*buf++) >> 2) & 3; |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| { |
| static const gchar *page_state_str[4] = { |
| "Normal case", "ACQUISITION POINT", "Mode Change", "RESERVED" |
| }; |
| |
| GST_DEBUG ("PAGE: page_id = %u, length = %d, page_time_out = %u secs, " |
| "page_state = %s", page_id, buf_size, dvb_sub->page_time_out, |
| page_state_str[page_state]); |
| } |
| #endif |
| |
| if (page_state == 2) { /* Mode change */ |
| delete_state (dvb_sub); |
| } |
| |
| tmp_display_list = dvb_sub->display_list; |
| dvb_sub->display_list = NULL; |
| dvb_sub->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 = dvb_sub->display_list; |
| dvb_sub->display_list = display; |
| dvb_sub->display_list_size++; |
| |
| GST_LOG ("PAGE: REGION information: ID = %u, address = %ux%u", 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) |
| { |
| 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 = dvb_sub->region_list; |
| dvb_sub->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) { |
| GST_WARNING ("region depth %d is invalid", 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); |
| } |
| |
| GST_DEBUG ("REGION: id = %u, (%ux%u)@%u-bit", region_id, region->width, |
| region->height, region->depth); |
| |
| if (fill) { |
| memset (region->pbuf, region->bgcolor, region->buf_size); |
| GST_DEBUG ("REGION: 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 = dvb_sub->object_list; |
| dvb_sub->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; |
| |
| GST_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) { |
| GST_DEBUG ("REGION DATA: fgcolor = %u, bgcolor = %u", |
| object_display->fgcolor, object_display->bgcolor); |
| } |
| } |
| } |
| |
| static void |
| _dvb_sub_parse_clut_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf, |
| gint buf_size) |
| { |
| const guint8 *buf_end = buf + buf_size; |
| guint8 clut_id; |
| DVBSubCLUT *clut; |
| int entry_id, depth, full_range; |
| int y, cr, cb, alpha; |
| |
| GST_MEMDUMP ("DVB clut packet", buf, buf_size); |
| |
| clut_id = *buf++; |
| buf += 1; |
| |
| clut = get_clut (dvb_sub, clut_id); |
| |
| if (!clut) { |
| clut = g_slice_new (DVBSubCLUT); |
| |
| memcpy (clut, &default_clut, sizeof (DVBSubCLUT)); |
| |
| clut->id = clut_id; |
| |
| clut->next = dvb_sub->clut_list; |
| dvb_sub->clut_list = clut; |
| } |
| |
| while (buf + 4 < buf_end) { |
| entry_id = *buf++; |
| |
| depth = (*buf) & 0xe0; |
| |
| if (depth == 0) { |
| GST_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; |
| |
| GST_DEBUG ("CLUT DEFINITION: clut %d := (%d,%d,%d,%d)", entry_id, y, cb, cr, |
| alpha); |
| |
| if (depth & 0x80) |
| clut->clut4[entry_id] = AYUV (y, cb, cr, 255 - alpha); |
| if (depth & 0x40) |
| clut->clut16[entry_id] = AYUV (y, cb, cr, 255 - alpha); |
| if (depth & 0x20) |
| clut->clut256[entry_id] = AYUV (y, cb, cr, 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; |
| } |
| |
| GST_TRACE ("dbuf_len = %d", dbuf_len); |
| |
| while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) { |
| guint run_length = 0, clut_index = 0; |
| |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2); |
| |
| if (bits) { /* 2-bit_pixel-code */ |
| run_length = 1; |
| clut_index = bits; |
| } else { /* 2-bit_zero */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1); |
| if (bits == 1) { /* switch_1 == '1' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 3); |
| run_length += 3; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2); |
| } else { /* switch_1 == '0' */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1); |
| if (bits == 1) { /* switch_2 == '1' */ |
| run_length = 1; /* 1x pseudo-colour '00' */ |
| } else { /* switch_2 == '0' */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 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 */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4); |
| run_length += 12; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2); |
| break; |
| case 0x3: /* the following 10 bits contain run length coded pixel data */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8); |
| run_length += 29; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 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 */ |
| GST_TRACE ("RUNLEN: 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; |
| |
| GST_TRACE ("PIXEL: returning, read %u pixels", 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; |
| |
| GST_TRACE ("RUNLEN: srcbuf position %p, 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; |
| |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4); |
| |
| if (bits) { |
| run_length = 1; |
| clut_index = bits; |
| } else { |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1); |
| if (bits == 0) { /* switch_1 == '0' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 3); |
| if (!run_length) { |
| stop_parsing = TRUE; |
| } else { |
| run_length += 2; |
| } |
| } else { /* switch_1 == '1' */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1); |
| if (bits == 0) { /* switch_2 == '0' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2); |
| run_length += 4; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4); |
| } else { /* switch_2 == '1' */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 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' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4); |
| run_length += 9; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4); |
| break; |
| case 0x3: /* switch_3 == '11' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8); |
| run_length += 25; |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 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 */ |
| GST_TRACE ("RUNLEN: 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; |
| |
| GST_LOG ("Returning 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; |
| } |
| |
| GST_LOG ("dbuf_len = %d", 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; |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8); |
| |
| if (bits) { /* 8-bit_pixel-code */ |
| run_length = 1; |
| clut_index = bits; |
| } else { /* 8-bit_zero */ |
| bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1); |
| if (bits == 0) { /* switch_1 == '0' */ |
| /* run_length_1-127 for pseudo-colour _entry) '0x00' */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 7); |
| if (run_length == 0) { /* end_of_string_signal */ |
| stop_parsing = TRUE; |
| } |
| } else { /* switch_1 == '1' */ |
| /* run_length_3-127 */ |
| run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 7); |
| clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8); |
| |
| if (run_length < 3) { |
| GST_WARNING ("runlength value was %u, but the spec requires it " |
| "must be >=3", run_length); |
| } |
| } |
| } |
| |
| /* 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 */ |
| GST_TRACE ("RUNLEN: 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; |
| } |
| |
| GST_LOG ("Returning 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; |
| |
| GST_LOG ("DVB pixel block size %d, %s field:", buf_size, |
| top_bottom ? "bottom" : "top"); |
| |
| GST_MEMDUMP ("packet", buf, buf_size); |
| |
| if (region == NULL) { |
| GST_LOG ("Region is NULL, returning"); |
| 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) { |
| GST_LOG ("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", (guint) (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) { |
| /* FIXME: Be more verbose */ |
| GST_WARNING ("Invalid object location for data_type 0x%x!", |
| *(buf - 1)); |
| GST_MEMDUMP ("Remaining data after invalid object location:", buf, |
| (guint) (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) { |
| /* FIXME: Be more verbose */ |
| GST_WARNING ("Invalid object location for data_type 0x%x!", |
| *(buf - 1)); |
| GST_MEMDUMP ("Remaining data after invalid object location:", 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) { |
| GST_WARNING ("4-bit pixel string in %d-bit region!", region->depth); |
| return; |
| } |
| |
| if (region->depth == 8) |
| map_table = map4to8; |
| else |
| map_table = NULL; |
| |
| GST_LOG ("READ_4BIT_STRING: String data into position %dx%d; " |
| "buf before is %p", 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); |
| GST_DEBUG ("READ_4BIT_STRING finished: buf pointer now %p", buf); |
| break; |
| case 0x12: |
| if (dest_buf_filled) { |
| /* FIXME: Be more verbose */ |
| GST_WARNING ("Invalid object location for data_type 0x%x!", |
| *(buf - 1)); |
| GST_MEMDUMP ("Remaining data after invalid object location:", |
| buf, (guint) (buf_end - buf)); |
| return; |
| } |
| |
| if (region->depth < 8) { |
| GST_WARNING ("8-bit pixel string in %d-bit region!", 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: |
| GST_DEBUG ("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: |
| GST_DEBUG ("handling map2to8 table data"); |
| for (i = 0; i < 4; i++) |
| map2to8[i] = *buf++; |
| break; |
| case 0x22: |
| GST_DEBUG ("handling map4to8 table data"); |
| for (i = 0; i < 16; i++) |
| map4to8[i] = *buf++; |
| break; |
| case 0xf0: |
| GST_DEBUG ("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? */ |
| GST_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); |
| |
| GST_DEBUG ("OBJECT: a new object segment has occurred for object_id = %u", |
| object_id); |
| |
| if (!object) { |
| GST_WARNING ("Nothing known about object with ID %u yet, 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) { |
| GST_WARNING ("Field data size too large"); |
| 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; |
| |
| GST_DEBUG ("OBJECT: 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) { |
| GST_FIXME ("'a string of characters' coding method not supported yet!"); |
| } else { |
| GST_WARNING ("Unknown object coding 0x%x", coding_method); |
| } |
| } |
| |
| static gint |
| _dvb_sub_parse_display_definition_segment (DvbSub * dvb_sub, guint8 * buf, |
| gint buf_size) |
| { |
| int dds_version, info_byte; |
| |
| if (buf_size < 5) |
| return -1; |
| |
| info_byte = *buf++; |
| dds_version = info_byte >> 4; |
| |
| if (dvb_sub->display_def.version == dds_version) |
| return 0; /* already have this display definition version */ |
| |
| dvb_sub->display_def.version = dds_version; |
| dvb_sub->display_def.display_width = GST_READ_UINT16_BE (buf) + 1; |
| buf += 2; |
| dvb_sub->display_def.display_height = GST_READ_UINT16_BE (buf) + 1; |
| buf += 2; |
| |
| dvb_sub->display_def.window_flag = info_byte & 1 << 3; |
| |
| if (buf_size >= 13 && dvb_sub->display_def.window_flag) { |
| dvb_sub->display_def.window_x = GST_READ_UINT16_BE (buf); |
| buf += 2; |
| dvb_sub->display_def.window_y = GST_READ_UINT16_BE (buf); |
| buf += 2; |
| dvb_sub->display_def.window_width = |
| GST_READ_UINT16_BE (buf) - dvb_sub->display_def.window_x + 1; |
| buf += 2; |
| dvb_sub->display_def.window_height = |
| GST_READ_UINT16_BE (buf) - dvb_sub->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) |
| { |
| DVBSubRegionDisplay *display; |
| DVBSubtitles *sub; |
| DVBSubCLUT *clut; |
| guint32 *clut_table; |
| int i; |
| |
| GST_DEBUG ("DISPLAY SET END: page_id = %u, length = %d", page_id, buf_size); |
| |
| sub = g_slice_new0 (DVBSubtitles); |
| |
| #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 |
| |
| /* N.B. g_new0() will return NULL if num_rects is 0 */ |
| sub->num_rects = dvb_sub->display_list_size; |
| sub->rects = g_new0 (DVBSubtitleRect, sub->num_rects); |
| |
| i = 0; |
| |
| /* copy subtitle display and window information */ |
| sub->display_def = dvb_sub->display_def; |
| |
| for (display = dvb_sub->display_list; display; display = display->next) { |
| DVBSubtitleRect *rect; |
| DVBSubRegion *region; |
| |
| region = get_region (dvb_sub, display->region_id); |
| |
| if (!region) |
| continue; |
| |
| rect = &sub->rects[i]; |
| 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)); |
| |
| GST_MEMDUMP ("rect->pict.data.palette content", |
| (guint8 *) rect->pict.palette, (1 << region->depth) * sizeof (guint32)); |
| |
| rect->pict.data = g_malloc (region->buf_size); /* FIXME: Can we use GSlice here? */ |
| memcpy (rect->pict.data, region->pbuf, region->buf_size); |
| |
| GST_DEBUG ("DISPLAY: an object rect created: iteration %u, " |
| "pos: %d:%d, size: %dx%d", i, rect->x, rect->y, rect->w, rect->h); |
| |
| GST_MEMDUMP ("rect->pict.data content", rect->pict.data, region->buf_size); |
| |
| ++i; |
| } |
| |
| sub->pts = pts; |
| sub->page_time_out = dvb_sub->page_time_out; |
| sub->num_rects = i; |
| |
| if (dvb_sub->callbacks.new_data) { |
| dvb_sub->callbacks.new_data (dvb_sub, sub, dvb_sub->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; |
| |
| if (sub == NULL) |
| return; |
| |
| /* Now free up all the temporary memory we allocated */ |
| for (i = 0; i < sub->num_rects; ++i) { |
| g_free (sub->rects[i].pict.palette); |
| g_free (sub->rects[i].pict.data); |
| } |
| g_free (sub->rects); |
| g_slice_free (DVBSubtitles, sub); |
| } |
| |
| DvbSub * |
| dvb_sub_new (void) |
| { |
| static gsize inited = 0; |
| DvbSub *sub; |
| |
| if (g_once_init_enter (&inited)) { |
| dvb_sub_init (); |
| g_once_init_leave (&inited, TRUE); |
| } |
| |
| sub = g_slice_new0 (DvbSub); |
| |
| /* TODO: Add initialization code here */ |
| /* FIXME: Do we have a reason to initiate the members to zero, or are we guaranteed that anyway? */ |
| sub->region_list = NULL; |
| sub->object_list = NULL; |
| sub->page_time_out = 0; /* FIXME: Maybe 255 instead? */ |
| sub->pes_buffer = g_string_new (NULL); |
| |
| /* display/window information */ |
| sub->display_def.version = -1; |
| sub->display_def.window_flag = 0; |
| sub->display_def.display_width = 720; |
| sub->display_def.display_height = 576; |
| |
| return sub; |
| } |
| |
| void |
| dvb_sub_free (DvbSub * sub) |
| { |
| /* TODO: Add deinitalization code here */ |
| /* FIXME: Clear up region_list contents */ |
| delete_state (sub); |
| while (sub->display_list) { |
| DVBSubRegionDisplay *tmp = sub->display_list->next; |
| g_slice_free (DVBSubRegionDisplay, sub->display_list); |
| sub->display_list = tmp; |
| } |
| g_string_free (sub->pes_buffer, TRUE); |
| g_slice_free (DvbSub, sub); |
| } |
| |
| #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; |
| |
| GST_DEBUG ("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) */ |
| GST_WARNING ("Data length too short"); |
| return -1; |
| } |
| |
| if (data[pos++] != 0x20) { |
| GST_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) { |
| GST_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)) { |
| GST_WARNING ("Data after SYNC BYTE too short, less than needed to " |
| "even get to segment_length"); |
| return -2; |
| } |
| segment_type = data[pos++]; |
| GST_DEBUG ("=== Segment type is 0x%x", segment_type); |
| page_id = (data[pos] << 8) | data[pos + 1]; |
| GST_DEBUG ("page_id is 0x%x", page_id); |
| pos += 2; |
| segment_len = (data[pos] << 8) | data[pos + 1]; |
| GST_DEBUG ("segment_length is %d (0x%x 0x%x)", segment_len, data[pos], |
| data[pos + 1]); |
| pos += 2; |
| if ((len - pos) < segment_len) { |
| GST_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: |
| GST_DEBUG ("Page composition segment at buffer pos %u", 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: |
| GST_DEBUG ("Region composition segment at buffer pos %u", 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: |
| GST_DEBUG ("CLUT definition segment at buffer pos %u", 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: |
| GST_DEBUG ("Object data segment at buffer pos %u", 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: |
| GST_DEBUG ("display definition segment at buffer pos %u", pos); |
| _dvb_sub_parse_display_definition_segment (dvb_sub, data + pos, |
| segment_len); |
| break; |
| case DVB_SUB_SEGMENT_END_OF_DISPLAY_SET: |
| GST_DEBUG ("End of display set at buffer pos %u", pos); |
| _dvb_sub_parse_end_of_display_set (dvb_sub, page_id, data + pos, segment_len, pts); /* FIXME: Not sure about args */ |
| break; |
| default: |
| GST_FIXME ("Unhandled segment type 0x%x", segment_type); |
| break; |
| } |
| |
| pos += segment_len; |
| |
| if (pos == len) { |
| GST_WARNING ("Data ended without a PES data end marker"); |
| return 1; |
| } |
| } |
| |
| GST_LOG ("Processed %d bytes out of %d", 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) |
| { |
| g_return_if_fail (dvb_sub != NULL); |
| g_return_if_fail (callbacks != NULL); |
| |
| dvb_sub->callbacks = *callbacks; |
| dvb_sub->user_data = user_data; |
| } |