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