1 /* multipart muxer plugin for GStreamer
2 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-multipartmux
22 * @title: multipartmux
23 *
24 * MultipartMux uses the #GstCaps of the sink pad as the Content-type field for
25 * incoming buffers when muxing them to a multipart stream. Most of the time
26 * multipart streams are sequential JPEG frames.
27 *
28 * ## Sample pipelines
29 * |[
30 * gst-launch-1.0 videotestsrc ! video/x-raw, framerate='(fraction)'5/1 ! jpegenc ! multipartmux ! filesink location=/tmp/test.multipart
31 * ]| a pipeline to mux 5 JPEG frames per second into a multipart stream
32 * stored to a file.
33 *
34 */
35
36 /* FIXME: drop/merge tag events, or at least send them delayed after stream-start */
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40
41 #include "multipartmux.h"
42
43 GST_DEBUG_CATEGORY_STATIC (gst_multipart_mux_debug);
44 #define GST_CAT_DEFAULT gst_multipart_mux_debug
45
46 #define DEFAULT_BOUNDARY "ThisRandomString"
47
48 enum
49 {
50 PROP_0,
51 PROP_BOUNDARY
52 /* FILL ME */
53 };
54
55 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
56 GST_PAD_SRC,
57 GST_PAD_ALWAYS,
58 GST_STATIC_CAPS ("multipart/x-mixed-replace")
59 );
60
61 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
62 GST_PAD_SINK,
63 GST_PAD_REQUEST,
64 GST_STATIC_CAPS_ANY /* we can take anything, really */
65 );
66
67 typedef struct
68 {
69 const gchar *key;
70 const gchar *val;
71 } MimeTypeMap;
72
73 /* convert from gst structure names to mime types. Add more when needed. */
74 static const MimeTypeMap mimetypes[] = {
75 {"audio/x-mulaw", "audio/basic"},
76 {NULL, NULL}
77 };
78
79 static void gst_multipart_mux_finalize (GObject * object);
80
81 static gboolean gst_multipart_mux_handle_src_event (GstPad * pad,
82 GstObject * parent, GstEvent * event);
83 static GstPad *gst_multipart_mux_request_new_pad (GstElement * element,
84 GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
85 static GstStateChangeReturn gst_multipart_mux_change_state (GstElement *
86 element, GstStateChange transition);
87
88 static gboolean gst_multipart_mux_sink_event (GstCollectPads * pads,
89 GstCollectData * pad, GstEvent * event, GstMultipartMux * mux);
90 static GstFlowReturn gst_multipart_mux_collected (GstCollectPads * pads,
91 GstMultipartMux * mux);
92
93 static void gst_multipart_mux_set_property (GObject * object, guint prop_id,
94 const GValue * value, GParamSpec * pspec);
95 static void gst_multipart_mux_get_property (GObject * object, guint prop_id,
96 GValue * value, GParamSpec * pspec);
97
98 #define gst_multipart_mux_parent_class parent_class
99 G_DEFINE_TYPE (GstMultipartMux, gst_multipart_mux, GST_TYPE_ELEMENT);
100 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (multipartmux, "multipartmux",
101 GST_RANK_NONE, GST_TYPE_MULTIPART_MUX,
102 GST_DEBUG_CATEGORY_INIT (gst_multipart_mux_debug, "multipartmux", 0,
103 "multipart muxer"));
104
105 static void
gst_multipart_mux_class_init(GstMultipartMuxClass * klass)106 gst_multipart_mux_class_init (GstMultipartMuxClass * klass)
107 {
108 GObjectClass *gobject_class;
109 GstElementClass *gstelement_class;
110 gint i;
111
112 gobject_class = (GObjectClass *) klass;
113 gstelement_class = (GstElementClass *) klass;
114
115 parent_class = g_type_class_peek_parent (klass);
116
117 gobject_class->finalize = gst_multipart_mux_finalize;
118 gobject_class->get_property = gst_multipart_mux_get_property;
119 gobject_class->set_property = gst_multipart_mux_set_property;
120
121 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BOUNDARY,
122 g_param_spec_string ("boundary", "Boundary", "Boundary string",
123 DEFAULT_BOUNDARY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
124
125 gstelement_class->request_new_pad = gst_multipart_mux_request_new_pad;
126 gstelement_class->change_state = gst_multipart_mux_change_state;
127
128 gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
129 gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
130
131 gst_element_class_set_static_metadata (gstelement_class, "Multipart muxer",
132 "Codec/Muxer", "mux multipart streams", "Wim Taymans <wim@fluendo.com>");
133
134 /* populate mime types */
135 klass->mimetypes = g_hash_table_new (g_str_hash, g_str_equal);
136 for (i = 0; mimetypes[i].key; i++) {
137 g_hash_table_insert (klass->mimetypes, (gpointer) mimetypes[i].key,
138 (gpointer) mimetypes[i].val);
139 }
140 }
141
142 static void
gst_multipart_mux_init(GstMultipartMux * multipart_mux)143 gst_multipart_mux_init (GstMultipartMux * multipart_mux)
144 {
145 GstElementClass *klass = GST_ELEMENT_GET_CLASS (multipart_mux);
146
147 multipart_mux->srcpad =
148 gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
149 "src"), "src");
150 gst_pad_set_event_function (multipart_mux->srcpad,
151 gst_multipart_mux_handle_src_event);
152 gst_element_add_pad (GST_ELEMENT (multipart_mux), multipart_mux->srcpad);
153
154 multipart_mux->boundary = g_strdup (DEFAULT_BOUNDARY);
155
156 multipart_mux->collect = gst_collect_pads_new ();
157 gst_collect_pads_set_event_function (multipart_mux->collect,
158 (GstCollectPadsEventFunction)
159 GST_DEBUG_FUNCPTR (gst_multipart_mux_sink_event), multipart_mux);
160 gst_collect_pads_set_function (multipart_mux->collect,
161 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_multipart_mux_collected),
162 multipart_mux);
163 }
164
165 static void
gst_multipart_mux_finalize(GObject * object)166 gst_multipart_mux_finalize (GObject * object)
167 {
168 GstMultipartMux *multipart_mux;
169
170 multipart_mux = GST_MULTIPART_MUX (object);
171
172 g_free (multipart_mux->boundary);
173
174 if (multipart_mux->collect)
175 gst_object_unref (multipart_mux->collect);
176
177 G_OBJECT_CLASS (parent_class)->finalize (object);
178 }
179
180 static GstPad *
gst_multipart_mux_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * req_name,const GstCaps * caps)181 gst_multipart_mux_request_new_pad (GstElement * element,
182 GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
183 {
184 GstMultipartMux *multipart_mux;
185 GstPad *newpad;
186 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
187 gchar *name;
188
189 if (templ != gst_element_class_get_pad_template (klass, "sink_%u"))
190 goto wrong_template;
191
192 multipart_mux = GST_MULTIPART_MUX (element);
193
194 /* create new pad with the name */
195 name = g_strdup_printf ("sink_%u", multipart_mux->numpads);
196 newpad = gst_pad_new_from_template (templ, name);
197 g_free (name);
198
199 /* construct our own wrapper data structure for the pad to
200 * keep track of its status */
201 {
202 GstMultipartPadData *multipartpad;
203
204 multipartpad = (GstMultipartPadData *)
205 gst_collect_pads_add_pad (multipart_mux->collect, newpad,
206 sizeof (GstMultipartPadData), NULL, TRUE);
207
208 /* save a pointer to our data in the pad */
209 multipartpad->pad = newpad;
210 gst_pad_set_element_private (newpad, multipartpad);
211 multipart_mux->numpads++;
212 }
213
214 /* add the pad to the element */
215 gst_element_add_pad (element, newpad);
216
217 return newpad;
218
219 /* ERRORS */
220 wrong_template:
221 {
222 g_warning ("multipart_mux: this is not our template!");
223 return NULL;
224 }
225 }
226
227 /* handle events */
228 static gboolean
gst_multipart_mux_handle_src_event(GstPad * pad,GstObject * parent,GstEvent * event)229 gst_multipart_mux_handle_src_event (GstPad * pad, GstObject * parent,
230 GstEvent * event)
231 {
232 GstEventType type;
233
234 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
235
236 switch (type) {
237 case GST_EVENT_SEEK:
238 /* disable seeking for now */
239 return FALSE;
240 default:
241 break;
242 }
243
244 return gst_pad_event_default (pad, parent, event);
245 }
246
247 static const gchar *
gst_multipart_mux_get_mime(GstMultipartMux * mux,GstStructure * s)248 gst_multipart_mux_get_mime (GstMultipartMux * mux, GstStructure * s)
249 {
250 GstMultipartMuxClass *klass;
251 const gchar *mime;
252 const gchar *name;
253 gint rate;
254 gint channels;
255 gint bitrate = 0;
256
257 klass = GST_MULTIPART_MUX_GET_CLASS (mux);
258
259 name = gst_structure_get_name (s);
260
261 /* use hashtable to convert to mime type */
262 mime = g_hash_table_lookup (klass->mimetypes, name);
263 if (mime == NULL) {
264 if (!strcmp (name, "audio/x-adpcm"))
265 gst_structure_get_int (s, "bitrate", &bitrate);
266
267 switch (bitrate) {
268 case 16000:
269 mime = "audio/G726-16";
270 break;
271 case 24000:
272 mime = "audio/G726-24";
273 break;
274 case 32000:
275 mime = "audio/G726-32";
276 break;
277 case 40000:
278 mime = "audio/G726-40";
279 break;
280 default:
281 /* no mime type mapping, use name */
282 mime = name;
283 break;
284 }
285 }
286 /* RFC2046 requires audio/basic to be mulaw 8000Hz mono */
287 if (g_ascii_strcasecmp (mime, "audio/basic") == 0) {
288 if (gst_structure_get_int (s, "rate", &rate) &&
289 gst_structure_get_int (s, "channels", &channels)) {
290 if (rate != 8000 || channels != 1) {
291 mime = name;
292 }
293 } else {
294 mime = name;
295 }
296 }
297 return mime;
298 }
299
300 /*
301 * Given two pads, compare the buffers queued on it and return 0 if they have
302 * an equal priority, 1 if the new pad is better, -1 if the old pad is better
303 */
304 static gint
gst_multipart_mux_compare_pads(GstMultipartMux * multipart_mux,GstMultipartPadData * old,GstMultipartPadData * new)305 gst_multipart_mux_compare_pads (GstMultipartMux * multipart_mux,
306 GstMultipartPadData * old, GstMultipartPadData * new)
307 {
308 guint64 oldtime, newtime;
309
310 /* if the old pad doesn't contain anything or is even NULL, return
311 * the new pad as best candidate and vice versa */
312 if (old == NULL || old->buffer == NULL)
313 return 1;
314 if (new == NULL || new->buffer == NULL)
315 return -1;
316
317 if (GST_CLOCK_TIME_IS_VALID (old->dts_timestamp) &&
318 GST_CLOCK_TIME_IS_VALID (new->dts_timestamp)) {
319 oldtime = old->dts_timestamp;
320 newtime = new->dts_timestamp;
321 } else {
322 oldtime = old->pts_timestamp;
323 newtime = new->pts_timestamp;
324 }
325
326 /* no timestamp on old buffer, it must go first */
327 if (oldtime == GST_CLOCK_TIME_NONE)
328 return -1;
329
330 /* no timestamp on new buffer, it must go first */
331 if (newtime == GST_CLOCK_TIME_NONE)
332 return 1;
333
334 /* old buffer has higher timestamp, new one should go first */
335 if (newtime < oldtime)
336 return 1;
337 /* new buffer has higher timestamp, old one should go first */
338 else if (newtime > oldtime)
339 return -1;
340
341 /* same priority if all of the above failed */
342 return 0;
343 }
344
345 /* make sure a buffer is queued on all pads, returns a pointer to an multipartpad
346 * that holds the best buffer or NULL when no pad was usable */
347 static GstMultipartPadData *
gst_multipart_mux_queue_pads(GstMultipartMux * mux)348 gst_multipart_mux_queue_pads (GstMultipartMux * mux)
349 {
350 GSList *walk = NULL;
351 GstMultipartPadData *bestpad = NULL;
352
353 g_return_val_if_fail (GST_IS_MULTIPART_MUX (mux), NULL);
354
355 /* try to make sure we have a buffer from each usable pad first */
356 walk = mux->collect->data;
357 while (walk) {
358 GstCollectData *data = (GstCollectData *) walk->data;
359 GstMultipartPadData *pad = (GstMultipartPadData *) data;
360
361 walk = g_slist_next (walk);
362
363 /* try to get a new buffer for this pad if needed and possible */
364 if (pad->buffer == NULL) {
365 GstBuffer *buf = NULL;
366
367 buf = gst_collect_pads_pop (mux->collect, data);
368
369 /* Store timestamps with segment_start and preroll */
370 if (buf && GST_BUFFER_PTS_IS_VALID (buf)) {
371 pad->pts_timestamp =
372 gst_segment_to_running_time (&data->segment, GST_FORMAT_TIME,
373 GST_BUFFER_PTS (buf));
374 } else {
375 pad->pts_timestamp = GST_CLOCK_TIME_NONE;
376 }
377 if (buf && GST_BUFFER_DTS_IS_VALID (buf)) {
378 pad->dts_timestamp =
379 gst_segment_to_running_time (&data->segment, GST_FORMAT_TIME,
380 GST_BUFFER_DTS (buf));
381 } else {
382 pad->dts_timestamp = GST_CLOCK_TIME_NONE;
383 }
384
385
386 pad->buffer = buf;
387 }
388
389 /* we should have a buffer now, see if it is the best stream to
390 * pull on */
391 if (pad->buffer != NULL) {
392 if (gst_multipart_mux_compare_pads (mux, bestpad, pad) > 0) {
393 bestpad = pad;
394 }
395 }
396 }
397
398 return bestpad;
399 }
400
401 static gboolean
gst_multipart_mux_sink_event(GstCollectPads * pads,GstCollectData * data,GstEvent * event,GstMultipartMux * mux)402 gst_multipart_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
403 GstEvent * event, GstMultipartMux * mux)
404 {
405 gboolean ret;
406
407 switch (GST_EVENT_TYPE (event)) {
408 case GST_EVENT_FLUSH_STOP:
409 {
410 mux->need_segment = TRUE;
411 break;
412 }
413 default:
414 break;
415 }
416
417 ret = gst_collect_pads_event_default (pads, data, event, FALSE);
418
419 return ret;
420 }
421
422 /* basic idea:
423 *
424 * 1) find a pad to pull on, this is done by pulling on all pads and
425 * looking at the buffers to decide which one should be muxed first.
426 * 2) create a new buffer for the header
427 * 3) push both buffers on best pad, go to 1
428 */
429 static GstFlowReturn
gst_multipart_mux_collected(GstCollectPads * pads,GstMultipartMux * mux)430 gst_multipart_mux_collected (GstCollectPads * pads, GstMultipartMux * mux)
431 {
432 GstMultipartPadData *best;
433 GstFlowReturn ret = GST_FLOW_OK;
434 gchar *header = NULL;
435 size_t headerlen;
436 GstBuffer *headerbuf = NULL;
437 GstBuffer *footerbuf = NULL;
438 GstBuffer *databuf = NULL;
439 GstStructure *structure = NULL;
440 GstCaps *caps;
441 const gchar *mime;
442
443 GST_DEBUG_OBJECT (mux, "all pads are collected");
444
445 if (mux->need_stream_start) {
446 gchar s_id[32];
447
448 /* stream-start (FIXME: create id based on input ids) */
449 g_snprintf (s_id, sizeof (s_id), "multipartmux-%08x", g_random_int ());
450 gst_pad_push_event (mux->srcpad, gst_event_new_stream_start (s_id));
451
452 mux->need_stream_start = FALSE;
453 }
454
455 /* queue buffers on all pads; find a buffer with the lowest timestamp */
456 best = gst_multipart_mux_queue_pads (mux);
457 if (!best)
458 /* EOS */
459 goto eos;
460 else if (!best->buffer)
461 goto buffer_error;
462
463 /* If not negotiated yet set caps on src pad */
464 if (!mux->negotiated) {
465 GstCaps *newcaps;
466
467 newcaps = gst_caps_new_simple ("multipart/x-mixed-replace",
468 "boundary", G_TYPE_STRING, mux->boundary, NULL);
469
470 if (!gst_pad_set_caps (mux->srcpad, newcaps)) {
471 gst_caps_unref (newcaps);
472 goto nego_error;
473 }
474
475 gst_caps_unref (newcaps);
476 mux->negotiated = TRUE;
477 }
478
479 /* see if we need to push a segment */
480 if (mux->need_segment) {
481 GstClockTime time;
482 GstSegment segment;
483
484 if (best->dts_timestamp != GST_CLOCK_TIME_NONE) {
485 time = best->dts_timestamp;
486 } else if (best->pts_timestamp != GST_CLOCK_TIME_NONE) {
487 time = best->pts_timestamp;
488 } else {
489 time = 0;
490 }
491
492 /* for the segment, we take the first timestamp we see, we don't know the
493 * length and the position is 0 */
494 gst_segment_init (&segment, GST_FORMAT_TIME);
495 segment.start = time;
496
497 gst_pad_push_event (mux->srcpad, gst_event_new_segment (&segment));
498
499 mux->need_segment = FALSE;
500 }
501
502 caps = gst_pad_get_current_caps (best->pad);
503 if (caps == NULL)
504 goto no_caps;
505
506 structure = gst_caps_get_structure (caps, 0);
507 if (!structure) {
508 gst_caps_unref (caps);
509 goto no_caps;
510 }
511
512 /* get the mime type for the structure */
513 mime = gst_multipart_mux_get_mime (mux, structure);
514 gst_caps_unref (caps);
515
516 header = g_strdup_printf ("--%s\r\nContent-Type: %s\r\n"
517 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n",
518 mux->boundary, mime, gst_buffer_get_size (best->buffer));
519 headerlen = strlen (header);
520
521 headerbuf = gst_buffer_new_allocate (NULL, headerlen, NULL);
522 gst_buffer_fill (headerbuf, 0, header, headerlen);
523 g_free (header);
524
525 /* the header has the same timestamps as the data buffer (which we will push
526 * below) and has a duration of 0 */
527 GST_BUFFER_PTS (headerbuf) = best->pts_timestamp;
528 GST_BUFFER_DTS (headerbuf) = best->dts_timestamp;
529 GST_BUFFER_DURATION (headerbuf) = 0;
530 GST_BUFFER_OFFSET (headerbuf) = mux->offset;
531 mux->offset += headerlen;
532 GST_BUFFER_OFFSET_END (headerbuf) = mux->offset;
533
534 GST_DEBUG_OBJECT (mux, "pushing %" G_GSIZE_FORMAT " bytes header buffer",
535 headerlen);
536 ret = gst_pad_push (mux->srcpad, headerbuf);
537 if (ret != GST_FLOW_OK)
538 /* push always takes ownership of the buffer, even after an error, so we
539 * don't need to unref headerbuf here. */
540 goto beach;
541
542 /* take best->buffer, we don't need to unref it later as we will push it
543 * now. */
544 databuf = gst_buffer_make_writable (best->buffer);
545 best->buffer = NULL;
546
547 /* we need to updated the timestamps to match the running_time */
548 GST_BUFFER_PTS (databuf) = best->pts_timestamp;
549 GST_BUFFER_DTS (databuf) = best->dts_timestamp;
550 GST_BUFFER_OFFSET (databuf) = mux->offset;
551 mux->offset += gst_buffer_get_size (databuf);
552 GST_BUFFER_OFFSET_END (databuf) = mux->offset;
553 GST_BUFFER_FLAG_SET (databuf, GST_BUFFER_FLAG_DELTA_UNIT);
554
555 GST_DEBUG_OBJECT (mux, "pushing %" G_GSIZE_FORMAT " bytes data buffer",
556 gst_buffer_get_size (databuf));
557 ret = gst_pad_push (mux->srcpad, databuf);
558 if (ret != GST_FLOW_OK)
559 /* push always takes ownership of the buffer, even after an error, so we
560 * don't need to unref headerbuf here. */
561 goto beach;
562
563 footerbuf = gst_buffer_new_allocate (NULL, 2, NULL);
564 gst_buffer_fill (footerbuf, 0, "\r\n", 2);
565
566 /* the footer has the same timestamps as the data buffer and has a
567 * duration of 0 */
568 GST_BUFFER_PTS (footerbuf) = best->pts_timestamp;
569 GST_BUFFER_DTS (footerbuf) = best->dts_timestamp;
570 GST_BUFFER_DURATION (footerbuf) = 0;
571 GST_BUFFER_OFFSET (footerbuf) = mux->offset;
572 mux->offset += 2;
573 GST_BUFFER_OFFSET_END (footerbuf) = mux->offset;
574 GST_BUFFER_FLAG_SET (footerbuf, GST_BUFFER_FLAG_DELTA_UNIT);
575
576 GST_DEBUG_OBJECT (mux, "pushing 2 bytes footer buffer");
577 ret = gst_pad_push (mux->srcpad, footerbuf);
578
579 beach:
580 if (best && best->buffer) {
581 gst_buffer_unref (best->buffer);
582 best->buffer = NULL;
583 }
584 return ret;
585
586 /* ERRORS */
587 buffer_error:
588 {
589 /* There is a best but no buffer, this is not quite right.. */
590 GST_ELEMENT_ERROR (mux, STREAM, FAILED, (NULL), ("internal muxing error"));
591 ret = GST_FLOW_ERROR;
592 goto beach;
593 }
594 eos:
595 {
596 GST_DEBUG_OBJECT (mux, "Pushing EOS");
597 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
598 ret = GST_FLOW_EOS;
599 goto beach;
600 }
601 nego_error:
602 {
603 GST_WARNING_OBJECT (mux, "failed to set caps");
604 GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL));
605 ret = GST_FLOW_NOT_NEGOTIATED;
606 goto beach;
607 }
608 no_caps:
609 {
610 GST_WARNING_OBJECT (mux, "no caps on the incoming buffer %p", best->buffer);
611 GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL));
612 ret = GST_FLOW_NOT_NEGOTIATED;
613 goto beach;
614 }
615 }
616
617 static void
gst_multipart_mux_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)618 gst_multipart_mux_get_property (GObject * object,
619 guint prop_id, GValue * value, GParamSpec * pspec)
620 {
621 GstMultipartMux *mux;
622
623 mux = GST_MULTIPART_MUX (object);
624
625 switch (prop_id) {
626 case PROP_BOUNDARY:
627 g_value_set_string (value, mux->boundary);
628 break;
629 default:
630 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
631 break;
632 }
633 }
634
635 static void
gst_multipart_mux_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)636 gst_multipart_mux_set_property (GObject * object,
637 guint prop_id, const GValue * value, GParamSpec * pspec)
638 {
639 GstMultipartMux *mux;
640
641 mux = GST_MULTIPART_MUX (object);
642
643 switch (prop_id) {
644 case PROP_BOUNDARY:
645 g_free (mux->boundary);
646 mux->boundary = g_value_dup_string (value);
647 break;
648 default:
649 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
650 break;
651 }
652 }
653
654 static GstStateChangeReturn
gst_multipart_mux_change_state(GstElement * element,GstStateChange transition)655 gst_multipart_mux_change_state (GstElement * element, GstStateChange transition)
656 {
657 GstMultipartMux *multipart_mux;
658 GstStateChangeReturn ret;
659
660 multipart_mux = GST_MULTIPART_MUX (element);
661
662 switch (transition) {
663 case GST_STATE_CHANGE_READY_TO_PAUSED:
664 multipart_mux->offset = 0;
665 multipart_mux->negotiated = FALSE;
666 multipart_mux->need_segment = TRUE;
667 multipart_mux->need_stream_start = TRUE;
668 GST_DEBUG_OBJECT (multipart_mux, "starting collect pads");
669 gst_collect_pads_start (multipart_mux->collect);
670 break;
671 case GST_STATE_CHANGE_PAUSED_TO_READY:
672 GST_DEBUG_OBJECT (multipart_mux, "stopping collect pads");
673 gst_collect_pads_stop (multipart_mux->collect);
674 break;
675 default:
676 break;
677 }
678
679 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
680 if (ret == GST_STATE_CHANGE_FAILURE)
681 return ret;
682
683 switch (transition) {
684 default:
685 break;
686 }
687
688 return ret;
689 }
690