e1ab657090aec2b2faf3dcad1adbd8daa03df211
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_pad_template (element_class,
109 gst_static_pad_template_get (&src_template));
110 gst_element_class_add_pad_template (element_class,
111 gst_static_pad_template_get (&subtitle_template));
113 gst_element_class_set_details_simple (element_class, "DVD subtitle decoder",
114 "Codec/Decoder/Video", "Decodes DVD subtitles into AYUV video frames",
115 "Wim Taymans <wim.taymans@gmail.com>, "
116 "Jan Schmidt <thaytan@mad.scientist.com>");
117 }
119 static void
120 gst_dvd_sub_dec_class_init (GstDvdSubDecClass * klass)
121 {
122 GObjectClass *gobject_class;
124 gobject_class = (GObjectClass *) klass;
126 gobject_class->finalize = gst_dvd_sub_dec_finalize;
127 }
129 static void
130 gst_dvd_sub_dec_init (GstDvdSubDec * dec, GstDvdSubDecClass * klass)
131 {
132 GstPadTemplate *tmpl;
134 dec->sinkpad = gst_pad_new_from_static_template (&subtitle_template, "sink");
135 gst_pad_set_chain_function (dec->sinkpad,
136 GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_chain));
137 gst_pad_set_event_function (dec->sinkpad,
138 GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_sink_event));
139 gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
140 gst_pad_set_setcaps_function (dec->sinkpad, gst_dvd_sub_dec_sink_setcaps);
142 tmpl = gst_static_pad_template_get (&src_template);
143 dec->srcpad = gst_pad_new_from_template (tmpl, "src");
144 gst_pad_set_event_function (dec->srcpad,
145 GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_src_event));
146 gst_pad_use_fixed_caps (dec->srcpad);
147 gst_object_unref (tmpl);
148 gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
150 /* FIXME: aren't there more possible sizes? (tpm) */
151 dec->in_width = 720;
152 dec->in_height = 576;
154 dec->partialbuf = NULL;
155 dec->have_title = FALSE;
156 dec->parse_pos = NULL;
157 dec->forced_display = FALSE;
158 dec->visible = FALSE;
160 memcpy (dec->current_clut, default_clut, sizeof (guint32) * 16);
162 gst_setup_palette (dec);
164 dec->next_ts = 0;
165 dec->next_event_ts = GST_CLOCK_TIME_NONE;
167 dec->out_buffer = NULL;
168 dec->buf_dirty = TRUE;
169 dec->use_ARGB = FALSE;
170 }
172 static void
173 gst_dvd_sub_dec_finalize (GObject * gobject)
174 {
175 GstDvdSubDec *dec = GST_DVD_SUB_DEC (gobject);
177 if (dec->partialbuf) {
178 gst_buffer_unref (dec->partialbuf);
179 dec->partialbuf = NULL;
180 }
182 G_OBJECT_CLASS (parent_class)->finalize (gobject);
183 }
185 static gboolean
186 gst_dvd_sub_dec_src_event (GstPad * pad, GstEvent * event)
187 {
188 GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
189 gboolean res = FALSE;
191 switch (GST_EVENT_TYPE (event)) {
192 default:
193 res = gst_pad_event_default (pad, event);
194 break;
195 }
197 gst_object_unref (dec);
198 return res;
199 }
201 static GstClockTime
202 gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec)
203 {
204 guchar *start = GST_BUFFER_DATA (dec->partialbuf);
205 guchar *buf;
206 guint16 ticks;
207 GstClockTime event_delay;
209 /* If starting a new buffer, follow the first DCSQ ptr */
210 if (dec->parse_pos == start) {
211 buf = dec->parse_pos + dec->data_size;
212 } else {
213 buf = dec->parse_pos;
214 }
216 ticks = GST_READ_UINT16_BE (buf);
217 event_delay = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
219 GST_DEBUG_OBJECT (dec, "returning delay %" GST_TIME_FORMAT " from offset %u",
220 GST_TIME_ARGS (event_delay), (guint) (buf - dec->parse_pos));
222 return event_delay;
223 }
225 /*
226 * Parse the next event time in the current subpicture buffer, stopping
227 * when time advances to the next state.
228 */
229 static void
230 gst_dvd_sub_dec_parse_subpic (GstDvdSubDec * dec)
231 {
232 #define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \
233 { GST_WARNING("Subtitle stream broken parsing %c", *buf); \
234 broken = TRUE; break; }
236 guchar *start = GST_BUFFER_DATA (dec->partialbuf);
237 guchar *buf;
238 guchar *end;
239 gboolean broken = FALSE;
240 gboolean last_seq = FALSE;
241 guchar *next_seq = NULL;
242 GstClockTime event_time;
244 /* nothing to do if we finished this buffer already */
245 if (dec->parse_pos == NULL)
246 return;
248 g_return_if_fail (dec->packet_size >= 4);
250 end = start + dec->packet_size;
251 if (dec->parse_pos == start) {
252 buf = dec->parse_pos + dec->data_size;
253 } else {
254 buf = dec->parse_pos;
255 }
257 g_assert (buf >= start && buf < end);
259 /* If the next control sequence is at the current offset, this is
260 * the last one */
261 next_seq = start + GST_READ_UINT16_BE (buf + 2);
262 last_seq = (next_seq == buf);
263 buf += 4;
265 while ((buf < end) && (!broken)) {
266 switch (*buf) {
267 case SPU_FORCE_DISPLAY: /* Forced display menu subtitle */
268 dec->forced_display = TRUE;
269 dec->buf_dirty = TRUE;
270 GST_DEBUG_OBJECT (dec, "SPU FORCE_DISPLAY");
271 buf++;
272 break;
273 case SPU_SHOW: /* Show the subtitle in this packet */
274 dec->visible = TRUE;
275 dec->buf_dirty = TRUE;
276 GST_DEBUG_OBJECT (dec, "SPU SHOW at %" GST_TIME_FORMAT,
277 GST_TIME_ARGS (dec->next_event_ts));
278 buf++;
279 break;
280 case SPU_HIDE:
281 /* 02 ff (ff) is the end of the packet, hide the subpicture */
282 dec->visible = FALSE;
283 dec->buf_dirty = TRUE;
285 GST_DEBUG_OBJECT (dec, "SPU HIDE at %" GST_TIME_FORMAT,
286 GST_TIME_ARGS (dec->next_event_ts));
287 buf++;
288 break;
289 case SPU_SET_PALETTE: /* palette */
290 PARSE_BYTES_NEEDED (3);
292 GST_DEBUG_OBJECT (dec, "SPU SET_PALETTE");
294 dec->subtitle_index[3] = buf[1] >> 4;
295 dec->subtitle_index[2] = buf[1] & 0xf;
296 dec->subtitle_index[1] = buf[2] >> 4;
297 dec->subtitle_index[0] = buf[2] & 0xf;
298 gst_setup_palette (dec);
300 dec->buf_dirty = TRUE;
301 buf += 3;
302 break;
303 case SPU_SET_ALPHA: /* transparency palette */
304 PARSE_BYTES_NEEDED (3);
306 GST_DEBUG_OBJECT (dec, "SPU SET_ALPHA");
308 dec->subtitle_alpha[3] = buf[1] >> 4;
309 dec->subtitle_alpha[2] = buf[1] & 0xf;
310 dec->subtitle_alpha[1] = buf[2] >> 4;
311 dec->subtitle_alpha[0] = buf[2] & 0xf;
312 gst_setup_palette (dec);
314 dec->buf_dirty = TRUE;
315 buf += 3;
316 break;
317 case SPU_SET_SIZE: /* image coordinates */
318 PARSE_BYTES_NEEDED (7);
320 dec->left =
321 CLAMP ((((guint) buf[1]) << 4) | (buf[2] >> 4), 0,
322 (dec->in_width - 1));
323 dec->top =
324 CLAMP ((((guint) buf[4]) << 4) | (buf[5] >> 4), 0,
325 (dec->in_height - 1));
326 dec->right =
327 CLAMP ((((buf[2] & 0x0f) << 8) | buf[3]), 0, (dec->in_width - 1));
328 dec->bottom =
329 CLAMP ((((buf[5] & 0x0f) << 8) | buf[6]), 0, (dec->in_height - 1));
331 GST_DEBUG_OBJECT (dec, "SPU SET_SIZE left %d, top %d, right %d, "
332 "bottom %d", dec->left, dec->top, dec->right, dec->bottom);
334 dec->buf_dirty = TRUE;
335 buf += 7;
336 break;
337 case SPU_SET_OFFSETS: /* image 1 / image 2 offsets */
338 PARSE_BYTES_NEEDED (5);
340 dec->offset[0] = (((guint) buf[1]) << 8) | buf[2];
341 dec->offset[1] = (((guint) buf[3]) << 8) | buf[4];
342 GST_DEBUG_OBJECT (dec, "Offset1 %d, Offset2 %d",
343 dec->offset[0], dec->offset[1]);
345 dec->buf_dirty = TRUE;
346 buf += 5;
347 break;
348 case SPU_WIPE:
349 {
350 guint length;
352 PARSE_BYTES_NEEDED (3);
354 GST_WARNING_OBJECT (dec, "SPU_WIPE not yet implemented");
356 length = (buf[1] << 8) | (buf[2]);
357 buf += 1 + length;
359 dec->buf_dirty = TRUE;
360 break;
361 }
362 case SPU_END:
363 buf = (last_seq) ? end : next_seq;
365 /* Start a new control sequence */
366 if (buf + 4 < end) {
367 guint16 ticks = GST_READ_UINT16_BE (buf);
369 event_time = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
371 GST_DEBUG_OBJECT (dec,
372 "Next DCSQ at offset %u, delay %g secs (%d ticks)",
373 (guint) (buf - start),
374 gst_util_guint64_to_gdouble (event_time / GST_SECOND), ticks);
376 dec->parse_pos = buf;
377 if (event_time > 0) {
378 dec->next_event_ts += event_time;
380 GST_LOG_OBJECT (dec, "Exiting parse loop with time %g",
381 gst_guint64_to_gdouble (dec->next_event_ts) /
382 gst_guint64_to_gdouble (GST_SECOND));
383 return;
384 }
385 } else {
386 dec->parse_pos = NULL;
387 dec->next_event_ts = GST_CLOCK_TIME_NONE;
388 GST_LOG_OBJECT (dec, "Finished all cmds. Exiting parse loop");
389 return;
390 }
391 default:
392 GST_ERROR
393 ("Invalid sequence in subtitle packet header (%.2x). Skipping",
394 *buf);
395 broken = TRUE;
396 dec->parse_pos = NULL;
397 break;
398 }
399 }
400 }
402 static inline int
403 gst_get_nibble (guchar * buffer, RLE_state * state)
404 {
405 if (state->aligned) {
406 state->next = buffer[state->offset[state->id]++];
407 state->aligned = 0;
408 return state->next >> 4;
409 } else {
410 state->aligned = 1;
411 return state->next & 0xf;
412 }
413 }
415 /* Premultiply the current lookup table into the "target" cache */
416 static void
417 gst_setup_palette (GstDvdSubDec * dec)
418 {
419 gint i;
420 guint32 col;
421 Color_val *target_yuv = dec->palette_cache_yuv;
422 Color_val *target2_yuv = dec->hl_palette_cache_yuv;
423 Color_val *target_rgb = dec->palette_cache_rgb;
424 Color_val *target2_rgb = dec->hl_palette_cache_rgb;
426 for (i = 0; i < 4; i++, target2_yuv++, target_yuv++) {
427 col = dec->current_clut[dec->subtitle_index[i]];
428 target_yuv->Y_R = (col >> 16) & 0xff;
429 target_yuv->V_B = (col >> 8) & 0xff;
430 target_yuv->U_G = col & 0xff;
431 target_yuv->A = dec->subtitle_alpha[i] * 0xff / 0xf;
433 col = dec->current_clut[dec->menu_index[i]];
434 target2_yuv->Y_R = (col >> 16) & 0xff;
435 target2_yuv->V_B = (col >> 8) & 0xff;
436 target2_yuv->U_G = col & 0xff;
437 target2_yuv->A = dec->menu_alpha[i] * 0xff / 0xf;
439 /* If ARGB flag set, then convert YUV palette to RGB */
440 /* Using integer aritmetic */
441 if (dec->use_ARGB) {
442 guchar C = target_yuv->Y_R - 16;
443 guchar D = target_yuv->U_G - 128;
444 guchar E = target_yuv->V_B - 128;
446 target_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
447 target_rgb->U_G =
448 CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
449 target_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
450 target_rgb->A = target_yuv->A;
452 C = target2_yuv->Y_R - 16;
453 D = target2_yuv->U_G - 128;
454 E = target2_yuv->V_B - 128;
456 target2_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
457 target2_rgb->U_G =
458 CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
459 target2_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
460 target2_rgb->A = target2_yuv->A;
461 }
462 target_rgb++;
463 target2_rgb++;
464 }
465 }
467 static inline guint
468 gst_get_rle_code (guchar * buffer, RLE_state * state)
469 {
470 gint code;
472 code = gst_get_nibble (buffer, state);
473 if (code < 0x4) { /* 4 .. f */
474 code = (code << 4) | gst_get_nibble (buffer, state);
475 if (code < 0x10) { /* 1x .. 3x */
476 code = (code << 4) | gst_get_nibble (buffer, state);
477 if (code < 0x40) { /* 04x .. 0fx */
478 code = (code << 4) | gst_get_nibble (buffer, state);
479 }
480 }
481 }
482 return code;
483 }
485 #define DRAW_RUN(target,len,c) \
486 G_STMT_START { \
487 gint i = 0; \
488 if ((c)->A) { \
489 for (i = 0; i < (len); i++) { \
490 *(target)++ = (c)->A; \
491 *(target)++ = (c)->Y_R; \
492 *(target)++ = (c)->U_G; \
493 *(target)++ = (c)->V_B; \
494 } \
495 } else { \
496 (target) += 4 * (len); \
497 } \
498 } G_STMT_END
500 /*
501 * This function steps over each run-length segment, drawing
502 * into the YUVA/ARGB buffers as it goes. UV are composited and then output
503 * at half width/height
504 */
505 static void
506 gst_draw_rle_line (GstDvdSubDec * dec, guchar * buffer, RLE_state * state)
507 {
508 gint length, colourid;
509 guint code;
510 gint x, right;
511 guchar *target;
513 target = state->target;
515 x = dec->left;
516 right = dec->right + 1;
518 while (x < right) {
519 gboolean in_hl;
520 const Color_val *colour_entry;
522 code = gst_get_rle_code (buffer, state);
523 length = code >> 2;
524 colourid = code & 3;
525 if (dec->use_ARGB)
526 colour_entry = dec->palette_cache_rgb + colourid;
527 else
528 colour_entry = dec->palette_cache_yuv + colourid;
530 /* Length = 0 implies fill to the end of the line */
531 /* Restrict the colour run to the end of the line */
532 if (length == 0 || x + length > right)
533 length = right - x;
535 /* Check if this run of colour touches the highlight region */
536 in_hl = ((x <= state->hl_right) && (x + length) >= state->hl_left);
537 if (in_hl) {
538 gint run;
540 /* Draw to the left of the highlight */
541 if (x <= state->hl_left) {
542 run = MIN (length, state->hl_left - x + 1);
544 DRAW_RUN (target, run, colour_entry);
545 length -= run;
546 x += run;
547 }
549 /* Draw across the highlight region */
550 if (x <= state->hl_right) {
551 const Color_val *hl_colour;
552 if (dec->use_ARGB)
553 hl_colour = dec->hl_palette_cache_rgb + colourid;
554 else
555 hl_colour = dec->hl_palette_cache_yuv + colourid;
557 run = MIN (length, state->hl_right - x + 1);
559 DRAW_RUN (target, run, hl_colour);
560 length -= run;
561 x += run;
562 }
563 }
565 /* Draw the rest of the run */
566 if (length > 0) {
567 DRAW_RUN (target, length, colour_entry);
568 x += length;
569 }
570 }
571 }
573 /*
574 * Decode the RLE subtitle image and blend with the current
575 * frame buffer.
576 */
577 static void
578 gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec, GstBuffer * buf)
579 {
580 gint y;
581 gint Y_stride = 4 * dec->in_width;
582 guchar *buffer = GST_BUFFER_DATA (dec->partialbuf);
584 gint hl_top, hl_bottom;
585 gint last_y;
586 RLE_state state;
588 GST_DEBUG_OBJECT (dec, "Merging subtitle on frame at time %" GST_TIME_FORMAT,
589 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
591 state.id = 0;
592 state.aligned = 1;
593 state.next = 0;
594 state.offset[0] = dec->offset[0];
595 state.offset[1] = dec->offset[1];
597 if (dec->current_button) {
598 hl_top = dec->hl_top;
599 hl_bottom = dec->hl_bottom;
600 } else {
601 hl_top = -1;
602 hl_bottom = -1;
603 }
604 last_y = MIN (dec->bottom, dec->in_height);
606 y = dec->top;
607 state.target = GST_BUFFER_DATA (buf) + 4 * dec->left + (y * Y_stride);
609 /* Now draw scanlines until we hit last_y or end of RLE data */
610 for (; ((state.offset[1] < dec->data_size + 2) && (y <= last_y)); y++) {
611 /* Set up to draw the highlight if we're in the right scanlines */
612 if (y > hl_bottom || y < hl_top) {
613 state.hl_left = -1;
614 state.hl_right = -1;
615 } else {
616 state.hl_left = dec->hl_left;
617 state.hl_right = dec->hl_right;
618 }
619 gst_draw_rle_line (dec, buffer, &state);
621 state.target += Y_stride;
623 /* Realign the RLE state for the next line */
624 if (!state.aligned)
625 gst_get_nibble (buffer, &state);
626 state.id = !state.id;
627 }
628 }
630 static void
631 gst_send_empty_fill (GstDvdSubDec * dec, GstClockTime ts)
632 {
633 if (dec->next_ts < ts) {
634 GST_LOG_OBJECT (dec, "Sending newsegment update to advance time to %"
635 GST_TIME_FORMAT, GST_TIME_ARGS (ts));
637 gst_pad_push_event (dec->srcpad,
638 gst_event_new_new_segment (TRUE, 1.0, GST_FORMAT_TIME, ts, -1, ts));
639 }
640 dec->next_ts = ts;
641 }
643 static GstFlowReturn
644 gst_send_subtitle_frame (GstDvdSubDec * dec, GstClockTime end_ts)
645 {
646 GstFlowReturn flow;
647 GstBuffer *out_buf;
648 gint x, y;
650 g_assert (dec->have_title);
651 g_assert (dec->next_ts <= end_ts);
653 /* Check if we need to redraw the output buffer */
654 if (dec->buf_dirty) {
655 if (dec->out_buffer) {
656 gst_buffer_unref (dec->out_buffer);
657 dec->out_buffer = NULL;
658 }
660 flow = gst_pad_alloc_buffer_and_set_caps (dec->srcpad, 0,
661 4 * dec->in_width * dec->in_height, GST_PAD_CAPS (dec->srcpad),
662 &out_buf);
664 if (flow != GST_FLOW_OK) {
665 GST_DEBUG_OBJECT (dec, "alloc buffer failed: flow = %s",
666 gst_flow_get_name (flow));
667 goto out;
668 }
670 /* Clear the buffer */
671 /* FIXME - move this into the buffer rendering code */
672 for (y = 0; y < dec->in_height; y++) {
673 guchar *line = GST_BUFFER_DATA (out_buf) + 4 * dec->in_width * y;
675 for (x = 0; x < dec->in_width; x++) {
676 line[0] = 0; /* A */
677 if (!dec->use_ARGB) {
678 line[1] = 16; /* Y */
679 line[2] = 128; /* U */
680 line[3] = 128; /* V */
681 } else {
682 line[1] = 0; /* R */
683 line[2] = 0; /* G */
684 line[3] = 0; /* B */
685 }
687 line += 4;
688 }
689 }
691 /* FIXME: do we really want to honour the forced_display flag
692 * for subtitles streans? */
693 if (dec->visible || dec->forced_display) {
694 gst_dvd_sub_dec_merge_title (dec, out_buf);
695 }
697 dec->out_buffer = out_buf;
698 dec->buf_dirty = FALSE;
699 }
701 out_buf = gst_buffer_create_sub (dec->out_buffer, 0,
702 GST_BUFFER_SIZE (dec->out_buffer));
704 GST_BUFFER_TIMESTAMP (out_buf) = dec->next_ts;
705 GST_BUFFER_DURATION (out_buf) = GST_CLOCK_DIFF (dec->next_ts, end_ts);
707 GST_DEBUG_OBJECT (dec, "Sending subtitle buffer with ts %"
708 GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT,
709 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)),
710 GST_BUFFER_DURATION (out_buf));
712 gst_buffer_set_caps (out_buf, GST_PAD_CAPS (dec->srcpad));
714 flow = gst_pad_push (dec->srcpad, out_buf);
716 out:
718 dec->next_ts = end_ts;
719 return flow;
720 }
722 /* Walk time forward, processing any subtitle events as needed. */
723 static GstFlowReturn
724 gst_dvd_sub_dec_advance_time (GstDvdSubDec * dec, GstClockTime new_ts)
725 {
726 GstFlowReturn ret = GST_FLOW_OK;
728 GST_LOG_OBJECT (dec, "Advancing time to %" GST_TIME_FORMAT,
729 GST_TIME_ARGS (new_ts));
731 if (!dec->have_title) {
732 gst_send_empty_fill (dec, new_ts);
733 return ret;
734 }
736 while (dec->next_ts < new_ts) {
737 GstClockTime next_ts = new_ts;
739 if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts) &&
740 dec->next_event_ts < next_ts) {
741 /* We might need to process the subtitle cmd queue */
742 next_ts = dec->next_event_ts;
743 }
745 /*
746 * Now, either output a filler or a frame spanning
747 * dec->next_ts to next_ts
748 */
749 if (dec->visible || dec->forced_display) {
750 ret = gst_send_subtitle_frame (dec, next_ts);
751 } else {
752 gst_send_empty_fill (dec, next_ts);
753 }
755 /*
756 * and then process some subtitle cmds if we need
757 */
758 if (next_ts == dec->next_event_ts)
759 gst_dvd_sub_dec_parse_subpic (dec);
760 }
762 return ret;
763 }
765 static GstFlowReturn
766 gst_dvd_sub_dec_chain (GstPad * pad, GstBuffer * buf)
767 {
768 GstFlowReturn ret = GST_FLOW_OK;
769 GstDvdSubDec *dec;
770 guint8 *data;
771 glong size = 0;
773 dec = GST_DVD_SUB_DEC (GST_PAD_PARENT (pad));
775 GST_DEBUG_OBJECT (dec, "Have buffer of size %d, ts %"
776 GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT, GST_BUFFER_SIZE (buf),
777 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_DURATION (buf));
779 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
780 if (!GST_CLOCK_TIME_IS_VALID (dec->next_ts)) {
781 dec->next_ts = GST_BUFFER_TIMESTAMP (buf);
782 }
784 /* Move time forward to the start of the new buffer */
785 ret = gst_dvd_sub_dec_advance_time (dec, GST_BUFFER_TIMESTAMP (buf));
786 }
788 if (dec->have_title) {
789 gst_buffer_unref (dec->partialbuf);
790 dec->partialbuf = NULL;
791 dec->have_title = FALSE;
792 }
794 GST_DEBUG_OBJECT (dec, "Got subtitle buffer, pts %" GST_TIME_FORMAT,
795 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
797 /* deal with partial frame from previous buffer */
798 if (dec->partialbuf) {
799 GstBuffer *merge;
801 merge = gst_buffer_join (dec->partialbuf, buf);
802 dec->partialbuf = merge;
803 } else {
804 dec->partialbuf = buf;
805 }
807 data = GST_BUFFER_DATA (dec->partialbuf);
808 size = GST_BUFFER_SIZE (dec->partialbuf);
810 if (size > 4) {
811 dec->packet_size = GST_READ_UINT16_BE (data);
813 if (dec->packet_size == size) {
814 GST_LOG_OBJECT (dec, "Subtitle packet size %d, current size %ld",
815 dec->packet_size, size);
817 dec->data_size = GST_READ_UINT16_BE (data + 2);
819 /* Reset parameters for a new subtitle buffer */
820 dec->parse_pos = data;
821 dec->forced_display = FALSE;
822 dec->visible = FALSE;
824 dec->have_title = TRUE;
825 dec->next_event_ts = GST_BUFFER_TIMESTAMP (dec->partialbuf);
827 if (!GST_CLOCK_TIME_IS_VALID (dec->next_event_ts))
828 dec->next_event_ts = dec->next_ts;
830 dec->next_event_ts += gst_dvd_sub_dec_get_event_delay (dec);
831 }
832 }
834 return ret;
835 }
837 static gboolean
838 gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps)
839 {
840 GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
841 gboolean ret = FALSE;
842 guint32 fourcc = GST_MAKE_FOURCC ('A', 'Y', 'U', 'V');
843 GstCaps *out_caps = NULL, *peer_caps = NULL;
845 GST_DEBUG_OBJECT (dec, "setcaps called with %" GST_PTR_FORMAT, caps);
847 dec->out_fourcc = fourcc;
848 out_caps = gst_caps_new_simple ("video/x-raw-yuv",
849 "width", G_TYPE_INT, dec->in_width,
850 "height", G_TYPE_INT, dec->in_height,
851 "format", GST_TYPE_FOURCC, dec->out_fourcc,
852 "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
854 peer_caps = gst_pad_get_allowed_caps (dec->srcpad);
855 if (G_LIKELY (peer_caps)) {
856 guint i = 0, n = 0;
857 n = gst_caps_get_size (peer_caps);
858 GST_DEBUG_OBJECT (dec, "peer allowed caps (%u structure(s)) are %"
859 GST_PTR_FORMAT, n, peer_caps);
861 for (i = 0; i < n; i++) {
862 GstStructure *s = gst_caps_get_structure (peer_caps, i);
863 /* Check if the peer pad support ARGB format, if yes change caps */
864 if (gst_structure_has_name (s, "video/x-raw-rgb") &&
865 gst_structure_has_field (s, "alpha_mask")) {
866 gst_caps_unref (out_caps);
867 GST_DEBUG_OBJECT (dec, "trying with fourcc %" GST_FOURCC_FORMAT,
868 GST_FOURCC_ARGS (fourcc));
869 out_caps = gst_caps_new_simple ("video/x-raw-rgb",
870 "width", G_TYPE_INT, dec->in_width,
871 "height", G_TYPE_INT, dec->in_height,
872 "framerate", GST_TYPE_FRACTION, 0, 1,
873 "bpp", G_TYPE_INT, 32,
874 "depth", G_TYPE_INT, 32,
875 "red_mask", G_TYPE_INT, 16711680,
876 "green_mask", G_TYPE_INT, 65280,
877 "blue_mask", G_TYPE_INT, 255,
878 "alpha_mask", G_TYPE_INT, -16777216,
879 "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
880 if (gst_pad_peer_accept_caps (dec->srcpad, out_caps)) {
881 GST_DEBUG_OBJECT (dec, "peer accepted format %" GST_FOURCC_FORMAT,
882 GST_FOURCC_ARGS (fourcc));
883 /* If ARGB format then set the flag */
884 dec->use_ARGB = TRUE;
885 break;
886 }
887 }
888 }
889 gst_caps_unref (peer_caps);
890 }
891 GST_DEBUG_OBJECT (dec, "setting caps downstream to %" GST_PTR_FORMAT,
892 out_caps);
893 if (gst_pad_set_caps (dec->srcpad, out_caps)) {
894 dec->out_fourcc = fourcc;
895 } else {
896 GST_WARNING_OBJECT (dec, "failed setting downstream caps");
897 gst_caps_unref (out_caps);
898 goto beach;
899 }
901 gst_caps_unref (out_caps);
902 ret = TRUE;
904 beach:
905 gst_object_unref (dec);
906 return ret;
907 }
909 static gboolean
910 gst_dvd_sub_dec_sink_event (GstPad * pad, GstEvent * event)
911 {
912 GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
913 gboolean ret = FALSE;
915 GST_LOG_OBJECT (dec, "%s event", GST_EVENT_TYPE_NAME (event));
917 switch (GST_EVENT_TYPE (event)) {
918 case GST_EVENT_CUSTOM_DOWNSTREAM:{
919 GstClockTime ts = GST_EVENT_TIMESTAMP (event);
921 if (event->structure != NULL &&
922 gst_structure_has_name (event->structure, "application/x-gst-dvd")) {
924 if (GST_CLOCK_TIME_IS_VALID (ts))
925 gst_dvd_sub_dec_advance_time (dec, ts);
927 if (gst_dvd_sub_dec_handle_dvd_event (dec, event)) {
928 /* gst_dvd_sub_dec_advance_time (dec, dec->next_ts + GST_SECOND / 30.0); */
929 gst_event_unref (event);
930 ret = TRUE;
931 break;
932 }
933 }
935 ret = gst_pad_event_default (pad, event);
936 break;
937 }
938 case GST_EVENT_NEWSEGMENT:{
939 gboolean update;
940 GstFormat format;
941 gint64 start, stop, pos;
943 gst_event_parse_new_segment (event, &update, NULL, &format, &start,
944 &stop, &pos);
946 if (update) {
947 /* update ... advance time */
948 if (GST_CLOCK_TIME_IS_VALID (pos)) {
949 GST_DEBUG_OBJECT (dec, "Got segment update, advancing time from %"
950 GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
951 GST_TIME_ARGS (dec->next_ts), GST_TIME_ARGS (pos));
953 gst_dvd_sub_dec_advance_time (dec, pos);
954 } else {
955 GST_WARNING_OBJECT (dec, "Got segment update with invalid position");
956 }
957 gst_event_unref (event);
958 ret = TRUE;
959 } else {
960 /* not just an update ... */
962 /* Turn off forced highlight display */
963 // dec->forced_display = 0;
964 // dec->current_button = 0;
965 if (dec->partialbuf) {
966 gst_buffer_unref (dec->partialbuf);
967 dec->partialbuf = NULL;
968 dec->have_title = FALSE;
969 }
971 if (GST_CLOCK_TIME_IS_VALID (pos))
972 dec->next_ts = pos;
973 else
974 dec->next_ts = GST_CLOCK_TIME_NONE;
976 GST_DEBUG_OBJECT (dec, "Got newsegment, new time = %"
977 GST_TIME_FORMAT, GST_TIME_ARGS (dec->next_ts));
979 ret = gst_pad_event_default (pad, event);
980 }
981 break;
982 }
983 case GST_EVENT_FLUSH_STOP:{
984 /* Turn off forced highlight display */
985 dec->forced_display = 0;
986 dec->current_button = 0;
988 if (dec->partialbuf) {
989 gst_buffer_unref (dec->partialbuf);
990 dec->partialbuf = NULL;
991 dec->have_title = FALSE;
992 }
994 ret = gst_pad_event_default (pad, event);
995 break;
996 }
997 default:{
998 ret = gst_pad_event_default (pad, event);
999 break;
1000 }
1001 }
1002 gst_object_unref (dec);
1003 return ret;
1004 }
1006 static gboolean
1007 gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec, GstEvent * event)
1008 {
1009 GstStructure *structure;
1010 const gchar *event_name;
1012 structure = (GstStructure *) gst_event_get_structure (event);
1014 if (structure == NULL)
1015 goto not_handled;
1017 event_name = gst_structure_get_string (structure, "event");
1019 GST_LOG_OBJECT (dec,
1020 "DVD event %s with timestamp %" G_GINT64_FORMAT " on sub pad",
1021 GST_STR_NULL (event_name), GST_EVENT_TIMESTAMP (event));
1023 if (event_name == NULL)
1024 goto not_handled;
1026 if (strcmp (event_name, "dvd-spu-highlight") == 0) {
1027 gint button;
1028 gint palette, sx, sy, ex, ey;
1029 gint i;
1031 /* Details for the highlight region to display */
1032 if (!gst_structure_get_int (structure, "button", &button) ||
1033 !gst_structure_get_int (structure, "palette", &palette) ||
1034 !gst_structure_get_int (structure, "sx", &sx) ||
1035 !gst_structure_get_int (structure, "sy", &sy) ||
1036 !gst_structure_get_int (structure, "ex", &ex) ||
1037 !gst_structure_get_int (structure, "ey", &ey)) {
1038 GST_ERROR_OBJECT (dec, "Invalid dvd-spu-highlight event received");
1039 return TRUE;
1040 }
1041 dec->current_button = button;
1042 dec->hl_left = sx;
1043 dec->hl_top = sy;
1044 dec->hl_right = ex;
1045 dec->hl_bottom = ey;
1046 for (i = 0; i < 4; i++) {
1047 dec->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f;
1048 dec->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f;
1049 }
1051 GST_DEBUG_OBJECT (dec, "New button activated highlight=(%d,%d) to (%d,%d) "
1052 "palette 0x%x", sx, sy, ex, ey, palette);
1053 gst_setup_palette (dec);
1055 dec->buf_dirty = TRUE;
1056 } else if (strcmp (event_name, "dvd-spu-clut-change") == 0) {
1057 /* Take a copy of the colour table */
1058 gchar name[16];
1059 int i;
1060 gint value;
1062 GST_LOG_OBJECT (dec, "New colour table received");
1063 for (i = 0; i < 16; i++) {
1064 g_snprintf (name, sizeof (name), "clut%02d", i);
1065 if (!gst_structure_get_int (structure, name, &value)) {
1066 GST_ERROR_OBJECT (dec, "dvd-spu-clut-change event did not "
1067 "contain %s field", name);
1068 break;
1069 }
1070 dec->current_clut[i] = (guint32) (value);
1071 }
1073 gst_setup_palette (dec);
1075 dec->buf_dirty = TRUE;
1076 } else if (strcmp (event_name, "dvd-spu-stream-change") == 0
1077 || strcmp (event_name, "dvd-spu-reset-highlight") == 0) {
1078 /* Turn off forced highlight display */
1079 dec->current_button = 0;
1081 GST_LOG_OBJECT (dec, "Clearing button state");
1082 dec->buf_dirty = TRUE;
1083 } else if (strcmp (event_name, "dvd-spu-still-frame") == 0) {
1084 /* Handle a still frame */
1085 GST_LOG_OBJECT (dec, "Received still frame notification");
1086 } else {
1087 goto not_handled;
1088 }
1090 return TRUE;
1092 not_handled:
1093 {
1094 /* Ignore all other unknown events */
1095 GST_LOG_OBJECT (dec, "Ignoring other custom event %" GST_PTR_FORMAT,
1096 structure);
1097 return FALSE;
1098 }
1099 }
1101 static gboolean
1102 plugin_init (GstPlugin * plugin)
1103 {
1104 if (!gst_element_register (plugin, "dvdsubdec", GST_RANK_NONE,
1105 GST_TYPE_DVD_SUB_DEC) ||
1106 !gst_element_register (plugin, "dvdsubparse", GST_RANK_NONE,
1107 GST_TYPE_DVD_SUB_PARSE)) {
1108 return FALSE;
1109 }
1111 GST_DEBUG_CATEGORY_INIT (gst_dvd_sub_dec_debug, "dvdsubdec", 0,
1112 "DVD subtitle decoder");
1114 return TRUE;
1115 }
1117 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1118 GST_VERSION_MINOR,
1119 "dvdsub",
1120 "DVD subtitle parser and decoder", plugin_init,
1121 VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);