From 65ff17660b65e29e4888142009d3045825c7fa15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 27 Nov 2014 11:16:35 +0000 Subject: [PATCH] tests: add interactive test for accurate seeking For some audio formats. https://bugzilla.gnome.org/show_bug.cgi?id=655276 --- tests/icles/.gitignore | 1 + tests/icles/Makefile.am | 14 +- tests/icles/test-accurate-seek.c | 275 +++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 tests/icles/test-accurate-seek.c diff --git a/tests/icles/.gitignore b/tests/icles/.gitignore index 0b5704b32a..2810ce2f04 100644 --- a/tests/icles/.gitignore +++ b/tests/icles/.gitignore @@ -1,5 +1,6 @@ equalizer-test gdkpixbufsink-test +test-accurate-seek test-oss4 ximagesrc-test v4l2src-test diff --git a/tests/icles/Makefile.am b/tests/icles/Makefile.am index 9ff34a9c52..a4c9f35914 100644 --- a/tests/icles/Makefile.am +++ b/tests/icles/Makefile.am @@ -39,6 +39,12 @@ equalizer_test_SOURCES = equalizer-test.c equalizer_test_CFLAGS = $(GST_CFLAGS) equalizer_test_LDADD = $(GST_LIBS) +test_accurate_seek_SOURCES = test-accurate-seek.c +test_accurate_seek_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) +test_accurate_seek_LDADD = $(GST_PLUGINS_BASE_LIBS) -lgstapp-$(GST_API_VERSION) \ + $(GST_BASE_LIBS) $(GST_LIBS) + videocrop_test_SOURCES = videocrop-test.c videocrop_test_CFLAGS = $(GST_CFLAGS) videocrop_test_LDADD = $(GST_LIBS) @@ -51,5 +57,9 @@ videocrop2_test_SOURCES = videocrop2-test.c videocrop2_test_CFLAGS = $(GST_CFLAGS) videocrop2_test_LDADD = $(GST_LIBS) -noinst_PROGRAMS = $(GTK_TESTS) $(OSS4_TESTS) $(V4L2_TESTS) $(X_TESTS) equalizer-test videocrop-test videobox-test videocrop2-test - +noinst_PROGRAMS = $(GTK_TESTS) $(OSS4_TESTS) $(V4L2_TESTS) $(X_TESTS) \ + equalizer-test \ + test-accurate-seek \ + videocrop-test \ + videobox-test \ + videocrop2-test diff --git a/tests/icles/test-accurate-seek.c b/tests/icles/test-accurate-seek.c new file mode 100644 index 0000000000..9e9da104ef --- /dev/null +++ b/tests/icles/test-accurate-seek.c @@ -0,0 +1,275 @@ +/* GStreamer interactive test for accurate seeking + * Copyright (C) 2014 Tim-Philipp Müller + * + * 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. + * + * Based on python script by Kibeom Kim + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define _GNU_SOURCE /* for memmem */ +#include + +#include +#include +#include +#include + +#define SAMPLE_FREQ 44100 + +static GstClockTime +sample_to_nanotime (guint sample) +{ + return (guint64) ((1.0 * sample * GST_SECOND / SAMPLE_FREQ) + 0.5); +} + +static guint +nanotime_to_sample (GstClockTime nanotime) +{ + return nanotime * SAMPLE_FREQ / GST_SECOND; +} + +static GstBuffer * +generate_test_data (guint N) +{ + gint16 *left, *right, *stereo; + guint largeN, i, j; + + /* 32767 = (2 ** 15) - 1 */ + /* 32768 = (2 ** 15) */ + largeN = ((N + 32767) / 32768) * 32768; + left = g_new0 (gint16, largeN); + right = g_new0 (gint16, largeN); + stereo = g_new0 (gint16, 2 * largeN); + + for (i = 0; i < (largeN / 32768); ++i) { + gint c = 0; + + for (j = i * 32768; j < ((i + 1) * 32768); ++j) { + left[j] = i; + + if (i % 2 == 0) { + right[j] = c; + } else { + right[j] = 32767 - c; + } + ++c; + } + } + + /* could just fill stereo directly from the start, but keeping original code for now */ + for (i = 0; i < largeN; ++i) { + stereo[(2 * i) + 0] = left[i]; + stereo[(2 * i) + 1] = right[i]; + } + g_free (left); + g_free (right); + + return gst_buffer_new_wrapped (stereo, 2 * largeN * sizeof (gint16)); +} + +static void +generate_test_sound (const gchar * fn, const gchar * launch_string, + guint num_samples) +{ + GstElement *pipeline, *src, *parse, *enc_bin, *sink; + GstFlowReturn flow; + GstMessage *msg; + GstBuffer *buf; + GstCaps *caps; + + pipeline = gst_pipeline_new (NULL); + + src = gst_element_factory_make ("appsrc", NULL); + + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, SAMPLE_FREQ, "channels", G_TYPE_INT, 2, + "layout", G_TYPE_STRING, "interleaved", + "channel-mask", GST_TYPE_BITMASK, (guint64) 3, NULL); + g_object_set (src, "caps", caps, "format", GST_FORMAT_TIME, NULL); + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + gst_caps_unref (caps); + + /* audioparse to put proper timestamps on buffers for us, without which + * vorbisenc in particular is unhappy (or oggmux, rather) */ + parse = gst_element_factory_make ("audioparse", NULL); + if (parse != NULL) { + g_object_set (parse, "use-sink-caps", TRUE, NULL); + } else { + parse = gst_element_factory_make ("identity", NULL); + g_warning ("audioparse element not available, vorbis/ogg might not work\n"); + } + + enc_bin = gst_parse_bin_from_description (launch_string, TRUE, NULL); + + sink = gst_element_factory_make ("filesink", NULL); + g_object_set (sink, "location", fn, NULL); + + gst_bin_add_many (GST_BIN (pipeline), src, parse, enc_bin, sink, NULL); + + gst_element_link_many (src, parse, enc_bin, sink, NULL); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + buf = generate_test_data (num_samples); + flow = gst_app_src_push_buffer (GST_APP_SRC (src), buf); + g_assert (flow == GST_FLOW_OK); + + gst_app_src_end_of_stream (GST_APP_SRC (src)); + + /*g_print ("generating test sound %s, waiting for EOS..\n", fn); */ + + msg = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipeline), + GST_CLOCK_TIME_NONE, GST_MESSAGE_EOS | GST_MESSAGE_ERROR); + + g_assert (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS); + gst_message_unref (msg); + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + /* g_print ("Done %s\n", fn); */ +} + +static void +test_seek_FORMAT_TIME_by_sample (const gchar * fn, GList * seek_positions) +{ + GstElement *pipeline, *src, *sink; + GstAdapter *adapter; + GstSample *sample; + GstCaps *caps; + gconstpointer answer; + guint answer_size; + + pipeline = gst_parse_launch ("filesrc name=src ! decodebin ! " + "audioconvert dithering=0 ! appsink name=sink", NULL); + + src = gst_bin_get_by_name (GST_BIN (pipeline), "src"); + g_object_set (src, "location", fn, NULL); + gst_object_unref (src); + + sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink"); + caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, SAMPLE_FREQ, "channels", G_TYPE_INT, 2, NULL); + g_object_set (sink, "caps", caps, "sync", FALSE, NULL); + gst_caps_unref (caps); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + /* wait for preroll, so we can seek */ + gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipeline), GST_CLOCK_TIME_NONE, + GST_MESSAGE_ASYNC_DONE); + + /* first, read entire file to end */ + adapter = gst_adapter_new (); + while ((sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)))) { + gst_adapter_push (adapter, gst_buffer_ref (gst_sample_get_buffer (sample))); + gst_sample_unref (sample); + } + answer_size = gst_adapter_available (adapter); + answer = gst_adapter_map (adapter, answer_size); + /* g_print ("%s: read %u bytes\n", fn, answer_size); */ + + g_print ("%10s\t%10s\t%10s\n", "requested", "sample per ts", "actual(data)"); + + while (seek_positions != NULL) { + gconstpointer found; + GstMapInfo map; + GstBuffer *buf; + gboolean ret; + guint actual_position, buffer_timestamp_position; + guint seek_sample; + + seek_sample = GPOINTER_TO_UINT (seek_positions->data); + + ret = gst_element_seek_simple (pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, + sample_to_nanotime (seek_sample)); + + g_assert (ret); + + sample = gst_app_sink_pull_sample (GST_APP_SINK (sink)); + + buf = gst_sample_get_buffer (sample); + gst_buffer_map (buf, &map, GST_MAP_READ); + found = memmem (answer, answer_size, map.data, map.size); + gst_buffer_unmap (buf, &map); + + g_assert (found != NULL); + actual_position = ((goffset) ((guint8 *) found - (guint8 *) answer)) / 4; + buffer_timestamp_position = nanotime_to_sample (GST_BUFFER_PTS (buf)); + g_print ("%10u\t%10u\t%10u\n", seek_sample, buffer_timestamp_position, + actual_position); + gst_sample_unref (sample); + + seek_positions = seek_positions->next; + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (sink); + gst_object_unref (pipeline); + g_object_unref (adapter); +} + +static GList * +create_test_samples (guint from, guint to, guint step) +{ + GQueue q = G_QUEUE_INIT; + guint i; + + for (i = from; i < to; i += step) + g_queue_push_tail (&q, GUINT_TO_POINTER (i)); + + return q.head; +} + +#define SECS 10 + +int +main (int argc, char **argv) +{ + GList *test_samples; + + gst_init (&argc, &argv); + + test_samples = create_test_samples (SAMPLE_FREQ, SAMPLE_FREQ * 2, 5000); + + g_print ("\nwav:\n"); + generate_test_sound ("test.wav", "wavenc", SAMPLE_FREQ * SECS); + test_seek_FORMAT_TIME_by_sample ("test.wav", test_samples); + + g_print ("\nflac:\n"); + generate_test_sound ("test.flac", "flacenc", SAMPLE_FREQ * SECS); + test_seek_FORMAT_TIME_by_sample ("test.flac", test_samples); + + g_print ("\nogg:\n"); + generate_test_sound ("test.ogg", + "audioconvert dithering=0 ! vorbisenc quality=1 ! oggmux", + SAMPLE_FREQ * SECS); + test_seek_FORMAT_TIME_by_sample ("test.ogg", test_samples); + + g_print ("\nmp3:\n"); + generate_test_sound ("test.mp3", "lamemp3enc bitrate=320", + SAMPLE_FREQ * SECS); + test_seek_FORMAT_TIME_by_sample ("test.mp3", test_samples); + + g_list_free (test_samples); + return 0; +}