static gboolean gst_kate_enc_source_query (GstPad * pad, GstQuery * query) { GstKateEnc *ke; gboolean res = FALSE; ke = GST_KATE_ENC (gst_pad_get_parent (pad)); GST_DEBUG ("source query %d", GST_QUERY_TYPE (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_enc_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val)) { return gst_pad_query_default (pad, query); } gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); res = TRUE; } break; default: res = gst_pad_query_default (pad, query); break; } gst_object_unref (ke); return res; }
/* chain function * this function does the actual processing */ static GstFlowReturn gst_kate_enc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) { GstKateEnc *ke = GST_KATE_ENC (parent); GstFlowReturn rflow; GST_DEBUG_OBJECT (ke, "got packet, %" G_GSIZE_FORMAT " bytes", gst_buffer_get_size (buf)); /* first push headers if we haven't done that yet */ rflow = gst_kate_enc_flush_headers (ke); if (G_LIKELY (rflow == GST_FLOW_OK)) { /* flush any packet we had waiting */ rflow = gst_kate_enc_flush_waiting (ke, GST_BUFFER_TIMESTAMP (buf)); if (G_LIKELY (rflow == GST_FLOW_OK)) { if (ke->format == GST_KATE_FORMAT_SPU) { /* encode a kate_bitmap */ rflow = gst_kate_enc_chain_spu (ke, buf); } else { /* encode text */ rflow = gst_kate_enc_chain_text (ke, buf); } } } gst_buffer_unref (buf); return rflow; }
/* chain function * this function does the actual processing */ static GstFlowReturn gst_kate_enc_chain (GstPad * pad, GstBuffer * buf) { GstKateEnc *ke = GST_KATE_ENC (gst_pad_get_parent (pad)); GstFlowReturn rflow = GST_FLOW_OK; GstCaps *caps; const gchar *mime_type = NULL; GST_DEBUG_OBJECT (ke, "got packet, %u bytes", GST_BUFFER_SIZE (buf)); /* get the type of the data we're being sent */ caps = GST_PAD_CAPS (pad); if (G_UNLIKELY (caps == NULL)) { GST_WARNING_OBJECT (ke, "No input caps set"); rflow = GST_FLOW_NOT_NEGOTIATED; } else { const GstStructure *structure = gst_caps_get_structure (caps, 0); if (structure) mime_type = gst_structure_get_name (structure); if (mime_type) { GST_LOG_OBJECT (ke, "Packet has MIME type %s", mime_type); /* first push headers if we haven't done that yet */ rflow = gst_kate_enc_flush_headers (ke); if (G_LIKELY (rflow == GST_FLOW_OK)) { /* flush any packet we had waiting */ rflow = gst_kate_enc_flush_waiting (ke, GST_BUFFER_TIMESTAMP (buf)); if (G_LIKELY (rflow == GST_FLOW_OK)) { if (!strcmp (mime_type, GST_KATE_SPU_MIME_TYPE)) { /* encode a kate_bitmap */ rflow = gst_kate_enc_chain_spu (ke, buf); } else { /* encode text */ rflow = gst_kate_enc_chain_text (ke, buf, mime_type); } } } } else { GST_WARNING_OBJECT (ke, "Packet has no MIME type, ignored"); } } gst_buffer_unref (buf); gst_object_unref (ke); GST_LOG_OBJECT (ke, "Leaving chain function"); return rflow; }
static void gst_kate_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstKateEnc *ke = GST_KATE_ENC (object); const char *str; switch (prop_id) { case ARG_LANGUAGE: if (ke->language) { g_free (ke->language); ke->language = NULL; } str = g_value_get_string (value); if (str) ke->language = g_strdup (str); break; case ARG_CATEGORY: if (ke->category) { g_free (ke->category); ke->category = NULL; } str = g_value_get_string (value); if (str) ke->category = g_strdup (str); break; case ARG_GRANULE_RATE_NUM: ke->granule_rate_numerator = g_value_get_int (value); break; case ARG_GRANULE_RATE_DEN: ke->granule_rate_denominator = g_value_get_int (value); break; case ARG_GRANULE_SHIFT: ke->granule_rate_denominator = g_value_get_int (value); break; case ARG_KEEPALIVE_MIN_TIME: ke->keepalive_min_time = g_value_get_float (value); break; case ARG_ORIGINAL_CANVAS_WIDTH: ke->original_canvas_width = g_value_get_int (value); break; case ARG_ORIGINAL_CANVAS_HEIGHT: ke->original_canvas_height = g_value_get_int (value); break; case ARG_DEFAULT_SPU_DURATION: ke->default_spu_duration = g_value_get_float (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static gboolean gst_kate_enc_convert (GstPad * pad, GstFormat src_fmt, gint64 src_val, GstFormat * dest_fmt, gint64 * dest_val) { GstKateEnc *ke; gboolean res = FALSE; if (src_fmt == *dest_fmt) { *dest_val = src_val; return TRUE; } ke = GST_KATE_ENC (gst_pad_get_parent (pad)); if (!ke->initialized) { GST_WARNING_OBJECT (ke, "not initialized yet"); gst_object_unref (ke); return FALSE; } if (src_fmt == GST_FORMAT_BYTES || *dest_fmt == GST_FORMAT_BYTES) { GST_WARNING_OBJECT (ke, "unsupported format"); gst_object_unref (ke); return FALSE; } switch (src_fmt) { case GST_FORMAT_DEFAULT: switch (*dest_fmt) { case GST_FORMAT_TIME: *dest_val = gst_kate_enc_granule_time (&ke->k, src_val); res = TRUE; break; default: res = FALSE; break; } break; default: res = FALSE; break; } if (!res) { GST_WARNING_OBJECT (ke, "unsupported format"); } gst_object_unref (ke); return res; }
static void gst_kate_enc_dispose (GObject * object) { GstKateEnc *ke = GST_KATE_ENC (object); GST_LOG_OBJECT (ke, "disposing"); if (ke->language) { g_free (ke->language); ke->language = NULL; } if (ke->category) { g_free (ke->category); ke->category = NULL; } GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); }
static gboolean gst_kate_enc_setcaps (GstPad * pad, GstCaps * caps) { GstKateEnc *ke; ke = GST_KATE_ENC (GST_PAD_PARENT (pad)); GST_LOG_OBJECT (ke, "input caps: %" GST_PTR_FORMAT, caps); /* One day we could try to automatically set the category based on the * input format, assuming that the input is subtitles. Currently that * doesn't work yet though, because we send the header packets already from * the sink event handler when receiving the newsegment event, so before * the first buffer (might be tricky to change too, given that there could * be no data at the beginning for a long time). So for now we just try to * make sure people didn't set the category to something obviously wrong. */ if (ke->category != NULL) { GstStructure *s = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (s, "text/plain") || gst_structure_has_name (s, "text/x-pango-markup")) { if (strcmp (ke->category, "K-SPU") == 0 || strcmp (ke->category, "spu-subtitles") == 0) { GST_ELEMENT_WARNING (ke, LIBRARY, SETTINGS, (NULL), ("Category set to '%s', but input is text-based.", ke->category)); } } else if (gst_structure_has_name (s, "video/x-dvd-subpicture")) { if (strcmp (ke->category, "SUB") == 0 || strcmp (ke->category, "subtitles") == 0) { GST_ELEMENT_WARNING (ke, LIBRARY, SETTINGS, (NULL), ("Category set to '%s', but input is subpictures.", ke->category)); } } else { GST_ERROR_OBJECT (ke, "unexpected input caps %" GST_PTR_FORMAT, caps); return FALSE; } } return TRUE; }
static void gst_kate_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstKateEnc *ke = GST_KATE_ENC (object); switch (prop_id) { case ARG_LANGUAGE: g_value_set_string (value, ke->language ? ke->language : ""); break; case ARG_CATEGORY: g_value_set_string (value, ke->category ? ke->category : ""); break; case ARG_GRANULE_RATE_NUM: g_value_set_int (value, ke->granule_rate_numerator); break; case ARG_GRANULE_RATE_DEN: g_value_set_int (value, ke->granule_rate_denominator); break; case ARG_GRANULE_SHIFT: g_value_set_int (value, ke->granule_shift); break; case ARG_KEEPALIVE_MIN_TIME: g_value_set_float (value, ke->keepalive_min_time); break; case ARG_ORIGINAL_CANVAS_WIDTH: g_value_set_int (value, ke->original_canvas_width); break; case ARG_ORIGINAL_CANVAS_HEIGHT: g_value_set_int (value, ke->original_canvas_height); break; case ARG_DEFAULT_SPU_DURATION: g_value_set_float (value, ke->default_spu_duration); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static void gst_kate_enc_metadata_set1 (const GstTagList * list, const gchar * tag, gpointer kateenc) { GstKateEnc *ke = GST_KATE_ENC (kateenc); GList *vc_list, *l; vc_list = gst_tag_to_vorbis_comments (list, tag); for (l = vc_list; l != NULL; l = l->next) { const gchar *vc_string = (const gchar *) l->data; gchar *key = NULL, *val = NULL; GST_LOG_OBJECT (ke, "Kate comment: %s", vc_string); if (gst_tag_parse_extended_comment (vc_string, &key, NULL, &val, TRUE)) { kate_comment_add_tag (&ke->kc, key, val); g_free (key); g_free (val); } } g_list_foreach (vc_list, (GFunc) g_free, NULL); g_list_free (vc_list); }
static gboolean gst_kate_enc_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstKateEnc *ke = GST_KATE_ENC (parent); const GstStructure *structure; gboolean ret; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_CAPS: { GstCaps *caps; gst_event_parse_caps (event, &caps); ret = gst_kate_enc_setcaps (ke, caps); gst_event_unref (event); break; } case GST_EVENT_SEGMENT:{ GstSegment seg; GST_LOG_OBJECT (ke, "Got newsegment event"); gst_event_copy_segment (event, &seg); if (!ke->headers_sent) { if (ke->pending_segment) gst_event_unref (ke->pending_segment); ke->pending_segment = event; event = NULL; } if (ke->initialized) { GST_LOG_OBJECT (ke, "ensuring all headers are in"); if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { GST_WARNING_OBJECT (ke, "Failed to flush headers"); } else { if (seg.format != GST_FORMAT_TIME || !GST_CLOCK_TIME_IS_VALID (seg.start)) { GST_WARNING_OBJECT (ke, "No time in newsegment event %p, format %d, timestamp %" G_GINT64_FORMAT, event, (int) seg.format, seg.start); /* to be safe, we'd need to generate a keepalive anyway, but we'd have to guess at the timestamp to use; a good guess would be the last known timestamp plus the keepalive time, but if we then get a packet with a timestamp less than this, it would fail to encode, which would be Bad. If we don't encode a keepalive, we run the risk of stalling the pipeline and hanging, which is Very Bad. Oh dear. We can't exit(-1), can we ? */ } else { float t = seg.start / (double) GST_SECOND; if (ke->delayed_spu && t - ke->delayed_start / (double) GST_SECOND >= ke->default_spu_duration) { if (G_UNLIKELY (gst_kate_enc_flush_waiting (ke, seg.start) != GST_FLOW_OK)) { GST_WARNING_OBJECT (ke, "Failed to encode delayed packet"); /* continue with new segment handling anyway */ } } GST_LOG_OBJECT (ke, "ts %f, last %f (min %f)", t, ke->last_timestamp / (double) GST_SECOND, ke->keepalive_min_time); if (ke->keepalive_min_time > 0.0f && t - ke->last_timestamp / (double) GST_SECOND >= ke->keepalive_min_time) { /* we only generate a keepalive if there is no SPU waiting, as it would mean out of sequence start times - and granulepos */ if (!ke->delayed_spu) { gst_kate_enc_generate_keepalive (ke, seg.start); } } } } } if (event) ret = gst_pad_push_event (ke->srcpad, event); else ret = TRUE; break; } case GST_EVENT_CUSTOM_DOWNSTREAM: GST_LOG_OBJECT (ke, "Got custom downstream event"); /* adapted from the dvdsubdec element */ structure = gst_event_get_structure (event); if (structure != NULL && gst_structure_has_name (structure, "application/x-gst-dvd")) { if (ke->initialized) { GST_LOG_OBJECT (ke, "ensuring all headers are in"); if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { GST_WARNING_OBJECT (ke, "Failed to flush headers"); } else { const gchar *event_name = gst_structure_get_string (structure, "event"); if (event_name) { if (!strcmp (event_name, "dvd-spu-clut-change")) { gchar name[16]; int idx; gboolean found; gint value; GST_INFO_OBJECT (ke, "New CLUT received"); for (idx = 0; idx < 16; ++idx) { g_snprintf (name, sizeof (name), "clut%02d", idx); found = gst_structure_get_int (structure, name, &value); if (found) { ke->spu_clut[idx] = value; } else { GST_WARNING_OBJECT (ke, "DVD CLUT event did not contain %s field", name); } } } else if (!strcmp (event_name, "dvd-lang-codes")) { /* we can't know which stream corresponds to us */ } } else { GST_WARNING_OBJECT (ke, "custom downstream event with no name"); } } } } ret = gst_pad_push_event (ke->srcpad, event); break; case GST_EVENT_TAG: GST_LOG_OBJECT (ke, "Got tag event"); if (ke->tags) { GstTagList *list; gst_event_parse_tag (event, &list); gst_tag_list_insert (ke->tags, list, gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (ke))); } else { g_assert_not_reached (); } ret = gst_pad_event_default (pad, parent, event); break; case GST_EVENT_EOS: GST_INFO_OBJECT (ke, "Got EOS event"); if (ke->initialized) { GST_LOG_OBJECT (ke, "ensuring all headers are in"); if (gst_kate_enc_flush_headers (ke) != GST_FLOW_OK) { GST_WARNING_OBJECT (ke, "Failed to flush headers"); } else { kate_packet kp; int ret; GstClockTime delayed_end = ke->delayed_start + ke->default_spu_duration * GST_SECOND; if (G_UNLIKELY (gst_kate_enc_flush_waiting (ke, delayed_end) != GST_FLOW_OK)) { GST_WARNING_OBJECT (ke, "Failed to encode delayed packet"); /* continue with EOS handling anyway */ } ret = kate_encode_finish (&ke->k, -1, &kp); if (ret < 0) { GST_WARNING_OBJECT (ke, "Failed to encode EOS packet: %s", gst_kate_util_get_error_message (ret)); } else { kate_int64_t granpos = kate_encode_get_granule (&ke->k); GST_LOG_OBJECT (ke, "EOS packet encoded"); if (gst_kate_enc_push_and_free_kate_packet (ke, &kp, granpos, ke->latest_end_time, 0, FALSE)) { GST_WARNING_OBJECT (ke, "Failed to push EOS packet"); } } } } ret = gst_pad_event_default (pad, parent, event); break; default: GST_LOG_OBJECT (ke, "Got unhandled event"); ret = gst_pad_event_default (pad, parent, event); break; } return ret; }
static GstStateChangeReturn gst_kate_enc_change_state (GstElement * element, GstStateChange transition) { GstKateEnc *ke = GST_KATE_ENC (element); GstStateChangeReturn res; int ret; GST_INFO_OBJECT (ke, "gst_kate_enc_change_state"); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: ke->tags = gst_tag_list_new_empty (); break; case GST_STATE_CHANGE_READY_TO_PAUSED: GST_DEBUG_OBJECT (ke, "READY -> PAUSED, initializing kate state"); ret = kate_info_init (&ke->ki); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to initialize kate info structure: %s", gst_kate_util_get_error_message (ret)); break; } if (ke->language) { ret = kate_info_set_language (&ke->ki, ke->language); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to set stream language: %s", gst_kate_util_get_error_message (ret)); break; } } if (ke->category) { ret = kate_info_set_category (&ke->ki, ke->category); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to set stream category: %s", gst_kate_util_get_error_message (ret)); break; } } ret = kate_info_set_original_canvas_size (&ke->ki, ke->original_canvas_width, ke->original_canvas_height); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to set original canvas size: %s", gst_kate_util_get_error_message (ret)); break; } ret = kate_comment_init (&ke->kc); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to initialize kate comment structure: %s", gst_kate_util_get_error_message (ret)); break; } ret = kate_encode_init (&ke->k, &ke->ki); if (ret < 0) { GST_WARNING_OBJECT (ke, "failed to initialize kate state: %s", gst_kate_util_get_error_message (ret)); break; } ke->headers_sent = FALSE; ke->initialized = TRUE; ke->last_timestamp = 0; ke->latest_end_time = 0; ke->format = GST_KATE_FORMAT_UNDEFINED; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; case GST_STATE_CHANGE_READY_TO_NULL: gst_tag_list_unref (ke->tags); ke->tags = NULL; break; default: break; } res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (res == GST_STATE_CHANGE_FAILURE) { GST_WARNING_OBJECT (ke, "Parent failed to change state"); return res; } switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: GST_DEBUG_OBJECT (ke, "PAUSED -> READY, clearing kate state"); if (ke->initialized) { kate_clear (&ke->k); kate_info_clear (&ke->ki); kate_comment_clear (&ke->kc); ke->initialized = FALSE; ke->last_timestamp = 0; ke->latest_end_time = 0; } gst_event_replace (&ke->pending_segment, NULL); break; case GST_STATE_CHANGE_READY_TO_NULL: break; default: break; } GST_DEBUG_OBJECT (ke, "State change done"); return res; }