1 /* GStreamer mplex (mjpegtools) wrapper
2  * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
3  * (c) 2008 Mark Nauwelaerts <mnauw@users.sourceforge.net>
4  *
5  * gstmplex.cc: gstreamer mplex wrapper
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:element-mplex
25  * @see_also: mpeg2enc
26  *
27  * This element is an audio/video multiplexer for MPEG-1/2 video streams
28  * and (un)compressed audio streams such as AC3, MPEG layer I/II/III.
29  * It is based on the <ulink url="http://mjpeg.sourceforge.net/">mjpegtools</ulink> library.
30  * Documentation on creating MPEG videos in general can be found in the
31  * <ulink url="https://sourceforge.net/docman/display_doc.php?docid=3456&group_id=5776">MJPEG Howto</ulink>
32  * and the man-page of the mplex tool documents the properties of this element,
33  * which are shared with the mplex tool.
34  *
35  * <refsect2>
36  * <title>Example pipeline</title>
37  * |[
38  * gst-launch-1.0 -v videotestsrc num-buffers=1000 ! mpeg2enc ! mplex ! filesink location=videotestsrc.mpg
39  * ]| This example pipeline will encode a test video source to an
40  * MPEG1 elementary stream and multiplexes this to an MPEG system stream.
41  * <para>
42  * If several streams are being multiplexed, there should (as usual) be
43  * a queue in each stream, and due to mplex' buffering the capacities of these
44  * may have to be set to a few times the default settings to prevent the
45  * pipeline stalling.
46  * </para>
47  * </refsect2>
48  */
49 
50 #ifdef HAVE_CONFIG_H
51 #include "config.h"
52 #endif
53 
54 #include <string.h>
55 
56 #include <gst/glib-compat-private.h>
57 #include <gst/audio/audio.h>
58 
59 #include "gstmplex.hh"
60 #include "gstmplexoutputstream.hh"
61 #include "gstmplexibitstream.hh"
62 #include "gstmplexjob.hh"
63 
64 GST_DEBUG_CATEGORY (mplex_debug);
65 
66 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
67     GST_PAD_SRC,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS ("video/mpeg, systemstream = (boolean) true ")
70     );
71 
72 static GstStaticPadTemplate video_sink_templ =
73 GST_STATIC_PAD_TEMPLATE ("video_%u",
74     GST_PAD_SINK,
75     GST_PAD_REQUEST,
76     GST_STATIC_CAPS ("video/mpeg, "
77         "mpegversion = (int) { 1, 2 }, "
78         "systemstream = (boolean) false, "
79         "width = (int) [ 16, 4096 ], "
80         "height = (int) [ 16, 4096 ], framerate = (fraction) [ 0, MAX ]")
81     );
82 
83 #define COMMON_AUDIO_CAPS \
84   "channels = (int) [ 1, 8 ], " \
85   "rate = (int) [ 8000, 96000 ]"
86 
87 static GstStaticPadTemplate audio_sink_templ =
88     GST_STATIC_PAD_TEMPLATE ("audio_%u",
89     GST_PAD_SINK,
90     GST_PAD_REQUEST,
91     GST_STATIC_CAPS ("audio/mpeg, "
92         "mpegversion = (int) 1, "
93         "layer = (int) [ 1, 3 ], "
94         COMMON_AUDIO_CAPS "; "
95         "audio/x-ac3, "
96         COMMON_AUDIO_CAPS "; "
97         "audio/x-dts; "
98         "audio/x-raw, "
99         "format = (string) { S16BE, S20BE, S24BE }, "
100         "rate = (int) { 48000, 96000 }, " "channels = (int) [ 1, 6 ]")
101     );
102 
103 /* FIXME: subtitles */
104 
105 static void gst_mplex_finalize (GObject * object);
106 static void gst_mplex_reset (GstMplex * mplex);
107 static void gst_mplex_loop (GstMplex * mplex);
108 static GstPad *gst_mplex_request_new_pad (GstElement * element,
109     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
110 static void gst_mplex_release_pad (GstElement * element, GstPad * pad);
111 static gboolean gst_mplex_src_activate_mode (GstPad * pad, GstObject * parent,
112     GstPadMode mode, gboolean active);
113 static GstStateChangeReturn gst_mplex_change_state (GstElement * element,
114     GstStateChange transition);
115 
116 static void gst_mplex_get_property (GObject * object,
117     guint prop_id, GValue * value, GParamSpec * pspec);
118 static void gst_mplex_set_property (GObject * object,
119     guint prop_id, const GValue * value, GParamSpec * pspec);
120 
121 #define parent_class gst_mplex_parent_class
122 G_DEFINE_TYPE (GstMplex, gst_mplex, GST_TYPE_ELEMENT);
123 
124 static void
gst_mplex_class_init(GstMplexClass * klass)125 gst_mplex_class_init (GstMplexClass * klass)
126 {
127   GObjectClass *object_class = G_OBJECT_CLASS (klass);
128   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
129 
130   GST_DEBUG_CATEGORY_INIT (mplex_debug, "mplex", 0, "MPEG video/audio muxer");
131 
132   object_class->set_property = gst_mplex_set_property;
133   object_class->get_property = gst_mplex_get_property;
134 
135   /* register properties */
136   GstMplexJob::initProperties (object_class);
137 
138   object_class->finalize = GST_DEBUG_FUNCPTR (gst_mplex_finalize);
139 
140   element_class->change_state = GST_DEBUG_FUNCPTR (gst_mplex_change_state);
141   element_class->request_new_pad =
142       GST_DEBUG_FUNCPTR (gst_mplex_request_new_pad);
143   element_class->release_pad = GST_DEBUG_FUNCPTR (gst_mplex_release_pad);
144 
145   gst_element_class_set_static_metadata (element_class,
146       "mplex video multiplexer", "Codec/Muxer",
147       "High-quality MPEG/DVD/SVCD/VCD video/audio multiplexer",
148       "Andrew Stevens <andrew.stevens@nexgo.de>\n"
149       "Ronald Bultje <rbultje@ronald.bitfreak.net>\n"
150       "Mark Nauwelaerts <mnauw@users.sourceforge.net>");
151 
152   gst_element_class_add_static_pad_template (element_class, &src_templ);
153   gst_element_class_add_static_pad_template (element_class, &video_sink_templ);
154   gst_element_class_add_static_pad_template (element_class, &audio_sink_templ);
155 }
156 
157 static void
gst_mplex_finalize(GObject * object)158 gst_mplex_finalize (GObject * object)
159 {
160   GstMplex *mplex = GST_MPLEX (object);
161   GSList *walk;
162 
163   /* release all pads */
164   walk = mplex->pads;
165   while (walk) {
166     GstMplexPad *mpad = (GstMplexPad *) walk->data;
167 
168     if (mpad->pad)
169       gst_object_unref (mpad->pad);
170     mpad->pad = NULL;
171     walk = walk->next;
172   }
173 
174   /* clean up what's left of them */
175   gst_mplex_reset (mplex);
176 
177   /* ... and of the rest */
178   delete mplex->job;
179 
180   g_mutex_clear (&mplex->tlock);
181 
182   G_OBJECT_CLASS (parent_class)->finalize (object);
183 }
184 
185 static void
gst_mplex_init(GstMplex * mplex)186 gst_mplex_init (GstMplex * mplex)
187 {
188   GstElement *element = GST_ELEMENT (mplex);
189 
190   mplex->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
191   gst_element_add_pad (element, mplex->srcpad);
192   gst_pad_use_fixed_caps (mplex->srcpad);
193   gst_pad_set_activatemode_function (mplex->srcpad,
194       GST_DEBUG_FUNCPTR (gst_mplex_src_activate_mode));
195 
196   mplex->job = new GstMplexJob ();
197   mplex->num_apads = 0;
198   mplex->num_vpads = 0;
199 
200   g_mutex_init (&mplex->tlock);
201 
202   gst_mplex_reset (mplex);
203 }
204 
205 static void
gst_mplex_reset(GstMplex * mplex)206 gst_mplex_reset (GstMplex * mplex)
207 {
208   GSList *walk;
209   GSList *nlist = NULL;
210 
211   mplex->eos = FALSE;
212   mplex->srcresult = GST_FLOW_CUSTOM_SUCCESS;
213 
214   /* reset existing streams */
215   walk = mplex->pads;
216   while (walk != NULL) {
217     GstMplexPad *mpad;
218 
219     mpad = (GstMplexPad *) walk->data;
220 
221     mpad->needed = 0;
222     mpad->eos = FALSE;
223     gst_adapter_clear (mpad->adapter);
224     if (mpad->bs) {
225       delete mpad->bs;
226 
227       mpad->bs = NULL;
228     }
229 
230     if (!mpad->pad) {
231       g_cond_clear (&mpad->cond);
232       g_object_unref (mpad->adapter);
233       g_free (mpad);
234     } else
235       nlist = g_slist_append (nlist, mpad);
236 
237     walk = walk->next;
238   }
239 
240   g_slist_free (mplex->pads);
241   mplex->pads = nlist;
242 
243   /* clear mplex stuff */
244   /* clean up stream settings */
245   while (!mplex->job->streams.empty ()) {
246     delete mplex->job->streams.back ();
247 
248     mplex->job->streams.pop_back ();
249   }
250   while (!mplex->job->video_param.empty ()) {
251     delete mplex->job->video_param.back ();
252 
253     mplex->job->video_param.pop_back ();
254   }
255   while (!mplex->job->lpcm_param.empty ()) {
256     delete mplex->job->lpcm_param.back ();
257 
258     mplex->job->lpcm_param.pop_back ();
259   }
260   mplex->job->audio_tracks = 0;
261   mplex->job->video_tracks = 0;
262   mplex->job->lpcm_tracks = 0;
263 }
264 
265 static gboolean
gst_mplex_setcaps(GstPad * pad,GstCaps * caps)266 gst_mplex_setcaps (GstPad * pad, GstCaps * caps)
267 {
268   GstMplex *mplex;
269   const gchar *mime;
270   GstStructure *structure;
271   StreamKind type;
272   JobStream *jobstream;
273   GstMplexIBitStream *inputstream;
274   GstMplexPad *mpad;
275   GstCaps *othercaps, *templ;
276   gboolean ret = TRUE;
277 
278   mplex = GST_MPLEX (GST_PAD_PARENT (pad));
279 
280   /* does not go well to negotiate when started */
281   if (mplex->srcresult != GST_FLOW_CUSTOM_SUCCESS)
282     goto refuse_renegotiation;
283 
284   /* since muxer does not really check much ... */
285   templ = gst_pad_get_pad_template_caps (pad);
286   othercaps = gst_caps_intersect (caps, templ);
287   gst_caps_unref (templ);
288   if (othercaps)
289     gst_caps_unref (othercaps);
290   else
291     goto refuse_caps;
292 
293   /* set the fixed template caps on the srcpad, should accept without objection */
294   othercaps = gst_pad_get_pad_template_caps (mplex->srcpad);
295   ret = gst_pad_set_caps (mplex->srcpad, othercaps);
296   gst_caps_unref (othercaps);
297   if (!ret)
298     goto refuse_caps;
299 
300   structure = gst_caps_get_structure (caps, 0);
301   mime = gst_structure_get_name (structure);
302 
303   if (!strcmp (mime, "video/mpeg")) {   /* video */
304     VideoParams *params;
305 
306     type = MPEG_VIDEO;
307     if (mplex->job->bufsize)
308       params = VideoParams::Checked (mplex->job->bufsize);
309     else
310       params = VideoParams::Default (mplex->job->mux_format);
311     /* set standard values if forced by the selected profile */
312     if (params->Force (mplex->job->mux_format))
313       GST_WARNING_OBJECT (mplex,
314           "overriding non-standard option due to selected profile");
315 
316     mplex->job->video_param.push_back (params);
317     mplex->job->video_tracks++;
318   } else {                      /* audio */
319     if (!strcmp (mime, "audio/mpeg")) {
320       type = MPEG_AUDIO;
321     } else if (!strcmp (mime, "audio/x-ac3")) {
322       type = AC3_AUDIO;
323     } else if (!strcmp (mime, "audio/x-dts")) {
324       type = DTS_AUDIO;
325     } else if (!strcmp (mime, "audio/x-raw")) {
326       LpcmParams *params;
327       gint bits, chans, rate;
328       GstAudioInfo info;
329 
330       type = LPCM_AUDIO;
331 
332       gst_audio_info_init (&info);
333       if (!gst_audio_info_from_caps (&info, caps))
334         goto refuse_caps;
335 
336       rate = GST_AUDIO_INFO_RATE (&info);
337       chans = GST_AUDIO_INFO_CHANNELS (&info);
338       bits = GST_AUDIO_INFO_DEPTH (&info);
339 
340       /* set LPCM params */
341       params = LpcmParams::Checked (rate, chans, bits);
342 
343       mplex->job->lpcm_param.push_back (params);
344       mplex->job->lpcm_tracks++;
345     } else
346       goto refuse_caps;
347 
348     mplex->job->audio_tracks++;
349   }
350 
351   mpad = (GstMplexPad *) gst_pad_get_element_private (pad);
352   g_return_val_if_fail (mpad, FALSE);
353   inputstream = new GstMplexIBitStream (mpad);
354   mpad->bs = inputstream;
355   jobstream = new JobStream (inputstream, type);
356   mplex->job->streams.push_back (jobstream);
357 
358   return TRUE;
359 
360 refuse_caps:
361   {
362     GST_WARNING_OBJECT (mplex, "refused caps %" GST_PTR_FORMAT, caps);
363 
364     /* undo if we were a bit too fast/confident */
365     if (gst_pad_has_current_caps (mplex->srcpad))
366       gst_pad_set_caps (mplex->srcpad, NULL);
367 
368     return FALSE;
369   }
370 refuse_renegotiation:
371   {
372     GST_WARNING_OBJECT (mplex, "already started; "
373         "refused (re)negotiation (to %" GST_PTR_FORMAT ")", caps);
374 
375     return FALSE;
376   }
377 }
378 
379 static void
gst_mplex_loop(GstMplex * mplex)380 gst_mplex_loop (GstMplex * mplex)
381 {
382   GstMplexOutputStream *out = NULL;
383   Multiplexor *mux = NULL;
384   GSList *walk;
385   GstSegment segment;
386 
387   /* do not try to resume muxing after it finished
388    * this can be relevant mainly/only in case of forced state change */
389   if (mplex->eos)
390     goto eos;
391 
392   /* inform downstream about what's coming */
393   gst_segment_init (&segment, GST_FORMAT_BYTES);
394   gst_pad_push_event (mplex->srcpad, gst_event_new_segment (&segment));
395 
396   /* hm (!) each inputstream really needs an initial read
397    * so that all is internally in the proper state */
398   walk = mplex->pads;
399   while (walk != NULL) {
400     GstMplexPad *mpad;
401 
402     mpad = (GstMplexPad *) walk->data;
403     mpad->bs->ReadBuffer ();
404 
405     walk = walk->next;
406   }
407 
408   /* create new multiplexer with inputs/output */
409   out = new GstMplexOutputStream (mplex, mplex->srcpad);
410 #if GST_MJPEGTOOLS_API >= 10900
411   mux = new Multiplexor (*mplex->job, *out, NULL);
412 #else
413   mux = new Multiplexor (*mplex->job, *out);
414 #endif
415 
416   if (mux) {
417     mux->Multiplex ();
418     delete mux;
419     delete out;
420 
421     /* if not well and truly eos, something strange happened  */
422     if (!mplex->eos) {
423       GST_ERROR_OBJECT (mplex, "muxing task ended without being eos");
424       /* notify there is no point in collecting any more */
425       GST_MPLEX_MUTEX_LOCK (mplex);
426       mplex->srcresult = GST_FLOW_ERROR;
427       GST_MPLEX_SIGNAL_ALL (mplex);
428       GST_MPLEX_MUTEX_UNLOCK (mplex);
429     } else
430       goto eos;
431   } else {
432     GST_WARNING_OBJECT (mplex, "failed to create Multiplexor");
433   }
434 
435   /* fall-through */
436 done:
437   {
438     /* no need to run wildly, stopped elsewhere, e.g. state change */
439     GST_DEBUG_OBJECT (mplex, "pausing muxing task");
440     gst_pad_pause_task (mplex->srcpad);
441 
442     return;
443   }
444 eos:
445   {
446     GST_DEBUG_OBJECT (mplex, "encoding task reached eos");
447     goto done;
448   }
449 }
450 
451 static gboolean
gst_mplex_sink_event(GstPad * sinkpad,GstObject * parent,GstEvent * event)452 gst_mplex_sink_event (GstPad * sinkpad, GstObject * parent, GstEvent * event)
453 {
454   GstMplex *mplex;
455   GstMplexPad *mpad;
456   gboolean result = TRUE;
457 
458   mplex = (GstMplex *) parent;
459   mpad = (GstMplexPad *) gst_pad_get_element_private (sinkpad);
460   g_return_val_if_fail (mpad, FALSE);
461 
462   switch (GST_EVENT_TYPE (event)) {
463     case GST_EVENT_FLUSH_START:
464       /* forward event */
465       gst_pad_event_default (sinkpad, parent, event);
466 
467       /* now unblock the chain function */
468       GST_MPLEX_MUTEX_LOCK (mplex);
469       mplex->srcresult = GST_FLOW_FLUSHING;
470       GST_MPLEX_SIGNAL (mplex, mpad);
471       GST_MPLEX_MUTEX_UNLOCK (mplex);
472       /* no way to pause/restart loop task */
473       goto done;
474     case GST_EVENT_FLUSH_STOP:
475       /* forward event */
476       gst_pad_event_default (sinkpad, parent, event);
477 
478       /* clear state and resume */
479       GST_MPLEX_MUTEX_LOCK (mplex);
480       gst_adapter_clear (mpad->adapter);
481       mplex->srcresult = GST_FLOW_OK;
482       GST_MPLEX_MUTEX_UNLOCK (mplex);
483       goto done;
484     case GST_EVENT_SEGMENT:
485       /* eat segments; we make our own (byte)stream */
486       gst_event_unref (event);
487       goto done;
488     case GST_EVENT_EOS:
489       /* inform this pad that it can stop now */
490       GST_MPLEX_MUTEX_LOCK (mplex);
491       mpad->eos = TRUE;
492       GST_MPLEX_SIGNAL (mplex, mpad);
493       GST_MPLEX_MUTEX_UNLOCK (mplex);
494 
495       /* eat this event for now, task will send eos when finished */
496       gst_event_unref (event);
497       goto done;
498     case GST_EVENT_CAPS:
499     {
500       GstCaps *caps;
501 
502       gst_event_parse_caps (event, &caps);
503       result = gst_mplex_setcaps (sinkpad, caps);
504       gst_event_unref (event);
505       goto done;
506 
507     }
508     default:
509       /* for a serialized event, wait until earlier data is gone,
510        * though this is no guarantee as to when task is done with it.
511        * Only wait if loop has been started already */
512       if (GST_EVENT_IS_SERIALIZED (event)) {
513         GST_MPLEX_MUTEX_LOCK (mplex);
514         while (mplex->srcresult == GST_FLOW_OK && !mpad->needed)
515           GST_MPLEX_WAIT (mplex, mpad);
516         GST_MPLEX_MUTEX_UNLOCK (mplex);
517       }
518       break;
519   }
520 
521   result = gst_pad_event_default (sinkpad, parent, event);
522 
523 done:
524   return result;
525 }
526 
527 /* starts task if conditions are right for it
528  * must be called with mutex_lock held */
529 static void
gst_mplex_start_task(GstMplex * mplex)530 gst_mplex_start_task (GstMplex * mplex)
531 {
532   /* start task to create multiplexor and start muxing */
533   if (G_UNLIKELY (mplex->srcresult == GST_FLOW_CUSTOM_SUCCESS)
534       && mplex->job->video_tracks == mplex->num_vpads
535       && mplex->job->audio_tracks == mplex->num_apads) {
536     gst_pad_start_task (mplex->srcpad, (GstTaskFunction) gst_mplex_loop, mplex,
537         NULL);
538     mplex->srcresult = GST_FLOW_OK;
539   }
540 }
541 
542 static GstFlowReturn
gst_mplex_chain(GstPad * sinkpad,GstObject * parent,GstBuffer * buffer)543 gst_mplex_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer)
544 {
545   GstMplex *mplex;
546   GstMplexPad *mpad;
547 
548   mplex = (GstMplex *) parent;
549   mpad = (GstMplexPad *) gst_pad_get_element_private (sinkpad);
550   g_return_val_if_fail (mpad, GST_FLOW_ERROR);
551 
552   /* check if pad were properly negotiated and set up */
553   if (G_UNLIKELY (!mpad->bs)) {
554     GST_ELEMENT_ERROR (mplex, CORE, NEGOTIATION, (NULL),
555         ("input pad has not been set up prior to chain function"));
556     return GST_FLOW_NOT_NEGOTIATED;
557   }
558 
559   GST_MPLEX_MUTEX_LOCK (mplex);
560 
561   gst_mplex_start_task (mplex);
562 
563   if (G_UNLIKELY (mpad->eos))
564     goto eos;
565 
566   if (G_UNLIKELY (mplex->srcresult != GST_FLOW_OK))
567     goto ignore;
568 
569   gst_adapter_push (mpad->adapter, buffer);
570   buffer = NULL;
571   while (gst_adapter_available (mpad->adapter) >= mpad->needed) {
572     GST_MPLEX_SIGNAL (mplex, mpad);
573     GST_MPLEX_WAIT (mplex, mpad);
574     /* may have become flushing or in error */
575     if (G_UNLIKELY (mplex->srcresult != GST_FLOW_OK))
576       goto ignore;
577     /* or been removed */
578     if (G_UNLIKELY (mpad->eos))
579       goto eos;
580   }
581 
582   GST_MPLEX_MUTEX_UNLOCK (mplex);
583 
584   return GST_FLOW_OK;
585 
586 /* special cases */
587 eos:
588   {
589     GST_DEBUG_OBJECT (mplex, "ignoring buffer at end-of-stream");
590     GST_MPLEX_MUTEX_UNLOCK (mplex);
591 
592     gst_buffer_unref (buffer);
593     return GST_FLOW_EOS;
594   }
595 ignore:
596   {
597     GstFlowReturn ret = mplex->srcresult;
598 
599     GST_DEBUG_OBJECT (mplex, "ignoring buffer because src task encountered %s",
600         gst_flow_get_name (ret));
601     GST_MPLEX_MUTEX_UNLOCK (mplex);
602 
603     if (buffer)
604       gst_buffer_unref (buffer);
605     return ret;
606   }
607 }
608 
609 static GstPad *
gst_mplex_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * name,const GstCaps * caps)610 gst_mplex_request_new_pad (GstElement * element,
611     GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
612 {
613   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
614   GstMplex *mplex = GST_MPLEX (element);
615   gchar *padname;
616   GstPad *newpad;
617   GstMplexPad *mpad;
618 
619   if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
620     GST_DEBUG_OBJECT (mplex, "request pad audio %d", mplex->num_apads);
621     padname = g_strdup_printf ("audio_%u", mplex->num_apads++);
622   } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
623     GST_DEBUG_OBJECT (mplex, "request pad video %d", mplex->num_vpads);
624     padname = g_strdup_printf ("video_%u", mplex->num_vpads++);
625   } else {
626     GST_WARNING_OBJECT (mplex, "This is not our template!");
627     return NULL;
628   }
629 
630   newpad = gst_pad_new_from_template (templ, padname);
631   g_free (padname);
632 
633   mpad = g_new0 (GstMplexPad, 1);
634   mpad->adapter = gst_adapter_new ();
635   g_cond_init (&mpad->cond);
636   gst_object_ref (newpad);
637   mpad->pad = newpad;
638 
639   gst_pad_set_chain_function (newpad, GST_DEBUG_FUNCPTR (gst_mplex_chain));
640   gst_pad_set_event_function (newpad, GST_DEBUG_FUNCPTR (gst_mplex_sink_event));
641   gst_pad_set_element_private (newpad, mpad);
642   gst_element_add_pad (element, newpad);
643   mplex->pads = g_slist_append (mplex->pads, mpad);
644 
645   return newpad;
646 }
647 
648 static void
gst_mplex_release_pad(GstElement * element,GstPad * pad)649 gst_mplex_release_pad (GstElement * element, GstPad * pad)
650 {
651   GstMplex *mplex = GST_MPLEX (element);
652   GstMplexPad *mpad;
653 
654   g_return_if_fail (pad);
655   mpad = (GstMplexPad *) gst_pad_get_element_private (pad);
656   g_return_if_fail (mpad);
657 
658   if (gst_element_remove_pad (element, pad)) {
659     gchar *padname;
660 
661     GST_MPLEX_MUTEX_LOCK (mplex);
662     mpad->eos = TRUE;
663     g_assert (mpad->pad == pad);
664     mpad->pad = NULL;
665     /* wake up if waiting on this pad */
666     GST_MPLEX_SIGNAL (mplex, mpad);
667 
668     padname = gst_object_get_name (GST_OBJECT (pad));
669     /* now only drop what might be last ref */
670     gst_object_unref (pad);
671     if (strstr (padname, "audio")) {
672       mplex->num_apads--;
673     } else {
674       mplex->num_vpads--;
675     }
676     g_free (padname);
677 
678     /* may now be up to us to get things going */
679     if (GST_STATE (element) > GST_STATE_READY)
680       gst_mplex_start_task (mplex);
681     GST_MPLEX_MUTEX_UNLOCK (mplex);
682   }
683 }
684 
685 static void
gst_mplex_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)686 gst_mplex_get_property (GObject * object,
687     guint prop_id, GValue * value, GParamSpec * pspec)
688 {
689   GST_MPLEX (object)->job->getProperty (prop_id, value);
690 }
691 
692 static void
gst_mplex_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)693 gst_mplex_set_property (GObject * object,
694     guint prop_id, const GValue * value, GParamSpec * pspec)
695 {
696   GST_MPLEX (object)->job->setProperty (prop_id, value);
697 }
698 
699 static gboolean
gst_mplex_src_activate_mode(GstPad * pad,GstObject * parent,GstPadMode mode,gboolean active)700 gst_mplex_src_activate_mode (GstPad * pad, GstObject * parent,
701     GstPadMode mode, gboolean active)
702 {
703   gboolean result = TRUE;
704   GstMplex *mplex;
705 
706   mplex = GST_MPLEX (parent);
707 
708   if (mode != GST_PAD_MODE_PUSH)
709     return FALSE;
710 
711   if (active) {
712     /* chain will start task once all streams have been setup */
713   } else {
714     /* end the muxing loop by forcing eos and unblock chains */
715     GST_MPLEX_MUTEX_LOCK (mplex);
716     mplex->eos = TRUE;
717     mplex->srcresult = GST_FLOW_FLUSHING;
718     GST_MPLEX_SIGNAL_ALL (mplex);
719     GST_MPLEX_MUTEX_UNLOCK (mplex);
720 
721     /* muxing loop should have ended now and can be joined */
722     result = gst_pad_stop_task (pad);
723   }
724 
725   return result;
726 }
727 
728 static GstStateChangeReturn
gst_mplex_change_state(GstElement * element,GstStateChange transition)729 gst_mplex_change_state (GstElement * element, GstStateChange transition)
730 {
731   GstMplex *mplex = GST_MPLEX (element);
732   GstStateChangeReturn ret;
733 
734   switch (transition) {
735     case GST_STATE_CHANGE_NULL_TO_READY:
736       break;
737     case GST_STATE_CHANGE_READY_TO_PAUSED:
738       break;
739     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
740       break;
741     default:
742       break;
743   }
744 
745   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
746   if (ret == GST_STATE_CHANGE_FAILURE)
747     goto done;
748 
749   switch (transition) {
750     case GST_STATE_CHANGE_PAUSED_TO_READY:
751       gst_mplex_reset (mplex);
752       break;
753     default:
754       break;
755   }
756 
757 done:
758   return ret;
759 }
760 
761 #ifndef GST_DISABLE_GST_DEBUG
762 
763 static mjpeg_log_handler_t old_handler = NULL;
764 
765 /* note that this will affect all mjpegtools elements/threads */
766 static void
gst_mplex_log_callback(log_level_t level,const char * message)767 gst_mplex_log_callback (log_level_t level, const char *message)
768 {
769   GstDebugLevel gst_level;
770 
771 #if GST_MJPEGTOOLS_API >= 10900
772   static const gint mjpeg_log_error = mjpeg_loglev_t ("error");
773   static const gint mjpeg_log_warn = mjpeg_loglev_t ("warn");
774   static const gint mjpeg_log_info = mjpeg_loglev_t ("info");
775   static const gint mjpeg_log_debug = mjpeg_loglev_t ("debug");
776 #else
777   static const gint mjpeg_log_error = LOG_ERROR;
778   static const gint mjpeg_log_warn = LOG_WARN;
779   static const gint mjpeg_log_info = LOG_INFO;
780   static const gint mjpeg_log_debug = LOG_DEBUG;
781 #endif
782 
783   if (level == mjpeg_log_error) {
784     gst_level = GST_LEVEL_ERROR;
785   } else if (level == mjpeg_log_warn) {
786     gst_level = GST_LEVEL_WARNING;
787   } else if (level == mjpeg_log_info) {
788     gst_level = GST_LEVEL_INFO;
789   } else if (level == mjpeg_log_debug) {
790     gst_level = GST_LEVEL_DEBUG;
791   } else {
792     gst_level = GST_LEVEL_INFO;
793   }
794 
795   /* message could have a % in it, do not segfault in such case */
796   gst_debug_log (mplex_debug, gst_level, "", "", 0, NULL, "%s", message);
797 
798   /* chain up to the old handler;
799    * this could actually be a handler from another mjpegtools based
800    * gstreamer element; in which case messages can come out double or from
801    * the wrong element ... */
802   old_handler (level, message);
803 }
804 #endif
805 
806 static gboolean
plugin_init(GstPlugin * plugin)807 plugin_init (GstPlugin * plugin)
808 {
809 #ifndef GST_DISABLE_GST_DEBUG
810   old_handler = mjpeg_log_set_handler (gst_mplex_log_callback);
811   g_assert (old_handler != NULL);
812 #endif
813   /* in any case, we do not want default handler output */
814   mjpeg_default_handler_verbosity (0);
815 
816   return gst_element_register (plugin, "mplex", GST_RANK_NONE, GST_TYPE_MPLEX);
817 }
818 
819 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
820     GST_VERSION_MINOR,
821     mplex,
822     "High-quality MPEG/DVD/SVCD/VCD video/audio multiplexer",
823     plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
824