Move files from gst-plugins-ugly into the "subprojects/gst-plugins-ugly/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-ugly / gst / dvdsub / gstdvdsubdec.c
1 /* GStreamer
2  * Copyright (C) <2005> Jan Schmidt <jan@fluendo.com>
3  * Copyright (C) <2002> Wim Taymans <wim@fluendo.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "gstdvdsubdec.h"
26 #include "gstdvdsubparse.h"
27 #include <string.h>
28
29 GST_DEBUG_CATEGORY_STATIC (gst_dvd_sub_dec_debug);
30 #define GST_CAT_DEFAULT (gst_dvd_sub_dec_debug)
31
32 #define gst_dvd_sub_dec_parent_class parent_class
33 G_DEFINE_TYPE (GstDvdSubDec, gst_dvd_sub_dec, GST_TYPE_ELEMENT);
34 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (dvdsubdec, "dvdsubdec", GST_RANK_NONE,
35     GST_TYPE_DVD_SUB_DEC, GST_DEBUG_CATEGORY_INIT (gst_dvd_sub_dec_debug,
36         "dvdsubdec", 0, "DVD subtitle decoder"));
37
38 static gboolean gst_dvd_sub_dec_src_event (GstPad * srcpad, GstObject * parent,
39     GstEvent * event);
40 static GstFlowReturn gst_dvd_sub_dec_chain (GstPad * pad, GstObject * parent,
41     GstBuffer * buf);
42
43 static gboolean gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec,
44     GstEvent * event);
45 static void gst_dvd_sub_dec_finalize (GObject * gobject);
46 static void gst_setup_palette (GstDvdSubDec * dec);
47 static void gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec,
48     GstVideoFrame * frame);
49 static GstClockTime gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec);
50 static gboolean gst_dvd_sub_dec_sink_event (GstPad * pad, GstObject * parent,
51     GstEvent * event);
52 static gboolean gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps);
53
54 static GstFlowReturn gst_send_subtitle_frame (GstDvdSubDec * dec,
55     GstClockTime end_ts);
56
57 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
58     GST_PAD_SRC,
59     GST_PAD_ALWAYS,
60     GST_STATIC_CAPS ("video/x-raw, format = (string) { AYUV, ARGB },"
61         "width = (int) 720, height = (int) 576, framerate = (fraction) 0/1")
62     );
63
64 static GstStaticPadTemplate subtitle_template = GST_STATIC_PAD_TEMPLATE ("sink",
65     GST_PAD_SINK,
66     GST_PAD_ALWAYS,
67     GST_STATIC_CAPS ("subpicture/x-dvd")
68     );
69
70
71 enum
72 {
73   SPU_FORCE_DISPLAY = 0x00,
74   SPU_SHOW = 0x01,
75   SPU_HIDE = 0x02,
76   SPU_SET_PALETTE = 0x03,
77   SPU_SET_ALPHA = 0x04,
78   SPU_SET_SIZE = 0x05,
79   SPU_SET_OFFSETS = 0x06,
80   SPU_WIPE = 0x07,
81   SPU_END = 0xff
82 };
83
84 static const guint32 default_clut[16] = {
85   0xb48080, 0x248080, 0x628080, 0xd78080,
86   0x808080, 0x808080, 0x808080, 0x808080,
87   0x808080, 0x808080, 0x808080, 0x808080,
88   0x808080, 0x808080, 0x808080, 0x808080
89 };
90
91 typedef struct RLE_state
92 {
93   gint id;
94   gint aligned;
95   gint offset[2];
96   gint hl_left;
97   gint hl_right;
98
99   guchar *target;
100
101   guchar next;
102 }
103 RLE_state;
104
105 static void
106 gst_dvd_sub_dec_class_init (GstDvdSubDecClass * klass)
107 {
108   GObjectClass *gobject_class;
109   GstElementClass *gstelement_class;
110
111   gobject_class = (GObjectClass *) klass;
112   gstelement_class = (GstElementClass *) klass;
113
114   gobject_class->finalize = gst_dvd_sub_dec_finalize;
115
116   gst_element_class_add_static_pad_template (gstelement_class, &src_template);
117   gst_element_class_add_static_pad_template (gstelement_class,
118       &subtitle_template);
119
120   gst_element_class_set_static_metadata (gstelement_class,
121       "DVD subtitle decoder", "Codec/Decoder/Video",
122       "Decodes DVD subtitles into AYUV video frames",
123       "Wim Taymans <wim.taymans@gmail.com>, "
124       "Jan Schmidt <thaytan@mad.scientist.com>");
125 }
126
127 static void
128 gst_dvd_sub_dec_init (GstDvdSubDec * dec)
129 {
130   GstPadTemplate *tmpl;
131
132   dec->sinkpad = gst_pad_new_from_static_template (&subtitle_template, "sink");
133   gst_pad_set_chain_function (dec->sinkpad,
134       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_chain));
135   gst_pad_set_event_function (dec->sinkpad,
136       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_sink_event));
137   gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
138
139   tmpl = gst_static_pad_template_get (&src_template);
140   dec->srcpad = gst_pad_new_from_template (tmpl, "src");
141   gst_pad_set_event_function (dec->srcpad,
142       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_src_event));
143   gst_pad_use_fixed_caps (dec->srcpad);
144   gst_object_unref (tmpl);
145   gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
146
147   /* FIXME: aren't there more possible sizes? (tpm) */
148   dec->in_width = 720;
149   dec->in_height = 576;
150
151   dec->partialbuf = NULL;
152   dec->have_title = FALSE;
153   dec->parse_pos = NULL;
154   dec->forced_display = FALSE;
155   dec->visible = FALSE;
156
157   memcpy (dec->current_clut, default_clut, sizeof (guint32) * 16);
158
159   gst_setup_palette (dec);
160
161   dec->next_ts = 0;
162   dec->next_event_ts = GST_CLOCK_TIME_NONE;
163
164   dec->buf_dirty = TRUE;
165   dec->use_ARGB = FALSE;
166 }
167
168 static void
169 gst_dvd_sub_dec_finalize (GObject * gobject)
170 {
171   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gobject);
172
173   if (dec->partialbuf) {
174     gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
175     gst_buffer_unref (dec->partialbuf);
176     dec->partialbuf = NULL;
177   }
178
179   G_OBJECT_CLASS (parent_class)->finalize (gobject);
180 }
181
182 static gboolean
183 gst_dvd_sub_dec_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
184 {
185   gboolean res = FALSE;
186
187   switch (GST_EVENT_TYPE (event)) {
188     default:
189       res = gst_pad_event_default (pad, parent, event);
190       break;
191   }
192
193   return res;
194 }
195
196 static GstClockTime
197 gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec)
198 {
199   guchar *buf;
200   guint16 ticks;
201   GstClockTime event_delay;
202
203   /* If starting a new buffer, follow the first DCSQ ptr */
204   if (dec->parse_pos == dec->partialmap.data) {
205     buf = dec->parse_pos + dec->data_size;
206   } else {
207     buf = dec->parse_pos;
208   }
209
210   ticks = GST_READ_UINT16_BE (buf);
211   event_delay = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
212
213   GST_DEBUG_OBJECT (dec, "returning delay %" GST_TIME_FORMAT " from offset %u",
214       GST_TIME_ARGS (event_delay), (guint) (buf - dec->parse_pos));
215
216   return event_delay;
217 }
218
219 /*
220  * Parse the next event time in the current subpicture buffer, stopping
221  * when time advances to the next state. 
222  */
223 static void
224 gst_dvd_sub_dec_parse_subpic (GstDvdSubDec * dec)
225 {
226 #define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \
227   { GST_WARNING("Subtitle stream broken parsing %c", *buf); \
228     broken = TRUE; break; }
229
230   guchar *start = dec->partialmap.data;
231   guchar *buf;
232   guchar *end;
233   gboolean broken = FALSE;
234   gboolean last_seq = FALSE;
235   guchar *next_seq = NULL;
236   GstClockTime event_time;
237
238   /* nothing to do if we finished this buffer already */
239   if (dec->parse_pos == NULL)
240     return;
241
242   g_return_if_fail (dec->packet_size >= 4);
243
244   end = start + dec->packet_size;
245   if (dec->parse_pos == start) {
246     buf = dec->parse_pos + dec->data_size;
247   } else {
248     buf = dec->parse_pos;
249   }
250
251   g_assert (buf >= start && buf < end);
252
253   /* If the next control sequence is at the current offset, this is 
254    * the last one */
255   next_seq = start + GST_READ_UINT16_BE (buf + 2);
256   last_seq = (next_seq == buf);
257   buf += 4;
258
259   while ((buf < end) && (!broken)) {
260     switch (*buf) {
261       case SPU_FORCE_DISPLAY:  /* Forced display menu subtitle */
262         dec->forced_display = TRUE;
263         dec->buf_dirty = TRUE;
264         GST_DEBUG_OBJECT (dec, "SPU FORCE_DISPLAY");
265         buf++;
266         break;
267       case SPU_SHOW:           /* Show the subtitle in this packet */
268         dec->visible = TRUE;
269         dec->buf_dirty = TRUE;
270         GST_DEBUG_OBJECT (dec, "SPU SHOW at %" GST_TIME_FORMAT,
271             GST_TIME_ARGS (dec->next_event_ts));
272         buf++;
273         break;
274       case SPU_HIDE:
275         /* 02 ff (ff) is the end of the packet, hide the subpicture */
276         dec->visible = FALSE;
277         dec->buf_dirty = TRUE;
278
279         GST_DEBUG_OBJECT (dec, "SPU HIDE at %" GST_TIME_FORMAT,
280             GST_TIME_ARGS (dec->next_event_ts));
281         buf++;
282         break;
283       case SPU_SET_PALETTE:    /* palette */
284         PARSE_BYTES_NEEDED (3);
285
286         GST_DEBUG_OBJECT (dec, "SPU SET_PALETTE");
287
288         dec->subtitle_index[3] = buf[1] >> 4;
289         dec->subtitle_index[2] = buf[1] & 0xf;
290         dec->subtitle_index[1] = buf[2] >> 4;
291         dec->subtitle_index[0] = buf[2] & 0xf;
292         gst_setup_palette (dec);
293
294         dec->buf_dirty = TRUE;
295         buf += 3;
296         break;
297       case SPU_SET_ALPHA:      /* transparency palette */
298         PARSE_BYTES_NEEDED (3);
299
300         GST_DEBUG_OBJECT (dec, "SPU SET_ALPHA");
301
302         dec->subtitle_alpha[3] = buf[1] >> 4;
303         dec->subtitle_alpha[2] = buf[1] & 0xf;
304         dec->subtitle_alpha[1] = buf[2] >> 4;
305         dec->subtitle_alpha[0] = buf[2] & 0xf;
306         gst_setup_palette (dec);
307
308         dec->buf_dirty = TRUE;
309         buf += 3;
310         break;
311       case SPU_SET_SIZE:       /* image coordinates */
312         PARSE_BYTES_NEEDED (7);
313
314         dec->top = ((buf[4] & 0x3f) << 4) | ((buf[5] & 0xe0) >> 4);
315         dec->left = ((buf[1] & 0x3f) << 4) | ((buf[2] & 0xf0) >> 4);
316         dec->right = ((buf[2] & 0x03) << 8) | buf[3];
317         dec->bottom = ((buf[5] & 0x03) << 8) | buf[6];
318
319         GST_DEBUG_OBJECT (dec, "SPU SET_SIZE left %d, top %d, right %d, "
320             "bottom %d", dec->left, dec->top, dec->right, dec->bottom);
321
322         dec->buf_dirty = TRUE;
323         buf += 7;
324         break;
325       case SPU_SET_OFFSETS:    /* image 1 / image 2 offsets */
326         PARSE_BYTES_NEEDED (5);
327
328         dec->offset[0] = (((guint) buf[1]) << 8) | buf[2];
329         dec->offset[1] = (((guint) buf[3]) << 8) | buf[4];
330         GST_DEBUG_OBJECT (dec, "Offset1 %d, Offset2 %d",
331             dec->offset[0], dec->offset[1]);
332
333         dec->buf_dirty = TRUE;
334         buf += 5;
335         break;
336       case SPU_WIPE:
337       {
338         guint length;
339
340         PARSE_BYTES_NEEDED (3);
341
342         GST_WARNING_OBJECT (dec, "SPU_WIPE not yet implemented");
343
344         length = (buf[1] << 8) | (buf[2]);
345         buf += 1 + length;
346
347         dec->buf_dirty = TRUE;
348         break;
349       }
350       case SPU_END:
351         buf = (last_seq) ? end : next_seq;
352
353         /* Start a new control sequence */
354         if (buf + 4 < end) {
355           guint16 ticks = GST_READ_UINT16_BE (buf);
356
357           event_time = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
358
359           GST_DEBUG_OBJECT (dec,
360               "Next DCSQ at offset %u, delay %g secs (%d ticks)",
361               (guint) (buf - start),
362               gst_util_guint64_to_gdouble (event_time / GST_SECOND), ticks);
363
364           dec->parse_pos = buf;
365           if (event_time > 0) {
366             dec->next_event_ts += event_time;
367
368             GST_LOG_OBJECT (dec, "Exiting parse loop with time %g",
369                 gst_guint64_to_gdouble (dec->next_event_ts) /
370                 gst_guint64_to_gdouble (GST_SECOND));
371             return;
372           }
373           break;
374         } else {
375           dec->parse_pos = NULL;
376           dec->next_event_ts = GST_CLOCK_TIME_NONE;
377           GST_LOG_OBJECT (dec, "Finished all cmds. Exiting parse loop");
378           return;
379         }
380       default:
381         GST_ERROR
382             ("Invalid sequence in subtitle packet header (%.2x). Skipping",
383             *buf);
384         broken = TRUE;
385         dec->parse_pos = NULL;
386         break;
387     }
388   }
389 }
390
391 static inline int
392 gst_get_nibble (guchar * buffer, RLE_state * state)
393 {
394   if (state->aligned) {
395     state->next = buffer[state->offset[state->id]++];
396     state->aligned = 0;
397     return state->next >> 4;
398   } else {
399     state->aligned = 1;
400     return state->next & 0xf;
401   }
402 }
403
404 /* Premultiply the current lookup table into the "target" cache */
405 static void
406 gst_setup_palette (GstDvdSubDec * dec)
407 {
408   gint i;
409   guint32 col;
410   Color_val *target_yuv = dec->palette_cache_yuv;
411   Color_val *target2_yuv = dec->hl_palette_cache_yuv;
412   Color_val *target_rgb = dec->palette_cache_rgb;
413   Color_val *target2_rgb = dec->hl_palette_cache_rgb;
414
415   for (i = 0; i < 4; i++, target2_yuv++, target_yuv++) {
416     col = dec->current_clut[dec->subtitle_index[i]];
417     target_yuv->Y_R = (col >> 16) & 0xff;
418     target_yuv->V_B = (col >> 8) & 0xff;
419     target_yuv->U_G = col & 0xff;
420     target_yuv->A = dec->subtitle_alpha[i] * 0xff / 0xf;
421
422     col = dec->current_clut[dec->menu_index[i]];
423     target2_yuv->Y_R = (col >> 16) & 0xff;
424     target2_yuv->V_B = (col >> 8) & 0xff;
425     target2_yuv->U_G = col & 0xff;
426     target2_yuv->A = dec->menu_alpha[i] * 0xff / 0xf;
427
428     /* If ARGB flag set, then convert YUV palette to RGB */
429     /* Using integer arithmetic */
430     if (dec->use_ARGB) {
431       guchar C = target_yuv->Y_R - 16;
432       guchar D = target_yuv->U_G - 128;
433       guchar E = target_yuv->V_B - 128;
434
435       target_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
436       target_rgb->U_G =
437           CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
438       target_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
439       target_rgb->A = target_yuv->A;
440
441       C = target2_yuv->Y_R - 16;
442       D = target2_yuv->U_G - 128;
443       E = target2_yuv->V_B - 128;
444
445       target2_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
446       target2_rgb->U_G =
447           CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
448       target2_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
449       target2_rgb->A = target2_yuv->A;
450     }
451     target_rgb++;
452     target2_rgb++;
453   }
454 }
455
456 static inline guint
457 gst_get_rle_code (guchar * buffer, RLE_state * state)
458 {
459   gint code;
460
461   code = gst_get_nibble (buffer, state);
462   if (code < 0x4) {             /* 4 .. f */
463     code = (code << 4) | gst_get_nibble (buffer, state);
464     if (code < 0x10) {          /* 1x .. 3x */
465       code = (code << 4) | gst_get_nibble (buffer, state);
466       if (code < 0x40) {        /* 04x .. 0fx */
467         code = (code << 4) | gst_get_nibble (buffer, state);
468       }
469     }
470   }
471   return code;
472 }
473
474 #define DRAW_RUN(target,len,c)                  \
475 G_STMT_START {                                  \
476   gint i = 0;                                   \
477   if ((c)->A) {                                 \
478     for (i = 0; i < (len); i++) {               \
479       *(target)++ = (c)->A;                     \
480       *(target)++ = (c)->Y_R;                   \
481       *(target)++ = (c)->U_G;                   \
482       *(target)++ = (c)->V_B;                   \
483     }                                           \
484   } else {                                      \
485     (target) += 4 * (len);                      \
486   }                                             \
487 } G_STMT_END
488
489 /* 
490  * This function steps over each run-length segment, drawing 
491  * into the YUVA/ARGB buffers as it goes. UV are composited and then output
492  * at half width/height
493  */
494 static void
495 gst_draw_rle_line (GstDvdSubDec * dec, guchar * buffer, RLE_state * state)
496 {
497   gint length, colourid;
498   guint code;
499   gint x, right;
500   guchar *target;
501
502   target = state->target;
503
504   x = dec->left;
505   right = dec->right + 1;
506
507   while (x < right) {
508     gboolean in_hl;
509     const Color_val *colour_entry;
510
511     code = gst_get_rle_code (buffer, state);
512     length = code >> 2;
513     colourid = code & 3;
514     if (dec->use_ARGB)
515       colour_entry = dec->palette_cache_rgb + colourid;
516     else
517       colour_entry = dec->palette_cache_yuv + colourid;
518
519     /* Length = 0 implies fill to the end of the line */
520     /* Restrict the colour run to the end of the line */
521     if (length == 0 || x + length > right)
522       length = right - x;
523
524     /* Check if this run of colour touches the highlight region */
525     in_hl = ((x <= state->hl_right) && (x + length) >= state->hl_left);
526     if (in_hl) {
527       gint run;
528
529       /* Draw to the left of the highlight */
530       if (x <= state->hl_left) {
531         run = MIN (length, state->hl_left - x + 1);
532
533         DRAW_RUN (target, run, colour_entry);
534         length -= run;
535         x += run;
536       }
537
538       /* Draw across the highlight region */
539       if (x <= state->hl_right) {
540         const Color_val *hl_colour;
541         if (dec->use_ARGB)
542           hl_colour = dec->hl_palette_cache_rgb + colourid;
543         else
544           hl_colour = dec->hl_palette_cache_yuv + colourid;
545
546         run = MIN (length, state->hl_right - x + 1);
547
548         DRAW_RUN (target, run, hl_colour);
549         length -= run;
550         x += run;
551       }
552     }
553
554     /* Draw the rest of the run */
555     if (length > 0) {
556       DRAW_RUN (target, length, colour_entry);
557       x += length;
558     }
559   }
560 }
561
562 /*
563  * Decode the RLE subtitle image and blend with the current
564  * frame buffer.
565  */
566 static void
567 gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec, GstVideoFrame * frame)
568 {
569   gint y;
570   gint Y_stride;
571   guchar *buffer = dec->partialmap.data;
572   gint hl_top, hl_bottom;
573   gint last_y;
574   RLE_state state;
575   guint8 *Y_data;
576
577   GST_DEBUG_OBJECT (dec, "Merging subtitle on frame");
578
579   Y_data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
580   Y_stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
581
582   state.id = 0;
583   state.aligned = 1;
584   state.next = 0;
585   state.offset[0] = dec->offset[0];
586   state.offset[1] = dec->offset[1];
587
588   /* center the image when display rectangle exceeds the video width */
589   if (dec->in_width <= dec->right) {
590     gint left, disp_width;
591
592     disp_width = dec->right - dec->left + 1;
593     left = (dec->in_width - disp_width) / 2;
594     dec->left = left;
595     dec->right = left + disp_width - 1;
596
597     /* if it clips to the right, shift it left, but only till zero */
598     if (dec->right >= dec->in_width) {
599       gint shift = dec->right - dec->in_width - 1;
600       if (shift > dec->left)
601         shift = dec->left;
602       dec->left -= shift;
603       dec->right -= shift;
604     }
605
606     GST_DEBUG_OBJECT (dec, "clipping width to %d,%d",
607         dec->left, dec->in_width - 1);
608   }
609
610   /* for the height, bring it up till it fits as well as it can. We
611    * assume the picture is in the lower part. We should better check where it
612    * is and do something more clever. */
613   if (dec->in_height <= dec->bottom) {
614
615     /* shift it up, but only till zero */
616     gint shift = dec->bottom - dec->in_height - 1;
617     if (shift > dec->top)
618       shift = dec->top;
619     dec->top -= shift;
620     dec->bottom -= shift;
621
622     /* start on even line */
623     if (dec->top & 1) {
624       dec->top--;
625       dec->bottom--;
626     }
627
628     GST_DEBUG_OBJECT (dec, "clipping height to %d,%d",
629         dec->top, dec->in_height - 1);
630   }
631
632   if (dec->current_button) {
633     hl_top = dec->hl_top;
634     hl_bottom = dec->hl_bottom;
635   } else {
636     hl_top = -1;
637     hl_bottom = -1;
638   }
639   last_y = MIN (dec->bottom, dec->in_height);
640
641   y = dec->top;
642   state.target = Y_data + 4 * dec->left + (y * Y_stride);
643
644   /* Now draw scanlines until we hit last_y or end of RLE data */
645   for (; ((state.offset[1] < dec->data_size + 2) && (y <= last_y)); y++) {
646     /* Set up to draw the highlight if we're in the right scanlines */
647     if (y > hl_bottom || y < hl_top) {
648       state.hl_left = -1;
649       state.hl_right = -1;
650     } else {
651       state.hl_left = dec->hl_left;
652       state.hl_right = dec->hl_right;
653     }
654     gst_draw_rle_line (dec, buffer, &state);
655
656     state.target += Y_stride;
657
658     /* Realign the RLE state for the next line */
659     if (!state.aligned)
660       gst_get_nibble (buffer, &state);
661     state.id = !state.id;
662   }
663 }
664
665 static void
666 gst_send_empty_fill (GstDvdSubDec * dec, GstClockTime ts)
667 {
668   if (dec->next_ts < ts) {
669     GST_LOG_OBJECT (dec, "Sending GAP event update to advance time to %"
670         GST_TIME_FORMAT, GST_TIME_ARGS (ts));
671
672     gst_pad_push_event (dec->srcpad,
673         gst_event_new_gap (dec->next_ts, ts - dec->next_ts));
674   }
675   dec->next_ts = ts;
676 }
677
678 static GstFlowReturn
679 gst_send_subtitle_frame (GstDvdSubDec * dec, GstClockTime end_ts)
680 {
681   GstFlowReturn flow;
682   GstBuffer *out_buf;
683   GstVideoFrame frame;
684   guint8 *data;
685   gint x, y;
686   static GstAllocationParams params = { 0, 3, 0, 0, };
687
688   g_assert (dec->have_title);
689   g_assert (dec->next_ts <= end_ts);
690
691   /* Check if we need to redraw the output buffer */
692   if (!dec->buf_dirty) {
693     flow = GST_FLOW_OK;
694     goto out;
695   }
696
697   out_buf =
698       gst_buffer_new_allocate (NULL, GST_VIDEO_INFO_SIZE (&dec->info), &params);
699   gst_video_frame_map (&frame, &dec->info, out_buf, GST_MAP_READWRITE);
700
701   data = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
702
703   /* Clear the buffer */
704   /* FIXME - move this into the buffer rendering code */
705   for (y = 0; y < dec->in_height; y++) {
706     guchar *line = data + 4 * dec->in_width * y;
707
708     for (x = 0; x < dec->in_width; x++) {
709       line[0] = 0;              /* A */
710       if (!dec->use_ARGB) {
711         line[1] = 16;           /* Y */
712         line[2] = 128;          /* U */
713         line[3] = 128;          /* V */
714       } else {
715         line[1] = 0;            /* R */
716         line[2] = 0;            /* G */
717         line[3] = 0;            /* B */
718       }
719
720       line += 4;
721     }
722   }
723
724   /* FIXME: do we really want to honour the forced_display flag
725    * for subtitles streans? */
726   if (dec->visible || dec->forced_display) {
727     gst_dvd_sub_dec_merge_title (dec, &frame);
728   }
729
730   gst_video_frame_unmap (&frame);
731
732   dec->buf_dirty = FALSE;
733
734   GST_BUFFER_TIMESTAMP (out_buf) = dec->next_ts;
735   if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts)) {
736     GST_BUFFER_DURATION (out_buf) = GST_CLOCK_DIFF (dec->next_ts,
737         dec->next_event_ts);
738   } else {
739     GST_BUFFER_DURATION (out_buf) = GST_CLOCK_TIME_NONE;
740   }
741
742   GST_DEBUG_OBJECT (dec, "Sending subtitle buffer with ts %"
743       GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT,
744       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)),
745       GST_BUFFER_DURATION (out_buf));
746
747   flow = gst_pad_push (dec->srcpad, out_buf);
748
749 out:
750   dec->next_ts = end_ts;
751   return flow;
752 }
753
754 /* Walk time forward, processing any subtitle events as needed. */
755 static GstFlowReturn
756 gst_dvd_sub_dec_advance_time (GstDvdSubDec * dec, GstClockTime new_ts)
757 {
758   GstFlowReturn ret = GST_FLOW_OK;
759
760   GST_LOG_OBJECT (dec, "Advancing time to %" GST_TIME_FORMAT,
761       GST_TIME_ARGS (new_ts));
762
763   if (!dec->have_title) {
764     gst_send_empty_fill (dec, new_ts);
765     return ret;
766   }
767
768   while (dec->next_ts < new_ts) {
769     GstClockTime next_ts = new_ts;
770
771     if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts) &&
772         dec->next_event_ts < next_ts) {
773       /* We might need to process the subtitle cmd queue */
774       next_ts = dec->next_event_ts;
775     }
776
777     /* 
778      * Now, either output a filler or a frame spanning
779      * dec->next_ts to next_ts
780      */
781     if (dec->visible || dec->forced_display) {
782       ret = gst_send_subtitle_frame (dec, next_ts);
783     } else {
784       gst_send_empty_fill (dec, next_ts);
785     }
786
787     /*
788      * and then process some subtitle cmds if we need
789      */
790     if (next_ts == dec->next_event_ts)
791       gst_dvd_sub_dec_parse_subpic (dec);
792   }
793
794   return ret;
795 }
796
797 static GstFlowReturn
798 gst_dvd_sub_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
799 {
800   GstFlowReturn ret = GST_FLOW_OK;
801   GstDvdSubDec *dec;
802   guint8 *data;
803   glong size = 0;
804
805   dec = GST_DVD_SUB_DEC (parent);
806
807   GST_DEBUG_OBJECT (dec, "Have buffer of size %" G_GSIZE_FORMAT ", ts %"
808       GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT, gst_buffer_get_size (buf),
809       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_DURATION (buf));
810
811   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
812     if (!GST_CLOCK_TIME_IS_VALID (dec->next_ts)) {
813       dec->next_ts = GST_BUFFER_TIMESTAMP (buf);
814     }
815
816     /* Move time forward to the start of the new buffer */
817     ret = gst_dvd_sub_dec_advance_time (dec, GST_BUFFER_TIMESTAMP (buf));
818   }
819
820   if (dec->have_title) {
821     gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
822     gst_buffer_unref (dec->partialbuf);
823     dec->partialbuf = NULL;
824     dec->have_title = FALSE;
825   }
826
827   GST_DEBUG_OBJECT (dec, "Got subtitle buffer, pts %" GST_TIME_FORMAT,
828       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
829
830   /* deal with partial frame from previous buffer */
831   if (dec->partialbuf) {
832     gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
833     dec->partialbuf = gst_buffer_append (dec->partialbuf, buf);
834   } else {
835     dec->partialbuf = buf;
836   }
837
838   gst_buffer_map (dec->partialbuf, &dec->partialmap, GST_MAP_READ);
839
840   data = dec->partialmap.data;
841   size = dec->partialmap.size;
842
843   if (size > 4) {
844     dec->packet_size = GST_READ_UINT16_BE (data);
845
846     if (dec->packet_size == size) {
847       GST_LOG_OBJECT (dec, "Subtitle packet size %d, current size %ld",
848           dec->packet_size, size);
849
850       dec->data_size = GST_READ_UINT16_BE (data + 2);
851
852       /* Reset parameters for a new subtitle buffer */
853       dec->parse_pos = data;
854       dec->forced_display = FALSE;
855       dec->visible = FALSE;
856
857       dec->have_title = TRUE;
858       dec->next_event_ts = GST_BUFFER_TIMESTAMP (dec->partialbuf);
859
860       if (!GST_CLOCK_TIME_IS_VALID (dec->next_event_ts))
861         dec->next_event_ts = dec->next_ts;
862
863       dec->next_event_ts += gst_dvd_sub_dec_get_event_delay (dec);
864     }
865   }
866
867   return ret;
868 }
869
870 static gboolean
871 gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps)
872 {
873   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
874   gboolean ret = FALSE;
875   GstCaps *out_caps = NULL, *peer_caps = NULL;
876
877   GST_DEBUG_OBJECT (dec, "setcaps called with %" GST_PTR_FORMAT, caps);
878
879   out_caps = gst_caps_new_simple ("video/x-raw",
880       "format", G_TYPE_STRING, "AYUV",
881       "width", G_TYPE_INT, dec->in_width,
882       "height", G_TYPE_INT, dec->in_height,
883       "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
884
885   peer_caps = gst_pad_get_allowed_caps (dec->srcpad);
886   if (G_LIKELY (peer_caps)) {
887     guint i = 0, n = 0;
888
889     n = gst_caps_get_size (peer_caps);
890     GST_DEBUG_OBJECT (dec, "peer allowed caps (%u structure(s)) are %"
891         GST_PTR_FORMAT, n, peer_caps);
892
893     for (i = 0; i < n; i++) {
894       GstStructure *s = gst_caps_get_structure (peer_caps, i);
895       /* Check if the peer pad support ARGB format, if yes change caps */
896       if (gst_structure_has_name (s, "video/x-raw")) {
897         GstCaps *downstream_caps;
898         gst_caps_unref (out_caps);
899         GST_DEBUG_OBJECT (dec, "trying with ARGB");
900
901         out_caps = gst_caps_new_simple ("video/x-raw",
902             "format", G_TYPE_STRING, "ARGB",
903             "width", G_TYPE_INT, dec->in_width,
904             "height", G_TYPE_INT, dec->in_height,
905             "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
906
907         downstream_caps = gst_pad_peer_query_caps (dec->srcpad, NULL);
908         if (gst_caps_can_intersect (downstream_caps, out_caps)) {
909           gst_caps_unref (downstream_caps);
910           GST_DEBUG_OBJECT (dec, "peer accepted ARGB");
911           /* If ARGB format then set the flag */
912           dec->use_ARGB = TRUE;
913           break;
914         }
915         gst_caps_unref (downstream_caps);
916       }
917     }
918     gst_caps_unref (peer_caps);
919   }
920   GST_DEBUG_OBJECT (dec, "setting caps downstream to %" GST_PTR_FORMAT,
921       out_caps);
922   if (gst_pad_set_caps (dec->srcpad, out_caps)) {
923     gst_video_info_from_caps (&dec->info, out_caps);
924   } else {
925     GST_WARNING_OBJECT (dec, "failed setting downstream caps");
926     gst_caps_unref (out_caps);
927     goto beach;
928   }
929
930   gst_caps_unref (out_caps);
931   ret = TRUE;
932
933 beach:
934   gst_object_unref (dec);
935   return ret;
936 }
937
938 static gboolean
939 gst_dvd_sub_dec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
940 {
941   GstDvdSubDec *dec = GST_DVD_SUB_DEC (parent);
942   gboolean ret = FALSE;
943
944   GST_LOG_OBJECT (dec, "%s event", GST_EVENT_TYPE_NAME (event));
945
946   switch (GST_EVENT_TYPE (event)) {
947     case GST_EVENT_CAPS:
948     {
949       GstCaps *caps;
950
951       gst_event_parse_caps (event, &caps);
952       ret = gst_dvd_sub_dec_sink_setcaps (pad, caps);
953       gst_event_unref (event);
954       break;
955     }
956     case GST_EVENT_CUSTOM_DOWNSTREAM:{
957
958       if (gst_event_has_name (event, "application/x-gst-dvd")) {
959         const GstStructure *s = gst_event_get_structure (event);
960         GstClockTime ts = GST_CLOCK_TIME_NONE;
961
962         if (gst_structure_get_clock_time (s, "ts", &ts)
963             && GST_CLOCK_TIME_IS_VALID (ts))
964           gst_dvd_sub_dec_advance_time (dec, ts);
965
966         if (gst_dvd_sub_dec_handle_dvd_event (dec, event)) {
967           /* gst_dvd_sub_dec_advance_time (dec, dec->next_ts + GST_SECOND / 30.0); */
968           gst_event_unref (event);
969           ret = TRUE;
970           break;
971         }
972       }
973
974       ret = gst_pad_event_default (pad, parent, event);
975       break;
976     }
977     case GST_EVENT_GAP:
978     {
979       GstClockTime start, duration;
980
981       gst_event_parse_gap (event, &start, &duration);
982       if (GST_CLOCK_TIME_IS_VALID (start)) {
983         if (GST_CLOCK_TIME_IS_VALID (duration))
984           start += duration;
985         /* we do not expect another buffer until after gap,
986          * so that is our position now */
987         GST_DEBUG_OBJECT (dec, "Got GAP event, advancing time from %"
988             GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
989             GST_TIME_ARGS (dec->next_ts), GST_TIME_ARGS (start));
990
991         gst_dvd_sub_dec_advance_time (dec, start);
992       } else {
993         GST_WARNING_OBJECT (dec, "Got GAP event with invalid position");
994       }
995
996       gst_event_unref (event);
997       ret = TRUE;
998       break;
999     }
1000     case GST_EVENT_SEGMENT:
1001     {
1002       GstSegment seg;
1003
1004       gst_event_copy_segment (event, &seg);
1005
1006       {
1007 #if 0
1008         /* Turn off forced highlight display */
1009         dec->forced_display = 0;
1010         dec->current_button = 0;
1011 #endif
1012         if (dec->partialbuf) {
1013           gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
1014           gst_buffer_unref (dec->partialbuf);
1015           dec->partialbuf = NULL;
1016           dec->have_title = FALSE;
1017         }
1018
1019         if (GST_CLOCK_TIME_IS_VALID (seg.time))
1020           dec->next_ts = seg.time;
1021         else
1022           dec->next_ts = GST_CLOCK_TIME_NONE;
1023
1024         GST_DEBUG_OBJECT (dec, "Got newsegment, new time = %"
1025             GST_TIME_FORMAT, GST_TIME_ARGS (dec->next_ts));
1026
1027         ret = gst_pad_event_default (pad, parent, event);
1028       }
1029       break;
1030     }
1031     case GST_EVENT_FLUSH_STOP:{
1032       /* Turn off forced highlight display */
1033       dec->forced_display = 0;
1034       dec->current_button = 0;
1035
1036       if (dec->partialbuf) {
1037         gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
1038         gst_buffer_unref (dec->partialbuf);
1039         dec->partialbuf = NULL;
1040         dec->have_title = FALSE;
1041       }
1042
1043       ret = gst_pad_event_default (pad, parent, event);
1044       break;
1045     }
1046     default:{
1047       ret = gst_pad_event_default (pad, parent, event);
1048       break;
1049     }
1050   }
1051   return ret;
1052 }
1053
1054 static gboolean
1055 gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec, GstEvent * event)
1056 {
1057   GstStructure *structure;
1058   const gchar *event_name;
1059
1060   structure = (GstStructure *) gst_event_get_structure (event);
1061
1062   if (structure == NULL)
1063     goto not_handled;
1064
1065   event_name = gst_structure_get_string (structure, "event");
1066
1067   GST_LOG_OBJECT (dec,
1068       "DVD event %s with timestamp %" G_GINT64_FORMAT " on sub pad",
1069       GST_STR_NULL (event_name), GST_EVENT_TIMESTAMP (event));
1070
1071   if (event_name == NULL)
1072     goto not_handled;
1073
1074   if (strcmp (event_name, "dvd-spu-highlight") == 0) {
1075     gint button;
1076     gint palette, sx, sy, ex, ey;
1077     gint i;
1078
1079     /* Details for the highlight region to display */
1080     if (!gst_structure_get_int (structure, "button", &button) ||
1081         !gst_structure_get_int (structure, "palette", &palette) ||
1082         !gst_structure_get_int (structure, "sx", &sx) ||
1083         !gst_structure_get_int (structure, "sy", &sy) ||
1084         !gst_structure_get_int (structure, "ex", &ex) ||
1085         !gst_structure_get_int (structure, "ey", &ey)) {
1086       GST_ERROR_OBJECT (dec, "Invalid dvd-spu-highlight event received");
1087       return TRUE;
1088     }
1089     dec->current_button = button;
1090     dec->hl_left = sx;
1091     dec->hl_top = sy;
1092     dec->hl_right = ex;
1093     dec->hl_bottom = ey;
1094     for (i = 0; i < 4; i++) {
1095       dec->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f;
1096       dec->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f;
1097     }
1098
1099     GST_DEBUG_OBJECT (dec, "New button activated highlight=(%d,%d) to (%d,%d) "
1100         "palette 0x%x", sx, sy, ex, ey, palette);
1101     gst_setup_palette (dec);
1102
1103     dec->buf_dirty = TRUE;
1104   } else if (strcmp (event_name, "dvd-spu-clut-change") == 0) {
1105     /* Take a copy of the colour table */
1106     gchar name[16];
1107     int i;
1108     gint value;
1109
1110     GST_LOG_OBJECT (dec, "New colour table received");
1111     for (i = 0; i < 16; i++) {
1112       g_snprintf (name, sizeof (name), "clut%02d", i);
1113       if (!gst_structure_get_int (structure, name, &value)) {
1114         GST_ERROR_OBJECT (dec, "dvd-spu-clut-change event did not "
1115             "contain %s field", name);
1116         break;
1117       }
1118       dec->current_clut[i] = (guint32) (value);
1119     }
1120
1121     gst_setup_palette (dec);
1122
1123     dec->buf_dirty = TRUE;
1124   } else if (strcmp (event_name, "dvd-spu-stream-change") == 0
1125       || strcmp (event_name, "dvd-spu-reset-highlight") == 0) {
1126     /* Turn off forced highlight display */
1127     dec->current_button = 0;
1128
1129     GST_LOG_OBJECT (dec, "Clearing button state");
1130     dec->buf_dirty = TRUE;
1131   } else if (strcmp (event_name, "dvd-spu-still-frame") == 0) {
1132     /* Handle a still frame */
1133     GST_LOG_OBJECT (dec, "Received still frame notification");
1134   } else {
1135     goto not_handled;
1136   }
1137
1138   return TRUE;
1139
1140 not_handled:
1141   {
1142     /* Ignore all other unknown events */
1143     GST_LOG_OBJECT (dec, "Ignoring other custom event %" GST_PTR_FORMAT,
1144         structure);
1145     return FALSE;
1146   }
1147 }
1148
1149 static gboolean
1150 plugin_init (GstPlugin * plugin)
1151 {
1152   gboolean ret = FALSE;
1153
1154   ret |= GST_ELEMENT_REGISTER (dvdsubdec, plugin);
1155   ret |= GST_ELEMENT_REGISTER (dvdsubparse, plugin);
1156
1157   return ret;
1158 }
1159
1160 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1161     GST_VERSION_MINOR,
1162     dvdsub,
1163     "DVD subtitle parser and decoder", plugin_init,
1164     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);