b44fee5d81dc00a584c4f8e8060282ea7bf4d432
[platform/upstream/gstreamer.git] / gst / multipart / multipartdemux.c
1 /* GStreamer
2  * Copyright (C) 2006 Sjoerd Simons <sjoerd@luon.net>
3  * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
4  *
5  * gstmultipartdemux.c: multipart stream demuxer
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 /**
24  * SECTION:element-multipartdemux
25  * @see_also: #GstMultipartMux
26  *
27  * MultipartDemux uses the Content-type field of incoming buffers to demux and 
28  * push data to dynamic source pads. Most of the time multipart streams are 
29  * sequential JPEG frames generated from a live source such as a network source
30  * or a camera.
31  *
32  * The output buffers of the multipartdemux typically have no timestamps and are
33  * usually played as fast as possible (at the rate that the source provides the
34  * data).
35  *
36  * the content in multipart files is separated with a boundary string that can
37  * be configured specifically with the #GstMultipartDemux:boundary property
38  * otherwise it will be autodetected.
39  *
40  * <refsect2>
41  * <title>Sample pipelines</title>
42  * |[
43  * gst-launch-1.0 filesrc location=/tmp/test.multipart ! multipartdemux ! image/jpeg,framerate=\(fraction\)5/1 ! jpegparse ! jpegdec ! videoconvert ! autovideosink
44  * ]| a simple pipeline to demux a multipart file muxed with #GstMultipartMux
45  * containing JPEG frames.
46  * </refsect2>
47  */
48
49 #ifdef HAVE_CONFIG_H
50 #include "config.h"
51 #endif
52
53 #include "multipartdemux.h"
54
55 GST_DEBUG_CATEGORY_STATIC (gst_multipart_demux_debug);
56 #define GST_CAT_DEFAULT gst_multipart_demux_debug
57
58 #define DEFAULT_BOUNDARY                NULL
59 #define DEFAULT_SINGLE_STREAM   FALSE
60
61 enum
62 {
63   PROP_0,
64   PROP_BOUNDARY,
65   PROP_SINGLE_STREAM
66 };
67
68 static GstStaticPadTemplate multipart_demux_src_template_factory =
69 GST_STATIC_PAD_TEMPLATE ("src_%u",
70     GST_PAD_SRC,
71     GST_PAD_SOMETIMES,
72     GST_STATIC_CAPS_ANY);
73
74 static GstStaticPadTemplate multipart_demux_sink_template_factory =
75 GST_STATIC_PAD_TEMPLATE ("sink",
76     GST_PAD_SINK,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS ("multipart/x-mixed-replace")
79     );
80
81 typedef struct
82 {
83   const gchar *key;
84   const gchar *val;
85 } GstNamesMap;
86
87 /* convert from mime types to gst structure names. Add more when needed. The
88  * mime-type is stored as lowercase */
89 static const GstNamesMap gstnames[] = {
90   /* RFC 2046 says audio/basic is mulaw, mono, 8000Hz */
91   {"audio/basic", "audio/x-mulaw, channels=1, rate=8000"},
92   {"audio/g726-16",
93       "audio/x-adpcm, bitrate=16000, layout=g726, channels=1, rate=8000"},
94   {"audio/g726-24",
95       "audio/x-adpcm, bitrate=24000, layout=g726, channels=1, rate=8000"},
96   {"audio/g726-32",
97       "audio/x-adpcm, bitrate=32000, layout=g726, channels=1, rate=8000"},
98   {"audio/g726-40",
99       "audio/x-adpcm, bitrate=40000, layout=g726, channels=1, rate=8000"},
100   /* Panasonic Network Cameras non-standard types */
101   {"audio/g726",
102       "audio/x-adpcm, bitrate=32000, layout=g726, channels=1, rate=8000"},
103   {NULL, NULL}
104 };
105
106
107 static GstFlowReturn gst_multipart_demux_chain (GstPad * pad,
108     GstObject * parent, GstBuffer * buf);
109
110 static GstStateChangeReturn gst_multipart_demux_change_state (GstElement *
111     element, GstStateChange transition);
112
113 static void gst_multipart_set_property (GObject * object, guint prop_id,
114     const GValue * value, GParamSpec * pspec);
115
116 static void gst_multipart_get_property (GObject * object, guint prop_id,
117     GValue * value, GParamSpec * pspec);
118
119 static void gst_multipart_demux_dispose (GObject * object);
120
121 #define gst_multipart_demux_parent_class parent_class
122 G_DEFINE_TYPE (GstMultipartDemux, gst_multipart_demux, GST_TYPE_ELEMENT);
123
124 static void
125 gst_multipart_demux_class_init (GstMultipartDemuxClass * klass)
126 {
127   int i;
128
129   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
130   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
131
132   gobject_class->dispose = gst_multipart_demux_dispose;
133   gobject_class->set_property = gst_multipart_set_property;
134   gobject_class->get_property = gst_multipart_get_property;
135
136   g_object_class_install_property (gobject_class, PROP_BOUNDARY,
137       g_param_spec_string ("boundary", "Boundary",
138           "The boundary string separating data, automatic if NULL",
139           DEFAULT_BOUNDARY,
140           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
141
142   /**
143    * GstMultipartDemux:single-stream:
144    *
145    * Assume that there is only one stream whose content-type will
146    * not change and emit no-more-pads as soon as the first boundary
147    * content is parsed, decoded, and pads are linked.
148    */
149   g_object_class_install_property (gobject_class, PROP_SINGLE_STREAM,
150       g_param_spec_boolean ("single-stream", "Single Stream",
151           "Assume that there is only one stream whose content-type will not change and emit no-more-pads as soon as the first boundary content is parsed, decoded, and pads are linked",
152           DEFAULT_SINGLE_STREAM, G_PARAM_READWRITE));
153
154   /* populate gst names and mime types pairs */
155   klass->gstnames = g_hash_table_new (g_str_hash, g_str_equal);
156   for (i = 0; gstnames[i].key; i++) {
157     g_hash_table_insert (klass->gstnames, (gpointer) gstnames[i].key,
158         (gpointer) gstnames[i].val);
159   }
160
161   gstelement_class->change_state = gst_multipart_demux_change_state;
162
163   gst_element_class_add_pad_template (gstelement_class,
164       gst_static_pad_template_get (&multipart_demux_sink_template_factory));
165   gst_element_class_add_pad_template (gstelement_class,
166       gst_static_pad_template_get (&multipart_demux_src_template_factory));
167   gst_element_class_set_static_metadata (gstelement_class, "Multipart demuxer",
168       "Codec/Demuxer",
169       "demux multipart streams",
170       "Wim Taymans <wim.taymans@gmail.com>, Sjoerd Simons <sjoerd@luon.net>");
171 }
172
173 static void
174 gst_multipart_demux_init (GstMultipartDemux * multipart)
175 {
176   /* create the sink pad */
177   multipart->sinkpad =
178       gst_pad_new_from_static_template (&multipart_demux_sink_template_factory,
179       "sink");
180   gst_element_add_pad (GST_ELEMENT_CAST (multipart), multipart->sinkpad);
181   gst_pad_set_chain_function (multipart->sinkpad,
182       GST_DEBUG_FUNCPTR (gst_multipart_demux_chain));
183
184   multipart->adapter = gst_adapter_new ();
185   multipart->boundary = DEFAULT_BOUNDARY;
186   multipart->mime_type = NULL;
187   multipart->content_length = -1;
188   multipart->header_completed = FALSE;
189   multipart->scanpos = 0;
190   multipart->singleStream = DEFAULT_SINGLE_STREAM;
191   multipart->have_group_id = FALSE;
192   multipart->group_id = G_MAXUINT;
193 }
194
195 static void
196 gst_multipart_demux_remove_src_pads (GstMultipartDemux * demux)
197 {
198   while (demux->srcpads != NULL) {
199     GstMultipartPad *mppad = demux->srcpads->data;
200
201     gst_element_remove_pad (GST_ELEMENT (demux), mppad->pad);
202     g_free (mppad->mime);
203     g_free (mppad);
204     demux->srcpads = g_slist_delete_link (demux->srcpads, demux->srcpads);
205   }
206   demux->srcpads = NULL;
207   demux->numpads = 0;
208 }
209
210 static void
211 gst_multipart_demux_dispose (GObject * object)
212 {
213   GstMultipartDemux *demux = GST_MULTIPART_DEMUX (object);
214
215   if (demux->adapter != NULL)
216     g_object_unref (demux->adapter);
217   demux->adapter = NULL;
218   g_free (demux->boundary);
219   demux->boundary = NULL;
220   g_free (demux->mime_type);
221   demux->mime_type = NULL;
222   gst_multipart_demux_remove_src_pads (demux);
223
224   G_OBJECT_CLASS (parent_class)->dispose (object);
225 }
226
227 static const gchar *
228 gst_multipart_demux_get_gstname (GstMultipartDemux * demux, gchar * mimetype)
229 {
230   GstMultipartDemuxClass *klass;
231   const gchar *gstname;
232
233   klass = GST_MULTIPART_DEMUX_GET_CLASS (demux);
234
235   /* use hashtable to convert to gst name */
236   gstname = g_hash_table_lookup (klass->gstnames, mimetype);
237   if (gstname == NULL) {
238     /* no gst name mapping, use mime type */
239     gstname = mimetype;
240   }
241   GST_DEBUG_OBJECT (demux, "gst name for %s is %s", mimetype, gstname);
242   return gstname;
243 }
244
245 static GstFlowReturn
246 gst_multipart_combine_flows (GstMultipartDemux * demux, GstMultipartPad * pad,
247     GstFlowReturn ret)
248 {
249   GSList *walk;
250
251   /* store the value */
252   pad->last_ret = ret;
253
254   /* any other error that is not-linked can be returned right
255    * away */
256   if (ret != GST_FLOW_NOT_LINKED)
257     goto done;
258
259   /* only return NOT_LINKED if all other pads returned NOT_LINKED */
260   for (walk = demux->srcpads; walk; walk = g_slist_next (walk)) {
261     GstMultipartPad *opad = (GstMultipartPad *) walk->data;
262
263     ret = opad->last_ret;
264     /* some other return value (must be SUCCESS but we can return
265      * other values as well) */
266     if (ret != GST_FLOW_NOT_LINKED)
267       goto done;
268   }
269   /* if we get here, all other pads were unlinked and we return
270    * NOT_LINKED then */
271 done:
272   return ret;
273 }
274
275 static GstMultipartPad *
276 gst_multipart_find_pad_by_mime (GstMultipartDemux * demux, gchar * mime,
277     gboolean * created)
278 {
279   GSList *walk;
280
281   walk = demux->srcpads;
282   while (walk) {
283     GstMultipartPad *pad = (GstMultipartPad *) walk->data;
284
285     if (!strcmp (pad->mime, mime)) {
286       if (created) {
287         *created = FALSE;
288       }
289       return pad;
290     }
291
292     walk = walk->next;
293   }
294   /* pad not found, create it */
295   {
296     GstPad *pad;
297     GstMultipartPad *mppad;
298     gchar *name;
299     const gchar *capsname;
300     GstCaps *caps;
301     gchar *stream_id;
302     GstEvent *event;
303
304     mppad = g_new0 (GstMultipartPad, 1);
305
306     GST_DEBUG_OBJECT (demux, "creating pad with mime: %s", mime);
307
308     name = g_strdup_printf ("src_%u", demux->numpads);
309     pad =
310         gst_pad_new_from_static_template (&multipart_demux_src_template_factory,
311         name);
312     g_free (name);
313
314     mppad->pad = pad;
315     mppad->mime = g_strdup (mime);
316     mppad->last_ret = GST_FLOW_OK;
317     mppad->last_ts = GST_CLOCK_TIME_NONE;
318     mppad->discont = TRUE;
319
320     demux->srcpads = g_slist_prepend (demux->srcpads, mppad);
321     demux->numpads++;
322
323     gst_pad_use_fixed_caps (pad);
324     gst_pad_set_active (pad, TRUE);
325     gst_element_add_pad (GST_ELEMENT_CAST (demux), pad);
326
327     /* prepare and send stream-start */
328     if (!demux->have_group_id) {
329       event = gst_pad_get_sticky_event (demux->sinkpad,
330           GST_EVENT_STREAM_START, 0);
331
332       if (event) {
333         demux->have_group_id =
334             gst_event_parse_group_id (event, &demux->group_id);
335         gst_event_unref (event);
336       } else if (!demux->have_group_id) {
337         demux->have_group_id = TRUE;
338         demux->group_id = gst_util_group_id_next ();
339       }
340     }
341
342     stream_id = gst_pad_create_stream_id (pad,
343         GST_ELEMENT_CAST (demux), demux->mime_type);
344
345     event = gst_event_new_stream_start (stream_id);
346     if (demux->have_group_id)
347       gst_event_set_group_id (event, demux->group_id);
348
349     gst_pad_push_event (pad, event);
350     g_free (stream_id);
351
352     /* take the mime type, convert it to the caps name */
353     capsname = gst_multipart_demux_get_gstname (demux, mime);
354     caps = gst_caps_from_string (capsname);
355     GST_DEBUG_OBJECT (demux, "caps for pad: %s", capsname);
356     gst_pad_set_caps (pad, caps);
357     gst_caps_unref (caps);
358
359     if (created) {
360       *created = TRUE;
361     }
362
363     if (demux->singleStream) {
364       gst_element_no_more_pads (GST_ELEMENT_CAST (demux));
365     }
366
367     return mppad;
368   }
369 }
370
371 static gboolean
372 get_line_end (const guint8 * data, const guint8 * dataend, guint8 ** end,
373     guint8 ** next)
374 {
375   guint8 *x;
376   gboolean foundr = FALSE;
377
378   for (x = (guint8 *) data; x < dataend; x++) {
379     if (*x == '\r') {
380       foundr = TRUE;
381     } else if (*x == '\n') {
382       *end = x - (foundr ? 1 : 0);
383       *next = x + 1;
384       return TRUE;
385     }
386   }
387   return FALSE;
388 }
389
390 static guint
391 get_mime_len (const guint8 * data, guint maxlen)
392 {
393   guint8 *x;
394
395   x = (guint8 *) data;
396   while (*x != '\0' && *x != '\r' && *x != '\n' && *x != ';') {
397     x++;
398   }
399   return x - data;
400 }
401
402 static gint
403 multipart_parse_header (GstMultipartDemux * multipart)
404 {
405   const guint8 *data;
406   const guint8 *dataend;
407   gchar *boundary;
408   int boundary_len;
409   int datalen;
410   guint8 *pos;
411   guint8 *end, *next;
412
413   datalen = gst_adapter_available (multipart->adapter);
414   data = gst_adapter_map (multipart->adapter, datalen);
415   dataend = data + datalen;
416
417   /* Skip leading whitespace, pos endposition should at least leave space for
418    * the boundary and a \n */
419   for (pos = (guint8 *) data; pos < dataend - 4 && g_ascii_isspace (*pos);
420       pos++);
421
422   if (pos >= dataend - 4)
423     goto need_more_data;
424
425   if (G_UNLIKELY (pos[0] != '-' || pos[1] != '-')) {
426     GST_DEBUG_OBJECT (multipart, "No boundary available");
427     goto wrong_header;
428   }
429
430   /* First the boundary */
431   if (!get_line_end (pos, dataend, &end, &next))
432     goto need_more_data;
433
434   /* Ignore the leading -- */
435   boundary_len = end - pos - 2;
436   boundary = (gchar *) pos + 2;
437   if (boundary_len < 1) {
438     GST_DEBUG_OBJECT (multipart, "No boundary available");
439     goto wrong_header;
440   }
441
442   if (G_UNLIKELY (multipart->boundary == NULL)) {
443     /* First time we see the boundary, copy it */
444     multipart->boundary = g_strndup (boundary, boundary_len);
445     multipart->boundary_len = boundary_len;
446   } else if (G_UNLIKELY (boundary_len != multipart->boundary_len)) {
447     /* Something odd is going on, either the boundary indicated EOS or it's
448      * invalid */
449     if (G_UNLIKELY (boundary_len == multipart->boundary_len + 2 &&
450             !strncmp (boundary, multipart->boundary, multipart->boundary_len) &&
451             !strncmp (boundary + multipart->boundary_len, "--", 2)))
452       goto eos;
453
454     GST_DEBUG_OBJECT (multipart,
455         "Boundary length doesn't match detected boundary (%d <> %d",
456         boundary_len, multipart->boundary_len);
457     goto wrong_header;
458   } else if (G_UNLIKELY (strncmp (boundary, multipart->boundary, boundary_len))) {
459     GST_DEBUG_OBJECT (multipart, "Boundary doesn't match previous boundary");
460     goto wrong_header;
461   }
462
463   pos = next;
464   while (get_line_end (pos, dataend, &end, &next)) {
465     guint len = end - pos;
466
467     if (len == 0) {
468       /* empty line, data starts behind us */
469       GST_DEBUG_OBJECT (multipart,
470           "Parsed the header - boundary: %s, mime-type: %s, content-length: %d",
471           multipart->boundary, multipart->mime_type, multipart->content_length);
472       gst_adapter_unmap (multipart->adapter);
473       return next - data;
474     }
475
476     if (len >= 14 && !g_ascii_strncasecmp ("content-type:", (gchar *) pos, 13)) {
477       guint mime_len;
478
479       /* only take the mime type up to the first ; if any. After ; there can be
480        * properties that we don't handle yet. */
481       mime_len = get_mime_len (pos + 14, len - 14);
482
483       g_free (multipart->mime_type);
484       multipart->mime_type = g_ascii_strdown ((gchar *) pos + 14, mime_len);
485     } else if (len >= 15 &&
486         !g_ascii_strncasecmp ("content-length:", (gchar *) pos, 15)) {
487       multipart->content_length =
488           g_ascii_strtoull ((gchar *) pos + 15, NULL, 10);
489     }
490     pos = next;
491   }
492
493 need_more_data:
494   GST_DEBUG_OBJECT (multipart, "Need more data for the header");
495   gst_adapter_unmap (multipart->adapter);
496
497   return MULTIPART_NEED_MORE_DATA;
498
499 wrong_header:
500   {
501     GST_ELEMENT_ERROR (multipart, STREAM, DEMUX, (NULL),
502         ("Boundary not found in the multipart header"));
503     gst_adapter_unmap (multipart->adapter);
504     return MULTIPART_DATA_ERROR;
505   }
506 eos:
507   {
508     GST_DEBUG_OBJECT (multipart, "we are EOS");
509     gst_adapter_unmap (multipart->adapter);
510     return MULTIPART_DATA_EOS;
511   }
512 }
513
514 static gint
515 multipart_find_boundary (GstMultipartDemux * multipart, gint * datalen)
516 {
517   /* Adaptor is positioned at the start of the data */
518   const guint8 *data, *pos;
519   const guint8 *dataend;
520   gint len;
521
522   if (multipart->content_length >= 0) {
523     /* fast path, known content length :) */
524     len = multipart->content_length;
525     if (gst_adapter_available (multipart->adapter) >= len + 2) {
526       *datalen = len;
527       data = gst_adapter_map (multipart->adapter, len + 1);
528
529       /* If data[len] contains \r then assume a newline is \r\n */
530       if (data[len] == '\r')
531         len += 2;
532       else if (data[len] == '\n')
533         len += 1;
534
535       gst_adapter_unmap (multipart->adapter);
536       /* Don't check if boundary is actually there, but let the header parsing
537        * bail out if it isn't */
538       return len;
539     } else {
540       /* need more data */
541       return MULTIPART_NEED_MORE_DATA;
542     }
543   }
544
545   len = gst_adapter_available (multipart->adapter);
546   if (len == 0)
547     return MULTIPART_NEED_MORE_DATA;
548   data = gst_adapter_map (multipart->adapter, len);
549   dataend = data + len;
550
551   for (pos = data + multipart->scanpos;
552       pos <= dataend - multipart->boundary_len - 2; pos++) {
553     if (*pos == '-' && pos[1] == '-' &&
554         !strncmp ((gchar *) pos + 2,
555             multipart->boundary, multipart->boundary_len)) {
556       /* Found the boundary! Check if there was a newline before the boundary */
557       len = pos - data;
558       if (pos - 2 > data && pos[-2] == '\r')
559         len -= 2;
560       else if (pos - 1 > data && pos[-1] == '\n')
561         len -= 1;
562       *datalen = len;
563
564       gst_adapter_unmap (multipart->adapter);
565       multipart->scanpos = 0;
566       return pos - data;
567     }
568   }
569   gst_adapter_unmap (multipart->adapter);
570   multipart->scanpos = pos - data;
571   return MULTIPART_NEED_MORE_DATA;
572 }
573
574 static GstFlowReturn
575 gst_multipart_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
576 {
577   GstMultipartDemux *multipart;
578   GstAdapter *adapter;
579   gint size = 1;
580   GstFlowReturn res;
581
582   multipart = GST_MULTIPART_DEMUX (parent);
583   adapter = multipart->adapter;
584
585   res = GST_FLOW_OK;
586
587   if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
588     GSList *l;
589
590     for (l = multipart->srcpads; l != NULL; l = l->next) {
591       GstMultipartPad *srcpad = l->data;
592
593       srcpad->discont = TRUE;
594     }
595     gst_adapter_clear (adapter);
596   }
597   gst_adapter_push (adapter, buf);
598
599   while (gst_adapter_available (adapter) > 0) {
600     GstMultipartPad *srcpad;
601     GstBuffer *outbuf;
602     gboolean created;
603     gint datalen;
604
605     if (G_UNLIKELY (!multipart->header_completed)) {
606       if ((size = multipart_parse_header (multipart)) < 0) {
607         goto nodata;
608       } else {
609         gst_adapter_flush (adapter, size);
610         multipart->header_completed = TRUE;
611       }
612     }
613     if ((size = multipart_find_boundary (multipart, &datalen)) < 0) {
614       goto nodata;
615     }
616
617     /* Invalidate header info */
618     multipart->header_completed = FALSE;
619     multipart->content_length = -1;
620
621     if (G_UNLIKELY (datalen <= 0)) {
622       GST_DEBUG_OBJECT (multipart, "skipping empty content.");
623       gst_adapter_flush (adapter, size - datalen);
624     } else {
625       GstClockTime ts;
626
627       srcpad =
628           gst_multipart_find_pad_by_mime (multipart,
629           multipart->mime_type, &created);
630
631       ts = gst_adapter_prev_pts (adapter, NULL);
632       outbuf = gst_adapter_take_buffer (adapter, datalen);
633       gst_adapter_flush (adapter, size - datalen);
634
635       if (created) {
636         GstTagList *tags;
637         GstSegment segment;
638
639         gst_segment_init (&segment, GST_FORMAT_TIME);
640
641         /* Push new segment, first buffer has 0 timestamp */
642         gst_pad_push_event (srcpad->pad, gst_event_new_segment (&segment));
643
644         tags = gst_tag_list_new (GST_TAG_CONTAINER_FORMAT, "Multipart", NULL);
645         gst_tag_list_set_scope (tags, GST_TAG_SCOPE_GLOBAL);
646         gst_pad_push_event (srcpad->pad, gst_event_new_tag (tags));
647       }
648
649       outbuf = gst_buffer_make_writable (outbuf);
650       if (srcpad->last_ts == GST_CLOCK_TIME_NONE || srcpad->last_ts != ts) {
651         GST_BUFFER_TIMESTAMP (outbuf) = ts;
652         srcpad->last_ts = ts;
653       } else {
654         GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE;
655       }
656
657       if (srcpad->discont) {
658         GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
659         srcpad->discont = FALSE;
660       } else {
661         GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DISCONT);
662       }
663
664       GST_DEBUG_OBJECT (multipart,
665           "pushing buffer with timestamp %" GST_TIME_FORMAT,
666           GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)));
667       res = gst_pad_push (srcpad->pad, outbuf);
668       res = gst_multipart_combine_flows (multipart, srcpad, res);
669       if (res != GST_FLOW_OK)
670         break;
671     }
672   }
673
674 nodata:
675   if (G_UNLIKELY (size == MULTIPART_DATA_ERROR))
676     return GST_FLOW_ERROR;
677   if (G_UNLIKELY (size == MULTIPART_DATA_EOS))
678     return GST_FLOW_EOS;
679
680   return res;
681 }
682
683 static GstStateChangeReturn
684 gst_multipart_demux_change_state (GstElement * element,
685     GstStateChange transition)
686 {
687   GstMultipartDemux *multipart;
688   GstStateChangeReturn ret;
689
690   multipart = GST_MULTIPART_DEMUX (element);
691
692   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
693   if (ret == GST_STATE_CHANGE_FAILURE)
694     return ret;
695
696   switch (transition) {
697     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
698       break;
699     case GST_STATE_CHANGE_PAUSED_TO_READY:
700       multipart->header_completed = FALSE;
701       g_free (multipart->boundary);
702       multipart->boundary = NULL;
703       g_free (multipart->mime_type);
704       multipart->mime_type = NULL;
705       gst_adapter_clear (multipart->adapter);
706       multipart->content_length = -1;
707       multipart->scanpos = 0;
708       gst_multipart_demux_remove_src_pads (multipart);
709       multipart->have_group_id = FALSE;
710       multipart->group_id = G_MAXUINT;
711       break;
712     case GST_STATE_CHANGE_READY_TO_NULL:
713       break;
714     default:
715       break;
716   }
717
718   return ret;
719 }
720
721
722 static void
723 gst_multipart_set_property (GObject * object, guint prop_id,
724     const GValue * value, GParamSpec * pspec)
725 {
726   GstMultipartDemux *filter;
727
728   filter = GST_MULTIPART_DEMUX (object);
729
730   switch (prop_id) {
731     case PROP_BOUNDARY:
732       /* Not really that usefull anymore as we can reliably autoscan */
733       g_free (filter->boundary);
734       filter->boundary = g_value_dup_string (value);
735       if (filter->boundary != NULL) {
736         filter->boundary_len = strlen (filter->boundary);
737       }
738       break;
739     case PROP_SINGLE_STREAM:
740       filter->singleStream = g_value_get_boolean (value);
741       break;
742     default:
743       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
744       break;
745   }
746 }
747
748 static void
749 gst_multipart_get_property (GObject * object, guint prop_id,
750     GValue * value, GParamSpec * pspec)
751 {
752   GstMultipartDemux *filter;
753
754   filter = GST_MULTIPART_DEMUX (object);
755
756   switch (prop_id) {
757     case PROP_BOUNDARY:
758       g_value_set_string (value, filter->boundary);
759       break;
760     case PROP_SINGLE_STREAM:
761       g_value_set_boolean (value, filter->singleStream);
762       break;
763     default:
764       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
765       break;
766   }
767 }
768
769
770
771 gboolean
772 gst_multipart_demux_plugin_init (GstPlugin * plugin)
773 {
774   GST_DEBUG_CATEGORY_INIT (gst_multipart_demux_debug,
775       "multipartdemux", 0, "multipart demuxer");
776
777   return gst_element_register (plugin, "multipartdemux", GST_RANK_PRIMARY,
778       GST_TYPE_MULTIPART_DEMUX);
779 }