| /* GStreamer mplex (mjpegtools) wrapper |
| * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net> |
| * (c) 2008 Mark Nauwelaerts <mnauw@users.sourceforge.net> |
| * |
| * gstmplex.cc: gstreamer mplex wrapper |
| * |
| * 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-mplex |
| * @see_also: mpeg2enc |
| * |
| * This element is an audio/video multiplexer for MPEG-1/2 video streams |
| * and (un)compressed audio streams such as AC3, MPEG layer I/II/III. |
| * It is based on the <ulink url="http://mjpeg.sourceforge.net/">mjpegtools</ulink> library. |
| * Documentation on creating MPEG videos in general can be found in the |
| * <ulink url="https://sourceforge.net/docman/display_doc.php?docid=3456&group_id=5776">MJPEG Howto</ulink> |
| * and the man-page of the mplex tool documents the properties of this element, |
| * which are shared with the mplex tool. |
| * |
| * <refsect2> |
| * <title>Example pipeline</title> |
| * |[ |
| * gst-launch-1.0 -v videotestsrc num-buffers=1000 ! mpeg2enc ! mplex ! filesink location=videotestsrc.mpg |
| * ]| This example pipeline will encode a test video source to an |
| * MPEG1 elementary stream and multiplexes this to an MPEG system stream. |
| * <para> |
| * If several streams are being multiplexed, there should (as usual) be |
| * a queue in each stream, and due to mplex' buffering the capacities of these |
| * may have to be set to a few times the default settings to prevent the |
| * pipeline stalling. |
| * </para> |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| |
| #include <gst/glib-compat-private.h> |
| #include <gst/audio/audio.h> |
| |
| #include "gstmplex.hh" |
| #include "gstmplexoutputstream.hh" |
| #include "gstmplexibitstream.hh" |
| #include "gstmplexjob.hh" |
| |
| GST_DEBUG_CATEGORY (mplex_debug); |
| |
| static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/mpeg, systemstream = (boolean) true ") |
| ); |
| |
| static GstStaticPadTemplate video_sink_templ = |
| GST_STATIC_PAD_TEMPLATE ("video_%u", |
| GST_PAD_SINK, |
| GST_PAD_REQUEST, |
| GST_STATIC_CAPS ("video/mpeg, " |
| "mpegversion = (int) { 1, 2 }, " |
| "systemstream = (boolean) false, " |
| "width = (int) [ 16, 4096 ], " |
| "height = (int) [ 16, 4096 ], framerate = (fraction) [ 0, MAX ]") |
| ); |
| |
| #define COMMON_AUDIO_CAPS \ |
| "channels = (int) [ 1, 8 ], " \ |
| "rate = (int) [ 8000, 96000 ]" |
| |
| static GstStaticPadTemplate audio_sink_templ = |
| GST_STATIC_PAD_TEMPLATE ("audio_%u", |
| GST_PAD_SINK, |
| GST_PAD_REQUEST, |
| GST_STATIC_CAPS ("audio/mpeg, " |
| "mpegversion = (int) 1, " |
| "layer = (int) [ 1, 3 ], " |
| COMMON_AUDIO_CAPS "; " |
| "audio/x-ac3, " |
| COMMON_AUDIO_CAPS "; " |
| "audio/x-dts; " |
| "audio/x-raw, " |
| "format = (string) { S16BE, S20BE, S24BE }, " |
| "rate = (int) { 48000, 96000 }, " "channels = (int) [ 1, 6 ]") |
| ); |
| |
| /* FIXME: subtitles */ |
| |
| static void gst_mplex_finalize (GObject * object); |
| static void gst_mplex_reset (GstMplex * mplex); |
| static void gst_mplex_loop (GstMplex * mplex); |
| static GstPad *gst_mplex_request_new_pad (GstElement * element, |
| GstPadTemplate * templ, const gchar * name, const GstCaps * caps); |
| static void gst_mplex_release_pad (GstElement * element, GstPad * pad); |
| static gboolean gst_mplex_src_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active); |
| static GstStateChangeReturn gst_mplex_change_state (GstElement * element, |
| GstStateChange transition); |
| |
| static void gst_mplex_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec); |
| static void gst_mplex_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec); |
| |
| #define parent_class gst_mplex_parent_class |
| G_DEFINE_TYPE (GstMplex, gst_mplex, GST_TYPE_ELEMENT); |
| |
| static void |
| gst_mplex_class_init (GstMplexClass * klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (mplex_debug, "mplex", 0, "MPEG video/audio muxer"); |
| |
| object_class->set_property = gst_mplex_set_property; |
| object_class->get_property = gst_mplex_get_property; |
| |
| /* register properties */ |
| GstMplexJob::initProperties (object_class); |
| |
| object_class->finalize = GST_DEBUG_FUNCPTR (gst_mplex_finalize); |
| |
| element_class->change_state = GST_DEBUG_FUNCPTR (gst_mplex_change_state); |
| element_class->request_new_pad = |
| GST_DEBUG_FUNCPTR (gst_mplex_request_new_pad); |
| element_class->release_pad = GST_DEBUG_FUNCPTR (gst_mplex_release_pad); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "mplex video multiplexer", "Codec/Muxer", |
| "High-quality MPEG/DVD/SVCD/VCD video/audio multiplexer", |
| "Andrew Stevens <andrew.stevens@nexgo.de>\n" |
| "Ronald Bultje <rbultje@ronald.bitfreak.net>\n" |
| "Mark Nauwelaerts <mnauw@users.sourceforge.net>"); |
| |
| gst_element_class_add_static_pad_template (element_class, &src_templ); |
| gst_element_class_add_static_pad_template (element_class, &video_sink_templ); |
| gst_element_class_add_static_pad_template (element_class, &audio_sink_templ); |
| } |
| |
| static void |
| gst_mplex_finalize (GObject * object) |
| { |
| GstMplex *mplex = GST_MPLEX (object); |
| GSList *walk; |
| |
| /* release all pads */ |
| walk = mplex->pads; |
| while (walk) { |
| GstMplexPad *mpad = (GstMplexPad *) walk->data; |
| |
| if (mpad->pad) |
| gst_object_unref (mpad->pad); |
| mpad->pad = NULL; |
| walk = walk->next; |
| } |
| |
| /* clean up what's left of them */ |
| gst_mplex_reset (mplex); |
| |
| /* ... and of the rest */ |
| delete mplex->job; |
| |
| g_mutex_clear (&mplex->tlock); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_mplex_init (GstMplex * mplex) |
| { |
| GstElement *element = GST_ELEMENT (mplex); |
| |
| mplex->srcpad = gst_pad_new_from_static_template (&src_templ, "src"); |
| gst_element_add_pad (element, mplex->srcpad); |
| gst_pad_use_fixed_caps (mplex->srcpad); |
| gst_pad_set_activatemode_function (mplex->srcpad, |
| GST_DEBUG_FUNCPTR (gst_mplex_src_activate_mode)); |
| |
| mplex->job = new GstMplexJob (); |
| mplex->num_apads = 0; |
| mplex->num_vpads = 0; |
| |
| g_mutex_init (&mplex->tlock); |
| |
| gst_mplex_reset (mplex); |
| } |
| |
| static void |
| gst_mplex_reset (GstMplex * mplex) |
| { |
| GSList *walk; |
| GSList *nlist = NULL; |
| |
| mplex->eos = FALSE; |
| mplex->srcresult = GST_FLOW_CUSTOM_SUCCESS; |
| |
| /* reset existing streams */ |
| walk = mplex->pads; |
| while (walk != NULL) { |
| GstMplexPad *mpad; |
| |
| mpad = (GstMplexPad *) walk->data; |
| |
| mpad->needed = 0; |
| mpad->eos = FALSE; |
| gst_adapter_clear (mpad->adapter); |
| if (mpad->bs) { |
| delete mpad->bs; |
| |
| mpad->bs = NULL; |
| } |
| |
| if (!mpad->pad) { |
| g_cond_clear (&mpad->cond); |
| g_object_unref (mpad->adapter); |
| g_free (mpad); |
| } else |
| nlist = g_slist_append (nlist, mpad); |
| |
| walk = walk->next; |
| } |
| |
| g_slist_free (mplex->pads); |
| mplex->pads = nlist; |
| |
| /* clear mplex stuff */ |
| /* clean up stream settings */ |
| while (!mplex->job->streams.empty ()) { |
| delete mplex->job->streams.back (); |
| |
| mplex->job->streams.pop_back (); |
| } |
| while (!mplex->job->video_param.empty ()) { |
| delete mplex->job->video_param.back (); |
| |
| mplex->job->video_param.pop_back (); |
| } |
| while (!mplex->job->lpcm_param.empty ()) { |
| delete mplex->job->lpcm_param.back (); |
| |
| mplex->job->lpcm_param.pop_back (); |
| } |
| mplex->job->audio_tracks = 0; |
| mplex->job->video_tracks = 0; |
| mplex->job->lpcm_tracks = 0; |
| } |
| |
| static gboolean |
| gst_mplex_setcaps (GstPad * pad, GstCaps * caps) |
| { |
| GstMplex *mplex; |
| const gchar *mime; |
| GstStructure *structure; |
| StreamKind type; |
| JobStream *jobstream; |
| GstMplexIBitStream *inputstream; |
| GstMplexPad *mpad; |
| GstCaps *othercaps, *templ; |
| gboolean ret = TRUE; |
| |
| mplex = GST_MPLEX (GST_PAD_PARENT (pad)); |
| |
| /* does not go well to negotiate when started */ |
| if (mplex->srcresult != GST_FLOW_CUSTOM_SUCCESS) |
| goto refuse_renegotiation; |
| |
| /* since muxer does not really check much ... */ |
| templ = gst_pad_get_pad_template_caps (pad); |
| othercaps = gst_caps_intersect (caps, templ); |
| gst_caps_unref (templ); |
| if (othercaps) |
| gst_caps_unref (othercaps); |
| else |
| goto refuse_caps; |
| |
| /* set the fixed template caps on the srcpad, should accept without objection */ |
| othercaps = gst_pad_get_pad_template_caps (mplex->srcpad); |
| ret = gst_pad_set_caps (mplex->srcpad, othercaps); |
| gst_caps_unref (othercaps); |
| if (!ret) |
| goto refuse_caps; |
| |
| structure = gst_caps_get_structure (caps, 0); |
| mime = gst_structure_get_name (structure); |
| |
| if (!strcmp (mime, "video/mpeg")) { /* video */ |
| VideoParams *params; |
| |
| type = MPEG_VIDEO; |
| if (mplex->job->bufsize) |
| params = VideoParams::Checked (mplex->job->bufsize); |
| else |
| params = VideoParams::Default (mplex->job->mux_format); |
| /* set standard values if forced by the selected profile */ |
| if (params->Force (mplex->job->mux_format)) |
| GST_WARNING_OBJECT (mplex, |
| "overriding non-standard option due to selected profile"); |
| |
| mplex->job->video_param.push_back (params); |
| mplex->job->video_tracks++; |
| } else { /* audio */ |
| if (!strcmp (mime, "audio/mpeg")) { |
| type = MPEG_AUDIO; |
| } else if (!strcmp (mime, "audio/x-ac3")) { |
| type = AC3_AUDIO; |
| } else if (!strcmp (mime, "audio/x-dts")) { |
| type = DTS_AUDIO; |
| } else if (!strcmp (mime, "audio/x-raw")) { |
| LpcmParams *params; |
| gint bits, chans, rate; |
| GstAudioInfo info; |
| |
| type = LPCM_AUDIO; |
| |
| gst_audio_info_init (&info); |
| if (!gst_audio_info_from_caps (&info, caps)) |
| goto refuse_caps; |
| |
| rate = GST_AUDIO_INFO_RATE (&info); |
| chans = GST_AUDIO_INFO_CHANNELS (&info); |
| bits = GST_AUDIO_INFO_DEPTH (&info); |
| |
| /* set LPCM params */ |
| params = LpcmParams::Checked (rate, chans, bits); |
| |
| mplex->job->lpcm_param.push_back (params); |
| mplex->job->lpcm_tracks++; |
| } else |
| goto refuse_caps; |
| |
| mplex->job->audio_tracks++; |
| } |
| |
| mpad = (GstMplexPad *) gst_pad_get_element_private (pad); |
| g_return_val_if_fail (mpad, FALSE); |
| inputstream = new GstMplexIBitStream (mpad); |
| mpad->bs = inputstream; |
| jobstream = new JobStream (inputstream, type); |
| mplex->job->streams.push_back (jobstream); |
| |
| return TRUE; |
| |
| refuse_caps: |
| { |
| GST_WARNING_OBJECT (mplex, "refused caps %" GST_PTR_FORMAT, caps); |
| |
| /* undo if we were a bit too fast/confident */ |
| if (gst_pad_has_current_caps (mplex->srcpad)) |
| gst_pad_set_caps (mplex->srcpad, NULL); |
| |
| return FALSE; |
| } |
| refuse_renegotiation: |
| { |
| GST_WARNING_OBJECT (mplex, "already started; " |
| "refused (re)negotiation (to %" GST_PTR_FORMAT ")", caps); |
| |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_mplex_loop (GstMplex * mplex) |
| { |
| GstMplexOutputStream *out = NULL; |
| Multiplexor *mux = NULL; |
| GSList *walk; |
| GstSegment segment; |
| |
| /* do not try to resume muxing after it finished |
| * this can be relevant mainly/only in case of forced state change */ |
| if (mplex->eos) |
| goto eos; |
| |
| /* inform downstream about what's coming */ |
| gst_segment_init (&segment, GST_FORMAT_BYTES); |
| gst_pad_push_event (mplex->srcpad, gst_event_new_segment (&segment)); |
| |
| /* hm (!) each inputstream really needs an initial read |
| * so that all is internally in the proper state */ |
| walk = mplex->pads; |
| while (walk != NULL) { |
| GstMplexPad *mpad; |
| |
| mpad = (GstMplexPad *) walk->data; |
| mpad->bs->ReadBuffer (); |
| |
| walk = walk->next; |
| } |
| |
| /* create new multiplexer with inputs/output */ |
| out = new GstMplexOutputStream (mplex, mplex->srcpad); |
| #if GST_MJPEGTOOLS_API >= 10900 |
| mux = new Multiplexor (*mplex->job, *out, NULL); |
| #else |
| mux = new Multiplexor (*mplex->job, *out); |
| #endif |
| |
| if (mux) { |
| mux->Multiplex (); |
| delete mux; |
| delete out; |
| |
| /* if not well and truly eos, something strange happened */ |
| if (!mplex->eos) { |
| GST_ERROR_OBJECT (mplex, "muxing task ended without being eos"); |
| /* notify there is no point in collecting any more */ |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| mplex->srcresult = GST_FLOW_ERROR; |
| GST_MPLEX_SIGNAL_ALL (mplex); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| } else |
| goto eos; |
| } else { |
| GST_WARNING_OBJECT (mplex, "failed to create Multiplexor"); |
| } |
| |
| /* fall-through */ |
| done: |
| { |
| /* no need to run wildly, stopped elsewhere, e.g. state change */ |
| GST_DEBUG_OBJECT (mplex, "pausing muxing task"); |
| gst_pad_pause_task (mplex->srcpad); |
| |
| return; |
| } |
| eos: |
| { |
| GST_DEBUG_OBJECT (mplex, "encoding task reached eos"); |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_mplex_sink_event (GstPad * sinkpad, GstObject * parent, GstEvent * event) |
| { |
| GstMplex *mplex; |
| GstMplexPad *mpad; |
| gboolean result = TRUE; |
| |
| mplex = (GstMplex *) parent; |
| mpad = (GstMplexPad *) gst_pad_get_element_private (sinkpad); |
| g_return_val_if_fail (mpad, FALSE); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| /* forward event */ |
| gst_pad_event_default (sinkpad, parent, event); |
| |
| /* now unblock the chain function */ |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| mplex->srcresult = GST_FLOW_FLUSHING; |
| GST_MPLEX_SIGNAL (mplex, mpad); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| /* no way to pause/restart loop task */ |
| goto done; |
| case GST_EVENT_FLUSH_STOP: |
| /* forward event */ |
| gst_pad_event_default (sinkpad, parent, event); |
| |
| /* clear state and resume */ |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| gst_adapter_clear (mpad->adapter); |
| mplex->srcresult = GST_FLOW_OK; |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| goto done; |
| case GST_EVENT_SEGMENT: |
| /* eat segments; we make our own (byte)stream */ |
| gst_event_unref (event); |
| goto done; |
| case GST_EVENT_EOS: |
| /* inform this pad that it can stop now */ |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| mpad->eos = TRUE; |
| GST_MPLEX_SIGNAL (mplex, mpad); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| |
| /* eat this event for now, task will send eos when finished */ |
| gst_event_unref (event); |
| goto done; |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| result = gst_mplex_setcaps (sinkpad, caps); |
| gst_event_unref (event); |
| goto done; |
| |
| } |
| default: |
| /* for a serialized event, wait until earlier data is gone, |
| * though this is no guarantee as to when task is done with it. |
| * Only wait if loop has been started already */ |
| if (GST_EVENT_IS_SERIALIZED (event)) { |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| while (mplex->srcresult == GST_FLOW_OK && !mpad->needed) |
| GST_MPLEX_WAIT (mplex, mpad); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| } |
| break; |
| } |
| |
| result = gst_pad_event_default (sinkpad, parent, event); |
| |
| done: |
| return result; |
| } |
| |
| /* starts task if conditions are right for it |
| * must be called with mutex_lock held */ |
| static void |
| gst_mplex_start_task (GstMplex * mplex) |
| { |
| /* start task to create multiplexor and start muxing */ |
| if (G_UNLIKELY (mplex->srcresult == GST_FLOW_CUSTOM_SUCCESS) |
| && mplex->job->video_tracks == mplex->num_vpads |
| && mplex->job->audio_tracks == mplex->num_apads) { |
| gst_pad_start_task (mplex->srcpad, (GstTaskFunction) gst_mplex_loop, mplex, |
| NULL); |
| mplex->srcresult = GST_FLOW_OK; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_mplex_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstMplex *mplex; |
| GstMplexPad *mpad; |
| |
| mplex = (GstMplex *) parent; |
| mpad = (GstMplexPad *) gst_pad_get_element_private (sinkpad); |
| g_return_val_if_fail (mpad, GST_FLOW_ERROR); |
| |
| /* check if pad were properly negotiated and set up */ |
| if (G_UNLIKELY (!mpad->bs)) { |
| GST_ELEMENT_ERROR (mplex, CORE, NEGOTIATION, (NULL), |
| ("input pad has not been set up prior to chain function")); |
| return GST_FLOW_NOT_NEGOTIATED; |
| } |
| |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| |
| gst_mplex_start_task (mplex); |
| |
| if (G_UNLIKELY (mpad->eos)) |
| goto eos; |
| |
| if (G_UNLIKELY (mplex->srcresult != GST_FLOW_OK)) |
| goto ignore; |
| |
| gst_adapter_push (mpad->adapter, buffer); |
| buffer = NULL; |
| while (gst_adapter_available (mpad->adapter) >= mpad->needed) { |
| GST_MPLEX_SIGNAL (mplex, mpad); |
| GST_MPLEX_WAIT (mplex, mpad); |
| /* may have become flushing or in error */ |
| if (G_UNLIKELY (mplex->srcresult != GST_FLOW_OK)) |
| goto ignore; |
| /* or been removed */ |
| if (G_UNLIKELY (mpad->eos)) |
| goto eos; |
| } |
| |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| |
| return GST_FLOW_OK; |
| |
| /* special cases */ |
| eos: |
| { |
| GST_DEBUG_OBJECT (mplex, "ignoring buffer at end-of-stream"); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| |
| gst_buffer_unref (buffer); |
| return GST_FLOW_EOS; |
| } |
| ignore: |
| { |
| GstFlowReturn ret = mplex->srcresult; |
| |
| GST_DEBUG_OBJECT (mplex, "ignoring buffer because src task encountered %s", |
| gst_flow_get_name (ret)); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| |
| if (buffer) |
| gst_buffer_unref (buffer); |
| return ret; |
| } |
| } |
| |
| static GstPad * |
| gst_mplex_request_new_pad (GstElement * element, |
| GstPadTemplate * templ, const gchar * name, const GstCaps * caps) |
| { |
| GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); |
| GstMplex *mplex = GST_MPLEX (element); |
| gchar *padname; |
| GstPad *newpad; |
| GstMplexPad *mpad; |
| |
| if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) { |
| GST_DEBUG_OBJECT (mplex, "request pad audio %d", mplex->num_apads); |
| padname = g_strdup_printf ("audio_%u", mplex->num_apads++); |
| } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) { |
| GST_DEBUG_OBJECT (mplex, "request pad video %d", mplex->num_vpads); |
| padname = g_strdup_printf ("video_%u", mplex->num_vpads++); |
| } else { |
| GST_WARNING_OBJECT (mplex, "This is not our template!"); |
| return NULL; |
| } |
| |
| newpad = gst_pad_new_from_template (templ, padname); |
| g_free (padname); |
| |
| mpad = g_new0 (GstMplexPad, 1); |
| mpad->adapter = gst_adapter_new (); |
| g_cond_init (&mpad->cond); |
| gst_object_ref (newpad); |
| mpad->pad = newpad; |
| |
| gst_pad_set_chain_function (newpad, GST_DEBUG_FUNCPTR (gst_mplex_chain)); |
| gst_pad_set_event_function (newpad, GST_DEBUG_FUNCPTR (gst_mplex_sink_event)); |
| gst_pad_set_element_private (newpad, mpad); |
| gst_element_add_pad (element, newpad); |
| mplex->pads = g_slist_append (mplex->pads, mpad); |
| |
| return newpad; |
| } |
| |
| static void |
| gst_mplex_release_pad (GstElement * element, GstPad * pad) |
| { |
| GstMplex *mplex = GST_MPLEX (element); |
| GstMplexPad *mpad; |
| |
| g_return_if_fail (pad); |
| mpad = (GstMplexPad *) gst_pad_get_element_private (pad); |
| g_return_if_fail (mpad); |
| |
| if (gst_element_remove_pad (element, pad)) { |
| gchar *padname; |
| |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| mpad->eos = TRUE; |
| g_assert (mpad->pad == pad); |
| mpad->pad = NULL; |
| /* wake up if waiting on this pad */ |
| GST_MPLEX_SIGNAL (mplex, mpad); |
| |
| padname = gst_object_get_name (GST_OBJECT (pad)); |
| /* now only drop what might be last ref */ |
| gst_object_unref (pad); |
| if (strstr (padname, "audio")) { |
| mplex->num_apads--; |
| } else { |
| mplex->num_vpads--; |
| } |
| g_free (padname); |
| |
| /* may now be up to us to get things going */ |
| if (GST_STATE (element) > GST_STATE_READY) |
| gst_mplex_start_task (mplex); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| } |
| } |
| |
| static void |
| gst_mplex_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec) |
| { |
| GST_MPLEX (object)->job->getProperty (prop_id, value); |
| } |
| |
| static void |
| gst_mplex_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec) |
| { |
| GST_MPLEX (object)->job->setProperty (prop_id, value); |
| } |
| |
| static gboolean |
| gst_mplex_src_activate_mode (GstPad * pad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| gboolean result = TRUE; |
| GstMplex *mplex; |
| |
| mplex = GST_MPLEX (parent); |
| |
| if (mode != GST_PAD_MODE_PUSH) |
| return FALSE; |
| |
| if (active) { |
| /* chain will start task once all streams have been setup */ |
| } else { |
| /* end the muxing loop by forcing eos and unblock chains */ |
| GST_MPLEX_MUTEX_LOCK (mplex); |
| mplex->eos = TRUE; |
| mplex->srcresult = GST_FLOW_FLUSHING; |
| GST_MPLEX_SIGNAL_ALL (mplex); |
| GST_MPLEX_MUTEX_UNLOCK (mplex); |
| |
| /* muxing loop should have ended now and can be joined */ |
| result = gst_pad_stop_task (pad); |
| } |
| |
| return result; |
| } |
| |
| static GstStateChangeReturn |
| gst_mplex_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstMplex *mplex = GST_MPLEX (element); |
| GstStateChangeReturn ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| goto done; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_mplex_reset (mplex); |
| break; |
| default: |
| break; |
| } |
| |
| done: |
| return ret; |
| } |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| |
| static mjpeg_log_handler_t old_handler = NULL; |
| |
| /* note that this will affect all mjpegtools elements/threads */ |
| static void |
| gst_mplex_log_callback (log_level_t level, const char *message) |
| { |
| GstDebugLevel gst_level; |
| |
| #if GST_MJPEGTOOLS_API >= 10900 |
| static const gint mjpeg_log_error = mjpeg_loglev_t ("error"); |
| static const gint mjpeg_log_warn = mjpeg_loglev_t ("warn"); |
| static const gint mjpeg_log_info = mjpeg_loglev_t ("info"); |
| static const gint mjpeg_log_debug = mjpeg_loglev_t ("debug"); |
| #else |
| static const gint mjpeg_log_error = LOG_ERROR; |
| static const gint mjpeg_log_warn = LOG_WARN; |
| static const gint mjpeg_log_info = LOG_INFO; |
| static const gint mjpeg_log_debug = LOG_DEBUG; |
| #endif |
| |
| if (level == mjpeg_log_error) { |
| gst_level = GST_LEVEL_ERROR; |
| } else if (level == mjpeg_log_warn) { |
| gst_level = GST_LEVEL_WARNING; |
| } else if (level == mjpeg_log_info) { |
| gst_level = GST_LEVEL_INFO; |
| } else if (level == mjpeg_log_debug) { |
| gst_level = GST_LEVEL_DEBUG; |
| } else { |
| gst_level = GST_LEVEL_INFO; |
| } |
| |
| /* message could have a % in it, do not segfault in such case */ |
| gst_debug_log (mplex_debug, gst_level, "", "", 0, NULL, "%s", message); |
| |
| /* chain up to the old handler; |
| * this could actually be a handler from another mjpegtools based |
| * gstreamer element; in which case messages can come out double or from |
| * the wrong element ... */ |
| old_handler (level, message); |
| } |
| #endif |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| #ifndef GST_DISABLE_GST_DEBUG |
| old_handler = mjpeg_log_set_handler (gst_mplex_log_callback); |
| g_assert (old_handler != NULL); |
| #endif |
| /* in any case, we do not want default handler output */ |
| mjpeg_default_handler_verbosity (0); |
| |
| return gst_element_register (plugin, "mplex", GST_RANK_NONE, GST_TYPE_MPLEX); |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| mplex, |
| "High-quality MPEG/DVD/SVCD/VCD video/audio multiplexer", |
| plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |