/* GStreamer * Copyright (C) <2005> Jan Schmidt * Copyright (C) <2002> Wim Taymans * * 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 GST_DEBUG_CATEGORY_STATIC (gst_dvd_sub_dec_debug); #define GST_CAT_DEFAULT (gst_dvd_sub_dec_debug) #define gst_dvd_sub_dec_parent_class parent_class G_DEFINE_TYPE (GstDvdSubDec, gst_dvd_sub_dec, GST_TYPE_ELEMENT); GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (dvdsubdec, "dvdsubdec", GST_RANK_NONE, GST_TYPE_DVD_SUB_DEC, GST_DEBUG_CATEGORY_INIT (gst_dvd_sub_dec_debug, "dvdsubdec", 0, "DVD subtitle decoder")); 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") ); 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_static_pad_template (gstelement_class, &src_template); gst_element_class_add_static_pad_template (gstelement_class, &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 , " "Jan Schmidt "); } 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 arithmetic */ 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), ¶ms); 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")) { GstCaps *downstream_caps; 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); downstream_caps = gst_pad_peer_query_caps (dec->srcpad, NULL); if (gst_caps_can_intersect (downstream_caps, out_caps)) { gst_caps_unref (downstream_caps); GST_DEBUG_OBJECT (dec, "peer accepted ARGB"); /* If ARGB format then set the flag */ dec->use_ARGB = TRUE; break; } gst_caps_unref (downstream_caps); } } 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:{ if (gst_event_has_name (event, "application/x-gst-dvd")) { const GstStructure *s = gst_event_get_structure (event); GstClockTime ts = GST_CLOCK_TIME_NONE; if (gst_structure_get_clock_time (s, "ts", &ts) && 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) { gboolean ret = FALSE; ret |= GST_ELEMENT_REGISTER (dvdsubdec, plugin); ret |= GST_ELEMENT_REGISTER (dvdsubparse, plugin); return ret; } 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);