/* concat muxer plugin for GStreamer * Copyright (C) 2004 Renato Filhps * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:element-concatmux * @short_description: Concat that takes one or several digital streams * and muxes them to a single stream. * * * Sample pipelines * * Here is a simple pipeline to concat 2 files into a another file: * * gst-launch concatmux name=m ! filesink location=output.txt filesrc location=file_a.txt ! m. filesrc location=file_b.txt ! m. * * * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include GST_DEBUG_CATEGORY_STATIC(gst_concat_mux_debug); #define GST_CAT_DEFAULT gst_concat_mux_debug #define GST_TYPE_CONCAT_MUX (gst_concat_mux_get_type()) #define GST_CONCAT_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CONCAT_MUX, GstConcatMux)) #define GST_CONCAT_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CONCAT_MUX, GstConcatMux)) #define GST_IS_CONCAT_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CONCAT_MUX)) #define GST_IS_CONCAT_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CONCAT_MUX)) typedef struct _GstConcatMux GstConcatMux; typedef struct _GstConcatMuxClass GstConcatMuxClass; /** * GstConcatMux: * * The opaque #GstConcatMux structure. */ struct _GstConcatMux { GstElement element; /* Caps */ GstCaps *sink_caps; /* pad */ GstPad *srcpad; GstPad *sinkpad; /* sinkpads */ GSList *sinks; gint numpads; /* offset in stream */ guint64 offset; guint64 timeoffset; guint64 start_time; gboolean negotiated; gboolean resync; gboolean done; }; struct _GstConcatMuxClass { GstElementClass parent_class; }; /* elementfactory information */ static const GstElementDetails gst_concat_mux_details = GST_ELEMENT_DETAILS("Concat muxer", "Codec/Muxer", "mux concat streams", "Renato Filho "); static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE("sink_%d", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY); static void gst_concat_mux_base_init(gpointer g_class); static void gst_concat_mux_class_init(GstConcatMuxClass * klass); static void gst_concat_mux_init(GstConcatMux * concat_mux); static void gst_concat_mux_finalize(GObject * object); static gboolean gst_concat_mux_handle_src_event(GstPad * pad, GstEvent * event); static gboolean gst_concat_mux_handle_sink_event(GstPad * pad, GstEvent * event); static GstPad *gst_concat_mux_request_new_pad(GstElement * element, GstPadTemplate * templ, const gchar * name); static GstStateChangeReturn gst_concat_mux_change_state(GstElement * element, GstStateChange transition); static GstFlowReturn gst_concat_mux_chain(GstPad * pad, GstBuffer * buf); static void gst_concat_mux_clear(GstConcatMux * mux); static GstElementClass *parent_class = NULL; GType gst_concat_mux_get_type(void) { static GType concat_mux_type = 0; if (!concat_mux_type) { static const GTypeInfo concat_mux_info = { sizeof(GstConcatMuxClass), gst_concat_mux_base_init, NULL, (GClassInitFunc) gst_concat_mux_class_init, NULL, NULL, sizeof(GstConcatMux), 0, (GInstanceInitFunc) gst_concat_mux_init, }; concat_mux_type = g_type_register_static(GST_TYPE_ELEMENT, "GstConcatMux", &concat_mux_info, 0); } return concat_mux_type; } static void gst_concat_mux_base_init(gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); gst_element_class_add_pad_template(element_class, gst_static_pad_template_get (&src_factory)); gst_element_class_add_pad_template(element_class, gst_static_pad_template_get (&sink_factory)); gst_element_class_set_details(element_class, &gst_concat_mux_details); } static void gst_concat_mux_class_init(GstConcatMuxClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; parent_class = g_type_class_peek_parent(klass); gobject_class->finalize = gst_concat_mux_finalize; gstelement_class->request_new_pad = gst_concat_mux_request_new_pad; gstelement_class->change_state = gst_concat_mux_change_state; } static void gst_concat_mux_init(GstConcatMux * concat_mux) { GstElementClass *klass = GST_ELEMENT_GET_CLASS(concat_mux); concat_mux->srcpad = gst_pad_new_from_template(gst_element_class_get_pad_template(klass, "src"), "src"); gst_pad_set_event_function(concat_mux->srcpad, gst_concat_mux_handle_src_event); gst_element_add_pad(GST_ELEMENT(concat_mux), concat_mux->srcpad); } static void gst_concat_mux_finalize(GObject * object) { GstConcatMux *concat_mux; concat_mux = GST_CONCAT_MUX(object); gst_concat_mux_clear(GST_CONCAT_MUX(object)); G_OBJECT_CLASS(parent_class)->finalize(object); } static void gst_concat_mux_free_pad(gpointer data, gpointer user_data) { GMutex *mux; mux = gst_pad_get_element_private(GST_PAD(data)); g_mutex_unlock(mux); g_mutex_free(mux); gst_object_unref(GST_OBJECT(data)); } static void gst_concat_mux_clear(GstConcatMux * mux) { mux->resync = TRUE; mux->timeoffset = 0; mux->offset = 0; mux->negotiated = FALSE; mux->done = TRUE; if (mux->sinks != NULL) { g_slist_foreach(mux->sinks, gst_concat_mux_free_pad, mux); g_slist_free(mux->sinks); mux->sinks = NULL; } } static GstPadLinkReturn gst_concat_mux_sinkconnect(GstPad * pad, GstPad * peer) { gchar *pad_name = NULL; GstConcatMux *concat_mux; concat_mux = GST_CONCAT_MUX(gst_pad_get_parent(pad)); if (concat_mux->sink_caps != NULL) { GstCaps *peer_caps = gst_pad_get_caps(peer); GstCaps *intersect; intersect = gst_caps_intersect(concat_mux->sink_caps, peer_caps); if (intersect == NULL) { gst_caps_unref(peer_caps); return GST_PAD_LINK_NOFORMAT; } gst_caps_unref(peer_caps); gst_caps_unref(intersect); } else { concat_mux->sink_caps = gst_pad_get_caps(pad); } pad_name = gst_pad_get_name(pad); GST_DEBUG_OBJECT(concat_mux, "sinkconnect triggered on %s", pad_name); g_free(pad_name); gst_object_unref(concat_mux); return GST_PAD_LINK_OK; } static GstPad * gst_concat_mux_request_new_pad(GstElement * element, GstPadTemplate * templ, const gchar * req_name) { GstConcatMux *concat_mux; GstPad *newpad; GstElementClass *klass = GST_ELEMENT_GET_CLASS(element); GMutex *mutex; g_return_val_if_fail(templ != NULL, NULL); if (templ->direction != GST_PAD_SINK) { g_warning("concat_mux: request pad that is not a SINK pad\n"); return NULL; } g_return_val_if_fail(GST_IS_CONCAT_MUX(element), NULL); concat_mux = GST_CONCAT_MUX(element); if (templ == gst_element_class_get_pad_template(klass, "sink_%d")) { gchar *name; /* create new pad with the name */ name = g_strdup_printf("sink_%02d", concat_mux->numpads); g_debug("NEw pad %s", name); newpad = gst_pad_new_from_template(templ, name); g_free(name); concat_mux->sinks = g_slist_append(concat_mux->sinks, newpad); g_debug("New sink %p / %d", newpad, g_slist_length(concat_mux->sinks)); concat_mux->numpads++; } else { g_warning("concat_mux: this is not our template!\n"); return NULL; } mutex = g_mutex_new(); if (concat_mux->sinkpad == NULL) { concat_mux->sinkpad = newpad; } else { g_mutex_lock(mutex); } gst_pad_set_element_private(newpad, mutex); /* setup some pad functions */ gst_pad_set_link_function(newpad, gst_concat_mux_sinkconnect); gst_pad_set_event_function(newpad, gst_concat_mux_handle_sink_event); gst_pad_set_chain_function(newpad, gst_concat_mux_chain); /* add the pad to the element */ gst_element_add_pad(element, newpad); return newpad; } /* handle events */ static gboolean gst_concat_mux_handle_src_event(GstPad * pad, GstEvent * event) { GstConcatMux *concat_mux; GstEventType type; concat_mux = GST_CONCAT_MUX(gst_pad_get_parent(pad)); type = event ? GST_EVENT_TYPE(event) : GST_EVENT_UNKNOWN; switch (type) { case GST_EVENT_SEEK: /* disable seeking for now */ return FALSE; default: break; } gst_object_unref(concat_mux); return gst_pad_event_default(pad, event); } /* handle events */ static gboolean gst_concat_mux_handle_sink_event(GstPad * pad, GstEvent * event) { GstConcatMux *mux; GstEventType type; mux = GST_CONCAT_MUX(gst_pad_get_parent(pad)); type = event ? GST_EVENT_TYPE(event) : GST_EVENT_UNKNOWN; switch (type) { case GST_EVENT_EOS: { mux->resync = TRUE; g_debug("sink EOS %p / %d", pad, g_slist_length(mux->sinks)); /* mark pad eos */ mux->sinks = g_slist_remove(mux->sinks, pad); g_debug("sink len %d", g_slist_length(mux->sinks)); if (g_slist_length(mux->sinks) != 0) { GMutex *mutex; mux->sinkpad = mux->sinks->data; mutex = (GMutex *) gst_pad_get_element_private(mux->sinkpad); g_mutex_unlock(mutex); g_debug("sink pad %p", mux->sinkpad); return TRUE; } g_debug("sink list is empty"); } default: break; } gst_object_unref(mux); return gst_pad_event_default(pad, event); } static GstFlowReturn gst_concat_mux_chain(GstPad * pad, GstBuffer * buf) { GstConcatMux *mux = (GstConcatMux *) GST_PAD_PARENT(pad); GstBuffer *databuf = NULL; GstFlowReturn ret = GST_FLOW_OK; GMutex *mutex; mutex = (GMutex *) gst_pad_get_element_private(pad); g_mutex_lock(mutex); if (mux->done) { g_debug("DONE pad %p", pad); g_mutex_unlock(mutex); return GST_FLOW_OK; } databuf = gst_buffer_make_metadata_writable(buf); if (!mux->negotiated) { /* GstCaps *newcaps; newcaps = gst_pad_get_caps (mux->sinkpad); g_debug ("CAPS: %s",gst_caps_to_string (newcaps)); if (!gst_pad_set_caps (mux->srcpad, newcaps)) goto nego_error; */ mux->negotiated = TRUE; } /* g_debug ("Running [%s]\n" "\tTOFFSET [%"G_GUINT64_FORMAT"]\n" "\tB_TSTAMP [%"G_GUINT64_FORMAT"]\n" "\tB_DURATION [%"G_GUINT64_FORMAT"]\n" "\tOFFSET [%"G_GUINT64_FORMAT"]\n" "\tB_OFFSET [%"G_GUINT64_FORMAT"]", gst_element_get_name (mux), mux->timeoffset, GST_BUFFER_TIMESTAMP (databuf), GST_BUFFER_DURATION (databuf), mux->offset, GST_BUFFER_OFFSET (databuf)); */ if (mux->resync) { g_debug("RESYNC [%s]", gst_element_get_name(mux)); mux->timeoffset += GST_BUFFER_TIMESTAMP(databuf); GST_BUFFER_TIMESTAMP(databuf) = mux->timeoffset; mux->timeoffset += GST_BUFFER_DURATION(databuf); mux->offset += GST_BUFFER_OFFSET(databuf); GST_BUFFER_OFFSET(databuf) = mux->offset; mux->offset += GST_BUFFER_SIZE(databuf); mux->resync = FALSE; } else { GST_BUFFER_TIMESTAMP(databuf) = mux->timeoffset; mux->timeoffset += GST_BUFFER_DURATION(databuf); GST_BUFFER_OFFSET(databuf) = mux->offset; mux->offset += GST_BUFFER_SIZE(databuf); } gst_buffer_set_caps(databuf, GST_PAD_CAPS(pad)); ret = gst_pad_push(mux->srcpad, databuf); //gst_buffer_unref (buf); g_mutex_unlock(mutex); return ret; /* nego_error: { GST_WARNING_OBJECT (mux, "failed to set caps"); GST_ELEMENT_ERROR (mux, CORE, NEGOTIATION, (NULL), (NULL)); return GST_FLOW_NOT_NEGOTIATED; } */ /* 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 GstStateChangeReturn gst_concat_mux_change_state(GstElement * element, GstStateChange transition) { GstConcatMux *concat_mux; GstStateChangeReturn ret; concat_mux = GST_CONCAT_MUX(element); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: concat_mux->done = FALSE; concat_mux->resync = TRUE; GST_DEBUG_OBJECT(concat_mux, "starting collect pads"); break; case GST_STATE_CHANGE_PAUSED_TO_READY: GST_DEBUG_OBJECT(concat_mux, "stopping collect pads"); gst_concat_mux_clear(concat_mux); 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_concat_mux_plugin_init(GstPlugin * plugin) { #ifdef ENABLE_NLS setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); #endif /* ENABLE_NLS */ GST_DEBUG_CATEGORY_INIT(gst_concat_mux_debug, "concatmux", 0, "concat muxer"); return gst_element_register(plugin, "concatmux", GST_RANK_NONE, GST_TYPE_CONCAT_MUX); } GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, "concatmux", "Concat streamers", gst_concat_mux_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)