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