2e6e48dde7e5599ac23f767fde5cbf14107e9b57
[platform/upstream/gstreamer.git] / gst / dvbsuboverlay / dvb-sub.c
1 /* dvb-sub.c - DVB subtitle decoding
2  * Copyright (C) Mart Raudsepp 2009 <mart.raudsepp@artecdesign.ee>
3  * Copyright (C) 2010 ONELAN Ltd.
4  * 
5  * Heavily uses code algorithms ported from ffmpeg's libavcodec/dvbsubdec.c,
6  * especially the segment parsers. The original license applies to this
7  * ported code and the whole code in this file as well.
8  *
9  * Original copyright information follows:
10  */
11 /*
12  * DVB subtitle decoding for ffmpeg
13  * Copyright (c) 2005 Ian Caulfield
14  *
15  * This file is part of FFmpeg.
16  *
17  * FFmpeg is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU Lesser General Public
19  * License as published by the Free Software Foundation; either
20  * version 2.1 of the License, or (at your option) any later version.
21  *
22  * FFmpeg is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * Lesser General Public License for more details.
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with FFmpeg; if not, write to the Free Software
29  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30  */
31
32 #include <string.h>             /* memset */
33 #include <gst/gstutils.h>       /* GST_READ_UINT16_BE */
34 #include <gst/base/gstbitreader.h>      /* GstBitReader */
35
36 #include "dvb-sub.h"
37
38 GST_DEBUG_CATEGORY_STATIC (dvbsub_debug);
39 #define GST_CAT_DEFAULT dvbsub_debug
40
41 static void dvb_sub_init (void);
42
43 /* FIXME: Are we waiting for an acquisition point before trying to do things? */
44 /* FIXME: In the end convert some of the guint8/16 (especially stack variables) back to gint for access efficiency */
45
46 /**
47  * SECTION:dvb-sub
48  * @title: GstDvbSub
49  * @short_description: a DVB subtitle parsing class
50  * @stability: Unstable
51  *
52  * The #DvbSub represents an object used for parsing a DVB subpicture,
53  * and signalling the API user for new bitmaps to show on screen.
54  */
55
56 #define AYUV(y,u,v,a) (((a) << 24) | ((y) << 16) | ((u) << 8) | (v))
57 #define RGBA_TO_AYUV(r,g,b,a) ((((guint32)(a)) << 24) | ((rgb_to_y(r,g,b)) << 16) | ((rgb_to_u(r,g,b)) << 8) | (rgb_to_v(r,g,b)))
58
59
60 typedef struct DVBSubCLUT
61 {
62   int id;                       /* default_clut uses -1 for this, so guint8 isn't fine without adaptations first */
63
64   guint32 clut4[4];
65   guint32 clut16[16];
66   guint32 clut256[256];
67
68   struct DVBSubCLUT *next;
69 } DVBSubCLUT;
70
71 static DVBSubCLUT default_clut;
72
73 typedef struct DVBSubObjectDisplay
74 {
75   /* FIXME: Use more correct sizes */
76   int object_id;
77   int region_id;
78
79   int x_pos;
80   int y_pos;
81
82   int fgcolor;
83   int bgcolor;
84
85   /* FIXME: Should we use GSList? The relating interaction and pointer assigment is quite complex and perhaps unsuited for a plain GSList anyway */
86   struct DVBSubObjectDisplay *region_list_next;
87   struct DVBSubObjectDisplay *object_list_next;
88 } DVBSubObjectDisplay;
89
90 typedef struct DVBSubObject
91 {
92   /* FIXME: Use more correct sizes */
93   int id;                       /* FIXME: Use guint8 after checking it's fine in all code using it */
94
95   int type;
96
97   /* FIXME: Should we use GSList? */
98   DVBSubObjectDisplay *display_list;
99   struct DVBSubObject *next;
100 } DVBSubObject;
101
102 typedef struct DVBSubRegionDisplay
103 {                               /* FIXME: Figure out if this structure is only used temporarily in page_segment parser, or also more */
104   int region_id;
105
106   int x_pos;
107   int y_pos;
108
109   struct DVBSubRegionDisplay *next;
110 } DVBSubRegionDisplay;
111
112 typedef struct DVBSubRegion
113 {
114   guint8 id;
115   guint16 width;
116   guint16 height;
117   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 */
118
119   guint8 clut;
120   guint8 bgcolor;
121
122   /* FIXME: Validate these fields existence and exact types */
123   guint8 *pbuf;
124   int buf_size;
125
126   DVBSubObjectDisplay *display_list;
127
128   struct DVBSubRegion *next;
129 } DVBSubRegion;
130
131 struct _DvbSub
132 {
133   DvbSubCallbacks callbacks;
134   gpointer user_data;
135
136   guint8 page_time_out;
137   DVBSubRegion *region_list;
138   DVBSubCLUT *clut_list;
139   DVBSubObject *object_list;
140   /* FIXME... */
141   int display_list_size;
142   DVBSubRegionDisplay *display_list;
143   GString *pes_buffer;
144   DVBSubtitleWindow display_def;
145 };
146
147 typedef enum
148 {
149   TOP_FIELD = 0,
150   BOTTOM_FIELD = 1
151 } DvbSubPixelDataSubBlockFieldType;
152
153 static inline gint
154 rgb_to_y (gint r, gint g, gint b)
155 {
156   gint ret;
157
158   ret = (gint) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16));
159   ret = CLAMP (ret, 0, 255);
160   return ret;
161 }
162
163 static inline gint
164 rgb_to_u (gint r, gint g, gint b)
165 {
166   gint ret;
167
168   ret =
169       (gint) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) +
170       128);
171   ret = CLAMP (ret, 0, 255);
172   return ret;
173 }
174
175 static inline gint
176 rgb_to_v (gint r, gint g, gint b)
177 {
178   gint ret;
179
180   ret =
181       (gint) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) +
182       128);
183   ret = CLAMP (ret, 0, 255);
184   return ret;
185 }
186
187 static DVBSubObject *
188 get_object (DvbSub * dvb_sub, guint16 object_id)
189 {
190   DVBSubObject *ptr = dvb_sub->object_list;
191
192   while (ptr && ptr->id != object_id) {
193     ptr = ptr->next;
194   }
195
196   return ptr;
197 }
198
199 static DVBSubCLUT *
200 get_clut (DvbSub * dvb_sub, gint clut_id)
201 {
202   DVBSubCLUT *ptr = dvb_sub->clut_list;
203
204   while (ptr && ptr->id != clut_id) {
205     ptr = ptr->next;
206   }
207
208   return ptr;
209 }
210
211 static DVBSubRegion *
212 get_region (DvbSub * dvb_sub, guint8 region_id)
213 {
214   DVBSubRegion *ptr = dvb_sub->region_list;
215
216   while (ptr && ptr->id != region_id) {
217     ptr = ptr->next;
218   }
219
220   return ptr;
221 }
222
223 static void
224 delete_region_display_list (DvbSub * dvb_sub, DVBSubRegion * region)
225 {
226   DVBSubObject *object, *obj2;
227   DVBSubObject **obj2_ptr;
228   DVBSubObjectDisplay *display, *obj_disp, **obj_disp_ptr;
229
230   while (region->display_list) {
231     display = region->display_list;
232
233     object = get_object (dvb_sub, display->object_id);
234
235     if (object) {
236       obj_disp_ptr = &object->display_list;
237       obj_disp = *obj_disp_ptr;
238
239       while (obj_disp && obj_disp != display) {
240         obj_disp_ptr = &obj_disp->object_list_next;
241         obj_disp = *obj_disp_ptr;
242       }
243
244       if (obj_disp) {
245         *obj_disp_ptr = obj_disp->object_list_next;
246
247         if (!object->display_list) {
248           obj2_ptr = (DVBSubObject **) & dvb_sub->object_list;  /* FIXME: Evil casting */
249           obj2 = *obj2_ptr;
250
251           while (obj2 != object) {
252             g_assert (obj2);
253             obj2_ptr = &obj2->next;
254             obj2 = *obj2_ptr;
255           }
256
257           *obj2_ptr = obj2->next;
258
259           g_slice_free (DVBSubObject, obj2);
260         }
261       }
262     }
263
264     region->display_list = display->region_list_next;
265
266     g_slice_free (DVBSubObjectDisplay, display);
267   }
268 }
269
270 static void
271 delete_state (DvbSub * dvb_sub)
272 {
273   DVBSubRegion *region;
274
275   while (dvb_sub->region_list) {
276     region = dvb_sub->region_list;
277
278     dvb_sub->region_list = region->next;
279
280     delete_region_display_list (dvb_sub, region);
281     g_free (region->pbuf);
282
283     g_slice_free (DVBSubRegion, region);
284   }
285
286   g_slice_free_chain (DVBSubCLUT, dvb_sub->clut_list, next);
287   dvb_sub->clut_list = NULL;
288
289   /* Should already be NULL */
290   g_warn_if_fail (dvb_sub->object_list == NULL);
291 }
292
293 static void
294 dvb_sub_init (void)
295 {
296   int i, r, g, b, a = 0;
297
298   GST_DEBUG_CATEGORY_INIT (dvbsub_debug, "dvbsub", 0, "dvbsuboverlay parser");
299
300   /* Initialize the static default_clut structure, from which other clut
301    * structures are initialized from (to start off with default CLUTs
302    * as defined in the specification). */
303   default_clut.id = -1;
304
305   default_clut.clut4[0] = RGBA_TO_AYUV (0, 0, 0, 0);
306   default_clut.clut4[1] = RGBA_TO_AYUV (255, 255, 255, 255);
307   default_clut.clut4[2] = RGBA_TO_AYUV (0, 0, 0, 255);
308   default_clut.clut4[3] = RGBA_TO_AYUV (127, 127, 127, 255);
309
310   default_clut.clut16[0] = RGBA_TO_AYUV (0, 0, 0, 0);
311   for (i = 1; i < 16; i++) {
312     if (i < 8) {
313       r = (i & 1) ? 255 : 0;
314       g = (i & 2) ? 255 : 0;
315       b = (i & 4) ? 255 : 0;
316     } else {
317       r = (i & 1) ? 127 : 0;
318       g = (i & 2) ? 127 : 0;
319       b = (i & 4) ? 127 : 0;
320     }
321     default_clut.clut16[i] = RGBA_TO_AYUV (r, g, b, 255);
322   }
323
324   default_clut.clut256[0] = RGBA_TO_AYUV (0, 0, 0, 0);
325   for (i = 1; i < 256; i++) {
326     if (i < 8) {
327       r = (i & 1) ? 255 : 0;
328       g = (i & 2) ? 255 : 0;
329       b = (i & 4) ? 255 : 0;
330       a = 63;
331     } else {
332       switch (i & 0x88) {
333         case 0x00:
334           r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
335           g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
336           b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
337           a = 255;
338           break;
339         case 0x08:
340           r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
341           g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
342           b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
343           a = 127;
344           break;
345         case 0x80:
346           r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
347           g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
348           b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
349           a = 255;
350           break;
351         case 0x88:
352           r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
353           g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
354           b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
355           a = 255;
356           break;
357       }
358     }
359     default_clut.clut256[i] = RGBA_TO_AYUV (r, g, b, a);
360   }
361 }
362
363 static void
364 _dvb_sub_parse_page_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
365     gint buf_size)
366 {                               /* FIXME: Use guint for buf_size here and in many other places? */
367   DVBSubRegionDisplay *display;
368   DVBSubRegionDisplay *tmp_display_list, **tmp_ptr;
369
370   const guint8 *buf_end = buf + buf_size;
371   guint8 region_id;
372   guint8 page_state;
373
374   if (buf_size < 1)
375     return;
376
377   dvb_sub->page_time_out = *buf++;
378   page_state = ((*buf++) >> 2) & 3;
379
380 #ifndef GST_DISABLE_GST_DEBUG
381   {
382     static const gchar *page_state_str[4] = {
383       "Normal case", "ACQUISITION POINT", "Mode Change", "RESERVED"
384     };
385
386     GST_DEBUG ("PAGE: page_id = %u, length = %d, page_time_out = %u secs, "
387         "page_state = %s", page_id, buf_size, dvb_sub->page_time_out,
388         page_state_str[page_state]);
389   }
390 #endif
391
392   if (page_state == 2) {        /* Mode change */
393     delete_state (dvb_sub);
394   }
395
396   tmp_display_list = dvb_sub->display_list;
397   dvb_sub->display_list = NULL;
398   dvb_sub->display_list_size = 0;
399
400   while (buf + 5 < buf_end) {
401     region_id = *buf++;
402     buf += 1;
403
404     display = tmp_display_list;
405     tmp_ptr = &tmp_display_list;
406
407     while (display && display->region_id != region_id) {
408       tmp_ptr = &display->next;
409       display = display->next;
410     }
411
412     if (!display)
413       display = g_slice_new0 (DVBSubRegionDisplay);
414
415     display->region_id = region_id;
416
417     display->x_pos = GST_READ_UINT16_BE (buf);
418     buf += 2;
419     display->y_pos = GST_READ_UINT16_BE (buf);
420     buf += 2;
421
422     *tmp_ptr = display->next;
423
424     display->next = dvb_sub->display_list;
425     dvb_sub->display_list = display;
426     dvb_sub->display_list_size++;
427
428     GST_LOG ("PAGE: REGION information: ID = %u, address = %ux%u", region_id,
429         display->x_pos, display->y_pos);
430   }
431
432   while (tmp_display_list) {
433     display = tmp_display_list;
434
435     tmp_display_list = display->next;
436
437     g_slice_free (DVBSubRegionDisplay, display);
438   }
439 }
440
441 static void
442 _dvb_sub_parse_region_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
443     gint buf_size)
444 {
445   const guint8 *buf_end = buf + buf_size;
446   guint8 region_id;
447   guint16 object_id;
448   DVBSubRegion *region;
449   DVBSubObject *object;
450   DVBSubObjectDisplay *object_display;
451   gboolean fill;
452
453   if (buf_size < 10)
454     return;
455
456   region_id = *buf++;
457
458   region = get_region (dvb_sub, region_id);
459
460   if (!region) {                /* Create a new region */
461     region = g_slice_new0 (DVBSubRegion);
462     region->id = region_id;
463     region->next = dvb_sub->region_list;
464     dvb_sub->region_list = region;
465   }
466
467   fill = ((*buf++) >> 3) & 1;
468
469   region->width = GST_READ_UINT16_BE (buf);
470   buf += 2;
471   region->height = GST_READ_UINT16_BE (buf);
472   buf += 2;
473
474   if (region->width * region->height != region->buf_size) {     /* FIXME: Read closer from spec what happens when dimensions change */
475     g_free (region->pbuf);
476
477     region->buf_size = region->width * region->height;
478
479     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 */
480
481     fill = 1;                   /* FIXME: Validate from spec that fill is forced on (in the following codes context) when dimensions change */
482   }
483
484   region->depth = 1 << (((*buf++) >> 2) & 7);
485   if (region->depth < 2 || region->depth > 8) {
486     GST_WARNING ("region depth %d is invalid", region->depth);
487     region->depth = 4;          /* FIXME: Check from spec this is the default? */
488   }
489
490   region->clut = *buf++;
491
492   if (region->depth == 8) {
493     region->bgcolor = *buf++;
494     buf += 1;                   /* Skip undefined 4-bit and 2-bit field */
495   } else {
496     buf += 1;
497
498     if (region->depth == 4)
499       region->bgcolor = (((*buf++) >> 4) & 15);
500     else
501       region->bgcolor = (((*buf++) >> 2) & 3);
502   }
503
504   GST_DEBUG ("REGION: id = %u, (%ux%u)@%u-bit", region_id, region->width,
505       region->height, region->depth);
506
507   if (fill) {
508     memset (region->pbuf, region->bgcolor, region->buf_size);
509     GST_DEBUG ("REGION: filling region (%u) with bgcolor = %u", region->id,
510         region->bgcolor);
511   }
512
513   delete_region_display_list (dvb_sub, region); /* Delete the region display list for current region - FIXME: why? */
514
515   while (buf + 6 <= buf_end) {
516     object_id = GST_READ_UINT16_BE (buf);
517     buf += 2;
518
519     object = get_object (dvb_sub, object_id);
520
521     if (!object) {
522       object = g_slice_new0 (DVBSubObject);
523
524       object->id = object_id;
525
526       object->next = dvb_sub->object_list;
527       dvb_sub->object_list = object;
528     }
529
530     object->type = (*buf) >> 6;
531
532     object_display = g_slice_new0 (DVBSubObjectDisplay);
533
534     object_display->object_id = object_id;
535     object_display->region_id = region_id;
536
537     object_display->x_pos = GST_READ_UINT16_BE (buf) & 0xfff;
538     buf += 2;
539     object_display->y_pos = GST_READ_UINT16_BE (buf) & 0xfff;
540     buf += 2;
541
542     if ((object->type == 1 || object->type == 2) && buf + 2 <= buf_end) {
543       object_display->fgcolor = *buf++;
544       object_display->bgcolor = *buf++;
545     }
546
547     object_display->region_list_next = region->display_list;
548     region->display_list = object_display;
549
550     object_display->object_list_next = object->display_list;
551     object->display_list = object_display;
552
553     GST_DEBUG ("REGION DATA: object_id = %u, region_id = %u, pos = %ux%u, "
554         "obj_type = %u", object->id, region->id, object_display->x_pos,
555         object_display->y_pos, object->type);
556
557     if (object->type == 1 || object->type == 2) {
558       GST_DEBUG ("REGION DATA: fgcolor = %u, bgcolor = %u",
559           object_display->fgcolor, object_display->bgcolor);
560     }
561   }
562 }
563
564 static void
565 _dvb_sub_parse_clut_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
566     gint buf_size)
567 {
568   const guint8 *buf_end = buf + buf_size;
569   guint8 clut_id;
570   DVBSubCLUT *clut;
571   int entry_id, depth, full_range;
572   int y, cr, cb, alpha;
573
574   GST_MEMDUMP ("DVB clut packet", buf, buf_size);
575
576   clut_id = *buf++;
577   buf += 1;
578
579   clut = get_clut (dvb_sub, clut_id);
580
581   if (!clut) {
582     clut = g_slice_new (DVBSubCLUT);
583
584     memcpy (clut, &default_clut, sizeof (DVBSubCLUT));
585
586     clut->id = clut_id;
587
588     clut->next = dvb_sub->clut_list;
589     dvb_sub->clut_list = clut;
590   }
591
592   while (buf + 4 < buf_end) {
593     entry_id = *buf++;
594
595     depth = (*buf) & 0xe0;
596
597     if (depth == 0) {
598       GST_WARNING ("Invalid clut depth 0x%x!", *buf);
599       return;
600     }
601
602     full_range = (*buf++) & 1;
603
604     if (full_range) {
605       y = *buf++;
606       cr = *buf++;
607       cb = *buf++;
608       alpha = *buf++;
609     } else {
610       y = buf[0] & 0xfc;
611       cr = (((buf[0] & 3) << 2) | ((buf[1] >> 6) & 3)) << 4;
612       cb = (buf[1] << 2) & 0xf0;
613       alpha = (buf[1] << 6) & 0xc0;
614
615       buf += 2;
616     }
617
618     if (y == 0)
619       alpha = 0xff;
620
621     GST_DEBUG ("CLUT DEFINITION: clut %d := (%d,%d,%d,%d)", entry_id, y, cb, cr,
622         alpha);
623
624     if (depth & 0x80)
625       clut->clut4[entry_id] = AYUV (y, cb, cr, 255 - alpha);
626     if (depth & 0x40)
627       clut->clut16[entry_id] = AYUV (y, cb, cr, 255 - alpha);
628     if (depth & 0x20)
629       clut->clut256[entry_id] = AYUV (y, cb, cr, 255 - alpha);
630   }
631 }
632
633 // FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
634 // FFMPEG-FIXME: refactoring as done here
635 static int
636 _dvb_sub_read_2bit_string (guint8 * destbuf, gint dbuf_len,
637     const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
638 {
639   GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
640   /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
641
642   gboolean stop_parsing = FALSE;
643   guint32 bits = 0;
644   guint32 pixels_read = 0;
645
646   GST_TRACE ("dbuf_len = %d", dbuf_len);
647
648   /* Need at least 2 bits remaining */
649   while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 1)) {
650     guint run_length = 0, clut_index = 0;
651
652     bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
653
654     if (bits) {                 /* 2-bit_pixel-code */
655       run_length = 1;
656       clut_index = bits;
657     } else {                    /* 2-bit_zero */
658       bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1);
659       if (bits == 1) {          /* switch_1 == '1' */
660         run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 3);
661         run_length += 3;
662         clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
663       } else {                  /* switch_1 == '0' */
664         bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1);
665         if (bits == 1) {        /* switch_2 == '1' */
666           run_length = 1;       /* 1x pseudo-colour '00' */
667         } else {                /* switch_2 == '0' */
668           bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
669           switch (bits) {       /* switch_3 */
670             case 0x0:          /* end of 2-bit/pixel_code_string */
671               stop_parsing = TRUE;
672               break;
673             case 0x1:          /* two pixels shall be set to pseudo colour (entry) '00' */
674               run_length = 2;
675               break;
676             case 0x2:          /* the following 6 bits contain run length coded pixel data */
677               run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
678               run_length += 12;
679               clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
680               break;
681             case 0x3:          /* the following 10 bits contain run length coded pixel data */
682               run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8);
683               run_length += 29;
684               clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
685               break;
686           }
687         }
688       }
689     }
690
691     /* If run_length is zero, continue. Only case happening is when
692      * stop_parsing is TRUE too, so next cycle shouldn't run */
693     if (run_length == 0)
694       continue;
695
696     /* Trim the run_length to not go beyond the line end and consume
697      * it from remaining length of dest line */
698     run_length = MIN (run_length, dbuf_len);
699     dbuf_len -= run_length;
700
701     /* Make clut_index refer to the index into the desired bit depths
702      * CLUT definition table */
703     if (map_table)
704       clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
705
706     /* Now we can simply memset run_length count of destination bytes
707      * to clut_index, but only if not non_modifying */
708     GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer, "
709         "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
710
711     if (!(non_mod == 1 && clut_index == 1))
712       memset (destbuf, clut_index, run_length);
713
714     destbuf += run_length;
715     pixels_read += run_length;
716   }
717
718   // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
719   //gst_bit_reader_skip_to_byte (&gb);
720   *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
721
722   GST_TRACE ("PIXEL: returning, read %u pixels", pixels_read);
723   // FIXME: Shouldn't need this variable if tracking things in the loop better
724   return pixels_read;
725 }
726
727 // FFMPEG-FIXME: The same code in ffmpeg is much more complex, it could use the same
728 // FFMPEG-FIXME: refactoring as done here, explained in commit 895296c3
729 static int
730 _dvb_sub_read_4bit_string (guint8 * destbuf, gint dbuf_len,
731     const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
732 {
733   GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
734   /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
735   gboolean stop_parsing = FALSE;
736   guint32 bits = 0;
737   guint32 pixels_read = 0;
738
739   GST_TRACE ("RUNLEN: srcbuf position %p, buf_size = %d; destination buffer "
740       "size is %d @ %p", *srcbuf, buf_size, dbuf_len, destbuf);
741
742   /* Need at least 4 bits */
743   while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 3)) {
744     guint run_length = 0, clut_index = 0;
745
746     bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
747
748     if (bits) {
749       run_length = 1;
750       clut_index = bits;
751     } else {
752       bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1);
753       if (bits == 0) {          /* switch_1 == '0' */
754         run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 3);
755         if (!run_length) {
756           stop_parsing = TRUE;
757         } else {
758           run_length += 2;
759         }
760       } else {                  /* switch_1 == '1' */
761         bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1);
762         if (bits == 0) {        /* switch_2 == '0' */
763           run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
764           run_length += 4;
765           clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
766         } else {                /* switch_2 == '1' */
767           bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 2);
768           switch (bits) {
769             case 0x0:          /* switch_3 == '00' */
770               run_length = 1;   /* 1 pixel of pseudo-color 0 */
771               break;
772             case 0x1:          /* switch_3 == '01' */
773               run_length = 2;   /* 2 pixels of pseudo-color 0 */
774               break;
775             case 0x2:          /* switch_3 == '10' */
776               run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
777               run_length += 9;
778               clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
779               break;
780             case 0x3:          /* switch_3 == '11' */
781               run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8);
782               run_length += 25;
783               clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 4);
784               break;
785           }
786         }
787       }
788     }
789
790     /* If run_length is zero, continue. Only case happening is when
791      * stop_parsing is TRUE too, so next cycle shouldn't run */
792     if (run_length == 0)
793       continue;
794
795     /* Trim the run_length to not go beyond the line end and consume
796      * it from remaining length of dest line */
797     run_length = MIN (run_length, dbuf_len);
798     dbuf_len -= run_length;
799
800     /* Make clut_index refer to the index into the desired bit depths
801      * CLUT definition table */
802     if (map_table)
803       clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
804
805     /* Now we can simply memset run_length count of destination bytes
806      * to clut_index, but only if not non_modifying */
807     GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer; "
808         "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
809
810     if (!(non_mod == 1 && clut_index == 1))
811       memset (destbuf, clut_index, run_length);
812
813     destbuf += run_length;
814     pixels_read += run_length;
815   }
816
817   // FIXME: Test skip_to_byte instead of adding 7 bits, once everything else is working good
818   //gst_bit_reader_skip_to_byte (&gb);
819   *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
820
821   GST_LOG ("Returning with %u pixels read", pixels_read);
822
823   // FIXME: Shouldn't need this variable if tracking things in the loop better
824   return pixels_read;
825 }
826
827 static int
828 _dvb_sub_read_8bit_string (guint8 * destbuf, gint dbuf_len,
829     const guint8 ** srcbuf, gint buf_size, guint8 non_mod, guint8 * map_table)
830 {
831   GstBitReader gb = GST_BIT_READER_INIT (*srcbuf, buf_size);
832   /* FIXME: Handle FALSE returns from gst_bit_reader_get_* calls? */
833
834   gboolean stop_parsing = FALSE;
835   guint32 bits = 0;
836   guint32 pixels_read = 0;
837
838   GST_LOG ("dbuf_len = %d", dbuf_len);
839
840   /* FFMPEG-FIXME: ffmpeg uses a manual byte walking algorithm, which might be more performant,
841    * FFMPEG-FIXME: but it does almost absolutely no buffer length checking, so could walk over
842    * FFMPEG-FIXME: memory boundaries. While we don't check gst_bit_reader_get_bits_uint32
843    * FFMPEG-FIXME: return values either and therefore might get some pixels corrupted, we at
844    * FFMPEG-FIXME: lest have no chance of reading memory we don't own and visual corruption
845    * FFMPEG-FIXME: is guaranteed anyway when not all bytes are present */
846   /* Rephrased - it's better to work with bytes with default value '0' instead of reading from memory we don't own. */
847   while (!stop_parsing && (gst_bit_reader_get_remaining (&gb) > 7)) {
848     guint run_length = 0, clut_index = 0;
849     bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8);
850
851     if (bits) {                 /* 8-bit_pixel-code */
852       run_length = 1;
853       clut_index = bits;
854     } else {                    /* 8-bit_zero */
855       bits = gst_bit_reader_get_bits_uint32_unchecked (&gb, 1);
856       if (bits == 0) {          /* switch_1 == '0' */
857         /* run_length_1-127 for pseudo-colour _entry) '0x00' */
858         run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 7);
859         if (run_length == 0) {  /* end_of_string_signal */
860           stop_parsing = TRUE;
861         }
862       } else {                  /* switch_1 == '1' */
863         /* run_length_3-127 */
864         run_length = gst_bit_reader_get_bits_uint32_unchecked (&gb, 7);
865         clut_index = gst_bit_reader_get_bits_uint32_unchecked (&gb, 8);
866
867         if (run_length < 3) {
868           GST_WARNING ("runlength value was %u, but the spec requires it "
869               "must be >=3", run_length);
870         }
871       }
872     }
873
874     /* If run_length is zero, continue. Only case happening is when
875      * stop_parsing is TRUE too, so next cycle shouldn't run */
876     if (run_length == 0)
877       continue;
878
879     /* Trim the run_length to not go beyond the line end and consume
880      * it from remaining length of dest line */
881     run_length = MIN (run_length, dbuf_len);
882     dbuf_len -= run_length;
883
884     /* Make clut_index refer to the index into the desired bit depths
885      * CLUT definition table */
886     if (map_table)
887       clut_index = map_table[clut_index];       /* now clut_index signifies the index into map_table dest */
888
889     /* Now we can simply memset run_length count of destination bytes
890      * to clut_index, but only if not non_modifying */
891     GST_TRACE ("RUNLEN: setting %u pixels to color 0x%x in destination buffer; "
892         "dbuf_len left is %d pixels", run_length, clut_index, dbuf_len);
893
894     if (!(non_mod == 1 && clut_index == 1))
895       memset (destbuf, clut_index, run_length);
896
897     destbuf += run_length;
898     pixels_read += run_length;
899   }
900
901   GST_LOG ("Returning with %u pixels read", pixels_read);
902
903   *srcbuf += (gst_bit_reader_get_pos (&gb) + 7) >> 3;
904
905   // FIXME: Shouldn't need this variable if tracking things in the loop better
906   return pixels_read;
907 }
908
909 static void
910 _dvb_sub_parse_pixel_data_block (DvbSub * dvb_sub,
911     DVBSubObjectDisplay * display, const guint8 * buf, gint buf_size,
912     DvbSubPixelDataSubBlockFieldType top_bottom, guint8 non_mod)
913 {
914   DVBSubRegion *region = get_region (dvb_sub, display->region_id);
915   const guint8 *buf_end = buf + buf_size;
916   guint8 *pbuf;
917   int x_pos, y_pos;
918   int i;
919   gboolean dest_buf_filled = FALSE;
920
921   guint8 map2to4[] = { 0x0, 0x7, 0x8, 0xf };
922   guint8 map2to8[] = { 0x00, 0x77, 0x88, 0xff };
923   guint8 map4to8[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
924     0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
925   };
926   guint8 *map_table;
927
928   GST_LOG ("DVB pixel block size %d, %s field:", buf_size,
929       top_bottom ? "bottom" : "top");
930
931   GST_MEMDUMP ("packet", buf, buf_size);
932
933   if (region == NULL) {
934     GST_LOG ("Region is NULL, returning");
935     return;
936   }
937
938   pbuf = region->pbuf;
939
940   x_pos = display->x_pos;
941   y_pos = display->y_pos;
942
943   if ((y_pos & 1) != top_bottom)
944     y_pos++;
945
946   while (buf < buf_end) {
947     GST_LOG ("Iteration start, %u bytes remaining; buf = %p, "
948         "buf_end = %p; Region is number %u, with a dimension of %dx%d; "
949         "We are at position %dx%d", (guint) (buf_end - buf), buf, buf_end,
950         region->id, region->width, region->height, x_pos, y_pos);
951
952     // FFMPEG-FIXME: ffmpeg doesn't check for equality and so can overflow destination buffer later on with bad input data
953     // 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
954     // FIXME: Removed x_pos checking here, because we don't want to turn dest_buf_filled to TRUE permanently in that case
955     // FIXME: We assume that region->width - x_pos as dbuf_len to read_nbit_string will take care of that case nicely;
956     // FIXME: That is, that read_nbit_string never scribbles anything if dbuf_len passed to it is zero due to this.
957     if (y_pos >= region->height) {
958       dest_buf_filled = TRUE;
959     }
960
961     switch (*buf++) {
962       case 0x10:
963         if (dest_buf_filled) {
964           /* FIXME: Be more verbose */
965           GST_WARNING ("Invalid object location for data_type 0x%x!",
966               *(buf - 1));
967           GST_MEMDUMP ("Remaining data after invalid object location:", buf,
968               (guint) (buf_end - buf));
969           return;
970         }
971
972         if (region->depth == 8)
973           map_table = map2to8;
974         else if (region->depth == 4)
975           map_table = map2to4;
976         else
977           map_table = NULL;
978
979         // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
980         // FFMPEG-FIXME: therefore potentially walk over the memory area we own
981         x_pos +=
982             _dvb_sub_read_2bit_string (pbuf + (y_pos * region->width) + x_pos,
983             region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
984         break;
985       case 0x11:
986         if (dest_buf_filled) {
987           /* FIXME: Be more verbose */
988           GST_WARNING ("Invalid object location for data_type 0x%x!",
989               *(buf - 1));
990           GST_MEMDUMP ("Remaining data after invalid object location:", buf,
991               buf_end - buf);
992           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)
993         }
994
995         if (region->depth < 4) {
996           GST_WARNING ("4-bit pixel string in %d-bit region!", region->depth);
997           return;
998         }
999
1000         if (region->depth == 8)
1001           map_table = map4to8;
1002         else
1003           map_table = NULL;
1004
1005         GST_LOG ("READ_4BIT_STRING: String data into position %dx%d; "
1006             "buf before is %p", x_pos, y_pos, buf);
1007         // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
1008         // FFMPEG-FIXME: therefore potentially walk over the memory area we own
1009         x_pos +=
1010             _dvb_sub_read_4bit_string (pbuf + (y_pos * region->width) + x_pos,
1011             region->width - x_pos, &buf, buf_end - buf, non_mod, map_table);
1012         GST_DEBUG ("READ_4BIT_STRING finished: buf pointer now %p", buf);
1013         break;
1014       case 0x12:
1015         if (dest_buf_filled) {
1016           /* FIXME: Be more verbose */
1017           GST_WARNING ("Invalid object location for data_type 0x%x!",
1018               *(buf - 1));
1019           GST_MEMDUMP ("Remaining data after invalid object location:",
1020               buf, (guint) (buf_end - buf));
1021           return;
1022         }
1023
1024         if (region->depth < 8) {
1025           GST_WARNING ("8-bit pixel string in %d-bit region!", region->depth);
1026           return;
1027         }
1028         // FFMPEG-FIXME: ffmpeg code passes buf_size instead of buf_end - buf, and could
1029         // FFMPEG-FIXME: therefore potentially walk over the memory area we own
1030         x_pos +=
1031             _dvb_sub_read_8bit_string (pbuf + (y_pos * region->width) + x_pos,
1032             region->width - x_pos, &buf, buf_end - buf, non_mod, NULL);
1033         break;
1034
1035       case 0x20:
1036         GST_DEBUG ("handling map2to4 table data");
1037         /* FIXME: I don't see any guards about buffer size here - buf++ happens with the switch, but
1038          * FIXME: buffer is walked without length checks? Same deal in other map table cases */
1039         map2to4[0] = (*buf) >> 4;
1040         map2to4[1] = (*buf++) & 0xf;
1041         map2to4[2] = (*buf) >> 4;
1042         map2to4[3] = (*buf++) & 0xf;
1043         break;
1044       case 0x21:
1045         GST_DEBUG ("handling map2to8 table data");
1046         for (i = 0; i < 4; i++)
1047           map2to8[i] = *buf++;
1048         break;
1049       case 0x22:
1050         GST_DEBUG ("handling map4to8 table data");
1051         for (i = 0; i < 16; i++)
1052           map4to8[i] = *buf++;
1053         break;
1054       case 0xf0:
1055         GST_DEBUG ("end of object line code encountered");
1056         x_pos = display->x_pos;
1057         y_pos += 2;
1058         break;
1059       default:
1060         /* FIXME: Do we consume word align stuffing byte that could follow top/bottom data? */
1061         GST_WARNING ("Unknown/unsupported pixel block 0x%x", *(buf - 1));
1062     }
1063   }
1064 }
1065
1066 static void
1067 _dvb_sub_parse_object_segment (DvbSub * dvb_sub, guint16 page_id, guint8 * buf,
1068     gint buf_size)
1069 {
1070   const guint8 *buf_end = buf + buf_size;
1071   guint object_id;
1072   DVBSubObject *object;
1073
1074   guint8 coding_method, non_modifying_color;
1075
1076   object_id = GST_READ_UINT16_BE (buf);
1077   buf += 2;
1078
1079   object = get_object (dvb_sub, object_id);
1080
1081   GST_DEBUG ("OBJECT: a new object segment has occurred for object_id = %u",
1082       object_id);
1083
1084   if (!object) {
1085     GST_WARNING ("Nothing known about object with ID %u yet, bailing out",
1086         object_id);
1087     return;
1088   }
1089
1090   coding_method = ((*buf) >> 2) & 3;
1091   non_modifying_color = ((*buf++) >> 1) & 1;
1092
1093   if (coding_method == 0) {
1094     const guint8 *block;
1095     DVBSubObjectDisplay *display;
1096     guint16 top_field_len, bottom_field_len;
1097
1098     top_field_len = GST_READ_UINT16_BE (buf);
1099     buf += 2;
1100     bottom_field_len = GST_READ_UINT16_BE (buf);
1101     buf += 2;
1102
1103     if (buf + top_field_len + bottom_field_len > buf_end) {
1104       GST_WARNING ("Field data size too large");
1105       return;
1106     }
1107
1108     /* FIXME: Potential optimization opportunity here - parse the object pixmap only once, and copy it to all the
1109      * FIXME: regions that need it. One object being in multiple regions is a rare occurrence in real life, however */
1110     for (display = object->display_list; display;
1111         display = display->object_list_next) {
1112       block = buf;
1113
1114       GST_DEBUG ("OBJECT: parsing top and bottom part of object id %d; "
1115           "top_field_len = %u, bottom_field_len = %u",
1116           display->object_id, top_field_len, bottom_field_len);
1117
1118       _dvb_sub_parse_pixel_data_block (dvb_sub, display, block, top_field_len,
1119           TOP_FIELD, non_modifying_color);
1120
1121       if (bottom_field_len > 0)
1122         block = buf + top_field_len;
1123       else
1124         bottom_field_len = top_field_len;
1125
1126       _dvb_sub_parse_pixel_data_block (dvb_sub, display, block,
1127           bottom_field_len, BOTTOM_FIELD, non_modifying_color);
1128     }
1129
1130   } else if (coding_method == 1) {
1131     GST_FIXME ("'a string of characters' coding method not supported yet!");
1132   } else {
1133     GST_WARNING ("Unknown object coding 0x%x", coding_method);
1134   }
1135 }
1136
1137 static gint
1138 _dvb_sub_parse_display_definition_segment (DvbSub * dvb_sub, guint8 * buf,
1139     gint buf_size)
1140 {
1141   int dds_version, info_byte;
1142
1143   if (buf_size < 5)
1144     return -1;
1145
1146   info_byte = *buf++;
1147   dds_version = info_byte >> 4;
1148
1149   if (dvb_sub->display_def.version == dds_version)
1150     return 0;                   /* already have this display definition version */
1151
1152   dvb_sub->display_def.version = dds_version;
1153   dvb_sub->display_def.display_width = GST_READ_UINT16_BE (buf) + 1;
1154   buf += 2;
1155   dvb_sub->display_def.display_height = GST_READ_UINT16_BE (buf) + 1;
1156   buf += 2;
1157
1158   dvb_sub->display_def.window_flag = info_byte & 1 << 3;
1159
1160   if (buf_size >= 13 && dvb_sub->display_def.window_flag) {
1161     dvb_sub->display_def.window_x = GST_READ_UINT16_BE (buf);
1162     buf += 2;
1163     dvb_sub->display_def.window_width =
1164         GST_READ_UINT16_BE (buf) - dvb_sub->display_def.window_x + 1;
1165     buf += 2;
1166     dvb_sub->display_def.window_y = GST_READ_UINT16_BE (buf);
1167     buf += 2;
1168     dvb_sub->display_def.window_height =
1169         GST_READ_UINT16_BE (buf) - dvb_sub->display_def.window_y + 1;
1170   }
1171
1172   return 0;
1173 }
1174
1175 static gint
1176 _dvb_sub_parse_end_of_display_set (DvbSub * dvb_sub, guint16 page_id,
1177     guint64 pts)
1178 {
1179   DVBSubRegionDisplay *display;
1180   DVBSubtitles *sub;
1181   DVBSubCLUT *clut;
1182   guint32 *clut_table;
1183   int i;
1184
1185   GST_DEBUG ("DISPLAY SET END: page_id = %u", page_id);
1186
1187   sub = g_slice_new0 (DVBSubtitles);
1188
1189 #if 0                           /* FIXME: PTS stuff not figured out yet */
1190   sub->start_display_time = 0;
1191   sub->end_display_time = priv->page_time_out * 1000;
1192   sub->format = 0;              /* 0 = graphics */
1193 #endif
1194
1195   /* N.B. g_new0() will return NULL if num_rects is 0 */
1196   sub->num_rects = dvb_sub->display_list_size;
1197   sub->rects = g_new0 (DVBSubtitleRect, sub->num_rects);
1198
1199   i = 0;
1200
1201   /* copy subtitle display and window information */
1202   sub->display_def = dvb_sub->display_def;
1203
1204   for (display = dvb_sub->display_list; display; display = display->next) {
1205     DVBSubtitleRect *rect;
1206     DVBSubRegion *region;
1207
1208     region = get_region (dvb_sub, display->region_id);
1209
1210     if (!region)
1211       continue;
1212
1213     rect = &sub->rects[i];
1214     rect->x = display->x_pos;
1215     rect->y = display->y_pos;
1216     rect->w = region->width;
1217     rect->h = region->height;
1218 #if 0                           /* FIXME: Don't think we need to save the number of colors in the palette when we are saving as RGBA? */
1219     rect->nb_colors = 16;
1220 #endif
1221 #if 0                           /* FIXME: Needed to be specified once we support strings of characters based subtitles */
1222     rect->type = SUBTITLE_BITMAP;
1223 #endif
1224     rect->pict.rowstride = region->width;
1225     rect->pict.palette_bits_count = region->depth;
1226
1227     clut = get_clut (dvb_sub, region->clut);
1228
1229     if (!clut)
1230       clut = &default_clut;
1231
1232     switch (region->depth) {
1233       case 2:
1234         clut_table = clut->clut4;
1235         break;
1236       case 8:
1237         clut_table = clut->clut256;
1238         break;
1239       case 4:
1240       default:
1241         clut_table = clut->clut16;
1242         break;
1243     }
1244
1245     /* FIXME: Tweak this to be saved in a format most suitable for Qt and GStreamer instead.
1246      * Currently kept in AVPicture for quick save_display_set testing */
1247     rect->pict.palette = g_malloc ((1 << region->depth) * sizeof (guint32));    /* FIXME: Can we use GSlice here? */
1248     memcpy (rect->pict.palette, clut_table,
1249         (1 << region->depth) * sizeof (guint32));
1250
1251     GST_MEMDUMP ("rect->pict.data.palette content",
1252         (guint8 *) rect->pict.palette, (1 << region->depth) * sizeof (guint32));
1253
1254     rect->pict.data = g_malloc (region->buf_size);      /* FIXME: Can we use GSlice here? */
1255     memcpy (rect->pict.data, region->pbuf, region->buf_size);
1256
1257     GST_DEBUG ("DISPLAY: an object rect created: iteration %u, "
1258         "pos: %d:%d, size: %dx%d", i, rect->x, rect->y, rect->w, rect->h);
1259
1260     GST_MEMDUMP ("rect->pict.data content", rect->pict.data, region->buf_size);
1261
1262     ++i;
1263   }
1264
1265   sub->pts = pts;
1266   sub->page_time_out = dvb_sub->page_time_out;
1267   sub->num_rects = i;
1268
1269   if (dvb_sub->callbacks.new_data) {
1270     dvb_sub->callbacks.new_data (dvb_sub, sub, dvb_sub->user_data);
1271   } else {
1272     /* No-one responsible to clean up memory, so do it ourselves */
1273     /* FIXME: Just don't bother with all this palette image creation in the first place then... */
1274     dvb_subtitles_free (sub);
1275   }
1276
1277   return 1;                     /* FIXME: The caller of this function is probably supposed to do something with the return value */
1278 }
1279
1280 void
1281 dvb_subtitles_free (DVBSubtitles * sub)
1282 {
1283   int i;
1284
1285   if (sub == NULL)
1286     return;
1287
1288   /* Now free up all the temporary memory we allocated */
1289   for (i = 0; i < sub->num_rects; ++i) {
1290     g_free (sub->rects[i].pict.palette);
1291     g_free (sub->rects[i].pict.data);
1292   }
1293   g_free (sub->rects);
1294   g_slice_free (DVBSubtitles, sub);
1295 }
1296
1297 DvbSub *
1298 dvb_sub_new (void)
1299 {
1300   static gsize inited = 0;
1301   DvbSub *sub;
1302
1303   if (g_once_init_enter (&inited)) {
1304     dvb_sub_init ();
1305     g_once_init_leave (&inited, TRUE);
1306   }
1307
1308   sub = g_slice_new0 (DvbSub);
1309
1310   /* TODO: Add initialization code here */
1311   /* FIXME: Do we have a reason to initiate the members to zero, or are we guaranteed that anyway? */
1312   sub->region_list = NULL;
1313   sub->object_list = NULL;
1314   sub->page_time_out = 0;       /* FIXME: Maybe 255 instead? */
1315   sub->pes_buffer = g_string_new (NULL);
1316
1317   /* display/window information */
1318   sub->display_def.version = -1;
1319   sub->display_def.window_flag = 0;
1320   sub->display_def.display_width = 720;
1321   sub->display_def.display_height = 576;
1322
1323   return sub;
1324 }
1325
1326 void
1327 dvb_sub_free (DvbSub * sub)
1328 {
1329   /* TODO: Add deinitalization code here */
1330   /* FIXME: Clear up region_list contents */
1331   delete_state (sub);
1332   while (sub->display_list) {
1333     DVBSubRegionDisplay *tmp = sub->display_list->next;
1334     g_slice_free (DVBSubRegionDisplay, sub->display_list);
1335     sub->display_list = tmp;
1336   }
1337   g_string_free (sub->pes_buffer, TRUE);
1338   g_slice_free (DvbSub, sub);
1339 }
1340
1341 #define DVB_SUB_SEGMENT_PAGE_COMPOSITION 0x10
1342 #define DVB_SUB_SEGMENT_REGION_COMPOSITION 0x11
1343 #define DVB_SUB_SEGMENT_CLUT_DEFINITION 0x12
1344 #define DVB_SUB_SEGMENT_OBJECT_DATA 0x13
1345 #define DVB_SUB_SEGMENT_DISPLAY_DEFINITION 0x14
1346 #define DVB_SUB_SEGMENT_END_OF_DISPLAY_SET 0x80
1347 #define DVB_SUB_SEGMENT_STUFFING 0xFF
1348
1349 #define DVB_SUB_SYNC_BYTE 0x0f
1350 /**
1351  * dvb_sub_feed_with_pts:
1352  * @dvb_sub: a #DvbSub
1353  * @pts: The PTS of the data
1354  * @data: The data to feed to the parser
1355  * @len: Length of the data
1356  *
1357  * Feeds the DvbSub parser with new binary data to parse,
1358  * with an associated PTS value. E.g, data left after PES
1359  * packet header has been already parsed, which contains
1360  * the PTS information).
1361  *
1362  * Return value: -1 if data was unhandled (e.g, not a subtitle packet),
1363  *                               -2 if data parsing was unsuccesful (e.g, length was invalid),
1364  *                                0 or positive if data was handled. If positive, then amount of data consumed on success. FIXME: List the positive return values.
1365  */
1366 gint
1367 dvb_sub_feed_with_pts (DvbSub * dvb_sub, guint64 pts, guint8 * data, gint len)
1368 {
1369   unsigned int pos = 0;
1370   guint8 segment_type;
1371   guint16 segment_len;
1372   guint16 page_id;
1373
1374   GST_DEBUG ("pts=%" G_GUINT64_FORMAT " and length %d", pts, len);
1375
1376   g_return_val_if_fail (data != NULL || len == 0, -1);
1377
1378   if (G_UNLIKELY (data == NULL)) {
1379     GST_DEBUG ("no data; forcing end-of-display-set");
1380     _dvb_sub_parse_end_of_display_set (dvb_sub, 0, pts);
1381     return 0;
1382   }
1383
1384   if (len <= 3) {               /* len(0x20 0x00 end_of_PES_data_field_marker) */
1385     GST_WARNING ("Data length too short");
1386     return -1;
1387   }
1388
1389   if (data[pos++] != 0x20) {
1390     GST_WARNING ("Tried to handle a PES packet private data that isn't a "
1391         "subtitle packet (does not start with 0x20)");
1392     return -1;
1393   }
1394
1395   if (data[pos++] != 0x00) {
1396     GST_WARNING ("'Subtitle stream in this PES packet' was not 0x00, so this "
1397         "is in theory not a DVB subtitle stream (but some other subtitle "
1398         "standard?); bailing out");
1399     return -1;
1400   }
1401
1402   while (data[pos++] == DVB_SUB_SYNC_BYTE) {
1403     if ((len - pos) < (2 * 2 + 1)) {
1404       GST_WARNING ("Data after SYNC BYTE too short, less than needed to "
1405           "even get to segment_length");
1406       return -2;
1407     }
1408     segment_type = data[pos++];
1409     GST_DEBUG ("=== Segment type is 0x%x", segment_type);
1410     page_id = (data[pos] << 8) | data[pos + 1];
1411     GST_DEBUG ("page_id is 0x%x", page_id);
1412     pos += 2;
1413     segment_len = (data[pos] << 8) | data[pos + 1];
1414     GST_DEBUG ("segment_length is %d (0x%x 0x%x)", segment_len, data[pos],
1415         data[pos + 1]);
1416     pos += 2;
1417     if ((len - pos) < segment_len) {
1418       GST_WARNING ("segment_length was told to be %u, but we only have "
1419           "%d bytes left", segment_len, len - pos);
1420       return -2;
1421     }
1422     // TODO: Parse the segment per type  (this is probably a leftover TODO that is now done?)
1423     /* FIXME: Handle differing PTS values - all segments of a given display set must be with the same PTS,
1424      * FIXME: but we let it slip and just take it for granted in end_of_display_set */
1425     switch (segment_type) {
1426       case DVB_SUB_SEGMENT_PAGE_COMPOSITION:
1427         GST_DEBUG ("Page composition segment at buffer pos %u", pos);
1428         _dvb_sub_parse_page_segment (dvb_sub, page_id, data + pos, segment_len);        /* FIXME: Not sure about args */
1429         break;
1430       case DVB_SUB_SEGMENT_REGION_COMPOSITION:
1431         GST_DEBUG ("Region composition segment at buffer pos %u", pos);
1432         _dvb_sub_parse_region_segment (dvb_sub, page_id, data + pos, segment_len);      /* FIXME: Not sure about args */
1433         break;
1434       case DVB_SUB_SEGMENT_CLUT_DEFINITION:
1435         GST_DEBUG ("CLUT definition segment at buffer pos %u", pos);
1436         _dvb_sub_parse_clut_segment (dvb_sub, page_id, data + pos, segment_len);        /* FIXME: Not sure about args */
1437         break;
1438       case DVB_SUB_SEGMENT_OBJECT_DATA:
1439         GST_DEBUG ("Object data segment at buffer pos %u", pos);
1440         _dvb_sub_parse_object_segment (dvb_sub, page_id, data + pos, segment_len);      /* FIXME: Not sure about args */
1441         break;
1442       case DVB_SUB_SEGMENT_DISPLAY_DEFINITION:
1443         GST_DEBUG ("display definition segment at buffer pos %u", pos);
1444         _dvb_sub_parse_display_definition_segment (dvb_sub, data + pos,
1445             segment_len);
1446         break;
1447       case DVB_SUB_SEGMENT_END_OF_DISPLAY_SET:
1448         GST_DEBUG ("End of display set at buffer pos %u", pos);
1449         _dvb_sub_parse_end_of_display_set (dvb_sub, page_id, pts);      /* FIXME: Not sure about args */
1450         break;
1451       default:
1452         GST_FIXME ("Unhandled segment type 0x%x", segment_type);
1453         break;
1454     }
1455
1456     pos += segment_len;
1457
1458     if (pos == len) {
1459       GST_WARNING ("Data ended without a PES data end marker");
1460       return 1;
1461     }
1462   }
1463
1464   GST_LOG ("Processed %d bytes out of %d", pos, len);
1465   return pos;
1466 }
1467
1468 /**
1469  * dvb_sub_set_callbacks:
1470  * @dvb_sub: a #DvbSub
1471  * @callbacks: the callbacks to install
1472  * @user_data: a user_data argument for the callback
1473  *
1474  * Set callback which will be executed when new subpictures are available.
1475  */
1476 void
1477 dvb_sub_set_callbacks (DvbSub * dvb_sub, DvbSubCallbacks * callbacks,
1478     gpointer user_data)
1479 {
1480   g_return_if_fail (dvb_sub != NULL);
1481   g_return_if_fail (callbacks != NULL);
1482
1483   dvb_sub->callbacks = *callbacks;
1484   dvb_sub->user_data = user_data;
1485 }