mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 07:47:17 +00:00
tests: Add reverse playback verification test
Plays a requested URI forward to EOS, then backward and checks that the same timestamp range(s) are covered.
This commit is contained in:
parent
cefc518494
commit
b508867795
3 changed files with 377 additions and 1 deletions
1
tests/icles/.gitignore
vendored
1
tests/icles/.gitignore
vendored
|
@ -7,6 +7,7 @@ stress-playbin
|
|||
stress-videooverlay
|
||||
test-effect-switch
|
||||
test-overlay-blending
|
||||
test-reverseplay
|
||||
test-textoverlay
|
||||
test-scale
|
||||
test-box
|
||||
|
|
|
@ -92,6 +92,10 @@ test_box_SOURCES = test-box.c
|
|||
test_box_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||
test_box_LDADD = $(GST_LIBS) $(LIBM)
|
||||
|
||||
test_reverseplay_SOURCES = test-reverseplay.c
|
||||
test_reverseplay_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||
test_reverseplay_LDADD = $(GST_LIBS) $(LIBM)
|
||||
|
||||
noinst_PROGRAMS = $(X_TESTS) $(PANGO_TESTS) \
|
||||
audio-trickplay playbin-text position-formats stress-playbin \
|
||||
test-scale test-box test-effect-switch test-overlay-blending
|
||||
test-scale test-box test-effect-switch test-overlay-blending test-reverseplay
|
||||
|
|
371
tests/icles/test-reverseplay.c
Normal file
371
tests/icles/test-reverseplay.c
Normal file
|
@ -0,0 +1,371 @@
|
|||
/* Test example which plays a given file forward, then
|
||||
* at EOS, plays the entire file in reverse
|
||||
* and checks that reverse playback generates the same
|
||||
* output as forward playback but reversed
|
||||
*/
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#include <gst/gst.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _PlayState PlayState;
|
||||
|
||||
typedef struct _StreamTSRange
|
||||
{
|
||||
GstClockTime start;
|
||||
GstClockTime end;
|
||||
} StreamTSRange;
|
||||
|
||||
typedef struct _StreamInfo
|
||||
{
|
||||
PlayState *state;
|
||||
GstPad *pad;
|
||||
|
||||
GstSegment seg;
|
||||
|
||||
GArray *fwd_times;
|
||||
GArray *bkwd_times;
|
||||
} StreamInfo;
|
||||
|
||||
struct _PlayState
|
||||
{
|
||||
GstElement *pipe;
|
||||
GMainLoop *loop;
|
||||
gboolean fwd_play;
|
||||
gint n_sinks;
|
||||
|
||||
GMutex output_lock;
|
||||
};
|
||||
|
||||
static void
|
||||
warning_cb (GstBus * bus, GstMessage * msg, gpointer foo)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gchar *dbg = NULL;
|
||||
|
||||
gst_message_parse_warning (msg, &err, &dbg);
|
||||
|
||||
g_printerr ("WARNING: %s (%s)\n", err->message, (dbg) ? dbg : "no details");
|
||||
|
||||
g_error_free (err);
|
||||
g_free (dbg);
|
||||
}
|
||||
|
||||
static void
|
||||
error_cb (GstBus * bus, GstMessage * msg, PlayState * state)
|
||||
{
|
||||
GError *err = NULL;
|
||||
gchar *dbg = NULL;
|
||||
|
||||
gst_message_parse_error (msg, &err, &dbg);
|
||||
|
||||
g_printerr ("ERROR: %s (%s)\n", err->message, (dbg) ? dbg : "no details");
|
||||
|
||||
g_main_loop_quit (state->loop);
|
||||
|
||||
g_error_free (err);
|
||||
g_free (dbg);
|
||||
}
|
||||
|
||||
static void
|
||||
eos_cb (GstBus * bus, GstMessage * msg, PlayState * state)
|
||||
{
|
||||
if (state->fwd_play) {
|
||||
g_print ("EOS - finished forward play. Starting reverse\n");
|
||||
state->fwd_play = FALSE;
|
||||
gst_element_seek (state->pipe, -1.0, GST_FORMAT_TIME,
|
||||
GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH,
|
||||
GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);
|
||||
|
||||
return;
|
||||
}
|
||||
g_print ("EOS - exiting\n");
|
||||
g_main_loop_quit (state->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
state_cb (GstBus * bus, GstMessage * msg, PlayState * state)
|
||||
{
|
||||
if (msg->src == GST_OBJECT (state->pipe)) {
|
||||
GstState old_state, new_state, pending_state;
|
||||
|
||||
gst_message_parse_state_changed (msg, &old_state, &new_state,
|
||||
&pending_state);
|
||||
if (new_state == GST_STATE_PLAYING)
|
||||
g_print ("Decoding ...\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_destroy_stream_info (StreamInfo * si)
|
||||
{
|
||||
g_array_free (si->fwd_times, TRUE);
|
||||
g_array_free (si->bkwd_times, TRUE);
|
||||
g_object_unref (si->pad);
|
||||
g_free (si);
|
||||
}
|
||||
|
||||
static void
|
||||
extend_times (StreamInfo * si, GstClockTime start, GstClockTime end)
|
||||
{
|
||||
PlayState *state = si->state;
|
||||
StreamTSRange *ts = NULL;
|
||||
StreamTSRange tsn;
|
||||
GArray *a;
|
||||
guint i, n;
|
||||
|
||||
/* Set up new entry, in case we need it */
|
||||
tsn.start = start;
|
||||
tsn.end = end;
|
||||
|
||||
if (state->fwd_play) {
|
||||
a = si->fwd_times;
|
||||
n = a->len;
|
||||
/* if playing forward, see if this new time extends the last entry */
|
||||
i = n - 1;
|
||||
} else {
|
||||
a = si->bkwd_times;
|
||||
n = a->len;
|
||||
/* if playing backward, see if this new time extends the earliest entry */
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
ts = &g_array_index (a, StreamTSRange, i);
|
||||
if (start > ts->start) {
|
||||
/* This entry is after the most recent entry */
|
||||
/* Tolerance of 1 millisecond allowed for imprecision */
|
||||
if (ts->end + GST_MSECOND >= start) {
|
||||
GST_LOG ("%p extending entry %d to %" GST_TIME_FORMAT,
|
||||
si, i, GST_TIME_ARGS (end));
|
||||
ts->end = end;
|
||||
return;
|
||||
}
|
||||
|
||||
/* new start > ts->start, so this new entry goes after the first one */
|
||||
GST_LOG ("%p inserting new entry %d %" GST_TIME_FORMAT
|
||||
" to %" GST_TIME_FORMAT, si, i + 1, GST_TIME_ARGS (start),
|
||||
GST_TIME_ARGS (end));
|
||||
g_array_insert_val (a, i + 1, tsn);
|
||||
return;
|
||||
} else if (end + GST_MSECOND > ts->start) {
|
||||
/* This entry precedes the current one, but overlaps it */
|
||||
GST_LOG ("%p pre-extending entry %d to %" GST_TIME_FORMAT,
|
||||
si, i, GST_TIME_ARGS (start));
|
||||
ts->start = start;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
/* otherwise insert a new entry before/at the start */
|
||||
GST_LOG ("%p New entry %d - %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
|
||||
si, i, GST_TIME_ARGS (start), GST_TIME_ARGS (end));
|
||||
g_array_insert_val (a, i, tsn);
|
||||
}
|
||||
|
||||
static void
|
||||
dump_times (StreamInfo * si)
|
||||
{
|
||||
PlayState *state = si->state;
|
||||
guint i;
|
||||
GArray *a;
|
||||
|
||||
g_mutex_lock (&state->output_lock);
|
||||
if (state->fwd_play)
|
||||
a = si->fwd_times;
|
||||
else
|
||||
a = si->bkwd_times;
|
||||
|
||||
g_print ("Pad %s times:\n", GST_PAD_NAME (si->pad));
|
||||
for (i = 0; i < a->len; i++) {
|
||||
StreamTSRange *ts = &g_array_index (a, StreamTSRange, i);
|
||||
|
||||
g_print (" %u %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
|
||||
i, GST_TIME_ARGS (ts->start), GST_TIME_ARGS (ts->end));
|
||||
}
|
||||
g_mutex_unlock (&state->output_lock);
|
||||
}
|
||||
|
||||
static GstPadProbeReturn
|
||||
handle_output (GstPad * pad, GstPadProbeInfo * info, StreamInfo * si)
|
||||
{
|
||||
GstClockTime start, end;
|
||||
GstBuffer *buf;
|
||||
|
||||
GST_LOG_OBJECT (pad, "Fired probe type 0x%x\n", info->type);
|
||||
|
||||
if (info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST) {
|
||||
g_warning ("Buffer list handling not implemented");
|
||||
return GST_PAD_PROBE_DROP;
|
||||
}
|
||||
|
||||
if (info->type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
|
||||
GstEvent *event = gst_pad_probe_info_get_event (info);
|
||||
switch (GST_EVENT_TYPE (event)) {
|
||||
case GST_EVENT_SEGMENT:
|
||||
gst_event_copy_segment (event, &si->seg);
|
||||
break;
|
||||
case GST_EVENT_EOS:
|
||||
dump_times (si);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GST_PAD_PROBE_PASS;
|
||||
}
|
||||
|
||||
buf = gst_pad_probe_info_get_buffer (info);
|
||||
if (!GST_BUFFER_PTS_IS_VALID (buf))
|
||||
goto done;
|
||||
end = start = GST_BUFFER_PTS (buf);
|
||||
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
||||
end += GST_BUFFER_DURATION (buf);
|
||||
|
||||
gst_segment_clip (&si->seg, GST_FORMAT_TIME, start, end, &start, &end);
|
||||
start = gst_segment_to_stream_time (&si->seg, GST_FORMAT_TIME, start);
|
||||
end = gst_segment_to_stream_time (&si->seg, GST_FORMAT_TIME, end);
|
||||
|
||||
/* Now extend measured time range to include new times */
|
||||
extend_times (si, start, end);
|
||||
|
||||
done:
|
||||
return GST_PAD_PROBE_PASS;
|
||||
}
|
||||
|
||||
static void
|
||||
pad_added_cb (GstElement * decodebin, GstPad * pad, PlayState * state)
|
||||
{
|
||||
GstPadLinkReturn ret;
|
||||
GstElement *fakesink;
|
||||
GstPad *fakesink_pad;
|
||||
StreamInfo *si;
|
||||
|
||||
fakesink = gst_element_factory_make ("fakesink", NULL);
|
||||
#if 0
|
||||
if (state->n_sinks == 1)
|
||||
g_object_set (fakesink, "silent", FALSE, NULL);
|
||||
#endif
|
||||
|
||||
si = g_new0 (StreamInfo, 1);
|
||||
si->pad = g_object_ref (pad);
|
||||
si->state = state;
|
||||
si->fwd_times = g_array_new (FALSE, TRUE, sizeof (StreamTSRange));
|
||||
si->bkwd_times = g_array_new (FALSE, TRUE, sizeof (StreamTSRange));
|
||||
|
||||
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_DATA_DOWNSTREAM,
|
||||
(GstPadProbeCallback) handle_output, si, (GDestroyNotify)
|
||||
_destroy_stream_info);
|
||||
|
||||
state->n_sinks++;
|
||||
gst_bin_add (GST_BIN (state->pipe), fakesink);
|
||||
|
||||
gst_element_sync_state_with_parent (fakesink);
|
||||
|
||||
fakesink_pad = gst_element_get_static_pad (fakesink, "sink");
|
||||
|
||||
ret = gst_pad_link (pad, fakesink_pad);
|
||||
if (!GST_PAD_LINK_SUCCESSFUL (ret)) {
|
||||
g_printerr ("Failed to link %s:%s to %s:%s (ret = %d)\n",
|
||||
GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (fakesink_pad), ret);
|
||||
} else {
|
||||
g_printerr ("Linked %s:%s to %s:%s\n", GST_DEBUG_PAD_NAME (pad),
|
||||
GST_DEBUG_PAD_NAME (fakesink_pad));
|
||||
}
|
||||
|
||||
gst_object_unref (fakesink_pad);
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar * argv[])
|
||||
{
|
||||
PlayState state;
|
||||
GstElement *decoder;
|
||||
GstElement *source;
|
||||
GstStateChangeReturn res;
|
||||
GstBus *bus;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
|
||||
if (argc != 2) {
|
||||
g_printerr ("Decode file from start to end.\n");
|
||||
g_printerr ("Usage: %s URI\n\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
/* Start with zeroed-state */
|
||||
memset (&state, 0, sizeof (PlayState));
|
||||
|
||||
state.loop = g_main_loop_new (NULL, TRUE);
|
||||
state.pipe = gst_pipeline_new ("pipeline");
|
||||
state.fwd_play = TRUE;
|
||||
g_mutex_init (&state.output_lock);
|
||||
|
||||
bus = gst_pipeline_get_bus (GST_PIPELINE (state.pipe));
|
||||
gst_bus_add_signal_watch (bus);
|
||||
|
||||
g_signal_connect (bus, "message::eos", G_CALLBACK (eos_cb), &state);
|
||||
g_signal_connect (bus, "message::error", G_CALLBACK (error_cb), &state);
|
||||
g_signal_connect (bus, "message::warning", G_CALLBACK (warning_cb), NULL);
|
||||
g_signal_connect (bus, "message::state-changed", G_CALLBACK (state_cb),
|
||||
&state);
|
||||
|
||||
#if 0
|
||||
g_signal_connect (state.pipe, "deep-notify",
|
||||
G_CALLBACK (gst_object_default_deep_notify), NULL);
|
||||
#endif
|
||||
|
||||
source = gst_element_factory_make ("giosrc", "source");
|
||||
g_assert (source);
|
||||
|
||||
if (argv[1] && strstr (argv[1], "://") != NULL) {
|
||||
g_object_set (G_OBJECT (source), "location", argv[1], NULL);
|
||||
} else if (argv[1]) {
|
||||
gchar *uri = g_strdup_printf ("file://%s", argv[1]);
|
||||
|
||||
g_object_set (G_OBJECT (source), "location", uri, NULL);
|
||||
g_free (uri);
|
||||
}
|
||||
|
||||
decoder = gst_element_factory_make ("decodebin", "decoder");
|
||||
g_assert (decoder);
|
||||
|
||||
gst_bin_add (GST_BIN (state.pipe), source);
|
||||
gst_bin_add (GST_BIN (state.pipe), decoder);
|
||||
|
||||
gst_element_link_pads (source, "src", decoder, "sink");
|
||||
|
||||
g_signal_connect (decoder, "pad-added", G_CALLBACK (pad_added_cb), &state);
|
||||
|
||||
res = gst_element_set_state (state.pipe, GST_STATE_PLAYING);
|
||||
if (res == GST_STATE_CHANGE_FAILURE) {
|
||||
g_print ("could not play\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_main_loop_run (state.loop);
|
||||
|
||||
/* tidy up */
|
||||
gst_element_set_state (state.pipe, GST_STATE_NULL);
|
||||
gst_object_unref (state.pipe);
|
||||
gst_object_unref (bus);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue