/* GStreamer * Copyright (C) 2008 Vincent Penquerc'h * * 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. */ /* FIXME: shouldn't all this GstKateDecoderBase stuff really be a base class? */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_TIGER #include #endif #include #include "gstkate.h" #include "gstkateutil.h" GST_DEBUG_CATEGORY_EXTERN (gst_kateutil_debug); #define GST_CAT_DEFAULT gst_kateutil_debug static void gst_kate_util_decoder_base_free_event_queue (GstKateDecoderBase * decoder); GstCaps * gst_kate_util_set_header_on_caps (GstElement * element, GstCaps * caps, GList * headers) { GstStructure *structure; GValue array = { 0 }; GST_LOG_OBJECT (element, "caps: %" GST_PTR_FORMAT, caps); if (G_UNLIKELY (!caps)) return NULL; if (G_UNLIKELY (!headers)) return NULL; caps = gst_caps_make_writable (caps); structure = gst_caps_get_structure (caps, 0); g_value_init (&array, GST_TYPE_ARRAY); while (headers) { GValue value = { 0 }; GstBuffer *buffer = headers->data; g_assert (buffer); g_value_init (&value, GST_TYPE_BUFFER); buffer = gst_buffer_copy (buffer); GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_HEADER); gst_value_take_buffer (&value, buffer); gst_value_array_append_value (&array, &value); g_value_unset (&value); headers = headers->next; } gst_structure_take_value (structure, "streamheader", &array); GST_LOG_OBJECT (element, "here are the newly set caps: %" GST_PTR_FORMAT, caps); return caps; } void gst_kate_util_install_decoder_base_properties (GObjectClass * gobject_class) { g_object_class_install_property (gobject_class, ARG_DEC_BASE_LANGUAGE, g_param_spec_string ("language", "Language", "The language of the stream", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, ARG_DEC_BASE_CATEGORY, g_param_spec_string ("category", "Category", "The category of the stream", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, ARG_DEC_BASE_ORIGINAL_CANVAS_WIDTH, g_param_spec_int ("original-canvas-width", "Original canvas width (0 is unspecified)", "The canvas width this stream was authored for", 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, ARG_DEC_BASE_ORIGINAL_CANVAS_HEIGHT, g_param_spec_int ("original-canvas-height", "Original canvas height", "The canvas height this stream was authored for (0 is unspecified)", 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } void gst_kate_util_decode_base_init (GstKateDecoderBase * decoder, gboolean delay_events) { if (G_UNLIKELY (!decoder)) return; decoder->language = NULL; decoder->category = NULL; decoder->original_canvas_width = 0; decoder->original_canvas_height = 0; decoder->tags = NULL; decoder->tags_changed = FALSE; decoder->initialized = FALSE; decoder->delay_events = delay_events; decoder->event_queue = NULL; } static void gst_kate_util_decode_base_reset (GstKateDecoderBase * decoder) { g_free (decoder->language); decoder->language = NULL; g_free (decoder->category); decoder->category = NULL; if (decoder->tags) { gst_tag_list_unref (decoder->tags); decoder->tags = NULL; } decoder->tags_changed = FALSE; decoder->original_canvas_width = 0; decoder->original_canvas_height = 0; if (decoder->event_queue) { gst_kate_util_decoder_base_free_event_queue (decoder); } decoder->initialized = FALSE; } gboolean gst_kate_util_decoder_base_queue_event (GstKateDecoderBase * decoder, GstEvent * event, gboolean (*handler) (GstPad *, GstObject *, GstEvent *), GstObject * parent, GstPad * pad) { gboolean can_be_queued; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: case GST_EVENT_FLUSH_STOP: case GST_EVENT_EOS: can_be_queued = FALSE; break; default: can_be_queued = TRUE; break; } if (GST_EVENT_IS_STICKY (event) && GST_EVENT_TYPE (event) < GST_EVENT_CAPS) can_be_queued = FALSE; if (decoder->delay_events && can_be_queued) { GstKateDecoderBaseQueuedEvent *item; GST_DEBUG_OBJECT (decoder, "We have to delay the event"); item = g_slice_new (GstKateDecoderBaseQueuedEvent); if (item) { item->event = event; item->parent = parent; item->pad = pad; item->handler = handler; g_queue_push_tail (decoder->event_queue, item); return TRUE; } else { return FALSE; } } else { return FALSE; } } static void gst_kate_util_decoder_base_free_event_queue (GstKateDecoderBase * decoder) { while (decoder->event_queue->length) { GstKateDecoderBaseQueuedEvent *item = (GstKateDecoderBaseQueuedEvent *) g_queue_pop_head (decoder->event_queue); gst_event_unref (item->event); g_slice_free (GstKateDecoderBaseQueuedEvent, item); } g_queue_free (decoder->event_queue); decoder->event_queue = NULL; } static void gst_kate_util_decoder_base_drain_event_queue (GstKateDecoderBase * decoder) { decoder->delay_events = FALSE; if (decoder->event_queue->length == 0) return; GST_DEBUG_OBJECT (decoder, "We can now drain all events!"); while (decoder->event_queue->length) { GstKateDecoderBaseQueuedEvent *item = (GstKateDecoderBaseQueuedEvent *) g_queue_pop_head (decoder->event_queue); (*item->handler) (item->pad, item->parent, item->event); g_slice_free (GstKateDecoderBaseQueuedEvent, item); } } void gst_kate_util_decoder_base_add_tags (GstKateDecoderBase * decoder, GstTagList * tags, gboolean take_ownership_of_tags) { if (!decoder->tags) { if (!take_ownership_of_tags) tags = gst_tag_list_ref (tags); decoder->tags = tags; } else { GstTagList *old = decoder->tags; decoder->tags = gst_tag_list_merge (old, tags, GST_TAG_MERGE_REPLACE); gst_tag_list_unref (old); if (take_ownership_of_tags) gst_tag_list_unref (tags); } decoder->tags_changed = TRUE; } GstEvent * gst_kate_util_decoder_base_get_tag_event (GstKateDecoderBase * decoder) { if (!decoder->tags) return NULL; decoder->tags_changed = FALSE; return gst_event_new_tag (gst_tag_list_ref (decoder->tags)); } gboolean gst_kate_util_decoder_base_get_property (GstKateDecoderBase * decoder, GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { gboolean res = TRUE; switch (prop_id) { case ARG_DEC_BASE_LANGUAGE: g_value_set_string (value, decoder->language); break; case ARG_DEC_BASE_CATEGORY: g_value_set_string (value, decoder->category); break; case ARG_DEC_BASE_ORIGINAL_CANVAS_WIDTH: g_value_set_int (value, decoder->original_canvas_width); break; case ARG_DEC_BASE_ORIGINAL_CANVAS_HEIGHT: g_value_set_int (value, decoder->original_canvas_height); break; default: res = FALSE; break; } return res; } static inline gboolean gst_kate_util_is_utf8_string (const char *value, size_t len) { if (len == 0) return FALSE; if (memchr (value, 0, len - 1)) return FALSE; if (value[len - 1]) return FALSE; return (kate_text_validate (kate_utf8, value, len) >= 0); } GstFlowReturn gst_kate_util_decoder_base_chain_kate_packet (GstKateDecoderBase * decoder, GstElement * element, GstPad * pad, GstBuffer * buf, GstPad * srcpad, GstPad * tagpad, GstCaps ** src_caps, const kate_event ** ev) { kate_packet kp; int ret; GstFlowReturn rflow = GST_FLOW_OK; gboolean is_header; GstMapInfo info; gsize header_size; guint8 header[1]; header_size = gst_buffer_extract (buf, 0, header, 1); GST_DEBUG_OBJECT (element, "got kate packet, %zu bytes, type %02x", gst_buffer_get_size (buf), header_size == 0 ? -1 : header[0]); is_header = header_size > 0 && (header[0] & 0x80); if (!is_header && decoder->tags_changed) { /* after we've processed headers, send any tags before processing the data packet */ GST_DEBUG_OBJECT (element, "Not a header, sending tags for pad %s:%s", GST_DEBUG_PAD_NAME (tagpad)); gst_pad_push_event (tagpad, gst_kate_util_decoder_base_get_tag_event (decoder)); } if (gst_buffer_map (buf, &info, GST_MAP_READ)) { kate_packet_wrap (&kp, info.size, info.data); ret = kate_high_decode_packetin (&decoder->k, &kp, ev); gst_buffer_unmap (buf, &info); } else { GST_ELEMENT_ERROR (element, STREAM, DECODE, (NULL), ("Failed to map buffer")); return GST_FLOW_ERROR; } if (G_UNLIKELY (ret < 0)) { GST_ELEMENT_ERROR (element, STREAM, DECODE, (NULL), ("Failed to decode Kate packet: %s", gst_kate_util_get_error_message (ret))); return GST_FLOW_ERROR; } if (G_UNLIKELY (ret > 0)) { GST_DEBUG_OBJECT (element, "kate_high_decode_packetin has received EOS packet"); } /* headers may be interesting to retrieve information from */ if (G_UNLIKELY (is_header)) { switch (header[0]) { case 0x80: /* ID header */ GST_INFO_OBJECT (element, "Parsed ID header: language %s, category %s", decoder->k.ki->language, decoder->k.ki->category); if (src_caps) { if (*src_caps) { gst_caps_unref (*src_caps); *src_caps = NULL; } if (strcmp (decoder->k.ki->category, "K-SPU") == 0 || strcmp (decoder->k.ki->category, "spu-subtitles") == 0) { *src_caps = gst_caps_new_empty_simple ("subpicture/x-dvd"); } else if (decoder->k.ki->text_markup_type == kate_markup_none) { *src_caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING, "utf8", NULL); } else { *src_caps = gst_caps_new_simple ("text/x-raw", "format", G_TYPE_STRING, "pango-markup", NULL); } GST_INFO_OBJECT (srcpad, "Setting caps: %" GST_PTR_FORMAT, *src_caps); if (!gst_pad_set_caps (srcpad, *src_caps)) { GST_ERROR_OBJECT (srcpad, "Failed to set caps %" GST_PTR_FORMAT, *src_caps); } } if (decoder->k.ki->language && *decoder->k.ki->language) { GstTagList *tags = gst_tag_list_new_empty (); gchar *lang_code; /* en_GB -> en */ lang_code = g_ascii_strdown (decoder->k.ki->language, -1); g_strdelimit (lang_code, NULL, '\0'); gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_LANGUAGE_CODE, lang_code, NULL); g_free (lang_code); /* TODO: category - where should it go ? */ gst_kate_util_decoder_base_add_tags (decoder, tags, TRUE); } /* update properties */ if (decoder->language) g_free (decoder->language); decoder->language = g_strdup (decoder->k.ki->language); if (decoder->category) g_free (decoder->category); decoder->category = g_strdup (decoder->k.ki->category); decoder->original_canvas_width = decoder->k.ki->original_canvas_width; decoder->original_canvas_height = decoder->k.ki->original_canvas_height; /* we can now send away any event we've delayed, as the src pad now has caps */ gst_kate_util_decoder_base_drain_event_queue (decoder); break; case 0x81: /* Vorbis comments header */ GST_INFO_OBJECT (element, "Parsed comments header"); { gchar *encoder = NULL; GstTagList *list = gst_tag_list_from_vorbiscomment_buffer (buf, (const guint8 *) "\201kate\0\0\0\0", 9, &encoder); if (!list) { GST_ERROR_OBJECT (element, "failed to decode comment header"); list = gst_tag_list_new_empty (); } if (encoder) { gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, encoder, NULL); g_free (encoder); } gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SUBTITLE_CODEC, "Kate", NULL); gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER_VERSION, decoder->k.ki->bitstream_version_major, NULL); gst_kate_util_decoder_base_add_tags (decoder, list, TRUE); if (decoder->initialized) { gst_pad_push_event (tagpad, gst_event_new_tag (gst_tag_list_ref (decoder->tags))); } } break; default: break; } } #if ((KATE_VERSION_MAJOR<<16)|(KATE_VERSION_MINOR<<8)|KATE_VERSION_PATCH) >= 0x000400 else if (*ev && (*ev)->meta) { int count = kate_meta_query_count ((*ev)->meta); if (count > 0) { GstTagList *evtags = gst_tag_list_new_empty (); int idx; GST_DEBUG_OBJECT (decoder, "Kate event has %d attached metadata", count); for (idx = 0; idx < count; ++idx) { const char *tag, *value; size_t len; if (kate_meta_query ((*ev)->meta, idx, &tag, &value, &len) < 0) { GST_WARNING_OBJECT (decoder, "Failed to retrieve metadata %d", idx); } else { if (gst_kate_util_is_utf8_string (value, len)) { gchar *compound = g_strdup_printf ("%s=%s", tag, value); GST_DEBUG_OBJECT (decoder, "Metadata %d: %s=%s (%zu bytes)", idx, tag, value, len); gst_tag_list_add (evtags, GST_TAG_MERGE_APPEND, GST_TAG_EXTENDED_COMMENT, compound, NULL); g_free (compound); } else { GST_INFO_OBJECT (decoder, "Metadata %d, (%s, %zu bytes) is binary, ignored", idx, tag, len); } } } gst_kate_util_decoder_base_add_tags (decoder, evtags, TRUE); gst_pad_push_event (tagpad, gst_kate_util_decoder_base_get_tag_event (decoder)); } } #endif return rflow; } GstStateChangeReturn gst_kate_decoder_base_change_state (GstKateDecoderBase * decoder, GstElement * element, GstElementClass * parent_class, GstStateChange transition) { GstStateChangeReturn res; int ret; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: GST_DEBUG_OBJECT (element, "READY -> PAUSED, initializing kate state"); ret = kate_high_decode_init (&decoder->k); if (ret < 0) { GST_WARNING_OBJECT (element, "failed to initialize kate state: %s", gst_kate_util_get_error_message (ret)); } gst_segment_init (&decoder->kate_segment, GST_FORMAT_UNDEFINED); decoder->kate_flushing = FALSE; decoder->initialized = TRUE; decoder->event_queue = g_queue_new (); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; default: break; } res = parent_class->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: GST_DEBUG_OBJECT (element, "PAUSED -> READY, clearing kate state"); if (decoder->initialized) { kate_high_decode_clear (&decoder->k); decoder->initialized = FALSE; } gst_segment_init (&decoder->kate_segment, GST_FORMAT_UNDEFINED); decoder->kate_flushing = TRUE; gst_kate_util_decode_base_reset (decoder); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_kate_util_decode_base_reset (decoder); break; default: break; } return res; } void gst_kate_util_decoder_base_set_flushing (GstKateDecoderBase * decoder, gboolean flushing) { decoder->kate_flushing = flushing; gst_segment_init (&decoder->kate_segment, GST_FORMAT_UNDEFINED); } void gst_kate_util_decoder_base_segment_event (GstKateDecoderBase * decoder, GstEvent * event) { GstSegment seg; gst_event_copy_segment (event, &seg); GST_DEBUG_OBJECT (decoder, "kate pad segment: %" GST_SEGMENT_FORMAT, &seg); decoder->kate_segment = seg; } gboolean gst_kate_util_decoder_base_update_segment (GstKateDecoderBase * decoder, GstElement * element, GstBuffer * buf) { guint64 clip_start = 0, clip_stop = 0; gboolean in_seg; if (decoder->kate_flushing) { GST_LOG_OBJECT (element, "Kate pad flushing, buffer ignored"); return FALSE; } if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buf))) { GstClockTime stop; if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buf))) stop = GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf); else stop = GST_CLOCK_TIME_NONE; in_seg = gst_segment_clip (&decoder->kate_segment, GST_FORMAT_TIME, GST_BUFFER_TIMESTAMP (buf), stop, &clip_start, &clip_stop); } else { in_seg = TRUE; } if (in_seg) { if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { decoder->kate_segment.position = clip_start; } } else { GST_INFO_OBJECT (element, "Kate buffer not in segment, ignored"); } return in_seg; } static GstClockTime gst_kate_util_granule_time (kate_state * k, gint64 granulepos) { if (G_UNLIKELY (granulepos == -1)) return -1; return kate_granule_time (k->ki, granulepos) * GST_SECOND; } /* conversions on the sink: - default is granules at num/den rate (subject to the granule shift) - default -> time is possible - bytes do not mean anything, packets can be any number of bytes, and we have no way to know the number of bytes emitted without decoding conversions on the source: - nothing */ gboolean gst_kate_decoder_base_convert (GstKateDecoderBase * decoder, GstElement * element, GstPad * pad, GstFormat src_fmt, gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val) { gboolean res = FALSE; if (src_fmt == *dest_fmt) { *dest_val = src_val; return TRUE; } if (!decoder->initialized) { GST_WARNING_OBJECT (element, "not initialized yet"); return FALSE; } if (src_fmt == GST_FORMAT_BYTES || *dest_fmt == GST_FORMAT_BYTES) { GST_WARNING_OBJECT (element, "unsupported format"); return FALSE; } switch (src_fmt) { case GST_FORMAT_DEFAULT: switch (*dest_fmt) { case GST_FORMAT_TIME: *dest_val = gst_kate_util_granule_time (&decoder->k, src_val); res = TRUE; break; default: res = FALSE; break; } break; default: res = FALSE; break; } if (!res) { GST_WARNING_OBJECT (element, "unsupported format"); } return res; } gboolean gst_kate_decoder_base_sink_query (GstKateDecoderBase * decoder, GstElement * element, GstPad * pad, GstObject * parent, GstQuery * query) { switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CONVERT: { GstFormat src_fmt, dest_fmt; gint64 src_val, dest_val; gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); if (!gst_kate_decoder_base_convert (decoder, element, pad, src_fmt, src_val, &dest_fmt, &dest_val)) { return gst_pad_query_default (pad, parent, query); } gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); return TRUE; } default: return gst_pad_query_default (pad, parent, query); } } const char * gst_kate_util_get_error_message (int ret) { switch (ret) { case KATE_E_NOT_FOUND: return "value not found"; case KATE_E_INVALID_PARAMETER: return "invalid parameter"; case KATE_E_OUT_OF_MEMORY: return "out of memory"; case KATE_E_BAD_GRANULE: return "bad granule"; case KATE_E_INIT: return "initialization error"; case KATE_E_BAD_PACKET: return "bad packet"; case KATE_E_TEXT: return "invalid/truncated text"; case KATE_E_LIMIT: return "a limit was exceeded"; case KATE_E_VERSION: return "unsupported bitstream version"; case KATE_E_NOT_KATE: return "not a kate bitstream"; case KATE_E_BAD_TAG: return "bad tag"; case KATE_E_IMPL: return "not implemented"; #ifdef HAVE_TIGER case TIGER_E_NOT_FOUND: return "value not found"; case TIGER_E_INVALID_PARAMETER: return "invalid parameter"; case TIGER_E_OUT_OF_MEMORY: return "out of memory"; case TIGER_E_CAIRO_ERROR: return "Cairo error"; case TIGER_E_BAD_SURFACE_TYPE: return "bad surface type"; #endif default: return "unknown error"; } }