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