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