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