diff options
author | Alex Ashley <bugzilla@ashley-family.net> | 2015-11-10 13:13:35 +0000 |
---|---|---|
committer | Thiago Santos <thiagoss@osg.samsung.com> | 2015-12-22 11:15:32 -0300 |
commit | ae3ed25025e34ea9b09df59d22d7ebd7294560bc (patch) | |
tree | 1e65aa4ff0c89ec15a10d8469c918f4be76db84a | |
parent | 0e34c02dd60f8d301a1b4c1bbcd3c90ee05bdb0a (diff) | |
download | gstreamer-plugins-bad-ae3ed25025e34ea9b09df59d22d7ebd7294560bc.tar.gz |
dashdemux: tests: Refactor into adaptive_engine components
To allow code from dash_demux.c to be used by other elements
that are based upon GstAdaptiveDemux, the code has been
refactored into four new files:
adaptive_demux_engine.[ch]
adaptive_demux_common.[ch]
The code in adaptive_demux_engine.c provides a generic
test engine for elements based upon GstAdaptiveDemux.
The code in adaptive_demux_common.c provides a set
of utility functions that are common between the tests
for hlsdemux and dashdemux.
As part of the refactoring, variables in structures were
renamed from using camelCase to underscore_case to match other
GStreamer source code.
The fake_http_src was renamed test_http_src and changed to use
callbacks to provide input data and error conditions. Rather than
using an array of input data that tries to encode all the
possible use cases for the GstTestHTTPSrc element, use a struct of
callbacks.
Users of this element are obliged to implement at least the src_start
callback, which provides a way to link from a URI to the settings
for that URI.
-rw-r--r-- | tests/check/Makefile.am | 2 | ||||
-rw-r--r-- | tests/check/elements/adaptive_demux_common.c | 454 | ||||
-rw-r--r-- | tests/check/elements/adaptive_demux_common.h | 208 | ||||
-rw-r--r-- | tests/check/elements/adaptive_demux_engine.c | 473 | ||||
-rw-r--r-- | tests/check/elements/adaptive_demux_engine.h | 185 | ||||
-rw-r--r-- | tests/check/elements/dash_demux.c | 1349 | ||||
-rw-r--r-- | tests/check/elements/fake_http_src.c | 511 | ||||
-rw-r--r-- | tests/check/elements/fake_http_src.h | 61 | ||||
-rw-r--r-- | tests/check/elements/test_http_src.c | 716 | ||||
-rw-r--r-- | tests/check/elements/test_http_src.h | 132 |
10 files changed, 2485 insertions, 1606 deletions
diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 6dd8dec5a..e558782d9 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -456,7 +456,7 @@ elements_dash_demux_LDADD = \ -lgstapp-$(GST_API_VERSION) \ $(top_builddir)/gst-libs/gst/adaptivedemux/libgstadaptivedemux-@GST_API_VERSION@.la -elements_dash_demux_SOURCES = elements/fake_http_src.c elements/fake_http_src.h elements/dash_demux.c +elements_dash_demux_SOURCES = elements/test_http_src.c elements/test_http_src.h elements/adaptive_demux_engine.c elements/adaptive_demux_engine.h elements/adaptive_demux_common.c elements/adaptive_demux_common.h elements/dash_demux.c pipelines_streamheader_CFLAGS = $(GIO_CFLAGS) $(AM_CFLAGS) pipelines_streamheader_LDADD = $(GIO_LIBS) $(LDADD) diff --git a/tests/check/elements/adaptive_demux_common.c b/tests/check/elements/adaptive_demux_common.c new file mode 100644 index 000000000..67d724d82 --- /dev/null +++ b/tests/check/elements/adaptive_demux_common.c @@ -0,0 +1,454 @@ +/* A set of utility functions that are common between elements + * based upon GstAdaptiveDemux + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include "adaptive_demux_engine.h" +#include "adaptive_demux_common.h" + +#define GST_TEST_HTTP_SRC_NAME "testhttpsrc" + +struct _GstAdaptiveDemuxTestCaseClass +{ + GObjectClass parent_class; +}; + +#define gst_adaptive_demux_test_case_parent_class parent_class + +static void gst_adaptive_demux_test_case_dispose (GObject * object); +static void gst_adaptive_demux_test_case_finalize (GObject * object); +static void gst_adaptive_demux_test_case_clear (GstAdaptiveDemuxTestCase * + testData); + +G_DEFINE_TYPE (GstAdaptiveDemuxTestCase, gst_adaptive_demux_test_case, + G_TYPE_OBJECT); + +static void +gst_adaptive_demux_test_case_class_init (GstAdaptiveDemuxTestCaseClass * klass) +{ + GObjectClass *object = G_OBJECT_CLASS (klass); + + object->dispose = gst_adaptive_demux_test_case_dispose; + object->finalize = gst_adaptive_demux_test_case_finalize; +} + +static void +gst_adaptive_demux_test_case_init (GstAdaptiveDemuxTestCase * testData) +{ + testData->output_streams = NULL; + testData->test_task = NULL; + g_rec_mutex_init (&testData->test_task_lock); + g_mutex_init (&testData->test_task_state_lock); + g_cond_init (&testData->test_task_state_cond); + gst_adaptive_demux_test_case_clear (testData); +} + +static void +gst_adaptive_demux_test_case_clear (GstAdaptiveDemuxTestCase * testData) +{ + if (testData->output_streams) { + g_list_free (testData->output_streams); + testData->output_streams = NULL; + } + testData->count_of_finished_streams = 0; + if (testData->test_task) { + gst_task_stop (testData->test_task); + gst_task_join (testData->test_task); + gst_object_unref (testData->test_task); + testData->test_task = NULL; + } + testData->signal_context = NULL; + testData->test_task_state = TEST_TASK_STATE_NOT_STARTED; + testData->threshold_for_seek = 0; + testData->signal_context = NULL; +} + + +static void +gst_adaptive_demux_test_case_dispose (GObject * object) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (object); + + gst_adaptive_demux_test_case_clear (testData); + + GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); +} + +static void +gst_adaptive_demux_test_case_finalize (GObject * object) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (object); + + g_cond_clear (&testData->test_task_state_cond); + g_mutex_clear (&testData->test_task_state_lock); + g_rec_mutex_clear (&testData->test_task_lock); + if (testData->test_task) { + gst_task_stop (testData->test_task); + gst_task_join (testData->test_task); + gst_object_unref (testData->test_task); + testData->test_task = NULL; + } + if (testData->output_streams) { + g_list_free (testData->output_streams); + testData->output_streams = NULL; + } + + GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +/** + * gst_adaptive_demux_test_case_new: + * + * Creates a new #GstAdaptiveDemuxTestCase. Free with g_object_unref(). + * + * Returns: (transfer full): a new #GstAdaptiveDemuxTestCase + */ +GstAdaptiveDemuxTestCase * +gst_adaptive_demux_test_case_new (void) +{ + return g_object_newv (GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE, 0, NULL); +} + + +GstAdaptiveDemuxTestExpectedOutput * +gst_adaptive_demux_test_find_test_data_by_stream (GstAdaptiveDemuxTestCase * + testData, GstAdaptiveDemuxTestOutputStream * stream, guint * index) +{ + gchar *pad_name; + GstAdaptiveDemuxTestExpectedOutput *ret = NULL; + guint count = 0; + + pad_name = gst_pad_get_name (stream->pad); + fail_unless (pad_name != NULL); + for (GList * walk = testData->output_streams; walk; walk = g_list_next (walk)) { + GstAdaptiveDemuxTestExpectedOutput *td = walk->data; + if (strcmp (td->name, pad_name) == 0) { + ret = td; + if (index) + *index = count; + } + ++count; + } + g_free (pad_name); + return ret; +} + +/* function to validate data received by AppSink */ +gboolean +gst_adaptive_demux_test_check_received_data (GstAdaptiveDemuxTestEngine * + engine, GstAdaptiveDemuxTestOutputStream * stream, GstBuffer * buffer, + gpointer user_data) +{ + GstMapInfo info; + guint pattern; + guint64 streamOffset; + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData; + + fail_unless (stream != NULL); + fail_unless (engine->pipeline != NULL); + testOutputStreamData = + gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL); + fail_unless (testOutputStreamData != NULL); + streamOffset = stream->segment_start + stream->segment_received_size; + if (testOutputStreamData->expected_data) { + gsize size = gst_buffer_get_size (buffer); + if (gst_buffer_memcmp (buffer, 0, + &testOutputStreamData->expected_data[streamOffset], size) == 0) { + return TRUE; + } + /* If buffers do not match, fall back to a slower byte-based check + so that the test can output the position where the received data + diverges from expected_data + */ + } + + gst_buffer_map (buffer, &info, GST_MAP_READ); + + GST_DEBUG + ("segment_start = %" G_GUINT64_FORMAT " segment_received_size = %" + G_GUINT64_FORMAT " bufferSize=%d", + stream->segment_start, stream->segment_received_size, (gint) info.size); + + pattern = streamOffset - streamOffset % sizeof (pattern); + for (guint64 i = 0; i != info.size; ++i) { + guint received = info.data[i]; + guint expected; + + if (testOutputStreamData->expected_data) { + fail_unless (streamOffset + i < testOutputStreamData->expected_size); + expected = testOutputStreamData->expected_data[streamOffset + i]; + } else { + gchar pattern_byte_to_read; + + pattern_byte_to_read = (streamOffset + i) % sizeof (pattern); + if (pattern_byte_to_read == 0) { + pattern = streamOffset + i; + } + + expected = (pattern >> (pattern_byte_to_read * 8)) & 0xFF; +#if 0 + GST_DEBUG + ("received '0x%02x' expected '0x%02x' offset %" G_GUINT64_FORMAT + " pattern=%08x byte_to_read=%d", + received, expected, i, pattern, pattern_byte_to_read); +#endif + } + + fail_unless (received == expected, + "output validation failed: received '0x%02x' expected '0x%02x' byte %" + G_GUINT64_FORMAT " offset=%" G_GUINT64_FORMAT "\n", received, expected, + i, streamOffset); + } + + gst_buffer_unmap (buffer, &info); + return TRUE; +} + +/* function to check total size of data received by AppSink + * will be called when AppSink receives eos. + */ +void +gst_adaptive_demux_test_check_size_of_received_data (GstAdaptiveDemuxTestEngine + * engine, GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData; + + testOutputStreamData = + gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL); + fail_unless (testOutputStreamData != NULL); + + fail_unless (stream->total_received_size == + testOutputStreamData->expected_size, + "size validation failed, expected %d received %d", + testOutputStreamData->expected_size, stream->total_received_size); + testData->count_of_finished_streams++; + if (testData->count_of_finished_streams == + g_list_length (testData->output_streams)) { + g_main_loop_quit (engine->loop); + } +} + +typedef struct _SeekTaskContext +{ + GstElement *pipeline; + GstTask *task; +} SeekTaskContext; + +/* function to generate a seek event. Will be run in a separate thread */ +static void +testSeekTaskDoSeek (gpointer user_data) +{ + SeekTaskContext *context = (SeekTaskContext *) user_data; + GstTask *task; + + GST_DEBUG ("testSeekTaskDoSeek calling seek"); + + /* seek to 5ms. + * Because there is only one fragment, we expect the whole file to be + * downloaded again + */ + if (!gst_element_seek_simple (GST_ELEMENT (context->pipeline), + GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, + 5 * GST_MSECOND)) { + fail ("Seek failed!\n"); + } + GST_DEBUG ("seek ok"); + task = context->task; + g_slice_free (SeekTaskContext, context); + gst_task_stop (task); +} + +/* function to be called during seek test when demux sends data to AppSink + * It monitors the data sent and after a while will generate a seek request. + */ +static gboolean +testSeekAdaptiveDemuxSendsData (GstAdaptiveDemuxTestEngine * engine, + GstAdaptiveDemuxTestOutputStream * stream, + GstBuffer * buffer, gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + SeekTaskContext *seekContext; + GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData; + guint index = 0; + + testOutputStreamData = + gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, + &index); + fail_unless (testOutputStreamData != NULL); + /* first entry in testData->output_streams is the + PAD on which to perform the seek */ + if (index == 0 && + testData->test_task == NULL && + (stream->total_received_size + stream->segment_received_size) >= + testData->threshold_for_seek) { + testData->threshold_for_seek = + stream->total_received_size + stream->segment_received_size; + + /* the seek will be to the beginning of the file, so expect to receive + * threshold_for_seek + a whole file + */ + testOutputStreamData->expected_size += testData->threshold_for_seek; + + GST_DEBUG ("starting seek task"); + + g_mutex_lock (&testData->test_task_state_lock); + testData->test_task_state = + TEST_TASK_STATE_WAITING_FOR_TESTSRC_STATE_CHANGE; + g_mutex_unlock (&testData->test_task_state_lock); + + seekContext = g_slice_new (SeekTaskContext); + seekContext->pipeline = engine->pipeline; + testData->test_task = seekContext->task = + gst_task_new ((GstTaskFunction) testSeekTaskDoSeek, seekContext, NULL); + gst_task_set_lock (testData->test_task, &testData->test_task_lock); + gst_task_start (testData->test_task); + + GST_DEBUG ("seek task started"); + + g_mutex_lock (&testData->test_task_state_lock); + + GST_DEBUG ("waiting for seek task to change state on testsrc"); + + /* wait for test_task to run, send a flush start event to AppSink + * and change the testhttpsrc element state from PLAYING to PAUSED + */ + while (testData->test_task_state == + TEST_TASK_STATE_WAITING_FOR_TESTSRC_STATE_CHANGE) { + g_cond_wait (&testData->test_task_state_cond, + &testData->test_task_state_lock); + } + g_mutex_unlock (&testData->test_task_state_lock); + /* we can continue now, but this buffer will be rejected by AppSink + * because it is in flushing mode + */ + GST_DEBUG ("seek task changed state on testsrc, resuming"); + } + + return TRUE; +} + +/* callback called when main_loop detects a state changed event */ +static void +testSeekOnStateChanged (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstState old_state, new_state; + const char *srcName = GST_OBJECT_NAME (msg->src); + + gst_message_parse_state_changed (msg, &old_state, &new_state, NULL); + GST_DEBUG ("Element %s changed state from %s to %s", + GST_OBJECT_NAME (msg->src), + gst_element_state_get_name (old_state), + gst_element_state_get_name (new_state)); + + if (strstr (srcName, "bin") == srcName && + old_state == GST_STATE_PLAYING && new_state == GST_STATE_PAUSED) { + g_mutex_lock (&testData->test_task_state_lock); + if (testData->test_task_state == + TEST_TASK_STATE_WAITING_FOR_TESTSRC_STATE_CHANGE) { + GST_DEBUG ("changing test_task_state"); + testData->test_task_state = TEST_TASK_STATE_EXITING; + g_cond_signal (&testData->test_task_state_cond); + } + g_mutex_unlock (&testData->test_task_state_lock); + } +} + +/* + * Issue a seek request after media segment has started to be downloaded + * on the first pad listed in GstAdaptiveDemuxTestOutputStreamData and the + * first chunk of at least one byte has already arrived in AppSink + */ +static void +testSeekPreTestCallback (GstAdaptiveDemuxTestEngine * engine, + gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstBus *bus; + + /* register a callback to listen for state change events */ + bus = gst_pipeline_get_bus (GST_PIPELINE (engine->pipeline)); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message::state-changed", + G_CALLBACK (testSeekOnStateChanged), testData); +} + +/* function to check total size of data received by AppSink + * will be called when AppSink receives eos. + */ +void gst_adaptive_demux_test_download_error_size_of_received_data + (GstAdaptiveDemuxTestEngine * engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData; + + testOutputStreamData = + gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL); + fail_unless (testOutputStreamData != NULL); + /* expect to receive more than 0 */ + fail_unless (stream->total_received_size > 0, + "size validation failed for %s, expected > 0, received %d", + testOutputStreamData->name, stream->total_received_size); + + /* expect to receive less than file size */ + fail_unless (stream->total_received_size < + testOutputStreamData->expected_size, + "size validation failed for %s, expected < %d received %d", + testOutputStreamData->name, testOutputStreamData->expected_size, + stream->total_received_size); + if (testData->count_of_finished_streams == + g_list_length (testData->output_streams)) { + g_main_loop_quit (engine->loop); + } +} + +void +gst_adaptive_demux_test_seek (const gchar * element_name, + const gchar * manifest_uri, GstAdaptiveDemuxTestCase * testData) +{ + GstAdaptiveDemuxTestCallbacks cb = { 0 }; + cb.appsink_received_data = gst_adaptive_demux_test_check_received_data; + cb.appsink_eos = gst_adaptive_demux_test_check_size_of_received_data; + cb.pre_test = testSeekPreTestCallback; + cb.demux_sent_data = testSeekAdaptiveDemuxSendsData; + gst_adaptive_demux_test_run (element_name, manifest_uri, &cb, testData); + /* the call to g_object_unref of testData will clean up the seek task */ +} + +void +gst_adaptive_demux_test_setup (void) +{ + GstRegistry *registry; + gboolean ret; + + registry = gst_registry_get (); + ret = gst_test_http_src_register_plugin (registry, GST_TEST_HTTP_SRC_NAME); + fail_unless (ret); +} + +void +gst_adaptive_demux_test_teardown (void) +{ + gst_test_http_src_install_callbacks (NULL, NULL); + gst_test_http_src_set_default_blocksize (0); +} diff --git a/tests/check/elements/adaptive_demux_common.h b/tests/check/elements/adaptive_demux_common.h new file mode 100644 index 000000000..3969582b3 --- /dev/null +++ b/tests/check/elements/adaptive_demux_common.h @@ -0,0 +1,208 @@ +/* A set of utility functions that are common between elements + * based upon GstAdaptiveDemux + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_ADAPTIVE_DEMUX_COMMON_TEST_H__ +#define __GST_ADAPTIVE_DEMUX_COMMON_TEST_H__ + +#include <gst/gst.h> +#include "adaptive_demux_engine.h" + +G_BEGIN_DECLS + +#define GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE \ + (gst_adaptive_demux_test_case_get_type()) +#define GST_ADAPTIVE_DEMUX_TEST_CASE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE, GstAdaptiveDemuxTestCase)) +#define GST_ADAPTIVE_DEMUX_TEST_CASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE, GstAdaptiveDemuxTestCaseClass)) +#define GST_ADAPTIVE_DEMUX_TEST_CASE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE, GstAdaptiveDemuxTestCaseClass)) +#define GST_IS_ADAPTIVE_DEMUX_TEST_CASE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE)) +#define GST_IS_ADAPTIVE_DEMUX_TEST_CASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ADAPTIVE_DEMUX_TEST_CASE)) + +/** + * TestTaskState: + * The seek test uses a separate task to perform the seek operation. + * After starting the task, the caller blocks until the seek task + * flushes the AppSink and changes the GstFakeSoupHTTPSrc element state from + * PLAYING to PAUSED. + * When that event is detected, the caller is allowed to resume. + * Any data that will be sent to AppSink after resume will be rejected because + * AppSink is in flushing mode. + */ +typedef enum +{ + TEST_TASK_STATE_NOT_STARTED, + TEST_TASK_STATE_WAITING_FOR_TESTSRC_STATE_CHANGE, + TEST_TASK_STATE_EXITING, +} TestTaskState; + +/** + * GstAdaptiveDemuxTestExpectedOutput: + * Structure used to store output stream related data. + * It is used by the test during output validation. + * The fields are set by the testcase function prior + * to starting the test. + */ +typedef struct _GstAdaptiveDemuxTestExpectedOutput +{ + /* the name of the demux src pad generating this stream */ + const char *name; + /* the expected size on this stream */ + guint64 expected_size; + /* the expected data on this stream (optional) */ + const guint8* expected_data; +} GstAdaptiveDemuxTestExpectedOutput; + +typedef struct _GstAdaptiveDemuxTestCaseClass GstAdaptiveDemuxTestCaseClass; +typedef struct _GstAdaptiveDemuxTestCase +{ + GObject parent; + + /* output data used to validate the test + * list of GstAdaptiveDemuxTestExpectedOutput, one entry per stream + */ + GList *output_streams; /*GList<GstAdaptiveDemuxTestExpectedOutput>*/ + + /* the number of streams that finished. + * Main thread will stop the pipeline when all streams are finished + * (i.e. count_of_finished_streams == g_list_length(output_streams) ) + */ + guint count_of_finished_streams; + + /* taskTesk... is a set of variables that can be used by a test + * that needs to perform operations from another thread + * For example, it is used by the seek test to perform the seek + * operation + */ + GstTask *test_task; + GRecMutex test_task_lock; + TestTaskState test_task_state; + GMutex test_task_state_lock; + GCond test_task_state_cond; + + /* seek test will wait for this amount of bytes to be sent by + * demux to AppSink before triggering a seek request + */ + guint64 threshold_for_seek; + + gpointer signal_context; +} GstAdaptiveDemuxTestCase; + +/* high-level unit test functions */ + +/** + * gst_adaptive_demux_test_setup: + * Causes the test HTTP src element to be registered + */ +void gst_adaptive_demux_test_setup (void); + +void gst_adaptive_demux_test_teardown (void); + +GType gst_adaptive_demux_test_case_get_type (void); + +/** + * gst_adaptive_demux_test_case_new: creates new #GstAdaptiveDemuxTestCase + * object. Use #g_object_unref to free. + */ +GstAdaptiveDemuxTestCase * gst_adaptive_demux_test_case_new (void) G_GNUC_MALLOC; + +/** + * gst_adaptive_demux_test_seek: test that element supports seeking + * @element_name: The name of the demux element (e.g. "dashdemux") + * @manifest_uri: The URI of the manifest to load + * @testData: The #GstAdaptiveDemuxTestCase that the test expects the + * demux element to produce. + * + * Creates a pipeline and starts it. Once data is flowing, request a + * seek to almost the start of the stream. + */ +void gst_adaptive_demux_test_seek (const gchar * element_name, + const gchar * manifest_uri, + GstAdaptiveDemuxTestCase *testData); + +/* Utility functions for use within a unit test */ + +/** + * gst_adaptive_demux_test_check_size_of_received_data: + * @engine: The #GstAdaptiveDemuxTestEngine that caused this callback + * @stream: The #GstAdaptiveDemuxTestOutputStream that caused this callback + * @user_data: A pointer to a #GstAdaptiveDemuxTestCase object + * + * This function can be used as an EOS callback to check that the + * size of received data equals expected_size. It should be used with + * a test that expects the entire file to be downloaded. + */ +void gst_adaptive_demux_test_check_size_of_received_data( + GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + +/** + * gst_adaptive_demux_test_download_error_size_of_received_data: + * @engine: The #GstAdaptiveDemuxTestEngine that caused this callback + * @stream: The #GstAdaptiveDemuxTestOutputStream that caused this callback + * @user_data: A pointer to a #GstAdaptiveDemuxTestCase object + * + * This function can be used as an EOS callback to check that the + * size of received data is >0 && <expected_size. It should be used with + * a test that does not expect the entire file to be downloaded. + */ +void gst_adaptive_demux_test_download_error_size_of_received_data ( + GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + +/** + * gst_adaptive_demux_test_check_received_data: + * @engine: The #GstAdaptiveDemuxTestEngine that caused this callback + * @stream: The #GstAdaptiveDemuxTestOutputStream that caused this callback + * @buffer: The #GstBuffer containing the data to check + * @user_data: A pointer to a #GstAdaptiveDemuxTestCase object + * Returns: TRUE if buffer contains the expected data. + * + * This function can be used as an appSinkGotData callback, to check + * that the contents of the received data matches the expected data + */ +gboolean gst_adaptive_demux_test_check_received_data ( + GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, + GstBuffer * buffer, + gpointer user_data); + +/** + * gst_adaptive_demux_test_find_test_data_by_stream: + * @testData: The #GstAdaptiveDemuxTestCase object that contains the + * output_streams list to search + * @stream: the #GstAdaptiveDemuxTestOutputStream to search for + * @index: (out) (allow none) the index of the entry in output_streams + * Returns: The #GstAdaptiveDemuxTestExpectedOutput that matches @stream, or + * %NULL if not found + * + * Search the list of output test data for the entry that matches stream. + */ +GstAdaptiveDemuxTestExpectedOutput * gst_adaptive_demux_test_find_test_data_by_stream ( + GstAdaptiveDemuxTestCase * testData, + GstAdaptiveDemuxTestOutputStream * stream, + guint * index); + +G_END_DECLS +#endif /* __GST_ADAPTIVE_DEMUX_COMMON_TEST_H__ */ diff --git a/tests/check/elements/adaptive_demux_engine.c b/tests/check/elements/adaptive_demux_engine.c new file mode 100644 index 000000000..9c7770822 --- /dev/null +++ b/tests/check/elements/adaptive_demux_engine.c @@ -0,0 +1,473 @@ +/* A generic test engine for elements based upon GstAdaptiveDemux + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include "adaptive_demux_engine.h" + +typedef struct _GstAdaptiveDemuxTestEnginePrivate +{ + GstAdaptiveDemuxTestEngine engine; + const GstAdaptiveDemuxTestCallbacks *callbacks; + gpointer user_data; +} GstAdaptiveDemuxTestEnginePrivate; + + +#define GST_TEST_GET_LOCK(d) (&(((GstAdaptiveDemuxTestEnginePrivate*)(d))->engine.lock)) +#define GST_TEST_LOCK(d) g_mutex_lock (GST_TEST_GET_LOCK (d)) +#define GST_TEST_UNLOCK(d) g_mutex_unlock (GST_TEST_GET_LOCK (d)) + +static void +adaptive_demux_engine_stream_state_finalize (gpointer data) +{ + GstAdaptiveDemuxTestOutputStream *stream = + (GstAdaptiveDemuxTestOutputStream *) data; + if (stream->appsink) + gst_object_unref (stream->appsink); + if (stream->pad) + gst_object_unref (stream->pad); + if (stream->internal_pad) + gst_object_unref (stream->internal_pad); + g_slice_free (GstAdaptiveDemuxTestOutputStream, stream); +} + +/* get the testOutput entry in testData corresponding to the given AppSink */ +static GstAdaptiveDemuxTestOutputStream * +getTestOutputDataByAppsink (GstAdaptiveDemuxTestEnginePrivate * priv, + GstAppSink * appsink) +{ + for (guint i = 0; i < priv->engine.output_streams->len; ++i) { + GstAdaptiveDemuxTestOutputStream *state; + state = g_ptr_array_index (priv->engine.output_streams, i); + if (state->appsink == appsink) { + return state; + } + } + ck_abort_msg ("cannot find appsink %p in the output data", appsink); + return NULL; +} + +/* get the output stream entry in corresponding to the given Pad */ +static GstAdaptiveDemuxTestOutputStream * +getTestOutputDataByPad (GstAdaptiveDemuxTestEnginePrivate * priv, + GstPad * pad, gboolean abort_if_not_found) +{ + for (guint i = 0; i < priv->engine.output_streams->len; ++i) { + GstAdaptiveDemuxTestOutputStream *stream; + stream = g_ptr_array_index (priv->engine.output_streams, i); + if (stream->internal_pad == pad || stream->pad == pad) { + return stream; + } + } + if (abort_if_not_found) + ck_abort_msg ("cannot find pad %p in the output data", pad); + return NULL; +} + +/* callback called when AppSink receives data */ +static GstFlowReturn +on_appSinkNewSample (GstAppSink * appsink, gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GstAdaptiveDemuxTestEngine *engine; + GstAdaptiveDemuxTestOutputStream *testOutputStream = NULL; + GstSample *sample; + GstBuffer *buffer; + gboolean ret = TRUE; + + fail_unless (priv != NULL); + GST_TEST_LOCK (priv); + engine = &priv->engine; + testOutputStream = getTestOutputDataByAppsink (priv, appsink); + + sample = gst_app_sink_pull_sample (appsink); + fail_unless (sample != NULL); + buffer = gst_sample_get_buffer (sample); + fail_unless (buffer != NULL); + + /* call the test callback, if registered */ + if (priv->callbacks->appsink_received_data) + ret = priv->callbacks->appsink_received_data (engine, + testOutputStream, buffer, priv->user_data); + + testOutputStream->segment_received_size += gst_buffer_get_size (buffer); + + gst_sample_unref (sample); + + GST_TEST_UNLOCK (priv); + + if (!ret) + return GST_FLOW_EOS; + + return GST_FLOW_OK; +} + +/* callback called when AppSink receives eos */ +static void +on_appSinkEOS (GstAppSink * appsink, gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GstAdaptiveDemuxTestOutputStream *testOutputStream = NULL; + + fail_unless (priv != NULL); + GST_TEST_LOCK (priv); + testOutputStream = getTestOutputDataByAppsink (priv, appsink); + + testOutputStream->total_received_size += + testOutputStream->segment_received_size; + testOutputStream->segment_received_size = 0; + + if (priv->callbacks->appsink_eos) + priv->callbacks->appsink_eos (&priv->engine, + testOutputStream, priv->user_data); + + GST_TEST_UNLOCK (priv); +} + +/* callback called when demux sends data to AppSink */ +static GstPadProbeReturn +on_demux_sent_data (GstPad * pad, GstPadProbeInfo * info, gpointer data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) data; + GstAdaptiveDemuxTestOutputStream *stream = NULL; + GstBuffer *buffer; + + buffer = GST_PAD_PROBE_INFO_BUFFER (info); + + GST_TEST_LOCK (priv); + stream = getTestOutputDataByPad (priv, pad, TRUE); + + if (priv->callbacks->demux_sent_data) { + (*priv->callbacks->demux_sent_data) (&priv->engine, + stream, buffer, priv->user_data); + } + + GST_TEST_UNLOCK (priv); + return GST_PAD_PROBE_OK; +} + +/* callback called when demux receives events from GstFakeSoupHTTPSrc */ +static GstPadProbeReturn +on_demuxReceivesEvent (GstPad * pad, GstPadProbeInfo * info, gpointer data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) data; + GstAdaptiveDemuxTestOutputStream *stream = NULL; + GstEvent *event; + const GstSegment *segment; + + event = GST_PAD_PROBE_INFO_EVENT (info); + GST_DEBUG ("Received event %" GST_PTR_FORMAT " on pad %" GST_PTR_FORMAT, + event, pad); + + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + /* a new segment will start arriving + * update segment_start used by pattern validation + */ + gst_event_parse_segment (event, &segment); + + GST_TEST_LOCK (priv); + stream = getTestOutputDataByPad (priv, pad, TRUE); + stream->total_received_size += stream->segment_received_size; + stream->segment_received_size = 0; + stream->segment_start = segment->start; + GST_TEST_UNLOCK (priv); + } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GST_TEST_LOCK (priv); + stream = getTestOutputDataByPad (priv, pad, TRUE); + if (priv->callbacks->demux_sent_eos) { + priv->callbacks->demux_sent_eos (&priv->engine, stream, priv->user_data); + } + GST_TEST_UNLOCK (priv); + } + + return GST_PAD_PROBE_OK; +} + +/* callback called when demux creates a src pad. + * We will create an AppSink to get the data + */ +static void +on_demuxNewPad (GstElement * demux, GstPad * pad, gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GstElement *pipeline; + GstElement *sink; + gboolean ret; + gchar *name; + GstPad *internal_pad; + GstAppSinkCallbacks appSinkCallbacks; + GstAdaptiveDemuxTestOutputStream *stream; + GObjectClass *gobject_class; + + fail_unless (priv != NULL); + name = gst_pad_get_name (pad); + + stream = g_slice_new0 (GstAdaptiveDemuxTestOutputStream); + GST_DEBUG ("created pad %p", pad); + + sink = gst_element_factory_make ("appsink", name); + g_free (name); + fail_unless (sink != NULL); + + GST_TEST_LOCK (priv); + + /* register the AppSink pointer in the test output data */ + gst_object_ref (sink); + stream->appsink = GST_APP_SINK (sink); + + appSinkCallbacks.eos = on_appSinkEOS; + appSinkCallbacks.new_preroll = NULL; + appSinkCallbacks.new_sample = on_appSinkNewSample; + + gst_app_sink_set_callbacks (GST_APP_SINK (sink), &appSinkCallbacks, priv, + NULL); + + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) on_demux_sent_data, priv, NULL); + gobject_class = G_OBJECT_GET_CLASS (sink); + if (g_object_class_find_property (gobject_class, "sync")) { + GST_DEBUG ("Setting sync=FALSE on AppSink"); + g_object_set (G_OBJECT (sink), "sync", FALSE, NULL); + } + stream->pad = gst_object_ref (pad); + internal_pad = + GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (pad))); + + gst_pad_add_probe (internal_pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) on_demuxReceivesEvent, priv, NULL); + + /* keep the reference to the internal_pad. + * We will need it to identify the stream in the on_demuxReceivesEvent callback + */ + stream->internal_pad = internal_pad; + + g_ptr_array_add (priv->engine.output_streams, stream); + GST_TEST_UNLOCK (priv); + + pipeline = GST_ELEMENT (gst_element_get_parent (demux)); + fail_unless (pipeline != NULL); + ret = gst_bin_add (GST_BIN (pipeline), sink); + fail_unless_equals_int (ret, TRUE); + gst_object_unref (pipeline); + ret = gst_element_link (demux, sink); + fail_unless_equals_int (ret, TRUE); + ret = gst_element_sync_state_with_parent (sink); + fail_unless_equals_int (ret, TRUE); + GST_TEST_LOCK (priv); + if (priv->callbacks->demux_pad_added) { + priv->callbacks->demux_pad_added (&priv->engine, stream, priv->user_data); + } + GST_TEST_UNLOCK (priv); +} + +/* callback called when demux removes a src pad. + * We remove the AppSink associated with this pad + */ +static void +on_demuxPadRemoved (GstElement * demux, GstPad * pad, gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GstAdaptiveDemuxTestOutputStream *stream = NULL; + + fail_unless (priv != NULL); + + GST_DEBUG ("Pad removed: %" GST_PTR_FORMAT, pad); + + GST_TEST_LOCK (priv); + stream = getTestOutputDataByPad (priv, pad, FALSE); + if (stream) { + GstStateChangeReturn ret; + GstState currentState, pending; + GstElement *appSink; + + if (priv->callbacks->demux_pad_removed) { + priv->callbacks->demux_pad_removed (&priv->engine, stream, + priv->user_data); + } + fail_unless (stream->appsink != NULL); + fail_unless (stream->internal_pad != NULL); + gst_object_unref (stream->internal_pad); + stream->internal_pad = NULL; + appSink = GST_ELEMENT (stream->appsink); + ret = gst_element_get_state (appSink, ¤tState, &pending, 0); + if ((ret == GST_STATE_CHANGE_SUCCESS && currentState == GST_STATE_PLAYING) + || (ret == GST_STATE_CHANGE_ASYNC && pending == GST_STATE_PLAYING)) { + GST_DEBUG ("Changing AppSink element to PAUSED"); + gst_element_set_state (appSink, GST_STATE_PAUSED); + } + } + GST_TEST_UNLOCK (priv); +} + +/* callback called when main_loop detects an error message + * We will signal main loop to quit + */ +static void +on_ErrorMessageOnBus (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GError *err = NULL; + gchar *dbg_info = NULL; + + gst_message_parse_error (msg, &err, &dbg_info); + GST_DEBUG ("ERROR from element %s: '%s'. Debugging info: %s", + GST_OBJECT_NAME (msg->src), err->message, (dbg_info) ? dbg_info : "none"); + g_error_free (err); + g_free (dbg_info); + + GST_TEST_LOCK (priv); + + fail_unless (priv->callbacks->bus_error_message, + "unexpected error detected on bus"); + + priv->callbacks->bus_error_message (&priv->engine, msg, priv->user_data); + + GST_TEST_UNLOCK (priv); +} + +static gboolean +start_pipeline_playing (gpointer user_data) +{ + GstAdaptiveDemuxTestEnginePrivate *priv = + (GstAdaptiveDemuxTestEnginePrivate *) user_data; + GstStateChangeReturn stateChange; + + GST_DEBUG ("Moving pipeline to PLAYING state"); + stateChange = + gst_element_set_state (priv->engine.pipeline, GST_STATE_PLAYING); + fail_unless (stateChange != GST_STATE_CHANGE_FAILURE); + GST_DEBUG ("PLAYING stateChange = %d", stateChange); + return FALSE; +} + +/* + * Create a demux element, run a test using the input data and check + * the output data + */ +void +gst_adaptive_demux_test_run (const gchar * element_name, + const gchar * manifest_uri, + const GstAdaptiveDemuxTestCallbacks * callbacks, gpointer user_data) +{ + GstBus *bus; + GstElement *demux; + GstElement *manifest_source; + gboolean ret; + GstStateChangeReturn stateChange; + GstAdaptiveDemuxTestEnginePrivate *priv; + + priv = g_slice_new0 (GstAdaptiveDemuxTestEnginePrivate); + priv->engine.output_streams = + g_ptr_array_new_with_free_func + (adaptive_demux_engine_stream_state_finalize); + g_mutex_init (&priv->engine.lock); + priv->callbacks = callbacks; + priv->user_data = user_data; + priv->engine.loop = g_main_loop_new (NULL, TRUE); + fail_unless (priv->engine.loop != NULL); + GST_TEST_LOCK (priv); + priv->engine.pipeline = gst_pipeline_new ("pipeline"); + fail_unless (priv->engine.pipeline != NULL); + GST_DEBUG ("created pipeline %" GST_PTR_FORMAT, priv->engine.pipeline); + + /* register a callback to listen for error messages */ + bus = gst_pipeline_get_bus (GST_PIPELINE (priv->engine.pipeline)); + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + g_signal_connect (bus, "message::error", + G_CALLBACK (on_ErrorMessageOnBus), priv); + + manifest_source = + gst_element_make_from_uri (GST_URI_SRC, manifest_uri, NULL, NULL); + fail_unless (manifest_source != NULL); + priv->engine.manifest_source = manifest_source; + + demux = gst_check_setup_element (element_name); + fail_unless (demux != NULL); + priv->engine.demux = demux; + GST_DEBUG ("created demux %" GST_PTR_FORMAT, demux); + + g_signal_connect (demux, "pad-added", G_CALLBACK (on_demuxNewPad), priv); + g_signal_connect (demux, "pad-removed", + G_CALLBACK (on_demuxPadRemoved), priv); + + gst_bin_add_many (GST_BIN (priv->engine.pipeline), manifest_source, demux, + NULL); + ASSERT_OBJECT_REFCOUNT (manifest_source, element_name, 1); + ASSERT_OBJECT_REFCOUNT (demux, element_name, 1); + + ret = gst_element_link (manifest_source, demux); + fail_unless_equals_int (ret, TRUE); + + /* call a test callback before we start the pipeline */ + if (callbacks->pre_test) + (*callbacks->pre_test) (&priv->engine, priv->user_data); + + GST_TEST_UNLOCK (priv); + + GST_DEBUG ("Starting pipeline"); + stateChange = gst_element_set_state (priv->engine.pipeline, GST_STATE_PAUSED); + fail_unless (stateChange != GST_STATE_CHANGE_FAILURE); + /* wait for completion of the move to PAUSED */ + stateChange = gst_element_get_state (priv->engine.pipeline, NULL, NULL, + GST_CLOCK_TIME_NONE); + fail_unless (stateChange != GST_STATE_CHANGE_FAILURE); + + g_idle_add ((GSourceFunc) start_pipeline_playing, priv); + + /* block until a callback calls g_main_loop_quit (engine.loop) */ + GST_DEBUG ("main thread waiting for streams to finish"); + g_main_loop_run (priv->engine.loop); + GST_DEBUG ("main thread finished. Stopping pipeline"); + + /* no need to use gst_element_get_state as the move the GST_STATE_NULL + is always synchronous */ + stateChange = gst_element_set_state (priv->engine.pipeline, GST_STATE_NULL); + fail_unless (stateChange != GST_STATE_CHANGE_FAILURE); + + GST_TEST_LOCK (priv); + + /* call a test callback after the stop of the pipeline */ + if (callbacks->post_test) + (*callbacks->post_test) (&priv->engine, priv->user_data); + + g_signal_handlers_disconnect_by_func (bus, + G_CALLBACK (on_ErrorMessageOnBus), priv); + gst_bus_remove_signal_watch (bus); + g_signal_handlers_disconnect_by_func (demux, G_CALLBACK (on_demuxNewPad), + priv); + g_signal_handlers_disconnect_by_func (demux, G_CALLBACK (on_demuxPadRemoved), + priv); + + GST_DEBUG ("main thread pipeline stopped"); + gst_object_unref (priv->engine.pipeline); + priv->engine.pipeline = NULL; + g_main_loop_unref (priv->engine.loop); + g_ptr_array_unref (priv->engine.output_streams); + + GST_TEST_UNLOCK (priv); + g_mutex_clear (&priv->engine.lock); + g_slice_free (GstAdaptiveDemuxTestEnginePrivate, priv); +} diff --git a/tests/check/elements/adaptive_demux_engine.h b/tests/check/elements/adaptive_demux_engine.h new file mode 100644 index 000000000..267d7e860 --- /dev/null +++ b/tests/check/elements/adaptive_demux_engine.h @@ -0,0 +1,185 @@ +/* A generic test engine for elements based upon GstAdaptiveDemux + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_ADAPTIVE_DEMUX_TEST_ENGINE_H__ +#define __GST_ADAPTIVE_DEMUX_TEST_ENGINE_H__ + +#include <gst/gst.h> +#include <gst/app/gstappsink.h> +#include "test_http_src.h" + +G_BEGIN_DECLS + +typedef struct _GstAdaptiveDemuxTestEngine GstAdaptiveDemuxTestEngine; + +typedef struct _GstAdaptiveDemuxTestOutputStream { + /* the GstAppSink element getting the data for this stream */ + GstAppSink *appsink; + GstPad *pad; + /* the internal pad of adaptivedemux element used to send data to the GstAppSink element */ + GstPad *internal_pad; + /* current segment start offset */ + guint64 segment_start; + /* the size received so far on this segment */ + guint64 segment_received_size; + /* the total size received so far on this stream, excluding current segment */ + guint64 total_received_size; +} GstAdaptiveDemuxTestOutputStream; + +/* GstAdaptiveDemuxTestCallbacks: contains various callbacks that can + * be registered by a test. Not all callbacks needs to be configured + * by a test. A callback that is not required by a test must be set + * to NULL. + */ +typedef struct _GstAdaptiveDemuxTestCallbacks +{ + /** + * pre_test: called before starting the pipeline + * @engine: #GstAdaptiveDemuxTestEngine + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + */ + void (*pre_test) (GstAdaptiveDemuxTestEngine *engine, gpointer user_data); + + /** + * post_test: called after stopping the pipeline. + * @engine: #GstAdaptiveDemuxTestEngine + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + */ + void (*post_test) (GstAdaptiveDemuxTestEngine *engine, gpointer user_data); + + /** + * appsink_received_data: called each time AppSink receives data + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: #GstAdaptiveDemuxTestOutputStream + * @buffer: the #GstBuffer that was recevied by #GstAppSink + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + * Returns: #TRUE to continue processing, #FALSE to cause EOS + * + * Can be used by a test to perform additional operations (eg validate + * output data) + */ + gboolean (*appsink_received_data) (GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, + GstBuffer * buffer, gpointer user_data); + + /** + * appsink_eos: called each time AppSink receives eos + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: #GstAdaptiveDemuxTestOutputStream + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + * + * Can be used by a test to perform additional operations (eg validate + * output data) + */ + void (*appsink_eos) (GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + + /** + * demux_pad_added: called each time the demux creates a new pad + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: the #GstAdaptiveDemuxTestOutputStream that has been created + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + */ + void (*demux_pad_added) (GstAdaptiveDemuxTestEngine * engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + + /** + * demux_pad_removed: called each time the demux removes a pad + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: the #GstAdaptiveDemuxTestOutputStream that will no longer + * be used + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + */ + void (*demux_pad_removed) (GstAdaptiveDemuxTestEngine * engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + + /** + * demux_sent_data: called each time the demux sends data to AppSink + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: #GstAdaptiveDemuxTestOutputStream + * @buffer: the #GstBuffer that was sent by demux + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + */ + gboolean (*demux_sent_data) (GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, + GstBuffer * buffer, gpointer user_data); + + /** + * demux_sent_eos: called each time demux send an EOS event + * @engine: #GstAdaptiveDemuxTestEngine + * @stream: #GstAdaptiveDemuxTestOutputStream + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + * Can be used by a test to perform additional operations (eg validate + * output data) + */ + void (*demux_sent_eos) (GstAdaptiveDemuxTestEngine *engine, + GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data); + + /** + * bus_error_message: called if an error is posted to the bus + * @engine: #GstAdaptiveDemuxTestEngine + * @msg: the #GstMessage that contains the error + * @user_data: the user_data passed to gst_adaptive_demux_test_run() + * + * The callback can decide if this error is expected, or to fail + * the test + */ + void (*bus_error_message)(GstAdaptiveDemuxTestEngine *engine, + GstMessage * msg, gpointer user_data); +} GstAdaptiveDemuxTestCallbacks; + +/* structure containing all data used by a test + * Any callback defined by a test will receive this as first parameter + */ +struct _GstAdaptiveDemuxTestEngine +{ + GstElement *pipeline; + GstElement *demux; + GstElement *manifest_source; + GMainLoop *loop; + GPtrArray *output_streams; /* GPtrArray<GstAdaptiveDemuxTestOutputStream> */ + /* mutex to lock accesses to this structure when data is shared + * between threads */ + GMutex lock; +}; + +/** + * gst_adaptive_demux_test_run: + * @element_name: The name of the demux element (e.g. "dashdemux") + * @manifest_uri: The URI of the manifest to load + * @callbacks: The callbacks to use while the test is in operating + * @user_data: Opaque pointer that is passed to every callback + * + * Creates a pipeline with the specified demux element in it, + * connect a testhttpsrc element to this demux element and + * request manifest_uri. When the demux element adds a new + * pad, the engine will create an AppSink element and attach + * it to this pad. + * + * Information about these pads is collected in + * GstAdaptiveDemuxTestEngine::output_streams + */ +void gst_adaptive_demux_test_run (const gchar * element_name, + const gchar * manifest_uri, + const GstAdaptiveDemuxTestCallbacks * callbacks, + gpointer user_data); + +G_END_DECLS +#endif /* __GST_ADAPTIVE_DEMUX_TEST_ENGINE_H__ */ diff --git a/tests/check/elements/dash_demux.c b/tests/check/elements/dash_demux.c index 55f5c8cda..ce7556fb7 100644 --- a/tests/check/elements/dash_demux.c +++ b/tests/check/elements/dash_demux.c @@ -19,659 +19,79 @@ */ #include <gst/check/gstcheck.h> -#include <gst/app/gstappsink.h> -#include "fake_http_src.h" +#include "adaptive_demux_common.h" -#define GST_FAKE_SOUP_HTTP_SRC_NAME "fake-soup-http-src" +#define DEMUX_ELEMENT_NAME "dashdemux" -/* forward declarations */ -struct _GstDashDemuxTestData; +#define COPY_OUTPUT_TEST_DATA(outputTestData,testData) do { \ + guint otdPos, otdLen = sizeof((outputTestData)) / sizeof((outputTestData)[0]); \ + for(otdPos=0; otdPos<otdLen; ++otdPos){ \ + (testData)->output_streams = g_list_append ((testData)->output_streams, &(outputTestData)[otdPos]); \ + } \ + } while(0) -/* the seek test will use a separate task to perform the seek operation. - * After starting the task, the caller will block until the seek task - * flushes the AppSink and changes the GstFakeSoupHTTPSrc element state from - * PLAYING to PAUSED. - * When that event is detected, the caller is allowed to resume. - * Any data that will be sent to AppSink after resume will be rejected because - * AppSink is in flushing mode. - */ -typedef enum -{ - SEEK_TASK_STATE_NOT_STARTED, - SEEK_TASK_STATE_WAITING_FOR_FAKESRC_STATE_CHANGE, - SEEK_TASK_STATE_EXITING, -} SeekTaskState; - -/* scratchData space used by tests to store output stream related data. - * Structure is set to 0 when the test begins. - * If a test does not use some of the fields, they will remain 0. - * As new tests will want new functionality, this structure can gain new - * members without affecting existing tests. - */ -typedef struct _GstDashDemuxTestOutputStreamScratchData -{ - /* the GstAppSink element getting the data for this stream */ - GstAppSink *appsink; - /* the internal pad of adaptivedemux element used to send data to the GstAppSink element */ - GstPad *internalPad; - /* current segment start offset */ - guint64 segmentStart; - /* the size received so far on this segment */ - guint64 segmentReceivedSize; - /* the total size received so far on this stream, excluding current segment */ - guint64 totalReceivedSize; -} GstDashDemuxTestOutputStreamScratchData; - -/* structure to store output stream related data. - * Will be used by the test during output validation. - * The fields are set by the user in the test using static arrays. - * The scratchData field is set to a NULL pointer and later allocated by the - * test and initialised with 0. - */ -typedef struct _GstDashDemuxTestOutputStreamData -{ - /* the name of the dashdemux src pad generating this stream */ - const char *name; - /* the expected size on this stream */ - guint expectedSize; - /* stream level scratchData space to be used by test */ - GstDashDemuxTestOutputStreamScratchData *scratchData; -} GstDashDemuxTestOutputStreamData; - -/* callback that will be called before starting the pipeline. - */ -typedef void (*PreTestCallback) (struct _GstDashDemuxTestData * testData); - -/* callback that will be called after stopping the pipeline. - */ -typedef void (*PostTestCallback) (struct _GstDashDemuxTestData * testData); - -/* callback that will be called each time AppSink received data. - * Can be used by a test to perform additional operations (eg validate - * output data) - */ -typedef gboolean (*AppSinkGotDataCallback) (struct _GstDashDemuxTestData * - testData, GstDashDemuxTestOutputStreamData * testOutputStreamData, - GstBuffer * buffer); - -/* callback that will be called each time AppSink received eos. - * Can be used by a test to perform additional operations (eg validate - * output data) - */ -typedef gboolean (*AppSinkGotEosCallback) (struct _GstDashDemuxTestData * - testData, GstDashDemuxTestOutputStreamData * testOutputStreamData); - -/* callback that will be called each time dashdemux sends data to AppSink - */ -typedef gboolean (*DashdemuxSendsDataCallback) (struct _GstDashDemuxTestData - * testData, GstDashDemuxTestOutputStreamData * testOutputStreamData, - GstBuffer * buffer); - -/* structure containing various callbacks that can be registered by a test - * Not all callbacks needs to be configured by a test. - * As new tests will want new functionality, this structure can gain new - * members without affecting existing tests. - */ -typedef struct -{ - PreTestCallback preTestCallback; - PostTestCallback postTestCallback; - AppSinkGotDataCallback appSinkGotDataCallback; - AppSinkGotEosCallback appSinkGotEosCallback; - DashdemuxSendsDataCallback dashdemuxSendsDataCallback; -} Callbacks; - -/* scratchData space used by tests to store data. - * Structure is set to 0 when the test begins. - * If a test does not use some of the fields, they will remain 0. - * As new tests will want new functionality, this structure can gain new - * members without affecting existing tests. - */ -typedef struct _GstDashDemuxTestScratchData -{ - GstElement *pipeline; - GstElement *dashdemux; - GstElement *mpd_source; - GMainLoop *loop; - - /* task used by the seek test to perform the seek operation */ - GstTask *seekTask; - GRecMutex seekTaskLock; - - /* the state of the seekTask */ - SeekTaskState seekTaskState; - GMutex seekTaskStateLock; - GCond seekTaskStateCond; - - /* seek test will wait for this amount of bytes to be sent by dash to AppSink - * before triggering a seek request - */ - guint thresholdForSeek; - -} GstDashDemuxTestScratchData; - -/* structure containing all data used by a test - * Any callback defined by a test will receive this as first parameter - */ -typedef struct _GstDashDemuxTestData -{ - /* input data to configure the GstFakeSoupHTTPSrc element - * Null terminaled array of GstFakeHttpSrcInputData, one entry per stream - */ - const GstFakeHttpSrcInputData *inputStreamArray; - - /* output data used to validate the test - * array of GstDashDemuxTestOutputStreamData, one entry per stream - */ - GstDashDemuxTestOutputStreamData *outputStreamArray; - guint outputStreamArraySize; - - /* test level scratchData data to be used by test */ - GstDashDemuxTestScratchData *scratchData; - - /* mutex to lock accesses to this structure when data is shared between threads */ - GMutex lockTestData; - - /* the number of streams that finished. - * Main thread will stop the pipeline when all streams are finished - * (countStreamFinished == outputStreamArraySize) - */ - guint countStreamFinished; - - /* true if an error is expected on pipeline - * If this is not set and an error is received, the test is failed - */ - gboolean expectError; - - /* callbacks to be registered by test to influence the pipeline */ - const Callbacks *callbacks; - -} GstDashDemuxTestData; - -#define GST_TEST_GET_LOCK(d) (&(((GstDashDemuxTestData*)(d))->lockTestData)) -#define GST_TEST_LOCK(d) g_mutex_lock (GST_TEST_GET_LOCK (d)) -#define GST_TEST_UNLOCK(d) g_mutex_unlock (GST_TEST_GET_LOCK (d)) - -/* global pointer to test data, set before each test. - * We need it global so that gst_fake_soup_http_src element can get the data. - * Due to this, tests cannot be run in parallel. -*/ -static GstDashDemuxTestData *g_testData = NULL; - -static void -test_setup (void) -{ - GstRegistry *registry; - gboolean ret; - - registry = gst_registry_get (); - ret = - gst_fake_soup_http_src_register_plugin (registry, - GST_FAKE_SOUP_HTTP_SRC_NAME); - fail_unless (ret); -} - -static void -test_teardown (void) -{ -} - -/* get the testOutput entry in testData corresponding to the given AppSink */ -static GstDashDemuxTestOutputStreamData * -getTestOutputDataByAppsink (GstDashDemuxTestData * testData, - GstAppSink * appsink) -{ - for (guint i = 0; i < testData->outputStreamArraySize; ++i) { - if (testData->outputStreamArray[i].scratchData->appsink == appsink) { - return &testData->outputStreamArray[i]; - } - } - ck_abort_msg ("cannot find appsink %p in the output data", appsink); - return NULL; -} - -/* get the testOutput entry in testData corresponding to the given stream name */ -static GstDashDemuxTestOutputStreamData * -getTestOutputDataByName (GstDashDemuxTestData * testData, const char *name) +typedef struct _GstDashDemuxTestInputData { - for (guint i = 0; i < testData->outputStreamArraySize; ++i) { - if (strcmp (testData->outputStreamArray[i].name, name) == 0) { - return &testData->outputStreamArray[i]; - } - } - ck_abort_msg ("cannot find stream '%s' in the output data", name); - return NULL; -} + const gchar *uri; + const guint8 *payload; + guint64 size; +} GstDashDemuxTestInputData; -/* get the testOutput entry in testData corresponding to the given internal pad */ -static GstDashDemuxTestOutputStreamData * -getTestOutputDataByPad (GstDashDemuxTestData * testData, const GstPad * pad) -{ - for (guint i = 0; i < testData->outputStreamArraySize; ++i) { - if (testData->outputStreamArray[i].scratchData->internalPad == pad) { - return &testData->outputStreamArray[i]; +static gboolean +gst_dashdemux_http_src_start (GstTestHTTPSrc * src, + const gchar * uri, GstTestHTTPSrcInput * input_data, gpointer user_data) +{ + const GstDashDemuxTestInputData *input = + (const GstDashDemuxTestInputData *) user_data; + for (guint i = 0; input[i].uri; ++i) { + if (strcmp (input[i].uri, uri) == 0) { + input_data->context = (gpointer) & input[i]; + input_data->size = input[i].size; + if (input[i].size == 0) + input_data->size = strlen ((gchar *) input[i].payload); + return TRUE; } } - ck_abort_msg ("cannot find pad '%p' in the output data", pad); - return NULL; + return FALSE; } -/* callback called when AppSink receives data */ static GstFlowReturn -on_appSinkNewSample (GstAppSink * appsink, gpointer user_data) +gst_dashdemux_http_src_create (GstTestHTTPSrc * src, + guint64 offset, + guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data) { - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) user_data; - GstDashDemuxTestOutputStreamData *testOutputStreamData = NULL; - GstSample *sample; - GstBuffer *buffer; - gboolean ret = TRUE; - - fail_unless (testData != NULL); - - GST_TEST_LOCK (testData); - - testOutputStreamData = getTestOutputDataByAppsink (testData, appsink); - - sample = gst_app_sink_pull_sample (appsink); - buffer = gst_sample_get_buffer (sample); - - /* call the test callback, if registered */ - if (testData->callbacks->appSinkGotDataCallback) - ret = testData->callbacks->appSinkGotDataCallback (testData, - testOutputStreamData, buffer); - - testOutputStreamData->scratchData->segmentReceivedSize += - gst_buffer_get_size (buffer); - - gst_sample_unref (sample); - - GST_TEST_UNLOCK (testData); - - if (!ret) - return GST_FLOW_EOS; - - return GST_FLOW_OK; -} - -/* callback called when AppSink receives eos */ -static void -on_appSinkEOS (GstAppSink * appsink, gpointer user_data) -{ - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) user_data; - GstDashDemuxTestOutputStreamData *testOutputStreamData = NULL; - gboolean ret = TRUE; - - fail_unless (testData != NULL); + /* const GstDashDemuxTestInputData *input = + (const GstDashDemuxTestInputData *) user_data; */ + const GstDashDemuxTestInputData *input = + (const GstDashDemuxTestInputData *) context; + GstBuffer *buf; - GST_TEST_LOCK (testData); - - testOutputStreamData = getTestOutputDataByAppsink (testData, appsink); - - testOutputStreamData->scratchData->totalReceivedSize += - testOutputStreamData->scratchData->segmentReceivedSize; - testOutputStreamData->scratchData->segmentReceivedSize = 0; - - if (testData->callbacks->appSinkGotEosCallback) - ret = testData->callbacks->appSinkGotEosCallback (testData, - testOutputStreamData); - - if (ret) { - /* signal to the application that another stream has finished */ - testData->countStreamFinished++; - - if (testData->countStreamFinished == testData->outputStreamArraySize) { - g_main_loop_quit (testData->scratchData->loop); - } + buf = gst_buffer_new_allocate (NULL, length, NULL); + fail_if (buf == NULL, "Not enough memory to allocate buffer"); + if (input->payload) { + gst_buffer_fill (buf, 0, input->payload + offset, length); } else { - /* ignore this eos event, the stream is not finished yet */ - } - - GST_TEST_UNLOCK (testData); -} - -/* callback called when dash sends data to AppSink */ -static GstPadProbeReturn -on_dashSendsData (GstPad * pad, GstPadProbeInfo * info, gpointer data) -{ - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) data; - GstDashDemuxTestOutputStreamData *testOutputStreamData = NULL; - GstBuffer *buffer; - char *streamName; - - buffer = GST_PAD_PROBE_INFO_BUFFER (info); - - GST_TEST_LOCK (testData); - - streamName = gst_pad_get_name (pad); - testOutputStreamData = getTestOutputDataByName (testData, streamName); - g_free (streamName); - - if (testData->callbacks->dashdemuxSendsDataCallback) { - (*testData->callbacks->dashdemuxSendsDataCallback) (testData, - testOutputStreamData, buffer); - } - - GST_TEST_UNLOCK (testData); - - return GST_PAD_PROBE_OK; -} - -/* callback called when dash receives events from GstFakeSoupHTTPSrc */ -static GstPadProbeReturn -on_dashReceivesEvent (GstPad * pad, GstPadProbeInfo * info, gpointer data) -{ - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) data; - GstDashDemuxTestOutputStreamData *testOutputStreamData = NULL; - GstEvent *event; - const GstSegment *segment; - - event = GST_PAD_PROBE_INFO_EVENT (info); - GST_DEBUG ("dash received event %" GST_PTR_FORMAT " on pad %p", event, pad); - - if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { - /* a new segment will start arriving - * update segmentStart used by pattern validation - */ - gst_event_parse_segment (event, &segment); - - GST_TEST_LOCK (testData); + GstMapInfo info; + guint pattern; - testOutputStreamData = getTestOutputDataByPad (testData, pad); + pattern = offset - offset % sizeof (pattern); - testOutputStreamData->scratchData->totalReceivedSize += - testOutputStreamData->scratchData->segmentReceivedSize; - testOutputStreamData->scratchData->segmentReceivedSize = 0; - testOutputStreamData->scratchData->segmentStart = segment->start; - - GST_TEST_UNLOCK (testData); - } - - return GST_PAD_PROBE_OK; -} - -/* callback called when dashdemux creates a src pad. - * We will create an AppSink to get the data - */ -static void -on_dashNewPad (GstElement * dashdemux, GstPad * pad, gpointer data) -{ - GstElement *pipeline; - GstElement *sink; - gboolean ret; - char *name; - GstPad *internalPad; - GstAppSinkCallbacks appSinkCallbacks; - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) data; - GstDashDemuxTestOutputStreamData *testOutputStreamData = NULL; - - fail_unless (testData != NULL); - name = gst_pad_get_name (pad); - - GST_DEBUG ("created pad %p", pad); - - sink = gst_element_factory_make ("appsink", name); - fail_unless (sink != NULL); - - GST_TEST_LOCK (testData); - - testOutputStreamData = getTestOutputDataByName (testData, name); - g_free (name); - - /* register the AppSink pointer in the test output data */ - gst_object_ref (sink); - testOutputStreamData->scratchData->appsink = GST_APP_SINK (sink); - - appSinkCallbacks.eos = on_appSinkEOS; - appSinkCallbacks.new_preroll = NULL; - appSinkCallbacks.new_sample = on_appSinkNewSample; - - gst_app_sink_set_callbacks (GST_APP_SINK (sink), &appSinkCallbacks, testData, - NULL); - - gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, - (GstPadProbeCallback) on_dashSendsData, testData, NULL); - - internalPad = GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (pad))); - - gst_pad_add_probe (internalPad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - (GstPadProbeCallback) on_dashReceivesEvent, testData, NULL); - - /* keep the reference to the internalPad. - * We will need it to identify the stream in the on_dashReceivesEvent callback - */ - testOutputStreamData->scratchData->internalPad = internalPad; - - GST_TEST_UNLOCK (testData); - - pipeline = GST_ELEMENT (gst_element_get_parent (dashdemux)); - fail_unless (pipeline != NULL); - ret = gst_bin_add (GST_BIN (pipeline), sink); - fail_unless_equals_int (ret, TRUE); - gst_object_unref (pipeline); - ret = gst_element_link (dashdemux, sink); - fail_unless_equals_int (ret, TRUE); - ret = gst_element_sync_state_with_parent (sink); - fail_unless_equals_int (ret, TRUE); - -} - -/* callback called when main_loop detects an error message - * We will signal main loop to quit - */ -static void -on_ErrorMessageOnBus (GstBus * bus, GstMessage * msg, gpointer data) -{ - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) data; - GError *err = NULL; - gchar *dbg_info = NULL; - - gst_message_parse_error (msg, &err, &dbg_info); - GST_DEBUG ("ERROR from element %s: '%s'. Debugging info: %s", - GST_OBJECT_NAME (msg->src), err->message, (dbg_info) ? dbg_info : "none"); - g_error_free (err); - g_free (dbg_info); - - GST_TEST_LOCK (testData); - - fail_unless (testData->expectError == TRUE, - "unexpected error detected on bus"); - - g_main_loop_quit (testData->scratchData->loop); - - GST_TEST_UNLOCK (testData); -} - -/* - * Create a dashdemux element, run a test using the input data and check - * the output data - */ -static void -runTest (const GstFakeHttpSrcInputData * inputStreamArray, - GstDashDemuxTestOutputStreamData * outputStreamArray, - guint outputStreamArraySize, const Callbacks * callbacks) -{ - GstElement *pipeline; - GstBus *bus; - GstElement *dashdemux; - GstElement *mpd_source; - gboolean ret; - GstStateChangeReturn stateChange; - GstDashDemuxTestData *testData; - - testData = g_slice_new0 (GstDashDemuxTestData); - testData->inputStreamArray = inputStreamArray; - testData->outputStreamArray = outputStreamArray; - testData->outputStreamArraySize = outputStreamArraySize; - g_mutex_init (&testData->lockTestData); - testData->callbacks = callbacks; - - /* allocate the scratchData space */ - testData->scratchData = g_slice_new0 (GstDashDemuxTestScratchData); - for (guint i = 0; i < outputStreamArraySize; ++i) { - outputStreamArray[i].scratchData = - g_slice_new0 (GstDashDemuxTestOutputStreamScratchData); - } - - fail_unless (g_testData == NULL); - g_testData = testData; - gst_fake_soup_http_src_set_input_data (inputStreamArray); - - testData->scratchData->loop = g_main_loop_new (NULL, TRUE); - - GST_TEST_LOCK (testData); - - pipeline = gst_pipeline_new ("pipeline"); - fail_unless (pipeline != NULL); - testData->scratchData->pipeline = pipeline; - GST_DEBUG ("created pipeline %p", pipeline); - - /* register a callback to listen for error messages */ - bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); - gst_bus_add_signal_watch (bus); - g_signal_connect (bus, "message::error", - G_CALLBACK (on_ErrorMessageOnBus), testData); - - dashdemux = gst_check_setup_element ("dashdemux"); - fail_unless (dashdemux != NULL); - testData->scratchData->dashdemux = dashdemux; - GST_DEBUG ("created dash %p", dashdemux); - - g_signal_connect (dashdemux, "pad-added", G_CALLBACK (on_dashNewPad), - testData); - - ret = gst_bin_add (GST_BIN (pipeline), dashdemux); - fail_unless_equals_int (ret, TRUE); - - /* assume first entry in inputStreamArray configures the mpd stream */ - mpd_source = - gst_element_make_from_uri (GST_URI_SRC, inputStreamArray[0].uri, NULL, - NULL); - fail_unless (mpd_source != NULL); - testData->scratchData->mpd_source = mpd_source; - - ret = gst_bin_add (GST_BIN (pipeline), mpd_source); - fail_unless_equals_int (ret, TRUE); - - ret = gst_element_link (mpd_source, dashdemux); - fail_unless_equals_int (ret, TRUE); - - /* call a test callback before we start the pipeline */ - if (callbacks->preTestCallback) - (*callbacks->preTestCallback) (testData); - - GST_TEST_UNLOCK (testData); - - GST_DEBUG ("starting pipeline"); - gst_element_set_state (pipeline, GST_STATE_PLAYING); - stateChange = gst_element_get_state (pipeline, NULL, NULL, - GST_CLOCK_TIME_NONE); - fail_unless_equals_int (stateChange, GST_STATE_CHANGE_SUCCESS); - - /* block until all output streams received an eos event */ - GST_DEBUG ("main thread waiting for streams to finish"); - g_main_loop_run (testData->scratchData->loop); - GST_DEBUG ("main thread all streams finished. Stopping pipeline"); - - g_main_loop_unref (testData->scratchData->loop); - - stateChange = gst_element_set_state (pipeline, GST_STATE_NULL); - fail_unless_equals_int (stateChange, GST_STATE_CHANGE_SUCCESS); - GST_DEBUG ("main thread pipeline stopped"); - gst_object_unref (pipeline); - - GST_TEST_LOCK (testData); - - /* call a test callback after the stop of the pipeline */ - if (callbacks->postTestCallback) - (*callbacks->postTestCallback) (testData); - - GST_TEST_UNLOCK (testData); - - for (guint i = 0; i < outputStreamArraySize; ++i) { - if (outputStreamArray[i].scratchData->appsink) - gst_object_unref (outputStreamArray[i].scratchData->appsink); - - if (outputStreamArray[i].scratchData->internalPad) - gst_object_unref (outputStreamArray[i].scratchData->internalPad); - - g_slice_free (GstDashDemuxTestOutputStreamScratchData, - outputStreamArray[i].scratchData); - } - g_slice_free (GstDashDemuxTestScratchData, testData->scratchData); - - g_mutex_clear (&testData->lockTestData); - g_testData = NULL; - g_slice_free (GstDashDemuxTestData, testData); -} - -/******************** Dash demux test engine definition ends here *************/ - -/******************** Test specific code starts here **************************/ - -/* function to validate data received by AppSink */ -static gboolean -checkDataReceived (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData, GstBuffer * buffer) -{ - GstMapInfo info; - guint pattern; - guint64 streamOffset; - - gst_buffer_map (buffer, &info, GST_MAP_READ); - - GST_DEBUG - ("segmentStart = %" G_GUINT64_FORMAT " segmentReceivedSize = %" - G_GUINT64_FORMAT " bufferSize=%d", - testOutputStreamData->scratchData->segmentStart, - testOutputStreamData->scratchData->segmentReceivedSize, (gint) info.size); - - streamOffset = testOutputStreamData->scratchData->segmentStart + - testOutputStreamData->scratchData->segmentReceivedSize; - pattern = streamOffset - streamOffset % sizeof (pattern); - for (guint64 i = 0; i != info.size; ++i) { - guint received = info.data[i]; - guint expected; - gchar pattern_byte_to_read; - - pattern_byte_to_read = (streamOffset + i) % sizeof (pattern); - if (pattern_byte_to_read == 0) { - pattern = streamOffset + i; + gst_buffer_map (buf, &info, GST_MAP_WRITE); + for (guint64 i = 0; i < length; ++i) { + gchar pattern_byte_to_write = (offset + i) % sizeof (pattern); + if (pattern_byte_to_write == 0) { + pattern = offset + i; + } + info.data[i] = (pattern >> (pattern_byte_to_write * 8)) & 0xFF; } - expected = (pattern >> (pattern_byte_to_read * 8)) & 0xFF; - -/* - GST_DEBUG - ("received '0x%02x' expected '0x%02x' offset %" G_GUINT64_FORMAT - " pattern=%08x byte_to_read=%d", - received, expected, i, pattern, pattern_byte_to_read); -*/ - - fail_unless (received == expected, - "output validation failed: received '0x%02x' expected '0x%02x' offset %" - G_GUINT64_FORMAT " pattern=%08x byte_to_read=%d\n", received, expected, - i, pattern, pattern_byte_to_read); + gst_buffer_unmap (buf, &info); } - - gst_buffer_unmap (buffer, &info); - return TRUE; + *retbuf = buf; + return GST_FLOW_OK; } -/* function to check total size of data received by AppSink - * will be called when AppSink receives eos. - */ -static gboolean -checkSizeOfDataReceived (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData) -{ - fail_unless (testOutputStreamData->scratchData->totalReceivedSize == - testOutputStreamData->expectedSize, - "size validation failed for %s, expected %d received %d", - testOutputStreamData->name, testOutputStreamData->expectedSize, - testOutputStreamData->scratchData->totalReceivedSize); - - return TRUE; -} +/******************** Test specific code starts here **************************/ /* * Test an mpd with an audio and a video stream @@ -721,26 +141,34 @@ GST_START_TEST (simpleTest) " <Initialization range=\"0-233\" />" " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {"http://unit.test/audio.webm", NULL, 5000}, {"http://unit.test/video.webm", NULL, 9000}, {NULL, NULL, 0}, }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { {"audio_00", 5000, NULL}, - {"video_00", 9000, NULL}, + {"video_00", 9000, NULL} }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = checkDataReceived; - cb.appSinkGotEosCallback = checkSizeOfDataReceived; + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); - runTest (inputTestData, - outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]), - &cb); + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.appsink_received_data = + gst_adaptive_demux_test_check_received_data; + test_callbacks.appsink_eos = + gst_adaptive_demux_test_check_size_of_received_data; + + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, "http://unit.test/test.mpd", + &test_callbacks, testData); + g_object_unref (testData); } GST_END_TEST; @@ -828,30 +256,37 @@ GST_START_TEST (testTwoPeriods) " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {"http://unit.test/audio1.webm", NULL, 5001}, {"http://unit.test/video1.webm", NULL, 9001}, {"http://unit.test/audio2.webm", NULL, 5002}, {"http://unit.test/video2.webm", NULL, 9002}, {NULL, NULL, 0}, }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { {"audio_00", 5001, NULL}, {"video_00", 9001, NULL}, {"audio_01", 5002, NULL}, {"video_01", 9002, NULL}, }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = checkDataReceived; - cb.appSinkGotEosCallback = checkSizeOfDataReceived; - - runTest (inputTestData, - outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]), - &cb); + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.appsink_received_data = + gst_adaptive_demux_test_check_received_data; + test_callbacks.appsink_eos = + gst_adaptive_demux_test_check_size_of_received_data; + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, + "http://unit.test/test.mpd", &test_callbacks, testData); + gst_object_unref (testData); } GST_END_TEST; @@ -909,24 +344,25 @@ do \ } while (0) static void -setAndTestDashParams (GstDashDemuxTestData * testData) +setAndTestDashParams (GstAdaptiveDemuxTestEngine * engine, gpointer user_data) { - GstElement *dashdemux = testData->scratchData->dashdemux; + /* GstAdaptiveDemuxTestCase * testData = (GstAdaptiveDemuxTestCase*)user_data; */ + GObject *dashdemux = G_OBJECT (engine->demux); - test_int_prop (G_OBJECT (dashdemux), "connection-speed", 1000); - test_invalid_int_prop (G_OBJECT (dashdemux), "connection-speed", 4294967 + 1); + test_int_prop (dashdemux, "connection-speed", 1000); + test_invalid_int_prop (dashdemux, "connection-speed", 4294967 + 1); - test_float_prop (G_OBJECT (dashdemux), "bitrate-limit", 1); - test_invalid_float_prop (G_OBJECT (dashdemux), "bitrate-limit", 2.1); + test_float_prop (dashdemux, "bitrate-limit", 1); + test_invalid_float_prop (dashdemux, "bitrate-limit", 2.1); - test_int_prop (G_OBJECT (dashdemux), "max-buffering-time", 15); - test_invalid_int_prop (G_OBJECT (dashdemux), "max-buffering-time", 1); + test_int_prop (dashdemux, "max-buffering-time", 15); + test_invalid_int_prop (dashdemux, "max-buffering-time", 1); - test_float_prop (G_OBJECT (dashdemux), "bandwidth-usage", 0.5); - test_invalid_float_prop (G_OBJECT (dashdemux), "bandwidth-usage", 2); + test_float_prop (dashdemux, "bandwidth-usage", 0.5); + test_invalid_float_prop (dashdemux, "bandwidth-usage", 2); - test_int_prop (G_OBJECT (dashdemux), "max-bitrate", 1000); - test_invalid_int_prop (G_OBJECT (dashdemux), "max-bitrate", 10); + test_int_prop (dashdemux, "max-bitrate", 1000); + test_invalid_int_prop (dashdemux, "max-bitrate", 10); } /* @@ -963,215 +399,36 @@ GST_START_TEST (testParameters) " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {"http://unit.test/audio.webm", NULL, 5000}, {NULL, NULL, 0}, }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { {"audio_00", 5000, NULL}, }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; - Callbacks cb = { 0 }; - cb.preTestCallback = setAndTestDashParams; - cb.appSinkGotDataCallback = checkDataReceived; - cb.appSinkGotEosCallback = checkSizeOfDataReceived; + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.pre_test = setAndTestDashParams; + test_callbacks.appsink_received_data = + gst_adaptive_demux_test_check_received_data; + test_callbacks.appsink_eos = + gst_adaptive_demux_test_check_size_of_received_data; - runTest (inputTestData, - outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]), - &cb); + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, "http://unit.test/test.mpd", + &test_callbacks, testData); + gst_object_unref (testData); } GST_END_TEST; -/* check data received by AppSink when eos is received */ -static gboolean -testSeekCheckSizeOfDataReceived (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData) -{ - guint64 expectedSize; - - GST_DEBUG ("checkDataReceivedTestSeek for %s received %" G_GUINT64_FORMAT, - testOutputStreamData->name, - testOutputStreamData->scratchData->totalReceivedSize); - - /* the seek was to the beginning of the file, so expect to receive - * thresholdForSeek + a whole file - */ - expectedSize = testData->scratchData->thresholdForSeek + - testOutputStreamData->expectedSize; - fail_unless (testOutputStreamData->scratchData->totalReceivedSize == - expectedSize, - "size validation failed for %s, expected %" G_GUINT64_FORMAT " received %" - G_GUINT64_FORMAT, testOutputStreamData->name, expectedSize, - testOutputStreamData->scratchData->totalReceivedSize); - - return TRUE; -} - -/* function to generate a seek event. Will be run in a separate thread */ -static void -testSeekTaskDoSeek (GstDashDemuxTestData * testData) -{ - GST_DEBUG ("testSeekTaskDoSeek calling seek"); - - /* seek to 5ms. - * Because there is only one fragment, we expect the whole file to be - * downloaded again - */ - if (!gst_element_seek_simple (GST_ELEMENT (testData->scratchData->pipeline), - GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, - 5 * GST_MSECOND)) { - fail ("Seek failed!\n"); - } - GST_DEBUG ("seek ok"); - gst_task_stop (testData->scratchData->seekTask); -} - -/* function to be called during seek test when dash sends data to AppSink - * It monitors the data sent and after a while will generate a seek request. - */ -static gboolean -testSeekDashdemuxSendsData (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData, GstBuffer * buffer) -{ - if (strcmp (testOutputStreamData->name, "audio_00") == 0 && - testData->scratchData->seekTask == NULL && - testOutputStreamData->scratchData->totalReceivedSize + - testOutputStreamData->scratchData->segmentReceivedSize >= - testData->scratchData->thresholdForSeek) { - - GST_DEBUG ("starting seek task"); - - g_mutex_lock (&testData->scratchData->seekTaskStateLock); - testData->scratchData->seekTaskState = - SEEK_TASK_STATE_WAITING_FOR_FAKESRC_STATE_CHANGE; - g_mutex_unlock (&testData->scratchData->seekTaskStateLock); - - g_rec_mutex_init (&testData->scratchData->seekTaskLock); - testData->scratchData->seekTask = - gst_task_new ((GstTaskFunction) testSeekTaskDoSeek, testData, NULL); - gst_task_set_lock (testData->scratchData->seekTask, - &testData->scratchData->seekTaskLock); - gst_task_start (testData->scratchData->seekTask); - - GST_DEBUG ("seek task started"); - - g_mutex_lock (&testData->scratchData->seekTaskStateLock); - - GST_DEBUG ("waiting for seek task to change state on fakesrc"); - - /* wait for seekTask to run, send a flush start event to AppSink - * and change the fakesouphttpsrc element state from PLAYING to PAUSED - */ - while (testData->scratchData->seekTaskState == - SEEK_TASK_STATE_WAITING_FOR_FAKESRC_STATE_CHANGE) { - g_cond_wait (&testData->scratchData->seekTaskStateCond, - &testData->scratchData->seekTaskStateLock); - } - g_mutex_unlock (&testData->scratchData->seekTaskStateLock); - /* we can continue now, but this buffer will be rejected by AppSink - * because it is in flushing mode - */ - GST_DEBUG ("seek task changed state on fakesrc, resuming"); - } - - return TRUE; -} - -/* callback called when main_loop detects a state changed event */ -static void -testSeekOnStateChanged (GstBus * bus, GstMessage * msg, gpointer data) -{ - GstDashDemuxTestData *testData = (GstDashDemuxTestData *) data; - GstDashDemuxTestOutputStreamData *testOutputStreamData; - GstState old_state, new_state; - const char *srcName = GST_OBJECT_NAME (msg->src); - GstPad *internalPad; - - gst_message_parse_state_changed (msg, &old_state, &new_state, NULL); - GST_DEBUG ("Element %s changed state from %s to %s", - GST_OBJECT_NAME (msg->src), - gst_element_state_get_name (old_state), - gst_element_state_get_name (new_state)); - - if (strstr (srcName, "fakesouphttpsrc") == srcName && - old_state == GST_STATE_PLAYING && new_state == GST_STATE_PAUSED) { - GList *pads = GST_ELEMENT_PADS (msg->src); - GstObject *srcBin; - - /* src is a fake http src element. It should have only 1 pad */ - fail_unless (pads != NULL); - fail_unless (g_list_length (pads) == 1); - - /* fakeHTTPsrc element is placed inside a bin. Get the bin */ - srcBin = gst_object_get_parent (msg->src); - - /* the bin should have only 1 output pad */ - pads = GST_ELEMENT_PADS (srcBin); - fail_unless (pads != NULL); - fail_unless (g_list_length (pads) == 1); - - internalPad = gst_pad_get_peer (GST_PAD (pads->data)); - testOutputStreamData = getTestOutputDataByPad (testData, internalPad); - gst_object_unref (internalPad); - - if (strcmp (testOutputStreamData->name, "audio_00") == 0) { - g_mutex_lock (&testData->scratchData->seekTaskStateLock); - if (testData->scratchData->seekTaskState == - SEEK_TASK_STATE_WAITING_FOR_FAKESRC_STATE_CHANGE) { - GST_DEBUG ("changing seekTaskState"); - testData->scratchData->seekTaskState = SEEK_TASK_STATE_EXITING; - g_cond_signal (&testData->scratchData->seekTaskStateCond); - } - g_mutex_unlock (&testData->scratchData->seekTaskStateLock); - } - gst_object_unref (srcBin); - } -} - -static void -testSeekPreTestCallback (GstDashDemuxTestData * testData) -{ - GstBus *bus; - - /* media segment starts at 4687 - * Issue a seek request after media segment has started to be downloaded - * on audio_00 stream and the first chunk of GST_FAKE_SOUP_HTTP_SRC_MAX_BUF_SIZE - * has already arrived in AppSink - */ - testData->scratchData->thresholdForSeek = 4687 + - GST_FAKE_SOUP_HTTP_SRC_MAX_BUF_SIZE; - - g_mutex_init (&testData->scratchData->seekTaskStateLock); - g_cond_init (&testData->scratchData->seekTaskStateCond); - - /* register a callback to listen for state change events */ - bus = gst_pipeline_get_bus (GST_PIPELINE (testData->scratchData->pipeline)); - gst_bus_add_signal_watch (bus); - g_signal_connect (bus, "message::state-changed", - G_CALLBACK (testSeekOnStateChanged), testData); -} - -/* function to do test seek cleanup */ -static void -testSeekPostTestCallback (GstDashDemuxTestData * testData) -{ - GstDashDemuxTestScratchData *scratchData = testData->scratchData; - - fail_if (scratchData->seekTask == NULL, - "seek test did not create task to perform the seek"); - gst_task_stop (scratchData->seekTask); - gst_task_join (scratchData->seekTask); - GST_DEBUG ("task stopped"); - gst_object_unref (scratchData->seekTask); - g_rec_mutex_clear (&scratchData->seekTaskLock); - - g_cond_clear (&testData->scratchData->seekTaskStateCond); - g_mutex_clear (&testData->scratchData->seekTaskStateLock); -} - /* * Test seeking * @@ -1205,162 +462,60 @@ GST_START_TEST (testSeek) " <Initialization range=\"0-4451\" />" " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {"http://unit.test/audio.webm", NULL, 10000}, {NULL, NULL, 0}, }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { {"audio_00", 10000, NULL}, }; + GstAdaptiveDemuxTestCase *testData; - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = checkDataReceived; - cb.appSinkGotEosCallback = testSeekCheckSizeOfDataReceived; - cb.preTestCallback = testSeekPreTestCallback; - cb.postTestCallback = testSeekPostTestCallback; - cb.dashdemuxSendsDataCallback = testSeekDashdemuxSendsData; - - runTest (inputTestData, - outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]), - &cb); -} - -GST_END_TEST; + testData = gst_adaptive_demux_test_case_new (); -static void -testDownloadErrorPreTestCallback (GstDashDemuxTestData * testData) -{ - /* expect error on pipeline */ - testData->expectError = TRUE; -} + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); -/* - * Test error download - * - */ -GST_START_TEST (testDownloadError) -{ - const gchar *mpd = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" - " xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"" - " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\"" - " xmlns:yt=\"http://youtube.com/yt/2012/10/10\"" - " profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"" - " type=\"static\"" - " minBufferTime=\"PT1.500S\"" - " mediaPresentationDuration=\"PT0.5S\">" - " <Period>" - " <AdaptationSet mimeType=\"audio/webm\"" - " subsegmentAlignment=\"true\">" - " <Representation id=\"171\"" - " codecs=\"vorbis\"" - " audioSamplingRate=\"44100\"" - " startWithSAP=\"1\"" - " bandwidth=\"129553\">" - " <AudioChannelConfiguration" - " schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\"" - " value=\"2\" />" - " <BaseURL>audio_file_not_available.webm</BaseURL>" - " <SegmentBase indexRange=\"4452-4686\"" - " indexRangeExact=\"true\">" - " <Initialization range=\"0-4451\" />" - " </SegmentBase>" - " </Representation></AdaptationSet></Period></MPD>"; - - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, - {NULL, NULL, 0}, - }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { - {"audio_00", 5000, NULL}, - }; - - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = checkDataReceived; - cb.appSinkGotEosCallback = checkSizeOfDataReceived; - cb.preTestCallback = testDownloadErrorPreTestCallback; + /* media segment starts at 4687 + * Issue a seek request after media segment has started to be downloaded + * on the first pad listed in GstAdaptiveDemuxTestOutputStreamData and the + * first chunk of at least one byte has already arrived in AppSink + */ + testData->threshold_for_seek = 4687 + 1; - runTest (inputTestData, outputTestData, - sizeof (outputTestData) / sizeof (outputTestData[0]), &cb); + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_seek (DEMUX_ELEMENT_NAME, + "http://unit.test/test.mpd", testData); + gst_object_unref (testData); } GST_END_TEST; -/* testFragmentDownloadError is disabled until we redesign the test framework - * to allow better control on when fakeHTTPsrc element will create data. - * Currently that is being done asynchronously and there is no easy way - * to synchronise data generation with the data processing (the test) so that - * we can guarantee that error generation is done during data generation and - * not afterwards. - */ -#if 0 -/* generate error message on adaptive demux pipeline */ -static gboolean -testFragmentDownloadErrorCheckDataReceived (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData, GstBuffer * buffer) -{ - checkDataReceived (testData, testOutputStreamData, buffer); - - if (testOutputStreamData->scratchData->segmentReceivedSize > 2000) { - GstPad *srcBinPad; - GstObject *srcBin; - GstElement *fakeHttpSrcElement; - - srcBinPad = - gst_pad_get_peer (testOutputStreamData->scratchData->internalPad); - srcBin = gst_pad_get_parent (srcBinPad); - - fakeHttpSrcElement = gst_bin_get_by_interface (GST_BIN (srcBin), - GST_TYPE_URI_HANDLER); - - /* tell fake soup http src to post an error on the adaptive demux bus */ - gst_fake_soup_http_src_simulate_download_error ((GstFakeSoupHTTPSrc *) - fakeHttpSrcElement, 404); - - gst_object_unref (srcBinPad); - gst_object_unref (srcBin); - gst_object_unref (fakeHttpSrcElement); - - testData->expectError = TRUE; - } - - return TRUE; -} - -/* function to check total size of data received by AppSink - * will be called when AppSink receives eos. - */ -static gboolean -testFragmentDownloadErrorCheckSizeOfDataReceived (GstDashDemuxTestData * - testData, GstDashDemuxTestOutputStreamData * testOutputStreamData) +static void +testDownloadErrorMessageCallback (GstAdaptiveDemuxTestEngine * engine, + GstMessage * msg, gpointer user_data) { - /* expect to receive more than 0 */ - fail_unless (testOutputStreamData->scratchData->totalReceivedSize > 0, - "size validation failed for %s, expected > 0, received %d", - testOutputStreamData->name, - testOutputStreamData->scratchData->totalReceivedSize); - - /* expect to receive less than file size */ - fail_unless (testOutputStreamData->scratchData->totalReceivedSize < - testOutputStreamData->expectedSize, - "size validation failed for %s, expected < %d received %d", - testOutputStreamData->name, testOutputStreamData->expectedSize, - testOutputStreamData->scratchData->totalReceivedSize); + GError *err = NULL; + gchar *dbg_info = NULL; - return TRUE; + fail_unless (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR); + gst_message_parse_error (msg, &err, &dbg_info); + GST_DEBUG ("Error from element %s : %s\n", + GST_OBJECT_NAME (msg->src), err->message); + fail_unless_equals_string (GST_OBJECT_NAME (msg->src), DEMUX_ELEMENT_NAME); + /*GST_DEBUG ("dbg_info=%s\n", dbg_info); */ + g_error_free (err); + g_free (dbg_info); + g_main_loop_quit (engine->loop); } /* - * Test fragment download error - * Let the adaptive demux download a few bytes, then instruct the fake soup http - * src element to generate an error. + * Test error case of failing to download a segment */ -GST_START_TEST (testFragmentDownloadError) +GST_START_TEST (testDownloadError) { const gchar *mpd = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" @@ -1383,39 +538,47 @@ GST_START_TEST (testFragmentDownloadError) " <AudioChannelConfiguration" " schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\"" " value=\"2\" />" - " <BaseURL>audio.webm</BaseURL>" + " <BaseURL>audio_file_not_available.webm</BaseURL>" " <SegmentBase indexRange=\"4452-4686\"" " indexRangeExact=\"true\">" " <Initialization range=\"0-4451\" />" " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, - {"http://unit.test/audio.webm", NULL, 5000}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {NULL, NULL, 0}, }; - - GstDashDemuxTestOutputStreamData outputTestData[] = { - {"audio_00", 5000, NULL}, + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { + {"audio_00", 0, NULL}, }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = testFragmentDownloadErrorCheckDataReceived; - cb.appSinkGotEosCallback = testFragmentDownloadErrorCheckSizeOfDataReceived; + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.appsink_received_data = + gst_adaptive_demux_test_check_received_data; + test_callbacks.bus_error_message = testDownloadErrorMessageCallback; + test_callbacks.appsink_eos = + gst_adaptive_demux_test_check_size_of_received_data; - runTest (inputTestData, outputTestData, - sizeof (outputTestData) / sizeof (outputTestData[0]), &cb); + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, "http://unit.test/test.mpd", + &test_callbacks, testData); + gst_object_unref (testData); } GST_END_TEST; -#endif - /* generate queries to adaptive demux */ static gboolean -testQueryCheckDataReceived (GstDashDemuxTestData * testData, - GstDashDemuxTestOutputStreamData * testOutputStreamData, GstBuffer * buffer) +testQueryCheckDataReceived (GstAdaptiveDemuxTestEngine * engine, + GstAdaptiveDemuxTestOutputStream * stream, + GstBuffer * buffer, gpointer user_data) { GList *pads; GstPad *pad; @@ -1429,7 +592,7 @@ testQueryCheckDataReceived (GstDashDemuxTestData * testData, gchar *redirect_uri; gboolean redirect_permanent; - pads = GST_ELEMENT_PADS (testOutputStreamData->scratchData->appsink); + pads = GST_ELEMENT_PADS (stream->appsink); /* AppSink should have only 1 pad */ fail_unless (pads != NULL); @@ -1467,7 +630,8 @@ testQueryCheckDataReceived (GstDashDemuxTestData * testData, g_free (redirect_uri); gst_query_unref (query); - return checkDataReceived (testData, testOutputStreamData, buffer); + return gst_adaptive_demux_test_check_received_data (engine, + stream, buffer, user_data); } /* @@ -1503,24 +667,142 @@ GST_START_TEST (testQuery) " <Initialization range=\"0-4451\" />" " </SegmentBase>" " </Representation></AdaptationSet></Period></MPD>"; - - const GstFakeHttpSrcInputData inputTestData[] = { - {"http://unit.test/test.mpd", mpd, 0}, + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, {"http://unit.test/audio.webm", NULL, 5000}, {NULL, NULL, 0}, }; + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { + {"audio_00", 5000, NULL}, + }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; - GstDashDemuxTestOutputStreamData outputTestData[] = { + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = gst_dashdemux_http_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.appsink_received_data = testQueryCheckDataReceived; + test_callbacks.appsink_eos = + gst_adaptive_demux_test_check_size_of_received_data; + + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, + "http://unit.test/test.mpd", &test_callbacks, testData); + gst_object_unref (testData); +} + +GST_END_TEST; + +static GstFlowReturn +test_fragment_download_error_src_create (GstTestHTTPSrc * src, + guint64 offset, + guint length, GstBuffer ** retbuf, gpointer context, gpointer user_data) +{ + const GstDashDemuxTestInputData *input = + (const GstDashDemuxTestInputData *) context; + fail_unless (input != NULL); + if (!g_str_has_suffix (input->uri, ".mpd") && offset > 2000) { + GST_DEBUG ("network_error %s %" G_GUINT64_FORMAT " @ %d", + input->uri, offset, 2000); + GST_ELEMENT_ERROR (src, RESOURCE, READ, + (("A network error occurred, or the server closed the connection unexpectedly.")), ("A network error occurred, or the server closed the connection unexpectedly.")); + return GST_FLOW_ERROR; + } + return gst_dashdemux_http_src_create (src, offset, length, retbuf, context, + user_data); +} + +/* function to check total size of data received by AppSink + * will be called when AppSink receives eos. + */ +static void +testFragmentDownloadErrorCheckSizeOfDataReceived (GstAdaptiveDemuxTestEngine * + engine, GstAdaptiveDemuxTestOutputStream * stream, gpointer user_data) +{ + GstAdaptiveDemuxTestCase *testData = GST_ADAPTIVE_DEMUX_TEST_CASE (user_data); + GstAdaptiveDemuxTestExpectedOutput *testOutputStreamData; + + testOutputStreamData = + gst_adaptive_demux_test_find_test_data_by_stream (testData, stream, NULL); + fail_unless (testOutputStreamData != NULL); + + /* expect to receive more than 0 */ + fail_unless (stream->total_received_size > 0, + "size validation failed for %s, expected > 0, received %d", + testOutputStreamData->name, stream->total_received_size); + + /* expect to receive less than file size */ + fail_unless (stream->total_received_size < + testOutputStreamData->expected_size, + "size validation failed for %s, expected < %d received %d", + testOutputStreamData->name, testOutputStreamData->expected_size, + stream->total_received_size); +} + +/* + * Test fragment download error + * Let the adaptive demux download a few bytes, then instruct the + * GstTestHTTPSrc element to generate an error. + */ +GST_START_TEST (testFragmentDownloadError) +{ + const gchar *mpd = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + " xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"" + " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\"" + " xmlns:yt=\"http://youtube.com/yt/2012/10/10\"" + " profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"" + " type=\"static\"" + " minBufferTime=\"PT1.500S\"" + " mediaPresentationDuration=\"PT0.5S\">" + " <Period>" + " <AdaptationSet mimeType=\"audio/webm\"" + " subsegmentAlignment=\"true\">" + " <Representation id=\"171\"" + " codecs=\"vorbis\"" + " audioSamplingRate=\"44100\"" + " startWithSAP=\"1\"" + " bandwidth=\"129553\">" + " <AudioChannelConfiguration" + " schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\"" + " value=\"2\" />" + " <BaseURL>audio.webm</BaseURL>" + " <SegmentBase indexRange=\"4452-4686\"" + " indexRangeExact=\"true\">" + " <Initialization range=\"0-4451\" />" + " </SegmentBase>" + " </Representation></AdaptationSet></Period></MPD>"; + + GstDashDemuxTestInputData inputTestData[] = { + {"http://unit.test/test.mpd", (guint8 *) mpd, 0}, + {"http://unit.test/audio.webm", NULL, 5000}, + {NULL, NULL, 0}, + }; + GstTestHTTPSrcCallbacks http_src_callbacks = { 0 }; + GstAdaptiveDemuxTestExpectedOutput outputTestData[] = { {"audio_00", 5000, NULL}, }; + GstAdaptiveDemuxTestCallbacks test_callbacks = { 0 }; + GstAdaptiveDemuxTestCase *testData; + + testData = gst_adaptive_demux_test_case_new (); + http_src_callbacks.src_start = gst_dashdemux_http_src_start; + http_src_callbacks.src_create = test_fragment_download_error_src_create; + COPY_OUTPUT_TEST_DATA (outputTestData, testData); + test_callbacks.appsink_received_data = + gst_adaptive_demux_test_check_received_data; + test_callbacks.appsink_eos = testFragmentDownloadErrorCheckSizeOfDataReceived; + /* test_callbacks.demux_sent_eos = gst_adaptive_demux_test_check_size_of_received_data; */ - Callbacks cb = { 0 }; - cb.appSinkGotDataCallback = testQueryCheckDataReceived; - cb.appSinkGotEosCallback = checkSizeOfDataReceived; + test_callbacks.bus_error_message = testDownloadErrorMessageCallback; - runTest (inputTestData, - outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]), - &cb); + gst_test_http_src_install_callbacks (&http_src_callbacks, inputTestData); + gst_adaptive_demux_test_run (DEMUX_ELEMENT_NAME, + "http://unit.test/test.mpd", &test_callbacks, testData); + gst_object_unref (testData); } GST_END_TEST; @@ -1536,10 +818,11 @@ dash_demux_suite (void) tcase_add_test (tc_basicTest, testParameters); tcase_add_test (tc_basicTest, testSeek); tcase_add_test (tc_basicTest, testDownloadError); - //tcase_add_test (tc_basicTest, testFragmentDownloadError); + tcase_add_test (tc_basicTest, testFragmentDownloadError); tcase_add_test (tc_basicTest, testQuery); - tcase_add_unchecked_fixture (tc_basicTest, test_setup, test_teardown); + tcase_add_unchecked_fixture (tc_basicTest, gst_adaptive_demux_test_setup, + gst_adaptive_demux_test_teardown); suite_add_tcase (s, tc_basicTest); diff --git a/tests/check/elements/fake_http_src.c b/tests/check/elements/fake_http_src.c deleted file mode 100644 index a5575db90..000000000 --- a/tests/check/elements/fake_http_src.c +++ /dev/null @@ -1,511 +0,0 @@ -/* GStreamer unit test for MPEG-DASH - * - * Copyright (c) <2015> YouView TV Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include <gst/check/gstcheck.h> -#include <gst/base/gstbasesrc.h> - -#include "fake_http_src.h" - -#ifndef GST_PACKAGE_NAME -#define GST_PACKAGE_NAME "gst-plugins-bad" -#endif - -#ifndef GST_PACKAGE_ORIGIN -#define GST_PACKAGE_ORIGIN "https://developer.gnome.org/gstreamer/" -#endif - -#define GST_FAKE_SOUP_HTTP_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_FAKE_SOUP_HTTP_SRC, GstFakeSoupHTTPSrc)) -#define GST_FAKE_SOUP_HTTP_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_FAKE_SOUP_HTTP_SRC, GstFakeSoupHTTPSrcClass)) -#define GST_IS_FAKE_SOUP_HTTP_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_FAKE_SOUP_HTTP_SRC)) -#define GST_IS_FAKE_SOUP_HTTP_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_FAKE_SOUP_HTTP_SRC)) -#define GST_FAKE_SOUP_HTTP_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_FAKE_SOUP_HTTP_SRC, GstFakeSoupHTTPSrcClass)) - -#define GST_FAKE_SOUP_HTTP_SRC_GET_LOCK(d) (&(((GstFakeSoupHTTPSrc*)(d))->lock)) -#define GST_FAKE_SOUP_HTTP_SRC_LOCK(d) g_mutex_lock (GST_FAKE_SOUP_HTTP_SRC_GET_LOCK (d)) -#define GST_FAKE_SOUP_HTTP_SRC_UNLOCK(d) g_mutex_unlock (GST_FAKE_SOUP_HTTP_SRC_GET_LOCK (d)) - -typedef struct _GstFakeSoupHTTPSrc -{ - GstBaseSrc parent; - - /* uri for which to retrieve data */ - gchar *uri; - /* data to retrieve. - * If NULL, we will fake a buffer of size bytes, containing numbers in sequence - * 0, 4, 8, ... - * Each number is written on sizeof(int) bytes in little endian format - */ - const gchar *payload; - /* size of data to generate */ - guint size; - /* position from where to retrieve data */ - guint64 position; - /* index immediately after the last byte from the segment to be retrieved */ - guint64 segment_end; - - /* download error code to simulate during create function */ - guint downloadErrorCode; - - /* mutex to protect multithread access to this structure */ - GMutex lock; -} GstFakeSoupHTTPSrc; - -typedef struct _GstFakeSoupHTTPSrcClass -{ - GstBaseSrcClass parent_class; -} GstFakeSoupHTTPSrcClass; - -typedef struct _PluginInitContext -{ - const gchar *name; - guint rank; - GType type; -} PluginInitContext; - - -static const GstFakeHttpSrcInputData *gst_fake_soup_http_src_inputData = NULL; - -static GstStaticPadTemplate gst_dashdemux_test_source_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - -static void gst_fake_soup_http_src_uri_handler_init (gpointer g_iface, - gpointer iface_data); -static void gst_fake_soup_http_src_finalize (GObject * object); -static gboolean gst_fake_soup_http_src_is_seekable (GstBaseSrc * basesrc); -static gboolean gst_fake_soup_http_src_do_seek (GstBaseSrc * basesrc, - GstSegment * segment); -static gboolean gst_fake_soup_http_src_start (GstBaseSrc * basesrc); -static gboolean gst_fake_soup_http_src_stop (GstBaseSrc * basesrc); -static gboolean gst_fake_soup_http_src_get_size (GstBaseSrc * basesrc, - guint64 * size); -static GstFlowReturn gst_fake_soup_http_src_create (GstBaseSrc * basesrc, - guint64 offset, guint length, GstBuffer ** ret); - -#define _do_init \ - G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_fake_soup_http_src_uri_handler_init); - -#define gst_fake_soup_http_src_parent_class parent_class -G_DEFINE_TYPE_WITH_CODE (GstFakeSoupHTTPSrc, gst_fake_soup_http_src, - GST_TYPE_BASE_SRC, _do_init); - -static void -gst_fake_soup_http_src_class_init (GstFakeSoupHTTPSrcClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSrcClass *gstbasesrc_class; - - gobject_class = G_OBJECT_CLASS (klass); - gstelement_class = GST_ELEMENT_CLASS (klass); - gstbasesrc_class = GST_BASE_SRC_CLASS (klass); - - gobject_class->finalize = gst_fake_soup_http_src_finalize; - - gst_element_class_set_metadata (gstelement_class, - "Fake HTTP source element for unit tests", - "Source/Network", - "Use in unit tests", "Alex Ashley <alex.ashley@youview.com>"); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_dashdemux_test_source_template)); - - gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_start); - gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_stop); - gstbasesrc_class->is_seekable = - GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_is_seekable); - gstbasesrc_class->do_seek = - GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_do_seek); - gstbasesrc_class->get_size = - GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_get_size); - gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_fake_soup_http_src_create); - -} - -static void -gst_fake_soup_http_src_init (GstFakeSoupHTTPSrc * src) -{ - src->uri = NULL; - src->payload = NULL; - src->position = 0; - src->size = 0; - src->segment_end = 0; - src->downloadErrorCode = 0; - gst_base_src_set_blocksize (GST_BASE_SRC (src), - GST_FAKE_SOUP_HTTP_SRC_MAX_BUF_SIZE); - g_mutex_init (&src->lock); -} - -static void -gst_fake_soup_http_src_finalize (GObject * object) -{ - GstFakeSoupHTTPSrc *src; - - src = GST_FAKE_SOUP_HTTP_SRC (object); - - g_mutex_clear (&src->lock); - g_free (src->uri); - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static gboolean -gst_fake_soup_http_src_start (GstBaseSrc * basesrc) -{ - GstFakeSoupHTTPSrc *src; - const GstFakeHttpSrcInputData *input = gst_fake_soup_http_src_inputData; - - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - if (!src->uri) { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (("No URL set.")), - ("Missing location property")); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; - } - - for (guint i = 0; input[i].uri; ++i) { - if (strcmp (input[i].uri, src->uri) == 0) { - src->payload = input[i].payload; - src->position = 0; - if (src->payload) - src->size = strlen (src->payload); - else - src->size = input[i].size; - src->segment_end = src->size; - src->downloadErrorCode = 0; - gst_base_src_set_dynamic_size (basesrc, FALSE); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return TRUE; - } - } - - GST_WARNING - ("gst_fake_soup_http_src_start cannot find url '%s' in input data", - src->uri); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; -} - -static gboolean -gst_fake_soup_http_src_stop (GstBaseSrc * basesrc) -{ - GstFakeSoupHTTPSrc *src; - - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - src->payload = NULL; - src->position = 0; - src->size = 0; - - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return TRUE; -} - -static gboolean -gst_fake_soup_http_src_is_seekable (GstBaseSrc * basesrc) -{ - GstFakeSoupHTTPSrc *src; - gboolean ret; - - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - /* if size is set, we can seek */ - ret = src->size > 0; - - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - - return ret; -} - -static gboolean -gst_fake_soup_http_src_do_seek (GstBaseSrc * basesrc, GstSegment * segment) -{ - GstFakeSoupHTTPSrc *src; - - GST_DEBUG ("gst_fake_soup_http_src_do_seek start = %" G_GUINT64_FORMAT, - segment->start); - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - /* - According to RFC7233, the range is inclusive: - The first-byte-pos value in a byte-range-spec gives the byte-offset - of the first byte in a range. The last-byte-pos value gives the - byte-offset of the last byte in the range; that is, the byte - positions specified are inclusive. Byte offsets start at zero. - */ - - if (!src->uri) { - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; - } - - if (segment->start >= src->size) { - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; - } - - if (segment->stop != -1 && segment->stop > src->size) { - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; - } - - src->position = segment->start; - - if (segment->stop != -1) { - src->segment_end = segment->stop; - } - - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return TRUE; -} - -static gboolean -gst_fake_soup_http_src_get_size (GstBaseSrc * basesrc, guint64 * size) -{ - GstFakeSoupHTTPSrc *src; - const GstFakeHttpSrcInputData *input = gst_fake_soup_http_src_inputData; - - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - if (!src->uri) { - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; - } - - /* if it was started (payload or size configured), size is set */ - if (src->payload || src->size > 0) { - *size = src->size; - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return TRUE; - } - - /* it wasn't started, compute the size */ - for (guint i = 0; input[i].uri; ++i) { - if (strcmp (input[i].uri, src->uri) == 0) { - if (input[i].payload) - *size = strlen (input[i].payload); - else - *size = input[i].size; - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return TRUE; - } - } - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return FALSE; -} - -static GstFlowReturn -gst_fake_soup_http_src_create (GstBaseSrc * basesrc, guint64 offset, - guint length, GstBuffer ** ret) -{ - GstFakeSoupHTTPSrc *src; - guint bytes_read; - GstBuffer *buf; - - src = GST_FAKE_SOUP_HTTP_SRC (basesrc); - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - GST_DEBUG ("gst_fake_soup_http_src_create feeding from %" G_GUINT64_FORMAT, - src->position); - if (src->uri == NULL) { - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return GST_FLOW_ERROR; - } - if (src->downloadErrorCode) { - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, ("%s", - "Generated requested error"), ("%s (%d), URL: %s, Redirect to: %s", - "Generated requested error", src->downloadErrorCode, src->uri, - GST_STR_NULL (NULL))); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return GST_FLOW_ERROR; - } - - bytes_read = MIN ((src->segment_end - src->position), - GST_FAKE_SOUP_HTTP_SRC_MAX_BUF_SIZE); - if (bytes_read == 0) { - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return GST_FLOW_EOS; - } - - buf = gst_buffer_new_allocate (NULL, bytes_read, NULL); - fail_if (buf == NULL, "Not enough memory to allocate buffer"); - - if (src->payload) { - gst_buffer_fill (buf, 0, src->payload + src->position, bytes_read); - } else { - GstMapInfo info; - guint pattern; - - pattern = src->position - src->position % sizeof (pattern); - - gst_buffer_map (buf, &info, GST_MAP_WRITE); - for (guint64 i = 0; i < bytes_read; ++i) { - gchar pattern_byte_to_write = (src->position + i) % sizeof (pattern); - if (pattern_byte_to_write == 0) { - pattern = src->position + i; - } - info.data[i] = (pattern >> (pattern_byte_to_write * 8)) & 0xFF; - } - gst_buffer_unmap (buf, &info); - } - - GST_BUFFER_OFFSET (buf) = src->position; - GST_BUFFER_OFFSET_END (buf) = src->position + bytes_read; - *ret = buf; - - src->position += bytes_read; - - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return GST_FLOW_OK; -} - -/* must be called with the lock taken */ -static gboolean -gst_fake_soup_http_src_set_location (GstFakeSoupHTTPSrc * src, - const gchar * uri, GError ** error) -{ - g_free (src->uri); - src->uri = g_strdup (uri); - return TRUE; -} - -static GstURIType -gst_fake_soup_http_src_uri_get_type (GType type) -{ - return GST_URI_SRC; -} - -static const gchar *const * -gst_fake_soup_http_src_uri_get_protocols (GType type) -{ - static const gchar *protocols[] = { "http", NULL }; - - return protocols; -} - -static gchar * -gst_fake_soup_http_src_uri_get_uri (GstURIHandler * handler) -{ - GstFakeSoupHTTPSrc *src = GST_FAKE_SOUP_HTTP_SRC (handler); - gchar *uri; - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - uri = g_strdup (src->uri); - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - return uri; -} - -static gboolean -gst_fake_soup_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, - GError ** err) -{ - GstFakeSoupHTTPSrc *src = GST_FAKE_SOUP_HTTP_SRC (handler); - gboolean ret; - - GST_FAKE_SOUP_HTTP_SRC_LOCK (src); - - ret = gst_fake_soup_http_src_set_location (src, uri, err); - - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (src); - - return ret; -} - -static void -gst_fake_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data) -{ - GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; - - iface->get_type = gst_fake_soup_http_src_uri_get_type; - iface->get_protocols = gst_fake_soup_http_src_uri_get_protocols; - iface->get_uri = gst_fake_soup_http_src_uri_get_uri; - iface->set_uri = gst_fake_soup_http_src_uri_set_uri; -} - -static gboolean -gst_fake_soup_http_src_plugin_init_func (GstPlugin * plugin, gpointer user_data) -{ - PluginInitContext *context = (PluginInitContext *) user_data; - gboolean ret; - - ret = - gst_element_register (plugin, context->name, context->rank, - context->type); - return ret; -} - -gboolean -gst_fake_soup_http_src_register_plugin (GstRegistry * registry, - const gchar * name) -{ - gboolean ret; - PluginInitContext context; - - context.name = name; - context.rank = GST_RANK_PRIMARY + 1; - context.type = GST_TYPE_FAKE_SOUP_HTTP_SRC; - ret = gst_plugin_register_static_full (GST_VERSION_MAJOR, /* version */ - GST_VERSION_MINOR, /* version */ - name, /* name */ - "Replaces a souphttpsrc plugin and returns predefined data.", /* description */ - gst_fake_soup_http_src_plugin_init_func, /* init function */ - "0.0.0", /* version string */ - GST_LICENSE_UNKNOWN, /* license */ - __FILE__, /* source */ - GST_PACKAGE_NAME, /* package */ - GST_PACKAGE_ORIGIN, /* origin */ - &context /* user_data */ - ); - return ret; -} - -/** - * gst_fake_soup_http_src_set_input_data: - * @input: array of #GstFakeHttpSrcInputData that is used when - * responding to a request. The last entry in the array must - * have the uri field set to NULL - */ -void -gst_fake_soup_http_src_set_input_data (const GstFakeHttpSrcInputData * input) -{ - gst_fake_soup_http_src_inputData = input; -} - -void -gst_fake_soup_http_src_simulate_download_error (GstFakeSoupHTTPSrc * - fakeSoupHTTPSrc, guint downloadErrorCode) -{ - GST_FAKE_SOUP_HTTP_SRC_LOCK (fakeSoupHTTPSrc); - fakeSoupHTTPSrc->downloadErrorCode = downloadErrorCode; - GST_FAKE_SOUP_HTTP_SRC_UNLOCK (fakeSoupHTTPSrc); -} diff --git a/tests/check/elements/fake_http_src.h b/tests/check/elements/fake_http_src.h deleted file mode 100644 index abcaa169c..000000000 --- a/tests/check/elements/fake_http_src.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Fake HTTP source element - * - * Copyright (c) <2015> YouView TV Ltd - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include <gst/gst.h> - -G_BEGIN_DECLS - -#define GST_TYPE_FAKE_SOUP_HTTP_SRC (gst_fake_soup_http_src_get_type ()) - -/* structure used by tests to configure the GstFakeSoupHTTPSrc plugin. - * It specifies what data to be fed for the given uri. - * For the requested uri, it will return the data from payload. - * If the payload is NULL, it will fake a buffer of size bytes and return data from it. - * The buffer will contain a pattern, numbers 0, 4, 8, ... etc written on - * sizeof(int) bytes, in little endian format (eg if sizeof(int)=4, the first 12 - * bytes are 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00) - * Size is used only if payload is NULL. - */ -typedef struct _GstFakeHttpSrcInputData -{ - /* the uri for which data is being requested */ - const gchar *uri; - /* the payload to be returned */ - const gchar *payload; - /* the size of data to fake if payload is NULL */ - guint64 size; -} GstFakeHttpSrcInputData; - -typedef struct _GstFakeSoupHTTPSrc GstFakeSoupHTTPSrc; - -/* GstFakeSoupHTTPSrc will send buffers up to this size */ -#define GST_FAKE_SOUP_HTTP_SRC_MAX_BUF_SIZE (1024) - -GType gst_fake_soup_http_src_get_type (void); - -gboolean gst_fake_soup_http_src_register_plugin (GstRegistry * registry, const gchar * name); - -void gst_fake_soup_http_src_set_input_data (const GstFakeHttpSrcInputData *input); - -/* TODO: use SoupKnownStatusCode. But it requires makefile support to include - * <libsoup/soup.h> - */ -void gst_fake_soup_http_src_simulate_download_error ( - GstFakeSoupHTTPSrc *fakeSoupHTTPSrc, guint downloadErrorCode); diff --git a/tests/check/elements/test_http_src.c b/tests/check/elements/test_http_src.c new file mode 100644 index 000000000..88b0c4f21 --- /dev/null +++ b/tests/check/elements/test_http_src.c @@ -0,0 +1,716 @@ +/* HTTP source element for use in tests + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <gst/check/gstcheck.h> +#include <gst/base/gstbasesrc.h> + +#include "test_http_src.h" + +#ifndef GST_PACKAGE_NAME +#define GST_PACKAGE_NAME "gst-plugins-bad" +#endif + +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "https://developer.gnome.org/gstreamer/" +#endif + +#define GST_TEST_HTTP_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TEST_HTTP_SRC, GstTestHTTPSrc)) +#define GST_TEST_HTTP_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TEST_HTTP_SRC, GstTestHTTPSrcClass)) +#define GST_IS_TEST_HTTP_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TEST_HTTP_SRC)) +#define GST_IS_TEST_HTTP_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TEST_HTTP_SRC)) +#define GST_TEST_HTTP_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TEST_HTTP_SRC, GstTestHTTPSrcClass)) + +#define DEFAULT_USER_AGENT "GStreamer testhttpsrc " +#define DEFAULT_COMPRESS FALSE +#define DEFAULT_HTTP_METHOD NULL +#define DEFAULT_KEEP_ALIVE FALSE + +enum +{ + PROP_0, + PROP_USER_AGENT, + PROP_EXTRA_HEADERS, + PROP_COMPRESS, + PROP_KEEP_ALIVE, + PROP_METHOD, + PROP_LAST +}; + +typedef enum +{ + METHOD_INVALID, + METHOD_GET, + METHOD_POST, + METHOD_HEAD, + METHOD_OPTIONS +} HttpMethod; + +typedef struct _GstTestHTTPSrcMethodName +{ + const gchar *name; + HttpMethod method; +} GstTestHTTPSrcMethodName; + +static const GstTestHTTPSrcMethodName gst_test_http_src_methods[] = { + {"GET", METHOD_GET}, + {"POST", METHOD_POST}, + {"HEAD", METHOD_HEAD}, + {"OPTIONS", METHOD_OPTIONS}, + {NULL, METHOD_INVALID} +}; + +typedef struct _GstTestHTTPSrc +{ + GstBaseSrc parent; + + GMutex mutex; + + GstTestHTTPSrcInput input; + + gchar *uri; /* the uri for which data is being requested */ + gboolean compress; + gboolean keep_alive; + gchar *http_method_name; + HttpMethod http_method; + GstStructure *extra_headers; + gchar *user_agent; + + guint64 position; + /* index immediately after the last byte from the segment to be retrieved */ + guint64 segment_end; + + GstEvent *http_headers_event; + gboolean duration_changed; +} GstTestHTTPSrc; + +typedef struct _GstTestHTTPSrcClass +{ + GstBaseSrcClass parent_class; +} GstTestHTTPSrcClass; + +typedef struct _PluginInitContext +{ + const gchar *name; + guint rank; + GType type; +} PluginInitContext; + +static const GstTestHTTPSrcCallbacks *gst_test_http_src_callbacks = NULL; +static gpointer gst_test_http_src_callback_user_data = NULL; +static guint gst_test_http_src_blocksize = 0; + +static GstStaticPadTemplate gst_dashdemux_test_source_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static void gst_test_http_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); +static void gst_test_http_src_finalize (GObject * object); +static gboolean gst_test_http_src_is_seekable (GstBaseSrc * basesrc); +static gboolean gst_test_http_src_do_seek (GstBaseSrc * basesrc, + GstSegment * segment); +static gboolean gst_test_http_src_start (GstBaseSrc * basesrc); +static gboolean gst_test_http_src_stop (GstBaseSrc * basesrc); +static gboolean gst_test_http_src_get_size (GstBaseSrc * basesrc, + guint64 * size); +static GstFlowReturn gst_test_http_src_create (GstBaseSrc * basesrc, + guint64 offset, guint length, GstBuffer ** ret); +static void gst_test_http_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_test_http_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + + +#define _do_init \ + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_test_http_src_uri_handler_init); + +#define gst_test_http_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstTestHTTPSrc, gst_test_http_src, + GST_TYPE_BASE_SRC, _do_init); + +static void +gst_test_http_src_class_init (GstTestHTTPSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + + gobject_class->set_property = gst_test_http_src_set_property; + gobject_class->get_property = gst_test_http_src_get_property; + gobject_class->finalize = gst_test_http_src_finalize; + + g_object_class_install_property (gobject_class, PROP_COMPRESS, + g_param_spec_boolean ("compress", "Compress", + "Allow compressed content encodings", + DEFAULT_COMPRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS, + g_param_spec_boxed ("extra-headers", "Extra Headers", + "Extra headers to append to the HTTP request", + GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_KEEP_ALIVE, + g_param_spec_boolean ("keep-alive", "keep-alive", + "Use HTTP persistent connections", DEFAULT_KEEP_ALIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_METHOD, + g_param_spec_string ("method", "HTTP method", + "The HTTP method to use (GET, HEAD, OPTIONS, etc)", + DEFAULT_HTTP_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_USER_AGENT, + g_param_spec_string ("user-agent", "User-Agent", + "Value of the User-Agent HTTP request header field", + DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_set_metadata (gstelement_class, + "Test HTTP source element for unit tests", + "Source/Network", + "Use in unit tests", "Alex Ashley <alex.ashley@youview.com>"); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_dashdemux_test_source_template)); + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_test_http_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_test_http_src_stop); + gstbasesrc_class->is_seekable = + GST_DEBUG_FUNCPTR (gst_test_http_src_is_seekable); + gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_test_http_src_do_seek); + gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_test_http_src_get_size); + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_test_http_src_create); + +} + +static void +gst_test_http_src_init (GstTestHTTPSrc * src) +{ + g_mutex_init (&src->mutex); + src->uri = NULL; + memset (&src->input, 0, sizeof (src->input)); + src->compress = FALSE; + src->keep_alive = FALSE; + src->http_method_name = NULL; + src->http_method = METHOD_GET; + src->user_agent = NULL; + src->position = 0; + src->segment_end = 0; + src->http_headers_event = NULL; + src->duration_changed = FALSE; + if (gst_test_http_src_blocksize) + gst_base_src_set_blocksize (GST_BASE_SRC (src), + gst_test_http_src_blocksize); +} + +static void +gst_test_http_src_reset_input (GstTestHTTPSrc * src) +{ + src->input.context = NULL; + src->input.size = 0; + src->input.status_code = 0; + if (src->input.request_headers) { + gst_structure_free (src->input.request_headers); + src->input.request_headers = NULL; + } + if (src->input.response_headers) { + gst_structure_free (src->input.response_headers); + src->input.response_headers = NULL; + } + if (src->http_headers_event) { + gst_event_unref (src->http_headers_event); + src->http_headers_event = NULL; + } + if (src->extra_headers) { + gst_structure_free (src->extra_headers); + src->extra_headers = NULL; + } + src->duration_changed = FALSE; +} + +static void +gst_test_http_src_finalize (GObject * object) +{ + GstTestHTTPSrc *src; + + src = GST_TEST_HTTP_SRC (object); + + g_free (src->uri); + gst_test_http_src_reset_input (src); + g_mutex_clear (&src->mutex); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_test_http_src_start (GstBaseSrc * basesrc) +{ + GstTestHTTPSrc *src; + GstStructure *http_headers; + + src = GST_TEST_HTTP_SRC (basesrc); + g_mutex_lock (&src->mutex); + gst_test_http_src_reset_input (src); + if (!src->uri) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (("No URL set.")), + ("Missing location property")); + g_mutex_unlock (&src->mutex); + return FALSE; + } + if (!gst_test_http_src_callbacks) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (("Callbacks not registered.")), ("Callbacks not registered")); + g_mutex_unlock (&src->mutex); + return FALSE; + } + if (!gst_test_http_src_callbacks->src_start) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (("src_start callback not defined.")), + ("src_start callback not registered")); + g_mutex_unlock (&src->mutex); + return FALSE; + } + if (!gst_test_http_src_callbacks->src_start (src, src->uri, &src->input, + gst_test_http_src_callback_user_data)) { + if (src->input.status_code == 0) { + src->input.status_code = 404; + } + } else { + if (src->input.status_code == 0) { + src->input.status_code = 200; + } + src->position = 0; + src->segment_end = src->input.size; + gst_base_src_set_dynamic_size (basesrc, FALSE); + basesrc->segment.duration = src->input.size; + src->duration_changed = TRUE; + } + http_headers = gst_structure_new_empty ("http-headers"); + gst_structure_set (http_headers, "uri", G_TYPE_STRING, src->uri, NULL); + if (!src->input.request_headers) { + src->input.request_headers = gst_structure_new_empty ("request-headers"); + } + if (!gst_structure_has_field_typed (src->input.request_headers, + "User-Agent", G_TYPE_STRING)) { + gst_structure_set (src->input.request_headers, + "User-Agent", G_TYPE_STRING, + src->user_agent ? src->user_agent : DEFAULT_USER_AGENT, NULL); + } + if (!gst_structure_has_field_typed (src->input.request_headers, + "Connection", G_TYPE_STRING)) { + gst_structure_set (src->input.request_headers, + "Connection", G_TYPE_STRING, + src->keep_alive ? "Keep-Alive" : "Close", NULL); + } + if (src->compress + && !gst_structure_has_field_typed (src->input.request_headers, + "Accept-Encoding", G_TYPE_STRING)) { + gst_structure_set (src->input.request_headers, "Accept-Encoding", + G_TYPE_STRING, "compress, gzip", NULL); + } + gst_structure_set (http_headers, "request-headers", GST_TYPE_STRUCTURE, + src->input.request_headers, NULL); + if (!src->input.response_headers) { + src->input.response_headers = gst_structure_new_empty ("response-headers"); + } + if (!gst_structure_has_field_typed (src->input.response_headers, + "Connection", G_TYPE_STRING)) { + gst_structure_set (src->input.response_headers, + "Connection", G_TYPE_STRING, + src->keep_alive ? "keep-alive" : "close", NULL); + } + if (!gst_structure_has_field_typed (src->input.response_headers, + "Date", G_TYPE_STRING)) { + GDateTime *now; + gchar *date_str; + + now = g_date_time_new_now_local (); + fail_unless (now != NULL); + date_str = g_date_time_format (now, "%a, %e %b %Y %T %Z"); + fail_unless (date_str != NULL); + gst_structure_set (src->input.response_headers, + "Date", G_TYPE_STRING, date_str, NULL); + g_free (date_str); + g_date_time_unref (now); + } + gst_structure_set (http_headers, "response-headers", GST_TYPE_STRUCTURE, + src->input.response_headers, NULL); + if (src->http_headers_event) { + gst_event_unref (src->http_headers_event); + } + src->http_headers_event = + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers); + g_mutex_unlock (&src->mutex); + return TRUE; +} + +static gboolean +gst_test_http_src_stop (GstBaseSrc * basesrc) +{ + GstTestHTTPSrc *src; + + src = GST_TEST_HTTP_SRC (basesrc); + g_mutex_lock (&src->mutex); + src->position = 0; + gst_test_http_src_reset_input (src); + g_mutex_unlock (&src->mutex); + return TRUE; +} + +static gboolean +gst_test_http_src_is_seekable (GstBaseSrc * basesrc) +{ + GstTestHTTPSrc *src; + gboolean ret; + + src = GST_TEST_HTTP_SRC (basesrc); + g_mutex_lock (&src->mutex); + /* if size is set, we can seek */ + ret = src->input.size > 0; + g_mutex_unlock (&src->mutex); + return ret; +} + +static gboolean +gst_test_http_src_do_seek (GstBaseSrc * basesrc, GstSegment * segment) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (basesrc); + + GST_DEBUG ("gst_test_http_src_do_seek start = %" G_GUINT64_FORMAT, + segment->start); + + /* + According to RFC7233, the range is inclusive: + The first-byte-pos value in a byte-range-spec gives the byte-offset + of the first byte in a range. The last-byte-pos value gives the + byte-offset of the last byte in the range; that is, the byte + positions specified are inclusive. Byte offsets start at zero. + */ + + g_mutex_lock (&src->mutex); + if (!src->uri) { + GST_DEBUG ("attempt to seek before URI set"); + g_mutex_unlock (&src->mutex); + return FALSE; + } + if (src->input.status_code >= 200 && src->input.status_code < 300) { + if (segment->start >= src->input.size) { + GST_DEBUG ("attempt to seek to %" G_GUINT64_FORMAT " but size is %" + G_GUINT64_FORMAT, segment->start, src->input.size); + g_mutex_unlock (&src->mutex); + return FALSE; + } + if (segment->stop != -1 && segment->stop > src->input.size) { + g_mutex_unlock (&src->mutex); + return FALSE; + } + } else { + GST_DEBUG ("Attempt to seek on a URL that will generate HTTP error %u", + src->input.status_code); + } + src->position = segment->start; + + if (segment->stop != -1) { + src->segment_end = segment->stop; + } else { + src->segment_end = src->input.size; + } + g_mutex_unlock (&src->mutex); + return TRUE; +} + +static gboolean +gst_test_http_src_get_size (GstBaseSrc * basesrc, guint64 * size) +{ + GstTestHTTPSrc *src; + + src = GST_TEST_HTTP_SRC (basesrc); + + g_mutex_lock (&src->mutex); + /* if it was started, size is set */ + if (src->uri && src->input.status_code >= 200 && src->input.status_code < 300) { + *size = src->input.size; + g_mutex_unlock (&src->mutex); + return TRUE; + } + /* cannot get the size if it wasn't started */ + g_mutex_unlock (&src->mutex); + return FALSE; +} + +static GstFlowReturn +gst_test_http_src_create (GstBaseSrc * basesrc, guint64 offset, + guint length, GstBuffer ** retbuf) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (basesrc); + guint bytes_read; + GstFlowReturn ret = GST_FLOW_OK; + guint blocksize; + + fail_unless (gst_test_http_src_callbacks != NULL); + fail_unless (gst_test_http_src_callbacks->src_create != NULL); + + GST_OBJECT_LOCK (src); + blocksize = basesrc->blocksize; + GST_OBJECT_UNLOCK (src); + + g_mutex_lock (&src->mutex); + GST_DEBUG ("gst_test_http_src_create feeding from %" G_GUINT64_FORMAT, + src->position); + if (src->uri == NULL) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + g_mutex_unlock (&src->mutex); + return GST_FLOW_ERROR; + } + if (src->input.status_code < 200 || src->input.status_code >= 300) { + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, ("%s", + "Generated requested error"), ("%s (%d), URL: %s, Redirect to: %s", + "Generated requested error", src->input.status_code, src->uri, + GST_STR_NULL (NULL))); + g_mutex_unlock (&src->mutex); + return GST_FLOW_ERROR; + } + if (src->http_method == METHOD_INVALID) { + GST_ELEMENT_ERROR (src, RESOURCE, READ, ("%s", + "Invalid HTTP method"), ("%s (%s), URL: %s", + "Invalid HTTP method", src->http_method_name, src->uri)); + g_mutex_unlock (&src->mutex); + return GST_FLOW_ERROR; + } else if (src->http_method == METHOD_HEAD) { + ret = GST_FLOW_EOS; + goto http_events; + } + fail_unless_equals_uint64 (offset, src->position); + bytes_read = MIN ((src->segment_end - src->position), blocksize); + if (bytes_read == 0) { + ret = GST_FLOW_EOS; + goto http_events; + } + ret = gst_test_http_src_callbacks->src_create (src, + offset, bytes_read, retbuf, + src->input.context, gst_test_http_src_callback_user_data); + if (ret != GST_FLOW_OK) { + goto http_events; + } + + GST_BUFFER_OFFSET (*retbuf) = src->position; + GST_BUFFER_OFFSET_END (*retbuf) = src->position + bytes_read; + + src->position += bytes_read; +http_events: + if (src->http_headers_event) { + gst_pad_push_event (GST_BASE_SRC_PAD (src), src->http_headers_event); + src->http_headers_event = NULL; + } + if (src->duration_changed) { + src->duration_changed = FALSE; + gst_element_post_message (GST_ELEMENT (src), + gst_message_new_duration_changed (GST_OBJECT (src))); + } + + g_mutex_unlock (&src->mutex); + return ret; +} + +static void +gst_test_http_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (object); + + switch (prop_id) { + case PROP_USER_AGENT: + g_free (src->user_agent); + src->user_agent = g_value_dup_string (value); + break; + case PROP_EXTRA_HEADERS:{ + const GstStructure *s = gst_value_get_structure (value); + if (src->extra_headers) + gst_structure_free (src->extra_headers); + src->extra_headers = s ? gst_structure_copy (s) : NULL; + break; + } + case PROP_COMPRESS: + src->compress = g_value_get_boolean (value); + GST_DEBUG ("Set compress=%s", src->compress ? "TRUE" : "FALSE"); + break; + case PROP_KEEP_ALIVE: + src->keep_alive = g_value_get_boolean (value); + break; + case PROP_METHOD: + g_free (src->http_method_name); + src->http_method_name = g_value_dup_string (value); + src->http_method = METHOD_INVALID; + for (guint i = 0; gst_test_http_src_methods[i].name; ++i) { + if (strcmp (gst_test_http_src_methods[i].name, + src->http_method_name) == 0) { + src->http_method = gst_test_http_src_methods[i].method; + break; + } + } + /* we don't cause an error for an invalid method at this point, + as GstSoupHTTPSrc does not use the http_method_name string until + trying to open a connection. + */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_test_http_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (object); + + switch (prop_id) { + case PROP_USER_AGENT: + g_value_set_string (value, src->user_agent); + break; + case PROP_EXTRA_HEADERS: + gst_value_set_structure (value, src->extra_headers); + break; + case PROP_COMPRESS: + g_value_set_boolean (value, src->compress); + break; + case PROP_KEEP_ALIVE: + g_value_set_boolean (value, src->keep_alive); + break; + case PROP_METHOD: + g_value_set_string (value, src->http_method_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_test_http_src_set_location (GstTestHTTPSrc * src, + const gchar * uri, GError ** error) +{ + g_mutex_lock (&src->mutex); + g_free (src->uri); + src->uri = g_strdup (uri); + g_mutex_unlock (&src->mutex); + return TRUE; +} + +static GstURIType +gst_test_http_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_test_http_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { "http", NULL }; + + return protocols; +} + +static gchar * +gst_test_http_src_uri_get_uri (GstURIHandler * handler) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (handler); + gchar *ret; + g_mutex_lock (&src->mutex); + ret = g_strdup (src->uri); + g_mutex_unlock (&src->mutex); + return ret; +} + +static gboolean +gst_test_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** err) +{ + GstTestHTTPSrc *src = GST_TEST_HTTP_SRC (handler); + + return gst_test_http_src_set_location (src, uri, err); +} + +static void +gst_test_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_test_http_src_uri_get_type; + iface->get_protocols = gst_test_http_src_uri_get_protocols; + iface->get_uri = gst_test_http_src_uri_get_uri; + iface->set_uri = gst_test_http_src_uri_set_uri; +} + +static gboolean +gst_test_http_src_plugin_init_func (GstPlugin * plugin, gpointer user_data) +{ + PluginInitContext *context = (PluginInitContext *) user_data; + gboolean ret; + + ret = + gst_element_register (plugin, context->name, context->rank, + context->type); + return ret; +} + +gboolean +gst_test_http_src_register_plugin (GstRegistry * registry, const gchar * name) +{ + gboolean ret; + PluginInitContext context; + + context.name = name; + context.rank = GST_RANK_PRIMARY + 1; + context.type = GST_TYPE_TEST_HTTP_SRC; + ret = gst_plugin_register_static_full (GST_VERSION_MAJOR, /* version */ + GST_VERSION_MINOR, /* version */ + name, /* name */ + "Replaces a souphttpsrc plugin and returns predefined data.", /* description */ + gst_test_http_src_plugin_init_func, /* init function */ + "0.0.0", /* version string */ + GST_LICENSE_UNKNOWN, /* license */ + __FILE__, /* source */ + GST_PACKAGE_NAME, /* package */ + GST_PACKAGE_ORIGIN, /* origin */ + &context /* user_data */ + ); + return ret; +} + +void +gst_test_http_src_install_callbacks (const GstTestHTTPSrcCallbacks * + callbacks, gpointer user_data) +{ + gst_test_http_src_callbacks = callbacks; + gst_test_http_src_callback_user_data = user_data; +} + +void +gst_test_http_src_set_default_blocksize (guint blocksize) +{ + gst_test_http_src_blocksize = blocksize; +} diff --git a/tests/check/elements/test_http_src.h b/tests/check/elements/test_http_src.h new file mode 100644 index 000000000..61161afb4 --- /dev/null +++ b/tests/check/elements/test_http_src.h @@ -0,0 +1,132 @@ +/* HTTP source element for use in tests + * + * Copyright (c) <2015> YouView TV Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_TEST_HTTP_SRC_H__ +#define __GST_TEST_HTTP_SRC_H__ + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_TEST_HTTP_SRC (gst_test_http_src_get_type ()) + +/* structure used by src_start function to configure the + * GstTestHTTPSrc plugin. + * It specifies information about a given URI. + */ +typedef struct _GstTestHTTPSrcInput +{ + gpointer context; /* opaque pointer that can be used in callbacks */ + guint64 size; /* size of resource, in bytes */ + GstStructure *request_headers; + GstStructure *response_headers; + guint status_code; /* HTTP status code */ +} GstTestHTTPSrcInput; + +/* Opaque structure used by GstTestHTTPSrc */ +typedef struct _GstTestHTTPSrc GstTestHTTPSrc; + +typedef struct _GstTestHTTPSrcCallbacks { + /** + * src_start: + * @src: The #GstTestHTTPSrc calling this callback + * @uri: The URI that is being requested + * @input_data: (out) The implementation of this callback is + * responsible for filling in this #GstTestHTTPSrcInput + * with the appropriate information, return returning %TRUE. + * If returning %FALSE, only GstTestHTTPSrcInput::status_code + * should be updated. + * Returns: %TRUE if GstTestHTTPSrc should respond to this URI, + * using the supplied input_data. + * + * src_start is used to "open" the given URI. The callback must return + * %TRUE to simulate a success, and set appropriate fields in input_data. + * Returning %FALSE indicates that the request URI is not found. + * In this situation GstTestHTTPSrc will cause the appropriate + * 404 error to be posted to the bus + */ + gboolean (*src_start)(GstTestHTTPSrc *src, + const gchar *uri, + GstTestHTTPSrcInput *input_data, + gpointer user_data); + /** + * src_create: + * @src: the #GstTestHTTPSrc calling this callback + * @offset: the offset from the start of the resource + * @length: requested number of bytes + * @retbuf: (out) used to return a newly allocated #GstBuffer + * @context: (allow none) the value of the context field + * in #GstTestHTTPSrcInput. + * @user_data: the value of user_data provided to + * #gst_test_http_src_install_callbacks + * Returns: %GST_FLOW_OK to indicate success, or some other value of + * #GstFlowReturn to indicate EOS or error. + * + * The src_create function is used to create a #GstBuffer for + * simulating the data that is returned when accessing this + * "open" stream. It can also be used to simulate various error + * conditions by returning something other than %GST_FLOW_OK + */ + GstFlowReturn (*src_create)(GstTestHTTPSrc *src, + guint64 offset, + guint length, + GstBuffer ** retbuf, + gpointer context, + gpointer user_data); +} GstTestHTTPSrcCallbacks; + +GType gst_test_http_src_get_type (void); + +/** + * gst_test_http_src_register_plugin: + * @registry: the #GstRegistry to use for registering this plugin + * @name: the name to use for this plugin + * Returns: true if successful + * + * Registers this plugin with the GstRegitry using the given name. It will + * be given a high rank, so that it will be picked in preference to any + * other element that implements #GstURIHandler. + */ +gboolean gst_test_http_src_register_plugin (GstRegistry * registry, const gchar * name); + +/** + * gst_test_http_src_install_callbacks: + * @callbacks: the #GstTestHTTPSrcCallbacks callback functions that will + * be called every time this element is asked to open a URI or provide data + * for an open URI. + * @user_data: a pointer that is passed to every callback + */ +void gst_test_http_src_install_callbacks (const GstTestHTTPSrcCallbacks *callbacks, gpointer user_data); + +/** + * gst_test_http_src_set_default_blocksize: + * @blocksize: the default block size to use (0=use #GstBaseSrc default) + * + * Set the default blocksize that will be used by instances of + * #GstTestHTTPSrc. It specifies the size (in bytes) that will be + * returned in each #GstBuffer. This default can be overridden + * by an instance of #GstTestHTTPSrc using the "blocksize" property + * of #GstBaseSrc + */ +void gst_test_http_src_set_default_blocksize (guint blocksize); + +G_END_DECLS + +#endif /* __GST_TEST_HTTP_SRC_H__ */ |