1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 * libdvbsub - DVB subtitle decoding
4 * Copyright (C) Mart Raudsepp 2009 <mart.raudsepp@artecdesign.ee>
6 * Heavily uses code algorithms ported from ffmpeg's libavcodec/dvbsubdec.c,
7 * especially the segment parsers. The original license applies to this
8 * ported code and the whole code in this file as well.
10 * Original copyright information follows:
13 * DVB subtitle decoding for ffmpeg
14 * Copyright (c) 2005 Ian Caulfield
16 * This file is part of FFmpeg.
18 * FFmpeg is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU Lesser General Public
20 * License as published by the Free Software Foundation; either
21 * version 2.1 of the License, or (at your option) any later version.
23 * FFmpeg is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * Lesser General Public License for more details.
28 * You should have received a copy of the GNU Lesser General Public
29 * License along with FFmpeg; if not, write to the Free Software
30 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34 #include <string.h> /* memset */
35 #include <gst/gstutils.h> /* GST_READ_UINT16_BE */
36 #include <gst/base/gstbitreader.h> /* GstBitReader */
37 #include "ffmpeg-colorspace.h" /* YUV_TO_RGB1_CCIR */ /* FIXME: Just give YUV data to gstreamer then? */
39 GST_DEBUG_CATEGORY_STATIC (dvbsub_debug);
40 #define GST_CAT_DEFAULT dvbsub_debug
43 dvb_sub_init_debug (void)
45 GST_DEBUG_CATEGORY_INIT (dvbsub_debug, "dvbsub", 0, "dvbsuboverlay parser");
48 /* FIXME: Are we waiting for an acquisition point before trying to do things? */
49 /* FIXME: In the end convert some of the guint8/16 (especially stack variables) back to gint for access efficiency */
53 * @short_description: a DVB subtitle parsing class
54 * @stability: Unstable
56 * The #DvbSub represents an object used for parsing a DVB subpicture,
57 * and signalling the API user for new bitmaps to show on screen.
60 #define MAX_NEG_CROP 1024
61 static guint8 ff_cropTbl[256 + 2 * MAX_NEG_CROP] = { 0, };
63 #define cm (ff_cropTbl + MAX_NEG_CROP)
65 /* FIXME: This is really ARGB... We might need this configurable for performant
66 * FIXME: use in GStreamer as well if that likes RGBA more (Qt prefers ARGB) */
67 #define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
69 typedef struct DVBSubCLUT
71 int id; /* default_clut uses -1 for this, so guint8 isn't fine without adaptations first */
77 struct DVBSubCLUT *next;
80 static DVBSubCLUT default_clut;
82 typedef struct DVBSubObjectDisplay
84 /* FIXME: Use more correct sizes */
94 /* FIXME: Should we use GSList? The relating interaction and pointer assigment is quite complex and perhaps unsuited for a plain GSList anyway */
95 struct DVBSubObjectDisplay *region_list_next;
96 struct DVBSubObjectDisplay *object_list_next;
97 } DVBSubObjectDisplay;
99 typedef struct DVBSubObject
101 /* FIXME: Use more correct sizes */
102 int id; /* FIXME: Use guint8 after checking it's fine in all code using it */
106 /* FIXME: Should we use GSList? */
107 DVBSubObjectDisplay *display_list;
108 struct DVBSubObject *next;
111 typedef struct DVBSubRegionDisplay
112 { /* FIXME: Figure out if this structure is only used temporarily in page_segment parser, or also more */
118 struct DVBSubRegionDisplay *next;
119 } DVBSubRegionDisplay;
121 typedef struct DVBSubRegion
126 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 */
131 /* FIXME: Validate these fields existence and exact types */
135 DVBSubObjectDisplay *display_list;
137 struct DVBSubRegion *next;
140 typedef struct _DvbSubPrivate DvbSubPrivate;
141 struct _DvbSubPrivate
144 DvbSubCallbacks callbacks;
147 guint8 page_time_out;
148 DVBSubRegion *region_list;
149 DVBSubCLUT *clut_list;
150 DVBSubObject *object_list;
152 int display_list_size;
153 DVBSubRegionDisplay *display_list;
155 DVBSubtitleWindow display_def;
158 #define DVB_SUB_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), DVB_TYPE_SUB, DvbSubPrivate))
160 G_DEFINE_TYPE (DvbSub, dvb_sub, G_TYPE_OBJECT);
166 } DvbSubPixelDataSubBlockFieldType;
168 /* FIXME: It might make sense to pass DvbSubPrivate for all the get_* functions, instead of public DvbSub */
169 static DVBSubObject *
170 get_object (DvbSub * dvb_sub, guint16 object_id)
172 const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
173 DVBSubObject *ptr = priv->object_list;
175 while (ptr && ptr->id != object_id) {
183 get_clut (DvbSub * dvb_sub, gint clut_id)
185 const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
186 DVBSubCLUT *ptr = priv->clut_list;
188 while (ptr && ptr->id != clut_id) {
195 // FIXME: Just pass private_data pointer directly here and in other get_* helper functions?
196 static DVBSubRegion *
197 get_region (DvbSub * dvb_sub, guint8 region_id)
199 const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
200 DVBSubRegion *ptr = priv->region_list;
202 while (ptr && ptr->id != region_id) {
210 delete_region_display_list (DvbSub * dvb_sub, DVBSubRegion * region)
212 const DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
213 DVBSubObject *object, *obj2;
214 DVBSubObject **obj2_ptr;
215 DVBSubObjectDisplay *display, *obj_disp, **obj_disp_ptr;
217 while (region->display_list) {
218 display = region->display_list;
220 object = get_object (dvb_sub, display->object_id);
223 obj_disp_ptr = &object->display_list;
224 obj_disp = *obj_disp_ptr;
226 while (obj_disp && obj_disp != display) {
227 obj_disp_ptr = &obj_disp->object_list_next;
228 obj_disp = *obj_disp_ptr;
232 *obj_disp_ptr = obj_disp->object_list_next;
234 if (!object->display_list) {
235 obj2_ptr = (DVBSubObject **) & priv->object_list; /* FIXME: Evil casting */
238 while (obj2 != object) {
240 obj2_ptr = &obj2->next;
244 *obj2_ptr = obj2->next;
246 g_slice_free (DVBSubObject, obj2);
251 region->display_list = display->region_list_next;
253 g_slice_free (DVBSubObjectDisplay, display);
258 delete_state (DvbSub * dvb_sub)
260 DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
261 DVBSubRegion *region;
263 while (priv->region_list) {
264 region = priv->region_list;
266 priv->region_list = region->next;
268 delete_region_display_list (dvb_sub, region);
270 g_free (region->pbuf);
272 g_slice_free (DVBSubRegion, region);
275 g_slice_free_chain (DVBSubCLUT, priv->clut_list, next);
276 priv->clut_list = NULL;
278 /* Should already be null */
279 if (priv->object_list)
280 g_warning ("Memory deallocation error!");
284 dvb_sub_init (DvbSub * self)
288 self->private_data = priv = DVB_SUB_GET_PRIVATE (self);
290 /* TODO: Add initialization code here */
291 /* FIXME: Do we have a reason to initiate the members to zero, or are we guaranteed that anyway? */
292 priv->region_list = NULL;
293 priv->object_list = NULL;
294 priv->page_time_out = 0; /* FIXME: Maybe 255 instead? */
295 priv->pes_buffer = g_string_new (NULL);
297 /* display/window information */
298 priv->display_def.version = -1;
299 priv->display_def.window_flag = 0;
300 priv->display_def.display_width = 720;
301 priv->display_def.display_height = 576;
305 dvb_sub_finalize (GObject * object)
307 DvbSub *self = DVB_SUB (object);
308 DvbSubPrivate *priv = (DvbSubPrivate *) self->private_data;
309 /* TODO: Add deinitalization code here */
310 /* FIXME: Clear up region_list contents */
311 delete_state (self); /* close_pid should have called this, but lets be sure */
312 g_string_free (priv->pes_buffer, TRUE);
314 G_OBJECT_CLASS (dvb_sub_parent_class)->finalize (object);
317 /* init static data necessary for ffmpeg-colorspace conversion */
319 dsputil_static_init (void)
323 for (i = 0; i < 256; i++)
324 ff_cropTbl[i + MAX_NEG_CROP] = i;
325 for (i = 0; i < MAX_NEG_CROP; i++) {
327 ff_cropTbl[i + MAX_NEG_CROP + 256] = 255;
332 dvb_sub_class_init (DvbSubClass * klass)
334 int i, r, g, b, a = 0;
335 GObjectClass *object_class = (GObjectClass *) klass;
337 object_class->finalize = dvb_sub_finalize;
339 g_type_class_add_private (klass, sizeof (DvbSubPrivate));
341 dsputil_static_init (); /* Initializes ff_cropTbl table, used in YUV_TO_RGB conversion */
343 /* Initialize the static default_clut structure, from which other clut
344 * structures are initialized from (to start off with default CLUTs
345 * as defined in the specification). */
346 default_clut.id = -1;
348 default_clut.clut4[0] = RGBA (0, 0, 0, 0);
349 default_clut.clut4[1] = RGBA (255, 255, 255, 255);
350 default_clut.clut4[2] = RGBA (0, 0, 0, 255);
351 default_clut.clut4[3] = RGBA (127, 127, 127, 255);
353 default_clut.clut16[0] = RGBA (0, 0, 0, 0);
354 for (i = 1; i < 16; i++) {
356 r = (i & 1) ? 255 : 0;
357 g = (i & 2) ? 255 : 0;
358 b = (i & 4) ? 255 : 0;
360 r = (i & 1) ? 127 : 0;
361 g = (i & 2) ? 127 : 0;
362 b = (i & 4) ? 127 : 0;
364 default_clut.clut16[i] = RGBA (r, g, b, 255);
367 default_clut.clut256[0] = RGBA (0, 0, 0, 0);
368 for (i = 1; i < 256; i++) {
370 r = (i & 1) ? 255 : 0;
371 g = (i & 2) ? 255 : 0;
372 b = (i & 4) ? 255 : 0;
377 r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
378 g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
379 b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
383 r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
384 g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
385 b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
389 r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
390 g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
391 b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
395 r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
396 g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
397 b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
402 default_clut.clut256[i] = RGBA (r, g, b, a);
407 _dvb_sub_parse_page_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
409 { /* FIXME: Use guint for buf_size here and in many other places? */
410 DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
411 DVBSubRegionDisplay *display;
412 DVBSubRegionDisplay *tmp_display_list, **tmp_ptr;
414 const guint8 *buf_end = buf + buf_size;
418 #ifndef GST_DISABLE_GST_DEBUG
419 static int counter = 0; /* FIXME: static counter? I think not.. */
425 priv->page_time_out = *buf++;
426 page_state = ((*buf++) >> 2) & 3;
428 #ifndef GST_DISABLE_GST_DEBUG
430 static const gchar *page_state_str[4] = {
431 "Normal case", "ACQUISITION POINT", "Mode Change", "RESERVED"
435 GST_DEBUG ("PAGE: %d: page_id = %u, length = %d, page_time_out = %u secs, "
436 "page_state = %s", counter, page_id, buf_size, priv->page_time_out,
437 page_state_str[page_state]);
441 if (page_state == 2) { /* Mode change */
442 delete_state (dvb_sub);
445 tmp_display_list = priv->display_list;
446 priv->display_list = NULL;
447 priv->display_list_size = 0;
449 while (buf + 5 < buf_end) {
453 display = tmp_display_list;
454 tmp_ptr = &tmp_display_list;
456 while (display && display->region_id != region_id) {
457 tmp_ptr = &display->next;
458 display = display->next;
462 display = g_slice_new0 (DVBSubRegionDisplay);
464 display->region_id = region_id;
466 display->x_pos = GST_READ_UINT16_BE (buf);
468 display->y_pos = GST_READ_UINT16_BE (buf);
471 *tmp_ptr = display->next;
473 display->next = priv->display_list;
474 priv->display_list = display;
475 priv->display_list_size++;
477 GST_LOG ("PAGE %d: REGION information: ID = %u, address = %ux%u",
478 counter, region_id, display->x_pos, display->y_pos);
481 while (tmp_display_list) {
482 display = tmp_display_list;
484 tmp_display_list = display->next;
486 g_slice_free (DVBSubRegionDisplay, display);
491 _dvb_sub_parse_region_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
494 DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
496 const guint8 *buf_end = buf + buf_size;
499 DVBSubRegion *region;
500 DVBSubObject *object;
501 DVBSubObjectDisplay *object_display;
509 region = get_region (dvb_sub, region_id);
511 if (!region) { /* Create a new region */
512 region = g_slice_new0 (DVBSubRegion);
513 region->id = region_id;
514 region->next = priv->region_list;
515 priv->region_list = region;
518 fill = ((*buf++) >> 3) & 1;
520 region->width = GST_READ_UINT16_BE (buf);
522 region->height = GST_READ_UINT16_BE (buf);
525 if (region->width * region->height != region->buf_size) { /* FIXME: Read closer from spec what happens when dimensions change */
527 g_free (region->pbuf);
529 region->buf_size = region->width * region->height;
531 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 */
533 fill = 1; /* FIXME: Validate from spec that fill is forced on (in the following codes context) when dimensions change */
536 region->depth = 1 << (((*buf++) >> 2) & 7);
537 if (region->depth < 2 || region->depth > 8) {
538 GST_WARNING ("region depth %d is invalid", region->depth);
539 region->depth = 4; /* FIXME: Check from spec this is the default? */
542 region->clut = *buf++;
544 if (region->depth == 8)
545 region->bgcolor = *buf++;
549 if (region->depth == 4)
550 region->bgcolor = (((*buf++) >> 4) & 15);
552 region->bgcolor = (((*buf++) >> 2) & 3);
555 GST_DEBUG ("REGION: id = %u, (%ux%u)@%u-bit", region_id, region->width,
556 region->height, region->depth);
559 memset (region->pbuf, region->bgcolor, region->buf_size);
560 GST_DEBUG ("REGION: filling region (%u) with bgcolor = %u", region->id,
564 delete_region_display_list (dvb_sub, region); /* Delete the region display list for current region - FIXME: why? */
566 while (buf + 6 <= buf_end) {
567 object_id = GST_READ_UINT16_BE (buf);
570 object = get_object (dvb_sub, object_id);
573 object = g_slice_new0 (DVBSubObject);
575 object->id = object_id;
577 object->next = priv->object_list;
578 priv->object_list = object;
581 object->type = (*buf) >> 6;
583 object_display = g_slice_new0 (DVBSubObjectDisplay);
585 object_display->object_id = object_id;
586 object_display->region_id = region_id;
588 object_display->x_pos = GST_READ_UINT16_BE (buf) & 0xfff;
590 object_display->y_pos = GST_READ_UINT16_BE (buf) & 0xfff;
593 if ((object->type == 1 || object->type == 2) && buf + 2 <= buf_end) {
594 object_display->fgcolor = *buf++;
595 object_display->bgcolor = *buf++;
598 object_display->region_list_next = region->display_list;
599 region->display_list = object_display;
601 object_display->object_list_next = object->display_list;
602 object->display_list = object_display;
604 GST_DEBUG ("REGION DATA: object_id = %u, region_id = %u, pos = %ux%u, "
605 "obj_type = %u", object->id, region->id, object_display->x_pos,
606 object_display->y_pos, object->type);
608 if (object->type == 1 || object->type == 2) {
609 GST_DEBUG ("REGION DATA: fgcolor = %u, bgcolor = %u",
610 object_display->fgcolor, object_display->bgcolor);
616 _dvb_sub_parse_clut_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
619 DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
621 const guint8 *buf_end = buf + buf_size;
624 int entry_id, depth, full_range;
625 int y, cr, cb, alpha;
626 int r, g, b, r_add, g_add, b_add;
628 GST_MEMDUMP ("DVB clut packet", buf, buf_size);
633 clut = get_clut (dvb_sub, clut_id);
636 clut = g_slice_new (DVBSubCLUT); /* FIXME-MEMORY-LEAK: This seems to leak per valgrind */
638 memcpy (clut, &default_clut, sizeof (DVBSubCLUT));
642 clut->next = priv->clut_list;
643 priv->clut_list = clut;
646 while (buf + 4 < buf_end) {
649 depth = (*buf) & 0xe0;
652 GST_WARNING ("Invalid clut depth 0x%x!", *buf);
656 full_range = (*buf++) & 1;
665 cr = (((buf[0] & 3) << 2) | ((buf[1] >> 6) & 3)) << 4;
666 cb = (buf[1] << 2) & 0xf0;
667 alpha = (buf[1] << 6) & 0xc0;
675 YUV_TO_RGB1_CCIR (cb, cr);
676 YUV_TO_RGB2_CCIR (r, g, b, y);
678 GST_DEBUG ("CLUT DEFINITION: clut %d := (%d,%d,%d,%d)", entry_id, r, g, b,
682 clut->clut4[entry_id] = RGBA (r, g, b, 255 - alpha);
684 clut->clut16[entry_id] = RGBA (r, g, b, 255 - alpha);
686 clut->clut256[entry_id] = RGBA (r, g, b, 255 - alpha);
690 // FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
691 // FFMPEG-FIXME: refactoring as done here
693 _dvb_sub_read_2bit_string (guint8 * destbuf, gint dbuf_len,
694 const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
696 GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
697 /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
699 gboolean stop_parsing = FALSE;
701 guint32 pixels_read = 0;
703 static gboolean warning_shown = FALSE;
704 if (!warning_shown) {
705 g_warning ("Parsing 2bit color DVB sub-picture. This is not tested at all. "
706 "If you see this message, please provide the developers with sample "
707 "media with these subtitles, if possible.");
708 warning_shown = TRUE;
711 GST_TRACE ("dbuf_len = %d", dbuf_len);
713 while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
714 guint run_length = 0, clut_index = 0;
715 gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
717 if (bits) { /* 2-bit_pixel-code */
720 } else { /* 2-bit_zero */
721 gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
722 if (bits == 1) { /* switch_1 == '1' */
723 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 3);
725 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
726 } else { /* switch_1 == '0' */
727 gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
728 if (bits == 1) { /* switch_2 == '1' */
729 run_length = 1; /* 1x pseudo-colour '00' */
730 } else { /* switch_2 == '0' */
731 gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
732 switch (bits) { /* switch_3 */
733 case 0x0: /* end of 2-bit/pixel_code_string */
736 case 0x1: /* two pixels shall be set to pseudo colour (entry) '00' */
739 case 0x2: /* the following 6 bits contain run length coded pixel data */
740 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 4);
742 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
744 case 0x3: /* the following 10 bits contain run length coded pixel data */
745 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 8);
747 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 2);
754 /* If run_length is zero, continue. Only case happening is when
755 * stop_parsing is TRUE too, so next cycle shouldn't run */
759 /* Trim the run_length to not go beyond the line end and consume
760 * it from remaining length of dest line */
761 run_length = MIN (run_length, dbuf_len);
762 dbuf_len -= run_length;
764 /* Make clut_index refer to the index into the desired bit depths
765 * CLUT definition table */
767 clut_index = map_table[clut_index]; /* now clut_index signifies the index into map_table dest */
769 /* Now we can simply memset run_length count of destination bytes
770 * to clut_index, but only if not non_modifying */
771 GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer, "
772 "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
774 if (!(non_mod == 1 && bits == 1))
775 memset (destbuf, clut_index, run_length);
777 destbuf += run_length;
778 pixels_read += run_length;
781 // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
782 //gst_bit_reader_skip_to_byte (&gb);
783 *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
785 GST_TRACE ("PIXEL: returning, read %u pixels", pixels_read);
786 // FIXME: Shouldn't need this variable if tracking things in the loop better
790 // FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
791 // FFMPEG-FIXME: refactoring as done here, explained in commit 895296c3
793 _dvb_sub_read_4bit_string (guint8 * destbuf, gint dbuf_len,
794 const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
796 GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
797 /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
798 gboolean stop_parsing = FALSE;
800 guint32 pixels_read = 0;
802 GST_TRACE ("RUNLEN: srcbuf position %p, buf_size = %d; destination buffer "
803 "size is %d @ %p", *srcbuf, buf_size, dbuf_len, destbuf);
805 while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
806 guint run_length = 0, clut_index = 0;
807 gst_bit_reader_get_bits_uint32 (&gb, &bits, 4);
813 gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
814 if (bits == 0) { /* switch_1 == '0' */
815 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 3);
821 } else { /* switch_1 == '1' */
822 gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
823 if (bits == 0) { /* switch_2 == '0' */
824 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 2);
826 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
827 } else { /* switch_2 == '1' */
828 gst_bit_reader_get_bits_uint32 (&gb, &bits, 2);
830 case 0x0: /* switch_3 == '00' */
831 run_length = 1; /* 1 pixel of pseudo-color 0 */
833 case 0x1: /* switch_3 == '01' */
834 run_length = 2; /* 2 pixels of pseudo-color 0 */
836 case 0x2: /* switch_3 == '10' */
837 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 4);
839 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
841 case 0x3: /* switch_3 == '11' */
842 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 8);
844 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 4);
851 /* If run_length is zero, continue. Only case happening is when
852 * stop_parsing is TRUE too, so next cycle shouldn't run */
856 /* Trim the run_length to not go beyond the line end and consume
857 * it from remaining length of dest line */
858 run_length = MIN (run_length, dbuf_len);
859 dbuf_len -= run_length;
861 /* Make clut_index refer to the index into the desired bit depths
862 * CLUT definition table */
864 clut_index = map_table[clut_index]; /* now clut_index signifies the index into map_table dest */
866 /* Now we can simply memset run_length count of destination bytes
867 * to clut_index, but only if not non_modifying */
868 GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer; "
869 "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
871 if (!(non_mod == 1 && bits == 1))
872 memset (destbuf, clut_index, run_length);
874 destbuf += run_length;
875 pixels_read += run_length;
878 // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
879 //gst_bit_reader_skip_to_byte (&gb);
880 *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
882 GST_LOG ("Returning with %u pixels read", pixels_read);
884 // FIXME: Shouldn't need this variable if tracking things in the loop better
889 _dvb_sub_read_8bit_string (guint8 * destbuf, gint dbuf_len,
890 const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
892 GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
893 /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
895 gboolean stop_parsing = FALSE;
897 guint32 pixels_read = 0;
899 static gboolean warning_shown = FALSE;
900 if (!warning_shown) {
902 ("Parsing 8bit color DVB sub-picture. This is not tested at all. If you see this message, "
903 "please provide the developers with sample media with these subtitles, if possible.");
904 warning_shown = TRUE;
907 GST_LOG ("dbuf_len = %d", dbuf_len);
909 /* FFMPEG-FIXME: ffmpeg uses a manual byte walking algorithm, which might be more performant,
910 * FFMPEG-FIXME: but it does almost absolutely no buffer length checking, so could walk over
911 * FFMPEG-FIXME: memory boundaries. While we don't check gst_bit_reader_get_bits_uint32
912 * FFMPEG-FIXME: return values either and therefore might get some pixels corrupted, we at
913 * FFMPEG-FIXME: lest have no chance of reading memory we don't own and visual corruption
914 * FFMPEG-FIXME: is guaranteed anyway when not all bytes are present */
915 /* Rephrased - it's better to work with bytes with default value '0' instead of reading from memory we don't own. */
916 while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 0)) {
917 guint run_length = 0, clut_index = 0;
918 gst_bit_reader_get_bits_uint32 (&gb, &bits, 8);
920 if (bits) { /* 8-bit_pixel-code */
923 } else { /* 8-bit_zero */
924 gst_bit_reader_get_bits_uint32 (&gb, &bits, 1);
925 if (bits == 0) { /* switch_1 == '0' */
926 /* run_length_1-127 for pseudo-colour _entry) '0x00' */
927 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 7);
928 if (run_length == 0) { /* end_of_string_signal */
931 } else { /* switch_1 == '1' */
932 /* run_length_3-127 */
933 gst_bit_reader_get_bits_uint32 (&gb, &run_length, 7);
934 gst_bit_reader_get_bits_uint32 (&gb, &clut_index, 8);
936 if (run_length < 3) {
937 GST_WARNING ("runlength value was %u, but the spec requires it "
938 "must be >=3", run_length);
943 /* If run_length is zero, continue. Only case happening is when
944 * stop_parsing is TRUE too, so next cycle shouldn't run */
948 /* Trim the run_length to not go beyond the line end and consume
949 * it from remaining length of dest line */
950 run_length = MIN (run_length, dbuf_len);
951 dbuf_len -= run_length;
953 /* Make clut_index refer to the index into the desired bit depths
954 * CLUT definition table */
956 clut_index = map_table[clut_index]; /* now clut_index signifies the index into map_table dest */
958 /* Now we can simply memset run_length count of destination bytes
959 * to clut_index, but only if not non_modifying */
960 GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer; "
961 "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
963 if (!(non_mod == 1 && bits == 1))
964 memset (destbuf, clut_index, run_length);
966 destbuf += run_length;
967 pixels_read += run_length;
970 GST_LOG ("Returning with %u pixels read", pixels_read);
972 // FIXME: Shouldn't need this variable if tracking things in the loop better
977 _dvb_sub_parse_pixel_data_block (DvbSub * dvb_sub,
978 DVBSubObjectDisplay * display, const guint8 * buf, gint buf_size,
979 DvbSubPixelDataSubBlockFieldType top_bottom, guint8 non_mod)
981 DVBSubRegion *region = get_region (dvb_sub, display->region_id);
982 const guint8 *buf_end = buf + buf_size;
986 gboolean dest_buf_filled = FALSE;
988 guint8 map2to4[] = { 0x0, 0x7, 0x8, 0xf };
989 guint8 map2to8[] = { 0x00, 0x77, 0x88, 0xff };
990 guint8 map4to8[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
991 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
995 GST_LOG ("DVB pixel block size %d, %s field:", buf_size,
996 top_bottom ? "bottom" : "top");
998 GST_MEMDUMP ("packet", buf, buf_size);
1000 if (region == NULL) {
1001 GST_LOG ("Region is NULL, returning");
1005 pbuf = region->pbuf;
1007 x_pos = display->x_pos;
1008 y_pos = display->y_pos;
1010 if ((y_pos & 1) != top_bottom)
1013 while (buf < buf_end) {
1014 GST_LOG ("Iteration start, %u bytes missing from end; buf = %p, "
1015 "buf_end = %p; Region is number %u, with a dimension of %dx%d; "
1016 "We are at position %dx%d", (guint) (buf_end - buf), buf, buf_end,
1017 region->id, region->width, region->height, x_pos, y_pos);
1019 // FFMPEG-FIXME: ffmpeg doesn't check for equality and so can overflow destination buffer later on with bad input data
1020 // 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
1021 // FIXME: Removed x_pos checking here, because we don't want to turn dest_buf_filled to TRUE permanently in that case
1022 // FIXME: We assume that region->width - x_pos as dbuf_len to read_nbit_string will take care of that case nicely;
1023 // FIXME: That is, that read_nbit_string never scribbles anything if dbuf_len passed to it is zero due to this.
1024 if (y_pos >= region->height) {
1025 dest_buf_filled = TRUE;
1030 if (dest_buf_filled) {
1031 /* FIXME: Be more verbose */
1032 GST_WARNING ("Invalid object location for data_type 0x%x!",
1034 GST_MEMDUMP ("Remaining data after invalid object location:", buf,
1035 (guint) (buf_end - buf));
1039 if (region->depth == 8)
1040 map_table = map2to8;
1041 else if (region->depth == 4)
1042 map_table = map2to4;
1046 // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
1047 // FFMPEG-FIXME: therefore potentially walk over the memory area we own
1049 _dvb_sub_read_2bit_string (pbuf + (y_pos * region->width) + x_pos,
1050 region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
1053 if (dest_buf_filled) {
1054 /* FIXME: Be more verbose */
1055 GST_WARNING ("Invalid object location for data_type 0x%x!",
1057 GST_MEMDUMP ("Remaining data after invalid object location:", buf,
1059 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)
1062 if (region->depth < 4) {
1063 GST_WARNING ("4-bit pixel string in %d-bit region!", region->depth);
1067 if (region->depth == 8)
1068 map_table = map4to8;
1072 GST_LOG ("READ_4BIT_STRING: String data into position %dx%d; "
1073 "buf before is %p", x_pos, y_pos, buf);
1074 // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
1075 // FFMPEG-FIXME: therefore potentially walk over the memory area we own
1077 _dvb_sub_read_4bit_string (pbuf + (y_pos * region->width) + x_pos,
1078 region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
1079 GST_DEBUG ("READ_4BIT_STRING finished: buf pointer now %p", buf);
1082 if (dest_buf_filled) {
1083 /* FIXME: Be more verbose */
1084 GST_WARNING ("Invalid object location for data_type 0x%x!",
1086 GST_MEMDUMP ("Remaining data after invalid object location:",
1087 buf, (guint) (buf_end - buf));
1091 if (region->depth < 8) {
1092 GST_WARNING ("8-bit pixel string in %d-bit region!", region->depth);
1095 // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
1096 // FFMPEG-FIXME: therefore potentially walk over the memory area we own
1098 _dvb_sub_read_8bit_string (pbuf + (y_pos * region->width) + x_pos,
1099 region->width - x_pos, &buf, buf_end - buf, non_mod, NULL);
1103 GST_DEBUG ("handling map2to4 table data");
1104 /* FIXME: I don't see any guards about buffer size here - buf++ happens with the switch, but
1105 * FIXME: buffer is walked without length checks? Same deal in other map table cases */
1106 map2to4[0] = (*buf) >> 4;
1107 map2to4[1] = (*buf++) & 0xf;
1108 map2to4[2] = (*buf) >> 4;
1109 map2to4[3] = (*buf++) & 0xf;
1112 GST_DEBUG ("handling map2to8 table data");
1113 for (i = 0; i < 4; i++)
1114 map2to8[i] = *buf++;
1117 GST_DEBUG ("handling map4to8 table data");
1118 for (i = 0; i < 16; i++)
1119 map4to8[i] = *buf++;
1122 GST_DEBUG ("end of object line code encountered");
1123 x_pos = display->x_pos;
1127 /* FIXME: Do we consume word align stuffing byte that could follow top/bottom data? */
1128 GST_WARNING ("Unknown/unsupported pixel block 0x%x", *(buf - 1));
1134 _dvb_sub_parse_object_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
1137 const guint8 *buf_end = buf + buf_size;
1139 DVBSubObject *object;
1141 guint8 coding_method, non_modifying_color;
1143 object_id = GST_READ_UINT16_BE (buf);
1146 object = get_object (dvb_sub, object_id);
1148 GST_DEBUG ("OBJECT: a new object segment has occurred for object_id = %u",
1152 GST_WARNING ("Nothing known about object with ID %u yet, bailing out",
1157 coding_method = ((*buf) >> 2) & 3;
1158 non_modifying_color = ((*buf++) >> 1) & 1;
1160 if (coding_method == 0) {
1161 const guint8 *block;
1162 DVBSubObjectDisplay *display;
1163 guint16 top_field_len, bottom_field_len;
1165 top_field_len = GST_READ_UINT16_BE (buf);
1167 bottom_field_len = GST_READ_UINT16_BE (buf);
1170 if (buf + top_field_len + bottom_field_len > buf_end) {
1171 GST_WARNING ("Field data size too large");
1175 /* FIXME: Potential optimization opportunity here - parse the object pixmap only once, and copy it to all the
1176 * FIXME: regions that need it. One object being in multiple regions is a rare occurrence in real life, however */
1177 for (display = object->display_list; display;
1178 display = display->object_list_next) {
1181 GST_DEBUG ("OBJECT: parsing top and bottom part of object id %d; "
1182 "top_field_len = %u, bottom_field_len = %u",
1183 display->object_id, top_field_len, bottom_field_len);
1185 _dvb_sub_parse_pixel_data_block (dvb_sub, display, block, top_field_len,
1186 TOP_FIELD, non_modifying_color);
1188 if (bottom_field_len > 0)
1189 block = buf + top_field_len;
1191 bottom_field_len = top_field_len;
1193 _dvb_sub_parse_pixel_data_block (dvb_sub, display, block,
1194 bottom_field_len, BOTTOM_FIELD, non_modifying_color);
1197 } else if (coding_method == 1) {
1198 GST_FIXME ("'a string of characters' coding method not supported yet!");
1200 GST_WARNING ("Unknown object coding 0x%x", coding_method);
1205 _dvb_sub_parse_display_definition_segment (DvbSub * dvb_sub, guint8 * buf,
1208 int dds_version, info_byte;
1209 DvbSubPrivate *ctx = dvb_sub->private_data;
1215 dds_version = info_byte >> 4;
1217 if (ctx->display_def.version == dds_version)
1218 return 0; /* already have this display definition version */
1220 ctx->display_def.version = dds_version;
1221 ctx->display_def.display_width = GST_READ_UINT16_BE (buf) + 1;
1223 ctx->display_def.display_height = GST_READ_UINT16_BE (buf) + 1;
1226 ctx->display_def.window_flag = info_byte & 1 << 3;
1228 if (buf_size >= 13 && ctx->display_def.window_flag) {
1229 ctx->display_def.window_x = GST_READ_UINT16_BE (buf);
1231 ctx->display_def.window_y = GST_READ_UINT16_BE (buf);
1233 ctx->display_def.window_width =
1234 GST_READ_UINT16_BE (buf) - ctx->display_def.window_x + 1;
1236 ctx->display_def.window_height =
1237 GST_READ_UINT16_BE (buf) - ctx->display_def.window_y + 1;
1245 _dvb_sub_parse_end_of_display_set (DvbSub * dvb_sub, guint16 page_id,
1246 guint8 * buf, gint buf_size, guint64 pts)
1248 DvbSubPrivate *priv = (DvbSubPrivate *) dvb_sub->private_data;
1250 DVBSubtitles *sub = g_slice_new0 (DVBSubtitles);
1252 DVBSubRegion *region;
1253 DVBSubRegionDisplay *display;
1254 DVBSubtitleRect *rect;
1256 guint32 *clut_table;
1259 static unsigned counter = 0; /* DEBUG use only *//* FIXME: get rid */
1261 GST_DEBUG ("DISPLAY SET END: page_id = %u, length = %d", page_id, buf_size);
1264 #if 0 /* FIXME: PTS stuff not figured out yet */
1265 sub->start_display_time = 0;
1266 sub->end_display_time = priv->page_time_out * 1000;
1267 sub->format = 0; /* 0 = graphics */
1270 sub->num_rects = priv->display_list_size;
1272 if (sub->num_rects > 0) {
1273 // FIXME-MEMORY-LEAK: This structure is not freed up yet
1274 sub->rects = g_malloc0 (sizeof (*sub->rects) * sub->num_rects); /* GSlice? */
1275 for (i = 0; i < sub->num_rects; i++)
1276 sub->rects[i] = g_malloc0 (sizeof (*sub->rects[i])); /* GSlice? */
1281 /* copy subtitle display and window information */
1282 sub->display_def = priv->display_def;
1284 for (display = priv->display_list; display; display = display->next) {
1285 region = get_region (dvb_sub, display->region_id);
1286 rect = sub->rects[i];
1291 rect->x = display->x_pos;
1292 rect->y = display->y_pos;
1293 rect->w = region->width;
1294 rect->h = region->height;
1295 #if 0 /* FIXME: Don't think we need to save the number of colors in the palette when we are saving as RGBA? */
1296 rect->nb_colors = 16;
1298 #if 0 /* FIXME: Needed to be specified once we support strings of characters based subtitles */
1299 rect->type = SUBTITLE_BITMAP;
1301 rect->pict.rowstride = region->width;
1302 rect->pict.palette_bits_count = region->depth;
1304 clut = get_clut (dvb_sub, region->clut);
1307 clut = &default_clut;
1309 switch (region->depth) {
1311 clut_table = clut->clut4;
1314 clut_table = clut->clut256;
1318 clut_table = clut->clut16;
1322 /* FIXME: Tweak this to be saved in a format most suitable for Qt and GStreamer instead.
1323 * Currently kept in AVPicture for quick save_display_set testing */
1324 rect->pict.palette = g_malloc ((1 << region->depth) * sizeof (guint32)); /* FIXME: Can we use GSlice here? */
1325 memcpy (rect->pict.palette, clut_table,
1326 (1 << region->depth) * sizeof (guint32));
1328 GST_MEMDUMP ("rect->pict.data.palette content",
1329 (guint8 *) rect->pict.palette, (1 << region->depth) * sizeof (guint32));
1331 rect->pict.data = g_malloc (region->buf_size); /* FIXME: Can we use GSlice here? */
1332 memcpy (rect->pict.data, region->pbuf, region->buf_size);
1335 GST_DEBUG ("DISPLAY: an object rect created: number %u, iteration %u, "
1336 "pos: %d:%d, size: %dx%d", counter, i, rect->x, rect->y, rect->w,
1339 GST_MEMDUMP ("rect->pict.data content", rect->pict.data, region->buf_size);
1345 sub->page_time_out = priv->page_time_out;
1348 if (priv->callbacks.new_data) {
1349 priv->callbacks.new_data (dvb_sub, sub, priv->user_data);
1351 /* No-one responsible to clean up memory, so do it ourselves */
1352 /* FIXME: Just don't bother with all this palette image creation in the first place then... */
1353 dvb_subtitles_free (sub);
1356 return 1; /* FIXME: The caller of this function is probably supposed to do something with the return value */
1360 dvb_subtitles_free (DVBSubtitles * sub)
1363 DVBSubtitleRect *rect;
1368 /* Now free up all the temporary memory we allocated */
1369 for (i = 0; i < sub->num_rects; ++i) {
1370 rect = sub->rects[i];
1372 g_free (rect->pict.palette);
1373 g_free (rect->pict.data);
1376 g_free (sub->rects);
1377 g_slice_free (DVBSubtitles, sub);
1383 * Creates a new #DvbSub.
1385 * Return value: a newly created #DvbSub
1390 DvbSub *dvbsub = g_object_new (DVB_TYPE_SUB, NULL);
1395 #define DVB_SUB_SEGMENT_PAGE_COMPOSITION 0x10
1396 #define DVB_SUB_SEGMENT_REGION_COMPOSITION 0x11
1397 #define DVB_SUB_SEGMENT_CLUT_DEFINITION 0x12
1398 #define DVB_SUB_SEGMENT_OBJECT_DATA 0x13
1399 #define DVB_SUB_SEGMENT_DISPLAY_DEFINITION 0x14
1400 #define DVB_SUB_SEGMENT_END_OF_DISPLAY_SET 0x80
1401 #define DVB_SUB_SEGMENT_STUFFING 0xFF
1403 #define DVB_SUB_SYNC_BYTE 0x0f
1405 * dvb_sub_feed_with_pts:
1406 * @dvb_sub: a #DvbSub
1407 * @pts: The PTS of the data
1408 * @data: The data to feed to the parser
1409 * @len: Length of the data
1411 * Feeds the DvbSub parser with new binary data to parse,
1412 * with an associated PTS value. E.g, data left after PES
1413 * packet header has been already parsed, which contains
1414 * the PTS information).
1416 * Return value: -1 if data was unhandled (e.g, not a subtitle packet),
1417 * -2 if data parsing was unsuccesful (e.g, length was invalid),
1418 * 0 or positive if data was handled. If positive, then amount of data consumed on success. FIXME: List the positive return values.
1421 dvb_sub_feed_with_pts (DvbSub * dvb_sub, guint64 pts, guint8 * data, gint len)
1423 unsigned int pos = 0;
1424 guint8 segment_type;
1425 guint16 segment_len;
1428 GST_DEBUG ("pts=%" G_GUINT64_FORMAT " and length %d", pts, len);
1430 g_return_val_if_fail (data != NULL, -1);
1432 if (len <= 3) { /* len(0x20 0x00 end_of_PES_data_field_marker) */
1433 GST_WARNING ("Data length too short");
1437 if (data[pos++] != 0x20) {
1438 GST_WARNING ("Tried to handle a PES packet private data that isn't a "
1439 "subtitle packet (does not start with 0x20)");
1443 if (data[pos++] != 0x00) {
1444 GST_WARNING ("'Subtitle stream in this PES packet' was not 0x00, so this "
1445 "is in theory not a DVB subtitle stream (but some other subtitle "
1446 "standard?); bailing out");
1450 while (data[pos++] == DVB_SUB_SYNC_BYTE) {
1451 if ((len - pos) < (2 * 2 + 1)) {
1452 GST_WARNING ("Data after SYNC BYTE too short, less than needed to "
1453 "even get to segment_length");
1456 segment_type = data[pos++];
1457 GST_DEBUG ("=== Segment type is 0x%x", segment_type);
1458 page_id = (data[pos] << 8) | data[pos + 1];
1459 GST_DEBUG ("page_id is 0x%x", page_id);
1461 segment_len = (data[pos] << 8) | data[pos + 1];
1462 GST_DEBUG ("segment_length is %d (0x%x 0x%x)", segment_len, data[pos],
1465 if ((len - pos) < segment_len) {
1466 GST_WARNING ("segment_length was told to be %u, but we only have "
1467 "%d bytes left", segment_len, len - pos);
1470 // TODO: Parse the segment per type (this is probably a leftover TODO that is now done?)
1471 /* FIXME: Handle differing PTS values - all segments of a given display set must be with the same PTS,
1472 * FIXME: but we let it slip and just take it for granted in end_of_display_set */
1473 switch (segment_type) {
1474 case DVB_SUB_SEGMENT_PAGE_COMPOSITION:
1475 GST_DEBUG ("Page composition segment at buffer pos %u", pos);
1476 _dvb_sub_parse_page_segment (dvb_sub, page_id, data + pos, segment_len); /* FIXME: Not sure about args */
1478 case DVB_SUB_SEGMENT_REGION_COMPOSITION:
1479 GST_DEBUG ("Region composition segment at buffer pos %u", pos);
1480 _dvb_sub_parse_region_segment (dvb_sub, page_id, data + pos, segment_len); /* FIXME: Not sure about args */
1482 case DVB_SUB_SEGMENT_CLUT_DEFINITION:
1483 GST_DEBUG ("CLUT definition segment at buffer pos %u", pos);
1484 _dvb_sub_parse_clut_segment (dvb_sub, page_id, data + pos, segment_len); /* FIXME: Not sure about args */
1486 case DVB_SUB_SEGMENT_OBJECT_DATA:
1487 GST_DEBUG ("Object data segment at buffer pos %u", pos);
1488 _dvb_sub_parse_object_segment (dvb_sub, page_id, data + pos, segment_len); /* FIXME: Not sure about args */
1490 case DVB_SUB_SEGMENT_DISPLAY_DEFINITION:
1491 GST_DEBUG ("display definition segment at buffer pos %u", pos);
1492 _dvb_sub_parse_display_definition_segment (dvb_sub, data + pos,
1495 case DVB_SUB_SEGMENT_END_OF_DISPLAY_SET:
1496 GST_DEBUG ("End of display set at buffer pos %u", pos);
1497 _dvb_sub_parse_end_of_display_set (dvb_sub, page_id, data + pos, segment_len, pts); /* FIXME: Not sure about args */
1500 GST_FIXME ("Unhandled segment type 0x%x", segment_type);
1507 GST_WARNING ("Data ended without a PES data end marker");
1512 GST_LOG ("Processed %d bytes out of %d", pos, len);
1517 * dvb_sub_set_callbacks:
1518 * @dvb_sub: a #DvbSub
1519 * @callbacks: the callbacks to install
1520 * @user_data: a user_data argument for the callback
1522 * Set callback which will be executed when new subpictures are available.
1525 dvb_sub_set_callbacks (DvbSub * dvb_sub, DvbSubCallbacks * callbacks,
1528 DvbSubPrivate *priv;
1530 g_return_if_fail (dvb_sub != NULL);
1531 g_return_if_fail (DVB_IS_SUB (dvb_sub));
1532 g_return_if_fail (callbacks != NULL);
1534 priv = (DvbSubPrivate *) dvb_sub->private_data;
1536 priv->callbacks = *callbacks;
1537 priv->user_data = user_data;