mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 18:51:11 +00:00
0d06f4bc7c
Fixes spurious segfault in unit test, where the task was started again during shutdown when all pads were removed... and was then still running while the element was finalized.
823 lines
23 KiB
C++
823 lines
23 KiB
C++
/* 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)
|