diff options
author | Thibault Saunier <thibault.saunier@collabora.com> | 2012-05-04 18:07:10 -0400 |
---|---|---|
committer | Thibault Saunier <thibault.saunier@collabora.com> | 2012-05-04 18:07:10 -0400 |
commit | a06fcacd411a04ae6bd796a84f9c52da46d6f8e0 (patch) | |
tree | c93e651d9e6fd68f792998880f98dbc4a89bb166 /tests | |
parent | 8d44a46b50d52b6ef49743bc6ae1f783c686004c (diff) | |
parent | 033a2c77bddccd0f0befa6328fb5ffb1ab0724d4 (diff) |
Merge remote-tracking branch 'origin/0.10'
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 26 | ||||
-rw-r--r-- | tests/insanity-test-gst-decoder.c | 1240 | ||||
-rw-r--r-- | tests/insanity-test-gst-demuxer.c | 1590 | ||||
-rw-r--r-- | tests/insanity-test-gst-subtitles.c | 1113 | ||||
-rw-r--r-- | tests/media-descriptor-common.c | 98 | ||||
-rw-r--r-- | tests/media-descriptor-common.h | 110 | ||||
-rw-r--r-- | tests/media-descriptor-parser.c | 569 | ||||
-rw-r--r-- | tests/media-descriptor-parser.h | 86 | ||||
-rw-r--r-- | tests/media-descriptor-writer.c | 346 | ||||
-rw-r--r-- | tests/media-descriptor-writer.h | 83 |
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 */ |