| /* multipart muxer plugin for GStreamer |
| * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-multipartmux |
| * |
| * MultipartMux uses the #GstCaps of the sink pad as the Content-type field for |
| * incoming buffers when muxing them to a multipart stream. Most of the time |
| * multipart streams are sequential JPEG frames. |
| * |
| * <refsect2> |
| * <title>Sample pipelines</title> |
| * |[ |
| * gst-launch-1.0 videotestsrc ! video/x-raw, framerate='(fraction)'5/1 ! jpegenc ! multipartmux ! filesink location=/tmp/test.multipart |
| * ]| a pipeline to mux 5 JPEG frames per second into a multipart stream |
| * stored to a file. |
| * </refsect2> |
| */ |
| |
| /* FIXME: drop/merge tag events, or at least send them delayed after stream-start */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "multipartmux.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_multipart_mux_debug); |
| #define GST_CAT_DEFAULT gst_multipart_mux_debug |
| |
| #define DEFAULT_BOUNDARY "ThisRandomString" |
| |
| enum |
| { |
| PROP_0, |
| PROP_BOUNDARY |
| /* FILL ME */ |
| }; |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("multipart/x-mixed-replace") |
| ); |
| |
| static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u", |
| GST_PAD_SINK, |
| GST_PAD_REQUEST, |
| GST_STATIC_CAPS_ANY /* we can take anything, really */ |
| ); |
| |
| typedef struct |
| { |
| const gchar *key; |
| const gchar *val; |
| } MimeTypeMap; |
| |
| /* convert from gst structure names to mime types. Add more when needed. */ |
| static const MimeTypeMap mimetypes[] = { |
| {"audio/x-mulaw", "audio/basic"}, |
| {NULL, NULL} |
| }; |
| |
| static void gst_multipart_mux_finalize (GObject * object); |
| |
| static gboolean gst_multipart_mux_handle_src_event (GstPad * pad, |
| GstObject * parent, GstEvent * event); |
| static GstPad *gst_multipart_mux_request_new_pad (GstElement * element, |
| GstPadTemplate * templ, const gchar * name, const GstCaps * caps); |
| static GstStateChangeReturn gst_multipart_mux_change_state (GstElement * |
| element, GstStateChange transition); |
| |
| static gboolean gst_multipart_mux_sink_event (GstCollectPads * pads, |
| GstCollectData * pad, GstEvent * event, GstMultipartMux * mux); |
| static GstFlowReturn gst_multipart_mux_collected (GstCollectPads * pads, |
| GstMultipartMux * mux); |
| |
| static void gst_multipart_mux_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_multipart_mux_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| #define gst_multipart_mux_parent_class parent_class |
| G_DEFINE_TYPE (GstMultipartMux, gst_multipart_mux, GST_TYPE_ELEMENT); |
| |
| static void |
| gst_multipart_mux_class_init (GstMultipartMuxClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| gint i; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->finalize = gst_multipart_mux_finalize; |
| gobject_class->get_property = gst_multipart_mux_get_property; |
| gobject_class->set_property = gst_multipart_mux_set_property; |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BOUNDARY, |
| g_param_spec_string ("boundary", "Boundary", "Boundary string", |
| DEFAULT_BOUNDARY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gstelement_class->request_new_pad = gst_multipart_mux_request_new_pad; |
| gstelement_class->change_state = gst_multipart_mux_change_state; |
| |
| gst_element_class_add_static_pad_template (gstelement_class, &src_factory); |
| gst_element_class_add_static_pad_template (gstelement_class, &sink_factory); |
| |
| gst_element_class_set_static_metadata (gstelement_class, "Multipart muxer", |
| "Codec/Muxer", "mux multipart streams", "Wim Taymans <wim@fluendo.com>"); |
| |
| /* populate mime types */ |
| klass->mimetypes = g_hash_table_new (g_str_hash, g_str_equal); |
| for (i = 0; mimetypes[i].key; i++) { |
| g_hash_table_insert (klass->mimetypes, (gpointer) mimetypes[i].key, |
| (gpointer) mimetypes[i].val); |
| } |
| } |
| |
| static void |
| gst_multipart_mux_init (GstMultipartMux * multipart_mux) |
| { |
| GstElementClass *klass = GST_ELEMENT_GET_CLASS (multipart_mux); |
| |
| multipart_mux->srcpad = |
| gst_pad_new_from_template (gst_element_class_get_pad_template (klass, |
| "src"), "src"); |
| gst_pad_set_event_function (multipart_mux->srcpad, |
| gst_multipart_mux_handle_src_event); |
| gst_element_add_pad (GST_ELEMENT (multipart_mux), multipart_mux->srcpad); |
| |
| multipart_mux->boundary = g_strdup (DEFAULT_BOUNDARY); |
| |
| multipart_mux->collect = gst_collect_pads_new (); |
| gst_collect_pads_set_event_function (multipart_mux->collect, |
| (GstCollectPadsEventFunction) |
| GST_DEBUG_FUNCPTR (gst_multipart_mux_sink_event), multipart_mux); |
| gst_collect_pads_set_function (multipart_mux->collect, |
| (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_multipart_mux_collected), |
| multipart_mux); |
| } |
| |
| static void |
| gst_multipart_mux_finalize (GObject * object) |
| { |
| GstMultipartMux *multipart_mux; |
| |
| multipart_mux = GST_MULTIPART_MUX (object); |
| |
| g_free (multipart_mux->boundary); |
| |
| if (multipart_mux->collect) |
| gst_object_unref (multipart_mux->collect); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static GstPad * |
| gst_multipart_mux_request_new_pad (GstElement * element, |
| GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) |
| { |
| GstMultipartMux *multipart_mux; |
| GstPad *newpad; |
| GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); |
| gchar *name; |
| |
| if (templ != gst_element_class_get_pad_template (klass, "sink_%u")) |
| goto wrong_template; |
| |
| multipart_mux = GST_MULTIPART_MUX (element); |
| |
| /* create new pad with the name */ |
| name = g_strdup_printf ("sink_%u", multipart_mux->numpads); |
| newpad = gst_pad_new_from_template (templ, name); |
| g_free (name); |
| |
| /* construct our own wrapper data structure for the pad to |
| * keep track of its status */ |
| { |
| GstMultipartPadData *multipartpad; |
| |
| multipartpad = (GstMultipartPadData *) |
| gst_collect_pads_add_pad (multipart_mux->collect, newpad, |
| sizeof (GstMultipartPadData), NULL, TRUE); |
| |
| /* save a pointer to our data in the pad */ |
| multipartpad->pad = newpad; |
| gst_pad_set_element_private (newpad, multipartpad); |
| multipart_mux->numpads++; |
| } |
| |
| /* add the pad to the element */ |
| gst_element_add_pad (element, newpad); |
| |
| return newpad; |
| |
| /* ERRORS */ |
| wrong_template: |
| { |
| g_warning ("multipart_mux: this is not our template!"); |
| return NULL; |
| } |
| } |
| |
| /* handle events */ |
| static gboolean |
| gst_multipart_mux_handle_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstEventType type; |
| |
| type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; |
| |
| switch (type) { |
| case GST_EVENT_SEEK: |
| /* disable seeking for now */ |
| return FALSE; |
| default: |
| break; |
| } |
| |
| return gst_pad_event_default (pad, parent, event); |
| } |
| |
| static const gchar * |
| gst_multipart_mux_get_mime (GstMultipartMux * mux, GstStructure * s) |
| { |
| GstMultipartMuxClass *klass; |
| const gchar *mime; |
| const gchar *name; |
| gint rate; |
| gint channels; |
| gint bitrate = 0; |
| |
| klass = GST_MULTIPART_MUX_GET_CLASS (mux); |
| |
| name = gst_structure_get_name (s); |
| |
| /* use hashtable to convert to mime type */ |
| mime = g_hash_table_lookup (klass->mimetypes, name); |
| if (mime == NULL) { |
| if (!strcmp (name, "audio/x-adpcm")) |
| gst_structure_get_int (s, "bitrate", &bitrate); |
| |
| switch (bitrate) { |
| case 16000: |
| mime = "audio/G726-16"; |
| break; |
| case 24000: |
| mime = "audio/G726-24"; |
| break; |
| case 32000: |
| mime = "audio/G726-32"; |
| break; |
| case 40000: |
| mime = "audio/G726-40"; |
| break; |
| default: |
| /* no mime type mapping, use name */ |
| mime = name; |
| break; |
| } |
| } |
| /* RFC2046 requires audio/basic to be mulaw 8000Hz mono */ |
| if (g_ascii_strcasecmp (mime, "audio/basic") == 0) { |
| if (gst_structure_get_int (s, "rate", &rate) && |
| gst_structure_get_int (s, "channels", &channels)) { |
| if (rate != 8000 || channels != 1) { |
| mime = name; |
| } |
| } else { |
| mime = name; |
| } |
| } |
| return mime; |
| } |
| |
| /* |
| * Given two pads, compare the buffers queued on it and return 0 if they have |
| * an equal priority, 1 if the new pad is better, -1 if the old pad is better |
| */ |
| static gint |
| gst_multipart_mux_compare_pads (GstMultipartMux * multipart_mux, |
| GstMultipartPadData * old, GstMultipartPadData * new) |
| { |
| guint64 oldtime, newtime; |
| |
| /* if the old pad doesn't contain anything or is even NULL, return |
| * the new pad as best candidate and vice versa */ |
| if (old == NULL || old->buffer == NULL) |
| return 1; |
| if (new == NULL || new->buffer == NULL) |
| return -1; |
| |
| if (GST_CLOCK_TIME_IS_VALID (old->dts_timestamp) && |
| GST_CLOCK_TIME_IS_VALID (new->dts_timestamp)) { |
| oldtime = old->dts_timestamp; |
| newtime = new->dts_timestamp; |
| } else { |
| oldtime = old->pts_timestamp; |
| newtime = new->pts_timestamp; |
| } |
| |
| /* no timestamp on old buffer, it must go first */ |
| if (oldtime == GST_CLOCK_TIME_NONE) |
| return -1; |
| |
| /* no timestamp on new buffer, it must go first */ |
| if (newtime == GST_CLOCK_TIME_NONE) |
| return 1; |
| |
| /* old buffer has higher timestamp, new one should go first */ |
| if (newtime < oldtime) |
| return 1; |
| /* new buffer has higher timestamp, old one should go first */ |
| else if (newtime > oldtime) |
| return -1; |
| |
| /* same priority if all of the above failed */ |
| return 0; |
| } |
| |
| /* make sure a buffer is queued on all pads, returns a pointer to an multipartpad |
| * that holds the best buffer or NULL when no pad was usable */ |
| static GstMultipartPadData * |
| gst_multipart_mux_queue_pads (GstMultipartMux * mux) |
| { |
| GSList *walk = NULL; |
| GstMultipartPadData *bestpad = NULL; |
| |
| g_return_val_if_fail (GST_IS_MULTIPART_MUX (mux), NULL); |
| |
| /* try to make sure we have a buffer from each usable pad first */ |
| walk = mux->collect->data; |
| while (walk) { |
| GstCollectData *data = (GstCollectData *) walk->data; |
| GstMultipartPadData *pad = (GstMultipartPadData *) data; |
| |
| walk = g_slist_next (walk); |
| |
| /* try to get a new buffer for this pad if needed and possible */ |
| if (pad->buffer == NULL) { |
| GstBuffer *buf = NULL; |
| |
| buf = gst_collect_pads_pop (mux->collect, data); |
| |
| /* Store timestamps with segment_start and preroll */ |
| if (buf && GST_BUFFER_PTS_IS_VALID (buf)) { |
| pad->pts_timestamp = |
| gst_segment_to_running_time (&data->segment, GST_FORMAT_TIME, |
| GST_BUFFER_PTS (buf)); |
| } else { |
| pad->pts_timestamp = GST_CLOCK_TIME_NONE; |
| } |
| if (buf && GST_BUFFER_DTS_IS_VALID (buf)) { |
| pad->dts_timestamp = |
| gst_segment_to_running_time (&data->segment, GST_FORMAT_TIME, |
| GST_BUFFER_DTS (buf)); |
| } else { |
| pad->dts_timestamp = GST_CLOCK_TIME_NONE; |
| } |
| |
| |
| pad->buffer = buf; |
| } |
| |
| /* we should have a buffer now, see if it is the best stream to |
| * pull on */ |
| if (pad->buffer != NULL) { |
| if (gst_multipart_mux_compare_pads (mux, bestpad, pad) > 0) { |
| bestpad = pad; |
| } |
| } |
| } |
| |
| return bestpad; |
| } |
| |
| static gboolean |
| gst_multipart_mux_sink_event (GstCollectPads * pads, GstCollectData * data, |
| GstEvent * event, GstMultipartMux * mux) |
| { |
| gboolean ret; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_STOP: |
| { |
| mux->need_segment = TRUE; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| ret = gst_collect_pads_event_default (pads, data, event, FALSE); |
| |
| return ret; |
| } |
| |
| /* basic idea: |
| * |
| * 1) find a pad to pull on, this is done by pulling on all pads and |
| * looking at the buffers to decide which one should be muxed first. |
| * 2) create a new buffer for the header |
| * 3) push both buffers on best pad, go to 1 |
| */ |
| static GstFlowReturn |
| gst_multipart_mux_collected (GstCollectPads * pads, GstMultipartMux * mux) |
| { |
| GstMultipartPadData *best; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gchar *header = NULL; |
| size_t headerlen; |
| GstBuffer *headerbuf = NULL; |
| GstBuffer *footerbuf = NULL; |
| GstBuffer *databuf = NULL; |
| GstStructure *structure = NULL; |
| GstCaps *caps; |
| const gchar *mime; |
| |
| GST_DEBUG_OBJECT (mux, "all pads are collected"); |
| |
| if (mux->need_stream_start) { |
| gchar s_id[32]; |
| |
| /* stream-start (FIXME: create id based on input ids) */ |
| g_snprintf (s_id, sizeof (s_id), "multipartmux-%08x", g_random_int ()); |
| gst_pad_push_event (mux->srcpad, gst_event_new_stream_start (s_id)); |
| |
| mux->need_stream_start = FALSE; |
| } |
| |
| /* queue buffers on all pads; find a buffer with the lowest timestamp */ |
| best = gst_multipart_mux_queue_pads (mux); |
| if (!best) |
| /* EOS */ |
| goto eos; |
| else if (!best->buffer) |
| goto buffer_error; |
| |
| /* If not negotiated yet set caps on src pad */ |
| if (!mux->negotiated) { |
| GstCaps *newcaps; |
| |
| newcaps = gst_caps_new_simple ("multipart/x-mixed-replace", |
| "boundary", G_TYPE_STRING, mux->boundary, NULL); |
| |
| if (!gst_pad_set_caps (mux->srcpad, newcaps)) { |
| gst_caps_unref (newcaps); |
| goto nego_error; |
| } |
| |
| gst_caps_unref (newcaps); |
| mux->negotiated = TRUE; |
| } |
| |
| /* see if we need to push a segment */ |
| if (mux->need_segment) { |
| GstClockTime time; |
| GstSegment segment; |
| |
| if (best->dts_timestamp != GST_CLOCK_TIME_NONE) { |
| time = best->dts_timestamp; |
| } else if (best->pts_timestamp != GST_CLOCK_TIME_NONE) { |
| time = best->pts_timestamp; |
| } else { |
| time = 0; |
| } |
| |
| /* for the segment, we take the first timestamp we see, we don't know the |
| * length and the position is 0 */ |
| gst_segment_init (&segment, GST_FORMAT_TIME); |
| segment.start = time; |
| |
| gst_pad_push_event (mux->srcpad, gst_event_new_segment (&segment)); |
| |
| mux->need_segment = FALSE; |
| } |
| |
| caps = gst_pad_get_current_caps (best->pad); |
| if (caps == NULL) |
| goto no_caps; |
| |
| structure = gst_caps_get_structure (caps, 0); |
| if (!structure) { |
| gst_caps_unref (caps); |
| goto no_caps; |
| } |
| |
| /* get the mime type for the structure */ |
| mime = gst_multipart_mux_get_mime (mux, structure); |
| gst_caps_unref (caps); |
| |
| header = g_strdup_printf ("--%s\r\nContent-Type: %s\r\n" |
| "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n", |
| mux->boundary, mime, gst_buffer_get_size (best->buffer)); |
| headerlen = strlen (header); |
| |
| headerbuf = gst_buffer_new_allocate (NULL, headerlen, NULL); |
| gst_buffer_fill (headerbuf, 0, header, headerlen); |
| g_free (header); |
| |
| /* the header has the same timestamps as the data buffer (which we will push |
| * below) and has a duration of 0 */ |
| GST_BUFFER_PTS (headerbuf) = best->pts_timestamp; |
| GST_BUFFER_DTS (headerbuf) = best->dts_timestamp; |
| GST_BUFFER_DURATION (headerbuf) = 0; |
| GST_BUFFER_OFFSET (headerbuf) = mux->offset; |
| mux->offset += headerlen; |
| GST_BUFFER_OFFSET_END (headerbuf) = mux->offset; |
| |
| GST_DEBUG_OBJECT (mux, "pushing %" G_GSIZE_FORMAT " bytes header buffer", |
| headerlen); |
| ret = gst_pad_push (mux->srcpad, headerbuf); |
| if (ret != GST_FLOW_OK) |
| /* push always takes ownership of the buffer, even after an error, so we |
| * don't need to unref headerbuf here. */ |
| goto beach; |
| |
| /* take best->buffer, we don't need to unref it later as we will push it |
| * now. */ |
| databuf = gst_buffer_make_writable (best->buffer); |
| best->buffer = NULL; |
| |
| /* we need to updated the timestamps to match the running_time */ |
| GST_BUFFER_PTS (databuf) = best->pts_timestamp; |
| GST_BUFFER_DTS (databuf) = best->dts_timestamp; |
| GST_BUFFER_OFFSET (databuf) = mux->offset; |
| mux->offset += gst_buffer_get_size (databuf); |
| GST_BUFFER_OFFSET_END (databuf) = mux->offset; |
| GST_BUFFER_FLAG_SET (databuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| GST_DEBUG_OBJECT (mux, "pushing %" G_GSIZE_FORMAT " bytes data buffer", |
| gst_buffer_get_size (databuf)); |
| ret = gst_pad_push (mux->srcpad, databuf); |
| if (ret != GST_FLOW_OK) |
| /* push always takes ownership of the buffer, even after an error, so we |
| * don't need to unref headerbuf here. */ |
| goto beach; |
| |
| footerbuf = gst_buffer_new_allocate (NULL, 2, NULL); |
| gst_buffer_fill (footerbuf, 0, "\r\n", 2); |
| |
| /* the footer has the same timestamps as the data buffer and has a |
| * duration of 0 */ |
| GST_BUFFER_PTS (footerbuf) = best->pts_timestamp; |
| GST_BUFFER_DTS (footerbuf) = best->dts_timestamp; |
| GST_BUFFER_DURATION (footerbuf) = 0; |
| GST_BUFFER_OFFSET (footerbuf) = mux->offset; |
| mux->offset += 2; |
| GST_BUFFER_OFFSET_END (footerbuf) = mux->offset; |
| GST_BUFFER_FLAG_SET (footerbuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| GST_DEBUG_OBJECT (mux, "pushing 2 bytes footer buffer"); |
| ret = gst_pad_push (mux->srcpad, footerbuf); |
| |
| beach: |
| if (best && best->buffer) { |
| gst_buffer_unref (best->buffer); |
| best->buffer = NULL; |
| } |
| return ret; |
| |
| /* ERRORS */ |
| buffer_error: |
| { |
| /* There is a best but no buffer, this is not quite right.. */ |
| GST_ELEMENT_ERROR (mux, STREAM, FAILED, (NULL), ("internal muxing error")); |
| ret = GST_FLOW_ERROR; |
| goto beach; |
| } |
| eos: |
| { |
| GST_DEBUG_OBJECT (mux, "Pushing EOS"); |
| gst_pad_push_event (mux->srcpad, gst_event_new_eos ()); |
| ret = GST_FLOW_EOS; |
| goto beach; |
| } |
| nego_error: |
| { |
| GST_WARNING_OBJECT (mux, "failed to set caps"); |
| GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL)); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto beach; |
| } |
| no_caps: |
| { |
| GST_WARNING_OBJECT (mux, "no caps on the incoming buffer %p", best->buffer); |
| GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL)); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto beach; |
| } |
| } |
| |
| static void |
| gst_multipart_mux_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec) |
| { |
| GstMultipartMux *mux; |
| |
| mux = GST_MULTIPART_MUX (object); |
| |
| switch (prop_id) { |
| case PROP_BOUNDARY: |
| g_value_set_string (value, mux->boundary); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_multipart_mux_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec) |
| { |
| GstMultipartMux *mux; |
| |
| mux = GST_MULTIPART_MUX (object); |
| |
| switch (prop_id) { |
| case PROP_BOUNDARY: |
| g_free (mux->boundary); |
| mux->boundary = g_strdup (g_value_get_string (value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static GstStateChangeReturn |
| gst_multipart_mux_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstMultipartMux *multipart_mux; |
| GstStateChangeReturn ret; |
| |
| multipart_mux = GST_MULTIPART_MUX (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| multipart_mux->offset = 0; |
| multipart_mux->negotiated = FALSE; |
| multipart_mux->need_segment = TRUE; |
| multipart_mux->need_stream_start = TRUE; |
| GST_DEBUG_OBJECT (multipart_mux, "starting collect pads"); |
| gst_collect_pads_start (multipart_mux->collect); |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| GST_DEBUG_OBJECT (multipart_mux, "stopping collect pads"); |
| gst_collect_pads_stop (multipart_mux->collect); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| return ret; |
| |
| switch (transition) { |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| gboolean |
| gst_multipart_mux_plugin_init (GstPlugin * plugin) |
| { |
| GST_DEBUG_CATEGORY_INIT (gst_multipart_mux_debug, "multipartmux", 0, |
| "multipart muxer"); |
| |
| return gst_element_register (plugin, "multipartmux", GST_RANK_NONE, |
| GST_TYPE_MULTIPART_MUX); |
| } |