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