/* GStreamer * Copyright (C) <2009> Jan Schmidt * Copyright (C) <2009> Sebastian Dröge * * 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 #include #include #include "rsndec.h" GST_DEBUG_CATEGORY_STATIC (rsn_dec_debug); #define GST_CAT_DEFAULT rsn_dec_debug static GstStateChangeReturn rsn_dec_change_state (GstElement * element, GstStateChange transition); static void rsn_dec_dispose (GObject * gobj); static void cleanup_child (RsnDec * self); static GstBinClass *rsn_dec_parent_class = NULL; static void rsn_dec_class_init (RsnDecClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GST_DEBUG_CATEGORY_INIT (rsn_dec_debug, "rsndec", 0, "Resin DVD stream decoder"); rsn_dec_parent_class = (GstBinClass *) g_type_class_peek_parent (klass); object_class->dispose = rsn_dec_dispose; element_class->change_state = GST_DEBUG_FUNCPTR (rsn_dec_change_state); } static gboolean rsn_dec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { RsnDec *self = RSN_DEC (parent); gboolean ret = TRUE; const GstStructure *s = gst_event_get_structure (event); const gchar *name = (s ? gst_structure_get_name (s) : NULL); if (name && g_str_equal (name, "application/x-gst-dvd")) ret = gst_pad_push_event (GST_PAD_CAST (self->srcpad), event); else ret = self->sink_event_func (pad, parent, event); return ret; } static void rsn_dec_init (RsnDec * self, RsnDecClass * klass) { GstPadTemplate *templ; templ = gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "sink"); g_assert (templ != NULL); self->sinkpad = GST_GHOST_PAD_CAST (gst_ghost_pad_new_no_target_from_template ("sink", templ)); self->sink_event_func = GST_PAD_EVENTFUNC (self->sinkpad); gst_pad_set_event_function (GST_PAD_CAST (self->sinkpad), GST_DEBUG_FUNCPTR (rsn_dec_sink_event)); templ = gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass), "src"); g_assert (templ != NULL); self->srcpad = GST_GHOST_PAD_CAST (gst_ghost_pad_new_no_target_from_template ("src", templ)); gst_element_add_pad (GST_ELEMENT (self), GST_PAD_CAST (self->sinkpad)); gst_element_add_pad (GST_ELEMENT (self), GST_PAD_CAST (self->srcpad)); } static void rsn_dec_dispose (GObject * object) { RsnDec *self = (RsnDec *) object; cleanup_child (self); G_OBJECT_CLASS (rsn_dec_parent_class)->dispose (object); } static gboolean rsn_dec_set_child (RsnDec * self, GstElement * new_child) { GstPad *child_pad; if (self->current_decoder) { gst_ghost_pad_set_target (self->srcpad, NULL); gst_ghost_pad_set_target (self->sinkpad, NULL); gst_bin_remove ((GstBin *) self, self->current_decoder); self->current_decoder = NULL; } if (new_child == NULL) return TRUE; if (!gst_bin_add ((GstBin *) self, new_child)) return FALSE; child_pad = gst_element_get_static_pad (new_child, "sink"); if (child_pad == NULL) { return FALSE; } gst_ghost_pad_set_target (self->sinkpad, child_pad); gst_object_unref (child_pad); child_pad = gst_element_get_static_pad (new_child, "src"); if (child_pad == NULL) { return FALSE; } gst_ghost_pad_set_target (self->srcpad, child_pad); gst_object_unref (child_pad); GST_DEBUG_OBJECT (self, "Add child %" GST_PTR_FORMAT, new_child); self->current_decoder = new_child; gst_element_sync_state_with_parent (new_child); return TRUE; } static void cleanup_child (RsnDec * self) { GST_DEBUG_OBJECT (self, "Removing child element"); (void) rsn_dec_set_child (self, NULL); } typedef struct { GstCaps *desired_caps; GstCaps *decoder_caps; } RsnDecFactoryFilterCtx; static gboolean rsndec_factory_filter (GstPluginFeature * feature, RsnDecFactoryFilterCtx * ctx) { GstElementFactory *factory; guint rank; const gchar *klass; const GList *templates; GList *walk; gboolean can_sink = FALSE; /* we only care about element factories */ if (!GST_IS_ELEMENT_FACTORY (feature)) return FALSE; factory = GST_ELEMENT_FACTORY (feature); klass = gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS); /* only decoders can play */ if (strstr (klass, "Decoder") == NULL) return FALSE; /* only select elements with autoplugging rank */ rank = gst_plugin_feature_get_rank (feature); if (rank < GST_RANK_MARGINAL) return FALSE; /* See if the element has a sink pad that can possibly sink this caps */ /* get the templates from the element factory */ templates = gst_element_factory_get_static_pad_templates (factory); for (walk = (GList *) templates; walk && !can_sink; walk = g_list_next (walk)) { GstStaticPadTemplate *templ = walk->data; /* we only care about the sink templates */ if (templ->direction == GST_PAD_SINK) { GstCaps *intersect; GstCaps *tmpl_caps; /* try to intersect the caps with the caps of the template */ tmpl_caps = gst_static_caps_get (&templ->static_caps); intersect = gst_caps_intersect (ctx->desired_caps, tmpl_caps); gst_caps_unref (tmpl_caps); /* check if the intersection is empty */ if (!gst_caps_is_empty (intersect)) { /* non empty intersection, we can use this element */ can_sink = TRUE; ctx->decoder_caps = gst_caps_merge (ctx->decoder_caps, intersect); } else gst_caps_unref (intersect); } } if (can_sink) { GST_DEBUG ("Found decoder element %s (%s)", gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_LONGNAME), gst_plugin_feature_get_name (feature)); } return can_sink; } static gint sort_by_ranks (GstPluginFeature * f1, GstPluginFeature * f2) { gint diff; const gchar *rname1, *rname2; diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1); if (diff != 0) return diff; rname1 = gst_plugin_feature_get_name (f1); rname2 = gst_plugin_feature_get_name (f2); diff = strcmp (rname2, rname1); return diff; } static gpointer _get_decoder_factories (gpointer arg) { GstElementClass *klass = arg; GList *factories; GstPadTemplate *templ = gst_element_class_get_pad_template (klass, "sink"); RsnDecFactoryFilterCtx ctx = { NULL, }; GstCaps *raw; gboolean raw_audio; GstRegistry *registry = gst_registry_get (); ctx.desired_caps = gst_pad_template_get_caps (templ); raw = gst_caps_from_string ("audio/x-raw,format=(string){ F32LE, F32BE, F64LE, F64BE }"); raw_audio = gst_caps_can_intersect (raw, ctx.desired_caps); if (raw_audio) { GstCaps *sub = gst_caps_subtract (ctx.desired_caps, raw); ctx.desired_caps = sub; } else { gst_caps_ref (ctx.desired_caps); } gst_caps_unref (raw); /* Set decoder caps to empty. Will be filled by the factory_filter */ ctx.decoder_caps = gst_caps_new_empty (); GST_DEBUG ("Finding factories for caps: %" GST_PTR_FORMAT, ctx.desired_caps); factories = gst_registry_feature_filter (registry, (GstPluginFeatureFilter) rsndec_factory_filter, FALSE, &ctx); /* If these are audio caps, we add audioconvert, which is not a decoder, but allows raw audio to go through relatively unmolested - this will come handy when we have to send placeholder silence to allow preroll for those DVDs which have titles with no audio track. */ if (raw_audio) { GstPluginFeature *feature; GST_DEBUG ("These are audio caps, adding audioconvert"); feature = gst_registry_find_feature (registry, "audioconvert", GST_TYPE_ELEMENT_FACTORY); if (feature) { factories = g_list_append (factories, feature); } else { GST_WARNING ("Could not find feature audioconvert"); } } factories = g_list_sort (factories, (GCompareFunc) sort_by_ranks); GST_DEBUG ("Available decoder caps %" GST_PTR_FORMAT, ctx.decoder_caps); gst_caps_unref (ctx.decoder_caps); gst_caps_unref (ctx.desired_caps); return factories; } static GstStateChangeReturn rsn_dec_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; RsnDec *self = RSN_DEC (element); RsnDecClass *klass = RSN_DEC_GET_CLASS (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY:{ GstElement *new_child; const GList *decoder_factories; new_child = gst_element_factory_make ("autoconvert", NULL); decoder_factories = klass->get_decoder_factories (klass); g_object_set (G_OBJECT (new_child), "factories", decoder_factories, NULL); if (new_child == NULL || !rsn_dec_set_child (self, new_child)) ret = GST_STATE_CHANGE_FAILURE; break; } case GST_STATE_CHANGE_READY_TO_PAUSED: break; default: break; } if (ret == GST_STATE_CHANGE_FAILURE) return ret; ret = GST_ELEMENT_CLASS (rsn_dec_parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: break; case GST_STATE_CHANGE_READY_TO_NULL: cleanup_child (self); break; default: break; } return ret; } GType rsn_dec_get_type (void) { static volatile gsize type = 0; if (g_once_init_enter (&type)) { GType _type; static const GTypeInfo type_info = { sizeof (RsnDecClass), NULL, NULL, (GClassInitFunc) rsn_dec_class_init, NULL, NULL, sizeof (RsnDec), 0, (GInstanceInitFunc) rsn_dec_init, }; _type = g_type_register_static (GST_TYPE_BIN, "RsnDec", &type_info, G_TYPE_FLAG_ABSTRACT); g_once_init_leave (&type, _type); } return type; } /** Audio decoder subclass */ static GstStaticPadTemplate audio_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/mpeg,mpegversion=(int)1;" "audio/x-private1-lpcm;" "audio/x-private1-ac3;" "audio/ac3;" "audio/x-ac3;" "audio/x-private1-dts; audio/x-raw,format=(string)" GST_AUDIO_FORMATS_ALL) ); static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL)) ); G_DEFINE_TYPE (RsnAudioDec, rsn_audiodec, RSN_TYPE_DEC); static const GList * rsn_audiodec_get_decoder_factories (RsnDecClass * klass) { static GOnce gonce = G_ONCE_INIT; g_once (&gonce, _get_decoder_factories, klass); return (const GList *) gonce.retval; } static void rsn_audiodec_class_init (RsnAudioDecClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); RsnDecClass *dec_class = RSN_DEC_CLASS (klass); gst_element_class_add_static_pad_template (element_class, &audio_src_template); gst_element_class_add_static_pad_template (element_class, &audio_sink_template); gst_element_class_set_static_metadata (element_class, "RsnAudioDec", "Audio/Decoder", "Resin DVD audio stream decoder", "Jan Schmidt "); dec_class->get_decoder_factories = rsn_audiodec_get_decoder_factories; } static void rsn_audiodec_init (RsnAudioDec * self) { } /** Video decoder subclass */ static GstStaticPadTemplate video_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/mpeg, " "mpegversion = (int) [ 1, 2 ], " "systemstream = (bool) FALSE") ); static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)) ); G_DEFINE_TYPE (RsnVideoDec, rsn_videodec, RSN_TYPE_DEC); static const GList * rsn_videodec_get_decoder_factories (RsnDecClass * klass) { static GOnce gonce = G_ONCE_INIT; g_once (&gonce, _get_decoder_factories, klass); return (const GList *) gonce.retval; } static void rsn_videodec_class_init (RsnAudioDecClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); RsnDecClass *dec_class = RSN_DEC_CLASS (klass); gst_element_class_add_static_pad_template (element_class, &video_src_template); gst_element_class_add_static_pad_template (element_class, &video_sink_template); gst_element_class_set_static_metadata (element_class, "RsnVideoDec", "Video/Decoder", "Resin DVD video stream decoder", "Jan Schmidt "); dec_class->get_decoder_factories = rsn_videodec_get_decoder_factories; } static void rsn_videodec_init (RsnVideoDec * self) { }