gstreamer/tests/check/elements/adaptive_demux_common.c
Alex Ashley ae3ed25025 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.
2015-12-22 11:15:32 -03:00

454 lines
15 KiB
C

/* 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);
}