]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - glsdk/gst-plugins-ugly0-10.git/blob - gst/dvdsub/gstdvdsubdec.c
various: fix pad template ref leaks
[glsdk/gst-plugins-ugly0-10.git] / 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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
21 /* TODO: liboil-ise code, esp. use _splat() family of functions */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include "gstdvdsubdec.h"
28 #include "gstdvdsubparse.h"
29 #include <string.h>
31 GST_BOILERPLATE (GstDvdSubDec, gst_dvd_sub_dec, GstElement, GST_TYPE_ELEMENT);
33 static gboolean gst_dvd_sub_dec_src_event (GstPad * srcpad, GstEvent * event);
34 static GstFlowReturn gst_dvd_sub_dec_chain (GstPad * pad, GstBuffer * buf);
36 static gboolean gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec,
37     GstEvent * event);
38 static void gst_dvd_sub_dec_finalize (GObject * gobject);
39 static void gst_setup_palette (GstDvdSubDec * dec);
40 static void gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec, GstBuffer * buf);
41 static GstClockTime gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec);
42 static gboolean gst_dvd_sub_dec_sink_event (GstPad * pad, GstEvent * event);
43 static gboolean gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps);
45 static GstFlowReturn gst_send_subtitle_frame (GstDvdSubDec * dec,
46     GstClockTime end_ts);
48 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
49     GST_PAD_SRC,
50     GST_PAD_ALWAYS,
51     GST_STATIC_CAPS ("video/x-raw-yuv, format = (fourcc) AYUV, "
52         "width = (int) 720, height = (int) 576, framerate = (fraction) 0/1; "
53         "video/x-raw-rgb, "
54         "width = (int) 720, height = (int) 576, framerate = (fraction) 0/1, "
55         "bpp = (int) 32, endianness = (int) 4321, red_mask = (int) 16711680, "
56         "green_mask = (int) 65280, blue_mask = (int) 255, "
57         " alpha_mask = (int) -16777216, depth = (int) 32")
58     );
60 static GstStaticPadTemplate subtitle_template = GST_STATIC_PAD_TEMPLATE ("sink",
61     GST_PAD_SINK,
62     GST_PAD_ALWAYS,
63     GST_STATIC_CAPS ("video/x-dvd-subpicture")
64     );
66 GST_DEBUG_CATEGORY_STATIC (gst_dvd_sub_dec_debug);
67 #define GST_CAT_DEFAULT (gst_dvd_sub_dec_debug)
69 enum
70 {
71   SPU_FORCE_DISPLAY = 0x00,
72   SPU_SHOW = 0x01,
73   SPU_HIDE = 0x02,
74   SPU_SET_PALETTE = 0x03,
75   SPU_SET_ALPHA = 0x04,
76   SPU_SET_SIZE = 0x05,
77   SPU_SET_OFFSETS = 0x06,
78   SPU_WIPE = 0x07,
79   SPU_END = 0xff
80 };
82 static const guint32 default_clut[16] = {
83   0xb48080, 0x248080, 0x628080, 0xd78080,
84   0x808080, 0x808080, 0x808080, 0x808080,
85   0x808080, 0x808080, 0x808080, 0x808080,
86   0x808080, 0x808080, 0x808080, 0x808080
87 };
89 typedef struct RLE_state
90 {
91   gint id;
92   gint aligned;
93   gint offset[2];
94   gint hl_left;
95   gint hl_right;
97   guchar *target;
99   guchar next;
101 RLE_state;
103 static void
104 gst_dvd_sub_dec_base_init (gpointer klass)
106   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
108   gst_element_class_add_static_pad_template (element_class, &src_template);
109   gst_element_class_add_static_pad_template (element_class,
110       &subtitle_template);
112   gst_element_class_set_details_simple (element_class, "DVD subtitle decoder",
113       "Codec/Decoder/Video", "Decodes DVD subtitles into AYUV video frames",
114       "Wim Taymans <wim.taymans@gmail.com>, "
115       "Jan Schmidt <thaytan@mad.scientist.com>");
118 static void
119 gst_dvd_sub_dec_class_init (GstDvdSubDecClass * klass)
121   GObjectClass *gobject_class;
123   gobject_class = (GObjectClass *) klass;
125   gobject_class->finalize = gst_dvd_sub_dec_finalize;
128 static void
129 gst_dvd_sub_dec_init (GstDvdSubDec * dec, GstDvdSubDecClass * klass)
131   GstPadTemplate *tmpl;
133   dec->sinkpad = gst_pad_new_from_static_template (&subtitle_template, "sink");
134   gst_pad_set_chain_function (dec->sinkpad,
135       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_chain));
136   gst_pad_set_event_function (dec->sinkpad,
137       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_sink_event));
138   gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
139   gst_pad_set_setcaps_function (dec->sinkpad, gst_dvd_sub_dec_sink_setcaps);
141   tmpl = gst_static_pad_template_get (&src_template);
142   dec->srcpad = gst_pad_new_from_template (tmpl, "src");
143   gst_pad_set_event_function (dec->srcpad,
144       GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_src_event));
145   gst_pad_use_fixed_caps (dec->srcpad);
146   gst_object_unref (tmpl);
147   gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
149   /* FIXME: aren't there more possible sizes? (tpm) */
150   dec->in_width = 720;
151   dec->in_height = 576;
153   dec->partialbuf = NULL;
154   dec->have_title = FALSE;
155   dec->parse_pos = NULL;
156   dec->forced_display = FALSE;
157   dec->visible = FALSE;
159   memcpy (dec->current_clut, default_clut, sizeof (guint32) * 16);
161   gst_setup_palette (dec);
163   dec->next_ts = 0;
164   dec->next_event_ts = GST_CLOCK_TIME_NONE;
166   dec->buf_dirty = TRUE;
167   dec->use_ARGB = FALSE;
170 static void
171 gst_dvd_sub_dec_finalize (GObject * gobject)
173   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gobject);
175   if (dec->partialbuf) {
176     gst_buffer_unref (dec->partialbuf);
177     dec->partialbuf = NULL;
178   }
180   G_OBJECT_CLASS (parent_class)->finalize (gobject);
183 static gboolean
184 gst_dvd_sub_dec_src_event (GstPad * pad, GstEvent * event)
186   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
187   gboolean res = FALSE;
189   switch (GST_EVENT_TYPE (event)) {
190     default:
191       res = gst_pad_event_default (pad, event);
192       break;
193   }
195   gst_object_unref (dec);
196   return res;
199 static GstClockTime
200 gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec)
202   guchar *start = GST_BUFFER_DATA (dec->partialbuf);
203   guchar *buf;
204   guint16 ticks;
205   GstClockTime event_delay;
207   /* If starting a new buffer, follow the first DCSQ ptr */
208   if (dec->parse_pos == start) {
209     buf = dec->parse_pos + dec->data_size;
210   } else {
211     buf = dec->parse_pos;
212   }
214   ticks = GST_READ_UINT16_BE (buf);
215   event_delay = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
217   GST_DEBUG_OBJECT (dec, "returning delay %" GST_TIME_FORMAT " from offset %u",
218       GST_TIME_ARGS (event_delay), (guint) (buf - dec->parse_pos));
220   return event_delay;
223 /*
224  * Parse the next event time in the current subpicture buffer, stopping
225  * when time advances to the next state. 
226  */
227 static void
228 gst_dvd_sub_dec_parse_subpic (GstDvdSubDec * dec)
230 #define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \
231   { GST_WARNING("Subtitle stream broken parsing %c", *buf); \
232     broken = TRUE; break; }
234   guchar *start = GST_BUFFER_DATA (dec->partialbuf);
235   guchar *buf;
236   guchar *end;
237   gboolean broken = FALSE;
238   gboolean last_seq = FALSE;
239   guchar *next_seq = NULL;
240   GstClockTime event_time;
242   /* nothing to do if we finished this buffer already */
243   if (dec->parse_pos == NULL)
244     return;
246   g_return_if_fail (dec->packet_size >= 4);
248   end = start + dec->packet_size;
249   if (dec->parse_pos == start) {
250     buf = dec->parse_pos + dec->data_size;
251   } else {
252     buf = dec->parse_pos;
253   }
255   g_assert (buf >= start && buf < end);
257   /* If the next control sequence is at the current offset, this is 
258    * the last one */
259   next_seq = start + GST_READ_UINT16_BE (buf + 2);
260   last_seq = (next_seq == buf);
261   buf += 4;
263   while ((buf < end) && (!broken)) {
264     switch (*buf) {
265       case SPU_FORCE_DISPLAY:  /* Forced display menu subtitle */
266         dec->forced_display = TRUE;
267         dec->buf_dirty = TRUE;
268         GST_DEBUG_OBJECT (dec, "SPU FORCE_DISPLAY");
269         buf++;
270         break;
271       case SPU_SHOW:           /* Show the subtitle in this packet */
272         dec->visible = TRUE;
273         dec->buf_dirty = TRUE;
274         GST_DEBUG_OBJECT (dec, "SPU SHOW at %" GST_TIME_FORMAT,
275             GST_TIME_ARGS (dec->next_event_ts));
276         buf++;
277         break;
278       case SPU_HIDE:
279         /* 02 ff (ff) is the end of the packet, hide the subpicture */
280         dec->visible = FALSE;
281         dec->buf_dirty = TRUE;
283         GST_DEBUG_OBJECT (dec, "SPU HIDE at %" GST_TIME_FORMAT,
284             GST_TIME_ARGS (dec->next_event_ts));
285         buf++;
286         break;
287       case SPU_SET_PALETTE:    /* palette */
288         PARSE_BYTES_NEEDED (3);
290         GST_DEBUG_OBJECT (dec, "SPU SET_PALETTE");
292         dec->subtitle_index[3] = buf[1] >> 4;
293         dec->subtitle_index[2] = buf[1] & 0xf;
294         dec->subtitle_index[1] = buf[2] >> 4;
295         dec->subtitle_index[0] = buf[2] & 0xf;
296         gst_setup_palette (dec);
298         dec->buf_dirty = TRUE;
299         buf += 3;
300         break;
301       case SPU_SET_ALPHA:      /* transparency palette */
302         PARSE_BYTES_NEEDED (3);
304         GST_DEBUG_OBJECT (dec, "SPU SET_ALPHA");
306         dec->subtitle_alpha[3] = buf[1] >> 4;
307         dec->subtitle_alpha[2] = buf[1] & 0xf;
308         dec->subtitle_alpha[1] = buf[2] >> 4;
309         dec->subtitle_alpha[0] = buf[2] & 0xf;
310         gst_setup_palette (dec);
312         dec->buf_dirty = TRUE;
313         buf += 3;
314         break;
315       case SPU_SET_SIZE:       /* image coordinates */
316         PARSE_BYTES_NEEDED (7);
318         dec->top = ((buf[4] & 0x3f) << 4) | ((buf[5] & 0xe0) >> 4);
319         dec->left = ((buf[1] & 0x3f) << 4) | ((buf[2] & 0xf0) >> 4);
320         dec->right = ((buf[2] & 0x03) << 8) | buf[3];
321         dec->bottom = ((buf[5] & 0x03) << 8) | buf[6];
323         GST_DEBUG_OBJECT (dec, "SPU SET_SIZE left %d, top %d, right %d, "
324             "bottom %d", dec->left, dec->top, dec->right, dec->bottom);
326         dec->buf_dirty = TRUE;
327         buf += 7;
328         break;
329       case SPU_SET_OFFSETS:    /* image 1 / image 2 offsets */
330         PARSE_BYTES_NEEDED (5);
332         dec->offset[0] = (((guint) buf[1]) << 8) | buf[2];
333         dec->offset[1] = (((guint) buf[3]) << 8) | buf[4];
334         GST_DEBUG_OBJECT (dec, "Offset1 %d, Offset2 %d",
335             dec->offset[0], dec->offset[1]);
337         dec->buf_dirty = TRUE;
338         buf += 5;
339         break;
340       case SPU_WIPE:
341       {
342         guint length;
344         PARSE_BYTES_NEEDED (3);
346         GST_WARNING_OBJECT (dec, "SPU_WIPE not yet implemented");
348         length = (buf[1] << 8) | (buf[2]);
349         buf += 1 + length;
351         dec->buf_dirty = TRUE;
352         break;
353       }
354       case SPU_END:
355         buf = (last_seq) ? end : next_seq;
357         /* Start a new control sequence */
358         if (buf + 4 < end) {
359           guint16 ticks = GST_READ_UINT16_BE (buf);
361           event_time = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
363           GST_DEBUG_OBJECT (dec,
364               "Next DCSQ at offset %u, delay %g secs (%d ticks)",
365               (guint) (buf - start),
366               gst_util_guint64_to_gdouble (event_time / GST_SECOND), ticks);
368           dec->parse_pos = buf;
369           if (event_time > 0) {
370             dec->next_event_ts += event_time;
372             GST_LOG_OBJECT (dec, "Exiting parse loop with time %g",
373                 gst_guint64_to_gdouble (dec->next_event_ts) /
374                 gst_guint64_to_gdouble (GST_SECOND));
375             return;
376           }
377         } else {
378           dec->parse_pos = NULL;
379           dec->next_event_ts = GST_CLOCK_TIME_NONE;
380           GST_LOG_OBJECT (dec, "Finished all cmds. Exiting parse loop");
381           return;
382         }
383       default:
384         GST_ERROR
385             ("Invalid sequence in subtitle packet header (%.2x). Skipping",
386             *buf);
387         broken = TRUE;
388         dec->parse_pos = NULL;
389         break;
390     }
391   }
394 static inline int
395 gst_get_nibble (guchar * buffer, RLE_state * state)
397   if (state->aligned) {
398     state->next = buffer[state->offset[state->id]++];
399     state->aligned = 0;
400     return state->next >> 4;
401   } else {
402     state->aligned = 1;
403     return state->next & 0xf;
404   }
407 /* Premultiply the current lookup table into the "target" cache */
408 static void
409 gst_setup_palette (GstDvdSubDec * dec)
411   gint i;
412   guint32 col;
413   Color_val *target_yuv = dec->palette_cache_yuv;
414   Color_val *target2_yuv = dec->hl_palette_cache_yuv;
415   Color_val *target_rgb = dec->palette_cache_rgb;
416   Color_val *target2_rgb = dec->hl_palette_cache_rgb;
418   for (i = 0; i < 4; i++, target2_yuv++, target_yuv++) {
419     col = dec->current_clut[dec->subtitle_index[i]];
420     target_yuv->Y_R = (col >> 16) & 0xff;
421     target_yuv->V_B = (col >> 8) & 0xff;
422     target_yuv->U_G = col & 0xff;
423     target_yuv->A = dec->subtitle_alpha[i] * 0xff / 0xf;
425     col = dec->current_clut[dec->menu_index[i]];
426     target2_yuv->Y_R = (col >> 16) & 0xff;
427     target2_yuv->V_B = (col >> 8) & 0xff;
428     target2_yuv->U_G = col & 0xff;
429     target2_yuv->A = dec->menu_alpha[i] * 0xff / 0xf;
431     /* If ARGB flag set, then convert YUV palette to RGB */
432     /* Using integer aritmetic */
433     if (dec->use_ARGB) {
434       guchar C = target_yuv->Y_R - 16;
435       guchar D = target_yuv->U_G - 128;
436       guchar E = target_yuv->V_B - 128;
438       target_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
439       target_rgb->U_G =
440           CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
441       target_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
442       target_rgb->A = target_yuv->A;
444       C = target2_yuv->Y_R - 16;
445       D = target2_yuv->U_G - 128;
446       E = target2_yuv->V_B - 128;
448       target2_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
449       target2_rgb->U_G =
450           CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
451       target2_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
452       target2_rgb->A = target2_yuv->A;
453     }
454     target_rgb++;
455     target2_rgb++;
456   }
459 static inline guint
460 gst_get_rle_code (guchar * buffer, RLE_state * state)
462   gint code;
464   code = gst_get_nibble (buffer, state);
465   if (code < 0x4) {             /* 4 .. f */
466     code = (code << 4) | gst_get_nibble (buffer, state);
467     if (code < 0x10) {          /* 1x .. 3x */
468       code = (code << 4) | gst_get_nibble (buffer, state);
469       if (code < 0x40) {        /* 04x .. 0fx */
470         code = (code << 4) | gst_get_nibble (buffer, state);
471       }
472     }
473   }
474   return code;
477 #define DRAW_RUN(target,len,c)                  \
478 G_STMT_START {                                  \
479   gint i = 0;                                   \
480   if ((c)->A) {                                 \
481     for (i = 0; i < (len); i++) {               \
482       *(target)++ = (c)->A;                     \
483       *(target)++ = (c)->Y_R;                   \
484       *(target)++ = (c)->U_G;                   \
485       *(target)++ = (c)->V_B;                   \
486     }                                           \
487   } else {                                      \
488     (target) += 4 * (len);                      \
489   }                                             \
490 } G_STMT_END
492 /* 
493  * This function steps over each run-length segment, drawing 
494  * into the YUVA/ARGB buffers as it goes. UV are composited and then output
495  * at half width/height
496  */
497 static void
498 gst_draw_rle_line (GstDvdSubDec * dec, guchar * buffer, RLE_state * state)
500   gint length, colourid;
501   guint code;
502   gint x, right;
503   guchar *target;
505   target = state->target;
507   x = dec->left;
508   right = dec->right + 1;
510   while (x < right) {
511     gboolean in_hl;
512     const Color_val *colour_entry;
514     code = gst_get_rle_code (buffer, state);
515     length = code >> 2;
516     colourid = code & 3;
517     if (dec->use_ARGB)
518       colour_entry = dec->palette_cache_rgb + colourid;
519     else
520       colour_entry = dec->palette_cache_yuv + colourid;
522     /* Length = 0 implies fill to the end of the line */
523     /* Restrict the colour run to the end of the line */
524     if (length == 0 || x + length > right)
525       length = right - x;
527     /* Check if this run of colour touches the highlight region */
528     in_hl = ((x <= state->hl_right) && (x + length) >= state->hl_left);
529     if (in_hl) {
530       gint run;
532       /* Draw to the left of the highlight */
533       if (x <= state->hl_left) {
534         run = MIN (length, state->hl_left - x + 1);
536         DRAW_RUN (target, run, colour_entry);
537         length -= run;
538         x += run;
539       }
541       /* Draw across the highlight region */
542       if (x <= state->hl_right) {
543         const Color_val *hl_colour;
544         if (dec->use_ARGB)
545           hl_colour = dec->hl_palette_cache_rgb + colourid;
546         else
547           hl_colour = dec->hl_palette_cache_yuv + colourid;
549         run = MIN (length, state->hl_right - x + 1);
551         DRAW_RUN (target, run, hl_colour);
552         length -= run;
553         x += run;
554       }
555     }
557     /* Draw the rest of the run */
558     if (length > 0) {
559       DRAW_RUN (target, length, colour_entry);
560       x += length;
561     }
562   }
565 /*
566  * Decode the RLE subtitle image and blend with the current
567  * frame buffer.
568  */
569 static void
570 gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec, GstBuffer * buf)
572   gint y;
573   gint Y_stride = 4 * dec->in_width;
574   guchar *buffer = GST_BUFFER_DATA (dec->partialbuf);
576   gint hl_top, hl_bottom;
577   gint last_y;
578   RLE_state state;
580   GST_DEBUG_OBJECT (dec, "Merging subtitle on frame at time %" GST_TIME_FORMAT,
581       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
583   state.id = 0;
584   state.aligned = 1;
585   state.next = 0;
586   state.offset[0] = dec->offset[0];
587   state.offset[1] = dec->offset[1];
589   /* center the image when display rectangle exceeds the video width */
590   if (dec->in_width <= dec->right) {
591     gint left, disp_width;
593     disp_width = dec->right - dec->left + 1;
594     left = (dec->in_width - disp_width) / 2;
595     dec->left = left;
596     dec->right = left + disp_width - 1;
598     /* if it clips to the right, shift it left, but only till zero */
599     if (dec->right >= dec->in_width) {
600       gint shift = dec->right - dec->in_width - 1;
601       if (shift > dec->left)
602         shift = dec->left;
603       dec->left -= shift;
604       dec->right -= shift;
605     }
607     GST_DEBUG_OBJECT (dec, "clipping width to %d,%d",
608         dec->left, dec->in_width - 1);
609   }
611   /* for the height, bring it up till it fits as well as it can. We
612    * assume the picture is in the lower part. We should better check where it
613    * is and do something more clever. */
614   if (dec->in_height <= dec->bottom) {
616     /* shift it up, but only till zero */
617     gint shift = dec->bottom - dec->in_height - 1;
618     if (shift > dec->top)
619       shift = dec->top;
620     dec->top -= shift;
621     dec->bottom -= shift;
623     /* start on even line */
624     if (dec->top & 1) {
625       dec->top--;
626       dec->bottom--;
627     }
629     GST_DEBUG_OBJECT (dec, "clipping height to %d,%d",
630         dec->top, dec->in_height - 1);
631   }
633   if (dec->current_button) {
634     hl_top = dec->hl_top;
635     hl_bottom = dec->hl_bottom;
636   } else {
637     hl_top = -1;
638     hl_bottom = -1;
639   }
640   last_y = MIN (dec->bottom, dec->in_height);
642   y = dec->top;
643   state.target = GST_BUFFER_DATA (buf) + 4 * dec->left + (y * Y_stride);
645   /* Now draw scanlines until we hit last_y or end of RLE data */
646   for (; ((state.offset[1] < dec->data_size + 2) && (y <= last_y)); y++) {
647     /* Set up to draw the highlight if we're in the right scanlines */
648     if (y > hl_bottom || y < hl_top) {
649       state.hl_left = -1;
650       state.hl_right = -1;
651     } else {
652       state.hl_left = dec->hl_left;
653       state.hl_right = dec->hl_right;
654     }
655     gst_draw_rle_line (dec, buffer, &state);
657     state.target += Y_stride;
659     /* Realign the RLE state for the next line */
660     if (!state.aligned)
661       gst_get_nibble (buffer, &state);
662     state.id = !state.id;
663   }
666 static void
667 gst_send_empty_fill (GstDvdSubDec * dec, GstClockTime ts)
669   if (dec->next_ts < ts) {
670     GST_LOG_OBJECT (dec, "Sending newsegment update to advance time to %"
671         GST_TIME_FORMAT, GST_TIME_ARGS (ts));
673     gst_pad_push_event (dec->srcpad,
674         gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, ts, -1, ts));
675   }
676   dec->next_ts = ts;
679 static GstFlowReturn
680 gst_send_subtitle_frame (GstDvdSubDec * dec, GstClockTime end_ts)
682   GstFlowReturn flow;
683   GstBuffer *out_buf;
684   gint x, y;
686   g_assert (dec->have_title);
687   g_assert (dec->next_ts <= end_ts);
689   /* Check if we need to redraw the output buffer */
690   if (!dec->buf_dirty) {
691     flow = GST_FLOW_OK;
692     goto out;
693   }
695   flow = gst_pad_alloc_buffer_and_set_caps (dec->srcpad, 0,
696       4 * dec->in_width * dec->in_height, GST_PAD_CAPS (dec->srcpad), &out_buf);
698   if (flow != GST_FLOW_OK) {
699     GST_DEBUG_OBJECT (dec, "alloc buffer failed: flow = %s",
700         gst_flow_get_name (flow));
701     goto out;
702   }
704   /* Clear the buffer */
705   /* FIXME - move this into the buffer rendering code */
706   for (y = 0; y < dec->in_height; y++) {
707     guchar *line = GST_BUFFER_DATA (out_buf) + 4 * dec->in_width * y;
709     for (x = 0; x < dec->in_width; x++) {
710       line[0] = 0;              /* A */
711       if (!dec->use_ARGB) {
712         line[1] = 16;           /* Y */
713         line[2] = 128;          /* U */
714         line[3] = 128;          /* V */
715       } else {
716         line[1] = 0;            /* R */
717         line[2] = 0;            /* G */
718         line[3] = 0;            /* B */
719       }
721       line += 4;
722     }
723   }
725   /* FIXME: do we really want to honour the forced_display flag
726    * for subtitles streans? */
727   if (dec->visible || dec->forced_display) {
728     gst_dvd_sub_dec_merge_title (dec, out_buf);
729   }
731   dec->buf_dirty = FALSE;
733   GST_BUFFER_TIMESTAMP (out_buf) = dec->next_ts;
734   if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts)) {
735     GST_BUFFER_DURATION (out_buf) = GST_CLOCK_DIFF (dec->next_ts,
736         dec->next_event_ts);
737   } else {
738     GST_BUFFER_DURATION (out_buf) = GST_CLOCK_TIME_NONE;
739   }
741   GST_DEBUG_OBJECT (dec, "Sending subtitle buffer with ts %"
742       GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT,
743       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)),
744       GST_BUFFER_DURATION (out_buf));
746   gst_buffer_set_caps (out_buf, GST_PAD_CAPS (dec->srcpad));
748   flow = gst_pad_push (dec->srcpad, out_buf);
750 out:
751   dec->next_ts = end_ts;
752   return flow;
755 /* Walk time forward, processing any subtitle events as needed. */
756 static GstFlowReturn
757 gst_dvd_sub_dec_advance_time (GstDvdSubDec * dec, GstClockTime new_ts)
759   GstFlowReturn ret = GST_FLOW_OK;
761   GST_LOG_OBJECT (dec, "Advancing time to %" GST_TIME_FORMAT,
762       GST_TIME_ARGS (new_ts));
764   if (!dec->have_title) {
765     gst_send_empty_fill (dec, new_ts);
766     return ret;
767   }
769   while (dec->next_ts < new_ts) {
770     GstClockTime next_ts = new_ts;
772     if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts) &&
773         dec->next_event_ts < next_ts) {
774       /* We might need to process the subtitle cmd queue */
775       next_ts = dec->next_event_ts;
776     }
778     /* 
779      * Now, either output a filler or a frame spanning
780      * dec->next_ts to next_ts
781      */
782     if (dec->visible || dec->forced_display) {
783       ret = gst_send_subtitle_frame (dec, next_ts);
784     } else {
785       gst_send_empty_fill (dec, next_ts);
786     }
788     /*
789      * and then process some subtitle cmds if we need
790      */
791     if (next_ts == dec->next_event_ts)
792       gst_dvd_sub_dec_parse_subpic (dec);
793   }
795   return ret;
798 static GstFlowReturn
799 gst_dvd_sub_dec_chain (GstPad * pad, GstBuffer * buf)
801   GstFlowReturn ret = GST_FLOW_OK;
802   GstDvdSubDec *dec;
803   guint8 *data;
804   glong size = 0;
806   dec = GST_DVD_SUB_DEC (GST_PAD_PARENT (pad));
808   GST_DEBUG_OBJECT (dec, "Have buffer of size %d, ts %"
809       GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT, GST_BUFFER_SIZE (buf),
810       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_DURATION (buf));
812   if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
813     if (!GST_CLOCK_TIME_IS_VALID (dec->next_ts)) {
814       dec->next_ts = GST_BUFFER_TIMESTAMP (buf);
815     }
817     /* Move time forward to the start of the new buffer */
818     ret = gst_dvd_sub_dec_advance_time (dec, GST_BUFFER_TIMESTAMP (buf));
819   }
821   if (dec->have_title) {
822     gst_buffer_unref (dec->partialbuf);
823     dec->partialbuf = NULL;
824     dec->have_title = FALSE;
825   }
827   GST_DEBUG_OBJECT (dec, "Got subtitle buffer, pts %" GST_TIME_FORMAT,
828       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
830   /* deal with partial frame from previous buffer */
831   if (dec->partialbuf) {
832     GstBuffer *merge;
834     merge = gst_buffer_join (dec->partialbuf, buf);
835     dec->partialbuf = merge;
836   } else {
837     dec->partialbuf = buf;
838   }
840   data = GST_BUFFER_DATA (dec->partialbuf);
841   size = GST_BUFFER_SIZE (dec->partialbuf);
843   if (size > 4) {
844     dec->packet_size = GST_READ_UINT16_BE (data);
846     if (dec->packet_size == size) {
847       GST_LOG_OBJECT (dec, "Subtitle packet size %d, current size %ld",
848           dec->packet_size, size);
850       dec->data_size = GST_READ_UINT16_BE (data + 2);
852       /* Reset parameters for a new subtitle buffer */
853       dec->parse_pos = data;
854       dec->forced_display = FALSE;
855       dec->visible = FALSE;
857       dec->have_title = TRUE;
858       dec->next_event_ts = GST_BUFFER_TIMESTAMP (dec->partialbuf);
860       if (!GST_CLOCK_TIME_IS_VALID (dec->next_event_ts))
861         dec->next_event_ts = dec->next_ts;
863       dec->next_event_ts += gst_dvd_sub_dec_get_event_delay (dec);
864     }
865   }
867   return ret;
870 static gboolean
871 gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps)
873   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
874   gboolean ret = FALSE;
875   guint32 fourcc = GST_MAKE_FOURCC ('A', 'Y', 'U', 'V');
876   GstCaps *out_caps = NULL, *peer_caps = NULL;
878   GST_DEBUG_OBJECT (dec, "setcaps called with %" GST_PTR_FORMAT, caps);
880   dec->out_fourcc = fourcc;
881   out_caps = gst_caps_new_simple ("video/x-raw-yuv",
882       "width", G_TYPE_INT, dec->in_width,
883       "height", G_TYPE_INT, dec->in_height,
884       "format", GST_TYPE_FOURCC, dec->out_fourcc,
885       "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
887   peer_caps = gst_pad_get_allowed_caps (dec->srcpad);
888   if (G_LIKELY (peer_caps)) {
889     guint i = 0, n = 0;
890     n = gst_caps_get_size (peer_caps);
891     GST_DEBUG_OBJECT (dec, "peer allowed caps (%u structure(s)) are %"
892         GST_PTR_FORMAT, n, peer_caps);
894     for (i = 0; i < n; i++) {
895       GstStructure *s = gst_caps_get_structure (peer_caps, i);
896       /* Check if the peer pad support ARGB format, if yes change caps */
897       if (gst_structure_has_name (s, "video/x-raw-rgb") &&
898           gst_structure_has_field (s, "alpha_mask")) {
899         gst_caps_unref (out_caps);
900         GST_DEBUG_OBJECT (dec, "trying with fourcc %" GST_FOURCC_FORMAT,
901             GST_FOURCC_ARGS (fourcc));
902         out_caps = gst_caps_new_simple ("video/x-raw-rgb",
903             "width", G_TYPE_INT, dec->in_width,
904             "height", G_TYPE_INT, dec->in_height,
905             "framerate", GST_TYPE_FRACTION, 0, 1,
906             "bpp", G_TYPE_INT, 32,
907             "depth", G_TYPE_INT, 32,
908             "red_mask", G_TYPE_INT, 16711680,
909             "green_mask", G_TYPE_INT, 65280,
910             "blue_mask", G_TYPE_INT, 255,
911             "alpha_mask", G_TYPE_INT, -16777216,
912             "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
913         if (gst_pad_peer_accept_caps (dec->srcpad, out_caps)) {
914           GST_DEBUG_OBJECT (dec, "peer accepted format %" GST_FOURCC_FORMAT,
915               GST_FOURCC_ARGS (fourcc));
916           /* If ARGB format then set the flag */
917           dec->use_ARGB = TRUE;
918           break;
919         }
920       }
921     }
922     gst_caps_unref (peer_caps);
923   }
924   GST_DEBUG_OBJECT (dec, "setting caps downstream to %" GST_PTR_FORMAT,
925       out_caps);
926   if (gst_pad_set_caps (dec->srcpad, out_caps)) {
927     dec->out_fourcc = fourcc;
928   } else {
929     GST_WARNING_OBJECT (dec, "failed setting downstream caps");
930     gst_caps_unref (out_caps);
931     goto beach;
932   }
934   gst_caps_unref (out_caps);
935   ret = TRUE;
937 beach:
938   gst_object_unref (dec);
939   return ret;
942 static gboolean
943 gst_dvd_sub_dec_sink_event (GstPad * pad, GstEvent * event)
945   GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
946   gboolean ret = FALSE;
948   GST_LOG_OBJECT (dec, "%s event", GST_EVENT_TYPE_NAME (event));
950   switch (GST_EVENT_TYPE (event)) {
951     case GST_EVENT_CUSTOM_DOWNSTREAM:{
952       GstClockTime ts = GST_EVENT_TIMESTAMP (event);
954       if (event->structure != NULL &&
955           gst_structure_has_name (event->structure, "application/x-gst-dvd")) {
957         if (GST_CLOCK_TIME_IS_VALID (ts))
958           gst_dvd_sub_dec_advance_time (dec, ts);
960         if (gst_dvd_sub_dec_handle_dvd_event (dec, event)) {
961           /* gst_dvd_sub_dec_advance_time (dec, dec->next_ts + GST_SECOND / 30.0); */
962           gst_event_unref (event);
963           ret = TRUE;
964           break;
965         }
966       }
968       ret = gst_pad_event_default (pad, event);
969       break;
970     }
971     case GST_EVENT_NEWSEGMENT:{
972       gboolean update;
973       GstFormat format;
974       gint64 start, stop, pos;
976       gst_event_parse_new_segment (event, &update, NULL, &format, &start,
977           &stop, &pos);
979       if (update) {
980         /* update ... advance time */
981         if (GST_CLOCK_TIME_IS_VALID (pos)) {
982           GST_DEBUG_OBJECT (dec, "Got segment update, advancing time from %"
983               GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
984               GST_TIME_ARGS (dec->next_ts), GST_TIME_ARGS (pos));
986           gst_dvd_sub_dec_advance_time (dec, pos);
987         } else {
988           GST_WARNING_OBJECT (dec, "Got segment update with invalid position");
989         }
990         gst_event_unref (event);
991         ret = TRUE;
992       } else {
993         /* not just an update ... */
995         /* Turn off forced highlight display */
996         // dec->forced_display = 0;
997         // dec->current_button = 0;
998         if (dec->partialbuf) {
999           gst_buffer_unref (dec->partialbuf);
1000           dec->partialbuf = NULL;
1001           dec->have_title = FALSE;
1002         }
1004         if (GST_CLOCK_TIME_IS_VALID (pos))
1005           dec->next_ts = pos;
1006         else
1007           dec->next_ts = GST_CLOCK_TIME_NONE;
1009         GST_DEBUG_OBJECT (dec, "Got newsegment, new time = %"
1010             GST_TIME_FORMAT, GST_TIME_ARGS (dec->next_ts));
1012         ret = gst_pad_event_default (pad, event);
1013       }
1014       break;
1015     }
1016     case GST_EVENT_FLUSH_STOP:{
1017       /* Turn off forced highlight display */
1018       dec->forced_display = 0;
1019       dec->current_button = 0;
1021       if (dec->partialbuf) {
1022         gst_buffer_unref (dec->partialbuf);
1023         dec->partialbuf = NULL;
1024         dec->have_title = FALSE;
1025       }
1027       ret = gst_pad_event_default (pad, event);
1028       break;
1029     }
1030     default:{
1031       ret = gst_pad_event_default (pad, event);
1032       break;
1033     }
1034   }
1035   gst_object_unref (dec);
1036   return ret;
1039 static gboolean
1040 gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec, GstEvent * event)
1042   GstStructure *structure;
1043   const gchar *event_name;
1045   structure = (GstStructure *) gst_event_get_structure (event);
1047   if (structure == NULL)
1048     goto not_handled;
1050   event_name = gst_structure_get_string (structure, "event");
1052   GST_LOG_OBJECT (dec,
1053       "DVD event %s with timestamp %" G_GINT64_FORMAT " on sub pad",
1054       GST_STR_NULL (event_name), GST_EVENT_TIMESTAMP (event));
1056   if (event_name == NULL)
1057     goto not_handled;
1059   if (strcmp (event_name, "dvd-spu-highlight") == 0) {
1060     gint button;
1061     gint palette, sx, sy, ex, ey;
1062     gint i;
1064     /* Details for the highlight region to display */
1065     if (!gst_structure_get_int (structure, "button", &button) ||
1066         !gst_structure_get_int (structure, "palette", &palette) ||
1067         !gst_structure_get_int (structure, "sx", &sx) ||
1068         !gst_structure_get_int (structure, "sy", &sy) ||
1069         !gst_structure_get_int (structure, "ex", &ex) ||
1070         !gst_structure_get_int (structure, "ey", &ey)) {
1071       GST_ERROR_OBJECT (dec, "Invalid dvd-spu-highlight event received");
1072       return TRUE;
1073     }
1074     dec->current_button = button;
1075     dec->hl_left = sx;
1076     dec->hl_top = sy;
1077     dec->hl_right = ex;
1078     dec->hl_bottom = ey;
1079     for (i = 0; i < 4; i++) {
1080       dec->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f;
1081       dec->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f;
1082     }
1084     GST_DEBUG_OBJECT (dec, "New button activated highlight=(%d,%d) to (%d,%d) "
1085         "palette 0x%x", sx, sy, ex, ey, palette);
1086     gst_setup_palette (dec);
1088     dec->buf_dirty = TRUE;
1089   } else if (strcmp (event_name, "dvd-spu-clut-change") == 0) {
1090     /* Take a copy of the colour table */
1091     gchar name[16];
1092     int i;
1093     gint value;
1095     GST_LOG_OBJECT (dec, "New colour table received");
1096     for (i = 0; i < 16; i++) {
1097       g_snprintf (name, sizeof (name), "clut%02d", i);
1098       if (!gst_structure_get_int (structure, name, &value)) {
1099         GST_ERROR_OBJECT (dec, "dvd-spu-clut-change event did not "
1100             "contain %s field", name);
1101         break;
1102       }
1103       dec->current_clut[i] = (guint32) (value);
1104     }
1106     gst_setup_palette (dec);
1108     dec->buf_dirty = TRUE;
1109   } else if (strcmp (event_name, "dvd-spu-stream-change") == 0
1110       || strcmp (event_name, "dvd-spu-reset-highlight") == 0) {
1111     /* Turn off forced highlight display */
1112     dec->current_button = 0;
1114     GST_LOG_OBJECT (dec, "Clearing button state");
1115     dec->buf_dirty = TRUE;
1116   } else if (strcmp (event_name, "dvd-spu-still-frame") == 0) {
1117     /* Handle a still frame */
1118     GST_LOG_OBJECT (dec, "Received still frame notification");
1119   } else {
1120     goto not_handled;
1121   }
1123   return TRUE;
1125 not_handled:
1126   {
1127     /* Ignore all other unknown events */
1128     GST_LOG_OBJECT (dec, "Ignoring other custom event %" GST_PTR_FORMAT,
1129         structure);
1130     return FALSE;
1131   }
1134 static gboolean
1135 plugin_init (GstPlugin * plugin)
1137   if (!gst_element_register (plugin, "dvdsubdec", GST_RANK_NONE,
1138           GST_TYPE_DVD_SUB_DEC) ||
1139       !gst_element_register (plugin, "dvdsubparse", GST_RANK_NONE,
1140           GST_TYPE_DVD_SUB_PARSE)) {
1141     return FALSE;
1142   }
1144   GST_DEBUG_CATEGORY_INIT (gst_dvd_sub_dec_debug, "dvdsubdec", 0,
1145       "DVD subtitle decoder");
1147   return TRUE;
1150 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1151     GST_VERSION_MINOR,
1152     "dvdsub",
1153     "DVD subtitle parser and decoder", plugin_init,
1154     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);