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), ¶ms);
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