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.
This commit is contained in:
Alex Ashley 2015-11-10 13:13:35 +00:00 committed by Thiago Santos
parent 0e34c02dd6
commit ae3ed25025
10 changed files with 2486 additions and 1607 deletions

View file

@ -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)

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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, &currentState, &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);
}

View file

@ -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__ */

File diff suppressed because it is too large Load diff

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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__ */