summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorThibault Saunier <thibault.saunier@collabora.com>2012-05-04 18:07:10 -0400
committerThibault Saunier <thibault.saunier@collabora.com>2012-05-04 18:07:10 -0400
commita06fcacd411a04ae6bd796a84f9c52da46d6f8e0 (patch)
treec93e651d9e6fd68f792998880f98dbc4a89bb166 /tests
parent8d44a46b50d52b6ef49743bc6ae1f783c686004c (diff)
parent033a2c77bddccd0f0befa6328fb5ffb1ab0724d4 (diff)
Merge remote-tracking branch 'origin/0.10'
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.am26
-rw-r--r--tests/insanity-test-gst-decoder.c1240
-rw-r--r--tests/insanity-test-gst-demuxer.c1590
-rw-r--r--tests/insanity-test-gst-subtitles.c1113
-rw-r--r--tests/media-descriptor-common.c98
-rw-r--r--tests/media-descriptor-common.h110
-rw-r--r--tests/media-descriptor-parser.c569
-rw-r--r--tests/media-descriptor-parser.h86
-rw-r--r--tests/media-descriptor-writer.c346
-rw-r--r--tests/media-descriptor-writer.h83
10 files changed, 5259 insertions, 2 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8e7bac7..3e83269 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -9,6 +9,13 @@ AM_CPPFLAGS=-I $(top_srcdir)/lib
testdir=$(libexecdir)/insanity-gst/tests
+xml_noisnt_libraries=libtestsxmlhelper.la
+xml_noinst_headers=media-descriptor-parser.h media-descriptor-writer.h media-descriptor-common.h
+libtestsxmlhelper_la_LIBADD=$(common_ldadd)
+libtestsxmlhelper_la_CFLAGS=$(common_cflags)
+libtestsxmlhelper_la_SOURCES=media-descriptor-parser.c media-descriptor-writer.c media-descriptor-common.c
+
+
common_cflags=$(INSANITY_CFLAGS) $(GST_CFLAGS) $(GLIB_CFLAGS) $(GOBJECT_CFLAGS) $(GTHREAD_CFLAGS) $(WARNING_CFLAGS)
common_ldadd=$(INSANITY_LIBS) $(GST_LIBS) $(GLIB_LIBS) $(GOBJECT_LIBS) $(GTHREAD_LIBS)
@@ -28,6 +35,14 @@ insanity_test_gst_dvd_SOURCES=insanity-test-gst-dvd.c
insanity_test_gst_dvd_CFLAGS=$(GST_VIDEO_CFLAGS) $(common_cflags)
insanity_test_gst_dvd_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la $(GST_VIDEO_LIBS) $(common_ldadd)
+insanity_test_gst_demuxer_SOURCES=insanity-test-gst-demuxer.c
+insanity_test_gst_demuxer_CFLAGS=$(GST_PBUTILS_CFLAGS) $(GST_INTERFACES_CFLAGS) $(common_cflags)
+insanity_test_gst_demuxer_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la libtestsxmlhelper.la $(GST_INTERFACES_LIBS) $(GST_PBUTILS_LIBS) $(common_ldadd)
+
+insanity_test_gst_decoder_SOURCES=insanity-test-gst-decoder.c
+insanity_test_gst_decoder_CFLAGS=$(GST_INTERFACES_CFLAGS) $(common_cflags)
+insanity_test_gst_decoder_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la libtestsxmlhelper.la $(GST_INTERFACES_LIBS) $(common_ldadd)
+
insanity_test_gst_stream_switch_SOURCES=insanity-test-gst-stream-switch.c
insanity_test_gst_stream_switch_CFLAGS=$(GST_AUDIO_CFLAGS) $(GST_VIDEO_CFLAGS) $(GST_BASE_CFLAGS) $(common_cflags)
insanity_test_gst_stream_switch_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la $(GST_AUDIO_LIBS) $(GST_VIDEO_LIBS) $(GST_BASE_LIBS) $(common_ldadd)
@@ -36,6 +51,10 @@ insanity_test_gst_discoverer_SOURCES=insanity-test-gst-discoverer.c
insanity_test_gst_discoverer_CFLAGS=$(GST_PBUTILS_CFLAGS) $(common_cflags)
insanity_test_gst_discoverer_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la $(GST_PBUTILS_LIBS) $(common_ldadd)
+insanity_test_gst_subtitles_SOURCES=insanity-test-gst-subtitles.c
+insanity_test_gst_subtitles_CFLAGS=$(GST_VIDEO_CFLAGS) $(GST_PBUTILS_CFLAGS) $(common_cflags)
+insanity_test_gst_subtitles_LDADD=../lib/insanity-gst/libinsanity-gst-@GST_TARGET@.la libtestsxmlhelper.la $(GST_PBUTILS_LIBS) $(GST_VIDEO_LIBS) $(common_ldadd)
+
if HAVE_GST_RTSP_SERVER
insanity_test_gst_rtsp_SOURCES=insanity-test-gst-rtsp.c
insanity_test_gst_rtsp_CFLAGS=$(GST_RTSP_SERVER_CFLAGS) $(common_cflags)
@@ -68,8 +87,8 @@ libinsanityhelper_la_SOURCES = insanity-file-appsrc.c insanity-fake-appsink.c
helper_noinst_libraries=libinsanityhelper.la
helper_noinst_headers=insanity-file-appsrc.h insanity-fake-appsink.h
-noinst_LTLIBRARIES=$(http_noinst_libraries) $(helper_noinst_libraries)
-noinst_HEADERS=$(http_noinst_headers) $(helper_noinst_headers)
+noinst_LTLIBRARIES=$(http_noinst_libraries) $(helper_noinst_libraries) $(xml_noisnt_libraries)
+noinst_HEADERS=$(http_noinst_headers) $(helper_noinst_headers) $(xml_noinst_headers)
BUILT_SOURCES = $(built_headers) $(built_sources)
EXTRA_DIST = run-insanity-test-gst-generic-pipeline
@@ -82,6 +101,9 @@ test_PROGRAMS=\
insanity-test-gst-dvd \
insanity-test-gst-stream-switch \
insanity-test-gst-discoverer \
+ insanity-test-gst-demuxer \
+ insanity-test-gst-decoder \
+ insanity-test-gst-subtitles \
$(soup_tests) \
$(rtsp_test)
diff --git a/tests/insanity-test-gst-decoder.c b/tests/insanity-test-gst-decoder.c
new file mode 100644
index 0000000..4280bb1
--- /dev/null
+++ b/tests/insanity-test-gst-decoder.c
@@ -0,0 +1,1240 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 <insanity-gst/insanity-gst.h>
+#include "media-descriptor-parser.h"
+
+#define LOG(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "demuxer", INSANITY_LOG_LEVEL_DEBUG, format "\n", ##args)
+#define ERROR(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "demuxer", INSANITY_LOG_LEVEL_SPAM, format "\n", ##args)
+
+static GStaticMutex glob_mutex = G_STATIC_MUTEX_INIT;
+#define DECODER_TEST_LOCK() g_static_mutex_lock (&glob_mutex)
+#define DECODER_TEST_UNLOCK() g_static_mutex_unlock (&glob_mutex)
+
+/* Maximum difference of between the expected duration in the stream and
+ * the real position, will let a third ofsecond as various factors can create
+ * latency in the position query */
+#define POSITION_THRESHOLD (GST_SECOND * 1 / 3)
+#define SEEK_THRESHOLD (GST_SECOND * 3 / 4)
+
+/* timeout for gst_element_get_state() after a seek */
+#define SEEK_TIMEOUT (10 * GST_SECOND)
+#define FAST_FORWARD_PLAYING_THRESHOLD (G_USEC_PER_SEC / 3)
+
+/* How much time we allow without receiving any buffer or event
+ before deciding the pipeline is wedged. Second precision. */
+#define IDLE_TIMEOUT (GST_SECOND*20)
+#define WAIT_SEGMENT_TIMEOUT (GST_SECOND*3)
+
+typedef struct
+{
+ GstElement *decoder;
+ GstElement *fakesink;
+ GstPad *pad;
+ gulong probe_id;
+ InsanityTest *test;
+
+} ProbeContext;
+
+typedef enum
+{
+ TEST_NONE,
+ TEST_QUERIES,
+ TEST_POSITION,
+
+ /* Start seeking */
+ TEST_FAST_FORWARD, /* Always first seeking test */
+
+ /* Backward */
+ TEST_BACKWARD_PLAYBACK,
+ TEST_FAST_BACKWARD, /* Always last seeking test */
+
+} TestInProgress;
+
+/* Global GstElement-s */
+static GstElement *glob_src = NULL;
+static GstElement *glob_typefinder = NULL;
+static GstElement *glob_demuxer = NULL;
+static GstElement *glob_decoder = NULL;
+static GstElement *glob_pipeline = NULL;
+static GstElement *glob_multiqueue = NULL;
+
+static gboolean glob_testing_parser = FALSE;
+
+/* Gloabl fields */
+
+static ProbeContext *glob_prob_ctx = NULL;
+static MediaDescriptorParser *glob_parser = NULL;
+static GstClockTime glob_playback_duration = GST_CLOCK_TIME_NONE;
+static gboolean glob_push_mode = FALSE;
+static GstBuffer *glob_parsing_buf = NULL;
+
+static TestInProgress glob_in_progress = TEST_NONE;
+static GstSegment glob_last_segment;
+
+/* checking Checking wedge */
+static gint64 global_last_probe = 0;
+static gint64 global_last_seek = 0;
+static guint global_idle_timeout = 0;
+
+/* Use in None and seek modes */
+static gboolean glob_waiting_segment = TRUE;
+
+/* Check position test */
+static GstClockTime glob_first_pos_point = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_expected_pos = GST_CLOCK_TIME_NONE;
+
+/* Seeking, and duration queries tests */
+static GstClockTime glob_seekable = FALSE;
+static GstClockTime glob_duration = GST_CLOCK_TIME_NONE;
+
+/* First segment test */
+static gboolean glob_waiting_first_segment = TRUE;
+static GstClockTime glob_last_segment_start_time = GST_CLOCK_TIME_NONE;
+
+/* Seek modes test */
+static gdouble glob_seek_rate = 0;
+static gboolean glob_seek_got_segment = FALSE;
+static GstClockTime glob_seek_segment_seektime = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_seek_stop_ts = GST_CLOCK_TIME_NONE;
+
+/* Sequence number test */
+static guint glob_seqnum = 0;
+static gboolean glob_seqnum_found = FALSE;
+static gboolean glob_wrong_seqnum = FALSE;
+
+/* Segment clipping test */
+static gboolean glob_bad_segment_clipping = FALSE;
+
+static gboolean next_test (InsanityTest * test);
+
+static void
+clean_test (InsanityTest * test)
+{
+ glob_demuxer = NULL;
+ glob_pipeline = NULL;
+ glob_multiqueue = NULL;
+ glob_src = NULL;
+ glob_seekable = FALSE;
+ glob_duration = GST_CLOCK_TIME_NONE;
+ glob_waiting_first_segment = TRUE;
+ glob_waiting_segment = TRUE;
+ glob_testing_parser = FALSE;
+ glob_seek_rate = 0;
+ glob_seek_segment_seektime = GST_CLOCK_TIME_NONE;
+ glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+
+ glob_in_progress = TEST_NONE;
+
+ if (glob_prob_ctx != NULL) {
+ insanity_gst_test_remove_data_probe (INSANITY_GST_TEST (test),
+ glob_prob_ctx->pad, glob_prob_ctx->probe_id);
+
+ g_slice_free (ProbeContext, glob_prob_ctx);
+ glob_prob_ctx = NULL;
+ }
+
+ if (glob_parser != NULL) {
+ g_object_unref (glob_parser);
+ glob_parser = NULL;
+ }
+
+
+ global_idle_timeout = 0;
+ global_last_probe = 0;
+ global_last_seek = 0;
+ glob_expected_pos = GST_CLOCK_TIME_NONE;
+ glob_first_pos_point = GST_CLOCK_TIME_NONE;
+
+ glob_seqnum = 0;
+ glob_seqnum_found = FALSE;
+ glob_wrong_seqnum = FALSE;
+
+ glob_bad_segment_clipping = FALSE;
+
+}
+
+static const gchar *
+test_get_name (TestInProgress in_progress)
+{
+ switch (in_progress) {
+ case TEST_NONE:
+ return "None";
+ case TEST_QUERIES:
+ return "Queries";
+ case TEST_POSITION:
+ return "Postion";
+ case TEST_FAST_FORWARD:
+ return "Fast forward";
+ case TEST_BACKWARD_PLAYBACK:
+ return "Backward playback";
+ case TEST_FAST_BACKWARD:
+ return "Fast backward";
+ }
+
+ return NULL;
+}
+
+static void
+test_position (InsanityTest * test, GstBuffer * buf)
+{
+ GstQuery *query;
+ GstClockTimeDiff diff;
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf) == FALSE)
+ return;
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_first_pos_point) == FALSE) {
+ glob_first_pos_point = gst_segment_to_stream_time (&glob_last_segment,
+ glob_last_segment.format, GST_BUFFER_TIMESTAMP (buf));
+ }
+
+ glob_expected_pos = gst_segment_to_stream_time (&glob_last_segment,
+ glob_last_segment.format, GST_BUFFER_TIMESTAMP (buf));
+
+ diff = ABS (GST_CLOCK_DIFF (glob_expected_pos, glob_first_pos_point));
+
+ if (diff < glob_playback_duration * GST_SECOND)
+ return;
+
+ query = gst_query_new_position (GST_FORMAT_TIME);
+
+ if (gst_element_query (glob_decoder, query)) {
+ gint64 pos;
+ GstFormat fmt;
+ GstClockTimeDiff diff;
+
+ gst_query_parse_position (query, &fmt, &pos);
+ diff = ABS (GST_CLOCK_DIFF (glob_expected_pos, pos));
+
+ if (diff <= POSITION_THRESHOLD) {
+ insanity_test_validate_checklist_item (test, "position-detection", TRUE,
+ NULL);
+ } else {
+ gchar *validate_msg = g_strdup_printf ("Found position: %" GST_TIME_FORMAT
+ " expected: %" GST_TIME_FORMAT, GST_TIME_ARGS (pos),
+ GST_TIME_ARGS (glob_expected_pos));
+
+ insanity_test_validate_checklist_item (test, "position-detection",
+ FALSE, validate_msg);
+
+ g_free (validate_msg);
+ }
+ } else {
+ LOG (test,
+ "%s Does not handle position queries (position-detection \"SKIP\")",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+
+ next_test (test);
+}
+
+static void
+validate_current_test (InsanityTest * test, gboolean validate,
+ const gchar * msg)
+{
+ switch (glob_in_progress) {
+ case TEST_BACKWARD_PLAYBACK:
+ insanity_test_validate_checklist_item (test, "backward-playback",
+ validate, msg);
+ break;
+ case TEST_FAST_FORWARD:
+ insanity_test_validate_checklist_item (test, "fast-forward",
+ validate, msg);
+ break;
+ case TEST_FAST_BACKWARD:
+ glob_seqnum = 0;
+ insanity_test_validate_checklist_item (test, "fast-backward",
+ validate, msg);
+ break;
+ default:
+ ERROR (test, "Could not validate mode %i", glob_in_progress);
+ return;
+ }
+}
+
+static gboolean
+seek_mode_testing (InsanityTest * test)
+{
+ gboolean res;
+ GstEvent *event;
+ GstSeekFlags flags = GST_SEEK_FLAG_FLUSH;
+ GstSeekType stop_type = GST_SEEK_TYPE_NONE;
+
+ /* Reset global seek props */
+ glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+ glob_seek_stop_ts = GST_CLOCK_TIME_NONE;
+ glob_seek_segment_seektime = 0;
+
+ /* Set seeking arguments */
+ switch (glob_in_progress) {
+ case TEST_BACKWARD_PLAYBACK:
+ glob_seek_rate = -1;
+ glob_seek_stop_ts = glob_duration;
+ stop_type = GST_SEEK_TYPE_SET;
+ break;
+ case TEST_FAST_FORWARD:
+ glob_seek_rate = 2;
+ glob_seek_stop_ts = glob_duration / 2;
+ break;
+ case TEST_FAST_BACKWARD:
+ glob_seek_rate = -2;
+ glob_seek_stop_ts = glob_duration;
+ stop_type = GST_SEEK_TYPE_SET;
+ break;
+ default:
+ return FALSE;
+ }
+
+ glob_seek_got_segment = FALSE;
+ event = gst_event_new_seek (glob_seek_rate, GST_FORMAT_TIME,
+ flags, GST_SEEK_TYPE_SET, glob_seek_segment_seektime,
+ stop_type, glob_seek_stop_ts);
+
+ /* We didn't find any event/message with the seqnum we previously set */
+ if (glob_seqnum != 0 && glob_seqnum_found == FALSE)
+ glob_wrong_seqnum = TRUE;
+
+ glob_seqnum_found = FALSE;
+ glob_seqnum = gst_util_seqnum_next ();
+ gst_event_set_seqnum (event, glob_seqnum);
+ res = gst_element_send_event (glob_pipeline, event);
+ global_last_seek = g_get_monotonic_time ();
+
+ if (!res) {
+ validate_current_test (test, FALSE, "Could not send seek event");
+ glob_seek_rate = 0;
+ glob_seqnum = 0;
+
+ /* ... Next test */
+ next_test (test);
+ }
+
+ return FALSE;
+}
+
+static void
+test_queries (InsanityTest * test)
+{
+ GstQuery *query = gst_query_new_seeking (GST_FORMAT_TIME);
+
+ if (gst_element_query (glob_demuxer, query)) {
+ GstFormat fmt;
+ gboolean seekable, known_seekable;
+
+ gst_query_parse_seeking (query, &fmt, &seekable, NULL, NULL);
+ if (glob_parser == NULL) {
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ TRUE, "No media-descriptor file, result not verified against it");
+
+ glob_seekable = seekable;
+ } else {
+ known_seekable = media_descriptor_parser_get_seekable (glob_parser);
+
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ known_seekable == seekable, NULL);
+ glob_seekable = known_seekable;
+ }
+ } else {
+ if (glob_parser != NULL)
+ glob_seekable = media_descriptor_parser_get_seekable (glob_parser);
+
+ LOG (test,
+ "%s Does not handle seeking queries (seekable-detection \"SKIP\")",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+
+ gst_query_unref (query);
+ query = gst_query_new_duration (GST_FORMAT_TIME);
+ if (gst_element_query (glob_demuxer, query)) {
+ GstFormat fmt;
+ gchar *validate_msg = NULL;
+ gint64 duration;
+
+ if (glob_parser == NULL) {
+ gst_query_parse_duration (query, &fmt, &duration);
+ validate_msg =
+ g_strdup_printf ("Found duration %" GST_TIME_FORMAT
+ " No media-descriptor file, result not verified against it",
+ GST_TIME_ARGS (duration));
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ TRUE, validate_msg);
+
+ g_free (validate_msg);
+
+ glob_duration = duration;
+ } else {
+ glob_duration = media_descriptor_parser_get_duration (glob_parser);
+ gst_query_parse_duration (query, &fmt, &duration);
+
+ if (glob_duration != duration) {
+ validate_msg =
+ g_strdup_printf ("Found time %" GST_TIME_FORMAT "-> %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (duration),
+ GST_TIME_ARGS (glob_duration));
+
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ glob_duration == duration, validate_msg);
+
+ g_free (validate_msg);
+ } else {
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ TRUE, NULL);
+ }
+ }
+
+ } else {
+ if (glob_parser != NULL)
+ glob_duration = media_descriptor_parser_get_seekable (glob_parser);
+
+ LOG (test, "%s Does not handle duration queries "
+ "(duration-detection \"SKIP\")",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_duration) &&
+ glob_playback_duration > glob_duration) {
+ LOG (test, "playback_duration > media duration, setting it"
+ "to media_duration != 2");
+
+ glob_playback_duration = glob_duration / 2;
+ }
+ gst_query_unref (query);
+
+
+ next_test (test);
+}
+
+static const gchar *
+pipeline_mode_get_name (gboolean push_mode)
+{
+ if (push_mode == TRUE)
+ return "push";
+
+ return "pull";
+}
+
+static void
+unvalidate_seeking_tests (InsanityTest * test)
+{
+ gchar *message = g_strdup_printf ("%s not seekable in %s mode",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)), pipeline_mode_get_name (glob_push_mode));
+
+ insanity_test_validate_checklist_item (test, "fast-forward", TRUE, message);
+ insanity_test_validate_checklist_item (test, "fast-backward", TRUE, message);
+ insanity_test_validate_checklist_item (test, "backward-playback", TRUE,
+ message);
+
+ g_free (message);
+}
+
+static gboolean
+next_test (InsanityTest * test)
+{
+ switch (glob_in_progress) {
+ case TEST_NONE:
+ glob_in_progress = TEST_QUERIES;
+ test_queries (test);
+ break;
+ case TEST_QUERIES:
+ glob_in_progress = TEST_POSITION;
+ break;
+ case TEST_POSITION:
+ if (glob_seekable == FALSE) {
+ /* Do not enter seek mode tests and finnish the test */
+ unvalidate_seeking_tests (test);
+
+ insanity_test_done (test);
+ return FALSE;
+ }
+
+ glob_in_progress = TEST_BACKWARD_PLAYBACK;
+ glob_waiting_segment = TRUE;
+ g_timeout_add (1000, (GSourceFunc) & seek_mode_testing, test);
+ break;
+ case TEST_BACKWARD_PLAYBACK:
+ glob_in_progress = TEST_FAST_FORWARD;
+ glob_waiting_segment = TRUE;
+ g_timeout_add (1000, (GSourceFunc) & seek_mode_testing, test);
+ break;
+ case TEST_FAST_FORWARD:
+ glob_in_progress = TEST_FAST_BACKWARD;
+ glob_waiting_segment = TRUE;
+ g_timeout_add (1000, (GSourceFunc) & seek_mode_testing, test);
+ break;
+ default:
+ insanity_test_done (test);
+ return FALSE;
+ }
+
+ LOG (test, "%s in progress", test_get_name (glob_in_progress));
+
+ return FALSE;
+}
+
+static gboolean
+check_wedged (gpointer data)
+{
+ InsanityTest *test = data;
+ gboolean wedged = FALSE;
+ gint64 idle;
+
+ DECODER_TEST_LOCK ();
+ idle = (global_last_probe <= 0) ?
+ 0 : 1000 * (g_get_monotonic_time () - global_last_probe);
+
+ if (idle >= IDLE_TIMEOUT) {
+ wedged = TRUE;
+ LOG (test, "Nothing probed in too long");
+ } else if (glob_waiting_segment == TRUE) {
+ idle = (global_last_seek <= 0) ?
+ 0 : 1000 * (g_get_monotonic_time () - global_last_seek);
+
+ if (idle >= WAIT_SEGMENT_TIMEOUT) {
+ LOG (test, "Waited segment for too much time");
+ wedged = TRUE;
+ }
+ }
+
+ if (wedged) {
+ LOG (test, "Wedged, kicking");
+
+ switch (glob_in_progress) {
+ case TEST_NONE:
+ break;
+ case TEST_QUERIES:
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ FALSE, "No buffers or events were seen for a while");
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ FALSE, "No buffers or events were seen for a while");
+ break;
+ case TEST_POSITION:
+ insanity_test_validate_checklist_item (test, "position-detection",
+ FALSE, "No buffers or events were seen for a while");
+ case TEST_FAST_FORWARD:
+ insanity_test_validate_checklist_item (test, "fast-forward", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_BACKWARD_PLAYBACK:
+ insanity_test_validate_checklist_item (test, "backward-playback", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_FAST_BACKWARD:
+ insanity_test_validate_checklist_item (test, "fast-backward", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ }
+
+ global_last_probe = g_get_monotonic_time ();
+ g_idle_add ((GSourceFunc) & next_test, test);
+ }
+
+ DECODER_TEST_UNLOCK ();
+
+ return TRUE;
+}
+
+/* Pipeline Callbacks */
+static gboolean
+probe_cb (InsanityGstTest * ptest, GstPad * pad, GstMiniObject * object,
+ gpointer userdata)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ global_last_probe = g_get_monotonic_time ();
+
+ DECODER_TEST_LOCK ();
+ if (GST_IS_BUFFER (object)) {
+ GstBuffer *buf;
+ GstClockTime ts;
+
+ buf = GST_BUFFER (object);
+ ts = GST_BUFFER_TIMESTAMP (buf);
+
+ /* First check clipping */
+ if (glob_testing_parser == FALSE && GST_CLOCK_TIME_IS_VALID (ts) &&
+ glob_waiting_segment == FALSE) {
+ gint64 ts_end, cstart, cstop;
+
+ /* Check if buffer is completely outside the segment */
+ ts_end = ts;
+ if (GST_BUFFER_DURATION_IS_VALID (buf))
+ ts_end += GST_BUFFER_DURATION (buf);
+
+ /* Check if buffer is completely outside the segment */
+ ts_end = ts;
+ if (!gst_segment_clip (&glob_last_segment,
+ glob_last_segment.format, ts, ts_end, &cstart, &cstop)) {
+ char *msg = g_strdup_printf ("Got timestamp %" GST_TIME_FORMAT " -- %"
+ GST_TIME_FORMAT ", outside configured segment (%" GST_TIME_FORMAT
+ " -- %" GST_TIME_FORMAT "), method %s",
+ GST_TIME_ARGS (ts), GST_TIME_ARGS (ts_end),
+ GST_TIME_ARGS (glob_last_segment.start),
+ GST_TIME_ARGS (glob_last_segment.stop),
+ test_get_name (glob_in_progress));
+ insanity_test_validate_checklist_item (INSANITY_TEST (ptest),
+ "segment-clipping", FALSE, msg);
+ g_free (msg);
+ glob_bad_segment_clipping = TRUE;
+ }
+ }
+
+ switch (glob_in_progress) {
+ case TEST_NONE:
+ if (glob_waiting_first_segment == TRUE)
+ insanity_test_validate_checklist_item (test, "first-segment",
+ FALSE, "Got a buffer before the first segment");
+
+ /* Got the first buffer, starting testing dance */
+ next_test (test);
+ break;
+ case TEST_POSITION:
+ test_position (test, buf);
+ break;
+ case TEST_FAST_FORWARD:
+ case TEST_BACKWARD_PLAYBACK:
+ case TEST_FAST_BACKWARD:
+ {
+ gint64 stime_ts;
+
+ if (GST_CLOCK_TIME_IS_VALID (ts) == FALSE ||
+ glob_waiting_segment == TRUE) {
+ break;
+ }
+
+ stime_ts = gst_segment_to_stream_time (&glob_last_segment,
+ glob_last_segment.format, ts);
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_seek_first_buf_ts) == FALSE) {
+ GstClockTime expected_ts =
+ gst_segment_to_stream_time (&glob_last_segment,
+ glob_last_segment.format,
+ glob_seek_rate <
+ 0 ? glob_seek_stop_ts : glob_seek_segment_seektime);
+
+ GstClockTimeDiff diff = ABS (GST_CLOCK_DIFF (stime_ts, expected_ts));
+
+ if (diff > SEEK_THRESHOLD) {
+ gchar *valmsg =
+ g_strdup_printf ("Received buffer timestamp %" GST_TIME_FORMAT
+ " Seeek wanted %" GST_TIME_FORMAT "",
+ GST_TIME_ARGS (stime_ts),
+ GST_TIME_ARGS (expected_ts));
+
+ validate_current_test (test, FALSE, valmsg);
+ next_test (test);
+
+ g_free (valmsg);
+ } else
+ glob_seek_first_buf_ts = stime_ts;
+
+ } else {
+ GstClockTimeDiff diff =
+ GST_CLOCK_DIFF (stime_ts, glob_seek_first_buf_ts);
+
+ if (diff < 0)
+ diff = -diff;
+
+ if (diff >= glob_playback_duration * GST_SECOND) {
+ validate_current_test (test, TRUE, NULL);
+ next_test (test);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ } else if (GST_IS_EVENT (object)) {
+ GstEvent *event = GST_EVENT (object);
+ guint seqnum = gst_event_get_seqnum (event);
+
+ if (G_LIKELY (glob_seqnum_found == FALSE) && seqnum == glob_seqnum)
+ glob_seqnum_found = TRUE;
+
+ if (glob_seqnum_found == TRUE && seqnum != glob_seqnum) {
+ gchar *message = g_strdup_printf ("Current seqnum %i != "
+ "received %i", glob_seqnum, seqnum);
+
+ insanity_test_validate_checklist_item (test, "seqnum-management",
+ FALSE, message);
+
+ glob_wrong_seqnum = TRUE;
+ g_free (message);
+ }
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ GstFormat fmt;
+ gint64 start, stop, position;
+ gdouble rate, applied_rate;
+ gboolean update;
+
+ gst_event_parse_new_segment_full (event, &update, &rate,
+ &applied_rate, &fmt, &start, &stop, &position);
+ gst_segment_set_newsegment_full (&glob_last_segment, update, rate,
+ applied_rate, fmt, start, stop, position);
+
+ if (glob_waiting_segment == FALSE)
+ /* Cache the segment as it will be our reference but don't look
+ * further */
+ goto done;
+
+ glob_last_segment_start_time = start;
+ if (glob_waiting_first_segment == TRUE) {
+ insanity_test_validate_checklist_item (test, "first-segment", TRUE,
+ NULL);
+
+ glob_waiting_first_segment = FALSE;
+ } else if (glob_in_progress >= TEST_FAST_FORWARD &&
+ glob_in_progress <= TEST_FAST_BACKWARD) {
+ GstClockTimeDiff diff;
+ gboolean valid_stop = TRUE;
+ GstClockTimeDiff wdiff, rdiff;
+
+ rdiff =
+ ABS (GST_CLOCK_DIFF (stop, start)) * ABS (rate * applied_rate);
+ wdiff = ABS (GST_CLOCK_DIFF (glob_seek_stop_ts,
+ glob_seek_segment_seektime));
+
+ diff = GST_CLOCK_DIFF (position, glob_seek_segment_seektime);
+ if (diff < 0)
+ diff = -diff;
+
+ /* Now compare with the expected segment */
+ if ((rate * applied_rate) == glob_seek_rate && diff <= SEEK_THRESHOLD
+ && valid_stop) {
+ glob_seek_got_segment = TRUE;
+ } else {
+ GstClockTime stopdiff = ABS (GST_CLOCK_DIFF (rdiff, wdiff));
+
+ gchar *validate_msg =
+ g_strdup_printf ("Wrong segment received, Rate %f expected "
+ "%f, start time diff %" GST_TIME_FORMAT " stop diff %"
+ GST_TIME_FORMAT, (rate * applied_rate), glob_seek_rate,
+ GST_TIME_ARGS (diff), GST_TIME_ARGS (stopdiff));
+
+ validate_current_test (test, FALSE, validate_msg);
+ next_test (test);
+ g_free (validate_msg);
+ }
+ }
+
+ glob_waiting_segment = FALSE;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+done:
+ DECODER_TEST_UNLOCK ();
+ return TRUE;
+}
+
+static gboolean
+pad_added_cb (GstElement * element, GstPad * new_pad, InsanityTest * test)
+{
+ GstElement *fakesink;
+ GstPadTemplate *mqsinktmpl;
+ GstPadLinkReturn linkret;
+
+ GstIterator *it = NULL;
+ GstCaps *caps = NULL;
+ gboolean ret = TRUE;
+
+ gulong probe_id;
+ GstPad *mqsinkpad = NULL, *mqsrcpad = NULL, *ssinkpad = NULL, *decodesinkpad =
+ NULL, *decodesrcpad = NULL, *tmppad;
+
+ DECODER_TEST_LOCK ();
+
+ /* First check if the pad caps are compatible with the decoder */
+ caps = gst_pad_get_caps (new_pad);
+ decodesinkpad = gst_element_get_compatible_pad (glob_decoder, new_pad, caps);
+
+ if (decodesinkpad == NULL)
+ goto error;
+
+ mqsinktmpl =
+ gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS
+ (glob_multiqueue), "sink%d");
+
+ if (mqsinktmpl == NULL)
+ goto error;
+
+ mqsinkpad = gst_element_request_pad (glob_multiqueue, mqsinktmpl, NULL, NULL);
+
+ it = gst_pad_iterate_internal_links (mqsinkpad);
+ if (!it || (gst_iterator_next (it, (gpointer) & mqsrcpad)) != GST_ITERATOR_OK
+ || mqsrcpad == NULL) {
+ ERROR (test, "Couldn't get srcpad from multiqueue for sinkpad %"
+ GST_PTR_FORMAT, mqsinkpad);
+
+ goto error;
+ }
+
+ /* Finnish creating and add to bin */
+ fakesink = gst_element_factory_make ("fakesink", NULL);
+ gst_bin_add (GST_BIN (glob_pipeline), fakesink);
+ gst_element_sync_state_with_parent (fakesink);
+ gst_element_sync_state_with_parent (glob_decoder);
+
+ linkret = gst_pad_link (new_pad, mqsinkpad);
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting linking %" GST_PTR_FORMAT " with %" GST_PTR_FORMAT,
+ new_pad, mqsinkpad);
+ goto error;
+ }
+
+ /* Link to the decoder */
+ linkret = gst_pad_link (mqsrcpad, decodesinkpad);
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting linking %" GST_PTR_FORMAT " with %" GST_PTR_FORMAT,
+ mqsrcpad, decodesinkpad);
+ goto error;
+ }
+
+ /* Now link to the faksink */
+ decodesrcpad = gst_element_get_static_pad (glob_decoder, "src");
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting decoder srcpad");
+ goto error;
+ }
+
+ ssinkpad = gst_element_get_static_pad (fakesink, "sink");
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting fakesink sinkpad");
+ goto error;
+ }
+
+ linkret = gst_pad_link (decodesrcpad, ssinkpad);
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting linking %" GST_PTR_FORMAT " with %" GST_PTR_FORMAT,
+ decodesrcpad, ssinkpad);
+ goto error;
+ }
+
+ /* And install a probe to the decoder src pad */
+ if (insanity_gst_test_add_data_probe (INSANITY_GST_TEST (test),
+ GST_BIN (glob_pipeline), GST_OBJECT_NAME (glob_decoder),
+ GST_ELEMENT_NAME (decodesrcpad), &tmppad, &probe_id,
+ &probe_cb, NULL, NULL) == TRUE) {
+
+ glob_prob_ctx = g_slice_new0 (ProbeContext);
+ glob_prob_ctx->probe_id = probe_id;
+ glob_prob_ctx->pad = tmppad;
+ glob_prob_ctx->decoder = glob_decoder;
+ glob_prob_ctx->fakesink = fakesink;
+ glob_prob_ctx->test = test;
+
+ insanity_test_validate_checklist_item (test, "install-probes", TRUE, NULL);
+ } else {
+ insanity_test_validate_checklist_item (test,
+ "install-probes", FALSE, "Failed to attach probe to fakesink");
+
+ /* No reason to keep the test alive if there is a probe we can't add */
+ insanity_test_done (test);
+ goto error;
+ }
+
+ if (glob_parser)
+ media_descriptor_parser_add_stream (glob_parser, new_pad);
+
+done:
+ DECODER_TEST_UNLOCK ();
+
+ if (it)
+ gst_iterator_free (it);
+
+ if (decodesinkpad)
+ gst_object_unref (decodesinkpad);
+
+ if (caps)
+ gst_caps_unref (caps);
+
+ if (mqsinkpad)
+ gst_object_unref (mqsinkpad);
+
+ if (ssinkpad)
+ gst_object_unref (ssinkpad);
+
+ return ret;
+
+error:
+ ret = FALSE;
+ goto done;
+}
+
+static void
+type_found_cb (GstElement * typefind, guint probability,
+ GstCaps * caps, InsanityTest * test)
+{
+ GList *demuxers = NULL, *capable_demuxers = NULL;
+ GstPad *typefsrcpad = NULL;
+
+ typefsrcpad = gst_element_get_static_pad (typefind, "src");
+
+ /* First try to directly link to the decoder */
+ if (pad_added_cb (typefind, typefsrcpad, test) == TRUE)
+ return;
+
+ /* if we can't find a demuxer that is concidered as good
+ * (ie with rank primary, we just don't run the test */
+ demuxers = gst_element_factory_list_get_elements
+ (GST_ELEMENT_FACTORY_TYPE_DEMUXER, GST_RANK_PRIMARY);
+
+ if (demuxers == NULL) {
+ ERROR (test, "Could not find a demuxer concidered as good enough");
+ insanity_test_done (test);
+ goto done;
+ }
+
+ capable_demuxers = gst_element_factory_list_filter (demuxers, caps,
+ GST_PAD_SINK, FALSE);
+
+ glob_demuxer = gst_element_factory_create (capable_demuxers->data, "demuxer");
+ if (glob_demuxer == NULL) {
+ insanity_test_done (test);
+ goto done;
+ }
+
+ gst_bin_add (GST_BIN (glob_pipeline), glob_demuxer);
+ gst_element_link (glob_typefinder, glob_demuxer);
+ gst_element_sync_state_with_parent (glob_demuxer);
+
+ g_signal_connect (glob_demuxer, "pad-added", G_CALLBACK (pad_added_cb), test);
+
+done:
+ gst_plugin_feature_list_free (demuxers);
+ gst_plugin_feature_list_free (capable_demuxers);
+}
+
+static gboolean
+bus_message_cb (InsanityGstPipelineTest * ptest, GstMessage * msg)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS:
+ {
+ /* If we are waiting for a segment, keep waiting for it */
+ if (glob_waiting_segment) {
+ LOG (test, "EOS but waiting for a new segment... keep waiting");
+ return FALSE;
+ }
+
+ LOG (test, "EOS... current %s next test",
+ test_get_name (glob_in_progress));
+ next_test (test);
+ return FALSE;
+ }
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Test Callbacks and vmethods*/
+static GstPipeline *
+create_pipeline (InsanityGstPipelineTest * ptest, gpointer unused_data)
+{
+ gboolean uri_set;
+ GstElementFactory *decofactory = NULL;
+
+ GError *err = NULL;
+ InsanityTest *test = INSANITY_TEST (ptest);
+ gchar *decodername = NULL, *uri = NULL, *location = NULL;
+ const gchar *klass;
+
+ DECODER_TEST_LOCK ();
+ glob_pipeline = GST_ELEMENT (gst_pipeline_new ("pipeline"));
+
+ /* Create the source */
+ insanity_test_get_boolean_argument (test, "push-mode",
+ (gboolean *) & glob_push_mode);
+
+ insanity_test_get_string_argument (test, "location", &location);
+ if (location == NULL || g_strcmp0 (location, "") == 0) {
+ ERROR (test, "Location name not set");
+ goto failed;
+ }
+
+ uri = gst_filename_to_uri (location, &err);
+ if (err != NULL) {
+ ERROR (test, "Error creating uri %s", err->message);
+
+ goto failed;
+ } else if (glob_push_mode == FALSE) {
+ glob_src = gst_element_factory_make ("filesrc", "src");
+ } else {
+ gchar *tmpuri;
+
+ glob_src = gst_element_factory_make ("pushfilesrc", "src");
+ tmpuri = g_strconcat ("push", uri, NULL);
+ g_free (uri);
+
+ uri = tmpuri;
+ }
+
+ uri_set = gst_uri_handler_set_uri (GST_URI_HANDLER (glob_src), uri);
+ if (uri_set == FALSE) {
+ goto failed;
+ }
+
+ if (!insanity_test_get_string_argument (test, "decoder-name", &decodername) ||
+ g_strcmp0 (decodername, "") == 0) {
+ ERROR (test, "Decoder name not set");
+ goto failed;
+ }
+
+ /* ... create the decoder, will not be used until we typefind and
+ * plug the demuxer */
+ glob_decoder = gst_element_factory_make (decodername, "decoder");
+ if (glob_decoder == NULL)
+ goto failed;
+
+ /* We check wether the element is a parser or not */
+ decofactory = gst_element_get_factory (glob_decoder);
+ klass = gst_element_factory_get_klass (decofactory);
+ glob_testing_parser = g_strrstr (klass, "Parser") ? TRUE : FALSE;
+
+ if (glob_testing_parser == FALSE && g_strrstr (klass, "Decoder") == NULL) {
+ gchar *val_test = g_strdup_printf ("%s not a decoder nor a parser as"
+ " neither of \"Decoder\" nor \"parser\" where present in the element"
+ " factory klass: %s", decodername, klass);
+
+ insanity_test_validate_checklist_item (test, "testing-decoder-or-parser",
+ FALSE, val_test);
+
+ g_free (val_test);
+ goto failed;
+ } else {
+ insanity_test_validate_checklist_item (test, "testing-decoder-or-parser",
+ TRUE, NULL);
+ }
+
+ /* ... create the typefinder */
+ glob_typefinder = gst_element_factory_make ("typefind", "typefind");
+ if (glob_typefinder == NULL)
+ goto failed;
+
+ g_signal_connect (glob_typefinder, "have-type", G_CALLBACK (type_found_cb),
+ test);
+
+ /* And the multiqueue */
+ glob_multiqueue = gst_element_factory_make ("multiqueue", "multiqueue");
+ g_object_set (glob_multiqueue, "sync-by-running-time", TRUE, NULL);
+
+ gst_bin_add_many (GST_BIN (glob_pipeline), glob_src, glob_typefinder,
+ glob_multiqueue, glob_decoder, NULL);
+
+ if (gst_element_link (glob_src, glob_typefinder) == FALSE)
+ goto failed;
+
+done:
+ DECODER_TEST_UNLOCK ();
+
+ g_free (decodername);
+ g_free (uri);
+ g_free (location);
+ if (err != NULL)
+ g_error_free (err);
+
+ return GST_PIPELINE (glob_pipeline);
+
+failed:
+ if (glob_pipeline != NULL)
+ gst_object_unref (glob_pipeline);
+ if (glob_demuxer != NULL)
+ gst_object_unref (glob_decoder);
+ if (glob_src != NULL)
+ gst_object_unref (glob_src);
+ if (glob_multiqueue != NULL)
+ gst_object_unref (glob_multiqueue);
+
+ glob_pipeline = glob_demuxer = glob_decoder = glob_multiqueue = glob_src =
+ NULL;
+
+ goto done;
+}
+
+static gboolean
+start_cb (InsanityTest * test)
+{
+ gboolean ret = TRUE;
+
+ GError *err = NULL;
+ gchar *xmllocation = NULL, *location = NULL;
+
+ DECODER_TEST_LOCK ();
+
+ insanity_test_get_string_argument (test, "location", &location);
+ if (location == NULL || g_strcmp0 (location, "") == 0) {
+ ERROR (test, "Location name not set");
+ ret = FALSE;
+ goto done;
+ }
+
+ gst_segment_init (&glob_last_segment, GST_FORMAT_UNDEFINED);
+ glob_parsing_buf = gst_buffer_new ();
+ xmllocation = g_strconcat (location, ".xml", NULL);
+ glob_parser = media_descriptor_parser_new (test, xmllocation, &err);
+ if (glob_parser == NULL) {
+ LOG (test, "Could not create media descriptor parser: %s not testing it",
+ err->message);
+ goto done;
+ }
+
+done:
+ insanity_test_get_uint64_argument (test, "playback-duration",
+ &glob_playback_duration);
+ g_free (location);
+ g_free (xmllocation);
+
+ DECODER_TEST_UNLOCK ();
+
+ return ret;
+}
+
+static void
+reached_initial_state_cb (InsanityTest * test)
+{
+ /* and install wedged timeout */
+ global_idle_timeout =
+ g_timeout_add (1000, (GSourceFunc) & check_wedged, test);
+}
+
+static void
+teardown_cb (InsanityTest * test)
+{
+ clean_test (test);
+
+ gst_buffer_unref (glob_parsing_buf);
+}
+
+static gboolean
+stop_cb (InsanityTest * test)
+{
+ if (!glob_wrong_seqnum) {
+ insanity_test_validate_checklist_item (test, "seqnum-management", TRUE,
+ NULL);
+ }
+
+ if (glob_bad_segment_clipping == FALSE) {
+ if (glob_testing_parser == TRUE)
+ LOG (test, "Testing a parser, Didn't check \"segment-clipping\"");
+ else
+ insanity_test_validate_checklist_item (test, "segment-clipping", TRUE,
+ NULL);
+ }
+
+ /* We clean everything as the pipeline is rebuilt at each
+ * iteration of start/stop*/
+ clean_test (test);
+
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ InsanityGstPipelineTest *ptest;
+ InsanityTest *test;
+ gboolean ret;
+
+ const gchar *location = NULL;
+ const gchar *decoder_name = NULL;
+
+ g_type_init ();
+
+ ptest = insanity_gst_pipeline_test_new ("stream-switch-test", "Tests stream "
+ "switching inside playbin2", NULL);
+ test = INSANITY_TEST (ptest);
+ insanity_gst_pipeline_test_set_create_pipeline_in_start (ptest, TRUE);
+
+ /* Arguments */
+ insanity_test_add_string_argument (test, "location",
+ "The location to test on", NULL, FALSE, location);
+ insanity_test_add_string_argument (test, "decoder-name",
+ "The decoder element name to test", NULL, FALSE, decoder_name);
+ insanity_test_add_boolean_argument (test, "push-mode",
+ "Whether the pipeline should run in push mode or not (pull mode)",
+ NULL, FALSE, FALSE);
+ insanity_test_add_uint64_argument (test, "playback-duration",
+ "Stream time to playback for before seeking, in seconds", NULL, TRUE, 2);
+
+ /* Checklist */
+ insanity_test_add_checklist_item (test, "testing-decoder-or-parser",
+ "Whether the element we are testing (referenced with \"decoder-name\""
+ " is a decoder or a parser and thus can be tested here", NULL);
+ insanity_test_add_checklist_item (test, "install-probes",
+ "Probes were installed on the sinks", NULL);
+ insanity_test_add_checklist_item (test, "seekable-detection",
+ "The demuxer detects if a stream is seekable or not", NULL);
+ insanity_test_add_checklist_item (test, "duration-detection",
+ "The demuxer detects duration of the stream properly", NULL);
+
+ insanity_test_add_checklist_item (test, "position-detection",
+ "The demuxer detects the position in the stream properly", NULL);
+
+ insanity_test_add_checklist_item (test, "segment-clipping",
+ "Buffers were correctly clipped to the configured segment", NULL);
+ insanity_test_add_checklist_item (test, "first-segment", "The demuxer sends a"
+ " first segment with proper values before " "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "seqnum-management", "The events"
+ "we receive have the seqnum it should have", NULL);
+ insanity_test_add_checklist_item (test, "fast-forward", "The demuxer could "
+ " properly play the stream fast-forward" "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "fast-backward", "The demuxer could "
+ " properly play the stream fast-backward" "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "backward-playback",
+ "The demuxer could " " properly play the stream backward" "first buffers",
+ NULL);
+
+ insanity_gst_pipeline_test_set_create_pipeline_function (ptest,
+ &create_pipeline, NULL, NULL);
+
+ g_signal_connect (test, "start", G_CALLBACK (&start_cb), NULL);
+ g_signal_connect_after (test, "stop", G_CALLBACK (&stop_cb), NULL);
+ g_signal_connect_after (test, "bus-message", G_CALLBACK (&bus_message_cb), 0);
+ g_signal_connect_after (test, "teardown", G_CALLBACK (&teardown_cb), NULL);
+ g_signal_connect_after (test, "reached-initial-state",
+ G_CALLBACK (&reached_initial_state_cb), 0);
+
+ ret = insanity_test_run (test, &argc, &argv);
+
+ g_object_unref (test);
+
+ return ret ? 0 : 1;
+}
diff --git a/tests/insanity-test-gst-demuxer.c b/tests/insanity-test-gst-demuxer.c
new file mode 100644
index 0000000..eae0ba3
--- /dev/null
+++ b/tests/insanity-test-gst-demuxer.c
@@ -0,0 +1,1590 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 <insanity-gst/insanity-gst.h>
+#include <gst/pbutils/pbutils.h>
+
+#include "media-descriptor-parser.h"
+#include "media-descriptor-writer.h"
+
+#define LOG(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "demuxer", INSANITY_LOG_LEVEL_DEBUG, "In progess %s: " format, \
+ test_get_name (glob_in_progress), ##args)
+#define ERROR(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "demuxer", INSANITY_LOG_LEVEL_SPAM, "In progess %s: " format, \
+ test_get_name (glob_in_progress), ##args)
+
+static GStaticMutex glob_mutex = G_STATIC_MUTEX_INIT;
+#define DEMUX_TEST_LOCK() g_static_mutex_lock (&glob_mutex)
+#define DEMUX_TEST_UNLOCK() g_static_mutex_unlock (&glob_mutex)
+
+/* How far we allow a timestamp to be to match our target
+ * 3 quarters of a second for now. Seeking precision isn't
+ * very good it seems. Needs to be at the very least one
+ * frame's worth for low framerate video. */
+#define SEEK_THRESHOLD (GST_SECOND * 3 / 4)
+
+/* timeout for gst_element_get_state() after a seek */
+#define SEEK_TIMEOUT (10 * GST_SECOND)
+#define FAST_FORWARD_PLAYING_THRESHOLD (G_USEC_PER_SEC / 3)
+
+/* How much time we allow without receiving any buffer or event
+ * before deciding the pipeline is wedged. Second precision. */
+#define IDLE_TIMEOUT (GST_SECOND * 20)
+
+typedef struct
+{
+ GstElement *demuxer;
+ GstElement *fakesink;
+ GstPad *pad;
+ gulong probe_id;
+ gboolean unlinked;
+
+ InsanityTest *test;
+
+ GstSegment last_segment;
+ gboolean waiting_segment;
+ /* First segment test */
+ gboolean waiting_first_segment;
+
+} ProbeContext;
+
+typedef enum
+{
+ TEST_DESCRIPTOR_GENERATION,
+
+ TEST_NONE,
+ TEST_QUERIES,
+ TEST_POSITION,
+
+ /* Start seeking */
+ TEST_FAST_FORWARD, /* Always first seeking test */
+
+ /* Seeks modes */
+ TEST_SEGMENT_SEEK,
+ /* Backward */
+ TEST_BACKWARD_PLAYBACK,
+ TEST_FAST_BACKWARD, /* Always last seeking test */
+
+ TEST_UNLINK_PAD, /* Should always be last */
+
+} TestInProgress;
+
+/* Global GstElement-s */
+static GstElement *glob_demuxer = NULL;
+static GstElement *glob_pipeline = NULL;
+static GstElement *glob_multiqueue = NULL;
+static GstElement *glob_src = NULL;
+
+/* Gloabl fields */
+static guint16 glob_nb_pads = 0;
+static ProbeContext *glob_prob_ctxs = NULL;
+static MediaDescriptorParser *glob_parser = NULL;
+static GstClockTime glob_playback_duration = GST_CLOCK_TIME_NONE;
+static gboolean glob_push_mode = FALSE;
+static GstBuffer *glob_parsing_buf = NULL;
+
+static TestInProgress glob_in_progress = TEST_NONE;
+
+/* Media descriptor writer context */
+static MediaDescriptorWriter *glob_writer = NULL;
+static gboolean glob_pipeline_restarted = TRUE;
+
+/* checking Checking wedge */
+static gint64 global_last_probe = 0;
+static guint global_idle_timeout = 0;
+
+/* Before starting to seek */
+static gboolean glob_detecting_frame = FALSE;
+
+/* Seeking, position and duration queries tests */
+static GstClockTime glob_seekable = FALSE;
+static GstClockTime glob_duration = GST_CLOCK_TIME_NONE;
+
+/* Check position test */
+static GstClockTime glob_first_pos_point = GST_CLOCK_TIME_NONE;
+static GstPad *glob_position_check_pad = NULL;
+static GstClockTime glob_expected_pos = GST_CLOCK_TIME_NONE;
+
+/* Trick modes test */
+static gdouble glob_seek_rate = 0;
+static GstClockTime glob_seek_segment_seektime = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_seek_stop_ts = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+
+/* Sequence number test */
+static guint glob_seqnum = 0;
+static gboolean glob_seqnum_found = FALSE;
+static gboolean glob_wrong_seqnum = FALSE;
+
+/* Unlink pad testing*/
+static gboolean glob_unlinked_pad = FALSE;
+static guint glob_unlinked_buf_timeout = 0;
+static gboolean glob_buf_on_linked_pad = FALSE;
+
+static void block_pad_cb (GstPad * pad, gboolean blocked, InsanityTest * test);
+static gboolean next_test (InsanityTest * test);
+
+static void
+clean_test (InsanityTest * test)
+{
+ gint i;
+
+ glob_demuxer = NULL;
+ glob_pipeline = NULL;
+ glob_multiqueue = NULL;
+ glob_src = NULL;
+
+ glob_playback_duration = GST_CLOCK_TIME_NONE;
+ glob_push_mode = FALSE;
+
+ glob_in_progress = TEST_NONE;
+
+ global_last_probe = 0;
+ global_idle_timeout = 0;
+ glob_detecting_frame = FALSE;
+ glob_duration = GST_CLOCK_TIME_NONE;
+ glob_seekable = FALSE;
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ insanity_gst_test_remove_data_probe (INSANITY_GST_TEST (test),
+ glob_prob_ctxs[i].pad, glob_prob_ctxs[i].probe_id);
+ }
+
+ g_free (glob_prob_ctxs);
+ glob_prob_ctxs = NULL;
+
+ glob_nb_pads = 0;
+
+ g_clear_object (&glob_parser);
+ g_clear_object (&glob_writer);
+ glob_pipeline_restarted = TRUE;
+
+ glob_expected_pos = GST_CLOCK_TIME_NONE;
+ glob_first_pos_point = GST_CLOCK_TIME_NONE;
+ glob_position_check_pad = NULL;
+
+ glob_seek_rate = 0;
+ glob_seek_segment_seektime = GST_CLOCK_TIME_NONE;
+ glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+
+ glob_seqnum = 0;
+ glob_seqnum_found = FALSE;
+ glob_wrong_seqnum = FALSE;
+
+ glob_unlinked_pad = FALSE;
+ glob_unlinked_buf_timeout = 0;
+ glob_buf_on_linked_pad = FALSE;
+}
+
+/* Utils functions */
+static inline const gchar *
+test_get_name (TestInProgress in_progress)
+{
+ switch (in_progress) {
+ case TEST_DESCRIPTOR_GENERATION:
+ return "Generation media descriptor xml file";
+ case TEST_NONE:
+ return "None";
+ case TEST_QUERIES:
+ return "Queries";
+ case TEST_POSITION:
+ return "Postion";
+ case TEST_FAST_FORWARD:
+ return "Fast forward";
+ case TEST_SEGMENT_SEEK:
+ return "Segment seek";
+ case TEST_BACKWARD_PLAYBACK:
+ return "Backward playback";
+ case TEST_FAST_BACKWARD:
+ return "Fast backward";
+ case TEST_UNLINK_PAD:
+ return "Unlink pad";
+ }
+
+ return NULL;
+}
+
+static inline ProbeContext *
+find_probe_context (GstPad * pad)
+{
+ guint i;
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ if (glob_prob_ctxs[i].pad == pad)
+ return &glob_prob_ctxs[i];
+ }
+
+ return NULL;
+}
+
+static void
+validate_current_test (InsanityTest * test, gboolean validate,
+ const gchar * msg)
+{
+ switch (glob_in_progress) {
+ case TEST_BACKWARD_PLAYBACK:
+ insanity_test_validate_checklist_item (test, "backward-playback",
+ validate, msg);
+ break;
+ case TEST_FAST_FORWARD:
+ insanity_test_validate_checklist_item (test, "fast-forward",
+ validate, msg);
+ break;
+ case TEST_SEGMENT_SEEK:
+ insanity_test_validate_checklist_item (test, "segment-seek",
+ validate, msg);
+ break;
+ case TEST_FAST_BACKWARD:
+ glob_seqnum = 0;
+ insanity_test_validate_checklist_item (test, "fast-backward",
+ validate, msg);
+ break;
+ case TEST_UNLINK_PAD:
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling",
+ validate, msg);
+ break;
+ default:
+ ERROR (test, "Could not validate mode %i", glob_in_progress);
+ return;
+ }
+}
+
+static inline const gchar *
+pipeline_mode_get_name (gboolean push_mode)
+{
+ if (push_mode == TRUE)
+ return "push";
+
+ return "pull";
+}
+
+static gboolean
+idle_restart_pipeline (InsanityTest * test)
+{
+ LOG (test, "Restarting pipeline");
+ gst_element_set_state (glob_pipeline, GST_STATE_PAUSED);
+ gst_element_get_state (glob_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_element_seek_simple (glob_pipeline, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH, 0);
+ gst_element_set_state (glob_pipeline, GST_STATE_PLAYING);
+
+ return FALSE;
+}
+
+static void
+generate_xml_media_descriptor (InsanityTest * test)
+{
+ guint i;
+ GError *err = NULL;
+ GstDiscovererInfo *info = NULL;
+ gchar *location = NULL, *suburi = NULL;
+
+ GstDiscoverer *discoverer = gst_discoverer_new (5 * GST_SECOND, NULL);
+
+ insanity_test_get_string_argument (test, "location", &location);
+
+ if (G_UNLIKELY (discoverer == NULL)) {
+ ERROR (test, "Error creating discoverer: %s\n", err->message);
+ g_clear_error (&err);
+
+ insanity_test_done (test);
+ goto done;
+ }
+
+ suburi = gst_filename_to_uri (location, &err);
+ if (err) {
+ ERROR (test, "Could not construct filename");
+ g_clear_error (&err);
+ goto done;
+ }
+
+ info = gst_discoverer_discover_uri (discoverer, suburi, &err);
+ if (info == NULL) {
+ ERROR (test, "Error discovering: %s\n", err->message);
+ g_clear_error (&err);
+
+ insanity_test_done (test);
+ goto done;
+ }
+
+ glob_duration = gst_discoverer_info_get_duration (info);
+ glob_seekable = gst_discoverer_info_get_seekable (info);
+
+ glob_writer = media_descriptor_writer_new (test,
+ location, glob_duration, glob_seekable);
+
+ glob_in_progress = TEST_DESCRIPTOR_GENERATION;
+ glob_pipeline_restarted = FALSE;
+
+ g_idle_add ((GSourceFunc) idle_restart_pipeline, test);
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ media_descriptor_writer_add_stream (glob_writer, glob_prob_ctxs[i].pad);
+ }
+
+done:
+ g_free (location);
+ g_free (suburi);
+
+ if (discoverer != NULL)
+ g_object_unref (discoverer);
+
+ if (info != NULL)
+ gst_discoverer_info_unref (info);
+}
+
+/* Tests functions */
+static void
+test_unlink_pad (InsanityTest * test)
+{
+ DEMUX_TEST_LOCK ();
+ if (glob_nb_pads < 1) {
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling", FALSE,
+ "No pad can't unlink");
+ next_test (test);
+ return;
+ }
+
+ gst_pad_set_blocked_async (glob_prob_ctxs[0].pad, TRUE,
+ (GstPadBlockCallback) block_pad_cb, test);
+ DEMUX_TEST_UNLOCK ();
+}
+
+static inline gboolean
+is_waiting_first_segment (void)
+{
+ guint i;
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ if (glob_prob_ctxs[i].waiting_first_segment == TRUE) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static inline gboolean
+is_waiting_segment (void)
+{
+ guint i;
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ if (glob_prob_ctxs[i].waiting_segment == TRUE) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static inline void
+set_waiting_segment (void)
+{
+ guint i;
+
+ for (i = 0; i < glob_nb_pads; i++)
+ glob_prob_ctxs[i].waiting_first_segment = TRUE;
+
+}
+
+static void
+test_position (InsanityTest * test, GstBuffer * buf, GstPad * pad)
+{
+ GstQuery *query;
+ GstClockTimeDiff diff;
+ ProbeContext *probectx;
+
+ /* Make sure to always use the same Segment to convert buffer timestamp to
+ * stream time*/
+ if (glob_position_check_pad == NULL)
+ glob_position_check_pad = pad;
+ else if (glob_position_check_pad != pad)
+ return;
+
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf) == FALSE)
+ return;
+
+ probectx = find_probe_context (pad);
+ if (GST_CLOCK_TIME_IS_VALID (glob_first_pos_point) == FALSE) {
+ glob_first_pos_point = gst_segment_to_stream_time (&probectx->last_segment,
+ probectx->last_segment.format, GST_BUFFER_TIMESTAMP (buf));
+ }
+
+ glob_expected_pos = gst_segment_to_stream_time (&probectx->last_segment,
+ probectx->last_segment.format, GST_BUFFER_TIMESTAMP (buf));
+
+ diff = ABS (GST_CLOCK_DIFF (glob_expected_pos, glob_first_pos_point));
+
+ /* We wait the pipeline to run at least glob_playback_duration secs
+ * before actually testing the position */
+ if (diff < glob_playback_duration * GST_SECOND)
+ return;
+
+ query = gst_query_new_position (GST_FORMAT_TIME);
+ if (gst_element_query (glob_demuxer, query)) {
+ gint64 pos;
+ GstFormat fmt;
+ gchar *validate_msg = NULL;
+ gboolean validate;
+
+ gst_query_parse_position (query, &fmt, &pos);
+
+ validate = (pos == glob_expected_pos);
+ if (validate == FALSE) {
+ validate_msg = g_strdup_printf ("Found position: %" GST_TIME_FORMAT
+ " expected: %" GST_TIME_FORMAT, GST_TIME_ARGS (pos),
+ GST_TIME_ARGS (glob_expected_pos));
+ }
+
+ insanity_test_validate_checklist_item (test, "position-detection",
+ validate, validate_msg);
+
+ g_free (validate_msg);
+ } else {
+ LOG (test,
+ "%s Does not handle position queries (position-detection \"SKIP\")\n",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+ gst_query_unref (query);
+
+ next_test (test);
+}
+
+static gboolean
+test_seek_modes (InsanityTest * test)
+{
+ gboolean res;
+ GstEvent *event;
+ GstSeekFlags flags = GST_SEEK_FLAG_FLUSH;
+ GstSeekType stop_type = GST_SEEK_TYPE_SET;
+
+ /* Reset global seek props */
+ glob_seek_first_buf_ts = GST_CLOCK_TIME_NONE;
+ glob_seek_stop_ts = glob_duration;
+ glob_seek_segment_seektime = 0;
+
+ /* Set seeking arguments */
+ switch (glob_in_progress) {
+ case TEST_BACKWARD_PLAYBACK:
+ glob_seek_rate = -1;
+ break;
+ case TEST_SEGMENT_SEEK:
+ glob_seek_rate = 1;
+ if (glob_duration < 10 * GST_SECOND)
+ glob_seek_stop_ts = glob_duration / 2;
+ else
+ glob_seek_stop_ts = 10 * GST_SECOND;
+
+ flags = GST_SEEK_FLAG_SEGMENT;
+ break;
+ case TEST_FAST_FORWARD:
+ glob_seek_rate = 2;
+ if (glob_duration < 10 * GST_SECOND)
+ glob_seek_stop_ts = glob_duration / 2;
+ else
+ glob_seek_stop_ts = 10 * GST_SECOND;
+ stop_type = GST_SEEK_TYPE_NONE;
+ break;
+ case TEST_FAST_BACKWARD:
+ glob_seek_rate = -2;
+ break;
+ default:
+ return FALSE;
+ }
+
+ event = gst_event_new_seek (glob_seek_rate, GST_FORMAT_TIME,
+ flags, GST_SEEK_TYPE_SET, glob_seek_segment_seektime,
+ stop_type, glob_seek_stop_ts);
+
+ /* We didn't find any event/message with the seqnum we previously set */
+ if (glob_seqnum != 0 && glob_seqnum_found == FALSE)
+ glob_wrong_seqnum = TRUE;
+
+ glob_seqnum_found = FALSE;
+ glob_seqnum = gst_util_seqnum_next ();
+ gst_event_set_seqnum (event, glob_seqnum);
+ res = gst_element_send_event (glob_pipeline, event);
+
+ if (!res) {
+ validate_current_test (test, FALSE, "Could not send seek event");
+ glob_seek_rate = 0;
+ glob_seqnum = 0;
+
+ /* ... Next test */
+ next_test (test);
+ }
+
+ return FALSE;
+}
+
+static void
+test_queries (InsanityTest * test)
+{
+ GstQuery *query = gst_query_new_seeking (GST_FORMAT_TIME);
+
+ if (gst_element_query (glob_demuxer, query)) {
+ GstFormat fmt;
+ gboolean seekable, known_seekable;
+
+ gst_query_parse_seeking (query, &fmt, &seekable, NULL, NULL);
+ if (glob_parser == NULL) {
+ gchar *validate_msg = NULL;
+
+ validate_msg = g_strdup_printf ("No media-descriptor file, result (%s)"
+ "not verified against it", seekable ? "Seekable" : "Not seekable");
+
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ TRUE, validate_msg);
+
+ glob_seekable = seekable;
+
+ g_free (validate_msg);
+ } else {
+ known_seekable = media_descriptor_parser_get_seekable (glob_parser);
+
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ known_seekable == seekable, NULL);
+ glob_seekable = known_seekable;
+ }
+
+ } else {
+ if (glob_parser != NULL)
+ glob_seekable = media_descriptor_parser_get_seekable (glob_parser);
+
+ LOG (test, "%s Does not handle seeking queries (seekable-detection "
+ "\"SKIP\")\n", gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+ gst_query_unref (query);
+
+ query = gst_query_new_duration (GST_FORMAT_TIME);
+ if (gst_element_query (glob_demuxer, query)) {
+ GstFormat fmt;
+ gchar *validate_msg = NULL;
+ gint64 duration;
+
+ if (glob_parser == NULL) {
+ gst_query_parse_duration (query, &fmt, &duration);
+ validate_msg = g_strdup_printf ("Found duration %" GST_TIME_FORMAT
+ " No media-descriptor file, result not verified against it",
+ GST_TIME_ARGS (duration));
+
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ TRUE, validate_msg);
+ glob_duration = duration;
+
+ g_free (validate_msg);
+ validate_msg = NULL;
+ } else {
+ gboolean validate;
+
+ glob_duration = media_descriptor_parser_get_duration (glob_parser);
+ gst_query_parse_duration (query, &fmt, &duration);
+
+ if ((validate = (glob_duration == duration)) == FALSE) {
+ validate_msg = g_strdup_printf ("Found time %" GST_TIME_FORMAT "-> %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (duration),
+ GST_TIME_ARGS (glob_duration));
+ }
+
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ validate, validate_msg);
+
+ g_free (validate_msg);
+ validate_msg = NULL;
+ }
+
+ } else {
+ if (glob_parser != NULL)
+ glob_duration = media_descriptor_parser_get_seekable (glob_parser);
+
+ LOG (test, "%s Does not handle duration queries (duration-detection "
+ "\"SKIP\")\n", gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)));
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_duration) &&
+ glob_playback_duration > glob_duration) {
+ LOG (test, "playback_duration > media duration, setting it"
+ "to media_duration != 2");
+
+ glob_playback_duration = glob_duration / 2;
+ }
+ gst_query_unref (query);
+
+ next_test (test);
+}
+
+static gboolean
+next_test (InsanityTest * test)
+{
+ gchar *location = NULL, *xmllocation = NULL;
+ GError *err = NULL;
+
+ switch (glob_in_progress) {
+ case TEST_DESCRIPTOR_GENERATION:
+ {
+ insanity_test_get_string_argument (test, "location", &location);
+ xmllocation = g_strconcat (location, ".xml", NULL);
+
+ if (media_descriptor_writer_write (glob_writer, xmllocation) == FALSE) {
+ ERROR (test, "Could not write media descriptor");
+ insanity_test_done (test);
+ goto done;
+ }
+
+ glob_parser = media_descriptor_parser_new (test, xmllocation, &err);
+ if (glob_parser == NULL) {
+ ERROR (test, "Could not create media descriptor after writing it");
+ insanity_test_done (test);
+ goto done;
+ }
+
+ /* We reset the test so it starts again from the beginning */
+ glob_detecting_frame = TRUE;
+ glob_in_progress = TEST_NONE;
+ g_idle_add ((GSourceFunc) idle_restart_pipeline, test);
+
+ break;
+ }
+ case TEST_NONE:
+ glob_in_progress = TEST_QUERIES;
+ test_queries (test);
+ break;
+ case TEST_QUERIES:
+ glob_in_progress = TEST_POSITION;
+ break;
+ case TEST_POSITION:
+ if (glob_seekable == FALSE) {
+ /* Do not enter seek mode tests and jump to the unlink pad test */
+ LOG (test, "%s not seekable in %s mode \n",
+ gst_element_factory_get_longname (gst_element_get_factory
+ (glob_demuxer)), pipeline_mode_get_name (glob_push_mode));
+
+ glob_in_progress = TEST_UNLINK_PAD;
+ test_unlink_pad (test);
+
+ return FALSE;
+ }
+ /* Stop detecting frame as we start seeking */
+ glob_detecting_frame = FALSE;
+ glob_in_progress = TEST_BACKWARD_PLAYBACK;
+ set_waiting_segment ();
+ g_timeout_add (100, (GSourceFunc) & test_seek_modes, test);
+ break;
+ case TEST_BACKWARD_PLAYBACK:
+ glob_in_progress = TEST_SEGMENT_SEEK;
+ set_waiting_segment ();
+ g_timeout_add (100, (GSourceFunc) & test_seek_modes, test);
+ break;
+ case TEST_SEGMENT_SEEK:
+ glob_in_progress = TEST_FAST_FORWARD;
+ set_waiting_segment ();
+ g_timeout_add (100, (GSourceFunc) & test_seek_modes, test);
+ break;
+ case TEST_FAST_FORWARD:
+ glob_in_progress = TEST_FAST_BACKWARD;
+ set_waiting_segment ();
+ g_timeout_add (100, (GSourceFunc) & test_seek_modes, test);
+ break;
+ case TEST_FAST_BACKWARD:
+ glob_in_progress = TEST_UNLINK_PAD;
+ test_unlink_pad (test);
+ break;
+ default:
+ insanity_test_done (test);
+ return FALSE;
+ }
+
+ LOG (test, "%s in progress\n", test_get_name (glob_in_progress));
+
+done:
+ g_free (location);
+ g_free (xmllocation);
+
+ return FALSE;
+}
+
+static gboolean
+check_wedged (gpointer data)
+{
+ InsanityTest *test = data;
+ gint64 idle;
+
+ idle = (global_last_probe <= 0) ?
+ 0 : 1000 * (g_get_monotonic_time () - global_last_probe);
+ if (idle >= IDLE_TIMEOUT) {
+ LOG (test, "Wedged, kicking\n");
+
+ /* Unvalidate tests in progress */
+ switch (glob_in_progress) {
+ case TEST_DESCRIPTOR_GENERATION:
+ /* pass */
+ case TEST_NONE:
+ break;
+ case TEST_QUERIES:
+ insanity_test_validate_checklist_item (test, "seekable-detection",
+ FALSE, "No buffers or events were seen for a while");
+ insanity_test_validate_checklist_item (test, "duration-detection",
+ FALSE, "No buffers or events were seen for a while");
+ break;
+ case TEST_POSITION:
+ insanity_test_validate_checklist_item (test, "position-detection",
+ FALSE, "No buffers or events were seen for a while");
+ break;
+ case TEST_FAST_FORWARD:
+ insanity_test_validate_checklist_item (test, "fast-forward", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_SEGMENT_SEEK:
+ insanity_test_validate_checklist_item (test, "segment-seek", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_BACKWARD_PLAYBACK:
+ insanity_test_validate_checklist_item (test, "backward-playback", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_FAST_BACKWARD:
+ insanity_test_validate_checklist_item (test, "fast-backward", FALSE,
+ "No buffers or events were seen for a while");
+ break;
+ case TEST_UNLINK_PAD:
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling",
+ FALSE, "No buffers or events were seen for a while");
+ break;
+ }
+
+ global_last_probe = g_get_monotonic_time ();
+ g_idle_add ((GSourceFunc) & next_test, test);
+ }
+
+ return TRUE;
+}
+
+/* Pipeline Callbacks */
+static gboolean
+buf_on_unlinked_pad_seen_cb (InsanityTest * test)
+{
+ if (glob_nb_pads > 1) {
+ if (glob_buf_on_linked_pad == TRUE) {
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling",
+ TRUE, NULL);
+ next_test (test);
+ } else {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+fakesink_handoff_cb (GstElement * fsink, GstBuffer * buf, GstPad * pad,
+ InsanityTest * test)
+{
+ glob_buf_on_linked_pad = TRUE;
+
+ g_object_set (fsink, "signal-handoffs", FALSE, NULL);
+ g_signal_handlers_disconnect_by_func (fsink, fakesink_handoff_cb, test);
+}
+
+static void
+block_pad_cb (GstPad * pad, gboolean blocked, InsanityTest * test)
+{
+ ProbeContext *probectx;
+
+ probectx = find_probe_context (pad);
+
+ if (blocked == TRUE) {
+ GstPad *mqsinkpad, *mqueuesrcpad, *fakesinkpad;
+ GstState state;
+
+ mqsinkpad = gst_pad_get_peer (pad);
+ gst_pad_unlink (pad, mqsinkpad);
+ gst_object_unref (mqsinkpad);
+
+ fakesinkpad = gst_element_get_static_pad (probectx->fakesink, "sink");
+ mqueuesrcpad = gst_pad_get_peer (fakesinkpad);
+ gst_pad_unlink (mqueuesrcpad, fakesinkpad);
+ gst_object_unref (mqueuesrcpad);
+ gst_object_unref (fakesinkpad);
+
+ /* Avoid a race where fakesink state changes to NULL and
+ * then resynced with the pipeline state before removing it from the
+ * pipeline*/
+ gst_object_ref (probectx->fakesink);
+ gst_bin_remove (GST_BIN (glob_pipeline), probectx->fakesink);
+
+ gst_element_set_state (probectx->fakesink, GST_STATE_NULL);
+ if (gst_element_get_state (probectx->fakesink, &state, NULL,
+ GST_CLOCK_TIME_NONE) == GST_STATE_CHANGE_SUCCESS) {
+ gst_object_unref (probectx->fakesink);
+ gst_pad_set_blocked_async (probectx->pad, FALSE,
+ (GstPadBlockCallback) block_pad_cb, test);
+
+ } else {
+ validate_current_test (test, FALSE, "Could not set sink to STATE_NULL");
+ next_test (test);
+ }
+ } else {
+ probectx->unlinked = TRUE;
+ glob_unlinked_pad = TRUE;
+
+ if (glob_nb_pads > 1) {
+ guint i;
+
+ for (i = 0; i < glob_nb_pads; i++) {
+ if (glob_prob_ctxs[i].unlinked == FALSE) {
+ g_object_set (glob_prob_ctxs[i].fakesink, "signal-handoffs", TRUE,
+ NULL);
+ g_signal_connect (glob_prob_ctxs[i].fakesink, "handoff",
+ G_CALLBACK (fakesink_handoff_cb), test);
+ break;
+ }
+ }
+
+ /*Seek if possible to avoid hitting EOS */
+ if (glob_seekable) {
+ GstEvent *event;
+
+ event = gst_event_new_seek (1, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE,
+ GST_CLOCK_TIME_NONE);
+ set_waiting_segment ();
+ glob_seqnum_found = FALSE;
+ glob_seqnum = gst_util_seqnum_next ();
+ gst_event_set_seqnum (event, glob_seqnum);
+ gst_element_send_event (glob_pipeline, event);
+
+ /* ret is not really important here */
+ }
+ }
+ /* Else waiting error on the bus */
+ }
+}
+
+static inline void
+media_descriptor_probe (InsanityTest * test, GstPad * pad,
+ GstMiniObject * object, gpointer userdata)
+{
+ if (glob_pipeline_restarted == TRUE) {
+ if (GST_IS_BUFFER (object))
+ media_descriptor_writer_add_frame (glob_writer, pad, GST_BUFFER (object));
+ }
+}
+
+static gboolean
+probe_cb (InsanityGstTest * ptest, GstPad * pad, GstMiniObject * object,
+ gpointer userdata)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+ ProbeContext *probectx = find_probe_context (pad);
+
+ global_last_probe = g_get_monotonic_time ();
+
+ if (glob_in_progress == TEST_DESCRIPTOR_GENERATION) {
+ media_descriptor_probe (test, pad, object, userdata);
+ goto done;
+ }
+
+ if (GST_IS_BUFFER (object)) {
+ GstBuffer *buf = GST_BUFFER (object);
+ GstClockTime ts = GST_BUFFER_TIMESTAMP (buf);
+
+ if (glob_detecting_frame == TRUE) {
+ if (media_descriptor_parser_add_frame (glob_parser, pad, buf,
+ glob_parsing_buf) == FALSE) {
+
+ gchar *message = g_strdup_printf ("expect -> real "
+ " offset %" G_GUINT64_FORMAT "-> %" G_GUINT64_FORMAT
+ " off_end %" G_GUINT64_FORMAT " -> %" G_GUINT64_FORMAT
+ " duration %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT
+ " timestamp %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT
+ " Is Keyframe %i -> %i", GST_BUFFER_OFFSET (glob_parsing_buf),
+ GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET_END (glob_parsing_buf),
+ GST_BUFFER_OFFSET_END (buf),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (glob_parsing_buf)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (glob_parsing_buf)),
+ GST_TIME_ARGS (ts), GST_BUFFER_FLAG_IS_SET (glob_parsing_buf,
+ GST_BUFFER_FLAG_DELTA_UNIT) == FALSE,
+ GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) == FALSE);
+
+ insanity_test_validate_checklist_item (test, "frames-detection", FALSE,
+ message);
+
+ g_free (message);
+ } else {
+ insanity_test_validate_checklist_item (test, "frames-detection", TRUE,
+ NULL);
+ }
+ }
+
+ switch (glob_in_progress) {
+ case TEST_NONE:
+ {
+ if (probectx->waiting_first_segment == TRUE)
+ insanity_test_validate_checklist_item (test, "first-segment", FALSE,
+ "Got a buffer before the first segment");
+
+ if (is_waiting_first_segment () == FALSE) {
+ insanity_test_validate_checklist_item (test, "first-segment", TRUE,
+ NULL);
+ next_test (test);
+ }
+
+ break;
+ }
+ case TEST_POSITION:
+ test_position (test, buf, pad);
+ break;
+ case TEST_FAST_FORWARD:
+ case TEST_BACKWARD_PLAYBACK:
+ case TEST_FAST_BACKWARD:
+ {
+ gint64 stime_ts;
+
+ if (GST_CLOCK_TIME_IS_VALID (ts) == FALSE ||
+ is_waiting_segment () == TRUE) {
+ break;
+ }
+
+ stime_ts = gst_segment_to_stream_time (&probectx->last_segment,
+ probectx->last_segment.format, ts);
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_seek_first_buf_ts) == FALSE) {
+ GstClockTime expected_ts =
+ gst_segment_to_stream_time (&probectx->last_segment,
+ probectx->last_segment.format,
+ glob_seek_rate <
+ 0 ? glob_seek_stop_ts : glob_seek_segment_seektime);
+
+ if (stime_ts > expected_ts) {
+ gchar *valmsg =
+ g_strdup_printf ("Received buffer timestamp %" GST_TIME_FORMAT
+ " Seeek wanted %" GST_TIME_FORMAT " Rate: %lf \n",
+ GST_TIME_ARGS (stime_ts),
+ GST_TIME_ARGS (expected_ts), glob_seek_rate);
+
+ validate_current_test (test, FALSE, valmsg);
+ next_test (test);
+
+ g_free (valmsg);
+ } else
+ glob_seek_first_buf_ts = stime_ts;
+
+ } else {
+ GstClockTimeDiff diff =
+ GST_CLOCK_DIFF (stime_ts, glob_seek_first_buf_ts);
+
+ if (diff < 0)
+ diff = -diff;
+
+ if (diff >= glob_playback_duration * GST_SECOND) {
+ validate_current_test (test, TRUE, NULL);
+ next_test (test);
+ }
+ }
+ break;
+ }
+ case TEST_UNLINK_PAD:
+ {
+ if (probectx->unlinked == TRUE && glob_unlinked_buf_timeout == 0)
+ glob_unlinked_buf_timeout = g_timeout_add (100,
+ (GSourceFunc) & buf_on_unlinked_pad_seen_cb, test);
+ break;
+ }
+ default:
+ break;
+ }
+
+ } else if (GST_IS_EVENT (object)) {
+ GstEvent *event = GST_EVENT (object);
+ guint seqnum = gst_event_get_seqnum (event);
+
+ if (G_LIKELY (glob_seqnum_found == FALSE) && seqnum == glob_seqnum)
+ glob_seqnum_found = TRUE;
+
+ if (glob_seqnum_found == TRUE && seqnum != glob_seqnum) {
+ gchar *message = g_strdup_printf ("Current seqnum %i != "
+ "received %i", glob_seqnum, seqnum);
+
+ insanity_test_validate_checklist_item (test, "seqnum-management",
+ FALSE, message);
+
+ glob_wrong_seqnum = TRUE;
+ g_free (message);
+ }
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_TAG:
+ {
+ GstTagList *taglist;
+
+ if (glob_parser != NULL) {
+
+ gst_event_parse_tag (event, &taglist);
+
+ insanity_test_validate_checklist_item (test,
+ "tag-detection", media_descriptor_parser_add_taglist (glob_parser,
+ taglist), NULL);
+ }
+ break;
+ }
+ case GST_EVENT_NEWSEGMENT:
+ {
+ GstFormat fmt;
+ gint64 start, stop, position;
+ gdouble rate, applied_rate;
+ gboolean update;
+
+ if (glob_seqnum == 0 && glob_seqnum_found == FALSE) {
+ /* This should only happen for the first segment */
+ glob_seqnum = gst_event_get_seqnum (event);
+ glob_seqnum_found = TRUE;
+ }
+
+ gst_event_parse_new_segment_full (event, &update, &rate,
+ &applied_rate, &fmt, &start, &stop, &position);
+ gst_segment_set_newsegment_full (&probectx->last_segment, update, rate,
+ applied_rate, fmt, start, stop, position);
+
+ if (probectx->waiting_segment == FALSE)
+ /* Cache the segment as it will be our reference but don't look
+ * further */
+ goto done;
+
+ if (probectx->waiting_first_segment == TRUE) {
+ /* Make sure that a new segment has been received for each stream */
+ probectx->waiting_first_segment = FALSE;
+ probectx->waiting_segment = FALSE;
+ } else if (glob_in_progress >= TEST_FAST_FORWARD &&
+ glob_in_progress <= TEST_FAST_BACKWARD) {
+ GstClockTimeDiff diff;
+ GstClockTimeDiff wdiff, rdiff;
+
+ rdiff =
+ ABS (GST_CLOCK_DIFF (stop, start)) * ABS (rate * applied_rate);
+ wdiff =
+ ABS (GST_CLOCK_DIFF (glob_seek_stop_ts,
+ glob_seek_segment_seektime));
+
+ diff = GST_CLOCK_DIFF (position, glob_seek_segment_seektime);
+
+ /* Now compare with the expected segment */
+ if (((rate * applied_rate) == glob_seek_rate
+ && position == glob_seek_segment_seektime) == FALSE) {
+ GstClockTime stopdiff = ABS (GST_CLOCK_DIFF (rdiff, wdiff));
+
+ gchar *validate_msg =
+ g_strdup_printf ("Wrong segment received, Rate %lf expected "
+ "%f, start time diff %" GST_TIME_FORMAT " stop diff %"
+ GST_TIME_FORMAT, (rate * applied_rate), glob_seek_rate,
+ GST_TIME_ARGS (diff), GST_TIME_ARGS (stopdiff));
+
+ validate_current_test (test, FALSE, validate_msg);
+ next_test (test);
+ g_free (validate_msg);
+ }
+ }
+
+ probectx->waiting_segment = FALSE;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+done:
+ return TRUE;
+}
+
+static void
+pad_added_cb (GstElement * demuxer, GstPad * new_pad,
+ InsanityGstPipelineTest * ptest)
+{
+ GstElement *fakesink;
+ GstIterator *it = NULL;
+ GstPadTemplate *mqsinktmpl;
+ GstPadLinkReturn linkret;
+
+ gulong probe_id;
+ GstPad *mqsinkpad = NULL, *mqsrcpad = NULL, *ssinkpad = NULL, *tmppad;
+
+ DEMUX_TEST_LOCK ();
+ mqsinktmpl =
+ gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS
+ (glob_multiqueue), "sink%d");
+
+ if (mqsinktmpl == NULL)
+ goto done;
+
+ mqsinkpad = gst_element_request_pad (glob_multiqueue, mqsinktmpl, NULL, NULL);
+
+ it = gst_pad_iterate_internal_links (mqsinkpad);
+ if (!it || (gst_iterator_next (it, (gpointer) & mqsrcpad)) != GST_ITERATOR_OK
+ || mqsrcpad == NULL) {
+ ERROR (INSANITY_TEST (ptest), "Couldn't get srcpad from multiqueue for "
+ "sinkpad %" GST_PTR_FORMAT, mqsinkpad);
+
+ goto done;
+ }
+
+ fakesink = gst_element_factory_make ("fakesink", NULL);
+ gst_bin_add (GST_BIN (glob_pipeline), fakesink);
+ gst_element_sync_state_with_parent (fakesink);
+
+ ssinkpad = gst_element_get_static_pad (fakesink, "sink");
+ if (ssinkpad == NULL)
+ goto done;
+
+ linkret = gst_pad_link (mqsrcpad, ssinkpad);
+ if (linkret != GST_PAD_LINK_OK)
+ goto done;
+
+ linkret = gst_pad_link (new_pad, mqsinkpad);
+ if (linkret != GST_PAD_LINK_OK)
+ goto done;
+
+ if (insanity_gst_test_add_data_probe (INSANITY_GST_TEST (ptest),
+ GST_BIN (glob_pipeline), GST_OBJECT_NAME (demuxer),
+ GST_ELEMENT_NAME (new_pad), &tmppad, &probe_id,
+ &probe_cb, NULL, NULL) == TRUE) {
+
+ glob_prob_ctxs = g_renew (ProbeContext, glob_prob_ctxs, glob_nb_pads + 1);
+ glob_prob_ctxs[glob_nb_pads].probe_id = probe_id;
+ glob_prob_ctxs[glob_nb_pads].pad = tmppad;
+ glob_prob_ctxs[glob_nb_pads].demuxer = demuxer;
+ glob_prob_ctxs[glob_nb_pads].fakesink = fakesink;
+ glob_prob_ctxs[glob_nb_pads].test = INSANITY_TEST (ptest);
+ glob_prob_ctxs[glob_nb_pads].unlinked = FALSE;
+ glob_prob_ctxs[glob_nb_pads].waiting_first_segment = TRUE;
+ glob_prob_ctxs[glob_nb_pads].waiting_segment = TRUE;
+ gst_segment_init (&glob_prob_ctxs[glob_nb_pads].last_segment,
+ GST_FORMAT_UNDEFINED);
+
+ glob_nb_pads++;
+
+ insanity_test_validate_checklist_item (INSANITY_TEST (ptest),
+ "install-probes", TRUE, NULL);
+ } else {
+ insanity_test_validate_checklist_item (INSANITY_TEST (ptest),
+ "install-probes", FALSE, "Failed to attach probe to fakesink");
+
+ /* No reason to keep the test alive if there is a probe we can't add */
+ insanity_test_done (INSANITY_TEST (ptest));
+ goto done;
+ }
+
+ if (glob_parser)
+ media_descriptor_parser_add_stream (glob_parser, new_pad);
+ if (glob_writer)
+ media_descriptor_writer_add_stream (glob_writer, new_pad);
+
+done:
+ DEMUX_TEST_UNLOCK ();
+ if (it)
+ gst_iterator_free (it);
+
+ if (mqsinkpad)
+ gst_object_unref (mqsinkpad);
+
+ if (ssinkpad)
+ gst_object_unref (ssinkpad);
+}
+
+static gboolean
+bus_message_cb (InsanityGstPipelineTest * ptest, GstMessage * msg)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_ERROR:
+ {
+ if (glob_in_progress == TEST_UNLINK_PAD) {
+ if (glob_nb_pads == 1) {
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling",
+ TRUE, "Only one pad emited error as expected");
+
+ insanity_test_done (test);
+ return FALSE;
+ } else {
+ insanity_test_validate_checklist_item (test, "unlink-pad-handling",
+ FALSE, "Only one pad unlinked and crashed, shouldn't happen");
+ }
+ }
+ break;
+ }
+ case GST_MESSAGE_SEGMENT_DONE:
+ if (glob_in_progress == TEST_SEGMENT_SEEK) {
+ validate_current_test (test, TRUE, NULL);
+ next_test (test);
+ }
+
+ break;
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ if (glob_in_progress == TEST_DESCRIPTOR_GENERATION) {
+ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (glob_pipeline)) {
+ GstState oldstate, newstate, pending;
+ gst_message_parse_state_changed (msg, &oldstate, &newstate, &pending);
+
+ if (newstate == GST_STATE_PLAYING
+ && pending == GST_STATE_VOID_PENDING)
+ glob_pipeline_restarted = TRUE;
+ }
+ }
+ }
+ break;
+ case GST_MESSAGE_EOS:
+ {
+ LOG (test, "Got EOS\n");
+
+ if (glob_in_progress == TEST_SEGMENT_SEEK
+ && is_waiting_segment () == FALSE) {
+ insanity_test_validate_checklist_item (test, "segment-seek", FALSE,
+ "Received an EOS after a segment-seek, shouldn't happen");
+ next_test (test);
+ } else if (glob_in_progress == TEST_DESCRIPTOR_GENERATION &&
+ glob_pipeline_restarted == TRUE)
+ next_test (test);
+
+ /* Keep testing anyway */
+ return FALSE;
+ }
+ case GST_MESSAGE_TAG:
+ if (glob_in_progress == TEST_DESCRIPTOR_GENERATION) {
+ GstTagList *taglist = NULL;
+
+ gst_message_parse_tag (msg, &taglist);
+ media_descriptor_writer_add_taglist (glob_writer, taglist);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Test Callbacks and vmethods*/
+static GstPipeline *
+demux_test_create_pipeline (InsanityGstPipelineTest * ptest,
+ gpointer unused_data)
+{
+
+ GError *err = NULL;
+ const gchar *klass;
+ GstElementFactory *decofactory = NULL;
+ gchar *demuxname = NULL, *uri = NULL, *location = NULL;
+
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ DEMUX_TEST_LOCK ();
+ glob_pipeline = GST_ELEMENT (gst_pipeline_new ("pipeline"));
+
+ /* Create the source */
+ insanity_test_get_boolean_argument (test, "push-mode", &glob_push_mode);
+
+ insanity_test_get_string_argument (test, "location", &location);
+ if (location == NULL || g_strcmp0 (location, "") == 0) {
+ ERROR (test, "Location name not set");
+ goto failed;
+ }
+
+ uri = gst_filename_to_uri (location, &err);
+ if (err != NULL) {
+ ERROR (test, "Error creating uri %s", err->message);
+
+ goto failed;
+ } else if (glob_push_mode == FALSE) {
+
+ glob_src = gst_element_factory_make ("filesrc", "src");
+ } else {
+ gchar *tmpuri;
+
+ glob_src = gst_element_factory_make ("pushfilesrc", "src");
+ tmpuri = g_strconcat ("push", uri, NULL);
+ g_free (uri);
+
+ uri = tmpuri;
+ }
+
+ if (gst_uri_handler_set_uri (GST_URI_HANDLER (glob_src), uri) == FALSE)
+ goto failed;
+
+ /* ... create the demuxer */
+ if (!insanity_test_get_string_argument (test, "demuxer", &demuxname) ||
+ g_strcmp0 (demuxname, "") == 0) {
+ ERROR (test, "Demuxer name not set");
+ goto failed;
+ }
+ glob_demuxer = gst_element_factory_make (demuxname, "demuxer");
+ if (glob_demuxer == NULL)
+ goto failed;
+
+ g_signal_connect (glob_demuxer, "pad-added", G_CALLBACK (pad_added_cb),
+ ptest);
+
+ /* And the multiqueue */
+ glob_multiqueue = gst_element_factory_make ("multiqueue", "multiqueue");
+ g_object_set (glob_multiqueue, "sync-by-running-time", TRUE, NULL);
+
+ /* Add everything to the bin */
+ gst_bin_add_many (GST_BIN (glob_pipeline), glob_src, glob_demuxer,
+ glob_multiqueue, NULL);
+
+ /* ... and link */
+ if (gst_element_link (glob_src, glob_demuxer) == FALSE)
+ goto failed;
+
+ /* We check wether the element is a parser or not */
+ decofactory = gst_element_get_factory (glob_demuxer);
+ klass = gst_element_factory_get_klass (decofactory);
+
+ if (g_strrstr (klass, "Demuxer") == NULL) {
+ gchar *val_test = g_strdup_printf ("%s not a demuxer as "
+ "Demuxer not present in the element factory klass: %s", demuxname,
+ klass);
+
+ insanity_test_validate_checklist_item (test, "testing-demuxer",
+ FALSE, val_test);
+
+ g_free (val_test);
+ goto failed;
+ }
+
+ insanity_test_validate_checklist_item (test, "testing-demuxer", TRUE, NULL);
+
+done:
+ DEMUX_TEST_UNLOCK ();
+
+ g_free (demuxname);
+ g_free (uri);
+ g_free (location);
+ if (err != NULL)
+ g_error_free (err);
+
+ return GST_PIPELINE (glob_pipeline);
+
+failed:
+ if (glob_pipeline != NULL)
+ gst_object_unref (glob_pipeline);
+ if (glob_demuxer != NULL)
+ gst_object_unref (glob_demuxer);
+ if (glob_src != NULL)
+ gst_object_unref (glob_src);
+ if (glob_multiqueue != NULL)
+ gst_object_unref (glob_multiqueue);
+
+ glob_pipeline = glob_demuxer = glob_multiqueue = glob_src = NULL;
+
+ goto done;
+}
+
+static gboolean
+start_cb (InsanityTest * test)
+{
+ gboolean ret = TRUE;
+
+ GError *err = NULL;
+ gchar *xmllocation = NULL, *location = NULL;
+
+ DEMUX_TEST_LOCK ();
+
+ insanity_test_get_string_argument (test, "location", &location);
+ if (location == NULL || g_strcmp0 (location, "") == 0) {
+ ERROR (test, "Location name not set\n");
+ ret = FALSE;
+ goto done;
+ }
+
+ glob_parsing_buf = gst_buffer_new ();
+ xmllocation = g_strconcat (location, ".xml", NULL);
+ glob_parser = media_descriptor_parser_new (test, xmllocation, &err);
+ if (glob_parser == NULL) {
+ gboolean generate_media_desc;
+
+ LOG (test, "Could not create media descriptor parser: %s", err->message);
+
+ insanity_test_get_boolean_argument (test, "generate-media-descriptor",
+ (gboolean *) & generate_media_desc);
+
+ if (generate_media_desc == FALSE) {
+ ret = FALSE;
+ goto done;
+ }
+
+ LOG (test, "%s not found generating it", xmllocation);
+
+ generate_xml_media_descriptor (test);
+ goto done;
+ }
+
+ glob_detecting_frame = media_descriptor_parser_detects_frames (glob_parser);
+
+done:
+ g_free (location);
+ g_free (xmllocation);
+
+ insanity_test_get_uint64_argument (test, "playback-duration",
+ &glob_playback_duration);
+
+ DEMUX_TEST_UNLOCK ();
+
+ return ret;
+}
+
+static void
+reached_initial_state_cb (InsanityTest * test)
+{
+ /* and install wedged timeout */
+ global_idle_timeout =
+ g_timeout_add (1000, (GSourceFunc) & check_wedged, test);
+}
+
+static void
+teardown_cb (InsanityTest * test)
+{
+ clean_test (test);
+
+ /* Clear variables that are shared between all start/stop iterations */
+ gst_buffer_unref (glob_parsing_buf);
+ glob_parsing_buf = NULL;
+}
+
+static gboolean
+stop_cb (InsanityTest * test)
+{
+ if (glob_parser == NULL) {
+
+ LOG (test, "No xml file found \"stream-detection\", \"frame-detection\" "
+ "and \"tag-detection\" skiped\n");
+ } else if (glob_writer != NULL) {
+ LOG (test, "Xml file generated \"stream-detection\", \"frame-detection\" "
+ "and \"tag-detection\" do not mean much\n");
+ } else {
+ insanity_test_validate_checklist_item (test, "stream-detection",
+ media_descriptor_parser_all_stream_found (glob_parser), NULL);
+
+ insanity_test_validate_checklist_item (test, "tag-detection",
+ media_descriptor_parser_all_tags_found (glob_parser), NULL);
+ }
+
+ if (!glob_wrong_seqnum) {
+ insanity_test_validate_checklist_item (test, "seqnum-management", TRUE,
+ NULL);
+ }
+
+ /* We clean everything as the pipeline is rebuilt at each
+ * iteration of start/stop*/
+ clean_test (test);
+
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ InsanityGstPipelineTest *ptest;
+ InsanityTest *test;
+ gboolean ret;
+
+ const gchar *location = NULL;
+ const gchar *demuxer_name = NULL;
+
+ g_type_init ();
+
+ ptest = insanity_gst_pipeline_test_new ("stream-switch-test", "Tests stream "
+ "switching inside playbin2", NULL);
+ test = INSANITY_TEST (ptest);
+ insanity_gst_pipeline_test_set_create_pipeline_in_start (ptest, TRUE);
+
+ /* Arguments */
+ insanity_test_add_string_argument (test, "location",
+ "The location to test on", NULL, FALSE, location);
+ insanity_test_add_string_argument (test, "demuxer",
+ "The demuxer element name " "to test", NULL, FALSE, demuxer_name);
+ insanity_test_add_boolean_argument (test, "push-mode",
+ "Whether the pipeline should run in push mode or not (pull mode)",
+ NULL, FALSE, FALSE);
+ insanity_test_add_boolean_argument (test, "generate-media-descriptor",
+ "Whether you want to generate the media descriptor XML file if needed",
+ NULL, TRUE, TRUE);
+ insanity_test_add_uint64_argument (test, "playback-duration",
+ "Stream time to playback for before seeking, in seconds", NULL, TRUE, 2);
+
+ /* Checklist */
+ insanity_test_add_checklist_item (test, "testing-demuxer",
+ "Whether the element we are testing (referenced with \"decoder-name\""
+ "is indeed a demuxer", NULL);
+ insanity_test_add_checklist_item (test, "stream-detection", "The demuxer "
+ "detects the various stream and sets the caps properly", NULL);
+ insanity_test_add_checklist_item (test, "frames-detection", "The demuxer "
+ "detects the frames and its metadatas properly", NULL);
+ insanity_test_add_checklist_item (test, "install-probes",
+ "Probes were installed on the sinks", NULL);
+ insanity_test_add_checklist_item (test, "seekable-detection",
+ "The demuxer detects if a stream is seekable or not", NULL);
+ insanity_test_add_checklist_item (test, "duration-detection",
+ "The demuxer detects duration of the stream properly", NULL);
+ insanity_test_add_checklist_item (test, "position-detection",
+ "The demuxer detects the position in the stream properly", NULL);
+ insanity_test_add_checklist_item (test, "tag-detection",
+ "The demuxer detects the tags in the stream properly", NULL);
+ insanity_test_add_checklist_item (test, "first-segment", "The demuxer sends a"
+ " first segment with proper values before " "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "seqnum-management", "The events"
+ "we receive have the seqnum it should have", NULL);
+ insanity_test_add_checklist_item (test, "fast-forward", "The demuxer could "
+ " properly play the stream fast-forward" "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "fast-backward", "The demuxer could "
+ " properly play the stream fast-backward" "first buffers", NULL);
+ insanity_test_add_checklist_item (test, "segment-seek", "The demuxer could "
+ " properly segment seeking", NULL);
+ insanity_test_add_checklist_item (test, "backward-playback",
+ "The demuxer could " " properly play the stream backward" "first buffers",
+ NULL);
+ insanity_test_add_checklist_item (test, "unlink-pad-handling", "The demuxer"
+ "properly handles pad is unlinking (errors out if only 1 source pad, keep"
+ "pushing buffer on other pad otherwize)" "first buffers", NULL);
+
+ insanity_gst_pipeline_test_set_create_pipeline_function (ptest,
+ &demux_test_create_pipeline, NULL, NULL);
+
+ g_signal_connect (test, "start", G_CALLBACK (&start_cb), NULL);
+ g_signal_connect_after (test, "stop", G_CALLBACK (&stop_cb), NULL);
+ g_signal_connect_after (test, "bus-message", G_CALLBACK (&bus_message_cb), 0);
+ g_signal_connect_after (test, "teardown", G_CALLBACK (&teardown_cb), NULL);
+ g_signal_connect_after (test, "reached-initial-state",
+ G_CALLBACK (&reached_initial_state_cb), 0);
+
+ ret = insanity_test_run (test, &argc, &argv);
+
+ g_object_unref (test);
+
+ return ret ? 0 : 1;
+}
diff --git a/tests/insanity-test-gst-subtitles.c b/tests/insanity-test-gst-subtitles.c
new file mode 100644
index 0000000..6aeb3a8
--- /dev/null
+++ b/tests/insanity-test-gst-subtitles.c
@@ -0,0 +1,1113 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 <insanity-gst/insanity-gst.h>
+#include <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+
+#include "media-descriptor-parser.h"
+#include "media-descriptor-writer.h"
+
+#define LOG(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "subtitle", INSANITY_LOG_LEVEL_DEBUG, format "\n", ##args)
+#define ERROR(test, format, args...) \
+ INSANITY_LOG (INSANITY_TEST((test)), "subtitle", INSANITY_LOG_LEVEL_DEBUG, format "\n", ##args)
+
+static GStaticMutex glob_mutex = G_STATIC_MUTEX_INIT;
+#define SUBTITLES_TEST_LOCK() g_static_mutex_lock (&glob_mutex)
+#define SUBTITLES_TEST_UNLOCK() g_static_mutex_unlock (&glob_mutex)
+
+/* How far we allow a timestamp to be to match our target
+ * 3 quarters of a second for now. Seeking precision isn't
+ * very good it seems. Needs to be at the very least one
+ * frame's worth for low framerate video. */
+#define SEEK_THRESHOLD (GST_SECOND * 3 / 4)
+
+/* timeout for gst_element_get_state() after a seek */
+#define SEEK_TIMEOUT (10 * GST_SECOND)
+#define FAST_FORWARD_PLAYING_THRESHOLD (G_USEC_PER_SEC / 3)
+
+/* How much time we allow without receiving any buffer or event
+ * before deciding the pipeline is wedged. Second precision. */
+#define IDLE_TIMEOUT (GST_SECOND * 20)
+
+typedef struct
+{
+ GstElement *element;
+ GstPad *pad;
+ gulong probe_id;
+
+ InsanityTest *test;
+
+ GstSegment last_segment;
+ gboolean waiting_segment;
+
+ /* First segment test */
+ gboolean waiting_first_segment;
+
+} ProbeContext;
+
+typedef enum
+{
+ TEST_SUBTTILE_DESCRIPTOR_GENERATION,
+
+ TEST_NONE,
+ TEST_SUBTITLE_DETECTION
+} TestInProgress;
+
+/*******************************************************************************
+ * +--------+ +-------+ +-+ +----------+
+ * +>|typefind|+->|demuxer|+>|m|+->| |
+ * +------------+| +--------+ +-------+ |u| | subtitle |
+ * |videotestsrc|+ (if needed) |l| | | +--------+
+ * +------------+ |t| | |+->|fakesink|
+ * |i| | overlay | +--------+
+ * +---------+ +---------+ +-------+ |q| | |
+ * |filesrc +----|capsfiler|--|convert|-> |u|+->| |
+ * | | |1080*1920| +-------+ |e| | |
+ * +---------+ +---------+ |u| +----------+
+ * +-+
+ ******************************************************************************/
+
+/* Global GstElement-s */
+static GstElement *glob_pipeline = NULL;
+static GstElement *glob_uridecodebin = NULL; /* videotestsrc */
+static GstElement *glob_suboverlay = NULL; /* A subtitleoverlay bin */
+static GstElement *glob_videotestsrc = NULL;
+
+/* Gloabl fields */
+static MediaDescriptorParser *glob_parser = NULL;
+static GList *glob_subtitled_frames = NULL;
+
+/* Media descriptor writer context */
+static MediaDescriptorWriter *glob_writer = NULL;
+static GstClockTime glob_duration = 0;
+static GstClockTime glob_seekable = FALSE;
+static gboolean glob_pipeline_restarted = TRUE;
+/* Used to avoid frame multiplication in case of continuous subtitles*/
+static GstClockTime glob_last_subtitled_frame = GST_CLOCK_TIME_NONE;
+
+static ProbeContext *glob_suboverlay_src_probe = NULL;
+static ProbeContext *glob_renderer_sink_probe = NULL;
+static gboolean glob_sub_found = FALSE;
+static gboolean glob_wrong_rendered_buf = FALSE;
+static gboolean glob_sub_render_found = FALSE;
+
+static GstClockTime glob_first_subtitle_ts = GST_CLOCK_TIME_NONE;
+static GstClockTime glob_playback_duration = GST_CLOCK_TIME_NONE;
+static gboolean glob_push_mode = FALSE;
+
+static TestInProgress glob_in_progress = TEST_NONE;
+
+/* checking Checking wedge */
+static gint64 global_last_probe = 0;
+static guint global_idle_timeout = 0;
+
+static gboolean next_test (InsanityTest * test);
+
+static void
+clean_test (InsanityTest * test)
+{
+ glob_pipeline = NULL;
+ glob_videotestsrc = NULL;
+
+ glob_uridecodebin = NULL;
+ glob_suboverlay = NULL;
+
+ glob_playback_duration = GST_CLOCK_TIME_NONE;
+ glob_push_mode = FALSE;
+
+ glob_sub_found = FALSE;
+ glob_in_progress = TEST_NONE;
+
+ global_last_probe = 0;
+ global_idle_timeout = 0;
+ glob_seekable = FALSE;
+
+ if (glob_suboverlay_src_probe != NULL) {
+ insanity_gst_test_remove_data_probe (INSANITY_GST_TEST (test),
+ glob_suboverlay_src_probe->pad, glob_suboverlay_src_probe->probe_id);
+ g_slice_free (ProbeContext, glob_suboverlay_src_probe);
+ glob_suboverlay_src_probe = NULL;
+ }
+
+ if (glob_renderer_sink_probe != NULL) {
+ insanity_gst_test_remove_data_probe (INSANITY_GST_TEST (test),
+ glob_renderer_sink_probe->pad, glob_renderer_sink_probe->probe_id);
+ g_slice_free (ProbeContext, glob_renderer_sink_probe);
+ glob_renderer_sink_probe = NULL;
+ }
+
+ g_clear_object (&glob_parser);
+ g_clear_object (&glob_writer);
+
+ glob_first_subtitle_ts = GST_CLOCK_TIME_NONE;
+
+ if (glob_subtitled_frames) {
+ g_list_free_full (glob_subtitled_frames, (GDestroyNotify) gst_buffer_unref);
+ glob_subtitled_frames = NULL;
+ }
+
+ glob_wrong_rendered_buf = FALSE;
+ glob_pipeline_restarted = TRUE;
+ glob_last_subtitled_frame = GST_CLOCK_TIME_NONE;
+ glob_duration = 0;
+}
+
+/* Utils functions */
+static inline const gchar *
+test_get_name (TestInProgress in_progress)
+{
+ switch (in_progress) {
+ case TEST_SUBTTILE_DESCRIPTOR_GENERATION:
+ return "Generating XML descripton file";
+ case TEST_NONE:
+ return "None";
+ case TEST_SUBTITLE_DETECTION:
+ return "Detecting subtitles";
+ }
+
+ return NULL;
+}
+
+static inline const gchar *
+pipeline_mode_get_name (gboolean push_mode)
+{
+ if (push_mode == TRUE)
+ return "push";
+
+ return "pull";
+}
+
+static gboolean
+idle_restart_pipeline (void)
+{
+ gst_element_set_state (glob_pipeline, GST_STATE_PAUSED);
+ gst_element_get_state (glob_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_element_seek_simple (glob_pipeline, GST_FORMAT_TIME,
+ GST_SEEK_FLAG_FLUSH, 0);
+ gst_element_set_state (glob_pipeline, GST_STATE_PLAYING);
+ glob_pipeline_restarted = TRUE;
+
+ return FALSE;
+}
+
+static gint
+sort_subtitle_bufs (GstBuffer * buf1, GstBuffer * buf2)
+{
+ if (GST_BUFFER_TIMESTAMP (buf1) <= GST_BUFFER_TIMESTAMP (buf2))
+ return -1;
+
+ return 1;
+}
+
+static gboolean
+next_test (InsanityTest * test)
+{
+ gchar *sublocation = NULL, *xmllocation = NULL;
+ GError *err = NULL;
+
+ switch (glob_in_progress) {
+ case TEST_SUBTTILE_DESCRIPTOR_GENERATION:
+ {
+
+ insanity_test_get_string_argument (test, "sublocation", &sublocation);
+ xmllocation = g_strconcat (sublocation, ".subs.xml", NULL);
+
+ LOG (test, "Done generation XML file, saving it to %s", xmllocation);
+
+ if (media_descriptor_writer_write (glob_writer, xmllocation) == FALSE) {
+ ERROR (test, "Could not write media descriptor");
+ insanity_test_done (test);
+ goto done;
+ }
+
+ glob_parser = media_descriptor_parser_new (test, xmllocation, &err);
+ if (glob_parser == NULL) {
+ ERROR (test, "Could not create media descriptor after writing it");
+ insanity_test_done (test);
+ goto done;
+ }
+
+ glob_subtitled_frames = media_descriptor_parser_get_buffers (glob_parser,
+ NULL, (GCompareFunc) sort_subtitle_bufs);
+
+ if (glob_subtitled_frames == NULL) {
+ ERROR (test, "No subtitles frames found");
+ insanity_test_done (test);
+
+ goto done;
+ } else
+ glob_first_subtitle_ts =
+ GST_BUFFER_TIMESTAMP (glob_subtitled_frames->data);
+
+ /* We reset the test so it starts again from the beginning */
+ glob_in_progress = TEST_NONE;
+ g_idle_add ((GSourceFunc) idle_restart_pipeline, NULL);
+
+ break;
+ }
+ case TEST_NONE:
+ glob_in_progress = TEST_SUBTITLE_DETECTION;
+ break;
+ case TEST_SUBTITLE_DETECTION:
+ insanity_test_done (test);
+ break;
+ }
+
+ LOG (test, "%s in progress", test_get_name (glob_in_progress));
+
+done:
+ g_free (sublocation);
+ g_free (xmllocation);
+
+ return FALSE;
+}
+
+static gboolean
+check_wedged (gpointer data)
+{
+ InsanityTest *test = data;
+ gint64 idle;
+
+ idle = (global_last_probe <= 0) ?
+ 0 : 1000 * (g_get_monotonic_time () - global_last_probe);
+
+ if (idle >= IDLE_TIMEOUT) {
+ LOG (test, "Wedged, kicking");
+
+ /* Unvalidate tests in progress */
+ switch (glob_in_progress) {
+ case TEST_SUBTTILE_DESCRIPTOR_GENERATION:
+ insanity_test_done (test);
+ return FALSE;
+ case TEST_NONE:
+ break;
+ case TEST_SUBTITLE_DETECTION:
+ insanity_test_validate_checklist_item (test, "subtitle-rendered",
+ FALSE, "No buffers or events were seen for a while");
+ break;
+ }
+
+ global_last_probe = g_get_monotonic_time ();
+ g_idle_add ((GSourceFunc) & next_test, test);
+ }
+
+ return TRUE;
+}
+
+static void
+generate_xml_media_descriptor (InsanityTest * test)
+{
+ GError *err = NULL;
+ GstDiscovererInfo *info = NULL;
+ gchar *sublocation = NULL, *suburi = NULL;
+
+ GstDiscoverer *discoverer = gst_discoverer_new (5 * GST_SECOND, NULL);
+
+ insanity_test_get_string_argument (test, "sublocation", &sublocation);
+
+ if (G_UNLIKELY (discoverer == NULL)) {
+ ERROR (test, "Error creating discoverer: %s\n", err->message);
+ g_clear_error (&err);
+
+ insanity_test_done (test);
+ goto done;
+ }
+
+ suburi = gst_filename_to_uri (sublocation, &err);
+ if (err) {
+ ERROR (test, "Could not construct filename");
+ g_clear_error (&err);
+ goto done;
+ }
+
+ info = gst_discoverer_discover_uri (discoverer, suburi, &err);
+ if (info == NULL) {
+ ERROR (test, "Error discovering: %s\n", err->message);
+ g_clear_error (&err);
+
+ insanity_test_done (test);
+ goto done;
+ }
+
+ glob_duration = gst_discoverer_info_get_duration (info);
+ glob_seekable = gst_discoverer_info_get_seekable (info);
+
+ glob_writer = media_descriptor_writer_new (test,
+ sublocation, glob_duration, glob_seekable);
+
+ glob_in_progress = TEST_SUBTTILE_DESCRIPTOR_GENERATION;
+
+ g_idle_add ((GSourceFunc) idle_restart_pipeline, NULL);
+
+ media_descriptor_writer_add_stream (glob_writer,
+ glob_suboverlay_src_probe->pad);
+
+done:
+ if (discoverer != NULL)
+ g_object_unref (discoverer);
+
+ if (info != NULL)
+ gst_discoverer_info_unref (info);
+
+ g_free (sublocation);
+ g_free (suburi);
+}
+
+/* Pipeline Callbacks */
+static gboolean
+renderer_probe_cb (InsanityGstTest * ptest, GstPad * pad,
+ GstMiniObject * object, gpointer userdata)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ if (GST_IS_BUFFER (object)) {
+ gint64 stime_ts;
+ GstBuffer *buf = GST_BUFFER (object), *nbuf;
+
+ if (glob_in_progress == TEST_SUBTTILE_DESCRIPTOR_GENERATION)
+ goto done;
+
+ if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buf)) == FALSE
+ && glob_parser == NULL) {
+ gboolean generate_media_desc;
+
+ insanity_test_get_boolean_argument (test, "create-media-descriptor",
+ (gboolean *) & generate_media_desc);
+
+ /* We generate the XML file if needed and allowed by user */
+ if (generate_media_desc)
+ generate_xml_media_descriptor (test);
+ else
+ insanity_test_done (test);
+ } else if (glob_parser == NULL) {
+
+ /* Avoid using xml descriptor when not needed */
+ stime_ts =
+ gst_segment_to_stream_time (&glob_renderer_sink_probe->last_segment,
+ glob_renderer_sink_probe->last_segment.format,
+ GST_BUFFER_TIMESTAMP (buf));
+
+ if (GST_CLOCK_TIME_IS_VALID (glob_first_subtitle_ts) == FALSE)
+ glob_first_subtitle_ts = stime_ts;
+
+ nbuf = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (nbuf) = stime_ts;
+ GST_BUFFER_DURATION (nbuf) = GST_BUFFER_DURATION (buf);
+
+ glob_subtitled_frames = g_list_insert_sorted (glob_subtitled_frames, nbuf,
+ (GCompareFunc) sort_subtitle_bufs);
+ }
+ } else if (GST_IS_EVENT (object)) {
+ GstEvent *event = GST_EVENT (object);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ GstFormat fmt;
+ gint64 start, stop, position;
+ gdouble rate, applied_rate;
+ gboolean update;
+
+ /* We do not care about event during subtitle generation */
+ if (glob_in_progress == TEST_SUBTTILE_DESCRIPTOR_GENERATION)
+ goto done;
+
+ gst_event_parse_new_segment_full (event, &update, &rate,
+ &applied_rate, &fmt, &start, &stop, &position);
+ gst_segment_set_newsegment_full
+ (&glob_renderer_sink_probe->last_segment, update, rate,
+ applied_rate, fmt, start, stop, position);
+
+ if (glob_renderer_sink_probe->waiting_segment == FALSE)
+ /* Cache the segment as it will be our reference but don't look
+ * further */
+ goto done;
+
+ if (glob_renderer_sink_probe->waiting_first_segment == TRUE) {
+ /* Make sure that a new segment has been received for each stream */
+ glob_renderer_sink_probe->waiting_first_segment = FALSE;
+ glob_renderer_sink_probe->waiting_segment = FALSE;
+ }
+
+ glob_renderer_sink_probe->waiting_segment = FALSE;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+done:
+ return TRUE;
+}
+
+static gboolean
+frame_contains_subtitles (GstBuffer * buff)
+{
+ guint x, y, first_sub_pix_x = 0, first_sub_pix_y = 0, last_sub_y = 0;
+
+ guint8 *data = GST_BUFFER_DATA (buff);
+
+ for (y = 0; y < 1080; y++) {
+ for (x = 0; x < 1920 * 3; x += 3) {
+ if ((data[x + y * 1920 * 3] != 0x00) ||
+ (data[x + y * 1920 * 3 + 1] != 0x00) ||
+ (data[x + y * 1920 * 3 + 2] != 0x00)) {
+
+ if (first_sub_pix_x == 0) {
+ first_sub_pix_x = x / 3;
+ first_sub_pix_y = y;
+ }
+
+ last_sub_y = y;
+ }
+ }
+ }
+
+ if (last_sub_y != 0 && last_sub_y != first_sub_pix_y)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+probe_cb (InsanityGstTest * ptest, GstPad * pad, GstMiniObject * object,
+ gpointer userdata)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ global_last_probe = g_get_monotonic_time ();
+
+ if (GST_IS_BUFFER (object)) {
+ GstClockTime buf_start, buf_end;
+ GstBuffer *next_sub, *buf = GST_BUFFER (object);
+
+ buf_start =
+ gst_segment_to_stream_time (&glob_suboverlay_src_probe->last_segment,
+ glob_suboverlay_src_probe->last_segment.format,
+ GST_BUFFER_TIMESTAMP (buf));
+ buf_end = buf_start + GST_BUFFER_DURATION (buf);
+
+ if (glob_in_progress == TEST_SUBTTILE_DESCRIPTOR_GENERATION) {
+ if (glob_pipeline_restarted == TRUE) {
+ gboolean has_subs;
+
+ if (glob_duration > 0 && buf_end > glob_duration) {
+ /* Done according to the duration previously found by the
+ * discoverer */
+ next_test (test);
+ }
+
+ has_subs = frame_contains_subtitles (buf);
+ if (GST_CLOCK_TIME_IS_VALID (glob_last_subtitled_frame)) {
+ if (has_subs == FALSE) {
+ GstBuffer *nbuf = gst_buffer_new ();
+
+ GST_BUFFER_TIMESTAMP (nbuf) = glob_last_subtitled_frame;
+ GST_BUFFER_DURATION (nbuf) = buf_end - glob_last_subtitled_frame;
+ media_descriptor_writer_add_frame (glob_writer, pad, nbuf);
+
+ glob_last_subtitled_frame = GST_CLOCK_TIME_NONE;
+ gst_buffer_unref (nbuf);
+ }
+ } else if (has_subs) {
+ glob_last_subtitled_frame = buf_start;
+ }
+ }
+
+ goto done;
+ }
+
+ /* We played enough... next test */
+ if (GST_CLOCK_TIME_IS_VALID (glob_first_subtitle_ts) &&
+ buf_start >=
+ glob_first_subtitle_ts + glob_playback_duration * GST_SECOND) {
+ next_test (test);
+ }
+
+ switch (glob_in_progress) {
+ case TEST_NONE:
+ {
+
+ if (glob_suboverlay_src_probe->waiting_first_segment == TRUE) {
+ insanity_test_validate_checklist_item (test, "first-segment", FALSE,
+ "Got a buffer before the first segment");
+ }
+ next_test (test);
+ }
+ default:
+ break;
+ }
+
+ if (glob_subtitled_frames != NULL) {
+ GstClockTime sub_start, sub_end;
+
+ next_sub = GST_BUFFER (glob_subtitled_frames->data);
+
+ sub_start = GST_BUFFER_TIMESTAMP (next_sub);
+ sub_end = GST_BUFFER_DURATION_IS_VALID (next_sub) ?
+ GST_BUFFER_DURATION (next_sub) + sub_start : -1;
+
+ if (buf_start >= sub_start && buf_end < sub_end) {
+ if (frame_contains_subtitles (buf) == TRUE) {
+ glob_sub_render_found = TRUE;
+ insanity_test_validate_checklist_item (test, "subtitle-rendered",
+ TRUE, NULL);
+ } else {
+ gchar *msg = g_strdup_printf ("Subtitle start %" GST_TIME_FORMAT
+ " end %" GST_TIME_FORMAT " received buffer with no sub start %"
+ GST_TIME_FORMAT " end %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (sub_start),
+ GST_TIME_ARGS (sub_end), GST_TIME_ARGS (buf_start),
+ GST_TIME_ARGS (buf_end));
+
+ insanity_test_validate_checklist_item (test, "subtitle-rendered",
+ FALSE, msg);
+ glob_wrong_rendered_buf = TRUE;
+
+ g_free (msg);
+ }
+ } else if (buf_end > sub_end) {
+ /* We got a buffer that is after the subtitle we were waiting for
+ * remove that buffer as not waiting for it anymore */
+ gst_buffer_unref (next_sub);
+
+ glob_subtitled_frames = g_list_remove (glob_subtitled_frames, next_sub);
+ }
+ }
+
+ } else if (GST_IS_EVENT (object)) {
+ GstEvent *event = GST_EVENT (object);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ GstFormat fmt;
+ gint64 start, stop, position;
+ gdouble rate, applied_rate;
+ gboolean update;
+
+ gst_event_parse_new_segment_full (event, &update, &rate,
+ &applied_rate, &fmt, &start, &stop, &position);
+ gst_segment_set_newsegment_full
+ (&glob_suboverlay_src_probe->last_segment, update, rate,
+ applied_rate, fmt, start, stop, position);
+
+ if (glob_suboverlay_src_probe->waiting_first_segment == TRUE) {
+ insanity_test_validate_checklist_item (test, "first-segment", TRUE,
+ NULL);
+ glob_suboverlay_src_probe->waiting_first_segment = FALSE;
+ }
+
+ if (glob_suboverlay_src_probe->waiting_segment == FALSE)
+ /* Cache the segment as it will be our reference but don't look
+ * further */
+ goto done;
+
+ if (glob_suboverlay_src_probe->waiting_first_segment == TRUE) {
+ /* Make sure that a new segment has been received for each stream */
+ glob_suboverlay_src_probe->waiting_first_segment = FALSE;
+ glob_suboverlay_src_probe->waiting_segment = FALSE;
+ }
+
+ glob_suboverlay_src_probe->waiting_segment = FALSE;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+done:
+ return TRUE;
+}
+
+static gint
+find_renderer_subtitle_sinkpad (GstPad * pad)
+{
+ GstCaps *caps = gst_pad_get_caps (pad);
+ GstStructure *stru = gst_caps_get_structure (caps, 0);
+
+ gst_object_unref (pad);
+
+ if (g_strrstr (gst_structure_get_name (stru), "subpicture") != NULL)
+ return 0;
+ else if (g_strrstr (gst_structure_get_name (stru), "video") == NULL)
+ return 0;
+
+ return 1;
+
+}
+
+static void
+suboverlay_child_added_cb (GstElement * suboverlay, GstElement * child,
+ InsanityTest * test)
+{
+ GstIterator *it;
+ GstPad *render_sub_sink, *tmppad;
+ GstElementFactory *fact;
+ const gchar *klass, *name;
+ gulong probe_id;
+
+ gboolean is_renderer = FALSE;
+
+
+ /* cc-ed from -base/gstsubtitleoveraly.c */
+ fact = gst_element_get_factory (child);
+ klass = gst_element_factory_get_klass (fact);
+
+ if (GST_IS_BIN (child))
+ return;
+
+ name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (fact));
+
+ if (g_strrstr (klass, "Overlay/Subtitle") != NULL ||
+ g_strrstr (klass, "Overlay/SubPicture") != NULL)
+ is_renderer = TRUE;
+
+ else if (g_strcmp0 (name, "textoverlay") == 0)
+ is_renderer = TRUE;
+
+ if (is_renderer == FALSE)
+ return;
+
+ LOG (test, "Renderer found: %s", name);
+
+ /* Now adding the probe to the renderer "subtitle" sink pad */
+ it = gst_element_iterate_sink_pads (child);
+ render_sub_sink = gst_iterator_find_custom (it,
+ (GCompareFunc) find_renderer_subtitle_sinkpad, NULL);
+ gst_iterator_free (it);
+
+ if (insanity_gst_test_add_data_probe (INSANITY_GST_TEST (test),
+ GST_BIN (glob_pipeline), GST_OBJECT_NAME (child),
+ GST_ELEMENT_NAME (render_sub_sink), &tmppad, &probe_id,
+ &renderer_probe_cb, NULL, NULL) == TRUE) {
+
+ glob_renderer_sink_probe = g_slice_new0 (ProbeContext);
+ glob_renderer_sink_probe->probe_id = probe_id;
+ glob_renderer_sink_probe->pad = render_sub_sink;
+ glob_renderer_sink_probe->element = child;
+ glob_renderer_sink_probe->test = test;
+ glob_renderer_sink_probe->waiting_first_segment = TRUE;
+
+ insanity_test_validate_checklist_item (test, "install-probes", TRUE, NULL);
+ } else {
+ insanity_test_validate_checklist_item (test, "install-probes", FALSE,
+ "Failed to attach probe to fakesink");
+ insanity_test_done (test);
+
+ return;
+ }
+
+}
+
+static gboolean
+pad_added_cb (GstElement * element, GstPad * new_pad, InsanityTest * test)
+{
+ GstPadLinkReturn linkret;
+
+ GstIterator *it = NULL;
+ GstCaps *caps = NULL;
+ gboolean ret = TRUE;
+
+ GstPad *mqsinkpad = NULL, *mqsrcpad = NULL, *ssinkpad =
+ NULL, *suboverlaysinkpad = NULL;
+
+ SUBTITLES_TEST_LOCK ();
+
+ /* First check if the pad caps are compatible with the suboverlay */
+ caps = gst_pad_get_caps (new_pad);
+ suboverlaysinkpad = gst_element_get_compatible_pad (glob_suboverlay, new_pad,
+ caps);
+
+ if (suboverlaysinkpad == NULL) {
+ LOG (test,
+ "Pad %" GST_PTR_FORMAT " with caps %" GST_PTR_FORMAT " Not usefull",
+ new_pad, caps);
+ goto error;
+ }
+
+ glob_sub_found = TRUE;
+ insanity_test_validate_checklist_item (test, "testing-subtitles", TRUE, NULL);
+
+ /* Link to the decoder */
+ linkret = gst_pad_link (new_pad, suboverlaysinkpad);
+ if (linkret != GST_PAD_LINK_OK) {
+ ERROR (test, "Getting linking %" GST_PTR_FORMAT " with %" GST_PTR_FORMAT,
+ mqsrcpad, suboverlaysinkpad);
+ goto error;
+ }
+
+ LOG (test, "Pad %" GST_PTR_FORMAT " with caps %" GST_PTR_FORMAT " linked,"
+ " and ready to be tested", new_pad, caps);
+
+done:
+ SUBTITLES_TEST_UNLOCK ();
+
+ if (it)
+ gst_iterator_free (it);
+
+ if (suboverlaysinkpad)
+ gst_object_unref (suboverlaysinkpad);
+
+ if (caps)
+ gst_caps_unref (caps);
+
+ if (mqsinkpad)
+ gst_object_unref (mqsinkpad);
+
+ if (ssinkpad)
+ gst_object_unref (ssinkpad);
+
+ return ret;
+
+error:
+ ret = FALSE;
+ goto done;
+}
+
+static gboolean
+bus_message_cb (InsanityGstPipelineTest * ptest, GstMessage * msg)
+{
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_EOS:
+ {
+ LOG (test, "Got EOS");
+ if (glob_in_progress == TEST_SUBTTILE_DESCRIPTOR_GENERATION)
+ next_test (test);
+
+ /* Keep testing anyway */
+ return FALSE;
+ }
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Test Callbacks and vmethods*/
+static GstPipeline *
+create_pipeline (InsanityGstPipelineTest * ptest, gpointer unused_data)
+{
+ GstCaps *caps;
+ gulong probe_id;
+ GError *err = NULL;
+ GstIterator *it = NULL;
+ gchar *uri = NULL, *sublocation = NULL;
+ GstElement *capsfilter = NULL, *capsfilter1 = NULL, *colorspace =
+ NULL, *colorspace1 = NULL, *fakesink = NULL;
+ GstPad *fakesinksink = NULL, *tmppad = NULL;
+
+ InsanityTest *test = INSANITY_TEST (ptest);
+
+ SUBTITLES_TEST_LOCK ();
+ glob_pipeline = GST_ELEMENT (gst_pipeline_new ("pipeline"));
+
+ /* Create the source */
+ insanity_test_get_boolean_argument (test, "push-mode",
+ (gboolean *) & glob_push_mode);
+
+ insanity_test_get_string_argument (test, "sublocation", &sublocation);
+ if (sublocation == NULL || g_strcmp0 (sublocation, "") == 0) {
+ ERROR (test, "Location name not set\n");
+ goto creation_failed;
+ }
+
+ uri = gst_filename_to_uri (sublocation, &err);
+ if (err != NULL) {
+ ERROR (test, "Error creating uri %s", err->message);
+
+ goto creation_failed;
+ }
+ if (glob_push_mode == TRUE) {
+ gchar *tmpuri;
+
+ glob_uridecodebin = gst_element_factory_make ("pushfilesrc", "src");
+ tmpuri = g_strconcat ("push", uri, NULL);
+ g_free (uri);
+
+ uri = tmpuri;
+ }
+
+ glob_uridecodebin = gst_element_factory_make ("uridecodebin", "src");
+ g_signal_connect (glob_uridecodebin, "pad-added", G_CALLBACK (pad_added_cb),
+ test);
+ g_object_set (glob_uridecodebin, "uri", uri, NULL);
+
+ /* the subtitleoverlay */
+ glob_suboverlay =
+ gst_element_factory_make ("subtitleoverlay", "subtitleoverlay");
+ if (glob_suboverlay == NULL)
+ goto creation_failed;
+
+ /* the fakesink */
+ fakesink = gst_element_factory_make ("fakesink", "fakesink");
+ if (fakesink == NULL)
+ goto creation_failed;
+
+ /* and the videotestsrc */
+ glob_videotestsrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
+ if (glob_videotestsrc == NULL)
+ goto creation_failed;
+ g_object_set (glob_videotestsrc, "pattern", 2, "do-timestamp", TRUE, NULL);
+
+ /* Make sure the video is big enough */
+ capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ if (capsfilter == NULL)
+ goto creation_failed;
+
+ caps = gst_video_format_new_caps (GST_VIDEO_FORMAT_RGB, 1920, 1080, 25, 1,
+ 1, 1);
+
+ g_object_set (capsfilter, "caps", caps, NULL);
+
+ capsfilter1 = gst_element_factory_make ("capsfilter", NULL);
+ if (capsfilter1 == NULL)
+ goto creation_failed;
+
+ /* We want the last frame that we will "parse" to check if it contains
+ * subtitles to be in RGB to make simpler for us */
+ caps = gst_caps_from_string ("video/x-raw-rgb, bpp=24, height=(gint)1080,"
+ "width=(gint)1920;");
+ g_object_set (capsfilter1, "caps", caps, NULL);
+
+ colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
+ if (colorspace == NULL)
+ goto creation_failed;
+
+ colorspace1 = gst_element_factory_make ("ffmpegcolorspace", NULL);
+ if (colorspace1 == NULL)
+ goto creation_failed;
+
+ /* Now add to the pipeline */
+ gst_bin_add_many (GST_BIN (glob_pipeline), glob_uridecodebin,
+ glob_videotestsrc, capsfilter, glob_suboverlay,
+ capsfilter1, colorspace, colorspace1, fakesink, NULL);
+
+ /* link video branch elements */
+ gst_element_link_many (glob_videotestsrc, capsfilter,
+ glob_suboverlay, colorspace, capsfilter1, fakesink, NULL);
+
+ /* And install a probe to the subtitleoverlay src pad */
+ fakesinksink = gst_element_get_static_pad (fakesink, "sink");
+ if (fakesinksink == NULL)
+ goto failed;
+
+ if (insanity_gst_test_add_data_probe (INSANITY_GST_TEST (test),
+ GST_BIN (glob_pipeline), GST_OBJECT_NAME (fakesink),
+ GST_OBJECT_NAME (fakesinksink), &tmppad, &probe_id,
+ &probe_cb, NULL, NULL) == TRUE) {
+
+ glob_suboverlay_src_probe = g_slice_new0 (ProbeContext);
+ glob_suboverlay_src_probe->probe_id = probe_id;
+ glob_suboverlay_src_probe->pad = fakesinksink;
+ glob_suboverlay_src_probe->element = fakesink;
+ glob_suboverlay_src_probe->test = test;
+ glob_suboverlay_src_probe->waiting_first_segment = TRUE;
+
+ insanity_test_validate_checklist_item (test, "install-probes", TRUE, NULL);
+ } else {
+ insanity_test_validate_checklist_item (test,
+ "install-probes", FALSE, "Failed to attach probe to fakesink");
+ insanity_test_done (test);
+ goto failed;
+ }
+
+ g_signal_connect (GST_CHILD_PROXY (glob_suboverlay), "child-added",
+ G_CALLBACK (suboverlay_child_added_cb), test);
+
+done:
+ SUBTITLES_TEST_UNLOCK ();
+
+ g_free (uri);
+ g_free (sublocation);
+ if (err != NULL)
+ g_error_free (err);
+ if (it != NULL)
+ gst_iterator_free (it);
+
+ return GST_PIPELINE (glob_pipeline);
+
+failed:
+ if (glob_pipeline != NULL)
+ gst_object_unref (glob_pipeline);
+
+ glob_suboverlay = glob_pipeline = glob_videotestsrc =
+ glob_uridecodebin = NULL;
+ goto done;
+
+creation_failed:
+ if (glob_uridecodebin != NULL)
+ gst_object_unref (glob_uridecodebin);
+ if (glob_suboverlay != NULL)
+ gst_object_unref (glob_suboverlay);
+ if (glob_videotestsrc != NULL)
+ gst_object_unref (glob_videotestsrc);
+ if (fakesink != NULL)
+ gst_object_unref (fakesink);
+
+ goto failed;
+}
+
+static gboolean
+start_cb (InsanityTest * test)
+{
+ gboolean ret = TRUE;
+
+ gchar *sublocation = NULL, *xmllocation = NULL;
+ GError *err = NULL;
+
+ SUBTITLES_TEST_LOCK ();
+
+ insanity_test_get_string_argument (test, "sublocation", &sublocation);
+ if (sublocation == NULL || g_strcmp0 (sublocation, "") == 0) {
+ ERROR (test, "Location name not set\n");
+ ret = FALSE;
+ goto done;
+ }
+
+ xmllocation = g_strconcat (sublocation, ".subs.xml", NULL);
+ glob_parser = media_descriptor_parser_new (test, xmllocation, &err);
+ if (glob_parser == NULL) {
+ LOG (test, "Could not create media descriptor parser: %s not testing it",
+ err->message);
+ goto done;
+ } else {
+ glob_subtitled_frames = media_descriptor_parser_get_buffers (glob_parser,
+ NULL, (GCompareFunc) sort_subtitle_bufs);
+ if (glob_subtitled_frames == NULL) {
+ ERROR (test, "No subtitles frames found");
+ ret = FALSE;
+
+ goto done;
+ } else
+ glob_first_subtitle_ts =
+ GST_BUFFER_TIMESTAMP (glob_subtitled_frames->data);
+ }
+
+done:
+ g_free (sublocation);
+ g_free (xmllocation);
+
+ insanity_test_get_uint64_argument (test, "playback-duration",
+ &glob_playback_duration);
+
+ SUBTITLES_TEST_UNLOCK ();
+
+ return ret;
+}
+
+static void
+reached_initial_state_cb (InsanityTest * test)
+{
+ /* and install wedged timeout */
+ global_idle_timeout =
+ g_timeout_add (1000, (GSourceFunc) & check_wedged, test);
+}
+
+static void
+teardown_cb (InsanityTest * test)
+{
+ clean_test (test);
+}
+
+static gboolean
+stop_cb (InsanityTest * test)
+{
+ if (glob_sub_found == FALSE) {
+ /* A "not linked" error will be posted on the bus and the test will
+ * properly be stoped in this case */
+ insanity_test_validate_checklist_item (test, "testing-subtitles", FALSE,
+ NULL);
+ }
+ if (glob_wrong_rendered_buf == TRUE || glob_sub_render_found == FALSE) {
+ insanity_test_validate_checklist_item (test, "subtitle-rendered", FALSE,
+ NULL);
+ }
+
+ /* We clean everything as the pipeline is rebuilt at each
+ * iteration of start/stop*/
+ clean_test (test);
+
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ InsanityGstPipelineTest *ptest;
+ InsanityTest *test;
+ gboolean ret;
+
+ const gchar *sublocation = NULL;
+
+ g_type_init ();
+
+ ptest = insanity_gst_pipeline_test_new ("stream-switch-test", "Tests stream "
+ "switching inside playbin2", NULL);
+ test = INSANITY_TEST (ptest);
+ insanity_gst_pipeline_test_set_create_pipeline_in_start (ptest, TRUE);
+
+ /* Arguments */
+ insanity_test_add_string_argument (test, "sublocation",
+ "The sublocation to test on", NULL, FALSE, sublocation);
+ insanity_test_add_boolean_argument (test, "push-mode",
+ "Whether the pipeline should run in push mode or not (pull mode)",
+ NULL, FALSE, FALSE);
+ insanity_test_add_uint64_argument (test, "playback-duration",
+ "Stream time to playback for before seeking, in seconds", NULL, TRUE, 2);
+ insanity_test_add_boolean_argument (test, "create-media-descriptor",
+ "Whether to create the media descriptor XML file if not present",
+ NULL, TRUE, TRUE);
+
+ /* Checklist */
+ insanity_test_add_checklist_item (test, "testing-subtitles",
+ "Whether we found subtitle in @sublocation", NULL);
+ insanity_test_add_checklist_item (test, "install-probes",
+ "Probes were installed on the sinks", NULL);
+ insanity_test_add_checklist_item (test, "subtitle-rendered",
+ "The subtitles are properly rendered on top of the video", NULL);
+ insanity_test_add_checklist_item (test, "first-segment", "The demuxer sends a"
+ " first segment with proper values before " "first buffers", NULL);
+
+ insanity_gst_pipeline_test_set_create_pipeline_function (ptest,
+ &create_pipeline, NULL, NULL);
+
+ g_signal_connect (test, "start", G_CALLBACK (&start_cb), NULL);
+ g_signal_connect_after (test, "stop", G_CALLBACK (&stop_cb), NULL);
+ g_signal_connect_after (test, "bus-message", G_CALLBACK (&bus_message_cb), 0);
+ g_signal_connect_after (test, "teardown", G_CALLBACK (&teardown_cb), NULL);
+ g_signal_connect_after (test, "reached-initial-state",
+ G_CALLBACK (&reached_initial_state_cb), 0);
+
+ ret = insanity_test_run (test, &argc, &argv);
+
+ g_object_unref (test);
+
+ return ret ? 0 : 1;
+}
diff --git a/tests/media-descriptor-common.c b/tests/media-descriptor-common.c
new file mode 100644
index 0000000..87f1461
--- /dev/null
+++ b/tests/media-descriptor-common.c
@@ -0,0 +1,98 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 "media-descriptor-common.h"
+
+static inline void
+free_tagnode (TagNode * tagnode)
+{
+ g_free (tagnode->str_open);
+ g_free (tagnode->str_close);
+ if (tagnode->taglist)
+ gst_tag_list_free (tagnode->taglist);
+
+ g_slice_free (TagNode, tagnode);
+}
+
+static inline void
+free_tagsnode (TagsNode * tagsnode)
+{
+ g_free (tagsnode->str_open);
+ g_free (tagsnode->str_close);
+ g_list_free_full (tagsnode->tags, (GDestroyNotify) free_tagnode);
+ g_slice_free (TagsNode, tagsnode);
+}
+
+static inline void
+free_framenode (FrameNode * framenode)
+{
+ g_free (framenode->str_open);
+ g_free (framenode->str_close);
+
+ if (framenode->buf)
+ gst_buffer_unref (framenode->buf);
+
+ g_slice_free (FrameNode, framenode);
+}
+
+static inline void
+free_streamnode (StreamNode * streamnode)
+{
+ if (streamnode->caps)
+ gst_caps_unref (streamnode->caps);
+
+ g_list_free_full (streamnode->frames, (GDestroyNotify) free_framenode);
+
+ if (streamnode->pad)
+ gst_object_unref (streamnode->pad);
+
+ g_free (streamnode->padname);
+
+ g_free (streamnode->str_open);
+ g_free (streamnode->str_close);
+ g_slice_free (StreamNode, streamnode);
+}
+
+void
+free_filenode (FileNode * filenode)
+{
+ g_list_free_full (filenode->streams, (GDestroyNotify) free_streamnode);
+ g_list_free_full (filenode->tags, (GDestroyNotify) free_tagsnode);
+
+ g_free (filenode->str_open);
+ g_free (filenode->str_close);
+
+ g_slice_free (FileNode, filenode);
+}
+
+gboolean
+tag_node_compare (TagNode * tnode, const GstTagList * tlist)
+{
+ if (gst_structure_is_equal (GST_STRUCTURE (tlist),
+ GST_STRUCTURE (tnode->taglist)) == FALSE) {
+ return FALSE;
+ }
+
+ tnode->found = TRUE;
+
+ return TRUE;
+}
diff --git a/tests/media-descriptor-common.h b/tests/media-descriptor-common.h
new file mode 100644
index 0000000..cd88d44
--- /dev/null
+++ b/tests/media-descriptor-common.h
@@ -0,0 +1,110 @@
+/**
+ * Insanity QA system
+ *
+ * Copyright (c) 2012, Collabora Ltd
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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.
+ */
+
+#ifndef MEDIA_DESCRIPTOR_COMMON_H
+#define MEDIA_DESCRIPTOR_COMMON_H
+
+#include <glib.h>
+#include <insanity-gst/insanity-gst.h>
+
+/* Parsing structures */
+typedef struct
+{
+ /* Children */
+ /* StreamNode */
+ GList *streams;
+ /* TagsNode */
+ GList *tags;
+
+ /* attributes */
+ guint64 id;
+ gchar *location;
+ GstClockTime duration;
+ gboolean frame_detection;
+ gboolean seekable;
+
+
+ gchar *str_open;
+ gchar *str_close;
+} FileNode;
+
+typedef struct
+{
+ /* Children */
+ /* TagNode */
+ GList *tags;
+
+ gchar *str_open;
+ gchar *str_close;
+} TagsNode;
+
+typedef struct
+{
+ /* Children */
+ GstTagList *taglist;
+
+ /* Testing infos */
+ gboolean found;
+
+ gchar *str_open;
+ gchar *str_close;
+} TagNode;
+
+typedef struct
+{
+ /* Children */
+ /* FrameNode */
+ GList *frames;
+
+ /* Attributes */
+ GstCaps *caps;
+ guint64 id;
+ gchar *padname;
+
+ /* Testing infos */
+ GstPad *pad;
+ GList *cframe;
+
+ gchar *str_open;
+ gchar *str_close;
+} StreamNode;
+
+typedef struct
+{
+ /* Attributes */
+ guint64 id;
+ guint64 offset;
+ guint64 offset_end;
+ GstClockTime duration;
+ GstClockTime timestamp;
+ gboolean is_keyframe;
+
+ GstBuffer *buf;
+
+ gchar *str_open;
+ gchar *str_close;
+} FrameNode;
+
+void free_filenode (FileNode * filenode);
+gboolean tag_node_compare (TagNode * tnode, const GstTagList * tlist);
+
+#endif /* MEDIA_DESCRIPTOR_COMMON_H */
diff --git a/tests/media-descriptor-parser.c b/tests/media-descriptor-parser.c
new file mode 100644
index 0000000..cd54255
--- /dev/null
+++ b/tests/media-descriptor-parser.c
@@ -0,0 +1,569 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 "media-descriptor-parser.h"
+#include "media-descriptor-common.h"
+
+G_DEFINE_TYPE (MediaDescriptorParser, media_descriptor_parser, G_TYPE_OBJECT);
+
+#define LOG(test, format , args...) \
+ INSANITY_LOG (test, "mediadescparser", INSANITY_LOG_LEVEL_DEBUG, format "\n", ##args)
+#define ERROR(test, format, args...) \
+ INSANITY_LOG (test, "mediadescparser", INSANITY_LOG_LEVEL_SPAM, format "\n", ##args)
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ N_PROPERTIES
+};
+
+struct _MediaDescriptorParserPrivate
+{
+ gchar *xmlpath;
+ InsanityTest *test;
+
+ gchar *xmlcontent;
+ FileNode *filenode;
+ GMarkupParseContext *parsecontext;
+};
+
+/* Private methods and callbacks */
+static gint
+compare_frames (FrameNode * frm, FrameNode * frm1)
+{
+ if (frm->id < frm1->id)
+ return -1;
+
+ else if (frm->id == frm1->id)
+ return 0;
+
+ return 1;
+}
+
+static inline FileNode *
+deserialize_filenode (const gchar ** names, const gchar ** values)
+{
+ gint i;
+ FileNode *filenode = g_slice_new0 (FileNode);
+
+ for (i = 0; names[i] != NULL; i++) {
+ if (g_strcmp0 (names[i], "location") == 0)
+ filenode->location = g_strdup (values[i]);
+ else if (g_strcmp0 (names[i], "id") == 0)
+ filenode->id = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "frame-detection") == 0)
+ filenode->frame_detection = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "duration") == 0)
+ filenode->duration = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "seekable") == 0)
+ filenode->seekable = g_ascii_strtoull (values[i], NULL, 0);
+ }
+
+ return filenode;
+}
+
+static inline StreamNode *
+deserialize_streamnode (const gchar ** names, const gchar ** values)
+{
+ gint i;
+ StreamNode *streamnode = g_slice_new0 (StreamNode);
+
+ for (i = 0; names[i] != NULL; i++) {
+ if (g_strcmp0 (names[i], "id") == 0)
+ streamnode->id = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "caps") == 0)
+ streamnode->caps = gst_caps_from_string (values[i]);
+ else if (g_strcmp0 (names[i], "padname") == 0)
+ streamnode->padname = g_strdup (values[i]);
+ }
+
+ return streamnode;
+}
+
+static inline TagsNode *
+deserialize_tagsnode (const gchar ** names, const gchar ** values)
+{
+ TagsNode *tagsnode = g_slice_new0 (TagsNode);
+
+ return tagsnode;
+}
+
+static inline TagNode *
+deserialize_tagnode (const gchar ** names, const gchar ** values)
+{
+ gint i;
+ TagNode *tagnode = g_slice_new0 (TagNode);
+
+ for (i = 0; names[i] != NULL; i++) {
+ if (g_strcmp0 (names[i], "content") == 0)
+ tagnode->taglist = gst_structure_from_string (values[i], NULL);
+ }
+
+ return tagnode;
+}
+
+static inline FrameNode *
+deserialize_framenode (const gchar ** names, const gchar ** values)
+{
+ gint i;
+
+ FrameNode *framenode = g_slice_new0 (FrameNode);
+
+ for (i = 0; names[i] != NULL; i++) {
+ if (g_strcmp0 (names[i], "id") == 0)
+ framenode->id = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "offset") == 0)
+ framenode->offset = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "offset-end") == 0)
+ framenode->offset_end = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "duration") == 0)
+ framenode->duration = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "timestamp") == 0)
+ framenode->timestamp = g_ascii_strtoull (values[i], NULL, 0);
+ else if (g_strcmp0 (names[i], "is-keyframe") == 0)
+ framenode->is_keyframe = g_ascii_strtoull (values[i], NULL, 0);
+ }
+
+ framenode->buf = gst_buffer_new ();
+
+ GST_BUFFER_OFFSET (framenode->buf) = framenode->offset;
+ GST_BUFFER_OFFSET_END (framenode->buf) = framenode->offset_end;
+ GST_BUFFER_DURATION (framenode->buf) = framenode->duration;
+ GST_BUFFER_TIMESTAMP (framenode->buf) = framenode->timestamp;
+
+ if (framenode->is_keyframe == FALSE)
+ GST_BUFFER_FLAG_SET (framenode->buf, GST_BUFFER_FLAG_DELTA_UNIT);
+
+ return framenode;
+}
+
+
+static inline gboolean
+frame_node_compare (FrameNode * fnode, GstBuffer * buf, GstBuffer * expected)
+{
+ if (expected != NULL) {
+ GST_BUFFER_OFFSET (expected) = fnode->offset;
+ GST_BUFFER_OFFSET_END (expected) = fnode->offset_end;
+ GST_BUFFER_DURATION (expected) = fnode->duration;
+ GST_BUFFER_TIMESTAMP (expected) = fnode->timestamp;
+ if (fnode->is_keyframe)
+ GST_BUFFER_FLAG_SET (expected, GST_BUFFER_FLAG_DELTA_UNIT);
+ }
+
+ if ((fnode->offset == GST_BUFFER_OFFSET (buf) &&
+ fnode->offset_end == GST_BUFFER_OFFSET_END (buf) &&
+ fnode->duration == GST_BUFFER_DURATION (buf) &&
+ fnode->timestamp == GST_BUFFER_TIMESTAMP (buf) &&
+ fnode->is_keyframe == GST_BUFFER_FLAG_IS_SET (buf,
+ GST_BUFFER_FLAG_DELTA_UNIT)) == FALSE) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_start_element_cb (GMarkupParseContext * context,
+ const gchar * element_name, const gchar ** attribute_names,
+ const gchar ** attribute_values, gpointer user_data, GError ** error)
+{
+ MediaDescriptorParserPrivate *priv;
+
+ priv = MEDIA_DESCRIPTOR_PARSER (user_data)->priv;
+
+ if (g_strcmp0 (element_name, "file") == 0) {
+ priv->filenode = deserialize_filenode (attribute_names, attribute_values);
+ } else if (g_strcmp0 (element_name, "stream") == 0) {
+ priv->filenode->streams = g_list_prepend (priv->filenode->streams,
+ deserialize_streamnode (attribute_names, attribute_values));
+ } else if (g_strcmp0 (element_name, "frame") == 0) {
+ StreamNode *streamnode = priv->filenode->streams->data;
+
+ streamnode->cframe = streamnode->frames =
+ g_list_insert_sorted (streamnode->frames,
+ deserialize_framenode (attribute_names, attribute_values),
+ (GCompareFunc) compare_frames);
+ } else if (g_strcmp0 (element_name, "tags") == 0) {
+ priv->filenode->tags = g_list_prepend (priv->filenode->tags,
+ deserialize_tagsnode (attribute_names, attribute_values));
+ } else if (g_strcmp0 (element_name, "tag") == 0) {
+ TagsNode *tagsnode = priv->filenode->tags->data;
+
+ tagsnode->tags = g_list_prepend (tagsnode->tags,
+ deserialize_tagnode (attribute_names, attribute_values));
+ }
+}
+
+static void
+on_error_cb (GMarkupParseContext * context, GError * error, gpointer user_data)
+{
+ ERROR (MEDIA_DESCRIPTOR_PARSER (user_data)->priv->test,
+ "Error parsing file: %s", error->message);
+}
+
+static const GMarkupParser content_parser = {
+ on_start_element_cb,
+ NULL,
+ NULL,
+ NULL,
+ &on_error_cb
+};
+
+static gboolean
+set_xml_path (MediaDescriptorParser * parser, const gchar * path,
+ GError ** error)
+{
+ gsize xmlsize;
+ GError *err = NULL;
+ MediaDescriptorParserPrivate *priv = parser->priv;
+
+ if (!g_file_get_contents (path, &priv->xmlcontent, &xmlsize, &err))
+ goto failed;
+
+ priv->xmlpath = g_strdup (path);
+ priv->parsecontext = g_markup_parse_context_new (&content_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT, parser, NULL);
+
+ if (g_markup_parse_context_parse (priv->parsecontext, priv->xmlcontent,
+ xmlsize, &err) == FALSE)
+ goto failed;
+
+ return TRUE;
+
+failed:
+ g_propagate_error (error, err);
+ return FALSE;
+}
+
+/* GObject standard vmethods */
+static void
+dispose (MediaDescriptorParser * parser)
+{
+}
+
+static void
+finalize (MediaDescriptorParser * parser)
+{
+ MediaDescriptorParserPrivate *priv;
+
+ priv = parser->priv;
+
+ g_free (priv->xmlpath);
+ g_free (priv->xmlcontent);
+
+ if (priv->filenode)
+ free_filenode (priv->filenode);
+
+ if (priv->parsecontext != NULL)
+ g_markup_parse_context_free (priv->parsecontext);
+}
+
+
+static void
+get_property (GObject * gobject, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ g_assert_not_reached ();
+ }
+
+}
+
+static void
+set_property (GObject * gobject, guint prop_id, const GValue * value,
+ GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+media_descriptor_parser_init (MediaDescriptorParser * parser)
+{
+ MediaDescriptorParserPrivate *priv;
+
+ parser->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (parser,
+ MEDIA_DESCRIPTOR_PARSER_TYPE, MediaDescriptorParserPrivate);
+
+ priv->xmlpath = NULL;
+ priv->filenode = NULL;
+ priv->test = NULL;
+}
+
+static void
+media_descriptor_parser_class_init (MediaDescriptorParserClass * self_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (self_class);
+
+ g_type_class_add_private (self_class, sizeof (MediaDescriptorParserPrivate));
+ object_class->dispose = (void (*)(GObject * object)) dispose;
+ object_class->finalize = (void (*)(GObject * object)) finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+}
+
+/* Public methods */
+MediaDescriptorParser *
+media_descriptor_parser_new (InsanityTest * test,
+ const gchar * xmlpath, GError ** error)
+{
+ MediaDescriptorParser *parser;
+
+ g_return_val_if_fail (INSANITY_IS_TEST (test), NULL);
+
+ parser = g_object_new (MEDIA_DESCRIPTOR_PARSER_TYPE, NULL);
+ parser->priv->test = test;
+
+ if (set_xml_path (parser, xmlpath, error) == FALSE) {
+ g_object_unref (parser);
+
+ return NULL;
+ }
+
+
+ return parser;
+}
+
+gchar *
+media_descriptor_parser_get_xml_path (MediaDescriptorParser * parser)
+{
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), NULL);
+
+ return g_strdup (parser->priv->xmlpath);
+}
+
+gboolean
+media_descriptor_parser_add_stream (MediaDescriptorParser * parser,
+ GstPad * pad)
+{
+ GList *tmp;
+ gboolean ret = FALSE;
+ GstCaps *caps;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ caps = gst_pad_get_caps (pad);
+ for (tmp = parser->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *streamnode = (StreamNode *) tmp->data;
+
+ if (streamnode->pad == NULL && gst_caps_is_equal (streamnode->caps, caps)) {
+ ret = TRUE;
+ streamnode->pad = gst_object_ref (pad);
+
+ goto done;
+ }
+ }
+
+done:
+ if (caps != NULL)
+ gst_caps_unref (caps);
+
+ return ret;
+}
+
+gboolean
+media_descriptor_parser_all_stream_found (MediaDescriptorParser * parser)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ for (tmp = parser->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *streamnode = (StreamNode *) tmp->data;
+
+ if (streamnode->pad == NULL)
+ return FALSE;
+
+ }
+
+ return TRUE;
+}
+
+gboolean
+media_descriptor_parser_add_frame (MediaDescriptorParser * parser,
+ GstPad * pad, GstBuffer * buf, GstBuffer * expected)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ for (tmp = parser->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *streamnode = (StreamNode *) tmp->data;
+
+ if (streamnode->pad == pad && streamnode->cframe) {
+ FrameNode *fnode = streamnode->cframe->data;
+
+ streamnode->cframe = streamnode->cframe->next;
+ return frame_node_compare (fnode, buf, expected);
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+media_descriptor_parser_add_taglist (MediaDescriptorParser * parser,
+ GstTagList * taglist)
+{
+ GList *tmp, *tmptag;
+ TagsNode *tagsnode;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+ g_return_val_if_fail (GST_IS_STRUCTURE (taglist), FALSE);
+
+ for (tmp = parser->priv->filenode->tags; tmp; tmp = tmp->next) {
+ tagsnode = (TagsNode *) tmp->data;
+
+ for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) {
+ if (tag_node_compare ((TagNode *) tmptag->data, taglist)) {
+ LOG (parser->priv->test, "Adding tag %" GST_PTR_FORMAT, taglist);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+media_descriptor_parser_all_tags_found (MediaDescriptorParser * parser)
+{
+ GList *tmp, *tmptag;
+ TagsNode *tagsnode;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ for (tmp = parser->priv->filenode->tags; tmp; tmp = tmp->next) {
+ tagsnode = (TagsNode *) tmp->data;
+
+ for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) {
+ gchar *tag = NULL;
+
+ tag = gst_tag_list_to_string (((TagNode *) tmptag->data)->taglist);
+ if (((TagNode *) tmptag->data)->found == FALSE) {
+
+ if (((TagNode *) tmptag->data)->taglist != NULL) {
+ LOG (parser->priv->test, "Tag not found %s", tag);
+ } else {
+ LOG (parser->priv->test, "Tag not not properly deserialized");
+ }
+
+ ret = FALSE;
+ }
+
+ LOG (parser->priv->test, "Tag properly found found %s", tag);
+ g_free (tag);
+ }
+ }
+
+ return ret;
+}
+
+gboolean
+media_descriptor_parser_detects_frames (MediaDescriptorParser * parser)
+{
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ return parser->priv->filenode->frame_detection;
+}
+
+GstClockTime
+media_descriptor_parser_get_duration (MediaDescriptorParser * parser)
+{
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ return parser->priv->filenode->duration;
+}
+
+gboolean
+media_descriptor_parser_get_seekable (MediaDescriptorParser * parser)
+{
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ return parser->priv->filenode->seekable;
+}
+
+GList *
+media_descriptor_parser_get_buffers (MediaDescriptorParser * parser,
+ GstPad * pad, GCompareFunc compare_func)
+{
+ GList *ret = NULL, *tmpstream, *tmpframe;
+ gboolean check = (pad == NULL);
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_PARSER (parser), FALSE);
+ g_return_val_if_fail (parser->priv->filenode, FALSE);
+
+ for (tmpstream = parser->priv->filenode->streams; tmpstream;
+ tmpstream = tmpstream->next) {
+ StreamNode *streamnode = (StreamNode *) tmpstream->data;
+
+ if (pad && streamnode->pad == pad)
+ check = TRUE;
+
+ if (check) {
+ for (tmpframe = streamnode->frames; tmpframe; tmpframe = tmpframe->next) {
+ if (compare_func)
+ ret =
+ g_list_insert_sorted (ret,
+ gst_buffer_ref (((FrameNode *) tmpframe->data)->buf),
+ compare_func);
+ else
+ ret =
+ g_list_prepend (ret,
+ gst_buffer_ref (((FrameNode *) tmpframe->data)->buf));
+ }
+
+ if (pad != NULL)
+ goto done;
+ }
+ }
+
+
+done:
+ return ret;
+}
+
+GList *
+media_descriptor_parser_get_pads (MediaDescriptorParser * parser)
+{
+ GList *ret = NULL, *tmp;
+
+ for (tmp = parser->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *snode = (StreamNode *) tmp->data;
+ ret = g_list_append (ret, gst_pad_new (snode->padname, GST_PAD_UNKNOWN));
+ }
+
+ return ret;
+}
diff --git a/tests/media-descriptor-parser.h b/tests/media-descriptor-parser.h
new file mode 100644
index 0000000..b370f6e
--- /dev/null
+++ b/tests/media-descriptor-parser.h
@@ -0,0 +1,86 @@
+/**
+ * Insanity QA system
+ *
+ * Copyright (c) 2012, Collabora Ltd
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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.
+ */
+
+#ifndef MEDIA_DESCRIPTOR_PARSER_h
+#define MEDIA_DESCRIPTOR_PARSER_h
+
+#include <glib.h>
+#include <glib-object.h>
+#include <insanity-gst/insanity-gst.h>
+
+G_BEGIN_DECLS
+
+GType media_descriptor_parser_get_type (void);
+
+#define MEDIA_DESCRIPTOR_PARSER_TYPE (media_descriptor_parser_get_type ())
+#define MEDIA_DESCRIPTOR_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MEDIA_DESCRIPTOR_PARSER_TYPE, MediaDescriptorParser))
+#define MEDIA_DESCRIPTOR_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MEDIA_DESCRIPTOR_PARSER_TYPE, MediaDescriptorParserClass))
+#define IS_MEDIA_DESCRIPTOR_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MEDIA_DESCRIPTOR_PARSER_TYPE))
+#define IS_MEDIA_DESCRIPTOR_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MEDIA_DESCRIPTOR_PARSER_TYPE))
+#define MEDIA_DESCRIPTOR_PARSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MEDIA_DESCRIPTOR_PARSER_TYPE, MediaDescriptorParserClass))
+
+typedef struct _MediaDescriptorParserPrivate MediaDescriptorParserPrivate;
+
+
+typedef struct {
+ GObject parent;
+
+ MediaDescriptorParserPrivate *priv;
+} MediaDescriptorParser;
+
+typedef struct {
+
+ GObjectClass parent;
+
+} MediaDescriptorParserClass;
+
+MediaDescriptorParser * media_descriptor_parser_new (InsanityTest *test,
+ const gchar * xmlpath,
+ GError **error);
+
+gchar * media_descriptor_parser_get_xml_path (MediaDescriptorParser *parser);
+
+gboolean media_descriptor_parser_detects_frames (MediaDescriptorParser *parser);
+GstClockTime media_descriptor_parser_get_duration (MediaDescriptorParser *parser);
+gboolean media_descriptor_parser_get_seekable (MediaDescriptorParser * parser);
+
+gboolean media_descriptor_parser_add_stream (MediaDescriptorParser *parser,
+ GstPad *pad);
+gboolean media_descriptor_parser_add_taglist (MediaDescriptorParser *parser,
+ GstTagList *taglist);
+gboolean media_descriptor_parser_all_stream_found (MediaDescriptorParser *parser);
+gboolean media_descriptor_parser_all_tags_found (MediaDescriptorParser *parser);
+
+gboolean media_descriptor_parser_add_frame (MediaDescriptorParser *parser,
+ GstPad *pad,
+ GstBuffer *buf,
+ GstBuffer *expected);
+
+GList * media_descriptor_parser_get_buffers (MediaDescriptorParser * parser,
+ GstPad *pad,
+ GCompareFunc compare_func);
+
+GList * media_descriptor_parser_get_pads (MediaDescriptorParser * parser);
+
+G_END_DECLS
+
+#endif /* MEDIA_DESCRIPTOR_PARSER_h */
diff --git a/tests/media-descriptor-writer.c b/tests/media-descriptor-writer.c
new file mode 100644
index 0000000..c7c6ec4
--- /dev/null
+++ b/tests/media-descriptor-writer.c
@@ -0,0 +1,346 @@
+/**
+ * Gstreamer
+ *
+ * Copyright (c) 2012, Collabora Ltd.
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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 "media-descriptor-writer.h"
+#include "media-descriptor-common.h"
+#include <string.h>
+
+G_DEFINE_TYPE (MediaDescriptorWriter, media_descriptor_writer, G_TYPE_OBJECT);
+
+#define LOG(test, format, args...) \
+ INSANITY_LOG (test, "mediadescwriter", INSANITY_LOG_LEVEL_DEBUG, format, ##args)
+#define ERROR(test, format, args...) \
+ INSANITY_LOG (test, "mediadescwriter", INSANITY_LOG_LEVEL_SPAM, format, ##args)
+
+#define STR_APPEND(arg, nb_white) \
+ tmpstr = res; \
+ res = g_strdup_printf ("%s%*s%s%s", res, (nb_white), " ", (arg), "\n"); \
+ g_free (tmpstr);
+
+#define STR_APPEND0(arg) STR_APPEND((arg), 0)
+#define STR_APPEND1(arg) STR_APPEND((arg), 2)
+#define STR_APPEND2(arg) STR_APPEND((arg), 4)
+#define STR_APPEND3(arg) STR_APPEND((arg), 6)
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ N_PROPERTIES
+};
+
+struct _MediaDescriptorWriterPrivate
+{
+ InsanityTest *test;
+
+ FileNode *filenode;
+
+ GList *serialized_string;
+ guint stream_id;
+};
+
+static void
+finalize (MediaDescriptorWriter * parser)
+{
+ MediaDescriptorWriterPrivate *priv;
+
+ priv = parser->priv;
+
+ if (priv->filenode)
+ free_filenode (priv->filenode);
+}
+
+static void
+get_property (GObject * gobject, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ g_assert_not_reached ();
+ }
+
+}
+
+static void
+set_property (GObject * gobject, guint prop_id, const GValue * value,
+ GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+media_descriptor_writer_init (MediaDescriptorWriter * writer)
+{
+ MediaDescriptorWriterPrivate *priv;
+
+ writer->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (writer,
+ MEDIA_DESCRIPTOR_WRITER_TYPE, MediaDescriptorWriterPrivate);
+
+ priv->filenode = g_slice_new0 (FileNode);
+ priv->test = NULL;
+ priv->serialized_string = NULL;
+ priv->stream_id = 0;
+}
+
+static void
+media_descriptor_writer_class_init (MediaDescriptorWriterClass * self_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (self_class);
+
+ g_type_class_add_private (self_class, sizeof (MediaDescriptorWriterPrivate));
+ object_class->finalize = (void (*)(GObject * object)) finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+}
+
+/* Private methods */
+static gchar *
+serialize_filenode (MediaDescriptorWriter * writer)
+{
+ gchar *res, *tmpstr;
+ GList *tmp, *tmp2;
+ FileNode *filenode = writer->priv->filenode;
+
+ res = g_markup_printf_escaped ("<file duration=\"%" G_GUINT64_FORMAT
+ "\" frame-detection=\"%i\" location=\"%s\" seekable=\"%i\">",
+ filenode->duration, filenode->frame_detection, filenode->location,
+ filenode->seekable);
+
+ STR_APPEND1 ("<streams>");
+ for (tmp = filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *snode = ((StreamNode *) tmp->data);
+
+ STR_APPEND2 (snode->str_open);
+
+ for (tmp2 = snode->frames; tmp2; tmp2 = tmp2->next) {
+ STR_APPEND3 (((FrameNode *) tmp2->data)->str_open);
+ }
+ STR_APPEND2 (snode->str_close);
+ }
+ STR_APPEND1 ("</streams>");
+
+ for (tmp = filenode->tags; tmp; tmp = tmp->next) {
+ TagsNode *tagsnode = ((TagsNode *) tmp->data);
+
+ STR_APPEND1 (tagsnode->str_open);
+ for (tmp2 = tagsnode->tags; tmp2; tmp2 = tmp2->next) {
+ STR_APPEND2 (((TagNode *) tmp2->data)->str_open);
+ }
+ STR_APPEND1 (tagsnode->str_close);
+ }
+
+ STR_APPEND0 (filenode->str_close);
+
+ return res;
+}
+
+/* Public methods */
+MediaDescriptorWriter *
+media_descriptor_writer_new (InsanityTest * test,
+ const gchar * location, GstClockTime duration, gboolean seekable)
+{
+ MediaDescriptorWriter *writer;
+ FileNode *fnode;
+
+ g_return_val_if_fail (INSANITY_IS_TEST (test), NULL);
+
+ writer = g_object_new (MEDIA_DESCRIPTOR_WRITER_TYPE, NULL);
+ writer->priv->test = test;
+
+ fnode = writer->priv->filenode;
+ fnode->location = g_strdup (location);
+ fnode->duration = duration;
+ fnode->seekable = seekable;
+ fnode->str_open = NULL;
+
+ fnode->str_close = g_markup_printf_escaped ("</file>");
+
+ return writer;
+}
+
+gboolean
+media_descriptor_writer_add_stream (MediaDescriptorWriter * writer,
+ GstPad * pad)
+{
+ guint id = 0;
+ GList *tmp;
+ gboolean ret = FALSE;
+ GstCaps *caps;
+ gchar *capsstr = NULL, *padname = NULL;
+ StreamNode *snode = NULL;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_WRITER (writer), FALSE);
+ g_return_val_if_fail (writer->priv->filenode, FALSE);
+
+ caps = gst_pad_get_caps (pad);
+ for (tmp = writer->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *streamnode = (StreamNode *) tmp->data;
+
+ if (streamnode->pad == pad) {
+ goto done;
+ }
+ id++;
+ }
+
+ snode = g_slice_new0 (StreamNode);
+ snode->frames = NULL;
+ snode->cframe = NULL;
+
+ snode->caps = gst_caps_ref (caps);
+ snode->pad = gst_object_ref (pad);
+ snode->id = id;
+
+ capsstr = gst_caps_to_string (caps);
+ padname = gst_pad_get_name (pad);
+ snode->str_open =
+ g_markup_printf_escaped
+ ("<stream padname=\"%s\" caps=\"%s\" id=\"%i\">", padname, capsstr, id);
+
+ snode->str_close = g_markup_printf_escaped ("</stream>");
+
+ writer->priv->filenode->streams =
+ g_list_prepend (writer->priv->filenode->streams, snode);
+
+done:
+ if (caps != NULL)
+ gst_caps_unref (caps);
+ g_free (capsstr);
+ g_free (padname);
+
+ return ret;
+}
+
+gboolean
+media_descriptor_writer_add_taglist (MediaDescriptorWriter * writer,
+ const GstTagList * taglist)
+{
+ gchar *str_str = NULL;
+ TagsNode *tagsnode;
+ TagNode *tagnode;
+ GList *tmp, *tmptag;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_WRITER (writer), FALSE);
+ g_return_val_if_fail (writer->priv->filenode, FALSE);
+ g_return_val_if_fail (GST_IS_STRUCTURE (taglist), FALSE);
+
+ for (tmp = writer->priv->filenode->tags; tmp; tmp = tmp->next) {
+ tagsnode = (TagsNode *) tmp->data;
+
+ for (tmptag = tagsnode->tags; tmptag; tmptag = tmptag->next) {
+ if (tag_node_compare ((TagNode *) tmptag->data, taglist)) {
+ LOG (writer->priv->test,
+ "Tag already in... not adding again %" GST_PTR_FORMAT, taglist);
+ return TRUE;
+ }
+ }
+ }
+
+ if (writer->priv->filenode->tags == NULL) {
+ tagsnode = g_slice_new0 (TagsNode);
+ tagsnode->str_open = g_markup_printf_escaped ("<tags>");
+ tagsnode->str_close = g_markup_printf_escaped ("</tags>");
+ writer->priv->filenode->tags =
+ g_list_prepend (writer->priv->filenode->tags, tagsnode);
+ } else {
+ tagsnode = (TagsNode *) writer->priv->filenode->tags->data;
+ }
+
+ tagnode = g_slice_new0 (TagNode);
+ tagnode->taglist = gst_tag_list_copy (taglist);
+ gst_structure_remove_field (tagnode->taglist, "source-pad");
+ str_str = gst_tag_list_to_string (tagnode->taglist);
+ tagnode->str_open =
+ g_markup_printf_escaped ("<tag content=\"%s\"/>", str_str);
+ tagsnode->tags = g_list_prepend (tagsnode->tags, tagnode);
+
+ g_free (str_str);
+
+ return FALSE;
+}
+
+gboolean
+media_descriptor_writer_add_frame (MediaDescriptorWriter * writer,
+ GstPad * pad, GstBuffer * buf)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_WRITER (writer), FALSE);
+ g_return_val_if_fail (writer->priv->filenode, FALSE);
+
+ writer->priv->filenode->frame_detection = TRUE;
+
+ for (tmp = writer->priv->filenode->streams; tmp; tmp = tmp->next) {
+ StreamNode *streamnode = (StreamNode *) tmp->data;
+
+ if (streamnode->pad == pad) {
+ guint id = g_list_length (streamnode->frames);
+ FrameNode *fnode = g_slice_new0 (FrameNode);
+
+ fnode->id = id;
+ fnode->offset = GST_BUFFER_OFFSET (buf);
+ fnode->offset_end = GST_BUFFER_OFFSET_END (buf);
+ fnode->duration = GST_BUFFER_DURATION (buf);
+ fnode->timestamp = GST_BUFFER_TIMESTAMP (buf);
+ fnode->is_keyframe = (GST_BUFFER_FLAG_IS_SET (buf,
+ GST_BUFFER_FLAG_DELTA_UNIT) == FALSE);
+
+ fnode->str_open =
+ g_markup_printf_escaped (" <frame duration=\"%" G_GUINT64_FORMAT
+ "\" id=\"%i\" is-keyframe=\"%i\" offset=\"%" G_GUINT64_FORMAT
+ "\" offset-end=\"%" G_GUINT64_FORMAT "\" timestamp=\"%"
+ G_GUINT64_FORMAT "\" />", fnode->duration, id, fnode->is_keyframe,
+ fnode->offset, fnode->offset_end, fnode->timestamp);
+
+ fnode->str_close = NULL;
+
+ streamnode->frames = g_list_append (streamnode->frames, fnode);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+gboolean
+media_descriptor_writer_write (MediaDescriptorWriter * writer,
+ const gchar * filename)
+{
+ gboolean ret = FALSE;
+ gchar *serialized;
+
+ g_return_val_if_fail (IS_MEDIA_DESCRIPTOR_WRITER (writer), FALSE);
+ g_return_val_if_fail (writer->priv->filenode, FALSE);
+
+ serialized = serialize_filenode (writer);
+
+ if (g_file_set_contents (filename, serialized, -1, NULL) == TRUE)
+ ret = TRUE;
+
+
+ g_free (serialized);
+
+ return ret;
+}
diff --git a/tests/media-descriptor-writer.h b/tests/media-descriptor-writer.h
new file mode 100644
index 0000000..0d7827e
--- /dev/null
+++ b/tests/media-descriptor-writer.h
@@ -0,0 +1,83 @@
+/**
+ * Insanity QA system
+ *
+ * Copyright (c) 2012, Collabora Ltd
+ * Author: Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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.
+ */
+
+#ifndef MEDIA_DESCRIPTOR_WRITER_h
+#define MEDIA_DESCRIPTOR_WRITER_h
+
+#include <glib.h>
+#include <glib-object.h>
+#include <insanity-gst/insanity-gst.h>
+
+G_BEGIN_DECLS
+
+GType media_descriptor_writer_get_type (void);
+
+#define MEDIA_DESCRIPTOR_WRITER_TYPE (media_descriptor_writer_get_type ())
+#define MEDIA_DESCRIPTOR_WRITER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MEDIA_DESCRIPTOR_WRITER_TYPE, MediaDescriptorWriter))
+#define MEDIA_DESCRIPTOR_WRITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MEDIA_DESCRIPTOR_WRITER_TYPE, MediaDescriptorWriterClass))
+#define IS_MEDIA_DESCRIPTOR_WRITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MEDIA_DESCRIPTOR_WRITER_TYPE))
+#define IS_MEDIA_DESCRIPTOR_WRITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MEDIA_DESCRIPTOR_WRITER_TYPE))
+#define MEDIA_DESCRIPTOR_WRITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MEDIA_DESCRIPTOR_WRITER_TYPE, MediaDescriptorWriterClass))
+
+typedef struct _MediaDescriptorWriterPrivate MediaDescriptorWriterPrivate;
+
+
+typedef struct {
+ GObject parent;
+
+ MediaDescriptorWriterPrivate *priv;
+} MediaDescriptorWriter;
+
+typedef struct {
+
+ GObjectClass parent;
+
+} MediaDescriptorWriterClass;
+
+MediaDescriptorWriter * media_descriptor_writer_new (InsanityTest * test,
+ const gchar *location,
+ GstClockTime duration,
+ gboolean seekable);
+
+gchar * media_descriptor_writer_get_xml_path (MediaDescriptorWriter *writer);
+
+gboolean media_descriptor_writer_detects_frames (MediaDescriptorWriter *writer);
+GstClockTime media_descriptor_writer_get_duration (MediaDescriptorWriter *writer);
+gboolean media_descriptor_writer_get_seekable (MediaDescriptorWriter * writer);
+
+gboolean media_descriptor_writer_add_stream (MediaDescriptorWriter *writer,
+ GstPad *pad);
+gboolean media_descriptor_writer_add_taglist (MediaDescriptorWriter *writer,
+ const GstTagList *taglist);
+gboolean media_descriptor_writer_add_frame (MediaDescriptorWriter *writer,
+ GstPad *pad,
+ GstBuffer *buf);
+gboolean media_descriptor_writer_add_tags (MediaDescriptorWriter *writer,
+ GstPad *pad,
+ GstTagList *taglist);
+gboolean media_descriptor_writer_write (MediaDescriptorWriter * writer,
+ const gchar * filename);
+
+
+G_END_DECLS
+
+#endif /* MEDIA_DESCRIPTOR_WRITER_h */