mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 21:51:09 +00:00
e62eb34bfc
Use init macros to avoid gst-indent making a mess of it. https://bugzilla.gnome.org/show_bug.cgi?id=787980
6067 lines
169 KiB
C
6067 lines
169 KiB
C
/* GStreamer
|
|
*
|
|
* tests for the ipcpipelinesrc/ipcpipelinesink elements
|
|
*
|
|
* Copyright (C) 2015-2017 YouView TV Ltd
|
|
* Author: Vincent Penquerc'h <vincent.penquerch@collabora.co.uk>
|
|
* Author: George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#define _GNU_SOURCE /* See feature_test_macros(7) */
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/file.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <gst/check/gstcheck.h>
|
|
#include <string.h>
|
|
|
|
#ifndef HAVE_PIPE2
|
|
static int
|
|
pipe2 (int pipedes[2], int flags)
|
|
{
|
|
int ret = pipe (pipedes);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (flags != 0) {
|
|
ret = fcntl (pipedes[0], F_SETFL, flags);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = fcntl (pipedes[1], F_SETFL, flags);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* This enum contains flags that are used to configure the setup that
|
|
* test_base() will do internally */
|
|
typedef enum
|
|
{
|
|
/* Features related to the multi-process setup */
|
|
TEST_FEATURE_SPLIT_SINKS = 0x1, /* separate audio and video sink processes */
|
|
TEST_FEATURE_RECOVERY_SLAVE_PROCESS = 0x2,
|
|
TEST_FEATURE_RECOVERY_MASTER_PROCESS = 0x4,
|
|
|
|
TEST_FEATURE_HAS_VIDEO = 0x10,
|
|
TEST_FEATURE_LIVE = 0x20, /* sets is-live=true in {audio,video}testsrc */
|
|
TEST_FEATURE_ASYNC_SINK = 0x40, /* sets sync=false in fakesink */
|
|
TEST_FEATURE_ERROR_SINK = 0x80, /* generates error message in the slave */
|
|
TEST_FEATURE_LONG_DURATION = 0x100, /* bigger num-buffers in {audio,video}testsrc */
|
|
TEST_FEATURE_FILTER_SINK_CAPS = 0x200, /* plugs capsfilter before fakesink */
|
|
|
|
/* Source selection; Use only one of those, do not combine! */
|
|
TEST_FEATURE_TEST_SOURCE = 0x400,
|
|
TEST_FEATURE_WAV_SOURCE = 0x800,
|
|
TEST_FEATURE_MPEGTS_SOURCE = 0x1000 | TEST_FEATURE_HAS_VIDEO,
|
|
TEST_FEATURE_LIVE_A_SOURCE =
|
|
TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_LIVE | TEST_FEATURE_ASYNC_SINK,
|
|
TEST_FEATURE_LIVE_AV_SOURCE =
|
|
TEST_FEATURE_LIVE_A_SOURCE | TEST_FEATURE_HAS_VIDEO,
|
|
} TestFeatures;
|
|
|
|
/* This is the data structure that each function of the each test receives
|
|
* in user_data. It contains pointers to stack-allocated, test-specific
|
|
* structures that contain the test parameters (input data), the runtime
|
|
* data of the master (source) process (master data) and the runtime data
|
|
* of the slave (sink) process (slave data) */
|
|
typedef struct
|
|
{
|
|
gpointer id; /* input data struct */
|
|
gpointer md; /* master data struct */
|
|
gpointer sd; /* slave data struct */
|
|
|
|
TestFeatures features; /* the features that this test is running with */
|
|
|
|
/* whether there is both an audio and a video stream
|
|
* in this process'es pipeline */
|
|
gboolean two_streams;
|
|
|
|
/* the pipeline of this process; could be either master or slave */
|
|
GstElement *p;
|
|
|
|
/* this callback will be called in the master process when
|
|
* the master gets STATE_CHANGED with the new state being state_target */
|
|
void (*state_changed_cb) (gpointer);
|
|
GstState state_target;
|
|
|
|
/* used by EXCLUSIVE_CALL() */
|
|
gint exclusive_call_counter;
|
|
} test_data;
|
|
|
|
/* All pipelines do not start buffers at exactly zero, so we consider
|
|
timestamps within a small tolerance to be zero */
|
|
#define CLOSE_ENOUGH_TO_ZERO (GST_SECOND / 5)
|
|
|
|
/* milliseconds */
|
|
#define STEP_AT 100
|
|
#define PAUSE_AT 500
|
|
#define SEEK_AT 700
|
|
#define QUERY_AT 600
|
|
#define MESSAGE_AT 600
|
|
#define CRASH_AT 600
|
|
#define STOP_AT 600
|
|
|
|
/* Rough duration of the sample files we use */
|
|
#define MPEGTS_SAMPLE_ROUGH_DURATION (GST_SECOND * 64 / 10)
|
|
#define WAV_SAMPLE_ROUGH_DURATION (GST_SECOND * 65 / 10)
|
|
|
|
enum
|
|
{
|
|
MSG_ACK = 0,
|
|
MSG_START = 1
|
|
};
|
|
|
|
static GMainLoop *loop;
|
|
static gboolean child_dead;
|
|
static int pipesfa[2], pipesba[2], pipesfv[2], pipesbv[2];
|
|
static int ctlsock[2];
|
|
static int recovery_pid = 0;
|
|
static int check_fd = -1;
|
|
static GList *weak_refs = NULL;
|
|
|
|
/* lock helpers */
|
|
|
|
#define FAIL_IF(x) do { lock_check (); fail_if(x); unlock_check (); } while(0)
|
|
#define FAIL_UNLESS(x) do { lock_check (); fail_unless(x); unlock_check (); } while(0)
|
|
#define FAIL_UNLESS_EQUALS_INT(x,y) do { lock_check (); fail_unless_equals_int(x,y); unlock_check (); } while(0)
|
|
#define FAIL() do { lock_check (); fail(); unlock_check (); } while(0)
|
|
|
|
static void
|
|
lock_check (void)
|
|
{
|
|
flock (check_fd, LOCK_EX);
|
|
}
|
|
|
|
static void
|
|
unlock_check (void)
|
|
{
|
|
flock (check_fd, LOCK_UN);
|
|
}
|
|
|
|
static void
|
|
setup_lock (void)
|
|
{
|
|
gchar *name = NULL;
|
|
check_fd = g_file_open_tmp (NULL, &name, NULL);
|
|
unlink (name);
|
|
g_free (name);
|
|
}
|
|
|
|
/* tracking for ipcpipeline elements; this is used mainly to detect leaks,
|
|
* but also to provide a method for calling "disconnect" on all of them
|
|
* in the tests that require it */
|
|
|
|
static void
|
|
remove_weak_ref (GstElement * element)
|
|
{
|
|
weak_refs = g_list_remove (weak_refs, element);
|
|
}
|
|
|
|
static void
|
|
add_weak_ref (GstElement * element)
|
|
{
|
|
weak_refs = g_list_append (weak_refs, element);
|
|
g_object_weak_ref (G_OBJECT (element), (GWeakNotify) remove_weak_ref,
|
|
element);
|
|
}
|
|
|
|
static void
|
|
disconnect_ipcpipeline_elements (void)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = weak_refs; l; l = l->next) {
|
|
g_signal_emit_by_name (G_OBJECT (l->data), "disconnect", NULL);
|
|
}
|
|
}
|
|
|
|
/* helper functions */
|
|
|
|
#define EXCLUSIVE_CALL(td,func) \
|
|
G_STMT_START { \
|
|
if (!td->two_streams || \
|
|
g_atomic_int_add (&td->exclusive_call_counter, 1) == 1) { \
|
|
func; \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
static void
|
|
cleanup_bus (GstElement * pipeline)
|
|
{
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
|
|
gst_bus_set_flushing (GST_ELEMENT_BUS (pipeline), TRUE);
|
|
}
|
|
|
|
static void
|
|
setup_log (const char *logfile, int append)
|
|
{
|
|
FILE *f;
|
|
|
|
f = fopen (logfile, append ? "a+" : "w");
|
|
gst_debug_add_log_function (gst_debug_log_default, f, NULL);
|
|
}
|
|
|
|
static GstElement *
|
|
create_pipeline (const char *type)
|
|
{
|
|
GstElement *pipeline;
|
|
|
|
pipeline = gst_element_factory_make (type, NULL);
|
|
FAIL_UNLESS (pipeline);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GQuark
|
|
to_be_removed_quark (void)
|
|
{
|
|
static GQuark q = 0;
|
|
if (!q)
|
|
q = g_quark_from_static_string ("to_be_removed");
|
|
return q;
|
|
}
|
|
|
|
static gboolean
|
|
are_caps_audio (const GstCaps * caps)
|
|
{
|
|
GstStructure *structure;
|
|
const char *name;
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
name = gst_structure_get_name (structure);
|
|
return g_str_has_prefix (name, "audio/");
|
|
}
|
|
|
|
static gboolean
|
|
are_caps_video (const GstCaps * caps)
|
|
{
|
|
GstStructure *structure;
|
|
const char *name;
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
name = gst_structure_get_name (structure);
|
|
return (g_str_has_prefix (name, "video/")
|
|
&& strcmp (name, "video/x-dvd-subpicture"));
|
|
}
|
|
|
|
static int
|
|
caps2idx (GstCaps * caps, gboolean two_streams)
|
|
{
|
|
int idx;
|
|
|
|
if (!two_streams)
|
|
return 0;
|
|
|
|
if (are_caps_audio (caps)) {
|
|
idx = 0;
|
|
} else if (are_caps_video (caps)) {
|
|
idx = 1;
|
|
} else {
|
|
FAIL_IF (1);
|
|
idx = 0;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static int
|
|
pad2idx (GstPad * pad, gboolean two_streams)
|
|
{
|
|
GstCaps *caps;
|
|
int idx;
|
|
|
|
if (!two_streams)
|
|
return 0;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
if (!caps)
|
|
caps = gst_pad_get_pad_template_caps (pad);
|
|
FAIL_UNLESS (caps);
|
|
|
|
idx = caps2idx (caps, two_streams);
|
|
|
|
gst_caps_unref (caps);
|
|
return idx;
|
|
}
|
|
|
|
static gboolean
|
|
stop_pipeline (gpointer user_data)
|
|
{
|
|
GstElement *pipeline = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
gst_object_unref (pipeline);
|
|
g_main_loop_quit (loop);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
hook_peer_probe_types (const GValue * sinkv, GstPadProbeCallback probe,
|
|
unsigned int types, gpointer user_data)
|
|
{
|
|
GstElement *sink;
|
|
GstPad *pad, *peer;
|
|
|
|
sink = g_value_get_object (sinkv);
|
|
FAIL_UNLESS (sink);
|
|
pad = gst_element_get_static_pad (sink, "sink");
|
|
FAIL_UNLESS (pad);
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
gst_pad_add_probe (peer, types, probe, user_data, NULL);
|
|
gst_object_unref (peer);
|
|
gst_object_unref (pad);
|
|
}
|
|
|
|
static void
|
|
hook_probe_types (const GValue * sinkv, GstPadProbeCallback probe,
|
|
unsigned int types, gpointer user_data)
|
|
{
|
|
GstElement *sink;
|
|
GstPad *pad;
|
|
|
|
sink = g_value_get_object (sinkv);
|
|
FAIL_UNLESS (sink);
|
|
pad = gst_element_get_static_pad (sink, "sink");
|
|
FAIL_UNLESS (pad);
|
|
gst_pad_add_probe (pad, types, probe, user_data, NULL);
|
|
gst_object_unref (pad);
|
|
}
|
|
|
|
static void
|
|
hook_probe (const GValue * sinkv, GstPadProbeCallback probe, gpointer user_data)
|
|
{
|
|
hook_probe_types (sinkv, probe,
|
|
GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH |
|
|
GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, user_data);
|
|
}
|
|
|
|
/* the master process'es async GstBus callback */
|
|
static gboolean
|
|
master_bus_msg (GstBus * bus, GstMessage * message, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
/* elements we are removing might error out as they are taken out
|
|
of the pipeline, and fail to push. We don't care about those. */
|
|
if (g_object_get_qdata (G_OBJECT (GST_MESSAGE_SRC (message)),
|
|
to_be_removed_quark ()))
|
|
break;
|
|
|
|
gst_message_parse_error (message, &err, &dbg);
|
|
g_printerr ("ERROR: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("ERROR debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
case GST_MESSAGE_WARNING:{
|
|
GError *err;
|
|
gchar *dbg;
|
|
|
|
gst_message_parse_warning (message, &err, &dbg);
|
|
g_printerr ("WARNING: %s\n", err->message);
|
|
if (dbg != NULL)
|
|
g_printerr ("WARNING debug information: %s\n", dbg);
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
case GST_MESSAGE_EOS:
|
|
g_main_loop_quit (loop);
|
|
break;
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (td->p)
|
|
&& td->state_changed_cb) {
|
|
GstState state;
|
|
gst_message_parse_state_changed (message, NULL, &state, NULL);
|
|
if (state == td->state_target)
|
|
td->state_changed_cb (td);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* source construction functions */
|
|
|
|
static GstElement *
|
|
create_wavparse_source_loc (const char *loc, int fdina, int fdouta)
|
|
{
|
|
GstElement *sbin, *pipeline, *filesrc, *ipcpipelinesink;
|
|
GError *e = NULL;
|
|
|
|
pipeline = create_pipeline ("pipeline");
|
|
sbin =
|
|
gst_parse_bin_from_description ("pushfilesrc name=filesrc ! wavparse",
|
|
TRUE, &e);
|
|
FAIL_IF (e || !sbin);
|
|
gst_element_set_name (sbin, "source");
|
|
filesrc = gst_bin_get_by_name (GST_BIN (sbin), "filesrc");
|
|
FAIL_UNLESS (filesrc);
|
|
g_object_set (filesrc, "location", loc, NULL);
|
|
gst_object_unref (filesrc);
|
|
ipcpipelinesink =
|
|
gst_element_factory_make ("ipcpipelinesink", "ipcpipelinesink");
|
|
add_weak_ref (ipcpipelinesink);
|
|
g_object_set (ipcpipelinesink, "fdin", fdina, "fdout", fdouta, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), sbin, ipcpipelinesink, NULL);
|
|
FAIL_UNLESS (gst_element_link_many (sbin, ipcpipelinesink, NULL));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static void
|
|
on_pad_added (GstElement * element, GstPad * pad, gpointer data)
|
|
{
|
|
GstCaps *caps;
|
|
GstElement *next;
|
|
GstBin *pipeline = data;
|
|
GstPad *sink_pad;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
if (!caps)
|
|
caps = gst_pad_get_pad_template_caps (pad);
|
|
|
|
if (are_caps_video (caps)) {
|
|
next = gst_bin_get_by_name (GST_BIN (pipeline), "vqueue");
|
|
} else if (are_caps_audio (caps)) {
|
|
next = gst_bin_get_by_name (GST_BIN (pipeline), "aqueue");
|
|
} else {
|
|
gst_caps_unref (caps);
|
|
return;
|
|
}
|
|
gst_caps_unref (caps);
|
|
|
|
FAIL_UNLESS (next);
|
|
sink_pad = gst_element_get_static_pad (next, "sink");
|
|
FAIL_UNLESS (sink_pad);
|
|
FAIL_UNLESS (gst_pad_link (pad, sink_pad) == GST_PAD_LINK_OK);
|
|
gst_object_unref (sink_pad);
|
|
|
|
gst_object_unref (next);
|
|
}
|
|
|
|
static GstElement *
|
|
create_mpegts_source_loc (const char *loc, int fdina, int fdouta, int fdinv,
|
|
int fdoutv)
|
|
{
|
|
GstElement *pipeline, *filesrc, *tsdemux, *aqueue, *vqueue, *aipcpipelinesink,
|
|
*vipcpipelinesink;
|
|
|
|
pipeline = create_pipeline ("pipeline");
|
|
filesrc = gst_element_factory_make ("filesrc", NULL);
|
|
g_object_set (filesrc, "location", loc, NULL);
|
|
tsdemux = gst_element_factory_make ("tsdemux", NULL);
|
|
g_signal_connect (tsdemux, "pad-added", G_CALLBACK (on_pad_added), pipeline);
|
|
aqueue = gst_element_factory_make ("queue", "aqueue");
|
|
aipcpipelinesink = gst_element_factory_make ("ipcpipelinesink", NULL);
|
|
add_weak_ref (aipcpipelinesink);
|
|
g_object_set (aipcpipelinesink, "fdin", fdina, "fdout", fdouta, NULL);
|
|
vqueue = gst_element_factory_make ("queue", "vqueue");
|
|
vipcpipelinesink = gst_element_factory_make ("ipcpipelinesink", NULL);
|
|
add_weak_ref (vipcpipelinesink);
|
|
g_object_set (vipcpipelinesink, "fdin", fdinv, "fdout", fdoutv, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), filesrc, tsdemux, aqueue,
|
|
aipcpipelinesink, vqueue, vipcpipelinesink, NULL);
|
|
FAIL_UNLESS (gst_element_link_many (filesrc, tsdemux, NULL));
|
|
FAIL_UNLESS (gst_element_link_many (aqueue, aipcpipelinesink, NULL));
|
|
FAIL_UNLESS (gst_element_link_many (vqueue, vipcpipelinesink, NULL));
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
create_test_source (gboolean live, int fdina, int fdouta, int fdinv, int fdoutv,
|
|
gboolean audio, gboolean video, gboolean Long)
|
|
{
|
|
GstElement *pipeline, *audiotestsrc, *aipcpipelinesink;
|
|
GstElement *videotestsrc, *vipcpipelinesink;
|
|
int L = Long ? 2 : 1;
|
|
|
|
pipeline = create_pipeline ("pipeline");
|
|
|
|
if (audio) {
|
|
audiotestsrc = gst_element_factory_make ("audiotestsrc", "audiotestsrc");
|
|
g_object_set (audiotestsrc, "is-live", live, "num-buffers",
|
|
live ? 270 * L : 600, NULL);
|
|
aipcpipelinesink = gst_element_factory_make ("ipcpipelinesink",
|
|
"aipcpipelinesink");
|
|
add_weak_ref (aipcpipelinesink);
|
|
g_object_set (aipcpipelinesink, "fdin", fdina, "fdout", fdouta, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), audiotestsrc, aipcpipelinesink, NULL);
|
|
FAIL_UNLESS (gst_element_link_many (audiotestsrc, aipcpipelinesink, NULL));
|
|
}
|
|
|
|
if (video) {
|
|
videotestsrc = gst_element_factory_make ("videotestsrc", "videotestsrc");
|
|
g_object_set (videotestsrc, "is-live", live, "num-buffers",
|
|
live ? 190 * L : 600, NULL);
|
|
vipcpipelinesink =
|
|
gst_element_factory_make ("ipcpipelinesink", "vipcpipelinesink");
|
|
add_weak_ref (vipcpipelinesink);
|
|
g_object_set (vipcpipelinesink, "fdin", fdinv, "fdout", fdoutv, NULL);
|
|
gst_bin_add_many (GST_BIN (pipeline), videotestsrc, vipcpipelinesink, NULL);
|
|
FAIL_UNLESS (gst_element_link_many (videotestsrc, vipcpipelinesink, NULL));
|
|
}
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static GstElement *
|
|
create_source (TestFeatures features, int fdina, int fdouta, int fdinv,
|
|
int fdoutv, test_data * td)
|
|
{
|
|
GstElement *pipeline = NULL;
|
|
gboolean live = ! !(features & TEST_FEATURE_LIVE);
|
|
gboolean longdur = ! !(features & TEST_FEATURE_LONG_DURATION);
|
|
gboolean has_video = ! !(features & TEST_FEATURE_HAS_VIDEO);
|
|
|
|
if (features & TEST_FEATURE_TEST_SOURCE) {
|
|
|
|
pipeline = create_test_source (live, fdina, fdouta, fdinv, fdoutv, TRUE,
|
|
has_video, longdur);
|
|
} else if (features & TEST_FEATURE_WAV_SOURCE) {
|
|
pipeline = create_wavparse_source_loc ("../../tests/files/sine.wav", fdina,
|
|
fdouta);
|
|
} else if (features & TEST_FEATURE_MPEGTS_SOURCE) {
|
|
pipeline = create_mpegts_source_loc ("../../tests/files/test.ts", fdina,
|
|
fdouta, fdinv, fdoutv);
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
td->two_streams = has_video;
|
|
td->p = pipeline;
|
|
|
|
if (pipeline)
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), master_bus_msg, td);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
/* sink construction */
|
|
|
|
static GstElement *
|
|
create_sink (TestFeatures features, GstElement ** slave_pipeline,
|
|
int fdin, int fdout, const char *filter_caps)
|
|
{
|
|
GstElement *ipcpipelinesrc, *fakesink, *identity, *capsfilter, *endpoint;
|
|
GstCaps *caps;
|
|
|
|
if (!*slave_pipeline)
|
|
*slave_pipeline = create_pipeline ("ipcslavepipeline");
|
|
else
|
|
gst_object_ref (*slave_pipeline);
|
|
ipcpipelinesrc = gst_element_factory_make ("ipcpipelinesrc", NULL);
|
|
add_weak_ref (ipcpipelinesrc);
|
|
g_object_set (ipcpipelinesrc, "fdin", fdin, "fdout", fdout, NULL);
|
|
fakesink = gst_element_factory_make ("fakesink", NULL);
|
|
g_object_set (fakesink, "sync", !(features & TEST_FEATURE_ASYNC_SINK), NULL);
|
|
gst_bin_add_many (GST_BIN (*slave_pipeline), ipcpipelinesrc, fakesink, NULL);
|
|
endpoint = ipcpipelinesrc;
|
|
|
|
if (features & TEST_FEATURE_ERROR_SINK &&
|
|
!g_strcmp0 (filter_caps, "audio/x-raw")) {
|
|
identity = gst_element_factory_make ("identity", "error-element");
|
|
g_object_set (identity, "error-after", 5, NULL);
|
|
gst_bin_add (GST_BIN (*slave_pipeline), identity);
|
|
FAIL_UNLESS (gst_element_link_many (endpoint, identity, NULL));
|
|
endpoint = identity;
|
|
}
|
|
|
|
if ((features & TEST_FEATURE_FILTER_SINK_CAPS) && filter_caps) {
|
|
capsfilter = gst_element_factory_make ("capsfilter", NULL);
|
|
caps = gst_caps_from_string (filter_caps);
|
|
FAIL_UNLESS (caps);
|
|
g_object_set (capsfilter, "caps", caps, NULL);
|
|
gst_caps_unref (caps);
|
|
gst_bin_add (GST_BIN (*slave_pipeline), capsfilter);
|
|
FAIL_UNLESS (gst_element_link_many (endpoint, capsfilter, NULL));
|
|
endpoint = capsfilter;
|
|
}
|
|
FAIL_UNLESS (gst_element_link_many (endpoint, fakesink, NULL));
|
|
|
|
return *slave_pipeline;
|
|
}
|
|
|
|
static void
|
|
ensure_sink_setup (GstElement * sink, void (*setup_sink) (GstElement *, void *),
|
|
gpointer user_data)
|
|
{
|
|
static GQuark setup_done = 0;
|
|
test_data *td = user_data;
|
|
|
|
if (!setup_done)
|
|
setup_done = g_quark_from_static_string ("setup_done");
|
|
|
|
if (sink)
|
|
td->p = sink;
|
|
|
|
if (sink && setup_sink && !g_object_get_qdata (G_OBJECT (sink), setup_done)) {
|
|
g_object_set_qdata (G_OBJECT (sink), setup_done, GINT_TO_POINTER (1));
|
|
setup_sink (sink, user_data);
|
|
}
|
|
}
|
|
|
|
/* GstCheck multi-process setup helpers */
|
|
|
|
static void
|
|
on_child_exit (int signal)
|
|
{
|
|
int status = 0;
|
|
if (waitpid (-1, &status, 0) > 0 && status) {
|
|
FAIL ();
|
|
exit (status);
|
|
} else {
|
|
child_dead = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
die_on_child_death (void)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset (&sa, 0, sizeof (sa));
|
|
sa.sa_handler = on_child_exit;
|
|
sigaction (SIGCHLD, &sa, NULL);
|
|
}
|
|
|
|
static void
|
|
wait_for_recovery (void)
|
|
{
|
|
int value;
|
|
|
|
FAIL_UNLESS (ctlsock[1]);
|
|
FAIL_UNLESS (read (ctlsock[1], &value, sizeof (int)) == sizeof (int));
|
|
FAIL_UNLESS (value == MSG_START);
|
|
}
|
|
|
|
static void
|
|
ack_recovery (void)
|
|
{
|
|
int value = MSG_ACK;
|
|
FAIL_UNLESS (ctlsock[1]);
|
|
FAIL_UNLESS (write (ctlsock[1], &value, sizeof (int)) == sizeof (int));
|
|
}
|
|
|
|
static void
|
|
recreate_crashed_slave_process (void)
|
|
{
|
|
int value = MSG_START;
|
|
/* We don't recreate, because there seems to be some subtle issues
|
|
with forking after gst has started running. So we create a new
|
|
recovery process at start, and wake it up after the current
|
|
slave dies, so it can take its place. It's a bit hacky, but it
|
|
works. The spare process waits for SIGUSR2 to setup a replacement
|
|
pipeline and connect to the master. */
|
|
FAIL_UNLESS (recovery_pid);
|
|
FAIL_UNLESS (ctlsock[0]);
|
|
FAIL_UNLESS (write (ctlsock[0], &value, sizeof (int)) == sizeof (int));
|
|
FAIL_UNLESS (read (ctlsock[0], &value, sizeof (int)) == sizeof (int));
|
|
FAIL_UNLESS (value == MSG_ACK);
|
|
}
|
|
|
|
static gboolean
|
|
crash (gpointer user_data)
|
|
{
|
|
_exit (0);
|
|
}
|
|
|
|
static gboolean
|
|
unwind (gpointer user_data)
|
|
{
|
|
g_main_loop_quit (loop);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
on_unwind (int signal)
|
|
{
|
|
g_idle_add (unwind, NULL);
|
|
}
|
|
|
|
static void
|
|
listen_for_unwind (void)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset (&sa, 0, sizeof (sa));
|
|
sa.sa_handler = on_unwind;
|
|
sigaction (SIGUSR1, &sa, NULL);
|
|
}
|
|
|
|
static void
|
|
stop_listening_for_unwind (void)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset (&sa, 0, sizeof (sa));
|
|
sa.sa_handler = SIG_DFL;
|
|
sigaction (SIGUSR1, &sa, NULL);
|
|
}
|
|
|
|
#define TEST_BASE(...) test_base(__FUNCTION__,##__VA_ARGS__)
|
|
|
|
/*
|
|
* This is the main function driving the tests. All tests configure it
|
|
* by way of all the function pointers it takes as arguments, which have
|
|
* self-explanatory names.
|
|
* Most tests are run over a number of different pipelines with the same
|
|
* configuration (eg, a wavparse based pipeline, a live pipeline with
|
|
* test audio/video, etc). Those pipelines that have more than one sink
|
|
* (eg, MPEG-TS source demuxing audio and video) have a version with a
|
|
* single slave pipeline and process, and a version with the audio and
|
|
* video sinks in two different processes, each with its slave pipeline.
|
|
* The master and slave crash tests are also run via this function, and
|
|
* have specific code (grep for recovery).
|
|
* There is a fair amount of hairy stuff to do with letting the main
|
|
* check process when a subprocess has failed. Best not to look at it
|
|
* and let it do its thing.
|
|
* To add new tests, duplicate a set of tests, eg the *_end_of_stream
|
|
* ones, and s/_end_of_stream/new_test_name/g. Then do the same for
|
|
* the functions they pass as parameters to test_base. Typically, the
|
|
* source creation sets a message hook to catch things like async-done
|
|
* messages. Sink creation typically adds a probe to check that events,
|
|
* buffers, etc, come through as expected. The two success functions
|
|
* check all went well for the source and sink. Note that since all of
|
|
* these functions take the same user data structure, and the process
|
|
* will fork, writing something from one process will not be reflected
|
|
* in the other, so there is usually a subset of data relevant to the
|
|
* source, and another to the sink. But some have data relevant to both,
|
|
* it depends on the test and what you are doing.
|
|
* New tests do not have to use this framework, it just avoids spending
|
|
* more time and effort on multi process handling.
|
|
*/
|
|
static void
|
|
test_base (const char *name, TestFeatures features,
|
|
void (*run_source) (GstElement *, void *),
|
|
void (*setup_sink) (GstElement *, void *),
|
|
void (*check_success_source) (void *),
|
|
void (*check_success_sink) (void *),
|
|
gpointer input_data, gpointer master_data, gpointer slave_data)
|
|
{
|
|
GstElement *source = NULL, *asink = NULL, *vsink = NULL;
|
|
GstElement *slave_pipeline = NULL;
|
|
GstStateChangeReturn ret;
|
|
gboolean c_src, c_sink;
|
|
pid_t pid = 0;
|
|
unsigned char x;
|
|
int master_recovery_pid_comm[2] = { -1, -1 };
|
|
test_data td = { input_data, master_data, slave_data, features, FALSE, NULL,
|
|
NULL, GST_STATE_NULL, 0
|
|
};
|
|
|
|
g_print ("Testing: %s\n", name);
|
|
|
|
weak_refs = NULL;
|
|
|
|
FAIL_IF (pipe2 (pipesfa, O_NONBLOCK) < 0);
|
|
FAIL_IF (pipe2 (pipesba, O_NONBLOCK) < 0);
|
|
FAIL_IF (pipe2 (pipesfv, O_NONBLOCK) < 0);
|
|
FAIL_IF (pipe2 (pipesbv, O_NONBLOCK) < 0);
|
|
FAIL_IF (socketpair (PF_UNIX, SOCK_STREAM, 0, ctlsock) < 0);
|
|
|
|
FAIL_IF (pipesfa[0] < 0);
|
|
FAIL_IF (pipesfa[1] < 0);
|
|
FAIL_IF (pipesba[0] < 0);
|
|
FAIL_IF (pipesba[1] < 0);
|
|
FAIL_IF (pipesfv[0] < 0);
|
|
FAIL_IF (pipesfv[1] < 0);
|
|
FAIL_IF (pipesbv[0] < 0);
|
|
FAIL_IF (pipesbv[1] < 0);
|
|
|
|
gst_debug_remove_log_function (gst_debug_log_default);
|
|
|
|
listen_for_unwind ();
|
|
child_dead = FALSE;
|
|
|
|
if (features & TEST_FEATURE_RECOVERY_MASTER_PROCESS) {
|
|
/* the other master will let us know its child's PID so we can unwind
|
|
it when we're finished */
|
|
FAIL_IF (pipe2 (master_recovery_pid_comm, O_NONBLOCK) < 0);
|
|
|
|
recovery_pid = fork ();
|
|
if (recovery_pid > 0) {
|
|
/* we're the main process that libcheck waits for */
|
|
die_on_child_death ();
|
|
while (!child_dead)
|
|
g_usleep (1000);
|
|
/* leave some time for the slave to timeout (1 second), record error, etc */
|
|
g_usleep (1500 * 1000);
|
|
|
|
/* Discard anything that was sent to the previous process when it died */
|
|
while (read (pipesba[0], &x, 1) == 1);
|
|
|
|
FAIL_UNLESS (read (master_recovery_pid_comm[0], &pid,
|
|
sizeof (pid)) == sizeof (pid));
|
|
|
|
setup_log ("gstsrc.log", TRUE);
|
|
source = create_source (features, pipesba[0], pipesfa[1], pipesbv[0],
|
|
pipesfv[1], &td);
|
|
FAIL_UNLESS (source);
|
|
if (run_source)
|
|
run_source (source, &td);
|
|
goto setup_done;
|
|
}
|
|
}
|
|
|
|
if (features & TEST_FEATURE_RECOVERY_SLAVE_PROCESS) {
|
|
recovery_pid = fork ();
|
|
if (!recovery_pid) {
|
|
wait_for_recovery ();
|
|
|
|
/* Discard anything that was sent to the previous process when it died */
|
|
while (read (pipesfa[0], &x, 1) == 1);
|
|
|
|
setup_log ("gstasink.log", TRUE);
|
|
asink = create_sink (features, &slave_pipeline, pipesfa[0], pipesba[1],
|
|
"audio/x-raw");
|
|
FAIL_UNLESS (asink);
|
|
ensure_sink_setup (asink, setup_sink, &td);
|
|
ack_recovery ();
|
|
goto setup_done;
|
|
}
|
|
}
|
|
|
|
pid = fork ();
|
|
FAIL_IF (pid < 0);
|
|
if (pid) {
|
|
if (features & TEST_FEATURE_RECOVERY_MASTER_PROCESS) {
|
|
FAIL_UNLESS (write (master_recovery_pid_comm[1], &pid,
|
|
sizeof (pid)) == sizeof (pid));
|
|
}
|
|
die_on_child_death ();
|
|
if (features & TEST_FEATURE_SPLIT_SINKS) {
|
|
pid = fork ();
|
|
FAIL_IF (pid < 0);
|
|
if (pid) {
|
|
die_on_child_death ();
|
|
}
|
|
c_src = ! !pid;
|
|
c_sink = !pid;
|
|
} else {
|
|
c_src = TRUE;
|
|
c_sink = FALSE;
|
|
}
|
|
if (c_src) {
|
|
setup_log ("gstsrc.log", FALSE);
|
|
source = create_source (features, pipesba[0], pipesfa[1], pipesbv[0],
|
|
pipesfv[1], &td);
|
|
FAIL_UNLESS (source);
|
|
run_source (source, &td);
|
|
}
|
|
if (c_sink) {
|
|
setup_log ("gstasink.log", FALSE);
|
|
asink = create_sink (features, &slave_pipeline, pipesfa[0], pipesba[1],
|
|
"audio/x-raw");
|
|
FAIL_UNLESS (asink);
|
|
}
|
|
} else {
|
|
td.two_streams = (features & TEST_FEATURE_HAS_VIDEO) &&
|
|
!(features & TEST_FEATURE_SPLIT_SINKS);
|
|
|
|
if (features & TEST_FEATURE_HAS_VIDEO) {
|
|
setup_log ("gstvsink.log", FALSE);
|
|
vsink = create_sink (features, &slave_pipeline, pipesfv[0], pipesbv[1],
|
|
"video/x-raw");
|
|
FAIL_UNLESS (vsink);
|
|
}
|
|
if (!(features & TEST_FEATURE_SPLIT_SINKS)) {
|
|
setup_log ("gstasink.log", FALSE);
|
|
asink = create_sink (features, &slave_pipeline, pipesfa[0], pipesba[1],
|
|
"audio/x-raw");
|
|
FAIL_UNLESS (asink);
|
|
}
|
|
}
|
|
|
|
setup_done:
|
|
ensure_sink_setup (asink, setup_sink, &td);
|
|
ensure_sink_setup (vsink, setup_sink, &td);
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
g_main_loop_run (loop);
|
|
|
|
/* tell the child process to unwind too */
|
|
stop_listening_for_unwind ();
|
|
|
|
if (source) {
|
|
ret = gst_element_set_state (source, GST_STATE_NULL);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_SUCCESS
|
|
|| ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
if (pid)
|
|
kill (pid, SIGUSR1);
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
if (source) {
|
|
cleanup_bus (source);
|
|
if (check_success_source)
|
|
check_success_source (&td);
|
|
} else {
|
|
if (asink)
|
|
cleanup_bus (asink);
|
|
if (vsink)
|
|
cleanup_bus (vsink);
|
|
if (check_success_sink)
|
|
check_success_sink (&td);
|
|
}
|
|
|
|
disconnect_ipcpipeline_elements ();
|
|
|
|
close (pipesfa[0]);
|
|
close (pipesfa[1]);
|
|
close (pipesba[0]);
|
|
close (pipesba[1]);
|
|
close (pipesfv[0]);
|
|
close (pipesfv[1]);
|
|
close (pipesbv[0]);
|
|
close (pipesbv[1]);
|
|
|
|
/* If we have a child, we must now wait for it to be finished.
|
|
We can't just waitpid, because this child might be still doing
|
|
its shutdown, and might assert, and the die_on_child_death
|
|
function will exit with the right exit code if so. So we wait
|
|
for the child_dead boolean to be set, which die_on_child_death
|
|
sets if the child dies normally. */
|
|
if (pid) {
|
|
while (!child_dead)
|
|
g_usleep (1000);
|
|
}
|
|
|
|
if (source) {
|
|
FAIL_UNLESS_EQUALS_INT (GST_OBJECT_REFCOUNT_VALUE (source), 1);
|
|
gst_object_unref (source);
|
|
}
|
|
/* asink and vsink may be the same object, so refcount is not sure to be 1 */
|
|
if (asink)
|
|
gst_object_unref (asink);
|
|
if (vsink)
|
|
gst_object_unref (vsink);
|
|
|
|
/* cleanup tasks a bit earlier to make sure all weak refs are gone */
|
|
gst_task_cleanup_all ();
|
|
|
|
/* all ipcpipeline elements we created should now be destroyed */
|
|
if (weak_refs) {
|
|
#if 1
|
|
/* to make it easier to see what leaks */
|
|
GList *l;
|
|
for (l = weak_refs; l; l = l->next) {
|
|
g_print ("%s has %u refs\n", GST_ELEMENT_NAME (l->data),
|
|
GST_OBJECT_REFCOUNT_VALUE (l->data));
|
|
}
|
|
#endif
|
|
FAIL_UNLESS (0);
|
|
}
|
|
}
|
|
|
|
/**** play-pause test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing[2];
|
|
gboolean got_state_changed_to_paused;
|
|
} play_pause_master_data;
|
|
|
|
#define PLAY_PAUSE_MASTER_DATA_INIT { { FALSE, FALSE }, FALSE }
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_caps[2];
|
|
gboolean got_segment[2];
|
|
gboolean got_buffer[2];
|
|
} play_pause_slave_data;
|
|
|
|
#define PLAY_PAUSE_SLAVE_DATA_INIT \
|
|
{ { FALSE, FALSE }, { FALSE, FALSE }, { FALSE, FALSE } }
|
|
|
|
static gboolean
|
|
idlenull (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_NULL);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_SUCCESS);
|
|
gst_object_unref (td->p);
|
|
g_main_loop_quit (loop);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean idleplay (gpointer user_data);
|
|
static gboolean
|
|
idlepause (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_master_data *d = td->md;
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_PAUSED);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
if (ret == GST_STATE_CHANGE_SUCCESS || ret == GST_STATE_CHANGE_NO_PREROLL) {
|
|
/* if the state change is not async, we won't get an aync-done, but
|
|
this is expected, so set the flag here */
|
|
d->got_state_changed_to_paused = TRUE;
|
|
td->state_target = GST_STATE_PLAYING;
|
|
g_timeout_add (STEP_AT, idleplay, user_data);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
idleplay (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_master_data *d = td->md;
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_PLAYING);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
if (ret == GST_STATE_CHANGE_SUCCESS || ret == GST_STATE_CHANGE_NO_PREROLL) {
|
|
/* if the state change is not async, we won't get an aync-done, but
|
|
this is expected, so set the flag here */
|
|
d->got_state_changed_to_playing[1] = TRUE;
|
|
td->state_target = GST_STATE_NULL;
|
|
g_timeout_add (STEP_AT, idlenull, user_data);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
play_pause_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_master_data *d = td->md;
|
|
GstStateChangeReturn ret;
|
|
|
|
if (d->got_state_changed_to_paused) {
|
|
d->got_state_changed_to_playing[1] = TRUE;
|
|
td->state_target = GST_STATE_NULL;
|
|
ret = gst_element_set_state (td->p, GST_STATE_NULL);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_SUCCESS);
|
|
g_main_loop_quit (loop);
|
|
} else if (d->got_state_changed_to_playing[0]) {
|
|
d->got_state_changed_to_paused = TRUE;
|
|
td->state_target = GST_STATE_PLAYING;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) idleplay, td);
|
|
} else {
|
|
d->got_state_changed_to_playing[0] = TRUE;
|
|
td->state_target = GST_STATE_PAUSED;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) idlepause, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
play_pause_source (GstElement * source, void *user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = play_pause_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
play_pause_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_slave_data *d = td->sd;
|
|
GstCaps *caps;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
d->got_buffer[pad2idx (pad, td->two_streams)] = TRUE;
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
|
|
gst_event_parse_caps (info->data, &caps);
|
|
d->got_caps[caps2idx (caps, td->two_streams)] = TRUE;
|
|
} else if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEGMENT) {
|
|
d->got_segment[pad2idx (pad, td->two_streams)] = TRUE;
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_play_pause_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, play_pause_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_play_pause (GstElement * sink, void *user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_play_pause_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_play_pause (void *user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing[0]);
|
|
FAIL_UNLESS (d->got_state_changed_to_playing[1]);
|
|
FAIL_UNLESS (d->got_state_changed_to_paused);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_play_pause (void *user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
play_pause_slave_data *d = td->sd;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->got_caps[idx]);
|
|
FAIL_UNLESS (d->got_segment[idx]);
|
|
FAIL_UNLESS (d->got_buffer[idx]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_empty_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, play_pause_source, setup_sink_play_pause,
|
|
check_success_source_play_pause, check_success_sink_play_pause, NULL, &md,
|
|
&sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, play_pause_source, setup_sink_play_pause,
|
|
check_success_source_play_pause, check_success_sink_play_pause, NULL, &md,
|
|
&sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, play_pause_source,
|
|
setup_sink_play_pause, check_success_source_play_pause,
|
|
check_success_sink_play_pause, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
play_pause_source, setup_sink_play_pause, check_success_source_play_pause,
|
|
check_success_sink_play_pause, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, play_pause_source,
|
|
setup_sink_play_pause, check_success_source_play_pause,
|
|
check_success_sink_play_pause, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, play_pause_source,
|
|
setup_sink_play_pause, check_success_source_play_pause,
|
|
check_success_sink_play_pause, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_play_pause)
|
|
{
|
|
play_pause_master_data md = PLAY_PAUSE_MASTER_DATA_INIT;
|
|
play_pause_slave_data sd = PLAY_PAUSE_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
play_pause_source, setup_sink_play_pause, check_success_source_play_pause,
|
|
check_success_sink_play_pause, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** flushing seek test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean segment_seek;
|
|
gboolean pause;
|
|
} flushing_seek_input_data;
|
|
|
|
#define FLUSHING_SEEK_INPUT_DATA_INIT { FALSE, FALSE }
|
|
#define FLUSHING_SEEK_INPUT_DATA_INIT_PAUSED { FALSE, TRUE }
|
|
#define FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK { TRUE, FALSE }
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean got_segment_done;
|
|
gboolean seek_sent;
|
|
} flushing_seek_master_data;
|
|
|
|
#define FLUSHING_SEEK_MASTER_DATA_INIT { FALSE, FALSE, FALSE }
|
|
|
|
typedef struct
|
|
{
|
|
GstClockTime first_ts[2];
|
|
gboolean got_caps[2];
|
|
gboolean got_buffer_before_seek[2];
|
|
gboolean got_buffer_after_seek[2];
|
|
gboolean first_buffer_after_seek_has_timestamp_0[2];
|
|
gboolean got_segment_after_seek[2];
|
|
gboolean got_flush_start[2];
|
|
gboolean got_flush_stop[2];
|
|
} flushing_seek_slave_data;
|
|
|
|
#define FLUSHING_SEEK_SLAVE_DATA_INIT { { 0, 0 }, }
|
|
|
|
static gboolean
|
|
send_flushing_seek (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
const flushing_seek_input_data *i = td->id;
|
|
flushing_seek_master_data *d = td->md;
|
|
GstEvent *seek_event;
|
|
|
|
if (i->segment_seek) {
|
|
GST_INFO_OBJECT (td->p, "Sending segment seek");
|
|
seek_event =
|
|
gst_event_new_seek (1.0, GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0,
|
|
GST_SEEK_TYPE_SET, 1 * GST_SECOND);
|
|
FAIL_UNLESS (gst_element_send_event (td->p, seek_event));
|
|
} else {
|
|
GST_INFO_OBJECT (td->p, "Sending flushing seek");
|
|
gst_element_seek_simple (td->p, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 0);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p));
|
|
}
|
|
d->seek_sent = TRUE;
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
pause_before_seek (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_PAUSED);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
flushing_seek_bus_msg (GstBus * bus, GstMessage * message, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
flushing_seek_master_data *d = td->md;
|
|
|
|
if (GST_IS_PIPELINE (GST_MESSAGE_SRC (message))) {
|
|
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_DONE) {
|
|
d->got_segment_done = TRUE;
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p));
|
|
}
|
|
}
|
|
return master_bus_msg (bus, message, user_data);
|
|
}
|
|
|
|
static void
|
|
flushing_seek_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
const flushing_seek_input_data *i = td->id;
|
|
flushing_seek_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
if (i->pause)
|
|
g_timeout_add (PAUSE_AT, (GSourceFunc) pause_before_seek, td);
|
|
g_timeout_add (SEEK_AT, (GSourceFunc) send_flushing_seek, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
flushing_seek_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* we're on the source, there's already the basic master_bus_msg watch,
|
|
and gst doesn't want more than one watch, so we remove the watch and
|
|
call it directly when done in the new watch */
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (source));
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (source), flushing_seek_bus_msg,
|
|
user_data);
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = flushing_seek_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
flushing_seek_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
flushing_seek_slave_data *d = td->sd;
|
|
GstClockTime ts;
|
|
int idx;
|
|
GstCaps *caps;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
idx = pad2idx (pad, td->two_streams);
|
|
if (d->got_flush_stop[idx]) {
|
|
if (!d->got_buffer_after_seek[idx]) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
d->first_buffer_after_seek_has_timestamp_0[idx] =
|
|
(ts < d->first_ts[idx] + 10 * GST_MSECOND);
|
|
d->got_buffer_after_seek[idx] = TRUE;
|
|
}
|
|
} else if (!d->got_buffer_before_seek[idx]) {
|
|
d->got_buffer_before_seek[idx] = TRUE;
|
|
d->first_ts[idx] = GST_BUFFER_TIMESTAMP (info->data);
|
|
}
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
|
|
gst_event_parse_caps (info->data, &caps);
|
|
if (are_caps_audio (caps) || are_caps_video (caps)) {
|
|
idx = caps2idx (caps, td->two_streams);
|
|
d->got_caps[idx] = TRUE;
|
|
}
|
|
} else if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEGMENT) {
|
|
/* from the sink pipeline, we don't know whether the master issued a seek,
|
|
as the seek_sent memory location isn't directly accesible to us, so we
|
|
look for a segment after a buffer to mean a seek was sent */
|
|
idx = pad2idx (pad, td->two_streams);
|
|
if (d->got_buffer_before_seek[idx])
|
|
d->got_segment_after_seek[idx] = TRUE;
|
|
} else if (GST_EVENT_TYPE (info->data) == GST_EVENT_FLUSH_START) {
|
|
idx = pad2idx (pad, td->two_streams);
|
|
d->got_flush_start[idx] = TRUE;
|
|
} else if (GST_EVENT_TYPE (info->data) == GST_EVENT_FLUSH_STOP) {
|
|
idx = pad2idx (pad, td->two_streams);
|
|
if (d->got_buffer_before_seek[idx])
|
|
d->got_flush_stop[idx] = TRUE;
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_flushing_seek_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, flushing_seek_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_flushing_seek (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_flushing_seek_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_flushing_seek (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
const flushing_seek_input_data *i = td->id;
|
|
flushing_seek_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS (d->seek_sent);
|
|
FAIL_UNLESS (d->got_segment_done == i->segment_seek);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_flushing_seek (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
flushing_seek_slave_data *d = td->sd;
|
|
gint idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->got_caps[idx]);
|
|
FAIL_UNLESS (d->got_buffer_before_seek[idx]);
|
|
FAIL_UNLESS (d->got_buffer_after_seek[idx]);
|
|
FAIL_UNLESS (d->got_segment_after_seek[idx]);
|
|
FAIL_UNLESS (d->got_flush_start[idx]);
|
|
FAIL_UNLESS (d->got_flush_stop[idx]);
|
|
FAIL_UNLESS (d->first_buffer_after_seek_has_timestamp_0[idx]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_empty_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek, check_success_sink_flushing_seek, &id,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_flushing_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek, check_success_sink_flushing_seek, &id,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_empty_flushing_seek_in_pause)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_PAUSED;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_flushing_seek_in_pause)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_PAUSED;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_flushing_seek_in_pause)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_PAUSED;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_flushing_seek_in_pause)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_PAUSED;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_empty_segment_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_segment_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, flushing_seek_source,
|
|
setup_sink_flushing_seek, check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_segment_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_segment_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_segment_seek)
|
|
{
|
|
flushing_seek_input_data id = FLUSHING_SEEK_INPUT_DATA_INIT_SEGMENT_SEEK;
|
|
flushing_seek_master_data md = FLUSHING_SEEK_MASTER_DATA_INIT;
|
|
flushing_seek_slave_data sd = FLUSHING_SEEK_SLAVE_DATA_INIT;
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
flushing_seek_source, setup_sink_flushing_seek,
|
|
check_success_source_flushing_seek,
|
|
check_success_sink_flushing_seek, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** seek stress test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gint n_flushing_seeks;
|
|
gint n_paused_seeks;
|
|
gint n_segment_seeks;
|
|
} seek_stress_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean got_eos;
|
|
gboolean seek_sent;
|
|
guint64 t0;
|
|
} seek_stress_master_data;
|
|
|
|
static gboolean
|
|
send_seek_stress (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
seek_stress_input_data *i = td->id;
|
|
seek_stress_master_data *d = td->md;
|
|
GstEvent *seek_event;
|
|
unsigned int available, seekidx;
|
|
GstClockTime t, base;
|
|
|
|
/* Live streams don't like to be seeked too far away from the
|
|
"current" time, since they're live, so always seek near the
|
|
"real" time, so we still exercise seeking to another position
|
|
but still land somewhere close enough to "live" position. */
|
|
t = (g_get_monotonic_time () - d->t0) * 1000;
|
|
base = t > GST_SECOND / 2 ? t - GST_SECOND / 2 : 0;
|
|
t = base + g_random_int_range (0, GST_SECOND);
|
|
|
|
/* pick a random seek type among the ones we have left */
|
|
available = i->n_flushing_seeks + i->n_paused_seeks + i->n_segment_seeks;
|
|
if (available == 0) {
|
|
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (td->p),
|
|
GST_DEBUG_GRAPH_SHOW_ALL, "inter.test.toplaying");
|
|
FAIL_UNLESS (gst_element_set_state (td->p,
|
|
GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p));
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
seekidx = rand () % available;
|
|
if (seekidx < i->n_flushing_seeks) {
|
|
GST_INFO_OBJECT (td->p, "Sending flushing seek to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (t));
|
|
FAIL_UNLESS (gst_element_set_state (td->p,
|
|
GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE);
|
|
FAIL_UNLESS (gst_element_seek_simple (td->p, GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_FLUSH, t));
|
|
--i->n_flushing_seeks;
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
seekidx -= i->n_flushing_seeks;
|
|
|
|
if (seekidx < i->n_paused_seeks) {
|
|
GST_INFO_OBJECT (td->p,
|
|
"Sending flushing seek in paused to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (t));
|
|
FAIL_UNLESS (gst_element_set_state (td->p,
|
|
GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE);
|
|
FAIL_UNLESS (gst_element_seek_simple (td->p, GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_FLUSH, t));
|
|
--i->n_paused_seeks;
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
seekidx -= i->n_paused_seeks;
|
|
|
|
GST_INFO_OBJECT (td->p, "Sending segment seek to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (t));
|
|
seek_event =
|
|
gst_event_new_seek (1.0, GST_FORMAT_TIME,
|
|
GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, t,
|
|
GST_SEEK_TYPE_SET, t + 5 * GST_SECOND);
|
|
FAIL_UNLESS (gst_element_send_event (td->p, seek_event));
|
|
--i->n_segment_seeks;
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static gboolean
|
|
seek_stress_bus_msg (GstBus * bus, GstMessage * message, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
seek_stress_master_data *d = td->md;
|
|
|
|
if (GST_IS_PIPELINE (GST_MESSAGE_SRC (message))) {
|
|
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS ||
|
|
GST_MESSAGE_TYPE (message) == GST_MESSAGE_SEGMENT_DONE) {
|
|
d->got_eos = TRUE;
|
|
}
|
|
}
|
|
return master_bus_msg (bus, message, user_data);
|
|
}
|
|
|
|
static void
|
|
seek_stress_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
seek_stress_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
d->t0 = g_get_monotonic_time ();
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (10, (GSourceFunc) send_seek_stress, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
seek_stress_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* we're on the source, there's already the basic master_bus_msg watch,
|
|
and gst doesn't want more than one watch, so we remove the watch and
|
|
call it directly when done in the new watch */
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (source));
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (source), seek_stress_bus_msg, user_data);
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = seek_stress_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static void
|
|
check_success_source_seek_stress (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
seek_stress_input_data *i = td->id;
|
|
seek_stress_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_flushing_seeks, 0);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_paused_seeks, 0);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_segment_seeks, 0);
|
|
FAIL_IF (d->got_eos);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 100, 100 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, seek_stress_source, NULL,
|
|
check_success_source_seek_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 100, 100 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, seek_stress_source, NULL,
|
|
check_success_source_seek_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 100, 0 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, seek_stress_source, NULL,
|
|
check_success_source_seek_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 100, 0 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
seek_stress_source, NULL, check_success_source_seek_stress, NULL, &id,
|
|
&md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 0, 100 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE | TEST_FEATURE_LONG_DURATION,
|
|
seek_stress_source, NULL, check_success_source_seek_stress, NULL, &id,
|
|
&md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 0, 100 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_LONG_DURATION,
|
|
seek_stress_source, NULL, check_success_source_seek_stress, NULL, &id,
|
|
&md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_seek_stress)
|
|
{
|
|
seek_stress_input_data id = { 100, 0, 100 };
|
|
seek_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_LONG_DURATION |
|
|
TEST_FEATURE_SPLIT_SINKS,
|
|
seek_stress_source, NULL, check_success_source_seek_stress, NULL, &id,
|
|
&md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** upstream query test ****/
|
|
|
|
typedef struct
|
|
{
|
|
GstClockTime expected_duration;
|
|
|
|
/* In this test, the source does a position query (in the source pipeline
|
|
process), and must check its return against the last buffer timestamp
|
|
in the sink pipeline process. We open a pipe to let the sink send us
|
|
the timestamps it receives so the source can make the comparison. */
|
|
gint ts_pipes[2];
|
|
} upstream_query_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean got_correct_position;
|
|
gboolean got_correct_duration;
|
|
GstClockTime last_buffer_ts;
|
|
} upstream_query_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_caps[2];
|
|
gboolean got_buffer[2];
|
|
GstClockTime last_buffer_ts;
|
|
} upstream_query_slave_data;
|
|
|
|
static gboolean
|
|
send_upstream_queries (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
upstream_query_input_data *i = td->id;
|
|
upstream_query_master_data *d = td->md;
|
|
gint64 pos, dur, last;
|
|
|
|
FAIL_UNLESS (gst_element_query_position (td->p, GST_FORMAT_TIME, &pos));
|
|
|
|
/* read up the buffer ts sent by the sink process till the last one */
|
|
while (read (i->ts_pipes[0], &last, sizeof (last)) == sizeof (last)) {
|
|
/* timestamps may not be increasing because we are getting ts from
|
|
* both the audio and video streams; the position query will report
|
|
* the higher */
|
|
if (last > d->last_buffer_ts)
|
|
d->last_buffer_ts = last;
|
|
}
|
|
if (ABS ((gint64) (pos - d->last_buffer_ts)) <= CLOSE_ENOUGH_TO_ZERO)
|
|
d->got_correct_position = TRUE;
|
|
|
|
FAIL_UNLESS (gst_element_query_duration (td->p, GST_FORMAT_TIME, &dur));
|
|
if (GST_CLOCK_TIME_IS_VALID (i->expected_duration)) {
|
|
GstClockTimeDiff diff = GST_CLOCK_DIFF (dur, i->expected_duration);
|
|
if (diff >= -CLOSE_ENOUGH_TO_ZERO && diff <= CLOSE_ENOUGH_TO_ZERO)
|
|
d->got_correct_duration = TRUE;
|
|
} else {
|
|
if (!GST_CLOCK_TIME_IS_VALID (dur))
|
|
d->got_correct_duration = TRUE;
|
|
}
|
|
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline, td->p);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
upstream_query_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
upstream_query_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (QUERY_AT, (GSourceFunc) send_upstream_queries, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
upstream_query_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_changed_cb = upstream_query_on_state_changed;
|
|
td->state_target = GST_STATE_PLAYING;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
upstream_query_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
upstream_query_input_data *i = td->id;
|
|
upstream_query_slave_data *d = td->sd;
|
|
GstCaps *caps;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
d->got_buffer[pad2idx (pad, td->two_streams)] = TRUE;
|
|
if (GST_BUFFER_TIMESTAMP_IS_VALID (info->data)) {
|
|
d->last_buffer_ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
FAIL_UNLESS (write (i->ts_pipes[1], &d->last_buffer_ts,
|
|
sizeof (d->last_buffer_ts)) == sizeof (d->last_buffer_ts));
|
|
}
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
|
|
gst_event_parse_caps (info->data, &caps);
|
|
d->got_caps[caps2idx (caps, td->two_streams)] = TRUE;
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_upstream_query_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, upstream_query_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_upstream_query (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_upstream_query_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_upstream_query (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
upstream_query_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS (d->got_correct_position);
|
|
FAIL_UNLESS (d->got_correct_duration);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_upstream_query (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
upstream_query_slave_data *d = td->sd;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); ++idx) {
|
|
FAIL_UNLESS (d->got_caps[idx]);
|
|
FAIL_UNLESS (d->got_buffer[idx]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_empty_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { GST_CLOCK_TIME_NONE, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, upstream_query_source,
|
|
setup_sink_upstream_query, check_success_source_upstream_query,
|
|
check_success_sink_upstream_query, &id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { WAV_SAMPLE_ROUGH_DURATION, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, upstream_query_source,
|
|
setup_sink_upstream_query, check_success_source_upstream_query,
|
|
check_success_sink_upstream_query, &id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { MPEGTS_SAMPLE_ROUGH_DURATION, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, upstream_query_source,
|
|
setup_sink_upstream_query, check_success_source_upstream_query,
|
|
check_success_sink_upstream_query, &id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { MPEGTS_SAMPLE_ROUGH_DURATION, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
upstream_query_source, setup_sink_upstream_query,
|
|
check_success_source_upstream_query, check_success_sink_upstream_query,
|
|
&id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { GST_CLOCK_TIME_NONE, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE,
|
|
upstream_query_source, setup_sink_upstream_query,
|
|
check_success_source_upstream_query, check_success_sink_upstream_query,
|
|
&id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { GST_CLOCK_TIME_NONE, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE,
|
|
upstream_query_source, setup_sink_upstream_query,
|
|
check_success_source_upstream_query, check_success_sink_upstream_query,
|
|
&id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_upstream_query)
|
|
{
|
|
upstream_query_input_data id = { GST_CLOCK_TIME_NONE, };
|
|
upstream_query_master_data md = { 0 };
|
|
upstream_query_slave_data sd = { {0}
|
|
};
|
|
|
|
FAIL_UNLESS (pipe2 (id.ts_pipes, O_NONBLOCK) == 0);
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
upstream_query_source, setup_sink_upstream_query,
|
|
check_success_source_upstream_query, check_success_sink_upstream_query,
|
|
&id, &md, &sd);
|
|
close (id.ts_pipes[0]);
|
|
close (id.ts_pipes[1]);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** message test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
guint8 num_got_message;
|
|
guint8 num_sent_message;
|
|
} message_master_data;
|
|
|
|
static void
|
|
send_ipcpipeline_test_message_event (const GValue * v, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
message_master_data *d = td->md;
|
|
GstElement *element = g_value_get_object (v);
|
|
GstMessage *msg;
|
|
gboolean ret;
|
|
|
|
d->num_sent_message++;
|
|
|
|
msg = gst_message_new_element (GST_OBJECT (element),
|
|
gst_structure_new_empty ("ipcpipeline-test"));
|
|
ret = gst_element_send_event (element,
|
|
gst_event_new_sink_message ("ipcpipeline-test", msg));
|
|
FAIL_UNLESS (ret);
|
|
gst_message_unref (msg);
|
|
}
|
|
|
|
static gboolean
|
|
send_sink_message (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sources (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, send_ipcpipeline_test_message_event, td))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
message_bus_msg (GstBus * bus, GstMessage * message, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
message_master_data *d = td->md;
|
|
const GstStructure *structure;
|
|
|
|
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) {
|
|
structure = gst_message_get_structure (message);
|
|
FAIL_UNLESS (structure);
|
|
if (gst_structure_has_name (structure, "ipcpipeline-test")) {
|
|
d->num_got_message++;
|
|
if (d->num_got_message == d->num_sent_message)
|
|
g_main_loop_quit (loop);
|
|
}
|
|
}
|
|
return master_bus_msg (bus, message, user_data);
|
|
}
|
|
|
|
static void
|
|
message_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
message_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (MESSAGE_AT, (GSourceFunc) send_sink_message, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
message_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* we're on the source, there's already the basic master_bus_msg watch,
|
|
and gst doesn't want more than one watch, so we remove the watch and
|
|
call it directly when done in the new watch */
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (source));
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (source), message_bus_msg, user_data);
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = message_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static void
|
|
check_success_source_message (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
message_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS_EQUALS_INT (d->num_got_message, d->num_sent_message);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_message)
|
|
{
|
|
message_master_data md = { 0 };
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, message_source, NULL,
|
|
check_success_source_message, NULL, NULL, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_message)
|
|
{
|
|
message_master_data md = { 0 };
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, message_source, NULL,
|
|
check_success_source_message, NULL, NULL, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_message)
|
|
{
|
|
message_master_data md = { 0 };
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, message_source, NULL,
|
|
check_success_source_message, NULL, NULL, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_message)
|
|
{
|
|
message_master_data md = { 0 };
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, message_source, NULL,
|
|
check_success_source_message, NULL, NULL, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_message)
|
|
{
|
|
message_master_data md = { 0 };
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
message_source, NULL, check_success_source_message, NULL, NULL, &md,
|
|
NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** end of stream test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
} end_of_stream_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_buffer[2];
|
|
gboolean got_eos[2];
|
|
} end_of_stream_slave_data;
|
|
|
|
static void
|
|
end_of_stream_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
end_of_stream_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing)
|
|
d->got_state_changed_to_playing = TRUE;
|
|
}
|
|
|
|
static void
|
|
end_of_stream_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_changed_cb = end_of_stream_on_state_changed;
|
|
td->state_target = GST_STATE_PLAYING;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
end_of_stream_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
end_of_stream_slave_data *d = td->sd;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
d->got_buffer[pad2idx (pad, td->two_streams)] = TRUE;
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_EOS) {
|
|
d->got_eos[pad2idx (pad, td->two_streams)] = TRUE;
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_end_of_stream_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, end_of_stream_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_end_of_stream (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_end_of_stream_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_end_of_stream (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
end_of_stream_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_end_of_stream (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
end_of_stream_slave_data *d = td->sd;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->got_buffer[idx]);
|
|
FAIL_UNLESS (d->got_eos[idx]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_empty_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_ASYNC_SINK,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE | TEST_FEATURE_ASYNC_SINK,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_ASYNC_SINK,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS |
|
|
TEST_FEATURE_ASYNC_SINK,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_end_of_stream)
|
|
{
|
|
end_of_stream_master_data md = { 0 };
|
|
end_of_stream_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
end_of_stream_source, setup_sink_end_of_stream,
|
|
check_success_source_end_of_stream, check_success_sink_end_of_stream,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** reverse playback test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean seek_sent;
|
|
} reverse_playback_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_segment_with_negative_rate;
|
|
gboolean got_buffer_after_segment_with_negative_rate;
|
|
GstClockTime first_backward_buffer_timestamp;
|
|
gboolean got_buffer_one_second_early;
|
|
} reverse_playback_slave_data;
|
|
|
|
static gboolean
|
|
play_backwards (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reverse_playback_master_data *d = td->md;
|
|
gint64 pos;
|
|
gboolean ret;
|
|
|
|
FAIL_UNLESS (gst_element_query_position (td->p, GST_FORMAT_TIME, &pos));
|
|
|
|
ret =
|
|
gst_element_seek (td->p, -0.5, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_SET, 0,
|
|
GST_SEEK_TYPE_SET, pos);
|
|
FAIL_UNLESS (ret);
|
|
d->seek_sent = TRUE;
|
|
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
reverse_playback_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reverse_playback_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (2000, (GSourceFunc) play_backwards, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
reverse_playback_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = reverse_playback_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
reverse_playback_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reverse_playback_slave_data *d = td->sd;
|
|
|
|
if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_SEGMENT) {
|
|
const GstSegment *s;
|
|
gst_event_parse_segment (GST_EVENT (info->data), &s);
|
|
if (s->rate < 0)
|
|
d->got_segment_with_negative_rate = TRUE;
|
|
}
|
|
} else if (GST_IS_BUFFER (info->data)) {
|
|
GstClockTime ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
if (GST_CLOCK_TIME_IS_VALID (ts)) {
|
|
if (d->got_segment_with_negative_rate) {
|
|
if (d->got_buffer_after_segment_with_negative_rate) {
|
|
/* We test for 1 second, not just earlier, to make sure we don't
|
|
just see B frames, or whatever else */
|
|
if (ts < d->first_backward_buffer_timestamp - GST_SECOND) {
|
|
d->got_buffer_one_second_early = TRUE;
|
|
}
|
|
} else {
|
|
d->got_buffer_after_segment_with_negative_rate = TRUE;
|
|
d->first_backward_buffer_timestamp = ts;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_reverse_playback_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, reverse_playback_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_reverse_playback (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_reverse_playback_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_reverse_playback (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reverse_playback_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS (d->seek_sent);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_reverse_playback (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reverse_playback_slave_data *d = td->sd;
|
|
|
|
FAIL_UNLESS (d->got_segment_with_negative_rate);
|
|
FAIL_UNLESS (d->got_buffer_after_segment_with_negative_rate);
|
|
FAIL_UNLESS (GST_CLOCK_TIME_IS_VALID (d->first_backward_buffer_timestamp));
|
|
FAIL_UNLESS (d->first_backward_buffer_timestamp >= GST_SECOND);
|
|
FAIL_UNLESS (d->got_buffer_one_second_early);
|
|
}
|
|
|
|
GST_START_TEST (test_a_reverse_playback)
|
|
{
|
|
reverse_playback_master_data md = { 0 };
|
|
reverse_playback_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE,
|
|
reverse_playback_source, setup_sink_reverse_playback,
|
|
check_success_source_reverse_playback,
|
|
check_success_sink_reverse_playback, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_av_reverse_playback)
|
|
{
|
|
reverse_playback_master_data md = { 0 };
|
|
reverse_playback_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_HAS_VIDEO,
|
|
reverse_playback_source, setup_sink_reverse_playback,
|
|
check_success_source_reverse_playback,
|
|
check_success_sink_reverse_playback, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_av_2_reverse_playback)
|
|
{
|
|
reverse_playback_master_data md = { 0 };
|
|
reverse_playback_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_HAS_VIDEO |
|
|
TEST_FEATURE_SPLIT_SINKS,
|
|
reverse_playback_source, setup_sink_reverse_playback,
|
|
check_success_source_reverse_playback,
|
|
check_success_sink_reverse_playback, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** tags test ****/
|
|
|
|
enum
|
|
{
|
|
TEST_TAG_EMPTY,
|
|
TEST_TAG_TWO_TAGS,
|
|
N_TEST_TAGS
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean tags_sent[2][N_TEST_TAGS];
|
|
} tags_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean tags_received[N_TEST_TAGS];
|
|
} tags_slave_data;
|
|
|
|
static void
|
|
send_tags_on_pad (GstPad * pad, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_master_data *d = td->md;
|
|
GstEvent *e;
|
|
gint idx;
|
|
|
|
idx = pad2idx (pad, td->two_streams);
|
|
|
|
e = gst_event_new_tag (gst_tag_list_new_empty ());
|
|
FAIL_UNLESS (gst_pad_send_event (pad, e));
|
|
d->tags_sent[idx][TEST_TAG_EMPTY] = TRUE;
|
|
|
|
e = gst_event_new_tag (gst_tag_list_new (GST_TAG_TITLE, "title",
|
|
GST_TAG_BITRATE, 56000, NULL));
|
|
FAIL_UNLESS (gst_pad_send_event (pad, e));
|
|
d->tags_sent[idx][TEST_TAG_TWO_TAGS] = TRUE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
tags_probe_source (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_master_data *d = td->md;
|
|
GstClockTime ts;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
if (GST_CLOCK_TIME_IS_VALID (ts) && ts > STEP_AT * GST_MSECOND) {
|
|
gint idx = pad2idx (pad, td->two_streams);
|
|
if (!d->tags_sent[idx][0]) {
|
|
GstPad *peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
send_tags_on_pad (peer, td);
|
|
gst_object_unref (peer);
|
|
EXCLUSIVE_CALL (td, g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_tags_probe_source (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_peer_probe_types (v, tags_probe_source,
|
|
GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM, user_data);
|
|
}
|
|
|
|
static void
|
|
tags_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_master_data *d = td->md;
|
|
GstIterator *it;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, hook_tags_probe_source, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
}
|
|
|
|
static void
|
|
tags_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = tags_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
tags_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_slave_data *d = td->sd;
|
|
guint funsigned;
|
|
gchar *fstring = NULL;
|
|
|
|
if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_TAG) {
|
|
GstTagList *taglist = NULL;
|
|
gst_event_parse_tag (GST_EVENT (info->data), &taglist);
|
|
FAIL_UNLESS (taglist);
|
|
if (gst_tag_list_is_empty (taglist)) {
|
|
d->tags_received[TEST_TAG_EMPTY] = TRUE;
|
|
} else if (gst_tag_list_get_string (taglist, GST_TAG_TITLE, &fstring)
|
|
&& !strcmp (fstring, "title")
|
|
&& gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &funsigned)
|
|
&& funsigned == 56000) {
|
|
d->tags_received[TEST_TAG_TWO_TAGS] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
g_free (fstring);
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_tags_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, tags_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_tags (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_tags_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_tags (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_master_data *d = td->md;
|
|
gint n;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
for (n = 0; n < N_TEST_TAGS; ++n) {
|
|
FAIL_UNLESS (d->tags_sent[n]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_success_sink_tags (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
tags_slave_data *d = td->sd;
|
|
gint n;
|
|
|
|
for (n = 0; n < N_TEST_TAGS; ++n) {
|
|
FAIL_UNLESS (d->tags_received[n]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_empty_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, tags_source, setup_sink_tags,
|
|
check_success_source_tags, check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, tags_source, setup_sink_tags,
|
|
check_success_source_tags, check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, tags_source, setup_sink_tags,
|
|
check_success_source_tags, check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS, tags_source,
|
|
setup_sink_tags, check_success_source_tags, check_success_sink_tags, NULL,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, tags_source, setup_sink_tags,
|
|
check_success_source_tags, check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, tags_source, setup_sink_tags,
|
|
check_success_source_tags, check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_tags)
|
|
{
|
|
tags_master_data md = { 0 };
|
|
tags_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
tags_source, setup_sink_tags, check_success_source_tags,
|
|
check_success_sink_tags, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** nagivation test ****/
|
|
|
|
enum
|
|
{
|
|
TEST_NAV_MOUSE_MOVE,
|
|
TEST_NAV_KEY_PRESS,
|
|
N_NAVIGATION_EVENTS
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean navigation_received[N_NAVIGATION_EVENTS];
|
|
} navigation_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean started;
|
|
gboolean navigation_sent[N_NAVIGATION_EVENTS];
|
|
gint step;
|
|
} navigation_slave_data;
|
|
|
|
static GstPadProbeReturn
|
|
navigation_probe_source (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_master_data *d = td->md;
|
|
const GstStructure *s;
|
|
const gchar *string, *key;
|
|
double x, y;
|
|
|
|
if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_NAVIGATION) {
|
|
s = gst_event_get_structure (info->data);
|
|
FAIL_UNLESS (s);
|
|
|
|
/* mouse-move */
|
|
string = gst_structure_get_string (s, "event");
|
|
if (string && !strcmp (string, "mouse-move")) {
|
|
if (gst_structure_get_double (s, "pointer_x", &x) && x == 4.7) {
|
|
if (gst_structure_get_double (s, "pointer_y", &y) && y == 0.1) {
|
|
d->navigation_received[TEST_NAV_MOUSE_MOVE] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* key-press */
|
|
string = gst_structure_get_string (s, "event");
|
|
if (string && !strcmp (string, "key-press")) {
|
|
key = gst_structure_get_string (s, "key");
|
|
if (key && !strcmp (key, "Left")) {
|
|
d->navigation_received[TEST_NAV_KEY_PRESS] = TRUE;
|
|
}
|
|
}
|
|
|
|
/* drop at this point to imply successful handling; the upstream filesrc
|
|
* does not know how to handle navigation events and returns FALSE,
|
|
* which makes the test fail */
|
|
return GST_PAD_PROBE_DROP;
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_navigation_probe_source (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe_types (v, navigation_probe_source,
|
|
GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, user_data);
|
|
}
|
|
|
|
static void
|
|
navigation_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing)
|
|
d->got_state_changed_to_playing = TRUE;
|
|
}
|
|
|
|
static void
|
|
navigation_source (GstElement * source, void *user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (source));
|
|
while (gst_iterator_foreach (it, hook_navigation_probe_source, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = navigation_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static void
|
|
send_navigation_event (const GValue * v, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_slave_data *d = td->sd;
|
|
GstElement *sink;
|
|
GstPad *pad, *peer;
|
|
GstStructure *s;
|
|
GstEvent *e = NULL;
|
|
|
|
sink = g_value_get_object (v);
|
|
FAIL_UNLESS (sink);
|
|
pad = gst_element_get_static_pad (sink, "sink");
|
|
FAIL_UNLESS (pad);
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
gst_object_unref (pad);
|
|
|
|
switch (d->step) {
|
|
case TEST_NAV_MOUSE_MOVE:
|
|
s = gst_structure_new ("application/x-gst-navigation", "event",
|
|
G_TYPE_STRING, "mouse-move", "button", G_TYPE_INT, 0, "pointer_x",
|
|
G_TYPE_DOUBLE, 4.7, "pointer_y", G_TYPE_DOUBLE, 0.1, NULL);
|
|
e = gst_event_new_navigation (s);
|
|
break;
|
|
case TEST_NAV_KEY_PRESS:
|
|
s = gst_structure_new ("application/x-gst-navigation", "event",
|
|
G_TYPE_STRING, "key-press", "key", G_TYPE_STRING, "Left", NULL);
|
|
e = gst_event_new_navigation (s);
|
|
break;
|
|
}
|
|
|
|
FAIL_UNLESS (e);
|
|
FAIL_UNLESS (gst_pad_send_event (peer, e));
|
|
d->navigation_sent[d->step] = TRUE;
|
|
|
|
gst_object_unref (peer);
|
|
}
|
|
|
|
static gboolean
|
|
step_navigation (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_slave_data *d = td->sd;
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, send_navigation_event, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
if (++d->step < N_NAVIGATION_EVENTS)
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
/* we are in the slave; send EOS to force the master to stop the pipeline */
|
|
gst_element_post_message (GST_ELEMENT (td->p),
|
|
gst_message_new_eos (GST_OBJECT (td->p)));
|
|
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
navigation_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_slave_data *d = td->sd;
|
|
GstClockTime ts;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
if (GST_CLOCK_TIME_IS_VALID (ts) && ts > STEP_AT * GST_MSECOND) {
|
|
if (!d->started) {
|
|
d->started = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (50, step_navigation, td);
|
|
}
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_navigation_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, navigation_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_navigation (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_navigation_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_navigation (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_master_data *d = td->md;
|
|
gint n;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
for (n = 0; n < N_NAVIGATION_EVENTS; ++n) {
|
|
FAIL_UNLESS (d->navigation_received[n]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_success_sink_navigation (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
navigation_slave_data *d = td->sd;
|
|
gint n;
|
|
|
|
FAIL_UNLESS (d->started);
|
|
for (n = 0; n < N_NAVIGATION_EVENTS; ++n) {
|
|
FAIL_UNLESS (d->navigation_sent[n]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_non_live_av_navigation)
|
|
{
|
|
navigation_master_data md = { 0 };
|
|
navigation_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, navigation_source,
|
|
setup_sink_navigation, check_success_source_navigation,
|
|
check_success_sink_navigation, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_non_live_av_2_navigation)
|
|
{
|
|
navigation_master_data md = { 0 };
|
|
navigation_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
navigation_source, setup_sink_navigation, check_success_source_navigation,
|
|
check_success_sink_navigation, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_navigation)
|
|
{
|
|
navigation_master_data md = { 0 };
|
|
navigation_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, navigation_source,
|
|
setup_sink_navigation, check_success_source_navigation,
|
|
check_success_sink_navigation, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_navigation)
|
|
{
|
|
navigation_master_data md = { 0 };
|
|
navigation_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
navigation_source, setup_sink_navigation, check_success_source_navigation,
|
|
check_success_sink_navigation, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** reconfigure test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean reconfigure_sent[2];
|
|
} reconfigure_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean reconfigure_scheduled;
|
|
gboolean reconfigure_sent[2];
|
|
gboolean got_caps[2][2];
|
|
} reconfigure_slave_data;
|
|
|
|
static GstPadProbeReturn
|
|
reconfigure_source_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_master_data *d = td->md;
|
|
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_RECONFIGURE) {
|
|
gint idx = pad2idx (pad, td->two_streams);
|
|
d->reconfigure_sent[idx] = TRUE;
|
|
EXCLUSIVE_CALL (td, g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p)));
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_reconfigure_source_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe_types (v, reconfigure_source_probe,
|
|
GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, user_data);
|
|
}
|
|
|
|
static void
|
|
reconfigure_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing)
|
|
d->got_state_changed_to_playing = TRUE;
|
|
}
|
|
|
|
static void
|
|
reconfigure_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (source));
|
|
while (gst_iterator_foreach (it, hook_reconfigure_source_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = reconfigure_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static void
|
|
send_reconfigure_on_element (const GValue * v, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_slave_data *d = td->sd;
|
|
GstElement *sink, *capsfilter;
|
|
GstPad *pad, *peer;
|
|
GstCaps *caps = NULL;
|
|
|
|
sink = g_value_get_object (v);
|
|
FAIL_UNLESS (sink);
|
|
pad = gst_element_get_static_pad (sink, "sink");
|
|
FAIL_UNLESS (pad);
|
|
|
|
// look for the previous element, change caps if a capsfilter
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
capsfilter = GST_ELEMENT (gst_pad_get_parent (peer));
|
|
g_object_get (capsfilter, "caps", &caps, NULL);
|
|
FAIL_UNLESS (caps);
|
|
caps = gst_caps_make_writable (caps);
|
|
if (!strcmp (gst_structure_get_name (gst_caps_get_structure (caps, 0)),
|
|
"audio/x-raw")) {
|
|
gst_caps_set_simple (caps, "rate", G_TYPE_INT, 48000, NULL);
|
|
} else {
|
|
gst_caps_set_simple (caps, "width", G_TYPE_INT, 320, "height", G_TYPE_INT,
|
|
200, NULL);
|
|
}
|
|
g_object_set (capsfilter, "caps", caps, NULL);
|
|
FAIL_UNLESS (capsfilter);
|
|
|
|
gst_object_unref (capsfilter);
|
|
gst_object_unref (peer);
|
|
|
|
d->reconfigure_sent[caps2idx (caps, td->two_streams)] = TRUE;
|
|
|
|
gst_caps_unref (caps);
|
|
gst_object_unref (pad);
|
|
}
|
|
|
|
static gboolean
|
|
send_reconfigure (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, send_reconfigure_on_element, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
reconfigure_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_slave_data *d = td->sd;
|
|
GstClockTime ts;
|
|
GstCaps *caps;
|
|
int idx;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
if (GST_CLOCK_TIME_IS_VALID (ts) && ts >= STEP_AT * GST_MSECOND) {
|
|
if (!d->reconfigure_scheduled) {
|
|
d->reconfigure_scheduled = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_idle_add ((GSourceFunc) send_reconfigure, td);
|
|
}
|
|
}
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
|
|
gst_event_parse_caps (GST_EVENT (info->data), &caps);
|
|
idx = caps2idx (caps, td->two_streams);
|
|
if (d->reconfigure_sent[idx]) {
|
|
d->got_caps[idx][1] = TRUE;
|
|
} else {
|
|
d->got_caps[idx][0] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_reconfigure_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, reconfigure_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_reconfigure (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_reconfigure_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_reconfigure (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_master_data *d = td->md;
|
|
gint idx;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->reconfigure_sent[idx]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_success_sink_reconfigure (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
reconfigure_slave_data *d = td->sd;
|
|
gint idx;
|
|
|
|
FAIL_UNLESS (d->reconfigure_scheduled);
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->reconfigure_sent[idx]);
|
|
FAIL_UNLESS (d->got_caps[idx][0]);
|
|
FAIL_UNLESS (d->got_caps[idx][1]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_non_live_a_reconfigure)
|
|
{
|
|
reconfigure_master_data md = { 0 };
|
|
reconfigure_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_FILTER_SINK_CAPS,
|
|
reconfigure_source, setup_sink_reconfigure,
|
|
check_success_source_reconfigure, check_success_sink_reconfigure, NULL,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_non_live_av_reconfigure)
|
|
{
|
|
reconfigure_master_data md = { 0 };
|
|
reconfigure_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_HAS_VIDEO |
|
|
TEST_FEATURE_FILTER_SINK_CAPS,
|
|
reconfigure_source, setup_sink_reconfigure,
|
|
check_success_source_reconfigure, check_success_sink_reconfigure, NULL,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_reconfigure)
|
|
{
|
|
reconfigure_master_data md = { 0 };
|
|
reconfigure_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE | TEST_FEATURE_FILTER_SINK_CAPS,
|
|
reconfigure_source, setup_sink_reconfigure,
|
|
check_success_source_reconfigure, check_success_sink_reconfigure, NULL,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_reconfigure)
|
|
{
|
|
reconfigure_master_data md = { 0 };
|
|
reconfigure_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_FILTER_SINK_CAPS,
|
|
reconfigure_source, setup_sink_reconfigure,
|
|
check_success_source_reconfigure, check_success_sink_reconfigure, NULL,
|
|
&md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** state changes test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gint step;
|
|
GHashTable *fdin, *fdout;
|
|
gboolean waiting_state_change;
|
|
} state_changes_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gint n_null;
|
|
gint n_ready;
|
|
gint n_paused;
|
|
gint n_playing;
|
|
gboolean got_eos;
|
|
} state_changes_slave_data;
|
|
|
|
static void
|
|
set_fdin (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
g_object_set (key, "fdin", GPOINTER_TO_INT (value), NULL);
|
|
}
|
|
|
|
static void
|
|
set_fdout (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
g_object_set (key, "fdout", GPOINTER_TO_INT (value), NULL);
|
|
}
|
|
|
|
/*
|
|
* NULL
|
|
* 0: READY NULL READY PAUSED READY PAUSED READY NULL
|
|
* 8: READY PAUSED PLAYING PAUSED PLAYING PAUSED READY PAUSED READY NULL
|
|
* 18: disconnect
|
|
* 19: READY NULL READY PAUSED READY PAUSED READY NULL
|
|
* 27: READY PAUSED PLAYING PAUSED PLAYING PAUSED READY PAUSED READY NULL
|
|
* 37: reconnect
|
|
* 38: READY NULL READY PAUSED READY PAUSED READY NULL
|
|
* 46: READY PAUSED PLAYING PAUSED PLAYING
|
|
* 51: EOS
|
|
*/
|
|
static gboolean
|
|
step_state_changes (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_master_data *d = td->md;
|
|
gboolean ret = G_SOURCE_CONTINUE;
|
|
GstStateChangeReturn scret = GST_STATE_CHANGE_FAILURE;
|
|
GList *l;
|
|
int fdin, fdout;
|
|
|
|
if (d->waiting_state_change)
|
|
goto done;
|
|
|
|
switch (d->step++) {
|
|
case 1:
|
|
case 7:
|
|
case 17:
|
|
case 20:
|
|
case 26:
|
|
case 36:
|
|
case 39:
|
|
case 45:
|
|
scret = gst_element_set_state (td->p, GST_STATE_NULL);
|
|
FAIL_UNLESS_EQUALS_INT (scret, GST_STATE_CHANGE_SUCCESS);
|
|
break;
|
|
case 0:
|
|
case 2:
|
|
case 4:
|
|
case 6:
|
|
case 8:
|
|
case 14:
|
|
case 16:
|
|
case 38:
|
|
case 40:
|
|
case 42:
|
|
case 44:
|
|
case 46:
|
|
scret = gst_element_set_state (td->p, GST_STATE_READY);
|
|
FAIL_UNLESS_EQUALS_INT (scret, GST_STATE_CHANGE_SUCCESS);
|
|
break;
|
|
case 19:
|
|
case 21:
|
|
case 23:
|
|
case 25:
|
|
case 27:
|
|
case 33:
|
|
case 35:
|
|
/* while we are disconnected, we can't do NULL -> READY */
|
|
scret = gst_element_set_state (td->p, GST_STATE_READY);
|
|
FAIL_UNLESS_EQUALS_INT (scret, GST_STATE_CHANGE_FAILURE);
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
case 9:
|
|
case 11:
|
|
case 13:
|
|
case 15:
|
|
case 41:
|
|
case 43:
|
|
case 47:
|
|
case 49:
|
|
td->state_target = GST_STATE_PAUSED;
|
|
scret = gst_element_set_state (td->p, GST_STATE_PAUSED);
|
|
FAIL_IF (scret == GST_STATE_CHANGE_FAILURE);
|
|
break;
|
|
case 22:
|
|
case 24:
|
|
case 28:
|
|
case 30:
|
|
case 32:
|
|
case 34:
|
|
/* while we are disconnected, we can't do NULL -> READY */
|
|
scret = gst_element_set_state (td->p, GST_STATE_PAUSED);
|
|
FAIL_UNLESS_EQUALS_INT (scret, GST_STATE_CHANGE_FAILURE);
|
|
break;
|
|
case 10:
|
|
case 12:
|
|
case 48:
|
|
case 50:
|
|
td->state_target = GST_STATE_PLAYING;
|
|
scret = gst_element_set_state (td->p, GST_STATE_PLAYING);
|
|
FAIL_IF (scret == GST_STATE_CHANGE_FAILURE);
|
|
break;
|
|
case 29:
|
|
case 31:
|
|
/* while we are disconnected, we can't do NULL -> READY */
|
|
scret = gst_element_set_state (td->p, GST_STATE_PLAYING);
|
|
FAIL_UNLESS_EQUALS_INT (scret, GST_STATE_CHANGE_FAILURE);
|
|
break;
|
|
case 18:
|
|
d->fdin = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
d->fdout = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
for (l = weak_refs; l; l = l->next) {
|
|
g_object_get (l->data, "fdin", &fdin, "fdout", &fdout, NULL);
|
|
g_hash_table_insert (d->fdin, (gpointer) l->data,
|
|
GINT_TO_POINTER (fdin));
|
|
g_hash_table_insert (d->fdout, (gpointer) l->data,
|
|
GINT_TO_POINTER (fdout));
|
|
g_signal_emit_by_name (G_OBJECT (l->data), "disconnect", NULL);
|
|
}
|
|
break;
|
|
case 37:
|
|
g_hash_table_foreach (d->fdin, set_fdin, NULL);
|
|
g_hash_table_foreach (d->fdout, set_fdout, NULL);
|
|
g_hash_table_destroy (d->fdin);
|
|
g_hash_table_destroy (d->fdout);
|
|
break;
|
|
case 51:
|
|
/* send EOS early to avoid waiting for the actual end of the file */
|
|
gst_element_send_event (td->p, gst_event_new_eos ());
|
|
gst_object_unref (td->p);
|
|
ret = G_SOURCE_REMOVE;
|
|
break;
|
|
}
|
|
|
|
if (scret == GST_STATE_CHANGE_ASYNC)
|
|
d->waiting_state_change = TRUE;
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
state_changes_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_master_data *d = td->md;
|
|
|
|
d->waiting_state_change = FALSE;
|
|
}
|
|
|
|
static void
|
|
state_changes_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_master_data *d = td->md;
|
|
|
|
gst_object_ref (source);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) step_state_changes, td);
|
|
|
|
d->waiting_state_change = FALSE;
|
|
td->state_changed_cb = state_changes_state_changed;
|
|
}
|
|
|
|
static GstBusSyncReply
|
|
state_changes_sink_bus_msg (GstBus * bus, GstMessage * message,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_slave_data *d = td->sd;
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_EOS:
|
|
d->got_eos = TRUE;
|
|
break;
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
if (GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (td->p)) {
|
|
GstState state;
|
|
gst_message_parse_state_changed (message, NULL, &state, NULL);
|
|
switch (state) {
|
|
case GST_STATE_NULL:
|
|
d->n_null++;
|
|
break;
|
|
case GST_STATE_READY:
|
|
d->n_ready++;
|
|
break;
|
|
case GST_STATE_PAUSED:
|
|
d->n_paused++;
|
|
break;
|
|
case GST_STATE_PLAYING:
|
|
d->n_playing++;
|
|
break;
|
|
default:
|
|
fail_if (1);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return GST_BUS_PASS;
|
|
}
|
|
|
|
static void
|
|
setup_sink_state_changes (GstElement * sink, gpointer user_data)
|
|
{
|
|
g_object_set (sink, "auto-flush-bus", FALSE, NULL);
|
|
gst_bus_set_sync_handler (GST_ELEMENT_BUS (sink), state_changes_sink_bus_msg,
|
|
user_data, NULL);
|
|
}
|
|
|
|
static void
|
|
check_success_source_state_changes (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS_EQUALS_INT (d->step, 52);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_state_changes (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_slave_data *d = td->sd;
|
|
GstBus *bus;
|
|
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (td->p));
|
|
gst_bus_set_flushing (bus, TRUE);
|
|
gst_object_unref (bus);
|
|
|
|
FAIL_UNLESS (d->got_eos);
|
|
FAIL_UNLESS_EQUALS_INT (d->n_null, 6);
|
|
FAIL_UNLESS_EQUALS_INT (d->n_ready, 13);
|
|
FAIL_UNLESS_EQUALS_INT (d->n_paused, 11);
|
|
FAIL_UNLESS_EQUALS_INT (d->n_playing, 4);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_state_changes)
|
|
{
|
|
state_changes_master_data md = { 0 };
|
|
state_changes_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, state_changes_source,
|
|
setup_sink_state_changes, check_success_source_state_changes,
|
|
check_success_sink_state_changes, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_state_changes)
|
|
{
|
|
state_changes_master_data md = { 0 };
|
|
state_changes_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, state_changes_source,
|
|
setup_sink_state_changes, check_success_source_state_changes,
|
|
check_success_sink_state_changes, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_state_changes)
|
|
{
|
|
state_changes_master_data md = { 0 };
|
|
state_changes_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, state_changes_source,
|
|
setup_sink_state_changes, check_success_source_state_changes,
|
|
check_success_sink_state_changes, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_state_changes)
|
|
{
|
|
state_changes_master_data md = { 0 };
|
|
state_changes_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
state_changes_source, setup_sink_state_changes,
|
|
check_success_source_state_changes, check_success_sink_state_changes,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** state changes stress test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gint n_state_changes;
|
|
} state_changes_stress_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
gboolean async_state_change_completed;
|
|
} state_changes_stress_master_data;
|
|
|
|
static gboolean
|
|
step_state_changes_stress (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_stress_input_data *i = td->id;
|
|
state_changes_stress_master_data *d = td->md;
|
|
static const GstState states[] =
|
|
{ GST_STATE_NULL, GST_STATE_READY, GST_STATE_PAUSED, GST_STATE_PLAYING };
|
|
GstState state;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* wait for async state change to complete before continuing */
|
|
if (!d->async_state_change_completed)
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
if (i->n_state_changes == 0) {
|
|
ret = gst_element_set_state (td->p, GST_STATE_PLAYING);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline, td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
--i->n_state_changes;
|
|
|
|
state = states[rand () % 4];
|
|
ret = gst_element_set_state (td->p, state);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
|
|
if (ret == GST_STATE_CHANGE_ASYNC) {
|
|
td->state_target = state;
|
|
d->async_state_change_completed = FALSE;
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
state_changes_stress_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_stress_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (50, (GSourceFunc) step_state_changes_stress, td);
|
|
}
|
|
d->async_state_change_completed = TRUE;
|
|
}
|
|
|
|
static void
|
|
state_changes_stress_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = state_changes_stress_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static void
|
|
check_success_source_state_changes_stress (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
state_changes_stress_input_data *i = td->id;
|
|
state_changes_stress_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_state_changes, 0);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_state_changes_stress)
|
|
{
|
|
state_changes_stress_input_data id = { 500 };
|
|
state_changes_stress_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
state_changes_stress_source, NULL,
|
|
check_success_source_state_changes_stress, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** serialized query test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean sent_query[2];
|
|
gboolean got_query_reply[2];
|
|
GstPad *pad[2];
|
|
} serialized_query_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_query;
|
|
} serialized_query_slave_data;
|
|
|
|
static gboolean
|
|
send_drain (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
serialized_query_master_data *d = td->md;
|
|
GstQuery *q;
|
|
gint idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
q = gst_query_new_drain ();
|
|
FAIL_UNLESS (gst_pad_query (d->pad[idx], q));
|
|
d->got_query_reply[idx] = TRUE;
|
|
gst_query_unref (q);
|
|
gst_object_unref (d->pad[idx]);
|
|
}
|
|
|
|
g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline, gst_object_ref (td->p));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
serialized_query_probe_source (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
serialized_query_master_data *d = td->md;
|
|
GstClockTime ts;
|
|
gint idx;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
idx = pad2idx (pad, td->two_streams);
|
|
if (!d->sent_query[idx] && GST_CLOCK_TIME_IS_VALID (ts)
|
|
&& ts > STEP_AT * GST_MSECOND) {
|
|
d->sent_query[idx] = TRUE;
|
|
d->pad[idx] = gst_object_ref (pad);
|
|
EXCLUSIVE_CALL (td, g_idle_add (send_drain, td));
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_serialized_query_probe_source (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, serialized_query_probe_source, user_data);
|
|
}
|
|
|
|
static void
|
|
serialized_query_source (GstElement * source, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
GstStateChangeReturn ret;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (source));
|
|
while (gst_iterator_foreach (it, hook_serialized_query_probe_source,
|
|
user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
serialized_query_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
serialized_query_slave_data *d = td->sd;
|
|
|
|
if (GST_IS_QUERY (info->data)) {
|
|
if (GST_QUERY_TYPE (info->data) == GST_QUERY_DRAIN) {
|
|
d->got_query = TRUE;
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_serialized_query_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, serialized_query_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_serialized_query (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_serialized_query_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_serialized_query (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
serialized_query_master_data *d = td->md;
|
|
gint idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->sent_query[idx]);
|
|
FAIL_UNLESS (d->got_query_reply[idx]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_success_sink_serialized_query (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
serialized_query_slave_data *d = td->sd;
|
|
|
|
FAIL_UNLESS (d->got_query);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, serialized_query_source,
|
|
setup_sink_serialized_query, check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, serialized_query_source,
|
|
setup_sink_serialized_query, check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, serialized_query_source,
|
|
setup_sink_serialized_query, check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
serialized_query_source, setup_sink_serialized_query,
|
|
check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, serialized_query_source,
|
|
setup_sink_serialized_query, check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, serialized_query_source,
|
|
setup_sink_serialized_query, check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_serialized_query)
|
|
{
|
|
serialized_query_master_data md = { {0} };
|
|
serialized_query_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
serialized_query_source, setup_sink_serialized_query,
|
|
check_success_source_serialized_query,
|
|
check_success_sink_serialized_query, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** non serialized event test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean sent_event[2];
|
|
} non_serialized_event_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_event;
|
|
} non_serialized_event_slave_data;
|
|
|
|
static GstPadProbeReturn
|
|
non_serialized_event_probe_source (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
non_serialized_event_master_data *d = td->md;
|
|
GstClockTime ts;
|
|
GstEvent *e;
|
|
gint idx;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
ts = GST_BUFFER_TIMESTAMP (info->data);
|
|
idx = pad2idx (pad, td->two_streams);
|
|
if (!d->sent_event[idx]
|
|
&& GST_CLOCK_TIME_IS_VALID (ts) && ts > STEP_AT * GST_MSECOND) {
|
|
e = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB,
|
|
gst_structure_new ("name", "field", G_TYPE_INT, 42, NULL));
|
|
FAIL_UNLESS (e);
|
|
FAIL_UNLESS (gst_pad_send_event (pad, e));
|
|
d->sent_event[idx] = TRUE;
|
|
EXCLUSIVE_CALL (td, g_timeout_add (STEP_AT, (GSourceFunc) stop_pipeline,
|
|
gst_object_ref (td->p)));
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_non_serialized_event_probe_source (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, non_serialized_event_probe_source, user_data);
|
|
}
|
|
|
|
static void
|
|
non_serialized_event_source (GstElement * source, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
GstStateChangeReturn ret;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (source));
|
|
while (gst_iterator_foreach (it, hook_non_serialized_event_probe_source,
|
|
user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
non_serialized_event_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
non_serialized_event_slave_data *d = td->sd;
|
|
const GstStructure *s;
|
|
gint val;
|
|
|
|
if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CUSTOM_DOWNSTREAM_OOB) {
|
|
s = gst_event_get_structure (info->data);
|
|
FAIL_UNLESS (!strcmp (gst_structure_get_name (s), "name"));
|
|
FAIL_UNLESS (gst_structure_get_int (s, "field", &val));
|
|
FAIL_UNLESS (val == 42);
|
|
d->got_event = TRUE;
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_non_serialized_event_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, non_serialized_event_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_non_serialized_event (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_non_serialized_event_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_non_serialized_event (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
non_serialized_event_master_data *d = td->md;
|
|
gint idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->sent_event[idx]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_success_sink_non_serialized_event (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
non_serialized_event_slave_data *d = td->sd;
|
|
|
|
FAIL_UNLESS (d->got_event);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, non_serialized_event_source,
|
|
setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, non_serialized_event_source,
|
|
setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, non_serialized_event_source,
|
|
setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
non_serialized_event_source, setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, non_serialized_event_source,
|
|
setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, non_serialized_event_source,
|
|
setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_non_serialized_event)
|
|
{
|
|
non_serialized_event_master_data md = { {0} };
|
|
non_serialized_event_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
non_serialized_event_source, setup_sink_non_serialized_event,
|
|
check_success_source_non_serialized_event,
|
|
check_success_sink_non_serialized_event, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** meta test ****/
|
|
|
|
enum
|
|
{
|
|
TEST_META_PROTECTION = 0,
|
|
N_TEST_META
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
gboolean meta_sent[N_TEST_META];
|
|
} meta_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean meta_received[N_TEST_META];
|
|
} meta_slave_data;
|
|
|
|
static GstPadProbeReturn
|
|
meta_probe_source (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
meta_master_data *d = td->md;
|
|
GstBuffer *buffer;
|
|
GstProtectionMeta *meta;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
buffer = GST_BUFFER (info->data);
|
|
meta =
|
|
gst_buffer_add_protection_meta (buffer, gst_structure_new ("name",
|
|
"somefield", G_TYPE_INT, 42, NULL));
|
|
FAIL_UNLESS (meta);
|
|
d->meta_sent[TEST_META_PROTECTION] = TRUE;
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_meta_probe_source (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, meta_probe_source, user_data);
|
|
}
|
|
|
|
static void
|
|
meta_source (GstElement * source, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
GstStateChangeReturn ret;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (source));
|
|
while (gst_iterator_foreach (it, hook_meta_probe_source, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
|
|
g_timeout_add (STOP_AT, (GSourceFunc) stop_pipeline, gst_object_ref (source));
|
|
}
|
|
|
|
static gboolean
|
|
scan_meta (GstBuffer * buffer, GstMeta ** meta, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
meta_slave_data *d = td->sd;
|
|
int val;
|
|
GstStructure *s;
|
|
GstProtectionMeta *pmeta;
|
|
|
|
if ((*meta)->info->api == GST_PROTECTION_META_API_TYPE) {
|
|
pmeta = (GstProtectionMeta *) * meta;
|
|
FAIL_UNLESS (GST_IS_STRUCTURE (pmeta->info));
|
|
s = GST_STRUCTURE (pmeta->info);
|
|
FAIL_UNLESS (!strcmp (gst_structure_get_name (s), "name"));
|
|
FAIL_UNLESS (gst_structure_get_int (s, "somefield", &val));
|
|
FAIL_UNLESS (val == 42);
|
|
d->meta_received[TEST_META_PROTECTION] = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
meta_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
gst_buffer_foreach_meta (info->data, scan_meta, user_data);
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_meta_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, meta_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_meta (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_meta_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_meta (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
meta_master_data *d = td->md;
|
|
size_t n;
|
|
|
|
for (n = 0; n < N_TEST_META; ++n)
|
|
FAIL_UNLESS (d->meta_sent[n]);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_meta (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
meta_slave_data *d = td->sd;
|
|
size_t n;
|
|
|
|
for (n = 0; n < N_TEST_META; ++n)
|
|
FAIL_UNLESS (d->meta_received[n]);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE, meta_source, setup_sink_meta,
|
|
check_success_source_meta, check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, meta_source, setup_sink_meta,
|
|
check_success_source_meta, check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE, meta_source, setup_sink_meta,
|
|
check_success_source_meta, check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_SPLIT_SINKS, meta_source,
|
|
setup_sink_meta, check_success_source_meta, check_success_sink_meta,
|
|
NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE, meta_source, setup_sink_meta,
|
|
check_success_source_meta, check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, meta_source, setup_sink_meta,
|
|
check_success_source_meta, check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_meta)
|
|
{
|
|
meta_master_data md = { {0}
|
|
};
|
|
meta_slave_data sd = { {0}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
meta_source, setup_sink_meta, check_success_source_meta,
|
|
check_success_sink_meta, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** source change test ****/
|
|
|
|
typedef struct
|
|
{
|
|
void (*switcher) (GstElement *, char *name);
|
|
} source_change_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean source_change_scheduled;
|
|
gboolean source_changed;
|
|
} source_change_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_caps[2][2];
|
|
gboolean got_buffer[2][2];
|
|
GstCaps *caps[2];
|
|
} source_change_slave_data;
|
|
|
|
static gboolean
|
|
stop_source (gpointer user_data)
|
|
{
|
|
GstElement *source = user_data;
|
|
|
|
FAIL_UNLESS (gst_element_set_state (source,
|
|
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
|
|
gst_object_unref (source);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
remove_source (gpointer user_data)
|
|
{
|
|
GstElement *source = user_data;
|
|
|
|
FAIL_UNLESS (gst_element_set_state (source,
|
|
GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS);
|
|
gst_bin_remove (GST_BIN (GST_ELEMENT_PARENT (source)), source);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
switch_to_aiff (GstElement * pipeline, char *name)
|
|
{
|
|
GstElement *sbin, *filesrc, *ipcpipelinesink;
|
|
GError *e = NULL;
|
|
|
|
sbin =
|
|
gst_parse_bin_from_description ("pushfilesrc name=filesrc ! aiffparse",
|
|
TRUE, &e);
|
|
FAIL_IF (e || !sbin);
|
|
gst_element_set_name (sbin, name);
|
|
filesrc = gst_bin_get_by_name (GST_BIN (sbin), "filesrc");
|
|
FAIL_UNLESS (filesrc);
|
|
g_object_set (filesrc, "location", "../../tests/files/s16be-id3v2.aiff",
|
|
NULL);
|
|
gst_object_unref (filesrc);
|
|
gst_bin_add (GST_BIN (pipeline), sbin);
|
|
ipcpipelinesink = gst_bin_get_by_name (GST_BIN (pipeline), "ipcpipelinesink");
|
|
FAIL_UNLESS (ipcpipelinesink);
|
|
FAIL_UNLESS (gst_element_link (sbin, ipcpipelinesink));
|
|
gst_object_unref (ipcpipelinesink);
|
|
gst_element_sync_state_with_parent (sbin);
|
|
g_free (name);
|
|
}
|
|
|
|
static void
|
|
switch_av (GstElement * pipeline, char *name, gboolean live, gboolean Long)
|
|
{
|
|
GstElement *src, *ipcpipelinesink;
|
|
gint L = Long ? 10 : 1;
|
|
|
|
if (g_str_has_prefix (name, "videotestsrc")) {
|
|
/* replace video source with audio source */
|
|
src = gst_element_factory_make ("audiotestsrc", NULL);
|
|
FAIL_UNLESS (src);
|
|
g_object_set (src, "is-live", live, "num-buffers", live ? 27 * L : -1,
|
|
NULL);
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
ipcpipelinesink =
|
|
gst_bin_get_by_name (GST_BIN (pipeline), "vipcpipelinesink");
|
|
FAIL_UNLESS (ipcpipelinesink);
|
|
FAIL_UNLESS (gst_element_link (src, ipcpipelinesink));
|
|
gst_object_unref (ipcpipelinesink);
|
|
gst_element_sync_state_with_parent (src);
|
|
}
|
|
|
|
if (g_str_has_prefix (name, "audiotestsrc")) {
|
|
/* replace audio source with video source */
|
|
src = gst_element_factory_make ("videotestsrc", NULL);
|
|
FAIL_UNLESS (src);
|
|
g_object_set (src, "is-live", live, "num-buffers", live ? 19 * L : -1,
|
|
NULL);
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
ipcpipelinesink =
|
|
gst_bin_get_by_name (GST_BIN (pipeline), "aipcpipelinesink");
|
|
FAIL_UNLESS (ipcpipelinesink);
|
|
FAIL_UNLESS (gst_element_link (src, ipcpipelinesink));
|
|
gst_object_unref (ipcpipelinesink);
|
|
gst_element_sync_state_with_parent (src);
|
|
}
|
|
|
|
g_free (name);
|
|
}
|
|
|
|
static void
|
|
switch_live_av (GstElement * pipeline, char *name)
|
|
{
|
|
switch_av (pipeline, name, TRUE, FALSE);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
change_source_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
const source_change_input_data *i = td->id;
|
|
source_change_master_data *d = td->md;
|
|
GstElement *source;
|
|
GstPad *peer;
|
|
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
FAIL_UNLESS (gst_pad_unlink (pad, peer));
|
|
gst_object_unref (peer);
|
|
|
|
source = GST_ELEMENT (gst_element_get_parent (pad));
|
|
FAIL_UNLESS (source);
|
|
g_object_set_qdata (G_OBJECT (source), to_be_removed_quark (),
|
|
GINT_TO_POINTER (1));
|
|
|
|
gst_bin_remove (GST_BIN (GST_ELEMENT_PARENT (source)), source);
|
|
(*i->switcher) (td->p, gst_element_get_name (source));
|
|
|
|
g_idle_add (stop_source, source);
|
|
|
|
d->source_changed = TRUE;
|
|
|
|
gst_object_unref (td->p);
|
|
return GST_PAD_PROBE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
change_source (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstElement *source;
|
|
GstPad *pad;
|
|
static const char *const names[] =
|
|
{ "source", "audiotestsrc", "videotestsrc" };
|
|
gboolean found = FALSE;
|
|
size_t n;
|
|
|
|
for (n = 0; n < G_N_ELEMENTS (names); ++n) {
|
|
source = gst_bin_get_by_name (GST_BIN (td->p), names[n]);
|
|
if (source) {
|
|
found = TRUE;
|
|
pad = gst_element_get_static_pad (source, "src");
|
|
FAIL_UNLESS (pad);
|
|
gst_object_ref (td->p);
|
|
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, change_source_blocked,
|
|
user_data, NULL);
|
|
gst_object_unref (pad);
|
|
gst_object_unref (source);
|
|
}
|
|
}
|
|
FAIL_UNLESS (found);
|
|
|
|
gst_object_unref (td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
source_change_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
source_change_master_data *d = td->md;
|
|
|
|
if (!d->source_change_scheduled) {
|
|
d->source_change_scheduled = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (STEP_AT, change_source, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
source_change_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = source_change_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static int
|
|
scppad2idx (GstPad * pad, gboolean two_streams, GstCaps * newcaps)
|
|
{
|
|
static GQuark scpidx = 0;
|
|
gpointer p;
|
|
int idx;
|
|
GstCaps *caps;
|
|
|
|
if (!scpidx)
|
|
scpidx = g_quark_from_static_string ("scpidx");
|
|
|
|
if (!two_streams)
|
|
return 0;
|
|
|
|
p = g_object_get_qdata (G_OBJECT (pad), scpidx);
|
|
if (p)
|
|
return GPOINTER_TO_INT (p) - 1;
|
|
|
|
caps = gst_pad_get_current_caps (pad);
|
|
if (!caps)
|
|
caps = gst_pad_get_pad_template_caps (pad);
|
|
if ((!caps || gst_caps_is_any (caps)) && newcaps)
|
|
caps = gst_caps_ref (newcaps);
|
|
FAIL_UNLESS (caps);
|
|
idx = caps2idx (caps, two_streams);
|
|
gst_caps_unref (caps);
|
|
g_object_set_qdata (G_OBJECT (pad), scpidx, GINT_TO_POINTER (idx + 1));
|
|
return idx;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
source_change_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
source_change_slave_data *d = td->sd;
|
|
GstCaps *caps;
|
|
int idx;
|
|
|
|
if (GST_IS_BUFFER (info->data)) {
|
|
idx = scppad2idx (pad, td->two_streams, NULL);
|
|
if (d->got_caps[idx][1])
|
|
d->got_buffer[idx][1] = TRUE;
|
|
else if (d->got_caps[idx][0])
|
|
d->got_buffer[idx][0] = TRUE;
|
|
} else if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) {
|
|
gst_event_parse_caps (info->data, &caps);
|
|
idx = scppad2idx (pad, td->two_streams, caps);
|
|
if (!d->got_caps[idx][0]) {
|
|
FAIL_IF (d->caps[idx]);
|
|
d->got_caps[idx][0] = TRUE;
|
|
d->caps[idx] = gst_caps_ref (caps);
|
|
} else {
|
|
FAIL_UNLESS (d->caps);
|
|
if (gst_caps_is_equal (caps, d->caps[idx])) {
|
|
FAIL ();
|
|
} else {
|
|
gst_caps_replace (&d->caps[idx], NULL);
|
|
d->got_caps[idx][1] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_source_change_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, source_change_probe, user_data);
|
|
}
|
|
|
|
static void
|
|
setup_sink_source_change (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_source_change_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
check_success_source_source_change (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
source_change_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->source_change_scheduled);
|
|
FAIL_UNLESS (d->source_changed);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_source_change (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
source_change_slave_data *d = td->sd;
|
|
int idx;
|
|
|
|
for (idx = 0; idx < (td->two_streams ? 2 : 1); idx++) {
|
|
FAIL_UNLESS (d->got_caps[idx][0]);
|
|
FAIL_UNLESS (d->got_buffer[idx][0]);
|
|
FAIL_UNLESS (d->got_caps[idx][1]);
|
|
FAIL_UNLESS (d->got_buffer[idx][1]);
|
|
}
|
|
}
|
|
|
|
GST_START_TEST (test_non_live_source_change)
|
|
{
|
|
source_change_input_data id = { switch_to_aiff };
|
|
source_change_master_data md = { 0 };
|
|
source_change_slave_data sd = { {{0}
|
|
}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE, source_change_source,
|
|
setup_sink_source_change, check_success_source_source_change,
|
|
check_success_sink_source_change, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_source_change)
|
|
{
|
|
source_change_input_data id = { switch_live_av };
|
|
source_change_master_data md = { 0 };
|
|
source_change_slave_data sd = { {{0}
|
|
}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, source_change_source,
|
|
setup_sink_source_change, check_success_source_source_change,
|
|
check_success_sink_source_change, &id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_source_change)
|
|
{
|
|
source_change_input_data id = { switch_live_av };
|
|
source_change_master_data md = { 0 };
|
|
source_change_slave_data sd = { {{0}
|
|
}
|
|
};
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
source_change_source, setup_sink_source_change,
|
|
check_success_source_source_change, check_success_sink_source_change,
|
|
&id, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** dynamic pipeline change stress test ****/
|
|
|
|
typedef struct
|
|
{
|
|
guint n_switches_0;
|
|
void (*switcher0) (test_data *);
|
|
guint n_switches_1;
|
|
void (*switcher1) (test_data *);
|
|
} dynamic_pipeline_change_stress_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
GMutex mutex;
|
|
GCond cond;
|
|
guint n_blocks_left;
|
|
guint n_blocks_done;
|
|
gboolean adding_probes;
|
|
gboolean dynamic_pipeline_change_stress_scheduled;
|
|
} dynamic_pipeline_change_stress_master_data;
|
|
|
|
static gboolean dynamic_pipeline_change_stress_step (gpointer user_data);
|
|
|
|
static GstPadProbeReturn
|
|
dynamic_pipeline_change_stress_source_blocked_switch_av (GstPad * pad,
|
|
GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
GstElement *source;
|
|
GstPad *peer;
|
|
|
|
/* An idle pad probe could be called directly from the gst_pad_add_probe call
|
|
if the pad happens to be idle right now. This would deadlock us though, as
|
|
we need all pads to be blocked at the same time, so we need the iteration
|
|
over all pads to be done before the pad probes execute. So we keep track of
|
|
whether we're iterating to add the probes, and pass if so. */
|
|
if (d->adding_probes) {
|
|
return GST_PAD_PROBE_PASS;
|
|
}
|
|
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
FAIL_UNLESS (gst_pad_unlink (pad, peer));
|
|
gst_object_unref (peer);
|
|
|
|
source = GST_ELEMENT (gst_element_get_parent (pad));
|
|
FAIL_UNLESS (source);
|
|
g_object_set_qdata (G_OBJECT (source), to_be_removed_quark (),
|
|
GINT_TO_POINTER (1));
|
|
|
|
/* we want all pads to be blocked before we proceed */
|
|
g_mutex_lock (&d->mutex);
|
|
d->n_blocks_left--;
|
|
while (d->n_blocks_left > 0)
|
|
g_cond_wait (&d->cond, &d->mutex);
|
|
g_mutex_unlock (&d->mutex);
|
|
g_cond_broadcast (&d->cond);
|
|
|
|
g_mutex_lock (&d->mutex);
|
|
switch_av (td->p, gst_element_get_name (source),
|
|
! !(td->features & TEST_FEATURE_LIVE), TRUE);
|
|
g_mutex_unlock (&d->mutex);
|
|
|
|
g_idle_add_full (G_PRIORITY_HIGH, remove_source, source, g_object_unref);
|
|
|
|
if (g_atomic_int_dec_and_test (&d->n_blocks_done))
|
|
g_timeout_add (STEP_AT, dynamic_pipeline_change_stress_step, td);
|
|
|
|
return GST_PAD_PROBE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
change_audio_channel (GstElement * pipeline, char *name,
|
|
const char *ipcpipelinesink_name, gboolean live)
|
|
{
|
|
GstElement *src, *ipcpipelinesink;
|
|
|
|
/* replace audio source with video source */
|
|
src = gst_element_factory_make ("audiotestsrc", NULL);
|
|
FAIL_UNLESS (src);
|
|
g_object_set (src, "is-live", live, "num-buffers", live ? 190 : -1, NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipeline), src);
|
|
ipcpipelinesink =
|
|
gst_bin_get_by_name (GST_BIN (pipeline), ipcpipelinesink_name);
|
|
FAIL_UNLESS (ipcpipelinesink);
|
|
FAIL_UNLESS (gst_element_link (src, ipcpipelinesink));
|
|
gst_object_unref (ipcpipelinesink);
|
|
gst_element_sync_state_with_parent (src);
|
|
|
|
g_free (name);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
dynamic_pipeline_change_stress_source_blocked_change_audio_channel (GstPad *
|
|
pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
GstElement *source;
|
|
GstPad *peer;
|
|
const char *ipcpipelinesink_name;
|
|
|
|
/* An idle pad probe could be called directly from the gst_pad_add_probe call
|
|
if the pad happens to be idle right now. This would deadlock us though, as
|
|
we need all pads to be blocked at the same time, so we need the iteration
|
|
over all pads to be done before the pad probes execute. So we keep track of
|
|
whether we're iterating to add the probes, and pass if so. */
|
|
if (d->adding_probes) {
|
|
return GST_PAD_PROBE_PASS;
|
|
}
|
|
|
|
peer = gst_pad_get_peer (pad);
|
|
FAIL_UNLESS (peer);
|
|
ipcpipelinesink_name = GST_ELEMENT_NAME (GST_PAD_PARENT (peer));
|
|
FAIL_UNLESS (gst_pad_unlink (pad, peer));
|
|
gst_object_unref (peer);
|
|
|
|
source = GST_ELEMENT (gst_element_get_parent (pad));
|
|
FAIL_UNLESS (source);
|
|
g_object_set_qdata (G_OBJECT (source), to_be_removed_quark (),
|
|
GINT_TO_POINTER (1));
|
|
|
|
/* we want all pads to be blocked before we proceed */
|
|
g_mutex_lock (&d->mutex);
|
|
d->n_blocks_left--;
|
|
while (d->n_blocks_left > 0)
|
|
g_cond_wait (&d->cond, &d->mutex);
|
|
g_cond_broadcast (&d->cond);
|
|
g_mutex_unlock (&d->mutex);
|
|
|
|
g_mutex_lock (&d->mutex);
|
|
change_audio_channel (td->p, gst_element_get_name (source),
|
|
ipcpipelinesink_name, ! !(td->features & TEST_FEATURE_LIVE));
|
|
g_mutex_unlock (&d->mutex);
|
|
|
|
g_idle_add_full (G_PRIORITY_HIGH, remove_source, source, g_object_unref);
|
|
|
|
if (g_atomic_int_dec_and_test (&d->n_blocks_done))
|
|
g_timeout_add (STEP_AT, dynamic_pipeline_change_stress_step, td);
|
|
|
|
return GST_PAD_PROBE_REMOVE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const char *const *names;
|
|
size_t n_names;
|
|
GstPadProbeReturn (*f) (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data);
|
|
test_data *td;
|
|
} block_if_named_data;
|
|
|
|
static void
|
|
block_if_named (const GValue * v, gpointer user_data)
|
|
{
|
|
block_if_named_data *bind = user_data;
|
|
GstElement *e;
|
|
GstPad *pad;
|
|
size_t n;
|
|
|
|
e = g_value_get_object (v);
|
|
FAIL_UNLESS (e);
|
|
for (n = 0; n < bind->n_names; ++n) {
|
|
if (g_str_has_prefix (GST_ELEMENT_NAME (e), bind->names[n])) {
|
|
pad = gst_element_get_static_pad (e, "src");
|
|
FAIL_UNLESS (pad);
|
|
|
|
if (!g_object_get_qdata (G_OBJECT (e), to_be_removed_quark ()))
|
|
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, bind->f, bind->td,
|
|
NULL);
|
|
gst_object_unref (pad);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
count_audio_sources (const GValue * v, gpointer user_data)
|
|
{
|
|
GstElement *e;
|
|
|
|
e = g_value_get_object (v);
|
|
FAIL_UNLESS (e);
|
|
|
|
// we don't want to count the sources that are in the process
|
|
// of being removed asynchronously
|
|
if (g_object_get_qdata (G_OBJECT (e), to_be_removed_quark ()))
|
|
return;
|
|
|
|
if (g_str_has_prefix (GST_ELEMENT_NAME (e), "audiotestsrc"))
|
|
++ * (guint *) user_data;
|
|
}
|
|
|
|
static void
|
|
dynamic_pipeline_change_stress_swap_source (test_data * td)
|
|
{
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
static const char *const names[] =
|
|
{ "source", "audiotestsrc", "videotestsrc" };
|
|
block_if_named_data bind = { names, sizeof (names) / sizeof (names[0]),
|
|
dynamic_pipeline_change_stress_source_blocked_switch_av, td
|
|
};
|
|
GstIterator *it;
|
|
|
|
/* we have two sources, we need to wait for both */
|
|
d->n_blocks_left = d->n_blocks_done = 2;
|
|
|
|
it = gst_bin_iterate_sources (GST_BIN (td->p));
|
|
d->adding_probes = TRUE;
|
|
while (gst_iterator_foreach (it, block_if_named, &bind)) {
|
|
GST_INFO_OBJECT (td->p, "Resync");
|
|
gst_iterator_resync (it);
|
|
}
|
|
d->adding_probes = FALSE;
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static void
|
|
dynamic_pipeline_change_stress_change_audio_channel (test_data * td)
|
|
{
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
static const char *const names[] = { "audiotestsrc" };
|
|
block_if_named_data bind = { names, sizeof (names) / sizeof (names[0]),
|
|
dynamic_pipeline_change_stress_source_blocked_change_audio_channel, td
|
|
};
|
|
GstIterator *it;
|
|
guint audio_sources;
|
|
|
|
/* we have either zero or one audio source */
|
|
it = gst_bin_iterate_sources (GST_BIN (td->p));
|
|
audio_sources = 0;
|
|
while (gst_iterator_foreach (it, count_audio_sources, &audio_sources)) {
|
|
GST_INFO_OBJECT (td->p, "Resync");
|
|
gst_iterator_resync (it);
|
|
}
|
|
gst_iterator_free (it);
|
|
d->n_blocks_left = d->n_blocks_done = audio_sources;
|
|
|
|
it = gst_bin_iterate_sources (GST_BIN (td->p));
|
|
d->adding_probes = TRUE;
|
|
while (gst_iterator_foreach (it, block_if_named, &bind)) {
|
|
GST_INFO_OBJECT (td->p, "Resync");
|
|
gst_iterator_resync (it);
|
|
}
|
|
d->adding_probes = FALSE;
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
static gboolean
|
|
dynamic_pipeline_change_stress_step (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_input_data *i = td->id;
|
|
guint available, idx;
|
|
|
|
/* pick a random action among the ones we have left */
|
|
available = i->n_switches_0 + i->n_switches_1;
|
|
if (available == 0) {
|
|
GST_INFO_OBJECT (td->p, "Destroying pipeline");
|
|
FAIL_UNLESS (gst_element_set_state (td->p,
|
|
GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE);
|
|
g_timeout_add (STEP_AT, stop_pipeline, td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
idx = rand () % available;
|
|
if (idx < i->n_switches_0) {
|
|
(*i->switcher0) (td);
|
|
--i->n_switches_0;
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
idx -= i->n_switches_0;
|
|
|
|
if (idx < i->n_switches_1) {
|
|
(*i->switcher1) (td);
|
|
--i->n_switches_1;
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
idx -= i->n_switches_1;
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
dynamic_pipeline_change_stress_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
|
|
if (!d->dynamic_pipeline_change_stress_scheduled) {
|
|
d->dynamic_pipeline_change_stress_scheduled = TRUE;
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (STEP_AT, dynamic_pipeline_change_stress_step, td);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dynamic_pipeline_change_stress (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
GstStateChangeReturn ret;
|
|
|
|
g_mutex_init (&d->mutex);
|
|
g_cond_init (&d->cond);
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = dynamic_pipeline_change_stress_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
check_success_source_dynamic_pipeline_change_stress (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
dynamic_pipeline_change_stress_input_data *i = td->id;
|
|
dynamic_pipeline_change_stress_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->dynamic_pipeline_change_stress_scheduled);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_switches_0, 0);
|
|
FAIL_UNLESS_EQUALS_INT (i->n_switches_1, 0);
|
|
|
|
g_cond_clear (&d->cond);
|
|
g_mutex_clear (&d->mutex);
|
|
}
|
|
|
|
GST_START_TEST (test_non_live_av_dynamic_pipeline_change_stress)
|
|
{
|
|
dynamic_pipeline_change_stress_input_data id = { 100,
|
|
dynamic_pipeline_change_stress_swap_source, 100,
|
|
dynamic_pipeline_change_stress_change_audio_channel
|
|
};
|
|
dynamic_pipeline_change_stress_master_data md = { {0} };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_HAS_VIDEO,
|
|
dynamic_pipeline_change_stress, NULL,
|
|
check_success_source_dynamic_pipeline_change_stress, NULL, &id, &md,
|
|
NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_non_live_av_2_dynamic_pipeline_change_stress)
|
|
{
|
|
dynamic_pipeline_change_stress_input_data id = { 100,
|
|
dynamic_pipeline_change_stress_swap_source, 100,
|
|
dynamic_pipeline_change_stress_change_audio_channel
|
|
};
|
|
dynamic_pipeline_change_stress_master_data md = { {0} };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_HAS_VIDEO |
|
|
TEST_FEATURE_SPLIT_SINKS, dynamic_pipeline_change_stress, NULL,
|
|
check_success_source_dynamic_pipeline_change_stress, NULL, &id, &md,
|
|
NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_dynamic_pipeline_change_stress)
|
|
{
|
|
dynamic_pipeline_change_stress_input_data id = { 100,
|
|
dynamic_pipeline_change_stress_swap_source, 100,
|
|
dynamic_pipeline_change_stress_change_audio_channel
|
|
};
|
|
dynamic_pipeline_change_stress_master_data md = { {0} };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE, dynamic_pipeline_change_stress, NULL,
|
|
check_success_source_dynamic_pipeline_change_stress, NULL, &id, &md,
|
|
NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_dynamic_pipeline_change_stress)
|
|
{
|
|
dynamic_pipeline_change_stress_input_data id = { 100,
|
|
dynamic_pipeline_change_stress_swap_source, 100,
|
|
dynamic_pipeline_change_stress_change_audio_channel
|
|
};
|
|
dynamic_pipeline_change_stress_master_data md = { {0} };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_SPLIT_SINKS,
|
|
dynamic_pipeline_change_stress, NULL,
|
|
check_success_source_dynamic_pipeline_change_stress, NULL, &id, &md,
|
|
NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** error from slave test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean crash;
|
|
} error_from_slave_input_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean second_pass;
|
|
gboolean got_state_changed_to_playing_on_first_pass;
|
|
gboolean got_error_on_first_pass;
|
|
gboolean got_state_changed_to_playing_on_second_pass;
|
|
gboolean got_error_on_second_pass;
|
|
} error_from_slave_master_data;
|
|
|
|
static gboolean
|
|
bump_through_NULL (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
error_from_slave_input_data *i = td->id;
|
|
error_from_slave_master_data *d = td->md;
|
|
GstStateChangeReturn ret;
|
|
GstElement *sink;
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_NULL);
|
|
if (!i->crash) {
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
FAIL_UNLESS (gst_element_get_state (td->p, NULL, NULL,
|
|
GST_CLOCK_TIME_NONE) == GST_STATE_CHANGE_SUCCESS);
|
|
|
|
d->second_pass = TRUE;
|
|
|
|
if (i->crash) {
|
|
recreate_crashed_slave_process ();
|
|
/* give the process time to be created in the other process */
|
|
g_usleep (500 * 1000);
|
|
|
|
/* reconnect to to slave process */
|
|
sink = gst_bin_get_by_name (GST_BIN (td->p), "ipcpipelinesink");
|
|
FAIL_UNLESS (sink);
|
|
g_object_set (sink, "fdin", pipesba[0], "fdout", pipesfa[1], NULL);
|
|
gst_object_unref (sink);
|
|
}
|
|
|
|
ret = gst_element_set_state (td->p, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_SUCCESS
|
|
|| ret == GST_STATE_CHANGE_ASYNC);
|
|
|
|
g_timeout_add (STOP_AT, (GSourceFunc) stop_pipeline, td->p);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
disconnect (const GValue * v, gpointer user_data)
|
|
{
|
|
GstElement *e;
|
|
|
|
e = g_value_get_object (v);
|
|
FAIL_UNLESS (e);
|
|
g_signal_emit_by_name (G_OBJECT (e), "disconnect", NULL);
|
|
}
|
|
|
|
static gboolean
|
|
error_from_slave_source_bus_msg (GstBus * bus, GstMessage * message,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
error_from_slave_input_data *i = td->id;
|
|
error_from_slave_master_data *d = td->md;
|
|
|
|
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
|
|
if (!d->second_pass) {
|
|
if (!d->got_error_on_first_pass) {
|
|
GstIterator *it;
|
|
|
|
d->got_error_on_first_pass = TRUE;
|
|
|
|
if (i->crash) {
|
|
it = gst_bin_iterate_sinks (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, disconnect, NULL))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
}
|
|
|
|
gst_object_ref (td->p);
|
|
g_timeout_add (STEP_AT, bump_through_NULL, td);
|
|
}
|
|
|
|
/* don't pass the expected error */
|
|
return TRUE;
|
|
}
|
|
} else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_EOS) {
|
|
if (!d->second_pass) {
|
|
/* We'll get an expected EOS as the source reacts to the error */
|
|
return TRUE;
|
|
}
|
|
}
|
|
return master_bus_msg (bus, message, user_data);
|
|
}
|
|
|
|
static void
|
|
error_from_slave_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
error_from_slave_master_data *d = td->md;
|
|
|
|
if (d->second_pass)
|
|
d->got_state_changed_to_playing_on_second_pass = TRUE;
|
|
else
|
|
d->got_state_changed_to_playing_on_first_pass = TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
error_from_slave_position_getter (GstElement * element)
|
|
{
|
|
gint64 pos;
|
|
|
|
/* we do not care about the result */
|
|
gst_element_query_position (element, GST_FORMAT_TIME, &pos);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
error_from_slave_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
/* we're on the source, there's already the basic master_bus_msg watch,
|
|
and gst doesn't want more than one watch, so we remove the watch and
|
|
call it directly when done in the new watch */
|
|
gst_bus_remove_watch (GST_ELEMENT_BUS (source));
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (source), error_from_slave_source_bus_msg,
|
|
user_data);
|
|
g_timeout_add (STEP_AT, (GSourceFunc) error_from_slave_position_getter,
|
|
source);
|
|
|
|
td->state_changed_cb = error_from_slave_on_state_changed;
|
|
td->state_target = GST_STATE_PLAYING;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC);
|
|
}
|
|
|
|
static gboolean
|
|
error_from_slave_sink_bus_msg (GstBus * bus, GstMessage * message,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
error_from_slave_input_data *i = td->id;
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:
|
|
if (!strcmp (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)),
|
|
"error-element"))
|
|
g_object_set (GST_MESSAGE_SRC (message), "error-after", -1, NULL);
|
|
break;
|
|
case GST_MESSAGE_ASYNC_DONE:
|
|
if (GST_IS_PIPELINE (GST_MESSAGE_SRC (message))) {
|
|
/* We have two identical processes, and only one must crash. They can
|
|
be distinguished by recovery_pid, however. */
|
|
if (i->crash && recovery_pid)
|
|
g_timeout_add (CRASH_AT, (GSourceFunc) crash, NULL);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
setup_sink_error_from_slave (GstElement * sink, gpointer user_data)
|
|
{
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (sink), error_from_slave_sink_bus_msg,
|
|
user_data);
|
|
}
|
|
|
|
static void
|
|
check_success_source_error_from_slave (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
error_from_slave_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->second_pass);
|
|
FAIL_UNLESS (d->got_state_changed_to_playing_on_first_pass);
|
|
FAIL_UNLESS (d->got_state_changed_to_playing_on_second_pass);
|
|
FAIL_UNLESS (d->got_error_on_first_pass);
|
|
FAIL_IF (d->got_error_on_second_pass);
|
|
}
|
|
|
|
GST_START_TEST (test_empty_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_TEST_SOURCE | TEST_FEATURE_ERROR_SINK,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE | TEST_FEATURE_ERROR_SINK,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_ERROR_SINK,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_mpegts_2_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_MPEGTS_SOURCE | TEST_FEATURE_ERROR_SINK |
|
|
TEST_FEATURE_SPLIT_SINKS,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_a_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_A_SOURCE | TEST_FEATURE_ERROR_SINK,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_ERROR_SINK,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_live_av_2_error_from_slave)
|
|
{
|
|
error_from_slave_input_data id = { FALSE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_LIVE_AV_SOURCE | TEST_FEATURE_ERROR_SINK |
|
|
TEST_FEATURE_SPLIT_SINKS,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_wavparse_slave_process_crash)
|
|
{
|
|
error_from_slave_input_data id = { TRUE };
|
|
error_from_slave_master_data md = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE | TEST_FEATURE_RECOVERY_SLAVE_PROCESS,
|
|
error_from_slave_source, setup_sink_error_from_slave,
|
|
check_success_source_error_from_slave, NULL, &id, &md, NULL);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
/**** master process crash test ****/
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_state_changed_to_playing;
|
|
} master_process_crash_master_data;
|
|
|
|
typedef struct
|
|
{
|
|
gboolean got_error;
|
|
gboolean got_eos;
|
|
} master_process_crash_slave_data;
|
|
|
|
static void
|
|
master_process_crash_on_state_changed (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
master_process_crash_master_data *d = td->md;
|
|
|
|
if (!d->got_state_changed_to_playing) {
|
|
d->got_state_changed_to_playing = TRUE;
|
|
|
|
/* We have two identical processes, and only one must crash. They can
|
|
be distinguished by recovery_pid, however. */
|
|
if (!recovery_pid)
|
|
g_timeout_add (CRASH_AT, (GSourceFunc) crash, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
master_process_crash_source (GstElement * source, gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
GstStateChangeReturn ret;
|
|
|
|
td->state_target = GST_STATE_PLAYING;
|
|
td->state_changed_cb = master_process_crash_on_state_changed;
|
|
ret = gst_element_set_state (source, GST_STATE_PLAYING);
|
|
FAIL_UNLESS (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
master_process_crash_probe (GstPad * pad, GstPadProbeInfo * info,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
master_process_crash_slave_data *d = td->sd;
|
|
|
|
if (GST_IS_EVENT (info->data)) {
|
|
if (GST_EVENT_TYPE (info->data) == GST_EVENT_EOS) {
|
|
d->got_eos = TRUE;
|
|
}
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
hook_master_process_crash_probe (const GValue * v, gpointer user_data)
|
|
{
|
|
hook_probe (v, master_process_crash_probe, user_data);
|
|
}
|
|
|
|
static gboolean
|
|
go_to_NULL_and_reconnect (gpointer user_data)
|
|
{
|
|
GstElement *pipeline = user_data;
|
|
GstStateChangeReturn ret;
|
|
GstElement *src;
|
|
|
|
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
|
|
FAIL_IF (ret == GST_STATE_CHANGE_FAILURE);
|
|
|
|
/* reconnect to to master process */
|
|
src = gst_bin_get_by_name (GST_BIN (pipeline), "ipcpipelinesrc0");
|
|
FAIL_UNLESS (src);
|
|
g_object_set (src, "fdin", pipesfa[0], "fdout", pipesba[1], NULL);
|
|
gst_object_unref (src);
|
|
|
|
gst_object_unref (pipeline);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
master_process_crash_bus_msg (GstBus * bus, GstMessage * message,
|
|
gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
master_process_crash_slave_data *d = td->sd;
|
|
GstIterator *it;
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:
|
|
if (!d->got_error) {
|
|
it = gst_bin_iterate_sources (GST_BIN (td->p));
|
|
while (gst_iterator_foreach (it, disconnect, NULL))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
g_timeout_add (10, (GSourceFunc) go_to_NULL_and_reconnect,
|
|
gst_object_ref (td->p));
|
|
d->got_error = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
setup_sink_master_process_crash (GstElement * sink, gpointer user_data)
|
|
{
|
|
GstIterator *it;
|
|
|
|
it = gst_bin_iterate_sinks (GST_BIN (sink));
|
|
while (gst_iterator_foreach (it, hook_master_process_crash_probe, user_data))
|
|
gst_iterator_resync (it);
|
|
gst_iterator_free (it);
|
|
|
|
gst_bus_add_watch (GST_ELEMENT_BUS (sink), master_process_crash_bus_msg,
|
|
user_data);
|
|
}
|
|
|
|
static void
|
|
check_success_source_master_process_crash (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
master_process_crash_master_data *d = td->md;
|
|
|
|
FAIL_UNLESS (d->got_state_changed_to_playing);
|
|
}
|
|
|
|
static void
|
|
check_success_sink_master_process_crash (gpointer user_data)
|
|
{
|
|
test_data *td = user_data;
|
|
master_process_crash_slave_data *d = td->sd;
|
|
|
|
FAIL_UNLESS (d->got_error);
|
|
FAIL_UNLESS (d->got_eos);
|
|
}
|
|
|
|
GST_START_TEST (test_wavparse_master_process_crash)
|
|
{
|
|
master_process_crash_master_data md = { 0 };
|
|
master_process_crash_slave_data sd = { 0 };
|
|
|
|
TEST_BASE (TEST_FEATURE_WAV_SOURCE | TEST_FEATURE_RECOVERY_MASTER_PROCESS,
|
|
master_process_crash_source, setup_sink_master_process_crash,
|
|
check_success_source_master_process_crash,
|
|
check_success_sink_master_process_crash, NULL, &md, &sd);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
static Suite *
|
|
ipcpipeline_suite (void)
|
|
{
|
|
Suite *s = suite_create ("ipcpipeline");
|
|
TCase *tc_chain = tcase_create ("general");
|
|
|
|
setup_lock ();
|
|
|
|
suite_add_tcase (s, tc_chain);
|
|
tcase_set_timeout (tc_chain, 180);
|
|
|
|
/* play_pause tests put the pipeline in PLAYING state, then in
|
|
PAUSED state, then in PLAYING state again. The sink expects
|
|
async-done messages or state change successes. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_play_pause);
|
|
tcase_add_test (tc_chain, test_wavparse_play_pause);
|
|
tcase_add_test (tc_chain, test_mpegts_play_pause);
|
|
tcase_add_test (tc_chain, test_mpegts_2_play_pause);
|
|
tcase_add_test (tc_chain, test_live_a_play_pause);
|
|
tcase_add_test (tc_chain, test_live_av_play_pause);
|
|
tcase_add_test (tc_chain, test_live_av_2_play_pause);
|
|
}
|
|
|
|
/* flushing_seek tests perform a flushing seek in PLAYING
|
|
state. The sinks check a buffer with the target timestamp
|
|
is received after the seek. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_flushing_seek);
|
|
tcase_add_test (tc_chain, test_wavparse_flushing_seek);
|
|
tcase_add_test (tc_chain, test_mpegts_flushing_seek);
|
|
tcase_add_test (tc_chain, test_mpegts_2_flushing_seek);
|
|
tcase_add_test (tc_chain, test_live_a_flushing_seek);
|
|
tcase_add_test (tc_chain, test_live_av_flushing_seek);
|
|
tcase_add_test (tc_chain, test_live_av_2_flushing_seek);
|
|
}
|
|
|
|
/* flushing_seek_in_pause tests perform a flushing seek in
|
|
PAUSED state. These are disabled for live pipelines since
|
|
those will not generate data in PAUSED, so we won't get
|
|
a buffer. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_flushing_seek_in_pause);
|
|
tcase_add_test (tc_chain, test_wavparse_flushing_seek_in_pause);
|
|
tcase_add_test (tc_chain, test_mpegts_flushing_seek_in_pause);
|
|
tcase_add_test (tc_chain, test_mpegts_2_flushing_seek_in_pause);
|
|
/* live scenarios skipped: live sources do not generate buffers
|
|
* when paused */
|
|
}
|
|
|
|
/* segment_seek tests perform a segment seek in PLAYING
|
|
state. The sinks check a buffer with the target timestamp
|
|
is received after the seek, and that a SEGMENT_DONE is
|
|
received at the end of the segment. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_segment_seek);
|
|
tcase_add_test (tc_chain, test_wavparse_segment_seek);
|
|
/* mpegts skipped: tsdemux does not support segment seeks */
|
|
tcase_add_test (tc_chain, test_live_a_segment_seek);
|
|
tcase_add_test (tc_chain, test_live_av_segment_seek);
|
|
tcase_add_test (tc_chain, test_live_av_2_segment_seek);
|
|
}
|
|
|
|
/* seek_stress tests perform stress testing on seeks, then waits
|
|
in PLAYING for EOS or segment-done. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_seek_stress);
|
|
tcase_add_test (tc_chain, test_wavparse_seek_stress);
|
|
tcase_add_test (tc_chain, test_mpegts_seek_stress);
|
|
tcase_add_test (tc_chain, test_mpegts_2_seek_stress);
|
|
tcase_add_test (tc_chain, test_live_a_seek_stress);
|
|
tcase_add_test (tc_chain, test_live_av_seek_stress);
|
|
tcase_add_test (tc_chain, test_live_av_2_seek_stress);
|
|
}
|
|
|
|
/* upstream_query tests send position and duration queries, and
|
|
checks the results are as expected. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_upstream_query);
|
|
tcase_add_test (tc_chain, test_wavparse_upstream_query);
|
|
tcase_add_test (tc_chain, test_mpegts_upstream_query);
|
|
tcase_add_test (tc_chain, test_mpegts_2_upstream_query);
|
|
tcase_add_test (tc_chain, test_live_a_upstream_query);
|
|
tcase_add_test (tc_chain, test_live_av_upstream_query);
|
|
tcase_add_test (tc_chain, test_live_av_2_upstream_query);
|
|
}
|
|
|
|
/* message tests send a sink message downstream, which causes
|
|
the sinks to reply with the embedded event, which is checked.
|
|
This is not possible when elements go into pull mode. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_message);
|
|
tcase_add_test (tc_chain, test_wavparse_message);
|
|
/* mpegts skipped because it goes into pull mode:
|
|
https://bugzilla.gnome.org/show_bug.cgi?id=751637 */
|
|
tcase_add_test (tc_chain, test_live_a_message);
|
|
tcase_add_test (tc_chain, test_live_av_message);
|
|
tcase_add_test (tc_chain, test_live_av_2_message);
|
|
}
|
|
|
|
/* end_of_stream tests check the EOS event and message are
|
|
properly received when the stream reaches its end. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_end_of_stream);
|
|
tcase_add_test (tc_chain, test_wavparse_end_of_stream);
|
|
tcase_add_test (tc_chain, test_mpegts_end_of_stream);
|
|
tcase_add_test (tc_chain, test_mpegts_2_end_of_stream);
|
|
tcase_add_test (tc_chain, test_live_a_end_of_stream);
|
|
tcase_add_test (tc_chain, test_live_av_end_of_stream);
|
|
tcase_add_test (tc_chain, test_live_av_2_end_of_stream);
|
|
}
|
|
|
|
/* reverse_playback tests issue a seek with negative rate,
|
|
and check buffers timestamp are in decreasing order.
|
|
This does not work with sources which do not support
|
|
negative playback rate (live ones, and some demuxers). */
|
|
if (1) {
|
|
/* wavparse and tsdemux does not support backward playback */
|
|
tcase_add_test (tc_chain, test_a_reverse_playback);
|
|
tcase_add_test (tc_chain, test_av_reverse_playback);
|
|
tcase_add_test (tc_chain, test_av_2_reverse_playback);
|
|
}
|
|
|
|
/* tags tests check tags are carried to the slave. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_tags);
|
|
tcase_add_test (tc_chain, test_wavparse_tags);
|
|
tcase_add_test (tc_chain, test_mpegts_tags);
|
|
tcase_add_test (tc_chain, test_mpegts_2_tags);
|
|
tcase_add_test (tc_chain, test_live_a_tags);
|
|
tcase_add_test (tc_chain, test_live_av_tags);
|
|
tcase_add_test (tc_chain, test_live_av_2_tags);
|
|
}
|
|
|
|
/* reconfigure tests that pipeline reconfiguration via
|
|
the reconfigure event works */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_non_live_a_reconfigure);
|
|
tcase_add_test (tc_chain, test_non_live_av_reconfigure);
|
|
tcase_add_test (tc_chain, test_live_a_reconfigure);
|
|
tcase_add_test (tc_chain, test_live_av_reconfigure);
|
|
}
|
|
|
|
/* state_change tests issue a number of state changes in
|
|
(hopefully) all interesting configurations, and checks
|
|
the state changes occured on the slave pipeline. The links
|
|
are disconnected and reconnected to check it all still
|
|
works after this. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_state_changes);
|
|
tcase_add_test (tc_chain, test_wavparse_state_changes);
|
|
tcase_add_test (tc_chain, test_mpegts_state_changes);
|
|
tcase_add_test (tc_chain, test_mpegts_2_state_changes);
|
|
/* live scenarios skipped: live sources will cause no buffer
|
|
* to flow in PAUSED, so the pipeline will only finish READY->PAUSED
|
|
* once switching to PLAYING */
|
|
}
|
|
|
|
/* state_changes_stress tests change state randomly and rapidly. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_wavparse_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_mpegts_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_mpegts_2_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_live_a_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_live_av_state_changes_stress);
|
|
tcase_add_test (tc_chain, test_live_av_2_state_changes_stress);
|
|
}
|
|
|
|
/* serialized_query tests checks that a serialized query is
|
|
handled by the slave pipeline. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_serialized_query);
|
|
tcase_add_test (tc_chain, test_wavparse_serialized_query);
|
|
tcase_add_test (tc_chain, test_mpegts_serialized_query);
|
|
tcase_add_test (tc_chain, test_mpegts_2_serialized_query);
|
|
tcase_add_test (tc_chain, test_live_a_serialized_query);
|
|
tcase_add_test (tc_chain, test_live_av_serialized_query);
|
|
tcase_add_test (tc_chain, test_live_av_2_serialized_query);
|
|
}
|
|
|
|
/* non_serialized_event tests checks that a non serialized event
|
|
is handled by the slave pipeline. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_wavparse_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_mpegts_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_mpegts_2_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_live_a_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_live_av_non_serialized_event);
|
|
tcase_add_test (tc_chain, test_live_av_2_non_serialized_event);
|
|
}
|
|
|
|
/* meta tests checks that GstMeta on buffers are correctly
|
|
received by the slave pipeline. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_meta);
|
|
tcase_add_test (tc_chain, test_wavparse_meta);
|
|
tcase_add_test (tc_chain, test_mpegts_meta);
|
|
tcase_add_test (tc_chain, test_mpegts_2_meta);
|
|
tcase_add_test (tc_chain, test_live_a_meta);
|
|
tcase_add_test (tc_chain, test_live_av_meta);
|
|
tcase_add_test (tc_chain, test_live_av_2_meta);
|
|
}
|
|
|
|
/* source_change tests checks that the pipelines can handle a
|
|
change of source/caps. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_non_live_source_change);
|
|
tcase_add_test (tc_chain, test_live_av_source_change);
|
|
tcase_add_test (tc_chain, test_live_av_2_source_change);
|
|
}
|
|
|
|
/* navigation tests checks that navigation events from the slave
|
|
are received by the master. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_non_live_av_navigation);
|
|
tcase_add_test (tc_chain, test_non_live_av_2_navigation);
|
|
tcase_add_test (tc_chain, test_live_av_navigation);
|
|
tcase_add_test (tc_chain, test_live_av_2_navigation);
|
|
}
|
|
|
|
/* dynamic_pipeline_change_stress tests stress tests dynamic
|
|
pipeline changes. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_non_live_av_dynamic_pipeline_change_stress);
|
|
tcase_add_test (tc_chain,
|
|
test_non_live_av_2_dynamic_pipeline_change_stress);
|
|
tcase_add_test (tc_chain, test_live_av_dynamic_pipeline_change_stress);
|
|
tcase_add_test (tc_chain, test_live_av_2_dynamic_pipeline_change_stress);
|
|
}
|
|
|
|
/* error_from_slave tests checks an error message issued
|
|
by the slave pipeline is received by the master pipeline. */
|
|
if (1) {
|
|
tcase_add_test (tc_chain, test_empty_error_from_slave);
|
|
tcase_add_test (tc_chain, test_wavparse_error_from_slave);
|
|
tcase_add_test (tc_chain, test_mpegts_error_from_slave);
|
|
tcase_add_test (tc_chain, test_mpegts_2_error_from_slave);
|
|
tcase_add_test (tc_chain, test_live_a_error_from_slave);
|
|
tcase_add_test (tc_chain, test_live_av_error_from_slave);
|
|
tcase_add_test (tc_chain, test_live_av_2_error_from_slave);
|
|
}
|
|
|
|
/* slave_process_crash tests test that a crash of the slave
|
|
process can be recovered from by the master, which can
|
|
replace the slave process and continue. */
|
|
tcase_add_test (tc_chain, test_wavparse_slave_process_crash);
|
|
|
|
/* master_process_crash tests test that a crash of the master
|
|
process can be recovered from by the slave. I don't recall
|
|
how the recovery from that works, but it does! A watchdog
|
|
process replaces the master process, and the slave will
|
|
go to NULL and reconnect after it gets a timeout talking
|
|
with the master pipeline. */
|
|
tcase_add_test (tc_chain, test_wavparse_master_process_crash);
|
|
|
|
return s;
|
|
}
|
|
|
|
GST_CHECK_MAIN (ipcpipeline);
|