/* GStreamer encoding bin * Copyright (C) 2009 Edward Hervey * (C) 2009 Nokia Corporation * * 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 "gstencodebin.h" #include "gstsmartencoder.h" #include "gststreamsplitter.h" #include "gststreamcombiner.h" #include /** * SECTION:element-encodebin * * EncodeBin provides a bin for encoding/muxing various streams according to * a specified #GstEncodingProfile. * * Based on the profile that was set (via the #GstEncodeBin:profile property), * EncodeBin will internally select and configure the required elements * (encoders, muxers, but also audio and video converters) so that you can * provide it raw or pre-encoded streams of data in input and have your * encoded/muxed/converted stream in output. * * * Features * * * Automatic encoder and muxer selection based on elements available on the * system. * * * Conversion of raw audio/video streams (scaling, framerate conversion, * colorspace conversion, samplerate conversion) to conform to the profile * output format. * * * Variable number of streams. If the presence property for a stream encoding * profile is 0, you can request any number of sink pads for it via the * standard request pad gstreamer API or the #GstEncodeBin::request-pad action * signal. * * * Avoid reencoding (passthrough). If the input stream is already encoded and is * compatible with what the #GstEncodingProfile expects, then the stream won't * be re-encoded but just passed through downstream to the muxer or the output. * * * Mix pre-encoded and raw streams as input. In addition to the passthrough * feature above, you can feed both raw audio/video *AND* already-encoded data * to a pad. #GstEncodeBin will take care of passing through the compatible * segments and re-encoding the segments of media that need encoding. * * * Standard behaviour is to use a #GstEncodingContainerProfile to have both * encoding and muxing performed. But you can also provide a single stream * profile (like #GstEncodingAudioProfile) to only have the encoding done and * handle the encoded output yourself. * * * Audio imperfection corrections. Incoming audio streams can have non perfect * timestamps (jitter), like the streams coming from ASF files. #GstEncodeBin * will automatically fix those imperfections for you. See * #GstEncodeBin:audio-jitter-tolerance for more details. * * * Variable or Constant video framerate. If your #GstEncodingVideoProfile has * the variableframerate property deactivated (default), then the incoming * raw video stream will be retimestampped in order to produce a constant * framerate. * * * Cross-boundary re-encoding. When feeding compatible pre-encoded streams that * fall on segment boundaries, and for supported formats (right now only H263), * the GOP will be decoded/reencoded when needed to produce an encoded output * that fits exactly within the request GstSegment. * * * Missing plugin support. If a #GstElement is missing to encode/mux to the * request profile formats, a missing-plugin #GstMessage will be posted on the * #GstBus, allowing systems that support the missing-plugin system to offer the * user a way to install the missing element. * * * */ /* TODO/FIXME * * Handling mp3!xing!idv3 and theora!ogg tagsetting scenarios: * Once we have chosen a muxer: * When a new stream is requested: * If muxer isn't 'Formatter' OR doesn't have a TagSetter interface: * Find a Formatter for the given stream (preferably with TagSetter) * Insert that before muxer **/ #define fast_pad_link(a,b) gst_pad_link_full((a),(b),GST_PAD_LINK_CHECK_NOTHING) #define fast_element_link(a,b) gst_element_link_pads_full((a),"src",(b),"sink",GST_PAD_LINK_CHECK_NOTHING) typedef enum { GST_ENCODEBIN_FLAG_NO_AUDIO_CONVERSION = (1 << 0), GST_ENCODEBIN_FLAG_NO_VIDEO_CONVERSION = (1 << 1) } GstEncodeBinFlags; #define GST_TYPE_ENCODEBIN_FLAGS (gst_encodebin_flags_get_type()) GType gst_encodebin_flags_get_type (void); /* generic templates */ static GstStaticPadTemplate muxer_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate video_sink_template = GST_STATIC_PAD_TEMPLATE ("video_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate audio_sink_template = GST_STATIC_PAD_TEMPLATE ("audio_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY); /* static GstStaticPadTemplate text_sink_template = */ /* GST_STATIC_PAD_TEMPLATE ("text_%u", */ /* GST_PAD_SINK, */ /* GST_PAD_REQUEST, */ /* GST_STATIC_CAPS_ANY); */ static GstStaticPadTemplate private_sink_template = GST_STATIC_PAD_TEMPLATE ("private_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY); struct _GstEncodeBin { GstBin parent; /* the profile field is only valid if it could be entirely setup */ GstEncodingProfile *profile; GList *streams; /* List of StreamGroup, not sorted */ GstElement *muxer; /* Ghostpad with changing target */ GstPad *srcpad; /* TRUE if in PAUSED/PLAYING */ gboolean active; /* available muxers, encoders and parsers */ GList *muxers; GList *formatters; GList *encoders; GList *parsers; /* Increasing counter for unique pad name */ guint last_pad_id; /* Cached caps for identification */ GstCaps *raw_video_caps; GstCaps *raw_audio_caps; /* GstCaps *raw_text_caps; */ guint queue_buffers_max; guint queue_bytes_max; guint64 queue_time_max; guint64 tolerance; gboolean avoid_reencoding; GstEncodeBinFlags flags; }; struct _GstEncodeBinClass { GstBinClass parent; /* Action Signals */ GstPad *(*request_pad) (GstEncodeBin * encodebin, GstCaps * caps); GstPad *(*request_profile_pad) (GstEncodeBin * encodebin, const gchar * profilename); }; typedef struct _StreamGroup StreamGroup; struct _StreamGroup { GstEncodeBin *ebin; GstEncodingProfile *profile; GstPad *ghostpad; /* Sink ghostpad */ GstElement *inqueue; /* Queue just after the ghostpad */ GstElement *splitter; GList *converters; /* List of conversion GstElement */ GstElement *capsfilter; /* profile->restriction (if non-NULL/ANY) */ GstElement *encoder; /* Encoder (can be NULL) */ GstElement *combiner; GstElement *parser; GstElement *smartencoder; GstElement *outfilter; /* Output capsfilter (streamprofile.format) */ GstElement *formatter; GstElement *outqueue; /* Queue just before the muxer */ }; /* Default for queues (same defaults as queue element) */ #define DEFAULT_QUEUE_BUFFERS_MAX 200 #define DEFAULT_QUEUE_BYTES_MAX 10 * 1024 * 1024 #define DEFAULT_QUEUE_TIME_MAX GST_SECOND #define DEFAULT_AUDIO_JITTER_TOLERANCE 20 * GST_MSECOND #define DEFAULT_AVOID_REENCODING FALSE #define DEFAULT_FLAGS 0 #define DEFAULT_RAW_CAPS \ "video/x-raw; " \ "audio/x-raw; " \ "text/x-raw; " \ "subpicture/x-dvd; " \ "subpicture/x-pgs" /* Properties */ enum { PROP_0, PROP_PROFILE, PROP_QUEUE_BUFFERS_MAX, PROP_QUEUE_BYTES_MAX, PROP_QUEUE_TIME_MAX, PROP_AUDIO_JITTER_TOLERANCE, PROP_AVOID_REENCODING, PROP_FLAGS, PROP_LAST }; /* Signals */ enum { SIGNAL_REQUEST_PAD, SIGNAL_REQUEST_PROFILE_PAD, LAST_SIGNAL }; #define C_FLAGS(v) ((guint) v) GType gst_encodebin_flags_get_type (void) { static const GFlagsValue values[] = { {C_FLAGS (GST_ENCODEBIN_FLAG_NO_AUDIO_CONVERSION), "Do not use audio " "conversion elements", "no-audio-conversion"}, {C_FLAGS (GST_ENCODEBIN_FLAG_NO_VIDEO_CONVERSION), "Do not use video " "conversion elements", "no-video-conversion"}, {0, NULL, NULL} }; static volatile GType id = 0; if (g_once_init_enter ((gsize *) & id)) { GType _id; _id = g_flags_register_static ("GstEncodeBinFlags", values); g_once_init_leave ((gsize *) & id, _id); } return id; } static guint gst_encode_bin_signals[LAST_SIGNAL] = { 0 }; static GstStaticCaps default_raw_caps = GST_STATIC_CAPS (DEFAULT_RAW_CAPS); GST_DEBUG_CATEGORY_STATIC (gst_encode_bin_debug); #define GST_CAT_DEFAULT gst_encode_bin_debug G_DEFINE_TYPE (GstEncodeBin, gst_encode_bin, GST_TYPE_BIN); static void gst_encode_bin_dispose (GObject * object); static void gst_encode_bin_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_encode_bin_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstStateChangeReturn gst_encode_bin_change_state (GstElement * element, GstStateChange transition); static GstPad *gst_encode_bin_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps); static void gst_encode_bin_release_pad (GstElement * element, GstPad * pad); static gboolean gst_encode_bin_set_profile (GstEncodeBin * ebin, GstEncodingProfile * profile); static void gst_encode_bin_tear_down_profile (GstEncodeBin * ebin); static gboolean gst_encode_bin_setup_profile (GstEncodeBin * ebin, GstEncodingProfile * profile); static StreamGroup *_create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, const gchar * sinkpadname, GstCaps * sinkcaps); static void stream_group_remove (GstEncodeBin * ebin, StreamGroup * sgroup); static GstPad *gst_encode_bin_request_pad_signal (GstEncodeBin * encodebin, GstCaps * caps); static GstPad *gst_encode_bin_request_profile_pad_signal (GstEncodeBin * encodebin, const gchar * profilename); static inline GstElement *_get_formatter (GstEncodeBin * ebin, GstEncodingProfile * sprof); static void gst_encode_bin_class_init (GstEncodeBinClass * klass) { GObjectClass *gobject_klass; GstElementClass *gstelement_klass; gobject_klass = (GObjectClass *) klass; gstelement_klass = (GstElementClass *) klass; gobject_klass->dispose = gst_encode_bin_dispose; gobject_klass->set_property = gst_encode_bin_set_property; gobject_klass->get_property = gst_encode_bin_get_property; /* Properties */ /** * GstEncodeBin:profile: * * The #GstEncodingProfile to use. This property must be set before going * to %GST_STATE_PAUSED or higher. */ g_object_class_install_property (gobject_klass, PROP_PROFILE, g_param_spec_object ("profile", "Profile", "The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_QUEUE_BYTES_MAX, g_param_spec_uint ("queue-bytes-max", "Max. size (kB)", "Max. amount of data in the queue (bytes, 0=disable)", 0, G_MAXUINT, DEFAULT_QUEUE_BYTES_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_QUEUE_BUFFERS_MAX, g_param_spec_uint ("queue-buffers-max", "Max. size (buffers)", "Max. number of buffers in the queue (0=disable)", 0, G_MAXUINT, DEFAULT_QUEUE_BUFFERS_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_QUEUE_TIME_MAX, g_param_spec_uint64 ("queue-time-max", "Max. size (ns)", "Max. amount of data in the queue (in ns, 0=disable)", 0, G_MAXUINT64, DEFAULT_QUEUE_TIME_MAX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_AUDIO_JITTER_TOLERANCE, g_param_spec_uint64 ("audio-jitter-tolerance", "Audio jitter tolerance", "Amount of timestamp jitter/imperfection to allow on audio streams before inserting/dropping samples (ns)", 0, G_MAXUINT64, DEFAULT_AUDIO_JITTER_TOLERANCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_klass, PROP_AVOID_REENCODING, g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding", "Whether to re-encode portions of compatible video streams that lay on segment boundaries", DEFAULT_AVOID_REENCODING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstEncodeBin:flags * * Control the behaviour of encodebin. */ g_object_class_install_property (gobject_klass, PROP_FLAGS, g_param_spec_flags ("flags", "Flags", "Flags to control behaviour", GST_TYPE_ENCODEBIN_FLAGS, DEFAULT_FLAGS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /* Signals */ /** * GstEncodeBin::request-pad * @encodebin: a #GstEncodeBin instance * @caps: a #GstCaps * * Use this method to request an unused sink request #GstPad that can take the * provided @caps as input. You must release the pad with * gst_element_release_request_pad() when you are done with it. * * Returns: A compatible #GstPad, or %NULL if no compatible #GstPad could be * created or is available. */ gst_encode_bin_signals[SIGNAL_REQUEST_PAD] = g_signal_new ("request-pad", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstEncodeBinClass, request_pad), NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_PAD, 1, GST_TYPE_CAPS); /** * GstEncodeBin::request-profile-pad * @encodebin: a #GstEncodeBin instance * @profilename: the name of a #GstEncodingProfile * * Use this method to request an unused sink request #GstPad from the profile * @profilename. You must release the pad with * gst_element_release_request_pad() when you are done with it. * * Returns: A compatible #GstPad, or %NULL if no compatible #GstPad could be * created or is available. */ gst_encode_bin_signals[SIGNAL_REQUEST_PROFILE_PAD] = g_signal_new ("request-profile-pad", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstEncodeBinClass, request_profile_pad), NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_PAD, 1, G_TYPE_STRING); klass->request_pad = gst_encode_bin_request_pad_signal; klass->request_profile_pad = gst_encode_bin_request_profile_pad_signal; gst_element_class_add_pad_template (gstelement_klass, gst_static_pad_template_get (&muxer_src_template)); gst_element_class_add_pad_template (gstelement_klass, gst_static_pad_template_get (&video_sink_template)); gst_element_class_add_pad_template (gstelement_klass, gst_static_pad_template_get (&audio_sink_template)); /* gst_element_class_add_pad_template (gstelement_klass, */ /* gst_static_pad_template_get (&text_sink_template)); */ gst_element_class_add_pad_template (gstelement_klass, gst_static_pad_template_get (&private_sink_template)); gstelement_klass->change_state = GST_DEBUG_FUNCPTR (gst_encode_bin_change_state); gstelement_klass->request_new_pad = GST_DEBUG_FUNCPTR (gst_encode_bin_request_new_pad); gstelement_klass->release_pad = GST_DEBUG_FUNCPTR (gst_encode_bin_release_pad); gst_element_class_set_static_metadata (gstelement_klass, "Encoder Bin", "Generic/Bin/Encoder", "Convenience encoding/muxing element", "Edward Hervey "); } static void gst_encode_bin_dispose (GObject * object) { GstEncodeBin *ebin = (GstEncodeBin *) object; if (ebin->muxers) gst_plugin_feature_list_free (ebin->muxers); if (ebin->formatters) gst_plugin_feature_list_free (ebin->formatters); if (ebin->encoders) gst_plugin_feature_list_free (ebin->encoders); if (ebin->parsers) gst_plugin_feature_list_free (ebin->parsers); gst_encode_bin_tear_down_profile (ebin); if (ebin->raw_video_caps) gst_caps_unref (ebin->raw_video_caps); if (ebin->raw_audio_caps) gst_caps_unref (ebin->raw_audio_caps); /* if (ebin->raw_text_caps) */ /* gst_caps_unref (ebin->raw_text_caps); */ G_OBJECT_CLASS (gst_encode_bin_parent_class)->dispose (object); } static void gst_encode_bin_init (GstEncodeBin * encode_bin) { GstPadTemplate *tmpl; encode_bin->muxers = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_MUXER, GST_RANK_MARGINAL); encode_bin->formatters = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_FORMATTER, GST_RANK_SECONDARY); encode_bin->encoders = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_ENCODER, GST_RANK_MARGINAL); encode_bin->parsers = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_PARSER, GST_RANK_MARGINAL); encode_bin->raw_video_caps = gst_caps_from_string ("video/x-raw"); encode_bin->raw_audio_caps = gst_caps_from_string ("audio/x-raw"); /* encode_bin->raw_text_caps = */ /* gst_caps_from_string ("text/x-raw"); */ encode_bin->queue_buffers_max = DEFAULT_QUEUE_BUFFERS_MAX; encode_bin->queue_bytes_max = DEFAULT_QUEUE_BYTES_MAX; encode_bin->queue_time_max = DEFAULT_QUEUE_TIME_MAX; encode_bin->tolerance = DEFAULT_AUDIO_JITTER_TOLERANCE; encode_bin->avoid_reencoding = DEFAULT_AVOID_REENCODING; encode_bin->flags = DEFAULT_FLAGS; tmpl = gst_static_pad_template_get (&muxer_src_template); encode_bin->srcpad = gst_ghost_pad_new_no_target_from_template ("src", tmpl); gst_object_unref (tmpl); gst_pad_set_active (encode_bin->srcpad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (encode_bin), encode_bin->srcpad); } static void gst_encode_bin_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstEncodeBin *ebin = (GstEncodeBin *) object; switch (prop_id) { case PROP_PROFILE: gst_encode_bin_set_profile (ebin, (GstEncodingProfile *) g_value_get_object (value)); break; case PROP_QUEUE_BUFFERS_MAX: ebin->queue_buffers_max = g_value_get_uint (value); break; case PROP_QUEUE_BYTES_MAX: ebin->queue_bytes_max = g_value_get_uint (value); break; case PROP_QUEUE_TIME_MAX: ebin->queue_time_max = g_value_get_uint64 (value); break; case PROP_AUDIO_JITTER_TOLERANCE: ebin->tolerance = g_value_get_uint64 (value); break; case PROP_AVOID_REENCODING: ebin->avoid_reencoding = g_value_get_boolean (value); break; case PROP_FLAGS: ebin->flags = g_value_get_flags (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_encode_bin_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstEncodeBin *ebin = (GstEncodeBin *) object; switch (prop_id) { case PROP_PROFILE: g_value_set_object (value, (GObject *) ebin->profile); break; case PROP_QUEUE_BUFFERS_MAX: g_value_set_uint (value, ebin->queue_buffers_max); break; case PROP_QUEUE_BYTES_MAX: g_value_set_uint (value, ebin->queue_bytes_max); break; case PROP_QUEUE_TIME_MAX: g_value_set_uint64 (value, ebin->queue_time_max); break; case PROP_AUDIO_JITTER_TOLERANCE: g_value_set_uint64 (value, ebin->tolerance); break; case PROP_AVOID_REENCODING: g_value_set_boolean (value, ebin->avoid_reencoding); break; case PROP_FLAGS: g_value_set_flags (value, ebin->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static inline gboolean are_raw_caps (const GstCaps * caps) { GstCaps *raw = gst_static_caps_get (&default_raw_caps); if (gst_caps_can_intersect (caps, raw)) { gst_caps_unref (raw); return TRUE; } gst_caps_unref (raw); return FALSE; } /* Returns the number of time a given stream profile is currently used * in encodebin */ static inline guint stream_profile_used_count (GstEncodeBin * ebin, GstEncodingProfile * sprof) { guint nbprofused = 0; GList *tmp; for (tmp = ebin->streams; tmp; tmp = tmp->next) { StreamGroup *sgroup = (StreamGroup *) tmp->data; if (sgroup->profile == sprof) nbprofused++; } return nbprofused; } static inline GstEncodingProfile * next_unused_stream_profile (GstEncodeBin * ebin, GType ptype, const gchar * name, GstCaps * caps) { GST_DEBUG_OBJECT (ebin, "ptype:%s, caps:%" GST_PTR_FORMAT, g_type_name (ptype), caps); if (G_UNLIKELY (ptype == G_TYPE_NONE && caps != NULL)) { /* Identify the profile type based on raw caps */ if (gst_caps_can_intersect (ebin->raw_video_caps, caps)) ptype = GST_TYPE_ENCODING_VIDEO_PROFILE; else if (gst_caps_can_intersect (ebin->raw_audio_caps, caps)) ptype = GST_TYPE_ENCODING_AUDIO_PROFILE; /* else if (gst_caps_can_intersect (ebin->raw_text_caps, caps)) */ /* ptype = GST_TYPE_ENCODING_TEXT_PROFILE; */ GST_DEBUG_OBJECT (ebin, "Detected profile type as being %s", g_type_name (ptype)); } if (GST_IS_ENCODING_CONTAINER_PROFILE (ebin->profile)) { const GList *tmp; if (name) { /* If we have a name, try to find a profile with the same name */ tmp = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); for (; tmp; tmp = tmp->next) { GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data; const gchar *profilename = gst_encoding_profile_get_name (sprof); if (profilename && !strcmp (name, profilename)) { guint presence = gst_encoding_profile_get_presence (sprof); GST_DEBUG ("Found profile matching the requested name"); if (presence == 0 || presence > stream_profile_used_count (ebin, sprof)) return sprof; GST_WARNING ("Matching stream already used"); return NULL; } } GST_DEBUG ("No profiles matching requested pad name, carrying on with normal stream matching"); } for (tmp = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); tmp; tmp = tmp->next) { GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data; /* Pick an available Stream profile for which: * * either it is of the compatible raw type, * * OR we can pass it through directly without encoding */ if (G_TYPE_FROM_INSTANCE (sprof) == ptype) { guint presence = gst_encoding_profile_get_presence (sprof); GST_DEBUG ("Found a stream profile with the same type"); if (presence == 0 || (presence > stream_profile_used_count (ebin, sprof))) return sprof; } else if (caps && ptype == G_TYPE_NONE) { GstCaps *outcaps; gboolean res; outcaps = gst_encoding_profile_get_input_caps (sprof); GST_DEBUG ("Unknown stream, seeing if it's compatible with %" GST_PTR_FORMAT, outcaps); res = gst_caps_can_intersect (outcaps, caps); gst_caps_unref (outcaps); if (res) return sprof; } } } return NULL; } static GstPad * request_pad_for_stream (GstEncodeBin * encodebin, GType ptype, const gchar * name, GstCaps * caps) { StreamGroup *sgroup; GstEncodingProfile *sprof; GST_DEBUG_OBJECT (encodebin, "name:%s caps:%" GST_PTR_FORMAT, name, caps); /* Figure out if we have a unused GstEncodingProfile we can use for * these caps */ sprof = next_unused_stream_profile (encodebin, ptype, name, caps); if (G_UNLIKELY (sprof == NULL)) goto no_stream_profile; sgroup = _create_stream_group (encodebin, sprof, name, caps); if (G_UNLIKELY (sgroup == NULL)) goto no_stream_group; return sgroup->ghostpad; no_stream_profile: { GST_WARNING_OBJECT (encodebin, "Couldn't find a compatible stream profile"); return NULL; } no_stream_group: { GST_WARNING_OBJECT (encodebin, "Couldn't create a StreamGroup"); return NULL; } } static GstPad * gst_encode_bin_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps) { GstEncodeBin *ebin = (GstEncodeBin *) element; GstPad *res = NULL; GST_DEBUG_OBJECT (element, "templ:%s, name:%s", templ->name_template, name); /* Identify the stream group (if name or caps have been provided) */ if (caps != NULL || name != NULL) { res = request_pad_for_stream (ebin, G_TYPE_NONE, name, (GstCaps *) caps); } if (res == NULL) { GType ptype = G_TYPE_NONE; if (!strcmp (templ->name_template, "video_%u")) ptype = GST_TYPE_ENCODING_VIDEO_PROFILE; else if (!strcmp (templ->name_template, "audio_%u")) ptype = GST_TYPE_ENCODING_AUDIO_PROFILE; /* else if (!strcmp (templ->name_template, "text_%u")) */ /* ptype = GST_TYPE_ENCODING_TEXT_PROFILE; */ /* FIXME : Check uniqueness of pad */ /* FIXME : Check that the requested number is the last one, and if not, * update the last_pad_id variable so that we don't create a pad with * the same name/number in the future */ res = request_pad_for_stream (ebin, ptype, name, NULL); } return res; } static GstPad * gst_encode_bin_request_pad_signal (GstEncodeBin * encodebin, GstCaps * caps) { GstPad *pad = request_pad_for_stream (encodebin, G_TYPE_NONE, NULL, caps); return pad ? GST_PAD_CAST (gst_object_ref (pad)) : NULL; } static GstPad * gst_encode_bin_request_profile_pad_signal (GstEncodeBin * encodebin, const gchar * profilename) { GstPad *pad = request_pad_for_stream (encodebin, G_TYPE_NONE, profilename, NULL); return pad ? GST_PAD_CAST (gst_object_ref (pad)) : NULL; } static inline StreamGroup * find_stream_group_from_pad (GstEncodeBin * ebin, GstPad * pad) { GList *tmp; for (tmp = ebin->streams; tmp; tmp = tmp->next) { StreamGroup *sgroup = (StreamGroup *) tmp->data; if (G_UNLIKELY (sgroup->ghostpad == pad)) return sgroup; } return NULL; } static void gst_encode_bin_release_pad (GstElement * element, GstPad * pad) { GstEncodeBin *ebin = (GstEncodeBin *) element; StreamGroup *sgroup; /* Find the associated StreamGroup */ sgroup = find_stream_group_from_pad (ebin, pad); if (G_UNLIKELY (sgroup == NULL)) goto no_stream_group; /* Release objects/data associated with the StreamGroup */ stream_group_remove (ebin, sgroup); return; no_stream_group: { GST_WARNING_OBJECT (ebin, "Couldn't find corresponding StreamGroup"); return; } } /* Create a parser for the given stream profile */ static inline GstElement * _get_parser (GstEncodeBin * ebin, GstEncodingProfile * sprof) { GList *parsers1, *parsers, *tmp; GstElement *parser = NULL; GstElementFactory *parserfact = NULL; GstCaps *format; format = gst_encoding_profile_get_format (sprof); GST_DEBUG ("Getting list of parsers for format %" GST_PTR_FORMAT, format); /* FIXME : requesting twice the parsers twice is a bit ugly, we should * have a method to request on more than one condition */ parsers1 = gst_element_factory_list_filter (ebin->parsers, format, GST_PAD_SRC, FALSE); parsers = gst_element_factory_list_filter (parsers1, format, GST_PAD_SINK, FALSE); gst_plugin_feature_list_free (parsers1); if (G_UNLIKELY (parsers == NULL)) { GST_DEBUG ("Couldn't find any compatible parsers"); goto beach; } for (tmp = parsers; tmp; tmp = tmp->next) { /* FIXME : We're only picking the first one so far */ /* FIXME : signal the user if he wants this */ parserfact = (GstElementFactory *) tmp->data; break; } if (parserfact) parser = gst_element_factory_create (parserfact, NULL); gst_plugin_feature_list_free (parsers); beach: if (format) gst_caps_unref (format); return parser; } static GstElement * _create_element_and_set_preset (GstElementFactory * factory, const gchar * preset, const gchar * name, const gchar * preset_name) { GstElement *res = NULL; GST_DEBUG ("Creating element from factory %s (preset factory name: %s" " preset name: %s)", GST_OBJECT_NAME (factory), preset, preset_name); res = gst_element_factory_create (factory, name); if (preset && GST_IS_PRESET (res)) { if (preset_name == NULL || g_strcmp0 (GST_OBJECT_NAME (factory), preset_name) == 0) { if (!gst_preset_load_preset (GST_PRESET (res), preset)) { GST_WARNING ("Couldn't set preset [%s] on element [%s]", preset, GST_OBJECT_NAME (factory)); gst_object_unref (res); res = NULL; } } else { GST_DEBUG ("Using a preset with no preset name, making use of the" " proper element without setting any property"); } } else if (preset_name && g_strcmp0 (GST_OBJECT_NAME (factory), preset_name)) { gst_object_unref (res); res = NULL; } /* Else we keep it */ return res; } /* Create the encoder for the given stream profile */ static inline GstElement * _get_encoder (GstEncodeBin * ebin, GstEncodingProfile * sprof) { GList *encoders, *tmp; GstElement *encoder = NULL; GstElementFactory *encoderfact = NULL; GstCaps *format; const gchar *preset, *preset_name; format = gst_encoding_profile_get_format (sprof); preset = gst_encoding_profile_get_preset (sprof); preset_name = gst_encoding_profile_get_preset_name (sprof); GST_DEBUG ("Getting list of encoders for format %" GST_PTR_FORMAT, format); /* If stream caps are raw, return identity */ if (G_UNLIKELY (are_raw_caps (format))) { GST_DEBUG ("Stream format is raw, returning identity as the encoder"); encoder = gst_element_factory_make ("identity", NULL); goto beach; } encoders = gst_element_factory_list_filter (ebin->encoders, format, GST_PAD_SRC, FALSE); if (G_UNLIKELY (encoders == NULL)) { GST_DEBUG ("Couldn't find any compatible encoders"); goto beach; } for (tmp = encoders; tmp; tmp = tmp->next) { encoderfact = (GstElementFactory *) tmp->data; if ((encoder = _create_element_and_set_preset (encoderfact, preset, NULL, preset_name))) break; } gst_plugin_feature_list_free (encoders); beach: if (format) gst_caps_unref (format); return encoder; } static GstPad * local_element_request_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps) { GstPad *newpad = NULL; GstElementClass *oclass; oclass = GST_ELEMENT_GET_CLASS (element); if (oclass->request_new_pad) newpad = (oclass->request_new_pad) (element, templ, name, caps); if (newpad) gst_object_ref (newpad); return newpad; } static GstPad * gst_element_get_pad_from_template (GstElement * element, GstPadTemplate * templ) { GstPad *ret = NULL; GstPadPresence presence; /* If this function is ever exported, we need check the validity of `element' * and `templ', and to make sure the template actually belongs to the * element. */ presence = GST_PAD_TEMPLATE_PRESENCE (templ); switch (presence) { case GST_PAD_ALWAYS: case GST_PAD_SOMETIMES: ret = gst_element_get_static_pad (element, templ->name_template); if (!ret && presence == GST_PAD_ALWAYS) g_warning ("Element %s has an ALWAYS template %s, but no pad of the same name", GST_OBJECT_NAME (element), templ->name_template); break; case GST_PAD_REQUEST: ret = gst_element_request_pad (element, templ, NULL, NULL); break; } return ret; } /* FIXME : Improve algorithm for finding compatible muxer sink pad */ static inline GstPad * get_compatible_muxer_sink_pad (GstEncodeBin * ebin, GstElement * encoder, GstCaps * sinkcaps) { GstPad *sinkpad; GstPadTemplate *srctempl = NULL; GstPadTemplate *sinktempl; if (encoder) { GstPad *srcpad; srcpad = gst_element_get_static_pad (encoder, "src"); srctempl = gst_pad_get_pad_template (srcpad); GST_DEBUG_OBJECT (ebin, "Attempting to find pad from muxer %s compatible with %s:%s", GST_ELEMENT_NAME (ebin->muxer), GST_DEBUG_PAD_NAME (srcpad)); gst_object_unref (srcpad); sinktempl = gst_element_get_compatible_pad_template (ebin->muxer, srctempl); gst_object_unref (srctempl); } else { srctempl = gst_pad_template_new ("whatever", GST_PAD_SRC, GST_PAD_ALWAYS, sinkcaps); g_assert (srctempl != NULL); sinktempl = gst_element_get_compatible_pad_template (ebin->muxer, srctempl); g_object_unref (srctempl); } if (G_UNLIKELY (sinktempl == NULL)) goto no_template; sinkpad = gst_element_get_pad_from_template (ebin->muxer, sinktempl); return sinkpad; no_template: { GST_WARNING_OBJECT (ebin, "No compatible pad available on muxer"); return NULL; } } static gboolean _has_class (GstElement * element, const gchar * classname) { GstElementClass *klass; const gchar *value; klass = GST_ELEMENT_GET_CLASS (element); value = gst_element_class_get_metadata (klass, GST_ELEMENT_METADATA_KLASS); return strstr (value, classname) != NULL; } /* FIXME : Add handling of streams that don't need encoding */ /* FIXME : Add handling of streams that don't require conversion elements */ /* * Create the elements, StreamGroup, add the sink pad, link it to the muxer * * sinkpadname: If non-NULL, that name will be assigned to the sink ghost pad * sinkcaps: If non-NULL will be used to figure out how to setup the group */ static StreamGroup * _create_stream_group (GstEncodeBin * ebin, GstEncodingProfile * sprof, const gchar * sinkpadname, GstCaps * sinkcaps) { StreamGroup *sgroup = NULL; GstPad *sinkpad, *srcpad, *muxerpad = NULL; /* Element we will link to the encoder */ GstElement *last = NULL; GList *tmp, *tosync = NULL; GstCaps *format, *restriction; const gchar *missing_element_name; format = gst_encoding_profile_get_format (sprof); restriction = gst_encoding_profile_get_restriction (sprof); GST_DEBUG ("Creating group. format %" GST_PTR_FORMAT ", for caps %" GST_PTR_FORMAT, format, sinkcaps); GST_DEBUG ("avoid_reencoding:%d", ebin->avoid_reencoding); sgroup = g_slice_new0 (StreamGroup); sgroup->ebin = ebin; sgroup->profile = sprof; /* NOTE for people reading this code: * * We construct the group starting by the furthest downstream element * and making our way up adding/syncing/linking as we go. * * There are two parallel paths: * * One for raw data which goes through converters and encoders * * One for already encoded data */ /* Exception to the rule above: * We check if we have an available encoder so we can abort early */ /* FIXME : What if we only want to do passthrough ??? */ GST_LOG ("Checking for encoder availability"); sgroup->encoder = _get_encoder (ebin, sprof); if (G_UNLIKELY (sgroup->encoder == NULL)) goto no_encoder; /* Muxer. * If we are handling a container profile, figure out if the muxer has a * sinkpad compatible with the selected profile */ if (ebin->muxer) { muxerpad = get_compatible_muxer_sink_pad (ebin, NULL, format); if (G_UNLIKELY (muxerpad == NULL)) goto no_muxer_pad; } /* Output Queue. * We only use a 1buffer long queue here, the actual queueing will be done * in the input queue */ last = sgroup->outqueue = gst_element_factory_make ("queue", NULL); g_object_set (sgroup->outqueue, "max-size-buffers", (guint32) 1, "max-size-bytes", (guint32) 0, "max-size-time", (guint64) 0, "silent", TRUE, NULL); gst_bin_add (GST_BIN (ebin), sgroup->outqueue); tosync = g_list_append (tosync, sgroup->outqueue); srcpad = gst_element_get_static_pad (sgroup->outqueue, "src"); if (muxerpad) { if (G_UNLIKELY (fast_pad_link (srcpad, muxerpad) != GST_PAD_LINK_OK)) { goto muxer_link_failure; } gst_object_unref (muxerpad); } else { gst_ghost_pad_set_target (GST_GHOST_PAD (ebin->srcpad), srcpad); } gst_object_unref (srcpad); /* Check if we need a formatter * If we have no muxer or * if the muxer isn't a formatter and doesn't implement the tagsetter interface */ if (!ebin->muxer || (!GST_IS_TAG_SETTER (ebin->muxer) && !_has_class (ebin->muxer, "Formatter"))) { sgroup->formatter = _get_formatter (ebin, sprof); if (sgroup->formatter) { GST_DEBUG ("Adding formatter for %" GST_PTR_FORMAT, format); gst_bin_add (GST_BIN (ebin), sgroup->formatter); tosync = g_list_append (tosync, sgroup->formatter); if (G_UNLIKELY (!fast_element_link (sgroup->formatter, last))) goto formatter_link_failure; last = sgroup->formatter; } } /* Output capsfilter * This will receive the format caps from the streamprofile */ GST_DEBUG ("Adding output capsfilter for %" GST_PTR_FORMAT, format); sgroup->outfilter = gst_element_factory_make ("capsfilter", NULL); g_object_set (sgroup->outfilter, "caps", format, NULL); gst_bin_add (GST_BIN (ebin), sgroup->outfilter); tosync = g_list_append (tosync, sgroup->outfilter); if (G_UNLIKELY (!fast_element_link (sgroup->outfilter, last))) goto outfilter_link_failure; last = sgroup->outfilter; sgroup->parser = _get_parser (ebin, sprof); if (sgroup->parser != NULL) { GST_DEBUG ("Got a parser %s", GST_ELEMENT_NAME (sgroup->parser)); gst_bin_add (GST_BIN (ebin), sgroup->parser); tosync = g_list_append (tosync, sgroup->parser); if (G_UNLIKELY (!gst_element_link (sgroup->parser, last))) goto parser_link_failure; last = sgroup->parser; } /* Stream combiner */ sgroup->combiner = g_object_new (GST_TYPE_STREAM_COMBINER, NULL); gst_bin_add (GST_BIN (ebin), sgroup->combiner); tosync = g_list_append (tosync, sgroup->combiner); if (G_UNLIKELY (!fast_element_link (sgroup->combiner, last))) goto combiner_link_failure; /* Stream splitter */ sgroup->splitter = g_object_new (GST_TYPE_STREAM_SPLITTER, NULL); gst_bin_add (GST_BIN (ebin), sgroup->splitter); tosync = g_list_append (tosync, sgroup->splitter); /* Input queue * FIXME : figure out what max-size to use for the input queue */ sgroup->inqueue = gst_element_factory_make ("queue", NULL); g_object_set (sgroup->inqueue, "max-size-buffers", (guint32) ebin->queue_buffers_max, "max-size-bytes", (guint32) ebin->queue_bytes_max, "max-size-time", (guint64) ebin->queue_time_max, "silent", TRUE, NULL); gst_bin_add (GST_BIN (ebin), sgroup->inqueue); tosync = g_list_append (tosync, sgroup->inqueue); if (G_UNLIKELY (!fast_element_link (sgroup->inqueue, sgroup->splitter))) goto splitter_link_failure; /* Expose input queue sink pad as ghostpad */ sinkpad = gst_element_get_static_pad (sgroup->inqueue, "sink"); if (sinkpadname == NULL) { gchar *pname = g_strdup_printf ("%s_%u", gst_encoding_profile_get_type_nick (sprof), ebin->last_pad_id++); GST_DEBUG ("Adding ghost pad %s", pname); sgroup->ghostpad = gst_ghost_pad_new (pname, sinkpad); g_free (pname); } else sgroup->ghostpad = gst_ghost_pad_new (sinkpadname, sinkpad); gst_object_unref (sinkpad); /* Path 1 : Already-encoded data */ sinkpad = local_element_request_pad (sgroup->combiner, NULL, "passthroughsink", NULL); if (G_UNLIKELY (sinkpad == NULL)) goto no_combiner_sinkpad; if (ebin->avoid_reencoding) { GstCaps *tmpcaps; GST_DEBUG ("Asked to use Smart Encoder"); sgroup->smartencoder = g_object_new (GST_TYPE_SMART_ENCODER, NULL); /* Check if stream format is compatible */ srcpad = gst_element_get_static_pad (sgroup->smartencoder, "src"); tmpcaps = gst_pad_query_caps (srcpad, NULL); if (!gst_caps_can_intersect (tmpcaps, format)) { GST_DEBUG ("We don't have a smart encoder for the stream format"); gst_object_unref (sgroup->smartencoder); sgroup->smartencoder = NULL; } else { gst_bin_add ((GstBin *) ebin, sgroup->smartencoder); fast_pad_link (srcpad, sinkpad); tosync = g_list_append (tosync, sgroup->smartencoder); sinkpad = gst_element_get_static_pad (sgroup->smartencoder, "sink"); } gst_caps_unref (tmpcaps); g_object_unref (srcpad); } srcpad = local_element_request_pad (sgroup->splitter, NULL, "passthroughsrc", NULL); if (G_UNLIKELY (srcpad == NULL)) goto no_splitter_srcpad; /* Go straight to splitter */ if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) goto passthrough_link_failure; g_object_unref (sinkpad); g_object_unref (srcpad); /* Path 2 : Conversion / Encoding */ /* 1. Create the encoder */ GST_LOG ("Adding encoder"); last = sgroup->encoder; gst_bin_add ((GstBin *) ebin, sgroup->encoder); tosync = g_list_append (tosync, sgroup->encoder); sinkpad = local_element_request_pad (sgroup->combiner, NULL, "encodingsink", NULL); if (G_UNLIKELY (sinkpad == NULL)) goto no_combiner_sinkpad; srcpad = gst_element_get_static_pad (sgroup->encoder, "src"); if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) goto encoder_link_failure; g_object_unref (sinkpad); g_object_unref (srcpad); /* 3. Create the conversion/restriction elements */ /* 3.1. capsfilter */ if (restriction && !gst_caps_is_any (restriction)) { GST_LOG ("Adding capsfilter for restriction caps : %" GST_PTR_FORMAT, restriction); last = sgroup->capsfilter = gst_element_factory_make ("capsfilter", NULL); g_object_set (sgroup->capsfilter, "caps", restriction, NULL); gst_bin_add ((GstBin *) ebin, sgroup->capsfilter); tosync = g_list_append (tosync, sgroup->capsfilter); fast_element_link (sgroup->capsfilter, sgroup->encoder); } /* 3.2. restriction elements */ /* FIXME : Once we have properties for specific converters, use those */ if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) { const gboolean native_video = ! !(ebin->flags & GST_ENCODEBIN_FLAG_NO_VIDEO_CONVERSION); GstElement *cspace = NULL, *scale, *vrate, *cspace2 = NULL; GST_LOG ("Adding conversion elements for video stream"); if (!native_video) { cspace = gst_element_factory_make ("videoconvert", NULL); scale = gst_element_factory_make ("videoscale", NULL); if (!scale) { missing_element_name = "videoscale"; goto missing_element; } /* 4-tap scaling and black borders */ g_object_set (scale, "method", 2, "add-borders", TRUE, NULL); cspace2 = gst_element_factory_make ("videoconvert", NULL); if (!cspace || !cspace2) { missing_element_name = "videoconvert"; goto missing_element; } gst_bin_add_many ((GstBin *) ebin, cspace, scale, cspace2, NULL); tosync = g_list_append (tosync, cspace); tosync = g_list_append (tosync, scale); tosync = g_list_append (tosync, cspace2); sgroup->converters = g_list_prepend (sgroup->converters, cspace); sgroup->converters = g_list_prepend (sgroup->converters, scale); sgroup->converters = g_list_prepend (sgroup->converters, cspace2); if (!fast_element_link (cspace, scale) || !fast_element_link (scale, cspace2)) goto converter_link_failure; } if (!gst_encoding_video_profile_get_variableframerate (GST_ENCODING_VIDEO_PROFILE (sprof))) { vrate = gst_element_factory_make ("videorate", NULL); if (!vrate) { missing_element_name = "videorate"; goto missing_element; } gst_bin_add ((GstBin *) ebin, vrate); tosync = g_list_prepend (tosync, vrate); sgroup->converters = g_list_prepend (sgroup->converters, vrate); if ((!native_video && !fast_element_link (cspace2, vrate)) || !fast_element_link (vrate, last)) goto converter_link_failure; if (!native_video) last = cspace; else last = vrate; } else if (!native_video) { if (!fast_element_link (cspace2, last)) goto converter_link_failure; last = cspace; } } else if (GST_IS_ENCODING_AUDIO_PROFILE (sprof) && !(ebin->flags & GST_ENCODEBIN_FLAG_NO_AUDIO_CONVERSION)) { GstElement *aconv, *ares, *arate, *aconv2; GST_LOG ("Adding conversion elements for audio stream"); arate = gst_element_factory_make ("audiorate", NULL); g_object_set (arate, "tolerance", (guint64) ebin->tolerance, NULL); if (!arate) { missing_element_name = "audiorate"; goto missing_element; } aconv = gst_element_factory_make ("audioconvert", NULL); aconv2 = gst_element_factory_make ("audioconvert", NULL); ares = gst_element_factory_make ("audioresample", NULL); if (!aconv || !aconv2) { missing_element_name = "audioconvert"; goto missing_element; } if (!ares) { missing_element_name = "audioresample"; goto missing_element; } gst_bin_add_many ((GstBin *) ebin, arate, aconv, ares, aconv2, NULL); tosync = g_list_append (tosync, arate); tosync = g_list_append (tosync, aconv); tosync = g_list_append (tosync, ares); tosync = g_list_append (tosync, aconv2); if (!fast_element_link (arate, aconv) || !fast_element_link (aconv, ares) || !fast_element_link (ares, aconv2) || !fast_element_link (aconv2, last)) goto converter_link_failure; sgroup->converters = g_list_prepend (sgroup->converters, arate); sgroup->converters = g_list_prepend (sgroup->converters, aconv); sgroup->converters = g_list_prepend (sgroup->converters, ares); sgroup->converters = g_list_prepend (sgroup->converters, aconv2); last = arate; } /* Link to stream splitter */ sinkpad = gst_element_get_static_pad (last, "sink"); srcpad = local_element_request_pad (sgroup->splitter, NULL, "encodingsrc", NULL); if (G_UNLIKELY (srcpad == NULL)) goto no_splitter_srcpad; if (G_UNLIKELY (fast_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)) goto splitter_encoding_failure; g_object_unref (sinkpad); g_object_unref (srcpad); /* End of Stream 2 setup */ /* Sync all elements to parent state */ for (tmp = tosync; tmp; tmp = tmp->next) gst_element_sync_state_with_parent ((GstElement *) tmp->data); g_list_free (tosync); /* Add ghostpad */ GST_DEBUG ("Adding ghostpad %s:%s", GST_DEBUG_PAD_NAME (sgroup->ghostpad)); gst_pad_set_active (sgroup->ghostpad, TRUE); gst_element_add_pad ((GstElement *) ebin, sgroup->ghostpad); /* Add StreamGroup to our list of streams */ GST_DEBUG ("Done creating elements, adding StreamGroup to our controlled stream list"); ebin->streams = g_list_prepend (ebin->streams, sgroup); if (format) gst_caps_unref (format); if (restriction) gst_caps_unref (restriction); return sgroup; splitter_encoding_failure: GST_ERROR_OBJECT (ebin, "Error linking splitter to encoding stream"); goto cleanup; no_encoder: GST_ERROR_OBJECT (ebin, "Couldn't create encoder for format %" GST_PTR_FORMAT, format); /* missing plugin support */ gst_element_post_message (GST_ELEMENT_CAST (ebin), gst_missing_encoder_message_new (GST_ELEMENT_CAST (ebin), format)); GST_ELEMENT_ERROR (ebin, CORE, MISSING_PLUGIN, (NULL), ("Couldn't create encoder for format %" GST_PTR_FORMAT, format)); goto cleanup; no_muxer_pad: GST_ERROR_OBJECT (ebin, "Couldn't find a compatible muxer pad to link encoder to"); goto cleanup; missing_element: gst_element_post_message (GST_ELEMENT_CAST (ebin), gst_missing_element_message_new (GST_ELEMENT_CAST (ebin), missing_element_name)); GST_ELEMENT_ERROR (ebin, CORE, MISSING_PLUGIN, (_("Missing element '%s' - check your GStreamer installation."), missing_element_name), (NULL)); goto cleanup; encoder_link_failure: GST_ERROR_OBJECT (ebin, "Failed to link the encoder"); goto cleanup; muxer_link_failure: GST_ERROR_OBJECT (ebin, "Couldn't link encoder to muxer"); goto cleanup; formatter_link_failure: GST_ERROR_OBJECT (ebin, "Couldn't link output filter to output queue"); goto cleanup; outfilter_link_failure: GST_ERROR_OBJECT (ebin, "Couldn't link output filter to output queue/formatter"); goto cleanup; passthrough_link_failure: GST_ERROR_OBJECT (ebin, "Failed linking splitter in passthrough mode"); goto cleanup; no_splitter_srcpad: GST_ERROR_OBJECT (ebin, "Couldn't get a source pad from the splitter"); goto cleanup; no_combiner_sinkpad: GST_ERROR_OBJECT (ebin, "Couldn't get a sink pad from the combiner"); goto cleanup; splitter_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking to the splitter"); goto cleanup; combiner_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking to the combiner"); goto cleanup; parser_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking the parser"); goto cleanup; converter_link_failure: GST_ERROR_OBJECT (ebin, "Failure linking the video converters"); goto cleanup; cleanup: /* FIXME : Actually properly cleanup everything */ if (format) gst_caps_unref (format); if (restriction) gst_caps_unref (restriction); g_slice_free (StreamGroup, sgroup); return NULL; } static gboolean _gst_caps_match_foreach (GQuark field_id, const GValue * value, gpointer data) { GstStructure *structure = data; const GValue *other_value = gst_structure_id_get_value (structure, field_id); if (G_UNLIKELY (other_value == NULL)) return FALSE; if (gst_value_compare (value, other_value) == GST_VALUE_EQUAL) { return TRUE; } return FALSE; } /* * checks that there is at least one structure on caps_a that has * all its fields exactly the same as one structure on caps_b */ static gboolean _gst_caps_match (const GstCaps * caps_a, const GstCaps * caps_b) { gint i, j; gboolean res = FALSE; for (i = 0; i < gst_caps_get_size (caps_a); i++) { GstStructure *structure_a = gst_caps_get_structure (caps_a, i); for (j = 0; j < gst_caps_get_size (caps_b); j++) { GstStructure *structure_b = gst_caps_get_structure (caps_b, j); res = gst_structure_foreach (structure_a, _gst_caps_match_foreach, structure_b); if (res) goto end; } } end: return res; } static gboolean _factory_can_handle_caps (GstElementFactory * factory, const GstCaps * caps, GstPadDirection dir, gboolean exact) { const GList *templates; templates = gst_element_factory_get_static_pad_templates (factory); while (templates) { GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data; if (template->direction == dir) { GstCaps *tmp = gst_static_caps_get (&template->static_caps); if ((exact && _gst_caps_match (caps, tmp)) || (!exact && gst_caps_can_intersect (tmp, caps))) { gst_caps_unref (tmp); return TRUE; } gst_caps_unref (tmp); } templates = g_list_next (templates); } return FALSE; } static inline GstElement * _get_formatter (GstEncodeBin * ebin, GstEncodingProfile * sprof) { GList *formatters, *tmpfmtr; GstElement *formatter = NULL; GstElementFactory *formatterfact = NULL; GstCaps *format; const gchar *preset, *preset_name; format = gst_encoding_profile_get_format (sprof); preset = gst_encoding_profile_get_preset (sprof); preset_name = gst_encoding_profile_get_preset_name (sprof); GST_DEBUG ("Getting list of formatters for format %" GST_PTR_FORMAT, format); formatters = gst_element_factory_list_filter (ebin->formatters, format, GST_PAD_SRC, FALSE); if (formatters == NULL) goto beach; /* FIXME : signal the user if he wants this */ for (tmpfmtr = formatters; tmpfmtr; tmpfmtr = tmpfmtr->next) { formatterfact = (GstElementFactory *) tmpfmtr->data; GST_DEBUG_OBJECT (ebin, "Trying formatter %s", GST_OBJECT_NAME (formatterfact)); if ((formatter = _create_element_and_set_preset (formatterfact, preset, NULL, preset_name))) break; } gst_plugin_feature_list_free (formatters); beach: if (format) gst_caps_unref (format); return formatter; } static gint compare_elements (gconstpointer a, gconstpointer b, gpointer udata) { GstCaps *caps = udata; GstElementFactory *fac_a = (GstElementFactory *) a; GstElementFactory *fac_b = (GstElementFactory *) b; /* FIXME not quite sure this is the best algorithm to order the elements * Some caps similarity comparison algorithm would fit better than going * boolean (equals/not equals). */ gboolean equals_a = _factory_can_handle_caps (fac_a, caps, GST_PAD_SRC, TRUE); gboolean equals_b = _factory_can_handle_caps (fac_b, caps, GST_PAD_SRC, TRUE); if (equals_a == equals_b) { return gst_plugin_feature_get_rank ((GstPluginFeature *) fac_b) - gst_plugin_feature_get_rank ((GstPluginFeature *) fac_a); } else if (equals_a) { return -1; } else if (equals_b) { return 1; } return 0; } static inline GstElement * _get_muxer (GstEncodeBin * ebin) { GList *muxers, *formatters, *tmpmux; GstElement *muxer = NULL; GstElementFactory *muxerfact = NULL; const GList *tmp; GstCaps *format; const gchar *preset, *preset_name; format = gst_encoding_profile_get_format (ebin->profile); preset = gst_encoding_profile_get_preset (ebin->profile); preset_name = gst_encoding_profile_get_preset_name (ebin->profile); GST_DEBUG ("Getting list of muxers for format %" GST_PTR_FORMAT, format); muxers = gst_element_factory_list_filter (ebin->muxers, format, GST_PAD_SRC, TRUE); formatters = gst_element_factory_list_filter (ebin->formatters, format, GST_PAD_SRC, TRUE); muxers = g_list_sort_with_data (muxers, compare_elements, (gpointer) format); formatters = g_list_sort_with_data (formatters, compare_elements, (gpointer) format); muxers = g_list_concat (muxers, formatters); if (muxers == NULL) goto beach; /* FIXME : signal the user if he wants this */ for (tmpmux = muxers; tmpmux; tmpmux = tmpmux->next) { gboolean cansinkstreams = TRUE; const GList *profiles = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); muxerfact = (GstElementFactory *) tmpmux->data; GST_DEBUG ("Trying muxer %s", GST_OBJECT_NAME (muxerfact)); /* See if the muxer can sink all of our stream profile caps */ for (tmp = profiles; tmp; tmp = tmp->next) { GstEncodingProfile *sprof = (GstEncodingProfile *) tmp->data; GstCaps *sformat = gst_encoding_profile_get_format (sprof); if (!_factory_can_handle_caps (muxerfact, sformat, GST_PAD_SINK, FALSE)) { GST_DEBUG ("Skipping muxer because it can't sink caps %" GST_PTR_FORMAT, sformat); cansinkstreams = FALSE; if (sformat) gst_caps_unref (sformat); break; } if (sformat) gst_caps_unref (sformat); } /* Only use a muxer than can use all streams and than can accept the * preset (which may be present or not) */ if (cansinkstreams && (muxer = _create_element_and_set_preset (muxerfact, preset, "muxer", preset_name))) break; } gst_plugin_feature_list_free (muxers); beach: if (format) gst_caps_unref (format); return muxer; } static gboolean create_elements_and_pads (GstEncodeBin * ebin) { gboolean ret = TRUE; GstElement *muxer = NULL; GstPad *muxerpad; const GList *tmp, *profiles; GstEncodingProfile *sprof; GST_DEBUG ("Current profile : %s", gst_encoding_profile_get_name (ebin->profile)); if (GST_IS_ENCODING_CONTAINER_PROFILE (ebin->profile)) { /* 1. Get the compatible muxer */ muxer = _get_muxer (ebin); if (G_UNLIKELY (muxer == NULL)) goto no_muxer; /* Record the muxer */ ebin->muxer = muxer; gst_bin_add ((GstBin *) ebin, muxer); /* 2. Ghost the muxer source pad */ /* FIXME : We should figure out if it's a static/request/dyamic pad, * but for the time being let's assume it's a static pad :) */ muxerpad = gst_element_get_static_pad (muxer, "src"); if (G_UNLIKELY (muxerpad == NULL)) goto no_muxer_pad; if (!gst_ghost_pad_set_target (GST_GHOST_PAD (ebin->srcpad), muxerpad)) goto no_muxer_ghost_pad; gst_object_unref (muxerpad); /* 3. Activate fixed presence streams */ profiles = gst_encoding_container_profile_get_profiles (GST_ENCODING_CONTAINER_PROFILE (ebin->profile)); for (tmp = profiles; tmp; tmp = tmp->next) { sprof = (GstEncodingProfile *) tmp->data; GST_DEBUG ("Trying stream profile with presence %d", gst_encoding_profile_get_presence (sprof)); if (gst_encoding_profile_get_presence (sprof) != 0) { if (G_UNLIKELY (_create_stream_group (ebin, sprof, NULL, NULL) == NULL)) goto stream_error; } } gst_element_sync_state_with_parent (muxer); } else { if (G_UNLIKELY (_create_stream_group (ebin, ebin->profile, NULL, NULL) == NULL)) goto stream_error; } return ret; no_muxer: { GstCaps *format = gst_encoding_profile_get_format (ebin->profile); GST_WARNING ("No available muxer for %" GST_PTR_FORMAT, format); /* missing plugin support */ gst_element_post_message (GST_ELEMENT_CAST (ebin), gst_missing_encoder_message_new (GST_ELEMENT_CAST (ebin), format)); GST_ELEMENT_ERROR (ebin, CORE, MISSING_PLUGIN, (NULL), ("No available muxer for format %" GST_PTR_FORMAT, format)); if (format) gst_caps_unref (format); return FALSE; } no_muxer_pad: { GST_WARNING ("Can't get source pad from muxer (%s)", GST_ELEMENT_NAME (muxer)); gst_bin_remove (GST_BIN (ebin), muxer); return FALSE; } no_muxer_ghost_pad: { GST_WARNING ("Couldn't set %s:%s as source ghostpad target", GST_DEBUG_PAD_NAME (muxerpad)); gst_bin_remove (GST_BIN (ebin), muxer); gst_object_unref (muxerpad); return FALSE; } stream_error: { GST_WARNING ("Could not create Streams"); if (muxer) gst_bin_remove (GST_BIN (ebin), muxer); ebin->muxer = NULL; return FALSE; } } static void release_pads (const GValue * item, GstElement * elt) { GstPad *pad = g_value_get_object (item); GstPad *peer = NULL; GST_DEBUG_OBJECT (elt, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad)); /* Unlink from its peer pad */ if ((peer = gst_pad_get_peer (pad))) { if (GST_PAD_DIRECTION (peer) == GST_PAD_SRC) gst_pad_unlink (peer, pad); else gst_pad_unlink (pad, peer); gst_object_unref (peer); } /* Release it from the object */ gst_element_release_request_pad (elt, pad); } static void inline stream_group_free (GstEncodeBin * ebin, StreamGroup * sgroup) { GList *tmp; GstPad *tmppad; GstPad *pad; GST_DEBUG_OBJECT (ebin, "Freeing StreamGroup %p", sgroup); if (ebin->muxer) { /* outqueue - Muxer */ tmppad = gst_element_get_static_pad (sgroup->outqueue, "src"); pad = gst_pad_get_peer (tmppad); /* Remove muxer request sink pad */ gst_pad_unlink (tmppad, pad); if (GST_PAD_TEMPLATE_PRESENCE (GST_PAD_PAD_TEMPLATE (pad)) == GST_PAD_REQUEST) gst_element_release_request_pad (ebin->muxer, pad); gst_object_unref (tmppad); gst_object_unref (pad); } if (sgroup->outqueue) gst_element_set_state (sgroup->outqueue, GST_STATE_NULL); if (sgroup->formatter) { /* capsfilter - formatter - outqueue */ gst_element_set_state (sgroup->formatter, GST_STATE_NULL); gst_element_set_state (sgroup->outfilter, GST_STATE_NULL); gst_element_unlink (sgroup->formatter, sgroup->outqueue); gst_element_unlink (sgroup->outfilter, sgroup->formatter); } else { /* Capsfilter - outqueue */ gst_element_set_state (sgroup->outfilter, GST_STATE_NULL); gst_element_unlink (sgroup->outfilter, sgroup->outqueue); } gst_element_set_state (sgroup->outqueue, GST_STATE_NULL); gst_bin_remove (GST_BIN (ebin), sgroup->outqueue); /* streamcombiner - parser - capsfilter */ if (sgroup->parser) { gst_element_set_state (sgroup->parser, GST_STATE_NULL); gst_element_unlink (sgroup->parser, sgroup->outfilter); gst_element_unlink (sgroup->combiner, sgroup->parser); gst_bin_remove ((GstBin *) ebin, sgroup->parser); } /* Sink Ghostpad */ if (sgroup->ghostpad) gst_element_remove_pad (GST_ELEMENT_CAST (ebin), sgroup->ghostpad); if (sgroup->inqueue) gst_element_set_state (sgroup->inqueue, GST_STATE_NULL); if (sgroup->encoder) gst_element_set_state (sgroup->encoder, GST_STATE_NULL); if (sgroup->outfilter) gst_element_set_state (sgroup->outfilter, GST_STATE_NULL); if (sgroup->smartencoder) gst_element_set_state (sgroup->smartencoder, GST_STATE_NULL); if (sgroup->capsfilter) { gst_element_set_state (sgroup->capsfilter, GST_STATE_NULL); gst_element_unlink (sgroup->capsfilter, sgroup->encoder); gst_bin_remove ((GstBin *) ebin, sgroup->capsfilter); } for (tmp = sgroup->converters; tmp; tmp = tmp->next) { GstElement *elt = (GstElement *) tmp->data; gst_element_set_state (elt, GST_STATE_NULL); gst_bin_remove ((GstBin *) ebin, elt); } if (sgroup->converters) g_list_free (sgroup->converters); if (sgroup->combiner) { GstIterator *it = gst_element_iterate_sink_pads (sgroup->combiner); GstIteratorResult itret = GST_ITERATOR_OK; while (itret == GST_ITERATOR_OK || itret == GST_ITERATOR_RESYNC) { itret = gst_iterator_foreach (it, (GstIteratorForeachFunction) release_pads, sgroup->combiner); gst_iterator_resync (it); } gst_iterator_free (it); gst_element_set_state (sgroup->combiner, GST_STATE_NULL); gst_bin_remove ((GstBin *) ebin, sgroup->combiner); } if (sgroup->splitter) { GstIterator *it = gst_element_iterate_src_pads (sgroup->splitter); GstIteratorResult itret = GST_ITERATOR_OK; while (itret == GST_ITERATOR_OK || itret == GST_ITERATOR_RESYNC) { itret = gst_iterator_foreach (it, (GstIteratorForeachFunction) release_pads, sgroup->splitter); gst_iterator_resync (it); } gst_iterator_free (it); gst_element_set_state (sgroup->splitter, GST_STATE_NULL); gst_bin_remove ((GstBin *) ebin, sgroup->splitter); } if (sgroup->inqueue) gst_bin_remove ((GstBin *) ebin, sgroup->inqueue); if (sgroup->encoder) gst_bin_remove ((GstBin *) ebin, sgroup->encoder); if (sgroup->smartencoder) gst_bin_remove ((GstBin *) ebin, sgroup->smartencoder); if (sgroup->outfilter) gst_bin_remove ((GstBin *) ebin, sgroup->outfilter); g_slice_free (StreamGroup, sgroup); } static void stream_group_remove (GstEncodeBin * ebin, StreamGroup * sgroup) { ebin->streams = g_list_remove (ebin->streams, sgroup); stream_group_free (ebin, sgroup); } static void gst_encode_bin_tear_down_profile (GstEncodeBin * ebin) { if (G_UNLIKELY (ebin->profile == NULL)) return; GST_DEBUG ("Tearing down profile %s", gst_encoding_profile_get_name (ebin->profile)); while (ebin->streams) stream_group_remove (ebin, (StreamGroup *) ebin->streams->data); /* Set ghostpad target to NULL */ gst_ghost_pad_set_target (GST_GHOST_PAD (ebin->srcpad), NULL); /* Remove muxer if present */ if (ebin->muxer) { gst_element_set_state (ebin->muxer, GST_STATE_NULL); gst_bin_remove (GST_BIN (ebin), ebin->muxer); ebin->muxer = NULL; } /* free/clear profile */ gst_encoding_profile_unref (ebin->profile); ebin->profile = NULL; } static gboolean gst_encode_bin_setup_profile (GstEncodeBin * ebin, GstEncodingProfile * profile) { gboolean res; g_return_val_if_fail (ebin->profile == NULL, FALSE); GST_DEBUG ("Setting up profile %p:%s (type:%s)", profile, gst_encoding_profile_get_name (profile), gst_encoding_profile_get_type_nick (profile)); ebin->profile = profile; gst_object_ref (ebin->profile); /* Create elements */ res = create_elements_and_pads (ebin); if (res == FALSE) gst_encode_bin_tear_down_profile (ebin); return res; } static gboolean gst_encode_bin_set_profile (GstEncodeBin * ebin, GstEncodingProfile * profile) { g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE); GST_DEBUG_OBJECT (ebin, "profile (%p) : %s", profile, gst_encoding_profile_get_name (profile)); if (G_UNLIKELY (ebin->active)) { GST_WARNING_OBJECT (ebin, "Element already active, can't change profile"); return FALSE; } /* If we're not active, we can deactivate the previous profile */ if (ebin->profile) { gst_encode_bin_tear_down_profile (ebin); } return gst_encode_bin_setup_profile (ebin, profile); } static inline gboolean gst_encode_bin_activate (GstEncodeBin * ebin) { ebin->active = ebin->profile != NULL; return ebin->active; } static void gst_encode_bin_deactivate (GstEncodeBin * ebin) { ebin->active = FALSE; } static GstStateChangeReturn gst_encode_bin_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstEncodeBin *ebin = (GstEncodeBin *) element; switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: case GST_STATE_CHANGE_PAUSED_TO_PLAYING: if (!gst_encode_bin_activate (ebin)) { ret = GST_STATE_CHANGE_FAILURE; goto beach; } break; default: break; } ret = GST_ELEMENT_CLASS (gst_encode_bin_parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) goto beach; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_encode_bin_deactivate (ebin); break; default: break; } beach: return ret; } static gboolean plugin_init (GstPlugin * plugin) { gboolean res; GST_DEBUG_CATEGORY_INIT (gst_encode_bin_debug, "encodebin", 0, "encoder bin"); #ifdef ENABLE_NLS GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, LOCALEDIR); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); #endif /* ENABLE_NLS */ res = gst_element_register (plugin, "encodebin", GST_RANK_NONE, GST_TYPE_ENCODE_BIN); return res; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, encoding, "various encoding-related elements", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)