mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 01:00:37 +00:00
c6243c7d00
Until we will have support to control the generating thread from fakeHTTPsrc element, the test testFragmentDownloadError is disabled. https://bugzilla.gnome.org/show_bug.cgi?id=757776
1549 lines
53 KiB
C
1549 lines
53 KiB
C
/* 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/app/gstappsink.h>
|
|
#include "fake_http_src.h"
|
|
|
|
#define GST_FAKE_SOUP_HTTP_SRC_NAME "fake-soup-http-src"
|
|
|
|
/* forward declarations */
|
|
struct _GstDashDemuxTestData;
|
|
|
|
/* 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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* 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];
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
} 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);
|
|
|
|
testOutputStreamData = getTestOutputDataByPad (testData, pad);
|
|
|
|
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;
|
|
}
|
|
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 (buffer, &info);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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 an mpd with an audio and a video stream
|
|
*
|
|
*/
|
|
GST_START_TEST (simpleTest)
|
|
{
|
|
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=\"PT135.743S\">"
|
|
" <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>"
|
|
" <AdaptationSet mimeType=\"video/webm\""
|
|
" subsegmentAlignment=\"true\">"
|
|
" <Representation id=\"242\""
|
|
" codecs=\"vp9\""
|
|
" width=\"426\""
|
|
" height=\"240\""
|
|
" startWithSAP=\"1\""
|
|
" bandwidth=\"490208\">"
|
|
" <BaseURL>video.webm</BaseURL>"
|
|
" <SegmentBase indexRange=\"234-682\""
|
|
" indexRangeExact=\"true\">"
|
|
" <Initialization range=\"0-233\" />"
|
|
" </SegmentBase>"
|
|
" </Representation></AdaptationSet></Period></MPD>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", mpd, 0},
|
|
{"http://unit.test/audio.webm", NULL, 5000},
|
|
{"http://unit.test/video.webm", NULL, 9000},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
GstDashDemuxTestOutputStreamData outputTestData[] = {
|
|
{"audio_00", 5000, NULL},
|
|
{"video_00", 9000, NULL},
|
|
};
|
|
|
|
Callbacks cb = { 0 };
|
|
cb.appSinkGotDataCallback = checkDataReceived;
|
|
cb.appSinkGotEosCallback = checkSizeOfDataReceived;
|
|
|
|
runTest (inputTestData,
|
|
outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]),
|
|
&cb);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/*
|
|
* Test an mpd with 2 periods
|
|
*
|
|
*/
|
|
GST_START_TEST (testTwoPeriods)
|
|
{
|
|
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=\"PT300S\">"
|
|
" <Period id=\"Period0\" duration=\"PT0.1S\">"
|
|
" <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>audio1.webm</BaseURL>"
|
|
" <SegmentBase indexRange=\"4452-4686\""
|
|
" indexRangeExact=\"true\">"
|
|
" <Initialization range=\"0-4451\" />"
|
|
" </SegmentBase>"
|
|
" </Representation>"
|
|
" </AdaptationSet>"
|
|
" <AdaptationSet mimeType=\"video/webm\""
|
|
" subsegmentAlignment=\"true\">"
|
|
" <Representation id=\"242\""
|
|
" codecs=\"vp9\""
|
|
" width=\"426\""
|
|
" height=\"240\""
|
|
" startWithSAP=\"1\""
|
|
" bandwidth=\"490208\">"
|
|
" <BaseURL>video1.webm</BaseURL>"
|
|
" <SegmentBase indexRange=\"234-682\""
|
|
" indexRangeExact=\"true\">"
|
|
" <Initialization range=\"0-233\" />"
|
|
" </SegmentBase>"
|
|
" </Representation>"
|
|
" </AdaptationSet>"
|
|
" </Period>"
|
|
" <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>audio2.webm</BaseURL>"
|
|
" <SegmentBase indexRange=\"4452-4686\""
|
|
" indexRangeExact=\"true\">"
|
|
" <Initialization range=\"0-4451\" />"
|
|
" </SegmentBase>"
|
|
" </Representation>"
|
|
" </AdaptationSet>"
|
|
" <AdaptationSet mimeType=\"video/webm\""
|
|
" subsegmentAlignment=\"true\">"
|
|
" <Representation id=\"242\""
|
|
" codecs=\"vp9\""
|
|
" width=\"426\""
|
|
" height=\"240\""
|
|
" startWithSAP=\"1\""
|
|
" bandwidth=\"490208\">"
|
|
" <BaseURL>video2.webm</BaseURL>"
|
|
" <SegmentBase indexRange=\"234-682\""
|
|
" indexRangeExact=\"true\">"
|
|
" <Initialization range=\"0-233\" />"
|
|
" </SegmentBase>"
|
|
" </Representation></AdaptationSet></Period></MPD>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", 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[] = {
|
|
{"audio_00", 5001, NULL},
|
|
{"video_00", 9001, NULL},
|
|
{"audio_01", 5002, NULL},
|
|
{"video_01", 9002, NULL},
|
|
};
|
|
|
|
Callbacks cb = { 0 };
|
|
cb.appSinkGotDataCallback = checkDataReceived;
|
|
cb.appSinkGotEosCallback = checkSizeOfDataReceived;
|
|
|
|
runTest (inputTestData,
|
|
outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]),
|
|
&cb);
|
|
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/* test setting a property on an object */
|
|
#define test_int_prop(object, name, value) \
|
|
do \
|
|
{ \
|
|
int val = value; \
|
|
int val_after; \
|
|
g_object_set (object, name, val, NULL); \
|
|
g_object_get (object, name, &val_after, NULL); \
|
|
fail_unless (val_after == val, "property check failed for %s: set to %d, but got %d", \
|
|
name, val, val_after); \
|
|
} while (0)
|
|
|
|
#define test_float_prop(object, name, value) \
|
|
do \
|
|
{ \
|
|
float val = value; \
|
|
float val_after; \
|
|
g_object_set (object, name, val, NULL); \
|
|
g_object_get (object, name, &val_after, NULL); \
|
|
fail_unless (val_after == val, "property check failed for %s: set to %f, but got %f", \
|
|
name, val, val_after); \
|
|
} while (0)
|
|
|
|
/* test setting an invalid value for a property on an object.
|
|
* Expect an assert and the property to remain unchanged
|
|
*/
|
|
#define test_invalid_int_prop(object, name, value) \
|
|
do \
|
|
{ \
|
|
int val_before; \
|
|
int val_after; \
|
|
int val = value; \
|
|
g_object_get (object, name, &val_before, NULL); \
|
|
ASSERT_WARNING (g_object_set (object, name, val, NULL)); \
|
|
g_object_get (object, name, &val_after, NULL); \
|
|
fail_unless (val_after == val_before, "property check failed for %s: before %d, after %d", \
|
|
name, val_before, val_after); \
|
|
} while (0)
|
|
|
|
#define test_invalid_float_prop(object, name, value) \
|
|
do \
|
|
{ \
|
|
float val_before; \
|
|
float val_after; \
|
|
float val = value; \
|
|
g_object_get (object, name, &val_before, NULL); \
|
|
ASSERT_WARNING (g_object_set (object, name, val, NULL)); \
|
|
g_object_get (object, name, &val_after, NULL); \
|
|
fail_unless (val_after == val_before, "property check failed for %s: before %f, after %f", \
|
|
name, val_before, val_after); \
|
|
} while (0)
|
|
|
|
static void
|
|
setAndTestDashParams (GstDashDemuxTestData * testData)
|
|
{
|
|
GstElement *dashdemux = testData->scratchData->dashdemux;
|
|
|
|
test_int_prop (G_OBJECT (dashdemux), "connection-speed", 1000);
|
|
test_invalid_int_prop (G_OBJECT (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_int_prop (G_OBJECT (dashdemux), "max-buffering-time", 15);
|
|
test_invalid_int_prop (G_OBJECT (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_int_prop (G_OBJECT (dashdemux), "max-bitrate", 1000);
|
|
test_invalid_int_prop (G_OBJECT (dashdemux), "max-bitrate", 10);
|
|
}
|
|
|
|
/*
|
|
* Test setting parameters
|
|
*
|
|
*/
|
|
GST_START_TEST (testParameters)
|
|
{
|
|
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=\"PT135.743S\">"
|
|
" <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>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", mpd, 0},
|
|
{"http://unit.test/audio.webm", NULL, 5000},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
GstDashDemuxTestOutputStreamData outputTestData[] = {
|
|
{"audio_00", 5000, NULL},
|
|
};
|
|
|
|
Callbacks cb = { 0 };
|
|
cb.preTestCallback = setAndTestDashParams;
|
|
cb.appSinkGotDataCallback = checkDataReceived;
|
|
cb.appSinkGotEosCallback = checkSizeOfDataReceived;
|
|
|
|
runTest (inputTestData,
|
|
outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]),
|
|
&cb);
|
|
}
|
|
|
|
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
|
|
*
|
|
*/
|
|
GST_START_TEST (testSeek)
|
|
{
|
|
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=\"PT135.743S\">"
|
|
" <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>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", mpd, 0},
|
|
{"http://unit.test/audio.webm", NULL, 10000},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
GstDashDemuxTestOutputStreamData outputTestData[] = {
|
|
{"audio_00", 10000, NULL},
|
|
};
|
|
|
|
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;
|
|
|
|
static void
|
|
testDownloadErrorPreTestCallback (GstDashDemuxTestData * testData)
|
|
{
|
|
/* expect error on pipeline */
|
|
testData->expectError = TRUE;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
runTest (inputTestData, outputTestData,
|
|
sizeof (outputTestData) / sizeof (outputTestData[0]), &cb);
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* 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);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Test fragment download error
|
|
* Let the adaptive demux download a few bytes, then instruct the fake soup http
|
|
* src 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>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", mpd, 0},
|
|
{"http://unit.test/audio.webm", NULL, 5000},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
GstDashDemuxTestOutputStreamData outputTestData[] = {
|
|
{"audio_00", 5000, NULL},
|
|
};
|
|
|
|
Callbacks cb = { 0 };
|
|
cb.appSinkGotDataCallback = testFragmentDownloadErrorCheckDataReceived;
|
|
cb.appSinkGotEosCallback = testFragmentDownloadErrorCheckSizeOfDataReceived;
|
|
|
|
runTest (inputTestData, outputTestData,
|
|
sizeof (outputTestData) / sizeof (outputTestData[0]), &cb);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
#endif
|
|
|
|
/* generate queries to adaptive demux */
|
|
static gboolean
|
|
testQueryCheckDataReceived (GstDashDemuxTestData * testData,
|
|
GstDashDemuxTestOutputStreamData * testOutputStreamData, GstBuffer * buffer)
|
|
{
|
|
GList *pads;
|
|
GstPad *pad;
|
|
GstQuery *query;
|
|
gboolean ret;
|
|
gint64 duration;
|
|
gboolean seekable;
|
|
gint64 segment_start;
|
|
gint64 segment_end;
|
|
gchar *uri;
|
|
gchar *redirect_uri;
|
|
gboolean redirect_permanent;
|
|
|
|
pads = GST_ELEMENT_PADS (testOutputStreamData->scratchData->appsink);
|
|
|
|
/* AppSink should have only 1 pad */
|
|
fail_unless (pads != NULL);
|
|
fail_unless (g_list_length (pads) == 1);
|
|
pad = GST_PAD (pads->data);
|
|
|
|
query = gst_query_new_duration (GST_FORMAT_TIME);
|
|
ret = gst_pad_peer_query (pad, query);
|
|
fail_unless (ret == TRUE);
|
|
gst_query_parse_duration (query, NULL, &duration);
|
|
fail_unless (duration == 135743 * GST_MSECOND);
|
|
gst_query_unref (query);
|
|
|
|
query = gst_query_new_seeking (GST_FORMAT_TIME);
|
|
ret = gst_pad_peer_query (pad, query);
|
|
fail_unless (ret == TRUE);
|
|
gst_query_parse_seeking (query, NULL, &seekable, &segment_start,
|
|
&segment_end);
|
|
fail_unless (seekable == TRUE);
|
|
fail_unless (segment_start == 0);
|
|
fail_unless (segment_end == duration);
|
|
gst_query_unref (query);
|
|
|
|
query = gst_query_new_uri ();
|
|
ret = gst_pad_peer_query (pad, query);
|
|
fail_unless (ret == TRUE);
|
|
gst_query_parse_uri (query, &uri);
|
|
gst_query_parse_uri_redirection (query, &redirect_uri);
|
|
gst_query_parse_uri_redirection_permanent (query, &redirect_permanent);
|
|
fail_unless (strcmp (uri, "http://unit.test/test.mpd") == 0);
|
|
/* adaptive demux does not reply with redirect information */
|
|
fail_unless (redirect_uri == NULL);
|
|
fail_unless (redirect_permanent == FALSE);
|
|
g_free (uri);
|
|
g_free (redirect_uri);
|
|
gst_query_unref (query);
|
|
|
|
return checkDataReceived (testData, testOutputStreamData, buffer);
|
|
}
|
|
|
|
/*
|
|
* Test queries
|
|
*
|
|
*/
|
|
GST_START_TEST (testQuery)
|
|
{
|
|
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=\"PT135.743S\">"
|
|
" <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>";
|
|
|
|
const GstFakeHttpSrcInputData inputTestData[] = {
|
|
{"http://unit.test/test.mpd", mpd, 0},
|
|
{"http://unit.test/audio.webm", NULL, 5000},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
GstDashDemuxTestOutputStreamData outputTestData[] = {
|
|
{"audio_00", 5000, NULL},
|
|
};
|
|
|
|
Callbacks cb = { 0 };
|
|
cb.appSinkGotDataCallback = testQueryCheckDataReceived;
|
|
cb.appSinkGotEosCallback = checkSizeOfDataReceived;
|
|
|
|
runTest (inputTestData,
|
|
outputTestData, sizeof (outputTestData) / sizeof (outputTestData[0]),
|
|
&cb);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
static Suite *
|
|
dash_demux_suite (void)
|
|
{
|
|
Suite *s = suite_create ("dash_demux");
|
|
TCase *tc_basicTest = tcase_create ("basicTest");
|
|
|
|
tcase_add_test (tc_basicTest, simpleTest);
|
|
tcase_add_test (tc_basicTest, testTwoPeriods);
|
|
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, testQuery);
|
|
|
|
tcase_add_unchecked_fixture (tc_basicTest, test_setup, test_teardown);
|
|
|
|
suite_add_tcase (s, tc_basicTest);
|
|
|
|
return s;
|
|
}
|
|
|
|
GST_CHECK_MAIN (dash_demux);
|