mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 05:31:15 +00:00
059965fe53
Found running `gst-inspect-1.0 -a |& grep -v ":" | grep @` Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4682>
829 lines
23 KiB
C++
829 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 [mjpegtools](http://mjpeg.sourceforge.net/) library.
|
|
* Documentation on creating MPEG videos in general can be found in the
|
|
* [MJPEG Howto](https://sourceforge.net/docman/display_doc.php?docid=3456&group_id=5776)
|
|
* and the man-page of the mplex tool documents the properties of this element,
|
|
* which are shared with the mplex tool.
|
|
*
|
|
* ## Example pipeline
|
|
*
|
|
* |[
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#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);
|
|
static gboolean mplex_element_init (GstPlugin * plugin);
|
|
|
|
#define parent_class gst_mplex_parent_class
|
|
G_DEFINE_TYPE (GstMplex, gst_mplex, GST_TYPE_ELEMENT);
|
|
GST_ELEMENT_REGISTER_DEFINE_CUSTOM (mplex, mplex_element_init);
|
|
|
|
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>, "
|
|
"Ronald Bultje <rbultje@ronald.bitfreak.net>, "
|
|
"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
|
|
mplex_element_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);
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
return GST_ELEMENT_REGISTER (mplex, plugin);
|
|
}
|
|
|
|
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)
|