/* multipart muxer plugin for GStreamer * Copyright (C) 2004 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include GST_DEBUG_CATEGORY_STATIC (gst_multipart_mux_debug); #define GST_CAT_DEFAULT gst_multipart_mux_debug #define GST_TYPE_MULTIPART_MUX (gst_multipart_mux_get_type()) #define GST_MULTIPART_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MULTIPART_MUX, GstMultipartMux)) #define GST_MULTIPART_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MULTIPART_MUX, GstMultipartMux)) #define GST_IS_MULTIPART_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MULTIPART_MUX)) #define GST_IS_MULTIPART_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MULTIPART_MUX)) typedef struct _GstMultipartMux GstMultipartMux; typedef struct _GstMultipartMuxClass GstMultipartMuxClass; /* all information needed for one multipart stream */ typedef struct { GstPad *pad; /* reference for this pad is held by element we belong to */ GstBuffer *buffer; /* the queued buffer for this pad */ gboolean eos; const gchar *mimetype; guint state; /* state of the pad */ } GstMultipartPad; struct _GstMultipartMux { GstElement element; /* pad */ GstPad *srcpad; /* sinkpads, a GSList of GstMultipartPads */ GSList *sinkpads; gint numpads; /* offset in stream */ guint64 offset; /* boundary string */ gchar *boundary; gboolean negotiated; }; struct _GstMultipartMuxClass { GstElementClass parent_class; }; /* elementfactory information */ static GstElementDetails gst_multipart_mux_details = GST_ELEMENT_DETAILS ("multipart muxer", "Codec/Muxer", "mux multipart streams", "Wim Taymans "); /* MultipartMux signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; #define DEFAULT_BOUNDARY "ThisRandomString" enum { ARG_0, ARG_BOUNDARY, }; static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("multipart/x-mixed-replace") ); static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY /* we can take anything, really */ ); static void gst_multipart_mux_base_init (gpointer g_class); static void gst_multipart_mux_class_init (GstMultipartMuxClass * klass); static void gst_multipart_mux_init (GstMultipartMux * multipart_mux); static void gst_multipart_mux_loop (GstElement * element); static gboolean gst_multipart_mux_handle_src_event (GstPad * pad, GstEvent * event); static GstPad *gst_multipart_mux_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name); static void gst_multipart_mux_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_multipart_mux_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstElementStateReturn gst_multipart_mux_change_state (GstElement * element); static GstElementClass *parent_class = NULL; /*static guint gst_multipart_mux_signals[LAST_SIGNAL] = { 0 }; */ GType gst_multipart_mux_get_type (void) { static GType multipart_mux_type = 0; if (!multipart_mux_type) { static const GTypeInfo multipart_mux_info = { sizeof (GstMultipartMuxClass), gst_multipart_mux_base_init, NULL, (GClassInitFunc) gst_multipart_mux_class_init, NULL, NULL, sizeof (GstMultipartMux), 0, (GInstanceInitFunc) gst_multipart_mux_init, }; multipart_mux_type = g_type_register_static (GST_TYPE_ELEMENT, "GstMultipartMux", &multipart_mux_info, 0); } return multipart_mux_type; } static void gst_multipart_mux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&src_factory)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_factory)); gst_element_class_set_details (element_class, &gst_multipart_mux_details); } static void gst_multipart_mux_class_init (GstMultipartMuxClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; parent_class = g_type_class_ref (GST_TYPE_ELEMENT); gstelement_class->request_new_pad = gst_multipart_mux_request_new_pad; gstelement_class->change_state = gst_multipart_mux_change_state; gstelement_class->get_property = gst_multipart_mux_get_property; gstelement_class->set_property = gst_multipart_mux_set_property; g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BOUNDARY, g_param_spec_string ("boundary", "Boundary", "Boundary string", DEFAULT_BOUNDARY, G_PARAM_READWRITE)); } static const GstEventMask * gst_multipart_mux_get_sink_event_masks (GstPad * pad) { static const GstEventMask gst_multipart_mux_sink_event_masks[] = { {GST_EVENT_EOS, 0}, {0,} }; return gst_multipart_mux_sink_event_masks; } static void gst_multipart_mux_init (GstMultipartMux * multipart_mux) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (multipart_mux); multipart_mux->srcpad = gst_pad_new_from_template (gst_element_class_get_pad_template (klass, "src"), "src"); gst_pad_set_event_function (multipart_mux->srcpad, gst_multipart_mux_handle_src_event); gst_element_add_pad (GST_ELEMENT (multipart_mux), multipart_mux->srcpad); GST_FLAG_SET (GST_ELEMENT (multipart_mux), GST_ELEMENT_EVENT_AWARE); multipart_mux->sinkpads = NULL; multipart_mux->boundary = g_strdup (DEFAULT_BOUNDARY); multipart_mux->negotiated = FALSE; gst_element_set_loop_function (GST_ELEMENT (multipart_mux), gst_multipart_mux_loop); } static GstPadLinkReturn gst_multipart_mux_sinkconnect (GstPad * pad, const GstCaps * vscaps) { GstMultipartMux *multipart_mux; GstMultipartPad *mppad; GstStructure *structure; multipart_mux = GST_MULTIPART_MUX (gst_pad_get_parent (pad)); mppad = (GstMultipartPad *) gst_pad_get_element_private (pad); GST_DEBUG ("multipart_mux: sinkconnect triggered on %s", gst_pad_get_name (pad)); structure = gst_caps_get_structure (vscaps, 0); mppad->mimetype = gst_structure_get_name (structure); return GST_PAD_LINK_OK; } static void gst_multipart_mux_pad_link (GstPad * pad, GstPad * peer, gpointer data) { //GstMultipartMux *multipart_mux = GST_MULTIPART_MUX (data); const gchar *padname = gst_pad_get_name (pad); GST_DEBUG ("pad '%s' connected", padname); } static void gst_multipart_mux_pad_unlink (GstPad * pad, GstPad * peer, gpointer data) { //GstMultipartMux *multipart_mux = GST_MULTIPART_MUX (data); const gchar *padname = gst_pad_get_name (pad); GST_DEBUG ("pad '%s' unlinked", padname); } static GstPad * gst_multipart_mux_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * req_name) { GstMultipartMux *multipart_mux; GstPad *newpad; GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); g_return_val_if_fail (templ != NULL, NULL); if (templ->direction != GST_PAD_SINK) { g_warning ("multipart_mux: request pad that is not a SINK pad\n"); return NULL; } g_return_val_if_fail (GST_IS_MULTIPART_MUX (element), NULL); multipart_mux = GST_MULTIPART_MUX (element); if (templ == gst_element_class_get_pad_template (klass, "sink_%d")) { gchar *name; /* create new pad with the name */ name = g_strdup_printf ("sink_%02d", multipart_mux->numpads); newpad = gst_pad_new_from_template (templ, name); g_free (name); /* construct our own wrapper data structure for the pad to * keep track of its status */ { GstMultipartPad *multipartpad = g_new0 (GstMultipartPad, 1); multipartpad->pad = newpad; multipartpad->eos = FALSE; /* save a pointer to our data in the pad */ gst_pad_set_element_private (newpad, multipartpad); /* store our data for the pad */ multipart_mux->sinkpads = g_slist_prepend (multipart_mux->sinkpads, multipartpad); multipart_mux->numpads++; } } else { g_warning ("multipart_mux: this is not our template!\n"); return NULL; } g_signal_connect (newpad, "linked", G_CALLBACK (gst_multipart_mux_pad_link), (gpointer) multipart_mux); g_signal_connect (newpad, "unlinked", G_CALLBACK (gst_multipart_mux_pad_unlink), (gpointer) multipart_mux); /* setup some pad functions */ gst_pad_set_link_function (newpad, gst_multipart_mux_sinkconnect); gst_pad_set_event_mask_function (newpad, gst_multipart_mux_get_sink_event_masks); /* dd the pad to the element */ gst_element_add_pad (element, newpad); return newpad; } /* handle events */ static gboolean gst_multipart_mux_handle_src_event (GstPad * pad, GstEvent * event) { GstMultipartMux *multipart_mux; GstEventType type; multipart_mux = GST_MULTIPART_MUX (gst_pad_get_parent (pad)); type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; switch (type) { case GST_EVENT_SEEK: /* disable seeking for now */ return FALSE; default: break; } return gst_pad_event_default (pad, event); } static GstBuffer * gst_multipart_mux_next_buffer (GstMultipartPad * pad) { GstData *data = NULL; while (data == NULL) { GST_LOG ("muxer: pulling %s:%s", GST_DEBUG_PAD_NAME (pad->pad)); data = gst_pad_pull (pad->pad); /* if it's an event, handle it */ if (GST_IS_EVENT (data)) { GstEventType type; GstMultipartMux *multipart_mux; GstEvent *event = GST_EVENT (data); multipart_mux = GST_MULTIPART_MUX (gst_pad_get_parent (pad->pad)); type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; switch (type) { case GST_EVENT_EOS: return NULL; default: gst_pad_event_default (pad->pad, event); break; } data = NULL; } } return GST_BUFFER (data); } /* * Given two pads, compare the buffers queued on it and return 0 if they have * an equal priority, 1 if the new pad is better, -1 if the old pad is better */ static gint gst_multipart_mux_compare_pads (GstMultipartMux * multipart_mux, GstMultipartPad * old, GstMultipartPad * new) { guint64 oldtime, newtime; /* if the old pad doesn't contain anything or is even NULL, return * the new pad as best candidate and vice versa */ if (old == NULL || old->buffer == NULL) return 1; if (new == NULL || new->buffer == NULL) return -1; /* no timestamp on old buffer, it must go first */ oldtime = GST_BUFFER_TIMESTAMP (old->buffer); if (oldtime == GST_CLOCK_TIME_NONE) return -1; /* no timestamp on new buffer, it must go first */ newtime = GST_BUFFER_TIMESTAMP (new->buffer); if (newtime == GST_CLOCK_TIME_NONE) return 1; /* old buffer has higher timestamp, new one should go first */ if (newtime < oldtime) return 1; /* new buffer has higher timestamp, old one should go first */ else if (newtime > oldtime) return -1; /* same priority if all of the above failed */ return 0; } /* make sure a buffer is queued on all pads, returns a pointer to an multipartpad * that holds the best buffer or NULL when no pad was usable */ static GstMultipartPad * gst_multipart_mux_queue_pads (GstMultipartMux * multipart_mux) { GstMultipartPad *bestpad = NULL; GSList *walk; /* try to make sure we have a buffer from each usable pad first */ walk = multipart_mux->sinkpads; while (walk) { GstMultipartPad *pad = (GstMultipartPad *) walk->data; walk = walk->next; /* try to get a new buffer for this pad if needed and possible */ if (pad->buffer == NULL && GST_PAD_IS_USABLE (pad->pad)) { pad->buffer = gst_multipart_mux_next_buffer (pad); /* no next buffer, try another pad */ if (pad->buffer == NULL) continue; } /* skip unusable pads */ if (!GST_PAD_IS_USABLE (pad->pad)) continue; /* we should have a buffer now, see if it is the best pad to * pull on */ if (pad->buffer != NULL) { if (gst_multipart_mux_compare_pads (multipart_mux, bestpad, pad) > 0) { bestpad = pad; } } } return bestpad; } /* basic idea: * * 1) find a pad to pull on, this is done by pulling on all pads and * looking at the buffers to decide which one should be muxed first. * 2) push buffer on best pad, go to 1 */ static void gst_multipart_mux_loop (GstElement * element) { GstMultipartMux *mux; GstMultipartPad *pad; GstBuffer *newbuf, *buf; gchar *header; gint headerlen; gint newlen; mux = GST_MULTIPART_MUX (element); /* we don't know which pad to pull on, find one */ pad = gst_multipart_mux_queue_pads (mux); if (pad == NULL) { /* no pad to pull on, send EOS */ if (GST_PAD_IS_USABLE (mux->srcpad)) gst_pad_push (mux->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); gst_element_set_eos (element); return; } /* now see if we have a buffer */ buf = pad->buffer; if (buf == NULL) { /* no buffer, get one */ buf = gst_multipart_mux_next_buffer (pad); if (buf == NULL) { /* data exhausted on this pad (EOS) */ return; } } /* FIXME, negotiated is not set to FALSE properly after * reconnect */ if (!mux->negotiated) { GstCaps *newcaps; newcaps = gst_caps_new_simple ("multipart/x-mixed-replace", "boundary", G_TYPE_STRING, mux->boundary, NULL); if (GST_PAD_LINK_FAILED (gst_pad_try_set_caps (mux->srcpad, newcaps))) { GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL)); return; } mux->negotiated = TRUE; } header = g_strdup_printf ("\n--%s\nContent-type: %s\n\n", mux->boundary, pad->mimetype); headerlen = strlen (header); newlen = headerlen + GST_BUFFER_SIZE (buf); newbuf = gst_pad_alloc_buffer (mux->srcpad, GST_BUFFER_OFFSET_NONE, newlen); memcpy (GST_BUFFER_DATA (newbuf), header, headerlen); memcpy (GST_BUFFER_DATA (newbuf) + headerlen, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); GST_BUFFER_TIMESTAMP (newbuf) = GST_BUFFER_TIMESTAMP (buf); GST_BUFFER_DURATION (newbuf) = GST_BUFFER_DURATION (buf); GST_BUFFER_OFFSET (newbuf) = mux->offset; g_free (header); mux->offset += newlen; gst_pad_push (mux->srcpad, GST_DATA (newbuf)); gst_buffer_unref (buf); pad->buffer = NULL; } static void gst_multipart_mux_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstMultipartMux *mux; mux = GST_MULTIPART_MUX (object); switch (prop_id) { case ARG_BOUNDARY: g_value_set_string (value, mux->boundary); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_multipart_mux_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMultipartMux *mux; mux = GST_MULTIPART_MUX (object); switch (prop_id) { case ARG_BOUNDARY: g_free (mux->boundary); mux->boundary = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstElementStateReturn gst_multipart_mux_change_state (GstElement * element) { GstMultipartMux *multipart_mux; gint transition = GST_STATE_TRANSITION (element); g_return_val_if_fail (GST_IS_MULTIPART_MUX (element), GST_STATE_FAILURE); multipart_mux = GST_MULTIPART_MUX (element); switch (transition) { case GST_STATE_NULL_TO_READY: case GST_STATE_READY_TO_PAUSED: multipart_mux->offset = 0; multipart_mux->negotiated = FALSE; break; case GST_STATE_PAUSED_TO_PLAYING: case GST_STATE_PLAYING_TO_PAUSED: case GST_STATE_PAUSED_TO_READY: case GST_STATE_READY_TO_NULL: break; } if (GST_ELEMENT_CLASS (parent_class)->change_state) return GST_ELEMENT_CLASS (parent_class)->change_state (element); return GST_STATE_SUCCESS; } gboolean gst_multipart_mux_plugin_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (gst_multipart_mux_debug, "multipartmux", 0, "multipart muxer"); return gst_element_register (plugin, "multipartmux", GST_RANK_NONE, GST_TYPE_MULTIPART_MUX); }