summaryrefslogtreecommitdiff
path: root/gst/dvdsub/gstdvdsubdec.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst/dvdsub/gstdvdsubdec.c')
-rw-r--r--gst/dvdsub/gstdvdsubdec.c1160
1 files changed, 1160 insertions, 0 deletions
diff --git a/gst/dvdsub/gstdvdsubdec.c b/gst/dvdsub/gstdvdsubdec.c
new file mode 100644
index 0000000..2589ee6
--- /dev/null
+++ b/gst/dvdsub/gstdvdsubdec.c
@@ -0,0 +1,1160 @@
+/* GStreamer
+ * Copyright (C) <2005> Jan Schmidt <jan@fluendo.com>
+ * Copyright (C) <2002> Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdvdsubdec.h"
+#include "gstdvdsubparse.h"
+#include <string.h>
+
+#define gst_dvd_sub_dec_parent_class parent_class
+G_DEFINE_TYPE (GstDvdSubDec, gst_dvd_sub_dec, GST_TYPE_ELEMENT);
+
+static gboolean gst_dvd_sub_dec_src_event (GstPad * srcpad, GstObject * parent,
+ GstEvent * event);
+static GstFlowReturn gst_dvd_sub_dec_chain (GstPad * pad, GstObject * parent,
+ GstBuffer * buf);
+
+static gboolean gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec,
+ GstEvent * event);
+static void gst_dvd_sub_dec_finalize (GObject * gobject);
+static void gst_setup_palette (GstDvdSubDec * dec);
+static void gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec,
+ GstVideoFrame * frame);
+static GstClockTime gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec);
+static gboolean gst_dvd_sub_dec_sink_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps);
+
+static GstFlowReturn gst_send_subtitle_frame (GstDvdSubDec * dec,
+ GstClockTime end_ts);
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-raw, format = (string) { AYUV, ARGB },"
+ "width = (int) 720, height = (int) 576, framerate = (fraction) 0/1")
+ );
+
+static GstStaticPadTemplate subtitle_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("subpicture/x-dvd")
+ );
+
+GST_DEBUG_CATEGORY_STATIC (gst_dvd_sub_dec_debug);
+#define GST_CAT_DEFAULT (gst_dvd_sub_dec_debug)
+
+enum
+{
+ SPU_FORCE_DISPLAY = 0x00,
+ SPU_SHOW = 0x01,
+ SPU_HIDE = 0x02,
+ SPU_SET_PALETTE = 0x03,
+ SPU_SET_ALPHA = 0x04,
+ SPU_SET_SIZE = 0x05,
+ SPU_SET_OFFSETS = 0x06,
+ SPU_WIPE = 0x07,
+ SPU_END = 0xff
+};
+
+static const guint32 default_clut[16] = {
+ 0xb48080, 0x248080, 0x628080, 0xd78080,
+ 0x808080, 0x808080, 0x808080, 0x808080,
+ 0x808080, 0x808080, 0x808080, 0x808080,
+ 0x808080, 0x808080, 0x808080, 0x808080
+};
+
+typedef struct RLE_state
+{
+ gint id;
+ gint aligned;
+ gint offset[2];
+ gint hl_left;
+ gint hl_right;
+
+ guchar *target;
+
+ guchar next;
+}
+RLE_state;
+
+static void
+gst_dvd_sub_dec_class_init (GstDvdSubDecClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->finalize = gst_dvd_sub_dec_finalize;
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&src_template));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&subtitle_template));
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "DVD subtitle decoder", "Codec/Decoder/Video",
+ "Decodes DVD subtitles into AYUV video frames",
+ "Wim Taymans <wim.taymans@gmail.com>, "
+ "Jan Schmidt <thaytan@mad.scientist.com>");
+}
+
+static void
+gst_dvd_sub_dec_init (GstDvdSubDec * dec)
+{
+ GstPadTemplate *tmpl;
+
+ dec->sinkpad = gst_pad_new_from_static_template (&subtitle_template, "sink");
+ gst_pad_set_chain_function (dec->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_chain));
+ gst_pad_set_event_function (dec->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_sink_event));
+ gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
+
+ tmpl = gst_static_pad_template_get (&src_template);
+ dec->srcpad = gst_pad_new_from_template (tmpl, "src");
+ gst_pad_set_event_function (dec->srcpad,
+ GST_DEBUG_FUNCPTR (gst_dvd_sub_dec_src_event));
+ gst_pad_use_fixed_caps (dec->srcpad);
+ gst_object_unref (tmpl);
+ gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
+
+ /* FIXME: aren't there more possible sizes? (tpm) */
+ dec->in_width = 720;
+ dec->in_height = 576;
+
+ dec->partialbuf = NULL;
+ dec->have_title = FALSE;
+ dec->parse_pos = NULL;
+ dec->forced_display = FALSE;
+ dec->visible = FALSE;
+
+ memcpy (dec->current_clut, default_clut, sizeof (guint32) * 16);
+
+ gst_setup_palette (dec);
+
+ dec->next_ts = 0;
+ dec->next_event_ts = GST_CLOCK_TIME_NONE;
+
+ dec->buf_dirty = TRUE;
+ dec->use_ARGB = FALSE;
+}
+
+static void
+gst_dvd_sub_dec_finalize (GObject * gobject)
+{
+ GstDvdSubDec *dec = GST_DVD_SUB_DEC (gobject);
+
+ if (dec->partialbuf) {
+ gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
+ gst_buffer_unref (dec->partialbuf);
+ dec->partialbuf = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (gobject);
+}
+
+static gboolean
+gst_dvd_sub_dec_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ gboolean res = FALSE;
+
+ switch (GST_EVENT_TYPE (event)) {
+ default:
+ res = gst_pad_event_default (pad, parent, event);
+ break;
+ }
+
+ return res;
+}
+
+static GstClockTime
+gst_dvd_sub_dec_get_event_delay (GstDvdSubDec * dec)
+{
+ guchar *buf;
+ guint16 ticks;
+ GstClockTime event_delay;
+
+ /* If starting a new buffer, follow the first DCSQ ptr */
+ if (dec->parse_pos == dec->partialmap.data) {
+ buf = dec->parse_pos + dec->data_size;
+ } else {
+ buf = dec->parse_pos;
+ }
+
+ ticks = GST_READ_UINT16_BE (buf);
+ event_delay = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
+
+ GST_DEBUG_OBJECT (dec, "returning delay %" GST_TIME_FORMAT " from offset %u",
+ GST_TIME_ARGS (event_delay), (guint) (buf - dec->parse_pos));
+
+ return event_delay;
+}
+
+/*
+ * Parse the next event time in the current subpicture buffer, stopping
+ * when time advances to the next state.
+ */
+static void
+gst_dvd_sub_dec_parse_subpic (GstDvdSubDec * dec)
+{
+#define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \
+ { GST_WARNING("Subtitle stream broken parsing %c", *buf); \
+ broken = TRUE; break; }
+
+ guchar *start = dec->partialmap.data;
+ guchar *buf;
+ guchar *end;
+ gboolean broken = FALSE;
+ gboolean last_seq = FALSE;
+ guchar *next_seq = NULL;
+ GstClockTime event_time;
+
+ /* nothing to do if we finished this buffer already */
+ if (dec->parse_pos == NULL)
+ return;
+
+ g_return_if_fail (dec->packet_size >= 4);
+
+ end = start + dec->packet_size;
+ if (dec->parse_pos == start) {
+ buf = dec->parse_pos + dec->data_size;
+ } else {
+ buf = dec->parse_pos;
+ }
+
+ g_assert (buf >= start && buf < end);
+
+ /* If the next control sequence is at the current offset, this is
+ * the last one */
+ next_seq = start + GST_READ_UINT16_BE (buf + 2);
+ last_seq = (next_seq == buf);
+ buf += 4;
+
+ while ((buf < end) && (!broken)) {
+ switch (*buf) {
+ case SPU_FORCE_DISPLAY: /* Forced display menu subtitle */
+ dec->forced_display = TRUE;
+ dec->buf_dirty = TRUE;
+ GST_DEBUG_OBJECT (dec, "SPU FORCE_DISPLAY");
+ buf++;
+ break;
+ case SPU_SHOW: /* Show the subtitle in this packet */
+ dec->visible = TRUE;
+ dec->buf_dirty = TRUE;
+ GST_DEBUG_OBJECT (dec, "SPU SHOW at %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (dec->next_event_ts));
+ buf++;
+ break;
+ case SPU_HIDE:
+ /* 02 ff (ff) is the end of the packet, hide the subpicture */
+ dec->visible = FALSE;
+ dec->buf_dirty = TRUE;
+
+ GST_DEBUG_OBJECT (dec, "SPU HIDE at %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (dec->next_event_ts));
+ buf++;
+ break;
+ case SPU_SET_PALETTE: /* palette */
+ PARSE_BYTES_NEEDED (3);
+
+ GST_DEBUG_OBJECT (dec, "SPU SET_PALETTE");
+
+ dec->subtitle_index[3] = buf[1] >> 4;
+ dec->subtitle_index[2] = buf[1] & 0xf;
+ dec->subtitle_index[1] = buf[2] >> 4;
+ dec->subtitle_index[0] = buf[2] & 0xf;
+ gst_setup_palette (dec);
+
+ dec->buf_dirty = TRUE;
+ buf += 3;
+ break;
+ case SPU_SET_ALPHA: /* transparency palette */
+ PARSE_BYTES_NEEDED (3);
+
+ GST_DEBUG_OBJECT (dec, "SPU SET_ALPHA");
+
+ dec->subtitle_alpha[3] = buf[1] >> 4;
+ dec->subtitle_alpha[2] = buf[1] & 0xf;
+ dec->subtitle_alpha[1] = buf[2] >> 4;
+ dec->subtitle_alpha[0] = buf[2] & 0xf;
+ gst_setup_palette (dec);
+
+ dec->buf_dirty = TRUE;
+ buf += 3;
+ break;
+ case SPU_SET_SIZE: /* image coordinates */
+ PARSE_BYTES_NEEDED (7);
+
+ dec->top = ((buf[4] & 0x3f) << 4) | ((buf[5] & 0xe0) >> 4);
+ dec->left = ((buf[1] & 0x3f) << 4) | ((buf[2] & 0xf0) >> 4);
+ dec->right = ((buf[2] & 0x03) << 8) | buf[3];
+ dec->bottom = ((buf[5] & 0x03) << 8) | buf[6];
+
+ GST_DEBUG_OBJECT (dec, "SPU SET_SIZE left %d, top %d, right %d, "
+ "bottom %d", dec->left, dec->top, dec->right, dec->bottom);
+
+ dec->buf_dirty = TRUE;
+ buf += 7;
+ break;
+ case SPU_SET_OFFSETS: /* image 1 / image 2 offsets */
+ PARSE_BYTES_NEEDED (5);
+
+ dec->offset[0] = (((guint) buf[1]) << 8) | buf[2];
+ dec->offset[1] = (((guint) buf[3]) << 8) | buf[4];
+ GST_DEBUG_OBJECT (dec, "Offset1 %d, Offset2 %d",
+ dec->offset[0], dec->offset[1]);
+
+ dec->buf_dirty = TRUE;
+ buf += 5;
+ break;
+ case SPU_WIPE:
+ {
+ guint length;
+
+ PARSE_BYTES_NEEDED (3);
+
+ GST_WARNING_OBJECT (dec, "SPU_WIPE not yet implemented");
+
+ length = (buf[1] << 8) | (buf[2]);
+ buf += 1 + length;
+
+ dec->buf_dirty = TRUE;
+ break;
+ }
+ case SPU_END:
+ buf = (last_seq) ? end : next_seq;
+
+ /* Start a new control sequence */
+ if (buf + 4 < end) {
+ guint16 ticks = GST_READ_UINT16_BE (buf);
+
+ event_time = gst_util_uint64_scale (ticks, 1024 * GST_SECOND, 90000);
+
+ GST_DEBUG_OBJECT (dec,
+ "Next DCSQ at offset %u, delay %g secs (%d ticks)",
+ (guint) (buf - start),
+ gst_util_guint64_to_gdouble (event_time / GST_SECOND), ticks);
+
+ dec->parse_pos = buf;
+ if (event_time > 0) {
+ dec->next_event_ts += event_time;
+
+ GST_LOG_OBJECT (dec, "Exiting parse loop with time %g",
+ gst_guint64_to_gdouble (dec->next_event_ts) /
+ gst_guint64_to_gdouble (GST_SECOND));
+ return;
+ }
+ break;
+ } else {
+ dec->parse_pos = NULL;
+ dec->next_event_ts = GST_CLOCK_TIME_NONE;
+ GST_LOG_OBJECT (dec, "Finished all cmds. Exiting parse loop");
+ return;
+ }
+ default:
+ GST_ERROR
+ ("Invalid sequence in subtitle packet header (%.2x). Skipping",
+ *buf);
+ broken = TRUE;
+ dec->parse_pos = NULL;
+ break;
+ }
+ }
+}
+
+static inline int
+gst_get_nibble (guchar * buffer, RLE_state * state)
+{
+ if (state->aligned) {
+ state->next = buffer[state->offset[state->id]++];
+ state->aligned = 0;
+ return state->next >> 4;
+ } else {
+ state->aligned = 1;
+ return state->next & 0xf;
+ }
+}
+
+/* Premultiply the current lookup table into the "target" cache */
+static void
+gst_setup_palette (GstDvdSubDec * dec)
+{
+ gint i;
+ guint32 col;
+ Color_val *target_yuv = dec->palette_cache_yuv;
+ Color_val *target2_yuv = dec->hl_palette_cache_yuv;
+ Color_val *target_rgb = dec->palette_cache_rgb;
+ Color_val *target2_rgb = dec->hl_palette_cache_rgb;
+
+ for (i = 0; i < 4; i++, target2_yuv++, target_yuv++) {
+ col = dec->current_clut[dec->subtitle_index[i]];
+ target_yuv->Y_R = (col >> 16) & 0xff;
+ target_yuv->V_B = (col >> 8) & 0xff;
+ target_yuv->U_G = col & 0xff;
+ target_yuv->A = dec->subtitle_alpha[i] * 0xff / 0xf;
+
+ col = dec->current_clut[dec->menu_index[i]];
+ target2_yuv->Y_R = (col >> 16) & 0xff;
+ target2_yuv->V_B = (col >> 8) & 0xff;
+ target2_yuv->U_G = col & 0xff;
+ target2_yuv->A = dec->menu_alpha[i] * 0xff / 0xf;
+
+ /* If ARGB flag set, then convert YUV palette to RGB */
+ /* Using integer aritmetic */
+ if (dec->use_ARGB) {
+ guchar C = target_yuv->Y_R - 16;
+ guchar D = target_yuv->U_G - 128;
+ guchar E = target_yuv->V_B - 128;
+
+ target_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
+ target_rgb->U_G =
+ CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
+ target_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
+ target_rgb->A = target_yuv->A;
+
+ C = target2_yuv->Y_R - 16;
+ D = target2_yuv->U_G - 128;
+ E = target2_yuv->V_B - 128;
+
+ target2_rgb->Y_R = CLAMP (((298 * C + 409 * E + 128) >> 8), 0, 255);
+ target2_rgb->U_G =
+ CLAMP (((298 * C - 100 * D - 128 * E + 128) >> 8), 0, 255);
+ target2_rgb->V_B = CLAMP (((298 * C + 516 * D + 128) >> 8), 0, 255);
+ target2_rgb->A = target2_yuv->A;
+ }
+ target_rgb++;
+ target2_rgb++;
+ }
+}
+
+static inline guint
+gst_get_rle_code (guchar * buffer, RLE_state * state)
+{
+ gint code;
+
+ code = gst_get_nibble (buffer, state);
+ if (code < 0x4) { /* 4 .. f */
+ code = (code << 4) | gst_get_nibble (buffer, state);
+ if (code < 0x10) { /* 1x .. 3x */
+ code = (code << 4) | gst_get_nibble (buffer, state);
+ if (code < 0x40) { /* 04x .. 0fx */
+ code = (code << 4) | gst_get_nibble (buffer, state);
+ }
+ }
+ }
+ return code;
+}
+
+#define DRAW_RUN(target,len,c) \
+G_STMT_START { \
+ gint i = 0; \
+ if ((c)->A) { \
+ for (i = 0; i < (len); i++) { \
+ *(target)++ = (c)->A; \
+ *(target)++ = (c)->Y_R; \
+ *(target)++ = (c)->U_G; \
+ *(target)++ = (c)->V_B; \
+ } \
+ } else { \
+ (target) += 4 * (len); \
+ } \
+} G_STMT_END
+
+/*
+ * This function steps over each run-length segment, drawing
+ * into the YUVA/ARGB buffers as it goes. UV are composited and then output
+ * at half width/height
+ */
+static void
+gst_draw_rle_line (GstDvdSubDec * dec, guchar * buffer, RLE_state * state)
+{
+ gint length, colourid;
+ guint code;
+ gint x, right;
+ guchar *target;
+
+ target = state->target;
+
+ x = dec->left;
+ right = dec->right + 1;
+
+ while (x < right) {
+ gboolean in_hl;
+ const Color_val *colour_entry;
+
+ code = gst_get_rle_code (buffer, state);
+ length = code >> 2;
+ colourid = code & 3;
+ if (dec->use_ARGB)
+ colour_entry = dec->palette_cache_rgb + colourid;
+ else
+ colour_entry = dec->palette_cache_yuv + colourid;
+
+ /* Length = 0 implies fill to the end of the line */
+ /* Restrict the colour run to the end of the line */
+ if (length == 0 || x + length > right)
+ length = right - x;
+
+ /* Check if this run of colour touches the highlight region */
+ in_hl = ((x <= state->hl_right) && (x + length) >= state->hl_left);
+ if (in_hl) {
+ gint run;
+
+ /* Draw to the left of the highlight */
+ if (x <= state->hl_left) {
+ run = MIN (length, state->hl_left - x + 1);
+
+ DRAW_RUN (target, run, colour_entry);
+ length -= run;
+ x += run;
+ }
+
+ /* Draw across the highlight region */
+ if (x <= state->hl_right) {
+ const Color_val *hl_colour;
+ if (dec->use_ARGB)
+ hl_colour = dec->hl_palette_cache_rgb + colourid;
+ else
+ hl_colour = dec->hl_palette_cache_yuv + colourid;
+
+ run = MIN (length, state->hl_right - x + 1);
+
+ DRAW_RUN (target, run, hl_colour);
+ length -= run;
+ x += run;
+ }
+ }
+
+ /* Draw the rest of the run */
+ if (length > 0) {
+ DRAW_RUN (target, length, colour_entry);
+ x += length;
+ }
+ }
+}
+
+/*
+ * Decode the RLE subtitle image and blend with the current
+ * frame buffer.
+ */
+static void
+gst_dvd_sub_dec_merge_title (GstDvdSubDec * dec, GstVideoFrame * frame)
+{
+ gint y;
+ gint Y_stride;
+ guchar *buffer = dec->partialmap.data;
+ gint hl_top, hl_bottom;
+ gint last_y;
+ RLE_state state;
+ guint8 *Y_data;;
+
+ GST_DEBUG_OBJECT (dec, "Merging subtitle on frame");
+
+ Y_data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
+ Y_stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
+
+ state.id = 0;
+ state.aligned = 1;
+ state.next = 0;
+ state.offset[0] = dec->offset[0];
+ state.offset[1] = dec->offset[1];
+
+ /* center the image when display rectangle exceeds the video width */
+ if (dec->in_width <= dec->right) {
+ gint left, disp_width;
+
+ disp_width = dec->right - dec->left + 1;
+ left = (dec->in_width - disp_width) / 2;
+ dec->left = left;
+ dec->right = left + disp_width - 1;
+
+ /* if it clips to the right, shift it left, but only till zero */
+ if (dec->right >= dec->in_width) {
+ gint shift = dec->right - dec->in_width - 1;
+ if (shift > dec->left)
+ shift = dec->left;
+ dec->left -= shift;
+ dec->right -= shift;
+ }
+
+ GST_DEBUG_OBJECT (dec, "clipping width to %d,%d",
+ dec->left, dec->in_width - 1);
+ }
+
+ /* for the height, bring it up till it fits as well as it can. We
+ * assume the picture is in the lower part. We should better check where it
+ * is and do something more clever. */
+ if (dec->in_height <= dec->bottom) {
+
+ /* shift it up, but only till zero */
+ gint shift = dec->bottom - dec->in_height - 1;
+ if (shift > dec->top)
+ shift = dec->top;
+ dec->top -= shift;
+ dec->bottom -= shift;
+
+ /* start on even line */
+ if (dec->top & 1) {
+ dec->top--;
+ dec->bottom--;
+ }
+
+ GST_DEBUG_OBJECT (dec, "clipping height to %d,%d",
+ dec->top, dec->in_height - 1);
+ }
+
+ if (dec->current_button) {
+ hl_top = dec->hl_top;
+ hl_bottom = dec->hl_bottom;
+ } else {
+ hl_top = -1;
+ hl_bottom = -1;
+ }
+ last_y = MIN (dec->bottom, dec->in_height);
+
+ y = dec->top;
+ state.target = Y_data + 4 * dec->left + (y * Y_stride);
+
+ /* Now draw scanlines until we hit last_y or end of RLE data */
+ for (; ((state.offset[1] < dec->data_size + 2) && (y <= last_y)); y++) {
+ /* Set up to draw the highlight if we're in the right scanlines */
+ if (y > hl_bottom || y < hl_top) {
+ state.hl_left = -1;
+ state.hl_right = -1;
+ } else {
+ state.hl_left = dec->hl_left;
+ state.hl_right = dec->hl_right;
+ }
+ gst_draw_rle_line (dec, buffer, &state);
+
+ state.target += Y_stride;
+
+ /* Realign the RLE state for the next line */
+ if (!state.aligned)
+ gst_get_nibble (buffer, &state);
+ state.id = !state.id;
+ }
+}
+
+static void
+gst_send_empty_fill (GstDvdSubDec * dec, GstClockTime ts)
+{
+ if (dec->next_ts < ts) {
+ GST_LOG_OBJECT (dec, "Sending GAP event update to advance time to %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (ts));
+
+ gst_pad_push_event (dec->srcpad,
+ gst_event_new_gap (dec->next_ts, ts - dec->next_ts));
+ }
+ dec->next_ts = ts;
+}
+
+static GstFlowReturn
+gst_send_subtitle_frame (GstDvdSubDec * dec, GstClockTime end_ts)
+{
+ GstFlowReturn flow;
+ GstBuffer *out_buf;
+ GstVideoFrame frame;
+ guint8 *data;
+ gint x, y;
+ static GstAllocationParams params = { 0, 3, 0, 0, };
+
+ g_assert (dec->have_title);
+ g_assert (dec->next_ts <= end_ts);
+
+ /* Check if we need to redraw the output buffer */
+ if (!dec->buf_dirty) {
+ flow = GST_FLOW_OK;
+ goto out;
+ }
+
+ out_buf =
+ gst_buffer_new_allocate (NULL, GST_VIDEO_INFO_SIZE (&dec->info),
+ &params);
+ gst_video_frame_map (&frame, &dec->info, out_buf, GST_MAP_READWRITE);
+
+ data = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
+
+ /* Clear the buffer */
+ /* FIXME - move this into the buffer rendering code */
+ for (y = 0; y < dec->in_height; y++) {
+ guchar *line = data + 4 * dec->in_width * y;
+
+ for (x = 0; x < dec->in_width; x++) {
+ line[0] = 0; /* A */
+ if (!dec->use_ARGB) {
+ line[1] = 16; /* Y */
+ line[2] = 128; /* U */
+ line[3] = 128; /* V */
+ } else {
+ line[1] = 0; /* R */
+ line[2] = 0; /* G */
+ line[3] = 0; /* B */
+ }
+
+ line += 4;
+ }
+ }
+
+ /* FIXME: do we really want to honour the forced_display flag
+ * for subtitles streans? */
+ if (dec->visible || dec->forced_display) {
+ gst_dvd_sub_dec_merge_title (dec, &frame);
+ }
+
+ gst_video_frame_unmap (&frame);
+
+ dec->buf_dirty = FALSE;
+
+ GST_BUFFER_TIMESTAMP (out_buf) = dec->next_ts;
+ if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts)) {
+ GST_BUFFER_DURATION (out_buf) = GST_CLOCK_DIFF (dec->next_ts,
+ dec->next_event_ts);
+ } else {
+ GST_BUFFER_DURATION (out_buf) = GST_CLOCK_TIME_NONE;
+ }
+
+ GST_DEBUG_OBJECT (dec, "Sending subtitle buffer with ts %"
+ GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)),
+ GST_BUFFER_DURATION (out_buf));
+
+ flow = gst_pad_push (dec->srcpad, out_buf);
+
+out:
+ dec->next_ts = end_ts;
+ return flow;
+}
+
+/* Walk time forward, processing any subtitle events as needed. */
+static GstFlowReturn
+gst_dvd_sub_dec_advance_time (GstDvdSubDec * dec, GstClockTime new_ts)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+
+ GST_LOG_OBJECT (dec, "Advancing time to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (new_ts));
+
+ if (!dec->have_title) {
+ gst_send_empty_fill (dec, new_ts);
+ return ret;
+ }
+
+ while (dec->next_ts < new_ts) {
+ GstClockTime next_ts = new_ts;
+
+ if (GST_CLOCK_TIME_IS_VALID (dec->next_event_ts) &&
+ dec->next_event_ts < next_ts) {
+ /* We might need to process the subtitle cmd queue */
+ next_ts = dec->next_event_ts;
+ }
+
+ /*
+ * Now, either output a filler or a frame spanning
+ * dec->next_ts to next_ts
+ */
+ if (dec->visible || dec->forced_display) {
+ ret = gst_send_subtitle_frame (dec, next_ts);
+ } else {
+ gst_send_empty_fill (dec, next_ts);
+ }
+
+ /*
+ * and then process some subtitle cmds if we need
+ */
+ if (next_ts == dec->next_event_ts)
+ gst_dvd_sub_dec_parse_subpic (dec);
+ }
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_dvd_sub_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstDvdSubDec *dec;
+ guint8 *data;
+ glong size = 0;
+
+ dec = GST_DVD_SUB_DEC (parent);
+
+ GST_DEBUG_OBJECT (dec, "Have buffer of size %" G_GSIZE_FORMAT ", ts %"
+ GST_TIME_FORMAT ", dur %" G_GINT64_FORMAT, gst_buffer_get_size (buf),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_DURATION (buf));
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+ if (!GST_CLOCK_TIME_IS_VALID (dec->next_ts)) {
+ dec->next_ts = GST_BUFFER_TIMESTAMP (buf);
+ }
+
+ /* Move time forward to the start of the new buffer */
+ ret = gst_dvd_sub_dec_advance_time (dec, GST_BUFFER_TIMESTAMP (buf));
+ }
+
+ if (dec->have_title) {
+ gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
+ gst_buffer_unref (dec->partialbuf);
+ dec->partialbuf = NULL;
+ dec->have_title = FALSE;
+ }
+
+ GST_DEBUG_OBJECT (dec, "Got subtitle buffer, pts %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
+
+ /* deal with partial frame from previous buffer */
+ if (dec->partialbuf) {
+ gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
+ dec->partialbuf = gst_buffer_append (dec->partialbuf, buf);
+ } else {
+ dec->partialbuf = buf;
+ }
+
+ gst_buffer_map (dec->partialbuf, &dec->partialmap, GST_MAP_READ);
+
+ data = dec->partialmap.data;
+ size = dec->partialmap.size;
+
+ if (size > 4) {
+ dec->packet_size = GST_READ_UINT16_BE (data);
+
+ if (dec->packet_size == size) {
+ GST_LOG_OBJECT (dec, "Subtitle packet size %d, current size %ld",
+ dec->packet_size, size);
+
+ dec->data_size = GST_READ_UINT16_BE (data + 2);
+
+ /* Reset parameters for a new subtitle buffer */
+ dec->parse_pos = data;
+ dec->forced_display = FALSE;
+ dec->visible = FALSE;
+
+ dec->have_title = TRUE;
+ dec->next_event_ts = GST_BUFFER_TIMESTAMP (dec->partialbuf);
+
+ if (!GST_CLOCK_TIME_IS_VALID (dec->next_event_ts))
+ dec->next_event_ts = dec->next_ts;
+
+ dec->next_event_ts += gst_dvd_sub_dec_get_event_delay (dec);
+ }
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_dvd_sub_dec_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstDvdSubDec *dec = GST_DVD_SUB_DEC (gst_pad_get_parent (pad));
+ gboolean ret = FALSE;
+ GstCaps *out_caps = NULL, *peer_caps = NULL;
+
+ GST_DEBUG_OBJECT (dec, "setcaps called with %" GST_PTR_FORMAT, caps);
+
+ out_caps = gst_caps_new_simple ("video/x-raw",
+ "format", G_TYPE_STRING, "AYUV",
+ "width", G_TYPE_INT, dec->in_width,
+ "height", G_TYPE_INT, dec->in_height,
+ "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
+
+ peer_caps = gst_pad_get_allowed_caps (dec->srcpad);
+ if (G_LIKELY (peer_caps)) {
+ guint i = 0, n = 0;
+
+ n = gst_caps_get_size (peer_caps);
+ GST_DEBUG_OBJECT (dec, "peer allowed caps (%u structure(s)) are %"
+ GST_PTR_FORMAT, n, peer_caps);
+
+ for (i = 0; i < n; i++) {
+ GstStructure *s = gst_caps_get_structure (peer_caps, i);
+ /* Check if the peer pad support ARGB format, if yes change caps */
+ if (gst_structure_has_name (s, "video/x-raw")) {
+ gst_caps_unref (out_caps);
+ GST_DEBUG_OBJECT (dec, "trying with ARGB");
+
+ out_caps = gst_caps_new_simple ("video/x-raw",
+ "format", G_TYPE_STRING, "ARGB",
+ "width", G_TYPE_INT, dec->in_width,
+ "height", G_TYPE_INT, dec->in_height,
+ "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
+
+ if (gst_pad_peer_query_accept_caps (dec->srcpad, out_caps)) {
+ GST_DEBUG_OBJECT (dec, "peer accepted ARGB");
+ /* If ARGB format then set the flag */
+ dec->use_ARGB = TRUE;
+ break;
+ }
+ }
+ }
+ gst_caps_unref (peer_caps);
+ }
+ GST_DEBUG_OBJECT (dec, "setting caps downstream to %" GST_PTR_FORMAT,
+ out_caps);
+ if (gst_pad_set_caps (dec->srcpad, out_caps)) {
+ gst_video_info_from_caps (&dec->info, out_caps);
+ } else {
+ GST_WARNING_OBJECT (dec, "failed setting downstream caps");
+ gst_caps_unref (out_caps);
+ goto beach;
+ }
+
+ gst_caps_unref (out_caps);
+ ret = TRUE;
+
+beach:
+ gst_object_unref (dec);
+ return ret;
+}
+
+static gboolean
+gst_dvd_sub_dec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ GstDvdSubDec *dec = GST_DVD_SUB_DEC (parent);
+ gboolean ret = FALSE;
+
+ GST_LOG_OBJECT (dec, "%s event", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
+ ret = gst_dvd_sub_dec_sink_setcaps (pad, caps);
+ gst_event_unref (event);
+ break;
+ }
+ case GST_EVENT_CUSTOM_DOWNSTREAM:{
+ GstClockTime ts = GST_EVENT_TIMESTAMP (event);
+
+ if (gst_event_has_name (event, "application/x-gst-dvd")) {
+ if (GST_CLOCK_TIME_IS_VALID (ts))
+ gst_dvd_sub_dec_advance_time (dec, ts);
+
+ if (gst_dvd_sub_dec_handle_dvd_event (dec, event)) {
+ /* gst_dvd_sub_dec_advance_time (dec, dec->next_ts + GST_SECOND / 30.0); */
+ gst_event_unref (event);
+ ret = TRUE;
+ break;
+ }
+ }
+
+ ret = gst_pad_event_default (pad, parent, event);
+ break;
+ }
+ case GST_EVENT_GAP:
+ {
+ GstClockTime start, duration;
+
+ gst_event_parse_gap (event, &start, &duration);
+ if (GST_CLOCK_TIME_IS_VALID (start)) {
+ if (GST_CLOCK_TIME_IS_VALID (duration))
+ start += duration;
+ /* we do not expect another buffer until after gap,
+ * so that is our position now */
+ GST_DEBUG_OBJECT (dec, "Got GAP event, advancing time from %"
+ GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (dec->next_ts), GST_TIME_ARGS (start));
+
+ gst_dvd_sub_dec_advance_time (dec, start);
+ } else {
+ GST_WARNING_OBJECT (dec, "Got GAP event with invalid position");
+ }
+
+ gst_event_unref (event);
+ ret = TRUE;
+ break;
+ }
+ case GST_EVENT_SEGMENT:
+ {
+ GstSegment seg;
+
+ gst_event_copy_segment (event, &seg);
+
+ {
+#if 0
+ /* Turn off forced highlight display */
+ dec->forced_display = 0;
+ dec->current_button = 0;
+#endif
+ if (dec->partialbuf) {
+ gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
+ gst_buffer_unref (dec->partialbuf);
+ dec->partialbuf = NULL;
+ dec->have_title = FALSE;
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (seg.time))
+ dec->next_ts = seg.time;
+ else
+ dec->next_ts = GST_CLOCK_TIME_NONE;
+
+ GST_DEBUG_OBJECT (dec, "Got newsegment, new time = %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (dec->next_ts));
+
+ ret = gst_pad_event_default (pad, parent, event);
+ }
+ break;
+ }
+ case GST_EVENT_FLUSH_STOP:{
+ /* Turn off forced highlight display */
+ dec->forced_display = 0;
+ dec->current_button = 0;
+
+ if (dec->partialbuf) {
+ gst_buffer_unmap (dec->partialbuf, &dec->partialmap);
+ gst_buffer_unref (dec->partialbuf);
+ dec->partialbuf = NULL;
+ dec->have_title = FALSE;
+ }
+
+ ret = gst_pad_event_default (pad, parent, event);
+ break;
+ }
+ default:{
+ ret = gst_pad_event_default (pad, parent, event);
+ break;
+ }
+ }
+ return ret;
+}
+
+static gboolean
+gst_dvd_sub_dec_handle_dvd_event (GstDvdSubDec * dec, GstEvent * event)
+{
+ GstStructure *structure;
+ const gchar *event_name;
+
+ structure = (GstStructure *) gst_event_get_structure (event);
+
+ if (structure == NULL)
+ goto not_handled;
+
+ event_name = gst_structure_get_string (structure, "event");
+
+ GST_LOG_OBJECT (dec,
+ "DVD event %s with timestamp %" G_GINT64_FORMAT " on sub pad",
+ GST_STR_NULL (event_name), GST_EVENT_TIMESTAMP (event));
+
+ if (event_name == NULL)
+ goto not_handled;
+
+ if (strcmp (event_name, "dvd-spu-highlight") == 0) {
+ gint button;
+ gint palette, sx, sy, ex, ey;
+ gint i;
+
+ /* Details for the highlight region to display */
+ if (!gst_structure_get_int (structure, "button", &button) ||
+ !gst_structure_get_int (structure, "palette", &palette) ||
+ !gst_structure_get_int (structure, "sx", &sx) ||
+ !gst_structure_get_int (structure, "sy", &sy) ||
+ !gst_structure_get_int (structure, "ex", &ex) ||
+ !gst_structure_get_int (structure, "ey", &ey)) {
+ GST_ERROR_OBJECT (dec, "Invalid dvd-spu-highlight event received");
+ return TRUE;
+ }
+ dec->current_button = button;
+ dec->hl_left = sx;
+ dec->hl_top = sy;
+ dec->hl_right = ex;
+ dec->hl_bottom = ey;
+ for (i = 0; i < 4; i++) {
+ dec->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f;
+ dec->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f;
+ }
+
+ GST_DEBUG_OBJECT (dec, "New button activated highlight=(%d,%d) to (%d,%d) "
+ "palette 0x%x", sx, sy, ex, ey, palette);
+ gst_setup_palette (dec);
+
+ dec->buf_dirty = TRUE;
+ } else if (strcmp (event_name, "dvd-spu-clut-change") == 0) {
+ /* Take a copy of the colour table */
+ gchar name[16];
+ int i;
+ gint value;
+
+ GST_LOG_OBJECT (dec, "New colour table received");
+ for (i = 0; i < 16; i++) {
+ g_snprintf (name, sizeof (name), "clut%02d", i);
+ if (!gst_structure_get_int (structure, name, &value)) {
+ GST_ERROR_OBJECT (dec, "dvd-spu-clut-change event did not "
+ "contain %s field", name);
+ break;
+ }
+ dec->current_clut[i] = (guint32) (value);
+ }
+
+ gst_setup_palette (dec);
+
+ dec->buf_dirty = TRUE;
+ } else if (strcmp (event_name, "dvd-spu-stream-change") == 0
+ || strcmp (event_name, "dvd-spu-reset-highlight") == 0) {
+ /* Turn off forced highlight display */
+ dec->current_button = 0;
+
+ GST_LOG_OBJECT (dec, "Clearing button state");
+ dec->buf_dirty = TRUE;
+ } else if (strcmp (event_name, "dvd-spu-still-frame") == 0) {
+ /* Handle a still frame */
+ GST_LOG_OBJECT (dec, "Received still frame notification");
+ } else {
+ goto not_handled;
+ }
+
+ return TRUE;
+
+not_handled:
+ {
+ /* Ignore all other unknown events */
+ GST_LOG_OBJECT (dec, "Ignoring other custom event %" GST_PTR_FORMAT,
+ structure);
+ return FALSE;
+ }
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_element_register (plugin, "dvdsubdec", GST_RANK_NONE,
+ GST_TYPE_DVD_SUB_DEC) ||
+ !gst_element_register (plugin, "dvdsubparse", GST_RANK_NONE,
+ GST_TYPE_DVD_SUB_PARSE)) {
+ return FALSE;
+ }
+
+ GST_DEBUG_CATEGORY_INIT (gst_dvd_sub_dec_debug, "dvdsubdec", 0,
+ "DVD subtitle decoder");
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ dvdsub,
+ "DVD subtitle parser and decoder", plugin_init,
+ VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);