| /* GStreamer |
| * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
| * |
| * 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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| |
| /*#define DEBUG_ENABLED */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include "gstmpeg2subt.h" |
| #include <string.h> |
| |
| static void gst_mpeg2subt_class_init (GstMpeg2SubtClass * klass); |
| static void gst_mpeg2subt_base_init (GstMpeg2SubtClass * klass); |
| static void gst_mpeg2subt_init (GstMpeg2Subt * mpeg2subt); |
| static void gst_mpeg2subt_loop (GstElement * element); |
| |
| static GstCaps *gst_mpeg2subt_getcaps_video (GstPad * pad); |
| static GstPadLinkReturn gst_mpeg2subt_link_video (GstPad * pad, |
| const GstCaps * caps); |
| static void gst_mpeg2subt_handle_video (GstMpeg2Subt * mpeg2subt, |
| GstData * _data); |
| static gboolean gst_mpeg2subt_src_event (GstPad * pad, GstEvent * event); |
| static void gst_mpeg2subt_handle_subtitle (GstMpeg2Subt * mpeg2subt, |
| GstData * _data); |
| |
| static void gst_mpeg2subt_merge_title (GstMpeg2Subt * mpeg2subt, |
| GstBuffer * buf); |
| static void gst_mpeg2subt_handle_dvd_event (GstMpeg2Subt * mpeg2subt, |
| GstEvent * event, gboolean from_sub_pad); |
| static void gst_mpeg2subt_finalize (GObject * gobject); |
| static void gst_mpeg2subt_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_mpeg2subt_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_setup_palette (GstMpeg2Subt * mpeg2subt, guchar * indexes, |
| guchar * alpha); |
| static void gst_update_still_frame (GstMpeg2Subt * mpeg2subt); |
| |
| /* elementfactory information */ |
| static const GstElementDetails mpeg2subt_details = |
| GST_ELEMENT_DETAILS ("MPEG-2 subtitle decoder", |
| "Codec/Decoder/Video", |
| "Decodes and merges MPEG2 subtitles into a video frame", |
| "Wim Taymans <wim.taymans@chello.be>\n" |
| "Jan Schmidt <thaytan@mad.scientist.com>"); |
| |
| static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " /* YV12 later */ |
| "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") |
| ); |
| |
| static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) { I420 }, " /* YV12 later */ |
| "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ]") |
| ); |
| |
| static GstStaticPadTemplate subtitle_template = |
| GST_STATIC_PAD_TEMPLATE ("subtitle", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/x-dvd-subpicture") |
| ); |
| |
| GST_DEBUG_CATEGORY_STATIC (mpeg2subt_debug); |
| #define GST_CAT_DEFAULT (mpeg2subt_debug) |
| |
| /* GstMpeg2Subt signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0, |
| ARG_SKIP |
| /* FILL ME */ |
| }; |
| |
| 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 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 clip_left; |
| gint clip_right; |
| |
| guchar *target_Y; |
| guchar *target_U; |
| guchar *target_V; |
| guchar *target_A; |
| |
| guchar next; |
| } |
| RLE_state; |
| |
| static GstElementClass *parent_class = NULL; |
| |
| /*static guint gst_mpeg2subt_signals[LAST_SIGNAL] = { 0 };*/ |
| |
| GType |
| gst_mpeg2subt_get_type (void) |
| { |
| static GType mpeg2subt_type = 0; |
| |
| if (!mpeg2subt_type) { |
| static const GTypeInfo mpeg2subt_info = { |
| sizeof (GstMpeg2SubtClass), |
| (GBaseInitFunc) gst_mpeg2subt_base_init, |
| NULL, |
| (GClassInitFunc) gst_mpeg2subt_class_init, |
| NULL, |
| NULL, |
| sizeof (GstMpeg2Subt), |
| 0, |
| (GInstanceInitFunc) gst_mpeg2subt_init, |
| }; |
| |
| mpeg2subt_type = |
| g_type_register_static (GST_TYPE_ELEMENT, "GstMpeg2Subt", |
| &mpeg2subt_info, 0); |
| |
| GST_DEBUG_CATEGORY_INIT (mpeg2subt_debug, "mpeg2subt", 0, |
| "MPEG2 subtitle overlay element"); |
| } |
| |
| return mpeg2subt_type; |
| } |
| |
| static void |
| gst_mpeg2subt_base_init (GstMpeg2SubtClass * klass) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&src_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&video_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&subtitle_template)); |
| |
| gst_element_class_set_details (element_class, &mpeg2subt_details); |
| } |
| |
| static void |
| gst_mpeg2subt_class_init (GstMpeg2SubtClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SKIP, g_param_spec_int ("skip", "skip", "skip", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE)); /* CHECKME */ |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->set_property = gst_mpeg2subt_set_property; |
| gobject_class->get_property = gst_mpeg2subt_get_property; |
| gobject_class->finalize = gst_mpeg2subt_finalize; |
| } |
| |
| static void |
| gst_mpeg2subt_init (GstMpeg2Subt * mpeg2subt) |
| { |
| mpeg2subt->videopad = |
| gst_pad_new_from_template (gst_static_pad_template_get |
| (&video_template), "video"); |
| gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->videopad); |
| gst_pad_set_link_function (mpeg2subt->videopad, |
| GST_DEBUG_FUNCPTR (gst_mpeg2subt_link_video)); |
| gst_pad_set_getcaps_function (mpeg2subt->videopad, |
| GST_DEBUG_FUNCPTR (gst_mpeg2subt_getcaps_video)); |
| |
| mpeg2subt->subtitlepad = |
| gst_pad_new_from_template (gst_static_pad_template_get |
| (&subtitle_template), "subtitle"); |
| gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->subtitlepad); |
| |
| mpeg2subt->srcpad = |
| gst_pad_new_from_template (gst_static_pad_template_get |
| (&src_template), "src"); |
| gst_element_add_pad (GST_ELEMENT (mpeg2subt), mpeg2subt->srcpad); |
| gst_pad_set_getcaps_function (mpeg2subt->srcpad, |
| GST_DEBUG_FUNCPTR (gst_mpeg2subt_getcaps_video)); |
| gst_pad_set_link_function (mpeg2subt->srcpad, |
| GST_DEBUG_FUNCPTR (gst_mpeg2subt_link_video)); |
| gst_pad_set_event_function (mpeg2subt->srcpad, |
| GST_DEBUG_FUNCPTR (gst_mpeg2subt_src_event)); |
| |
| gst_element_set_loop_function (GST_ELEMENT (mpeg2subt), gst_mpeg2subt_loop); |
| GST_OBJECT_FLAG_SET (GST_ELEMENT (mpeg2subt), GST_ELEMENT_EVENT_AWARE); |
| |
| mpeg2subt->partialbuf = NULL; |
| mpeg2subt->hold_frame = NULL; |
| mpeg2subt->still_frame = NULL; |
| mpeg2subt->have_title = FALSE; |
| mpeg2subt->start_display_time = GST_CLOCK_TIME_NONE; |
| mpeg2subt->end_display_time = GST_CLOCK_TIME_NONE; |
| mpeg2subt->forced_display = FALSE; |
| |
| memcpy (mpeg2subt->current_clut, default_clut, sizeof (guint32) * 16); |
| gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, mpeg2subt->menu_alpha); |
| gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, |
| mpeg2subt->subtitle_alpha); |
| |
| memset (mpeg2subt->out_buffers, 0, sizeof (mpeg2subt->out_buffers)); |
| mpeg2subt->pending_video_buffer = NULL; |
| mpeg2subt->next_video_time = GST_CLOCK_TIME_NONE; |
| mpeg2subt->pending_subtitle_buffer = NULL; |
| mpeg2subt->next_subtitle_time = GST_CLOCK_TIME_NONE; |
| } |
| |
| static void |
| gst_mpeg2subt_finalize (GObject * gobject) |
| { |
| GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gobject); |
| gint i; |
| |
| for (i = 0; i < 3; i++) { |
| if (mpeg2subt->out_buffers[i]) |
| g_free (mpeg2subt->out_buffers[i]); |
| } |
| |
| if (mpeg2subt->partialbuf) |
| gst_buffer_unref (mpeg2subt->partialbuf); |
| } |
| |
| static GstCaps * |
| gst_mpeg2subt_getcaps_video (GstPad * pad) |
| { |
| GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); |
| GstPad *otherpad; |
| |
| otherpad = |
| (pad == mpeg2subt->srcpad) ? mpeg2subt->videopad : mpeg2subt->srcpad; |
| |
| return gst_pad_get_allowed_caps (otherpad); |
| } |
| |
| static GstPadLinkReturn |
| gst_mpeg2subt_link_video (GstPad * pad, const GstCaps * caps) |
| { |
| GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); |
| GstPad *otherpad; |
| GstPadLinkReturn ret; |
| GstStructure *structure; |
| gint width, height; |
| gint i; |
| |
| otherpad = |
| (pad == mpeg2subt->srcpad) ? mpeg2subt->videopad : mpeg2subt->srcpad; |
| |
| ret = gst_pad_try_set_caps (otherpad, caps); |
| if (GST_PAD_LINK_FAILED (ret)) { |
| return ret; |
| } |
| |
| structure = gst_caps_get_structure (caps, 0); |
| |
| if (!gst_structure_get_int (structure, "width", &width) || |
| !gst_structure_get_int (structure, "height", &height)) { |
| return GST_PAD_LINK_REFUSED; |
| } |
| |
| mpeg2subt->in_width = width; |
| mpeg2subt->in_height = height; |
| |
| /* Allocate compositing buffers */ |
| for (i = 0; i < 3; i++) { |
| if (mpeg2subt->out_buffers[i]) |
| g_free (mpeg2subt->out_buffers[i]); |
| mpeg2subt->out_buffers[i] = g_malloc (sizeof (guint16) * width); |
| } |
| |
| return GST_PAD_LINK_OK; |
| } |
| |
| static void |
| gst_mpeg2subt_handle_video (GstMpeg2Subt * mpeg2subt, GstData * _data) |
| { |
| if (GST_IS_BUFFER (_data)) { |
| GstBuffer *buf = GST_BUFFER (_data); |
| guchar *data; |
| glong size; |
| |
| data = GST_BUFFER_DATA (buf); |
| size = GST_BUFFER_SIZE (buf); |
| |
| if (mpeg2subt->still_frame) { |
| gst_buffer_unref (mpeg2subt->still_frame); |
| mpeg2subt->still_frame = NULL; |
| } |
| |
| if (!mpeg2subt->hold_frame) { |
| mpeg2subt->hold_frame = buf; |
| } else { |
| GstBuffer *out_buf; |
| |
| out_buf = mpeg2subt->hold_frame; |
| mpeg2subt->hold_frame = buf; |
| |
| if (mpeg2subt->have_title) { |
| if ((mpeg2subt->forced_display && (mpeg2subt->current_button != 0)) |
| || |
| ((mpeg2subt->start_display_time <= GST_BUFFER_TIMESTAMP (out_buf)) |
| && (mpeg2subt->end_display_time >= |
| GST_BUFFER_TIMESTAMP (out_buf)))) { |
| out_buf = gst_buffer_copy_on_write (out_buf); |
| gst_mpeg2subt_merge_title (mpeg2subt, out_buf); |
| } |
| } |
| |
| gst_pad_push (mpeg2subt->srcpad, GST_DATA (out_buf)); |
| } |
| } else if (GST_IS_EVENT (_data)) { |
| switch (GST_EVENT_TYPE (GST_EVENT (_data))) { |
| case GST_EVENT_ANY: |
| gst_mpeg2subt_handle_dvd_event (mpeg2subt, GST_EVENT (_data), FALSE); |
| gst_data_unref (_data); |
| break; |
| case GST_EVENT_DISCONTINUOUS: |
| /* Turn off forced highlight display */ |
| mpeg2subt->forced_display = 0; |
| if (mpeg2subt->still_frame) { |
| gst_buffer_unref (mpeg2subt->still_frame); |
| mpeg2subt->still_frame = NULL; |
| } |
| if (mpeg2subt->hold_frame) { |
| gst_buffer_unref (mpeg2subt->hold_frame); |
| mpeg2subt->hold_frame = NULL; |
| } |
| gst_pad_push (mpeg2subt->srcpad, _data); |
| break; |
| default: |
| gst_pad_push (mpeg2subt->srcpad, _data); |
| break; |
| } |
| } else |
| gst_data_unref (_data); |
| } |
| |
| static gboolean |
| gst_mpeg2subt_src_event (GstPad * pad, GstEvent * event) |
| { |
| GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (gst_pad_get_parent (pad)); |
| |
| return gst_pad_send_event (GST_PAD_PEER (mpeg2subt->videopad), event); |
| } |
| |
| static void |
| gst_mpeg2subt_parse_header (GstMpeg2Subt * mpeg2subt) |
| { |
| #define PARSE_BYTES_NEEDED(x) if ((buf+(x)) >= end) \ |
| { GST_WARNING("Subtitle stream broken parsing %d", *buf); \ |
| broken = TRUE; break; } |
| |
| guchar *buf; |
| guchar *start = GST_BUFFER_DATA (mpeg2subt->partialbuf); |
| guchar *end; |
| gboolean broken = FALSE; |
| gboolean last_seq = FALSE; |
| guchar *next_seq = NULL; |
| guint event_time; |
| |
| mpeg2subt->forced_display = FALSE; |
| g_return_if_fail (mpeg2subt->packet_size >= 4); |
| |
| buf = start + mpeg2subt->data_size; |
| end = buf + mpeg2subt->packet_size; |
| event_time = GUINT16_FROM_BE (*(guint16 *) (buf)); |
| next_seq = start + GUINT16_FROM_BE (*(guint16 *) (buf + 2)); |
| /* If the next control sequence is at the current offset, this is |
| * the last one */ |
| last_seq = (next_seq == buf); |
| buf += 4; |
| |
| while ((buf < end) && (!broken)) { |
| switch (*buf) { |
| case SPU_FORCE_DISPLAY: /* Forced display menu subtitle */ |
| mpeg2subt->forced_display = TRUE; |
| buf++; |
| break; |
| case SPU_SHOW: /* Show the subtitle in this packet */ |
| mpeg2subt->start_display_time = |
| GST_BUFFER_TIMESTAMP (mpeg2subt->partialbuf) + |
| ((GST_SECOND * event_time) / 90); |
| GST_DEBUG ("Subtitle starts at %" G_GUINT64_FORMAT, |
| mpeg2subt->end_display_time); |
| buf++; |
| break; |
| case SPU_HIDE: /* 02 ff (ff) is the end of the packet, hide the */ |
| mpeg2subt->end_display_time = |
| GST_BUFFER_TIMESTAMP (mpeg2subt->partialbuf) + |
| ((GST_SECOND * event_time) / 90); |
| GST_DEBUG ("Subtitle ends at %" G_GUINT64_FORMAT, |
| mpeg2subt->end_display_time); |
| buf++; |
| break; |
| case SPU_SET_PALETTE: /* palette */ |
| PARSE_BYTES_NEEDED (3); |
| |
| mpeg2subt->subtitle_index[3] = buf[1] >> 4; |
| mpeg2subt->subtitle_index[2] = buf[1] & 0xf; |
| mpeg2subt->subtitle_index[1] = buf[2] >> 4; |
| mpeg2subt->subtitle_index[0] = buf[2] & 0xf; |
| buf += 3; |
| break; |
| case SPU_SET_ALPHA: /* transparency palette */ |
| PARSE_BYTES_NEEDED (3); |
| |
| mpeg2subt->subtitle_alpha[3] = buf[1] >> 4; |
| mpeg2subt->subtitle_alpha[2] = buf[1] & 0xf; |
| mpeg2subt->subtitle_alpha[1] = buf[2] >> 4; |
| mpeg2subt->subtitle_alpha[0] = buf[2] & 0xf; |
| buf += 3; |
| break; |
| case SPU_SET_SIZE: /* image coordinates */ |
| PARSE_BYTES_NEEDED (7); |
| |
| mpeg2subt->left = |
| CLAMP ((((unsigned int) buf[1]) << 4) | (buf[2] >> 4), 0, |
| (mpeg2subt->in_width - 1)); |
| mpeg2subt->top = |
| CLAMP ((((unsigned int) buf[4]) << 4) | (buf[5] >> 4), 0, |
| (mpeg2subt->in_height - 1)); |
| mpeg2subt->right = |
| CLAMP ((((buf[2] & 0x0f) << 8) | buf[3]), 0, |
| (mpeg2subt->in_width - 1)); |
| mpeg2subt->bottom = |
| CLAMP ((((buf[5] & 0x0f) << 8) | buf[6]), 0, |
| (mpeg2subt->in_height - 1)); |
| |
| GST_DEBUG ("left %d, top %d, right %d, bottom %d", mpeg2subt->left, |
| mpeg2subt->top, mpeg2subt->right, mpeg2subt->bottom); |
| buf += 7; |
| break; |
| case SPU_SET_OFFSETS: /* image 1 / image 2 offsets */ |
| PARSE_BYTES_NEEDED (5); |
| mpeg2subt->offset[0] = (((unsigned int) buf[1]) << 8) | buf[2]; |
| mpeg2subt->offset[1] = (((unsigned int) buf[3]) << 8) | buf[4]; |
| GST_DEBUG ("Offset1 %d, Offset2 %d", mpeg2subt->offset[0], |
| mpeg2subt->offset[1]); |
| buf += 5; |
| break; |
| case SPU_WIPE: |
| { |
| guint length; |
| |
| GST_WARNING ("SPU_WIPE not yet implemented"); |
| PARSE_BYTES_NEEDED (3); |
| |
| length = (buf[1] << 8) | (buf[2]); |
| buf += 1 + length; |
| } |
| break; |
| case SPU_END: |
| buf = (last_seq) ? end : next_seq; |
| |
| /* Start a new control sequence */ |
| if (buf + 4 < end) { |
| event_time = GUINT16_FROM_BE (*(guint16 *) (buf)); |
| next_seq = start + GUINT16_FROM_BE (*(guint16 *) (buf + 2)); |
| last_seq = (next_seq == buf); |
| } |
| buf += 4; |
| break; |
| default: |
| GST_ERROR |
| ("Invalid sequence in subtitle packet header (%.2x). Skipping", |
| *buf); |
| broken = TRUE; |
| break; |
| } |
| } |
| |
| if (!mpeg2subt->forced_display) |
| gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, |
| mpeg2subt->subtitle_alpha); |
| } |
| |
| 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 palette_cache */ |
| static void |
| gst_setup_palette (GstMpeg2Subt * mpeg2subt, guchar * indexes, guchar * alpha) |
| { |
| gint i; |
| YUVA_val *target = mpeg2subt->palette_cache; |
| |
| for (i = 0; i < 4; i++, target++) { |
| guint32 col = mpeg2subt->current_clut[indexes[i]]; |
| |
| target->Y = (guint16) ((col >> 16) & 0xff) * alpha[i]; |
| target->U = (guint16) ((col >> 8) & 0xff) * alpha[i]; |
| target->V = (guint16) (col & 0xff) * alpha[i]; |
| target->A = alpha[i]; |
| } |
| } |
| |
| 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; |
| } |
| |
| /* |
| * This function steps over each run-length segment, drawing |
| * into the YUVA buffers as it goes. UV are composited and then output |
| * at half width/height |
| */ |
| static void |
| gst_draw_rle_line (GstMpeg2Subt * mpeg2subt, guchar * buffer, RLE_state * state) |
| { |
| gint length, colourid; |
| gint right = mpeg2subt->right + 1; |
| YUVA_val *colour_entry; |
| guint code; |
| gint x; |
| gboolean in_clip = FALSE; |
| guchar *target_Y; |
| guint16 *target_U; |
| guint16 *target_V; |
| guint16 *target_A; |
| |
| target_Y = state->target_Y; |
| target_U = mpeg2subt->out_buffers[0]; |
| target_V = mpeg2subt->out_buffers[1]; |
| target_A = mpeg2subt->out_buffers[2]; |
| x = mpeg2subt->left; |
| while (x < right) { |
| code = gst_get_rle_code (buffer, state); |
| length = code >> 2; |
| colourid = code & 3; |
| colour_entry = mpeg2subt->palette_cache + colourid; |
| |
| /* Length = 0 implies fill to the end of the line */ |
| if (length == 0) |
| length = right - x; |
| else { |
| /* Restrict the colour run to the end of the line */ |
| length = length < (right - x) ? length : (right - x); |
| } |
| |
| /* Check if this run of colour crosses into the clip region */ |
| in_clip = (((x + length) >= state->clip_left) && (x <= state->clip_right)); |
| |
| /* Draw YA onto the frame via target_Y, UVA into the composite buffers */ |
| if ((in_clip) && (colour_entry->A)) { |
| guint16 inv_alpha = 0xf - colour_entry->A; |
| gint i; |
| |
| for (i = 0; i < length; i++) { |
| *target_Y = ((inv_alpha * (*target_Y)) + colour_entry->Y) / 0xf; |
| *target_U += colour_entry->U; |
| *target_V += colour_entry->V; |
| *target_A += colour_entry->A; |
| target_Y++; |
| target_U++; |
| target_V++; |
| target_A++; |
| } |
| } else { |
| target_Y += length; |
| target_U += length; |
| target_V += length; |
| target_A += length; |
| } |
| x += length; |
| } |
| } |
| |
| inline void |
| gst_merge_uv_data (GstMpeg2Subt * mpeg2subt, guchar * buffer, RLE_state * state) |
| { |
| gint x; |
| guchar *target_V; |
| guchar *target_U; |
| gint width = mpeg2subt->right - mpeg2subt->left + 1; |
| |
| guint16 *comp_U; |
| guint16 *comp_V; |
| guint16 *comp_A; |
| |
| /* The compositing buffers should contain the results of accumulating 2 scanlines of |
| * U, V (premultiplied) and A data. Merge them back into their output buffers at |
| * half width/height. |
| */ |
| target_U = state->target_U; |
| target_V = state->target_V; |
| comp_U = mpeg2subt->out_buffers[0]; |
| comp_V = mpeg2subt->out_buffers[1]; |
| comp_A = mpeg2subt->out_buffers[2]; |
| |
| for (x = 0; x < width; x += 2) { |
| guint16 temp1, temp2; |
| |
| /* Average out the alpha accumulated to compute transparency */ |
| guint16 alpha = (comp_A[0] + comp_A[1]); |
| |
| if (alpha > 0) { |
| temp1 = (*target_U) * ((4 * 0xf) - alpha) + comp_U[0] + comp_U[1]; |
| temp2 = (*target_V) * ((4 * 0xf) - alpha) + comp_V[0] + comp_V[1]; |
| *target_U = temp1 / (4 * 0xf); |
| *target_V = temp2 / (4 * 0xf); |
| }; |
| comp_U += 2; |
| comp_V += 2; |
| comp_A += 2; |
| target_U++; |
| target_V++; |
| } |
| } |
| |
| /* |
| * Decode the RLE subtitle image and blend with the current |
| * frame buffer. |
| */ |
| static void |
| gst_mpeg2subt_merge_title (GstMpeg2Subt * mpeg2subt, GstBuffer * buf) |
| { |
| gint y; |
| gint width = mpeg2subt->right - mpeg2subt->left + 1; |
| gint Y_stride; |
| gint UV_stride; |
| |
| guchar *buffer = GST_BUFFER_DATA (mpeg2subt->partialbuf); |
| gint last_y; |
| gint first_y; |
| RLE_state state; |
| |
| /* Set up the initial offsets, remembering the half-res size for UV in I420 packing |
| * see http://www.fourcc.org for details |
| */ |
| Y_stride = mpeg2subt->in_width; |
| UV_stride = (mpeg2subt->in_width + 1) / 2; |
| |
| GST_DEBUG ("Merging subtitle on frame at time %" G_GUINT64_FORMAT |
| " using %s colour table", GST_BUFFER_TIMESTAMP (buf), |
| mpeg2subt->forced_display ? "menu" : "subtitle"); |
| |
| state.id = 0; |
| state.aligned = 1; |
| state.offset[0] = mpeg2subt->offset[0]; |
| state.offset[1] = mpeg2subt->offset[1]; |
| |
| /* skip over lines until we hit the clip region */ |
| if (mpeg2subt->forced_display) { |
| state.clip_right = mpeg2subt->clip_right; |
| state.clip_left = mpeg2subt->clip_left; |
| last_y = mpeg2subt->clip_bottom; |
| first_y = mpeg2subt->clip_top; |
| } else { |
| state.clip_right = mpeg2subt->right; |
| state.clip_left = mpeg2subt->left; |
| last_y = mpeg2subt->bottom; |
| first_y = mpeg2subt->top; |
| } |
| |
| for (y = mpeg2subt->top; y < first_y; y++) { |
| /* Skip a line of RLE data */ |
| gint length; |
| guint code; |
| gint x = 0; |
| |
| while (x < width) { |
| code = gst_get_rle_code (buffer, &state); |
| length = code >> 2; |
| |
| /* Length = 0 implies fill to the end of the line so we're done */ |
| if (length == 0) |
| break; |
| |
| x += length; |
| } |
| if (!state.aligned) |
| gst_get_nibble (buffer, &state); |
| state.id = !state.id; |
| } |
| |
| state.target_Y = GST_BUFFER_DATA (buf) + mpeg2subt->left + (y * Y_stride); |
| state.target_V = GST_BUFFER_DATA (buf) + (Y_stride * mpeg2subt->in_height) |
| + ((mpeg2subt->left) / 2) + ((y / 2) * UV_stride); |
| state.target_U = |
| state.target_V + UV_stride * ((mpeg2subt->in_height + 1) / 2); |
| |
| memset (mpeg2subt->out_buffers[0], 0, sizeof (guint16) * Y_stride); |
| memset (mpeg2subt->out_buffers[1], 0, sizeof (guint16) * Y_stride); |
| memset (mpeg2subt->out_buffers[2], 0, sizeof (guint16) * Y_stride); |
| |
| /* Now draw scanlines until we hit last_y or end of RLE data */ |
| for (; ((state.offset[1] < mpeg2subt->data_size + 2) && (y <= last_y)); y++) { |
| gst_draw_rle_line (mpeg2subt, buffer, &state); |
| if (state.id) { |
| gst_merge_uv_data (mpeg2subt, buffer, &state); |
| |
| /* Clear the compositing buffers */ |
| memset (mpeg2subt->out_buffers[0], 0, sizeof (guint16) * Y_stride); |
| memset (mpeg2subt->out_buffers[1], 0, sizeof (guint16) * Y_stride); |
| memset (mpeg2subt->out_buffers[2], 0, sizeof (guint16) * Y_stride); |
| |
| state.target_U += UV_stride; |
| state.target_V += UV_stride; |
| } |
| state.target_Y += 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_update_still_frame (GstMpeg2Subt * mpeg2subt) |
| { |
| GstBuffer *out_buf; |
| |
| if ((mpeg2subt->still_frame) && |
| (mpeg2subt->have_title) && |
| ((mpeg2subt->forced_display && (mpeg2subt->current_button != 0)))) { |
| gst_buffer_ref (mpeg2subt->still_frame); |
| out_buf = gst_buffer_copy_on_write (mpeg2subt->still_frame); |
| gst_mpeg2subt_merge_title (mpeg2subt, out_buf); |
| gst_pad_push (mpeg2subt->srcpad, GST_DATA (out_buf)); |
| } |
| } |
| |
| static void |
| gst_mpeg2subt_handle_subtitle (GstMpeg2Subt * mpeg2subt, GstData * _data) |
| { |
| g_return_if_fail (_data != NULL); |
| |
| if (GST_IS_BUFFER (_data)) { |
| GstBuffer *buf = GST_BUFFER (_data); |
| guchar *data; |
| glong size = 0; |
| |
| if (mpeg2subt->have_title) { |
| gst_buffer_unref (mpeg2subt->partialbuf); |
| mpeg2subt->partialbuf = NULL; |
| mpeg2subt->have_title = FALSE; |
| } |
| |
| GST_DEBUG ("Got subtitle buffer, pts %" G_GUINT64_FORMAT, |
| GST_BUFFER_TIMESTAMP (buf)); |
| |
| /* deal with partial frame from previous buffer */ |
| if (mpeg2subt->partialbuf) { |
| GstBuffer *merge; |
| |
| merge = gst_buffer_merge (mpeg2subt->partialbuf, buf); |
| gst_buffer_unref (mpeg2subt->partialbuf); |
| gst_buffer_unref (buf); |
| mpeg2subt->partialbuf = merge; |
| } else { |
| mpeg2subt->partialbuf = buf; |
| } |
| |
| data = GST_BUFFER_DATA (mpeg2subt->partialbuf); |
| size = GST_BUFFER_SIZE (mpeg2subt->partialbuf); |
| |
| if (size > 4) { |
| mpeg2subt->packet_size = GST_READ_UINT16_BE (data); |
| |
| if (mpeg2subt->packet_size == size) { |
| GST_LOG ("Subtitle packet size %d, current size %ld", |
| mpeg2subt->packet_size, size); |
| |
| mpeg2subt->data_size = GST_READ_UINT16_BE (data + 2); |
| mpeg2subt->have_title = TRUE; |
| |
| gst_mpeg2subt_parse_header (mpeg2subt); |
| } |
| } |
| } else if (GST_IS_EVENT (_data)) { |
| switch (GST_EVENT_TYPE (GST_EVENT (_data))) { |
| case GST_EVENT_ANY: |
| GST_LOG ("DVD event on subtitle pad with timestamp %llu", |
| GST_EVENT_TIMESTAMP (GST_EVENT (_data))); |
| gst_mpeg2subt_handle_dvd_event (mpeg2subt, GST_EVENT (_data), TRUE); |
| break; |
| case GST_EVENT_EMPTY: |
| if (GST_CLOCK_TIME_IS_VALID (mpeg2subt->next_video_time) && |
| (mpeg2subt->next_video_time > 0)) { |
| mpeg2subt->next_subtitle_time = mpeg2subt->next_video_time + 1; |
| GST_LOG ("Forwarding subtitle time to %llu", |
| mpeg2subt->next_subtitle_time); |
| } |
| gst_update_still_frame (mpeg2subt); |
| break; |
| default: |
| GST_LOG ("Got event of type %d on subtitle pad", |
| GST_EVENT_TYPE (GST_EVENT (_data))); |
| break; |
| } |
| gst_data_unref (_data); |
| } else |
| gst_data_unref (_data); |
| } |
| |
| static void |
| gst_mpeg2subt_handle_dvd_event (GstMpeg2Subt * mpeg2subt, GstEvent * event, |
| gboolean from_sub_pad) |
| { |
| GstStructure *structure; |
| const gchar *event_type; |
| |
| structure = event->event_data.structure.structure; |
| |
| event_type = gst_structure_get_string (structure, "event"); |
| g_return_if_fail (event_type != NULL); |
| |
| if (from_sub_pad && !strcmp (event_type, "dvd-spu-highlight")) { |
| 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 ("Invalid dvd-spu-highlight event received"); |
| return; |
| } |
| mpeg2subt->current_button = button; |
| mpeg2subt->clip_left = sx; |
| mpeg2subt->clip_top = sy; |
| mpeg2subt->clip_right = ex; |
| mpeg2subt->clip_bottom = ey; |
| for (i = 0; i < 4; i++) { |
| mpeg2subt->menu_alpha[i] = ((guint32) (palette) >> (i * 4)) & 0x0f; |
| mpeg2subt->menu_index[i] = ((guint32) (palette) >> (16 + (i * 4))) & 0x0f; |
| } |
| |
| GST_DEBUG ("New button activated clip=(%d,%d) to (%d,%d) palette 0x%x", sx, |
| sy, ex, ey, palette); |
| gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, mpeg2subt->menu_alpha); |
| |
| gst_update_still_frame (mpeg2subt); |
| } else if (from_sub_pad && !strcmp (event_type, "dvd-spu-clut-change")) { |
| /* Take a copy of the colour table */ |
| guchar name[16]; |
| int i; |
| gint value; |
| |
| GST_LOG ("New colour table recieved"); |
| for (i = 0; i < 16; i++) { |
| sprintf (name, "clut%02d", i); |
| if (!gst_structure_get_int (structure, name, &value)) { |
| GST_ERROR ("dvd-spu-clut-change event did not contain %s field", name); |
| break; |
| } |
| mpeg2subt->current_clut[i] = (guint32) (value); |
| } |
| |
| if (mpeg2subt->forced_display) |
| gst_setup_palette (mpeg2subt, mpeg2subt->menu_index, |
| mpeg2subt->menu_alpha); |
| else |
| gst_setup_palette (mpeg2subt, mpeg2subt->subtitle_index, |
| mpeg2subt->subtitle_alpha); |
| |
| gst_update_still_frame (mpeg2subt); |
| } else if ((from_sub_pad && !strcmp (event_type, "dvd-spu-stream-change")) |
| || (from_sub_pad && !strcmp (event_type, "dvd-spu-reset-highlight"))) { |
| /* Turn off forced highlight display */ |
| mpeg2subt->current_button = 0; |
| mpeg2subt->clip_left = mpeg2subt->left; |
| mpeg2subt->clip_top = mpeg2subt->top; |
| mpeg2subt->clip_right = mpeg2subt->right; |
| mpeg2subt->clip_bottom = mpeg2subt->bottom; |
| GST_LOG ("Clearing button state"); |
| gst_update_still_frame (mpeg2subt); |
| } else if (!from_sub_pad && !strcmp (event_type, "dvd-spu-still-frame")) { |
| /* Handle a still frame */ |
| GST_LOG ("Received still frame notification"); |
| if (mpeg2subt->still_frame) |
| gst_buffer_unref (mpeg2subt->still_frame); |
| mpeg2subt->still_frame = mpeg2subt->hold_frame; |
| mpeg2subt->hold_frame = NULL; |
| gst_update_still_frame (mpeg2subt); |
| } else { |
| /* Ignore all other unknown events */ |
| GST_LOG ("Ignoring DVD event %s from %s pad", event_type, |
| from_sub_pad ? "sub" : "video"); |
| } |
| } |
| |
| static void |
| gst_mpeg2subt_loop (GstElement * element) |
| { |
| GstMpeg2Subt *mpeg2subt = GST_MPEG2SUBT (element); |
| GstData *data; |
| GstClockTime timestamp = 0; |
| |
| /* Process any pending video buffer */ |
| if (mpeg2subt->pending_video_buffer) { |
| gst_mpeg2subt_handle_video (mpeg2subt, mpeg2subt->pending_video_buffer); |
| mpeg2subt->pending_video_buffer = NULL; |
| } |
| data = mpeg2subt->pending_video_buffer = gst_pad_pull (mpeg2subt->videopad); |
| if (!data) |
| return; |
| |
| if (GST_IS_BUFFER (data)) { |
| timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER (data)); |
| } else if (GST_IS_EVENT (data)) { |
| timestamp = GST_EVENT_TIMESTAMP (GST_EVENT (data)); |
| } else { |
| GST_WARNING ("Got GstData of unknown type %d", GST_DATA_TYPE (data)); |
| } |
| if (timestamp && GST_CLOCK_TIME_IS_VALID (timestamp) && (timestamp > 0)) { |
| mpeg2subt->next_video_time = timestamp; |
| GST_LOG ("next_video_time = %llu, next_subtitle_time = %llu", |
| mpeg2subt->next_video_time, mpeg2subt->next_subtitle_time); |
| } |
| |
| /* Process subtitle buffers until we get one beyond 'next_video_time' */ |
| if (mpeg2subt->pending_subtitle_buffer) { |
| gst_mpeg2subt_handle_subtitle (mpeg2subt, |
| mpeg2subt->pending_subtitle_buffer); |
| mpeg2subt->pending_subtitle_buffer = NULL; |
| } |
| data = mpeg2subt->pending_subtitle_buffer = |
| gst_pad_pull (mpeg2subt->subtitlepad); |
| if (!data) { |
| return; |
| } |
| |
| if (GST_IS_BUFFER (data)) { |
| timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER (data)); |
| } else if (GST_IS_EVENT (data)) { |
| timestamp = GST_EVENT_TIMESTAMP (GST_EVENT (data)); |
| } else { |
| GST_WARNING ("Got GstData of unknown type %d", GST_DATA_TYPE (data)); |
| } |
| if (GST_CLOCK_TIME_IS_VALID (timestamp) && (timestamp > 0)) { |
| mpeg2subt->next_subtitle_time = timestamp; |
| GST_LOG ("next_subtitle_time = %llu, next_video_time = %llu", |
| mpeg2subt->next_subtitle_time, mpeg2subt->next_video_time); |
| } |
| } |
| |
| static void |
| gst_mpeg2subt_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstMpeg2Subt *src; |
| |
| g_return_if_fail (GST_IS_MPEG2SUBT (object)); |
| src = GST_MPEG2SUBT (object); |
| |
| switch (prop_id) { |
| default: |
| break; |
| } |
| } |
| |
| static void |
| gst_mpeg2subt_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstMpeg2Subt *src; |
| |
| g_return_if_fail (GST_IS_MPEG2SUBT (object)); |
| src = GST_MPEG2SUBT (object); |
| |
| switch (prop_id) { |
| default: |
| break; |
| } |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "mpeg2subt", |
| GST_RANK_NONE, GST_TYPE_MPEG2SUBT); |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| "mpeg2sub", |
| "MPEG-2 video subtitle parser", |
| plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |