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;
100 }
101 RLE_state;
103 static void
104 gst_dvd_sub_dec_base_init (gpointer klass)
105 {
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>");
116 }
118 static void
119 gst_dvd_sub_dec_class_init (GstDvdSubDecClass * klass)
120 {
121 GObjectClass *gobject_class;
123 gobject_class = (GObjectClass *) klass;
125 gobject_class->finalize = gst_dvd_sub_dec_finalize;
126 }
128 static void
129 gst_dvd_sub_dec_init (GstDvdSubDec * dec, GstDvdSubDecClass * klass)
130 {
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;
168 }
170 static void
171 gst_dvd_sub_dec_finalize (GObject * gobject)
172 {
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);
181 }
183 static gboolean
184 gst_dvd_sub_dec_src_event (GstPad * pad, GstEvent * event)
185 {
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;
197 }
199 static GstClockTime
200 gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec)
201 {
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;
221 }
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)
229 {
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 }
392 }
394 static inline int
395 gst_get_nibble (guchar * buffer, RLE_state * state)
396 {
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 }
405 }
407 /* Premultiply the current lookup table into the "target" cache */
408 static void
409 gst_setup_palette (GstDvdSubDec * dec)
410 {
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 }
457 }
459 static inline guint
460 gst_get_rle_code (guchar * buffer, RLE_state * state)
461 {
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;
475 }
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)
499 {
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 }
563 }
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)
571 {
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 }
664 }
666 static void
667 gst_send_empty_fill (GstDvdSubDec * dec, GstClockTime ts)
668 {
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;
677 }
679 static GstFlowReturn
680 gst_send_subtitle_frame (GstDvdSubDec * dec, GstClockTime end_ts)
681 {
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;
753 }
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)
758 {
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;
796 }
798 static GstFlowReturn
799 gst_dvd_sub_dec_chain (GstPad * pad, GstBuffer * buf)
800 {
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;
868 }
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 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;
940 }
942 static gboolean
943 gst_dvd_sub_dec_sink_event (GstPad * pad, GstEvent * event)
944 {
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;
1037 }
1039 static gboolean
1040 gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec, GstEvent * event)
1041 {
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 }
1132 }
1134 static gboolean
1135 plugin_init (GstPlugin * plugin)
1136 {
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;
1148 }
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);