matroskademux: configurable timestamp gap handling
[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., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, 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 filesrc location=/tmp/test.multipart ! multipartdemux ! jpegdec ! ffmpegcolorspace ! ximagesink
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 /* signals and args */
59 enum
60 {
61   /* FILL ME */
62   LAST_SIGNAL
63 };
64
65 #define DEFAULT_AUTOSCAN                FALSE
66 #define DEFAULT_BOUNDARY                NULL
67 #define DEFAULT_SINGLE_STREAM   FALSE
68
69 enum
70 {
71   PROP_0,
72   PROP_AUTOSCAN,
73   PROP_BOUNDARY,
74   PROP_SINGLE_STREAM
75 };
76
77 static GstStaticPadTemplate multipart_demux_src_template_factory =
78 GST_STATIC_PAD_TEMPLATE ("src_%d",
79     GST_PAD_SRC,
80     GST_PAD_SOMETIMES,
81     GST_STATIC_CAPS_ANY);
82
83 static GstStaticPadTemplate multipart_demux_sink_template_factory =
84 GST_STATIC_PAD_TEMPLATE ("sink",
85     GST_PAD_SINK,
86     GST_PAD_ALWAYS,
87     GST_STATIC_CAPS ("multipart/x-mixed-replace")
88     );
89
90 typedef struct
91 {
92   const gchar *key;
93   const gchar *val;
94 } GstNamesMap;
95
96 /* convert from mime types to gst structure names. Add more when needed. The
97  * mime-type is stored as lowercase */
98 static const GstNamesMap gstnames[] = {
99   /* RFC 2046 says audio/basic is mulaw, mono, 8000Hz */
100   {"audio/basic", "audio/x-mulaw, channels=1, rate=8000"},
101   {"audio/g726-16",
102       "audio/x-adpcm, bitrate=16000, layout=g726, channels=1, rate=8000"},
103   {"audio/g726-24",
104       "audio/x-adpcm, bitrate=24000, layout=g726, channels=1, rate=8000"},
105   {"audio/g726-32",
106       "audio/x-adpcm, bitrate=32000, layout=g726, channels=1, rate=8000"},
107   {"audio/g726-40",
108       "audio/x-adpcm, bitrate=40000, layout=g726, channels=1, rate=8000"},
109   /* Panasonic Network Cameras non-standard types */
110   {"audio/g726",
111       "audio/x-adpcm, bitrate=32000, layout=g726, channels=1, rate=8000"},
112   {NULL, NULL}
113 };
114
115
116 static GstFlowReturn gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf);
117
118 static GstStateChangeReturn gst_multipart_demux_change_state (GstElement *
119     element, GstStateChange transition);
120
121 static void gst_multipart_set_property (GObject * object, guint prop_id,
122     const GValue * value, GParamSpec * pspec);
123
124 static void gst_multipart_get_property (GObject * object, guint prop_id,
125     GValue * value, GParamSpec * pspec);
126
127 static void gst_multipart_demux_finalize (GObject * object);
128
129 GST_BOILERPLATE (GstMultipartDemux, gst_multipart_demux, GstElement,
130     GST_TYPE_ELEMENT);
131
132 static void
133 gst_multipart_demux_base_init (gpointer g_class)
134 {
135   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
136
137   gst_element_class_add_pad_template (element_class,
138       gst_static_pad_template_get (&multipart_demux_sink_template_factory));
139   gst_element_class_add_pad_template (element_class,
140       gst_static_pad_template_get (&multipart_demux_src_template_factory));
141   gst_element_class_set_details_simple (element_class, "Multipart demuxer",
142       "Codec/Demuxer",
143       "demux multipart streams",
144       "Wim Taymans <wim.taymans@gmail.com>, Sjoerd Simons <sjoerd@luon.net>");
145 }
146
147 static void
148 gst_multipart_demux_class_init (GstMultipartDemuxClass * klass)
149 {
150   int i;
151
152   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
153   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
154
155   gobject_class->finalize = gst_multipart_demux_finalize;
156   gobject_class->set_property = gst_multipart_set_property;
157   gobject_class->get_property = gst_multipart_get_property;
158
159   g_object_class_install_property (gobject_class, PROP_BOUNDARY,
160       g_param_spec_string ("boundary", "Boundary",
161           "The boundary string separating data, automatic if NULL",
162           DEFAULT_BOUNDARY,
163           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
164
165   g_object_class_install_property (gobject_class, PROP_AUTOSCAN,
166       g_param_spec_boolean ("autoscan", "autoscan",
167           "Try to autofind the prefix (deprecated unused, see boundary)",
168           DEFAULT_AUTOSCAN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
169
170   /**
171    * GstMultipartDemux::single-stream:
172    *
173    * Assume that there is only one stream whose content-type will
174    * not change and emit no-more-pads as soon as the first boundary
175    * content is parsed, decoded, and pads are linked.
176    *
177    * Since: 0.10.31
178    */
179   g_object_class_install_property (gobject_class, PROP_SINGLE_STREAM,
180       g_param_spec_boolean ("single-stream", "Single Stream",
181           "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",
182           DEFAULT_SINGLE_STREAM, G_PARAM_READWRITE));
183
184   /* populate gst names and mime types pairs */
185   klass->gstnames = g_hash_table_new (g_str_hash, g_str_equal);
186   for (i = 0; gstnames[i].key; i++) {
187     g_hash_table_insert (klass->gstnames, (gpointer) gstnames[i].key,
188         (gpointer) gstnames[i].val);
189   }
190
191   gstelement_class->change_state = gst_multipart_demux_change_state;
192 }
193
194 static void
195 gst_multipart_demux_init (GstMultipartDemux * multipart,
196     GstMultipartDemuxClass * g_class)
197 {
198   /* create the sink pad */
199   multipart->sinkpad =
200       gst_pad_new_from_static_template (&multipart_demux_sink_template_factory,
201       "sink");
202   gst_element_add_pad (GST_ELEMENT_CAST (multipart), multipart->sinkpad);
203   gst_pad_set_chain_function (multipart->sinkpad,
204       GST_DEBUG_FUNCPTR (gst_multipart_demux_chain));
205
206   multipart->adapter = gst_adapter_new ();
207   multipart->boundary = DEFAULT_BOUNDARY;
208   multipart->mime_type = NULL;
209   multipart->content_length = -1;
210   multipart->header_completed = FALSE;
211   multipart->scanpos = 0;
212   multipart->autoscan = DEFAULT_AUTOSCAN;
213   multipart->singleStream = DEFAULT_SINGLE_STREAM;
214 }
215
216 static void
217 gst_multipart_pad_free (GstMultipartPad * mppad)
218 {
219   g_free (mppad->mime);
220   g_free (mppad);
221 }
222
223 static void
224 gst_multipart_demux_finalize (GObject * object)
225 {
226   GstMultipartDemux *demux = GST_MULTIPART_DEMUX (object);
227
228   g_object_unref (demux->adapter);
229   g_free (demux->boundary);
230   g_free (demux->mime_type);
231   g_slist_foreach (demux->srcpads, (GFunc) gst_multipart_pad_free, NULL);
232   g_slist_free (demux->srcpads);
233
234   G_OBJECT_CLASS (parent_class)->finalize (object);
235 }
236
237 static const gchar *
238 gst_multipart_demux_get_gstname (GstMultipartDemux * demux, gchar * mimetype)
239 {
240   GstMultipartDemuxClass *klass;
241   const gchar *gstname;
242
243   klass = GST_MULTIPART_DEMUX_GET_CLASS (demux);
244
245   /* use hashtable to convert to gst name */
246   gstname = g_hash_table_lookup (klass->gstnames, mimetype);
247   if (gstname == NULL) {
248     /* no gst name mapping, use mime type */
249     gstname = mimetype;
250   }
251   GST_DEBUG_OBJECT (demux, "gst name for %s is %s", mimetype, gstname);
252   return gstname;
253 }
254
255 static GstFlowReturn
256 gst_multipart_combine_flows (GstMultipartDemux * demux, GstMultipartPad * pad,
257     GstFlowReturn ret)
258 {
259   GSList *walk;
260
261   /* store the value */
262   pad->last_ret = ret;
263
264   /* any other error that is not-linked can be returned right
265    * away */
266   if (ret != GST_FLOW_NOT_LINKED)
267     goto done;
268
269   /* only return NOT_LINKED if all other pads returned NOT_LINKED */
270   for (walk = demux->srcpads; walk; walk = g_slist_next (walk)) {
271     GstMultipartPad *opad = (GstMultipartPad *) walk->data;
272
273     ret = opad->last_ret;
274     /* some other return value (must be SUCCESS but we can return
275      * other values as well) */
276     if (ret != GST_FLOW_NOT_LINKED)
277       goto done;
278   }
279   /* if we get here, all other pads were unlinked and we return
280    * NOT_LINKED then */
281 done:
282   return ret;
283 }
284
285 static GstMultipartPad *
286 gst_multipart_find_pad_by_mime (GstMultipartDemux * demux, gchar * mime,
287     gboolean * created)
288 {
289   GSList *walk;
290
291   walk = demux->srcpads;
292   while (walk) {
293     GstMultipartPad *pad = (GstMultipartPad *) walk->data;
294
295     if (!strcmp (pad->mime, mime)) {
296       if (created) {
297         *created = FALSE;
298       }
299       return pad;
300     }
301
302     walk = walk->next;
303   }
304   /* pad not found, create it */
305   {
306     GstPad *pad;
307     GstMultipartPad *mppad;
308     gchar *name;
309     const gchar *capsname;
310     GstCaps *caps;
311
312     mppad = g_new0 (GstMultipartPad, 1);
313
314     GST_DEBUG_OBJECT (demux, "creating pad with mime: %s", mime);
315
316     name = g_strdup_printf ("src_%d", demux->numpads);
317     pad =
318         gst_pad_new_from_static_template (&multipart_demux_src_template_factory,
319         name);
320     g_free (name);
321
322     /* take the mime type, convert it to the caps name */
323     capsname = gst_multipart_demux_get_gstname (demux, mime);
324     caps = gst_caps_from_string (capsname);
325     GST_DEBUG_OBJECT (demux, "caps for pad: %s", capsname);
326     gst_pad_use_fixed_caps (pad);
327     gst_pad_set_caps (pad, caps);
328     gst_caps_unref (caps);
329
330     mppad->pad = pad;
331     mppad->mime = g_strdup (mime);
332     mppad->last_ret = GST_FLOW_OK;
333
334     demux->srcpads = g_slist_prepend (demux->srcpads, mppad);
335     demux->numpads++;
336
337     gst_pad_set_active (pad, TRUE);
338     gst_element_add_pad (GST_ELEMENT_CAST (demux), pad);
339
340     if (created) {
341       *created = TRUE;
342     }
343
344     if (demux->singleStream) {
345       gst_element_no_more_pads (GST_ELEMENT_CAST (demux));
346     }
347
348     return mppad;
349   }
350 }
351
352 static gboolean
353 get_line_end (const guint8 * data, const guint8 * dataend, guint8 ** end,
354     guint8 ** next)
355 {
356   guint8 *x;
357   gboolean foundr = FALSE;
358
359   for (x = (guint8 *) data; x < dataend; x++) {
360     if (*x == '\r') {
361       foundr = TRUE;
362     } else if (*x == '\n') {
363       *end = x - (foundr ? 1 : 0);
364       *next = x + 1;
365       return TRUE;
366     }
367   }
368   return FALSE;
369 }
370
371 static guint
372 get_mime_len (const guint8 * data, guint maxlen)
373 {
374   guint8 *x;
375
376   x = (guint8 *) data;
377   while (*x != '\0' && *x != '\r' && *x != '\n' && *x != ';') {
378     x++;
379   }
380   return x - data;
381 }
382
383 static gint
384 multipart_parse_header (GstMultipartDemux * multipart)
385 {
386   const guint8 *data;
387   const guint8 *dataend;
388   gchar *boundary;
389   int boundary_len;
390   int datalen;
391   guint8 *pos;
392   guint8 *end, *next;
393
394   datalen = gst_adapter_available (multipart->adapter);
395   data = gst_adapter_peek (multipart->adapter, datalen);
396   dataend = data + datalen;
397
398   /* Skip leading whitespace, pos endposition should at least leave space for
399    * the boundary and a \n */
400   for (pos = (guint8 *) data; pos < dataend - 4 && g_ascii_isspace (*pos);
401       pos++);
402
403   if (pos >= dataend - 4) {
404     return MULTIPART_NEED_MORE_DATA;
405   }
406
407   if (G_UNLIKELY (pos[0] != '-' || pos[1] != '-')) {
408     GST_DEBUG_OBJECT (multipart, "No boundary available");
409     goto wrong_header;
410   }
411
412   /* First the boundary */
413   if (!get_line_end (pos, dataend, &end, &next))
414     return MULTIPART_NEED_MORE_DATA;
415
416   /* Ignore the leading -- */
417   boundary_len = end - pos - 2;
418   boundary = (gchar *) pos + 2;
419   if (boundary_len < 1) {
420     GST_DEBUG_OBJECT (multipart, "No boundary available");
421     goto wrong_header;
422   }
423
424   if (G_UNLIKELY (multipart->boundary == NULL)) {
425     /* First time we see the boundary, copy it */
426     multipart->boundary = g_strndup (boundary, boundary_len);
427     multipart->boundary_len = boundary_len;
428   } else if (G_UNLIKELY (boundary_len != multipart->boundary_len)) {
429     /* Something odd is going on, either the boundary indicated EOS or it's
430      * invalid */
431     if (G_UNLIKELY (boundary_len == multipart->boundary_len + 2 &&
432             !strncmp (boundary, multipart->boundary, multipart->boundary_len) &&
433             !strncmp (boundary + multipart->boundary_len, "--", 2))) {
434       return MULTIPART_DATA_EOS;
435     }
436     GST_DEBUG_OBJECT (multipart,
437         "Boundary length doesn't match detected boundary (%d <> %d",
438         boundary_len, multipart->boundary_len);
439     goto wrong_header;
440   } else if (G_UNLIKELY (strncmp (boundary, multipart->boundary, boundary_len))) {
441     GST_DEBUG_OBJECT (multipart, "Boundary doesn't match previous boundary");
442     goto wrong_header;
443   }
444
445
446   pos = next;
447   while (get_line_end (pos, dataend, &end, &next)) {
448     guint len = end - pos;
449
450     if (len == 0) {
451       /* empty line, data starts behind us */
452       GST_DEBUG_OBJECT (multipart,
453           "Parsed the header - boundary: %s, mime-type: %s, content-length: %d",
454           multipart->boundary, multipart->mime_type, multipart->content_length);
455       return next - data;
456     }
457
458     if (len >= 14 && !g_ascii_strncasecmp ("content-type:", (gchar *) pos, 13)) {
459       guint mime_len;
460
461       /* only take the mime type up to the first ; if any. After ; there can be
462        * properties that we don't handle yet. */
463       mime_len = get_mime_len (pos + 14, len - 14);
464
465       g_free (multipart->mime_type);
466       multipart->mime_type = g_ascii_strdown ((gchar *) pos + 14, mime_len);
467     } else if (len >= 15 &&
468         !g_ascii_strncasecmp ("content-length:", (gchar *) pos, 15)) {
469       multipart->content_length =
470           g_ascii_strtoull ((gchar *) pos + 15, NULL, 10);
471     }
472     pos = next;
473   }
474   GST_DEBUG_OBJECT (multipart, "Need more data for the header");
475   return MULTIPART_NEED_MORE_DATA;
476
477 wrong_header:
478   {
479     GST_ELEMENT_ERROR (multipart, STREAM, DEMUX, (NULL),
480         ("Boundary not found in the multipart header"));
481     return MULTIPART_DATA_ERROR;
482   }
483 }
484
485 static gint
486 multipart_find_boundary (GstMultipartDemux * multipart, gint * datalen)
487 {
488   /* Adaptor is positioned at the start of the data */
489   const guint8 *data, *pos;
490   const guint8 *dataend;
491   gint len;
492
493   if (multipart->content_length >= 0) {
494     /* fast path, known content length :) */
495     len = multipart->content_length;
496     if (gst_adapter_available (multipart->adapter) >= len + 2) {
497       *datalen = len;
498       data = gst_adapter_peek (multipart->adapter, len + 1);
499
500       /* If data[len] contains \r then assume a newline is \r\n */
501       if (data[len] == '\r')
502         len += 2;
503       else if (data[len] == '\n')
504         len += 1;
505       /* Don't check if boundary is actually there, but let the header parsing 
506        * bail out if it isn't */
507       return len;
508     } else {
509       /* need more data */
510       return MULTIPART_NEED_MORE_DATA;
511     }
512   }
513
514   len = gst_adapter_available (multipart->adapter);
515   if (len == 0)
516     return MULTIPART_NEED_MORE_DATA;
517   data = gst_adapter_peek (multipart->adapter, len);
518   dataend = data + len;
519
520   for (pos = data + multipart->scanpos;
521       pos <= dataend - multipart->boundary_len - 2; pos++) {
522     if (*pos == '-' && pos[1] == '-' &&
523         !strncmp ((gchar *) pos + 2,
524             multipart->boundary, multipart->boundary_len)) {
525       /* Found the boundary! Check if there was a newline before the boundary */
526       len = pos - data;
527       if (pos - 2 > data && pos[-2] == '\r')
528         len -= 2;
529       else if (pos - 1 > data && pos[-1] == '\n')
530         len -= 1;
531       *datalen = len;
532
533       multipart->scanpos = 0;
534       return pos - data;
535     }
536   }
537   multipart->scanpos = pos - data;
538   return MULTIPART_NEED_MORE_DATA;
539 }
540
541 static GstFlowReturn
542 gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf)
543 {
544   GstMultipartDemux *multipart;
545   GstAdapter *adapter;
546   GstClockTime timestamp;
547   gint size = 1;
548   GstFlowReturn res;
549
550   multipart = GST_MULTIPART_DEMUX (gst_pad_get_parent (pad));
551   adapter = multipart->adapter;
552
553   res = GST_FLOW_OK;
554
555   timestamp = GST_BUFFER_TIMESTAMP (buf);
556
557   if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
558     gst_adapter_clear (adapter);
559   }
560   gst_adapter_push (adapter, buf);
561
562   while (gst_adapter_available (adapter) > 0) {
563     GstMultipartPad *srcpad;
564     GstBuffer *outbuf;
565     gboolean created;
566     gint datalen;
567
568     if (G_UNLIKELY (!multipart->header_completed)) {
569       if ((size = multipart_parse_header (multipart)) < 0) {
570         goto nodata;
571       } else {
572         gst_adapter_flush (adapter, size);
573         multipart->header_completed = TRUE;
574       }
575     }
576     if ((size = multipart_find_boundary (multipart, &datalen)) < 0) {
577       goto nodata;
578     }
579
580     /* Invalidate header info */
581     multipart->header_completed = FALSE;
582     multipart->content_length = -1;
583
584     if (G_UNLIKELY (datalen <= 0)) {
585       GST_DEBUG_OBJECT (multipart, "skipping empty content.");
586       gst_adapter_flush (adapter, size - datalen);
587     } else {
588       srcpad =
589           gst_multipart_find_pad_by_mime (multipart,
590           multipart->mime_type, &created);
591       outbuf = gst_adapter_take_buffer (adapter, datalen);
592       gst_adapter_flush (adapter, size - datalen);
593
594       gst_buffer_set_caps (outbuf, GST_PAD_CAPS (srcpad->pad));
595       if (created) {
596         GstTagList *tags;
597
598         /* Push new segment, first buffer has 0 timestamp */
599         gst_pad_push_event (srcpad->pad,
600             gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0));
601
602         tags =
603             gst_tag_list_new_full (GST_TAG_CONTAINER_FORMAT, "Multipart", NULL);
604         gst_pad_push_event (srcpad->pad, gst_event_new_tag (tags));
605
606         GST_BUFFER_TIMESTAMP (outbuf) = 0;
607       } else {
608         GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
609       }
610       GST_DEBUG_OBJECT (multipart,
611           "pushing buffer with timestamp %" GST_TIME_FORMAT,
612           GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)));
613       GST_DEBUG_OBJECT (multipart, "buffer has caps %" GST_PTR_FORMAT,
614           GST_BUFFER_CAPS (outbuf));
615       res = gst_pad_push (srcpad->pad, outbuf);
616       res = gst_multipart_combine_flows (multipart, srcpad, res);
617       if (res != GST_FLOW_OK)
618         break;
619     }
620   }
621
622 nodata:
623   gst_object_unref (multipart);
624
625   if (G_UNLIKELY (size == MULTIPART_DATA_ERROR))
626     return GST_FLOW_ERROR;
627   if (G_UNLIKELY (size == MULTIPART_DATA_EOS))
628     return GST_FLOW_UNEXPECTED;
629
630   return res;
631 }
632
633 static GstStateChangeReturn
634 gst_multipart_demux_change_state (GstElement * element,
635     GstStateChange transition)
636 {
637   GstMultipartDemux *multipart;
638   GstStateChangeReturn ret;
639
640   multipart = GST_MULTIPART_DEMUX (element);
641
642   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
643   if (ret == GST_STATE_CHANGE_FAILURE)
644     return ret;
645
646   switch (transition) {
647     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
648       break;
649     case GST_STATE_CHANGE_PAUSED_TO_READY:
650       multipart->header_completed = FALSE;
651       g_free (multipart->boundary);
652       multipart->boundary = NULL;
653       g_free (multipart->mime_type);
654       multipart->mime_type = NULL;
655       gst_adapter_clear (multipart->adapter);
656       break;
657     case GST_STATE_CHANGE_READY_TO_NULL:
658       break;
659     default:
660       break;
661   }
662
663   return ret;
664 }
665
666
667 static void
668 gst_multipart_set_property (GObject * object, guint prop_id,
669     const GValue * value, GParamSpec * pspec)
670 {
671   GstMultipartDemux *filter;
672
673   g_return_if_fail (GST_IS_MULTIPART_DEMUX (object));
674   filter = GST_MULTIPART_DEMUX (object);
675
676   switch (prop_id) {
677     case PROP_BOUNDARY:
678       /* Not really that usefull anymore as we can reliably autoscan */
679       g_free (filter->boundary);
680       filter->boundary = g_value_dup_string (value);
681       if (filter->boundary != NULL) {
682         filter->boundary_len = strlen (filter->boundary);
683       }
684       break;
685     case PROP_AUTOSCAN:
686       filter->autoscan = g_value_get_boolean (value);
687       break;
688     case PROP_SINGLE_STREAM:
689       filter->singleStream = g_value_get_boolean (value);
690       break;
691     default:
692       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
693       break;
694   }
695 }
696
697 static void
698 gst_multipart_get_property (GObject * object, guint prop_id,
699     GValue * value, GParamSpec * pspec)
700 {
701   GstMultipartDemux *filter;
702
703   g_return_if_fail (GST_IS_MULTIPART_DEMUX (object));
704   filter = GST_MULTIPART_DEMUX (object);
705
706   switch (prop_id) {
707     case PROP_BOUNDARY:
708       g_value_set_string (value, filter->boundary);
709       break;
710     case PROP_AUTOSCAN:
711       g_value_set_boolean (value, filter->autoscan);
712       break;
713     case PROP_SINGLE_STREAM:
714       g_value_set_boolean (value, filter->singleStream);
715       break;
716     default:
717       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
718       break;
719   }
720 }
721
722
723
724 gboolean
725 gst_multipart_demux_plugin_init (GstPlugin * plugin)
726 {
727   GST_DEBUG_CATEGORY_INIT (gst_multipart_demux_debug,
728       "multipartdemux", 0, "multipart demuxer");
729
730   return gst_element_register (plugin, "multipartdemux", GST_RANK_PRIMARY,
731       GST_TYPE_MULTIPART_DEMUX);
732 }