diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/check/Makefile.am | 8 | ||||
-rw-r--r-- | tests/check/elements/.gitignore | 1 | ||||
-rw-r--r-- | tests/check/elements/rawaudioparse.c | 339 | ||||
-rw-r--r-- | tests/check/elements/rawvideoparse.c | 459 |
4 files changed, 807 insertions, 0 deletions
diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index b16c96890..04230573f 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -274,6 +274,8 @@ check_PROGRAMS = \ elements/netsim \ elements/pcapparse \ elements/pnm \ + elements/rawaudioparse \ + elements/rawvideoparse \ elements/rtponvifparse \ elements/rtponviftimestamp \ elements/id3mux \ @@ -340,6 +342,12 @@ elements_h264parse_LDADD = libparser.la $(LDADD) elements_pcapparse_LDADD = libparser.la $(LDADD) +elements_rawaudioparse_LDADD = $(GST_BASE_LIBS) -lgstbase-@GST_API_VERSION@ -lgstaudio-@GST_API_VERSION@ $(LDADD) +elements_rawaudioparse_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) + +elements_rawvideoparse_LDADD = $(GST_BASE_LIBS) -lgstbase-@GST_API_VERSION@ -lgstvideo-@GST_API_VERSION@ $(LDADD) +elements_rawvideoparse_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) + libs_mpegvideoparser_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \ -DGST_USE_UNSTABLE_API \ diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index 5910320a7..05552cce9 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -46,6 +46,7 @@ neonhttpsrc netsim ofa pcapparse +rawaudioparse rtponvif rganalysis rglimiter diff --git a/tests/check/elements/rawaudioparse.c b/tests/check/elements/rawaudioparse.c new file mode 100644 index 000000000..62ab975e8 --- /dev/null +++ b/tests/check/elements/rawaudioparse.c @@ -0,0 +1,339 @@ +/* GStreamer + * + * unit test for rawaudioparse + * + * Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org> + * + * 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. + */ + +/* FIXME: GValueArray is deprecated, but there is currently no viabla alternative + * See https://bugzilla.gnome.org/show_bug.cgi?id=667228 */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include <gst/check/gstcheck.h> +#include <gst/audio/audio.h> + +/* Checks are hardcoded to expect stereo 16-bit data. The sample rate + * however varies from the default of 40 kHz in some tests to see the + * differences in calculated buffer durations. */ +#define NUM_TEST_SAMPLES 512 +#define NUM_TEST_CHANNELS 2 +#define TEST_SAMPLE_RATE 40000 +#define TEST_SAMPLE_FORMAT GST_AUDIO_FORMAT_S16 + +/* For ease of programming we use globals to keep refs for our floating + * src and sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mysrcpad, *mysinkpad; + +typedef struct +{ + GstElement *rawaudioparse; + GstAdapter *test_data_adapter; +} +RawAudParseTestCtx; + +/* Sets up a rawaudioparse element and a GstAdapter that contains 512 test + * audio samples. The samples a monotonically increasing set from the values + * 0 to 511 for the left and 512 to 1023 for the right channel. The result + * is a GstAdapter that contains the interleaved 16-bit integer values: + * 0,512,1,513,2,514, ... 511,1023 . This set is used in the checks to see + * if rawaudioparse's output buffers contain valid data. */ +static void +setup_rawaudioparse (RawAudParseTestCtx * testctx, gboolean use_sink_caps, + gboolean set_properties, GstCaps * incaps, GstFormat format) +{ + GstElement *rawaudioparse; + GstAdapter *test_data_adapter; + GstBuffer *buffer; + guint i; + guint16 samples[NUM_TEST_SAMPLES * NUM_TEST_CHANNELS]; + + + /* Setup the rawaudioparse element and the pads */ + + static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL)) + ); + static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + rawaudioparse = gst_check_setup_element ("rawaudioparse"); + + g_object_set (G_OBJECT (rawaudioparse), "use-sink-caps", use_sink_caps, NULL); + if (set_properties) + g_object_set (G_OBJECT (rawaudioparse), "sample-rate", TEST_SAMPLE_RATE, + "num-channels", NUM_TEST_CHANNELS, "pcm-format", TEST_SAMPLE_FORMAT, + NULL); + + fail_unless (gst_element_set_state (rawaudioparse, + GST_STATE_PAUSED) == GST_STATE_CHANGE_SUCCESS, + "could not set to paused"); + + mysrcpad = gst_check_setup_src_pad (rawaudioparse, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (rawaudioparse, &sinktemplate); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + gst_check_setup_events (mysrcpad, rawaudioparse, incaps, format); + if (incaps) + gst_caps_unref (incaps); + + + /* Fill the adapter with the interleaved 0..511 and + * 512..1023 samples */ + for (i = 0; i < NUM_TEST_SAMPLES; ++i) { + guint c; + for (c = 0; c < NUM_TEST_CHANNELS; ++c) + samples[i * NUM_TEST_CHANNELS + c] = c * NUM_TEST_SAMPLES + i; + } + + test_data_adapter = gst_adapter_new (); + buffer = gst_buffer_new_allocate (NULL, sizeof (samples), NULL); + gst_buffer_fill (buffer, 0, samples, sizeof (samples)); + gst_adapter_push (test_data_adapter, buffer); + + + testctx->rawaudioparse = rawaudioparse; + testctx->test_data_adapter = test_data_adapter; +} + +static void +cleanup_rawaudioparse (RawAudParseTestCtx * testctx) +{ + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (testctx->rawaudioparse); + gst_check_teardown_sink_pad (testctx->rawaudioparse); + gst_check_teardown_element (testctx->rawaudioparse); + + g_object_unref (G_OBJECT (testctx->test_data_adapter)); +} + + +static void +push_data_and_check_output (RawAudParseTestCtx * testctx, gsize num_in_bytes, + gsize expected_num_out_bytes, gint64 expected_pts, gint64 expected_dur, + guint expected_num_buffers_in_list, guint bpf, guint16 channel0_start, + guint16 channel1_start) +{ + GstBuffer *inbuf, *outbuf; + guint num_buffers; + + /* Simulate upstream input by taking num_in_bytes bytes from the adapter */ + inbuf = gst_adapter_take_buffer (testctx->test_data_adapter, num_in_bytes); + fail_unless (inbuf != NULL); + + /* Push the input data and check that the output buffers list grew as + * expected */ + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless_equals_int (num_buffers, expected_num_buffers_in_list); + + /* Take the latest output buffer */ + outbuf = g_list_nth_data (buffers, num_buffers - 1); + fail_unless (outbuf != NULL); + + /* Verify size, PTS, duration of the output buffer */ + fail_unless_equals_uint64 (expected_num_out_bytes, + gst_buffer_get_size (outbuf)); + fail_unless_equals_uint64 (expected_pts, GST_BUFFER_PTS (outbuf)); + fail_unless_equals_uint64 (expected_dur, GST_BUFFER_DURATION (outbuf)); + + /* Go through all of the samples in the output buffer and check that they are + * valid. The samples are interleaved. The offsets specified by channel0_start + * and channel1_start are the expected values of the first sample for each + * channel in the buffer. So, if channel0_start is 512, then sample #0 in the + * buffer must have value 512, and if channel1_start is 700, then sample #1 + * in the buffer must have value 700 etc. */ + { + guint i, num_frames; + guint16 *s; + GstMapInfo map_info; + guint channel_starts[2] = { channel0_start, channel1_start }; + + gst_buffer_map (outbuf, &map_info, GST_MAP_READ); + num_frames = map_info.size / bpf; + s = (guint16 *) (map_info.data); + + for (i = 0; i < num_frames; ++i) { + guint c; + + for (c = 0; i < NUM_TEST_CHANNELS; ++i) { + guint16 expected = channel_starts[c] + i; + guint16 actual = s[i * NUM_TEST_CHANNELS + c]; + + fail_unless_equals_int (expected, actual); + } + } + + gst_buffer_unmap (outbuf, &map_info); + } +} + + +GST_START_TEST (test_push_unaligned_data_properties_config) +{ + RawAudParseTestCtx testctx; + + setup_rawaudioparse (&testctx, FALSE, TRUE, NULL, GST_FORMAT_BYTES); + + /* Send in data buffers that are not aligned to multiples of the + * frame size (= sample size * num_channels). This tests if rawaudioparse + * aligns output data properly. + * + * The second line sends in 99 bytes, and expects 100 bytes in the + * output buffer. This is because the first buffer contains 45 bytes, + * and rawaudioparse is expected to output 44 bytes (which is an integer + * multiple of the frame size). The leftover 1 byte then gets prepended + * to the input buffer with 99 bytes, resulting in 100 bytes, which is + * an integer multiple of the frame size. + */ + + push_data_and_check_output (&testctx, 45, 44, GST_USECOND * 0, + GST_USECOND * 275, 1, 4, 0, 512); + push_data_and_check_output (&testctx, 99, 100, GST_USECOND * 275, + GST_USECOND * 625, 2, 4, 11, 523); + push_data_and_check_output (&testctx, 18, 16, GST_USECOND * 900, + GST_USECOND * 100, 3, 4, 36, 548); + + cleanup_rawaudioparse (&testctx); +} + +GST_END_TEST; + +GST_START_TEST (test_push_unaligned_data_sink_caps_config) +{ + RawAudParseTestCtx testctx; + GstAudioInfo ainfo; + GstCaps *caps; + + /* This test is essentially the same as test_push_unaligned_data_properties_config, + * except that rawaudioparse uses the sink caps config instead of the property config. */ + + gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, TEST_SAMPLE_RATE, + NUM_TEST_CHANNELS, NULL); + caps = gst_audio_info_to_caps (&ainfo); + + setup_rawaudioparse (&testctx, TRUE, FALSE, caps, GST_FORMAT_BYTES); + + push_data_and_check_output (&testctx, 45, 44, GST_USECOND * 0, + GST_USECOND * 275, 1, 4, 0, 512); + push_data_and_check_output (&testctx, 99, 100, GST_USECOND * 275, + GST_USECOND * 625, 2, 4, 11, 523); + push_data_and_check_output (&testctx, 18, 16, GST_USECOND * 900, + GST_USECOND * 100, 3, 4, 36, 548); + + cleanup_rawaudioparse (&testctx); +} + +GST_END_TEST; + +GST_START_TEST (test_push_swapped_channels) +{ + RawAudParseTestCtx testctx; + GValueArray *valarray; + GValue val = G_VALUE_INIT; + + /* Send in 40 bytes and use a nonstandard channel order (left and right channels + * swapped). Expected behavior is for rawaudioparse to reorder the samples inside + * output buffers to conform to the GStreamer channel order. For this reason, + * channel0 offset is 512 and channel1 offset is 0 in the check below. */ + + setup_rawaudioparse (&testctx, FALSE, TRUE, NULL, GST_FORMAT_BYTES); + + valarray = g_value_array_new (2); + g_value_init (&val, GST_TYPE_AUDIO_CHANNEL_POSITION); + g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT); + g_value_array_insert (valarray, 0, &val); + g_value_set_enum (&val, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT); + g_value_array_insert (valarray, 1, &val); + g_object_set (G_OBJECT (testctx.rawaudioparse), "channel-positions", + valarray, NULL); + g_value_array_free (valarray); + g_value_unset (&val); + + push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 0, + GST_USECOND * 250, 1, 4, 512, 0); + + cleanup_rawaudioparse (&testctx); +} + +GST_END_TEST; + +GST_START_TEST (test_config_switch) +{ + RawAudParseTestCtx testctx; + GstAudioInfo ainfo; + GstCaps *caps; + + /* Start processing with the properties config active, then mid-stream switch to + * the sink caps config. The properties config is altered to have a different + * sample rate than the sink caps to be able to detect the switch. The net effect + * is that output buffer durations are altered. For example, 40 bytes equal + * 10 samples, and this equals 500 us with 20 kHz or 250 us with 40 kHz. */ + + gst_audio_info_set_format (&ainfo, TEST_SAMPLE_FORMAT, TEST_SAMPLE_RATE, + NUM_TEST_CHANNELS, NULL); + caps = gst_audio_info_to_caps (&ainfo); + + setup_rawaudioparse (&testctx, FALSE, TRUE, caps, GST_FORMAT_BYTES); + + g_object_set (G_OBJECT (testctx.rawaudioparse), "sample-rate", 20000, NULL); + + /* Push in data with properties config active, expecting duration calculations + * to be based on the 20 kHz sample rate */ + push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 0, + GST_USECOND * 500, 1, 4, 0, 512); + push_data_and_check_output (&testctx, 20, 20, GST_USECOND * 500, + GST_USECOND * 250, 2, 4, 10, 522); + + /* Perform the switch */ + g_object_set (G_OBJECT (testctx.rawaudioparse), "use-sink-caps", TRUE, NULL); + + /* Push in data with sink caps config active, expecting duration calculations + * to be based on the 40 kHz sample rate */ + push_data_and_check_output (&testctx, 40, 40, GST_USECOND * 750, + GST_USECOND * 250, 3, 4, 15, 527); + + cleanup_rawaudioparse (&testctx); +} + +GST_END_TEST; + + +static Suite * +rawaudioparse_suite (void) +{ + Suite *s = suite_create ("rawaudioparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_push_unaligned_data_properties_config); + tcase_add_test (tc_chain, test_push_unaligned_data_sink_caps_config); + tcase_add_test (tc_chain, test_push_swapped_channels); + tcase_add_test (tc_chain, test_config_switch); + + return s; +} + +GST_CHECK_MAIN (rawaudioparse); diff --git a/tests/check/elements/rawvideoparse.c b/tests/check/elements/rawvideoparse.c new file mode 100644 index 000000000..b071370db --- /dev/null +++ b/tests/check/elements/rawvideoparse.c @@ -0,0 +1,459 @@ +/* GStreamer + * + * unit test for rawvideoparse + * + * Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org> + * + * 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. + */ + +/* FIXME: GValueArray is deprecated, but there is currently no viabla alternative + * See https://bugzilla.gnome.org/show_bug.cgi?id=667228 */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include <gst/check/gstcheck.h> +#include <gst/video/video.h> + +/* The checks use as test data an 8x8 Y444 image, with 25 Hz framerate. In the + * sink caps configuration, the stride is 8 bytes, and the frames are tightly + * packed together. In the properties configuration, the stride is 10 bytes, the + * planes aren't tightly packed (there are 20 bytes between the planes), and the + * frames overall have padding between them (the overall frame size is + * stride (10) * height (8) * num-planes (3) + bytes-between-planes (20) * 2 + * = 280 bytes, and the frame stride is 500 bytes, so there are 220 bytes of + * extra padding between frames). + * + * In the test 8x8 frame, the pixels are all set to #000000, except for two + * pixels: (xofs+1 yofs+0) is set to #8899AA, (xofs+0 yofs+1) is set to #112233. + * The first frame uses the offsets xofs=0 yofs=0. The second frame uses + * xofs=1 yofs=0 etc. For each configuration, there is a separate set of frames, + * each stored in the GstAdapter in the Context struct. + * + * During the tests, as part of the checks, the pixels are verified to have the + * right values. The pattern of the pixels was chosen to easily detect stride + * errors, incorrect plane offsets etc. + */ + +#define TEST_WIDTH 8 +#define TEST_HEIGHT 8 +#define TEST_FRAMERATE_N 25 +#define TEST_FRAMERATE_D 1 +#define TEST_FRAME_FORMAT GST_VIDEO_FORMAT_Y444 +#define NUM_TEST_PLANES 3 + +#define PROP_CTX_PLANE_STRIDE 10 +#define PROP_CTX_FRAME_STRIDE 500 +#define PROP_CTX_PLANE_PADDING 20 +#define PROP_CTX_PLANE_SIZE (PROP_CTX_PLANE_STRIDE * TEST_HEIGHT + PROP_CTX_PLANE_PADDING) + +GstElement *rawvideoparse; + +/* For ease of programming we use globals to keep refs for our floating + * src and sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mysrcpad, *mysinkpad; + +typedef struct +{ + GstAdapter *data; + guint plane_stride; + guint plane_size; +} +Context; + +static Context properties_ctx, sinkcaps_ctx; + +static void +set_pixel (Context const *ctx, guint8 * pixels, guint x, guint y, guint32 color) +{ + guint i; + guint ofs = y * ctx->plane_stride + x; + for (i = 0; i < NUM_TEST_PLANES; ++i) + pixels[ctx->plane_size * i + ofs] = + (color >> ((NUM_TEST_PLANES - 1 - i) * 8)) & 0xFF; +} + +static guint32 +get_pixel (Context const *ctx, const guint8 * pixels, guint x, guint y) +{ + guint i; + guint ofs = y * ctx->plane_stride + x; + guint32 color = 0; + for (i = 0; i < NUM_TEST_PLANES; ++i) + color |= + ((guint32) (pixels[ctx->plane_size * i + ofs])) << ((NUM_TEST_PLANES - + 1 - i) * 8); + return color; +} + +static void +fill_test_pattern (Context const *ctx, GstBuffer * buffer, guint xofs, + guint yofs) +{ + guint8 *pixels; + GstMapInfo map_info; + + gst_buffer_map (buffer, &map_info, GST_MAP_WRITE); + pixels = map_info.data; + + memset (pixels, 0, ctx->plane_size * NUM_TEST_PLANES); + set_pixel (ctx, pixels, 1 + xofs, 0 + yofs, 0x8899AA); + set_pixel (ctx, pixels, 0 + xofs, 1 + yofs, 0x112233); + + gst_buffer_unmap (buffer, &map_info); +} + +static void +check_test_pattern (Context const *ctx, GstBuffer * buffer, guint xofs, + guint yofs) +{ + guint x, y; + guint8 *pixels; + GstMapInfo map_info; + + gst_buffer_map (buffer, &map_info, GST_MAP_READ); + pixels = map_info.data; + + fail_unless_equals_uint64_hex (get_pixel (ctx, pixels, 1 + xofs, 0 + yofs), + 0x8899AA); + fail_unless_equals_uint64_hex (get_pixel (ctx, pixels, 0 + xofs, 1 + yofs), + 0x112233); + + for (y = 0; y < TEST_HEIGHT; ++y) { + for (x = 0; x < TEST_WIDTH; ++x) { + if ((x == (1 + xofs) && y == (0 + yofs)) || (x == (0 + xofs) + && y == (1 + yofs))) + continue; + + fail_unless_equals_uint64_hex (get_pixel (ctx, pixels, x, y), 0x000000); + } + } + + gst_buffer_unmap (buffer, &map_info); +} + + +static void +setup_rawvideoparse (gboolean use_sink_caps, + gboolean set_properties, GstCaps * incaps, GstFormat format) +{ + guint i; + + + /* Setup the rawvideoparse element and the pads */ + + static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)) + ); + static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + + rawvideoparse = gst_check_setup_element ("rawvideoparse"); + + properties_ctx.plane_stride = PROP_CTX_PLANE_STRIDE; + properties_ctx.plane_size = PROP_CTX_PLANE_SIZE; + properties_ctx.data = gst_adapter_new (); + + sinkcaps_ctx.plane_stride = TEST_WIDTH; + sinkcaps_ctx.plane_size = TEST_WIDTH * TEST_HEIGHT; + sinkcaps_ctx.data = gst_adapter_new (); + + g_object_set (G_OBJECT (rawvideoparse), "use-sink-caps", use_sink_caps, NULL); + if (set_properties) { + GValueArray *plane_offsets, *plane_strides; + GValue val = G_VALUE_INIT; + + g_value_init (&val, G_TYPE_UINT); + + plane_offsets = g_value_array_new (NUM_TEST_PLANES); + for (i = 0; i < NUM_TEST_PLANES; ++i) { + g_value_set_uint (&val, properties_ctx.plane_size * i); + g_value_array_insert (plane_offsets, i, &val); + } + + plane_strides = g_value_array_new (NUM_TEST_PLANES); + for (i = 0; i < NUM_TEST_PLANES; ++i) { + g_value_set_uint (&val, properties_ctx.plane_stride); + g_value_array_insert (plane_strides, i, &val); + } + + g_value_unset (&val); + + g_object_set (G_OBJECT (rawvideoparse), "width", TEST_WIDTH, "height", + TEST_HEIGHT, "frame-stride", PROP_CTX_FRAME_STRIDE, "framerate", + TEST_FRAMERATE_N, TEST_FRAMERATE_D, "plane-offsets", plane_offsets, + "plane-strides", plane_strides, "format", TEST_FRAME_FORMAT, NULL); + + g_value_array_free (plane_offsets); + g_value_array_free (plane_strides); + } + + /* Check that the plane stride/offset values are correct */ + { + GValueArray *plane_offsets_array; + GValueArray *plane_strides_array; + /* By default, 320x240 i420 is used as format */ + guint plane_offsets[3] = { 0, 76800, 96000 }; + guint plane_strides[3] = { 320, 160, 160 }; + + if (set_properties) { + /* When properties are explicitely set, we use Y444 as video format, + * so in that case, plane stride values are all the same */ + plane_offsets[0] = properties_ctx.plane_size * 0; + plane_offsets[1] = properties_ctx.plane_size * 1; + plane_offsets[2] = properties_ctx.plane_size * 2; + plane_strides[0] = plane_strides[1] = plane_strides[2] = + properties_ctx.plane_stride; + } + + g_object_get (G_OBJECT (rawvideoparse), "plane-offsets", + &plane_offsets_array, "plane-strides", &plane_strides_array, NULL); + fail_unless (plane_offsets_array != NULL); + fail_unless (plane_strides_array != NULL); + fail_unless (plane_offsets_array->n_values == + plane_strides_array->n_values); + + for (i = 0; i < plane_offsets_array->n_values; ++i) { + GValue *gvalue; + + gvalue = g_value_array_get_nth (plane_offsets_array, i); + fail_unless (gvalue != NULL); + fail_unless_equals_uint64 (plane_offsets[i], g_value_get_uint (gvalue)); + + gvalue = g_value_array_get_nth (plane_strides_array, i); + fail_unless (gvalue != NULL); + fail_unless_equals_uint64 (plane_strides[i], g_value_get_uint (gvalue)); + } + + g_value_array_free (plane_offsets_array); + g_value_array_free (plane_strides_array); + } + + fail_unless (gst_element_set_state (rawvideoparse, + GST_STATE_PAUSED) == GST_STATE_CHANGE_SUCCESS, + "could not set to paused"); + + mysrcpad = gst_check_setup_src_pad (rawvideoparse, &srctemplate); + mysinkpad = gst_check_setup_sink_pad (rawvideoparse, &sinktemplate); + + gst_pad_set_active (mysrcpad, TRUE); + gst_pad_set_active (mysinkpad, TRUE); + + gst_check_setup_events (mysrcpad, rawvideoparse, incaps, format); + if (incaps) + gst_caps_unref (incaps); + + + /* Fill the adapters with test frames */ + + for (i = 0; i < 10; ++i) { + GstBuffer *buffer = + gst_buffer_new_allocate (NULL, PROP_CTX_FRAME_STRIDE, NULL); + gst_buffer_memset (buffer, 0, 0xCC, gst_buffer_get_size (buffer)); + fill_test_pattern (&properties_ctx, buffer, i, 0); + gst_adapter_push (properties_ctx.data, buffer); + } + + for (i = 0; i < 10; ++i) { + GstBuffer *buffer = + gst_buffer_new_allocate (NULL, sinkcaps_ctx.plane_size * 3, NULL); + gst_buffer_memset (buffer, 0, 0xCC, gst_buffer_get_size (buffer)); + fill_test_pattern (&sinkcaps_ctx, buffer, i, 0); + gst_adapter_push (sinkcaps_ctx.data, buffer); + } +} + +static void +cleanup_rawvideoparse (void) +{ + gst_pad_set_active (mysrcpad, FALSE); + gst_pad_set_active (mysinkpad, FALSE); + gst_check_teardown_src_pad (rawvideoparse); + gst_check_teardown_sink_pad (rawvideoparse); + gst_check_teardown_element (rawvideoparse); + + g_object_unref (G_OBJECT (properties_ctx.data)); + g_object_unref (G_OBJECT (sinkcaps_ctx.data)); +} + +static void +push_data_and_check_output (Context * ctx, gsize num_in_bytes, + gsize expected_num_out_bytes, gint64 expected_pts, gint64 expected_dur, + guint expected_num_buffers_in_list, guint buf_idx, guint xofs, guint yofs) +{ + GstBuffer *inbuf, *outbuf; + guint num_buffers; + + /* Simulate upstream input by taking num_in_bytes bytes from the adapter */ + inbuf = gst_adapter_take_buffer (ctx->data, num_in_bytes); + fail_unless (inbuf != NULL); + + /* Push the input data and check that the output buffers list grew as + * expected */ + fail_unless (gst_pad_push (mysrcpad, inbuf) == GST_FLOW_OK); + num_buffers = g_list_length (buffers); + fail_unless_equals_int (num_buffers, expected_num_buffers_in_list); + + /* Take the output buffer */ + outbuf = g_list_nth_data (buffers, buf_idx); + fail_unless (outbuf != NULL); + + /* Verify size, PTS, duration of the output buffer */ + fail_unless_equals_uint64 (expected_num_out_bytes, + gst_buffer_get_size (outbuf)); + fail_unless_equals_uint64 (expected_pts, GST_BUFFER_PTS (outbuf)); + fail_unless_equals_uint64 (expected_dur, GST_BUFFER_DURATION (outbuf)); + + /* Check that the pixels have the correct values */ + check_test_pattern (ctx, outbuf, xofs, yofs); +} + + +GST_START_TEST (test_push_unaligned_data_properties_config) +{ + setup_rawvideoparse (FALSE, TRUE, NULL, GST_FORMAT_BYTES); + + /* Send in data buffers that are not aligned to multiples of the + * frame size (= sample size * num_channels). This tests if rawvideoparse + * aligns output data properly. + * + * The second line sends a buffer with multiple frames inside. + * rawvideoparse will then parse this buffer repeatedly (and prepend + * leftover data from the earlier parse iteration), explaining why + * all of a sudden there are 4 output buffers, compared to just one + * earlier. The output data is expected to be 280 bytes large, since this + * is the size of the actual frame, without extra padding at the end. + */ + push_data_and_check_output (&properties_ctx, 511, 280, GST_MSECOND * 0, + GST_MSECOND * 40, 1, 0, 0, 0); + push_data_and_check_output (&properties_ctx, 1940, 280, GST_MSECOND * 40, + GST_MSECOND * 40, 4, 1, 1, 0); + push_data_and_check_output (&properties_ctx, 10, 280, GST_MSECOND * 80, + GST_MSECOND * 40, 4, 2, 2, 0); + + cleanup_rawvideoparse (); +} + +GST_END_TEST; + +GST_START_TEST (test_push_unaligned_data_sink_caps_config) +{ + GstVideoInfo vinfo; + GstCaps *caps; + + /* This test is essentially the same as test_push_unaligned_data_properties_config, + * except that rawvideoparse uses the sink caps config instead of the property config. + * Also, the input sizes are different, since the sink caps config does not use extra + * padding between planes and does use a stride that directly corresponds to the width, + * resulting in smaller frame size (192 bytes vs 280 bytes). */ + + gst_video_info_set_format (&vinfo, TEST_FRAME_FORMAT, TEST_WIDTH, + TEST_HEIGHT); + GST_VIDEO_INFO_FPS_N (&vinfo) = 25; + GST_VIDEO_INFO_FPS_D (&vinfo) = 1; + caps = gst_video_info_to_caps (&vinfo); + + setup_rawvideoparse (TRUE, FALSE, caps, GST_FORMAT_BYTES); + + push_data_and_check_output (&sinkcaps_ctx, 250, 192, GST_MSECOND * 0, + GST_MSECOND * 40, 1, 0, 0, 0); + push_data_and_check_output (&sinkcaps_ctx, 811, 192, GST_MSECOND * 40, + GST_MSECOND * 40, 5, 1, 1, 0); + push_data_and_check_output (&sinkcaps_ctx, 10, 192, GST_MSECOND * 80, + GST_MSECOND * 40, 5, 2, 2, 0); + + cleanup_rawvideoparse (); +} + +GST_END_TEST; + +GST_START_TEST (test_config_switch) +{ + GstVideoInfo vinfo; + GstCaps *caps; + + /* Start processing with the properties config active, then mid-stream switch to + * the sink caps config. Since the sink caps config does not use padding, its + * frame size is smaller. The buffer duration stays the same (since it only depends + * on the framerate), but the expected output buffer size is different). */ + + gst_video_info_set_format (&vinfo, TEST_FRAME_FORMAT, TEST_WIDTH, + TEST_HEIGHT); + GST_VIDEO_INFO_FPS_N (&vinfo) = 25; + GST_VIDEO_INFO_FPS_D (&vinfo) = 1; + caps = gst_video_info_to_caps (&vinfo); + + setup_rawvideoparse (FALSE, TRUE, caps, GST_FORMAT_BYTES); + + /* Push in data with properties config active */ + push_data_and_check_output (&properties_ctx, 500, 280, GST_MSECOND * 0, + GST_MSECOND * 40, 1, 0, 0, 0); + push_data_and_check_output (&properties_ctx, 500, 280, GST_MSECOND * 40, + GST_MSECOND * 40, 2, 1, 1, 0); + + /* Perform the switch */ + g_object_set (G_OBJECT (rawvideoparse), "use-sink-caps", TRUE, NULL); + + /* Push in data with sink caps config active, expecting a different frame size */ + push_data_and_check_output (&sinkcaps_ctx, 192, 192, GST_MSECOND * 80, + GST_MSECOND * 40, 3, 2, 0, 0); + + cleanup_rawvideoparse (); +} + +GST_END_TEST; + +GST_START_TEST (test_push_with_no_framerate) +{ + /* Test the special case when no framerate is set. The parser is expected to + * still work then, but without setting duration or PTS/DTS (it cannot do that, + * because these require a nonzero framerate). The output buffers have PTS 0, + * all subsequent ones have no set PTS. */ + + setup_rawvideoparse (FALSE, TRUE, NULL, GST_FORMAT_BYTES); + g_object_set (G_OBJECT (rawvideoparse), "framerate", 0, 1, NULL); + + push_data_and_check_output (&properties_ctx, 500, 280, 0, GST_CLOCK_TIME_NONE, + 1, 0, 0, 0); + push_data_and_check_output (&properties_ctx, 500, 280, GST_CLOCK_TIME_NONE, + GST_CLOCK_TIME_NONE, 2, 1, 1, 0); + + cleanup_rawvideoparse (); +} + +GST_END_TEST; + + +static Suite * +rawvideoparse_suite (void) +{ + Suite *s = suite_create ("rawvideoparse"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_push_unaligned_data_properties_config); + tcase_add_test (tc_chain, test_push_unaligned_data_sink_caps_config); + tcase_add_test (tc_chain, test_config_switch); + tcase_add_test (tc_chain, test_push_with_no_framerate); + + return s; +} + +GST_CHECK_MAIN (rawvideoparse); |