diff --git a/gst/mpegtsmux/Makefile.am b/gst/mpegtsmux/Makefile.am index c433af3c3d..8f62b763bb 100644 --- a/gst/mpegtsmux/Makefile.am +++ b/gst/mpegtsmux/Makefile.am @@ -8,7 +8,7 @@ libgstmpegtsmux_la_SOURCES = \ mpegtsmux_aac.c libgstmpegtsmux_la_CFLAGS = $(GST_CFLAGS) -libgstmpegtsmux_la_LIBADD = $(top_builddir)/gst/mpegtsmux/tsmux/libtsmux.la $(GST_LIBS) $(GST_BASE_LIBS) +libgstmpegtsmux_la_LIBADD = $(top_builddir)/gst/mpegtsmux/tsmux/libtsmux.la $(GST_LIBS) $(GST_BASE_LIBS) -lgstvideo-@GST_MAJORMINOR@ libgstmpegtsmux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstmpegtsmux_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/gst/mpegtsmux/mpegtsmux.c b/gst/mpegtsmux/mpegtsmux.c index 3b4b71af6a..43949e4d40 100644 --- a/gst/mpegtsmux/mpegtsmux.c +++ b/gst/mpegtsmux/mpegtsmux.c @@ -88,6 +88,8 @@ #include #include +#include + #include "mpegtsmux.h" #include "mpegtsmux_h264.h" @@ -151,6 +153,8 @@ static void mpegtsmux_release_pad (GstElement * element, GstPad * pad); static GstStateChangeReturn mpegtsmux_change_state (GstElement * element, GstStateChange transition); static void mpegtsdemux_set_header_on_caps (MpegTsMux * mux); +static gboolean mpegtsmux_sink_event (GstPad * pad, GstEvent * event); +static gboolean mpegtsmux_src_event (GstPad * pad, GstEvent * event); GST_BOILERPLATE (MpegTsMux, mpegtsmux, GstElement, GST_TYPE_ELEMENT); @@ -215,6 +219,7 @@ mpegtsmux_init (MpegTsMux * mux, MpegTsMuxClass * g_class) mux->srcpad = gst_pad_new_from_static_template (&mpegtsmux_src_factory, "src"); gst_pad_use_fixed_caps (mux->srcpad); + gst_pad_set_event_function (mux->srcpad, mpegtsmux_src_event); gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); mux->collect = gst_collect_pads_new (); @@ -238,6 +243,8 @@ mpegtsmux_init (MpegTsMux * mux, MpegTsMuxClass * g_class) mux->prog_map = NULL; mux->streamheader = NULL; mux->streamheader_sent = FALSE; + mux->force_key_unit_event = NULL; + mux->pending_key_unit_ts = GST_CLOCK_TIME_NONE; } static void @@ -652,6 +659,201 @@ mpegtsmux_choose_best_stream (MpegTsMux * mux) #define COLLECT_DATA_PAD(collect_data) (((GstCollectData *)(collect_data))->pad) +static MpegTsPadData * +find_pad_data (MpegTsMux * mux, GstPad * pad) +{ + GSList *walk; + MpegTsPadData *ts_data = NULL; + + GST_COLLECT_PADS_PAD_LOCK (mux->collect); + walk = mux->collect->abidata.ABI.pad_list; + while (walk) { + if (((GstCollectData *) walk->data)->pad == pad) { + ts_data = (MpegTsPadData *) walk->data; + break; + } + + walk = g_slist_next (walk); + } + GST_COLLECT_PADS_PAD_UNLOCK (mux->collect); + + return ts_data; +} + +static gboolean +mpegtsmux_sink_event (GstPad * pad, GstEvent * event) +{ + MpegTsMux *mux = GST_MPEG_TSMUX (gst_pad_get_parent (pad)); + MpegTsPadData *ts_data; + gboolean res = TRUE; + gboolean forward = TRUE; + + ts_data = find_pad_data (mux, pad); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_DOWNSTREAM: + { + GstClockTime timestamp, stream_time, running_time; + gboolean all_headers; + guint count; + + if (!gst_video_event_is_force_key_unit (event)) + goto out; + + forward = FALSE; + + gst_video_event_parse_downstream_force_key_unit (event, + ×tamp, &stream_time, &running_time, &all_headers, &count); + GST_INFO_OBJECT (mux, "have downstream force-key-unit event on pad %s, " + "seqnum %d, running-time %" GST_TIME_FORMAT " count %d", + gst_pad_get_name (pad), gst_event_get_seqnum (event), + GST_TIME_ARGS (running_time), count); + + if (mux->force_key_unit_event != NULL) { + GST_INFO_OBJECT (mux, "skipping downstream force key unit event " + "as an upstream force key unit is already queued"); + goto out; + } + + if (!all_headers) + goto out; + + mux->pending_key_unit_ts = running_time; + gst_event_replace (&mux->force_key_unit_event, event); + break; + } + default: + break; + } + +out: + if (forward) + res = ts_data->eventfunc (pad, event); + + gst_object_unref (mux); + return res; +} + +static gboolean +mpegtsmux_src_event (GstPad * pad, GstEvent * event) +{ + MpegTsMux *mux = GST_MPEG_TSMUX (gst_pad_get_parent (pad)); + gboolean res = TRUE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_UPSTREAM: + { + GstIterator *iter; + GstIteratorResult iter_ret; + GstPad *sinkpad; + GstClockTime running_time; + gboolean all_headers, done; + guint count; + + if (!gst_video_event_is_force_key_unit (event)) + break; + + gst_video_event_parse_upstream_force_key_unit (event, + &running_time, &all_headers, &count); + + GST_INFO_OBJECT (mux, "received upstream force-key-unit event, " + "seqnum %d running_time %" GST_TIME_FORMAT " all_headers %d count %d", + gst_event_get_seqnum (event), GST_TIME_ARGS (running_time), + all_headers, count); + + if (!all_headers) + break; + + mux->pending_key_unit_ts = running_time; + gst_event_replace (&mux->force_key_unit_event, event); + + iter = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (mux)); + done = FALSE; + while (!done) { + gboolean res = FALSE, tmp; + iter_ret = gst_iterator_next (iter, (gpointer *) & sinkpad); + + switch (iter_ret) { + case GST_ITERATOR_DONE: + done = TRUE; + break; + case GST_ITERATOR_OK: + GST_INFO_OBJECT (mux, "forwarding to %s", + gst_pad_get_name (sinkpad)); + tmp = gst_pad_push_event (sinkpad, gst_event_ref (event)); + GST_INFO_OBJECT (mux, "result %d", tmp); + /* succeed if at least one pad succeeds */ + res |= tmp; + gst_object_unref (sinkpad); + break; + case GST_ITERATOR_ERROR: + done = TRUE; + break; + case GST_ITERATOR_RESYNC: + break; + } + } + + gst_event_unref (event); + break; + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (mux); + return res; +} + +static GstEvent * +check_pending_key_unit_event (GstEvent * pending_event, GstSegment * segment, + GstClockTime timestamp, guint flags, GstClockTime pending_key_unit_ts) +{ + GstClockTime running_time, stream_time; + gboolean all_headers; + guint count; + GstEvent *event = NULL; + + g_return_val_if_fail (pending_event != NULL, NULL); + g_return_val_if_fail (segment != NULL, NULL); + + if (pending_event == NULL) + goto out; + + if (GST_CLOCK_TIME_IS_VALID (pending_key_unit_ts) && + timestamp == GST_CLOCK_TIME_NONE) + goto out; + + running_time = gst_segment_to_running_time (segment, + GST_FORMAT_TIME, timestamp); + + GST_INFO ("now %" GST_TIME_FORMAT " wanted %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time), GST_TIME_ARGS (pending_key_unit_ts)); + if (GST_CLOCK_TIME_IS_VALID (pending_key_unit_ts) && + running_time < pending_key_unit_ts) + goto out; + + if (flags & GST_BUFFER_FLAG_DELTA_UNIT) { + GST_INFO ("pending force key unit, waiting for keyframe"); + goto out; + } + + stream_time = gst_segment_to_stream_time (segment, + GST_FORMAT_TIME, timestamp); + + gst_video_event_parse_upstream_force_key_unit (pending_event, + NULL, &all_headers, &count); + + event = + gst_video_event_new_downstream_force_key_unit (timestamp, stream_time, + running_time, all_headers, count); + gst_event_set_seqnum (event, gst_event_get_seqnum (pending_event)); + +out: + return event; +} + static GstFlowReturn mpegtsmux_collected (GstCollectPads * pads, MpegTsMux * mux) { @@ -686,6 +888,41 @@ mpegtsmux_collected (GstCollectPads * pads, MpegTsMux * mux) return GST_FLOW_ERROR; } + if (mux->force_key_unit_event != NULL) { + GstEvent *event; + + event = check_pending_key_unit_event (mux->force_key_unit_event, + &best->collect.segment, GST_BUFFER_TIMESTAMP (buf), + GST_BUFFER_FLAGS (buf), mux->pending_key_unit_ts); + if (event) { + GstClockTime running_time; + guint count; + GList *cur; + + mux->pending_key_unit_ts = GST_CLOCK_TIME_NONE; + gst_event_replace (&mux->force_key_unit_event, NULL); + + gst_video_event_parse_downstream_force_key_unit (event, + NULL, NULL, &running_time, NULL, &count); + + GST_INFO_OBJECT (mux, "pushing downstream force-key-unit event %d " + "%" GST_TIME_FORMAT " count %d", gst_event_get_seqnum (event), + GST_TIME_ARGS (running_time), count); + gst_pad_push_event (mux->srcpad, event); + + /* output PAT */ + mux->tsmux->last_pat_ts = -1; + + /* output PMT for each program */ + for (cur = g_list_first (mux->tsmux->programs); cur != NULL; + cur = g_list_next (cur)) { + TsMuxProgram *program = (TsMuxProgram *) cur->data; + + program->last_pmt_ts = -1; + } + } + } + if (G_UNLIKELY (prog->pcr_stream == NULL)) { /* Take the first data stream for the PCR */ GST_DEBUG_OBJECT (COLLECT_DATA_PAD (best), @@ -765,6 +1002,9 @@ mpegtsmux_request_new_pad (GstElement * element, if (pad_data == NULL) goto pad_failure; + pad_data->eventfunc = pad->eventfunc; + gst_pad_set_event_function (pad, mpegtsmux_sink_event); + pad_data->pid = pid; pad_data->last_ts = GST_CLOCK_TIME_NONE; pad_data->codec_data = NULL; diff --git a/gst/mpegtsmux/mpegtsmux.h b/gst/mpegtsmux/mpegtsmux.h index b45dd34aad..26003a8714 100644 --- a/gst/mpegtsmux/mpegtsmux.h +++ b/gst/mpegtsmux/mpegtsmux.h @@ -128,6 +128,8 @@ struct MpegTsMux { GList *streamheader; gboolean streamheader_sent; + GstClockTime pending_key_unit_ts; + GstEvent *force_key_unit_event; }; struct MpegTsMuxClass { @@ -157,7 +159,8 @@ struct MpegTsPadData { gboolean eos; gint prog_id; /* The program id to which it is attached to (not program pid) */ - TsMuxProgram *prog; /* The program to which this stream belongs to */ + TsMuxProgram *prog; /* The program to which this stream belongs to */ + GstPadEventFunction eventfunc; }; GType mpegtsmux_get_type (void); diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index ca7ced8b4a..fb0a09460b 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -188,6 +188,7 @@ check_PROGRAMS = \ $(check_logoinsert) \ elements/h263parse \ elements/h264parse \ + elements/mpegtsmux \ elements/mpegvideoparse \ elements/mpeg4videoparse \ elements/mxfdemux \ @@ -315,6 +316,10 @@ elements_rtpmux_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-0.10 $(GST_BASE_LIBS) elements_assrender_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) elements_assrender_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-0.10 -lgstapp-0.10 $(GST_BASE_LIBS) $(LDADD) +elements_mpegtsmux_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_mpegtsmux_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-0.10 $(GST_BASE_LIBS) $(LDADD) + + EXTRA_DIST = gst-plugins-bad.supp orc_cog_CFLAGS = $(ORC_CFLAGS) diff --git a/tests/check/elements/mpegtsmux.c b/tests/check/elements/mpegtsmux.c new file mode 100644 index 0000000000..aad06eb6b8 --- /dev/null +++ b/tests/check/elements/mpegtsmux.c @@ -0,0 +1,325 @@ +/* GStreamer + * + * Copyright (C) 2011 Alessandro Decina + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate video_src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-h264") + ); + +static GstStaticPadTemplate audio_src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/mpeg") + ); + +typedef struct _TestData +{ + GstEvent *sink_event; + GstEvent *src_event1; + GstEvent *src_event2; + gint src_events; +} TestData; + +typedef struct _ThreadData +{ + GstPad *pad; + GstBuffer *buffer; + GstFlowReturn flow_return; + GThread *thread; +} ThreadData; + +static gboolean +src_event (GstPad * pad, GstEvent * event) +{ + TestData *data = (TestData *) gst_pad_get_element_private (pad); + + if (event->type == GST_EVENT_CUSTOM_UPSTREAM) { + data->src_events += 1; + if (data->src_event1 != NULL) + data->src_event2 = event; + else + data->src_event1 = event; + } + + return TRUE; +} + +static gboolean +sink_event (GstPad * pad, GstEvent * event) +{ + TestData *data = (TestData *) gst_pad_get_element_private (pad); + + if (event->type == GST_EVENT_CUSTOM_DOWNSTREAM) + data->sink_event = event; + + return TRUE; +} + +static void +link_sinks (GstElement * mpegtsmux, + GstPad ** src1, GstPad ** src2, GstPad ** src3, TestData * test_data) +{ + GstPad *mux_sink1, *mux_sink2, *mux_sink3; + GstCaps *caps; + + /* link 3 sink pads, 2 video 1 audio */ + *src1 = gst_pad_new_from_static_template (&video_src_template, "src1"); + gst_pad_set_active (*src1, TRUE); + gst_pad_set_element_private (*src1, test_data); + gst_pad_set_event_function (*src1, src_event); + mux_sink1 = gst_element_get_request_pad (mpegtsmux, "sink_1"); + fail_unless (gst_pad_link (*src1, mux_sink1) == GST_PAD_LINK_OK); + + *src2 = gst_pad_new_from_static_template (&video_src_template, "src2"); + gst_pad_set_active (*src2, TRUE); + gst_pad_set_element_private (*src2, test_data); + gst_pad_set_event_function (*src2, src_event); + mux_sink2 = gst_element_get_request_pad (mpegtsmux, "sink_2"); + fail_unless (gst_pad_link (*src2, mux_sink2) == GST_PAD_LINK_OK); + + *src3 = gst_pad_new_from_static_template (&audio_src_template, "src3"); + gst_pad_set_active (*src3, TRUE); + gst_pad_set_element_private (*src3, test_data); + gst_pad_set_event_function (*src3, src_event); + mux_sink3 = gst_element_get_request_pad (mpegtsmux, "sink_3"); + fail_unless (gst_pad_link (*src3, mux_sink3) == GST_PAD_LINK_OK); + + caps = gst_caps_new_simple ("video/x-h264", NULL); + gst_pad_set_caps (mux_sink1, caps); + gst_pad_set_caps (mux_sink2, caps); + gst_caps_unref (caps); + caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 4, NULL); + gst_pad_set_caps (mux_sink3, caps); + gst_caps_unref (caps); + + gst_object_unref (mux_sink1); + gst_object_unref (mux_sink2); + gst_object_unref (mux_sink3); +} + +static void +link_src (GstElement * mpegtsmux, GstPad ** sink, TestData * test_data) +{ + GstPad *mux_src; + + mux_src = gst_element_get_static_pad (mpegtsmux, "src"); + *sink = gst_pad_new_from_static_template (&sink_template, "sink"); + gst_pad_set_active (*sink, TRUE); + gst_pad_set_event_function (*sink, sink_event); + gst_pad_set_element_private (*sink, test_data); + fail_unless (gst_pad_link (mux_src, *sink) == GST_PAD_LINK_OK); + + gst_object_unref (mux_src); +} + +static gpointer +pad_push_thread (gpointer user_data) +{ + ThreadData *data = (ThreadData *) user_data; + + data->flow_return = gst_pad_push (data->pad, data->buffer); + + return NULL; +} + +static ThreadData * +pad_push (GstPad * pad, GstBuffer * buffer, GstClockTime timestamp) +{ + ThreadData *data; + + data = g_new0 (ThreadData, 1); + data->pad = pad; + data->buffer = buffer; + GST_BUFFER_TIMESTAMP (buffer) = timestamp; + data->thread = g_thread_create (pad_push_thread, data, TRUE, NULL); + + return data; +} + +GST_START_TEST (test_force_key_unit_event_downstream) +{ + GstElement *mpegtsmux; + GstPad *sink; + GstPad *src1; + GstPad *src2; + GstPad *src3; + GstEvent *sink_event; + GstClockTime timestamp, stream_time, running_time; + gboolean all_headers = TRUE; + gint count = 0; + ThreadData *thread_data_1, *thread_data_2, *thread_data_3, *thread_data_4; + TestData test_data = { 0, }; + + mpegtsmux = gst_check_setup_element ("mpegtsmux"); + gst_element_set_state (mpegtsmux, GST_STATE_PLAYING); + + link_src (mpegtsmux, &sink, &test_data); + link_sinks (mpegtsmux, &src1, &src2, &src3, &test_data); + + /* hack: make sure collectpads builds collect->data */ + gst_pad_push_event (src1, gst_event_new_flush_start ()); + gst_pad_push_event (src1, gst_event_new_flush_stop ()); + + /* send a force-key-unit event with running_time=2s */ + timestamp = stream_time = running_time = 2 * GST_SECOND; + sink_event = gst_video_event_new_downstream_force_key_unit (timestamp, + stream_time, running_time, all_headers, count); + + fail_unless (gst_pad_push_event (src1, sink_event)); + fail_unless (test_data.sink_event == NULL); + + /* push 4 buffers, make sure mpegtsmux handles the force-key-unit event when + * the buffer with the requested running time is collected */ + thread_data_1 = pad_push (src1, gst_buffer_new (), 1 * GST_SECOND); + thread_data_2 = pad_push (src2, gst_buffer_new (), 2 * GST_SECOND); + thread_data_3 = pad_push (src3, gst_buffer_new (), 3 * GST_SECOND); + + g_thread_join (thread_data_1->thread); + fail_unless (test_data.sink_event == NULL); + + /* push again on src1 so that the buffer on src2 is collected */ + thread_data_4 = pad_push (src1, gst_buffer_new (), 4 * GST_SECOND); + + g_thread_join (thread_data_2->thread); + fail_unless (test_data.sink_event != NULL); + + gst_element_set_state (mpegtsmux, GST_STATE_NULL); + + g_thread_join (thread_data_3->thread); + g_thread_join (thread_data_4->thread); + + g_free (thread_data_1); + g_free (thread_data_2); + g_free (thread_data_3); + g_free (thread_data_4); + gst_object_unref (src1); + gst_object_unref (src2); + gst_object_unref (src3); + gst_object_unref (sink); + gst_object_unref (mpegtsmux); +} + +GST_END_TEST; + +GST_START_TEST (test_force_key_unit_event_upstream) +{ + GstElement *mpegtsmux; + GstPad *sink; + GstPad *src1; + GstPad *src2; + GstPad *src3; + GstEvent *event; + GstClockTime timestamp, stream_time, running_time; + gboolean all_headers = TRUE; + gint count = 0; + TestData test_data = { 0, }; + ThreadData *thread_data_1, *thread_data_2, *thread_data_3, *thread_data_4; + gint32 seqnum; + + mpegtsmux = gst_check_setup_element ("mpegtsmux"); + gst_element_set_state (mpegtsmux, GST_STATE_PLAYING); + + link_src (mpegtsmux, &sink, &test_data); + link_sinks (mpegtsmux, &src1, &src2, &src3, &test_data); + + /* hack: make sure collectpads builds collect->data */ + gst_pad_push_event (src1, gst_event_new_flush_start ()); + gst_pad_push_event (src1, gst_event_new_flush_stop ()); + + /* send an upstream force-key-unit event with running_time=2s */ + timestamp = stream_time = running_time = 2 * GST_SECOND; + event = + gst_video_event_new_upstream_force_key_unit (running_time, TRUE, count); + seqnum = gst_event_get_seqnum (event); + fail_unless (gst_pad_push_event (sink, event)); + + fail_unless (test_data.sink_event == NULL); + fail_unless_equals_int (test_data.src_events, 3); + + /* send downstream events with unrelated seqnums */ + event = gst_video_event_new_downstream_force_key_unit (timestamp, + stream_time, running_time, all_headers, count); + fail_unless (gst_pad_push_event (src1, event)); + event = gst_video_event_new_downstream_force_key_unit (timestamp, + stream_time, running_time, all_headers, count); + fail_unless (gst_pad_push_event (src2, event)); + + /* events should be skipped */ + fail_unless (test_data.sink_event == NULL); + + /* push 4 buffers, make sure mpegtsmux handles the force-key-unit event when + * the buffer with the requested running time is collected */ + thread_data_1 = pad_push (src1, gst_buffer_new (), 1 * GST_SECOND); + thread_data_2 = pad_push (src2, gst_buffer_new (), 2 * GST_SECOND); + thread_data_3 = pad_push (src3, gst_buffer_new (), 3 * GST_SECOND); + + g_thread_join (thread_data_1->thread); + fail_unless (test_data.sink_event == NULL); + + /* push again on src1 so that the buffer on src2 is collected */ + thread_data_4 = pad_push (src1, gst_buffer_new (), 4 * GST_SECOND); + + g_thread_join (thread_data_2->thread); + fail_unless (test_data.sink_event != NULL); + + gst_element_set_state (mpegtsmux, GST_STATE_NULL); + + g_thread_join (thread_data_3->thread); + g_thread_join (thread_data_4->thread); + + g_free (thread_data_1); + g_free (thread_data_2); + g_free (thread_data_3); + g_free (thread_data_4); + + gst_object_unref (src1); + gst_object_unref (src2); + gst_object_unref (src3); + gst_object_unref (sink); + gst_object_unref (mpegtsmux); +} + +GST_END_TEST; + +static Suite * +mpegtsmux_suite (void) +{ + Suite *s = suite_create ("mpegtsmux"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + + tcase_add_test (tc_chain, test_force_key_unit_event_downstream); + tcase_add_test (tc_chain, test_force_key_unit_event_upstream); + + return s; +} + +GST_CHECK_MAIN (mpegtsmux);