/* GStreamer * Copyright (C) 2004 Wim Taymans * * gstmultipartdemux.c: multipart stream demuxer * * 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. */ /** * SECTION:element-multipartdemux * @short_description: Demuxer that takes a multipart digital stream as input * and demuxes one or many digital streams from it. * @see_also: #GstMultipartMux * * * * MultipartDemux uses the Content-type field of incoming buffers to demux and * push data to dynamic source pads. Most of the time multipart streams are * sequential JPEG frames generated from a live source such as a network source * or a camera. * * Sample pipelines * * Here is a simple pipeline to demux a multipart file muxed with * #GstMultipartMux containing JPEG frames at a rate of 5 frames per second : * * gst-launch filesrc location=/tmp/test.multipart ! multipartdemux ! jpegdec ! video/x-raw-yuv, framerate=(fraction)5/1 ! ffmpegcolorspace ! ximagesink * * * * The output buffers of the multipartdemux typically have no timestamps and are usually * played as fast as possible (at the rate that the source provides the data). * * * the content in multipart files is separated with a boundary string that can be * configured specifically with the "boundary" property or can be autodetected by * setting the "autoscan" property to TRUE. * * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #define GST_TYPE_MULTIPART_DEMUX (gst_multipart_demux_get_type()) #define GST_MULTIPART_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MULTIPART_DEMUX, GstMultipartDemux)) #define GST_MULTIPART_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MULTIPART_DEMUX, GstMultipartDemux)) #define GST_IS_MULTIPART_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MULTIPART_DEMUX)) #define GST_IS_MULTIPART_DEMUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MULTIPART_DEMUX)) typedef struct _GstMultipartDemux GstMultipartDemux; typedef struct _GstMultipartDemuxClass GstMultipartDemuxClass; #define MAX_LINE_LEN 500 /* all information needed for one multipart stream */ typedef struct { GstPad *pad; /* reference for this pad is held by element we belong to */ gchar *mime; guint64 offset; /* end offset of last buffer */ guint64 known_offset; /* last known offset */ guint flags; } GstMultipartPad; /** * GstMultipartDemux: * * The opaque #GstMultipartDemux structure. */ struct _GstMultipartDemux { GstElement element; /* pad */ GstPad *sinkpad; GSList *srcpads; gint numpads; gchar *parsing_mime; gchar *buffer; gint maxlen; gint bufsize; gint scanpos; gint lastpos; gchar *prefix; gint prefixLen; gboolean first_frame; gboolean autoscan; }; struct _GstMultipartDemuxClass { GstElementClass parent_class; }; GST_DEBUG_CATEGORY_STATIC (gst_multipart_demux_debug); #define GST_CAT_DEFAULT gst_multipart_demux_debug /* elementfactory information */ static const GstElementDetails gst_multipart_demux_details = GST_ELEMENT_DETAILS ("Multipart demuxer", "Codec/Demuxer", "demux multipart streams", "Wim Taymans "); /* signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; #define DEFAULT_BOUNDARY "ThisRandomString" #define DEFAULT_AUTOSCAN FALSE enum { PROP_0, PROP_BOUNDARY, PROP_AUTOSCAN /* FILL ME */ }; static GstStaticPadTemplate multipart_demux_src_template_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate multipart_demux_sink_template_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("multipart/x-mixed-replace") ); static GstFlowReturn gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf); static GstStateChangeReturn gst_multipart_demux_change_state (GstElement * element, GstStateChange transition); static void gst_multipart_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_multipart_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_multipart_demux_finalize (GObject * object); GST_BOILERPLATE (GstMultipartDemux, gst_multipart_demux, GstElement, GST_TYPE_ELEMENT) static void gst_multipart_demux_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); gst_element_class_set_details (element_class, &gst_multipart_demux_details); gobject_class->set_property = gst_multipart_set_property; gobject_class->get_property = gst_multipart_get_property; gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&multipart_demux_sink_template_factory)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&multipart_demux_src_template_factory)); g_object_class_install_property (gobject_class, PROP_BOUNDARY, g_param_spec_string ("boundary", "Boundary", "The boundary string separating data", DEFAULT_BOUNDARY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PROP_AUTOSCAN, g_param_spec_boolean ("autoscan", "autoscan", "Try to autofind the prefix", DEFAULT_AUTOSCAN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void gst_multipart_demux_class_init (GstMultipartDemuxClass * klass) { GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gstelement_class->change_state = gst_multipart_demux_change_state; gobject_class->finalize = gst_multipart_demux_finalize; } static void gst_multipart_demux_init (GstMultipartDemux * multipart, GstMultipartDemuxClass * g_class) { /* create the sink pad */ multipart->sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&multipart_demux_sink_template_factory), "sink"); gst_element_add_pad (GST_ELEMENT (multipart), multipart->sinkpad); gst_pad_set_chain_function (multipart->sinkpad, GST_DEBUG_FUNCPTR (gst_multipart_demux_chain)); multipart->maxlen = 4096; } static void gst_multipart_demux_finalize (GObject * object) { GstMultipartDemux *demux = GST_MULTIPART_DEMUX (object); g_free (demux->prefix); G_OBJECT_CLASS (parent_class)->finalize (object); } static GstMultipartPad * gst_multipart_find_pad_by_mime (GstMultipartDemux * demux, gchar * mime, gboolean * created) { GSList *walk; walk = demux->srcpads; while (walk) { GstMultipartPad *pad = (GstMultipartPad *) walk->data; if (!strcmp (pad->mime, mime)) { if (created) { *created = FALSE; } return pad; } walk = walk->next; } /* pad not found, create it */ { GstPad *pad; GstMultipartPad *mppad; gchar *name; GstCaps *caps; mppad = g_new0 (GstMultipartPad, 1); GST_DEBUG_OBJECT (demux, "creating pad with mime: %s", mime); name = g_strdup_printf ("src_%d", demux->numpads); pad = gst_pad_new_from_template (gst_static_pad_template_get (&multipart_demux_src_template_factory), name); g_free (name); caps = gst_caps_from_string (mime); gst_pad_use_fixed_caps (pad); gst_pad_set_caps (pad, caps); mppad->pad = pad; mppad->mime = g_strdup (mime); demux->srcpads = g_slist_prepend (demux->srcpads, mppad); demux->numpads++; gst_element_add_pad (GST_ELEMENT (demux), pad); if (created) { *created = TRUE; } return mppad; } } static GstFlowReturn gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf) { GstMultipartDemux *multipart; gint size, matchpos; guchar *data; GstFlowReturn ret = GST_FLOW_OK; multipart = GST_MULTIPART_DEMUX (gst_pad_get_parent (pad)); data = GST_BUFFER_DATA (buf); size = GST_BUFFER_SIZE (buf); /* first make sure our buffer is long enough */ if (multipart->bufsize + size > multipart->maxlen) { gint newsize = (multipart->bufsize + size) * 2; multipart->buffer = g_realloc (multipart->buffer, newsize); multipart->maxlen = newsize; } /* copy bytes into the buffer */ memcpy (multipart->buffer + multipart->bufsize, data, size); multipart->bufsize += size; if (multipart->first_frame && multipart->autoscan) { /* find the prefix if this is the first buffer */ /* the prefix is like --prefix\r\n */ size_t i, start; i = 0; start = -1; while ((i + 1) < multipart->bufsize) { if (-1 == start) { if ((multipart->buffer[i] == '-') && (multipart->buffer[i + 1] == '-')) { start = i + 2; /* discart -- */ } } else { /* look for \r\n or \n\n */ if ((multipart->buffer[i] == '\n') || ((multipart->buffer[i] == '\r') && (multipart->buffer[i + 1] == '\n'))) { /* found first \r\n, the prefix is from 0 to i */ g_free (multipart->prefix); multipart->prefix = g_strndup (multipart->buffer + start, (i - start)); multipart->prefixLen = strlen (multipart->prefix); GST_DEBUG_OBJECT (multipart, "set prefix to [%s]\n", multipart->prefix); multipart->first_frame = FALSE; break; } } i++; } } /* find \n */ while (multipart->scanpos < multipart->bufsize) { if (multipart->buffer[multipart->scanpos] == '\n') { break; } multipart->scanpos++; } /* then scan for the boundary */ for (matchpos = 0; multipart->scanpos + multipart->prefixLen + MAX_LINE_LEN - matchpos < multipart->bufsize; multipart->scanpos++) { if (multipart->buffer[multipart->scanpos] == multipart->prefix[matchpos]) { matchpos++; if (matchpos == multipart->prefixLen) { int datalen; int i, start; gchar *mime_type; multipart->scanpos++; start = multipart->scanpos; /* find \n */ for (i = 0; i < MAX_LINE_LEN; i++) { if (multipart->buffer[multipart->scanpos] == '\n') break; multipart->scanpos++; matchpos++; } mime_type = g_strndup (multipart->buffer + start, multipart->scanpos - start); multipart->scanpos += 2; matchpos += 3; datalen = multipart->scanpos - matchpos; if (datalen > 0 && multipart->parsing_mime) { GstBuffer *outbuf; GstMultipartPad *srcpad; gboolean created = FALSE; srcpad = gst_multipart_find_pad_by_mime (multipart, multipart->parsing_mime, &created); if (srcpad != NULL) { ret = gst_pad_alloc_buffer_and_set_caps (srcpad->pad, GST_BUFFER_OFFSET_NONE, datalen, GST_PAD_CAPS (srcpad->pad), &outbuf); if (ret != GST_FLOW_OK) { GST_WARNING_OBJECT (multipart, "failed allocating a %d bytes " "buffer", datalen); } else { memcpy (GST_BUFFER_DATA (outbuf), multipart->buffer, datalen); if (created) { GstEvent *event; /* Push new segment */ event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0); if (GST_IS_EVENT (event)) { gst_pad_push_event (srcpad->pad, event); } GST_BUFFER_TIMESTAMP (outbuf) = 0; } else { GST_BUFFER_TIMESTAMP (outbuf) = -1; } ret = gst_pad_push (srcpad->pad, outbuf); } } } /* move rest downward */ multipart->bufsize -= multipart->scanpos; memmove (multipart->buffer, multipart->buffer + multipart->scanpos, multipart->bufsize); g_free (multipart->parsing_mime); multipart->parsing_mime = mime_type; multipart->scanpos = 0; } } else { matchpos = 0; } if (ret != GST_FLOW_OK) break; } gst_buffer_unref (buf); gst_object_unref (multipart); return ret; } static GstStateChangeReturn gst_multipart_demux_change_state (GstElement * element, GstStateChange transition) { GstMultipartDemux *multipart; GstStateChangeReturn ret; multipart = GST_MULTIPART_DEMUX (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: break; case GST_STATE_CHANGE_READY_TO_PAUSED: multipart->buffer = g_malloc (multipart->maxlen); multipart->first_frame = TRUE; multipart->parsing_mime = NULL; multipart->numpads = 0; multipart->scanpos = 0; multipart->lastpos = 0; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: g_free (multipart->parsing_mime); multipart->parsing_mime = NULL; g_free (multipart->buffer); multipart->buffer = NULL; break; case GST_STATE_CHANGE_READY_TO_NULL: break; default: break; } return ret; } static void gst_multipart_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMultipartDemux *filter; g_return_if_fail (GST_IS_MULTIPART_DEMUX (object)); filter = GST_MULTIPART_DEMUX (object); switch (prop_id) { case PROP_BOUNDARY: g_free (filter->prefix); filter->prefix = g_value_dup_string (value); filter->prefixLen = strlen (filter->prefix); break; case PROP_AUTOSCAN: filter->autoscan = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_multipart_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstMultipartDemux *filter; g_return_if_fail (GST_IS_MULTIPART_DEMUX (object)); filter = GST_MULTIPART_DEMUX (object); switch (prop_id) { case PROP_BOUNDARY: g_value_set_string (value, filter->prefix); break; case PROP_AUTOSCAN: g_value_set_boolean (value, filter->autoscan); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } gboolean gst_multipart_demux_plugin_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (gst_multipart_demux_debug, "multipartdemux", 0, "multipart demuxer"); return gst_element_register (plugin, "multipartdemux", GST_RANK_PRIMARY, GST_TYPE_MULTIPART_DEMUX); }