mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 11:29:55 +00:00
455 lines
15 KiB
C
455 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);
|
||
|
}
|